From 3c47103b39a1490bbf64280cf3bd54f7cc0c0b2c Mon Sep 17 00:00:00 2001 From: Siwat Sirichai Date: Fri, 9 Aug 2019 09:01:56 +0700 Subject: [PATCH] Add Library --- .../.github/ISSUE_TEMPLATE.md | 46 + .../.github/PULL_REQUEST_TEMPLATE.md | 26 + .../Adafruit_MCP23017.cpp | 283 + .../Adafruit_MCP23017.h | 97 + .../README.md | 15 + .../examples/button/button.ino | 32 + .../examples/interrupt/interrupt.ino | 123 + .../examples/toggle/toggle.ino | 35 + .../keywords.txt | 21 + .../library.properties | 9 + .../license.txt | 26 + .../Adafruit_FONA_Library/Adafruit_FONA.cpp | 2092 ++++++ .../Adafruit_FONA_Library/Adafruit_FONA.h | 266 + libraries/Adafruit_FONA_Library/README.md | 29 + .../FONA3G_setbaud/FONA3G_setbaud.ino | 77 + .../FONA_SMS_Response/FONA_SMS_Response.ino | 143 + .../examples/FONAtest/FONAtest.ino | 886 +++ .../examples/FONAtest_KEY_mod.zip | Bin 0 -> 6318 bytes .../FONAtest_KEY_mod/FONAtest_KEY_mod.ino | 909 +++ .../examples/GPS/GPS.ino | 112 + .../examples/IncomingCall/IncomingCall.ino | 62 + .../includes/FONAConfig.h | 34 + .../includes/FONAExtIncludes.h | 33 + .../includes/platform/FONAPlatStd.h | 70 + .../includes/platform/FONAPlatform.h | 62 + .../Adafruit_FONA_Library/library.properties | 9 + .../Adafruit_MQTT_Library/Adafruit_MQTT.cpp | 851 +++ .../Adafruit_MQTT_Library/Adafruit_MQTT.h | 302 + .../Adafruit_MQTT_Client.cpp | 106 + .../Adafruit_MQTT_Client.h | 61 + .../Adafruit_MQTT_FONA.h | 142 + libraries/Adafruit_MQTT_Library/LICENSE | 22 + libraries/Adafruit_MQTT_Library/README.md | 57 + .../adafruitio_anon_time_esp8266.ino | 122 + .../adafruitio_errors_esp8266.ino | 155 + .../adafruitio_secure_airlift.ino | 183 + .../adafruitio_secure_esp8266.ino | 135 + .../adafruitio_time_esp8266.ino | 122 + .../mqtt_2subs_esp8266/mqtt_2subs_esp8266.ino | 157 + .../examples/mqtt_arbitrary_data/README.md | 108 + .../mqtt_arbitrary_data.ino | 184 + .../python_subscriber/mqtt_figure.png | Bin 0 -> 885336 bytes .../python_subscriber/requirements.txt | 1 + .../python_subscriber/subscriber.py | 114 + .../examples/mqtt_esp8266/mqtt_esp8266.ino | 147 + .../mqtt_esp8266_callback.ino | 185 + .../examples/mqtt_ethernet/mqtt_ethernet.ino | 128 + .../examples/mqtt_fona/fonahelper.cpp | 50 + .../examples/mqtt_fona/mqtt_fona.ino | 169 + .../examples/mqtt_winc1500/mqtt_winc1500.ino | 151 + .../examples/mqtt_yun/mqtt_yun.ino | 116 + libraries/Adafruit_MQTT_Library/keywords.txt | 20 + .../Adafruit_MQTT_Library/library.properties | 9 + libraries/ArduinoOTA/ArduinoOTA.png | Bin 0 -> 17226 bytes libraries/ArduinoOTA/LICENSE | 504 ++ libraries/ArduinoOTA/README.md | 108 + .../examples/ATmega_SD/ATmega_SD.ino | 64 + .../examples/OTEthernet/OTEthernet.ino | 53 + .../examples/OTEthernet_SD/OTEthernet_SD.ino | 65 + .../examples/WiFi101_OTA/WiFi101_OTA.ino | 81 + .../examples/WiFi101_OTA/arduino_secrets.h | 3 + .../WiFi101_SD_OTA/WiFi101_SD_OTA.ino | 93 + .../examples/WiFi101_SD_OTA/arduino_secrets.h | 3 + .../ArduinoOTA/extras/avr/platform.local.txt | 9 + .../extras/esp32/platform.local.txt | 6 + .../extras/esp8266/platform.local.txt | 6 + libraries/ArduinoOTA/keywords.txt | 24 + libraries/ArduinoOTA/library.properties | 10 + libraries/ArduinoOTA/src/ArduinoOTA.h | 112 + libraries/ArduinoOTA/src/InternalStorage.cpp | 159 + libraries/ArduinoOTA/src/InternalStorage.h | 55 + .../ArduinoOTA/src/InternalStorageAVR.cpp | 77 + libraries/ArduinoOTA/src/InternalStorageAVR.h | 50 + .../ArduinoOTA/src/InternalStorageESP.cpp | 64 + libraries/ArduinoOTA/src/InternalStorageESP.h | 44 + libraries/ArduinoOTA/src/OTAStorage.cpp | 58 + libraries/ArduinoOTA/src/OTAStorage.h | 69 + libraries/ArduinoOTA/src/SDStorage.h | 55 + libraries/ArduinoOTA/src/SerialFlashStorage.h | 72 + libraries/ArduinoOTA/src/WiFiOTA.cpp | 358 + libraries/ArduinoOTA/src/WiFiOTA.h | 64 + libraries/ArduinoOTA/src/utility/optiboot.h | 200 + libraries/ArduinoThread-master/.gitignore | 13 + libraries/ArduinoThread-master/LICENSE.txt | 21 + libraries/ArduinoThread-master/README.md | 208 + .../StaticThreadController.h | 75 + libraries/ArduinoThread-master/Thread.cpp | 52 + libraries/ArduinoThread-master/Thread.h | 89 + .../ArduinoThread-master/ThreadController.cpp | 114 + .../ArduinoThread-master/ThreadController.h | 53 + .../ControllerInController.ino | 78 + .../ControllerWithTimer.ino | 100 + .../CustomTimedThread/CustomTimedThread.ino | 257 + .../examples/SensorThread/SensorThread.ino | 105 + .../examples/SimpleThread/SimpleThread.ino | 35 + .../SimpleThreadController.ino | 48 + .../StaticThreadController.ino | 56 + .../extras/ArduinoThread.png | Bin 0 -> 8512 bytes libraries/ArduinoThread-master/keywords.txt | 32 + libraries/ArduinoThread-master/library.json | 12 + .../ArduinoThread-master/library.properties | 9 + libraries/ButtonDebounce/LICENSE | 674 ++ libraries/ButtonDebounce/README.md | 36 + .../examples/simple_btn/simple_btn.ino | 13 + .../simple_callback/simple_callback.ino | 15 + libraries/ButtonDebounce/keywords.txt | 24 + libraries/ButtonDebounce/library.properties | 10 + .../ButtonDebounce/src/ButtonDebounce.cpp | 31 + libraries/ButtonDebounce/src/ButtonDebounce.h | 28 + libraries/ESP32_BLE_Arduino/README.md | 15 + .../examples/BLE_client/BLE_client.ino | 160 + .../examples/BLE_iBeacon/BLE_iBeacon.ino | 103 + .../examples/BLE_notify/BLE_notify.ino | 110 + .../examples/BLE_scan/BLE_scan.ino | 40 + .../examples/BLE_server/BLE_server.ino | 45 + .../BLE_server_multiconnect.ino | 111 + .../examples/BLE_uart/BLE_uart.ino | 125 + .../examples/BLE_write/BLE_write.ino | 65 + .../ESP32_BLE_Arduino/library.properties | 10 + libraries/ESP32_BLE_Arduino/src/BLE2902.cpp | 62 + libraries/ESP32_BLE_Arduino/src/BLE2902.h | 34 + libraries/ESP32_BLE_Arduino/src/BLE2904.cpp | 74 + libraries/ESP32_BLE_Arduino/src/BLE2904.h | 74 + .../ESP32_BLE_Arduino/src/BLEAddress.cpp | 95 + libraries/ESP32_BLE_Arduino/src/BLEAddress.h | 34 + .../src/BLEAdvertisedDevice.cpp | 529 ++ .../src/BLEAdvertisedDevice.h | 123 + .../ESP32_BLE_Arduino/src/BLEAdvertising.cpp | 505 ++ .../ESP32_BLE_Arduino/src/BLEAdvertising.h | 78 + libraries/ESP32_BLE_Arduino/src/BLEBeacon.cpp | 89 + libraries/ESP32_BLE_Arduino/src/BLEBeacon.h | 43 + .../src/BLECharacteristic.cpp | 760 +++ .../ESP32_BLE_Arduino/src/BLECharacteristic.h | 137 + .../src/BLECharacteristicMap.cpp | 133 + libraries/ESP32_BLE_Arduino/src/BLEClient.cpp | 536 ++ libraries/ESP32_BLE_Arduino/src/BLEClient.h | 103 + .../ESP32_BLE_Arduino/src/BLEDescriptor.cpp | 296 + .../ESP32_BLE_Arduino/src/BLEDescriptor.h | 77 + .../src/BLEDescriptorMap.cpp | 147 + libraries/ESP32_BLE_Arduino/src/BLEDevice.cpp | 646 ++ libraries/ESP32_BLE_Arduino/src/BLEDevice.h | 99 + .../ESP32_BLE_Arduino/src/BLEEddystoneTLM.cpp | 150 + .../ESP32_BLE_Arduino/src/BLEEddystoneTLM.h | 51 + .../ESP32_BLE_Arduino/src/BLEEddystoneURL.cpp | 148 + .../ESP32_BLE_Arduino/src/BLEEddystoneURL.h | 43 + .../ESP32_BLE_Arduino/src/BLEExceptions.cpp | 9 + .../ESP32_BLE_Arduino/src/BLEExceptions.h | 31 + .../ESP32_BLE_Arduino/src/BLEHIDDevice.cpp | 243 + .../ESP32_BLE_Arduino/src/BLEHIDDevice.h | 75 + .../src/BLERemoteCharacteristic.cpp | 588 ++ .../src/BLERemoteCharacteristic.h | 84 + .../src/BLERemoteDescriptor.cpp | 181 + .../src/BLERemoteDescriptor.h | 55 + .../src/BLERemoteService.cpp | 340 + .../ESP32_BLE_Arduino/src/BLERemoteService.h | 85 + libraries/ESP32_BLE_Arduino/src/BLEScan.cpp | 331 + libraries/ESP32_BLE_Arduino/src/BLEScan.h | 83 + .../ESP32_BLE_Arduino/src/BLESecurity.cpp | 104 + libraries/ESP32_BLE_Arduino/src/BLESecurity.h | 72 + libraries/ESP32_BLE_Arduino/src/BLEServer.cpp | 424 ++ libraries/ESP32_BLE_Arduino/src/BLEServer.h | 140 + .../ESP32_BLE_Arduino/src/BLEService.cpp | 418 ++ libraries/ESP32_BLE_Arduino/src/BLEService.h | 97 + .../ESP32_BLE_Arduino/src/BLEServiceMap.cpp | 134 + libraries/ESP32_BLE_Arduino/src/BLEUUID.cpp | 407 ++ libraries/ESP32_BLE_Arduino/src/BLEUUID.h | 39 + libraries/ESP32_BLE_Arduino/src/BLEUtils.cpp | 2033 ++++++ libraries/ESP32_BLE_Arduino/src/BLEUtils.h | 63 + libraries/ESP32_BLE_Arduino/src/BLEValue.cpp | 139 + libraries/ESP32_BLE_Arduino/src/BLEValue.h | 39 + libraries/ESP32_BLE_Arduino/src/FreeRTOS.cpp | 274 + libraries/ESP32_BLE_Arduino/src/FreeRTOS.h | 71 + .../ESP32_BLE_Arduino/src/GeneralUtils.cpp | 544 ++ .../ESP32_BLE_Arduino/src/GeneralUtils.h | 35 + .../ESP32_BLE_Arduino/src/HIDKeyboardTypes.h | 402 ++ libraries/ESP32_BLE_Arduino/src/HIDTypes.h | 96 + libraries/ESP32_Rest_Client/LICENSE | 674 ++ libraries/ESP32_Rest_Client/README.md | 2 + libraries/ESP32_Rest_Client/RestClient.cpp | 170 + libraries/ESP32_Rest_Client/RestClient.h | 32 + .../examples/SimpleGet/simpleget.ino | 20 + libraries/ESP32_Rest_Client/library.json | 18 + .../ESP32_Rest_Client/library.properties | 9 + libraries/ESP8266_MQTT_Mesh/LICENSE | 674 ++ libraries/ESP8266_MQTT_Mesh/README.md | 159 + .../ESP8266_MQTT_Mesh/docs/MeshTopology.md | 25 + .../docs/images/MeshTopology.png | Bin 0 -> 81450 bytes .../examples/ESP8266MeshHelloWorld/README.md | 34 + .../data/ssl/fingerprint | 1 + .../ESP8266MeshHelloWorld/data/ssl/server.cer | Bin 0 -> 587 bytes .../ESP8266MeshHelloWorld/data/ssl/server.key | Bin 0 -> 609 bytes .../ESP8266MeshHelloWorld/platformio.ini | 39 + .../src/ESP8266MeshHelloWorld.ino | 98 + .../src/credentials.h.example | 16 + .../ESP8266MeshHelloWorld/src/ssl_cert.h | 87 + .../IRBlaster/IRRemote.brd | 1932 ++++++ .../IRBlaster/IRRemote.pdf | Bin 0 -> 14893 bytes .../IRBlaster/IRRemote.sch | 6065 +++++++++++++++++ .../IRBlaster/bt_regulator.lbr | 1248 ++++ .../IRBlaster/esp8266modules.lbr | 1308 ++++ .../IRBlaster/transistors.lbr | Bin 0 -> 12360 bytes .../examples/ESP8266MeshIRRemote/Makefile | 441 ++ .../examples/ESP8266MeshIRRemote/README.md | 34 + .../examples/ESP8266MeshIRRemote/config.mk | 4 + .../lib/IRremoteESP8266/Contributors.md | 20 + .../lib/IRremoteESP8266/IRremoteESP8266.cpp | 1400 ++++ .../lib/IRremoteESP8266/IRremoteESP8266.h | 160 + .../lib/IRremoteESP8266/IRremoteInt.h | 198 + .../lib/IRremoteESP8266/LICENSE.txt | 458 ++ .../lib/IRremoteESP8266/README.md | 27 + .../examples/IRServer/IRServer.ino | 88 + .../examples/IRrecvDemo/IRrecvDemo.ino | 28 + .../examples/IRrecvDump/IRrecvDump.ino | 89 + .../examples/IRrecvDumpV2/IRrecvDumpV2.ino | 169 + .../examples/IRsendDemo/IRsendDemo.ino | 25 + .../JVCPanasonicSendDemo.ino | 29 + .../lib/IRremoteESP8266/keywords.txt | 57 + .../lib/IRremoteESP8266/library.json | 12 + .../lib/IRremoteESP8266/library.properties | 9 + .../ESP8266MeshIRRemote/platformio.ini | 21 + .../src/ESP8266MeshIRRemote.ino | 240 + .../ESP8266MeshIRRemote/src/QueueArray.h | 242 + .../examples/ESP8266MeshSensor/Makefile | 441 ++ .../examples/ESP8266MeshSensor/README.md | 27 + .../examples/ESP8266MeshSensor/config.mk | 4 + .../ESP8266MeshSensor/credentials.h.example | 12 + .../examples/ESP8266MeshSensor/platformio.ini | 27 + .../src/ESP8266MeshSensor.ino | 393 ++ .../ESP8266MeshSensor/src/capabilities.h | 15 + libraries/ESP8266_MQTT_Mesh/library.json | 43 + .../ESP8266_MQTT_Mesh/library.properties | 10 + libraries/ESP8266_MQTT_Mesh/src/Base64.cpp | 140 + libraries/ESP8266_MQTT_Mesh/src/Base64.h | 91 + .../ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.cpp | 1131 +++ .../ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.h | 252 + .../src/ESP8266MQTTMeshBuilder.h | 142 + libraries/ESP8266_MQTT_Mesh/src/WiFiCompat.h | 54 + .../utils/dump_stacktrace.py | 49 + .../utils/gen_server_cert.sh | 52 + .../utils/get_mqtt_fingerprint.py | 61 + libraries/ESP8266_MQTT_Mesh/utils/send_ota.py | 201 + libraries/FlowMeter-master/.gitattributes | 2 + libraries/FlowMeter-master/.gitignore | 28 + libraries/FlowMeter-master/.gitmodules | 3 + libraries/FlowMeter-master/.travis.yml | 20 + libraries/FlowMeter-master/keywords.txt | 47 + libraries/FlowMeter-master/library.properties | 9 + .../Documentation/schematic.png | Bin 0 -> 97692 bytes .../Documentation/schematic3bit.png | Bin 0 -> 76019 bytes libraries/bitluni_ESP32Lib/README.md | 152 + .../Utilities/SpriteEditor.html | 624 ++ .../Utilities/StlConverter.html | 255 + .../examples/6BitMode/6BitMode.ino | 97 + .../examples/GFXWrapper/GFXWrapper.ino | 32 + .../examples/Raytracer/Raytracer.h | 151 + .../examples/Raytracer/Raytracer.ino | 90 + .../examples/VGA2DFeatures/VGA2DFeatures.ino | 138 + .../examples/VGA2DFeatures/rock.h | 200 + .../examples/VGA3DEngine/VGA3DEngine.ino | 93 + .../examples/VGA3DEngine/thinker.h | 417 ++ .../VGACustomResolution.ino | 102 + .../examples/VGADemo14Bit/VGADemo14Bit.ino | 71 + .../examples/VGAFonts/VGAFonts.ino | 71 + .../examples/VGAHelloWorld/VGAHelloWorld.ino | 31 + .../examples/VGAHighRes/VGAHighRes.ino | 67 + .../VGANoFrameBuffer/VGANoFrameBuffer.ino | 83 + .../examples/VGASprites/VGASprites.ino | 56 + .../examples/VGASprites/explosion.h | 891 +++ .../VGAWiFiTextTerminal.ino | 116 + .../examples/VGAWiFiTextTerminal/page.h | 92 + libraries/bitluni_ESP32Lib/keywords.txt | 0 libraries/bitluni_ESP32Lib/library.json | 22 + libraries/bitluni_ESP32Lib/library.properties | 10 + .../bitluni_ESP32Lib/src/Audio/AudioOutput.h | 58 + .../bitluni_ESP32Lib/src/Audio/AudioSystem.h | 209 + .../src/Controller/GameControllers.h | 128 + libraries/bitluni_ESP32Lib/src/ESP32Lib.h | 14 + libraries/bitluni_ESP32Lib/src/GfxWrapper.h | 31 + .../bitluni_ESP32Lib/src/Graphics/Animation.h | 86 + .../bitluni_ESP32Lib/src/Graphics/Engine3D.h | 69 + .../bitluni_ESP32Lib/src/Graphics/Entity.h | 26 + .../bitluni_ESP32Lib/src/Graphics/Font.h | 35 + .../bitluni_ESP32Lib/src/Graphics/Graphics.h | 698 ++ .../src/Graphics/GraphicsR1G1B1A1.h | 110 + .../Graphics/GraphicsR1G1B1A1X2S2Swapped.h | 91 + .../src/Graphics/GraphicsR2G2B2A2.h | 142 + .../src/Graphics/GraphicsR2G2B2S2Swapped.h | 142 + .../src/Graphics/GraphicsR5G5B4A2.h | 108 + .../src/Graphics/GraphicsR5G5B4S2Swapped.h | 112 + .../bitluni_ESP32Lib/src/Graphics/Image.h | 52 + .../src/Graphics/ImageDrawer.h | 113 + .../bitluni_ESP32Lib/src/Graphics/Mesh.h | 137 + .../bitluni_ESP32Lib/src/Graphics/Sprites.h | 108 + .../src/Graphics/TriangleTree.h | 126 + .../src/I2S/DMABufferDescriptor.h | 110 + libraries/bitluni_ESP32Lib/src/I2S/I2S.cpp | 360 + libraries/bitluni_ESP32Lib/src/I2S/I2S.h | 60 + libraries/bitluni_ESP32Lib/src/Math/Matrix.h | 199 + .../bitluni_ESP32Lib/src/Math/MatrixFast.h | 179 + .../src/Ressources/CodePage437_8x14.h | 3 + .../src/Ressources/CodePage437_8x16.h | 3 + .../src/Ressources/CodePage437_8x19.h | 3 + .../src/Ressources/CodePage437_8x8.h | 3 + .../src/Ressources/CodePage437_9x16.h | 3 + .../bitluni_ESP32Lib/src/Ressources/Font6x8.h | 291 + .../bitluni_ESP32Lib/src/Ressources/Font8x8.h | 386 ++ libraries/bitluni_ESP32Lib/src/Tools/Log.h | 26 + libraries/bitluni_ESP32Lib/src/VGA/Mode.h | 122 + .../bitluni_ESP32Lib/src/VGA/PinConfig.h | 102 + libraries/bitluni_ESP32Lib/src/VGA/VGA.cpp | 225 + libraries/bitluni_ESP32Lib/src/VGA/VGA.h | 99 + libraries/bitluni_ESP32Lib/src/VGA/VGA14Bit.h | 126 + .../bitluni_ESP32Lib/src/VGA/VGA14BitI.h | 117 + libraries/bitluni_ESP32Lib/src/VGA/VGA3Bit.h | 114 + libraries/bitluni_ESP32Lib/src/VGA/VGA3BitI.h | 139 + libraries/bitluni_ESP32Lib/src/VGA/VGA6Bit.h | 131 + libraries/bitluni_ESP32Lib/src/VGA/VGA6BitI.h | 157 + libraries/readme.txt | 1 + 318 files changed, 56465 insertions(+) create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/.github/ISSUE_TEMPLATE.md create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.cpp create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.h create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/README.md create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/examples/button/button.ino create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/examples/interrupt/interrupt.ino create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/examples/toggle/toggle.ino create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/keywords.txt create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/library.properties create mode 100644 libraries/Adafruit-MCP23017-Arduino-Library-master/license.txt create mode 100644 libraries/Adafruit_FONA_Library/Adafruit_FONA.cpp create mode 100644 libraries/Adafruit_FONA_Library/Adafruit_FONA.h create mode 100644 libraries/Adafruit_FONA_Library/README.md create mode 100644 libraries/Adafruit_FONA_Library/examples/FONA3G_setbaud/FONA3G_setbaud.ino create mode 100644 libraries/Adafruit_FONA_Library/examples/FONA_SMS_Response/FONA_SMS_Response.ino create mode 100644 libraries/Adafruit_FONA_Library/examples/FONAtest/FONAtest.ino create mode 100644 libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod.zip create mode 100644 libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod/FONAtest_KEY_mod.ino create mode 100644 libraries/Adafruit_FONA_Library/examples/GPS/GPS.ino create mode 100644 libraries/Adafruit_FONA_Library/examples/IncomingCall/IncomingCall.ino create mode 100644 libraries/Adafruit_FONA_Library/includes/FONAConfig.h create mode 100644 libraries/Adafruit_FONA_Library/includes/FONAExtIncludes.h create mode 100644 libraries/Adafruit_FONA_Library/includes/platform/FONAPlatStd.h create mode 100644 libraries/Adafruit_FONA_Library/includes/platform/FONAPlatform.h create mode 100644 libraries/Adafruit_FONA_Library/library.properties create mode 100644 libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp create mode 100644 libraries/Adafruit_MQTT_Library/Adafruit_MQTT.h create mode 100644 libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.cpp create mode 100644 libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.h create mode 100644 libraries/Adafruit_MQTT_Library/Adafruit_MQTT_FONA.h create mode 100644 libraries/Adafruit_MQTT_Library/LICENSE create mode 100644 libraries/Adafruit_MQTT_Library/README.md create mode 100644 libraries/Adafruit_MQTT_Library/examples/adafruitio_anon_time_esp8266/adafruitio_anon_time_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/adafruitio_errors_esp8266/adafruitio_errors_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_airlift/adafruitio_secure_airlift.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_esp8266/adafruitio_secure_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/adafruitio_time_esp8266/adafruitio_time_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_2subs_esp8266/mqtt_2subs_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/README.md create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/mqtt_arbitrary_data.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/mqtt_figure.png create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/requirements.txt create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/subscriber.py create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266/mqtt_esp8266.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266_callback/mqtt_esp8266_callback.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_ethernet/mqtt_ethernet.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_fona/fonahelper.cpp create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_fona/mqtt_fona.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_winc1500/mqtt_winc1500.ino create mode 100644 libraries/Adafruit_MQTT_Library/examples/mqtt_yun/mqtt_yun.ino create mode 100644 libraries/Adafruit_MQTT_Library/keywords.txt create mode 100644 libraries/Adafruit_MQTT_Library/library.properties create mode 100644 libraries/ArduinoOTA/ArduinoOTA.png create mode 100644 libraries/ArduinoOTA/LICENSE create mode 100644 libraries/ArduinoOTA/README.md create mode 100644 libraries/ArduinoOTA/examples/ATmega_SD/ATmega_SD.ino create mode 100644 libraries/ArduinoOTA/examples/OTEthernet/OTEthernet.ino create mode 100644 libraries/ArduinoOTA/examples/OTEthernet_SD/OTEthernet_SD.ino create mode 100644 libraries/ArduinoOTA/examples/WiFi101_OTA/WiFi101_OTA.ino create mode 100644 libraries/ArduinoOTA/examples/WiFi101_OTA/arduino_secrets.h create mode 100644 libraries/ArduinoOTA/examples/WiFi101_SD_OTA/WiFi101_SD_OTA.ino create mode 100644 libraries/ArduinoOTA/examples/WiFi101_SD_OTA/arduino_secrets.h create mode 100644 libraries/ArduinoOTA/extras/avr/platform.local.txt create mode 100644 libraries/ArduinoOTA/extras/esp32/platform.local.txt create mode 100644 libraries/ArduinoOTA/extras/esp8266/platform.local.txt create mode 100644 libraries/ArduinoOTA/keywords.txt create mode 100644 libraries/ArduinoOTA/library.properties create mode 100644 libraries/ArduinoOTA/src/ArduinoOTA.h create mode 100644 libraries/ArduinoOTA/src/InternalStorage.cpp create mode 100644 libraries/ArduinoOTA/src/InternalStorage.h create mode 100644 libraries/ArduinoOTA/src/InternalStorageAVR.cpp create mode 100644 libraries/ArduinoOTA/src/InternalStorageAVR.h create mode 100644 libraries/ArduinoOTA/src/InternalStorageESP.cpp create mode 100644 libraries/ArduinoOTA/src/InternalStorageESP.h create mode 100644 libraries/ArduinoOTA/src/OTAStorage.cpp create mode 100644 libraries/ArduinoOTA/src/OTAStorage.h create mode 100644 libraries/ArduinoOTA/src/SDStorage.h create mode 100644 libraries/ArduinoOTA/src/SerialFlashStorage.h create mode 100644 libraries/ArduinoOTA/src/WiFiOTA.cpp create mode 100644 libraries/ArduinoOTA/src/WiFiOTA.h create mode 100644 libraries/ArduinoOTA/src/utility/optiboot.h create mode 100644 libraries/ArduinoThread-master/.gitignore create mode 100644 libraries/ArduinoThread-master/LICENSE.txt create mode 100644 libraries/ArduinoThread-master/README.md create mode 100644 libraries/ArduinoThread-master/StaticThreadController.h create mode 100644 libraries/ArduinoThread-master/Thread.cpp create mode 100644 libraries/ArduinoThread-master/Thread.h create mode 100644 libraries/ArduinoThread-master/ThreadController.cpp create mode 100644 libraries/ArduinoThread-master/ThreadController.h create mode 100644 libraries/ArduinoThread-master/examples/ControllerInController/ControllerInController.ino create mode 100644 libraries/ArduinoThread-master/examples/ControllerWithTimer/ControllerWithTimer.ino create mode 100644 libraries/ArduinoThread-master/examples/CustomTimedThread/CustomTimedThread.ino create mode 100644 libraries/ArduinoThread-master/examples/SensorThread/SensorThread.ino create mode 100644 libraries/ArduinoThread-master/examples/SimpleThread/SimpleThread.ino create mode 100644 libraries/ArduinoThread-master/examples/SimpleThreadController/SimpleThreadController.ino create mode 100644 libraries/ArduinoThread-master/examples/StaticThreadController/StaticThreadController.ino create mode 100644 libraries/ArduinoThread-master/extras/ArduinoThread.png create mode 100644 libraries/ArduinoThread-master/keywords.txt create mode 100644 libraries/ArduinoThread-master/library.json create mode 100644 libraries/ArduinoThread-master/library.properties create mode 100644 libraries/ButtonDebounce/LICENSE create mode 100644 libraries/ButtonDebounce/README.md create mode 100644 libraries/ButtonDebounce/examples/simple_btn/simple_btn.ino create mode 100644 libraries/ButtonDebounce/examples/simple_callback/simple_callback.ino create mode 100644 libraries/ButtonDebounce/keywords.txt create mode 100644 libraries/ButtonDebounce/library.properties create mode 100644 libraries/ButtonDebounce/src/ButtonDebounce.cpp create mode 100644 libraries/ButtonDebounce/src/ButtonDebounce.h create mode 100644 libraries/ESP32_BLE_Arduino/README.md create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_client/BLE_client.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_iBeacon/BLE_iBeacon.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_notify/BLE_notify.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_scan/BLE_scan.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_server/BLE_server.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_server_multiconnect/BLE_server_multiconnect.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_uart/BLE_uart.ino create mode 100644 libraries/ESP32_BLE_Arduino/examples/BLE_write/BLE_write.ino create mode 100644 libraries/ESP32_BLE_Arduino/library.properties create mode 100644 libraries/ESP32_BLE_Arduino/src/BLE2902.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLE2902.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLE2904.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLE2904.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAddress.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAddress.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAdvertising.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEAdvertising.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEBeacon.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEBeacon.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLECharacteristic.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLECharacteristic.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEClient.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEClient.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEDescriptor.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEDescriptor.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEDevice.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEDevice.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEExceptions.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEExceptions.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteService.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLERemoteService.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEScan.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEScan.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLESecurity.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLESecurity.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEServer.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEServer.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEService.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEService.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEServiceMap.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEUUID.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEUUID.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEUtils.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEUtils.h create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEValue.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/BLEValue.h create mode 100644 libraries/ESP32_BLE_Arduino/src/FreeRTOS.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/FreeRTOS.h create mode 100644 libraries/ESP32_BLE_Arduino/src/GeneralUtils.cpp create mode 100644 libraries/ESP32_BLE_Arduino/src/GeneralUtils.h create mode 100644 libraries/ESP32_BLE_Arduino/src/HIDKeyboardTypes.h create mode 100644 libraries/ESP32_BLE_Arduino/src/HIDTypes.h create mode 100644 libraries/ESP32_Rest_Client/LICENSE create mode 100644 libraries/ESP32_Rest_Client/README.md create mode 100644 libraries/ESP32_Rest_Client/RestClient.cpp create mode 100644 libraries/ESP32_Rest_Client/RestClient.h create mode 100644 libraries/ESP32_Rest_Client/examples/SimpleGet/simpleget.ino create mode 100644 libraries/ESP32_Rest_Client/library.json create mode 100644 libraries/ESP32_Rest_Client/library.properties create mode 100644 libraries/ESP8266_MQTT_Mesh/LICENSE create mode 100644 libraries/ESP8266_MQTT_Mesh/README.md create mode 100644 libraries/ESP8266_MQTT_Mesh/docs/MeshTopology.md create mode 100644 libraries/ESP8266_MQTT_Mesh/docs/images/MeshTopology.png create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/README.md create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/fingerprint create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/server.cer create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/server.key create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/platformio.ini create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ESP8266MeshHelloWorld.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/credentials.h.example create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ssl_cert.h create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.brd create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.pdf create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.sch create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/bt_regulator.lbr create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/esp8266modules.lbr create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/transistors.lbr create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/Makefile create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/README.md create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/config.mk create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/Contributors.md create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.cpp create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.h create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteInt.h create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/LICENSE.txt create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/README.md create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRServer/IRServer.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDemo/IRrecvDemo.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDump/IRrecvDump.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDumpV2/IRrecvDumpV2.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRsendDemo/IRsendDemo.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/JVCPanasonicSendDemo/JVCPanasonicSendDemo.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/keywords.txt create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.json create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.properties create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/platformio.ini create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/ESP8266MeshIRRemote.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/QueueArray.h create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/Makefile create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/README.md create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/config.mk create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/credentials.h.example create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/platformio.ini create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/ESP8266MeshSensor.ino create mode 100644 libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/capabilities.h create mode 100644 libraries/ESP8266_MQTT_Mesh/library.json create mode 100644 libraries/ESP8266_MQTT_Mesh/library.properties create mode 100644 libraries/ESP8266_MQTT_Mesh/src/Base64.cpp create mode 100644 libraries/ESP8266_MQTT_Mesh/src/Base64.h create mode 100644 libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.cpp create mode 100644 libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.h create mode 100644 libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMeshBuilder.h create mode 100644 libraries/ESP8266_MQTT_Mesh/src/WiFiCompat.h create mode 100644 libraries/ESP8266_MQTT_Mesh/utils/dump_stacktrace.py create mode 100644 libraries/ESP8266_MQTT_Mesh/utils/gen_server_cert.sh create mode 100644 libraries/ESP8266_MQTT_Mesh/utils/get_mqtt_fingerprint.py create mode 100644 libraries/ESP8266_MQTT_Mesh/utils/send_ota.py create mode 100644 libraries/FlowMeter-master/.gitattributes create mode 100644 libraries/FlowMeter-master/.gitignore create mode 100644 libraries/FlowMeter-master/.gitmodules create mode 100644 libraries/FlowMeter-master/.travis.yml create mode 100644 libraries/FlowMeter-master/keywords.txt create mode 100644 libraries/FlowMeter-master/library.properties create mode 100644 libraries/bitluni_ESP32Lib/Documentation/schematic.png create mode 100644 libraries/bitluni_ESP32Lib/Documentation/schematic3bit.png create mode 100644 libraries/bitluni_ESP32Lib/README.md create mode 100644 libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html create mode 100644 libraries/bitluni_ESP32Lib/Utilities/StlConverter.html create mode 100644 libraries/bitluni_ESP32Lib/examples/6BitMode/6BitMode.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/GFXWrapper/GFXWrapper.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.h create mode 100644 libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/VGA2DFeatures.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/rock.h create mode 100644 libraries/bitluni_ESP32Lib/examples/VGA3DEngine/VGA3DEngine.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGA3DEngine/thinker.h create mode 100644 libraries/bitluni_ESP32Lib/examples/VGACustomResolution/VGACustomResolution.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGADemo14Bit/VGADemo14Bit.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGAFonts/VGAFonts.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGAHelloWorld/VGAHelloWorld.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGAHighRes/VGAHighRes.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGANoFrameBuffer/VGANoFrameBuffer.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGASprites/VGASprites.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGASprites/explosion.h create mode 100644 libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/VGAWiFiTextTerminal.ino create mode 100644 libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/page.h create mode 100644 libraries/bitluni_ESP32Lib/keywords.txt create mode 100644 libraries/bitluni_ESP32Lib/library.json create mode 100644 libraries/bitluni_ESP32Lib/library.properties create mode 100644 libraries/bitluni_ESP32Lib/src/Audio/AudioOutput.h create mode 100644 libraries/bitluni_ESP32Lib/src/Audio/AudioSystem.h create mode 100644 libraries/bitluni_ESP32Lib/src/Controller/GameControllers.h create mode 100644 libraries/bitluni_ESP32Lib/src/ESP32Lib.h create mode 100644 libraries/bitluni_ESP32Lib/src/GfxWrapper.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Animation.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Engine3D.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Entity.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Font.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Graphics.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR1G1B1A1.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR1G1B1A1X2S2Swapped.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR2G2B2A2.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR2G2B2S2Swapped.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR5G5B4A2.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/GraphicsR5G5B4S2Swapped.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Image.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/ImageDrawer.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Mesh.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/Sprites.h create mode 100644 libraries/bitluni_ESP32Lib/src/Graphics/TriangleTree.h create mode 100644 libraries/bitluni_ESP32Lib/src/I2S/DMABufferDescriptor.h create mode 100644 libraries/bitluni_ESP32Lib/src/I2S/I2S.cpp create mode 100644 libraries/bitluni_ESP32Lib/src/I2S/I2S.h create mode 100644 libraries/bitluni_ESP32Lib/src/Math/Matrix.h create mode 100644 libraries/bitluni_ESP32Lib/src/Math/MatrixFast.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/CodePage437_8x14.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/CodePage437_8x16.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/CodePage437_8x19.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/CodePage437_8x8.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/CodePage437_9x16.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/Font6x8.h create mode 100644 libraries/bitluni_ESP32Lib/src/Ressources/Font8x8.h create mode 100644 libraries/bitluni_ESP32Lib/src/Tools/Log.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/Mode.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/PinConfig.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA.cpp create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA14Bit.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA14BitI.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA3Bit.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA3BitI.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA6Bit.h create mode 100644 libraries/bitluni_ESP32Lib/src/VGA/VGA6BitI.h create mode 100644 libraries/readme.txt diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/.github/ISSUE_TEMPLATE.md b/libraries/Adafruit-MCP23017-Arduino-Library-master/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..f0e2614 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/.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/libraries/Adafruit-MCP23017-Arduino-Library-master/.github/PULL_REQUEST_TEMPLATE.md b/libraries/Adafruit-MCP23017-Arduino-Library-master/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7b641eb --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/.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/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.cpp b/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.cpp new file mode 100644 index 0000000..aba5e0b --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.cpp @@ -0,0 +1,283 @@ +/*************************************************** + This is a library for the MCP23017 i2c port expander + + These displays 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 + ****************************************************/ + +#ifdef __AVR + #include +#elif defined(ESP8266) + #include +#endif +#include "Adafruit_MCP23017.h" + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// minihelper to keep Arduino backward compatibility +static inline void wiresend(uint8_t x) { +#if ARDUINO >= 100 + Wire.write((uint8_t) x); +#else + Wire.send(x); +#endif +} + +static inline uint8_t wirerecv(void) { +#if ARDUINO >= 100 + return Wire.read(); +#else + return Wire.receive(); +#endif +} + +/** + * Bit number associated to a give Pin + */ +uint8_t Adafruit_MCP23017::bitForPin(uint8_t pin){ + return pin%8; +} + +/** + * Register address, port dependent, for a given PIN + */ +uint8_t Adafruit_MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr){ + return(pin<8) ?portAaddr:portBaddr; +} + +/** + * Reads a given register + */ +uint8_t Adafruit_MCP23017::readRegister(uint8_t addr){ + // read the current GPINTEN + Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(addr); + Wire.endTransmission(); + Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); + return wirerecv(); +} + + +/** + * Writes a given register + */ +void Adafruit_MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue){ + // Write the register + Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(regAddr); + wiresend(regValue); + Wire.endTransmission(); +} + + +/** + * Helper to update a single bit of an A/B register. + * - Reads the current register value + * - Writes the new register value + */ +void Adafruit_MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr) { + uint8_t regValue; + uint8_t regAddr=regForPin(pin,portAaddr,portBaddr); + uint8_t bit=bitForPin(pin); + regValue = readRegister(regAddr); + + // set the value for the particular bit + bitWrite(regValue,bit,pValue); + + writeRegister(regAddr,regValue); +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the MCP23017 given its HW selected address, see datasheet for Address selection. + */ +void Adafruit_MCP23017::begin(uint8_t addr) { + if (addr > 7) { + addr = 7; + } + i2caddr = addr; + + Wire.begin(); + + // set defaults! + // all inputs on port A and B + writeRegister(MCP23017_IODIRA,0xff); + writeRegister(MCP23017_IODIRB,0xff); +} + +/** + * Initializes the default MCP23017, with 000 for the configurable part of the address + */ +void Adafruit_MCP23017::begin(void) { + begin(0); +} + +/** + * Sets the pin mode to either INPUT or OUTPUT + */ +void Adafruit_MCP23017::pinMode(uint8_t p, uint8_t d) { + updateRegisterBit(p,(d==INPUT),MCP23017_IODIRA,MCP23017_IODIRB); +} + +/** + * Reads all 16 pins (port A and B) into a single 16 bits variable. + */ +uint16_t Adafruit_MCP23017::readGPIOAB() { + uint16_t ba = 0; + uint8_t a; + + // read the current GPIO output latches + Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(MCP23017_GPIOA); + Wire.endTransmission(); + + Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 2); + a = wirerecv(); + ba = wirerecv(); + ba <<= 8; + ba |= a; + + return ba; +} + +/** + * Read a single port, A or B, and return its current 8 bit value. + * Parameter b should be 0 for GPIOA, and 1 for GPIOB. + */ +uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) { + + // read the current GPIO output latches + Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); + if (b == 0) + wiresend(MCP23017_GPIOA); + else { + wiresend(MCP23017_GPIOB); + } + Wire.endTransmission(); + + Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1); + return wirerecv(); +} + +/** + * Writes all the pins in one go. This method is very useful if you are implementing a multiplexed matrix and want to get a decent refresh rate. + */ +void Adafruit_MCP23017::writeGPIOAB(uint16_t ba) { + Wire.beginTransmission(MCP23017_ADDRESS | i2caddr); + wiresend(MCP23017_GPIOA); + wiresend(ba & 0xFF); + wiresend(ba >> 8); + Wire.endTransmission(); +} + +void Adafruit_MCP23017::digitalWrite(uint8_t pin, uint8_t d) { + uint8_t gpio; + uint8_t bit=bitForPin(pin); + + + // read the current GPIO output latches + uint8_t regAddr=regForPin(pin,MCP23017_OLATA,MCP23017_OLATB); + gpio = readRegister(regAddr); + + // set the pin and direction + bitWrite(gpio,bit,d); + + // write the new GPIO + regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB); + writeRegister(regAddr,gpio); +} + +void Adafruit_MCP23017::pullUp(uint8_t p, uint8_t d) { + updateRegisterBit(p,d,MCP23017_GPPUA,MCP23017_GPPUB); +} + +uint8_t Adafruit_MCP23017::digitalRead(uint8_t pin) { + uint8_t bit=bitForPin(pin); + uint8_t regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB); + return (readRegister(regAddr) >> bit) & 0x1; +} + +/** + * Configures the interrupt system. both port A and B are assigned the same configuration. + * Mirroring will OR both INTA and INTB pins. + * Opendrain will set the INT pin to value or open drain. + * polarity will set LOW or HIGH on interrupt. + * Default values after Power On Reset are: (false, false, LOW) + * If you are connecting the INTA/B pin to arduino 2/3, you should configure the interupt handling as FALLING with + * the default configuration. + */ +void Adafruit_MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, uint8_t polarity){ + // configure the port A + uint8_t ioconfValue=readRegister(MCP23017_IOCONA); + bitWrite(ioconfValue,6,mirroring); + bitWrite(ioconfValue,2,openDrain); + bitWrite(ioconfValue,1,polarity); + writeRegister(MCP23017_IOCONA,ioconfValue); + + // Configure the port B + ioconfValue=readRegister(MCP23017_IOCONB); + bitWrite(ioconfValue,6,mirroring); + bitWrite(ioconfValue,2,openDrain); + bitWrite(ioconfValue,1,polarity); + writeRegister(MCP23017_IOCONB,ioconfValue); +} + +/** + * Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING. + * + * Note that the interrupt condition finishes when you read the information about the port / value + * that caused the interrupt or you read the port itself. Check the datasheet can be confusing. + * + */ +void Adafruit_MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode) { + + // set the pin interrupt control (0 means change, 1 means compare against given value); + updateRegisterBit(pin,(mode!=CHANGE),MCP23017_INTCONA,MCP23017_INTCONB); + // if the mode is not CHANGE, we need to set up a default value, different value triggers interrupt + + // In a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1. + // In a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0. + updateRegisterBit(pin,(mode==FALLING),MCP23017_DEFVALA,MCP23017_DEFVALB); + + // enable the pin for interrupt + updateRegisterBit(pin,HIGH,MCP23017_GPINTENA,MCP23017_GPINTENB); + +} + +uint8_t Adafruit_MCP23017::getLastInterruptPin(){ + uint8_t intf; + + // try port A + intf=readRegister(MCP23017_INTFA); + for(int i=0;i<8;i++) if (bitRead(intf,i)) return i; + + // try port B + intf=readRegister(MCP23017_INTFB); + for(int i=0;i<8;i++) if (bitRead(intf,i)) return i+8; + + return MCP23017_INT_ERR; + +} +uint8_t Adafruit_MCP23017::getLastInterruptPinValue(){ + uint8_t intPin=getLastInterruptPin(); + if(intPin!=MCP23017_INT_ERR){ + uint8_t intcapreg=regForPin(intPin,MCP23017_INTCAPA,MCP23017_INTCAPB); + uint8_t bit=bitForPin(intPin); + return (readRegister(intcapreg)>>bit) & (0x01); + } + + return MCP23017_INT_ERR; +} + + diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.h b/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.h new file mode 100644 index 0000000..3793aec --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/Adafruit_MCP23017.h @@ -0,0 +1,97 @@ +/*************************************************** + This is a library for the MCP23017 i2c port expander + + These displays 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 + ****************************************************/ + +#ifndef _Adafruit_MCP23017_H_ +#define _Adafruit_MCP23017_H_ + +// Don't forget the Wire library +#ifndef ARDUINO_AVR_GEMMA + //TinyWireM is now part of + // Adafruit version of Wire Library, so this + // will work with Adafruit ATtiny85's + //But Arduino Gemma doesn't use that library + // We do NOT want to include Wire if it's an arduino Gemma + #include +#else + #include + #define Wire TinyWireM +#endif + + +class Adafruit_MCP23017 { +public: + void begin(uint8_t addr); + void begin(void); + + void pinMode(uint8_t p, uint8_t d); + void digitalWrite(uint8_t p, uint8_t d); + void pullUp(uint8_t p, uint8_t d); + uint8_t digitalRead(uint8_t p); + + void writeGPIOAB(uint16_t); + uint16_t readGPIOAB(); + uint8_t readGPIO(uint8_t b); + + void setupInterrupts(uint8_t mirroring, uint8_t open, uint8_t polarity); + void setupInterruptPin(uint8_t p, uint8_t mode); + uint8_t getLastInterruptPin(); + uint8_t getLastInterruptPinValue(); + + private: + uint8_t i2caddr; + + uint8_t bitForPin(uint8_t pin); + uint8_t regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr); + + uint8_t readRegister(uint8_t addr); + void writeRegister(uint8_t addr, uint8_t value); + + /** + * Utility private method to update a register associated with a pin (whether port A/B) + * reads its value, updates the particular bit, and writes its value. + */ + void updateRegisterBit(uint8_t p, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr); + +}; + +#define MCP23017_ADDRESS 0x20 + +// registers +#define MCP23017_IODIRA 0x00 +#define MCP23017_IPOLA 0x02 +#define MCP23017_GPINTENA 0x04 +#define MCP23017_DEFVALA 0x06 +#define MCP23017_INTCONA 0x08 +#define MCP23017_IOCONA 0x0A +#define MCP23017_GPPUA 0x0C +#define MCP23017_INTFA 0x0E +#define MCP23017_INTCAPA 0x10 +#define MCP23017_GPIOA 0x12 +#define MCP23017_OLATA 0x14 + + +#define MCP23017_IODIRB 0x01 +#define MCP23017_IPOLB 0x03 +#define MCP23017_GPINTENB 0x05 +#define MCP23017_DEFVALB 0x07 +#define MCP23017_INTCONB 0x09 +#define MCP23017_IOCONB 0x0B +#define MCP23017_GPPUB 0x0D +#define MCP23017_INTFB 0x0F +#define MCP23017_INTCAPB 0x11 +#define MCP23017_GPIOB 0x13 +#define MCP23017_OLATB 0x15 + +#define MCP23017_INT_ERR 255 + +#endif diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/README.md b/libraries/Adafruit-MCP23017-Arduino-Library-master/README.md new file mode 100644 index 0000000..16d43f2 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/README.md @@ -0,0 +1,15 @@ +This is a library for the MCP23017 I2c Port Expander + +These chips use I2C to communicate, 2 pins 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, check license.txt for more information +All text above must be included in any redistribution + +To download. click the DOWNLOADS button in the top right corner, rename the uncompressed folder Adafruit_MCP23017. Check that the Adafruit_MCP23017 folder contains Adafruit_MCP23017.cpp and Adafruit_MCP23017.h + +Place the Adafruit_MCP23017 library folder your /libraries/ folder. You may need to create the libraries subfolder if its your first library. Restart the IDE. diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/button/button.ino b/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/button/button.ino new file mode 100644 index 0000000..962b8c4 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/button/button.ino @@ -0,0 +1,32 @@ +#include +#include "Adafruit_MCP23017.h" + +// Basic pin reading and pullup test for the MCP23017 I/O expander +// public domain! + +// Connect pin #12 of the expander to Analog 5 (i2c clock) +// Connect pin #13 of the expander to Analog 4 (i2c data) +// Connect pins #15, 16 and 17 of the expander to ground (address selection) +// Connect pin #9 of the expander to 5V (power) +// Connect pin #10 of the expander to ground (common ground) +// Connect pin #18 through a ~10kohm resistor to 5V (reset pin, active low) + +// Input #0 is on pin 21 so connect a button or switch from there to ground + +Adafruit_MCP23017 mcp; + +void setup() { + mcp.begin(); // use default address 0 + + mcp.pinMode(0, INPUT); + mcp.pullUp(0, HIGH); // turn on a 100K pullup internally + + pinMode(13, OUTPUT); // use the p13 LED as debugging +} + + + +void loop() { + // The LED will 'echo' the button + digitalWrite(13, mcp.digitalRead(0)); +} \ No newline at end of file diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/interrupt/interrupt.ino b/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/interrupt/interrupt.ino new file mode 100644 index 0000000..15247c5 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/examples/interrupt/interrupt.ino @@ -0,0 +1,123 @@ +// Install the LowPower library for optional sleeping support. +// See loop() function comments for details on usage. +//#include + +#include +#include + +Adafruit_MCP23017 mcp; + +byte ledPin=13; + +// Interrupts from the MCP will be handled by this PIN +byte arduinoIntPin=3; + +// ... and this interrupt vector +byte arduinoInterrupt=1; + +volatile boolean awakenByInterrupt = false; + +// Two pins at the MCP (Ports A/B where some buttons have been setup.) +// Buttons connect the pin to grond, and pins are pulled up. +byte mcpPinA=7; +byte mcpPinB=15; + +void setup(){ + + Serial.begin(9600); + Serial.println("MCP23007 Interrupt Test"); + + pinMode(arduinoIntPin,INPUT); + + mcp.begin(); // use default address 0 + + // We mirror INTA and INTB, so that only one line is required between MCP and Arduino for int reporting + // The INTA/B will not be Floating + // INTs will be signaled with a LOW + mcp.setupInterrupts(true,false,LOW); + + // configuration for a button on port A + // interrupt will triger when the pin is taken to ground by a pushbutton + mcp.pinMode(mcpPinA, INPUT); + mcp.pullUp(mcpPinA, HIGH); // turn on a 100K pullup internally + mcp.setupInterruptPin(mcpPinA,FALLING); + + // similar, but on port B. + mcp.pinMode(mcpPinB, INPUT); + mcp.pullUp(mcpPinB, HIGH); // turn on a 100K pullup internall + mcp.setupInterruptPin(mcpPinB,FALLING); + + // We will setup a pin for flashing from the int routine + pinMode(ledPin, OUTPUT); // use the p13 LED as debugging + +} + +// The int handler will just signal that the int has happen +// we will do the work from the main loop. +void intCallBack(){ + awakenByInterrupt=true; +} + +void handleInterrupt(){ + + // Get more information from the MCP from the INT + uint8_t pin=mcp.getLastInterruptPin(); + uint8_t val=mcp.getLastInterruptPinValue(); + + // We will flash the led 1 or 2 times depending on the PIN that triggered the Interrupt + // 3 and 4 flases are supposed to be impossible conditions... just for debugging. + uint8_t flashes=4; + if(pin==mcpPinA) flashes=1; + if(pin==mcpPinB) flashes=2; + if(val!=LOW) flashes=3; + + // simulate some output associated to this + for(int i=0;i +#include "Adafruit_MCP23017.h" + +// Basic pin reading and pullup test for the MCP23017 I/O expander +// public domain! + +// Connect pin #12 of the expander to Analog 5 (i2c clock) +// Connect pin #13 of the expander to Analog 4 (i2c data) +// Connect pins #15, 16 and 17 of the expander to ground (address selection) +// Connect pin #9 of the expander to 5V (power) +// Connect pin #10 of the expander to ground (common ground) +// Connect pin #18 through a ~10kohm resistor to 5V (reset pin, active low) + +// Output #0 is on pin 21 so connect an LED or whatever from that to ground + +Adafruit_MCP23017 mcp; + +void setup() { + mcp.begin(); // use default address 0 + + mcp.pinMode(0, OUTPUT); +} + + +// flip the pin #0 up and down + +void loop() { + delay(100); + + mcp.digitalWrite(0, HIGH); + + delay(100); + + mcp.digitalWrite(0, LOW); +} \ No newline at end of file diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/keywords.txt b/libraries/Adafruit-MCP23017-Arduino-Library-master/keywords.txt new file mode 100644 index 0000000..9014299 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/keywords.txt @@ -0,0 +1,21 @@ +####################################### +# Syntax Coloring Map for MCP23017 +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +MCP23017 KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +pullUp KEYWORD2 +writeGPIOAB KEYWORD2 +readGPIOAB KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/library.properties b/libraries/Adafruit-MCP23017-Arduino-Library-master/library.properties new file mode 100644 index 0000000..98ef595 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/library.properties @@ -0,0 +1,9 @@ +name=Adafruit MCP23017 Arduino Library +version=1.0.4 +author=Adafruit +maintainer=Adafruit +sentence=Library for the MCP23017 I2C Port Expander +paragraph=Library for the MCP23017 I2C Port Expander +category=Signal Input/Output +url=https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library +architectures=* diff --git a/libraries/Adafruit-MCP23017-Arduino-Library-master/license.txt b/libraries/Adafruit-MCP23017-Arduino-Library-master/license.txt new file mode 100644 index 0000000..f6a0f22 --- /dev/null +++ b/libraries/Adafruit-MCP23017-Arduino-Library-master/license.txt @@ -0,0 +1,26 @@ +Software License Agreement (BSD License) + +Copyright (c) 2012, 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/libraries/Adafruit_FONA_Library/Adafruit_FONA.cpp b/libraries/Adafruit_FONA_Library/Adafruit_FONA.cpp new file mode 100644 index 0000000..9a78181 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/Adafruit_FONA.cpp @@ -0,0 +1,2092 @@ +/*************************************************** + This is a library for our Adafruit FONA Cellular Module + + Designed specifically to work with the Adafruit FONA + ----> http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + + These displays use TTL Serial 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 + ****************************************************/ + // next line per http://postwarrior.com/arduino-ethershield-error-prog_char-does-not-name-a-type/ + +#include "Adafruit_FONA.h" + +#if defined(ESP8266) + // ESP8266 doesn't have the min and max functions natively available like + // AVR libc seems to provide. Include the STL algorithm library to get these. + // Unfortunately algorithm isn't available in AVR libc so this is ESP8266 + // specific (and likely needed for ARM or other platforms, but they lack + // software serial and are currently incompatible with the FONA library). + #include + using namespace std; +#endif + + +Adafruit_FONA::Adafruit_FONA(int8_t rst) +{ + _rstpin = rst; + + apn = F("FONAnet"); + apnusername = 0; + apnpassword = 0; + mySerial = 0; + httpsredirect = false; + useragent = F("FONA"); + ok_reply = F("OK"); +} + +uint8_t Adafruit_FONA::type(void) { + return _type; +} + +boolean Adafruit_FONA::begin(Stream &port) { + mySerial = &port; + + pinMode(_rstpin, OUTPUT); + digitalWrite(_rstpin, HIGH); + delay(10); + digitalWrite(_rstpin, LOW); + delay(100); + digitalWrite(_rstpin, HIGH); + + DEBUG_PRINTLN(F("Attempting to open comm with ATs")); + // give 7 seconds to reboot + int16_t timeout = 7000; + + while (timeout > 0) { + while (mySerial->available()) mySerial->read(); + if (sendCheckReply(F("AT"), ok_reply)) + break; + while (mySerial->available()) mySerial->read(); + if (sendCheckReply(F("AT"), F("AT"))) + break; + delay(500); + timeout-=500; + } + + if (timeout <= 0) { +#ifdef ADAFRUIT_FONA_DEBUG + DEBUG_PRINTLN(F("Timeout: No response to AT... last ditch attempt.")); +#endif + sendCheckReply(F("AT"), ok_reply); + delay(100); + sendCheckReply(F("AT"), ok_reply); + delay(100); + sendCheckReply(F("AT"), ok_reply); + delay(100); + } + + // turn off Echo! + sendCheckReply(F("ATE0"), ok_reply); + delay(100); + + if (! sendCheckReply(F("ATE0"), ok_reply)) { + return false; + } + + // turn on hangupitude + sendCheckReply(F("AT+CVHU=0"), ok_reply); + + delay(100); + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINTLN("ATI"); + + mySerial->println("ATI"); + readline(500, true); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + + + if (prog_char_strstr(replybuffer, (prog_char *)F("SIM808 R14")) != 0) { + _type = FONA808_V2; + } else if (prog_char_strstr(replybuffer, (prog_char *)F("SIM808 R13")) != 0) { + _type = FONA808_V1; + } else if (prog_char_strstr(replybuffer, (prog_char *)F("SIM800 R13")) != 0) { + _type = FONA800L; + } else if (prog_char_strstr(replybuffer, (prog_char *)F("SIMCOM_SIM5320A")) != 0) { + _type = FONA3G_A; + } else if (prog_char_strstr(replybuffer, (prog_char *)F("SIMCOM_SIM5320E")) != 0) { + _type = FONA3G_E; + } + + if (_type == FONA800L) { + // determine if L or H + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINTLN("AT+GMM"); + + mySerial->println("AT+GMM"); + readline(500, true); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + + if (prog_char_strstr(replybuffer, (prog_char *)F("SIM800H")) != 0) { + _type = FONA800H; + } + } + +#if defined(FONA_PREF_SMS_STORAGE) + sendCheckReply(F("AT+CPMS=" FONA_PREF_SMS_STORAGE "," FONA_PREF_SMS_STORAGE "," FONA_PREF_SMS_STORAGE), ok_reply); +#endif + + return true; +} + + +/********* Serial port ********************************************/ +boolean Adafruit_FONA::setBaudrate(uint16_t baud) { + return sendCheckReply(F("AT+IPREX="), baud, ok_reply); +} + +/********* Real Time Clock ********************************************/ + +boolean Adafruit_FONA::readRTC(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *hr, uint8_t *min, uint8_t *sec) { + uint16_t v; + sendParseReply(F("AT+CCLK?"), F("+CCLK: "), &v, '/', 0); + *year = v; + + DEBUG_PRINTLN(*year); +} + +boolean Adafruit_FONA::enableRTC(uint8_t i) { + if (! sendCheckReply(F("AT+CLTS="), i, ok_reply)) + return false; + return sendCheckReply(F("AT&W"), ok_reply); +} + + +/********* BATTERY & ADC ********************************************/ + +/* returns value in mV (uint16_t) */ +boolean Adafruit_FONA::getBattVoltage(uint16_t *v) { + return sendParseReply(F("AT+CBC"), F("+CBC: "), v, ',', 2); +} + +/* returns value in mV (uint16_t) */ +boolean Adafruit_FONA_3G::getBattVoltage(uint16_t *v) { + float f; + boolean b = sendParseReply(F("AT+CBC"), F("+CBC: "), &f, ',', 2); + *v = f*1000; + return b; +} + + +/* returns the percentage charge of battery as reported by sim800 */ +boolean Adafruit_FONA::getBattPercent(uint16_t *p) { + return sendParseReply(F("AT+CBC"), F("+CBC: "), p, ',', 1); +} + +boolean Adafruit_FONA::getADCVoltage(uint16_t *v) { + return sendParseReply(F("AT+CADC?"), F("+CADC: 1,"), v); +} + +/********* SIM ***********************************************************/ + +uint8_t Adafruit_FONA::unlockSIM(char *pin) +{ + char sendbuff[14] = "AT+CPIN="; + sendbuff[8] = pin[0]; + sendbuff[9] = pin[1]; + sendbuff[10] = pin[2]; + sendbuff[11] = pin[3]; + sendbuff[12] = '\0'; + + return sendCheckReply(sendbuff, ok_reply); +} + +uint8_t Adafruit_FONA::getSIMCCID(char *ccid) { + getReply(F("AT+CCID")); + // up to 28 chars for reply, 20 char total ccid + if (replybuffer[0] == '+') { + // fona 3g? + strncpy(ccid, replybuffer+8, 20); + } else { + // fona 800 or 800 + strncpy(ccid, replybuffer, 20); + } + ccid[20] = 0; + + readline(); // eat 'OK' + + return strlen(ccid); +} + +/********* IMEI **********************************************************/ + +uint8_t Adafruit_FONA::getIMEI(char *imei) { + getReply(F("AT+GSN")); + + // up to 15 chars + strncpy(imei, replybuffer, 15); + imei[15] = 0; + + readline(); // eat 'OK' + + return strlen(imei); +} + +/********* NETWORK *******************************************************/ + +uint8_t Adafruit_FONA::getNetworkStatus(void) { + uint16_t status; + + if (! sendParseReply(F("AT+CREG?"), F("+CREG: "), &status, ',', 1)) return 0; + + return status; +} + + +uint8_t Adafruit_FONA::getRSSI(void) { + uint16_t reply; + + if (! sendParseReply(F("AT+CSQ"), F("+CSQ: "), &reply) ) return 0; + + return reply; +} + +/********* AUDIO *******************************************************/ + +boolean Adafruit_FONA::setAudio(uint8_t a) { + // 0 is headset, 1 is external audio + if (a > 1) return false; + + return sendCheckReply(F("AT+CHFA="), a, ok_reply); +} + +uint8_t Adafruit_FONA::getVolume(void) { + uint16_t reply; + + if (! sendParseReply(F("AT+CLVL?"), F("+CLVL: "), &reply) ) return 0; + + return reply; +} + +boolean Adafruit_FONA::setVolume(uint8_t i) { + return sendCheckReply(F("AT+CLVL="), i, ok_reply); +} + + +boolean Adafruit_FONA::playDTMF(char dtmf) { + char str[4]; + str[0] = '\"'; + str[1] = dtmf; + str[2] = '\"'; + str[3] = 0; + return sendCheckReply(F("AT+CLDTMF=3,"), str, ok_reply); +} + +boolean Adafruit_FONA::playToolkitTone(uint8_t t, uint16_t len) { + return sendCheckReply(F("AT+STTONE=1,"), t, len, ok_reply); +} + +boolean Adafruit_FONA_3G::playToolkitTone(uint8_t t, uint16_t len) { + if (! sendCheckReply(F("AT+CPTONE="), t, ok_reply)) + return false; + delay(len); + return sendCheckReply(F("AT+CPTONE=0"), ok_reply); +} + +boolean Adafruit_FONA::setMicVolume(uint8_t a, uint8_t level) { + // 0 is headset, 1 is external audio + if (a > 1) return false; + + return sendCheckReply(F("AT+CMIC="), a, level, ok_reply); +} + +/********* FM RADIO *******************************************************/ + + +boolean Adafruit_FONA::FMradio(boolean onoff, uint8_t a) { + if (! onoff) { + return sendCheckReply(F("AT+FMCLOSE"), ok_reply); + } + + // 0 is headset, 1 is external audio + if (a > 1) return false; + + return sendCheckReply(F("AT+FMOPEN="), a, ok_reply); +} + +boolean Adafruit_FONA::tuneFMradio(uint16_t station) { + // Fail if FM station is outside allowed range. + if ((station < 870) || (station > 1090)) + return false; + + return sendCheckReply(F("AT+FMFREQ="), station, ok_reply); +} + +boolean Adafruit_FONA::setFMVolume(uint8_t i) { + // Fail if volume is outside allowed range (0-6). + if (i > 6) { + return false; + } + // Send FM volume command and verify response. + return sendCheckReply(F("AT+FMVOLUME="), i, ok_reply); +} + +int8_t Adafruit_FONA::getFMVolume() { + uint16_t level; + + if (! sendParseReply(F("AT+FMVOLUME?"), F("+FMVOLUME: "), &level) ) return 0; + + return level; +} + +int8_t Adafruit_FONA::getFMSignalLevel(uint16_t station) { + // Fail if FM station is outside allowed range. + if ((station < 875) || (station > 1080)) { + return -1; + } + + // Send FM signal level query command. + // Note, need to explicitly send timeout so right overload is chosen. + getReply(F("AT+FMSIGNAL="), station, FONA_DEFAULT_TIMEOUT_MS); + // Check response starts with expected value. + char *p = prog_char_strstr(replybuffer, PSTR("+FMSIGNAL: ")); + if (p == 0) return -1; + p+=11; + // Find second colon to get start of signal quality. + p = strchr(p, ':'); + if (p == 0) return -1; + p+=1; + // Parse signal quality. + int8_t level = atoi(p); + readline(); // eat the "OK" + return level; +} + +/********* PWM/BUZZER **************************************************/ + +boolean Adafruit_FONA::setPWM(uint16_t period, uint8_t duty) { + if (period > 2000) return false; + if (duty > 100) return false; + + return sendCheckReply(F("AT+SPWM=0,"), period, duty, ok_reply); +} + +/********* CALL PHONES **************************************************/ +boolean Adafruit_FONA::callPhone(char *number) { + char sendbuff[35] = "ATD"; + strncpy(sendbuff+3, number, min(30, (int)strlen(number))); + uint8_t x = strlen(sendbuff); + sendbuff[x] = ';'; + sendbuff[x+1] = 0; + //DEBUG_PRINTLN(sendbuff); + + return sendCheckReply(sendbuff, ok_reply); +} + + +uint8_t Adafruit_FONA::getCallStatus(void) { + uint16_t phoneStatus; + + if (! sendParseReply(F("AT+CPAS"), F("+CPAS: "), &phoneStatus)) + return FONA_CALL_FAILED; // 1, since 0 is actually a known, good reply + + return phoneStatus; // 0 ready, 2 unkown, 3 ringing, 4 call in progress +} + +boolean Adafruit_FONA::hangUp(void) { + return sendCheckReply(F("ATH0"), ok_reply); +} + +boolean Adafruit_FONA_3G::hangUp(void) { + getReply(F("ATH")); + + return (prog_char_strstr(replybuffer, (prog_char *)F("VOICE CALL: END")) != 0); +} + +boolean Adafruit_FONA::pickUp(void) { + return sendCheckReply(F("ATA"), ok_reply); +} + +boolean Adafruit_FONA_3G::pickUp(void) { + return sendCheckReply(F("ATA"), F("VOICE CALL: BEGIN")); +} + + +void Adafruit_FONA::onIncomingCall() { + + DEBUG_PRINT(F("> ")); DEBUG_PRINTLN(F("Incoming call...")); + + Adafruit_FONA::_incomingCall = true; +} + +boolean Adafruit_FONA::_incomingCall = false; + +boolean Adafruit_FONA::callerIdNotification(boolean enable, uint8_t interrupt) { + if(enable){ + attachInterrupt(interrupt, onIncomingCall, FALLING); + return sendCheckReply(F("AT+CLIP=1"), ok_reply); + } + + detachInterrupt(interrupt); + return sendCheckReply(F("AT+CLIP=0"), ok_reply); +} + +boolean Adafruit_FONA::incomingCallNumber(char* phonenum) { + //+CLIP: "",145,"",0,"",0 + if(!Adafruit_FONA::_incomingCall) + return false; + + readline(); + while(!prog_char_strcmp(replybuffer, (prog_char*)F("RING")) == 0) { + flushInput(); + readline(); + } + + readline(); //reads incoming phone number line + + parseReply(F("+CLIP: \""), phonenum, '"'); + + + DEBUG_PRINT(F("Phone Number: ")); + DEBUG_PRINTLN(replybuffer); + + + Adafruit_FONA::_incomingCall = false; + return true; +} + +/********* SMS **********************************************************/ + +uint8_t Adafruit_FONA::getSMSInterrupt(void) { + uint16_t reply; + + if (! sendParseReply(F("AT+CFGRI?"), F("+CFGRI: "), &reply) ) return 0; + + return reply; +} + +boolean Adafruit_FONA::setSMSInterrupt(uint8_t i) { + return sendCheckReply(F("AT+CFGRI="), i, ok_reply); +} + +int8_t Adafruit_FONA::getNumSMS(void) { + uint16_t numsms; + + // get into text mode + if (! sendCheckReply(F("AT+CMGF=1"), ok_reply)) return -1; + + // ask how many sms are stored + if (sendParseReply(F("AT+CPMS?"), F(FONA_PREF_SMS_STORAGE ","), &numsms)) + return numsms; + if (sendParseReply(F("AT+CPMS?"), F("\"SM\","), &numsms)) + return numsms; + if (sendParseReply(F("AT+CPMS?"), F("\"SM_P\","), &numsms)) + return numsms; + return -1; +} + +// Reading SMS's is a bit involved so we don't use helpers that may cause delays or debug +// printouts! +boolean Adafruit_FONA::readSMS(uint8_t i, char *smsbuff, + uint16_t maxlen, uint16_t *readlen) { + // text mode + if (! sendCheckReply(F("AT+CMGF=1"), ok_reply)) return false; + + // show all text mode parameters + if (! sendCheckReply(F("AT+CSDH=1"), ok_reply)) return false; + + // parse out the SMS len + uint16_t thesmslen = 0; + + + DEBUG_PRINT(F("AT+CMGR=")); + DEBUG_PRINTLN(i); + + + //getReply(F("AT+CMGR="), i, 1000); // do not print debug! + mySerial->print(F("AT+CMGR=")); + mySerial->println(i); + readline(1000); // timeout + + //DEBUG_PRINT(F("Reply: ")); DEBUG_PRINTLN(replybuffer); + // parse it out... + + + DEBUG_PRINTLN(replybuffer); + + + if (! parseReply(F("+CMGR:"), &thesmslen, ',', 11)) { + *readlen = 0; + return false; + } + + readRaw(thesmslen); + + flushInput(); + + uint16_t thelen = min(maxlen, (uint16_t)strlen(replybuffer)); + strncpy(smsbuff, replybuffer, thelen); + smsbuff[thelen] = 0; // end the string + + + DEBUG_PRINTLN(replybuffer); + + *readlen = thelen; + return true; +} + +// Retrieve the sender of the specified SMS message and copy it as a string to +// the sender buffer. Up to senderlen characters of the sender will be copied +// and a null terminator will be added if less than senderlen charactesr are +// copied to the result. Returns true if a result was successfully retrieved, +// otherwise false. +boolean Adafruit_FONA::getSMSSender(uint8_t i, char *sender, int senderlen) { + // Ensure text mode and all text mode parameters are sent. + if (! sendCheckReply(F("AT+CMGF=1"), ok_reply)) return false; + if (! sendCheckReply(F("AT+CSDH=1"), ok_reply)) return false; + + + DEBUG_PRINT(F("AT+CMGR=")); + DEBUG_PRINTLN(i); + + + // Send command to retrieve SMS message and parse a line of response. + mySerial->print(F("AT+CMGR=")); + mySerial->println(i); + readline(1000); + + + DEBUG_PRINTLN(replybuffer); + + + // Parse the second field in the response. + boolean result = parseReplyQuoted(F("+CMGR:"), sender, senderlen, ',', 1); + // Drop any remaining data from the response. + flushInput(); + return result; +} + +boolean Adafruit_FONA::sendSMS(char *smsaddr, char *smsmsg) { + if (! sendCheckReply(F("AT+CMGF=1"), ok_reply)) return false; + + char sendcmd[30] = "AT+CMGS=\""; + strncpy(sendcmd+9, smsaddr, 30-9-2); // 9 bytes beginning, 2 bytes for close quote + null + sendcmd[strlen(sendcmd)] = '\"'; + + if (! sendCheckReply(sendcmd, F("> "))) return false; + + DEBUG_PRINT(F("> ")); DEBUG_PRINTLN(smsmsg); + + mySerial->println(smsmsg); + mySerial->println(); + mySerial->write(0x1A); + + DEBUG_PRINTLN("^Z"); + + if ( (_type == FONA3G_A) || (_type == FONA3G_E) ) { + // Eat two sets of CRLF + readline(200); + //DEBUG_PRINT("Line 1: "); DEBUG_PRINTLN(strlen(replybuffer)); + readline(200); + //DEBUG_PRINT("Line 2: "); DEBUG_PRINTLN(strlen(replybuffer)); + } + readline(10000); // read the +CMGS reply, wait up to 10 seconds!!! + //DEBUG_PRINT("Line 3: "); DEBUG_PRINTLN(strlen(replybuffer)); + if (strstr(replybuffer, "+CMGS") == 0) { + return false; + } + readline(1000); // read OK + //DEBUG_PRINT("* "); DEBUG_PRINTLN(replybuffer); + + if (strcmp(replybuffer, "OK") != 0) { + return false; + } + + return true; +} + + +boolean Adafruit_FONA::deleteSMS(uint8_t i) { + if (! sendCheckReply(F("AT+CMGF=1"), ok_reply)) return false; + // read an sms + char sendbuff[12] = "AT+CMGD=000"; + sendbuff[8] = (i / 100) + '0'; + i %= 100; + sendbuff[9] = (i / 10) + '0'; + i %= 10; + sendbuff[10] = i + '0'; + + return sendCheckReply(sendbuff, ok_reply, 2000); +} + +/********* USSD *********************************************************/ + +boolean Adafruit_FONA::sendUSSD(char *ussdmsg, char *ussdbuff, uint16_t maxlen, uint16_t *readlen) { + if (! sendCheckReply(F("AT+CUSD=1"), ok_reply)) return false; + + char sendcmd[30] = "AT+CUSD=1,\""; + strncpy(sendcmd+11, ussdmsg, 30-11-2); // 11 bytes beginning, 2 bytes for close quote + null + sendcmd[strlen(sendcmd)] = '\"'; + + if (! sendCheckReply(sendcmd, ok_reply)) { + *readlen = 0; + return false; + } else { + readline(10000); // read the +CUSD reply, wait up to 10 seconds!!! + //DEBUG_PRINT("* "); DEBUG_PRINTLN(replybuffer); + char *p = prog_char_strstr(replybuffer, PSTR("+CUSD: ")); + if (p == 0) { + *readlen = 0; + return false; + } + p+=7; //+CUSD + // Find " to get start of ussd message. + p = strchr(p, '\"'); + if (p == 0) { + *readlen = 0; + return false; + } + p+=1; //" + // Find " to get end of ussd message. + char *strend = strchr(p, '\"'); + + uint16_t lentocopy = min(maxlen-1, strend - p); + strncpy(ussdbuff, p, lentocopy+1); + ussdbuff[lentocopy] = 0; + *readlen = lentocopy; + } + return true; +} + + +/********* TIME **********************************************************/ + +boolean Adafruit_FONA::enableNetworkTimeSync(boolean onoff) { + if (onoff) { + if (! sendCheckReply(F("AT+CLTS=1"), ok_reply)) + return false; + } else { + if (! sendCheckReply(F("AT+CLTS=0"), ok_reply)) + return false; + } + + flushInput(); // eat any 'Unsolicted Result Code' + + return true; +} + +boolean Adafruit_FONA::enableNTPTimeSync(boolean onoff, FONAFlashStringPtr ntpserver) { + if (onoff) { + if (! sendCheckReply(F("AT+CNTPCID=1"), ok_reply)) + return false; + + mySerial->print(F("AT+CNTP=\"")); + if (ntpserver != 0) { + mySerial->print(ntpserver); + } else { + mySerial->print(F("pool.ntp.org")); + } + mySerial->println(F("\",0")); + readline(FONA_DEFAULT_TIMEOUT_MS); + if (strcmp(replybuffer, "OK") != 0) + return false; + + if (! sendCheckReply(F("AT+CNTP"), ok_reply, 10000)) + return false; + + uint16_t status; + readline(10000); + if (! parseReply(F("+CNTP:"), &status)) + return false; + } else { + if (! sendCheckReply(F("AT+CNTPCID=0"), ok_reply)) + return false; + } + + return true; +} + +boolean Adafruit_FONA::getTime(char *buff, uint16_t maxlen) { + getReply(F("AT+CCLK?"), (uint16_t) 10000); + if (strncmp(replybuffer, "+CCLK: ", 7) != 0) + return false; + + char *p = replybuffer+7; + uint16_t lentocopy = min(maxlen-1, (int)strlen(p)); + strncpy(buff, p, lentocopy+1); + buff[lentocopy] = 0; + + readline(); // eat OK + + return true; +} + +/********* GPS **********************************************************/ + + +boolean Adafruit_FONA::enableGPS(boolean onoff) { + uint16_t state; + + // first check if its already on or off + + if (_type == FONA808_V2) { + if (! sendParseReply(F("AT+CGNSPWR?"), F("+CGNSPWR: "), &state) ) + return false; + } else { + if (! sendParseReply(F("AT+CGPSPWR?"), F("+CGPSPWR: "), &state)) + return false; + } + + if (onoff && !state) { + if (_type == FONA808_V2) { + if (! sendCheckReply(F("AT+CGNSPWR=1"), ok_reply)) // try GNS command + return false; + } else { + if (! sendCheckReply(F("AT+CGPSPWR=1"), ok_reply)) + return false; + } + } else if (!onoff && state) { + if (_type == FONA808_V2) { + if (! sendCheckReply(F("AT+CGNSPWR=0"), ok_reply)) // try GNS command + return false; + } else { + if (! sendCheckReply(F("AT+CGPSPWR=0"), ok_reply)) + return false; + } + } + return true; +} + + + +boolean Adafruit_FONA_3G::enableGPS(boolean onoff) { + uint16_t state; + + // first check if its already on or off + if (! Adafruit_FONA::sendParseReply(F("AT+CGPS?"), F("+CGPS: "), &state) ) + return false; + + if (onoff && !state) { + if (! sendCheckReply(F("AT+CGPS=1"), ok_reply)) + return false; + } else if (!onoff && state) { + if (! sendCheckReply(F("AT+CGPS=0"), ok_reply)) + return false; + // this takes a little time + readline(2000); // eat '+CGPS: 0' + } + return true; +} + +int8_t Adafruit_FONA::GPSstatus(void) { + if (_type == FONA808_V2) { + // 808 V2 uses GNS commands and doesn't have an explicit 2D/3D fix status. + // Instead just look for a fix and if found assume it's a 3D fix. + getReply(F("AT+CGNSINF")); + char *p = prog_char_strstr(replybuffer, (prog_char*)F("+CGNSINF: ")); + if (p == 0) return -1; + p+=10; + readline(); // eat 'OK' + if (p[0] == '0') return 0; // GPS is not even on! + + p+=2; // Skip to second value, fix status. + //DEBUG_PRINTLN(p); + // Assume if the fix status is '1' then we have a 3D fix, otherwise no fix. + if (p[0] == '1') return 3; + else return 1; + } + if (_type == FONA3G_A || _type == FONA3G_E) { + // FONA 3G doesn't have an explicit 2D/3D fix status. + // Instead just look for a fix and if found assume it's a 3D fix. + getReply(F("AT+CGPSINFO")); + char *p = prog_char_strstr(replybuffer, (prog_char*)F("+CGPSINFO:")); + if (p == 0) return -1; + if (p[10] != ',') return 3; // if you get anything, its 3D fix + return 0; + } + else { + // 808 V1 looks for specific 2D or 3D fix state. + getReply(F("AT+CGPSSTATUS?")); + char *p = prog_char_strstr(replybuffer, (prog_char*)F("SSTATUS: Location ")); + if (p == 0) return -1; + p+=18; + readline(); // eat 'OK' + //DEBUG_PRINTLN(p); + if (p[0] == 'U') return 0; + if (p[0] == 'N') return 1; + if (p[0] == '2') return 2; + if (p[0] == '3') return 3; + } + // else + return 0; +} + +uint8_t Adafruit_FONA::getGPS(uint8_t arg, char *buffer, uint8_t maxbuff) { + int32_t x = arg; + + if ( (_type == FONA3G_A) || (_type == FONA3G_E) ) { + getReply(F("AT+CGPSINFO")); + } else if (_type == FONA808_V1) { + getReply(F("AT+CGPSINF="), x); + } else { + getReply(F("AT+CGNSINF")); + } + + char *p = prog_char_strstr(replybuffer, (prog_char*)F("SINF")); + if (p == 0) { + buffer[0] = 0; + return 0; + } + + p+=6; + + uint8_t len = max(maxbuff-1, (int)strlen(p)); + strncpy(buffer, p, len); + buffer[len] = 0; + + readline(); // eat 'OK' + return len; +} + +boolean Adafruit_FONA::getGPS(float *lat, float *lon, float *speed_kph, float *heading, float *altitude) { + + char gpsbuffer[120]; + + // we need at least a 2D fix + if (GPSstatus() < 2) + return false; + + // grab the mode 2^5 gps csv from the sim808 + uint8_t res_len = getGPS(32, gpsbuffer, 120); + + // make sure we have a response + if (res_len == 0) + return false; + + if (_type == FONA3G_A || _type == FONA3G_E) { + // Parse 3G respose + // +CGPSINFO:4043.000000,N,07400.000000,W,151015,203802.1,-12.0,0.0,0 + // skip beginning + char *tok; + + // grab the latitude + char *latp = strtok(gpsbuffer, ","); + if (! latp) return false; + + // grab latitude direction + char *latdir = strtok(NULL, ","); + if (! latdir) return false; + + // grab longitude + char *longp = strtok(NULL, ","); + if (! longp) return false; + + // grab longitude direction + char *longdir = strtok(NULL, ","); + if (! longdir) return false; + + // skip date & time + tok = strtok(NULL, ","); + tok = strtok(NULL, ","); + + // only grab altitude if needed + if (altitude != NULL) { + // grab altitude + char *altp = strtok(NULL, ","); + if (! altp) return false; + *altitude = atof(altp); + } + + // only grab speed if needed + if (speed_kph != NULL) { + // grab the speed in km/h + char *speedp = strtok(NULL, ","); + if (! speedp) return false; + + *speed_kph = atof(speedp); + } + + // only grab heading if needed + if (heading != NULL) { + + // grab the speed in knots + char *coursep = strtok(NULL, ","); + if (! coursep) return false; + + *heading = atof(coursep); + } + + double latitude = atof(latp); + double longitude = atof(longp); + + // convert latitude from minutes to decimal + float degrees = floor(latitude / 100); + double minutes = latitude - (100 * degrees); + minutes /= 60; + degrees += minutes; + + // turn direction into + or - + if (latdir[0] == 'S') degrees *= -1; + + *lat = degrees; + + // convert longitude from minutes to decimal + degrees = floor(longitude / 100); + minutes = longitude - (100 * degrees); + minutes /= 60; + degrees += minutes; + + // turn direction into + or - + if (longdir[0] == 'W') degrees *= -1; + + *lon = degrees; + + } else if (_type == FONA808_V2) { + // Parse 808 V2 response. See table 2-3 from here for format: + // http://www.adafruit.com/datasheets/SIM800%20Series_GNSS_Application%20Note%20V1.00.pdf + + // skip GPS run status + char *tok = strtok(gpsbuffer, ","); + if (! tok) return false; + + // skip fix status + tok = strtok(NULL, ","); + if (! tok) return false; + + // skip date + tok = strtok(NULL, ","); + if (! tok) return false; + + // grab the latitude + char *latp = strtok(NULL, ","); + if (! latp) return false; + + // grab longitude + char *longp = strtok(NULL, ","); + if (! longp) return false; + + *lat = atof(latp); + *lon = atof(longp); + + // only grab altitude if needed + if (altitude != NULL) { + // grab altitude + char *altp = strtok(NULL, ","); + if (! altp) return false; + + *altitude = atof(altp); + } + + // only grab speed if needed + if (speed_kph != NULL) { + // grab the speed in km/h + char *speedp = strtok(NULL, ","); + if (! speedp) return false; + + *speed_kph = atof(speedp); + } + + // only grab heading if needed + if (heading != NULL) { + + // grab the speed in knots + char *coursep = strtok(NULL, ","); + if (! coursep) return false; + + *heading = atof(coursep); + } + } + else { + // Parse 808 V1 response. + + // skip mode + char *tok = strtok(gpsbuffer, ","); + if (! tok) return false; + + // skip date + tok = strtok(NULL, ","); + if (! tok) return false; + + // skip fix + tok = strtok(NULL, ","); + if (! tok) return false; + + // grab the latitude + char *latp = strtok(NULL, ","); + if (! latp) return false; + + // grab latitude direction + char *latdir = strtok(NULL, ","); + if (! latdir) return false; + + // grab longitude + char *longp = strtok(NULL, ","); + if (! longp) return false; + + // grab longitude direction + char *longdir = strtok(NULL, ","); + if (! longdir) return false; + + double latitude = atof(latp); + double longitude = atof(longp); + + // convert latitude from minutes to decimal + float degrees = floor(latitude / 100); + double minutes = latitude - (100 * degrees); + minutes /= 60; + degrees += minutes; + + // turn direction into + or - + if (latdir[0] == 'S') degrees *= -1; + + *lat = degrees; + + // convert longitude from minutes to decimal + degrees = floor(longitude / 100); + minutes = longitude - (100 * degrees); + minutes /= 60; + degrees += minutes; + + // turn direction into + or - + if (longdir[0] == 'W') degrees *= -1; + + *lon = degrees; + + // only grab speed if needed + if (speed_kph != NULL) { + + // grab the speed in knots + char *speedp = strtok(NULL, ","); + if (! speedp) return false; + + // convert to kph + *speed_kph = atof(speedp) * 1.852; + + } + + // only grab heading if needed + if (heading != NULL) { + + // grab the speed in knots + char *coursep = strtok(NULL, ","); + if (! coursep) return false; + + *heading = atof(coursep); + + } + + // no need to continue + if (altitude == NULL) + return true; + + // we need at least a 3D fix for altitude + if (GPSstatus() < 3) + return false; + + // grab the mode 0 gps csv from the sim808 + res_len = getGPS(0, gpsbuffer, 120); + + // make sure we have a response + if (res_len == 0) + return false; + + // skip mode + tok = strtok(gpsbuffer, ","); + if (! tok) return false; + + // skip lat + tok = strtok(NULL, ","); + if (! tok) return false; + + // skip long + tok = strtok(NULL, ","); + if (! tok) return false; + + // grab altitude + char *altp = strtok(NULL, ","); + if (! altp) return false; + + *altitude = atof(altp); + } + + return true; + +} + +boolean Adafruit_FONA::enableGPSNMEA(uint8_t i) { + + char sendbuff[15] = "AT+CGPSOUT=000"; + sendbuff[11] = (i / 100) + '0'; + i %= 100; + sendbuff[12] = (i / 10) + '0'; + i %= 10; + sendbuff[13] = i + '0'; + + if (_type == FONA808_V2) { + if (i) + return sendCheckReply(F("AT+CGNSTST=1"), ok_reply); + else + return sendCheckReply(F("AT+CGNSTST=0"), ok_reply); + } else { + return sendCheckReply(sendbuff, ok_reply, 2000); + } +} + + +/********* GPRS **********************************************************/ + + +boolean Adafruit_FONA::enableGPRS(boolean onoff) { + + if (onoff) { + // disconnect all sockets + sendCheckReply(F("AT+CIPSHUT"), F("SHUT OK"), 20000); + + if (! sendCheckReply(F("AT+CGATT=1"), ok_reply, 10000)) + return false; + + // set bearer profile! connection type GPRS + if (! sendCheckReply(F("AT+SAPBR=3,1,\"CONTYPE\",\"GPRS\""), + ok_reply, 10000)) + return false; + + // set bearer profile access point name + if (apn) { + // Send command AT+SAPBR=3,1,"APN","" where is the configured APN value. + if (! sendCheckReplyQuoted(F("AT+SAPBR=3,1,\"APN\","), apn, ok_reply, 10000)) + return false; + + // send AT+CSTT,"apn","user","pass" + flushInput(); + + mySerial->print(F("AT+CSTT=\"")); + mySerial->print(apn); + if (apnusername) { + mySerial->print("\",\""); + mySerial->print(apnusername); + } + if (apnpassword) { + mySerial->print("\",\""); + mySerial->print(apnpassword); + } + mySerial->println("\""); + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINT(F("AT+CSTT=\"")); + DEBUG_PRINT(apn); + + if (apnusername) { + DEBUG_PRINT("\",\""); + DEBUG_PRINT(apnusername); + } + if (apnpassword) { + DEBUG_PRINT("\",\""); + DEBUG_PRINT(apnpassword); + } + DEBUG_PRINTLN("\""); + + if (! expectReply(ok_reply)) return false; + + // set username/password + if (apnusername) { + // Send command AT+SAPBR=3,1,"USER","" where is the configured APN username. + if (! sendCheckReplyQuoted(F("AT+SAPBR=3,1,\"USER\","), apnusername, ok_reply, 10000)) + return false; + } + if (apnpassword) { + // Send command AT+SAPBR=3,1,"PWD","" where is the configured APN password. + if (! sendCheckReplyQuoted(F("AT+SAPBR=3,1,\"PWD\","), apnpassword, ok_reply, 10000)) + return false; + } + } + + // open GPRS context + if (! sendCheckReply(F("AT+SAPBR=1,1"), ok_reply, 30000)) + return false; + + // bring up wireless connection + if (! sendCheckReply(F("AT+CIICR"), ok_reply, 10000)) + return false; + + } else { + // disconnect all sockets + if (! sendCheckReply(F("AT+CIPSHUT"), F("SHUT OK"), 20000)) + return false; + + // close GPRS context + if (! sendCheckReply(F("AT+SAPBR=0,1"), ok_reply, 10000)) + return false; + + if (! sendCheckReply(F("AT+CGATT=0"), ok_reply, 10000)) + return false; + + } + return true; +} + +boolean Adafruit_FONA_3G::enableGPRS(boolean onoff) { + + if (onoff) { + // disconnect all sockets + //sendCheckReply(F("AT+CIPSHUT"), F("SHUT OK"), 5000); + + if (! sendCheckReply(F("AT+CGATT=1"), ok_reply, 10000)) + return false; + + + // set bearer profile access point name + if (apn) { + // Send command AT+CGSOCKCONT=1,"IP","" where is the configured APN name. + if (! sendCheckReplyQuoted(F("AT+CGSOCKCONT=1,\"IP\","), apn, ok_reply, 10000)) + return false; + + // set username/password + if (apnusername) { + char authstring[100] = "AT+CGAUTH=1,1,\""; + char *strp = authstring + strlen(authstring); + prog_char_strcpy(strp, (prog_char *)apnusername); + strp+=prog_char_strlen((prog_char *)apnusername); + strp[0] = '\"'; + strp++; + strp[0] = 0; + + if (apnpassword) { + strp[0] = ','; strp++; + strp[0] = '\"'; strp++; + prog_char_strcpy(strp, (prog_char *)apnpassword); + strp+=prog_char_strlen((prog_char *)apnpassword); + strp[0] = '\"'; + strp++; + strp[0] = 0; + } + + if (! sendCheckReply(authstring, ok_reply, 10000)) + return false; + } + } + + // connect in transparent + if (! sendCheckReply(F("AT+CIPMODE=1"), ok_reply, 10000)) + return false; + // open network (?) + if (! sendCheckReply(F("AT+NETOPEN=,,1"), F("Network opened"), 10000)) + return false; + + readline(); // eat 'OK' + } else { + // close GPRS context + if (! sendCheckReply(F("AT+NETCLOSE"), F("Network closed"), 10000)) + return false; + + readline(); // eat 'OK' + } + + return true; +} + +uint8_t Adafruit_FONA::GPRSstate(void) { + uint16_t state; + + if (! sendParseReply(F("AT+CGATT?"), F("+CGATT: "), &state) ) + return -1; + + return state; +} + +void Adafruit_FONA::setGPRSNetworkSettings(FONAFlashStringPtr apn, + FONAFlashStringPtr username, FONAFlashStringPtr password) { + this->apn = apn; + this->apnusername = username; + this->apnpassword = password; +} + +boolean Adafruit_FONA::getGSMLoc(uint16_t *errorcode, char *buff, uint16_t maxlen) { + + getReply(F("AT+CIPGSMLOC=1,1"), (uint16_t)10000); + + if (! parseReply(F("+CIPGSMLOC: "), errorcode)) + return false; + + char *p = replybuffer+14; + uint16_t lentocopy = min(maxlen-1, (int)strlen(p)); + strncpy(buff, p, lentocopy+1); + + readline(); // eat OK + + return true; +} + +boolean Adafruit_FONA::getGSMLoc(float *lat, float *lon) { + + uint16_t returncode; + char gpsbuffer[120]; + + // make sure we could get a response + if (! getGSMLoc(&returncode, gpsbuffer, 120)) + return false; + + // make sure we have a valid return code + if (returncode != 0) + return false; + + // +CIPGSMLOC: 0,-74.007729,40.730160,2015/10/15,19:24:55 + // tokenize the gps buffer to locate the lat & long + char *longp = strtok(gpsbuffer, ","); + if (! longp) return false; + + char *latp = strtok(NULL, ","); + if (! latp) return false; + + *lat = atof(latp); + *lon = atof(longp); + + return true; + +} +/********* TCP FUNCTIONS ************************************/ + + +boolean Adafruit_FONA::TCPconnect(char *server, uint16_t port) { + flushInput(); + + // close all old connections + if (! sendCheckReply(F("AT+CIPSHUT"), F("SHUT OK"), 20000) ) return false; + + // single connection at a time + if (! sendCheckReply(F("AT+CIPMUX=0"), ok_reply) ) return false; + + // manually read data + if (! sendCheckReply(F("AT+CIPRXGET=1"), ok_reply) ) return false; + + + DEBUG_PRINT(F("AT+CIPSTART=\"TCP\",\"")); + DEBUG_PRINT(server); + DEBUG_PRINT(F("\",\"")); + DEBUG_PRINT(port); + DEBUG_PRINTLN(F("\"")); + + + mySerial->print(F("AT+CIPSTART=\"TCP\",\"")); + mySerial->print(server); + mySerial->print(F("\",\"")); + mySerial->print(port); + mySerial->println(F("\"")); + + if (! expectReply(ok_reply)) return false; + if (! expectReply(F("CONNECT OK"))) return false; + + // looks like it was a success (?) + return true; +} + +boolean Adafruit_FONA::TCPclose(void) { + return sendCheckReply(F("AT+CIPCLOSE"), ok_reply); +} + +boolean Adafruit_FONA::TCPconnected(void) { + if (! sendCheckReply(F("AT+CIPSTATUS"), ok_reply, 100) ) return false; + readline(100); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return (strcmp(replybuffer, "STATE: CONNECT OK") == 0); +} + +boolean Adafruit_FONA::TCPsend(char *packet, uint8_t len) { + + DEBUG_PRINT(F("AT+CIPSEND=")); + DEBUG_PRINTLN(len); +#ifdef ADAFRUIT_FONA_DEBUG + for (uint16_t i=0; iprint(F("AT+CIPSEND=")); + mySerial->println(len); + readline(); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + if (replybuffer[0] != '>') return false; + + mySerial->write(packet, len); + readline(3000); // wait up to 3 seconds to send the data + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + + return (strcmp(replybuffer, "SEND OK") == 0); +} + +uint16_t Adafruit_FONA::TCPavailable(void) { + uint16_t avail; + + if (! sendParseReply(F("AT+CIPRXGET=4"), F("+CIPRXGET: 4,"), &avail, ',', 0) ) return false; + + + DEBUG_PRINT (avail); DEBUG_PRINTLN(F(" bytes available")); + + + return avail; +} + + +uint16_t Adafruit_FONA::TCPread(uint8_t *buff, uint8_t len) { + uint16_t avail; + + mySerial->print(F("AT+CIPRXGET=2,")); + mySerial->println(len); + readline(); + if (! parseReply(F("+CIPRXGET: 2,"), &avail, ',', 0)) return false; + + readRaw(avail); + +#ifdef ADAFRUIT_FONA_DEBUG + DEBUG_PRINT (avail); DEBUG_PRINTLN(F(" bytes read")); + for (uint8_t i=0;i ")); + DEBUG_PRINT(F("AT+HTTPPARA=\"")); + DEBUG_PRINT(parameter); + DEBUG_PRINTLN('"'); + + + mySerial->print(F("AT+HTTPPARA=\"")); + mySerial->print(parameter); + if (quoted) + mySerial->print(F("\",\"")); + else + mySerial->print(F("\",")); +} + +boolean Adafruit_FONA::HTTP_para_end(boolean quoted) { + if (quoted) + mySerial->println('"'); + else + mySerial->println(); + + return expectReply(ok_reply); +} + +boolean Adafruit_FONA::HTTP_para(FONAFlashStringPtr parameter, + const char *value) { + HTTP_para_start(parameter, true); + mySerial->print(value); + return HTTP_para_end(true); +} + +boolean Adafruit_FONA::HTTP_para(FONAFlashStringPtr parameter, + FONAFlashStringPtr value) { + HTTP_para_start(parameter, true); + mySerial->print(value); + return HTTP_para_end(true); +} + +boolean Adafruit_FONA::HTTP_para(FONAFlashStringPtr parameter, + int32_t value) { + HTTP_para_start(parameter, false); + mySerial->print(value); + return HTTP_para_end(false); +} + +boolean Adafruit_FONA::HTTP_data(uint32_t size, uint32_t maxTime) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); + DEBUG_PRINT(F("AT+HTTPDATA=")); + DEBUG_PRINT(size); + DEBUG_PRINT(','); + DEBUG_PRINTLN(maxTime); + + + mySerial->print(F("AT+HTTPDATA=")); + mySerial->print(size); + mySerial->print(","); + mySerial->println(maxTime); + + return expectReply(F("DOWNLOAD")); +} + +boolean Adafruit_FONA::HTTP_action(uint8_t method, uint16_t *status, + uint16_t *datalen, int32_t timeout) { + // Send request. + if (! sendCheckReply(F("AT+HTTPACTION="), method, ok_reply)) + return false; + + // Parse response status and size. + readline(timeout); + if (! parseReply(F("+HTTPACTION:"), status, ',', 1)) + return false; + if (! parseReply(F("+HTTPACTION:"), datalen, ',', 2)) + return false; + + return true; +} + +boolean Adafruit_FONA::HTTP_readall(uint16_t *datalen) { + getReply(F("AT+HTTPREAD")); + if (! parseReply(F("+HTTPREAD:"), datalen, ',', 0)) + return false; + + return true; +} + +boolean Adafruit_FONA::HTTP_ssl(boolean onoff) { + return sendCheckReply(F("AT+HTTPSSL="), onoff ? 1 : 0, ok_reply); +} + +/********* HTTP HIGH LEVEL FUNCTIONS ***************************/ + +boolean Adafruit_FONA::HTTP_GET_start(char *url, + uint16_t *status, uint16_t *datalen){ + if (! HTTP_setup(url)) + return false; + + // HTTP GET + if (! HTTP_action(FONA_HTTP_GET, status, datalen, 30000)) + return false; + + DEBUG_PRINT(F("Status: ")); DEBUG_PRINTLN(*status); + DEBUG_PRINT(F("Len: ")); DEBUG_PRINTLN(*datalen); + + // HTTP response data + if (! HTTP_readall(datalen)) + return false; + + return true; +} + +/* +boolean Adafruit_FONA_3G::HTTP_GET_start(char *ipaddr, char *path, uint16_t port + uint16_t *status, uint16_t *datalen){ + char send[100] = "AT+CHTTPACT=\""; + char *sendp = send + strlen(send); + memset(sendp, 0, 100 - strlen(send)); + + strcpy(sendp, ipaddr); + sendp+=strlen(ipaddr); + sendp[0] = '\"'; + sendp++; + sendp[0] = ','; + itoa(sendp, port); + getReply(send, 500); + + return; + + if (! HTTP_setup(url)) + + return false; + + // HTTP GET + if (! HTTP_action(FONA_HTTP_GET, status, datalen)) + return false; + + DEBUG_PRINT("Status: "); DEBUG_PRINTLN(*status); + DEBUG_PRINT("Len: "); DEBUG_PRINTLN(*datalen); + + // HTTP response data + if (! HTTP_readall(datalen)) + return false; + + return true; +} +*/ + +void Adafruit_FONA::HTTP_GET_end(void) { + HTTP_term(); +} + +boolean Adafruit_FONA::HTTP_POST_start(char *url, + FONAFlashStringPtr contenttype, + const uint8_t *postdata, uint16_t postdatalen, + uint16_t *status, uint16_t *datalen){ + if (! HTTP_setup(url)) + return false; + + if (! HTTP_para(F("CONTENT"), contenttype)) { + return false; + } + + // HTTP POST data + if (! HTTP_data(postdatalen, 10000)) + return false; + mySerial->write(postdata, postdatalen); + if (! expectReply(ok_reply)) + return false; + + // HTTP POST + if (! HTTP_action(FONA_HTTP_POST, status, datalen)) + return false; + + DEBUG_PRINT(F("Status: ")); DEBUG_PRINTLN(*status); + DEBUG_PRINT(F("Len: ")); DEBUG_PRINTLN(*datalen); + + // HTTP response data + if (! HTTP_readall(datalen)) + return false; + + return true; +} + +void Adafruit_FONA::HTTP_POST_end(void) { + HTTP_term(); +} + +void Adafruit_FONA::setUserAgent(FONAFlashStringPtr useragent) { + this->useragent = useragent; +} + +void Adafruit_FONA::setHTTPSRedirect(boolean onoff) { + httpsredirect = onoff; +} + +/********* HTTP HELPERS ****************************************/ + +boolean Adafruit_FONA::HTTP_setup(char *url) { + // Handle any pending + HTTP_term(); + + // Initialize and set parameters + if (! HTTP_init()) + return false; + if (! HTTP_para(F("CID"), 1)) + return false; + if (! HTTP_para(F("UA"), useragent)) + return false; + if (! HTTP_para(F("URL"), url)) + return false; + + // HTTPS redirect + if (httpsredirect) { + if (! HTTP_para(F("REDIR"),1)) + return false; + + if (! HTTP_ssl(true)) + return false; + } + + return true; +} + +/********* HELPERS *********************************************/ + +boolean Adafruit_FONA::expectReply(FONAFlashStringPtr reply, + uint16_t timeout) { + readline(timeout); + + DEBUG_PRINT(F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return (prog_char_strcmp(replybuffer, (prog_char*)reply) == 0); +} + +/********* LOW LEVEL *******************************************/ + +inline int Adafruit_FONA::available(void) { + return mySerial->available(); +} + +inline size_t Adafruit_FONA::write(uint8_t x) { + return mySerial->write(x); +} + +inline int Adafruit_FONA::read(void) { + return mySerial->read(); +} + +inline int Adafruit_FONA::peek(void) { + return mySerial->peek(); +} + +inline void Adafruit_FONA::flush() { + mySerial->flush(); +} + +void Adafruit_FONA::flushInput() { + // Read all available serial input to flush pending data. + uint16_t timeoutloop = 0; + while (timeoutloop++ < 40) { + while(available()) { + read(); + timeoutloop = 0; // If char was received reset the timer + } + delay(1); + } +} + +uint16_t Adafruit_FONA::readRaw(uint16_t b) { + uint16_t idx = 0; + + while (b && (idx < sizeof(replybuffer)-1)) { + if (mySerial->available()) { + replybuffer[idx] = mySerial->read(); + idx++; + b--; + } + } + replybuffer[idx] = 0; + + return idx; +} + +uint8_t Adafruit_FONA::readline(uint16_t timeout, boolean multiline) { + uint16_t replyidx = 0; + + while (timeout--) { + if (replyidx >= 254) { + //DEBUG_PRINTLN(F("SPACE")); + break; + } + + while(mySerial->available()) { + char c = mySerial->read(); + if (c == '\r') continue; + if (c == 0xA) { + if (replyidx == 0) // the first 0x0A is ignored + continue; + + if (!multiline) { + timeout = 0; // the second 0x0A is the end of the line + break; + } + } + replybuffer[replyidx] = c; + //DEBUG_PRINT(c, HEX); DEBUG_PRINT("#"); DEBUG_PRINTLN(c); + replyidx++; + } + + if (timeout == 0) { + //DEBUG_PRINTLN(F("TIMEOUT")); + break; + } + delay(1); + } + replybuffer[replyidx] = 0; // null term + return replyidx; +} + +uint8_t Adafruit_FONA::getReply(char *send, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINTLN(send); + + + mySerial->println(send); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +uint8_t Adafruit_FONA::getReply(FONAFlashStringPtr send, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINTLN(send); + + + mySerial->println(send); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +// Send prefix, suffix, and newline. Return response (and also set replybuffer with response). +uint8_t Adafruit_FONA::getReply(FONAFlashStringPtr prefix, char *suffix, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINT(prefix); DEBUG_PRINTLN(suffix); + + + mySerial->print(prefix); + mySerial->println(suffix); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +// Send prefix, suffix, and newline. Return response (and also set replybuffer with response). +uint8_t Adafruit_FONA::getReply(FONAFlashStringPtr prefix, int32_t suffix, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINT(prefix); DEBUG_PRINTLN(suffix, DEC); + + + mySerial->print(prefix); + mySerial->println(suffix, DEC); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +// Send prefix, suffix, suffix2, and newline. Return response (and also set replybuffer with response). +uint8_t Adafruit_FONA::getReply(FONAFlashStringPtr prefix, int32_t suffix1, int32_t suffix2, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINT(prefix); + DEBUG_PRINT(suffix1, DEC); DEBUG_PRINT(','); DEBUG_PRINTLN(suffix2, DEC); + + + mySerial->print(prefix); + mySerial->print(suffix1); + mySerial->print(','); + mySerial->println(suffix2, DEC); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +// Send prefix, ", suffix, ", and newline. Return response (and also set replybuffer with response). +uint8_t Adafruit_FONA::getReplyQuoted(FONAFlashStringPtr prefix, FONAFlashStringPtr suffix, uint16_t timeout) { + flushInput(); + + + DEBUG_PRINT(F("\t---> ")); DEBUG_PRINT(prefix); + DEBUG_PRINT('"'); DEBUG_PRINT(suffix); DEBUG_PRINTLN('"'); + + + mySerial->print(prefix); + mySerial->print('"'); + mySerial->print(suffix); + mySerial->println('"'); + + uint8_t l = readline(timeout); + + DEBUG_PRINT (F("\t<--- ")); DEBUG_PRINTLN(replybuffer); + + return l; +} + +boolean Adafruit_FONA::sendCheckReply(char *send, char *reply, uint16_t timeout) { + if (! getReply(send, timeout) ) + return false; +/* + for (uint8_t i=0; i http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + + These displays use TTL Serial 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 + ****************************************************/ +#ifndef ADAFRUIT_FONA_H +#define ADAFRUIT_FONA_H + +#include "includes/FONAConfig.h" +#include "includes/FONAExtIncludes.h" +#include "includes/platform/FONAPlatform.h" + + + +#define FONA800L 1 +#define FONA800H 6 + +#define FONA808_V1 2 +#define FONA808_V2 3 + +#define FONA3G_A 4 +#define FONA3G_E 5 + +// Set the preferred SMS storage. +// Use "SM" for storage on the SIM. +// Use "ME" for internal storage on the FONA chip +#define FONA_PREF_SMS_STORAGE "\"SM\"" +//#define FONA_PREF_SMS_STORAGE "\"ME\"" + +#define FONA_HEADSETAUDIO 0 +#define FONA_EXTAUDIO 1 + +#define FONA_STTONE_DIALTONE 1 +#define FONA_STTONE_BUSY 2 +#define FONA_STTONE_CONGESTION 3 +#define FONA_STTONE_PATHACK 4 +#define FONA_STTONE_DROPPED 5 +#define FONA_STTONE_ERROR 6 +#define FONA_STTONE_CALLWAIT 7 +#define FONA_STTONE_RINGING 8 +#define FONA_STTONE_BEEP 16 +#define FONA_STTONE_POSTONE 17 +#define FONA_STTONE_ERRTONE 18 +#define FONA_STTONE_INDIANDIALTONE 19 +#define FONA_STTONE_USADIALTONE 20 + +#define FONA_DEFAULT_TIMEOUT_MS 500 + +#define FONA_HTTP_GET 0 +#define FONA_HTTP_POST 1 +#define FONA_HTTP_HEAD 2 + +#define FONA_CALL_READY 0 +#define FONA_CALL_FAILED 1 +#define FONA_CALL_UNKNOWN 2 +#define FONA_CALL_RINGING 3 +#define FONA_CALL_INPROGRESS 4 + +class Adafruit_FONA : public FONAStreamType { + public: + Adafruit_FONA(int8_t r); + boolean begin(FONAStreamType &port); + uint8_t type(); + + // Stream + int available(void); + size_t write(uint8_t x); + int read(void); + int peek(void); + void flush(); + + // FONA 3G requirements + boolean setBaudrate(uint16_t baud); + + // RTC + boolean enableRTC(uint8_t i); + boolean readRTC(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *hr, uint8_t *min, uint8_t *sec); + + // Battery and ADC + boolean getADCVoltage(uint16_t *v); + boolean getBattPercent(uint16_t *p); + boolean getBattVoltage(uint16_t *v); + + // SIM query + uint8_t unlockSIM(char *pin); + uint8_t getSIMCCID(char *ccid); + uint8_t getNetworkStatus(void); + uint8_t getRSSI(void); + + // IMEI + uint8_t getIMEI(char *imei); + + // set Audio output + boolean setAudio(uint8_t a); + boolean setVolume(uint8_t i); + uint8_t getVolume(void); + boolean playToolkitTone(uint8_t t, uint16_t len); + boolean setMicVolume(uint8_t a, uint8_t level); + boolean playDTMF(char tone); + + // FM radio functions. + boolean tuneFMradio(uint16_t station); + boolean FMradio(boolean onoff, uint8_t a = FONA_HEADSETAUDIO); + boolean setFMVolume(uint8_t i); + int8_t getFMVolume(); + int8_t getFMSignalLevel(uint16_t station); + + // SMS handling + boolean setSMSInterrupt(uint8_t i); + uint8_t getSMSInterrupt(void); + int8_t getNumSMS(void); + boolean readSMS(uint8_t i, char *smsbuff, uint16_t max, uint16_t *readsize); + boolean sendSMS(char *smsaddr, char *smsmsg); + boolean deleteSMS(uint8_t i); + boolean getSMSSender(uint8_t i, char *sender, int senderlen); + boolean sendUSSD(char *ussdmsg, char *ussdbuff, uint16_t maxlen, uint16_t *readlen); + + // Time + boolean enableNetworkTimeSync(boolean onoff); + boolean enableNTPTimeSync(boolean onoff, FONAFlashStringPtr ntpserver=0); + boolean getTime(char *buff, uint16_t maxlen); + + // GPRS handling + boolean enableGPRS(boolean onoff); + uint8_t GPRSstate(void); + boolean getGSMLoc(uint16_t *replycode, char *buff, uint16_t maxlen); + boolean getGSMLoc(float *lat, float *lon); + void setGPRSNetworkSettings(FONAFlashStringPtr apn, FONAFlashStringPtr username=0, FONAFlashStringPtr password=0); + + // GPS handling + boolean enableGPS(boolean onoff); + int8_t GPSstatus(void); + uint8_t getGPS(uint8_t arg, char *buffer, uint8_t maxbuff); + boolean getGPS(float *lat, float *lon, float *speed_kph=0, float *heading=0, float *altitude=0); + boolean enableGPSNMEA(uint8_t nmea); + + // TCP raw connections + boolean TCPconnect(char *server, uint16_t port); + boolean TCPclose(void); + boolean TCPconnected(void); + boolean TCPsend(char *packet, uint8_t len); + uint16_t TCPavailable(void); + uint16_t TCPread(uint8_t *buff, uint8_t len); + + // HTTP low level interface (maps directly to SIM800 commands). + boolean HTTP_init(); + boolean HTTP_term(); + void HTTP_para_start(FONAFlashStringPtr parameter, boolean quoted = true); + boolean HTTP_para_end(boolean quoted = true); + boolean HTTP_para(FONAFlashStringPtr parameter, const char *value); + boolean HTTP_para(FONAFlashStringPtr parameter, FONAFlashStringPtr value); + boolean HTTP_para(FONAFlashStringPtr parameter, int32_t value); + boolean HTTP_data(uint32_t size, uint32_t maxTime=10000); + boolean HTTP_action(uint8_t method, uint16_t *status, uint16_t *datalen, int32_t timeout = 10000); + boolean HTTP_readall(uint16_t *datalen); + boolean HTTP_ssl(boolean onoff); + + // HTTP high level interface (easier to use, less flexible). + boolean HTTP_GET_start(char *url, uint16_t *status, uint16_t *datalen); + void HTTP_GET_end(void); + boolean HTTP_POST_start(char *url, FONAFlashStringPtr contenttype, const uint8_t *postdata, uint16_t postdatalen, uint16_t *status, uint16_t *datalen); + void HTTP_POST_end(void); + void setUserAgent(FONAFlashStringPtr useragent); + + // HTTPS + void setHTTPSRedirect(boolean onoff); + + // PWM (buzzer) + boolean setPWM(uint16_t period, uint8_t duty = 50); + + // Phone calls + boolean callPhone(char *phonenum); + uint8_t getCallStatus(void); + boolean hangUp(void); + boolean pickUp(void); + boolean callerIdNotification(boolean enable, uint8_t interrupt = 0); + boolean incomingCallNumber(char* phonenum); + + // Helper functions to verify responses. + boolean expectReply(FONAFlashStringPtr reply, uint16_t timeout = 10000); + boolean sendCheckReply(char *send, char *reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + boolean sendCheckReply(FONAFlashStringPtr send, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + boolean sendCheckReply(char* send, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + + + protected: + int8_t _rstpin; + uint8_t _type; + + char replybuffer[255]; + FONAFlashStringPtr apn; + FONAFlashStringPtr apnusername; + FONAFlashStringPtr apnpassword; + boolean httpsredirect; + FONAFlashStringPtr useragent; + FONAFlashStringPtr ok_reply; + + // HTTP helpers + boolean HTTP_setup(char *url); + + void flushInput(); + uint16_t readRaw(uint16_t b); + uint8_t readline(uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS, boolean multiline = false); + uint8_t getReply(char *send, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + uint8_t getReply(FONAFlashStringPtr send, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + uint8_t getReply(FONAFlashStringPtr prefix, char *suffix, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + uint8_t getReply(FONAFlashStringPtr prefix, int32_t suffix, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + uint8_t getReply(FONAFlashStringPtr prefix, int32_t suffix1, int32_t suffix2, uint16_t timeout); // Don't set default value or else function call is ambiguous. + uint8_t getReplyQuoted(FONAFlashStringPtr prefix, FONAFlashStringPtr suffix, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + + boolean sendCheckReply(FONAFlashStringPtr prefix, char *suffix, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + boolean sendCheckReply(FONAFlashStringPtr prefix, int32_t suffix, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + boolean sendCheckReply(FONAFlashStringPtr prefix, int32_t suffix, int32_t suffix2, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + boolean sendCheckReplyQuoted(FONAFlashStringPtr prefix, FONAFlashStringPtr suffix, FONAFlashStringPtr reply, uint16_t timeout = FONA_DEFAULT_TIMEOUT_MS); + + + boolean parseReply(FONAFlashStringPtr toreply, + uint16_t *v, char divider = ',', uint8_t index=0); + boolean parseReply(FONAFlashStringPtr toreply, + char *v, char divider = ',', uint8_t index=0); + boolean parseReplyQuoted(FONAFlashStringPtr toreply, + char *v, int maxlen, char divider, uint8_t index); + + boolean sendParseReply(FONAFlashStringPtr tosend, + FONAFlashStringPtr toreply, + uint16_t *v, char divider = ',', uint8_t index=0); + + static boolean _incomingCall; + static void onIncomingCall(); + + FONAStreamType *mySerial; +}; + +class Adafruit_FONA_3G : public Adafruit_FONA { + + public: + Adafruit_FONA_3G (int8_t r) : Adafruit_FONA(r) { _type = FONA3G_A; } + + boolean getBattVoltage(uint16_t *v); + boolean playToolkitTone(uint8_t t, uint16_t len); + boolean hangUp(void); + boolean pickUp(void); + boolean enableGPRS(boolean onoff); + boolean enableGPS(boolean onoff); + + protected: + boolean parseReply(FONAFlashStringPtr toreply, + float *f, char divider, uint8_t index); + + boolean sendParseReply(FONAFlashStringPtr tosend, + FONAFlashStringPtr toreply, + float *f, char divider = ',', uint8_t index=0); +}; + +#endif diff --git a/libraries/Adafruit_FONA_Library/README.md b/libraries/Adafruit_FONA_Library/README.md new file mode 100644 index 0000000..73452ca --- /dev/null +++ b/libraries/Adafruit_FONA_Library/README.md @@ -0,0 +1,29 @@ +# Adafruit FONA Library [![Build Status](https://secure.travis-ci.org/adafruit/Adafruit_FONA_Library.svg?branch=master)](https://travis-ci.org/adafruit/Adafruit_FONA_Library) + +**This library requires Arduino v1.0.6 or higher** + +This is a library for the Adafruit FONA Cellular GSM Breakouts etc + +Designed specifically to work with the Adafruit FONA Breakout + * https://www.adafruit.com/products/1946 + * https://www.adafruit.com/products/1963 + * http://www.adafruit.com/products/2468 + * http://www.adafruit.com/products/2542 + +These modules use TTL Serial 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! + +Check out the links above for our tutorials and wiring diagrams + +Written by Limor Fried/Ladyada for Adafruit Industries. +BSD license, all text above must be included in any redistribution +With updates from Samy Kamkar + +To download. click the DOWNLOADS button in the top right corner, rename the uncompressed folder Adafruit_FONA +Check that the Adafruit_FONA folder contains Adafruit_FONA.cpp and Adafruit_FONA.h + +Place the Adafruit_FONA library folder your *arduinosketchfolder*/libraries/ folder. +You may need to create the libraries subfolder if its your first library. Restart the IDE. diff --git a/libraries/Adafruit_FONA_Library/examples/FONA3G_setbaud/FONA3G_setbaud.ino b/libraries/Adafruit_FONA_Library/examples/FONA3G_setbaud/FONA3G_setbaud.ino new file mode 100644 index 0000000..b0be4c5 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/FONA3G_setbaud/FONA3G_setbaud.ino @@ -0,0 +1,77 @@ +/*************************************************** + This is an example for our Adafruit FONA Cellular Module + since the FONA 3G does not do auto-baud very well, this demo + fixes the baud rate to 4800 from the default 115200 + + Designed specifically to work with the Adafruit FONA 3G + ----> http://www.adafruit.com/products/2691 + ----> http://www.adafruit.com/products/2687 + + These cellular modules use TTL Serial 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_FONA.h" + +#define FONA_RX 2 +#define FONA_TX 3 +#define FONA_RST 4 + +// this is a large buffer for replies +char replybuffer[255]; + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0); + +void setup() { + while (!Serial); + + Serial.begin(115200); + Serial.println(F("FONA set baudrate")); + + Serial.println(F("First trying 115200 baud")); + // start at 115200 baud + fonaSerial->begin(115200); + fona.begin(*fonaSerial); + + // send the command to reset the baud rate to 4800 + fona.setBaudrate(4800); + + // restart with 4800 baud + fonaSerial->begin(4800); + Serial.println(F("Initializing @ 4800 baud...")); + + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while(1); + } + Serial.println(F("FONA is OK")); + + // Print module IMEI number. + char imei[15] = {0}; // MUST use a 16 character buffer for IMEI! + uint8_t imeiLen = fona.getIMEI(imei); + if (imeiLen > 0) { + Serial.print("Module IMEI: "); Serial.println(imei); + } + +} + +void loop() { +} \ No newline at end of file diff --git a/libraries/Adafruit_FONA_Library/examples/FONA_SMS_Response/FONA_SMS_Response.ino b/libraries/Adafruit_FONA_Library/examples/FONA_SMS_Response/FONA_SMS_Response.ino new file mode 100644 index 0000000..411651f --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/FONA_SMS_Response/FONA_SMS_Response.ino @@ -0,0 +1,143 @@ +/*************************************************** + This is an example for our Adafruit FONA Cellular Module + + Designed specifically to work with the Adafruit FONA + ----> http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + ----> http://www.adafruit.com/products/2468 + ----> http://www.adafruit.com/products/2542 + + These cellular modules use TTL Serial 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 + ****************************************************/ + +/* +THIS CODE IS STILL IN PROGRESS! + +Open up the serial console on the Arduino at 115200 baud to interact with FONA + + +This code will receive an SMS, identify the sender's phone number, and automatically send a response + +*/ + +#include "Adafruit_FONA.h" + +#define FONA_RX 2 +#define FONA_TX 3 +#define FONA_RST 4 + +// this is a large buffer for replies +char replybuffer[255]; + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +// Use this for FONA 800 and 808s +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); +// Use this one for FONA 3G +//Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST); + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0); + +void setup() { + while (!Serial); + + Serial.begin(115200); + Serial.println(F("FONA SMS caller ID test")); + Serial.println(F("Initializing....(May take 3 seconds)")); + + // make it slow so its easy to read! + fonaSerial->begin(4800); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while(1); + } + Serial.println(F("FONA is OK")); + + // Print SIM card IMEI number. + char imei[16] = {0}; // MUST use a 16 character buffer for IMEI! + uint8_t imeiLen = fona.getIMEI(imei); + if (imeiLen > 0) { + Serial.print("SIM card IMEI: "); Serial.println(imei); + } + + fonaSerial->print("AT+CNMI=2,1\r\n"); //set up the FONA to send a +CMTI notification when an SMS is received + + Serial.println("FONA Ready"); +} + + +char fonaNotificationBuffer[64]; //for notifications from the FONA +char smsBuffer[250]; + +void loop() { + + char* bufPtr = fonaNotificationBuffer; //handy buffer pointer + + if (fona.available()) //any data available from the FONA? + { + int slot = 0; //this will be the slot number of the SMS + int charCount = 0; + //Read the notification into fonaInBuffer + do { + *bufPtr = fona.read(); + Serial.write(*bufPtr); + delay(1); + } while ((*bufPtr++ != '\n') && (fona.available()) && (++charCount < (sizeof(fonaNotificationBuffer)-1))); + + //Add a terminal NULL to the notification string + *bufPtr = 0; + + //Scan the notification string for an SMS received notification. + // If it's an SMS message, we'll get the slot number in 'slot' + if (1 == sscanf(fonaNotificationBuffer, "+CMTI: " FONA_PREF_SMS_STORAGE ",%d", &slot)) { + Serial.print("slot: "); Serial.println(slot); + + char callerIDbuffer[32]; //we'll store the SMS sender number in here + + // Retrieve SMS sender address/phone number. + if (! fona.getSMSSender(slot, callerIDbuffer, 31)) { + Serial.println("Didn't find SMS message in slot!"); + } + Serial.print(F("FROM: ")); Serial.println(callerIDbuffer); + + // Retrieve SMS value. + uint16_t smslen; + if (fona.readSMS(slot, smsBuffer, 250, &smslen)) { // pass in buffer and max len! + Serial.println(smsBuffer); + } + + //Send back an automatic response + Serial.println("Sending reponse..."); + if (!fona.sendSMS(callerIDbuffer, "Hey, I got your text!")) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + } + + // delete the original msg after it is processed + // otherwise, we will fill up all the slots + // and then we won't be able to receive SMS anymore + if (fona.deleteSMS(slot)) { + Serial.println(F("OK!")); + } else { + Serial.print(F("Couldn't delete SMS in slot ")); Serial.println(slot); + fona.print(F("AT+CMGD=?\r\n")); + } + } + } +} diff --git a/libraries/Adafruit_FONA_Library/examples/FONAtest/FONAtest.ino b/libraries/Adafruit_FONA_Library/examples/FONAtest/FONAtest.ino new file mode 100644 index 0000000..3f669f9 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/FONAtest/FONAtest.ino @@ -0,0 +1,886 @@ +/*************************************************** + This is an example for our Adafruit FONA Cellular Module + + Designed specifically to work with the Adafruit FONA + ----> http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + ----> http://www.adafruit.com/products/2468 + ----> http://www.adafruit.com/products/2542 + + These cellular modules use TTL Serial 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 + ****************************************************/ + +/* +THIS CODE IS STILL IN PROGRESS! + +Open up the serial console on the Arduino at 115200 baud to interact with FONA + +Note that if you need to set a GPRS APN, username, and password scroll down to +the commented section below at the end of the setup() function. +*/ +#include "Adafruit_FONA.h" + +#define FONA_RX 2 +#define FONA_TX 3 +#define FONA_RST 4 + +// this is a large buffer for replies +char replybuffer[255]; + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +// Use this for FONA 800 and 808s +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); +// Use this one for FONA 3G +//Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST); + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0); + +uint8_t type; + +void setup() { + while (!Serial); + + Serial.begin(115200); + Serial.println(F("FONA basic test")); + Serial.println(F("Initializing....(May take 3 seconds)")); + + fonaSerial->begin(4800); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while (1); + } + type = fona.type(); + Serial.println(F("FONA is OK")); + Serial.print(F("Found ")); + switch (type) { + case FONA800L: + Serial.println(F("FONA 800L")); break; + case FONA800H: + Serial.println(F("FONA 800H")); break; + case FONA808_V1: + Serial.println(F("FONA 808 (v1)")); break; + case FONA808_V2: + Serial.println(F("FONA 808 (v2)")); break; + case FONA3G_A: + Serial.println(F("FONA 3G (American)")); break; + case FONA3G_E: + Serial.println(F("FONA 3G (European)")); break; + default: + Serial.println(F("???")); break; + } + + // Print module IMEI number. + char imei[16] = {0}; // MUST use a 16 character buffer for IMEI! + uint8_t imeiLen = fona.getIMEI(imei); + if (imeiLen > 0) { + Serial.print("Module IMEI: "); Serial.println(imei); + } + + // Optionally configure a GPRS APN, username, and password. + // You might need to do this to access your network's GPRS/data + // network. Contact your provider for the exact APN, username, + // and password values. Username and password are optional and + // can be removed, but APN is required. + //fona.setGPRSNetworkSettings(F("your APN"), F("your username"), F("your password")); + + // Optionally configure HTTP gets to follow redirects over SSL. + // Default is not to follow SSL redirects, however if you uncomment + // the following line then redirects over SSL will be followed. + //fona.setHTTPSRedirect(true); + + printMenu(); +} + +void printMenu(void) { + Serial.println(F("-------------------------------------")); + Serial.println(F("[?] Print this menu")); + Serial.println(F("[a] read the ADC 2.8V max (FONA800 & 808)")); + Serial.println(F("[b] read the Battery V and % charged")); + Serial.println(F("[C] read the SIM CCID")); + Serial.println(F("[U] Unlock SIM with PIN code")); + Serial.println(F("[i] read RSSI")); + Serial.println(F("[n] get Network status")); + Serial.println(F("[v] set audio Volume")); + Serial.println(F("[V] get Volume")); + Serial.println(F("[H] set Headphone audio (FONA800 & 808)")); + Serial.println(F("[e] set External audio (FONA800 & 808)")); + Serial.println(F("[T] play audio Tone")); + Serial.println(F("[P] PWM/Buzzer out (FONA800 & 808)")); + + // FM (SIM800 only!) + Serial.println(F("[f] tune FM radio (FONA800)")); + Serial.println(F("[F] turn off FM (FONA800)")); + Serial.println(F("[m] set FM volume (FONA800)")); + Serial.println(F("[M] get FM volume (FONA800)")); + Serial.println(F("[q] get FM station signal level (FONA800)")); + + // Phone + Serial.println(F("[c] make phone Call")); + Serial.println(F("[A] get call status")); + Serial.println(F("[h] Hang up phone")); + Serial.println(F("[p] Pick up phone")); + + // SMS + Serial.println(F("[N] Number of SMSs")); + Serial.println(F("[r] Read SMS #")); + Serial.println(F("[R] Read All SMS")); + Serial.println(F("[d] Delete SMS #")); + Serial.println(F("[s] Send SMS")); + Serial.println(F("[u] Send USSD")); + + // Time + Serial.println(F("[y] Enable network time sync (FONA 800 & 808)")); + Serial.println(F("[Y] Enable NTP time sync (GPRS FONA 800 & 808)")); + Serial.println(F("[t] Get network time")); + + // GPRS + Serial.println(F("[G] Enable GPRS")); + Serial.println(F("[g] Disable GPRS")); + Serial.println(F("[l] Query GSMLOC (GPRS)")); + Serial.println(F("[w] Read webpage (GPRS)")); + Serial.println(F("[W] Post to website (GPRS)")); + + // GPS + if ((type == FONA3G_A) || (type == FONA3G_E) || (type == FONA808_V1) || (type == FONA808_V2)) { + Serial.println(F("[O] Turn GPS on (FONA 808 & 3G)")); + Serial.println(F("[o] Turn GPS off (FONA 808 & 3G)")); + Serial.println(F("[L] Query GPS location (FONA 808 & 3G)")); + if (type == FONA808_V1) { + Serial.println(F("[x] GPS fix status (FONA808 v1 only)")); + } + Serial.println(F("[E] Raw NMEA out (FONA808)")); + } + + Serial.println(F("[S] create Serial passthru tunnel")); + Serial.println(F("-------------------------------------")); + Serial.println(F("")); + +} +void loop() { + Serial.print(F("FONA> ")); + while (! Serial.available() ) { + if (fona.available()) { + Serial.write(fona.read()); + } + } + + char command = Serial.read(); + Serial.println(command); + + + switch (command) { + case '?': { + printMenu(); + break; + } + + case 'a': { + // read the ADC + uint16_t adc; + if (! fona.getADCVoltage(&adc)) { + Serial.println(F("Failed to read ADC")); + } else { + Serial.print(F("ADC = ")); Serial.print(adc); Serial.println(F(" mV")); + } + break; + } + + case 'b': { + // read the battery voltage and percentage + uint16_t vbat; + if (! fona.getBattVoltage(&vbat)) { + Serial.println(F("Failed to read Batt")); + } else { + Serial.print(F("VBat = ")); Serial.print(vbat); Serial.println(F(" mV")); + } + + + if (! fona.getBattPercent(&vbat)) { + Serial.println(F("Failed to read Batt")); + } else { + Serial.print(F("VPct = ")); Serial.print(vbat); Serial.println(F("%")); + } + + break; + } + + case 'U': { + // Unlock the SIM with a PIN code + char PIN[5]; + flushSerial(); + Serial.println(F("Enter 4-digit PIN")); + readline(PIN, 3); + Serial.println(PIN); + Serial.print(F("Unlocking SIM card: ")); + if (! fona.unlockSIM(PIN)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'C': { + // read the CCID + fona.getSIMCCID(replybuffer); // make sure replybuffer is at least 21 bytes! + Serial.print(F("SIM CCID = ")); Serial.println(replybuffer); + break; + } + + case 'i': { + // read the RSSI + uint8_t n = fona.getRSSI(); + int8_t r; + + Serial.print(F("RSSI = ")); Serial.print(n); Serial.print(": "); + if (n == 0) r = -115; + if (n == 1) r = -111; + if (n == 31) r = -52; + if ((n >= 2) && (n <= 30)) { + r = map(n, 2, 30, -110, -54); + } + Serial.print(r); Serial.println(F(" dBm")); + + break; + } + + case 'n': { + // read the network/cellular status + uint8_t n = fona.getNetworkStatus(); + Serial.print(F("Network status ")); + Serial.print(n); + Serial.print(F(": ")); + if (n == 0) Serial.println(F("Not registered")); + if (n == 1) Serial.println(F("Registered (home)")); + if (n == 2) Serial.println(F("Not registered (searching)")); + if (n == 3) Serial.println(F("Denied")); + if (n == 4) Serial.println(F("Unknown")); + if (n == 5) Serial.println(F("Registered roaming")); + break; + } + + /*** Audio ***/ + case 'v': { + // set volume + flushSerial(); + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + Serial.print(F("Set Vol [0-8] ")); + } else { + Serial.print(F("Set Vol % [0-100] ")); + } + uint8_t vol = readnumber(); + Serial.println(); + if (! fona.setVolume(vol)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'V': { + uint8_t v = fona.getVolume(); + Serial.print(v); + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + Serial.println(" / 8"); + } else { + Serial.println("%"); + } + break; + } + + case 'H': { + // Set Headphone output + if (! fona.setAudio(FONA_HEADSETAUDIO)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + fona.setMicVolume(FONA_HEADSETAUDIO, 15); + break; + } + case 'e': { + // Set External output + if (! fona.setAudio(FONA_EXTAUDIO)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + + fona.setMicVolume(FONA_EXTAUDIO, 10); + break; + } + + case 'T': { + // play tone + flushSerial(); + Serial.print(F("Play tone #")); + uint8_t kittone = readnumber(); + Serial.println(); + // play for 1 second (1000 ms) + if (! fona.playToolkitTone(kittone, 1000)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** FM Radio ***/ + + case 'f': { + // get freq + flushSerial(); + Serial.print(F("FM Freq (eg 1011 == 101.1 MHz): ")); + uint16_t station = readnumber(); + Serial.println(); + // FM radio ON using headset + if (fona.FMradio(true, FONA_HEADSETAUDIO)) { + Serial.println(F("Opened")); + } + if (! fona.tuneFMradio(station)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Tuned")); + } + break; + } + case 'F': { + // FM radio off + if (! fona.FMradio(false)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + case 'm': { + // Set FM volume. + flushSerial(); + Serial.print(F("Set FM Vol [0-6]:")); + uint8_t vol = readnumber(); + Serial.println(); + if (!fona.setFMVolume(vol)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + case 'M': { + // Get FM volume. + uint8_t fmvol = fona.getFMVolume(); + if (fmvol < 0) { + Serial.println(F("Failed")); + } else { + Serial.print(F("FM volume: ")); + Serial.println(fmvol, DEC); + } + break; + } + case 'q': { + // Get FM station signal level (in decibels). + flushSerial(); + Serial.print(F("FM Freq (eg 1011 == 101.1 MHz): ")); + uint16_t station = readnumber(); + Serial.println(); + int8_t level = fona.getFMSignalLevel(station); + if (level < 0) { + Serial.println(F("Failed! Make sure FM radio is on (tuned to station).")); + } else { + Serial.print(F("Signal level (dB): ")); + Serial.println(level, DEC); + } + break; + } + + /*** PWM ***/ + + case 'P': { + // PWM Buzzer output @ 2KHz max + flushSerial(); + Serial.print(F("PWM Freq, 0 = Off, (1-2000): ")); + uint16_t freq = readnumber(); + Serial.println(); + if (! fona.setPWM(freq)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** Call ***/ + case 'c': { + // call a phone! + char number[30]; + flushSerial(); + Serial.print(F("Call #")); + readline(number, 30); + Serial.println(); + Serial.print(F("Calling ")); Serial.println(number); + if (!fona.callPhone(number)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + } + + break; + } + case 'A': { + // get call status + int8_t callstat = fona.getCallStatus(); + switch (callstat) { + case 0: Serial.println(F("Ready")); break; + case 1: Serial.println(F("Could not get status")); break; + case 3: Serial.println(F("Ringing (incoming)")); break; + case 4: Serial.println(F("Ringing/in progress (outgoing)")); break; + default: Serial.println(F("Unknown")); break; + } + break; + } + + case 'h': { + // hang up! + if (! fona.hangUp()) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'p': { + // pick up! + if (! fona.pickUp()) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** SMS ***/ + + case 'N': { + // read the number of SMS's! + int8_t smsnum = fona.getNumSMS(); + if (smsnum < 0) { + Serial.println(F("Could not read # SMS")); + } else { + Serial.print(smsnum); + Serial.println(F(" SMS's on SIM card!")); + } + break; + } + case 'r': { + // read an SMS + flushSerial(); + Serial.print(F("Read #")); + uint8_t smsn = readnumber(); + Serial.print(F("\n\rReading SMS #")); Serial.println(smsn); + + // Retrieve SMS sender address/phone number. + if (! fona.getSMSSender(smsn, replybuffer, 250)) { + Serial.println("Failed!"); + break; + } + Serial.print(F("FROM: ")); Serial.println(replybuffer); + + // Retrieve SMS value. + uint16_t smslen; + if (! fona.readSMS(smsn, replybuffer, 250, &smslen)) { // pass in buffer and max len! + Serial.println("Failed!"); + break; + } + Serial.print(F("***** SMS #")); Serial.print(smsn); + Serial.print(" ("); Serial.print(smslen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + + break; + } + case 'R': { + // read all SMS + int8_t smsnum = fona.getNumSMS(); + uint16_t smslen; + int8_t smsn; + + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + smsn = 0; // zero indexed + smsnum--; + } else { + smsn = 1; // 1 indexed + } + + for ( ; smsn <= smsnum; smsn++) { + Serial.print(F("\n\rReading SMS #")); Serial.println(smsn); + if (!fona.readSMS(smsn, replybuffer, 250, &smslen)) { // pass in buffer and max len! + Serial.println(F("Failed!")); + break; + } + // if the length is zero, its a special case where the index number is higher + // so increase the max we'll look at! + if (smslen == 0) { + Serial.println(F("[empty slot]")); + smsnum++; + continue; + } + + Serial.print(F("***** SMS #")); Serial.print(smsn); + Serial.print(" ("); Serial.print(smslen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + } + break; + } + + case 'd': { + // delete an SMS + flushSerial(); + Serial.print(F("Delete #")); + uint8_t smsn = readnumber(); + + Serial.print(F("\n\rDeleting SMS #")); Serial.println(smsn); + if (fona.deleteSMS(smsn)) { + Serial.println(F("OK!")); + } else { + Serial.println(F("Couldn't delete")); + } + break; + } + + case 's': { + // send an SMS! + char sendto[21], message[141]; + flushSerial(); + Serial.print(F("Send to #")); + readline(sendto, 20); + Serial.println(sendto); + Serial.print(F("Type out one-line message (140 char): ")); + readline(message, 140); + Serial.println(message); + if (!fona.sendSMS(sendto, message)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + } + + break; + } + + case 'u': { + // send a USSD! + char message[141]; + flushSerial(); + Serial.print(F("Type out one-line message (140 char): ")); + readline(message, 140); + Serial.println(message); + + uint16_t ussdlen; + if (!fona.sendUSSD(message, replybuffer, 250, &ussdlen)) { // pass in buffer and max len! + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + Serial.print(F("***** USSD Reply")); + Serial.print(" ("); Serial.print(ussdlen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + } + } + + /*** Time ***/ + + case 'y': { + // enable network time sync + if (!fona.enableNetworkTimeSync(true)) + Serial.println(F("Failed to enable")); + break; + } + + case 'Y': { + // enable NTP time sync + if (!fona.enableNTPTimeSync(true, F("pool.ntp.org"))) + Serial.println(F("Failed to enable")); + break; + } + + case 't': { + // read the time + char buffer[23]; + + fona.getTime(buffer, 23); // make sure replybuffer is at least 23 bytes! + Serial.print(F("Time = ")); Serial.println(buffer); + break; + } + + + /*********************************** GPS (SIM808 only) */ + + case 'o': { + // turn GPS off + if (!fona.enableGPS(false)) + Serial.println(F("Failed to turn off")); + break; + } + case 'O': { + // turn GPS on + if (!fona.enableGPS(true)) + Serial.println(F("Failed to turn on")); + break; + } + case 'x': { + int8_t stat; + // check GPS fix + stat = fona.GPSstatus(); + if (stat < 0) + Serial.println(F("Failed to query")); + if (stat == 0) Serial.println(F("GPS off")); + if (stat == 1) Serial.println(F("No fix")); + if (stat == 2) Serial.println(F("2D fix")); + if (stat == 3) Serial.println(F("3D fix")); + break; + } + + case 'L': { + // check for GPS location + char gpsdata[120]; + fona.getGPS(0, gpsdata, 120); + if (type == FONA808_V1) + Serial.println(F("Reply in format: mode,longitude,latitude,altitude,utctime(yyyymmddHHMMSS),ttff,satellites,speed,course")); + else + Serial.println(F("Reply in format: mode,fixstatus,utctime(yyyymmddHHMMSS),latitude,longitude,altitude,speed,course,fixmode,reserved1,HDOP,PDOP,VDOP,reserved2,view_satellites,used_satellites,reserved3,C/N0max,HPA,VPA")); + Serial.println(gpsdata); + + break; + } + + case 'E': { + flushSerial(); + if (type == FONA808_V1) { + Serial.print(F("GPS NMEA output sentences (0 = off, 34 = RMC+GGA, 255 = all)")); + } else { + Serial.print(F("On (1) or Off (0)? ")); + } + uint8_t nmeaout = readnumber(); + + // turn on NMEA output + fona.enableGPSNMEA(nmeaout); + + break; + } + + /*********************************** GPRS */ + + case 'g': { + // turn GPRS off + if (!fona.enableGPRS(false)) + Serial.println(F("Failed to turn off")); + break; + } + case 'G': { + // turn GPRS on + if (!fona.enableGPRS(true)) + Serial.println(F("Failed to turn on")); + break; + } + case 'l': { + // check for GSMLOC (requires GPRS) + uint16_t returncode; + + if (!fona.getGSMLoc(&returncode, replybuffer, 250)) + Serial.println(F("Failed!")); + if (returncode == 0) { + Serial.println(replybuffer); + } else { + Serial.print(F("Fail code #")); Serial.println(returncode); + } + + break; + } + case 'w': { + // read website URL + uint16_t statuscode; + int16_t length; + char url[80]; + + flushSerial(); + Serial.println(F("NOTE: in beta! Use small webpages to read!")); + Serial.println(F("URL to read (e.g. wifitest.adafruit.com/testwifi/index.html):")); + Serial.print(F("http://")); readline(url, 79); + Serial.println(url); + + Serial.println(F("****")); + if (!fona.HTTP_GET_start(url, &statuscode, (uint16_t *)&length)) { + Serial.println("Failed!"); + break; + } + while (length > 0) { + while (fona.available()) { + char c = fona.read(); + + // Serial.write is too slow, we'll write directly to Serial register! +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */ + UDR0 = c; +#else + Serial.write(c); +#endif + length--; + if (! length) break; + } + } + Serial.println(F("\n****")); + fona.HTTP_GET_end(); + break; + } + + case 'W': { + // Post data to website + uint16_t statuscode; + int16_t length; + char url[80]; + char data[80]; + + flushSerial(); + Serial.println(F("NOTE: in beta! Use simple websites to post!")); + Serial.println(F("URL to post (e.g. httpbin.org/post):")); + Serial.print(F("http://")); readline(url, 79); + Serial.println(url); + Serial.println(F("Data to post (e.g. \"foo\" or \"{\"simple\":\"json\"}\"):")); + readline(data, 79); + Serial.println(data); + + Serial.println(F("****")); + if (!fona.HTTP_POST_start(url, F("text/plain"), (uint8_t *) data, strlen(data), &statuscode, (uint16_t *)&length)) { + Serial.println("Failed!"); + break; + } + while (length > 0) { + while (fona.available()) { + char c = fona.read(); + +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */ + UDR0 = c; +#else + Serial.write(c); +#endif + + length--; + if (! length) break; + } + } + Serial.println(F("\n****")); + fona.HTTP_POST_end(); + break; + } + /*****************************************/ + + case 'S': { + Serial.println(F("Creating SERIAL TUBE")); + while (1) { + while (Serial.available()) { + delay(1); + fona.write(Serial.read()); + } + if (fona.available()) { + Serial.write(fona.read()); + } + } + break; + } + + default: { + Serial.println(F("Unknown command")); + printMenu(); + break; + } + } + // flush input + flushSerial(); + while (fona.available()) { + Serial.write(fona.read()); + } + +} + +void flushSerial() { + while (Serial.available()) + Serial.read(); +} + +char readBlocking() { + while (!Serial.available()); + return Serial.read(); +} +uint16_t readnumber() { + uint16_t x = 0; + char c; + while (! isdigit(c = readBlocking())) { + //Serial.print(c); + } + Serial.print(c); + x = c - '0'; + while (isdigit(c = readBlocking())) { + Serial.print(c); + x *= 10; + x += c - '0'; + } + return x; +} + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout) { + uint16_t buffidx = 0; + boolean timeoutvalid = true; + if (timeout == 0) timeoutvalid = false; + + while (true) { + if (buffidx > maxbuff) { + //Serial.println(F("SPACE")); + break; + } + + while (Serial.available()) { + char c = Serial.read(); + + //Serial.print(c, HEX); Serial.print("#"); Serial.println(c); + + if (c == '\r') continue; + if (c == 0xA) { + if (buffidx == 0) // the first 0x0A is ignored + continue; + + timeout = 0; // the second 0x0A is the end of the line + timeoutvalid = true; + break; + } + buff[buffidx] = c; + buffidx++; + } + + if (timeoutvalid && timeout == 0) { + //Serial.println(F("TIMEOUT")); + break; + } + delay(1); + } + buff[buffidx] = 0; // null term + return buffidx; +} diff --git a/libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod.zip b/libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod.zip new file mode 100644 index 0000000000000000000000000000000000000000..5128e186995744fec4190dd9421ad62f1de5d856 GIT binary patch literal 6318 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 D4A{14 literal 0 HcmV?d00001 diff --git a/libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod/FONAtest_KEY_mod.ino b/libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod/FONAtest_KEY_mod.ino new file mode 100644 index 0000000..775c72d --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/FONAtest_KEY_mod/FONAtest_KEY_mod.ino @@ -0,0 +1,909 @@ +/*************************************************** + This is an example for our Adafruit FONA Cellular Module + + Designed specifically to work with the Adafruit FONA + ----> http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + ----> http://www.adafruit.com/products/2468 + ----> http://www.adafruit.com/products/2542 + + These cellular modules use TTL Serial 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 + ****************************************************/ + +/* +THIS CODE IS STILL IN PROGRESS! + +Open up the serial console on the Arduino at 115200 baud to interact with FONA + +Note that if you need to set a GPRS APN, username, and password scroll down to +the commented section below at the end of the setup() function. +*/ +#include "Adafruit_FONA.h" + +#define FONA_RX 2 +#define FONA_TX 3 +#define FONA_RST 4 +#define FONA_KEY 8 + +// this is a large buffer for replies +char replybuffer[255]; + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +// Use this for FONA 800 and 808s +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); +// Use this one for FONA 3G +//Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST); + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0); + +uint8_t type; + +void setup() { + while (!Serial); + + pinMode(FONA_KEY, OUTPUT); + digitalWrite(FONA_KEY, HIGH); + + Serial.begin(115200); + Serial.println(F("FONA basic test")); + Serial.println(F("Initializing....(May take 3 seconds)")); + + fonaSerial->begin(4800); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while (1); + } + type = fona.type(); + Serial.println(F("FONA is OK")); + Serial.print(F("Found ")); + switch (type) { + case FONA800L: + Serial.println(F("FONA 800L")); break; + case FONA800H: + Serial.println(F("FONA 800H")); break; + case FONA808_V1: + Serial.println(F("FONA 808 (v1)")); break; + case FONA808_V2: + Serial.println(F("FONA 808 (v2)")); break; + case FONA3G_A: + Serial.println(F("FONA 3G (American)")); break; + case FONA3G_E: + Serial.println(F("FONA 3G (European)")); break; + default: + Serial.println(F("???")); break; + } + + // Print module IMEI number. + char imei[15] = {0}; // MUST use a 16 character buffer for IMEI! + uint8_t imeiLen = fona.getIMEI(imei); + if (imeiLen > 0) { + Serial.print("Module IMEI: "); Serial.println(imei); + } + + // Optionally configure a GPRS APN, username, and password. + // You might need to do this to access your network's GPRS/data + // network. Contact your provider for the exact APN, username, + // and password values. Username and password are optional and + // can be removed, but APN is required. + //fona.setGPRSNetworkSettings(F("your APN"), F("your username"), F("your password")); + + // Optionally configure HTTP gets to follow redirects over SSL. + // Default is not to follow SSL redirects, however if you uncomment + // the following line then redirects over SSL will be followed. + //fona.setHTTPSRedirect(true); + + printMenu(); +} + +void printMenu(void) { + Serial.println(F("-------------------------------------")); + Serial.println(F("[?] Print this menu")); + Serial.println(F("[a] read the ADC 2.8V max (FONA800 & 808)")); + Serial.println(F("[b] read the Battery V and % charged")); + Serial.println(F("[C] read the SIM CCID")); + Serial.println(F("[U] Unlock SIM with PIN code")); + Serial.println(F("[i] read RSSI")); + Serial.println(F("[n] get Network status")); + Serial.println(F("[v] set audio Volume")); + Serial.println(F("[V] get Volume")); + Serial.println(F("[H] set Headphone audio (FONA800 & 808)")); + Serial.println(F("[e] set External audio (FONA800 & 808)")); + Serial.println(F("[T] play audio Tone")); + Serial.println(F("[P] PWM/Buzzer out (FONA800 & 808)")); + Serial.println(F("[Z] power off with Key")); + Serial.println(F("[z] power on with Key")); + + // FM (SIM800 only!) + Serial.println(F("[f] tune FM radio (FONA800)")); + Serial.println(F("[F] turn off FM (FONA800)")); + Serial.println(F("[m] set FM volume (FONA800)")); + Serial.println(F("[M] get FM volume (FONA800)")); + Serial.println(F("[q] get FM station signal level (FONA800)")); + + // Phone + Serial.println(F("[c] make phone Call")); + Serial.println(F("[A] get call status")); + Serial.println(F("[h] Hang up phone")); + Serial.println(F("[p] Pick up phone")); + + // SMS + Serial.println(F("[N] Number of SMSs")); + Serial.println(F("[r] Read SMS #")); + Serial.println(F("[R] Read All SMS")); + Serial.println(F("[d] Delete SMS #")); + Serial.println(F("[s] Send SMS")); + Serial.println(F("[u] Send USSD")); + + // Time + Serial.println(F("[y] Enable network time sync (FONA 800 & 808)")); + Serial.println(F("[Y] Enable NTP time sync (GPRS FONA 800 & 808)")); + Serial.println(F("[t] Get network time")); + + // GPRS + Serial.println(F("[G] Enable GPRS")); + Serial.println(F("[g] Disable GPRS")); + Serial.println(F("[l] Query GSMLOC (GPRS)")); + Serial.println(F("[w] Read webpage (GPRS)")); + Serial.println(F("[W] Post to website (GPRS)")); + + // GPS + if ((type == FONA3G_A) || (type == FONA3G_E) || (type == FONA808_V1) || (type == FONA808_V2)) { + Serial.println(F("[O] Turn GPS on (FONA 808 & 3G)")); + Serial.println(F("[o] Turn GPS off (FONA 808 & 3G)")); + Serial.println(F("[L] Query GPS location (FONA 808 & 3G)")); + if (type == FONA808_V1) { + Serial.println(F("[x] GPS fix status (FONA808 v1 only)")); + } + Serial.println(F("[E] Raw NMEA out (FONA808)")); + } + + Serial.println(F("[S] create Serial passthru tunnel")); + Serial.println(F("-------------------------------------")); + Serial.println(F("")); + +} +void loop() { + Serial.print(F("FONA> ")); + while (! Serial.available() ) { + if (fona.available()) { + Serial.write(fona.read()); + } + } + + char command = Serial.read(); + Serial.println(command); + + + switch (command) { + case '?': { + printMenu(); + break; + } + + case 'Z': { + digitalWrite(FONA_KEY, LOW); + delay(2000); + digitalWrite(FONA_KEY, HIGH); + delay(3000); + break; + } + case 'z': { + digitalWrite(FONA_KEY, LOW); + delay(2000); + digitalWrite(FONA_KEY, HIGH); + delay(3000); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + } + break; + } + case 'a': { + // read the ADC + uint16_t adc; + if (! fona.getADCVoltage(&adc)) { + Serial.println(F("Failed to read ADC")); + } else { + Serial.print(F("ADC = ")); Serial.print(adc); Serial.println(F(" mV")); + } + break; + } + + case 'b': { + // read the battery voltage and percentage + uint16_t vbat; + if (! fona.getBattVoltage(&vbat)) { + Serial.println(F("Failed to read Batt")); + } else { + Serial.print(F("VBat = ")); Serial.print(vbat); Serial.println(F(" mV")); + } + + + if (! fona.getBattPercent(&vbat)) { + Serial.println(F("Failed to read Batt")); + } else { + Serial.print(F("VPct = ")); Serial.print(vbat); Serial.println(F("%")); + } + + break; + } + + case 'U': { + // Unlock the SIM with a PIN code + char PIN[5]; + flushSerial(); + Serial.println(F("Enter 4-digit PIN")); + readline(PIN, 3); + Serial.println(PIN); + Serial.print(F("Unlocking SIM card: ")); + if (! fona.unlockSIM(PIN)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'C': { + // read the CCID + fona.getSIMCCID(replybuffer); // make sure replybuffer is at least 21 bytes! + Serial.print(F("SIM CCID = ")); Serial.println(replybuffer); + break; + } + + case 'i': { + // read the RSSI + uint8_t n = fona.getRSSI(); + int8_t r; + + Serial.print(F("RSSI = ")); Serial.print(n); Serial.print(": "); + if (n == 0) r = -115; + if (n == 1) r = -111; + if (n == 31) r = -52; + if ((n >= 2) && (n <= 30)) { + r = map(n, 2, 30, -110, -54); + } + Serial.print(r); Serial.println(F(" dBm")); + + break; + } + + case 'n': { + // read the network/cellular status + uint8_t n = fona.getNetworkStatus(); + Serial.print(F("Network status ")); + Serial.print(n); + Serial.print(F(": ")); + if (n == 0) Serial.println(F("Not registered")); + if (n == 1) Serial.println(F("Registered (home)")); + if (n == 2) Serial.println(F("Not registered (searching)")); + if (n == 3) Serial.println(F("Denied")); + if (n == 4) Serial.println(F("Unknown")); + if (n == 5) Serial.println(F("Registered roaming")); + break; + } + + /*** Audio ***/ + case 'v': { + // set volume + flushSerial(); + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + Serial.print(F("Set Vol [0-8] ")); + } else { + Serial.print(F("Set Vol % [0-100] ")); + } + uint8_t vol = readnumber(); + Serial.println(); + if (! fona.setVolume(vol)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'V': { + uint8_t v = fona.getVolume(); + Serial.print(v); + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + Serial.println(" / 8"); + } else { + Serial.println("%"); + } + break; + } + + case 'H': { + // Set Headphone output + if (! fona.setAudio(FONA_HEADSETAUDIO)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + fona.setMicVolume(FONA_HEADSETAUDIO, 15); + break; + } + case 'e': { + // Set External output + if (! fona.setAudio(FONA_EXTAUDIO)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + + fona.setMicVolume(FONA_EXTAUDIO, 10); + break; + } + + case 'T': { + // play tone + flushSerial(); + Serial.print(F("Play tone #")); + uint8_t kittone = readnumber(); + Serial.println(); + // play for 1 second (1000 ms) + if (! fona.playToolkitTone(kittone, 1000)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** FM Radio ***/ + + case 'f': { + // get freq + flushSerial(); + Serial.print(F("FM Freq (eg 1011 == 101.1 MHz): ")); + uint16_t station = readnumber(); + Serial.println(); + // FM radio ON using headset + if (fona.FMradio(true, FONA_HEADSETAUDIO)) { + Serial.println(F("Opened")); + } + if (! fona.tuneFMradio(station)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Tuned")); + } + break; + } + case 'F': { + // FM radio off + if (! fona.FMradio(false)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + case 'm': { + // Set FM volume. + flushSerial(); + Serial.print(F("Set FM Vol [0-6]:")); + uint8_t vol = readnumber(); + Serial.println(); + if (!fona.setFMVolume(vol)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + case 'M': { + // Get FM volume. + uint8_t fmvol = fona.getFMVolume(); + if (fmvol < 0) { + Serial.println(F("Failed")); + } else { + Serial.print(F("FM volume: ")); + Serial.println(fmvol, DEC); + } + break; + } + case 'q': { + // Get FM station signal level (in decibels). + flushSerial(); + Serial.print(F("FM Freq (eg 1011 == 101.1 MHz): ")); + uint16_t station = readnumber(); + Serial.println(); + int8_t level = fona.getFMSignalLevel(station); + if (level < 0) { + Serial.println(F("Failed! Make sure FM radio is on (tuned to station).")); + } else { + Serial.print(F("Signal level (dB): ")); + Serial.println(level, DEC); + } + break; + } + + /*** PWM ***/ + + case 'P': { + // PWM Buzzer output @ 2KHz max + flushSerial(); + Serial.print(F("PWM Freq, 0 = Off, (1-2000): ")); + uint16_t freq = readnumber(); + Serial.println(); + if (! fona.setPWM(freq)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** Call ***/ + case 'c': { + // call a phone! + char number[30]; + flushSerial(); + Serial.print(F("Call #")); + readline(number, 30); + Serial.println(); + Serial.print(F("Calling ")); Serial.println(number); + if (!fona.callPhone(number)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + } + + break; + } + case 'A': { + // get call status + int8_t callstat = fona.getCallStatus(); + switch (callstat) { + case 0: Serial.println(F("Ready")); break; + case 1: Serial.println(F("Could not get status")); break; + case 3: Serial.println(F("Ringing (incoming)")); break; + case 4: Serial.println(F("Ringing/in progress (outgoing)")); break; + default: Serial.println(F("Unknown")); break; + } + break; + } + + case 'h': { + // hang up! + if (! fona.hangUp()) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + case 'p': { + // pick up! + if (! fona.pickUp()) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + break; + } + + /*** SMS ***/ + + case 'N': { + // read the number of SMS's! + int8_t smsnum = fona.getNumSMS(); + if (smsnum < 0) { + Serial.println(F("Could not read # SMS")); + } else { + Serial.print(smsnum); + Serial.println(F(" SMS's on SIM card!")); + } + break; + } + case 'r': { + // read an SMS + flushSerial(); + Serial.print(F("Read #")); + uint8_t smsn = readnumber(); + Serial.print(F("\n\rReading SMS #")); Serial.println(smsn); + + // Retrieve SMS sender address/phone number. + if (! fona.getSMSSender(smsn, replybuffer, 250)) { + Serial.println("Failed!"); + break; + } + Serial.print(F("FROM: ")); Serial.println(replybuffer); + + // Retrieve SMS value. + uint16_t smslen; + if (! fona.readSMS(smsn, replybuffer, 250, &smslen)) { // pass in buffer and max len! + Serial.println("Failed!"); + break; + } + Serial.print(F("***** SMS #")); Serial.print(smsn); + Serial.print(" ("); Serial.print(smslen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + + break; + } + case 'R': { + // read all SMS + int8_t smsnum = fona.getNumSMS(); + uint16_t smslen; + int8_t smsn; + + if ( (type == FONA3G_A) || (type == FONA3G_E) ) { + smsn = 0; // zero indexed + smsnum--; + } else { + smsn = 1; // 1 indexed + } + + for ( ; smsn <= smsnum; smsn++) { + Serial.print(F("\n\rReading SMS #")); Serial.println(smsn); + if (!fona.readSMS(smsn, replybuffer, 250, &smslen)) { // pass in buffer and max len! + Serial.println(F("Failed!")); + break; + } + // if the length is zero, its a special case where the index number is higher + // so increase the max we'll look at! + if (smslen == 0) { + Serial.println(F("[empty slot]")); + smsnum++; + continue; + } + + Serial.print(F("***** SMS #")); Serial.print(smsn); + Serial.print(" ("); Serial.print(smslen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + } + break; + } + + case 'd': { + // delete an SMS + flushSerial(); + Serial.print(F("Delete #")); + uint8_t smsn = readnumber(); + + Serial.print(F("\n\rDeleting SMS #")); Serial.println(smsn); + if (fona.deleteSMS(smsn)) { + Serial.println(F("OK!")); + } else { + Serial.println(F("Couldn't delete")); + } + break; + } + + case 's': { + // send an SMS! + char sendto[21], message[141]; + flushSerial(); + Serial.print(F("Send to #")); + readline(sendto, 20); + Serial.println(sendto); + Serial.print(F("Type out one-line message (140 char): ")); + readline(message, 140); + Serial.println(message); + if (!fona.sendSMS(sendto, message)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + } + + break; + } + + case 'u': { + // send a USSD! + char message[141]; + flushSerial(); + Serial.print(F("Type out one-line message (140 char): ")); + readline(message, 140); + Serial.println(message); + + uint16_t ussdlen; + if (!fona.sendUSSD(message, replybuffer, 250, &ussdlen)) { // pass in buffer and max len! + Serial.println(F("Failed")); + } else { + Serial.println(F("Sent!")); + Serial.print(F("***** USSD Reply")); + Serial.print(" ("); Serial.print(ussdlen); Serial.println(F(") bytes *****")); + Serial.println(replybuffer); + Serial.println(F("*****")); + } + } + + /*** Time ***/ + + case 'y': { + // enable network time sync + if (!fona.enableNetworkTimeSync(true)) + Serial.println(F("Failed to enable")); + break; + } + + case 'Y': { + // enable NTP time sync + if (!fona.enableNTPTimeSync(true, F("pool.ntp.org"))) + Serial.println(F("Failed to enable")); + break; + } + + case 't': { + // read the time + char buffer[23]; + + fona.getTime(buffer, 23); // make sure replybuffer is at least 23 bytes! + Serial.print(F("Time = ")); Serial.println(buffer); + break; + } + + + /*********************************** GPS (SIM808 only) */ + + case 'o': { + // turn GPS off + if (!fona.enableGPS(false)) + Serial.println(F("Failed to turn off")); + break; + } + case 'O': { + // turn GPS on + if (!fona.enableGPS(true)) + Serial.println(F("Failed to turn on")); + break; + } + case 'x': { + int8_t stat; + // check GPS fix + stat = fona.GPSstatus(); + if (stat < 0) + Serial.println(F("Failed to query")); + if (stat == 0) Serial.println(F("GPS off")); + if (stat == 1) Serial.println(F("No fix")); + if (stat == 2) Serial.println(F("2D fix")); + if (stat == 3) Serial.println(F("3D fix")); + break; + } + + case 'L': { + // check for GPS location + char gpsdata[120]; + fona.getGPS(0, gpsdata, 120); + if (type == FONA808_V1) + Serial.println(F("Reply in format: mode,longitude,latitude,altitude,utctime(yyyymmddHHMMSS),ttff,satellites,speed,course")); + else + Serial.println(F("Reply in format: mode,fixstatus,utctime(yyyymmddHHMMSS),latitude,longitude,altitude,speed,course,fixmode,reserved1,HDOP,PDOP,VDOP,reserved2,view_satellites,used_satellites,reserved3,C/N0max,HPA,VPA")); + Serial.println(gpsdata); + + break; + } + + case 'E': { + flushSerial(); + if (type == FONA808_V1) { + Serial.print(F("GPS NMEA output sentences (0 = off, 34 = RMC+GGA, 255 = all)")); + } else { + Serial.print(F("On (1) or Off (0)? ")); + } + uint8_t nmeaout = readnumber(); + + // turn on NMEA output + fona.enableGPSNMEA(nmeaout); + + break; + } + + /*********************************** GPRS */ + + case 'g': { + // turn GPRS off + if (!fona.enableGPRS(false)) + Serial.println(F("Failed to turn off")); + break; + } + case 'G': { + // turn GPRS on + if (!fona.enableGPRS(true)) + Serial.println(F("Failed to turn on")); + break; + } + case 'l': { + // check for GSMLOC (requires GPRS) + uint16_t returncode; + + if (!fona.getGSMLoc(&returncode, replybuffer, 250)) + Serial.println(F("Failed!")); + if (returncode == 0) { + Serial.println(replybuffer); + } else { + Serial.print(F("Fail code #")); Serial.println(returncode); + } + + break; + } + case 'w': { + // read website URL + uint16_t statuscode; + int16_t length; + char url[80]; + + flushSerial(); + Serial.println(F("NOTE: in beta! Use small webpages to read!")); + Serial.println(F("URL to read (e.g. www.adafruit.com/testwifi/index.html):")); + Serial.print(F("http://")); readline(url, 79); + Serial.println(url); + + Serial.println(F("****")); + if (!fona.HTTP_GET_start(url, &statuscode, (uint16_t *)&length)) { + Serial.println("Failed!"); + break; + } + while (length > 0) { + while (fona.available()) { + char c = fona.read(); + + // Serial.write is too slow, we'll write directly to Serial register! +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */ + UDR0 = c; +#else + Serial.write(c); +#endif + length--; + if (! length) break; + } + } + Serial.println(F("\n****")); + fona.HTTP_GET_end(); + break; + } + + case 'W': { + // Post data to website + uint16_t statuscode; + int16_t length; + char url[80]; + char data[80]; + + flushSerial(); + Serial.println(F("NOTE: in beta! Use simple websites to post!")); + Serial.println(F("URL to post (e.g. httpbin.org/post):")); + Serial.print(F("http://")); readline(url, 79); + Serial.println(url); + Serial.println(F("Data to post (e.g. \"foo\" or \"{\"simple\":\"json\"}\"):")); + readline(data, 79); + Serial.println(data); + + Serial.println(F("****")); + if (!fona.HTTP_POST_start(url, F("text/plain"), (uint8_t *) data, strlen(data), &statuscode, (uint16_t *)&length)) { + Serial.println("Failed!"); + break; + } + while (length > 0) { + while (fona.available()) { + char c = fona.read(); + +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */ + UDR0 = c; +#else + Serial.write(c); +#endif + + length--; + if (! length) break; + } + } + Serial.println(F("\n****")); + fona.HTTP_POST_end(); + break; + } + /*****************************************/ + + case 'S': { + Serial.println(F("Creating SERIAL TUBE")); + while (1) { + while (Serial.available()) { + delay(1); + fona.write(Serial.read()); + } + if (fona.available()) { + Serial.write(fona.read()); + } + } + break; + } + + default: { + Serial.println(F("Unknown command")); + printMenu(); + break; + } + } + // flush input + flushSerial(); + while (fona.available()) { + Serial.write(fona.read()); + } + +} + +void flushSerial() { + while (Serial.available()) + Serial.read(); +} + +char readBlocking() { + while (!Serial.available()); + return Serial.read(); +} +uint16_t readnumber() { + uint16_t x = 0; + char c; + while (! isdigit(c = readBlocking())) { + //Serial.print(c); + } + Serial.print(c); + x = c - '0'; + while (isdigit(c = readBlocking())) { + Serial.print(c); + x *= 10; + x += c - '0'; + } + return x; +} + +uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout) { + uint16_t buffidx = 0; + boolean timeoutvalid = true; + if (timeout == 0) timeoutvalid = false; + + while (true) { + if (buffidx > maxbuff) { + //Serial.println(F("SPACE")); + break; + } + + while (Serial.available()) { + char c = Serial.read(); + + //Serial.print(c, HEX); Serial.print("#"); Serial.println(c); + + if (c == '\r') continue; + if (c == 0xA) { + if (buffidx == 0) // the first 0x0A is ignored + continue; + + timeout = 0; // the second 0x0A is the end of the line + timeoutvalid = true; + break; + } + buff[buffidx] = c; + buffidx++; + } + + if (timeoutvalid && timeout == 0) { + //Serial.println(F("TIMEOUT")); + break; + } + delay(1); + } + buff[buffidx] = 0; // null term + return buffidx; +} diff --git a/libraries/Adafruit_FONA_Library/examples/GPS/GPS.ino b/libraries/Adafruit_FONA_Library/examples/GPS/GPS.ino new file mode 100644 index 0000000..cce63d5 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/GPS/GPS.ino @@ -0,0 +1,112 @@ +/** + * ___ ___ _ _ _ ___ __ ___ ___ ___ ___ + * | __/ _ \| \| | /_\ ( _ )/ \( _ ) / __| _ \/ __| + * | _| (_) | .` |/ _ \ / _ \ () / _ \ | (_ | _/\__ \ + * |_| \___/|_|\_/_/ \_\ \___/\__/\___/ \___|_| |___/ + * + * This example is meant to work with the Adafruit + * FONA 808 or 3G Shield or Breakout + * + * Copyright: 2015 Adafruit + * Author: Todd Treece + * Licence: MIT + * + */ +#include "Adafruit_FONA.h" + +// standard pins for the shield, adjust as necessary +#define FONA_RX 2 +#define FONA_TX 3 +#define FONA_RST 4 + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); + +// Have a FONA 3G? use this object type instead +//Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST); + + +void setup() { + + while (! Serial); + + Serial.begin(115200); + Serial.println(F("Adafruit FONA 808 & 3G GPS demo")); + Serial.println(F("Initializing FONA... (May take a few seconds)")); + + fonaSerial->begin(4800); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while(1); + } + Serial.println(F("FONA is OK")); + // Try to enable GPRS + + + Serial.println(F("Enabling GPS...")); + fona.enableGPS(true); +} + +void loop() { + delay(2000); + + float latitude, longitude, speed_kph, heading, speed_mph, altitude; + + // if you ask for an altitude reading, getGPS will return false if there isn't a 3D fix + boolean gps_success = fona.getGPS(&latitude, &longitude, &speed_kph, &heading, &altitude); + + if (gps_success) { + + Serial.print("GPS lat:"); + Serial.println(latitude, 6); + Serial.print("GPS long:"); + Serial.println(longitude, 6); + Serial.print("GPS speed KPH:"); + Serial.println(speed_kph); + Serial.print("GPS speed MPH:"); + speed_mph = speed_kph * 0.621371192; + Serial.println(speed_mph); + Serial.print("GPS heading:"); + Serial.println(heading); + Serial.print("GPS altitude:"); + Serial.println(altitude); + + } else { + Serial.println("Waiting for FONA GPS 3D fix..."); + } + + // Fona 3G doesnt have GPRSlocation :/ + if ((fona.type() == FONA3G_A) || (fona.type() == FONA3G_E)) + return; + // Check for network, then GPRS + Serial.println(F("Checking for Cell network...")); + if (fona.getNetworkStatus() == 1) { + // network & GPRS? Great! Print out the GSM location to compare + boolean gsmloc_success = fona.getGSMLoc(&latitude, &longitude); + + if (gsmloc_success) { + Serial.print("GSMLoc lat:"); + Serial.println(latitude, 6); + Serial.print("GSMLoc long:"); + Serial.println(longitude, 6); + } else { + Serial.println("GSM location failed..."); + Serial.println(F("Disabling GPRS")); + fona.enableGPRS(false); + Serial.println(F("Enabling GPRS")); + if (!fona.enableGPRS(true)) { + Serial.println(F("Failed to turn GPRS on")); + } + } + } +} + diff --git a/libraries/Adafruit_FONA_Library/examples/IncomingCall/IncomingCall.ino b/libraries/Adafruit_FONA_Library/examples/IncomingCall/IncomingCall.ino new file mode 100644 index 0000000..8dba308 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/examples/IncomingCall/IncomingCall.ino @@ -0,0 +1,62 @@ +// FONA Incoming Call Number Example +// Listens for a call and displays the phone number of the caller (if available). +// Use this example to add phone call detection to your own FONA sketch. +#include "Adafruit_FONA.h" + +// Pins which are connected to the FONA. +// Note that this is different from FONAtest! +#define FONA_RX 3 +#define FONA_TX 4 +#define FONA_RST 5 + +// Note you need to map interrupt number to pin number +// for your board. On an Uno & Mega interrupt 0 is +// digital pin 2, and on a Leonardo interrupt 0 is +// digital pin 3. See this page for a complete table: +// http://arduino.cc/en/Reference/attachInterrupt +// Make sure this interrupt pin is connected to FONA RI! +#define FONA_RI_INTERRUPT 0 + +// We default to using software serial. If you want to use hardware serial +// (because softserial isnt supported) comment out the following three lines +// and uncomment the HardwareSerial line +#include +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); +SoftwareSerial *fonaSerial = &fonaSS; + +// Hardware serial is also possible! +// HardwareSerial *fonaSerial = &Serial1; + +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); + +void setup() { + Serial.begin(115200); + Serial.println(F("FONA incoming call example")); + Serial.println(F("Initializing....(May take 3 seconds)")); + + fonaSerial->begin(4800); + if (! fona.begin(*fonaSerial)) { + Serial.println(F("Couldn't find FONA")); + while(1); + } + Serial.println(F("FONA is OK")); + + // Enable incoming call notification. + if(fona.callerIdNotification(true, FONA_RI_INTERRUPT)) { + Serial.println(F("Caller id notification enabled.")); + } + else { + Serial.println(F("Caller id notification disabled")); + } +} + +void loop(){ + // Create a small string buffer to hold incoming call number. + char phone[32] = {0}; + // Check for an incoming call. Will return true if a call is incoming. + if(fona.incomingCallNumber(phone)){ + Serial.println(F("RING!")); + Serial.print(F("Phone Number: ")); + Serial.println(phone); + } +} diff --git a/libraries/Adafruit_FONA_Library/includes/FONAConfig.h b/libraries/Adafruit_FONA_Library/includes/FONAConfig.h new file mode 100644 index 0000000..5fa3e13 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/includes/FONAConfig.h @@ -0,0 +1,34 @@ +/* + * FONAConfig.h -- compile-time configuration + * This is part of the library for the Adafruit FONA Cellular Module + * + * Designed specifically to work with the Adafruit FONA + * ----> https://www.adafruit.com/products/1946 + * ----> https://www.adafruit.com/products/1963 + * ----> http://www.adafruit.com/products/2468 + * ----> http://www.adafruit.com/products/2542 + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Pat Deegan, http://flyingcarsandstuff.com, for inclusion in + * the Adafruit_FONA_Library and released under the + * BSD license, all text above must be included in any redistribution. + * + * Created on: Jan 16, 2016 + * Author: Pat Deegan + */ + +#ifndef ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONACONFIG_H_ +#define ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONACONFIG_H_ + +/* ADAFRUIT_FONA_DEBUG + * When defined, will cause extensive debug output on the + * DebugStream set in the appropriate platform/ header. + */ + +#define ADAFRUIT_FONA_DEBUG + + +#endif /* ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONACONFIG_H_ */ diff --git a/libraries/Adafruit_FONA_Library/includes/FONAExtIncludes.h b/libraries/Adafruit_FONA_Library/includes/FONAExtIncludes.h new file mode 100644 index 0000000..f8fe878 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/includes/FONAExtIncludes.h @@ -0,0 +1,33 @@ +/* + * FONAExtIncludes.h -- system-wide includes + * This is part of the library for the Adafruit FONA Cellular Module + * + * Designed specifically to work with the Adafruit FONA + * ----> https://www.adafruit.com/products/1946 + * ----> https://www.adafruit.com/products/1963 + * ----> http://www.adafruit.com/products/2468 + * ----> http://www.adafruit.com/products/2542 + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Pat Deegan, http://flyingcarsandstuff.com, for inclusion in + * the Adafruit_FONA_Library and released under the + * BSD license, all text above must be included in any redistribution. + * + * Created on: Jan 16, 2016 + * Author: Pat Deegan + */ + + +#ifndef ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONAEXTINCLUDES_H_ +#define ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONAEXTINCLUDES_H_ + + +#include "FONAConfig.h" +// include any system-wide includes required here + + + +#endif /* ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_FONAEXTINCLUDES_H_ */ diff --git a/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatStd.h b/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatStd.h new file mode 100644 index 0000000..573a3d3 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatStd.h @@ -0,0 +1,70 @@ +/* + * FONAPlatStd.h -- standard AVR/Arduino platform. + * + * This is part of the library for the Adafruit FONA Cellular Module + * + * Designed specifically to work with the Adafruit FONA + * ----> https://www.adafruit.com/products/1946 + * ----> https://www.adafruit.com/products/1963 + * ----> http://www.adafruit.com/products/2468 + * ----> http://www.adafruit.com/products/2542 + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Pat Deegan, http://flyingcarsandstuff.com, for inclusion in + * the Adafruit_FONA_Library and released under the + * BSD license, all text above must be included in any redistribution. + * + * Created on: Jan 16, 2016 + * Author: Pat Deegan + */ + + +#ifndef ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATSTD_H_ +#define ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATSTD_H_ + +#include "../FONAConfig.h" + + +#if (ARDUINO >= 100) + #include "Arduino.h" + #if !defined(__SAM3X8E__) && !defined(ARDUINO_ARCH_SAMD) // Arduino Due doesn't support #include + #endif +#else + #include "WProgram.h" + #include +#endif + +#if (defined(__AVR__)) +#include +#elif (defined(ESP8266)) +#include +#endif + +// DebugStream sets the Stream output to use +// for debug (only applies when ADAFRUIT_FONA_DEBUG +// is defined in config) +#define DebugStream Serial + +#ifdef ADAFRUIT_FONA_DEBUG +// need to do some debugging... +#define DEBUG_PRINT(...) DebugStream.print(__VA_ARGS__) +#define DEBUG_PRINTLN(...) DebugStream.println(__VA_ARGS__) +#endif + +// a few typedefs to keep things portable +typedef Stream FONAStreamType; +typedef const __FlashStringHelper * FONAFlashStringPtr; + +#define prog_char char PROGMEM + +#define prog_char_strcmp(a, b) strcmp_P((a), (b)) +// define prog_char_strncmp(a, b, c) strncmp_P((a), (b), (c)) +#define prog_char_strstr(a, b) strstr_P((a), (b)) +#define prog_char_strlen(a) strlen_P((a)) +#define prog_char_strcpy(to, fromprogmem) strcpy_P((to), (fromprogmem)) +//define prog_char_strncpy(to, from, len) strncpy_P((to), (fromprogmem), (len)) + +#endif /* ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATSTD_H_ */ diff --git a/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatform.h b/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatform.h new file mode 100644 index 0000000..0bf98d0 --- /dev/null +++ b/libraries/Adafruit_FONA_Library/includes/platform/FONAPlatform.h @@ -0,0 +1,62 @@ +/* + * FONAPlatform.h -- platform definitions includes. + * + * This is part of the library for the Adafruit FONA Cellular Module + * + * Designed specifically to work with the Adafruit FONA + * ----> https://www.adafruit.com/products/1946 + * ----> https://www.adafruit.com/products/1963 + * ----> http://www.adafruit.com/products/2468 + * ----> http://www.adafruit.com/products/2542 + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Pat Deegan, http://flyingcarsandstuff.com, for inclusion in + * the Adafruit_FONA_Library and released under the + * BSD license, all text above must be included in any redistribution. + * + * Created on: Jan 16, 2016 + * Author: Pat Deegan + */ + + +#ifndef ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATFORM_H_ +#define ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATFORM_H_ + +#include "../FONAConfig.h" + +// only "standard" config supported in this release -- namely AVR-based arduino type affairs +#include "FONAPlatStd.h" + + + +#ifndef DEBUG_PRINT +// debug is disabled + +#define DEBUG_PRINT(...) +#define DEBUG_PRINTLN(...) + +#endif + + +#ifndef prog_char_strcmp +#define prog_char_strcmp(a, b) strcmp((a), (b)) +#endif + +#ifndef prog_char_strstr +#define prog_char_strstr(a, b) strstr((a), (b)) +#endif + +#ifndef prog_char_strlen +#define prog_char_strlen(a) strlen((a)) +#endif + + +#ifndef prog_char_strcpy +#define prog_char_strcpy(to, fromprogmem) strcpy((to), (fromprogmem)) +#endif + + +#endif /* ADAFRUIT_FONA_LIBRARY_SRC_INCLUDES_PLATFORM_FONAPLATFORM_H_ */ diff --git a/libraries/Adafruit_FONA_Library/library.properties b/libraries/Adafruit_FONA_Library/library.properties new file mode 100644 index 0000000..fc20e2b --- /dev/null +++ b/libraries/Adafruit_FONA_Library/library.properties @@ -0,0 +1,9 @@ +name=Adafruit FONA Library +version=1.3.5 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for the Adafruit FONA +paragraph=Arduino library for the Adafruit FONA +category=Communication +url=https://github.com/adafruit/Adafruit_FONA +architectures=* diff --git a/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp new file mode 100644 index 0000000..42fcf95 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.cpp @@ -0,0 +1,851 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 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. +#include "Adafruit_MQTT.h" + +#if defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_SAMD_MKR1000) || defined(ARDUINO_ARCH_SAMD) +static char *dtostrf (double val, signed char width, unsigned char prec, char *sout) { + char fmt[20]; + sprintf(fmt, "%%%d.%df", width, prec); + sprintf(sout, fmt, val); + return sout; +} +#endif + +#if defined(ESP8266) +int strncasecmp(const char * str1, const char * str2, int len) { + int d = 0; + while(len--) { + int c1 = tolower(*str1++); + int c2 = tolower(*str2++); + if(((d = c1 - c2) != 0) || (c2 == '\0')) { + return d; + } + } + return 0; +} +#endif + + +void printBuffer(uint8_t *buffer, uint16_t len) { + DEBUG_PRINTER.print('\t'); + for (uint16_t i=0; i> 8; p++; + p[0] = len & 0xFF; p++; + memmove(p, s, len); + return p+len; +} +*/ + +static uint8_t *stringprint(uint8_t *p, const char *s, uint16_t maxlen=0) { + // If maxlen is specified (has a non-zero value) then use it as the maximum + // length of the source string to write to the buffer. Otherwise write + // the entire source string. + uint16_t len = strlen(s); + if (maxlen > 0 && len > maxlen) { + len = maxlen; + } + /* + for (uint8_t i=0; i> 8; p++; + p[0] = len & 0xFF; p++; + strncpy((char *)p, s, len); + return p+len; +} + + +// Adafruit_MQTT Definition //////////////////////////////////////////////////// + +Adafruit_MQTT::Adafruit_MQTT(const char *server, + uint16_t port, + const char *cid, + const char *user, + const char *pass) { + servername = server; + portnum = port; + clientid = cid; + username = user; + password = pass; + + // reset subscriptions + for (uint8_t i=0; itopic, subscriptions[i]->qos); + if (!sendPacket(buffer, len)) + return -1; + + if(MQTT_PROTOCOL_LEVEL < 3) // older versions didn't suback + break; + + // Check for SUBACK if using MQTT 3.1.1 or higher + // TODO: The Server is permitted to start sending PUBLISH packets matching the + // Subscription before the Server sends the SUBACK Packet. (will really need to use callbacks - ada) + + //Serial.println("\t**looking for suback"); + if (processPacketsUntil(buffer, MQTT_CTRL_SUBACK, SUBACK_TIMEOUT_MS)) { + success = true; + break; + } + } + if (! success) return -2; // failed to sub for some reason + } + + return 0; +} + +int8_t Adafruit_MQTT::connect(const char *user, const char *pass) +{ + username = user; + password = pass; + return connect(); +} + +uint16_t Adafruit_MQTT::processPacketsUntil(uint8_t *buffer, uint8_t waitforpackettype, uint16_t timeout) { + uint16_t len; + + while(true) { + len = readFullPacket(buffer, MAXBUFFERSIZE, timeout); + + if(len == 0){ + break; + } + + if ((buffer[0] >> 4) == waitforpackettype) + { + return len; + } + else + { + ERROR_PRINTLN(F("Dropped a packet")); + } + } + return 0; +} + +uint16_t Adafruit_MQTT::readFullPacket(uint8_t *buffer, uint16_t maxsize, uint16_t timeout) { + // will read a packet and Do The Right Thing with length + uint8_t *pbuff = buffer; + + uint8_t rlen; + + // read the packet type: + rlen = readPacket(pbuff, 1, timeout); + if (rlen != 1) return 0; + + DEBUG_PRINT(F("Packet Type:\t")); DEBUG_PRINTBUFFER(pbuff, rlen); + pbuff++; + + uint32_t value = 0; + uint32_t multiplier = 1; + uint8_t encodedByte; + + do { + rlen = readPacket(pbuff, 1, timeout); + if (rlen != 1) return 0; + encodedByte = pbuff[0]; // save the last read val + pbuff++; // get ready for reading the next byte + uint32_t intermediate = encodedByte & 0x7F; + intermediate *= multiplier; + value += intermediate; + multiplier *= 128; + if (multiplier > (128UL*128UL*128UL)) { + DEBUG_PRINT(F("Malformed packet len\n")); + return 0; + } + } while (encodedByte & 0x80); + + DEBUG_PRINT(F("Packet Length:\t")); DEBUG_PRINTLN(value); + + if (value > (maxsize - (pbuff-buffer) - 1)) { + DEBUG_PRINTLN(F("Packet too big for buffer")); + rlen = readPacket(pbuff, (maxsize - (pbuff-buffer) - 1), timeout); + } else { + rlen = readPacket(pbuff, value, timeout); + } + //DEBUG_PRINT(F("Remaining packet:\t")); DEBUG_PRINTBUFFER(pbuff, rlen); + + return ((pbuff - buffer)+rlen); +} + +const __FlashStringHelper* Adafruit_MQTT::connectErrorString(int8_t code) { + switch (code) { + case 1: return F("The Server does not support the level of the MQTT protocol requested"); + case 2: return F("The Client identifier is correct UTF-8 but not allowed by the Server"); + case 3: return F("The MQTT service is unavailable"); + case 4: return F("The data in the user name or password is malformed"); + case 5: return F("Not authorized to connect"); + case 6: return F("Exceeded reconnect rate limit. Please try again later."); + case 7: return F("You have been banned from connecting. Please contact the MQTT server administrator for more details."); + case -1: return F("Connection failed"); + case -2: return F("Failed to subscribe"); + default: return F("Unknown error"); + } +} + +bool Adafruit_MQTT::disconnect() { + + // Construct and send disconnect packet. + uint8_t len = disconnectPacket(buffer); + if (! sendPacket(buffer, len)) + DEBUG_PRINTLN(F("Unable to send disconnect packet")); + + return disconnectServer(); + +} + + +bool Adafruit_MQTT::publish(const char *topic, const char *data, uint8_t qos) { + return publish(topic, (uint8_t*)(data), strlen(data), qos); +} + +bool Adafruit_MQTT::publish(const char *topic, uint8_t *data, uint16_t bLen, uint8_t qos) { + // Construct and send publish packet. + uint16_t len = publishPacket(buffer, topic, data, bLen, qos); + if (!sendPacket(buffer, len)) + return false; + + // If QOS level is high enough verify the response packet. + if (qos > 0) { + len = readFullPacket(buffer, MAXBUFFERSIZE, PUBLISH_TIMEOUT_MS); + DEBUG_PRINT(F("Publish QOS1+ reply:\t")); + DEBUG_PRINTBUFFER(buffer, len); + if (len != 4) + return false; + if ((buffer[0] >> 4) != MQTT_CTRL_PUBACK) + return false; + uint16_t packnum = buffer[2]; + packnum <<= 8; + packnum |= buffer[3]; + + // we increment the packet_id_counter right after publishing so inc here too to match + packnum++; + if (packnum != packet_id_counter) + return false; + } + + return true; +} + +bool Adafruit_MQTT::will(const char *topic, const char *payload, uint8_t qos, uint8_t retain) { + + if (connected()) { + DEBUG_PRINT(F("Will defined after connect")); + return false; + } + + will_topic = topic; + will_payload = payload; + will_qos = qos; + will_retain = retain; + + return true; + +} + +bool Adafruit_MQTT::subscribe(Adafruit_MQTT_Subscribe *sub) { + uint8_t i; + // see if we are already subscribed + for (i=0; itopic); + + // sending unsubscribe failed + if (! sendPacket(buffer, len)) + return false; + + // if QoS for this subscription is 1 or 2, we need + // to wait for the unsuback to confirm unsubscription + if(subscriptions[i]->qos > 0 && MQTT_PROTOCOL_LEVEL > 3) { + + // wait for UNSUBACK + len = readFullPacket(buffer, MAXBUFFERSIZE, CONNECT_TIMEOUT_MS); + DEBUG_PRINT(F("UNSUBACK:\t")); + DEBUG_PRINTBUFFER(buffer, len); + + if ((len != 5) || (buffer[0] != (MQTT_CTRL_UNSUBACK << 4))) { + return false; // failure to unsubscribe + } + } + + subscriptions[i] = 0; + return true; + } + + } + + // subscription not found, so we are unsubscribed + return true; + +} + +void Adafruit_MQTT::processPackets(int16_t timeout) { + + uint32_t elapsed = 0, endtime, starttime = millis(); + + while (elapsed < (uint32_t)timeout) { + Adafruit_MQTT_Subscribe *sub = readSubscription(timeout - elapsed); + if (sub) { + //Serial.println("**** sub packet received"); + if (sub->callback_uint32t != NULL) { + // huh lets do the callback in integer mode + uint32_t data = 0; + data = atoi((char *)sub->lastread); + //Serial.print("*** calling int callback with : "); Serial.println(data); + sub->callback_uint32t(data); + } + else if (sub->callback_double != NULL) { + // huh lets do the callback in doublefloat mode + double data = 0; + data = atof((char *)sub->lastread); + //Serial.print("*** calling double callback with : "); Serial.println(data); + sub->callback_double(data); + } + else if (sub->callback_buffer != NULL) { + // huh lets do the callback in buffer mode + //Serial.print("*** calling buffer callback with : "); Serial.println((char *)sub->lastread); + sub->callback_buffer((char *)sub->lastread, sub->datalen); + } + else if (sub->callback_io != NULL) { + // huh lets do the callback in io mode + //Serial.print("*** calling io instance callback with : "); Serial.println((char *)sub->lastread); + ((sub->io_mqtt)->*(sub->callback_io))((char *)sub->lastread, sub->datalen); + } + } + + // keep track over elapsed time + endtime = millis(); + if (endtime < starttime) { + starttime = endtime; // looped around!") + } + elapsed += (endtime - starttime); + } +} + +Adafruit_MQTT_Subscribe *Adafruit_MQTT::readSubscription(int16_t timeout) { + uint16_t i, topiclen, datalen; + + // Check if data is available to read. + uint16_t len = readFullPacket(buffer, MAXBUFFERSIZE, timeout); // return one full packet + if (!len) + return NULL; // No data available, just quit. + DEBUG_PRINT("Packet len: "); DEBUG_PRINTLN(len); + DEBUG_PRINTBUFFER(buffer, len); + + if (len<3) return NULL; + if ((buffer[0] & 0xF0) != (MQTT_CTRL_PUBLISH) << 4) return NULL; + + // Parse out length of packet. + topiclen = buffer[3]; + DEBUG_PRINT(F("Looking for subscription len ")); DEBUG_PRINTLN(topiclen); + + // Find subscription associated with this packet. + for (i=0; itopic) != topiclen) + continue; + // Stop if the subscription topic matches the received topic. Be careful + // to make comparison case insensitive. + if (strncasecmp((char*)buffer+4, subscriptions[i]->topic, topiclen) == 0) { + DEBUG_PRINT(F("Found sub #")); DEBUG_PRINTLN(i); + break; + } + } + } + if (i==MAXSUBSCRIPTIONS) return NULL; // matching sub not found ??? + + uint8_t packet_id_len = 0; + uint16_t packetid = 0; + // Check if it is QoS 1, TODO: we dont support QoS 2 + if ((buffer[0] & 0x6) == 0x2) { + packet_id_len = 2; + packetid = buffer[topiclen+4]; + packetid <<= 8; + packetid |= buffer[topiclen+5]; + } + + // zero out the old data + memset(subscriptions[i]->lastread, 0, SUBSCRIPTIONDATALEN); + + datalen = len - topiclen - packet_id_len - 4; + if (datalen > SUBSCRIPTIONDATALEN) { + datalen = SUBSCRIPTIONDATALEN-1; // cut it off + } + // extract out just the data, into the subscription object itself + memmove(subscriptions[i]->lastread, buffer+4+topiclen+packet_id_len, datalen); + subscriptions[i]->datalen = datalen; + DEBUG_PRINT(F("Data len: ")); DEBUG_PRINTLN(datalen); + DEBUG_PRINT(F("Data: ")); DEBUG_PRINTLN((char *)subscriptions[i]->lastread); + + if ((MQTT_PROTOCOL_LEVEL > 3) &&(buffer[0] & 0x6) == 0x2) { + uint8_t ackpacket[4]; + + // Construct and send puback packet. + uint8_t len = pubackPacket(ackpacket, packetid); + if (!sendPacket(ackpacket, len)) + DEBUG_PRINT(F("Failed")); + } + + // return the valid matching subscription + return subscriptions[i]; +} + +void Adafruit_MQTT::flushIncoming(uint16_t timeout) { + // flush input! + DEBUG_PRINTLN(F("Flushing input buffer")); + while (readPacket(buffer, MAXBUFFERSIZE, timeout)); +} + +bool Adafruit_MQTT::ping(uint8_t num) { + //flushIncoming(100); + + while (num--) { + // Construct and send ping packet. + uint8_t len = pingPacket(buffer); + if (!sendPacket(buffer, len)) + continue; + + // Process ping reply. + len = processPacketsUntil(buffer, MQTT_CTRL_PINGRESP, PING_TIMEOUT_MS); + if (buffer[0] == (MQTT_CTRL_PINGRESP << 4)) + return true; + } + + return false; +} + +// Packet Generation Functions ///////////////////////////////////////////////// + +// The current MQTT spec is 3.1.1 and available here: +// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028 +// However this connect packet and code follows the MQTT 3.1 spec here (some +// small differences in the protocol): +// http://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect +uint8_t Adafruit_MQTT::connectPacket(uint8_t *packet) { + uint8_t *p = packet; + uint16_t len; + + // fixed header, connection messsage no flags + p[0] = (MQTT_CTRL_CONNECT << 4) | 0x0; + p+=2; + // fill in packet[1] last + +#if MQTT_PROTOCOL_LEVEL == 3 + p = stringprint(p, "MQIsdp"); +#elif MQTT_PROTOCOL_LEVEL == 4 + p = stringprint(p, "MQTT"); +#else + #error "MQTT level not supported" +#endif + + p[0] = MQTT_PROTOCOL_LEVEL; + p++; + + // always clean the session + p[0] = MQTT_CONN_CLEANSESSION; + + // set the will flags if needed + if (will_topic && pgm_read_byte(will_topic) != 0) { + + p[0] |= MQTT_CONN_WILLFLAG; + + if(will_qos == 1) + p[0] |= MQTT_CONN_WILLQOS_1; + else if(will_qos == 2) + p[0] |= MQTT_CONN_WILLQOS_2; + + if(will_retain == 1) + p[0] |= MQTT_CONN_WILLRETAIN; + + } + + if (pgm_read_byte(username) != 0) + p[0] |= MQTT_CONN_USERNAMEFLAG; + if (pgm_read_byte(password) != 0) + p[0] |= MQTT_CONN_PASSWORDFLAG; + p++; + + p[0] = MQTT_CONN_KEEPALIVE >> 8; + p++; + p[0] = MQTT_CONN_KEEPALIVE & 0xFF; + p++; + + if(MQTT_PROTOCOL_LEVEL == 3) { + p = stringprint(p, clientid, 23); // Limit client ID to first 23 characters. + } else { + if (pgm_read_byte(clientid) != 0) { + p = stringprint(p, clientid); + } else { + p[0] = 0x0; + p++; + p[0] = 0x0; + p++; + DEBUG_PRINTLN(F("SERVER GENERATING CLIENT ID")); + } + } + + if (will_topic && pgm_read_byte(will_topic) != 0) { + p = stringprint(p, will_topic); + p = stringprint(p, will_payload); + } + + if (pgm_read_byte(username) != 0) { + p = stringprint(p, username); + } + if (pgm_read_byte(password) != 0) { + p = stringprint(p, password); + } + + len = p - packet; + + packet[1] = len-2; // don't include the 2 bytes of fixed header data + DEBUG_PRINTLN(F("MQTT connect packet:")); + DEBUG_PRINTBUFFER(buffer, len); + return len; +} + + +// as per http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718040 +uint16_t Adafruit_MQTT::publishPacket(uint8_t *packet, const char *topic, + uint8_t *data, uint16_t bLen, uint8_t qos) { + uint8_t *p = packet; + uint16_t len=0; + + // calc length of non-header data + len += 2; // two bytes to set the topic size + len += strlen(topic); // topic length + if(qos > 0) { + len += 2; // qos packet id + } + len += bLen; // payload length + + // Now you can start generating the packet! + p[0] = MQTT_CTRL_PUBLISH << 4 | qos << 1; + p++; + + // fill in packet[1] last + do { + uint8_t encodedByte = len % 128; + len /= 128; + // if there are more data to encode, set the top bit of this byte + if ( len > 0 ) { + encodedByte |= 0x80; + } + p[0] = encodedByte; + p++; + } while ( len > 0 ); + + // topic comes before packet identifier + p = stringprint(p, topic); + + // add packet identifier. used for checking PUBACK in QOS > 0 + if(qos > 0) { + p[0] = (packet_id_counter >> 8) & 0xFF; + p[1] = packet_id_counter & 0xFF; + p+=2; + + // increment the packet id + packet_id_counter++; + } + + memmove(p, data, bLen); + p+= bLen; + len = p - packet; + DEBUG_PRINTLN(F("MQTT publish packet:")); + DEBUG_PRINTBUFFER(buffer, len); + return len; +} + +uint8_t Adafruit_MQTT::subscribePacket(uint8_t *packet, const char *topic, + uint8_t qos) { + uint8_t *p = packet; + uint16_t len; + + p[0] = MQTT_CTRL_SUBSCRIBE << 4 | MQTT_QOS_1 << 1; + // fill in packet[1] last + p+=2; + + // packet identifier. used for checking SUBACK + p[0] = (packet_id_counter >> 8) & 0xFF; + p[1] = packet_id_counter & 0xFF; + p+=2; + + // increment the packet id + packet_id_counter++; + + p = stringprint(p, topic); + + p[0] = qos; + p++; + + len = p - packet; + packet[1] = len-2; // don't include the 2 bytes of fixed header data + DEBUG_PRINTLN(F("MQTT subscription packet:")); + DEBUG_PRINTBUFFER(buffer, len); + return len; +} + + + +uint8_t Adafruit_MQTT::unsubscribePacket(uint8_t *packet, const char *topic) { + + uint8_t *p = packet; + uint16_t len; + + p[0] = MQTT_CTRL_UNSUBSCRIBE << 4 | 0x1; + // fill in packet[1] last + p+=2; + + // packet identifier. used for checking UNSUBACK + p[0] = (packet_id_counter >> 8) & 0xFF; + p[1] = packet_id_counter & 0xFF; + p+=2; + + // increment the packet id + packet_id_counter++; + + p = stringprint(p, topic); + + len = p - packet; + packet[1] = len-2; // don't include the 2 bytes of fixed header data + DEBUG_PRINTLN(F("MQTT unsubscription packet:")); + DEBUG_PRINTBUFFER(buffer, len); + return len; + +} + +uint8_t Adafruit_MQTT::pingPacket(uint8_t *packet) { + packet[0] = MQTT_CTRL_PINGREQ << 4; + packet[1] = 0; + DEBUG_PRINTLN(F("MQTT ping packet:")); + DEBUG_PRINTBUFFER(buffer, 2); + return 2; +} + +uint8_t Adafruit_MQTT::pubackPacket(uint8_t *packet, uint16_t packetid) { + packet[0] = MQTT_CTRL_PUBACK << 4; + packet[1] = 2; + packet[2] = packetid >> 8; + packet[3] = packetid; + DEBUG_PRINTLN(F("MQTT puback packet:")); + DEBUG_PRINTBUFFER(buffer, 4); + return 4; +} + +uint8_t Adafruit_MQTT::disconnectPacket(uint8_t *packet) { + packet[0] = MQTT_CTRL_DISCONNECT << 4; + packet[1] = 0; + DEBUG_PRINTLN(F("MQTT disconnect packet:")); + DEBUG_PRINTBUFFER(buffer, 2); + return 2; +} + +// Adafruit_MQTT_Publish Definition //////////////////////////////////////////// + +Adafruit_MQTT_Publish::Adafruit_MQTT_Publish(Adafruit_MQTT *mqttserver, + const char *feed, uint8_t q) { + mqtt = mqttserver; + topic = feed; + qos = q; +} +bool Adafruit_MQTT_Publish::publish(int32_t i) { + char payload[12]; + ltoa(i, payload, 10); + return mqtt->publish(topic, payload, qos); +} + +bool Adafruit_MQTT_Publish::publish(uint32_t i) { + char payload[11]; + ultoa(i, payload, 10); + return mqtt->publish(topic, payload, qos); +} + +bool Adafruit_MQTT_Publish::publish(double f, uint8_t precision) { + char payload[41]; // Need to technically hold float max, 39 digits and minus sign. + dtostrf(f, 0, precision, payload); + return mqtt->publish(topic, payload, qos); +} + +bool Adafruit_MQTT_Publish::publish(const char *payload) { + return mqtt->publish(topic, payload, qos); +} + +//publish buffer of arbitrary length +bool Adafruit_MQTT_Publish::publish(uint8_t *payload, uint16_t bLen) { + + return mqtt->publish(topic, payload, bLen, qos); +} + + +// Adafruit_MQTT_Subscribe Definition ////////////////////////////////////////// + +Adafruit_MQTT_Subscribe::Adafruit_MQTT_Subscribe(Adafruit_MQTT *mqttserver, + const char *feed, uint8_t q) { + mqtt = mqttserver; + topic = feed; + qos = q; + datalen = 0; + callback_uint32t = 0; + callback_buffer = 0; + callback_double = 0; + callback_io = 0; + io_mqtt = 0; +} + +void Adafruit_MQTT_Subscribe::setCallback(SubscribeCallbackUInt32Type cb) { + callback_uint32t = cb; +} + +void Adafruit_MQTT_Subscribe::setCallback(SubscribeCallbackDoubleType cb) { + callback_double = cb; +} + +void Adafruit_MQTT_Subscribe::setCallback(SubscribeCallbackBufferType cb) { + callback_buffer = cb; +} + +void Adafruit_MQTT_Subscribe::setCallback(AdafruitIO_MQTT *io, SubscribeCallbackIOType cb) { + callback_io = cb; + io_mqtt= io; +} + +void Adafruit_MQTT_Subscribe::removeCallback(void) { + callback_uint32t = 0; + callback_buffer = 0; + callback_double = 0; + callback_io = 0; + io_mqtt = 0; +} diff --git a/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.h b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.h new file mode 100644 index 0000000..dc229df --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT.h @@ -0,0 +1,302 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 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. +#ifndef _ADAFRUIT_MQTT_H_ +#define _ADAFRUIT_MQTT_H_ + +#include "Arduino.h" + +#if defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_STM32_FEATHER) +#define strncpy_P(dest, src, len) strncpy((dest), (src), (len)) +#define strncasecmp_P(f1, f2, len) strncasecmp((f1), (f2), (len)) +#endif + +#define ADAFRUIT_MQTT_VERSION_MAJOR 0 +#define ADAFRUIT_MQTT_VERSION_MINOR 17 +#define ADAFRUIT_MQTT_VERSION_PATCH 0 + +// Uncomment/comment to turn on/off debug output messages. +//#define MQTT_DEBUG +// Uncomment/comment to turn on/off error output messages. +#define MQTT_ERROR + +// Set where debug messages will be printed. +#define DEBUG_PRINTER Serial +// If using something like Zero or Due, change the above to SerialUSB + +// Define actual debug output functions when necessary. +#ifdef MQTT_DEBUG + #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); } + #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); } + #define DEBUG_PRINTBUFFER(buffer, len) { printBuffer(buffer, len); } +#else + #define DEBUG_PRINT(...) {} + #define DEBUG_PRINTLN(...) {} + #define DEBUG_PRINTBUFFER(buffer, len) {} +#endif + +#ifdef MQTT_ERROR + #define ERROR_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); } + #define ERROR_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); } + #define ERROR_PRINTBUFFER(buffer, len) { printBuffer(buffer, len); } +#else + #define ERROR_PRINT(...) {} + #define ERROR_PRINTLN(...) {} + #define ERROR_PRINTBUFFER(buffer, len) {} +#endif + +// Use 3 (MQTT 3.0) or 4 (MQTT 3.1.1) +#define MQTT_PROTOCOL_LEVEL 4 + +#define MQTT_CTRL_CONNECT 0x1 +#define MQTT_CTRL_CONNECTACK 0x2 +#define MQTT_CTRL_PUBLISH 0x3 +#define MQTT_CTRL_PUBACK 0x4 +#define MQTT_CTRL_PUBREC 0x5 +#define MQTT_CTRL_PUBREL 0x6 +#define MQTT_CTRL_PUBCOMP 0x7 +#define MQTT_CTRL_SUBSCRIBE 0x8 +#define MQTT_CTRL_SUBACK 0x9 +#define MQTT_CTRL_UNSUBSCRIBE 0xA +#define MQTT_CTRL_UNSUBACK 0xB +#define MQTT_CTRL_PINGREQ 0xC +#define MQTT_CTRL_PINGRESP 0xD +#define MQTT_CTRL_DISCONNECT 0xE + +#define MQTT_QOS_1 0x1 +#define MQTT_QOS_0 0x0 + +#define CONNECT_TIMEOUT_MS 6000 +#define PUBLISH_TIMEOUT_MS 500 +#define PING_TIMEOUT_MS 500 +#define SUBACK_TIMEOUT_MS 500 + +// Adjust as necessary, in seconds. Default to 5 minutes. +#define MQTT_CONN_KEEPALIVE 300 + +// Largest full packet we're able to send. +// Need to be able to store at least ~90 chars for a connect packet with full +// 23 char client ID. +#define MAXBUFFERSIZE (150) + +#define MQTT_CONN_USERNAMEFLAG 0x80 +#define MQTT_CONN_PASSWORDFLAG 0x40 +#define MQTT_CONN_WILLRETAIN 0x20 +#define MQTT_CONN_WILLQOS_1 0x08 +#define MQTT_CONN_WILLQOS_2 0x18 +#define MQTT_CONN_WILLFLAG 0x04 +#define MQTT_CONN_CLEANSESSION 0x02 + +// how much data we save in a subscription object +// and how many subscriptions we want to be able to track. +#if defined (__AVR_ATmega32U4__) || defined(__AVR_ATmega328P__) + #define MAXSUBSCRIPTIONS 5 + #define SUBSCRIPTIONDATALEN 20 +#else + #define MAXSUBSCRIPTIONS 15 + #define SUBSCRIPTIONDATALEN 100 +#endif + +class AdafruitIO_MQTT; // forward decl + +//Function pointer that returns an int +typedef void (*SubscribeCallbackUInt32Type)(uint32_t); +// returns a double +typedef void (*SubscribeCallbackDoubleType)(double); +// returns a chunk of raw data +typedef void (*SubscribeCallbackBufferType)(char *str, uint16_t len); +// returns an io data wrapper instance +typedef void (AdafruitIO_MQTT::*SubscribeCallbackIOType)(char *str, uint16_t len); + +extern void printBuffer(uint8_t *buffer, uint16_t len); + +class Adafruit_MQTT_Subscribe; // forward decl + +class Adafruit_MQTT { + public: + Adafruit_MQTT(const char *server, + uint16_t port, + const char *cid, + const char *user, + const char *pass); + + Adafruit_MQTT(const char *server, + uint16_t port, + const char *user = "", + const char *pass = ""); + virtual ~Adafruit_MQTT() {} + + // Connect to the MQTT server. Returns 0 on success, otherwise an error code + // that indicates something went wrong: + // -1 = Error connecting to server + // 1 = Wrong protocol + // 2 = ID rejected + // 3 = Server unavailable + // 4 = Bad username or password + // 5 = Not authenticated + // 6 = Failed to subscribe + // Use connectErrorString() to get a printable string version of the + // error. + int8_t connect(); + int8_t connect(const char *user, const char *pass); + + // Return a printable string version of the error code returned by + // connect(). This returns a __FlashStringHelper*, which points to a + // string stored in flash, but can be directly passed to e.g. + // Serial.println without any further processing. + const __FlashStringHelper* connectErrorString(int8_t code); + + // Sends MQTT disconnect packet and calls disconnectServer() + bool disconnect(); + + // Return true if connected to the MQTT server, otherwise false. + virtual bool connected() = 0; // Subclasses need to fill this in! + + // Set MQTT last will topic, payload, QOS, and retain. This needs + // to be called before connect() because it is sent as part of the + // connect control packet. + bool will(const char *topic, const char *payload, uint8_t qos = 0, uint8_t retain = 0); + + // Publish a message to a topic using the specified QoS level. Returns true + // if the message was published, false otherwise. + bool publish(const char *topic, const char *payload, uint8_t qos = 0); + bool publish(const char *topic, uint8_t *payload, uint16_t bLen, uint8_t qos = 0); + + // Add a subscription to receive messages for a topic. Returns true if the + // subscription could be added or was already present, false otherwise. + // Must be called before connect(), subscribing after the connection + // is made is not currently supported. + bool subscribe(Adafruit_MQTT_Subscribe *sub); + + // Unsubscribe from a previously subscribed MQTT topic. + bool unsubscribe(Adafruit_MQTT_Subscribe *sub); + + // Check if any subscriptions have new messages. Will return a reference to + // an Adafruit_MQTT_Subscribe object which has a new message. Should be called + // in the sketch's loop function to ensure new messages are recevied. Note + // that subscribe should be called first for each topic that receives messages! + Adafruit_MQTT_Subscribe *readSubscription(int16_t timeout=0); + + void processPackets(int16_t timeout); + + // Ping the server to ensure the connection is still alive. + bool ping(uint8_t n = 1); + + protected: + // Interface that subclasses need to implement: + + // Connect to the server and return true if successful, false otherwise. + virtual bool connectServer() = 0; + + // Disconnect from the MQTT server. Returns true if disconnected, false otherwise. + virtual bool disconnectServer() = 0; // Subclasses need to fill this in! + + // Send data to the server specified by the buffer and length of data. + virtual bool sendPacket(uint8_t *buffer, uint16_t len) = 0; + + // Read MQTT packet from the server. Will read up to maxlen bytes and store + // the data in the provided buffer. Waits up to the specified timeout (in + // milliseconds) for data to be available. + virtual uint16_t readPacket(uint8_t *buffer, uint16_t maxlen, int16_t timeout) = 0; + + // Read a full packet, keeping note of the correct length + uint16_t readFullPacket(uint8_t *buffer, uint16_t maxsize, uint16_t timeout); + // Properly process packets until you get to one you want + uint16_t processPacketsUntil(uint8_t *buffer, uint8_t waitforpackettype, uint16_t timeout); + + // Shared state that subclasses can use: + const char *servername; + int16_t portnum; + const char *clientid; + const char *username; + const char *password; + const char *will_topic; + const char *will_payload; + uint8_t will_qos; + uint8_t will_retain; + uint8_t buffer[MAXBUFFERSIZE]; // one buffer, used for all incoming/outgoing + uint16_t packet_id_counter; + + private: + Adafruit_MQTT_Subscribe *subscriptions[MAXSUBSCRIPTIONS]; + + void flushIncoming(uint16_t timeout); + + // Functions to generate MQTT packets. + uint8_t connectPacket(uint8_t *packet); + uint8_t disconnectPacket(uint8_t *packet); + uint16_t publishPacket(uint8_t *packet, const char *topic, uint8_t *payload, uint16_t bLen, uint8_t qos); + uint8_t subscribePacket(uint8_t *packet, const char *topic, uint8_t qos); + uint8_t unsubscribePacket(uint8_t *packet, const char *topic); + uint8_t pingPacket(uint8_t *packet); + uint8_t pubackPacket(uint8_t *packet, uint16_t packetid); +}; + + +class Adafruit_MQTT_Publish { + public: + Adafruit_MQTT_Publish(Adafruit_MQTT *mqttserver, const char *feed, uint8_t qos = 0); + + bool publish(const char *s); + bool publish(double f, uint8_t precision=2); // Precision controls the minimum number of digits after decimal. + // This might be ignored and a higher precision value sent. + bool publish(int32_t i); + bool publish(uint32_t i); + bool publish(uint8_t *b, uint16_t bLen); + + +private: + Adafruit_MQTT *mqtt; + const char *topic; + uint8_t qos; +}; + +class Adafruit_MQTT_Subscribe { + public: + Adafruit_MQTT_Subscribe(Adafruit_MQTT *mqttserver, const char *feedname, uint8_t q=0); + + void setCallback(SubscribeCallbackUInt32Type callb); + void setCallback(SubscribeCallbackDoubleType callb); + void setCallback(SubscribeCallbackBufferType callb); + void setCallback(AdafruitIO_MQTT *io, SubscribeCallbackIOType callb); + void removeCallback(void); + + const char *topic; + uint8_t qos; + + uint8_t lastread[SUBSCRIPTIONDATALEN]; + // Number valid bytes in lastread. Limited to SUBSCRIPTIONDATALEN-1 to + // ensure nul terminating lastread. + uint16_t datalen; + + SubscribeCallbackUInt32Type callback_uint32t; + SubscribeCallbackDoubleType callback_double; + SubscribeCallbackBufferType callback_buffer; + SubscribeCallbackIOType callback_io; + + AdafruitIO_MQTT *io_mqtt; + + private: + Adafruit_MQTT *mqtt; +}; + + +#endif diff --git a/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.cpp b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.cpp new file mode 100644 index 0000000..0a4312e --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.cpp @@ -0,0 +1,106 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 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. +#include "Adafruit_MQTT_Client.h" + + +bool Adafruit_MQTT_Client::connectServer() { + // Grab server name from flash and copy to buffer for name resolution. + memset(buffer, 0, sizeof(buffer)); + strcpy((char *)buffer, servername); + DEBUG_PRINT(F("Connecting to: ")); DEBUG_PRINTLN((char *)buffer); + // Connect and check for success (0 result). + int r = client->connect((char *)buffer, portnum); + DEBUG_PRINT(F("Connect result: ")); DEBUG_PRINTLN(r); + return r != 0; +} + +bool Adafruit_MQTT_Client::disconnectServer() { + // Stop connection if connected and return success (stop has no indication of + // failure). + if (client->connected()) { + client->stop(); + } + return true; +} + +bool Adafruit_MQTT_Client::connected() { + // Return true if connected, false if not connected. + return client->connected(); +} + +uint16_t Adafruit_MQTT_Client::readPacket(uint8_t *buffer, uint16_t maxlen, + int16_t timeout) { + /* Read data until either the connection is closed, or the idle timeout is reached. */ + uint16_t len = 0; + int16_t t = timeout; + + + while (client->connected() && (timeout >= 0)) { + //DEBUG_PRINT('.'); + while (client->available()) { + //DEBUG_PRINT('!'); + char c = client->read(); + timeout = t; // reset the timeout + buffer[len] = c; + //DEBUG_PRINTLN((uint8_t)c, HEX); + len++; + + if (maxlen == 0) { // handle zero-length packets + return 0; + } + + if (len == maxlen) { // we read all we want, bail + DEBUG_PRINT(F("Read data:\t")); + DEBUG_PRINTBUFFER(buffer, len); + return len; + } + } + timeout -= MQTT_CLIENT_READINTERVAL_MS; + delay(MQTT_CLIENT_READINTERVAL_MS); + } + return len; +} + +bool Adafruit_MQTT_Client::sendPacket(uint8_t *buffer, uint16_t len) { + uint16_t ret = 0; + + while (len > 0) { + if (client->connected()) { + // send 250 bytes at most at a time, can adjust this later based on Client + + uint16_t sendlen = len > 250 ? 250 : len; + //Serial.print("Sending: "); Serial.println(sendlen); + ret = client->write(buffer, sendlen); + DEBUG_PRINT(F("Client sendPacket returned: ")); DEBUG_PRINTLN(ret); + len -= ret; + + if (ret != sendlen) { + DEBUG_PRINTLN("Failed to send packet."); + return false; + } + } else { + DEBUG_PRINTLN(F("Connection failed!")); + return false; + } + } + return true; +} diff --git a/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.h b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.h new file mode 100644 index 0000000..12fdac5 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_Client.h @@ -0,0 +1,61 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 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. +#ifndef _ADAFRUIT_MQTT_CLIENT_H_ +#define _ADAFRUIT_MQTT_CLIENT_H_ + +#include "Client.h" +#include "Adafruit_MQTT.h" + + +// How long to delay waiting for new data to be available in readPacket. +#define MQTT_CLIENT_READINTERVAL_MS 10 + + +// MQTT client implementation for a generic Arduino Client interface. Can work +// with almost all Arduino network hardware like ethernet shield, wifi shield, +// and even other platforms like ESP8266. +class Adafruit_MQTT_Client : public Adafruit_MQTT { + public: + Adafruit_MQTT_Client(Client *client, const char *server, uint16_t port, + const char *cid, const char *user, const char *pass): + Adafruit_MQTT(server, port, cid, user, pass), + client(client) + {} + + Adafruit_MQTT_Client(Client *client, const char *server, uint16_t port, + const char *user="", const char *pass=""): + Adafruit_MQTT(server, port, user, pass), + client(client) + {} + + bool connectServer(); + bool disconnectServer(); + bool connected(); + uint16_t readPacket(uint8_t *buffer, uint16_t maxlen, int16_t timeout); + bool sendPacket(uint8_t *buffer, uint16_t len); + + private: + Client* client; +}; + + +#endif diff --git a/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_FONA.h b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_FONA.h new file mode 100644 index 0000000..e2af5a5 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/Adafruit_MQTT_FONA.h @@ -0,0 +1,142 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 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. +#ifndef _ADAFRUIT_MQTT_FONA_H_ +#define _ADAFRUIT_MQTT_FONA_H_ + +#include +#include "Adafruit_MQTT.h" + +#define MQTT_FONA_INTERAVAILDELAY 100 +#define MQTT_FONA_QUERYDELAY 500 + + +// FONA-specific version of the Adafruit_MQTT class. +// Note that this is defined as a header-only class to prevent issues with using +// the library on non-FONA platforms (since Arduino will include all .cpp files +// in the compilation of the library). +class Adafruit_MQTT_FONA : public Adafruit_MQTT { + public: + Adafruit_MQTT_FONA(Adafruit_FONA *f, const char *server, uint16_t port, + const char *cid, const char *user, const char *pass): + Adafruit_MQTT(server, port, cid, user, pass), + fona(f) + {} + + Adafruit_MQTT_FONA(Adafruit_FONA *f, const char *server, uint16_t port, + const char *user="", const char *pass=""): + Adafruit_MQTT(server, port, user, pass), + fona(f) + {} + + bool connectServer() { + char server[40]; + strncpy(server, servername, 40); +#ifdef ADAFRUIT_SLEEPYDOG_H + Watchdog.reset(); +#endif + + // connect to server + DEBUG_PRINTLN(F("Connecting to TCP")); + return fona->TCPconnect(server, portnum); + } + + bool disconnectServer() { + return fona->TCPclose(); + } + + bool connected() { + // Return true if connected, false if not connected. + return fona->TCPconnected(); + } + + uint16_t readPacket(uint8_t *buffer, uint16_t maxlen, int16_t timeout) { + uint8_t *buffp = buffer; + DEBUG_PRINTLN(F("Reading data..")); + + if (!fona->TCPconnected()) return 0; + + + /* Read data until either the connection is closed, or the idle timeout is reached. */ + uint16_t len = 0; + int16_t t = timeout; + uint16_t avail; + + while (fona->TCPconnected() && (timeout >= 0)) { + //DEBUG_PRINT('.'); + while (avail = fona->TCPavailable()) { + //DEBUG_PRINT('!'); + + if (len + avail > maxlen) { + avail = maxlen - len; + if (avail == 0) return len; + } + + // try to read the data into the end of the pointer + if (! fona->TCPread(buffp, avail)) return len; + + // read it! advance pointer + buffp += avail; + len += avail; + timeout = t; // reset the timeout + + //DEBUG_PRINTLN((uint8_t)c, HEX); + + if (len == maxlen) { // we read all we want, bail + DEBUG_PRINT(F("Read:\t")); + DEBUG_PRINTBUFFER(buffer, len); + return len; + } + } +#ifdef ADAFRUIT_SLEEPYDOG_H + Watchdog.reset(); +#endif + timeout -= MQTT_FONA_INTERAVAILDELAY; + timeout -= MQTT_FONA_QUERYDELAY; // this is how long it takes to query the FONA for avail() + delay(MQTT_FONA_INTERAVAILDELAY); + } + + return len; + } + + bool sendPacket(uint8_t *buffer, uint16_t len) { + DEBUG_PRINTLN(F("Writing packet")); + if (fona->TCPconnected()) { + boolean ret = fona->TCPsend((char *)buffer, len); + //DEBUG_PRINT(F("sendPacket returned: ")); DEBUG_PRINTLN(ret); + if (!ret) { + DEBUG_PRINTLN("Failed to send packet."); + return false; + } + } else { + DEBUG_PRINTLN(F("Connection failed!")); + return false; + } + return true; + } + + private: + uint32_t serverip; + Adafruit_FONA *fona; +}; + + +#endif diff --git a/libraries/Adafruit_MQTT_Library/LICENSE b/libraries/Adafruit_MQTT_Library/LICENSE new file mode 100644 index 0000000..04ebb92 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 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. + diff --git a/libraries/Adafruit_MQTT_Library/README.md b/libraries/Adafruit_MQTT_Library/README.md new file mode 100644 index 0000000..8c2e56d --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/README.md @@ -0,0 +1,57 @@ +# Adafruit MQTT Library [![Build Status](https://travis-ci.com/adafruit/Adafruit_MQTT_Library.svg?branch=master)](https://travis-ci.com/adafruit/Adafruit_MQTT_Library) + +Arduino library for MQTT support, including access to Adafruit IO. Works with +the Adafruit FONA, Arduino Yun, ESP8266 Arduino platforms, and anything that supports +Arduino's Client interface (like Ethernet shield). + +See included examples for how to use the library to access an MQTT service to +publish and subscribe to feeds. Note that this does not support the full MQTT +spec but is intended to support enough for QoS 0 and 1 publishing. + +Depends on the following other libraries depending on the target platform: + + - [Adafruit SleepyDog](https://github.com/adafruit/Adafruit_SleepyDog), watchdog + library used by FONA code for reliability. + + - [Adafruit FONA](https://github.com/adafruit/Adafruit_FONA_Library), required for + the FONA hardware. + +Future todos: + + - Subscription callbacks + + - remove watchdog + + + +## Compatibility + +MCU | Tested Works | Doesn't Work | Not Tested | Notes +------------------ | :----------: | :----------: | :---------: | ----- +Atmega328 @ 16MHz | | | X | +Atmega328 @ 12MHz | | | X | +Atmega32u4 @ 16MHz | | | X | +Atmega32u4 @ 8MHz | | | X | +ESP8266 | | | X | +Atmega2560 @ 16MHz | | | X | +ATSAM3X8E | | | X | +ATSAM21D | | | X | +ATSAMD51J20 | | | X | +ATtiny85 @ 16MHz | | | X | +ATtiny85 @ 8MHz | | | X | +Intel Curie @ 32MHz | | | X | +STM32F2 | | | X | + + * ATmega328 @ 16MHz : Arduino UNO, Adafruit Pro Trinket 5V, Adafruit Metro 328, Adafruit Metro Mini + * ATmega328 @ 12MHz : Adafruit Pro Trinket 3V + * ATmega32u4 @ 16MHz : Arduino Leonardo, Arduino Micro, Arduino Yun, Teensy 2.0 + * ATmega32u4 @ 8MHz : Adafruit Flora, Bluefruit Micro + * ESP8266 : Adafruit Huzzah + * ATmega2560 @ 16MHz : Arduino Mega + * ATSAM3X8E : Arduino Due + * ATSAM21D : Arduino Zero, M0 Pro + * ATSAMD51J20: Adafruit PyPortal + * ATtiny85 @ 16MHz : Adafruit Trinket 5V + * ATtiny85 @ 8MHz : Adafruit Gemma, Arduino Gemma, Adafruit Trinket 3V + + diff --git a/libraries/Adafruit_MQTT_Library/examples/adafruitio_anon_time_esp8266/adafruitio_anon_time_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/adafruitio_anon_time_esp8266/adafruitio_anon_time_esp8266.ino new file mode 100644 index 0000000..8175895 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/adafruitio_anon_time_esp8266/adafruitio_anon_time_esp8266.ino @@ -0,0 +1,122 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Adafruit IO Anonymous Time Query + + Must use the latest version of ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ +#define WLAN_SSID "network" +#define WLAN_PASS "password" + +/************************* Adafruit.io Setup *********************************/ +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 8883 + +WiFiClientSecure client; +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT); + +Adafruit_MQTT_Subscribe timefeed = Adafruit_MQTT_Subscribe(&mqtt, "time/seconds"); + +// set timezone offset from UTC +int timeZone = -4; // UTC - 4 eastern daylight time (nyc) +int interval = 4; // trigger every X hours + +int last_min = -1; + +void timecallback(uint32_t current) { + // adjust to local time zone + current += (timeZone * 60 * 60); + int curr_hour = (current / 60 / 60) % 24; + int curr_min = (current / 60 ) % 60; + int curr_sec = (current) % 60; + + Serial.print("Time: "); + Serial.print(curr_hour); Serial.print(':'); + Serial.print(curr_min); Serial.print(':'); + Serial.println(curr_sec); + + // only trigger on minute change + if(curr_min != last_min) { + last_min = curr_min; + + Serial.println("This will print out every minute!"); + } +} + +void setup() { + + Serial.begin(115200); + delay(10); + + Serial.print(F("\nAdafruit IO anonymous Time Demo")); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(F(" WiFi connected.")); + + timefeed.setCallback(timecallback); + mqtt.subscribe(&timefeed); + +} + +void loop() { + + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // wait 10 seconds for subscription messages + // since we have no other tasks in this example. + mqtt.processPackets(10000); + + // keep the connection alive + mqtt.ping(); + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + + Serial.println("MQTT Connected!"); +} \ No newline at end of file diff --git a/libraries/Adafruit_MQTT_Library/examples/adafruitio_errors_esp8266/adafruitio_errors_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/adafruitio_errors_esp8266/adafruitio_errors_esp8266.ino new file mode 100644 index 0000000..3b7dd0b --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/adafruitio_errors_esp8266/adafruitio_errors_esp8266.ino @@ -0,0 +1,155 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Example + + Must use ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + Error examples by Todd Treece for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "...your SSID..." +#define WLAN_PASS "...your password..." + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 // 8883 for MQTTS +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + +/************ Global State (you don't need to change this!) ******************/ + +// Create an ESP8266 WiFiClient class to connect to the MQTT server. +WiFiClient client; +// or... use WiFiFlientSecure for SSL +//WiFiClientSecure client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Error Reporting *********************************/ + +Adafruit_MQTT_Subscribe errors = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/errors"); +Adafruit_MQTT_Subscribe throttle = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/throttle"); + +/*************************** Sketch Code ************************************/ + +// Bug workaround for Arduino 1.6.6, it seems to need a function declaration +// for some reason (only affects ESP8266, likely an arduino-builder bug). +void MQTT_connect(); + +void setup() { + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit MQTT demo")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print("Connecting to "); + Serial.println(WLAN_SSID); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + Serial.println("WiFi connected"); + Serial.println("IP address: "); Serial.println(WiFi.localIP()); + + // Setup MQTT subscription for onoff feed + mqtt.subscribe(&onoffbutton); + + // Setup MQTT subscriptions for throttle & error messages + mqtt.subscribe(&throttle); + mqtt.subscribe(&errors); + +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + // try to spend your time here + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(5000))) { + if (subscription == &onoffbutton) { + Serial.print(F("Got onoff: ")); + Serial.println((char *)onoffbutton.lastread); + } else if(subscription == &errors) { + Serial.print(F("ERROR: ")); + Serial.println((char *)errors.lastread); + } else if(subscription == &throttle) { + Serial.println((char *)throttle.lastread); + } + } + + // Now we can publish stuff! + Serial.print(F("\nSending photocell val ")); + Serial.print(x); + Serial.print("..."); + if (! photocell.publish(x++)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_airlift/adafruitio_secure_airlift.ino b/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_airlift/adafruitio_secure_airlift.ino new file mode 100644 index 0000000..f92f58c --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_airlift/adafruitio_secure_airlift.ino @@ -0,0 +1,183 @@ +/**************************************************************** + Adafruit MQTT Library, Adafruit IO SSL/TLS Example for AirLift + + Must use the latest version of nina-fw from: + https://github.com/adafruit/nina-fw + + Works great with Adafruit AirLift ESP32 Co-Processors! + --> https://www.adafruit.com/product/4201 + --> https://www.adafruit.com/product/4116 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Brent Rubell for Adafruit Industries. + MIT license, all text above must be included in any redistribution + *******************************************************************/ + +#include +#include + +// For AirLift Breakout/Wing/Shield: Configure the following to match the ESP32 Pins! +#if !defined(SPIWIFI_SS) + // Don't change the names of these #define's! they match the variant ones + #define SPIWIFI SPI + #define SPIWIFI_SS 11 // Chip select pin + #define SPIWIFI_ACK 10 // a.k.a BUSY or READY pin + #define ESP32_RESETN 9 // Reset pin + #define ESP32_GPIO0 -1 // Not connected + #define SET_PINS 1 // Pins were set using this IFNDEF +#endif + +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "WLAN_SSID" +#define WLAN_PASS "WIFI_PASSWORD" +int keyIndex = 0; // your network key Index number (needed only for WEP) + +int status = WL_IDLE_STATUS; +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +// Using port 8883 for MQTTS +#define AIO_SERVERPORT 8883 +// Adafruit IO Account Configuration +// (to obtain these values, visit https://io.adafruit.com and click on Active Key) +#define AIO_USERNAME "YOUR_ADAFRUIT_IO_USERNAME" +#define AIO_KEY "YOUR_ADAFRUIT_IO_KEY" + +/************ Global State (you don't need to change this!) ******************/ + +// WiFiSSLClient for SSL/TLS support +WiFiSSLClient client; + +// Setup the MQTT client class by WLAN_PASSing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); +/****************************** Feeds ***************************************/ + +// Setup a feed called 'test' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish test = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/test"); + +/*************************** Sketch Code ************************************/ + +void setup() +{ + //Initialize serial and wait for port to open: + Serial.begin(115200); + while (!Serial) + { + ; // wait for serial port to connect. Needed for native USB port only + } + + // if the AirLift's pins were defined above... + #ifdef SET_PINS + WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI); + #endif + + // check for the wifi module + while (WiFi.status() == WL_NO_MODULE) + { + Serial.println("Communication with WiFi module failed!"); + delay(1000); + } + + String fv = WiFi.firmwareVersion(); + if (fv < "1.0.0") + { + Serial.println("Please upgrade the firmware"); + } + + // attempt to connect to Wifi network: + Serial.print("Attempting to connect to SSID: "); + Serial.println(WLAN_SSID); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + do + { + status = WiFi.begin(WLAN_SSID, WLAN_PASS); + delay(100); // wait until connected + } while (status != WL_CONNECTED); + Serial.println("Connected to wifi"); + printWiFiStatus(); +} + +uint32_t x = 0; + +void loop() +{ + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // Now we can publish stuff! + Serial.print(F("\nSending val ")); + Serial.print(x); + Serial.print(F(" to test feed...")); + if (!test.publish(x++)) + { + Serial.println(F("Failed")); + } + else + { + Serial.println(F("OK!")); + } + + // wait a couple seconds to avoid rate limit + delay(2000); +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() +{ + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) + { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) + { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) + { + // basically die and wait for WDT to reset me + while (1) + ; + } + } + + Serial.println("MQTT Connected!"); +} + +void printWiFiStatus() +{ + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your board's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); +} \ No newline at end of file diff --git a/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_esp8266/adafruitio_secure_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_esp8266/adafruitio_secure_esp8266.ino new file mode 100644 index 0000000..323d772 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/adafruitio_secure_esp8266/adafruitio_secure_esp8266.ino @@ -0,0 +1,135 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Adafruit IO SSL/TLS example + + Must use the latest version of ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + SSL/TLS additions by Todd Treece for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "WLAN_SSID" +#define WLAN_PASS "WIFI_PASSWORD" + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +// Using port 8883 for MQTTS +#define AIO_SERVERPORT 8883 +// Adafruit IO Account Configuration +// (to obtain these values, visit https://io.adafruit.com and click on Active Key) +#define AIO_USERNAME "YOUR_ADAFRUIT_IO_USERNAME" +#define AIO_KEY "YOUR_ADAFRUIT_IO_KEY" + +/************ Global State (you don't need to change this!) ******************/ + +// WiFiFlientSecure for SSL/TLS support +WiFiClientSecure client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +// io.adafruit.com SHA1 fingerprint +static const char *fingerprint PROGMEM = "77 00 54 2D DA E7 D8 03 27 31 23 99 EB 27 DB CB A5 4C 57 18"; + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'test' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish test = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/test"); + +/*************************** Sketch Code ************************************/ + +void setup() { + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit IO MQTTS (SSL/TLS) Example")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print("Connecting to "); + Serial.println(WLAN_SSID); + + delay(1000); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + delay(2000); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + Serial.println("WiFi connected"); + Serial.println("IP address: "); Serial.println(WiFi.localIP()); + + // check the fingerprint of io.adafruit.com's SSL cert + client.setFingerprint(fingerprint); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // Now we can publish stuff! + Serial.print(F("\nSending val ")); + Serial.print(x); + Serial.print(F(" to test feed...")); + if (! test.publish(x++)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + + // wait a couple seconds to avoid rate limit + delay(2000); + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/adafruitio_time_esp8266/adafruitio_time_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/adafruitio_time_esp8266/adafruitio_time_esp8266.ino new file mode 100644 index 0000000..44b8f1a --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/adafruitio_time_esp8266/adafruitio_time_esp8266.ino @@ -0,0 +1,122 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Adafruit IO SSL/TLS example + + Must use the latest version of ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + Time additions by Todd Treece for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ +#define WLAN_SSID "network" +#define WLAN_PASS "password" + +/************************* Adafruit.io Setup *********************************/ +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 8883 +#define AIO_USERNAME "user" +#define AIO_KEY "key" + +WiFiClientSecure client; +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_USERNAME, AIO_KEY); + +Adafruit_MQTT_Subscribe timefeed = Adafruit_MQTT_Subscribe(&mqtt, "time/seconds"); + +// set timezone offset from UTC +int timeZone = -4; // UTC - 4 eastern daylight time (nyc) +int interval = 4; // trigger every X hours +int hour = 0; // current hour + +void timecallback(uint32_t current) { + + // stash previous hour + int previous = hour; + + // adjust to local time zone + current += (timeZone * 60 * 60); + + // calculate current hour + hour = (current / 60 / 60) % 24; + + // only trigger on interval + if((hour != previous) && (hour % interval) == 0) { + Serial.println("Run your code here"); + } + +} + +void setup() { + + Serial.begin(115200); + delay(10); + + Serial.print(F("Adafruit IO Time Demo")); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(F(" WiFi connected.")); + + timefeed.setCallback(timecallback); + mqtt.subscribe(&timefeed); + +} + +void loop() { + + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // wait 10 seconds for subscription messages + // since we have no other tasks in this example. + mqtt.processPackets(10000); + + // keep the connection alive + mqtt.ping(); + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_2subs_esp8266/mqtt_2subs_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_2subs_esp8266/mqtt_2subs_esp8266.ino new file mode 100644 index 0000000..90b6b1b --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_2subs_esp8266/mqtt_2subs_esp8266.ino @@ -0,0 +1,157 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Example + + Must use ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +// the on off button feed turns this LED on/off +#define LED 2 +// the slider feed sets the PWM output of this pin +#define PWMOUT 12 + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "...your SSID..." +#define WLAN_PASS "...your password..." + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 // use 8883 for SSL +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + +/************ Global State (you don't need to change this!) ******************/ + +// Create an ESP8266 WiFiClient class to connect to the MQTT server. +WiFiClient client; +// or... use WiFiFlientSecure for SSL +//WiFiClientSecure client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_USERNAME, AIO_KEY); + +/****************************** Feeds ***************************************/ + +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); +Adafruit_MQTT_Subscribe slider = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/slider"); + +/*************************** Sketch Code ************************************/ + +// Bug workaround for Arduino 1.6.6, it seems to need a function declaration +// for some reason (only affects ESP8266, likely an arduino-builder bug). +void MQTT_connect(); + +void setup() { + pinMode(LED, OUTPUT); + pinMode(PWMOUT, OUTPUT); + + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit MQTT demo")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print("Connecting to "); + Serial.println(WLAN_SSID); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + Serial.println("WiFi connected"); + Serial.println("IP address: "); Serial.println(WiFi.localIP()); + + // Setup MQTT subscription for onoff & slider feed. + mqtt.subscribe(&onoffbutton); + mqtt.subscribe(&slider); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + // try to spend your time here + + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(5000))) { + // Check if its the onoff button feed + if (subscription == &onoffbutton) { + Serial.print(F("On-Off button: ")); + Serial.println((char *)onoffbutton.lastread); + + if (strcmp((char *)onoffbutton.lastread, "ON") == 0) { + digitalWrite(LED, LOW); + } + if (strcmp((char *)onoffbutton.lastread, "OFF") == 0) { + digitalWrite(LED, HIGH); + } + } + + // check if its the slider feed + if (subscription == &slider) { + Serial.print(F("Slider: ")); + Serial.println((char *)slider.lastread); + uint16_t sliderval = atoi((char *)slider.lastread); // convert to a number + analogWrite(PWMOUT, sliderval); + } + } + + // ping the server to keep the mqtt connection alive + if(! mqtt.ping()) { + mqtt.disconnect(); + } + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/README.md b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/README.md new file mode 100644 index 0000000..66b765c --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/README.md @@ -0,0 +1,108 @@ +# Adafruit MQTT Library Arbitrary Data Publish Example + +This example illustrates publishing an arbitrary data packet using the Adafruit MQTT library to an MQTT feed which can then be parsed by the included python subscriber client. Possible usage cases include adding metadata (collection time, sensor info etc) to a datapoint. + +![alt-text](https://raw.githubusercontent.com/stuthedew/Adafruit_MQTT_Library/Arbitrary_data_publish/examples/mqtt_arbitrary_data/python_subscriber/mqtt_figure.png "Arbitrary data flow diagram") + +My motivation for this was wanting to be able to include metadata to a post. +Specifically, I was playing around with a [Teviso RD3024 radiation sensor](http://www.teviso.com/en/products/radiation-sensor-rd3024.htm), and a salvaged Americium radiation source from a smoke detector, at varying distances from the sensor. I wanted a way to associate the collection time, and distance between the source and sensor with the actual radiation reading itself. + + +--- + +## Installing and configuring Mosquitto broker (minimal working setup): + +####_Installing on Raspberry Pi/Linux:_ + +```bash +sudo apt-get install mosquitto +cd /etc/mosquitto/ +#See "Configuring Mosquitto Broker below" +``` + +####_Installing On a Mac:_ +```bash +brew install mosquitto +cd /usr/local/etc/mosquitto +#See "Configuring Mosquitto Broker below" +``` + +--- + +####Configuring Mosquitto broker +```bash +sudo nano mosquitto.conf +``` +Now we have to enable a password file to correctly interface with the Adafruit MQTT library. Scroll about two thirds of the way down until you see: + +```bash +# ----------------------------------------------------------------- +# Default authentication and topic access control +# ----------------------------------------------------------------- +``` + +You should see `#password_file` about a paragraph after that. +Change + +```bash +#password_file +``` + +To + +```bash +password_file pwfile +``` + +Now `ctrl-x` to save and exit. + +You're almost done! We just have to create and populate the password file we just configured. The default user info is: +* **Arduino Subscriber:** + * Username: TestUser + * Password: TestUser + +* **Python Subscriber:** + * Username: TestPy + * Password: TestPy + +```bash +touch pwfile #create the password file +mosquitto_passwd pwfile TestUser #Enter and confirm password when prompted +mosquitto_passwd pwfile TestPy #Enter and confirm password when prompted +``` + +####Running Mosquitto broker +Now run Mosquitto broker to allow Arduino publisher and Python subscriber to communicate + +```bash +mosquitto +``` + +--- + +## Using Example Python Subscriber: + +####Installing Python subscriber +Install dependencies if you haven't already +```bash +cd ../Adafruit_MQTT_Library/examples/mqtt_arbitrary_buffer/python_subscriber +pip install -r requirements.txt +``` + + +####Installing Python subscriber +Run python script with default values and watch your parsed data print out. +```bash +python subscriber.py #Add -h flag to see modifiable options +``` + +Assuming that the Mosquitto broker is running in the background and the Adafruit_MQTT client (Arduino) is publishing, you should see the example data print out every 10 seconds. + +```bash +MQTT: Connection successful +Connection successful +Subscribed to /feeds/arb_packet +Received char Array: "Hello!", val1: -4533, val2: 73102, val3: 3354... +Received char Array: "Hello!", val1: -4533, val2: 83611, val3: 3354... +Received char Array: "Hello!", val1: -4533, val2: 94115, val3: 3354... +``` diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/mqtt_arbitrary_data.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/mqtt_arbitrary_data.ino new file mode 100644 index 0000000..0eb8060 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/mqtt_arbitrary_data.ino @@ -0,0 +1,184 @@ +/*************************************************** + Adafruit MQTT Library Arbitrary Data Example + + Must use ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Stuart Feichtinger + Modifed from the mqtt_esp8266 example written by Tony DiCola for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "...your SSID..." +#define WLAN_PASS "...your password..." + +/************************* Adafruit.io Setup *********************************/ + +#define ARB_SERVER "...host computer ip address..." +#define ARB_SERVERPORT 1883 // use 8883 for SSL +#define ARB_USERNAME "TestUser" +#define ARB_PW "TestUser" + + +/************ Global State (you don't need to change this!) ******************/ + +// Create an ESP8266 WiFiClient class to connect to the MQTT server. +WiFiClient client; +// or... use WiFiFlientSecure for SSL +//WiFiClientSecure client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, ARB_SERVER, ARB_SERVERPORT, ARB_USERNAME, ARB_PW); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'arb_packet' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +#define ARB_FEED "/feeds/arb_packet" +Adafruit_MQTT_Publish ap = Adafruit_MQTT_Publish(&mqtt, ARB_FEED); + + +// Arbitrary Payload +// Union allows for easier interaction of members in struct form with easy publishing +// of "raw" bytes +typedef union{ + //Customize struct with whatever variables/types you like. + + struct __attribute__((__packed__)){ // packed to eliminate padding for easier parsing. + char charAry[10]; + int16_t val1; + unsigned long val2; + uint16_t val3; + }s; + + uint8_t raw[sizeof(s)]; // For publishing + + /* + // Alternate Option with anonymous struct, but manual byte count: + + struct __attribute__((__packed__)){ // packed to eliminate padding for easier parsing. + char charAry[10]; // 10 x 1 byte = 10 bytes + int16_t val1; // 1 x 2 bytes = 2 bytes + unsigned long val2; // 1 x 4 bytes = 4 bytes + uint16_t val3; // 1 x 2 bytes = 2 bytes + ------------------- + TOTAL = 18 bytes + }; + uint8_t raw[18]; // For publishing +*/ + +} packet_t; + +/*************************** Sketch Code ************************************/ + +// Bug workaround for Arduino 1.6.6, it seems to need a function declaration +// for some reason (only affects ESP8266, likely an arduino-builder bug). +void MQTT_connect(); + +void setup() { + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit MQTT demo")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print(F("Connecting to ")); + Serial.println(WLAN_SSID); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print(F(".")); + } + Serial.println(); + + Serial.println(F("WiFi connected")); + Serial.println(F("IP address: ")); Serial.println(WiFi.localIP()); + +} + +packet_t arbPac; + +const char strVal[] PROGMEM = "Hello!"; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + //Update arbitrary packet values + strcpy_P(arbPac.s.charAry, strVal); + arbPac.s.val1 = -4533; + arbPac.s.val2 = millis(); + arbPac.s.val3 = 3354; + + /* + // Alternate Union with anonymous struct + // (see union declaration above) + + strcpy_P(arbPac.charAry, strVal); + arbPac.val1 = -4533; + arbPac.val2 = millis(); + arbPac.val3 = 3354; + */ + + if (! ap.publish(arbPac.raw, sizeof(packet_t))) + Serial.println(F("Publish Failed.")); + else { + Serial.println(F("Publish Success!")); + delay(500); + } + + delay(10000); + + + // ping the server to keep the mqtt connection alive + // NOT required if you are publishing once every KEEPALIVE seconds + /* + if(! mqtt.ping()) { + mqtt.disconnect(); + } + */ +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print(F("Connecting to MQTT... ")); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println(F("Retrying MQTT connection in 5 seconds...")); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + Serial.println(F("MQTT Connected!")); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/mqtt_figure.png b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/mqtt_figure.png new file mode 100644 index 0000000000000000000000000000000000000000..e0fd07562135e888aa77eefedd8385595bcca9ac GIT binary patch literal 885336 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�WA@;~C~BZOn6u3BT(#G~M667x$A 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

&Y*=GW|vEa!cgY zRP#}lqGwBNVmWWJp8ye*F!ww8gZ?^*`MPsBf+8!8eJLf4J^aKW=9us1@Qp}9>k^<6 z<~IBQxA&+PI+LA$>-e1^qTAD8vU}BzosYCAbKPQcUD0bA zLY(H^Ee*~{xH(4Mn<8Q@`Q9Q|ba!+;;!oYRAWQE#cgHM#`kif;LEZQj)Md5lP8*+# z8--Oh)97==omiM=hkX=Cah+wa<)TMjI8+pcP61W-@|NONew8`vi>O1@UNsqcmsCDY z;ur3Gm;=&U?t=)l^;SJYJ)M;OVpRSyZ>cD`yzn^@@2oS*FEHb={9x&ZZ&5SYO|B85 zA(h?>1ZkMVd7GKw7W5jI9{%JrzC75tyKhvCf8l9lu{#-zpcvlnJ96!$Ry{=6w>aS~ zZNF#1E?K$i9+eS^7q0f+qCW9Fi*YPxO4P7GlI%8?yT1M_quHis!e@^-jPPt4y|HTC ztbRU}6P@|qK#`IO!5u*tG?5o5I-{!o(~p#JAElZ4aX4HP3Q9L{c3PNj=3jV0xIPq$ zAcHFhW)FmdbPC3sH^TQ_1Nvzb#^-`7#Ar4k;#g|fY}dI&_HjxnfWA-c0? zp`J(cf38{?YjRQ(Je7qEQ8aewJrL~4;*tu?zSi&6{^{}_9`c{?8}e?EbSh1ZzgD-| z`b1Hnamdog$ik&@!`6rB!=ph0EMDbp&iCj5c$F~yE=u~o|L~*8LT2Fq_KAOrUb>Pv z?|n7~yq^Zr^p;eI;Uk5k1LwmNXQA5ATTe!wIF`tvy65^Lj{hrR(TA=O z{m$XX!B{)BI;6Vj$<)?JnA_va5_OjE+x3VGjQ}c^*V8u@EF81a-uo+qc^ElkGK9je ze!P-HPrI`8eSMn5H7ZFBt=&x;dIO$Z?|;C zbWUlG+ztN%51KUj=}sUew^1NTF9IeXiYwbZn41XHsbI~40ro8b#XUm}?L`T%<8@A! z1U{B=TA**>y#0ROuN}si4JSN@XzV5<0M=@M(yhsKc1$+VihUm%^1H(EcONiJ*W^QVkLz^!!X0+}&MH*||+KowxxvDu}Z-RLE z28&gQBuZEFsnd)2(69_Ts^(Pd)1e;&1SPw?;(`i za=sb%MZYQT3n@Kn1UhOqrtRMGOrraH&~|~=vrdz!6L9H!?3es@H_h1USi8YkE$J4; zI;B9ELdcxkf9nzNbGF7Q1ksC-PQ9}**yTvZV}!oKhBzQiQ<{1YMW~#VU-))pCaXWK z+bP6#>IV96?I0d*sJ>)H>&!zeA&ZZh*U1n|7slY$4|h31q(bfZGu%(6d-n37^~Vlq z)QKPUa0rL=XA>xb7VXYT8r-(Fzg_DU8!7mSoKmh}T>5GNDX*kAX{*SsQ*Q|8TkPrj z=YGi|BtWcb2f#n5y!u3#w_*DL@yrxIb(-2M`3~+y(keRcVJD6}Cs2@Qh3O4ZE%CU0 z@Y+gzVwqUHq0|DOUHIz8Y2)~sAuc~$bm+cMye_XxyUlMx!_ODr4$CS#7%t0M1>$uViH`uYXj;WQW%oHg{YVL`H zQCXTfs9c!?M{4EX0|i&&$h`;R7DWYA6cO;|ob&zO_vgO<0QiLm@AvC{UDxw^z1lUb zmroXeaYhvi-m}_(_IOX*!%;=exV!aC$_kGvnuzl_rh8%kW zzGy;NW@a52L0>90f;YKM#tN7#<4CbaQg!GwfpuecQ<%a83EL3Nx7u^TT?Ltwr!*PK(rvOHy3{S4CDjk zYeLugvZ0~(G=Z->J_9AUuM3fxA|@H|RP?8&;;bVBL%#C8LeOoYoNnZ*~g9woA@og<3h(q^mJKU#spIiwiOo?Z*SZDX% zpj2hTlJ34Yt(*`iE&wpy&I;DYV(bT&64WL7(BO)L0xp`|XQT7$66PVm4)CRda_FO^ zqx*OMT!msg98KS=XnMN(O!AV*R!nAAJ`;*=@X&C#4cy4&e`D?#jPf?UcAY8@=RkRQ4qW`QrPQXXof+j zqF}Y{p1oPJ54fLZ(*ZYCQ*_)l%DL0ds%~tzW!k=ag+;D$uc^N2K7lxZO1q1x=e8l2 zD*$?gjq5KJ((YOyiA2^i9nCeH_u5T))G~k{!;bKJ){_P0R1mer4X97uj3oKN%+ZM} z;J5{qr@BaIw?cyZ#I>q=H}R1EX!r5sfa-|GnfGCGM`4!0GEkjy$Is0h)|JhrYOFoZ zq0M9LpTFNrP}%6IRR?6nDOyMX-u@c=Wp+^AvDC25yrKYcjx(lxfB0=%i~;}(>ZDtb z&w{D)dFR9-xm8AZ!Y7iTAH*(5suwlI4~d0xGNe4u`9)}%=Ttl}Y_{|!c4%#CR04d7eoAtS2r+Ca`5NK?KjMUa3 zvp-HfcxC1pu;(O-L3IXJ_b@WPlT;ll1fE#JtnrbypP-kTCI|(C}=(+<>s> zxNDvi_fM^)53afoag0+T9bWeF%%P$3twr4AY*u1`>x<1E z>GakB<$8`A=u($-_}@dCB~+8O$(*+k&2ZSdzG==00ngU35njMP{UvDgnnTjwEz@Y= zr3e7beyV>h+oRM~-In{zVwn@JP$SU3({a`>VeGQYs1wB2%sb(ME1$-nNVf^83*q*g zsRo7>R^Q8VcYPbGsy3qvxwbVpTUw*8`>!yQOUmJ|2)#)cs&6KK7iIN!s^L;$wGrKQMiyT?I zlHCEA#M;%Zd^Bb+dXiVP{@sy_+L`sC^h&r&&cf~Mf;!t&o&D`-AT+ma2ri}Q96@Na zmiD28=c956y1#F?l`Go`tEmCyFW1w{c>F(4TB#ibyVci-i@F za#NNnBcaH2v(tFDz_D{??>dzChPYzwwGJ9(>hKKtt+tYg`0C7GHn#6XRZvEW+P7C2 zjoYqj5zi=kp2c3Y^M{oC&);nrRFr3|nJb;f8VCxzRo8v#O!Om{l@AB`r( z=(I%UZg_IzL4~J7V0!lbhW_OuH#PL6Bxj;UKN#!I0th!;XYSao{`cq+GfYGORbKs> z^#`pu@N=M%--6AM*hUZMO}Dk7#o7?_aTxRMaB_>PLyCYeJj2u1bD&jT2Sa{Z=pth? zr_EkF3Kr+gEjcQ#vwNs4S;`s1i>nD6CnZ#?^Ex-*$XQX@``Wo-0A)y4Cj7wu<41kUGZ;TOU6pJe~FwMtfNAE|kU3|RvRbqw$4 z&_*&`Z`}y#)|Z+}*YHXMO29n>>~z+5Am3F!eq_gLX1jIvJhyUUe5AEpL^?S`ZC0bO z0ql2S1+9v+cUEfL>gBWc`cCQUr^s#&WI_egU9X#hwk|G1fuExiW{mqT8oNZ}9JkS3 zFk3J}z8;nn0#bI;kHjwh;)d9PZ1z!CniRKbgN0o|acVsW|J*D5$D)F3?i;eDv*J?s zxQiNkL;bV@y!$_M!i75fwomX8iH0N(LUooZW~akIZ|yh#qbm~Ab8B*Ii|i{mQR&!v zO!%SNolXzF>a-F4=06XaJ-y$rj4bKt!%M$6x!XC0oYoH!nKrL%*t~Gi1Dsg>ZOZuO zoa<3@0IJA24|rs`MUs+j;^#wr=6U0#tn{3#dTCM)t8oT;6f8bOFdP^>GIGIIZ~DIPwOf;Vp#F3=bM?n-UF(oo1vPk5$-` zf0X#oPNn?_diU#ZcDhqmtQj3oC?=e;(TwCbe+dRe}d9xs`Yw5txB2q`ow7|Vr zv*ieLuP;fruP7W`7l`#enQ*z_v6n!QOfXW!b2G<;ogt#fweLIVf4-Lc4#r*HES5qC zXmVHjq|SDTUIW{Lpl58V1~O+?XkCh`x(k&V+4BmWx)n-~D~ezf7djyKE^fD?Ox;;) z#L_}&VN@{i&8SeoPBZATS{gt(&D5~A%nFUsH?!q#^;6hB08FN%E9wXx2bme1FBvaT zs?4vl)AYAZu&EHnd@7Ur+#kGFWE42`%(0>dlQ5~BV_k0VK6}B#^`kLvNuC7+ZA_F5 z4cvLt3#F+EPQ9u#EhMiU2fle-TaC_5~)&s`l|}0 z_zuF}wz7LyFh&G89s5SPG+x`D+yPL$3h0Dy==l2F8?F7deZrv)c|la&0)_s9cbcRA zCeU!(Y7DSJt}|ZmT*b4(qVpo0&VqYmr;T<`fT%2~$pf0J=VQ9HA^SM3e-#5k)EgEF z#}&296v|qA6a4N)!>t52)*ZII2)3Y00n(UXwT_>Ujv!k5>&^%kD4M52HqTKK!FdIU z^}lf$VHjx*($?e;Rpwi1jc0fcWIMbwVT1&jiO_}~w}7NIgoTY{FnN>#n|wm2y3 zyL&?xa5Px=dql|TCXs6d25i*s$1ClW2NndYY@aeYw~+0+8;NodMI-o_J`ke&pv8Zt zsc^(Nkxs6tpvTf4C795b0RXn1^Isb{z~+<&zpY4EwO3llTN6Cv;#6!5Ca50;F-VAt znjof-Ch~IW+m=F=RAqmrqKMk72o>(!D{EU&bF_?{hi|s*dh1>WZ7{nAQ@1wj;SAnJ zbtScbJ{&^w6PzCL*9|=TRJ~E`hysh2a7gEwcyxErpmqj7gp{CmUgMy;f8+4tzY~JD z{L@KU&w62HJ({JJP1d|n<*w;7><61F&uIhowR<}&rQPnA-`7G%g5UU@Rf5iaFbH+I z$L7@jALCpwB^F8v$B$6mrlO5Z%>Nv)L@HjQ%PEXVl*U)xH7Uq}V~Wr1O(MJ7i9<9wy3RX7>3F%v(G$F9@*^rl-tsyDgetT4+P&_u7_Jjf(c7x*Fb6>FTCe)e#IBFUpoz7d zT#qx+Z<`ZP5QPHU@Qa2*d7~3hl$x-a z?Ee3E0frBQ`SODcEPU3gY6A8Ojr2nlWZ9qBUxAQ$N>5N$xuk^6TeiO{ z1!$^sj;`CpW2lZ|`rXfuRjY=#_uTVD8*N{O+0>k`+7$WX%{eUY!8u_P`A<cDdzj{t5)JJh6-rsL?Zs?j%-^3A^=_&Zo+LP=s%O9||HmD_r>AcG-{C6Rg~rumiY z&`2z^NJKT~D$UyVlvO~lyd>T?-zLbVddCG+;6fiMnSGXXJqbpPE9BL?w1>uZ%q(k%6)d|^mu1Z|ek>!m%y4V_ao+8%Qg@i8ToT#OrUKTR7+7VY6vW?%m0QSDT zde#L4#P^;gYyx#t-Jzu+rWVv0F>HdFAS@&&+c0YaZ8;^)#JVzC+}qpiG_ZP(lJ>Pz zG1Qw@o1K_Hj8*mU;OBR>mb)R86-IapJAG1CVe=y()q|`aG$umVDf7QGH)51fD$IVp zwgJ|)Xa1}JHy4&8RF!E7Gs4nq3 z4x&1m;Wo}u%$tl<23or0Np&fnuPok9n&Z1o-fl`u{ z17UqPu#@nV-H!l-lJ&Uv7QT_pFxz;F+vU(aau^8Bey8)6T%LSg@Bgt)xr;_m8OnTN zj#`0SnQN(6kypp914;XmMB$fQkGTG>54NMW*JqzgI?N4dG7IbcD*pB+W^;P9EFE&n zH!{kz?rf$mQrAya)(6xbTfWlExFst!94Q?^w~ z5fiyfRgO)|-lQKF9G;soi&2N`=HGfOO7V;k1>4UhRi$g*Z%U;pPZopUS<7zZ? zZ8_&0wgjP*hb|Uz((%fdhKEV17tu^DuV3ujDLm(Ky7g9~dTxuIf3@4-%zD37p%S8? z+GWHd;5Z^s<#v%7R_tPX@Jf-DR=^_9YKK(GEmMM5+6ENAj8=K~8>KA4G~9!GkWqa* z#0-}Wmp0%5cb8S-KimnP=O$l4|2gWjtJnUje@l6nUTbDqvfSlOPSF^E5Q-;?ZM`;d zvUb{D4q>{y;v^Ft>9LiR3_z}E+)T7%11#djgvTXBp=B?1>=>r4?<9qtRl5qF?P$QS zz_-4rXHEocH>x9;EQfi{!~DRO&8kjx+2aANG6ErF_~#`;c0L(>e?b~g^-1Vd)lem7 zEqVM!BT7D(gS|LUyD|7wMHGu!m?L-O-A9zBqIiL<;))P47pbe^ZCkS!tQDC26s!PQP|nKW_h^<1$|y*MWc&?iX~h5_UAo;~1O^DM^7hGP@w@9cUM2X23vpRbix`GH6Vv}oX{ zZS1>|dYr(v}VrJ?Gu7 z82>*khVii;ut1|({e%)5sm$5spp~m{^=NV9FSzXAS6^9l1r3h%2OI=u{b&r5_3zNLX>Xwjr}nf3v-OwWA#4Eu^GUsJnw`mnQO}FZK(b`W5Pr zcFYRKpL=ECx}s<_N8>o=?5QeNxeZjC2Doq#%026ty3fI@fn-!Kp_@|t3M8uv&&zd| zOuF1}V7mO@QT|~6vhQ*i#^;{;_OJW2#On>*CWY?WN21@8a_#3DIE%hK%Y z2Kw8XC#nR_y^a7B%Zu86u9)Dk0H+XF*0#O@W4*@B+=6;W&VdpyvV{ateCdzdEe&p{cv*nf_q&+6(vCMN{?aX>xUrw16~Yx4t_55sv@%J==A$;_BH>XSr@H9~ zbjeKmnh(@1THJT4^gM4QYyPSVp+{uyqw_nCD(_VbAgI;F4jsE62X37Gsjw5>B{2om zM$eCUGVaa#AOJ@FflnjiAu znOg=nC5KA$JY4G{1davnUbgJ^8}(;lvSE;xM>G1S@O1-tNm%>{qYc|Q)%QsI{58$P zL~Up})GbaSlnG^}m<2qrM7E7OjG7V6vt_u967ouu)m1^TC85&B;cgr2BcV@$(_PgE zloDe!Ob=tFw%Oyrogst&{51Ymd4c_o#c*e>-h(7O>x`;`uOL~27L{6ZsiH}{k-T0k zdf2hF+;!T)dPeXsafz&+7=X^k-%S1p;TA5p@LXC8KENA+)m z4}kYOEsx&aUpN`Zys%Fk`otnsoMn=z< zs$vv$4}x4$=%DPeabuy^{N0=+zJ+<=f)~P?hSrCXyw3UB13+;v#}!P_j$pBX^JAhT zZ`r%gDAn3D#m_d7cUjdXtkS?%Id8P60?AodjR)}9dvOnGbg7n5m|D^FkMVi$g$6Sv zj3-yDLxm;e;Buu8W<%-kt-f?StO7&71(%cKe%Pa4!2Ln5VTW`Yg1s1Q zocGjhM+X*jf7EXmmKHk2%k#ib!5r55XdQ^uy|?FVrho2nzIfk3?aznI?9xnkLe?jm zaB6cq{6^z;I))`aKMDwb61e2$7$>V>D%*EL#v9G}giTPj<_&Tniw*emI~iEE0x)8B zOS7bw)mvDIElN@Q)8=Oi4oK!^uCGNv|6a4HMBY?lfn@KPL8;;MCjl6*(LlU1b8frb zxIhuZU4?ptMP-WHADI?WIex}`c|Eebm5HgB`gL~En0u#ofHa|#&27uHd6?=&jTt*P za{K!Yw+3=VI}S;co&R*p=ZTeVjIL+S6g>J2Mq#h1@Fey=x=G&VJ|J`J%%w3dg}pnM zjJdoQFzVR;zFaY>Yr&Y6;_mhcr{~bUE_fW@12k`Wh_>A?@o%bGpT;3GxCXLjpulBH ztle{&V-I%h>*QI5s$Bd`Vdu4uH!Y|2fjBns-8xez3lTEBPUQ7_6WBKdfmp2TdvEM!vA5V3+ z83f;s+)B~L8>>)Yj1Ygew7p6_%CX_?e03NvsK+5^H9J00XRTM_0H`(Sg5el{AViKL zv7SK{jZl%5D0TTX3vI-gLx#oEPeVg(+>kP36qANBLm%m;La4x7<# zB%&p{Drlk0ZNJZ|Bcbb$SLMn*#w@R^#uE3Yq1SnWK0TAkawM-BcSfzHAQ$>6uw5Zl zuu=*%(gKK13bfy$_Vd9ntoQ{H(0h>`Kp9rvv7p9HM=ZN(of9K!CKa$ntSwV2guZ-- zTD@X1x!DTMqi0Nd-vf<~KT=Tv4qsnL0oD>HN}c^SV7S}J{^$J8Z&0Pc)$gi`X1Y_o z$7DYij#m{RQEQdswanQw#cM4OB2}L-nvSXG(0yL$+_b4!D|jBE=RbPa)KqGJ0zr!C zrYBS2KPHn`V#oE&ZTX1goa%(BFY8;yE`4Y|Y?_$QxPUf6#K>^PQlhLct$o0Y1-UES^{(Ln8qDyre zwYy~8P%5Ysyo4C9Ug`6Qf2aYoyl9Gp`D-tGb>cRKWt|l)i^yxc@rlGO_4muzF9$37 z{e3Hpc^}Ek`1sgh3su$J1^AU?WEb6u^NhTd(vaS2inmU(LRd$*BReXbn&qlqjC$_7et zX9$dypbu8NsAWD$5TJ)vE;;>0gVsK<*j}aZkk!1`-4$wYJ-7of!Ns+GNdh&jT8P-M z3V$#}sjE`ruPwIsfsoZJn4$4@iD21lk*0q>^2`u0O;edtD_|7m$)DVUz5a43Ilu0tMM7 z$zX(Z#|4H6RiyYz^sAE3;h@&fE^!<|!dYLY)Mm-RE*Yt@W*d!piH(QQ|6wSIPxk9J z?i|Ag+%#r(F3u%u`3OS$$esBF0PgEif@qADWI@%;`*;YSiPgC8(t_Sy)tE7TCfbY_ z06!u3$s1e$Gt>n*w-2x^G!j-hXH19+0R#w$dAS-ar#>d?G5FZW8yyEbv*o1$K^azB z#IXXaxR$H_Nb-ro@6z}h5hJs9!#8qAaT%)8%|Ui!ygz+!K2Hh)4h`w+Fv}`DCg=Qj zq9<0L&6CXnTt@v@rk>Uq`&?RY%*2t`%22&PxZZ^Is8T60eKL!+Z5HUL<+pVhR=gW4 zYf4-aOoS-J7Z`sznC_xhdU@8|w&&y-D-h3lBQ#po4K>(=sjQu9Ri*=$+W#>g0{3G0 zCAIwIO26LxJZzttZCqifAjyXU%^b#QEt6?CT(t;z^(Bf?5l3_pPvcAyVM1J$aw*99EiXNq5rU(dkNceWAE-R zx({Q5TK1IbylJD3oC0p6XTJ#ul!3Se_XE|Drsws{f_8b)M1f|R9HhTpw4R|__k6@- z>{fyosxOrkPegO~#+G;8$fzaKzL=IlqXLUbm7A&2YoCuMG#@tM{yN7G@vzxWIVM`~ ziPra;Hi61SM_300%s~{+0UA=iSSS&)qXNO)`dK$@AWZLF@UIV!E^kF#CiWO#6XNY? z!do-~tD@vtf4qT8@o90Ex`&H_%1RVO$+~K0Xh@|*O0*rCk7cdO3H9p)=je6xUGErs z{F_J1XZBasnXn3I5Z&CU%GXOzX4ppeRvAc9yla&Oe&0aenhh zOCV`?kanUtNJL%wr_KX*JJ+vgTkZC?N3)p2syh4w^HZFV*HFD})%Ioa5Pb8`h7|T% zq{Irb1LA;lE**n4tq5XQ$=bWkMdit6a9pr^;1ihW1etjK^eRAV*5gkYj^a6{*x>pm#)U4tB(GmNcF*N(AnRoatR_P)zsIqmUn20& zz)eCBh2NH>`<;Wy|#KXi&*sQ2Lcen^y`FF^QWd=hEsI@Cb@4nO26j! zX8$UU%o?hUTT@}W{Xxc7w78lNNBz)n35&=$yKdVF)Sg2&MW{3|g{Pj0z!Fi{4&zQ| zZvVs8)T=7&Be=2UX0DzCGGmf3j3LntGuYZuWuN1n$p_o6;-*_amwIo&v`QL^MSkYEFOXV9=~FWU)8XSUOKNpfOB>eH=9JJAI)8JO!v`O9uxuAJkKe zG%JovPG!F&X@?t%)*+AB$kJOLA_V(9b}%KZNkzPUU zuhF46jAy&IuCthB{iA@$XLjdx8J;l`y2W=t6A-BNyQtS@Svjb=(Z5pF3<@HQ01`Hq zGoZvsQUOe@u{2$7tf1QN#;AWz17N5%^W%kt)9cRjwV;G?ny})CJC@h(o&(LfOQwH& zG(5>JTW?WpUtL7~h)$=qbIt1NR`y;aV%=!xytp|~)OpCb2NC5`$`^KW&17mZ(?Afy z^lF7T8m9Ns#)o9G>NUW|34pX+Ge#7n1N1Ky)2@ za@CH@K*sM0`X$1X)Z#}j02S?SaHrH&`nGL9?}QH-{A(`pet){E$7c&31=|ayIw5$( zZ+M@8o%Q*Dcq~@4{I@I&e-R_s-Yq!W1tyje2<4l2-DT**B=Dce!T*lr!fgaN$sAXv z4pQrO-1YRF3ggoFFNe$z;-_ouXU~pa+HZ7G`*T_GZP;2wnE1sMeH3}D;Z};8pk=AY zR)^12xE9pd9~;F9I|YSZDRTbxytqR*Y-P?IwkZOI%?q_m*D2Ez=v@tv!>t9n6;A@nSL4 zJ+xM_4`dg{OvBLeEdxP7nWDP2xgdq6?pLMJ$md$#l?F*Fv+ryepE&%#-HdsT-xIaH z!qy4!-3WER6^SPJA2s^+x^)X*6I@x0%3P1JuPGfgQ?mEtQ)5pD+^X+oV%Y|&t(xYc z2;ubKABNpC!elz+333P*=?@?~(wMz}Y@}v+Z})WB;TX*I! z814D@gZ7%AxzvZg;dhBqENqzN(94DV1zem3YSv`lWur^BuXQ0~mca6=&uozYWv3W8 zb0LgYU{UJ-c@%x6XK>b2mtbG_yC{3HU{~f{7u?@`XERw9%rB6bFJ`H&=|ewAI`Z}n zTwtEG^7*T7$9kie5#PV(lp98b5sZdGF6?R1)*#kk^`fuklbvIwa+Aozui&nDt4gti z3H2PM0xS!i_JQ?Rk`0qV*+~?9DrIc~O^@N%K2{XMHQla?FfHKw_L~#Jfx1e zx>ypOgjq-}|FaR0P~KEqj&9385O4vR*x{S$n9}y8K_yit@_VUty_@+N z_fg$6Hxmh2qV?|z%)}JxeSnQbnLJKpIe_{yV#P_yEo4?hj_aK!Q>$GY|>iOs)|2~)4sVaNsfoxfbm z-pjQM`V=r^){Dhi+yI--ZPRjCg&~gEnIecb;N)3m{)tt5}>~pm5Cvv`z z(>XWy{CsLED{RV5m;VbhP_*|Ay={`v$xHSdvYaQ=(Fm915hd;pR)E4N*>9;#6*u!XMT zE(<6;Hy6GEcu4VopM{Wp#F=BK1K)27y`DD4fGY@R?L&^;zXp8edaY!s$*##!B>l*z zS(2mOxm!cQzcW0(VGk237rOK-zXQuO+{V^N)TCou{Z+q>$JTFP(MfEt?P%R1^2*K9 z1_^$}<@`6jVP67dy+`ezH#H5b1Q3gwUjJmJq*1EY#^+0{3NKyFUb~-a16o>fC#zKX zuwq!n-<^b#1 zICOH#SBuE7dTte9;M|X>v7D6ZZ~GOZZOJWv=Sxc_ie|E$%xCQL2y=5374zjA_kivN z*q|+^1={lLGelyQUEtiLLY#suV?@Q+i^|DzA>Ie!>dHnJ6A)Sc4X4MxM~l zMqriWS&-+Pwrwd4mb`szzSM$8TiCf?RZ72Q1PN}=3|;d7?AMkL&8Mr?Rp`xZmvJ6H zLtKk?4+(IBBTUcxZ>EWCTCW%=_-y$tmV=CeR$J}tcl{FJF_H2<1f24pCZ&2 zcO&T?I|$*otu}Y4a@dH?=VgHu6I5${+@(BwqV<&^Ihr|G2fY3?G6zcc;C}_M)I3F1 zuACCu>vKmuY#bIHDJSJ>gm&&d8b%0D*1F!$oZSngoh&8|_+^OY4w22*svPe`DCjh? zUTkQNW5ytU?f|rs{u&v|z9-I!XCwSBZ^#3?n$OX)AQ@JBRYwo~DQyK(&^^PwCp__( z;Ofo|@BN2-|4vH`nxlbWv=L!kVC>Y3;U*bHm&50haueg&2q zJQr`|BYDM9_X`EzjTm)bTwiJGQX&oDmqb!E87pBamXjMiKqY@eij|j%dj#6kt&$YQ z>9oO_ET}AmfNZbOiA}&UGS;QT+ynQ$GFlbjc&(mYwPItsM;+bv4^Mi*IyUIBmZXH$ z784~GumrmPD`pdz7^!7fuYJ>qVFI6Tx>~_^y2Gt)LUAi3X~8w3z)VRC)L%K>*HBRj z*4~;yQNqaOfZizVukgqeYW+C(boI?F=YNU7`jE(%-N8PBIsxw0y$e3-E(R9LHXBF9 zKjV(;i%lXA?%Yl9PJTp^x1eA20fBh!zG)$?86kN-u6?%*r9fKi?XCxKtP8$n?8`tOGUn5A2~l@|Y-LW{4U;5`zM>2Ms#xv}|GkLoL= zS+jIow)p=)Avyc0=e-Xnu=k)~5 zb8zUdX#o6suD5#9!5XEcnKEW@5z;okjbFW(u#}^lsb>>7*yz$xb3M1?&FaPLb#t~6 z*yXWQ6yOX#+-}*oDGSxx0D+ddZB8CjtuE8?GCh(l=Zlmg&Tf_v@ECPk4d~1b+UV&$#Ou)Z) z3^#|EYU@*bg^dLF<4XE>1j^7(RlDu>W<%E9gCBAfkHVQDtuawu7x50C4a@$G$^64( z(;E1@bV}b<+|I9O5i(Q$^b`CxGxUQ~N{_F6-TVD*mT^M7PnT9sR;yX6Zgl{*NaxLl z`?LW-uPh%oy|nRPmBD{Q^pvB~)4768$Bl~4m%Nv%!eCLY{TuU8&FUbq{NH1j>|>rc zIhTsKIx)ts-DbNgEww&xer1AZKiobaMYL&EprdY%zHBXh^NsCI}46NS>oLpR8y~DzFPMg<|Rv{P?T)3q>tc1KTHX<3v5WR8I;?8 zzgJrfjH3^Sq+_AHI{C+r!`lO&h8J5%E@u3?V|3MvyRTIVB@?wX@y~~|hh9zNy8>JS zSI{})etN7Aq=aogRo=mb1>dV<&n-UEvzjz`&lI!3)V>r{6Nf)X@{ zOWXz4Vw_f`4Qw)COfeLqHHBo}CqCqJW{A=)7z38`wbPhMqAQmf)8#P~Qqs-mgc)vD zS7o{+AnD8Fv)jP~hx`Ri^ zJAK#T9;-l+rNw-U8K0YV1_UvIk(^sD6|H(aSU+zvpj|OE?E4}m;Jy}ye@h&Pc8+Yq zghbqw5^=_EHQvH-9UMIymIyBLahTl@Kmr!FHCkc#BlvYa3C*ipEuLGhf5OKbK`U#S z$%rye3hP+>Wcte5RQ=&#IatdOXlKCe&GciaSBqMc|Ln%iH$ID)%3j{f#@QIhyJYXv z(l8%)Vhyrzxm)x2hD8bk&r;j4f{1}1!G?ZVJ(?I|m+ehR6RjqFTK3$r^4NkL8KmLP zcm!mC)Ad=$mD6ype8PHbZ8j_w^>4ZHCSk_DOBo3T=H4pR4M4iXTX z&z!4SG=@}IJZ>T=FjvQSNFp5N&;Eg#8d$ndMEE48>D1+$NH6co(=NSw;mtI6&$A@4 z4N~Q_r{{;B2OLG~3Zrj(ZZgb%G(P+9Uo7|M{<{}86oI@>4V=!-%14on!6&IKnJ%ul zP3m=!a_#g6PX=nWBXcEzc}vAb82+F_C2clB*?}f}#)hVPHpHkvHf>zR+iC2)MUFvq zp<;;K7j+`@t&yH4e>y$nh+Hl5{&O=aqOPAhxnlaQ0T%60G$*<@w8ob;r`*?GrIN}N z4um_dG*_~R-z*b@Y5i#SO%VB$w}wiwpo<)g`0VFd?N|Ccec$9xk$Kn_Bim&2)ktiBLRI_Z;R1;1EgEyy?Kl1fBLFzXiIeWr$|LjhcuZ$j*woj2~>~!AT$Sbp1)+~ z>)&75YXH>>r##?F*ywrpx~P1W(Z;tseT>?J;S5Pp*l?GXj#2BJR(prh*r(omk$cAwIDw#X)GjlWNCYW&d8~ulS4K0O6OHjHr>nNwW0z`!ON$=Vw z2ejhvSl9Tx?aZ#!?o2%~z8(F-Y{tp8=Q7xvGk1O6Va&_yQf2A}(|gfjF&<{BU3xdQ zg5+sEor#AR%CUnkEZAmyu;Mt~PYM~+cjPSn=7%V}VFGwz7b8HCAO4FXo2RbFK8ul^ z(mu2%cO-bTw0s8mRzOa~(^2$bFFe z_KUb!ADIad0rBfBfC41kudMhd5?CPlY~ zNnqC+56Mo#t4x08!!aI~#kRL%N_!(z)>lTKeu|3;DSbsg(+u+eGy-x38~ydhJtjY8 zGddL8y|Y;I^k>FJGtopH+FKrO(%(S~g)$O|r-8rQf8CXmGkbcU>Ya(Jy z2dxUz%yDCk@&-=Al_=FqjY9=&!gKPM66R!7epG;A$hOpm0qz}Xb_+9b=u}u}q0X+G zL+9A_rU-y9UXiuQ;dcmrk?XJFw_KM_EatDW_?!%);@YDV3gA(HqaOyx5Z@2V=)RA6 z+=uY4qveYMRk>c4MacWK+09aLO=)Sc&`QB<%(iyAwP*j&&K!jSQjUKEY}7+*JH_wi z5tT>9)vIsZI`G83hUfje{`h*(<=NBFU1TJ2Ov14=Pd7;S?~kW)sGM>U(D>x)X!*t@ z2>Df$d!X7gi@T&qe4OgX?l;usZ~qEX*wIl9!j%40Ny?nIOF$L;f+10vKNo*0CJf=v z7OLcQLY!OOYojRHT^~6jBD{@nqY9KoQ!11=<6QG;>hC z`9JK~bx?L#iI;(F49ndJYr6f-UKBrbVt1`^^|g*tUD9qUD>n=Yt0~D5J}r87Hqp)Q zhV|3{Yhz1NAx+Kz&974maoZVFr7955(iEMc=P)o`R`@bYW5!;kW1xt@1MM}Fzr6nil&g+QMl(G5*OyWIWD94kGacM=4^ zaBk;nJ<-{`?n$jl%Hai1y_Y0~Y;H$Y*8!+oH?o0bs^P!caObRBO@_iN8~eZ?v28M)WmM~>+Z4Ra}`M^ zRt=V>ZSUaG>-on zEqTR%qqKk3jj!IjIH03`0}|2L;lEOl4+a;UL3Ybpfg<#>`BatF>0EVkcyD&lp+Ga# zaQ+9ANjo~H`nr>1iZkAuqoaxb+DW6OjJQUR`u|xcA5?geglj%t81Nnt7y!v5mda&$ zSX;Evi+mFA&3@dPx&g0Dp2WA0{6O@%mxir7yKL~{rNqAD{)QqHf zkH~kpEuyk{t+u0B!jp?oCSoigN7Rh-xDH;3-V&JqOYm8Pyeym`En_-C3A*L+A7%T( z=xGAX|1wsO;DzBh#>Y9T*X@0cd{{^zs_Vb0K%5>pM{MAh3j; zT%rAPukj&Vxv+pPw)Wb<2Ng0raz9>rckK8Vu*2OWhFzzWU4?W%8A&4=(l-$v+#3ed z*ZIGs>xV2BQeifMGxV|=%y<;;@z!VEI;AIQt5WEB-{0iC@;&+t&pU|blzGy0UxSHaM2o=RzSRPNf6LazUUZ-Ah2Gs61thdVVw@8)Cf+~B_ zdW8XlMmO+agcWOQ`6X;SAJHFOi{GHJm{cjN6}T0kZcz{#-O)#6lJO>5P8_~N%N9N^ z#Gg!*DczlB-S0E90|_(FtxDg>i;L7+t(WAn%T!&) z1qF-tD%R}GExTigYEij5%H?^y28OuwlmaV-v0D~ELPZTmXWcV}52nKF<$Lii2PYmH z7bpXg{O|FQ-!|f+fAqhq9?hleKGB={$fD%>4AoYBFIdXN2nxqzFhuV39pbQDFs4lz z>V}~6r{Zz3Ru&VJGoP!n>6;q&;I-a&ejV8$(~xGk*3whGnP%#u13w?+G0vY-ktno+ zt3Dsza!N9ITG42xprxrkH!Vb#3LCnhD_qMomNLmOro_myJt>gz=>I|o4Y%r()e)Wj zzeod}Mgdno2aAvsY2E=oR~=+7cU;{ry;whg6`AJ7An%uJj@BL*^a!AaG_~-SaHQd5 ztEslz&H&582dzP&p{nRK<4r?DDJX#*R~}()Pi-7#U7($k<6BxVdk%-mX>n$KTm$0_ zTq%0=zLM_*oBb=EQ&Xf!r*Lun^5!2K%16m7GYKQ1X3-Y-btz-f8WKdbV$}Vw;JTZe z#V4QcuI>1=e~KXf{ZA~hXS)rEqS-lwi9{<0>u%+7TFK-vLf-{);AmyXOa$SvAlUM7 z3)QO=#xDw25d>0E)5B2fc=&G9bH^c%TEdENYq-h5KXv3oEKq|Jp!frD?zsP@z$rzj zb<2>^>4=9dk^V2s;E6Ixo==@oN48}tyU5WBxkfqmC&YDY+l=FQ`x>6ko-xL1NX6!! ze?InRDwDcc;ia!Y8G)ha56oB4kw5g_vx_WHMbqrJr0v(@9q!5-koB799z@CxboE1k zgIjkedmmndD#$LA)GzaJkm*i1AFx|3K!>FqS7jv;rmC@)qInG*gnIQ>AV*`wEa z_q#9Sah%9gMiA{I2Wjz>ecsGYf*=8b4yP!)BP0S2M<&&p#~2KQwX<3*c9v$XY@?(K z*=Le$4=_I75OcSb)j?P@Mh&}(4>jO0yDiS&9dZII^;f_^=@%q7Q!EmvTkZMmv zmLFxC7HwqdIxRfeEQaBnmcaabDg4vwC5-iium;Sne(PLZIB8+6He))i<+E@E9&_@a zWJ`BJ?RtAWgc)vj71ApaJ!+Kn8Md!ZE8hyYQRkRya`=>3QpVb`Bf@KB)4eb^6cl1p_%5y;0Xo z<9Cauh1fO-j|!5d1o*7)*oUd^Y!j*03t(Z{z=^*N<|qrDzwLgM9g(rV{aaFP2^bT* zL>_xpVg%YyL`O(Y27`&%aMzc5@CRiL@@~Y+sj%(90c9J+FGx0_^x;0S1j-Wvgg&wU z(HLSmG_Y;@!s@KsZJ2~Y_(!AMAdBZf_a9g>Lo-TG!idV_iuuL|_J7dcnQWaFRgkR; z?p&X_U%vS+WGe^s$m4PU^u&Ya%0&rzK=yrj(2Aw9+jOl!rPf&)8!INw?V=O$1#%Mm zcny!dtMX;L!Yr&Ai@(1P`4CwRKKO^SuCu(QkZKm@xf$6-e}qescVr!gH!7^PGb;1X zaNiRZ%NFOP=3BBf0Jcwd44Y~%XdLlNmxB9hQDwzlg*Ww#c$ez&p%(d|iH4{!tTXA$ zSQz@s70w4)_*rbUN%B<*>W9edY56k?LhNlU2HiaVY@2x%>q;Njb8aa%sC}&kMl5kF zDn8^`{V$^4`!DJB{~y;pN9&kcdD6;(WhXNSsiiq-Q^%CbQ|6@H<;sc5s!YusWUh>q z%+kz3F5F;g;@*lA+yhiV#es|u=XKuS+xH)M{=&uMx?lIleVB^ly37`1Il5c@Z75E_ z(nMoz^Y>J=x3_Z3dMs%$|DfYN{!_mrQeh)*rlB}Qk83+tmHj?6&(n4|lZ18zzz~82 z#q+_fQjd-ocmLsPb`*(Ne|%fN5$2`0A{D}n((?;oJb z?Qfdt4dQ9l06d_bfA@=rfP(t zMKg9h15D4|^aUw(R#**k&gO?JjvTKvUhkCY2XHA#Fme_3alc5uE&*b{e$d&ZOh##8XSzpBI5$+^OH$5x(CQ27x1v=ks~c6LbyI&{o% zCV6~8{grd|dGSuzo|xM{O^Pcm$FAn-zf!47!uSPq13&({jv!mcL`?rs5ayT?3ueel zH%wh6GFA0^TQv^s+|L(^bQWA(`L|k4jiFkTr8t2|`CXHMKIvoJ;Hmgs=BHL=-Y3cr zgGutYs33HE<8s3w&~+r8FkD%?zQHdMKMo8Ah&V1O9(`V z5YDIYxvVHk%Heh0e5q`sbUu`4%Q0|c)ySG`pZ%ihXDa4w!|c=98o%d1@`1{ml*Yz} zuh-H;9v*!V(tp@}O+Y@U%8ONZb|>nRHo*zV7$in}%=-O!@Xy^J-vcH&pl11_C6{fi z>-Ks=-=d@e_AZU8HGP#3bLIE@DTmVc4csab^To<+U?wd;m@>LSy5wXG3Z6n z8OnfQ_yIFZh0(=`ur-*>F|FKbmC~2bWlnf0F)}L({7LzvA10<#jh{>5t284IOBef9 zmza8xC+r>BIdM+GF`)H3W<66&5>M%Y7s6*NJa_G40_zIxO21EhF((is!+sW*jrNC@ zdro)NRy@#>s7bNG*~_)5i1|OLE+jCU^&f8`k3qr=R7(@xy1XHyIuNB#wpj#$D9Ufi za^=24a?^?OUdBCE(!n4-i41ga@0oTZRaVD1|oqWOqLd6 z%O|oAJQStNT?6dTZvva?G#Yy0H+wWc4&OKQ_-e|{q(f5aQ2YXN=b%Ovpuyc zpxqV5?lC@=MgfPgNwo)$`Is=<$0q*l=i~+nOY2rPDCzy1sBNzbbvoR7B!{(5%_1@pqIR>qek+g;t1wr*z;`y`wt0RbmdNDCmweE8(MWnvay3rez2s@+?=as}{ zXUTB@TvYtA{rgE}oW9}WW!OcgEL9?OL_;^l#uGQZ`exN4T9K`^PMiqSr!39nnfilx z145h74;R*Yg1>%O*f!_ht}q#VFb%smboeo>YCRnnb`mAA!xRw=Yj0<=j4oey7i1cx z+M;K=+pJ(fUer*5raYJ`1JMx5+g5*upcpJqH6vEnaR25v?;F0&+iZ^11#Y1rA$rC` z*^a4HtcYP8=j=%9`nd(%GV8Z?O;K4%_xJVPkVtDfIRnYGU@}!@lF5)KH5=K+(|SD< z5)WqrNcr$|<4Q+6z=}mx@);Ae((sA;NK?N)fSv2lWJibXr11H~(U?7BL$&i2u|)5* zj$PsMgj9q3wQ!JNgsSDf^nFA2#p@SeUwu7zp75vT`=#9XP0(|va=p%-+QA-i)y^M! zAfNN%?>iT-e!KgnO!UV4ovKya3y- zl*JuWPpkmw&}!ePJkmkVZvxYdoHjQj+=MI}Nd3E2Z7q6wMq=!ydO^bg+h#OO9*(+! zP;eEk2Q4#w-@3LZ(Ak`?R%8naL?Res##+AuINg5`wc(Z=CuhOE9HZzZH+d;8cM9zM z_i?4!$i4eduuiK_zI@>btu}OO|8a0(w4LW|9V}DBe8FmQY8UgJ|Ff>Gp+aZQzWcn& z$dC3QK%$CQY7Ry>^dp0QU23u=R>kYf;>+}535=>2#yn|of|YG6D)^1s7R3f8DLS=z z4UhgFX4X3L!-G5NGFAgG_V?+Rz3}Z;AwIH4vZr}y_?aS9t(*;7*RN;a!=0H)ZYh1d z+mniW?9x0iBO zst{9Inb5im+AR)Tgwzq2zPzl<-v!)hhG3ltl$LuY7jaw0=n)^}K{LkX`{p)~9*`;1T^=l41$^rkuyD=BiT8XL+xI8Jl`_L+nV( zgJ3eYw8}%-_Ae{jjmKngW#DY|X?~iS;bUkp72ekT3zm{ErD8izVo}8la{>q{M|mB~T=9=63%54^8?oBj?S+UFFjU+_ko1thRiILJi!g3L zF4i}Y&}G41avyOA_y<6FBP1ZWj;_PGutt?M-0g8Un-1cwwQ)b|y$c_B?7kLnY^928 zmB$V%oA;)6H*U#v!n>9Y;5OJ!J?dV7V4Byd0Q{o7O_gdEe)%B2#(|;^0 z%lymvw~KOl9R7v$DLd;67(dkkssBEI={L!P+x~G4Iiq5$=A6+nvDOnY?h1SCwog_* z{C=VQK-=XXR_4+tq+_)S+9z0cRXIz#*IQzg4E%15ls*cArvILIO)5(}cW%}#=>^6| z%;;EY*mF33Q%SSF~dn{@@zvW8A#h&5MN-c~$8K9?EE~D-z?~Rx zLD7d&X6$kAodt&EQ1V%>e(b>pAU$na&4ll=IWb&cv3iF=JIhz7#0mw=^rf z<+1v3Ch8gGf~N+O-nXho_y{e-=K0{AHxf#bw#;@Dp3j~niu~AasYsN~^f7VT9R=1c zlzQxnG@YopoqcdXKf-@c==#F%VDs8~|7|1(+2e!Z`veCgVe5*bh*E$^l2sMz+fkL8 zcZ(WI@fJP3CM5jy#}&#wQED*ubVK7T z@%<;xzG~Rg*i$W-tDZkG!pUL`< zgqSCJEWRn)_}HjkgojK;n8@EUX1zlJk^nT;X{>h+UT*-z+IRJkQbmMHMQv2ybt^NQT-PJ@q`FS%R@ZJu}Cc*8AYW zdqhF>G4$j#qX9rtHD-gm&%8SEm2(LH7iZrPRq`xtvh|Vobk&YPl(18lXi-#{uhgD( zhU!S$w=7D<#R;BV<#eZAtK3TbyBPHH{e9fVEKm(7IJug?>+>a(@1kXA@LSU!Ij2Lr zZzmfI>IwvVrF+hFv9Dwecr#mbl5bt~XrmjV^8kUDGlZp}uM7%R(#J(TaijCX;$mO} zhIhAmLEZm}5`jSv=%oT=T>OWADvP5&UNh(;)^>him zu){Vs=3Sh)@YmlRhosN7P3)XO6l?ls#MP`1t~yH(#%)eA)%N`F=AQsJhytH2q1-;= z0c_<2?2j=u;uaOYD;mjuQJl zy8+t`Qf(scq;4W<|I>dzDLRM$ej7nkKU?=2@$!?-A?-bT6vXWoAKP?)IurN1w28FV zn`23hF0wjp2=>!oEFbvP%4382bwIx6*O3;&1MLR zVYo&u@j=sfW=p+nqy8SBcj6ntiBy)i@8Bua_Qhw3{Z@ zPC;NFk(_SBsI|leH{{RFA|YYJ0Xc@xap;Y*HVuEiqfI~OuZUk5=&0F>QAEz%B6s$r z(y+QmpcrN1&v-M)WluKQ1LA>^)Zh4G@i)lu{qd3P%4!9nxu)?ks*{lz-XYL6&IqtL zBej#%nU=x^7K%a|Bq53nTk?GNUvW-r5*|#@`?{f+IfnN|qZ@_8*;X!)>7iG20B88y z^4FytMy;0wSC_vhCe13zlpFi#T-?xB*3fw}j)lyo({;nTgBN9;c1lI5je0Y-YktsX8gM-1W;jP4CF>A* z#RKrbIk2Nzy}1si3>+!1`(+rgz$}w9_inOzN1fp2rSn3JNyJ@ah{GY12!rape2Q(7 zqGKQhV@OakT~4L0^GJx{{(SGsLM?w@A2cm)l~~vbmap8R9@Ui%YgJ<;b=bN*mS9ar zI0innM%HAkKD%&?LQ~rBpId6s;LOirU-4&h;oBTk%ecf?a@P{Nylr_5Js`is_4t)` zkH0IHfhG9pLJOH3V+(W+^-FA`U@bS0t}KS3lSCZi!K=-p=Ws0~|Wz)aaJ zUh0=aAOaFF(KzEJa(gXalpPmi;BsxKZlXuGIL}~vI@Kyx!*sE;A~V9JIP2iFQ*&5v zWsXR8+k;<%1hSf#!&oAQ*YifBpn zWsBDH^fK7IobiX37W%VlTVfnT|NT=-GLvy1I)JNJs=Kq>uLsY1N~GPY+1cZQ4P3sH zE2pw!ym|q5Wm-DooZ9A@MyZ}so#3?&-z2;>fXHf7x5=pg(aLUXOji$H$_#H|TbWP1 z-8BcD3~W+El$8CUWlErQ?;A{H&lD7oev}RM!Rz${91K%d828gMmF`z)XxO65vc~nw z9~m!*toLkt_nWCe`;1{YYC3tqRA&>Pw zis}rK>*+&UO8oMOW%hQ&fL_ma!k;E)kK=%&DMjXslBor)w$ZM&!#&b1|D^Rpb(k}U zTT)o`N->j}Xl?zViGMB~f4num0~8eLXA>Kb0CzsZWmv911>p47by-bpx{h`MDg4^V z?m!Tia;QF-6{pfLAIiN69~wgJo#?ods1+mFz_w3wf7{BHT0JAp@L)9=ueUW-?^XcB z!y@38NHA+C8z!_KS?CFzltzm*DiiQCb(-JM2tkYIfl`~HEWtCmrt8Ef3jn3(h8KF; zV_fb9IN4>$Ppj|T^s2I5+8Wer@_E;aN7so#6G}@b_DOf7oSaS6x9?EM{U&kG} zN%JH2Nx6&_rzY+XTRXO&6M)y>HvMl>^0u!1JA*}8JY%+{PX@omm}?@)DzN?CcaUvh zXI5jLR&mv*FHfs6%4i;2J@F0U=JLt6y34EkCdzXQWAg-znFLWMn}+Gn-3wf`))JNHsI?oqklN3}SYb74AP zD=6-+Gq4S>YYx{4Ce>a`Vo4CAx@!i>ZRK00;!_R^gP7(_5-z2-m`cF+=W+hKBtxw0UvG?&palxbpq^?hD_; zY7$n&m1^-9*nj0Gwumb4GixQbwGVOXQ}p!SXZeoW`Z(V+91<*9Ad%A+WP;3O(?OMy zuy`eR-=uCq%(o9#V}_MmE!Qa``_AIx%LgGcb4^>;-t@`XKd8xxD=D$;;bjB*vc5QY1pk9w@D)D$% zWM9|KB2Bgi%xIp2H$v&!SI$0~c1A2$&zV!|NWWq&0zczS0=CoBAHCiR{&W~n&V$rB z#l&KjKgMQm;hh|D)5HRmIpu0nUpVmWxFY>jHt^%{t?ToN$y-OMC$DbJMWv^UGO)io z{b6k44Cn-KR6WYwe8=nZL8_BA52@J(f+OBpA5@U%*hYZ593C|aZBM4P&BKf&=k+6+ zW|F#WH*^Y^&s9sr%|bT)77B{%F$A9vxu8DS(aImm`3Z8hkwKUXd5(eIhZ}=ddN>x3 zmbcg9vtj8m{*TR_5i_|7e1--xoVuczI4ov?V&;sU??Kk=O#hyqF2iY`zqtGlbM*b@ z?C7GMNPAEK^@2~EKK)sj)2<^oK?THcjn-rJasab#rz68{wzRv=mh&r|$xrzU!fTwz z5BZ#^TH-&tj!z%wmZ+-izQFD4 zQm8|jxksK1E|T+xmW)JUJf@pWG!{wR3r|-I7F!xLY%%7VIqphFv#-cIS8omb5rm`u4L%WQ>poAC? zwm$+2!jx|HL+{XlyR)Z~Rb!FC=g&rTKYntl}PxKj4{ngklH$7vQ()zPK7vfZsD=hN{Td#a&bh^ zn3w{Ln`5(VOGAb9o0eJV-AdE@XpXcpsu|XST-M-lVP0hfDt2%rQg`OJOcRGo&4Fo8 zMmLmd;-fYkT)(3Gwnkeqh2`A=FGKhpY3w7&=D$W+Q<;vmC2xr^2Nf`PPeVV3Fam8C z599v2V$4m=@oaP6O+N}Y&u&IMat-PzKXH6$juR*YdJFGN`6(@ zBt0LS&3UZ9-&S3Z+30wEM}K`ENVpC%3f-dRB$|8^e*P}=D1xN&4npG&Riq>dM zBq2P*h&csP6^8O63I8$=|6@2V{ibU5TQrQPDo&~5wr`YhOw$o|)4A@wYVN&{kIVF> zDJMWZB6Ud-L_~|Cp_{Rv1j-U=;)5vKy~j&b;k1AH25?f)RrnAd_)a#2XIIycyEaSb zzI293Y!91bys+7p6m9=FaByNQvbVR}edxy5z_qBTXqD8^a~IlP%kXj(&Edqutj2VC zL-{N{p(e7F!&w}~YuA;M&dzj4uVCBYM8S`>8M1Kkl~vz)Eag19_d!#FWL(Lxnl;pZ zdob{>XQR*0D)yW|dbV?J0mL73Uuu?8+MSIUSY~wdIs>=IcXEE+BSk1Kc>iaLDQb_-}e3&@I7_{}htlg7Y^$~PG9_I-Nzi-U{sPVE82P20f4W+O#@SGal+kSwK*fn*S0aL?B$qW)ZR+s2RiDY zvT>_<(cc>IlZ59J^_3xkD$-ckCbb>j`ctb751P97#d}QD8kRR!8xE}7?Gv2mYnynqqRL*+Rs5Q)58ly-$6Q02-50NXIqq~UN# zJ#eW$V5ev$2DnRseUQ+Di{1o;)OGUt1F8t|^4Wywr7Id{Ufqb0?r(5#e2MKKVDO#m zeV=d4$)b`D1m~?Q&Q|1yrX4-LR-wqVtNxhS`vkc&p&_KdGDH#A>CW*%D--rKd0rCt z&vgdfarF0@m#T(2somem0~kqivu7de_*T8H3M!0$>J;KYMyTAe6F!u&ms3De?@NoI zpyE6FE}&3$*Mmq1Z{$j%roJk(E#`dwc7 zu_OOrs(O@pR5g95fPQ`Eu~njs1NTSswaP&;W+9}q%O7k5F=q{zPSe-hxWCtT?~A$Z0Vk4`T0#_|BkvwG^lEzAHrD{|zBy=eE;hh+vx_cghKe}pzRxR8 zIiPoS?|Q@hd9AjA@P3E$0k^YZXQ7;|1I%V8$6w74o_Bvx^LRSi7ehbPBemF%qx&uo z87m}2cquhbcK;uIl{h3$)5M{;OFF+!uBTf~t{&_&^v;Z4n|QIq9&+&aU1|gfSUHE* z=YZ&Hb1Pm623hzad_Dv78g|j_KXCm&K0-a=Uld%76EI?UQ&IU*R}~a2w)2)HzZRNH zi&|#X>J;J?WbGW?#2+P-FXMI2oT=YxJC|{V40TWPV(-!Bm3k#wpZlnDL~A0U-A{AZ znpQi-3m8~2#c>wq`YTby!qL*)5C344kegEw0DHsh)e^>+_%Lp{yxq4(3~;7=?4<`< z;Z{+=q8w@G#(bxxzlsQhA*ajNJUxY|oiQz%t?FobX>UiSNgi)luUn~P$8w;v-QPPP zJF=t3QMIq9oxWD|%OpU^lEG;MO;@AbaYq{io?cy3L~(QGR#TYxnb)v4p`x{C6p4Lg7&A-x$qZ^|esh7>E^T>_RH#+%MHN*)&T>@78#FF_b#piQ;FJ9KSEJ1RX^tq?Lv;MoS9? zla1R={h9c+-;K=0_2Dyq<^G(d4DM%UEhB`RgtfTm_h7SApGZ~D3TQbxXB-1kU)C{7 z1hEn@TfU#M&L2S_G$p-F^gn)me~TOqo6JtoYERt~(Wr%R=+K&$UPp++=&UCt55xQx z^ga*e+lR#qru$%NXk@GD<-42Tm}Pam>zCTEqHGf@$=MTfWVR66~Gd00%oJp{zdeIP=^M`G0%-u)GFt9Z07H^DO0 z5)lv|s(618cA_l2ql!BJ`ob3y$FlkJk)M$*LB9TY3gvUG4PEgghkOHdadyA z>k=bJuedF6qpkpsj`mzfhxKfI^rU$0L~{Qf0%kRPw*wx{|BXFXthb&qzHrOe0`fDz zR~SbtYGGb!B$$SWJ!q1r$NJng@qeCRmi_nEEEA z!_z~#;(WT>r}T?H{;6Dz&9`54Ojy7FY`m`5E174lOI>XB{5skC>Q-IYi#S;@UG{kU zrXGjrFc+2UZEBuRJ{6oyhg)sIhq}+3PrUp1#WgP(L1)N^4pFLn97Fx>Y>k{N&BCB| zLH%Fc8-HTkj^nqJ^}qOSOk05#aZRbZrNg2QI8A<`v%ish=TC>WKA;RbAOld_OhHIu zQQP^x5@8b)P=Dq=RU3XH8etzqF>-U~^5n5y#^4W*t9YDN_YU7VfpYP4Z+>^|=2`0Ct`0(Kj&C2gVX zAD@fqj98s#2+gy8_nDgXMR_=PAVCk&=7zzOuSy{IE7~0$EBVqpU}>uY3fJ`Z4zR2I zL=AJEOev~B+xQ+Ji^54mFN%bLr2K%HKNe<0zY0LQjX0HWLcu| zsMVFshykC1&F?B#0hzH;`iN<@=FY@X&@;KGISX2h!yhsFq@MK~^93cza3$h4*_SrH zZIC#10!aKR!u8aOlL|{9Hai^j5yEle>hr%PigF?HN=wcZUj*g!<0SqMbfbFIA9k(j zLYEdqF(}yT`8rWHOT7rU=fUwLkrnXkyl~%xQn#Do%?4S$S$hTbwB35)zElYn(8q({ zncp8X5=&9*gtQ^Q@%xY;j8r3E(N?3tS2_)^0|PqQq!kx?M{=E@wLpxL!@M=_A6c5` zed8y>5qIwFK6m70;NGgs?F|}M}YUisoXg73;B=go(L;yRO+S^ zN2S5;=}FyD#8MR4nAi5yZou&Y<{oPEk!MuV9C$gvD}2QU<@;M#^e3y~gSUOlZhtb3 z+dQ~Z(Px@lQ`vG^qNnW9Io9D8PzWP@ZZ0I}VRy~ZgWp>9O_q~ zT!Gmu%|N+cVX!Qa9TdGSlZz0QkQYB@M^U@oW*F`4@inFUNElhc)$gW+$8GUf5h- zHQ^Nd2khA}hY#eQ;-&0M1`ss&gN_Ame1B*5c85OwJ5&>IIz!ndh_LW)fJ$cX-0D5) zUCP5|TbIuJH_g`vQ)&)gI7WcuAAY&i&WwM=z6E+>lfISRj64_iqVeV)P|zAA-tX8Q z;j9tJ;l97!7&oaS-qmDdVW$#$&PAfCeOc29OA0$lfcYYy@?Ue+v$=n{@n0CT3LCSW zPqJpWG93&tBaX%SAhDe$9h?gL%Kn*|3eUQlFW2j^Aav-+SbJJ#y#{_nK=lTlec?>2 zn>&gJqESPPw%0XwD@&Z4Ab>H!SXT`TAH?*a=W+wq zjDI1!*)#ouj4!VyZ}scMd>q2{2*$gYb24pPgbdW!oxF=+pKI-szG3PTknIZFY<0x3 zfBt=U`8worg4H`m3^1%R?i+(-Ba({lvBzVdShm*vImg zrz-!m{%*Nq%HeeMr@|U;&`SQuT)F^`jA8g2nfFFn1nkuf^&czG_OXkAAmx@nl@_T= z*SQb&r4fq0k*`r~F4a6V`18B5YqXq=tZ|78WB427jUS%8UnIs^ROnvp*hMcWv#XqO z_u#p!um|RUaJy=Z5y*_)sxl=D!=j_3Vp-t>^1Gk|@p3MsE0~QCm$@?Qwq9C%F$1>| z70NGCvCh2Zdf-)W&+W&coP^aTS?I>5+g7uQ@$+~qPC$r_(TsjgQ+L(()_oK}@?hdK zwt^XQqkt^?IrbV)RIG%=!69S3L8Eo_bclObQr_lHKiX|7(;2gwWp9K?N6jU>?|cF1 zc>9rZ3#;gLrIaP$@|YsL!)O+^awl5Cr5zfq7EF6WJmykRqPhuzz99o5Ml4U+m1HjO z<7n%CUtrS8Zh5nf+Bi!pZj$Tw*G-D-Dos{x@&tnu4Rx|Z3yY$ zg@UFtu8LM%DY;Qyf4BW>|1m0)F~|4J6I>IGbHCWuxok@&lY{WhVEzZb(;wO}i^t9R~q=SKEUEU4jmq zeRBGh9=s828T=FSj#bpCeagGC2#6b!yVx7Wg!iw0kzCuGE~z?=-qYnKR)*z zjx59#mTTZG$A+3kQr(po+{CsbMISBnT_aU6Ky(Zt4uYv`=sOmyw=l`U?tU1rBmD}$ z+1_jWnD8R^9y<8A;TK^`NId;HCo=@b;Tdp~+DYR3k{pmL&LXwNvmmgU!lD?N-y{z7qH|PP?#t7SzZF8a$Bx|}qSp%Y}6zcb6v)A)h3p+l# zc>g&$KgT<$=^4^Lf?NyEn0a({du4qy0|wkqUW?8XY_`Zu9y?ni=5}*g-l+OdWbAR} z+TpM9Qp1ZX_2Tgq$%k&QqAbK_N8(Kvdh!y&zGZgE)8&VrOBoe@W%dbf??f7)wZ5hS z&Tp{-caXM7#)W%lOcsN5I%yNTKfpJ5Lx-`gE^paQxRWIZBtp2`i<&(Jis&4(>Yxuh zn!6uv`#FVz<5Z57hq1Jh52DguHLi+fUTt37IRkk(IcF5^Bk3%0_LyKz9OA|L+yTz_dLy4+Q2;E3sHz54q52MCsEH%exr?;00a zERFvj8iH==_pJ?{qO`^M3I2)GJUX#M-^W2tIz3f+hwmpXa`bk8X(1)>vbd9~ZP>c8 z8dckRUr%gL+h-F{L!SADn|L_)h2zRmn^Ad~@1cl&nW*KyO5zB=NBxP|eo`Dz69kN*8=h&8^IFM6tm=5rLrX0J!I$Ujg@v4%;Wo9f` z>57k3@H=6L{Pqc`wZ|-0B6X_uCveI(eFg>+_qv3T1|0KEii*w}PA9b7QX+~u^+Hg= z4W_4##$yL|v$bklPZ;U<=Fdp58hfGj1-|~zAp@sX8srNJp5AXww)Qim9Bf3424`s%&jO9i5h;0V=!o&aA>e9q_bY zqGv|R?UZbZI`KR75GhRq2*u&ITRqjfIcXQZ;0O9CQ?0{4YHN4C!REYF{-+8NX-(1Ud)61afqijMGsL%a znTJY0Ce%Os{)r|{_TvP>#v(gU`@aVMz_nHlG^t5;mInkkn6C+HM&RxIf;Nt{ZOWJe zURQ2_8_m7Se8-JQmE^2f_bb71H|d^0EXktRMCL2FCuhAf=7{b<1V4DW(S8DS^t$?B zsVn7OX|{z**WPd_s+rE*ashYt8?pX)qkQY zVdmZqVnTR|;N6?jhYe3Iy@_&Ccwl$L%a-u0;@vNXZJFI~)AtBHY7(THl;6P)DFeU~ z(GBtSThWrbLEN9^ZAsa(F7n2SH8~7G@ebuoZF$Z;Fkl~iWm>5XOHX!&;U5fd*P>hI z4b(hgZ`6wxB{m}UGV}V5eVU4Z{_#qB#4sXDn2kF_3KgYV2Vu6m_Sx*f9Q$4ftseIZoFMR%Cx0H7ze1*pvCB!NiOe zi<05PlMiS{j=Z73iML8)*Uuvnw-5zMPHXo!vwVV!%bJwy&mC(y!wH<$vj6V2A~AOu z7ZI{E!acb#vuR%9*QDB;coXV`Ddac5#kPZxGmSdV{fyn<+V26DOz9(42=-6elA9mO zOI*1dODpy-F;1Onqq?N9skyoH5P|zFl4b45?olPibA~7!@BuDY4!8L}@JR*y9KyG^ zVg;;HU|6h)5AOqF!tA$zS)Ay#ay0TzviW<+L~k7XkTQ~Yp8(OT*FE72`bFbjZ(Q?UAQ8>@Z~7TFb)@?kLt3SGAPLdf z=Rj9pABu3&eoW|XZC(^+iXtM+=edPzbPa){G0Q<2V?8`wVc8Bs&!}@$W}(_+^v4Q5 zgGL#6*|C{72Ll{R0`66BK35u*@AQ%vE?Sf5xN=cB;Py{yFE2T7C@;pF=6~KInrqW> z$h2g<$1W>Tho7VVyCL{-&#L=4bH^l$udKfxuRps^^41Gx z72gS7b6i-}dsfJrI*Q~+%lW!RYf#K;Cu_=kWAWY+s^xCI1STXrj6QQp zA|tM4?;9&Q$Hf-0Ud!~*YQpegz+152#>Y7A3v=v#k0F^3?oOVCOuX-1b<3KZOF6!+B6xcfuU8n^>Sq zawN^>pC$Nxw916tfCHDeBmP|foB>Z^8_cK0!j*3~59YjBvJ06|q^E;T+f$A!wcI=r zX;sGPuQ7Z^8ApX=z=pK!Y1!edW=7J0%ydp$71!i9{^>3Qh9qmP9dyrd9B$d4ExPMy z{ieDRYs8rJOwPvs4R$K7O)X4sX%nNb{ATxu;@5-Tp}BIR&LGe2NqHE8zPxA2$bDON zgZmcoJi0a8G+?Fgx(2aWCTv>n zXj@6#2SD@A8CNi(NsZqQBeD<5(B!3wIfDu&zSVxS^Iy&c{_k9f(6n%8uc$fAVy?Oy z>1P=b%ApVPWgcN00sKYqnA!VHFqVegnwS$Mdj*D5YN_{}Ip1u}?Kj2wa<_k5wXZ2g z%n`oKy&g+XtxYS|abQUJ`n_}JPqmIm1QqS1Jqci{j_nqV0?o&zq*s|No7o?CyJ z7f^Oh_Zof~WK^b{S7I!^-YU`*vGtkmn*c4inhN(7qbIXiM9;j@(SEs0 z&AI_q-a9YWaxJOUMP)_0v?R0rY7W1uKJgC+5R|a`V?J3_e27K|adz!9bT5RK{KalA zT%j;>rAK&RbwjTn53`1(DdY!5T7X@Nap0_TOlspo7f`9vzfb@?U1=U>i!@sK)(kd` zK=fK#+!hLp!(egzk_sG#w!2sw+!#3*U6UO~`}V8z6c-atwmC*Zxk-mFr?}vNj-@5< zm359a7#V)`yfmI5UU%fi7~>QdfL!vO>jv(QZC+iv16QFYYC7BDwg*U+y7f7QR3c&I zhhnIx`=C#eF8`t#%JNC%FJF=%OJe<*1rX96LL}=kSbBDh&PHdwh19C2m_N?cTTTAC zxOD~p6`90H-5=m-1g&MeV!Ug|Ku-1|o4)pGjXK7X6#&a>k}IvQwuc_tO)V z_u>1Np1Ytha#j$J zKR5UJ>q;UHLg!itrh0X!#Gh&wfKhE;y>B!_)5l@gb|O_ocjbfYL8H&}h5%0ntm-4~ ziq|auB_$`ATzu3&KZ+*TujdyjkBn}M(AOVXb;Y0zJWGt8n|TIMm5;j=ae~*qP+s-5S&`&H?X%4_dMA4JypIx7N(f z<#L^8eK`^q6KbK!`(UF2b%$@n^gldz2r>84`Y-3fqj)DDU6Qz~}$iOpUWw#olWft#i3)$6{9wZb?!4ALVxH z!p-NtxObBWIh`-51?PH_%}2XW6$;pm=Y0`xTHLVPR;Gp2TH;1_X~^8$(w*#I?!4vj zm77eCw4cwifvkB+@5~QVj%WpH^MjgEhrGuZXB1Mo9-JauL+%pTjQHz3d2d{Rk>v8E zDSyc{e)`ZZxH&XzfA&<~=63%I*w7D3gQu5;UNk!P3>1K`h;EF$F57hGfHFy*))J{M zXXVpi+uKRl2w7x>ANve8(1tiUAgFhD#p8=0pCBI$@Jnhr*lDSHR>Q(t>#p)>o51HA z()f#?SX_Wh(uF4K;#Exa_$+&CtkahB`qwBzL27N5_ek;RGh)kq|B`%)-NVaBX!+)1 zeErj~)_7s8(Xk7ho+POljnc`>ks7?Iq_Rp1sL@p>^J*3WP`>ke;w~>WkNJWEa1pOT zCMd`#-$2A4Cd!I5U;h$IVTIQpZZ40^6_#~9X`NpITdXB9jsINj20>|e{;kEROn8`A zE;AXUzK1%vn>v=3-t7cKS5Hw7e-SvA`)+O;A{gf7R7>z>Je6!wsKU9;INN*UAgv_x zmBso2=F@ufa7U=T;)D7}p4D}Jj+SMoFj^2}58+y!hVotO_axgJk~{zIwRmr?eXRT$ z@$&kZ*(jl?(rZOGOj}a>UFnVQ!{RKvTMh1at|bP-p1;&JQy+_n`&sj{C@zj$?qy!9 zs^e_<#(nBhw#%bW`}0(E4IhjTf4oP|>ueshU9Bzc?Moo-Mw!@KlxQHlS`vYDH%nsq zn`B6$iNlNJvhG~VHlMejt`H0T?pl;rZ%KOwv!Cmhvg15`;wGMMNa7PgaTh|Fdg0b^ zqc1aFtx*nDYXa`CYrrla54kKdbBm=8_9>mT^l1e{fR2aEOkK(xwac}VpGSjAphvCu zr+BBQ|6aiK#=;G+O|r_4f`l&8X4FLp`e#uQ$!qH+r^3?D%kalF$aYgoA*;lk-I0E0 z<3+rYd(}pXzS0N=;CwU-qqf=RD;m>ltD;Un_%Y~u^&O7FI2)`g=sD@pBJUYIx)j&7 zt!T#*1KC@2y*JDGcWpl9GdmfOQl4jIp453CaPheev96tPoQ_My+HyE=+f`9uscJYS%wq--uTek~x^w%`WQirp>Wl3*=?dOR=jnF8 zTOR#^3iXnpXQvh{M0ouc3Hl#FgN%FT-FtpMOMJiyzt(ZH82*Mq;k;*@qvVB}Ra1N4 z_Ge1yFZ_m)Le91}0*ho?Wua1u5Br{yEh>8uSwAMwY8dah1<1&z9II zkx2M|WW9%5(rX_-URhd?DW^PXPAW@IS(2K#p^l}MCa0;nNX=2M+zT-t^q|KR@IpZEP9uTe(HQ$ay2KN=&aTFRtq zfl{xqY_$RaQbd)S!QZHlMr_4wP5W$^Ic)UZ5X^A(&%-6|h}q!GGPasLX9Vc71XD9T zJaK2yh1{QkXbXNXCF!H~gR-kdE~0wDIAmeB*)Zs-kA}x;WoD-VC#~3-URCUSbM<6K zB`LkvlN1NPPVDI@r{vv8oBZ1JAH2bNg)GHda=B9yYuNoqq<1-;3U zdcBX7k)AF!S+rR@CS~PSpQ@EnKpkxWek`bM4{$e;8*6Jjda=>%san9oXcI^)^7X9x z)*2X+ji5a}+5~`}@kn9=(58FFIX{kz|N8`rQX(m2uaXp>ziW~I+b5LqT*mUR+H$5DyG@#Slpga*?r^t#0(_GB`bUWvb~ep;bf8;#$?Zp z-hXuCqF0grb4lC_iJte(a-kc$T|$=IpITIX26)Z&_K~5nNMt*!zU%12-o5a90iWD} z$IOk_XnpU&Zzj*a?!8u&T*E$++AnM4SE}D|dh1(%X2ID+_|R`K#agip;<5{H0y$58 z3#W2k%&6)kLwP9BJDi@tZ0Ev%FqgYz8m2C(*pz+Cfm8nEkAtTv(GGjqy{?D;L%vts zYO(Z_zK{8kL}Ftl+#AskSh{OpL7u|?zcf=-_UZzvVnIT380b+qbVa|;aJU* zR?N?$ROvr#lN7^QQPfy5{fo*D#$i>%Vk4s`us1yGWm0=^6|26C@mWHCTPIF^g;>UG zcSDSYw7qtPXWSJq=kW)wbr|0bUH+^%n(X5A-nvORC!%_rbOd`%k!6tuaq+tY{|Iku zYz%(A6))*`kC*Ts?eGaG@RiuU_SbY+aheW?N`dDPHzsN1F7>}#fn$$#=E|*9$9c7 z8Byuu6;b@H<+jD)RdyQZ`mY*#`C>WhaJUyUq)6+T5M2teq-e4+WDb(>TeYxt?6`t=l++W zb){M70%%7(dKi-A|Khq=HTGzjtlyadBX%(yW^MetW)y;M>=~{o`$-Q>eQwNJ3byiTye~wT3-oNT?5gGf>lSt zDQ`Ycs}lRg{;)*&b}nK_Ib3_;cC~r|6rV?2tSVl;?O-F}~eXyoK>-e}4opFnnh({)23`;}RrZ1vn;cP)h1iqdZ5Om909 zuJxbf26Hp_kA++b%flX3%pdfdt9>3Opw-c@XK9ykWz07cQ&!~~rpSgY<4+jiWy`59 z@t@R1pnQ9+7En>*t%YmsT>@Cd4ZuQDV1ipLn>bnqa0*!1sODuB9%{uuTWTD6|?$*jj` zo*DEc!TsBW*|uck0x|2?IJ1c|dIy$12;3ib%-y1Lt((DUA4`7gZwdXzn<~M8C6@G^ zt;wG*{DvjMdJcu$QLMupC_tS42^RCbz^?O2e4zQ@kfY-#@0*FmVX_C9_m8>IGUi^t zE1NufIcP{K`@lP&>YNWhctx9X)4k>prHhgxW%{#M|IU#r@JRJbvYl_FOBNLX?Ntm0 zI1gE}5v-dkoVA{r)3=tN1kkK<%i*zN+CR?wl_U2&Tcd}<)RF4~QlV+2T`t@M#a@(w9y<2;MMc;*#dn*yhrq!KPF z%0JAyq-fasV*7RlJc`$4a>m(>(o zEs^i~TdWM6ll4CJ&oWsu5p&NPO(0=UO;eB;64Qe=c3-$Cjfa^e+Zm<=wDvxSBR*xB zr2ZM^MT;UuTS}A5n03(#y55itFj9{sKss2rwirRsihjl-tEvf~wR$KTIzJ#zr zI;wbN6`Qk6^&j8ap})b(r^*9(ZPuD)lfCsWgu|dLPpfg>k?5j?obEsqL?=y*{WH2r zlb&eT!_M^#g}i!O9rCkaw-(bw@xNT#NYzOL|Abktw%o2^{{ooauukjdOL#*vM^)<= zT?^0U?E;4V%*<*mO~|kd`NKXXx~ECpZ`AJ2`p?GQyeHFbS>@s67me`XQP$@i)vYz- zP0Fu?1Kf)T9Mkn#>NtsmV_YXnYqzs1=68U-OV{FC6Qt&{;-ptMm18ozih#;5 zjr}p9w|vCdgt7LS(%kls)nV{j3&S~zR^}FX5>}_Z!%XT}>ww-H8r~K`rxgcMW$mb= zGfLrM2-o4r8m`F3Udwk5`y1Qr`CKp6-8w?+8g4MU+#i&HyZ=?KqIUmP&k+ z{ocdG#2bmV-;{Y!B5^#OnLwDauY*Iz#5Mx)s=P+jrqc_D2JF3&`D4a&2@!e*4DQX) z>k(+Jp4qW`1HkS<)dt#b1=MR&4zuq@%gm|m3kH$dtC1m%r#(!f$+Llhe(cP%r`~5a zn+eKO@x}0?Dq5}0pexWCzp0k3{J>~Mc49rdt)KfdQ`?m8!R`fhug%4w3}li;U%*dB ztg;HCc~e>nRn;$z&5)+=a)5o&KsSn!)iy*gx5HZfwg@p{p*F$SKa%o)wE&imDMBOE zC3ttbkD90TWE=ZVN!-+wsdleqp#f@oVE)ed$%H!JRpjqaH?7#CeA}c@j^*;EuSsd->85 z;yOx$&#B`Di839PaJ>L)>i=?D;lDXeR#isrpiiF7pVk+w3;%MvDVBWYso&_?!;>X? z3u})$8qg{MwKe4re!LuYkh6CVk8?Pba2Dx)&hng|*h8Dw?#X7}Rxq2bX1NPRw?~pE zc1homb$YUqH2#(BWLXU{nzk-%&S3uVkjeVl3@JWW@w9C2=B#AKTh>Vjh<=0a=)E$# z>ZO(`J%*bKcc5hzyZsQl{j$X4+NiNz>D(gY*}mj0=hhg?b;L&h_XG`y`P|96`21>6$?P(-M6}_s9Pui!tWe*tZyEiLqRQ0H}uuuK`l#A|SnO7&6`@Pj3 zY6D;dbFYwV>_f_P&i={b4%Z`1)$Dk65^yzMj8v`aG!CF2~poTx5-E2J+uq(^+R_mp6CckQvVFDgoeN#+?h=>}DCzfTF>Y@z#1F0pzI z*WM4%uO-MJacYaYmOC%jN`#!$8xY&J>{+xgp*UrkU!6sp7~D=aUlrOc%K7%g=H>X` zGbq{AX!FR1xP&S#R84HM?_nGMiOi&3+_yyP`XDP@*}mI|eJ!G#>c70xVGI}_<4u{h z$nts5!%was8BHJbhVKGFYb!QeergFXwVY70VK-egOfwkgNi6M|qAa6Z6VgmCu(f}` zfK>mzHN}lO?x^4h#`?)Lk3Z_vt7oVEUoGmdqZhS@wYW%?^AdXk3cLbF_hjK0I{}IL z>B^asxUJ58iRKap*W?rYkS`C0XfekBL{NTx{ff5VXw?SC$_OUkRcj5*$HG0)VzjU7 zxU9L|v^dyAvI4~#tNR;Om1-AMDAN-Kw||w#j1Be71oNNC;>#!n>7H6GmLG570>%e0 zp{69OfH}}$*0)9bJT)^V{K|ThxKf$OPi_# zsO;lZp=^-EQW2V2#W0T)wH$7bd9 zzt5tteP1k3Pg*oAe> zHYhM;t1rjm9`~-Y15Z^FPq=Zg^)++w@5|eGvg^(t4xzssB(@)^R51B20`&s`x>2IZ)$hE2&RPF@U|?*lpg4cL$1WdblaieB)UPo83Wo=Zcy zt)yrSA%4+wDeM0M1gsP4%ER|i(!$zL=WOUAalC0z`A)sF&OO)?Dtvf#4C;TolKEbu zqnlf2v${8(MwT-cK90Dsfc9J0tsPu5p7{xF_z_ z*$g#{Z>bkHPfxe1p+!8ozq0ye4d;6_?z69Y?$;>$Cbp4y8bp#?xzGH9d9SoFdO|Kk z3Rx$C_dFDDWmE2Lzn+5JB`5EciILFI%tB-Rv`Vh3s+L{JNRx8seAOT|Vs51BzN7Lw z729*X_3@*j5F3SS3fd-i&e66~s%xXWk@e__17% z$Adn_s|8HG6~rR-_&CC;@P&czH-eW)Vsm>v-wd#5IqF4MOmZl%4;iwLL0AWf+noYh zbL7n-@A+CWsnzmm#pH1t6vrQbPSR?8n45lZ>lB8iQ|4~|iw@^4k4E8Y{gy`?NT}2P z*8}!q@1>kOY`pO~DYr3ew_kB7{ar)uL97|#(+gDzcW2d?f$UGf&5j=auf4BgaKV>B6$6-E#lxvizy_4?p*M|Z8*D0XB~TVS4e;# z-EephY=E=-uzASj=zlLPGV;VfFyoEc;!gE~E5j}`X`MF;f`^NYhE>%EqRr*Ob&Co? zIf}#~l%Cde^y&*e5<4LZw`H z4WSLJr=V{v#LAD6;JC4-m0!h~U2VKLdx#BaxiZoN5k593IxSk4#f8`_Tu(YTkdc8U zlzN7`-=xp;=bN8dat{x0%zmg+OZBCkP6*iSO5Uc<8kRzLcdoiR4<1Pk8pt`BtrFsy zg+TU7jP?IW+M*ESQ!0bwV#(R_NgHB2S`$sjppV_)o_%r~7C%VWu(;l_rj4JPSE(J^ zmI^-OM#OFH)ibJ?dBue@YjdAvPU@bS?4NP?=H-@y9ZB5D(tG^^gDM)(5YeTVVnsWU zh|bp{A?Mru4_#^scT0WH^0y2Jl&o=I=)OCZjQlx2Kl&!*E3DjW+)ft7O?TPh-leod z$-EJWgx*$-q7`FT1TO*Xc?!_lT%Jlo%Vv!ZO<6~{|1+G)Rg@y`SrA!At%!Q?d^z{k zs*e!imwMz~vG({Fb`~hpq%P}k?gBo$$JJLo62TeA5)5%!l43zmh`P2M{Kb-#UFddn@9V>oa zr+&w{zs!b@f)BR+*pz;3#aEgwnFrx*tOXmYR&TokO_2YEQA8_7(T+5z7WSee7q#O= z>c;21OX`|4_I+x8@nY9&(ASJBpTcAh`$_59I7T$o+Lzi}k(y z%BjpFKRGArr%uMHTv$?5H+Yj}W)}0O1Ih1luzMD$><4{VBNGd5M<&flLxT!+H2<)= zr~)lIRW65CQLd?Ka#(vRr4-060B>9+oUF^uA6V6zctW#nH!KJdQHI*UXF%PCWlP3h z!(K*)8B}cX!WUji3p?)BHNV^jrXzx<*y<1HFWv4;hUhvy|Gocx|QF!;&YCG$8ibIeZX5kjk z$`okeavHVvPDxOold;t^WtPl}mQD~PHjrd5xT{(j3MN@s2iCA;7y=!@&x)5;Q_h_~;=-ps5RQz+@7r)v!#r7-dTxef)$P7; z%e_HDPI>f%xuN{%#TLcXxi?w81u7zx=i{}CYmBupB-cd}@2S;IqA!BRe{E2dvo6JA^}4mGwP)pp=0$a5e455^Pw zl>W=#r#)~*G=7V+YfMR+^fz`Dv>8QB=;Ws7*)=|#<_6}Ql`Y!8g03-NgRm;=*FvdzJ;Y{NwXPp)hCi7+%HXKVlXNJr~8$6dOYHw2FzFKW+2=>T*!D0Zz61IP0Yn?+(J<|!m_dCwxO}-Ft*aH=fGos50gg7G1+$a-RVyO z%)`}c1KR+fd=lT=p8vpu4CFmnxdsQDYY6@s|4^H6^h6Nej+*6pj_9E7Asp5-y`L4- zGSpo-!_eDhgTb^F-*)Ye1zY?XLy>_mx&bcxj1s9 z&qyJ2#*dmqy}2bKn!VEsbcGd6d*TTi>?TAhDz#?#l8adccCAbYa8Cqzl%SXdtwb$E z=%y4wbX`8;SmF>r#6m5!rE{qDnT~ zKgh?LN*(9Ug{>+bNDm`Yzy}kh|LtY12ZG;LD`p%W^I4FK*~FMiv6k{yB?Nuv%-bul zi2D=lHSGnH@J3t;Izi#>zW{+q!{5}c4@LJ+p0+*zkmh#$llSgjJ^QNSPKQVqwTgN} zS>OAwU%NO?Dc4p1SX$ncgBW^@Iv}#4r5ZLD-K!;8lhY5~e4{fgszx6Be!OB_>rSJi zo2BGKD5E7`w_W~`tzf@%)>Y0-AT)t4jLsj!^5&jzZ+rqDoa}4SfSgoWa5s)>Po5l6 zOds8?|7om6W!b6h@#4p|6^{w3JMXx@OYRg^B~6n<=1l>ZHf^Oh;Rmk zy0e<7-pbO-N>^*|F5oCh;M+Y#DczLAHZ>ySUnp0fFqJ9kknv#(5FxLdofbLVd@d2R z+i&A87YYT*y3ZY`MV*i_wr6IXy%i%Ff$S}intVZm6RIEtw58(H+u62Dz1P?$gRVbh zJ4Jg38qKvXj{cD`m#OL!amN-${9@#$|ZE znRIq`7NMDM0D8zQMauR&oN~aMTzgAM)e7(Ld%Hv%&}fAGjq-R6!O0vzx=0WcgybBI9kP>6k9{;_ zQpMUMh;EVrq?9bSjJ(Q)j*UVlg3VL>r}`D)D@k)r`cRO{(H~sXMeW1)Z!Og~5;R~A z7d$@{lO4U1`oj^r8ur%NorV?jvAj^XnhKSaaQJkauHy5!#g^`FEg{KaZ0VsQ zhkG$k4H$roU|g()4R0!ZfC`_Z;zuws`78EpSEVqg`&(a4M+KcmRlz)M#QJ@4oR;02 zqbDa=aHSsC_>-35LpPQJgB?VO{80P*_SDsC(S~6md*hPJtq2&i+e_!(rZH)3sKA|@ zvlJYn&vWtlD;UMZnpbDtHJ*0k^#QcR-)alq_Ff;0nVS?9tjE>vD5*d)K_FE9quayc z3pw$G?)V$dd%E6op@_o3E)z!TD(Z&FALG!nvZtt^#dn7BD!q(8t;h0vM&XiBL0MIO zxX0K-A{`isk57fR;0~}S$%I} zrfUtiqa4084_`|{>0xYKX06p6~qi?XimvWFIA9o#3+PE8Kd&Pg*yi~qS(_Sf6; zD;qt@?f2g<(VXl2+U(CM0Cgv*PH-0Rf8OrYFaP-l*zLs z_Wqt2^uY@Y?Y%E#v1J!WX;DfV#Usz4{Kjn!_rdCzCzBndjC<0L=O%6L>j*nT0kXWi-9DFxrLtGyRb{lJzD^4tv>q z>6WF@yIz)#-(>3vSlMH<9E|1P5|nEo<9bqXpkE*Gbk+-1InhLUwZrgJx(ZSr@Gv(A z_m*`^jU@+zmD1jcg*oHG9(tgMEpz@f5L`>_9qfH~+BBN8fDR}0t=0;by?!YGEF%i% z&TAx;#9wiico;8_AB{KkT4=ek9oA7XTwsyxYXY`0G7h1faoi7U{r8dUyOMhZt`>?XbpPDK21q1a*L%5>NuoT!R zttzNad~>=e>8#84*IjnVyW-R8^kKEY#riBdR!OQyHcgA*o!&K5G52?)+SVSwY^5gw z#@?K#LC7@|YDI+wY$l#SW!(h6DA(K)ZgAgP_LSNh^HSWL4f*~U{d}VUM&xnz4m>+x zWHl7;%?o(pL-j}4Q)|Pm$3F=yMhK2KBCLE?_a?VY!#CZynV>m95#%g$Hl5?l zn1I*h*CIl0j2mUx>AoK+pi<0qiL@4rZ+POj)bz6_>|Du2}f)?2*>Y-$NY-q0m% z}^BZJvRMh-!#M&`!!hWccLIs$1X9G znWsmR<}Xyd3@>QU`G-_dP6B)pl*2ZAmjRI-^s(T`bw~K2hnu;3a`$W#_awJO^WrpS z`_`DNAR`=dt&LArXU7TG@!oJM6v@%9F1^%0cD8WjGUeEpEOHE0FRIJczn*N0neaJ3 z@)+aA5Axup6pxj|O6>XXaTHxr#aR5&xHwWwb%JC@h2w==7LZgoMKuBBLYUNhSgfsx z*>^$LvU3}-C#C^C1O53-E=L8=Yic0l(lLef-sx)(rV`KqLutDboUfiy#ry>n7b()x z*Ct!a!7$MHxkkN^O-TeCL12IZ$n*KhmPTP+dP+n#Ws0794yNA z@Rog=Z#)=U~1G z^A8yPqqbnGUP4?nnY*9*ykL}0@Bi3X>TPW_(+zZH`}NIcfw#{y7qB=>br2-PSS2(W zs|jH-bctMWl-^%92@`T#DJLs?R61*g%fcY8oyZMVcWTqzA;l^ z+hu1yHM=N?15VV0?~T`cUb}N4en%oo3Jx}{OhG5g0X3PO5a+F_RZIG*45tgplJ*>( zp>oWMhf_maTtS8JeDjOm$oVYc>=p|sByasWu{|s3U+xXA3EpN6n_XR$iDM2o4caPd zmPgh-^5S)t6eVLR`7a#e%*e?Rh*|bOGv7hbGGkp7eS79Mj#^MFDB{By`-GOO7~?Q6LY$K2j4f6bd0ibc8bIHX_OHk} z%B%6Be}E0RZ{|Add3%FBcrB~zbn?R;srgx$@cZ(@qRN+;cSGHFlx?& zULHziPOMT2Y6J<@=`dKxuc1l2yYYG|5J>FhHwCRr;wEtWc(P1Yx^5;gbltEl7h8iu zlp_bCUfN2f(w9amZMrDP&FSnvJdU%F zyvQAV8Ch6Mti+Y9?j#b6#}zCW)Br^Zsr{#>9u3~{n(WHe4-YM|HQUV7n4xiA-SlR( z7X%@HkUX_r!6^Nf+wn8$stl5Mo{w_6p;Phy`*^8IP79oxk2{0Jomy%k|NAOxbq1GJ zRNi2fHpJcRi{$p>CXk0V*mSH9jysS;aCl!kRC%UQ6wErSNuP*mnK6W55k?fAde6G! zUpAo$*O_qR?-_#$?N@&Zx&Spk;oG*rTFG#KqgEt48RfhKhHhZX5IalS5(FgtWTlc6 zq8Ivv&gXll%+L|hFosrmq9EF^0GHnpQULyY}Yp+DVl9}Rr&n3uJND5(> z%H(p(I^E>8qH|WH3-A5(WLeP%XIe_L-9f&i43f<}ZXZwcR&MmehKL+ zU~;EA%2|~Fhb`t(_mN=)ivw|$1VbV!k6LL%q%vAlj0?g(jI4ucOSN|dXA7v4(5?S6hBoq6= z*O)sV!%#e4hprCO3A31muW*+8y8(h!f>ep2H(PG5f~r=;lM@1!JQSl_H6OBkXFd@S&8 zaaKkK+XjLaINQjq&f~e|xwvL&m~*>*feosw8QEVhELzqTb|a|58xD~M99ksJY>QpE zQa$d^nG_{6y-laaS0n4Er8Va$occa_nKXvixdrzLcDo7I(}%~WmI`BCt3u*^>pk)V z6^SD|OhaMa>0x~mvfYHodnG|GC|kQD##@@=+trT_uZkd1?`k- z0*ZHlk?Vt+RRQCM#LPhD0o}O^kL;9#`&dplluaOnbVR;6Ra+gk2oli>HM#NtRh6il zw`;B2SzI6P3Tt%(wYp+e>-$#ntR`q{c^DW;-kKYuMi{hAv5qSPJ$jx5-Kd`|xwrWi zoY{D(9To|>p=`KvKBVh_Cx4SFM3<2WjPRfuyY@JuDVhv;8p%?P4Brl41akW6W(~{R z8e5HvsLl3OtH-vtooD0qIgeMZ=&#S+KVtpA2>GPWOajWo21bA?&jf+)C+>-MR3xJ(}4Z0=%&v zq1Od(&02YAAcc9U3A0L9cQ~=KoC@{??ldKhk$F5)Ignkjo%T7u{zu)9IzGP6QEp3X zy^!W%;+pk|^QIqN-z1JLNH~7BsOX!|uNkCnd@`H;c-_Z}%j9OFTaxq3Bd&$AzYjyu z{_#_zb*;^6N#offuUt<~+-kIJ7>aQ-V<~{Q-soY8%}J0^mNuY3iiaLI!sX=75*Hq5 zz=L?60S`0i!;}loK@oN>oaRd){C&#wU#^ugNgj(%+Cxoiw2PbSRX1 z^Ub>3R}xfOvXj7uy>az9rC(Bq{~qQ(dC?3icVZnFMjz^S1pc~13}IYiK5}y3U@M>> zbv>@Tcg~T~BN55~0m4vWYn|HUC4c9t1v{*H25lV-D?L0m8t*$Bmqe_Yipi8DB{%tW zVzP`ldbrII`itC|e4yM}TNe9Wbp}!AXvZZg>na&oaL+Pq_6v_KE1IouY$y~g4P9h+ zY0j|+I>b!vkjCfL8tU~bbZRhe0lxMs2 zc5nOlRL-Q;ZuQX0%hPOU(yP1a1>ZR}&bqsy3LZj3rJ?eC8n$B0EXUrBTJRhZrRk) z9U}(xg~y`15T_a%(LY`NQA3Q{bO(A|2c`BOrCNPYecBYfL7_hdJsTZOkVSncE9bLd zB@t7RTyi;B&_ETnTfEdCRxLP#_o;eJzr-X>jjk#R>#zZ|@CpBX(hh2|OuP%(Ed`Yp z(B_)!qNsLWxxwTk6_f`rHKi*8!YUSa4QsaJ3iR}*9KoQ<{{H%Qtfj+-(_!b(lEy%z zE^R5;L|a@Q<{nG+YKYMA;)~RwI)PbJzq;6|D!W zlvsi2-LY%8K z$oyk{fdzMiMaI7{lVY#(*>AS`jv}fr%#i%C1f2#&x#2W?VE1e?IFdi+XN4y|3g6il zOa{3zS-q1C%&@N9_-7g_4U|khC@TQah~4kbpCmk8%{VKOyH}dIEt3S znMHt)uooaoK|aD{(RUDhXQ2#HFNJ?&=t&C~J{+5^dPD%^aM~8X8Kzi9t2%X(@6Nt@ zR4b8Ft&HL|5`Xeb=#rChIkUH!I=|CP1^h@zGh$b{{#qTik(lr_)Je6cM%sD$sgmdYLT>|a{DhOHcAL_XlLoO}4e+Y{o%I*|DWp7*ndAKyyibpbzN!PZJ zvlU~YX{Ei7hPq;cWs6?r`t;vHRXi^I!fo=B2ACz+8nI#?`QO7txHkYWwRY=Cx{thu z?2JC>!<>uSziz&jLseC<<&xo>Z<9CIvN~FbHj2a3XtR|7zhlU*=0*_2!j~>pZkQkk zb)3@l@nSQXNvfG1wITFwfVdCo(2x!CXp;1Mppy5NQ&F!+KEN#u)uKSvm+%j(>}`Tt-aZ=By(w>hdo%dYgS;a zQ?Xo@kq9y|%np<~5{&2xx#@oc1Nu@wkH%>`KE5bbz z0m?8vx?dN3;!7z_L!~nfS7goT3v16UZEhx3Ab;f4Ot)b*6orqy?2>>gmHll|OdUA@ z4*B4fqR;Othj~B-8|u-BM&pzY*e`yi`oAW*4ENGLwnhdY1y3b|fl_b|I!lgdP$o|J zVo-WdATbv93m=nbh?D_ymbJ@2x1tn}Ym>C-_Iq>)ZqLUZ4P z(zUA~Q!ab+3$jDlMdd_{%?ek9RYKdZ2%ZFRbb`efE_9P%dA=tkJl>)wic)7obfEe2 zWu?Wm!T$Q9VBAdOdqC|knvq9uIKsoTD<6m;8?U+Cj%581Gq$T*=<}6H1sd9W;KAgn zhRGzW!!RJvM(0)aHf4%=#8%x6x}-o;mxfksJh>KBbv|I>L~sI5DXuxWz^JRk7KVRE z41DC&sV|zs(BzYta=4eV(bhTiWlp)?2J2|K%F&`Kl_xfh@tk+U71)HZi)PO`gDAkl zv;|#4E@EQwgj*5g4DPYJ#g7u?q(g|Ha@ilYO zaq2)Vj3zf|kmpElmWm3Z$ROF(0YchK@JKFD%Eza*OO}PKEwW!q06_1>bKjX6X!eot zAcn2LUXgJIa#mQ_a*cFZr4W-&2(1{z|0nN%Vf69{yH8uU3=GvqUG(OaD9LF}I0Jl2 zBnCqe8$JSoEsQClacui&Eq&Xlia!G5PAr>6u35Fn)u9o$^b<2G;L(z0^qrkAdWwYS z@U{$#ociA2e{)87Q>`(|ovA9E)*b`Fwi(OmKKi_V6}i~_-hT^&A9ar9+ge%St>wqv zRcSeBuY)rQ=BfXA>&V&cXSCR)etl91eOf5*C?l2}No;-pWL81yxnnIG3}+d7&Jt%^F@C9H^v?ZU&H4z!rH|`}&^}JT><&IN;X$GTT1x6( z9gvIkxRZNddedOwVK-i)P-Kuin8{hT`NTEc(KBb~RSLBEPSt*NZ?cTzdd( zjUlF8UQvbDQyJW^0n_`O42sv8rS!TTx!`6(h2=P~%f26(Fi-cD0`W$kxUNq8fzwa@5cap#(#mPZx19bO zQsTp) z#0vftRLAv{Tdam8S1SY|Iq;K6lZ64^9?r4hHMU5l$&o zpkSE(!FPevx5!s3ta8c0`CE-)sIZkMY9}B`iPR=jeI&l)$Kw zIWRVIxZG&^{n3utpR#%@X)HIImAW8! z;^q2ajM7_r{P0-IccZz|HT7-CgAJ$0mvQjQx*m}aX}*b4>U{%!tGGhndB-@sEMvc- zv|OsrT!)$T7bM*rJ&e|7Sm2?oSMQo$5CNz9>#fBU?e+UpJ-VYI%pr@(xhCiB-Qx3l zEhAhyCJCIE=6KDcxBo7UAfKFk7Pu$S3Ku?#g=CY6286`nZkeC{n(U zvFP{SM*At-1}+#a9KB;3VW1=8WiGIbUIgYndF<-Ry(M7w7EBwZbseGk=i`+i*w-WW8y}C9R?gSRvQFZ4#DN>$9#) zGG`ApeF95FP%3fj)9Mb^5PxIE`qKb^=gK>Z9{)%P{3lwNA^U;;t8K`Sb1%>o7hSi8xy7gC!qY>!p5yOA)h!hu?ZH0hsO`y7Vs3-PlY^54WH0lHCqwqjMFEvzrVL;W6&x9GjnYE8z+AT40or0N&$^lcw;Vdk)YH>C}y8z-hH1YA(jbVf7OpO4WQoX$8IurPV z!E2q6dOrr(;X}of$$qj@VMXzJbhKlMch?7%$jw<3YI1r+PlH_9=*JS;cNM;E&x}In zuW~5orM~&EwZFgw!3}kzsX5yuO`2KXI{MW4h41Ko3gc4Kfqpb}BVZ6N{p1VyKnL97 z@X*JmPd>_q4{F}Rs5Sy*`)xSZT&0B>rc^j+d2oHLx4*4YLC@zJX?M&^RiE|8yR=E# zEpSAq(+Bzk(57!gdgK#XxmsR(X=%TF|Dw)tB5(SigNW;#^}wZ#A?t#G{MZ|G#99BN zcF1@g{pLdxHa~9YGf6Of36ev?59h@?EPm*r_y0HmeUq=^I*uRWw^#OtE+hH*D^}^~ z?sKrHFitydA~=v|i|o#63axe)mkyr&3$-28SJ`b!4Rl`l7)$1~OH(TiyJ!B+|9v+m z@7|g0V;3HuhfXRig)NOf*W3~uB(2o$8!BBGO~c=$6gUzR$f4uv?7)OY-#7Yz4^EpZ z2UNY&oQ`_5_^vm0__VBJbuCc=Gdvn64C|HP8Spr&+U)>ACx|Sgw_ljCX)9-Jl=^|4 zv@x@if@~Uu2_l4jThnH+ng|w3 zkBUYha=;G@&r-X7eMp~Vm_7NUB13*qy;>8Y&jimp712KUOFEx(N-aqpURRxur%fM+ z7~1ltUL*j@dF)OU!zsNGgH>{5GNZ47AjX-)O8!Bt^=M5nT+jc6Zsi|ag-(a;0vJh_ zU7Vmi8f^_tHvwb2*Vh()dE{?!lHDp(l1nCP{Kq~ZLSwJ6rv^GoZR+~vunrji8*zu! zGNvX2@@R8EbiI*4VJvXWoA|^zd=|$|T+Y-b1d@Z{k8A4wKxyNZn6v^nkiUxOOh}Us zC?BkdlG5;o@bXKQ*d#>|%wQdJoQiQSdmJ?u+T`QCM6};l|K;yJ^*++$wp zeTvbL>yC3{S4&FU9`#3~(|aDVo>vAg7HTSvl}BXVJR6kebO@C&56*DaKurIsbK<+B(>O_9ObypW>u7sw#uCR0Q3*hS-KyG_Q z&mqqX<8fo=vQm-neD0Cd4;3~rPeS4BEfvv(rqL>6s3k_sN^^1Dv7fzt{Jp+mBzKB_ zSgss`SH5@KBy8D^IjI)n$oW5Xy@_Aa`TPDmO^fw$Vam!~+nq+!923C>r?Jr*Q>UCs zM^RKNC&2}AK}5{4BvWL{%3W$Ksa()8cf|z-cPmqH7Z6Z!0dWCCJJfu?=l3~}bIyam zfcNA5df)ebUC--&y=`S)xoqD>;dwYPnP)x?_gII*t2nG!<)UQAJ-YyEwh5(c^r~yP z)kp@eS6aG0BJQ$xs1;z*6&)Ec3pwY$))S&3#0ae(dRVd-nkP#oYb2ZD>MFDvDhg&& z8)T)K@m~(<+^o00@jHpaCNH&y0b4$j?~-qwpU@8$-r891KM(2eD{~PVko#LP0(7G& zZ~5UD8U6P})Lj4aAx0BHC$2fdpuXBS6WMMh+VTJ}%PLsCz72wK>;?cUExW<8H!yr} z|8zq#nHhjRe0zWMc({bs-un9zZdMXWdY2Jc4UFv?$#YG^i3J(Hiq-OtWV+P`W{|@u z&WzKK3`bvXez`H+I6UxG0XC)HB`h=^7x*K@1%t znBU!d8Sx^_U`Qo7OqAH+>d&9r93vdmR52Z2NZM%x;{`HK1ETE|({Dhl0kV4@OA5A+ z$C}x)yyt8+xCjgr9Wsx@l~y)(oUFZs#+OITLp#p<^S$63YS&7=gDR%QS(Q2&__wua zvg>3ZL955=Zx$CAtp(dqH_T2vO@pE~l#!FZX9MlA4 zSvb_Lyj|Y5RyvN&AGbb|@pzt*Vmm%QA~Zf6!~ZF)Y|jO^NX$iqkJ1TgDu)*X(+M_F zB}1-TvqL$h-kvV@;AYqw>pP9%wfZk!*cU&{|F@^Y`x1Xtbvb=pgy|lIZ1CxTL2@F# zXRgvZG!&-$i6-8?5u3)@KEz#Ww^f|PZC+jfeC*`^+Xlbvzr;{VfKm$l=}@V1z8)9; zr}1y|+Y{l)GNk_K^`SA-w_|77-)4zI4vuA2b(IT33y}NBzziUQA@|d9*l&5UMk~}1 z*jkHv^NRi;A@?^lFK}d>T{3;nYy*M`_UK}I@6|5=xK1)W+zup`Chmj{osY? z3XZNw+B)!rJZWcN;#6|l0nG!^Q(Xvz^p`tKpHchej9Yz?9fXhXGij0UIoDvAyz;Xk zd9Z-{z9M5d)Xy&x)N6&mkcK0oS~<~WRkU)@ocM8!RK~xYNs2`c(z~BE`ghzU3}vb! z>%Lm8&4;x zALbafPYh#a;^lt3)wi;5VxUE3ANs?zqrz#gFI8nP|Kht zeTM5RmIB9cZKflEZhxm!b&~ufjEKcg1j!HrOTZ_2d?w5b$i~+7FMs6!#3YzE0;Qd$ zamSi;9}KHaftp2O2^;Hd-tq`5CAXaO9;b7XW+NV&Zns!sOHyI5f?9uDjipx1kTFF2 zj0O!^np*&9g5c_Yr)Y~8xEbM*C*tfn0+RY-($5?DxgiBPR?`d61J(@|dWlKyS^zy5 zWb4Q+2)FH(PhLldB~`s2b)@``fu}I3zQ-|X$iLEBaNG^t#=LbIhK$XUqWX)dL0U}R zX+ciw8x8E&sXeF{9(o{&6|l5!eB@Gv&0!9mv6I4ir&+CNw`WFAb-5QvpbhKn_2Gm~ z!g{RypWnmq5nda`ECK#S_57T6`}%6nW7zhGj4`bOoq_uwrJl%)4N3OWkCp-JoO2F| z>Geu{26T*}fJT+o20?!no)GXxIL|!SqXsQreK~gu;BV81d><$+g^Wh*T6sXGd#)#7 z3-}dQS@3q`-UaTB3^7H1H)_g0G5Vo3I7A}=rOX1vMJqlw^C-@K+PU+IQRrLfF6ODX zdq6MzN&!c&Sd8KA4$WXVt|dA7gY0=y$kPW}>qUXYSj|t}9UOf5IL)8M(`l z0i~paHToUda0aXXwTT@J1DAvw3;1ut_P0D3#?wwQsZkvi!iLYzg&b&m8%3ulm1grM zbo+v7v>sNQ=sGp(FMFUENvm4POhVzYG`W^0AP-EA_||#T+Xf|mr<4GaP~&Mro9UBt zABy#P9!4Pj$mj4|W(DYX+KfhNv(+QSb^t1+%da3(;jeVT)cY-RC4sHq#P-)oT8lGt z;eU*SZxh;mNfhmJA z5_G4B*zIlm94X6~vX4U?!&hd{ZK4gMOkY3ajal4Y&XOv>wwW~8MB}+KgN+e2+a1Av*BTXc6xO7dKGcHgYF1#4p%QlI9S5;{JWScyux-Wt0TRG8fvs3AHR^{#_{ol@4`KSC z5d7{jfuT9eDj~cCG=By+J&4SWYzt1HO%<+0zycw1L_LWvPBBU&u zlg6=ujMyY_r)#eNpW*O)Nekvbe%nTks-SeqZZQ=vm|LVbuj-8%C4Ay(uGbiD z6G<1rS^v8W0Pu+?xw#N4Z;$4j#VkMhVhruWe8^Xdy!$N#!q?~yd*0tR#&y&ACQ&~l z{^dG+lw8L?oX+WSUF*sUrcFrO=nrgD_Fp4M*tov;MQKF#cvUOhWvXb0mLHCw-ogF_ z(A<-`XZZ~5jXoxP1apsK3_JlsagGFXkPD8x$8m~tCdk}NaQXCfSXotTfTb3pld2et zeBb&8SkcpyS%_M(7QnUzV&z4$E;G+Lxrk1q^fM0HW`Rdk9fN9yOQay(5jFap@boB{0C=xe2bHSRL znO`0Q%^B>{vUU*NrONwU>!R~!OK+47xk3E6{+T4h1PSkUP$eGN9Q6a6Bh4VKO~0^a zrOlm-owhh7V_U&x zkFgk8i(HT&E|l&{F3tHzx@hxPL$U+M(O<~}xx-2r*kjA-)L48i@1Ux=#gj~^?DIKf z4+wu^zTz>8z(S}LxLzjL| zMNx=iES6n?exDy-J7Y6XUdC~PxFh*nj{I2&DGKG_)8@22a~ z?h$=;ZoZ@@QZ1`I1^{2Sxw{Ox=q%GkNaONEwi;QJ32pbMJ{}w|A&`0H?{1Y(zNlJ z(aN%kk9En*eD1YSzE<0_k76~;8M&BntS+m`NZ4ZpNr0S<%;BYZUSlfGy&(0?@<$@zcmpQ;;F)KyjtBIj4ziSKT@CmjV&%&kdP z+ydzG>8}ihs=S>GCOemne%jEx91{vvI2~95K4t)4SV4iSAy?-lRi~{_MVL`#y;gct zkPEjvj{4U2TB9S#co5q_`;k{@SKQX~oOwX2%jowqVAGI8=-4t7!;pGz4LU@PIczL=(tMTCi zT{YYEepfFAl0&rBnrol6BLsPBwp}yuPn+Z3RyCm@v7*0n0aO07+=ZGWvu!II&jOh0 z*YquXc0$0pn&50ri{7EzMkR(yMM3@zUVyFbcORd3RH6WQ&Og6pM7&-a{()>HQ87}V@y?i zlk_&zCw8#r31%(umQsSP4b6+m@wTaETqr$IwTO9bU_l5ei_y2ylEst+m7tiZ%_xZw z-BCe56ozV}DY1;Pk66IQqaGgRBL9rB!xW^o}y>j*@FAqhDk1j7*||R7O5f&iq;3-;XFAd#ccYh=YK)ge zk^X$_f{BLBPQ&S(k!K$b@L4OGWBMxO+Uag}BeUo~Gf4VrRl|SfmsxethvPu`UPIkp zZmSbx0jR9XLu3-}!%ab=>KL|{-kFlcyAOyFwT?*t(#{!OIf~h5U_=ZlONe@Al3w*| zaRhIwHN#KA9{OFEXd@>lYp(zs{hDU-LV@6_Tb?_TD^KM0h)7sJh zTAhraqTC|-<3b&hVH2NCmiXe94ma72qG=9|=w+YZmsRJQON8Zq6(5~z1FT&Q`GL*` z9l*`3FIJp4(8rmRnxB1o>j)s@|JK(^poJV3x(4XY2e!U_Tn) zBW~|c`gIDD_KX?gI^=@f<}z8{ukGz&eV_#JBP|~#r<7Hu=s0w|e^^=9*YqA?K8k;I zj_!Cij7IQDp$65o?K{nj; zga`w_%?Q*ZB=L<%Y`Lf^BJucHSw^r?!K4a?&$3yY-$g3Mwxq(&jLfKb`UdQ4dg72) zYp^lA-xCr(60U=g%w1x_`xE`t9+F~XA02yFTlkk$4HcD;o*JC~enGH>$zz$CgOxXQFS?Nu@i#7@EzF+LmF^?Ur z0z}`Ma^>IRe8Z=pDe5PleIvrdiGj4Ts}ygP%*1!aRf%lzHf<{IvF+d~=WgTBMk#u~ zt>51yfJSqzz0dnw0c%YO+5^y~E0Sx+qwDx^gR_=qYI&!sv&wT6jVcuS(6|;~SrP5p zDYYvl!-~CT@@hXDbluwuPahj8I1Cee&qCBE@7Tfg^uuDcD-K}m7dnV8`bxEPz-c+} zv~!ho=dB?f8k-@Ot+i)~WBT9GdHdlIH_Ti~BCUXQ)362wi+=yuRv#ws81OSxu-hb} zSQ6Q;*jLHS$Z%}`k@j~bDJ>5ITlQl?BB3*Ty1m^c%xzVykZIF9$t~{@{Yu)%)ZIR< zQFd>C0yc~5t*KEhn-lZS4*%0L&5)j6b_Re6#uXO$cezs%`MQ9(c)WRgW?i^FKY&bFC*cRz6T=di>#|%!gJyR0 ztvG=^cg#FtC3TGSX`C98ATq*#-6fgY&i{Gs5GJPGko%~0AyS+CxhJ*!KRqc}XkTMQ zr-ptOz-iZ}%Xzrnr`gDn+ar%gIv)r^re~)QMC;kxH@V(~D+NsbU4{-5L+?W6=)Jc5 z9XnY?+BEf>TO0w7eS-IPZ$Fo=JyzS@)3`P_DAiQ0VE|ony|B?RTB}rx>S{|Kz7935 ze~yaBmF}SAS)ur9#m1|+d$l97);CVl=iW7DMjG05ZXheorw}LTgOrKmzhywA_jCY_ zQ2EQphCx;H6uK<~+Ze9s{>_csyLR72tv1-RFFe(eRFk(V?}cX#FR@pcs>eg&@oT{0 z*D$<{Z%p%3rJte%YI%eak;6|Z2s5{+?#X6U@lDyvKBjT@!0y;SoV+`uJY~N*3}Hs* z_Esed-GiX^l6Mj!xh#jmm!k2QeP)sa_ys<)U}7{pn~<3Fj)~APp$sQY zL7~Kak|7PGlwome0qi-eKONQ3+Hg^tbtA;SIXI0R@?@nj=reIl*aUQM1uJ?#C=(|W z&x-pqeR!j_-^)oPoK1m-YS^{qDm3aYFEAAtzC~Bu$V1;!b;B zNkpBltA?V>YTBzVF57%S_)-D{V3@vQHaBLmGL{xlD9SbowfUR*&b~$a$w4Z#(fozf z@Y1vu8ayp6cp-ESnv63wZFO9>FV^pCAk4{oQnjUbhhj)!zN_WBB~C2MlMo=Y zlVfg{0at_fniNgiq!r(VltnzIR)CaXwg_RCe9b`8`ONB?YWy}=Ec7hx=%PrVXwm61 zZ5--g4<`NSwQ+_*($+PkO~?u#?rU|{ z%+A)P*zMIzqyrpf*AUgT+vkS&;^Y#<7Lxn6i$DW`=6cSUiC)wsDv8+O8vVu3<+C{9e<3;SW~^xZ-Knx$m)&~Dl7x}fofuW`F4wRVHK*J_k)ZFsXu zxTLSMr?WjwvnsJt670bk67&k*?>W=dm#GYZkrydp%yM)bX|WAIep9&G`dQ*nco!c)r)bz^0Y94EPe$eKHPA@2`+? zOqYk5R6FsZOmlK@dM2^JJ@(}xwUlD*h!)>a)PPa)^xdRO-6~WbE9X8rpT{=mv3caE_FN@I!7#t{~5(@52G%MBmE8@zGFSUljYC%~jZBAL^EZ`^s=tYyijd zhTE@c1d4^0%&5r@3J@9Ij+vlpM4|0oQ*>Wv+eG=joT!JzuWc0D*5udlY_!cujiKYJ zH9fomPe?;k=y>G^-#*(t!3Cf_!BcDd%cM2Z5y_u}vgrkzcMU}V;nwDn^3gK9(*08^ zD?j^^%jiqwN%uM50pCF+(U)E(p}iH2Tr``A;r+~XmgZ)O7s}Qq8zzi$Ht6BpSlWYJ z%)*4GxTS*52>|SLSB}VUtI}75VQHRt{)FzD{BT17t!luE{_I7PySVGL0kkUFI;>CL z^4bc<#&IPv$)Cjdnd2 z5b)av{X5v8l^2?hCBG|4UMt?C)KfR*)u%v;fbf=g6)C6FV`J(6++y(WR&LULcIH;_ zm_~kTKrCdga?VE?kUJD3m%xpQ{85Xi)`9`I`$J$s{BL%75f%(3@Cb=-^IUqadxw~6 z@55}V@6a-~Ae1I!e>A=+DzqH-CZTX01B6nPLfIxXY~Zc7-}ZiSXFd;Aza^?ve_ilT zFmHzdnLnsFr#}$KQ;{x@syr&~Y z57luCP*?IZ@}vc0l!L7BFSW>Xnmag^`JlV17=HXyTZf7*5D_vtx>q!}76Scvpzjp5 zkJ}iFM0YZ?*r4F;1@xv%41=*DuQ+X_oqG|QV}XRDkp6RNJOnR=L{nd1VBU1|_K}aM zSu=*ThrLHqgW}^k-)ea^MjQ(%nnPua_VwoFqG0!~_fP<=?w_`i@q5k59$U%ko;DT! z^7l$o=7lx&_d*n++)TTv%70SaJ0CjtisagEzTB_CwRo$Ux&ZyaYRky-H`~2#`%@L=IK+kI# z3eM~*_mQz}J~%Y?8;g5+R@ERG6W;qIzWO(`*BZTa_&Kzj*sojSxv(HRcVGkO_acaX zvIyEMiV;e#Hc2W|tyMqG-|W?Jwie#Ltr}Ne;Uwz~ffJq-zenfuRfR7>$^v$dX*!C;1NLpnMmGH*>Q? z@6J}_@Bf+yZ@%UM7bi#8M7rp{sOG4UV8PNH0}I;lXIXKQc9w~(@)4s|8Us^+zd0{C z-|f+lhkelI4f2u?4K~tnoXw*(lg9na#u0}cH8lwq`uKs+RZB_~=caL>CjO3JQiT(A zmx=*2-pV3O4G|im>>7J6K}xk4piunCNVi1+h;vTFGk5pmrN@H@C4~f+oXQ$M)Ly-& zYYS%=Fx|f`4P|L4wGT$*8uH9~RpA8oUIn=ehic*yRuAXNUC?-0a zJJ`t#OlZ2uZN>$+3yZ}>C5=ALeLyL|1`8i++P2O$?vH_BJOG>zRS zY4dMXjLgL{8GW`R6-MsUhCzKZYW>-BZS;3@{OkfVGa8cf`b>V*BcIq@o+ZiCZP-ON zorQQ*R!^6H?jKNPka-~}vP7RcmGbd1ulB!{%hDDMVqyxmrTI9fnde$LC_V>zMAxGI zYP@zU{7RWt6Ui;>b1KE}zq4nbZE*BM^rWS2zm`%Z@%vXL`Q@`p5}s=XkTHP1m-N(u z4!5Y(*43ja>U)v7OyHkqff4Nj(sBUL<{-RRD3D3_nvZL5=E5uE=eQIWKX6bpQ+*)M zBtEwTNQrwY`7=|t;s}km!LK}W23ry4cJ9_gOWw%%1B7((pzIyz&d}_>_-HiHZsne_ zZNk+wS-{Y-soH=!_E2_nGRtOjaWAOq&;7Z86qYunuE5viR6kBCs=a^6D7Q{gsmJRh zHLZ!Pt}b#e6&Gy8kJwN9O4J%l;~ZSg6pgXEd8hZ?n#&uR&Edrh=Un@hdrMq<|Y4&D{9yYIsE7Xmu= zSJrKclgt6&wu69cNrg$N9%i@^*$JYAH%Os(I2Q~Rf_s%UEPkB2)F>8%bn;0&oVIYN z_Um<#xLc;_m>)D*nWvdQ*8i#BeN(PLX+)LH=NHO#ZK-1I7>Mq_;S}O`;W*XsGxgPi z4RzMv!ym0}p@B>G&k!|2Lr9auD&#+`kNTf#>#Z@c=u(6%yk&O6XO8IpBlv=IyMTVHnMWWaA6Ruy-*WC zwm5BQx?itaf7V8WmUdl}aoZ~FG{cxU!F@MNfKswmW7wd-A&I{elqt!Pz2 zTm#)tbd2AZFB`f+E07(gj!w6#P=u82UDvgaqL zx=}s`TlbdGI0kbuzfE2P%AQA@5OH%bumTR6etU|NB#^_?nkG-f&}coOjMn zJ^d|D@f@OV_LA{YKJk6uhSI&;LHd8Wcfv;V00(~Uo-dM@$sx>7u&>6xg6e$wqfY%t zz1u*O!`C(ct~%{Mh&+Pi%GlR?bN95v>fEcCxKQM^cBy?m5p-w*dG;DoVNYfAR&KgfDIsjOcRp43M+IEk=hGFYKM;wBTZ-p?3TxIURVjQ&=}L*Hy-xYH`v%# zzBTo@eV#?h6DrN>F$@j9^8#_)bxM$t4_g~aA(`XYA5)^MiV&_HN$9iiBNvM96Wk1Yq)yfMW9yu&*{DxkX*R<#O6YSsvW!NMdD49M7$32hLwuv6c zg4^E_R9AIxoyl(~ANn!Az4d(iH>AkR?C{@Ac-$G|JuneKFC7NH=3@g9T6A04rmsw7 z=$a2XPA*ts@fXY%A_Jf3nPmkBnVouItEh+h+MO#7VOC2o^nE~AgK3fZ0Oj=XlHh$S zm@>8OY@X(@wm>Tcg@}U;}q(5qLJjzWI&%g?8H0;$ZSWiCZ z2pWNh!tG<1!+q=5p@<3V5oqd!!wCI-L-L3$vakF8Ck1}kN@VyWRnpO6Zb2a|LlUR7 z%Qix_f1I*^@zIH`1MelfYRIZ^BO`koVnqkh%^L>13Q5%RvEeq)TvmpgU+Gc9`rs5* zV~&UY%7emFen8Z@iV+zzkwl~gT^(1Fh7Qnj{w{N4<#Us3^ZE(RV6!Z)o+sRHRl(19 z9UwWxwUGdJ_Ib^Q`^|F4*|Vi8!*$Na4j|Zp)E%iTeQNBs_&IJ(cXC%U*-vf!c_Syq z&nz4%VSBtA@;?U+5A5>Lq}a_N;%>v$8iy~w&R9ElGnWe31Up+8s0w%CY5cDcY5>nX z0djbtsAEFg5ZGj4Bzobo^Y0o;hDsa;81>E0Jz-HvA^m1gg3i$d^S9DSBsO;?fJ#HV z;d*_HKQSbomx!}gSg7KFR1CU^NX{jx=v}tT&{NAdbl2t~rQlriI7vT=mxOB`RsC;4 zlNS12xTF^)F_?QkCJO&|*TmzZpv5_*w7fzvQsPcKf~4 zYGH11ZtY|VsOpt{ZhEJ3$+7U^qWdHiE#%24JA3TcY&|1j6r2vif7}pEH?F^xEpmgV z8hv9|vm1bL?n7DlUqB=Sjl}9*ZaPfs`?iD~F;t1*2YJYots4Vsv^>%nk{mtFJT&R! z9EA)Sp?9^`0*=1^Ee}Y-@T%p>d}kp>yT@~>z2X~E`kAS#$R$s%&(grqYmj2wl05|tO zQ_p^XbEBm5I4&=|vX7k^f(wu{ErfX*q-&Q4c7eEec}$HUqF`0&hZh*BC31GUt9Wc9 z#Vcu4yANmLv}xQfK&JV9&bey*ky>x=x#YDHcEzEcF(5?_*ie=p1HQ-cKCOaz4|T)r zGa|b3T*MnMPBnq#zwWFTgG!#|OiyQu15$T3Gf4f~dngt#Y_xoEkWYaqH&UzXrrs)P z?W~Bc`m>c;6n3WLibF-Jr!wwMRM)V!wa5Rh>?WxLA0H9c9UlZ~_^`vdD4i2P+oi_xB zc-4)pj>;ayX0XHVj$RnO9?a0qwtf%-k&pSWRMn-qr%BYswPWeTO(5jgSb-$qb|u2=K>6QSWotr;#PS-y7kE(#H7%$;sGHiK#Nfa3yX z@TipnlCT%_HU@?9w-a3qu~2U#?@nKl@nX+UB`eR52ljYo6q3hJg>xTbAy$&KAc#dJ zFGOR|##>8Iqm8MuCvxQ_WT?WcT+uhQ!_2g)!VC41P_b?23Ec%qgWuW`+$oEyanjxl ztVEu4Zs<`H z)JGK=)w#N72E6m# z8RgHTs!!p#-Mbmu32T=ItTiAVDHIV-shtSJne>WZtzFN|5r?R{F>dq4rC2>%$D^WkzSBK>r2LBqorjVSm668`;X;y8-sl;pj|>=le1bhdc#!K zzICF+&q=es3lGre3^Mx zRTPozZsb|Po*rZ->J?!)?NvAHW}I1|1bivL6N~a6A=0M_g-JvQ1E~H`N29(_f$Dld z6?#i#6yadyH~Mq-J^Io3YW`FOnOt>$px_ZjpXd{Y3+TuoCtK>f)!(5B59O`gCW~0! z@<$#BWIUIfgASDLr12BU%`}Tn~D-WDv-KA z{Z7!J>~}Jxxhw{pY+)8H8JX>ICpnU-;LxiBPBT}>acpl#@ch1#Q!rVC5=u$^c-5-Z zZ~nsT7n^N=Tw?y%Ga0bF-#^G)n?Tw>9>RIAt z0Z9CYD>J_$G4499YeiA}z)o7WcvrZ+9nBw|Fq(dIG%;*2rPwu1Hn&0SM!`EtZ|3fh z9G7$r&m>^nR#t|b7TrUFclXt~=kdOdSzydorgtn;7!lG*9a@~0p!cQ6CH{=5Gh z2xz|x-DT#x0z*QUI0D&JN=SdStjz7DpH6Wxg<|BLmy8k`16FYX0_beV-y5xal`YTN zgrltb5i7{>(QC1*9lyytb;@$y?6J9;w>q?gqCNCMGLY;7rE!DhbOL=xv6lOQlk0l*C&rKvb|U_%go>b0ZAIWs6YBh< z0wbJj-mt130i=8ypH|xF?qpcqVM{$jHmG}tR;ciFmzm0@%=XEAdnWOovr5g9Cv|aa zzF*jkYS{eC=#4h36EtOJlpv}>w_2b~&oTQfV)XSj(ebRWnw$p609@%uIsO->3(Hv% z;q}Ff8~wFqYq&CPGU|2IB5--savARz$(BQYaDWXm78KYrcUJt7?WM&f^(8IA1t`~V zdixg*1-pOwxa96&>SHQuqws8GZM%SKC~r3=qth|6ins*&B_ozT-DcLFFsn2R&B-)O z4-0z{tTO;@8c1oSY~Bll(uVdVPt%VZ0o7G3ih>CA^Ff!Vdpph+h3OlA5=Ra{OAltS zvXsO>FQV#{bh3eyn(6^(vDNUOc0^M-y}H1LcX(r;Gb&Zc8##G09H~Qf8AGZDO^Mn2?_N;$bdi*P%K-=v-`{X|wkYVnRDz|3hbd{1n71>-4 zO*0GppWJRq3uiDjGrM(2;`7sI#PUsSMG9)?o7T$2TlA2fps*t&o4+KIi5d;WGr1N9 z+3^7(-*wy`a|jgsTs}_lsbPe%pA0)4a!NYwlv^o4=0tUo z^Sx?t;;sp;VBGy3bU%b)SZ+|Xjd~0$GBdY;SYAx<nMU2IUJ`h(z8C(4wyWXdq81|Ln^ zO0nOZzfH&|(XiQ?phgA#eeNK=`O#a?ouD?dn^6!jK8opE+IFpSrIR%2r5-Tio65B{ z-j^Ts2V?k>>^Bv=3nA>SR}dTYc*^GBe#t$n*6D7gU&W`G6S$smG&CT*zwVW)-hQ(9 zMW%79uU$nd+0VWGO77WJ#z;U(N7T1eZkzZwADfDc+k%egRE>O<1>X!5m*h-p&Y9*H zw`yr>6v?3t$OUdH)9u6SDxWFk_%=T9x*W!tET24^8t|sa= zG42{7OLTA#Y4ZE^56 zQBMVfkiN#Md~_Cle(2{VK}44Hp$gt68u5KmF1Y<~#KcheI*%GwARd(g8Bh@FYPYVnBg({szJbfoXNPm9eM zt2v6^OP}ui5{;v&u`*~!G{|`CbgfNXPmSG&UmIhUu3g5;yA9kzY>{*ad2}YM02bS# z%Ukq11k)c6eoFYvW5!=P^Pv*{^rnHc)snZUwzv`=Feyo*CnYs+SJlEw9d?xdMA8q5t@OH2f`}WpYnF!Jd2DmBldL%HAgi_lM|FP-K@B^*TdaDYV&HRlZrz@ zcA;X87-AE2+M1@6w^{B+K=y^TXLs_(eyQSgB-L1D(^K9NPKUf<&yEPKcn6ctF{lsb z8O&NcE+M5-*Gt7A z(&l?+J>W!ne7p%@Kf9t_qYt((V{p56I6?n3fne8%JIwV=b7Nz5Qg8VAVQYhx8$-n0 z4b?jL`Hs9=^aI^s+lfEhl!&k0zhC-YoC`9bLr_>K|SU z>^)_?-EqUZs1gsdI}wV`!^#Kp@$tbJmFQpYE`}VLmJounJ=Y1d9I~HPu#ugYi*p6=|t&m^*t~qIz3f+ zCrx8ShI`#R9bQXNfv+jv04+sEdV}9LD}gXQcN{ZX2?Q@L^Ci!uviGbw{-R{LLQc6Y z)So#cTq(aMdKy;BFWbYn+ZZx%7jDuzCC3i;f3bL}uDhXe6G8PgS(>aek%h&Qs!sig zK-XA?B}JF&@=nUw@+k5wE!IPG$ zbc2j|5oerff#0n6YefU?HpSTbbYln6bLL={-{f$+k+lQ!o?pRQpkP_$Oq|(L&-L1f zds;A2*B+McZ2PI91ag{IZB#S1e96;b?7!Du{BZ~~;pkayv<;tXwBKmM2qwq?z`#9o zZ{ekQ-OGPH(@U6G73+n5&=e?z%u~%e*qW_5A&d=K-T2f6q~#XOg!Uet>Qel})ktsn zV<|_!GySz{PsbnW)r>ELV)NJ3S=H%^-wbu8wmkZt_*b$81A;QwV7;Gmdw?&)fqx>s zjSPEpgK~cgc`^|;J04_C#KeTA1L_^toU2Wiw?+m}>FjYoMvd^deBum#v|&e(+^~Xr z>D>Y&;${9BYCYG=Iu0}ge7i4|#Ac}X412FF1kMf$Ngk$n4qH5$HTImjLv>I^Iy8oz zm0V{@>8hF9w&|-Q-LI?$d(M*@co^y^rlf<-jO(ElLFCC8@8E~f3q1l zcq?+{5vc;iKrN^9PR1GC5l0pD(-f&!$jx>$m(%w3LRBp0reCWgE2PUi&!dT?Q^xq` zRf`#twtgq_6f`#+S!y(l?b+{?eztbSI{8(!RM-DaI!sA32)O0eBnP9-z+TaHN~VV$ ze+ufZay&gYFfFYAq-mOnA}Un!)r7Z?8|>0jlq)M7Y$S75ZDx?B3L8y*KD^%#350`n zgkg3A0mEDK;h|vX4~`zx^hEd{mmOGD=-2Rfs2)~D%FZJAS{0@lyQDc)J|W|dRhy7x zeWT_t_`J98$z{vsAQF0J_6lErPC@(L@+io-u}Sz~6uxh9`J z!5F3(pJgcHkq~W>NY?H{Q>H$MJstA@XoksiYohB{T3bDj^KbNDYsAcoJNE|(_r|wf z%a@Jqe8lVJc|RvSBcm(K5N6h>?%(zCIXKZA=Un|A7+Qu|4P=~ImO(SN^Wu&vc|DA@dxzS)OIH|%sPzJ)V;01NYv_#ZS6e_ACea+oLk}0sjR6N^@xb) z;MvcGts$QDsMzQ8t|{slcYw6GMtg;DL}(5cU6~TNhq)n7jGcWy_WVF}u9M!8AB_kF zlczE!W$TNrG3>GBPi4;%gyMc;*myx4cWbP)W_x0==x~rkKt$;Mnk(aW22IWwamObe zI{yIz@hf{(90`u0lAM~kWXX#(fQ7i_Wp*QbuU$&29|Y~jzs)*@I&6az14*)(i)r%U zj*p*48ZxI`9xgx@S_L7jy1h=z4xSTm0hz&fS0zwOgk#0qolWm=~p= zz6w+O&!2D3z7e-2tNn39@9Zw0hiAOLKB4*W+wM~*ug;`>s~%)GP~hp_JTHm+h&v|D zwO%TPMg11#K5#jv%sr@2! z#UsUCYaJ(??Bt!Y4=Lv_Zyr^!pCkji-*z&S8>KeJ*{?vU+a+9t?``QHm0dw{M;<}^9sm?ysFLNzL6DV!vCiJJiB2}tHw>m1w&s94{KHCDfmh77z}rN zjc&5}xYRWF?$d^Jkl0{8WF0u3qY?UH@a7tuO1X`QsNR?qRHA{x(pXhX={ac=IqsIw zZg2o9Y_3Vn#B=F-=e|Gty|~qK8rmmHmG*$MMSnuFb@@>p^4E)NZnn!R`4pL-^#hhs zNER?{wyhg+kOqN}zuo?@`T>|KyRTu7f7D)-?CfyYZw50upLPX_tDWQ^q(=+BG5U5n z^+S`}S<6$^lb$c(xn7oT+Y)xCP9x+dHYFu&tNoQzozn(;6-uzYxz&E#IC?kf^7#}4 zBeQu_hw}2rVdsUUAbpwQTS=}>^A>U;+xU89!18G8F$x(}cw9r#{xn}3jSL(B+F^Be z`rz!WV9Gn70kFhFu+6cNs5#2I>Yoh#{H{scNILcv(G->Cgsd_SBA>> zZ0=t3_ot-&q~yOwd!H-PC-mjgI)I3oAOy8st+8u2KK!%Wu)Z%c~93jUI0c<1^B zJ{=&#&GvkM&^|hH^?`QBWB5Iadx5S-E~;sC9NMSII4-~PeRG6j=2zR;UW*JVNxC+# zXdq{~K|Z+*N)|^IrBi>ua(5mQaqU>%Zm;3#-Hrmdpc;Y~P|#=4o~}2|uuYaVmd$-W z5ht~~8qnXY)e|kY)6~+`8kf~RyaHD@ zlY{hDX3}HCd0a+~$Yt)243W^Am?_fuwD~PC{1>srmo;~4U8Z&XqpV1CY}-3Ivtr$* zJn2=s`(pc{DIE7fmnYJr$DMJPz;0`Iu3Nuhx_W@bDs9*P6kHqYn)F!vZxb^t?yud> zw%^m{c&|7|wlxc@xu6FdHVY3$GX(?Fm4#%QcdO9+0uwbC)y4Nvtj5`HwU?y(25M=> zb%6c?`hr$98z6sXTZjg&pNKc%2X*YdHeZcAFTLi^J05!(cl}#zHemA>UE_s%s{Ua= z#&cj!9w5i_%S?=tRa7On%9u4=#7dmMz5K8Oyy zm?zUw#l_!%Qh|s)@+&iXT^0IWCk;mcJ!`Mi{Zmm2o&WnJY3s<&U-uVA)lQlt|340q z)!2MYtDc^jylY}HzGb6tRp2KD& z-*`pe7*~r+H3u&+i}J^frxdO4Nlkc0p+L{j=Y#NG8T>1P*;M%~R~ZaC2poS>ez_l_%PQ7d!2YJ!1Zb9J&vAVmV9g)n z8u05ip|#2@eqIDuMpFfEe{XWjL1%jk?#E_=3+f)p3jhYwm5%eh=cVbP0t@^2s;_OM z`!HW#09nEHmXR!}`csyg&<7J++Jkt+66aC;f>t)$fWl%l3%uCKlurIY&i_-z9E2Qc zcY~=Re_0T>YZ~e=XGmKwRoqL#b6@B+w0~p2Ue{lq8iHenDQ}rQRokTer!!b>UmCmR zh&#S&?z#9ysaxLWD)!H$1BF?dnlK#ioyW9fF8*iS9KzX`?k#R{lR3b>#Vv>$ zAn3z6-{<@Me$Vy!8?K8F*ZY3o_iNpqDJh+Abj7CtAL3Q<>R?pxOQEAK#qmd01~uS} z)))~8SZ&l55i0~Y3D}%?Hx~XijPq@eDzt7A*CY-ZoAGX%e1oq=p z)PO~YS3OHAb-t7(dtShdxX=T}=@wh1=TcTxU+n`e80!>)USy~S-*Ha$QRvScl20yOLWn7&0th{ zRFBsv>DM*5ZLg+s{~Bgp1;}Ce?$xB{MZ(bjI;ai4$GWPCZAW;ZAq=+z$5;udw3PQ* ztW#&k@*~^WK1;n{R%&md2aG0SHS>8)#3eI-R6+Y2THRBky}tI(jzOZ<=+nI1)<)>` zqgd6@D{VN*7eot8W?^G;s`&8E9Ix4W=czDlJZr8C^gPR}Mbm6A*3zbTr^4|uzsEVM zid7rI0VkGsf7jE@}EL zm)xTq6Pm7JirRXkQmu9zqjAbsUV&?CO%axM$mqsvM(14ihmil(Du}-XwvVLNUKk7$# z@_e|E(R!yALx@Pr-O9M&*_NCm8>!NmZ5 zjRyK#EJlTBpXD(mu-nt{9*Cq@;r;PTo#r7Y$raxF6=ca};_gmga<95(3GK$xn;?;& z3IyjAf5edQQcz=aEa$5cw{^+Z@jzWKx4i>dJe5{x6T$;OL9R-kdvs8QmUl9A><0J ziFy#ppHCZ_9f%s=KV!6g6WE>zefOqh&#!nc5`SI6*?i4)qL@jtTvixaXbB__yw&|X z_oZCrGxfc;Q-wRTFJ>M?*sFvvH}8$MLIGK=Sabfs)tT)S(10@_Tipzr7Y>A(N;P+QZ&1AtkkzhM1qJlj)%;gBxi z7^#5CY1)%tA1q^TQ~^COL;I!3Ro3^>UCZ`4WJ#6Imx_N+C1QNnaw4mPa~SX*zi+u# zpsu+Uj;oqvxE~jsKqJ}G_1yI=?4hfZ%r8%CISOlii%A<=;*-hY$>9o*XKKLDg#-~z z_LTx+e1B<5DlI4-D0!d8R~op9hFXie3}hU=%uz}!ds+EsZ%dj|>QOm?`NM7ARyz-=%|6ub6ZLRRHo2&63Wel4q6hiwR~s@2_)C^JO};cF$#d zre-c#wE+Q|Nu56Y!uL>LqEp0|weXMBW?q3%+~EPHT4O9F1&)0u-CgYydD7yg$HGpe zkgDdd&e0zt3Ue`n=Z#pm)o9Cdf>)4o$eQbj<(Bitl8Nx`#H8idc>_P*tEdZtRzB;J7-zoDdbH!iBTxI;Wko)ZCl7#P`jVGtC{Jaqo+sHh|2GRjf@ks+ z{3a5DNX>>xOQU@qO1n_&obD5PG+MfV;@%^~s9cP%6VAoBLs~)=8qo|5M$pdavn7T1 zvRyvTGyc4S?{au8BhH8wzySgH`4p?+Z2_1r;Y}9fwDn6|e5C3I`AxgN{Q{yrKMD6; znpB`o+0LB!lBVb*Q`2Anx@SR+qG0_pbm5uQJ&pRz@dd>45^63_3YQYhTA2(PWY+B} zi+P3yk2Tg>PVd;lyHHg{jl1`bYbfTm&a;+&BOH8ks^57CA|3(lINzG~T2c)4G3kP+ z>9>D&N!n?TY%|mZofD3TC9&c>gyZ%9sqMt9Bb01!E6q#Z9vk*CnN|I!!Tq|y+5y@- zte-`)e)d1TLS={=ZhCNI&4W&J-tU_&b*BWF$UfCHL9j9Cu zf7)W5*48n#^z>1zX2#NQn0O2v^h(TdZje|Z6^(BJTP?6o@>e$Bl)i#?en})>ut!1m z1}MhB*@3r7ZDMFO=7TqcnUc6rrxx|9l_!+?o3=Y|9jLcdw}H_U2D8!SbxyM0s2570E;@47bW zwH)#rbrs#!XaS?K=by4HIK7mtFwgh)O!}bj8pRTq1L795XQ9Wk-FX<>ZuY6cWjL$* zRt|xKp2-fN*HI_lwf`;pr&m&v6105w(di5WCs8q9rN4te4X&@=`WiX5qgSMWm42HI zcqB%tI2~+{QMIfH^7i+vW%m=!TyfYDQ(7%~%f=+FzM~ip<1N6s3D=k$RnJ;=AJH!j z@>*#BsAB4;p0)n9gAx#9cYl2AlqEZ6xBRWxn}T+El1eb|#;wAKj5LllbJR0bOvh}p z5N8mO5KfHSvq_*wFiXca4DxMbx4!KvzIG$h`uO%6#@O=MAeH?@V>_|&>Lmj^3C-6& zUKZ#*65EE&m$PjSdnL6_#9Z?gbj$3_xFZ$S9W<#{sAXZ_BjLE{#Jn)GG z_gM;pW#bjR)`z|7=BnqQg+gcltU=6Qlr|SliRoc~Ret$%yQNYIrJbLx)m{b<`tvbq zryDl`x3e=za>GL|J{z4)%Enz$b=-eHbmhr{m85jH`6PnAU~6m^*8BOi;bgftizB()aOXg>ss4ZW<4vzW2q*Nxylu+_-ZT>v>X* z1-R>Gg739mmky9BwtW^>@7iHqF_9rQhxdKT9oy~ z^Oo)aKHRW^4W8}K@g{fNXW+9r)SIY&S^LZL8epGRa$BH<>O^tGy*^P{eggfPls=&o zgqi<_dQgZT5l!>Sl~Gr^Y+*jA@+T8pHlHKpJ$~kAAU!iBC#F3BdYhkRnn zqaJVJm!?GuG4qC-`o43ske* zM)*i}DNSp3SvQKrHdwYk-@)PIIGG`)zxPAWXk*GUIboXVQbg}^FZF3+XAlRN8WZlD zD8ct-WzyMw0B~R@^Fz7&eLDpzUB^^^s2}^K)iPufn-_N9Q12g%4SHn;Rcnsx>kL|A z@0`mbeaj@c)w;DC#?eQP-JVKcBdCyg0t5Br;l-y4ZF#)EH*uRyof?&z&>#p&Mil}i^% z5b$wucJ-h|t83DmI`q6EN~eog_p`;EuqtS#EL=#_>$Som1DY9q=Ek;om~TY8*)8<) zMQsbP-fRrcP%S<-tMdA>xbOb@#~@zRI7!TcfND^$)yf6=;DJ8SjB4uq=zcHg`9P8? zm>V(KY9Tor8!33Z_mNELf=DA!7rWr}wsH9}%$BhOEBe0_x&23(}y%2^# z_nRs)$hUF*Ul0M~l=`!Wzdj!hG_pE&0?=!UgP!QwaX?O{Ztc~Hgb9XH&%_@&<#0@M(!uQ2P$nsNWhzX|6w8LW$uMrc zkNgMLE9&}Z02-g@x~}WFsyv}|J{jD7T}YqLq+V(J{asFp+J%@e%Y)`hYRVRo-?0V? z(hzW-D}7K1DNHc@&}S#;ma|y8slGn_HSf%7gy&$cW4dzC_|;1_eP{XlmC}^|O6nF{ z_W@VL`IRd@X|f+RQQ;3d6$2X!yb_!kV!uxQLq}=iTvxf}UJq1(K9*?(#*bxCFkYxC z0h}Uis2jc_Vi;~AsPx0#WcT-x?T05t?BnWae2wk36EEDLw<#aM>Z9W*IsF1iKF-bg z+PaaE=%NOccE6Af6b)<4re-_OIz!VsTq4iVVwXT6GT-;E#1(Znf#&kePoy{0{+!Q$ z<_1|TT3@M&opG62H!cJ_`k*R^ZnkunqLPq2flKa|eHINy_Z2bd? zerwAgvVBKqBl1L)vh@5@pkZ;c)%LvHfRl`RUY}6y(TSz{-$L&kdDo*Hv*Ge){yuXq z2kF%B8KL41?PX%=ertPKD_^~{0S1=P#}7_cs6O!bR>M~HxwbE|%SPi6h41zEMO^9g zT4yC{ecuHrC?u_-zA2nbXFnDuAM?^Bt??Y);1SaJ%oOhns?-)JlN#R(e=$*JUcG(;oH%MbcB)A6UYXBCgZX#&=eB=_C2ZBMJwSBqy7iYiZx7(@YXpb5kXZxP zQ2Q$hNJ$l5`Oq)p7cy71lt_>1J?lYAcGH3_{_;D?>S=N(Ouv*}HNcRih{P53UK801-54Ul`wk3TW$uRJ zD}SuTLNI@iA((t$vH~5BVoh{OpipK`Pr#$%@^&+$#JjG}uOv$d>MDL}5du8uqGr_m zmmlhasTn!kC~%ot#2qL30TZul6m@ zimd-jlUQs=p*}4Jhyvr~{szL~A2HRPpM;Fq)o2>okqaGFs;gkkr`bnRcPqTvl<-$-=i`)O=p zLChkj+tBrGujcgo5A(a6w+&#O3~tf#>?>+~s9gV`94wl!x>f2g0h1y+{}$B{S6A>K z)JF0;$#Xy@bhzk=M4#Dry9!A@yXG+2G%+DrT|W8{=N> zQu0^ab(%Emd;6>DUB79USz4bch5cx*J|nvkJ!ty*?q!40*0E<<^!s9Yb6t-)zRr&8 zrd6Jk9_WRcdhp}%YIjMow}Y(!Kj)_s!gl0^M@ZS3T!V+o(Q8lDVDHDOPf8Fn2YXh` zkn5w=ET4x2oLUj$n@BDsJ2;}((5XF`^)>OqhPSq0`WF&nEMqPD+Tsb9`E+9IcEEj1 z0Qh$svu~b~h!KSVw9QoVU6HcJ?4t&;1PsBB>P$?~$+0 z!c7)GcL{j}o_Cb4%XsZ*n0z1 z$t+#i{^adaMi36)-wh!n79o@b@D)+aHu$T$XJ!~Or&tZ5OlDj3ns^NL72N{{7suYG z2x6-|M}Zex`#P7zLH=O;sm&{fDcZBpRk{I@Z+JKk9}RaL!Z{tfvIlDp|*OJFasI;h(^ z0Y((~SgS$uUXMJpK^`wI67Q*(x`(FXUa2k_?mF^#=`7LGoxOw4YkKPt9fP;U8p?fC zq=)=+^B@goje5;?YV=*Vj?=_y`_LUO-?`;zhqJ9ypC0$Z%?FulqCziH3^{u_K?^)e z^cbsEox8{@z*EK6C|mfEthFf22xe_~DOa9f=jM1O2umHIyVrD-8oBwr@xVBLF~;iI zTtNoE8|(){Tsi-#cEsmCCl+l(zD!XTD_Vk<>&q?)XOpy3>8YXpPp`-UZuUR$?;l`7 zCJ~**0S9}5&*l{X*R?5*d(|`yV|ZcC&+a;^Et*}MAT^&Tg|UUVCA;4ZRBJQN{S$hp zBhNHiNOxnoUC=C0LUEjIaLh9MAAlGP1~olczEDYSCi-1Pf)`ey(v>Aio+Td~if?t5 zJUO{*G(~N@u3&|F0n>EuBAr{~#p)aAr&>jlC<^!p<8JsEfztbqN;A9^jo_5zfqD zUXQeD(DZQBjocqehw-Xj3WW8F5D2Xu*3m8q?v>UAeM^3CThFDcfzCClLN#4p?L8$a zB*`#s_Cgl3;p^VNFa4iuCOKS4muEVVKFdv++H}opJgmc|7X))l(yNeDn5rn_YysBfvsu6{69oM!(woAdWGh>qOFpfy~EUg|yiBxo2pnnBdl!n`OYb^C8 zG^Xv>4tVy`T{hpyV(cm;kT=NDKsV>Y%mf_IVp79Q@hnV~t~^aNHEZ;zAYdLEw`I~HTgth@#um!4j2 z%>L2$>QxTAN$0EcT=jIVH6W#;dBWSq+e>oT1(P>JT^{*iv)(ZdKHc*%q4!n&&f1bu zm*-pP_RePeBI@4lb&u!o+$Dfg8Fb4(h|XgL3Ozz=T0cRAgoeje&u8t%$c23k%z{Kp z(99azy7f%lj?~gxT-+8T<=qAwbAJrQ_^Xwj>Q&)>ucD7&uoU~3Pfa`d*;;NCPNc}T z+;pWhAYO_Tkx8&%y476H)<)ZA^aU_A;fu`A_>NkMra}lG*>HJwHeqEcYf=90uKQKw zK`gHHexPq3CO9~7R1FE}_ozJ!eZRjO9?lsn|6VUi5@(d3E@-s$S>>+T7|hXuKMo(Z z5B=+iloI7RE!C}E57^r9m`96rg7T!t?^4qsRCO}vRoEETQxZNxwpH8<-3?xx*vhqk zz}^v5RDjAqqgM))2dU_Y?)!f0#Vl(iwOfRj->|^UJ@Uz1Lbkh@S=b}S?q2C{9peu; z*d%ESq!^56!iz*q^Ew9m*M53UJ_Y>R{rFp{dX2r)0?e#q_x7efclvpM3t2Rhn$J{A zBB1;=s)xZQtHJjOQT~BX`qFp*gz*CU$D-&BcE2P-*o!&O(ra8oW@pNHBky#c zr4LBXTM8IA$0Vc25I+4+2X8EC-fr~k!2WXxf4vR&m-_1SGbrHK(!fslz~W8^+lRi9 z>AU$R2)3G;Gd+bF(vIJug<9Rs@_CQOr-4b~^+tI-Dd-}p*>-6UYJ);R--qc&eNvv0 zi)_?)XxP)%9~O7kR~8>$cQFag4D5I=wjMAY8ZcQLJ`5+6pPnd$IlMH##r9=HDe}G) z$XbYiLrRv$ZfO$2l|b!a?I+K7V?4MYVmrM2IF0($3XCui)YT4;Qq5jYmesk5%}y{2 zp3SF4is_a7xi=Syng0v;MLtjU*k_oS>H#xeDA@OU^8m>V8@i-omLWg|sF7ag_OW>*m{_~)$Bp3-{^zSRG9M41VV9o}Mnx%jS)Z~}0 zc?E5q+uO#=PbxZ@ds3pl7}s zU}{$~V}-Vg40PIC7--EkI{XT*MqFlb9s-=e_xcNn77z)Tfi*05#P2f|QEDfeh;z+q z{Jm22ag=90ct4&fZ@#~|B6qXVX!VjZ;JPWUiJk4%W7=?MqiZo(I1JzHXiS~!jSiU1 z#0dyO7xR&l>A)v@rzzs@GWJ<M&w?$64o1MSCe)Y&-bmi%yd6^ z>1Uti@i?@p;-;{8Or-9BINn9@g`)AhJ5HF!HDWIkB{M(9te>+rM1pqP5j zMlkzG_juI%x}~I=L~5TlJRPw)CjR&6jGq4P{g8z3oa~CoCsTcZQm3<9UXINOLJ9Kf zh@=Os6{AWwRS3@WvnOAm+?!bS?^RUT2*_Qt(&lBO4fh)y!l~wOxdc%aC1E;v?C>Kd zrAOcz#kue3yngXVyAA)MbRj}-b&^|7n!cCq@Rq^!S5sP9?QA4U{4chX8CIaCkD2pW zebTbN$&Wv|q@?0j+)QQ7rnu$P18U#w&pz8X_ZUS-mmaXYwOeF8ovn%-QI!h2?)|ny zNmpAn!xpl$^D1t1eH6O5VECR^;e43_Ze^wqz7J9Ga~{xYbKk5x?Qf3RQ9hWC zBg_>nfxdP!yC&yD?_(rM%I9<o$zUZ)IeoI@cB9NZ#03x@q7X+Ey-s#-@B6S@C6LEYCeU-lg;Pk zjn*SBfsrNKd8THT`Y-ZknvHE25R9wD&N*Iv=L^Q@h+AU0+AYyglmRwgkiRl;AW|lp z5eF1Mehh%_+L?WvxxV~H_6oV#Jca)8MGI`|jL?L=DtbmoaQzL?Cr;&EwG%-ef*)-n zSW*j81Rgp>e+GTEv5p6mhh6U;SWeNHCkqp8p=Ee2a67mc=M z|1cPn;K|GkTW8UXfH2*l7pEnZ&WvY%odl;3Eqv<4=(77S-306`@f1C zSGJ>%Uh5fzAJ{Z%>U|4wo=)$Z{(h}@OR>tjuD)pcyBrwI%|{3eqKHtb(B&GAJIf?1jeF%SQq zdY21h8zluv#&gS>#=m9F{Qla<$`57F?kbg+HkFNsiWU6Mg?pt5FBCPr$V#Ktj_Qy**l4%T3XIhZ9VHK1%iK3Eb>=!n|Pa?R#nF0%J~P|I{(@6=iSXq@uDGeZ69loNEN;wc*Ay z@79~UhTEs@=G~oq7IHqWg+MmoT=h+eO=>IUu-J}(xslqnBLSL!w` zhOFGA)2`QiR{-}*b7cj5lu`S>EKxaqF^|+C5I2QRFb|0y5&n!Ia{G*L&K~o7v-ze| z@6|;p&EdFA`jPxMmTY|KLk7B)lv7+elZ8v)6}c?-ps0UM-h!D-tjs@Iw{(3e&~)kj zArxY+NxEGbu#(@-ly}bhd$9d!)}i-;q)2F%s@Slov-gkfyALQe}3Um^&L?aJ%oJ%5XUiP*2v6f!^l9e6>T6^gSHO&4yAXD#zCn_;r zGHY0Y;0+nwcU$d+;d=+lbW}vpt^SGK&ifCiTE+^AaPNk1#fz`gxdn0A9@nexvryl!K`C<%dPr}b zfaq+S@@l|u6L^6tYkqA+-g2fJniQlSww$vRIQrhQv)=Mfo&AIH1Y4bc^`W)7MHe-^ z&ym>>G-z6BK3ALbpWloKVu#X)sN+~K()e@V*C7Fqn&bqh1{s2 zKypVSyt)gd&3{&J!$D4ZPXfRXX5GYOdd$V^NwHY8FEAJiF?17sKJp#pDS@|v{D&nr zQwTiDA1_qT_>fX^!Qu)^yd*sT&w7Sf4wV0_t{E1<`r!VVBO_7gtpo)bB` z=*JmZXB7;$*ciAw&j+Me=r-jgQb5mJRHj5j3d-cBZ-T#%R<2b&sXG|61@r`F-+w#h z{Eg2!?ob-?qhRyr6HizHXXCJFDv-nYN8h1vRE;6aqk8|?(gUix;OHRIau?7f8ay>) zqvm4nI-9rS$*xcqFT3*v8x<0WP*N-V?F;rgRw=Gq=}y;5N>Iz-6>gTYM?{5b#ls)j zBU{=J3VFHqqk`rj&Ot6(V)*)~C1E%rGFXym*)gL0JotBS{Jh5W36 zy$8kUlq&Nu8C45`l4Mh90FULidRbb+bsOG;KEBYJ?TX6I$`2s&1d{V=u^b&x$uUE6 zQ3Qo&YtJ8`zvLy}yEOVjOMATSCpepLe|7tFwpjrzVmg4Q6biyKiZA*3S!f!^=R6w| zc1K3``A5a~!p>qzxrLbD66+YfJb;)v-Tb~Q^EZ-K$>lgik<}>iqWWe~ds?3gq0e%; zJJwmAr&E3@lU|#DAH5)V-}7{C!@0wtGR0f%vhsumPOjIza!38~%nDNzOpcS{c&R%; zE%BPv4S?_dZb$3Ag^-F%7ms*{&Saz)BCn7#&t9+7{^^u|lUZ2ZF6)ZKr%E9J+TK6$ z$LhMIi0^rgi~124RJ}$P5YUrfyn2lTT_3oYmppAP$Vm2r239Yp>ox4%_n{?hy}+K= z_Ql6;m?O19Exdt-Bl1NqH;Al+XPJaK2FbQi2X__17TdJBobO z@NNVu3Mny!@=IwRk(t8hjtS$k&@dM8kK0F6(k z39C*Okj#-7eTOP`#e`3lcBS`2M^cQMcO-Gw4XTRR9$&|Qou2WyPe~OjsNS@{Z*QA| zXxdNk;4-vt$8RqY7K0!AYh!vr^zG7qTmv5v{8h-Z+KePItJupCY>4}7|Mv3g{<9Z8 z(_7UT;0CMS;gNdBgkPeLKAkmcujuCSK85410c|*Cjj3u^5C-rEHh+Sp%|(}s|J$lS z!j5G+zoPeRBX}t4*lIn_w)?NVFxnpb{ufff!o;9IROhgK#q|cMMqG*2ZymhT5HsT1 znjVpd6(_$S{|WZ>nW{5f2yTL2!Ovp_yN{g$&K1juuHtkh`1B6igu@=aG-W!}m} zL^cB(o8<=uo1YuQWg_#J03{UJzoTPjWVK%5&lwo%gIWbW?tRmzC52J?vqWxSO-`hN zqNy{pkL#OT#CQ*??fbT>yyM@bFWkV=2jyx2$LF zU$t97RMo4Ij!)(syAhT7{P`gM55)mq0e;}^ghzw#Z!EKv$BG)kqz;`q1@M3S#3S~8 zq>f%6;Yrf9unnBaAndSeH<~%D5Dof+TOe;ATN0ToOZ2zadKhR`zM*2FbiPZ;SNVSF z@}S|udTD1OUPotPtoSq}G>X5EZ*mM#)OQA>2$uQfmeM91SaDC?j{IDQwORgadu_1! zyJlV_W@{vO+GcPg9Spa8N1ml6G4Ae6s&$_4)k^GX^s@dsH(hw$QfWsRqeUyb)4z2? zLS1@%DbK@+3QGD? z4@R3@c*_VsL~}k#*@2GPD0eKc7zu4=U7n@MB(`N|oKcLoiTdK1J#~4!O-R^mJ5h*Y zF?vCBeI+C3rT)=bS*?rCKNB0k+SY+{YEW*h`T}q|$p1de>9h^o6+!4>1Q43V?5v&% z!`)b2Wmev%D;w1I+v#>Jr`phv1LaKzUQOnQf@NNSC|4?}L8K&Y?|$pgWUaiZt%MW; ziWF2~uAU7#8+xn(ohj(&`#)Kx^x99P1)RH0pEK~)6QJ8eu)U9&tH)#XJD4`FI>iT{ zfmuqqE{cD64WX{E66L89-T7}7sBh%gO6Ss>lY8f^;TXjHB9UXWWd>(cgmN|0gdmHo zEn8wxeuB5)x2&B02do;w&mBaK-;d!*!nG_nZ$CKWxv?QE)>Ay_a8>dC$|c4tuwCUV zG>m=0-n%vsaoV~=!~|8l^ZKC)Eu5eN+8(96oY*({TgYRbHn#s_k@J?_mWJixHM?)? z#nzo~t-G;4)<-L(Q`IZUf53scP<mhh3 zC;nDnZW2t=>BYw#=o!zX=BesnEGsnrQ4sFpF*MTwpdWaf{RQlzv(vT6o2=lWb+U%> zcHzCU-8MwTG!c5aolWKeF0SPXh;r`n$5fV0F&!A%ZnjdbUY zwNfcQ!c}^q)CA4-e+1?suftW*^sJReCZ_S8(AD8J`v%L5{HcIG?msH@FWevfAGlxX zn{n7nY?4xXp1nRm=&Rbde^bqEYlZtJ2+dv+z#-ZY@v++Td_vOQ0^*e( z7N7fDGQ!Yh#il9|c}tO*p0uXFy0oGCGPUb>jl|5nf%>ufG3WSGWB>XaNPEi^mqeG3cJlB$i`cMvbv1_~j4UymMO$q|Q4i{w7{Cvd3wvE1 z4Pj~LMI@+NIN!Qg(jF}y+&J_y3@DsMnT?Khk@Hqk7%6@%ZTC9EtD!ZZ7U!@_i2Sa22SDx$08IHOIM6ryNH}p}pL470r;^ip-2xzwXCWM_sb%v#Xhw2BxKda^rg`%E0^WFmt zB;fH@EAn~Nu6qiaf!-Gl(W#OJ@p24ApN3bP}DB?5rVoaY|Q?VlR}>T5zxDmxHfD=3Equm6>h@Wi!c*xAoGhK z4;i$TR57pjKdzFQ$VW5f_@`9M!==WIDU{Wk*z^+V>r`H^=L4PPcO|83oR0s`Yf{$# z&xt>=%vEX;m#XsN&M8Oi6%~}4(?%^^jq%BdekFLqt1~Qt~g@)MPPcn~6Bu%WW?>|&y2@W#@T->kOxT2LU*KC}oGX=x8HFI-9 zJT+f(YGtOJT|sVMu|mQ$3>0f!J)>w{JqHkywuw@j7Z#H;z$lTayX_w~J$2eN0UM0t z7Q{8smF#%g8e+!S%qnc|1qQsn0G?mqHDnAB#mODKb{CeYqcY?Lx5}4x(*#*{Y4*%q z^SJU(j!}o;)3}t-z}OtZC*zo2kSLa1kql6bHP^pFe&$#rATNdAy|Lh7Pe!~jPJ`q} zUdyE>d;JRB$+R5M^uR}mOfE0S4M1LW2{yZ=TXpWNypbrwC=H0{qc1{*dAU2?J%z)T zVi^tvE3r5r&^l4Zq?EjUB1yS%hlK|f8?7(@oedqwOJ&+gH!N`XT`X#)NWeP=2-kQA zu2CHoz*dhgExdwCbXN}&oRb_3G=tZL$v75&pRQ>`Tl{RlCC!{lb~Y}t+C zuHo27yJ;@F1YtCD(^YIDjFaM}eu^rf|L;3d(iW@Rn=+T01~NG-)CyJ8bU3pruOI+A zh*z218sF#qYL%r8+kPjbjzR(WnGgZ9bq=HVL@@iY64yeP!Hj8@Hjw7nCQ%)U0DjE; zSp0v8G1vaT`jshIk{j%|kjaBC9oQkgXUpV}PJ!ie-PV6-O)p1#hc-K7d`k6QbBwds zZWM{sRJN`Ve~|}WjSMvz?AetRS=wG`CHsf0@j7>-(pyqSy=a*w*H0F&N%G(5k0FqS?zT#yb$ zg2z^Vc|&SU?8{aXRJYihOXpP2qP%h#XB0nSv{stF2saF|%Rcg`(|*KU6H=x9Y_{ae z2aw|1vl<Z zLuP1i-wQ+(yp|JVH-%p_6if4;%gM>oIk*C5$HR2JI;2G>moJENIv+mo0gBUx#|L8G z1zi-}hHl1dK$mA?FYT>T=hG#Vl$fV(P?>)%l;jZ;<5lq7JoG^Yqxs;9prl_)qylVB zhz7^C@tG&93m8-p`q!nk=>tY(TjIZdgMT~+vOGXFp2I=?1NM$CJmk(a<6TVKs@{-u z)w#4^4`>f?wDF;TuwtwG1&!@vtGzm~_J$7m58R#sf|<@mCzu`!{GM6-hmDI(mNN4n znLwpoBXjK^QNh&&sXRWMFfv~?%XKl_nmaV?LTb}=VsGtE(1G#cz*# za_mR0dk_aZci$2R6I4wD4yc|(B!}8?HPt^IbChHs7eNpd4Dy9 zUwcwPO6V5uNa-WbDnXae$=ahjcmeFT?2^`->;~L| zOg%Y&KE;f^R88=FmLTTIl>l#&3Um|>mbbX+I-%~hfgG{5fUgvRNP1bV$B?8}Aua|i z#4f64rZCgj-Z0l~L3ybM!Q9oVMx=`F%ukU-6(3=tSy@#3gsb{Lchd~VwTW4v6_8bc zKGUTAj=tGg%^cYw;t!s%Dm>RsJx2zc`uj2qwBe(>xJ%Si*>e+LFl*QQAL}Hy%cFCI zfo}M$Ut>G;+N0^f&;5S#B`%Ql?e{MHjN6C!qtJ;qffNsRa@3hJXlQ64EeR-v7nbGc zw0!~FSlZEsSZC@l6XVv$#0s|_^uF6J|&px2aX{|zs`?mycR!8TBlN1UI9x! z^0e;Tq|)YT#zy=*LyW+anbL8}sM^^4@DX6;OSE^65X^BtqjizQsOX}OKK)fX6Pu-i z-A)Jngh`IQgh0>b9K4KyfVR`?A59?0E`stfH&iO)Oboi?xx!vsc+!k6)Xf`zdSXG3 z5i5rZ4fMT7-ZtPeCP--4b=pT|MjT|RITCqDoO&2pd%_F$@VtL6%Rjf2FSMEUG~klfWYrIVP+aoOW2V^*44@V zw`O5ijgHwD@QTsK~DBrW8ao3Q+ZgD42 zp?oedj`Yyh!ARQtedC$2KZ*drsz%5pdu947uCY8YTPz!4?k3J|>H0lgQq{RDS~zQY zP$Llv26vp7MnSK*uwns%lS;vZOX=u!>S?_fKx`l_9Hy!HdxQ)Hx~Lj(QS1A?F!Byw z*iv+=!2|;#*Uc~S!uW3jx{gYRKq~MVBB_|aSiX`rf2%UrIPS7n7|mwV4zD*76=-Fq zlA-mn+6+DWj@KEEPkThH5jp7@J8yg8;ARnC>oD71jY^uZRDEv&l^c1|eAJrujla>wxz*H3BPZ!FHD36bvI);@;lnt8F~C0RvWtnD=Vc~WBM=(~_@V@;p&Orv+{D@1<`fN1*k zKkRQ2|AJpn5G6Q`-pgFJ-B;I~dUw#4w+Vv>(tTMiHe!R8YwiP6c&!ZXpBaH2X1e9X zt8{DJon{sYsZt=)VcIdf=Ii$;Y#2iA{Gez7{v0i3&9t~{G$p_Nwfjka$b!y7MZdeU zwk}wvR)SX>Rc~80)6{gscfY#R@r`10(~ZEfSbgBh7s=vX%e3#N@bE9MV`DPIqL2(0 zjXSh2tPA73c1m6=%GA=Nq#Fn3)emTM3N!^*nnVy=NANf>HqHd-zx@#ipe?sXW58Sn zIan9T?tJ6QYJQ#tk`we}zj>T30np_*7^dUeQhuQaW`!0*(<-e}dyk+`#kVLk z#;OfhC+h-mB5TSxSYnm4XX2ymg^Z-&{~7l=E;C_l-oxP9u+4^uU$ET+clusHc&3D6 zNKjHGt?IVUcp~X+`a!onK&n>-w^rpsf`9*yNyG48lg5AOkg4{`;n9CjyWq$FvLRcb zYy)59gFTy5cCK7kn_1O-Gjh^(CX^nQFEFETcZ?n~>we>0Z*q8&*D}xpDv#x-;q#0{kY!Fp`BsG&s?A`Dy|t}wE`cC_Ixn-SZ{F%zkfJmGdSRS zu;Tl$@z8ztbZL_aU_$9;dD-#YbnzK^biI<(iUN8AZC>#G$73RV-C>YGOnBumYmg>R zmeWjrOh2Jtx!nSL*8U#U^ip{O-MjO4`A3`-^n7HN*Q`W{TGuJg@*nrUFD#uZlx$s) ztEG~ES&cE+1HyInPjeV##@Ryy=CWE{Pdf~R;I~YgMU3(9xrR}t zj7yIYr^gQ+#sB`M` zrdR>2+m+f^&O-X+ovwuNBFUe|@e3?e&JY(I8~_o9E~1bS0Fljw59U)*)Hqe(E7RGw zmMJYpRQ{jd)vMXKEYRy;L9xI+wSgi%K&d1^r%OlzU!9X4*cOwAB~!}>w~{b47xvg~ zr(3*$bf3WO*W*}CL$~y5 zY+y%pT{=rfz(RMH{tDEddQ>C5h>9~!{63!*bX=3X@J^UMF&-o=i2Bjl)Y+u&`X?!j zl0xr|(WGWJ_TLBvqH6=TW_RejwUmjzii7Pd!1vz$uJp%fPA1Rst*5hXE|$~GN&1{B zybrAZuJ+oNpXNl3$apq(@q{F?8%86}_fHjXW!}o{( zhp_jKYBJ%r#>YWnl%}E-14KoL4ozC@C&}>-tW8LeShm=tt`S{JnO7|&e><5eL7?g_GmWAhl|ny z3^VNqQF@g$0+=?Nu)aJ^Kd%>x91TS)C{M0*jPEH!oyKOVyJl3!y8-bYoBdAFYUi}~ zvw%|#cBNQ=;nsb^%KH`iZK#M|Kxk$Lx!t}a9kpx+5_L}4IrthqnY2u#F85dwj679< zf$<)tbt|%!x`(rY4Vwr3D}603>`T6-n&6O7z)Q8{kFkc}ru9NGEAWqvr6YF4fV)vd zj2Z6LsJn09M6U?h=vjd`ykC(2b_$~MfB_G4Kfl8b$c?S85N;8g%ia2O{CjmP>VhW@ zsInr;SxeuutSEh__*@=ajE7a1ffG$uLdI=b7oK+9%pEM~>+KZ1R}x~;G-#Xdp3}M( z)c)=W~;9kwi_WpDf@5`)G zFa4*+K2E>Zdft63)Dl}XuDYf!urwc`JT4XD?=)uK1P&vZt*B?C=Fz4Wdle`B8%b;- z5p8Fcmx`FH%!2kDiNmd~Z5k^EBInfXl<$GbDJ|n~FIjKRQBnLfk8C!57CGccG3YZj z{nCCrOO*B;_g;ZBSbS66wFt6Pv-OWV!v|Q~*y|f7w%oA^iaHTN+#q%sZm~?4)T@}h z22_eLX9I4j(6@*z%fN9pCSqQqeG)He1hL7kJVt061v$6&W1!uwyJcORpH_2#imk>b zbW$U4i**CmwY}6K8E;?TtF0X3J#%f#q2K6P&-)m~0OwGo--Zxk{vl8$)u!y_%d_U> zPAS0$9YTlHM_;>x4TZ&b@4U)-o(#67NrROS5+f;8()?_Fu%vi!(Q4hQ%uB}v;>XWNQkmbeJ{r@DQzJ! zqswZuq_hhC+ zePuJN-#u=R>$M=Tpjf^hxRutXu+C-wLMK)!?q6a{rWn&cY(ILt*uY<9_Vbz(l}dYw zDRNzXgeoY;)!{gX9 zc#|zE&ijr4YG(|A- zZJJ`G8?UC*i)T#zmU*7-O}h@^63+3Ms*yRagc;fE-(i$<++TAH1q}Nj)uUp`7trez zJ-Bjpg8=MYQOvW3O3^OL0Bta$Q=xWaF-aQSs1m?T74pN871Mo~8>f*TS~NoeF4Qk( z$bnd67%sIM*Pcwg52doVX#*k_$Q7wwKMv{XbcVHni-mdn%8NMPg3j3^+6@~alcj9=TSFX$`}QhA@Es*wsse*Fa<>xjHM`#mDb zQO6ng7#JR~i<94@yw3bs+I)+n-uT(Rboo#1-QIN*5&Iwyt%8xZa6nc5SfE`RJ{f)c zx*>dGX(2AyrBTcNbY^eU-p;x@WGrKl;{Yb3y*vMbe$7{Vm0RP7-4}A2k8%M$a-)KC zCr2f#c~gsA@9Vk1_D6jj0&2WE_HSR0o6Z;47ltNh=hQk8?Qb`_?k$U4zTx!dAEX9{ z)(GKlq~mjz)<*`LEKm#|yJ0^_vcIWDPc7(wQvhf6d6_qL$b{EDFtz-FweOKfLg2BI zDfSP`p0p03{o&%8@O{?7_{Zvk%8Rvl4plmlq9yrhIF#FN&~{7b1oA->7xLi2ga#^D zi0~wqul}MKkt(M<`?O)kLhjV~Q+Vsixx6yK{h5K2=(zS{S%CXn@SIb6>IDw93AyWe zDx~7k(~b+9Gb{3fg3A%x@$8k#UuawU!V-SSU*8>07vRpTTh*$*fkDMG~3n_ ztG3I2lj{N7q9rB4e%n^wJZ-E*q~32e{}#~+5(jASvIlO!XXg>*h|Kzd0X*@nwdXnV z&TKZvXt4NUjQ3JbTTWku>DCr$$LUk&d%pJ?i-TvXFdc>+zndz~#%8R;ZXO4zNd&N) z&+#BR%u&rZ7a_YXA>R%!)0rphO$Ea^k9@($aSe6jqfAvtS_r>y z=c^Lf=ihmeuEr8n98vYvSTLK%1{>=w7{DZ|dVMwg!EMeQj{65iM%PqpvYUIjLo(t`(C>p?NjKRb9kxg5lwbI|HK2M`hH}6PfYMes5eo zuWEH)VP_h=_TJxA^e7U`tMf>-v@O^D`ys!sDR568&EpN|qm;U$+vNqEaU5!7e9`@# zgHh#oQsR+M6vvSU5Om~vnB!VsHPx^*PxFPT7x%m80L(jQYy8pAM*OrJqTPTa^XvOz z5EI42y&h2mHRTPm^Ry=Z_XW~C#mDIy05HjfVmtV+MTd9c;O2KEzO9J}2n*?A2=j}a z^)*D6=hYFRX2@-LFgtf?dHTl0i>&?Qow2UzFJQFslmK62`#)@)Nl!aOqM7f;$A|Mu z;}@gu^-0>JB_+Sf=`H(2GnF&xD6YWXyUL=*x8rf}CoOc?8=Er|Ie@<5k$FoPdhqhzO2qTrQxQIGC-lUw$Vxzl3%E;{< z1dVyE?bIw4D7SbO12QV1J(89YrEV?l)SNbA&#Vl#>T`q-y>0IxSr}>I-MB}aw-hbK z)PsY_xN1)H`o<8I z&mXDloWeQfsXMIPqMVb^JJuCs!8A5%-*d*(|5VL?txjP4j!e$7u4hRE$Ib~UPIPx= zaZdUaE{$3`tzA$tFkjm8E}d07h?C5!Qt>)W!W@$m3Ad}}(`f`SC9kD;{n`h(=lN44 zT&UaeqI%Z2=&-51D6t5fHaV(uf4ZTG=BblS#iQ!DXI8Kwv-$0R&K?f~2mO+zak852&YvD&b z_jdNP1e)4*?$Dd|(MiZG2J_g^lu&(>`HS8aZmpH8d@|;if`D?{gFotnD9Eo*JL5{Xgm_}Q zeB-oC#R_N7urpSoB5gObC5%y|=+xpg$=8Q%`Z5aplncVzwMZ?_Hdw1Sw{-Ok3*7rer9P4GT z?OPU`n7%C|@nZ@MdBRbDX%gTEc?Tbet*C#~6%OiL!F_);$O1W=f&Y5e|Ke_BOzaJ= zqn7Gc7l7(1blqr{mrpW6v~F{F>Z(Uu!*tZ<5iYuoKl(vd3TOVF}V5 zBWL&D?^wFLKJ?&?f15N?QPXMyx;tR8ULu@sFwyE+6(I6dKI69gTyd;be=VUoXWtSc zGHEpdYGAIuE?vu9C+KZ!hQ~K?5dM(ET5@kRP6_Lzs0WqPJ_LjlBVGXRzE1yIlUfvz zLv(p*!c&nSz47MdaqEz>uTmD0IziVb>hCp~JbWIVh#raHuu|))Xxg#xskSzS^6AYp zXxd1p(Qv1v9PLTcoIzEkQX$qgCw2%q?-JxR41~;x>Sdz1Iy$7gHM*9{mqKM^HaSKh z=>|b!Jlf@rZwAjJsH|V?z;6?Qmei%s9(+%7Vx(r5eGBDz{LVwR-*xS#g$0!@F1v%` z+5l+3@`Mw5G?1{<$ottZ?SwGo91|07>l{QLG#eki4r6|OEG4Od>`w-UMDd$tM|L^n zXQprn?0yrSna95O#9xVoI500c0n@DKnU#@&nY~>`y|tV`mjJ z&%rH8HeW~blV@M^0Hq(G&gkyq&PeW0cL22^F{uhvVq1Q7h57SiO(UGah!$p6TlwIw zn`p;x>a#h8&U{fhKqqH6-65vz;@wZA=L54L|Z=;4FDX zjpK~wUo-~94GM0B_}g@%4iEeSLa8X6)#X zvvYZp%(P$3)K1Rgg0fd!&4%qe-^Nx;xI!`#ZhG^VGsTCieL4DHEU7aq&PEQtYEP z8TGh{FZaJ!TVVX z&QhoGG^{r2T8){UUswCo?Cf9*$Z+%Ht0=t+pZm<@SX1A|Qk>g)$KnsZ4l~Yka;$F zi?On@;x+67TUuWwG%jxTWSQX6y@3!R!;(Pc;KcUhSrJXWv!w?`>YaspKf3K!+*9|K z|F(|(FVH*sjMwKrKoi3103Glt&5XIM-)sGoDQPa9?%#e`f?_#Fy8o1E7#aWAQWPc$ zl&U}ZXsfyS!<~>5Y8-i{M;$6UV9AT)R%l(_=dAt8UkBljW*FMlsD$oTAg~V)cgV5s zCIDamDzFxZz8yeOHP$wo#YfOQXbC{`rrP4m?jt81#K*rqWlPm_C$0oYX{vvkcvU{| zteiHWWi-X_40iUETfzFwTBa8_Rz6?<^;_BUQf^23@={OE=xObYFs^rADcCiar1!DU z$1OpD<`=lXLDQKNeDMNVXAu?Eo6gzb`-8JUVFZ)e@iD&ZbmPwigOth?%JVRdORLk< z(w_B9b8Mu@SX-}w^`msrw#P!Jwr8Vs7-BqO9gi|zNlJc9uYA~&jUk z!APx^oE)@h+dG^7_^=SJ>sNZHz|P$?N|A-mg@c5HVeRRmSMdIf#)OAC1k+peupi7{tp*sT6{v! ze;IifOb={A@oLb8B5LwW<4Sw!8C+XreZWKmqRRI0E3~v&*kawOQZ($y9Q1#GvNE0} zVWXF@_MyJDwcb;nHBgc5IlJF$6RHl1YDaAfvBkuKq7L5wJ`4cF9HJYv1uJ#ymRk=H zPCF|j;q%-DYL`yF_;fugSXlp#L-fxqaeh%~=bwavLlBv{Q@| zV65dtWzRE*=9ptf4ob&wiSepFbvc=x9XZkp!0PSo>>c0_G7+9Tp?p!n4Z_sAowP>x z)$Or``r*-?j5@`Kp;)52DD#onO0ehaSkG3~q)b#ksn})e+4_e)otefW8eky8$`4B& zTEY(Y*n6-0b$Zn4X%kzSJ}ljmx7?$xByQ5QfT>5#x}o210T~myQz>~+wxM+w`SZ_J zK_@(yf3`GdE`MjOgre!DZM-shV9=Y{62G@Pq|J0GXgk&Ae_h;`xOnY$dl_9Z%l zA=u0xJHJ9dad(6Cnv*91WgRYG{2}e?cF3qsJ!}vgM*{7kQ64ShDrZ01A{pY~pKD0x7t zdLVVxh`*S0MFeiiEoNs-BXyuMNHSTLenC+96}BfI#V^THK*G%qQ?tuL>l2-k-+ecmZV-F=Tu z9EtQL_fqP$K==!(N|~;uos5TWz+-C*mHR39FJ^}k#IMSTRgK823*}C#oKw-tSxy)_ z?^Oynfa?L?Q*R~mV6V>bGw`oxpMU%7!>@PG|9$Gu6Q6(nKYk53AGE-uV6x7-yZ16+ zKgPh_=my$JpQ}k>q}`FkjD6DD3pRU&Xa;>KbhBj-bWXe&y)GXxD>7h;weG8ZwoVdY z*V>4H#>~`#p`OO*J&d9aGO{4FaAk`)W<7hYl5^7X`f88D6z4FA5@b`n&b!#Kb4BmM z(@JVi(}Z8KBEoG1G)5Zw{HwS7gXuhlr)q&}>3s$ln$x}xr!~4t&iY0r_9Urh`nrDn z%tFJHKq+JqGM8Wuju4=YiF$WP; zF&(>EmknYV&Yx7+4g=O$^}!ar7$?qH?7yRSFS>6VHw>9h+=UHkg}6hgMG3nX zykm5HYwLySeDv{N1Im0-qrI*6AY5nAF8b#$hutEIN{{BnV^|vKjU{aITEoWV2$i>8 zQFDI0XCRnjW)b#tb#fu0hI8@XPFTPakkLxYwX*koo|--n;`7K}k(s2aKgZJ7iL$`=-?67 zlDR#jo`hrvZ||=PHLrV~&HS86z&pR$o@*{O;Bhw0lA3i1Z1YQS-L?($a{m(cw;Cm@ z@$$&WU%d-0j$+T+s8%g;!A!x8JO5bGr6N{3?=QEFq)5i5df@kixAOH}WAcNIJ#A{n zCwEKn=$QI>$4{X@vnq%C^NjcoTx&b0#`lwZ&M|}f+@xqlgW@AG$@$M5lWcZ&K3tOo zRu&k#peS&kn#&MAR8-HDmL-Yf;`VX@h)iiwPs*XTTyR-_!z9l;MwyVyc&)JJMk`8& z%9PzCPS~!J4)y=XePZIQJGA}dn0}2D-juY6}pKnU63WxC00yul62OEOEppl zFmr3=mA1F~@H>#Q4pAoZtY+{V8n2|CoG5IOaIP zynAN7P+Dey8$JocUrAV%nTsHi#2F_=i{RHMIx{j@ zmUaC%H;85Nf{0S36hGvUNJ5e{L`?5i$H?!d6!Hvf?sC{rehK+v0Erg{2YVN_xAaW> zZnM(%fJ>D7HNWmD6tlkb4c0_g87n2yG+7$3O>MV9eR}Wqb`bO3`i8kneX;w?uehl!_eazzdHi2dXDVEk4&145*r<4|j@&UvkO}s`vx27MtC^T;hQI6GSD<3QcgYBb zfiGRoe?#;`^6j0JsWE!&lVCF~R!01sgJe?eZzkZvb*W4c{%T~rnF*^r+(a_eUaqoi z4&5*>Mtf($NMsXRRFg1~ePHH562R>zf)1G;2`nOB@v20YqlYxBaq=~ZNd@=dGXzCM zlucY$uVK+pGpRfxd33s1pWW8y&?B>lHRS_qy0hn$9eG;CZ9=YumA8Sh`ccp(H>-7v zIy!n`yOXy6d4D~ZF;z*oViXZFJQhA@@<4m6{82@UHr4%v1nhLZ2G{)BAl@EdLHZ3g z#GQbVu&bqjM0^8M-+OUF+p^%c+nef5jBvQTc4LH2I6Cckvi&fuh}DW~|3wlk*`jiH zb0)q1QS%(s6h?4v`;aUMd&_ie{ROP6t^5{xi@<|9V`S$h)JtiJ)3tRb_wWdgJ&vmI zb-hSPvR7Cr2QeLSCn7R9C+9(Jabb*bf{LTQA$RB0hRz5o{Z!wcN0Wwkz=0N{FusDL zoMm0^kfr=7OCqVzr7sR+yqUA|8YldoV5P_X8t0#?-&Yikb zm)gWDPCh(D?)JOqEnP#W-*PKCHNJVix`20DWp(rK?O;SsV7;8VG{3zG`Ax;Vk8|qM z;FV#GfT)NOXFOL~=8wvu?rR^L^`mY4-W+$|QoSCW%EEf-OTQUe%UlIB$^}_2U z(Wung=wsoKfaO+ntj%JZi(y>3pjH6+BgNRj5D(@^-OH1iZ5@(tQsd8@%F8&N-bIlH zKYzzb_E0b9C@#Hia=yzeal(W8Go7qCv-fnTK8T3aFx!F0%#>olFzhh3E8{EI(GG|R zz3j6I>pP_NH2LkmC$M^KclP#adQJ0ndF!G3J3FEU`Qm)_(5oeCR(Me{zi>LqWag1yVDJ&DaO#0l^*1$>L|zfrw2eSvRK*6dB&- zfK@s@MKOB6qb;&TQr8j9iATcirl(0Gd*5xKYWD>F>3nZ$CQD5jDF2Fm!-^1FFX-kF ziE%KzyjZa)`vFI<{d1}w<@?@zC1ZshyIQZXX*{h_Mau`}#MkRp@80`Qq2}|ecR^^` z+`b@WH0`EulLxeR<Y``(x}>oAziHRZFz(nS+tz2M%nj%LH;EJ)zk)*PCVxWsGEBFDigo%&7VX~enFn(G_-C%8rn0eNVQ1YlIlfo zOv1cZTE~T!;dwq0Nrm6P#`8bsce*tK_}(LJ+I0Mh$gu5PZ{&?zbf7LQi|Xn`Hmu!8k!4K~V#5s{tvr|Fi%SHT4!k|=LHQOtXjomiL} z11yh|t7Lt)*`#pTYiB>F5-UWBUv?Kx$Ng=?94TIjh&5R);s@gtQ%E_xoskN+;+OZ` zAl6mOdw(I$7nQzKS+(XG^-0B7_%3=aX>e)UYg*$^b#t5^=3;13a?sA;t=i~%t}j5% zR+T30oq-XX{}6HxMa^9CHd&Ie9DfyVTAAHZW8waf5-)eEic9hc*DP{wSAHID2ZoND z{LMe;sE#_bs#^EG2*Au*Jpw$R@_j#g4n!eLZ{;x|weg@wrkP9yMyJrOQhTXCG| zDYJy}r2i%v9Cxm7?bVV}d3${;%ewjYU(Z1Ij>3xQZqjN&x4>F*5w&z|)4I|+C>LbhtpA{KR$FIgAhPK43ewofmzHdZw}@a<>p zkd<9&iq8Q+->F%)wbN(*;y3L{H3In>UMueldYv9qg8^$z3gA9_&2i5F-RXoQ<_>3gJswes6g zA~s|L>k`exNE0~cVM&jteFHjDw%7@pg!>u9+v}PXO$^=U(FF>1nx?W~72GuAt|zt! zreb+CtdcmG#g5t2chiIC-3R%X>=aHN?!Chj1z?A@fffD?=B7cP1XB*8TN!L zkl746K5l%aRi#@c8Vz08mNGSZUi}{{Hq3cue{N@9&#KwT(&OH@H9nlYNQHqv@?)E+ zJ=~3oJdFsxAIwMeSf3zMZjz2-Y`-k*M6z=gZria?aO2D7@ijNwhRua>`umqW1k#=^d%O zXa}jO>%QZ=R_Ci<%6cb>G}raKQH>nDHO#D#RMb+9)OXd$cQwZIxXM`5Ulx*LLZs>% zKA6cL{?uHTS2OV{(Bvi7Gq9p`^c zT9p0fDan}WD+&O2A2JU}Gj)lN@YPs2EJ*4lfp6;J>X2}|y8q=KdL^EoV95AM-=>_> zEzmAthc6SwJ)68Be5%nOuG0FBCzRUBq|QwjMZMEe9y~-Xb!-Am$=x#=#+%f>v<9=5 zZYxiYnJ;xL_#}xp727IlO6KI~=8FXJFz|9Tk$X&wpsaao0vk{2nX6;?<4o5Hcpl<@Ba$L*Wfe* zJ>glNb*l{2(n>O9H+5U>4u?qK6kNrb1sUd0SQ~uJfL-qbS6J%6YRx;_{{NL%hpnrU zANI*1d#Cz2d+kq0=4vfJTxdGukwS7=8d!A{1cz#FOgx$9e6L|O?ulv!{UJVN4sP%8 zXoYQQ_j#)A2_WOEHUP8;(sV$w$5|s&YxH1opkLm8Furv6`Juxup=nVlA6vZc_r~*! z9c}O6#>(w>J-Rrp9sbQUX>{uP);3{lHPgw%vh{+VW;etH7E86c z>FSNm1yu#iB#ARElL$(qV>f1FZMa*?TxP#rt+>l`kfUfP-vxIKPmX7nbp#E&*3-ol z?Px7R(H_fRJ$rAPKIzreOc`8UD8*l`N!N!98{&WQbmQfb7OZc1Z%m~CCdCn2mMzb8 zhoumLo^0mDP3Clx`|voP0Y>sf(mLcCJfBJ&dFE@$mw z#S|n^rzFf5K<&IFyhpv=h<`zkWtfuAU)5V4l4@)qbb< zKk^wd=YuxRgmo8f|J*sB4>rj6$IPm8V5<5bFon|V`20`=6pgq%9%|hGUjTJ94uK^=P$cgTFlh#M+l*vSW5| zH%uIprLOilnancl90snNwxTCW26UQ!eN5PT+^(SeIFD+TdRlxq>_JU)A05il;nD5cc!V$ao!p8JsMnDONGkR+(DZ2))HOpoypPe7gaQy z><(9(EGOnSw>g%M`GtcytBw&(8YIE+LG_1t{y6pwW2d$c?!v2$(XvFTYQ|7-!qUJ^ zX5)q{A>A&BK+ns!sfLCu?5}4{B9=l2wSggKBTz4znf%;`wJkR;S=Or@Pl~fs-=

tQ3IccqdKuEDy{*H49!Z+f{?A9 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

Hello from ESP8266, you can send NEC encoded IR signals from here!

Send 0xFFE01F

Send 0xFAB123

Send 0xFFE896

"); +} + +void handleIr(){ + for (uint8_t i=0; i + +int RECV_PIN = 2; //an IR detector/demodulatord is connected to GPIO pin 2 + +IRrecv irrecv(RECV_PIN); + +decode_results results; + +void setup() +{ + Serial.begin(9600); + irrecv.enableIRIn(); // Start the receiver +} + +void loop() { + if (irrecv.decode(&results)) { + Serial.println(results.value, HEX); + irrecv.resume(); // Receive the next value + } + delay(100); +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDump/IRrecvDump.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDump/IRrecvDump.ino new file mode 100644 index 0000000..454b066 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDump/IRrecvDump.ino @@ -0,0 +1,89 @@ +/* + * IRremoteESP8266: IRrecvDump - dump details of IR codes with IRrecv + * An IR detector/demodulator must be connected to the input RECV_PIN. + * Version 0.1 Sept, 2015 + * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009, Copyright 2009 Ken Shirriff, http://arcfn.com + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + * LG added by Darryl Smith (based on the JVC protocol) + */ + +#include + +int RECV_PIN = 2; //an IR detector/demodulatord is connected to GPIO pin 2 + +IRrecv irrecv(RECV_PIN); + +decode_results results; + +void setup() +{ + Serial.begin(9600); + irrecv.enableIRIn(); // Start the receiver +} + + +void dump(decode_results *results) { + // Dumps out the decode_results structure. + // Call this after IRrecv::decode() + int count = results->rawlen; + if (results->decode_type == UNKNOWN) { + Serial.print("Unknown encoding: "); + } + else if (results->decode_type == NEC) { + Serial.print("Decoded NEC: "); + + } + else if (results->decode_type == SONY) { + Serial.print("Decoded SONY: "); + } + else if (results->decode_type == RC5) { + Serial.print("Decoded RC5: "); + } + else if (results->decode_type == RC6) { + Serial.print("Decoded RC6: "); + } + else if (results->decode_type == PANASONIC) { + Serial.print("Decoded PANASONIC - Address: "); + Serial.print(results->panasonicAddress, HEX); + Serial.print(" Value: "); + } + else if (results->decode_type == LG) { + Serial.print("Decoded LG: "); + } + else if (results->decode_type == JVC) { + Serial.print("Decoded JVC: "); + } + else if (results->decode_type == AIWA_RC_T501) { + Serial.print("Decoded AIWA RC T501: "); + } + else if (results->decode_type == WHYNTER) { + Serial.print("Decoded Whynter: "); + } + Serial.print(results->value, HEX); + Serial.print(" ("); + Serial.print(results->bits, DEC); + Serial.println(" bits)"); + Serial.print("Raw ("); + Serial.print(count, DEC); + Serial.print("): "); + + for (int i = 1; i < count; i++) { + if (i & 1) { + Serial.print(results->rawbuf[i]*USECPERTICK, DEC); + } + else { + Serial.write('-'); + Serial.print((unsigned long) results->rawbuf[i]*USECPERTICK, DEC); + } + Serial.print(" "); + } + Serial.println(); +} + +void loop() { + if (irrecv.decode(&results)) { + Serial.println(results.value, HEX); + dump(&results); + irrecv.resume(); // Receive the next value + } +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDumpV2/IRrecvDumpV2.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDumpV2/IRrecvDumpV2.ino new file mode 100644 index 0000000..15a8cf2 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRrecvDumpV2/IRrecvDumpV2.ino @@ -0,0 +1,169 @@ +/* + * IRremoteESP8266: IRrecvDumpV2 - dump details of IR codes with IRrecv + * An IR detector/demodulator must be connected to the input RECV_PIN. + * Version 0.1 Sept, 2015 + * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009, Copyright 2009 Ken Shirriff, http://arcfn.com + */ + +#include + +int RECV_PIN = 2; //an IR detector/demodulator is connected to GPIO pin 2 + +IRrecv irrecv(RECV_PIN); + +void setup ( ) +{ + Serial.begin(9600); // Status message will be sent to PC at 9600 baud + irrecv.enableIRIn(); // Start the receiver +} + +//+============================================================================= +// Display IR code +// +void ircode (decode_results *results) +{ + // Panasonic has an Address + if (results->decode_type == PANASONIC) { + Serial.print(results->panasonicAddress, HEX); + Serial.print(":"); + } + + // Print Code + Serial.print(results->value, HEX); +} + +//+============================================================================= +// Display encoding type +// +void encoding (decode_results *results) +{ + switch (results->decode_type) { + default: + case UNKNOWN: Serial.print("UNKNOWN"); break ; + case NEC: Serial.print("NEC"); break ; + case SONY: Serial.print("SONY"); break ; + case RC5: Serial.print("RC5"); break ; + case RC6: Serial.print("RC6"); break ; + case DISH: Serial.print("DISH"); break ; + case SHARP: Serial.print("SHARP"); break ; + case JVC: Serial.print("JVC"); break ; + case SANYO: Serial.print("SANYO"); break ; + case MITSUBISHI: Serial.print("MITSUBISHI"); break ; + case SAMSUNG: Serial.print("SAMSUNG"); break ; + case LG: Serial.print("LG"); break ; + case WHYNTER: Serial.print("WHYNTER"); break ; + case AIWA_RC_T501: Serial.print("AIWA_RC_T501"); break ; + case PANASONIC: Serial.print("PANASONIC"); break ; + } +} + +//+============================================================================= +// Dump out the decode_results structure. +// +void dumpInfo (decode_results *results) +{ + // Show Encoding standard + Serial.print("Encoding : "); + encoding(results); + Serial.println(""); + + // Show Code & length + Serial.print("Code : "); + ircode(results); + Serial.print(" ("); + Serial.print(results->bits, DEC); + Serial.println(" bits)"); +} + +//+============================================================================= +// Dump out the decode_results structure. +// +void dumpRaw (decode_results *results) +{ + // Print Raw data + Serial.print("Timing["); + Serial.print(results->rawlen-1, DEC); + Serial.println("]: "); + + for (int i = 1; i < results->rawlen; i++) { + unsigned long x = results->rawbuf[i] * USECPERTICK; + if (!(i & 1)) { // even + Serial.print("-"); + if (x < 1000) Serial.print(" ") ; + if (x < 100) Serial.print(" ") ; + Serial.print(x, DEC); + } else { // odd + Serial.print(" "); + Serial.print("+"); + if (x < 1000) Serial.print(" ") ; + if (x < 100) Serial.print(" ") ; + Serial.print(x, DEC); + if (i < results->rawlen-1) Serial.print(", "); //',' not needed for last one + } + if (!(i % 8)) Serial.println(""); + } + Serial.println(""); // Newline +} + +//+============================================================================= +// Dump out the decode_results structure. +// +void dumpCode (decode_results *results) +{ + // Start declaration + Serial.print("unsigned int "); // variable type + Serial.print("rawData["); // array name + Serial.print(results->rawlen - 1, DEC); // array size + Serial.print("] = {"); // Start declaration + + // Dump data + for (int i = 1; i < results->rawlen; i++) { + Serial.print(results->rawbuf[i] * USECPERTICK, DEC); + if ( i < results->rawlen-1 ) Serial.print(","); // ',' not needed on last one + if (!(i & 1)) Serial.print(" "); + } + + // End declaration + Serial.print("};"); // + + // Comment + Serial.print(" // "); + encoding(results); + Serial.print(" "); + ircode(results); + + // Newline + Serial.println(""); + + // Now dump "known" codes + if (results->decode_type != UNKNOWN) { + + // Some protocols have an address + if (results->decode_type == PANASONIC) { + Serial.print("unsigned int addr = 0x"); + Serial.print(results->panasonicAddress, HEX); + Serial.println(";"); + } + + // All protocols have data + Serial.print("unsigned int data = 0x"); + Serial.print(results->value, HEX); + Serial.println(";"); + } +} + +//+============================================================================= +// The repeating section of the code +// +void loop ( ) +{ + decode_results results; // Somewhere to store the results + + if (irrecv.decode(&results)) { // Grab an IR code + dumpInfo(&results); // Output the results + dumpRaw(&results); // Output the results in RAW format + dumpCode(&results); // Output the results as source code + Serial.println(""); // Blank line between entries + irrecv.resume(); // Prepare for the next value + } +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRsendDemo/IRsendDemo.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRsendDemo/IRsendDemo.ino new file mode 100644 index 0000000..341db61 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRsendDemo/IRsendDemo.ino @@ -0,0 +1,25 @@ +/* + * IRremoteESP8266: IRsendDemo - demonstrates sending IR codes with IRsend + * An IR LED must be connected to ESP8266 pin 0. + * Version 0.1 June, 2015 + * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009, Copyright 2009 Ken Shirriff, http://arcfn.com + */ + +#include + +IRsend irsend(0); //an IR led is connected to GPIO pin 0 + +void setup() +{ + irsend.begin(); + Serial.begin(9600); +} + +void loop() { + Serial.println("NEC"); + irsend.sendNEC(0x00FFE01F, 36); + delay(2000); + Serial.println("Sony"); + irsend.sendSony(0xa90, 12); + delay(2000); +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/JVCPanasonicSendDemo/JVCPanasonicSendDemo.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/JVCPanasonicSendDemo/JVCPanasonicSendDemo.ino new file mode 100644 index 0000000..fddc559 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/JVCPanasonicSendDemo/JVCPanasonicSendDemo.ino @@ -0,0 +1,29 @@ +/* + * IRremoteESP8266: IRsendDemo - demonstrates sending IR codes with IRsend + * An IR LED must be connected to ESP8266 pin 0. + * Version 0.1 June, 2015 + * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009, Copyright 2009 Ken Shirriff, http://arcfn.com + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + */ +#include + +#define PanasonicAddress 0x4004 // Panasonic address (Pre data) +#define PanasonicPower 0x100BCBD // Panasonic Power button + +#define JVCPower 0xC5E8 + +IRsend irsend(0); //an IR led is connected to GPIO pin 0 + +void setup() +{ + irsend.begin(); +} + +void loop() { + irsend.sendPanasonic(PanasonicAddress,PanasonicPower); // This should turn your TV on and off + + irsend.sendJVC(JVCPower, 16,0); // hex value, 16 bits, no repeat + delayMicroseconds(50); // see http://www.sbprojects.com/knowledge/ir/jvc.php for information + irsend.sendJVC(JVCPower, 16,1); // hex value, 16 bits, repeat + delayMicroseconds(50); +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/keywords.txt b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/keywords.txt new file mode 100644 index 0000000..607e226 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/keywords.txt @@ -0,0 +1,57 @@ +####################################### +# Syntax Coloring Map For IRremote +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +decode_results KEYWORD1 +IRrecv KEYWORD1 +IRsend KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +decode KEYWORD2 +enableIRIn KEYWORD2 +disableIRIn KEYWORD2 +resume KEYWORD2 +begin KEYWORD2 +enableIROut KEYWORD2 +sendNEC KEYWORD2 +sendSony KEYWORD2 +sendSanyo KEYWORD2 +sendMitsubishi KEYWORD2 +sendRaw KEYWORD2 +sendRC5 KEYWORD2 +sendRC6 KEYWORD2 +sendDISH KEYWORD2 +sendSharp KEYWORD2 +sendSharpRaw KEYWORD2 +sendPanasonic KEYWORD2 +sendJVC KEYWORD2 +sendWhynter KEYWORD2 +sendSAMSUNG KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +NEC LITERAL1 +SONY LITERAL1 +SANYO LITERAL1 +MITSUBISHI LITERAL1 +RC5 LITERAL1 +RC6 LITERAL1 +DISH LITERAL1 +SHARP LITERAL1 +PANASONIC LITERAL1 +JVC LITERAL1 +LG LITERAL1 +SAMSUNG LITERAL1 +WHYNTER LITERAL1 +AIWA_RC_T501 LITERAL1 +UNKNOWN LITERAL1 +REPEAT LITERAL1 \ No newline at end of file diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.json b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.json new file mode 100644 index 0000000..9327be8 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.json @@ -0,0 +1,12 @@ +{ + "name": "IRremoteESP8266", + "keywords": "infrared, ir, remote", + "description": "Send and receive infrared signals with multiple protocols", + "repository": + { + "type": "git", + "url": "https://github.com/sebastienwarin/IRremoteESP8266.git" + }, + "frameworks": "arduino", + "platforms": "esp8266" +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.properties b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.properties new file mode 100644 index 0000000..d031ef7 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/library.properties @@ -0,0 +1,9 @@ +name=IRremoteESP8266 +version=1.0.0 +author=Sebastien Warin, Mark Szabo, Ken Shirriff +maintainer=Sebastien Warin +sentence=Send and receive infrared signals with multiple protocols. +paragraph=This library enables you to send and receive infra-red signals on an ESP8266. +category=Device Control +url=https://github.com/sebastienwarin/IRremoteESP8266 +architectures=esp8266 diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/platformio.ini b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/platformio.ini new file mode 100644 index 0000000..429959a --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/platformio.ini @@ -0,0 +1,21 @@ +# +# Example PlatformIO configuration file for SSL and non-SSL builds. +# +# Before you will be able to build the SSL version of this project, you will +# need to explicitly install the espressif8266_stage platform. +# +# To perform this installation, refer to step 1 of: +# http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version + +[platformio] +env_default = nossl + +[common] +framework = arduino +lib_deps = ESP8266MQTTMesh + +[env:nossl] +platform = espressif8266 +framework = arduino +board = esp01_1m +lib_deps = ${common.lib_deps} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/ESP8266MeshIRRemote.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/ESP8266MeshIRRemote.ino new file mode 100644 index 0000000..c5ea3e4 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/ESP8266MeshIRRemote.ino @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2016 PhracturedBlue + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Based on IRremoteESP8266: IRServer - demonstrates sending IR codes controlled from a webserver + * + * esp8266IR Remote allows Pronto IR codes to be sent via an IR LED connected on GPIO2 via http + * It is recommended to drive an n-fet transistor connected to a resistor and diode, since the + * esp8266 can only supply 12mA on its GPIO pin. + * + * Known commands: + * NOTE: The maximum length of topic + payload is ~1156 bytes + * /send : code= : send specified code one time + * /send : repeat=5,code= : send specified code 5 times + * /send : repeat=5,code=,repeat=3,pronto= : send code1 5 times followed by sending code2 3 times + * /send : repeat=5,file= : send pronto code previously saved in 'filename' 5 times + * /list : "" : returns a list of all saved files + * /debug: <1|0> : enable/disable debugging messages over MQTT + * /save/ : code= : save specified pronto code to 'filename' on esp8266 device + * /read/ : "" : return contents of 'flename' via MQTT + * + * will generally be something like: 'esp8266-in/mesh_esp8266-7' + * Version 0.2 2017-04-16 + */ + +/* See credentials.h.examle for contents of credentials.h */ +#include "credentials.h" +#include +#include + +#include +#include +#include "QueueArray.h" + +#define FIRMWARE_ID 0x2222 +#define FIRMWARE_VER "0.2" + +const wifi_conn networks[] = NETWORK_LIST; +const char* mqtt_server = MQTT_SERVER; +const char* mesh_password = MESH_PASSWORD; + +ESP8266MQTTMesh mesh = ESP8266MQTTMesh::Builder(networks, mqtt_server) + .setVersion(FIRMWARE_VER, FIRMWARE_ID) + .setMeshPassword(mesh_password) + .build(); + +#define ESP8266_LED 2 + +bool debug = false; + +class Cmd { + public: + Cmd(String _c, int _r, String _p, String _d = "") { + code = _c; + repeat = _r; + protocol = _p; + description = _d; + } + String code; + String protocol; + String description; + int repeat; +}; +QueueArray cmdQueue(10); + +IRsend irsend(ESP8266_LED); + +void handleList() { + String message = "{ \"Commands\": {"; + Dir dir = SPIFFS.openDir("/ir/"); + bool first = true; + while(dir.next()) { + if (! first) { + message += ","; + } + File f = dir.openFile("r"); + int size = f.size(); + f.close(); + message += " \"" + dir.fileName().substring(4) + "\": " + String(size); + first = false; + } + FSInfo fs_info; + SPIFFS.info(fs_info); + message += " }, \"Free\": " + String(fs_info.totalBytes - fs_info.usedBytes) + "}"; + mesh.publish("list", message.c_str()); +} + +Cmd *parse_code(const char *msg, bool queue = false) { + String code = ""; + String protocol = "pronto"; + int repeat = 0; + bool debug = false; + bool seen_repeat = false; + while (msg) { + char kv[1024]; + char key[16]; + const char *value; + ESP8266MQTTMesh::keyValue(msg, ',', kv, sizeof(kv), &msg); + if (! ESP8266MQTTMesh::keyValue(kv, '=', key, sizeof(key), &value)) { + continue; + }; + if (0 == strcmp(key, "repeat")) { + repeat = atoi(value); + seen_repeat = true; + } + else if (0 == strcmp(key, "protocol")) { + protocol = value; + } + else if(0 == strcmp(key, "code")) { + code = value; + if (queue) { + cmdQueue.push(new Cmd(code, repeat, protocol, "Code len: " + String(code.length()))); + } + } + else if(0 == strcmp(key, "file")) { + File f = SPIFFS.open("/ir/" + String(value), "r"); + if (! f) { + Serial.println("Failed to read file: " + String(value)); + continue; + } + protocol = f.readStringUntil('\n'); + code = f.readStringUntil('\n'); + String repeat_str = f.readStringUntil('\n'); + if (repeat_str != "" && !seen_repeat) { + repeat = repeat_str.toInt(); + } + if (queue) { + cmdQueue.push(new Cmd(code, repeat, protocol, "File: " + String(value))); + } + f.close(); + } + } + if (queue) { + return NULL; + } + Cmd *cmd = new Cmd(code, repeat, protocol); + return cmd; +} + +void callback(const char *topic, const char *msg) +{ + char *endStr; + if (0 == strcmp(topic, "list")) { + handleList(); + } + if (0 == strcmp(topic, "debug")) { + debug = atoi(msg); + } + else if (0 == strcmp(topic, "send")) { + parse_code(msg, true); + } + else if (strstr(topic, "read/") == topic) { + const char *filename = topic + 5; + File f = SPIFFS.open("/ir/" + String(filename), "r"); + if (! f) { + Serial.println("Failed to read file: " + String(filename)); + mesh.publish(topic, "{ \"Failed\": 1 }"); + return; + } + String json = "{"; + json += " \"protocol\": \"" + f.readStringUntil('\n') + "\""; + json += " \"code\": \"" + f.readStringUntil('\n') + "\""; + json += " \"repeat\": \"" + f.readStringUntil('\n') + "\""; + json += " }"; + mesh.publish(topic, json.c_str()); + } + else if (strstr(topic, "save/") == topic) { + const char *filename = topic + 5; + Cmd *cmd = parse_code(msg); + File f = SPIFFS.open("/ir/" + String(filename), "w"); + if (! f) { + Serial.println("Failed to create file: " + String(filename)); + mesh.publish(topic, "{ \"Failed\": 1 }"); + } else { + f.print(cmd->protocol + "\n"); + f.print(cmd->code + "\n"); + f.print(String(cmd->repeat) + "\n"); + f.close(); + } + delete cmd; + } +} +void setup(void){ + irsend.begin(); + + Serial.begin(115200); + mesh.setCallback(callback); + mesh.begin(); + Serial.println(""); + if (1) { + FSInfo fs_info; + SPIFFS.info(fs_info); + Serial.print("FS Used: "); + Serial.print(fs_info.usedBytes); + Serial.print(" Free: "); + Serial.print(fs_info.totalBytes - fs_info.usedBytes); + Serial.print(" Blocksize: "); + Serial.println(fs_info.blockSize); + Dir dir = SPIFFS.openDir("/ir/"); + while(dir.next()) { + Serial.println("File: " + dir.fileName()); + } + } +} + +void loop(void){ + if (! cmdQueue.isEmpty()) { + Cmd *nextCmd = cmdQueue.peek(); + Serial.println("Sendng code: Repeat=" + String(nextCmd->repeat) + " queue size= " + cmdQueue.count()); + if (debug) { + mesh.publish("tx", String("Queue Len: " + String(cmdQueue.count()) + " Repeat: " + String(nextCmd->repeat) + " Desc: " + nextCmd->description).c_str()); + } + while (1) { + irsend.sendPronto(nextCmd->code.c_str(), nextCmd->repeat ? true : false, true); + if (nextCmd->repeat >= -1) { + break; + } + nextCmd->repeat++; + } + //irsend.sendPronto(nextCmd->code.c_str(), nextCmd->repeat ? true : false, true); + if (nextCmd->repeat <= 1) { + nextCmd = cmdQueue.pop(); + delete nextCmd; + } else { + nextCmd->repeat--; + } + } +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/QueueArray.h b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/QueueArray.h new file mode 100644 index 0000000..689a1cb --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/src/QueueArray.h @@ -0,0 +1,242 @@ +/* + * QueueArray.h + * + * Library implementing a generic, dynamic queue (array version). + * + * --- + * + * Copyright (C) 2010 Efstathios Chatzikyriakidis (contact@efxa.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * --- + * + * 2013-11-07 Marcus Nowotny + * - pushing to a full array does not crash but returns false + * + * 2013-11-05 Marcus Nowotny + * - rewritten to use a default size + * - texts are now in flash + * + * Version 1.0 + * + * 2010-09-29 Efstathios Chatzikyriakidis + * + * - added resize(): for growing, shrinking the array size. + * + * 2010-09-25 Efstathios Chatzikyriakidis + * + * - added exit(), blink(): error reporting and handling methods. + * + * 2010-09-24 Alexander Brevig + * + * - added setPrinter(): indirectly reference a Serial object. + * + * 2010-09-20 Efstathios Chatzikyriakidis + * + * - initial release of the library. + * + * --- + * + * For the latest version see: http://www.arduino.cc/ + */ + +// header defining the interface of the source. +#ifndef _QUEUEARRAY_H +#define _QUEUEARRAY_H + +// include Arduino basic header. +#include + +// the definition of the queue class. +template +class QueueArray { + public: + // init the queue (constructor). + QueueArray (const uint16_t initialSize); + + // clear the queue (destructor). + ~QueueArray (); + + // push an item to the queue. + bool push (const T i); + + // pop an item from the queue. + T pop (); + + // get an item from the queue. + T peek () const; + + // check if the queue is empty. + bool isEmpty () const; + + // get the number of items in the queue. + uint16_t count () const; + + // check if the queue is full. + bool isFull () const; + + // set the printer of the queue. + void setStream (Stream & s); + + private: + + // exit report method in case of error. + void exit(const __FlashStringHelper*) const; + + // led blinking method in case of error. + void blink () const; + + Stream * stream; // the printer of the queue. + T * contents; // the array of the queue. + + uint16_t size; // the size of the queue. + uint16_t items; // the number of items of the queue. + + uint16_t head; // the head of the queue. + uint16_t tail; // the tail of the queue. +}; + +// init the queue (constructor). +template +QueueArray::QueueArray (const uint16_t initialSize) { + size = 0; // set the size of queue to zero. + items = 0; // set the number of items of queue to zero. + + head = 0; // set the head of the queue to zero. + tail = 0; // set the tail of the queue to zero. + + stream = NULL; // set the printer of queue to point nowhere. + + // allocate enough memory for the array. + contents = (T *) malloc (sizeof (T) * initialSize); + + // if there is a memory allocation error. + if (contents == NULL) + exit (F("QUEUE: insufficient memory to initialize queue.")); + + // set the initial size of the queue. + size = initialSize; +} + +// clear the queue (destructor). +template +QueueArray::~QueueArray () { + free (contents); // deallocate the array of the queue. + + contents = NULL; // set queue's array pointer to nowhere. + stream = NULL; // set the printer of queue to point nowhere. + + size = 0; // set the size of queue to zero. + items = 0; // set the number of items of queue to zero. + + head = 0; // set the head of the queue to zero. + tail = 0; // set the tail of the queue to zero. +} + +// push an item to the queue. +template +bool QueueArray::push (const T i) { + // check if the queue is full. + if (isFull ()) + // we cannot add anythif - just return false + return false; + + // store the item to the array. + contents[tail++] = i; + + // wrap-around index. + if (tail == size) tail = 0; + + // increase the items. + items++; + + //ok everything was fin + return true; +} + +// pop an item from the queue. +template +T QueueArray::pop () { + // check if the queue is empty. + if (isEmpty ()) + exit (F("QUEUE: can't pop item from queue: queue is empty.")); + + // fetch the item from the array. + T item = contents[head++]; + + // decrease the items. + items--; + + // wrap-around index. + if (head == size) head = 0; + + // return the item from the array. + return item; +} + +// get an item from the queue. +template +T QueueArray::peek () const { + // check if the queue is empty. + if (isEmpty ()) + exit (F("QUEUE: can't peek item from queue: queue is empty.")); + + // get the item from the array. + return contents[head]; +} + +// check if the queue is empty. +template +bool QueueArray::isEmpty () const { + return items == 0; +} + +// check if the queue is full. +template +bool QueueArray::isFull () const { + return items == size; +} + +// get the number of items in the queue. +template +uint16_t QueueArray::count () const { + return items; +} + +// set the printer of the queue. +template +void QueueArray::setStream (Stream & s) { + stream = &s; +} + +// exit report method in case of error. +template +void QueueArray::exit (const __FlashStringHelper * m) const { + // print the message if there is a printer. + if (stream) + stream->println (m); + + // loop blinking until hardware reset. + blink (); +} + +// led blinking method in case of error. +template +void QueueArray::blink () const { + while(1); + // solution selected due to lack of exit() and assert(). +} + +#endif // _QUEUEARRAY_H diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/Makefile b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/Makefile new file mode 100644 index 0000000..ce39dea --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/Makefile @@ -0,0 +1,441 @@ +#==================================================================================== +# makeESPArduino +# +# A makefile for ESP8286 and ESP32 Arduino projects. +# Edit the contents of this file to suit your project +# or just include it and override the applicable macros. +# +# License: GPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2016-2017 Peter Lerup. All rights reserved. +# +#==================================================================================== + +#==================================================================================== +# Project specfic values +#==================================================================================== + +# Include possible project makefile. This can be used to override the defaults below +-include $(firstword $(PROJ_CONF) $(dir $(SKETCH))config.mk) + +#=== Default values not available in the Arduino configuration files + +CHIP ?= esp8266 + +# Set chip specific default board unless specified +BOARD ?= $(if $(filter $(CHIP), esp32),esp32,generic) + +# Serial flashing parameters +UPLOAD_PORT ?= $(shell ls -1tr /dev/ttyUSB* | tail -1) +UPLOAD_VERB ?= -v + +# OTA parameters +ESP_ADDR ?= ESP_123456 +ESP_PORT ?= 8266 +ESP_PWD ?= 123 + +# HTTP update parameters +HTTP_ADDR ?= ESP_123456 +HTTP_URI ?= /update +HTTP_PWD ?= user +HTTP_USR ?= password + +# Output directory +BUILD_DIR ?= /tmp/mkESP/$(MAIN_NAME)_$(BOARD) + +# File system source directory +FS_DIR ?= $(dir $(SKETCH))data + +# Bootloader +BOOT_LOADER ?= $(ESP_ROOT)/bootloaders/eboot/eboot.elf + +#==================================================================================== +# Standard build logic and values +#==================================================================================== + +START_TIME := $(shell perl -e "print time();") + +# Utility functions +git_description = $(shell git -C $(1) describe --tags --always --dirty 2>/dev/null || echo Unknown) +time_string = $(shell date +$(1)) + +# ESP Arduino directories +ifndef ESP_ROOT + # Location not defined, find and use possible version in the Arduino IDE installation + OS ?= $(shell uname -s) + ifeq ($(OS), Windows_NT) + ARDUINO_DIR = $(shell cygpath -m $(LOCALAPPDATA)/Arduino15/packages/$(CHIP)) + else ifeq ($(OS), Darwin) + ARDUINO_DIR = $(HOME)/Library/Arduino15/packages/$(CHIP) + else + ARDUINO_DIR = $(HOME)/.arduino15/packages/$(CHIP) + endif + ESP_ROOT := $(lastword $(wildcard $(ARDUINO_DIR)/hardware/$(CHIP)/*)) + ifeq ($(ESP_ROOT),) + $(error No installed version of $(CHIP) Arduino found) + endif + ESP_ARDUINO_VERSION := $(notdir $(ESP_ROOT)) + # Find used version of compiler and tools + COMP_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/xtensa-lx106-elf-gcc/*)) + ESPTOOL_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/esptool/*)) + MKSPIFFS_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/mkspiffs/*)) +else + # Location defined, assume it is a git clone + ESP_ARDUINO_VERSION = $(call git_description,$(ESP_ROOT)) +endif +ESP_LIBS = $(ESP_ROOT)/libraries +SDK_ROOT = $(ESP_ROOT)/tools/sdk +TOOLS_ROOT = $(ESP_ROOT)/tools + +ifeq ($(wildcard $(ESP_ROOT)/cores/$(CHIP)),) + $(error $(ESP_ROOT) is not a vaild directory for $(CHIP)) +endif + +ESPTOOL_PY = esptool.py --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) + +# Search for sketch if not defined +SKETCH := $(realpath $(firstword \ + $(SKETCH) \ + $(wildcard *.ino) \ + $(if $(filter $(CHIP), esp32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WebServer/examples/HelloServer/HelloServer.ino) \ + ) \ +) +ifeq ($(wildcard $(SKETCH)),) + $(error Sketch $(SKETCH) not found) +endif + +# Main output definitions +MAIN_NAME := $(basename $(notdir $(SKETCH))) +MAIN_EXE = $(BUILD_DIR)/$(MAIN_NAME).bin +FS_IMAGE = $(BUILD_DIR)/FS.spiffs + +ifeq ($(OS), Windows_NT) + # Adjust critical paths + BUILD_DIR := $(shell cygpath -m $(BUILD_DIR)) + SKETCH := $(shell cygpath -m $(SKETCH)) +endif + +# Build file extensions +OBJ_EXT = .o +DEP_EXT = .d + +# Special tool definitions +OTA_TOOL ?= $(TOOLS_ROOT)/espota.py +HTTP_TOOL ?= curl + +# Core source files +CORE_DIR = $(ESP_ROOT)/cores/$(CHIP) +CORE_SRC := $(shell find $(CORE_DIR) -name "*.S" -o -name "*.c" -o -name "*.cpp") +CORE_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(CORE_SRC))) +CORE_LIB = $(BUILD_DIR)/arduino.ar + +# User defined compilation units and directories +ifeq ($(LIBS),) + # Automatically find directories with header files used by the sketch + FINDCMD := perl -e 'use File::Find;@d = split(" ", shift);while (<>) {$$f{"$$1"} = 1 if /^\s*\#include\s+[<"]([^>"]+)/;}find({follow => 1, wanted => sub {return if($$File::Find::dir =~ /examples|tests/);print $$File::Find::dir," " if $$f{$$_}}}, @d);' + LIBS := $(shell $(FINDCMD) "$(ESP_LIBS) $(HOME)/Arduino/libraries" $(SKETCH) ../../src/*.cpp ../../src/*.h) + ifeq ($(LIBS),) + # No dependencies found + LIBS = /dev/null + endif +endif +IGNORE_PATTERN := $(foreach dir,$(EXCLUDE_DIRS),$(dir)/%) +SKETCH_DIR = $(dir $(SKETCH)) +USER_INC := $(filter-out $(IGNORE_PATTERN),$(shell find -L $(SKETCH_DIR) $(LIBS) -name "*.h")) +USER_SRC := $(SKETCH) $(filter-out $(IGNORE_PATTERN),$(shell find -L $(SKETCH_DIR) $(LIBS) -name "*.S" -o -name "*.c" -o -name "*.cpp")) +# Object file suffix seems to be significant for the linker... +USER_OBJ := $(subst .ino,_.cpp,$(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC)))) +USER_DIRS := $(sort $(dir $(USER_SRC))) +USER_INC_DIRS := $(sort $(dir $(USER_INC))) + +# Use first flash definition for the board as default +FLASH_DEF ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.menu\.FlashSize\.([^\.]+)=/){ print "$$1"; exit;}} print "NA";') + +# The actual build commands are to be extracted from the Arduino description files +ARDUINO_MK = $(BUILD_DIR)/arduino.mk +ARDUINO_DESC := $(shell find $(ESP_ROOT) -maxdepth 1 -name "*.txt" | sort) +$(ARDUINO_MK): $(ARDUINO_DESC) $(MAKEFILE_LIST) | $(BUILD_DIR) + perl -e "$$PARSE_ARDUINO" $(BOARD) $(FLASH_DEF) $(ARDUINO_EXTRA_DESC) $(ARDUINO_DESC) >$(ARDUINO_MK) + +-include $(ARDUINO_MK) + +# Compilation directories and path +INCLUDE_DIRS += $(CORE_DIR) $(ESP_ROOT)/variants/$(INCLUDE_VARIANT) $(BUILD_DIR) +C_INCLUDES := $(foreach dir,$(INCLUDE_DIRS) $(USER_INC_DIRS),-I$(dir)) +VPATH += $(shell find $(CORE_DIR) -type d) $(USER_DIRS) + +# Automatically generated build information data +# Makes the build date and git descriptions at the actual build event available as string constants in the program +BUILD_INFO_H = $(BUILD_DIR)/buildinfo.h +BUILD_INFO_CPP = $(BUILD_DIR)/buildinfo.c++ +BUILD_INFO_OBJ = $(BUILD_INFO_CPP)$(OBJ_EXT) + +$(BUILD_INFO_H): | $(BUILD_DIR) + echo "typedef struct { const char *date, *time, *src_version, *env_version;} _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ + +# Build rules for the different source file types +$(BUILD_DIR)/%.cpp$(OBJ_EXT): %.cpp $(BUILD_INFO_H) $(ARDUINO_MK) + echo $(' >$(BUILD_INFO_CPP) + echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) + $(CPP_COM) $(BUILD_INFO_CPP) -o $(BUILD_INFO_OBJ) + $(LD_COM) + $(GEN_PART_COM) + $(ELF2BIN_COM) + $(SIZE_COM) | perl -e "$$MEM_USAGE" "$(MEM_FLASH)" "$(MEM_RAM)" +ifneq ($(FLASH_INFO),) + printf "Flash size: $(FLASH_INFO)\n\n" +endif + perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' + +upload flash: all + $(UPLOAD_COM) + +ota: all + $(OTA_TOOL) -i $(ESP_ADDR) -p $(ESP_PORT) -a $(ESP_PWD) -f $(MAIN_EXE) + +http: all + $(HTTP_TOOL) --verbose -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) + echo "\n" + +$(FS_IMAGE): $(wildcard $(FS_DIR)/*) +ifneq ($(CHIP),esp32) + echo Generating filesystem image: $(FS_IMAGE) + $(MKSPIFFS_COM) +else + echo No SPIFFS function available for $(CHIP) + exit 1 +endif + +fs: $(FS_IMAGE) + +upload_fs flash_fs: $(FS_IMAGE) + $(FS_UPLOAD_COM) + +FLASH_FILE ?= esp_flash.bin +dump_flash: + echo Dumping flash memory to file: $(FLASH_FILE) + $(ESPTOOL_PY) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) + +restore_flash: + echo Restoring flash memory from file: $(FLASH_FILE) + $(ESPTOOL_PY) write_flash -fs $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";print ($$2 eq "K" ? 2 : $$1*8);' $(FLASH_DEF))m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m 0 $(FLASH_FILE) + +clean: + echo Removing all build files + rm -rf $(BUILD_DIR)/* + +list_boards: + echo === Available boards === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^(\w+)\.name=(.+)/){ print sprintf("%-20s %s\n", $$1,$$2);} }' + +list_lib: + echo === User specific libraries === + perl -e 'foreach (@ARGV) {print "$$_\n"}' "* Include directories:" $(USER_INC_DIRS) "* Library source files:" $(USER_SRC) "Foo" $(IGNORE_PATTERN) + +list_flash_defs: + echo === Memory configurations for board: $(BOARD) === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.menu\.FlashSize.([^\.]+)=(.+)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + +help: + echo + echo "Generic makefile for building Arduino esp8266 and esp32 projects" + echo "This file can either be used directly or included from another makefile" + echo "" + echo "The following targets are available:" + echo " all (default) Build the project application" + echo " clean Remove all intermediate build files" + echo " flash Build and and flash the project application" + echo " flash_fs Build and and flash file system (when applicable)" + echo " ota Build and and flash via OTA" + echo " Params: ESP_ADDR, ESP_PORT and ESP_PWD" + echo " http Build and and flash via http (curl)" + echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" + echo " dump_flash Dump the whole board flash memory to a file" + echo " restore_flash Restore flash memory from a previously dumped file" + echo " list_lib Show a list of used library files and include paths" + echo "Configurable parameters:" + echo " SKETCH Main source file" + echo " If not specified the first sketch in current" + echo " directory will be used. If none is found there," + echo " a demo example will be used instead." + echo " LIBS Includes in the sketch file of libraries from within" + echo " the ESP Arduino directories are automatically" + echo " detected. If this is not enough, define this" + echo " variable with all libraries or directories needed." + echo " USER_LIBS Path to user installed Arduino libraries" + echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" + echo " BOARD Name of the target board. Default: '$(BOARD)'" + echo " Use 'list_boards' to get list of available ones" + echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" + echo " Use 'list_flash_defs' to get list of available ones" + echo " BUILD_DIR Directory for intermediate build files." + echo " Default '$(BUILD_DIR)'" + echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" + echo " FS_DIR File system root directory" + echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" + echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" + echo " FLASH_FILE File name for dump and restore flash operations" + echo " Default: '$(FLASH_FILE)'" + echo " VERBOSE Set to 1 to get full printout of the build" + echo " SINGLE_THREAD Use only one build thread" + echo + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +.PHONY: all +all: $(BUILD_DIR) $(ARDUINO_MK) $(BUILD_INFO_H) prebuild $(MAIN_EXE) + +prebuild: +ifdef USE_PREBUILD + $(PREBUILD_COM) +endif + +# Include all available dependencies +-include $(wildcard $(BUILD_DIR)/*$(DEP_EXT)) + +.DEFAULT_GOAL = all + +ifndef SINGLE_THREAD + # Use multithreaded builds by default + MAKEFLAGS += -j +endif + +ifndef VERBOSE + # Set silent mode as default + MAKEFLAGS += --silent +endif + +# Inline Perl scripts + +# Parse Arduino definitions and build commands from the descriptions +define PARSE_ARDUINO +my $$board = shift; +my $$flashSize = shift; +my %v; + +sub def_var { + my ($$name, $$var) = @_; + print "$$var ?= $$v{$$name}\n"; + $$v{$$name} = "\$$($$var)"; +} + +$$v{'runtime.platform.path'} = '$$(ESP_ROOT)'; +$$v{'includes'} = '$$(C_INCLUDES)'; +$$v{'runtime.ide.version'} = '10605'; +$$v{'build.arch'} = '$$(CHIP)'; +$$v{'build.project_name'} = '$$(MAIN_NAME)'; +$$v{'build.path'} = '$$(BUILD_DIR)'; +$$v{'object_files'} = '$$^ $$(BUILD_INFO_OBJ)'; + +foreach my $$fn (@ARGV) { + open($$f, $$fn) || die "Failed to open: $$fn\n"; + while (<$$f>) { + next unless /^(\w[\w\-\.]+)=(.*)/; + my ($$key, $$val) =($$1, $$2); + $$board_defined = 1 if $$key eq "$$board.name"; + $$key =~ s/$$board\.menu\.FlashSize\.$$flashSize\.//; + $$key =~ s/$$board\.menu\.FlashFreq\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.UploadSpeed\.[^\.]+\.//; + $$key =~ s/^$$board\.//; + $$v{$$key} ||= $$val; + } + close($$f); +} +$$v{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$$(COMP_PATH)'; +$$v{'runtime.tools.esptool.path'} ||= '$$(ESPTOOL_PATH)'; +$$v{'runtime.tools.mkspiffs.path'} ||= '$$(MKSPIFFS_PATH)'; + +die "* Uknown board $$board\n" unless $$board_defined; + +print "# Board definitions\n"; +def_var('build.f_cpu', 'F_CPU'); +def_var('build.flash_mode', 'FLASH_MODE'); +def_var('build.flash_freq', 'FLASH_SPEED'); +def_var('upload.resetmethod', 'UPLOAD_RESET'); +def_var('upload.speed', 'UPLOAD_SPEED'); +def_var('compiler.warning_flags', 'COMP_WARNINGS'); +$$v{'upload.verbose'} = '$$(UPLOAD_VERB)'; +$$v{'serial.port'} = '$$(UPLOAD_PORT)'; +$$v{'recipe.objcopy.hex.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; +$$v{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$$1\}/g; +$$v{'compiler.cpreprocessor.flags'} .= " \$$(C_PRE_PROC_FLAGS)"; +$$v{'build.extra_flags'} .= " \$$(BUILD_EXTRA_FLAGS)"; + +foreach my $$key (sort keys %v) { + while ($$v{$$key} =~/\{/) { + $$v{$$key} =~ s/\{([\w\-\.]+)\}/$$v{$$1}/; + $$v{$$key} =~ s/""//; + } + $$v{$$key} =~ s/ -o $$//; + $$v{$$key} =~ s/(-D\w+=)"([^"]+)"/$$1\\"$$2\\"/g; +} + +print "INCLUDE_VARIANT = $$v{'build.variant'}\n"; +print "# Commands\n"; +print "C_COM=$$v{'recipe.c.o.pattern'}\n"; +print "CPP_COM=$$v{'recipe.cpp.o.pattern'}\n"; +print "S_COM=$$v{'recipe.S.o.pattern'}\n"; +print "AR_COM=$$v{'recipe.ar.pattern'}\n"; +print "LD_COM=$$v{'recipe.c.combine.pattern'}\n"; +print "GEN_PART_COM=$$v{'recipe.objcopy.eep.pattern'}\n"; +print "ELF2BIN_COM=$$v{'recipe.objcopy.hex.pattern'}\n"; +print "SIZE_COM=$$v{'recipe.size.pattern'}\n"; +my $$flash_size = sprintf("0x%X", hex($$v{'build.spiffs_end'})-hex($$v{'build.spiffs_start'})); +print "MKSPIFFS_COM=$$v{'tools.mkspiffs.path'}/$$v{'tools.mkspiffs.cmd'} -b $$v{'build.spiffs_blocksize'} -s $$flash_size -c \$$(FS_DIR) \$$(FS_IMAGE)\n"; +print "UPLOAD_COM=$$v{'tools.esptool.upload.pattern'}\n"; +my $$fs_upload_com = $$v{'tools.esptool.upload.pattern'}; +$$fs_upload_com =~ s/(.+ -ca) .+/$$1 $$v{'build.spiffs_start'} -cf \$$(FS_IMAGE)/; +print "FS_UPLOAD_COM=$$fs_upload_com\n"; +my $$val = $$v{'recipe.hooks.core.prebuild.1.pattern'}; +$$val =~ s/bash -c "(.+)"/$$1/; +$$val =~ s/(#define .+0x)(\`)/"\\$$1\"$$2/; +$$val =~ s/(\\)//; +print "PREBUILD_COM=$$val\n"; +print "MEM_FLASH=$$v{'recipe.size.regex'}\n"; +print "MEM_RAM=$$v{'recipe.size.regex.data'}\n"; +print "FLASH_INFO=$$v{'menu.FlashSize.' . $$flashSize}\n" +endef +export PARSE_ARDUINO + +# Convert memory information +define MEM_USAGE +$$fp = shift; +$$rp = shift; +while (<>) { + $$r += $$1 if /$$rp/; + $$f += $$1 if /$$fp/; +} +print "\nMemory usage\n"; +print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "Ram:", $$r, "Flash:", $$f); +endef +export MEM_USAGE diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/README.md b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/README.md new file mode 100644 index 0000000..5030376 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/README.md @@ -0,0 +1,27 @@ +# ESP8266MeshSensor + +This example is designed to use the ESP8266MQTTMesh library with a Sonoff Relay +(it has only been tested with the SonoffPOW, but should work with other variants +with minor changes) + +## Required Libraries +The following libraries are required to build ths example +* ESP8266MQTTMesh +* HLW8012 (if using a Sonoff POW) +* OneWire (if using DS18B20) +* DallasTemperature (is using DS18B20) + +## Compiling +The collowing configurations are needed before compiling: +* copy credentials.h.example to credentials.h and update as needed +* ensure the relevant pins are defined if you have a DS18B20 or HLW8012 + +## MQTT Commands + +| Topic | Message | Description | +|---------------------|-------------|-------------| +| \/heartbeat | \ | How often to send current status in millseconds (default 60000) | +| \/expectedpower | \ | Calculate HLW8012 calibration based on current power | +| \/expectedvoltage | \ | Calculate HLW8012 calibration based on current voltage | +| \/expectedcurrent | \ | Calculate HLW8012 calibration based on current current | +| \/resetpower | \ | Reset all of the HLW8012 calibration values | diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/config.mk b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/config.mk new file mode 100644 index 0000000..96ab280 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/config.mk @@ -0,0 +1,4 @@ +FLASH_DEF = 2M +USER_LIBS = ${HOME}/Arduino/libraries/ +CPP_EXTRA = -Wall +BUILD_EXTRA_FLAGS = "-DMQTT_MAX_PACKET_SIZE=1152" diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/credentials.h.example b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/credentials.h.example new file mode 100644 index 0000000..bb0ea01 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/credentials.h.example @@ -0,0 +1,12 @@ +#define NETWORK_LIST { \ + "ssid 1", \ + "ssid 2", \ + "", \ + } +#define NETWORK_PASSWORD "network password" +#define MESH_PASSWORD "esp8266_sensor_mesh" +#define BASE_SSID "mesh_esp8266-" +#define MQTT_SERVER "MQTT Server IP Address" +#define MQTT_PORT 1883 +#define MESH_PORT 1884 + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/platformio.ini b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/platformio.ini new file mode 100644 index 0000000..a876351 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/platformio.ini @@ -0,0 +1,27 @@ +# +# Example PlatformIO configuration file for SSL and non-SSL builds. +# +# Before you will be able to build the SSL version of this project, you will +# need to explicitly install the espressif8266_stage platform. +# +# To perform this installation, refer to step 1 of: +# http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version + +[platformio] +env_default = nossl + +[common] +framework = arduino +lib_deps = ESP8266MQTTMesh +lib_deps_external = + HLW8012 + OneWire + DallasTemperature + +[env:nossl] +platform = espressif8266@~1.6.0 +framework = arduino +board = esp01_1m +lib_deps = + ${common.lib_deps} + ${common.lib_deps_external} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/ESP8266MeshSensor.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/ESP8266MeshSensor.ino new file mode 100644 index 0000000..e0804b4 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/ESP8266MeshSensor.ino @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2016 PhracturedBlue + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * HLW8012 code Copyright (C) 2016-2017 by Xose Pérez + */ + +/* Sonoff POW w/ DS18B20 attached to GPIO2(SDA) */ +#define GREEN_LED 15 //MTDO +#define RELAY 12 //MTDI +#define BUTTON 0 //GPIO0 +#define DS18B20 2 //GPIO2 +#define HLW8012_SEL 5 //GPIO5 +#define HLW8012_CF 14 //MTMS +#define HLW8012_CF1 13 //MTCK + +/* See credentials.h.examle for contents of credentials.h */ +#include "credentials.h" +#include "capabilities.h" +#include +#include +#if HAS_DS18B20 + #include + #include +#endif +#if HAS_HLW8012 + #include +#endif + +#include + +#if HAS_DS18B20 && HAS_HLW8012 + #define FIRMWARE_ID 0x4455 +#elif HAS_DS18B20 + #define FIRMWARE_ID 0x4454 +#elif HAS_HLW8012 + #define FIRMWARE_ID 0x4453 +#else + #define FIRMWARE_ID 0x4452 +#endif + +#define FIRMWARE_VER "0.8.1" +//const char* networks[] = NETWORK_LIST; +const wifi_conn networks[] = { + WIFI_CONN("foo", NETWORK_PASSWORD, NULL, false), + WIFI_CONN("bar", NETWORK_PASSWORD, "XX:XX:XX:XX:XX:XX", true), + NULL +}; +const char* mesh_password = MESH_PASSWORD; +const char* mqtt_server = MQTT_SERVER; + +#if HAS_DS18B20 +OneWire oneWire(DS18B20); +DallasTemperature ds18b20(&oneWire); +DeviceAddress ds18b20Address; +#endif + +#if HAS_HLW8012 +#define HLW8012_CURRENT_R 0.001 +#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Real: 2280k +#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Real 1.009k +#define HLW8012_UPDATE_INTERVAL 2000 +#define HLW8012_MIN_CURRENT 0.05 +#define HLW8012_MIN_POWER 10 +HLW8012 hlw8012; +unsigned int power_sum; +unsigned int voltage_sum; +double current_sum; +double energy; //in Watt-Seconds +unsigned long power_sample_count = 0; +bool hlw8012Enabled = false; + +void hlw8012_cf1_interrupt(); +void hlw8012_cf_interrupt(); +void hlw8012_enable_interrupts(bool enabled); +unsigned int hlw8012_getActivePower(); +double hlw8012_getCurrent(); +unsigned int hlw8012_getVoltage(); +#endif + +ESP8266MQTTMesh mesh = ESP8266MQTTMesh::Builder(networks, mqtt_server) + .setVersion(FIRMWARE_VER, FIRMWARE_ID) + .setMeshPassword(mesh_password) + .build(); + +bool relayState = false; +bool stateChanged = false; +int heartbeat = 60000; +float temperature = 0.0; + +void read_config(); +void save_config(); +void callback(const char *topic, const char *msg); +String build_json(); + +void setup() { + pinMode(GREEN_LED, OUTPUT); + pinMode(RELAY, OUTPUT); + pinMode(BUTTON, INPUT); + Serial.begin(115200); + delay(5000); + mesh.setCallback(callback); + mesh.begin(); +#if HAS_DS18B20 + ds18b20.begin(); + ds18b20.getAddress(ds18b20Address, 0); + ds18b20.setWaitForConversion(false); + ds18b20.requestTemperatures(); +#endif +Serial.println("HLW8012 start"); +#if HAS_HLW8012 + hlw8012.begin(HLW8012_CF, HLW8012_CF1, HLW8012_SEL, HIGH, true); + hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN); +#endif +Serial.println("HLW8012 end"); + //mesh.setup will initialize the filesystem + if (SPIFFS.exists("/config")) { + read_config(); + } +Serial.println("config end"); + digitalWrite(RELAY, relayState); +} + +void loop() { + static unsigned long pressed = 0; + static unsigned long lastSend = 0; + static bool needToSend = false; + + unsigned long now = millis(); + +#if HAS_DS18B20 + if (ds18b20.isConversionAvailable(ds18b20Address)) { + temperature = ds18b20.getTempF(ds18b20Address); + ds18b20.requestTemperatures(); + } +#endif + +#if HAS_HLW8012 + if (! hlw8012Enabled && mesh.connected()) { + hlw8012_enable_interrupts(true); + } else if (hlw8012Enabled && ! mesh.connected()) { + hlw8012_enable_interrupts(false); + } + static unsigned long last_hlw8012_update = 0; + if (now - last_hlw8012_update > HLW8012_UPDATE_INTERVAL) { + static unsigned int power[3] = {0}; + static unsigned int voltage[3] = {0}; + static double current[3] = {0}; + for (int i = 0; i < 2; i++) { + power[i] = power[i+1]; + voltage[i] = voltage[i+1]; + current[i] = current[i+1]; + } + power[2] = hlw8012_getActivePower(); + voltage[2] = hlw8012_getVoltage(); + current[2] = hlw8012_getCurrent(); + + //Spike removal + if (power[1] > 0 && power[0] == 0 && power[2] == 0) { + power[1] = 0; + } + if (current[1] > 0 && current[0] == 0 && current[2] == 0) { + current[1] = 0; + } + if (voltage[1] > 0 && voltage[0] == 0 && voltage[2] == 0) { + voltage[1] = 0; + } + power_sum += power[0]; + current_sum += current[0]; + voltage_sum += voltage[0]; + energy += 1.0 * power[0] * (now - last_hlw8012_update) / 1000.0; + power_sample_count++; + last_hlw8012_update = now; + } +#endif + if (! digitalRead(BUTTON)) { + if(pressed == 0) { + relayState = ! relayState; + digitalWrite(RELAY, relayState); + stateChanged = true; + } + pressed = now; + } else if (pressed && now - pressed > 100) { + pressed = 0; + } + if (stateChanged) { + save_config(); + needToSend = true; + stateChanged = false; + } else if (now - lastSend > heartbeat) { + needToSend = true; + } + if (! mesh.connected()) { + return; + } + if (needToSend) { + lastSend = now; + String data = build_json(); +#if HAS_HLW8012 + power_sum = 0; + current_sum = 0; + voltage_sum = 0; + power_sample_count = 0; +#endif + mesh.publish("status", data.c_str()); + needToSend = false; + } +} + +void callback(const char *topic, const char *msg) { + if (0 == strcmp(topic, "heartbeat")) { + unsigned int hb = strtoul(msg, NULL, 10); + if (hb > 10000) { + heartbeat = hb; + save_config(); + } + } + else if (0 == strcmp(topic, "state")) { + bool nextState = strtoul(msg, NULL, 10) ? true : false; + if (relayState != nextState) { + relayState = nextState; + digitalWrite(RELAY, relayState); + stateChanged = true; + } + } +#if HAS_HLW8012 + else if (0 == strcmp(topic, "expectedpower")) { + int pow = atoi(msg); + if (pow > 0) { + hlw8012.expectedActivePower(pow); + save_config(); + } + } + else if (0 == strcmp(topic, "expectedvoltage")) { + int volt = atoi(msg); + if (volt > 0) { + hlw8012.expectedVoltage(volt); + save_config(); + } + } + else if (0 == strcmp(topic, "expectedcurrent")) { + double current = atof(msg); + if (current > 0) { + hlw8012.expectedCurrent(current); + save_config(); + } + } + else if (0 == strcmp(topic, "resetpower")) { + int state = atoi(msg); + if (state > 0) { + hlw8012.resetMultipliers(); + save_config(); + } + } +#endif //HAS_HLW8012 +} + +String build_json() { + String msg = "{"; + msg += " \"relay\":\"" + String(relayState ? "ON" : "OFF") + "\""; +#if HAS_DS18B20 + msg += ", \"temp\":" + String(temperature, 2); +#endif +#if HAS_HLW8012 + double count = power_sample_count ? power_sample_count : 1; + double power = (double)power_sum / count; + double current = (double)current_sum / count; + double voltage = (double)voltage_sum / count; + double apparent= voltage * current; + double pfactor = (apparent > 0) ? 100 * power / apparent : 100; + if (pfactor > 100) { + pfactor = 100; + } + msg += ", \"power\":" + String(power, 3); + msg += ", \"current\":" + String(current, 3); + msg += ", \"voltage\":" + String(voltage, 3); + msg += ", \"pf\":" + String(pfactor, 3); + msg += ", \"energy\":" + String(energy / 3600, 3); //Watt-Hours +#endif + msg += "}"; + return msg; +} + +void read_config() { + File f = SPIFFS.open("/config", "r"); + if (! f) { + Serial.println("Failed to read config"); + return; + } + while(f.available()) { + char s[32]; + char key[32]; + const char *value; + s[f.readBytesUntil('\n', s, sizeof(s)-1)] = 0; + if (! ESP8266MQTTMesh::keyValue(s, '=', key, sizeof(key), &value)) { + continue; + } + if (0 == strcmp(key, "RELAY")) { + relayState = value[0] == '0' ? 0 : 1; + } + else if (0 == strcmp(key, "HEARTBEAT")) { + heartbeat = atoi(value); + if (heartbeat < 1000) { + heartbeat = 1000; + } else if (heartbeat > 60 * 60 * 1000) { + heartbeat = 5 * 60 * 1000; + } + } +#if HAS_HLW8012 + else if (0 == strcmp(key, "hlw8012PowerMult")) { + double dbl = atof(value); + if (dbl > 0) hlw8012.setPowerMultiplier(dbl); + + } + else if (0 == strcmp(key, "hlw8012CurrentMult")) { + double dbl = atof(value); + if (dbl > 0) hlw8012.setCurrentMultiplier(dbl); + } + else if (0 == strcmp(key, "hlw8012VoltageMult")) { + double dbl = atof(value); + if (dbl > 0) hlw8012.setVoltageMultiplier(dbl); + } +#endif //HAS_HLW8012 + } + f.close(); +} + +void save_config() { + File f = SPIFFS.open("/config", "w"); + if (! f) { + Serial.println("Failed to write config"); + return; + } + f.print("RELAY=" + String(relayState ? "1" : "0") + "\n"); + f.print("HEARTBEAT=" + String(heartbeat) + "\n"); +#if HAS_HLW8012 + f.print("hlw8012PowerMult=" + String(hlw8012.getPowerMultiplier())); + f.print("hlw8012CurrentMult=" + String(hlw8012.getCurrentMultiplier())); + f.print("hlw8012VoltageMult=" + String(hlw8012.getVoltageMultiplier())); +#endif + f.close(); +} + + +// When using interrupts we have to call the library entry point +// whenever an interrupt is triggered +#if HAS_HLW8012 +void hlw8012_cf1_interrupt() { + hlw8012.cf1_interrupt(); +} + +void hlw8012_cf_interrupt() { + hlw8012.cf_interrupt(); +} +void hlw8012_enable_interrupts(bool enabled) { + if (enabled) { + attachInterrupt(HLW8012_CF1, hlw8012_cf1_interrupt, CHANGE); + attachInterrupt(HLW8012_CF, hlw8012_cf_interrupt, CHANGE); + hlw8012Enabled = true; + } else { + detachInterrupt(HLW8012_CF1); + detachInterrupt(HLW8012_CF); + hlw8012Enabled = false; + } +} +unsigned int hlw8012_getActivePower() { + unsigned int power = hlw8012.getActivePower(); + if (power < HLW8012_MIN_POWER) power = 0; + return power; +} + +double hlw8012_getCurrent() { + double current = hlw8012.getCurrent(); + if (current < HLW8012_MIN_CURRENT) current = 0; + return current; +} + +unsigned int hlw8012_getVoltage() { + return hlw8012.getVoltage(); +} +#endif diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/capabilities.h b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/capabilities.h new file mode 100644 index 0000000..9376146 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshSensor/src/capabilities.h @@ -0,0 +1,15 @@ +#ifndef _CAPABILITIES_H_ +#define _CAPABILITIES_H_ + +#ifdef DS18B20 + #define HAS_DS18B20 1 +#else + #define HAS_DS18B20 0 +#endif + +#if defined(HLW8012_SEL) && defined(HLW8012_CF) && defined (HLW8012_CF1) + #define HAS_HLW8012 1 +#else + #define HAS_HLW8012 0 +#endif +#endif //_CAPABILITIES_H_ diff --git a/libraries/ESP8266_MQTT_Mesh/library.json b/libraries/ESP8266_MQTT_Mesh/library.json new file mode 100644 index 0000000..2f77119 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/library.json @@ -0,0 +1,43 @@ +{ + "name": "ESP8266MQTTMesh", + "keywords": "iot, home, automation, async, mqtt, client, esp8266, mesh, ota", + "description": "An Arduino for ESP8266 self-assembling Mesh network built around the MQTT protocol supporting OTA", + "version": "1.0.4", + "frameworks": "arduino", + "platforms": ["espressif8266", "espressif32"], + "authors": + { + "name": "PhracturedBlue", + "url": "https://github.com/PhracturedBlue" + }, + "repository": + { + "type": "git", + "url": "https://github.com/PhracturedBlue/ESP8266MQTTMesh.git" + }, + "dependencies": [ + { + "name": "ESPAsyncTCP", + "version": "^1.1.3", + "platforms": "espressif8266" + }, + { + "name": "AsyncMqttClient", + "version": "^0.8.1", + "platforms": "espressif8266" + }, + { + "name": "AsyncTCP", + "platforms": "espressif32" + }, + { + "name": "Ticker-esp32", + "platforms": "espressif32" + }, + { + "name": "AsyncMqttClient", + "version": "https://github.com/marvinroger/async-mqtt-client#101277d", + "platforms": "espressif32" + } + ] +} diff --git a/libraries/ESP8266_MQTT_Mesh/library.properties b/libraries/ESP8266_MQTT_Mesh/library.properties new file mode 100644 index 0000000..7587021 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/library.properties @@ -0,0 +1,10 @@ +name=ESP8266 MQTT Mesh +version=1.0.4 +author=PhracturedBlue +maintainer=PhracturedBlue +sentence=Self-assembling Mesh network built around the MQTT protocol supporting OTA +paragraph=Self-assembling mesh network built around the MQTT protocol for the ESP8266 and ESP32 with OTA support +category=Communication +url=https://github.com/PhracturedBlue/ESP8266MQTTMesh +architectures=esp8266,esp32 +includes=ESP8266MQTTMesh.h diff --git a/libraries/ESP8266_MQTT_Mesh/src/Base64.cpp b/libraries/ESP8266_MQTT_Mesh/src/Base64.cpp new file mode 100644 index 0000000..c59dccd --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/Base64.cpp @@ -0,0 +1,140 @@ +/*Copyright (C) 2013 Adam Rudd + +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 "Base64.h" +#include +const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* 'Private' declarations */ +inline void a3_to_a4(unsigned char * a4, unsigned char * a3); +inline void a4_to_a3(unsigned char * a3, unsigned char * a4); +inline unsigned char b64_lookup(char c); + +int base64_encode(char *output, const char *input, int inputLen) { + int i = 0, j = 0; + int encLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + while (inputLen--) { + a3[i++] = *(input++); + if (i == 3) { + a3_to_a4(a4, a3); + + for (i = 0; i < 4; i++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]); + } + + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) { + a3[j] = '\0'; + } + + a3_to_a4(a4, a3); + + for (j = 0; j < i + 1; j++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]); + } + + while ((i++ < 3)) { + output[encLen++] = '='; + } + } + output[encLen] = '\0'; + return encLen; +} + +int base64_decode(char * output, const char * input, int inputLen) { + int i = 0, j = 0; + int decLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + + while (inputLen--) { + if (*input == '=') { + break; + } + + a4[i++] = *(input++); + if (i == 4) { + for (i = 0; i < 4; i++) { + a4[i] = b64_lookup(a4[i]); + } + + a4_to_a3(a3, a4); + + for (i = 0; i < 3; i++) { + output[decLen++] = a3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + a4[j] = '\0'; + } + + for (j = 0; j < 4; j++) { + a4[j] = b64_lookup(a4[j]); + } + + a4_to_a3(a3, a4); + + for (j = 0; j < i - 1; j++) { + output[decLen++] = a3[j]; + } + } + output[decLen] = '\0'; + return decLen; +} + +int base64_enc_len(int plainLen) { + int n = plainLen; + return (n + 2 - ((n + 2) % 3)) / 3 * 4; +} + +int base64_dec_len(const char * input, int inputLen) { + int i = 0; + int numEq = 0; + for (i = inputLen - 1; input[i] == '='; i--) { + numEq++; + } + + return ((6 * inputLen) / 8) - numEq; +} + +inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = (a3[2] & 0x3f); +} + +inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; +} + +inline unsigned char b64_lookup(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 71; + if (c >= '0' && c <= '9') return c + 4; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} diff --git a/libraries/ESP8266_MQTT_Mesh/src/Base64.h b/libraries/ESP8266_MQTT_Mesh/src/Base64.h new file mode 100644 index 0000000..cb05f67 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/Base64.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 Adam Rudd. + * See LICENSE for more information + + + + + Copyright (C) 2013 Adam Rudd + +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 _BASE64_H +#define _BASE64_H + +/* b64_alphabet: + * Description: Base64 alphabet table, a mapping between integers + * and base64 digits + * Notes: This is an extern here but is defined in Base64.c + */ +extern const char b64_alphabet[]; + +/* base64_encode: + * Description: + * Encode a string of characters as base64 + * Parameters: + * output: the output buffer for the encoding, stores the encoded string + * input: the input buffer for the encoding, stores the binary to be encoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the encoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_encode(char *output, const char *input, int inputLen); + +/* base64_decode: + * Description: + * Decode a base64 encoded string into bytes + * Parameters: + * output: the output buffer for the decoding, + * stores the decoded binary + * input: the input buffer for the decoding, + * stores the base64 string to be decoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the decoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_decode(char *output, const char *input, int inputLen); + +/* base64_enc_len: + * Description: + * Returns the length of a base64 encoded string whose decoded + * form is inputLen bytes long + * Parameters: + * inputLen: the length of the decoded string + * Return value: + * The length of a base64 encoded string whose decoded form + * is inputLen bytes long + * Requirements: + * None + */ +int base64_enc_len(int inputLen); + +/* base64_dec_len: + * Description: + * Returns the length of the decoded form of a + * base64 encoded string + * Parameters: + * input: the base64 encoded string to be measured + * inputLen: the length of the base64 encoded string + * Return value: + * Returns the length of the decoded form of a + * base64 encoded string + * Requirements: + * 1. input must not be null + * 2. input must be greater than or equal to zero + */ +int base64_dec_len(const char *input, int inputLen); + +#endif // _BASE64_H diff --git a/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.cpp b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.cpp new file mode 100644 index 0000000..be659f4 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.cpp @@ -0,0 +1,1131 @@ +/* + * Copyright (C) 2016 PhracturedBlue + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ESP8266MQTTMesh.h" + +#define MESH_API_VER "001" + +#include "Base64.h" + +#include + +#if HAS_OTA +extern "C" { + #include "eboot_command.h" + #include "user_interface.h" + extern uint32_t _SPIFFS_start; +} +#endif + +#if !defined(ESP32) && ! defined(pgm_read_with_offset) //Requires Arduino core 2.4.0 + #error "This version of the ESP8266 library is not supported" +#endif + +enum { + NETWORK_MESH_NODE = -1, +}; + +//Define GATEWAY_ID to the value of ESP.getChipId() in order to prevent only a specific node from connecting via MQTT +#ifdef GATEWAY_ID + #define IS_GATEWAY (_chipID == GATEWAY_ID) +#else + #define IS_GATEWAY (1) +#endif + +#define NEXT_STATION(station_list) STAILQ_NEXT(station_list, next) + +//#define EMMDBG_LEVEL (EMMDBG_WIFI | EMMDBG_MQTT | EMMDBG_OTA) +#ifndef EMMDBG_LEVEL + #define EMMDBG_LEVEL EMMDBG_ALL_EXTRA +#endif + +#define dbgPrintln(lvl, msg) if (((lvl) & (EMMDBG_LEVEL)) == (lvl)) Serial.println("[" + String(__FUNCTION__) + "] " + msg) +size_t mesh_strlcat (char *dst, const char *src, size_t len) { + size_t slen = strlen(dst); + return strlcpy(dst + slen, src, len - slen); +} +#define strlcat mesh_strlcat + + +ESP8266MQTTMesh::ESP8266MQTTMesh(const wifi_conn *networks, + const char *mqtt_server, int mqtt_port, + const char *mqtt_username, const char *mqtt_password, + const char *firmware_ver, int firmware_id, + const char *mesh_ssid, const char *_mesh_password, int mesh_port, +#if ASYNC_TCP_SSL_ENABLED + bool mqtt_secure, const uint8_t *mqtt_fingerprint, ssl_cert_t mesh_secure, +#endif + const char *inTopic, const char *outTopic + ) : + networks(networks), + mqtt_server(mqtt_server), + mqtt_port(mqtt_port), + mqtt_username(mqtt_username), + mqtt_password(mqtt_password), + firmware_id(firmware_id), + firmware_ver(firmware_ver), + mesh_ssid(mesh_ssid), + mesh_port(mesh_port), +#if ASYNC_TCP_SSL_ENABLED + mqtt_secure(mqtt_secure), + mqtt_fingerprint(mqtt_fingerprint), + mesh_secure(mesh_secure), +#endif + inTopic(inTopic), + outTopic(outTopic), + espServer(mesh_port) +{ + + strlcpy(mesh_password, _mesh_password, 64-strlen(MESH_API_VER)); + strlcat(mesh_password, MESH_API_VER, 64); + mesh_bssid_key = 0x118d5b; //Seed + for (int i = 0; mesh_password[i] != 0; i++) { + mesh_bssid_key = lfsr(mesh_bssid_key, mesh_password[i]); + } + espClient[0] = new AsyncClient(); + itoa(_chipID, myID, 16); + strlcat(myID, "/", sizeof(myID)); +#if HAS_OTA + uint32_t usedSize = ESP.getSketchSize(); + // round one sector up + freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + //freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; + freeSpaceEnd = ESP.getFreeSketchSpace() + freeSpaceStart; +#endif +} + +void ESP8266MQTTMesh::setCallback(std::function _callback) { + callback = _callback; +} + +void ESP8266MQTTMesh::begin() { + int len = strlen(inTopic); + if (len > 16) { + dbgPrintln(EMMDBG_MSG, "Max inTopicLen == 16"); + die(); + } + if (inTopic[len-1] != '/') { + dbgPrintln(EMMDBG_MSG, "inTopic must end with '/'"); + die(); + } + len = strlen(outTopic); + if (len > 16) { + dbgPrintln(EMMDBG_MSG, "Max outTopicLen == 16"); + die(); + } + if (outTopic[len-1] != '/') { + dbgPrintln(EMMDBG_MSG, "outTopic must end with '/'"); + die(); + } + //dbgPrintln(EMMDBG_MSG, "Server: " + mqtt_server); + //dbgPrintln(EMMDBG_MSG, "Port: " + String(mqtt_port)); + //dbgPrintln(EMMDBG_MSG, "User: " + mqtt_username ? mqtt_username : "None"); + //dbgPrintln(EMMDBG_MSG, "PW: " + mqtt_password? mqtt_password : "None"); + //dbgPrintln(EMMDBG_MSG, "Secure: " + mqtt_secure ? "True" : "False"); + //dbgPrintln(EMMDBG_MSG, "Mesh: " + mesh_secure ? "True" : "False"); + //dbgPrintln(EMMDBG_MSG, "Port: " + String(mesh_port)); + + dbgPrintln(EMMDBG_MSG_EXTRA, "Starting Firmware " + String(firmware_id, HEX) + " : " + String(firmware_ver)); +#if HAS_OTA + dbgPrintln(EMMDBG_MSG_EXTRA, "OTA Start: 0x" + String(freeSpaceStart, HEX) + " OTA End: 0x" + String(freeSpaceEnd, HEX)); +#endif + uint8_t mac[6]; + generate_mac(mac, _chipID); + //char macstr[18]; + //sprintf(macstr,"%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + //dbgPrintln(EMMDBG_MSG, "Changing MAC address to: " + String(macstr)); + WiFi.disconnect(); + + // This is needed to ensure both wifi_set_macaddr() calls work + WiFi.mode(WIFI_AP_STA); + bool ok_ap = wifi_set_macaddr(SOFTAP_IF, const_cast(mac)); + mac[0] |= 0x04; + bool ok_sta = wifi_set_macaddr(STATION_IF, const_cast(mac)); + if (! ok_ap || ! ok_sta) { + dbgPrintln(EMMDBG_MSG, "Failed to set MAC address"); + die(); + } + dbgPrintln(EMMDBG_MSG, "MAC: " + WiFi.macAddress()); + dbgPrintln(EMMDBG_MSG, "SoftAPMAC: " + WiFi.softAPmacAddress()); + + // In the ESP8266 2.3.0 API, there seems to be a bug which prevents a node configured as + // WIFI_AP_STA from openning a TCP connection to it's gateway if the gateway is also + // in WIFI_AP_STA + WiFi.mode(WIFI_STA); + + this->connectWiFiEvents(); + + espClient[0]->setNoDelay(true); + espClient[0]->onConnect( [this](void * arg, AsyncClient *c) { this->onConnect(c); }, this); + espClient[0]->onDisconnect([this](void * arg, AsyncClient *c) { this->onDisconnect(c); }, this); + espClient[0]->onError( [this](void * arg, AsyncClient *c, int8_t error) { this->onError(c, error); }, this); + espClient[0]->onAck( [this](void * arg, AsyncClient *c, size_t len, uint32_t time){ this->onAck(c, len, time); }, this); + espClient[0]->onTimeout( [this](void * arg, AsyncClient *c, uint32_t time) { this->onTimeout(c, time); }, this); + espClient[0]->onData( [this](void * arg, AsyncClient *c, void* data, size_t len) { this->onData(c, data, len); }, this); + + espServer.onClient( [this](void * arg, AsyncClient *c){ this->onClient(c); }, this); + espServer.setNoDelay(true); +#if ASYNC_TCP_SSL_ENABLED + espServer.onSslFileRequest([this](void * arg, const char *filename, uint8_t **buf) -> int { return this->onSslFileRequest(filename, buf); }, this); + if (mesh_secure.cert) { + dbgPrintln(EMMDBG_WIFI, "Starting secure server"); + espServer.beginSecure("cert","key",NULL); + } else +#endif + espServer.begin(); + + mqttClient.onConnect( [this] (bool sessionPresent) { this->onMqttConnect(sessionPresent); }); + mqttClient.onDisconnect( [this] (AsyncMqttClientDisconnectReason reason) { this->onMqttDisconnect(reason); }); + mqttClient.onSubscribe( [this] (uint16_t packetId, uint8_t qos) { this->onMqttSubscribe(packetId, qos); }); + mqttClient.onUnsubscribe([this] (uint16_t packetId) { this->onMqttUnsubscribe(packetId); }); + mqttClient.onMessage( [this] (char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) + { this->onMqttMessage(topic, payload, properties, len, index, total); }); + mqttClient.onPublish( [this] (uint16_t packetId) { this->onMqttPublish(packetId); }); + mqttClient.setServer(mqtt_server, mqtt_port); + if (mqtt_username || mqtt_password) + mqttClient.setCredentials(mqtt_username, mqtt_password); + +#if ASYNC_TCP_SSL_ENABLED + mqttClient.setSecure(mqtt_secure); + if (mqtt_fingerprint) { + mqttClient.addServerFingerprint(mqtt_fingerprint); + } +#endif + //mqttClient.setCallback([this] (char* topic, byte* payload, unsigned int length) { this->mqtt_callback(topic, payload, length); }); + + + dbgPrintln(EMMDBG_WIFI_EXTRA, WiFi.status()); + dbgPrintln(EMMDBG_MSG_EXTRA, "Setup Complete"); + ap_ptr = NULL; + connect(); +} + +#ifdef USE_WIFI_ONEVENT +//The ESP32 does not support the std::function onEvent variants + +static ESP8266MQTTMesh *meshPtr; + +void staticWiFiEventHandler(system_event_id_t event, system_event_info_t info) +{ + meshPtr->WiFiEventHandler(event, info); +} + +void ESP8266MQTTMesh::WiFiEventHandler(system_event_id_t event, system_event_info_t info) +{ + switch(event) { + case SYSTEM_EVENT_STA_GOT_IP: + { + struct WiFiEventStationModeGotIP e; + e.ip = info.got_ip.ip_info.ip.addr; + e.mask = info.got_ip.ip_info.netmask.addr; + e.gw = info.got_ip.ip_info.gw.addr; + this->onWifiConnect(e); + break; + } + case SYSTEM_EVENT_STA_DISCONNECTED: + { + struct WiFiEventStationModeDisconnected e; + e.ssid.reserve(info.disconnected.ssid_len+1); + for(int i = 0; i < info.disconnected.ssid_len; i++) { + e.ssid += (char)info.disconnected.ssid[i]; + } + memcpy(e.bssid, info.disconnected.bssid, 6); + e.reason = info.disconnected.reason; + this->onWifiDisconnect(e); + break; + } + case SYSTEM_EVENT_AP_STACONNECTED: + { + this->onAPConnect(info.sta_connected); + break; + } + case SYSTEM_EVENT_AP_STADISCONNECTED: + this->onAPDisconnect(info.sta_disconnected); + break; + } +} + +void ESP8266MQTTMesh::connectWiFiEvents() +{ + meshPtr = this; + WiFi.onEvent(&staticWiFiEventHandler); +} +#else //USE_WIFI_ONEVENT +void ESP8266MQTTMesh::connectWiFiEvents() +{ + wifiConnectHandler = + WiFi.onStationModeGotIP( [this] (const WiFiEventStationModeGotIP& e) { this->onWifiConnect(e); }); + wifiDisconnectHandler = + WiFi.onStationModeDisconnected( [this] (const WiFiEventStationModeDisconnected& e) { this->onWifiDisconnect(e); }); + //wifiDHCPTimeoutHandler = + // WiFi.onStationModeDHCPTimeout( [this] () { this->onDHCPTimeout(); }); + wifiAPConnectHandler = + WiFi.onSoftAPModeStationConnected( [this] (const WiFiEventSoftAPModeStationConnected& ip) { this->onAPConnect(ip); }); + wifiAPDisconnectHandler = + WiFi.onSoftAPModeStationDisconnected([this] (const WiFiEventSoftAPModeStationDisconnected& ip) { this->onAPDisconnect(ip); }); +} +#endif //USE_WIFI_ONEVENT + +uint32_t ESP8266MQTTMesh::lfsr(uint32_t seed, uint8_t b) +{ + // Linear feedback shift register with 32-bit Xilinx polinomial x^32 + x^22 + x^2 + x + 1 + // http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf + static const uint32_t LFSR_FEEDBACK = 0x80200003ul; + static const uint32_t LFSR_INTAP = 32-1; + for (int i = 0; i < 8; ++i) { + seed = (seed >> 1) ^ ((-(seed & 1u) & LFSR_FEEDBACK) ^ ~((uint32_t)(b & 1) << LFSR_INTAP)); + b >>= 1; + } + return seed; +} + +uint32_t ESP8266MQTTMesh::encrypt_id(uint32_t id) { + // The lowest bits of the 1st octet need to be b'10 to indicate a + // locally-administered unicast MAC address + return (((lfsr(id, 0) * mesh_bssid_key) << 3) + 2) & 0x00FFFFFF; +} + +void ESP8266MQTTMesh::generate_mac(uint8_t *bssid, uint32_t id) { + uint32_t res = encrypt_id(id); + for(int i = 0; i < 3; i++) { + bssid[i] = (res >> (8 * i)) & 0xff; + } + for(int i = 0; i < 3; i++) { + bssid[5-i] = (id >> (8 * i)) & 0xff; + } +} + +bool ESP8266MQTTMesh::verify_bssid(uint8_t *bssid) { + //bit 3 is 1 for station and 0 for AP + uint32_t wanted = ((bssid[2] << 16) | (bssid[1] << 8) | bssid[0]) & 0xFFFFFFFB; + uint32_t id = (bssid[3] << 16) | (bssid[4] << 8) | bssid[5]; + uint32_t res = encrypt_id(id); + return res == wanted; +} + +bool ESP8266MQTTMesh::connected() { + return wifiConnected() && ((meshConnect && espClient[0] && espClient[0]->connected()) || mqttClient.connected()); +} + +void ESP8266MQTTMesh::scan() { + //Need to rescan + if (! scanning) { + ap_ptr = NULL; + WiFi.disconnect(); + WiFi.mode(WIFI_STA); + dbgPrintln(EMMDBG_WIFI, "Scanning for networks"); + WiFi.scanDelete(); + WiFi.scanNetworks(true,true); + scanning = true; + } + + //scanComplete returns <0 while scanning is in progress + int numberOfNetworksFound = WiFi.scanComplete(); + if (numberOfNetworksFound < 0) { + return; + } + dbgPrintln(EMMDBG_WIFI, "Found: " + String(numberOfNetworksFound)); + + scanning = false; + + //Initialize AP to be empty, and all unused APs on the unused list + if (ap_unused == NULL) { + ap_unused = ap; + } else { + for(ap_ptr = ap_unused; ap_ptr->next != NULL; ap_ptr = ap_ptr->next) + {} + ap_ptr->next = ap; + } + ap = NULL; + + int ssid_idx; + for(int i = 0; i < numberOfNetworksFound; i++) { + bool found = false; + int network_idx = NETWORK_MESH_NODE; + int rssi = WiFi.RSSI(i); + dbgPrintln(EMMDBG_WIFI, "Found SSID: '" + WiFi.SSID(i) + "' BSSID '" + WiFi.BSSIDstr(i) + "'" + " RSSI: " + String(rssi)); + if (IS_GATEWAY) { + network_idx = match_networks(WiFi.SSID(i).c_str(), WiFi.BSSIDstr(i).c_str()); + } + if(network_idx == NETWORK_MESH_NODE) { + if (WiFi.SSID(i).length()) { + dbgPrintln(EMMDBG_WIFI, "Did not match SSID list"); + continue; + } else { + if (! verify_bssid(WiFi.BSSID(i))) { + dbgPrintln(EMMDBG_WIFI, "Failed to match BSSID"); + continue; + } + } + } + ap_t *next_ap; + if (! ap_unused) { + next_ap = new ap_t; + } else { + next_ap =ap_unused; + ap_unused = ap_unused->next; + } + //Assign next_ap + next_ap->ssid_idx = network_idx; + next_ap->rssi = rssi; + memcpy(next_ap->bssid, WiFi.BSSID(i), 6); + + //sort by RSSI + ap_t *ap_last = NULL; + for(ap_ptr = ap; ap_ptr != NULL; ap_last = ap_ptr, ap_ptr = ap_ptr->next) { + if(network_idx >= 0) { + //AP is Wifi AP + if (ap_ptr->ssid_idx != NETWORK_MESH_NODE && rssi <= ap_ptr->rssi) { + continue; + } + } else { + //AP is mesh node + if (ap_ptr->ssid_idx != NETWORK_MESH_NODE || rssi <= ap_ptr->rssi) { + continue; + } + } + break; + } + //Insert next_ap before ap_ptr (i.e. at last_ap) + next_ap->next = ap_ptr; + if (ap_last) { + ap_last->next = next_ap; + } else { + //ap was empty, so create it + ap = next_ap; + } + } + ap_ptr = ap; +} + +int ESP8266MQTTMesh::match_networks(const char *ssid, const char *bssid) +{ + for(int idx = 0; networks[idx].ssid != NULL; idx++) { + if(networks[idx].bssid) { + if (strcmp(bssid, networks[idx].bssid) == 0) { + if (networks[idx].hidden && ssid[0] == 0) { + //hidden network + dbgPrintln(EMMDBG_WIFI, "Matched"); + return idx; + } + } else { + //Didn't match requested bssid + continue; + } + } + dbgPrintln(EMMDBG_WIFI_EXTRA, "Comparing " + String(networks[idx].ssid)); + if(ssid[0] != 0) { + if ((! networks[idx].hidden) && strcmp(ssid, networks[idx].ssid) == 0) { + //matched ssid (and bssid if needed) + dbgPrintln(EMMDBG_WIFI, "Matched"); + return idx; + } + } + } + return NETWORK_MESH_NODE; +} + +void ESP8266MQTTMesh::schedule_connect(float delay) { + dbgPrintln(EMMDBG_WIFI, "Scheduling reconnect for " + String(delay,2)+ " seconds from now"); + schedule.once(delay, connect, this); +} + +void ESP8266MQTTMesh::connect() { + if (WiFi.isConnected()) { + dbgPrintln(EMMDBG_WIFI, "Called connect when already connected!"); + return; + } + connecting = false; + retry_connect = 1; + lastReconnect = millis(); + if (scanning || ! ap_ptr) { + scan(); + } + if (scanning) { + schedule_connect(0.5); + return; + } + if (! ap_ptr) { + // No networks found, try again + schedule_connect(); + return; + } + int i = 0; + for (ap_t *p = ap; p != NULL; p = p->next, i++) { + dbgPrintln(EMMDBG_WIFI, String(i) + String(p == ap_ptr ? " * " : " ") + mac_str(p->bssid) + " " + String(p->rssi)); + } + char _mesh_ssid[32]; + const char *ssid; + const char *password; + if (ap_ptr->ssid_idx == NETWORK_MESH_NODE) { + //This is a mesh node + ssid = build_mesh_ssid(_mesh_ssid, ap_ptr->bssid); + password = mesh_password; + meshConnect = true; + } else { + ssid = networks[ap_ptr->ssid_idx].ssid; + password = networks[ap_ptr->ssid_idx].password; + meshConnect = false; + } + dbgPrintln(EMMDBG_WIFI, "Connecting to SSID : '" + String(ssid) + "' BSSID '" + mac_str(ap_ptr->bssid) + "'"); + WiFi.begin(ssid, password); + connecting = true; + lastStatus = lastReconnect; +} +String ESP8266MQTTMesh::mac_str(uint8_t *bssid) { + char mac[19]; + sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + return String(mac); +} +const char *ESP8266MQTTMesh::build_mesh_ssid(char buf[32], uint8_t *mac) { + char chipid[8]; + sprintf(chipid, "_%02x%02x%02x", mac[3], mac[4], mac[5]); + strlcpy(buf, mesh_ssid, sizeof(buf)-7); + strlcat(buf, chipid, sizeof(buf)); + return buf; +} + +void ESP8266MQTTMesh::parse_message(const char *topic, const char *msg) { + int inTopicLen = strlen(inTopic); + if (strstr(topic, inTopic) != topic) { + return; + } + const char *subtopic = topic + inTopicLen; + if (strstr(subtopic ,"ota/") == subtopic) { +#if HAS_OTA + const char *cmd = subtopic + 4; + handle_ota(cmd, msg); +#endif + return; + } + else if (strstr(subtopic ,"fw/") == subtopic) { + const char *cmd = subtopic + 3; + handle_fw(cmd); + } + if (! callback) { + return; + } + int myIDLen = strlen(myID); + if(strstr(subtopic, myID) == subtopic) { + //Only handle messages addressed to this node + callback(subtopic + myIDLen, msg); + } + else if(strstr(subtopic, "broadcast/") == subtopic) { + //Or messages sent to all nodes + callback(subtopic + 10, msg); + } +} + + +void ESP8266MQTTMesh::connect_mqtt() { + dbgPrintln(EMMDBG_MQTT, "Attempting MQTT connection (" + mqtt_server + ":" + String(mqtt_port) + ")..."); + // Attempt to connect + mqttClient.connect(); +} + + +void ESP8266MQTTMesh::publish(const char *subtopic, const char *msg, enum MSG_TYPE msgCmd) { + publish(outTopic, myID, subtopic, msg, msgCmd); +} + +void ESP8266MQTTMesh::publish_node(const char *subtopic, const char *msg, enum MSG_TYPE msgCmd) { + publish(inTopic, myID, subtopic, msg, msgCmd); +} + +void ESP8266MQTTMesh::publish(const char *topicDirection, const char *baseTopic, const char *subTopic, const char *msg, uint8_t msgType) { + char topic[64]; + strlcpy(topic, topicDirection, sizeof(topic)); + strlcat(topic, baseTopic, sizeof(topic)); + strlcat(topic, subTopic, sizeof(topic)); + dbgPrintln(EMMDBG_MQTT_EXTRA, "Sending: " + String(topic) + "=" + String(msg)); + if (! meshConnect) { + mqtt_publish(topic, msg, msgType); + } else { + send_message(0, topic, msg, msgType); + } +} + +void ESP8266MQTTMesh::shutdown_AP() { + if(! AP_ready) + return; + for (int i = 1; i <= ESP8266_NUM_CLIENTS; i++) { + if(espClient[i]) { + delete espClient[i]; + espClient[i] = NULL; + } + } + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_STA); + AP_ready = false; +} + +void ESP8266MQTTMesh::setup_AP() { + if (AP_ready) + return; + + uint octet3 = WiFi.gatewayIP()[2] + 1; + if (! octet3) { + octet3++; + } + IPAddress apIP(WiFi.gatewayIP()[0], + WiFi.gatewayIP()[1], + octet3, + 1); + IPAddress apGateway(apIP); + IPAddress apSubmask(255, 255, 255, 0); + WiFi.mode(WIFI_AP_STA); + WiFi.softAPConfig(apIP, apGateway, apSubmask); + char _mesh_ssid[32]; + uint8_t mac[6]; + build_mesh_ssid(_mesh_ssid, WiFi.softAPmacAddress(mac)); + WiFi.softAP(_mesh_ssid, mesh_password, WiFi.channel(), 1); + dbgPrintln(EMMDBG_WIFI, "Initialized AP as '" + String(_mesh_ssid) + "' IP '" + apIP.toString() + "'"); + connecting = false; //Connection complete + AP_ready = true; +} + +void ESP8266MQTTMesh::send_connected_msg() { + //The list of nodes is only stored on the broker. Individual nodes don't knowother node IDs + char topic[10]; + strlcpy(topic, myID, sizeof(topic)); + topic[strlen(topic)-1] = 0; // Chop off trailing '/' + publish(outTopic, "bssid/", topic, WiFi.softAPmacAddress().c_str(), MSG_TYPE_RETAIN_QOS_0); +} + +bool ESP8266MQTTMesh::send_message(int index, const char *topicOrMsg, const char *msg, uint8_t msgType) { + int topicLen = strlen(topicOrMsg); + int msgLen = 0; + int len = topicLen; + char msgTypeStr[2]; + if (msgType == 0) { + msgType = MSG_TYPE_INVALID; + } + msgTypeStr[0] = msgType; + msgTypeStr[1] = 0; + if (index == 0) { + //We only send the msgType upstream + espClient[index]->write(msgTypeStr,1); + } + espClient[index]->write(topicOrMsg); + if (msg) { + espClient[index]->write("=", 1); + espClient[index]->write(msg); + } + espClient[index]->write("\0", 1); + return true; +} + +void ESP8266MQTTMesh::broadcast_message(const char *topicOrMsg, const char *msg) { + for (int i = 1; i <= ESP8266_NUM_CLIENTS; i++) { + if (espClient[i]) { + send_message(i, topicOrMsg, msg); + } + } +} + +void ESP8266MQTTMesh::handle_client_data(int idx, char *rawdata) { + dbgPrintln(EMMDBG_MQTT, "Received: msg from " + espClient[idx]->remoteIP().toString() + " on " + (idx == 0 ? "STA" : "AP")); + const char *data = rawdata + (idx ? 1 : 0); + dbgPrintln(EMMDBG_MQTT_EXTRA, "--> '" + String(data) + "'"); + char topic[64]; + const char *msg; + if (! keyValue(data, '=', topic, sizeof(topic), &msg)) { + dbgPrintln(EMMDBG_MQTT, "Failed to handle message"); + return; + } + if (idx == 0) { + //This is a packet from MQTT, need to rebroadcast to each connected station + broadcast_message(data); + parse_message(topic, msg); + } else { + unsigned char msgType = rawdata[0]; + if (strstr(topic,"/mesh_cmd") == topic + strlen(topic) - 9) { + // We will handle this packet locally + } else { + if (! meshConnect) { + mqtt_publish(topic, msg, msgType); + } else { + send_message(0, data, NULL, msgType); + } + } + } +} + +uint16_t ESP8266MQTTMesh::mqtt_publish(const char *topic, const char *msg, uint8_t msgType) +{ + uint8_t qos = 0; + bool retain = false; + if (msgType == MSG_TYPE_RETAIN_QOS_0 + || msgType == MSG_TYPE_RETAIN_QOS_1 + || msgType == MSG_TYPE_RETAIN_QOS_2) + { + qos = msgType - MSG_TYPE_RETAIN_QOS_0; + retain = true; + } + else if(msgType == MSG_TYPE_QOS_0 + || msgType == MSG_TYPE_QOS_1 + || msgType == MSG_TYPE_QOS_2) + { + qos = msgType - MSG_TYPE_QOS_0; + } + mqttClient.publish(topic, qos, retain, msg); +} + +bool ESP8266MQTTMesh::keyValue(const char *data, char separator, char *key, int keylen, const char **value) { + int maxIndex = strlen(data)-1; + int i; + for(i=0; i<=maxIndex && i freeSpaceEnd - freeSpaceStart) { + return false; + } + MD5Builder _md5; + _md5.begin(); + uint32_t address = freeSpaceStart; + unsigned int len = ota_info.len; + while(len) { + int size = len > sizeof(buf) ? sizeof(buf) : len; + if (! ESP.flashRead(address, (uint32_t *)buf, (size + 3) & ~3)) { + return false; + } + _md5.add(buf, size); + address += size; + len -= size; + } + _md5.calculate(); + _md5.getBytes(buf); + for (int i = 0; i < 16; i++) { + if (buf[i] != ota_info.md5[i]) { + return false; + } + } + return true; +} + +char * ESP8266MQTTMesh::md5(const uint8_t *msg, int len) { + static char out[33]; + MD5Builder _md5; + _md5.begin(); + _md5.add(msg, len); + _md5.calculate(); + _md5.getChars(out); + return out; +} +void ESP8266MQTTMesh::erase_sector() { + int start = freeSpaceStart / FLASH_SECTOR_SIZE; + //erase flash area here + ESP.flashEraseSector(nextErase--); + if (nextErase >= start) { + schedule.once(0.0, erase_sector, this); + } else { + nextErase = 0; + char deltaStr[10]; + uint32_t delta = micros() - startTime; + itoa(delta, deltaStr, 10); + dbgPrintln(EMMDBG_OTA, "Erase complete in " + String(delta / 1000000.0, 6) + " seconds"); + publish("ota/erase", deltaStr); + } +} + +void ESP8266MQTTMesh::handle_ota(const char *cmd, const char *msg) { + dbgPrintln(EMMDBG_OTA_EXTRA, "OTA cmd " + String(cmd) + " Length: " + String(strlen(msg))); + //Should we allow enabling/disabling this? + bool pingback = true; + if(strstr(cmd, myID) == cmd) { + cmd += strlen(myID); + } else { + char *end; + unsigned int id = strtoul(cmd,&end, 16); + if (id != firmware_id || *end != '/') { + dbgPrintln(EMMDBG_OTA, "Ignoring OTA because firmwareID did not match " + String(firmware_id, HEX)); + return; + } + cmd += (end - cmd) + 1; //skip ID + } + if(0 == strcmp(cmd, "start")) { + dbgPrintln(EMMDBG_OTA_EXTRA, "OTA Start"); + parse_ota_info(msg); + if (ota_info.len == 0) { + dbgPrintln(EMMDBG_OTA, "Ignoring OTA because firmware length = 0"); + return; + } + dbgPrintln(EMMDBG_OTA, "-> " + String(msg)); + if (ota_info.len > freeSpaceEnd - freeSpaceStart) { + dbgPrintln(EMMDBG_MSG, "Not enough space for firmware: " + String(ota_info.len) + " > " + String(freeSpaceEnd - freeSpaceStart)); + return; + } + uint32_t end = (freeSpaceStart + ota_info.len + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); + nextErase = end / FLASH_SECTOR_SIZE - 1; + startTime = micros(); + dbgPrintln(EMMDBG_OTA, "Erasing " + String((end - freeSpaceStart)/ FLASH_SECTOR_SIZE) + " sectors"); + schedule.once(0.0, erase_sector, this); + } + else if(0 == strcmp(cmd, "check")) { + if (strlen(msg) > 0) { + char *out = md5((uint8_t *)msg, strlen(msg)); + publish("ota/check", out); + } else { + const char *md5ok = check_ota_md5() ? "MD5 Passed" : "MD5 Failed"; + dbgPrintln(EMMDBG_OTA, md5ok); + publish("ota/check", md5ok); + } + } + else if(0 == strcmp(cmd, "flash")) { + if (! check_ota_md5()) { + dbgPrintln(EMMDBG_MSG, "Flash failed due to md5 mismatch"); + publish("ota/flash", "Failed"); + return; + } + dbgPrintln(EMMDBG_OTA, "Flashing"); + + eboot_command ebcmd; + ebcmd.action = ACTION_COPY_RAW; + ebcmd.args[0] = freeSpaceStart; + ebcmd.args[1] = 0x00000; + ebcmd.args[2] = ota_info.len; + eboot_command_write(&ebcmd); + // If this is a broadcast OTA update, this would never go through + //publish("ota/flash", "Success"); + + shutdown_AP(); + mqttClient.disconnect(); + delay(100); + ESP.restart(); + die(); + } + else { + char *end; + unsigned int address = strtoul(cmd, &end, 10); + if (address > freeSpaceEnd - freeSpaceStart || end != cmd + strlen(cmd)) { + dbgPrintln(EMMDBG_MSG, "Illegal address " + String(address) + " specified"); + return; + } + int msglen = strlen(msg); + if (msglen > 1024) { + dbgPrintln(EMMDBG_MSG, "Message length " + String(msglen) + " too long"); + return; + } + byte data[768]; + long t = micros(); + int len = base64_decode((char *)data, msg, msglen); + if (address + len > freeSpaceEnd) { + dbgPrintln(EMMDBG_MSG, "Message length would run past end of free space"); + return; + } + dbgPrintln(EMMDBG_OTA_EXTRA, "Got " + String(len) + " bytes FW @ " + String(address, HEX)); + bool ok = ESP.flashWrite(freeSpaceStart + address, (uint32_t*) data, len); + dbgPrintln(EMMDBG_OTA, "Wrote " + String(len) + " bytes in " + String((micros() - t) / 1000000.0, 6) + " seconds"); + if (pingback) { + char topic[17]; + strlcpy(topic, "ota/md5/", sizeof(topic)); + itoa(address, topic + strlen(topic), 16); + publish(topic, md5(data, len)); + } + if (! ok) { + dbgPrintln(EMMDBG_MSG, "Failed to write firmware at " + String(freeSpaceStart + address, HEX) + " Length: " + String(len)); + } + } +} +#endif //HAS_OTA + +void ESP8266MQTTMesh::onWifiConnect(const WiFiEventStationModeGotIP& event) { + if (meshConnect) { + dbgPrintln(EMMDBG_WIFI, "Connecting to mesh: " + WiFi.gatewayIP().toString() + " on port: " + String(mesh_port)); +#if ASYNC_TCP_SSL_ENABLED + espClient[0]->connect(WiFi.gatewayIP(), mesh_port, mesh_secure.cert ? true : false); +#else + espClient[0]->connect(WiFi.gatewayIP(), mesh_port); +#endif + bufptr[0] = inbuffer[0]; + } else { + dbgPrintln(EMMDBG_WIFI, "Connecting to mqtt"); + connect_mqtt(); + } +} + +void ESP8266MQTTMesh::onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { + //Reasons are here: ESP8266WiFiType.h-> WiFiDisconnectReason + dbgPrintln(EMMDBG_WIFI, "Disconnected from Wi-Fi: " + event.ssid + " because: " + String(event.reason)); + WiFi.disconnect(); + if (! connecting) { + ap_ptr = NULL; + } else if (event.reason == WIFI_DISCONNECT_REASON_ASSOC_TOOMANY && retry_connect) { + // If we rebooted without a clean shutdown, we may still be associated with this AP, in which case + // we'll be booted and should try again + retry_connect--; + } else { + ap_ptr = ap_ptr->next; + } + if (! scanning) + schedule_connect(); +} + +//void ESP8266MQTTMesh::onDHCPTimeout() { +// dbgPrintln(EMMDBG_WIFI, "Failed to get DHCP info"); +//} + +void ESP8266MQTTMesh::onAPConnect(const WiFiEventSoftAPModeStationConnected& ip) { + dbgPrintln(EMMDBG_WIFI, "Got connection from Station"); +} + +void ESP8266MQTTMesh::onAPDisconnect(const WiFiEventSoftAPModeStationDisconnected& ip) { + dbgPrintln(EMMDBG_WIFI, "Got disconnection from Station"); +} + +void ESP8266MQTTMesh::onMqttConnect(bool sessionPresent) { + dbgPrintln(EMMDBG_MQTT, "MQTT Connected"); + // Once connected, publish an announcement... + char msg[64]; + get_fw_string(msg, sizeof(msg), "Connected"); + //strlcpy(publishMsg, outTopic, sizeof(publishMsg)); + //strlcat(publishMsg, WiFi.localIP().toString().c_str(), sizeof(publishMsg)); + mqttClient.publish("connect", 0, false, msg); + // ... and resubscribe + char subscribe[TOPIC_LEN]; + strlcpy(subscribe, inTopic, sizeof(subscribe)); + strlcat(subscribe, "#", sizeof(subscribe)); + mqttClient.subscribe(subscribe, 0); + + send_connected_msg(); + setup_AP(); +} + +void ESP8266MQTTMesh::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { + int r = (int8_t)reason; + dbgPrintln(EMMDBG_MQTT, "Disconnected from MQTT: " + String(r)); +#if ASYNC_TCP_SSL_ENABLED + if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) { + dbgPrintln(EMMDBG_MQTT, "Bad MQTT server fingerprint."); + if (WiFi.isConnected()) { + WiFi.disconnect(); + } + return; + } +#endif + shutdown_AP(); + if (WiFi.isConnected()) { + connect_mqtt(); + } +} + +void ESP8266MQTTMesh::onMqttSubscribe(uint16_t packetId, uint8_t qos) { + Serial.println("Subscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); + Serial.print(" qos: "); + Serial.println(qos); +} + +void ESP8266MQTTMesh::onMqttUnsubscribe(uint16_t packetId) { + Serial.println("Unsubscribe acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); +} + +void ESP8266MQTTMesh::onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + memcpy(inbuffer[0], payload, len); + inbuffer[0][len]= 0; + dbgPrintln(EMMDBG_MQTT_EXTRA, "Message arrived [" + String(topic) + "] '" + String(inbuffer[0]) + "'"); + broadcast_message(topic, inbuffer[0]); + parse_message(topic, inbuffer[0]); +} + +void ESP8266MQTTMesh::onMqttPublish(uint16_t packetId) { + //Serial.println("Publish acknowledged."); + //Serial.print(" packetId: "); + //Serial.println(packetId); +} + +#if ASYNC_TCP_SSL_ENABLED +int ESP8266MQTTMesh::onSslFileRequest(const char *filename, uint8_t **buf) { + if(strcmp(filename, "cert") == 0) { + *buf = (uint8_t *)mesh_secure.cert; + return mesh_secure.cert_len; + } else if(strcmp(filename, "key") == 0) { + *buf = (uint8_t *)mesh_secure.key; + return mesh_secure.key_len; + } else if(strcmp(filename, "fingerprint") == 0) { + *buf = (uint8_t *)mesh_secure.fingerprint; + return 20; + } else { + *buf = 0; + dbgPrintln(EMMDBG_WIFI, "Error reading SSL File: " + filename); + return 0; + } +} +#endif +void ESP8266MQTTMesh::onClient(AsyncClient* c) { + dbgPrintln(EMMDBG_WIFI, "Got client connection from: " + c->remoteIP().toString()); + for (int i = 1; i <= ESP8266_NUM_CLIENTS; i++) { + if (! espClient[i]) { + espClient[i] = c; + espClient[i]->onDisconnect([this](void * arg, AsyncClient *c) { this->onDisconnect(c); }, this); + espClient[i]->onError( [this](void * arg, AsyncClient *c, int8_t error) { this->onError(c, error); }, this); + espClient[i]->onAck( [this](void * arg, AsyncClient *c, size_t len, uint32_t time){ this->onAck(c, len, time); }, this); + espClient[i]->onTimeout( [this](void * arg, AsyncClient *c, uint32_t time) { this->onTimeout(c, time); }, this); + espClient[i]->onData( [this](void * arg, AsyncClient *c, void* data, size_t len) { this->onData(c, data, len); }, this); + bufptr[i] = inbuffer[i]; + return; + } + } + dbgPrintln(EMMDBG_WIFI, "Discarding client connection from: " + c->remoteIP().toString()); + delete c; +} + +void ESP8266MQTTMesh::onConnect(AsyncClient* c) { + dbgPrintln(EMMDBG_WIFI, "Connected to mesh"); +#if ASYNC_TCP_SSL_ENABLED + if (mesh_secure.cert) { + SSL* clientSsl = c->getSSL(); + bool sslFoundFingerprint = false; + uint8_t *fingerprint; + if (! clientSsl) { + dbgPrintln(EMMDBG_WIFI, "Connection is not secure"); + } else if(onSslFileRequest("fingerprint", &fingerprint)) { + if (ssl_match_fingerprint(clientSsl, fingerprint) == SSL_OK) { + sslFoundFingerprint = true; + } + free(fingerprint); + } + + if (!sslFoundFingerprint) { + dbgPrintln(EMMDBG_WIFI, "Couldn't match SSL fingerprint"); + c->close(true); + return; + } + } +#endif + send_connected_msg(); + setup_AP(); +} + +void ESP8266MQTTMesh::onDisconnect(AsyncClient* c) { + if (c == espClient[0]) { + dbgPrintln(EMMDBG_WIFI, "Disconnected from mesh"); + shutdown_AP(); + WiFi.disconnect(); + return; + } + for (int i = 1; i <= ESP8266_NUM_CLIENTS; i++) { + if (c == espClient[i]) { + dbgPrintln(EMMDBG_WIFI, "Disconnected from AP"); + delete espClient[i]; + espClient[i] = NULL; + } + } + dbgPrintln(EMMDBG_WIFI, "Disconnected unknown client"); +} +void ESP8266MQTTMesh::onError(AsyncClient* c, int8_t error) { + dbgPrintln(EMMDBG_WIFI, "Got error on " + c->remoteIP().toString() + ": " + String(error)); +} +void ESP8266MQTTMesh::onAck(AsyncClient* c, size_t len, uint32_t time) { + dbgPrintln(EMMDBG_WIFI_EXTRA, "Got ack on " + c->remoteIP().toString() + ": " + String(len) + " / " + String(time)); +} + +void ESP8266MQTTMesh::onTimeout(AsyncClient* c, uint32_t time) { + dbgPrintln(EMMDBG_WIFI, "Got timeout " + c->remoteIP().toString() + ": " + String(time)); + c->close(); +} + +void ESP8266MQTTMesh::onData(AsyncClient* c, void* data, size_t len) { + dbgPrintln(EMMDBG_WIFI_EXTRA, "Got data from " + c->remoteIP().toString()); + for (int idx = meshConnect ? 0 : 1; idx <= ESP8266_NUM_CLIENTS; idx++) { + if (espClient[idx] == c) { + char *dptr = (char *)data; + for (int i = 0; i < len; i++) { + *bufptr[idx]++ = dptr[i]; + if(! dptr[i]) { + handle_client_data(idx, inbuffer[idx]); + bufptr[idx] = inbuffer[idx]; + } + } + return; + } + } + dbgPrintln(EMMDBG_WIFI, "Could not find client"); +} diff --git a/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.h b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.h new file mode 100644 index 0000000..58fb6bc --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMesh.h @@ -0,0 +1,252 @@ +#ifndef _ESP8266MQTTMESH_H_ +#define _ESP8266MQTTMESH_H_ + +#if ! defined(MQTT_MAX_PACKET_SIZE) + #define MQTT_MAX_PACKET_SIZE 1152 +#endif +#if ! defined(ESP8266MESHMQTT_DISABLE_OTA) && ! defined(ESP32) + //By default we support OTA + #if ! defined(MQTT_MAX_PACKET_SIZE) || MQTT_MAX_PACKET_SIZE < (1024+128) + #error "Must define MQTT_MAX_PACKET_SIZE >= 1152" + #endif + #define HAS_OTA 1 +#else + #define HAS_OTA 0 +#endif + +#include + +#ifdef ESP32 + #include + #include + #define USE_WIFI_ONEVENT + #include "WiFiCompat.h" +#else + #include + #include + #include +#endif + +#include +#include +#include + +#ifdef ESP32 + #define _chipID ((unsigned long)ESP.getEfuseMac()) +#else + #define _chipID ESP.getChipId() +#endif + +#define TOPIC_LEN 64 + +#define EMMDBG_EXTRA 0x10000000 +#define EMMDBG_MSG 0x00000001 +#define EMMDBG_MSG_EXTRA (EMMDBG_EXTRA | EMMDBG_MSG) +#define EMMDBG_WIFI 0x00000002 +#define EMMDBG_WIFI_EXTRA (EMMDBG_EXTRA | EMMDBG_WIFI) +#define EMMDBG_MQTT 0x00000004 +#define EMMDBG_MQTT_EXTRA (EMMDBG_EXTRA | EMMDBG_MQTT) +#define EMMDBG_OTA 0x00000008 +#define EMMDBG_OTA_EXTRA (EMMDBG_EXTRA | EMMDBG_OTA) +#define EMMDBG_TIMING 0x00000010 +#define EMMDBG_TIMING_EXTRA (EMMDBG_EXTRA | EMMDBG_TIMING) +#define EMMDBG_FS 0x00000020 +#define EMMDBG_FS_EXTRA (EMMDBG_EXTRA | EMMDBG_OTA) +#define EMMDBG_ALL 0x8FFFFFFF +#define EMMDBG_ALL_EXTRA 0xFFFFFFFF +#define EMMDBG_NONE 0x00000000 + +#ifndef ESP8266_NUM_CLIENTS + #define ESP8266_NUM_CLIENTS 4 +#endif + +enum MSG_TYPE { + MSG_TYPE_NONE = 0xFE, + MSG_TYPE_INVALID = 0xFF, + MSG_TYPE_QOS_0 = 10, + MSG_TYPE_QOS_1 = 11, + MSG_TYPE_QOS_2 = 12, + MSG_TYPE_RETAIN_QOS_0 = 13, + MSG_TYPE_RETAIN_QOS_1 = 14, + MSG_TYPE_RETAIN_QOS_2 = 15, +}; + + +typedef struct { + const uint8_t *cert; + const uint8_t *key; + const uint8_t *fingerprint; + uint32_t cert_len; + uint32_t key_len; +} ssl_cert_t; + +typedef struct { + unsigned int len; + byte md5[16]; +} ota_info_t; + +typedef struct ap_t { + struct ap_t *next; + int32_t rssi; + uint8_t bssid[6]; + int16_t ssid_idx; +} ap_t; + +typedef struct { + const char *ssid; + const char *password; + const char *bssid; + bool hidden; +} wifi_conn; +#define WIFI_CONN(ssid, password, bssid, hidden) \ + { ssid, password, bssid, hidden } + +class ESP8266MQTTMesh { +public: + class Builder; +private: + const unsigned int firmware_id; + const char *firmware_ver; + const wifi_conn *networks; + + const char *mesh_ssid; + char mesh_password[64]; + const char *mqtt_server; + const char *mqtt_username; + const char *mqtt_password; + const int mqtt_port; + const int mesh_port; + uint32_t mesh_bssid_key; + + const char *inTopic; + const char *outTopic; +#if HAS_OTA + uint32_t freeSpaceStart; + uint32_t freeSpaceEnd; + uint32_t nextErase; + uint32_t startTime; + ota_info_t ota_info; +#endif +#if ASYNC_TCP_SSL_ENABLED + bool mqtt_secure; + ssl_cert_t mesh_secure; + const uint8_t *mqtt_fingerprint; +#endif + AsyncServer espServer; + AsyncClient *espClient[ESP8266_NUM_CLIENTS+1] = {0}; + uint8_t espMAC[ESP8266_NUM_CLIENTS+1][6]; + AsyncMqttClient mqttClient; + + Ticker schedule; + + int retry_connect; + ap_t *ap = NULL; + ap_t *ap_ptr; + ap_t *ap_unused = NULL; + char myID[10]; + char inbuffer[ESP8266_NUM_CLIENTS+1][MQTT_MAX_PACKET_SIZE]; + char *bufptr[ESP8266_NUM_CLIENTS+1]; + long lastMsg = 0; + char msg[50]; + int value = 0; + bool meshConnect = false; + unsigned long lastReconnect = 0; + unsigned long lastStatus = 0; + bool connecting = 0; + bool scanning = 0; + bool AP_ready = false; + std::function callback; + + bool wifiConnected() { return (WiFi.status() == WL_CONNECTED); } + void die() { while(1) {} } + + uint32_t lfsr(uint32_t seed, uint8_t b); + uint32_t encrypt_id(uint32_t id); + void generate_mac(uint8_t *bssid, uint32_t id); + bool verify_bssid(uint8_t *bssid); + + int match_networks(const char *ssid, const char *bssid); + void scan(); + void connect(); + static void connect(ESP8266MQTTMesh *e) { e->connect(); }; + String mac_str(uint8_t *bssid); + const char *build_mesh_ssid(char buf[32], uint8_t *mac); + void schedule_connect(float delay = 5.0); + void connect_mqtt(); + void shutdown_AP(); + void setup_AP(); + void handle_client_data(int idx, char *data); + void parse_message(const char *topic, const char *msg); + void mqtt_callback(const char* topic, const byte* payload, unsigned int length); + uint16_t mqtt_publish(const char *topic, const char *msg, uint8_t msgType); + void publish(const char *topicDirection, const char *baseTopic, const char *subTopic, const char *msg, uint8_t msgType); + bool send_message(int index, const char *topicOrMsg, const char *msg = NULL, uint8_t msgType = MSG_TYPE_NONE); + void send_messages(); + void send_connected_msg(); + void broadcast_message(const char *topicOrMsg, const char *msg = NULL); + void get_fw_string(char *msg, int len, const char *prefix); + void handle_fw(const char *cmd); + void handle_ota(const char *cmd, const char *msg); + void parse_ota_info(const char *str); + char * md5(const uint8_t *msg, int len); + bool check_ota_md5(); + void assign_subdomain(); + static void assign_subdomain(ESP8266MQTTMesh *e) { e->assign_subdomain(); }; + void erase_sector(); + static void erase_sector(ESP8266MQTTMesh *e) { e->erase_sector(); }; + + void connectWiFiEvents(); + +#ifndef USE_WIFI_ONEVENT + WiFiEventHandler wifiConnectHandler; + WiFiEventHandler wifiDisconnectHandler; + WiFiEventHandler wifiAPConnectHandler; + WiFiEventHandler wifiAPDisconnectHandler; +#endif + + void onWifiConnect(const WiFiEventStationModeGotIP& event); + void onWifiDisconnect(const WiFiEventStationModeDisconnected& event); + //void onDHCPTimeout(); + void onAPConnect(const WiFiEventSoftAPModeStationConnected& ip); + void onAPDisconnect(const WiFiEventSoftAPModeStationDisconnected& ip); + + void onMqttConnect(bool sessionPresent); + void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); + void onMqttSubscribe(uint16_t packetId, uint8_t qos); + void onMqttUnsubscribe(uint16_t packetId); + void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); + void onMqttPublish(uint16_t packetId); + + int onSslFileRequest(const char *filename, uint8_t **buf); + void onClient(AsyncClient* c); + void onConnect(AsyncClient* c); + void onDisconnect(AsyncClient* c); + void onError(AsyncClient* c, int8_t error); + void onAck(AsyncClient* c, size_t len, uint32_t time); + void onTimeout(AsyncClient* c, uint32_t time); + void onData(AsyncClient* c, void* data, size_t len); + + ESP8266MQTTMesh(const wifi_conn *networks, + const char *mqtt_server, int mqtt_port, + const char *mqtt_username, const char *mqtt_password, + const char *firmware_ver, int firmware_id, + const char *mesh_ssid, const char *mesh_password, int mesh_port, +#if ASYNC_TCP_SSL_ENABLED + bool mqtt_secure, const uint8_t *mqtt_fingerprint, ssl_cert_t mesh_secure, +#endif + const char *inTopic, const char *outTopic); +public: + void setCallback(std::function _callback); + void begin(); + void publish(const char *subtopic, const char *msg, enum MSG_TYPE msgCmd = MSG_TYPE_NONE); + void publish_node(const char *subtopic, const char *msg, enum MSG_TYPE msgCmd = MSG_TYPE_NONE); + bool connected(); + static bool keyValue(const char *data, char separator, char *key, int keylen, const char **value); +#ifdef USE_WIFI_ONEVENT + void WiFiEventHandler(system_event_id_t event, system_event_info_t info); +#endif +}; + +#include "ESP8266MQTTMeshBuilder.h" + +#endif //_ESP8266MQTTMESH_H_ diff --git a/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMeshBuilder.h b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMeshBuilder.h new file mode 100644 index 0000000..30a4e19 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/ESP8266MQTTMeshBuilder.h @@ -0,0 +1,142 @@ +#ifndef _ESP8266MQTTMESHBUILDER_H_ +#define _ESP8266MQTTMESHBUILDER_H_ + +class ESP8266MQTTMesh::Builder { +private: + const wifi_conn *networks; + + const char *mqtt_server; + int mqtt_port; + const char *mqtt_username; + const char *mqtt_password; + + const char *mesh_ssid; + const char *mesh_password; + int mesh_port; + + const char *inTopic; + const char *outTopic; + + unsigned int firmware_id; + const char *firmware_ver; +#if ASYNC_TCP_SSL_ENABLED + bool mqtt_secure; + ssl_cert_t mesh_secure; + const uint8_t *mqtt_fingerprint; + void fix_mqtt_port() { if (! mqtt_port) mqtt_port = mqtt_secure ? 8883 : 1883; }; +#else + void fix_mqtt_port() { if (! mqtt_port) mqtt_port = 1883; }; +#endif + +public: + Builder(const wifi_conn *networks, + const char *mqtt_server, + int mqtt_port = 0): + networks(networks), + mqtt_server(mqtt_server), + mqtt_port(mqtt_port), + mqtt_username(NULL), + mqtt_password(NULL), + firmware_id(0), + firmware_ver(NULL), + mesh_ssid("esp8266_mqtt_mesh"), + mesh_password("ESP8266MQTTMesh"), + mesh_port(1884), +#if ASYNC_TCP_SSL_ENABLED + mqtt_secure(false), + mqtt_fingerprint(NULL), + mesh_secure({NULL, NULL, NULL, 0, 0}), +#endif + inTopic("esp8266-in/"), + outTopic("esp8266-out/") + + {} + Builder& setVersion(const char *firmware_ver, int firmware_id) { + this->firmware_id = firmware_id; + this->firmware_ver = firmware_ver; + return *this; + } + Builder& setMqttAuth(const char *username, const char *password) { + this->mqtt_username = username; + this->mqtt_password = password; + return *this; + } + Builder& setMeshSSID(const char *ssid) { this->mesh_ssid = ssid; return *this; } + Builder& setMeshPassword(const char *password) { this->mesh_password = password; return *this; } + Builder& setMeshPort(int port) { this->mesh_port = port; return *this; } + Builder& setTopic(const char *inTopic, const char *outTopic) { + this->inTopic = inTopic; + this->outTopic = outTopic; + return *this; + } +#if ASYNC_TCP_SSL_ENABLED + Builder& setMqttSSL(bool enable, const uint8_t *fingerprint) { + this->mqtt_secure = enable; + this->mqtt_fingerprint = fingerprint; + return *this; + } + Builder & setMeshSSL(const uint8_t *ssl_cert, uint32_t ssl_cert_len, + const uint8_t *ssl_key, uint32_t ssl_key_len, + const uint8_t *ssl_fingerprint) { + this->mesh_secure.cert = ssl_cert; + this->mesh_secure.key = ssl_key; + this->mesh_secure.fingerprint = ssl_fingerprint; + this->mesh_secure.cert_len = ssl_cert_len; + this->mesh_secure.key_len = ssl_key_len; + return *this; + } +#endif + ESP8266MQTTMesh build() { + fix_mqtt_port(); + return( ESP8266MQTTMesh( + networks, + + mqtt_server, + mqtt_port, + mqtt_username, + mqtt_password, + + firmware_ver, + firmware_id, + + mesh_ssid, + mesh_password, + mesh_port, + +#if ASYNC_TCP_SSL_ENABLED + mqtt_secure, + mqtt_fingerprint, + mesh_secure, +#endif + + inTopic, + outTopic)); + } + ESP8266MQTTMesh *buildptr() { + fix_mqtt_port(); + return( new ESP8266MQTTMesh( + networks, + + mqtt_server, + mqtt_port, + mqtt_username, + mqtt_password, + + firmware_ver, + firmware_id, + + mesh_ssid, + mesh_password, + mesh_port, + +#if ASYNC_TCP_SSL_ENABLED + mqtt_secure, + mqtt_fingerprint, + mesh_secure, +#endif + + inTopic, + outTopic)); + } +}; +#endif //_ESP8266MQTTMESHBUILDER_H_ diff --git a/libraries/ESP8266_MQTT_Mesh/src/WiFiCompat.h b/libraries/ESP8266_MQTT_Mesh/src/WiFiCompat.h new file mode 100644 index 0000000..4bdf0ea --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/src/WiFiCompat.h @@ -0,0 +1,54 @@ +/* This header file defines compatible structs that are not present in ESP32 */ +#ifndef _WIFI_COMPAT_ +#define _WIFI_COMPAT_ + +#include + +typedef enum { + STATION_IF = 0, /**< ESP32 station interface */ + SOFTAP_IF, /**< ESP32 soft-AP interface */ + MAX_IF +} WIFI_INTERFACE; + +static bool wifi_set_macaddr(WIFI_INTERFACE if_index, uint8_t *macaddr) { + if (if_index == SOFTAP_IF) { + int ok = esp_base_mac_addr_set(macaddr); + return ok == ESP_OK; + } + return true; +} + +#define WIFI_DISCONNECT_REASON_ASSOC_TOOMANY WIFI_REASON_ASSOC_TOOMANY +struct WiFiEventStationModeGotIP +{ + IPAddress ip; + IPAddress mask; + IPAddress gw; +}; + +struct WiFiEventStationModeDisconnected +{ + String ssid; + uint8_t bssid[6]; + unsigned int reason; +}; + +/* +struct WiFiEventSoftAPModeStationConnected +{ + uint8_t mac[6]; + uint8_t aid; +}; +*/ +#define WiFiEventSoftAPModeStationConnected system_event_ap_staconnected_t +/* +struct WiFiEventSoftAPModeStationDisconnected +{ + uint8_t mac[6]; + uint8_t aid; +}; +*/ +#define WiFiEventSoftAPModeStationDisconnected system_event_ap_stadisconnected_t + +#endif //_WIFI_COMPAT_ + diff --git a/libraries/ESP8266_MQTT_Mesh/utils/dump_stacktrace.py b/libraries/ESP8266_MQTT_Mesh/utils/dump_stacktrace.py new file mode 100644 index 0000000..034dd19 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/utils/dump_stacktrace.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +import subprocess +import sys +import re +import os + +firmware = sys.argv[1] +stacktrace = sys.argv[2] +objdump = os.environ['HOME'] + "/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-objdump" + +dump = subprocess.check_output([objdump, '-S',firmware]).split('\n') +funcs = [] +start = None +end = None +for line in dump: + #40208544 <_ZN15ESP8266MQTTMesh8setup_APEv>: + match = re.match(r'([0-9a-f]{8})\s+<(.*)>:', line) + if match: + funcs.append([int(match.group(1), 16), match.group(2)]) + match = re.match(r'([0-9a-f]{8}):',line) + if match: + add = int(match.group(1), 16) + if not end or add > end: + end = add + if not start or add < start: + start = add +with open(stacktrace, "r") as fh: + in_stack = False + for line in fh: + if re.search(r'>>>stack>>>', line): + in_stack = True + if in_stack: + addrs = re.split(r'[: ]+', line) + for addr in addrs: + try: + add = int(addr, 16) + except: + continue + if add < start or add > end: + #print("Ignoring: %s (%08x, %08x)" % (addr, start, end)) + continue + for i in range(0, len(funcs)): + if funcs[i][0] <= add: + continue + print("%s : %s" % (addr, funcs[i-1][1])) + break + + + diff --git a/libraries/ESP8266_MQTT_Mesh/utils/gen_server_cert.sh b/libraries/ESP8266_MQTT_Mesh/utils/gen_server_cert.sh new file mode 100644 index 0000000..c8af25d --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/utils/gen_server_cert.sh @@ -0,0 +1,52 @@ +#!/bin/bash +#This script is a slightly modified copy of: +#https://github.com/me-no-dev/ESPAsyncTCP/blob/master/ssl/gen_server_cert.sh +cat > ca_cert.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = Espressif Systems +EOF + +openssl genrsa -out axTLS.ca_key.pem 2048 +openssl req -new -config ./ca_cert.conf -key axTLS.ca_key.pem -out axTLS.ca_x509.req +openssl x509 -req -sha1 -days 5000 -signkey axTLS.ca_key.pem -CAkey axTLS.ca_key.pem -in axTLS.ca_x509.req -out axTLS.ca_x509.pem + +cat > certs.conf << EOF +[ req ] +distinguished_name = req_distinguished_name +prompt = no + +[ req_distinguished_name ] + O = axTLS on ESP8266 + CN = esp8266.local +EOF + +openssl genrsa -out axTLS.key_1024.pem 1024 +openssl req -new -config ./certs.conf -key axTLS.key_1024.pem -out axTLS.x509_1024.req +openssl x509 -req -sha1 -CAcreateserial -days 5000 -CA axTLS.ca_x509.pem -CAkey axTLS.ca_key.pem -in axTLS.x509_1024.req -out axTLS.x509_1024.pem + +openssl rsa -outform DER -in axTLS.key_1024.pem -out axTLS.key_1024 +openssl x509 -outform DER -in axTLS.x509_1024.pem -out axTLS.x509_1024.cer + +cat axTLS.key_1024 > server.key +cat axTLS.x509_1024.cer > server.cer +python -c 'import os; import hashlib; os.write(1,hashlib.sha1(open("server.cer", "rb").read()).digest())' > fingerprint + +echo "const uint8_t ssl_key[] =" > ssl_cert.h +hexdump -v -e '16/1 "_x%02X" "\n"' server.key | sed 's/_/\\/g; s/\\x //g; s/.*/ "&"/' >> ssl_cert.h +echo ";" >> ssl_cert.h +echo "const uint32_t ssl_key_len = `cat server.key | wc -c`;" >> ssl_cert.h + +echo "const uint8_t ssl_cert[] =" >> ssl_cert.h +hexdump -v -e '16/1 "_x%02X" "\n"' server.cer | sed 's/_/\\/g; s/\\x //g; s/.*/ "&"/' >> ssl_cert.h +echo ";" >> ssl_cert.h +echo "const uint32_t ssl_cert_len = `cat server.cer | wc -c`;" >> ssl_cert.h + +echo "const uint8_t ssl_fingerprint[] =" >> ssl_cert.h +hexdump -v -e '16/1 "_x%02X" "\n"' fingerprint | sed 's/_/\\/g; s/\\x //g; s/.*/ "&"/' >> ssl_cert.h +echo ";" >> ssl_cert.h + +rm axTLS.* ca_cert.conf certs.conf diff --git a/libraries/ESP8266_MQTT_Mesh/utils/get_mqtt_fingerprint.py b/libraries/ESP8266_MQTT_Mesh/utils/get_mqtt_fingerprint.py new file mode 100644 index 0000000..921a251 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/utils/get_mqtt_fingerprint.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +# This script originally came from: +# https://github.com/marvinroger/async-mqtt-client/blob/master/scripts/get-fingerprint/get-fingerprint.py + +import argparse +import ssl +import hashlib +import binascii +import sys +import socket + +# The following came from https://tools.ietf.org/html/rfc5754#section-3 +# This is crude, but works without requireing additional dependencies + +signatures = { + "dsa_sha224": "30 0b 06 09 60 86 48 01 65 03 04 03 01", + "dsa_sha256": "30 0b 06 09 60 86 48 01 65 03 04 03 02", + "rsa_sha224": "30 0d 06 09 2a 86 48 86 f7 0d 01 01 0e 05 00", + "rsa_sha256": "30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00", + "rsa_sha384": "30 0d 06 09 2a 86 48 86 f7 0d 01 01 0c 05 00", + "rsa_sha512": "30 0d 06 09 2a 86 48 86 f7 0d 01 01 0d 05 00", + "ecdsa_sha224": "30 0a 06 08 2a 86 48 ce 3d 04 03 01", + "ecdsa_sha256": "30 0a 06 08 2a 86 48 ce 3d 04 03 02", + "ecdsa_sha384": "30 0a 06 08 2a 86 48 ce 3d 04 03 03", + "ecdsa_sha512": "30 0a 06 08 2a 86 48 ce 3d 04 03 04" +} + +parser = argparse.ArgumentParser(description='Compute SSL/TLS fingerprints.') +parser.add_argument('--host', required=True) +parser.add_argument('--port', default=8883) + +args = parser.parse_args() + +try: + cert_pem = ssl.get_server_certificate((args.host, args.port)) + cert_der = ssl.PEM_cert_to_DER_cert(cert_pem) +except socket.error as e: + if str(e).find("[Errno 111]") is not -1: + print("ERROR: Could not connect to %s:%s" % (args.host, args.port)) + elif str(e).find("[Errno 104]") is not -1: + print("ERROR: Mosquitto broker does not appear to be using TLS at %s:%s" % (args.host, args.port)) + print(e) + sys.exit(1) + +matches = [] +for k in signatures: + fingerprint = binascii.a2b_hex(signatures[k].replace(" ", "")) + if cert_der.find(fingerprint) is not -1: + matches.append(k) +if not matches: + print("WARNING: Couldn't identify signature algorithm") +else: + print("INFO: Found signature algorithm: " + ", ".join(matches)) + for sig in ("rsa_sha384", "rsa_sha512", "ecdsa_sha384", "ecdsa_sha512"): + if sig in matches: + print("ERROR: MQTT broker is using a %s signature which will not work with ESP8266" % (sig)) + +sha1 = hashlib.sha1(cert_der).hexdigest() + +print("const uint8_t MQTT_FINGERPRINT[] = {0x" + ",0x".join([sha1[i:i+2] for i in range(0, len(sha1), 2)]) + "};") diff --git a/libraries/ESP8266_MQTT_Mesh/utils/send_ota.py b/libraries/ESP8266_MQTT_Mesh/utils/send_ota.py new file mode 100644 index 0000000..ce8ad7c --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/utils/send_ota.py @@ -0,0 +1,201 @@ +#!/usr/bin/python3 + +import paho.mqtt.client as mqtt +import os +import sys +import argparse +import datetime +import hashlib +import base64 +import time +import ssl +import re +import queue + + +topic = "esp8266-" +inTopic = topic + "in" +outTopic = topic + "out" +send_topic = "" +name="" +passw="" +q = queue.Queue(); + +def regex(pattern, txt, group): + group.clear() + match = re.search(pattern, txt) + if match: + if match.groupdict(): + for k,v in match.groupdict().items(): + group[k] = v + else: + group.extend(match.groups()) + return True + return False + +def on_connect(client, userdata, flags, rc): + print("Connected with result code "+str(rc)) + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe("{}/#".format(outTopic)) + +# The callback for when a PUBLISH message is received from the server. +def on_message(client, userdata, msg): + #esp8266-out/mesh_esp8266-6/check=MD5 Passed + match = [] + if regex(r'/([0-9a-f]+)/ota/erase$', msg.topic, match): + q.put(["erase", match[0]]) + elif regex(r'([0-9a-f]+)/ota/md5/([0-9a-f]+)', msg.topic, match): + q.put(["md5", match[0], match[1], msg.payload]) + elif regex(r'([0-9a-f]+)/ota/check$', msg.topic, match): + q.put(["check", match[0], msg.payload]) + else: + #print("%s %-30s = %s" % (str(datetime.datetime.now()), msg.topic, str(msg.payload))); + pass + + +def wait_for(nodes, msgtype, maxTime, retries=0, pubcmd=None): + seen = {} + origTime = time.time() + startTime = origTime + while True: + try: + msg = q.get(True, 0.1) + if msg[0] == msgtype and (not nodes or msg[1] in nodes): + node = msg[1] + seen[node] = msg + else: + print("Got unexpected {} for node {}".format(msgtype, msg[1], msg)) + except queue.Empty: + if time.time() - startTime < maxTime: + continue + if retries: + retries -= 1 + print("{} node(s) missed the message, retrying".format(len(nodes) - len(seen.keys()))) + client.publish(pubcmd[0], pubcmd[1]) + startTime = time.time() + else: + print("{} node(s) missed the message and no retires left".format(len(nodes) - len(seen.keys()))) + if nodes and len(seen.keys()) == len(nodes): + break + #print("Elapsed time waiting for {} messages: {} seconds".format(msgtype, time.time() - origTime)) + return seen + +def send_firmware(client, data, nodes): + md5 = base64.b64encode(hashlib.md5(data).digest()) + payload = "md5:%s,len:%d" %(md5.decode(), len(data)) + print("Erasing...") + client.publish("{}start".format(send_topic), payload) + nodes = list(wait_for(nodes, 'erase', 10).keys()) + print("Updating firmware on the following nodes:\n\t{}".format("\n\t".join(nodes))) + pos = 0 + while len(data): + d = data[0:768] + b64d = base64.b64encode(d) + data = data[768:] + client.publish("{}{}".format(send_topic, str(pos)), b64d) + expected_md5 = hashlib.md5(d).hexdigest().encode('utf-8') + seen = {} + retries = 1 + seen = wait_for(nodes, 'md5', 1.0, 1, ["{}{}".format(send_topic, str(pos)), b64d]) + for node in nodes: + if node not in seen: + print("No MD5 found for {} at 0x{}".format(node, pos)) + return + addr = int(seen[node][2], 16) + md5 = seen[node][3] + if pos != addr: + print("Got unexpected address 0x{} (expected: 0x{}) from node {}".format(addr, pos, node)) + return + if md5 != expected_md5: + print("Got unexpected md5 for node {} at 0x{}".format(node, addr)) + print("\t {} (expected: {})".format(md5, expected_md5)) + return + pos += len(d) + if pos % (768 * 13) == 0: + print("Transmitted %d bytes" % (pos)) + print("Completed send") + client.publish("{}check".format(send_topic), "") + seen = wait_for(nodes, 'check', 5) + err = False + for node in nodes: + if node not in seen: + print("No verify result found for {}".format(node)) + err = True + if seen[node][2] != b'MD5 Passed': + print("Node {} did not pass final MD5 check: {}".format(node, seen[node][2])) + err = True + if err: + return + print("Checksum verified. Flashing and rebooting now...") + client.publish("{}flash".format(send_topic), "") + +def main(): + global inTopic, outTopic, name, passw, send_topic + parser = argparse.ArgumentParser() + parser.add_argument("--bin", help="Input file"); + parser.add_argument("--id", help="Firmware ID (n HEX)"); + parser.add_argument("--broker", help="MQTT broker"); + parser.add_argument("--port", help="MQTT broker port"); + parser.add_argument("--user", help="MQTT broker user"); + parser.add_argument("--password", help="MQTT broker password"); + parser.add_argument("--ssl", help="MQTT broker SSL support"); + parser.add_argument("--topic", help="MQTT mesh topic base (default: {}".format(topic)) + parser.add_argument("--intopic", help="MQTT mesh in-topic (default: {}".format(inTopic)) + parser.add_argument("--outtopic", help="MQTT mesh out-topic (default: {}".format(outTopic)) + parser.add_argument("--node", help=("Specific node to send firmware to")) + args = parser.parse_args() + if not os.path.isfile(args.bin): + print("File: " + args.bin + " does not exist") + sys.exit(1) + + if args.topic: + inTopic = args.topic + "in" + outTopic = args.topic + "out" + if args.intopic: + inTopic = args.intopic + if args.outtopic: + outTopic = args.outtopic + + if args.id: + send_topic = "{}/ota/{}/".format(inTopic, args.id) + elif args.node: + send_topic = "{}/ota/{}/".format(inTopic, args.node) + else: + print("Must specify either --id or --node") + sys.exit(1) + print("File: {}".format(args.bin)) + print("Sending to topic: {}".format(send_topic)) + print("Listening to topic: {}".format(outTopic)) + + if not args.broker: + args.broker = "127.0.0.1" + if not args.port: + args.port = 1883 + + if args.user: + name = args.user + if args.password: + passw = args.password + + client = mqtt.Client() + if args.ssl: + client.tls_set(ca_certs=None, certfile=None, keyfile=None, cert_reqs=ssl.CERT_REQUIRED,tls_version=ssl.PROTOCOL_TLS, ciphers=None) + if (args.user) or (args.password): + client.username_pw_set(name,passw) + client.on_connect = on_connect + client.on_message = on_message + + client.connect(args.broker, args.port, 60) + client.loop_start() + + fh = open(args.bin, "rb") + data = fh.read(); + fh.close() + + send_firmware(client, data, [args.node] if args.node else []) + + client.loop_stop() + client.disconnect() +main() diff --git a/libraries/FlowMeter-master/.gitattributes b/libraries/FlowMeter-master/.gitattributes new file mode 100644 index 0000000..bf5422e --- /dev/null +++ b/libraries/FlowMeter-master/.gitattributes @@ -0,0 +1,2 @@ +*.h linguist-language=Arduino +*.cpp linguist-language=Arduino diff --git a/libraries/FlowMeter-master/.gitignore b/libraries/FlowMeter-master/.gitignore new file mode 100644 index 0000000..b8bd026 --- /dev/null +++ b/libraries/FlowMeter-master/.gitignore @@ -0,0 +1,28 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/libraries/FlowMeter-master/.gitmodules b/libraries/FlowMeter-master/.gitmodules new file mode 100644 index 0000000..32fd792 --- /dev/null +++ b/libraries/FlowMeter-master/.gitmodules @@ -0,0 +1,3 @@ +[submodule "wiki"] + path = wiki + url = https://github.com/sekdiy/FlowMeter.wiki.git diff --git a/libraries/FlowMeter-master/.travis.yml b/libraries/FlowMeter-master/.travis.yml new file mode 100644 index 0000000..070b00c --- /dev/null +++ b/libraries/FlowMeter-master/.travis.yml @@ -0,0 +1,20 @@ +language: c +before_install: + - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16" + - sleep 3 + - export DISPLAY=:1.0 + - wget http://downloads.arduino.cc/arduino-1.6.5-linux64.tar.xz + - tar xf arduino-1.6.5-linux64.tar.xz + - sudo mv arduino-1.6.5 /usr/local/share/arduino + - sudo ln -s /usr/local/share/arduino/arduino /usr/local/bin/arduino +install: + - ln -s $PWD /usr/local/share/arduino/libraries/FlowMeter +# - arduino --install-library "Adafruit SleepyDog Library,Adafruit MQTT Library" +script: + - arduino --verify --board arduino:avr:uno $PWD/examples/Simple/Simple.ino + - arduino --verify --board arduino:avr:uno $PWD/examples/Simulator/Simulator.ino + - arduino --verify --board arduino:avr:uno $PWD/examples/Calibration/Calibration.ino +notifications: + email: + on_success: change + on_failure: change diff --git a/libraries/FlowMeter-master/keywords.txt b/libraries/FlowMeter-master/keywords.txt new file mode 100644 index 0000000..0787c0d --- /dev/null +++ b/libraries/FlowMeter-master/keywords.txt @@ -0,0 +1,47 @@ +####################################### +# Syntax Coloring Map For FlowMeter +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +FlowMeter KEYWORD1 +FlowSensorProperties KEYWORD1 +FlowSensorCalibration KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +getPin KEYWORD2 +getCurrentFlowrate KEYWORD2 +getCurrentFrequency KEYWORD2 +getCurrentVolume KEYWORD2 +getCurrentDuration KEYWORD2 +getCurrentError KEYWORD2 + +getTotalVolume KEYWORD2 +getTotalDuration KEYWORD2 +getTotalFlowrate KEYWORD2 +getTotalError KEYWORD2 + +setTotalVolume KEYWORD2 +setTotalDuration KEYWORD2 +setTotalError KEYWORD2 + +tick KEYWORD2 +count KEYWORD2 +reset KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +UncalibratedSensor KEYWORD2 +FS300A KEYWORD2 +FS400A KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/FlowMeter-master/library.properties b/libraries/FlowMeter-master/library.properties new file mode 100644 index 0000000..71ad931 --- /dev/null +++ b/libraries/FlowMeter-master/library.properties @@ -0,0 +1,9 @@ +name=FlowMeter +version=1.1.0 +author=Sebastian Kraus +maintainer=Sebastian Kraus +sentence=An Arduino flow meter library that provides calibrated liquid flow and volume measurement with flow sensors. +paragraph=This Flow Meter library is primarily intended for use with impeller flow sensors, although other types of sensors could be made to work. +category=Sensors +url=https://github.com/sekdiy/FlowMeter +architectures=* diff --git a/libraries/bitluni_ESP32Lib/Documentation/schematic.png b/libraries/bitluni_ESP32Lib/Documentation/schematic.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1c1e15e21f8b8c1707251325e301d3af56aa40 GIT binary patch literal 97692 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
%GUwWBl#tCY_c_n>HoWhX8qT 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 literal 0 HcmV?d00001 diff --git a/libraries/bitluni_ESP32Lib/Documentation/schematic3bit.png b/libraries/bitluni_ESP32Lib/Documentation/schematic3bit.png new file mode 100644 index 0000000000000000000000000000000000000000..f388aac0a09c16b7dafa7d9531275fa953c4a99c GIT binary patch literal 76019 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 Ywsyhm=G!eRz^_v$jE?6W`}OMo1evt<8 literal 0 HcmV?d00001 diff --git a/libraries/bitluni_ESP32Lib/README.md b/libraries/bitluni_ESP32Lib/README.md new file mode 100644 index 0000000..18315b5 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/README.md @@ -0,0 +1,152 @@ +# bitluni's ESP32Lib +ESP32Lib is a collection features for the ESP32 including highest performance VGA graphics (sprites, 3D), sound and game controllers packed in an Arduino library. +Check out https://youtube.com/bitlunislab for project updates + +If you found it useful please consider supporting my work on + +Patreon https://patreon.com/bitluni +Paypal https://paypal.me/bitluni + +# Acknowledgements +Thanks to Ivan Grokhotkov & Jeroen Domburg (aka Sprite_tm) for their great work on I2S revealing some nitty-gritty details and quirks of the ESP32. +Special thanks to Fabrizio Di Vittorio for the inpiration to look deeper into 8Bit modes enabling higher resolutions. He developed the FabGL library simultaneously. + +# License +bitluni 2019 +Creative Commons Attribution ShareAlike 4.0 +https://creativecommons.org/licenses/by-sa/4.0/ + +If you need another license, please feel free to contact me + +# Documentation + +## Installation + +This library only supports the ESP32. +I be able to install the ESP32 features in the board manager you need to add an additional Boards Manager URL in the Preferences (File -> Preferences) +``` +https://dl.espressif.com/dl/package_esp32_index.json +``` +The ESP32Lib can be found in the Library Manager (Sketch -> Include Library -> Manage Libaries) +To be able to use the library featues the main header needs to included in the sketch +```cpp +#include +``` + +## VGA Features + +ESP32Lib implements VGA output over I²S. +The highest possible resolution with this library is 800x600. +Many common resolutions like 320x240 are preconfigured und can be used without any effort. +Two color depths are available. 14Bit R5G5B4 and 3Bit(8 color) R1G1B1 for convenience and memory savings. + +To simplify things you can find boards specially designed to work with this library in my shop: +https://www.tindie.com/stores/bitluni/ +Any purchase supports the further development. Thanks! + +### Predefined Pin Configurations +A simplified way to configure the pins for shields from my tindie store is using the default predefined configurations VGAv01, VGABlackEdition, VGAWhiteEdition, PicoVGA like this: +```cpp +vga.init(vga.MODE320x200, vga.VGABlackEdition); +``` + +### Pin configuration + +An VGA cable can be used to connect to the ESP32 +The connector pins can be found here: https://en.wikipedia.org/wiki/VGA_connector +The 3Bit modes are very easy to set up. You can connect +the Ground, Red, Green, Blue, hSync and vSync to output pins of the ESP32. + +![3Bit color setup](/Documentation/schematic3bit.png) + +The 14Bit mode require a resistor ladder to be set up for each color (DAC) as shown here + +![14Bit color setup](/Documentation/schematic.png) + +There are limitation on which the VGA output an DACs can work: + +I/O GPIO pads are 0-19, 21-23, 25-27, 32-39 +Input/Output GPIOs are 0-19, 21-23, 25-27, 32-33. +GPIO pads 34-39 are input-only. + +Beware of pin 0. It selects the boot mode. +It's not suitable for color channels and might cause problems as sync signals as well. + +Pin 5 is often tied to the LED but can be used as hSync if you don't need the LED. +I²C is on 21 (SDA), 22(SCL) +DACs are 25, 26 +ins 1(TX), 3(RX) sould only be used if really don't need to communicate anymore. + +Here is an overview for your convenience: +(0), 2, 4, (5), 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 27, 32, 33 + +### Usage + +There are 4 diffent VGA Drivers **VGA3Bit**, **VGA3BitI**, **VGA14Bit** and **VGA14BitI**. +VGA3Bit, VGA14Bit are the high performance drivers that don't need any CPU time to +serve the VGA. However the VGA3Bit driver takes twice the memory compared to VGA3BitI. +The high performace drivers work the best with the WiFi features. The other driver might +cause errors. WiFi should connect first before VGA3BitI and VGA14BitI is initialized. +The *I* drivers are using an interrupt to feed the pixels to the I²S. This feature can be used for realtime outputs (Check the **VGANoFramebuffer** example). +An instance of the driver has to be created. The optional parameter (only for 14 bit versions) is the I²S bus to be used. If no parameter is given 1 is used by default to keep I²S0 free for audio output. +```cpp +VGA14Bit vga(1); +``` +Creating the instance does nothing until the init method is called once. The driver will initialize with one frame buffer by default (that's the memory where the pixels are stored). +Showing animated using only one frame buffer will cause flickering since the frame will be displayed while it is redrawn. To have smooth animations double buffering is recommended. +A second - the back buffer - is used to paint the graphics in the back ground. When the rendering is finished the front and back buffer are flipped. The disadvantage of the second buffer is +the doubled memory requirements. 320x240 will no longer work (320x200 will). +**Double buffering** is enabled using +```cpp +vga.setFrameBufferCount(2); +``` +before the init method is called (not calling it will result in a single buffer init) +```cpp +vga.init(vga.MODE320x200, redPins, greenPins, bluePins, hsyncPin, vsyncPin); +``` + +The R, G and B pins are passed as arrays for the 14Bit driver and as single integers for the 3Bit version. Please try the examples +The following modes are predefined: +- MODE320x480 +- MODE320x240 +- MODE320x120 +- MODE320x400 +- MODE320x200 +- MODE360x400 +- MODE360x200 +- MODE360x350 +- MODE360x175 +- MODE320x350 +- MODE320x175 +- MODE400x300 +- MODE400x150 +- MODE400x100 +- MODE200x150 +- MODE500x480 +- MODE500x240 +- MODE800x600 +- MODE720x400 +- MODE720x350 +- MODE640x480 +- MODE640x400 +- MODE800x600 + +These native modes require a too high pixel clock but can be used as a base to create a custom resolution. Please check out the **VGACustomResolution** example: +- MODE1280x1024 +- MODE1280x960 +- MODE1280x800 +- MODE1024x768 + +### 2D features +The vga instance implements several drawing methods that can be seen in the **VGA2DFeatures** example. +A complete list will be available in an API documentation soon... + +## Converting 3D Meshes +The Utilities folder provides a convenient [StlConverter](https://htmlpreview.github.io/?https://github.com/bitluni/ESP32Lib/blob/master/Utilities/StlConverter.html) that you can use directly from the browser. No worries, your files are not uploaded. +Make sure your STL is low poly. The 3D example use a model with < 5000 triangles. + + +## Converting Sprites and Images +The Utilities folder provides a convenient [SpriteEditor](https://htmlpreview.github.io/?https://github.com/bitluni/ESP32Lib/blob/master/Utilities/SpriteEditor.html) that you can use directly from the browser. No worries, your files are not uploaded. +The correct pixel format for VGA14Bit and VGA3Bit is R5G5B4A2 (that will be improved in future). You can import PNG files to use transparency. +Each sprite has the origin in the center by default. You can modify it by changing the x/y values of the first point definition. Clicking on the image creates additional points that can be used for other purpouses like hit boxes etc. \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html b/libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html new file mode 100644 index 0000000..255644e --- /dev/null +++ b/libraries/bitluni_ESP32Lib/Utilities/SpriteEditor.html @@ -0,0 +1,624 @@ + + +bitluni's Sprite Editor + + + + +

bitluni's Sprite Editor

+
+ + +
+
+ +check out
bitluni's lab + diff --git a/libraries/bitluni_ESP32Lib/Utilities/StlConverter.html b/libraries/bitluni_ESP32Lib/Utilities/StlConverter.html new file mode 100644 index 0000000..f4aebe8 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/Utilities/StlConverter.html @@ -0,0 +1,255 @@ + + + + + +

bitluni's STL Converter

+
+ + diff --git a/libraries/bitluni_ESP32Lib/examples/6BitMode/6BitMode.ino b/libraries/bitluni_ESP32Lib/examples/6BitMode/6BitMode.ino new file mode 100644 index 0000000..338d4ea --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/6BitMode/6BitMode.ino @@ -0,0 +1,97 @@ +//This example shows a rendering of Julia set. Please change the pinConfig if you are using a different board to PicoVGA +//cc by-sa 4.0 license +//bitluni + +//include libraries +#include +#include + +//VGA Device +VGA6Bit vga; +//Pin presets are avaialable for: VGAv01, VGABlackEdition, VGAWhiteEdition, PicoVGA +const PinConfig &pinConfig = VGA6Bit::PicoVGA; + +int taskData[2][3] = + { + {0, 0, 160}, + {0, 160, 320} + }; + +static float v = -1.5; +static float vs = 0.001; + +//https://en.wikipedia.org/wiki/Julia_set#Pseudocode_for_normal_Julia_sets +int julia(int x, int y, float cx, float cy) +{ + int zx = ((x - 159.5f) * (1.f / 320.f * 5.0f)) * (1 << 12); + int zy = ((y - 99.5f) * (1.f / 200.f * 3.0f)) * (1 << 12); + int i = 0; + const int maxi = 17; + int cxi = cx ; + int cyi = cy * (1 << 12); + while(zx * zx + zy * zy < (4 << 24) && i < maxi) + { + int xtemp = (zx * zx - zy * zy) >> 12; + zy = ((zx * zy) >> 11) + cyi; + zx = xtemp + cxi; + i++; + } + return i; +} + +int colors[] = { + 0b110001, 0b110010, 0b110011, 0b100011, 0b010011, + 0b000011, 0b000111, 0b001011, 0b001111, 0b001110, 0b001101, + 0b001100, 0b011100, 0b101100, 0b111100, 0b111000, 0b110100, + 0b110000}; + +void renderTask(void *param) +{ + int *data = (int*)param; + while(true) + { + while(!data[0]) delay(1); + for(int y = 0; y < 100; y++) + for(int x = data[1]; x < data[2]; x++) + { + int c = colors[julia(x, y, -0.74543f, v)]; + vga.dotFast(x, y, c); + vga.dotFast(319 - x, 199 - y, c); + } + data[0] = 0; + } +} + +//initial setup +void setup() +{ + //initializing i2s vga (with only one framebuffer) + vga.init(vga.MODE320x200, pinConfig); + TaskHandle_t xHandle = NULL; + xTaskCreatePinnedToCore(renderTask, "Render1", 2000, taskData[0], ( 2 | portPRIVILEGE_BIT ), &xHandle, 0); + xTaskCreatePinnedToCore(renderTask, "Render2", 2000, taskData[1], ( 2 | portPRIVILEGE_BIT ), &xHandle, 1); +} + +//just draw each frame +void loop() +{ + static unsigned long ot = 0; + unsigned long t = millis(); + unsigned long dt = t - ot; + ot = t; + taskData[0][0] = 1; + taskData[1][0] = 1; + //waiting for task to finish + while(taskData[0][0] || taskData[1][0]) delay(1); + v += vs * dt; + if(v > 1.5f) + { + v = 1.5f; + vs = -vs; + } + if(v < -1.5f) + { + v = -1.5f; + vs = -vs; + } +} diff --git a/libraries/bitluni_ESP32Lib/examples/GFXWrapper/GFXWrapper.ino b/libraries/bitluni_ESP32Lib/examples/GFXWrapper/GFXWrapper.ino new file mode 100644 index 0000000..2e4e11e --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/GFXWrapper/GFXWrapper.ino @@ -0,0 +1,32 @@ +//This example shows how to use the GfxWrapper to be able to use the Adafruit GFX library with VGA +//cc by-sa 4.0 license +//bitluni + +#include +#include +#include +#include +#include + +//VGA Device +VGA6Bit vga; +GfxWrapper gfx(vga, 640, 400); + +//initial setup +void setup() +{ + //initializing i2s vga (with only one framebuffer) + //Pin presets are avaialable for: VGAv01, VGABlackEdition, VGAWhiteEdition, PicoVGA + //But you can also use custom pins. Check the other examples + vga.init(vga.MODE640x400, vga.VGABlackEdition); + //using adafruit gfx + gfx.setFont(&FreeMonoBoldOblique24pt7b); + gfx.setCursor(100, 100); + gfx.print("Hello"); +} + +//the loop is done every frame +void loop() +{ + +} diff --git a/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.h b/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.h new file mode 100644 index 0000000..1ba8a7c --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.h @@ -0,0 +1,151 @@ +//cc by-sa 4.0 license +//bitluni +#pragma once + +class Ray +{ + public: + Ray(Vector pos, Vector dir) + :p(pos), d(dir) + { + } + Vector p; + Vector d; +}; + +class Raytracable +{ + public: + float reflection; + Vector c; + Raytracable() + { + reflection = 0; + } + virtual bool intersection(Ray &ray, Vector &i, float &t) const = 0; + virtual Vector normal(Vector &i) const = 0; + virtual Vector color(Vector &p) const = 0; +}; + +class Sphere : public Raytracable +{ + public: + float r; + float r2; + Vector p; + Sphere(Vector pos, float radius) + :p(pos), + r(radius), + r2(radius * radius) + { + } + + virtual bool intersection(Ray &ray, Vector &i, float &t) const + { + Vector L = p - ray.p; + float tca = L.dot(ray.d); + if(tca < 0) return false; + float d2 = L.dot(L) - tca * tca; + if (d2 >= r2) return false; + float thc = Vector::sqrt(r2 - d2); + float ct = tca - thc; + if(t <= ct) return false; + t = ct; + i = ray.p + ray.d * ct; + return true; + } + + virtual Vector normal(Vector &i) const + { + return (i - p) * (1.f / r); + } + + virtual Vector color(Vector &p) const + { + return c; + } +}; + +class Checker : public Raytracable +{ + public: + Checker() + { + } + + virtual bool intersection(Ray &ray, Vector &i, float &t) const + { + if(ray.d[1] >= 0 || ray.p[1] <= 0) return false; + float ct = ray.p[1] / -ray.d[1]; + if(ct >= t) return false; + i = Vector(ray.p[0] + ray.d[0] * ct, 0, ray.p[2] + ray.d[2] * ct); + t = ct; + return true; + } + + virtual Vector normal(Vector &i) const + { + return Vector(0, 1, 0); + } + + virtual Vector color(Vector &p) const + { + float c = ((int)p[0] + (int)p[2] + (p[0] >= 0 ? 1 : 0)) & 1; + return Vector(0.8 + 0.2 * c, c, c); + } +}; + +const float FAR = 10000; +Vector raytrace(Raytracable **objects, int count, Ray &r, Vector &light, int depth, Raytracable *self = 0) +{ + if(depth == 0) + return Vector(0, 0, 0); + Vector i; + float t = FAR; + Raytracable *best = 0; + for(int n = 0; n < count; n++) + { + Raytracable *o = objects[n]; + if(o != self && o->intersection(r, i, t)) + best = o; + } + float fog = t * 0.02f; + float fc = 0.5f - (r.d[1] < 0 ? 0 : r.d[1]) * 0.5; + Vector fogc = Vector(fc, fc, 1.0f); + if(fog >= 1) return fogc; + if(!best) + { + return fogc; + } + Vector n = best->normal(i); + float l = light.dot(n) * 0.9; + if(l < 0) + l = 0; + else + { + Ray r2(i, light); + Vector i2; + float t2 = FAR; + for(int n = 0; n < count; n++) + { + Raytracable *o = objects[n]; + if(o == best) continue; + if(o->intersection(r2, i2, t2)) + { + l = 0; + break; + } + } + } + Vector c = (best->color(i) * (0.1f + l)) * (1 - fog) + fogc * fog; + if(best->reflection == 0) + return c; + float dn = r.d.dot(n); + float fr = (0.2f + (1+dn) * 0.8f) * best->reflection; + if(fr < 0) fr = 0; + Vector refl = r.d - n * (dn * 2); + Ray nr = Ray(i, refl); + //return Vector(fr, fr, fr); + c = raytrace(objects, count, nr, light, depth - 1, best) * fr + c * (1 - fr); + return c; +} diff --git a/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.ino b/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.ino new file mode 100644 index 0000000..8ecfe20 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/Raytracer/Raytracer.ino @@ -0,0 +1,90 @@ +//A realtime raytraycer utilizing both cores of the ESP32. Please change the pin configuration in the setup if you are not using VGA v0.1 or the Black Edition shields. +//cc by-sa 4.0 license +//bitluni + +#include +#include +#include +#include +#include "Raytracer.h" + +//VGA Device +VGA14Bit vga; + +int taskData[2][3] = + { + {0, 0, 160}, + {0, 160, 320} + }; + +static Sphere sphere(Vector(0, 0.5f, 0), 1); +static Sphere sphere2(Vector(1, 1.5f, 0.5), 0.5f); +static Checker checker; +static Raytracable *objects[] = {&sphere, &sphere2, &checker}; +static Vector light = Vector(5, 4, -5); + +void raytraceTask(void *param) +{ + static Vector p(0, 1, -10); + int *data = (int*)param; + while(true) + { + while(!data[0]) delay(1); + for(int y = 0; y < 200; y++) + for(int x = data[1]; x < data[2]; x++) + { + Vector v(float(x - 160) * (1.f / 320), float(100 - y) * (1.f / 320), 1.f); + v.normalize(); + Ray r(p, v); + Vector c = raytrace(objects, 3, r, light, 3); + vga.dotFast(x, y, vga.RGB(c[0] * 255, c[1] * 255, c[2] * 255)); + } + data[0] = 0; + } +} + +//initial setup +void setup() +{ + //we need double buffering for smooth animations + vga.setFrameBufferCount(2); + //initializing i2s vga + //Pin presets are avaialable for: VGAv01, VGABlackEdition, VGAWhiteEdition, PicoVGA + //But you can also use custom pins. Check the other examples + vga.init(vga.MODE320x200, vga.VGABlackEdition); + //setting the font + vga.setFont(Font6x8); + light.normalize(); + sphere.reflection = 0.4f; + sphere2.reflection = 0.5f; + checker.reflection = 0.2f; + sphere.c = Vector(0, 1, 0); + sphere2.c = Vector(1, 0, 1); + static uint8_t ucParameterToPass; + TaskHandle_t xHandle = NULL; + xTaskCreatePinnedToCore(raytraceTask, "Raytracer1", 2000, taskData[0], ( 2 | portPRIVILEGE_BIT ), &xHandle, 0); + xTaskCreatePinnedToCore(raytraceTask, "Raytracer2", 2000, taskData[1], ( 2 | portPRIVILEGE_BIT ), &xHandle, 1); +} + +//the loop is done every frame +void loop() +{ + taskData[0][0] = 1; + taskData[1][0] = 1; + //waiting for task to finish + while(taskData[0][0] || taskData[1][0]) delay(1); + sphere2.p.v[0] = sin(millis() * 0.0005f) * 2; + sphere2.p.v[2] = cos(millis() * 0.0005f) * 2; + sphere.p.v[1] = sphere2.p[0] * 0.3 + 1; + //setting the text cursor to the lower left corner of the screen + vga.setCursor(0, 0); + //setting the text color to white with opaque black background + vga.setTextColor(vga.RGB(0xffffff), vga.RGBA(0, 0, 0, 0)); + //printing the fps + vga.print("ms/frame: "); + static long t = 0; + long ct = millis(); + vga.print(ct - t); + t = ct; + vga.show(); +} diff --git a/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/VGA2DFeatures.ino b/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/VGA2DFeatures.ino new file mode 100644 index 0000000..50c7091 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/VGA2DFeatures.ino @@ -0,0 +1,138 @@ +//This example shows several 2d drawing features available for VGA +//You need to connect a VGA screen cable and an external DAC (simple R2R does the job) to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +//include libraries +#include +#include + +//include a sprite +#include "rock.h" + +//pin configuration +const int redPins[] = {2, 4, 12, 13, 14}; +const int greenPins[] = {15, 16, 17, 18, 19}; +const int bluePins[] = {21, 22, 23, 27}; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA14Bit vga; + +//initial setup +void setup() +{ + //we need double buffering for smooth animations + vga.setFrameBufferCount(2); + //initializing i2s vga (with only one framebuffer) + vga.init(vga.MODE320x200, redPins, greenPins, bluePins, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); +} + +//just draw each frame +void loop() +{ + //some value for color ping pong + static int c = 0; + static int d = 1; + c += d; + if (c == 0 || c == 255) + d = -d; + + //radius ping pong + static int r = 0; + static int dr = 1; + r += dr; + if (r == 0 || r == 31) + dr = -dr; + + //clear the back buffer with black and start drawing + vga.clear(0); + + int x, y; + x = 22; + y = 5; + //set the text cursor + vga.setCursor(x + 30, y); + //print the text, println also exists + vga.print("dot(x,y,c)"); + //set a single pixel. dotAdd add the colors. dotMix uses the alpha to mix the colors + vga.dot(x + 60, y + 20, vga.RGB(c, 0, 255 - c)); + + x = 170; + y = 5; + vga.setCursor(x, y); + vga.print("line(x0,y0,x1,y1,c)"); + //draw a line + vga.line(x + c / 8 + 50, y + 10, x + 32 + 40 - c / 8, y + 30, vga.RGB(0, c, 255 - c)); + + x = 15; + y = 40; + vga.setCursor(x + 10, y); + vga.print("rect(x, y, w, h, c)"); + //draw a rectangle with the given width and height + vga.rect(x + 50, y + 15, 3 + c / 8, 19 - c / 16, vga.RGB(0, c, 255 - c)); + + x = 165; + y = 40; + vga.setCursor(x, y); + //draw a filled rectangle + vga.print("fillRect(x, y, w, h, c)"); + vga.fillRect(x + 50, y + 15, 35 - c / 8, 3 + c / 16, vga.RGB(255 - c, c, 0)); + + x = 25; + y = 80; + vga.setCursor(x + 10, y); + //draw a circle with the given radius + vga.print("circle(x,y,r,c)"); + vga.circle(x + 55, y + 20, 1 + r / 4, vga.RGB(255 - c, 0, c)); + + x = 172; + y = 80; + vga.setCursor(x, y); + //draw a filled circle + vga.print("fillCircle(x,y,r,c)"); + vga.fillCircle(x + 60, y + 20, 8 - r / 4, vga.RGB(c / 2, c / 2, 255 - c)); + + x = 10; + y = 120; + vga.setCursor(x + 10, y); + //draw an ellipse + vga.print("ellipse(x,y,rx,ry,c)"); + vga.ellipse(x + 70, y + 20, 1 + r / 2, 8 - r / 4, vga.RGB(255 - c, c, 0)); + + x = 160; + y = 120; + vga.setCursor(x, y); + //draw a filled ellipse + vga.print("fillEllipse(x,y,rx,ry,c)"); + vga.fillEllipse(x + 70, y + 20, 16 - r / 2, 1 + r / 4, vga.RGB(255 - c, c / 2, c / 2)); + + x = 15; + y = 160; + vga.setCursor(x + 35, y); + vga.print("print(text)"); + //generate a string + char text[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (int i = 0; i < 10; i++) + text[i] = 33 + (i + (c >> 2)); + vga.setCursor(x + 37, y + 17); + //set the text and background color with opaque alpha (use RGBA to get a semi transparent back) + vga.setTextColor(vga.RGB(c, 255 - c, 255), vga.RGB(0, c / 2, 127 - c / 2)); + vga.print(text); + //reset the text color. no second parameter makes the background transparent + vga.setTextColor(vga.RGB(255, 255, 255)); + + x = 165; + y = 160; + vga.setCursor(x + 15, y); + vga.print("image(image,x,y)"); + //draw the imported sprite. use millis() to calculate the sprite number + //Sprites uses "image(.." internally + rock.drawMix(vga, (millis() / 50) & 15, x + 65, y + 25); + + //show the rendering + vga.show(); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/rock.h b/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/rock.h new file mode 100644 index 0000000..f2dc1fd --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGA2DFeatures/rock.h @@ -0,0 +1,200 @@ +const int rockOffsets[] = {0, 836, 1634, 2394, 3230, 4028, 4788, 5472, 6232, 6952, 7712, 8552, 9388, 10148, 10908, 11592, 12352, }; +const short rockPointOffsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; +const unsigned short rockRes[][2] = {{19, 22}, {19, 21}, {20, 19}, {22, 19}, {21, 19}, {20, 19}, {18, 19}, {19, 20}, {18, 20}, {20, 19}, {20, 21}, {22, 19}, {20, 19}, {19, 20}, {19, 18}, {19, 20}, }; +const signed short rockPoints[][2] = {{10, 11}, {10, 10}, {10, 10}, {11, 9}, {11, 9}, {9, 9}, {9, 9}, {9, 10}, {9, 10}, {10, 10}, {10, 11}, {11, 10}, {10, 9}, {9, 10}, {9, 9}, {10, 10}, }; +const unsigned short rockPixels[] = { +0, 0, 0, 0, 0, 0, 0, 24047, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 56815, 56782, 40398, 24014, 7630, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24014, 56815, 56815, 56815, 56782, 56782, 23981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22957, 56782, +56782, 56815, 56815, 56782, 56782, 55725, 21834, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 40431, 56815, 57872, 56782, 56782, 57872, 56815, 56815, 55725, 54569, 19654, 0, 0, 0, 0, 0, +0, 0, 24047, 57872, 56815, 56815, 57872, 56815, 56815, 57872, 56815, 55725, 54635, 54635, 38251, 21834, 4360, 0, 0, 0, 5483, 55725, 56782, 56815, 56815, 57872, 56815, 56815, 56815, 56782, 55725, 55692, +55692, 54635, 54602, 19654, 0, 0, 6573, 39341, 55725, 55725, 56815, 56815, 56815, 56815, 56782, 55725, 55659, 55692, 55692, 54602, 53545, 53512, 34981, 0, 6573, 55725, 56749, 55692, 55692, 55725, 55725, 56782, +56782, 55725, 55692, 54602, 54602, 53545, 53512, 52422, 52389, 51365, 2213, 21867, 55692, 55725, 55692, 54635, 55659, 55692, 55725, 55725, 55692, 54602, 52422, 52422, 51365, 51365, 51365, 51365, 51365, 18597, 38218, 54635, +54635, 54635, 53545, 53512, 54602, 54602, 54635, 54602, 53479, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 19621, 20744, 54602, 54602, 53545, 53479, 52422, 52455, 52455, 53512, 53479, 51365, 51332, 51365, 51365, 51365, +51365, 51365, 51365, 34981, 2213, 52422, 53479, 53512, 53512, 52422, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 34981, 51365, 52389, 52389, 51365, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 4360, 0, 2213, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51332, 51365, 18597, 0, 0, 0, 0, +18597, 34981, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 2180, 0, 0, 0, 0, 0, 2147, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +34981, 0, 0, 0, 0, 0, 0, 0, 2213, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, +51332, 51365, 52389, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 19621, 3270, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 18597, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 18597, 3237, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 24014, 7630, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5417, 22924, 22924, 24014, 56815, 56782, 39341, 5450, +24014, 40365, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 6540, 55692, 55725, 56815, 57872, 56815, 56782, 40398, 56782, 56782, 55725, 21834, 0, 0, 0, 0, 0, 0, 0, 6540, 55692, +56749, 56815, 56815, 56815, 57872, 56815, 56782, 55725, 55725, 54602, 20744, 0, 0, 0, 0, 6540, 22957, 39341, 55725, 56782, 56782, 56782, 56749, 56815, 55725, 55692, 55692, 55692, 54635, 54602, 20777, 0, +0, 0, 39308, 55725, 55725, 56782, 56782, 56782, 55725, 55725, 56782, 55725, 55659, 54635, 54635, 54635, 55692, 38218, 4393, 0, 0, 39308, 55725, 55725, 56782, 56782, 55725, 55692, 55692, 55725, 55692, 54602, +53545, 54602, 54635, 54602, 53545, 36038, 0, 0, 21834, 54635, 54635, 55692, 55725, 55725, 55692, 54635, 54635, 54635, 53545, 53512, 53545, 54569, 53545, 52455, 34981, 0, 0, 20744, 54602, 53545, 53545, 54569, +54635, 54635, 54602, 54635, 54602, 53545, 52422, 52455, 52455, 52422, 52389, 34981, 0, 4393, 53512, 53545, 52422, 52422, 52455, 53512, 53479, 53512, 53545, 53545, 52455, 51365, 51365, 51365, 51365, 51365, 51365, 18564, +20744, 53545, 54569, 52422, 51365, 52389, 52389, 51365, 51365, 52422, 52422, 52389, 51365, 51365, 51365, 52422, 51365, 51365, 18597, 36038, 53545, 53545, 53479, 52422, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 52422, 51365, 51365, 18597, 36005, 53512, 53545, 53512, 52455, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 19621, 2147, 36005, 52389, 52389, 52422, 51365, 51365, +51365, 51365, 52422, 52422, 51365, 51365, 52389, 51365, 51365, 52389, 34981, 2213, 0, 18564, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 53512, 53545, 52455, 52389, 51365, 51365, 51332, 51365, 2213, 0, 0, +0, 2213, 34981, 51365, 51365, 51365, 51365, 51365, 53479, 53545, 53512, 52389, 51365, 51365, 51365, 51365, 2180, 0, 0, 0, 0, 2213, 18597, 18597, 34981, 34981, 51365, 51365, 52389, 52389, 51365, 51365, +51365, 52389, 34981, 3270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3270, 34948, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 2213, 34948, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18564, 34981, 34981, 2180, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 2213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 40431, 22957, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 40431, 56815, 56782, 39341, 21867, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24014, 22957, 22957, 40398, +56815, 57872, 56815, 56815, 56782, 56782, 22957, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 56815, 56815, 56815, 56815, 56815, 56782, 56815, 56815, 56782, 56782, 55725, 38218, 2147, 0, 0, 0, +0, 0, 0, 22957, 56782, 56815, 57872, 56782, 56782, 56782, 56782, 56815, 56782, 56749, 55692, 54602, 37128, 4393, 0, 0, 0, 0, 6573, 40398, 56782, 56782, 57872, 56815, 56782, 55725, 56782, 56782, +56782, 55725, 55692, 54635, 53545, 53512, 20744, 0, 0, 0, 21867, 55725, 56749, 56782, 56782, 56782, 56782, 55725, 55725, 55725, 55692, 54635, 55692, 54635, 53545, 53512, 37128, 0, 0, 0, 38185, 55692, +55725, 55725, 55725, 56782, 56782, 56749, 55692, 55692, 55692, 55692, 55692, 55659, 53545, 53512, 52422, 18597, 0, 18597, 53512, 54602, 55692, 55692, 55692, 55725, 55725, 55692, 54635, 54602, 54635, 55692, 55692, 55692, +54635, 53512, 51365, 34981, 2180, 18597, 52389, 52422, 53512, 53512, 52455, 53512, 54602, 54602, 54602, 54635, 54635, 54635, 54602, 54635, 54602, 53479, 51365, 51365, 18564, 2213, 51365, 51365, 52422, 52389, 51365, 51365, +52422, 52455, 54602, 54635, 55692, 55692, 54635, 54602, 53512, 52422, 51365, 51365, 18597, 0, 18597, 51365, 52389, 51365, 51365, 51365, 51365, 52455, 54635, 55692, 55692, 55725, 55692, 54569, 52455, 51365, 51365, 34981, +2180, 0, 2213, 51365, 51365, 51365, 51365, 51365, 51365, 52455, 55659, 55692, 55692, 55692, 54635, 53545, 52422, 51365, 51365, 18597, 0, 0, 2180, 51365, 51365, 51365, 51365, 51365, 51365, 53479, 54635, 55659, +54635, 54635, 53545, 52455, 52389, 51365, 34981, 3270, 0, 0, 2180, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 52455, 53479, 53512, 53479, 52455, 52389, 51365, 18597, 0, 0, 0, 0, 2213, 51365, +51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 52389, 51365, 52389, 51365, 51365, 18597, 0, 0, 0, 0, 0, 18597, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 36005, 34981, 51365, 51365, 51365, +51365, 18564, 0, 0, 0, 0, 0, 0, 2147, 18597, 34981, 51365, 51365, 34981, 18564, 0, 3270, 18597, 34981, 34981, 34981, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3237, +2213, 0, 0, 0, 0, 0, 0, 0, 2180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6573, 6573, 6540, 22924, 24014, 22957, 6540, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 6573, 56749, 56815, 56815, 56815, 56815, 56815, 55725, 39308, 20777, 22924, 22957, 22957, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7663, 56815, 57872, +56815, 56782, 56782, 56815, 56782, 56749, 55725, 55725, 55725, 55692, 6540, 0, 0, 0, 0, 0, 0, 0, 0, 40398, 56815, 56815, 56815, 56782, 56782, 56815, 56815, 56815, 56782, 55725, 55725, 55725, +39341, 5450, 0, 0, 0, 0, 0, 20777, 22924, 56782, 56815, 56782, 56815, 56815, 55725, 56749, 56815, 56782, 56782, 54602, 54635, 54635, 54602, 53512, 19621, 0, 0, 0, 0, 39308, 56782, 56782, +56782, 56815, 56782, 56782, 55725, 55725, 56782, 56782, 55725, 53512, 54569, 54569, 53545, 52455, 51365, 18597, 0, 0, 22924, 55725, 56815, 56782, 56782, 56815, 56782, 55725, 55725, 55725, 55725, 55725, 55725, 54602, +53512, 53545, 53512, 51365, 51365, 34981, 0, 21834, 54635, 55692, 55725, 55725, 56782, 56782, 56782, 55725, 55725, 55692, 55692, 54635, 55692, 55659, 54602, 53512, 52455, 51365, 51365, 34981, 0, 1057, 37128, 54635, +55692, 55725, 56782, 56782, 56782, 56815, 56815, 56782, 55725, 55692, 55659, 54635, 54602, 53545, 52455, 51365, 51365, 34981, 0, 0, 18564, 53512, 54635, 54602, 54602, 54602, 55692, 56749, 56782, 56782, 56782, 55725, +55725, 55725, 54635, 53512, 52422, 51365, 51365, 18597, 0, 0, 18597, 52389, 52422, 52389, 52389, 52422, 54569, 55725, 56782, 56782, 55725, 55692, 55692, 54635, 54569, 52422, 51332, 51365, 34981, 0, 0, 0, +2213, 51365, 51365, 51365, 51365, 51332, 52455, 54635, 55692, 55725, 55692, 54635, 54602, 53545, 52455, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 18597, 34981, 51365, 51365, 51365, 51332, 52455, 53545, 53545, +52455, 53512, 53512, 52422, 51365, 51365, 51365, 51332, 18564, 0, 0, 0, 0, 0, 2213, 34981, 51365, 51365, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34948, 2180, 0, +0, 0, 0, 0, 0, 2147, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18564, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 18597, 18564, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 34981, 34981, 34981, 34981, 34981, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22924, 40398, 24014, 6573, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7663, 24014, 24014, 6573, 22957, 56782, 56815, 56815, 39341, 22957, 22924, 5450, 0, 0, 0, 0, 0, 0, 0, 6507, 24047, 56815, +56815, 56815, 56815, 56815, 56815, 56815, 56815, 56782, 55725, 55692, 38218, 20744, 0, 0, 0, 0, 0, 22891, 55692, 56782, 56815, 56815, 56815, 56815, 56815, 56782, 56749, 55725, 55725, 55692, 54635, 53545, +37095, 5417, 0, 0, 0, 5450, 55692, 55692, 55725, 56782, 56815, 56815, 57872, 56815, 56782, 55725, 55725, 56749, 55692, 54602, 53512, 52422, 19654, 0, 0, 0, 6540, 55692, 55725, 55692, 55725, 56782, +56782, 56815, 56815, 56749, 55692, 55725, 55725, 55659, 54569, 53512, 52422, 34981, 0, 0, 0, 21867, 54635, 55692, 54635, 54602, 55659, 55725, 56782, 56782, 56782, 55725, 55692, 55659, 53545, 52455, 52422, 51365, +51365, 2213, 0, 0, 38251, 55692, 55692, 54635, 53512, 52455, 54602, 55692, 55725, 55725, 55725, 55692, 52455, 52389, 51365, 51365, 51365, 51365, 19621, 0, 0, 37128, 54602, 55692, 54635, 54602, 52455, 52422, +53545, 55692, 55725, 55725, 54635, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 3303, 53512, 53545, 55692, 55725, 55692, 55659, 53512, 53479, 55692, 55725, 55692, 52455, 51365, 51365, 51365, 51365, 51332, 51332, +34981, 0, 34981, 52422, 53545, 55692, 55725, 56782, 55725, 55692, 54602, 54635, 55692, 54635, 53545, 52389, 51365, 51365, 51365, 51365, 51365, 34981, 2213, 51365, 52389, 53545, 54602, 53545, 55659, 55725, 55692, 54602, +54602, 54602, 54569, 53545, 52422, 51365, 51365, 51365, 51365, 34981, 3270, 0, 18564, 34981, 52422, 52389, 52389, 52455, 54635, 54635, 54602, 54569, 53512, 52455, 52422, 51365, 51365, 51365, 51365, 34981, 3270, 0, +0, 0, 2213, 51365, 51365, 51365, 51365, 52422, 52422, 52422, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 18597, 2213, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 0, 0, 0, 0, 0, 0, 19654, 19621, 18597, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2180, 18597, 34981, 51365, +34981, 18597, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 24014, 24014, 24047, 7597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6573, 40431, 57872, 56815, 56815, 40398, 40365, 22957, 22957, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22924, 56815, 56815, 56815, 56815, 56782, 56749, 55725, 55725, 21834, 3270, 0, 0, 0, 0, 0, 0, 8720, 24014, 40398, 40398, +56815, 56782, 56782, 56782, 55725, 55725, 55692, 55692, 54635, 37161, 20744, 0, 0, 0, 0, 7663, 40431, 56815, 56782, 56782, 55725, 55725, 56749, 56782, 55725, 55725, 55692, 54602, 52422, 53512, 54635, 20777, +0, 0, 0, 40398, 56782, 56815, 55725, 55725, 55692, 55725, 56782, 56782, 55725, 55692, 54602, 53512, 51365, 53545, 54602, 52455, 18564, 0, 20777, 55692, 55725, 56782, 55725, 55725, 55692, 55692, 55725, 55725, +55692, 54602, 53479, 51365, 51365, 52422, 52389, 51365, 34981, 0, 20744, 54635, 55692, 55725, 56782, 55725, 55692, 55692, 55725, 55725, 54569, 52455, 51365, 51365, 51332, 51332, 51365, 51365, 34981, 0, 19621, 52455, +53512, 54635, 55725, 55725, 55725, 55725, 55692, 53512, 51365, 51332, 52389, 53512, 52389, 51365, 51332, 51365, 18597, 0, 18564, 51332, 51365, 53545, 55659, 55692, 55692, 55692, 54602, 51365, 51365, 51365, 53512, 53545, +52422, 51365, 51365, 34981, 2213, 0, 18597, 51365, 51365, 52389, 52455, 53479, 53545, 54635, 55692, 52455, 52455, 53545, 54635, 53545, 52389, 51365, 34981, 2180, 0, 0, 2180, 18597, 51365, 51365, 51365, 51332, +51365, 53512, 54635, 54602, 53512, 53545, 54569, 52422, 51365, 51365, 18597, 0, 0, 0, 0, 18597, 52389, 52389, 52422, 51365, 51365, 52422, 53545, 53545, 52455, 52455, 52455, 51365, 51365, 51365, 34981, 0, +0, 0, 0, 18597, 51365, 52422, 52422, 51365, 51332, 51365, 52422, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 52389, +51365, 51365, 51365, 51365, 51365, 34981, 2213, 0, 0, 0, 0, 0, 18597, 34981, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 52389, 34981, 2180, 0, 0, 0, 0, 0, 0, +0, 3270, 18597, 34981, 34981, 51365, 51365, 51365, 51365, 51365, 34981, 18597, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18564, 34981, 18597, 18597, 2180, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 24047, 7630, 6573, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6540, 39341, 22924, 22957, 40431, 57872, 57872, 56815, 40398, 39341, 6573, 0, 0, 0, 0, 0, 0, +0, 39341, 56782, 56815, 56782, 56815, 57872, 57872, 57872, 56815, 56815, 56782, 24014, 4360, 0, 0, 0, 0, 7630, 56782, 56815, 57872, 56815, 56815, 56815, 56815, 57872, 56782, 56815, 56815, 56749, 38251, +20744, 0, 0, 0, 22957, 55725, 56782, 57872, 56782, 56782, 56782, 56782, 56815, 56782, 56749, 55725, 55692, 54602, 52422, 2213, 0, 0, 4360, 55692, 55725, 56782, 55725, 55725, 55725, 55692, 55692, 54635, +54635, 54602, 53512, 52422, 51365, 18597, 0, 22924, 38251, 55692, 55692, 55692, 55692, 55692, 55692, 54635, 54602, 53512, 53479, 52422, 51365, 51365, 51365, 18597, 6573, 55725, 55692, 54602, 54602, 54602, 54635, 54635, +54635, 53545, 52455, 51365, 51365, 51365, 51332, 51365, 51365, 2213, 22924, 55692, 54602, 53512, 53512, 54602, 54635, 54635, 54602, 52455, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 38218, 54602, 53512, 52389, +51365, 52422, 54602, 54602, 54569, 52422, 51332, 52422, 52455, 52389, 51365, 51365, 51365, 34981, 19654, 52422, 52422, 51365, 51365, 51332, 53512, 54602, 53512, 51365, 51365, 53545, 53545, 52422, 51365, 51365, 51365, 34981, +19621, 51365, 51365, 52422, 52422, 52422, 53512, 53545, 52389, 51365, 52455, 54635, 54602, 52455, 51365, 51365, 51365, 18597, 0, 34981, 51365, 52422, 51365, 52389, 52455, 52422, 51365, 52455, 54569, 54635, 54602, 52422, +51365, 51365, 51365, 2180, 0, 0, 18597, 34981, 51365, 51365, 51365, 52422, 52422, 52455, 53512, 53545, 52422, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 2213, 34981, 51365, 51365, 52389, 51365, 52389, +51365, 52422, 52389, 51365, 34981, 18597, 0, 0, 0, 0, 0, 0, 2213, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 2213, 0, 0, 0, 0, 0, 0, 0, 0, 18597, +18597, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 34981, 34981, 2213, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 2213, 18597, 3270, 0, 4360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 22924, 39308, 21834, 6573, 40398, 23981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6573, 40398, 56815, 56782, 55725, +56782, 56782, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7597, 24014, 56782, 57872, 56815, 56815, 56782, 56782, 56815, 55725, 39275, 20744, 0, 0, 0, 0, 0, 0, 7630, 39341, +56782, 56815, 56815, 56815, 56815, 57872, 56815, 56815, 55725, 55725, 54602, 20744, 0, 0, 0, 0, 39341, 56782, 56782, 56815, 56815, 56782, 56815, 56782, 56782, 56815, 57872, 55725, 55692, 54602, 53512, 21834, +0, 0, 0, 39275, 55725, 56782, 56782, 56782, 56782, 56815, 56782, 55725, 56782, 56782, 55692, 53545, 52422, 52455, 38218, 0, 0, 0, 39275, 56782, 56782, 56782, 56815, 56815, 56815, 56815, 55725, 55692, +55659, 53512, 52422, 51365, 52389, 52422, 18597, 0, 0, 38218, 55725, 56815, 56815, 56782, 56782, 56782, 56782, 55692, 54602, 53512, 52389, 51365, 51365, 51365, 51365, 34981, 0, 0, 37128, 54635, 56782, 56782, +55725, 55725, 55725, 55692, 53545, 52422, 52389, 51365, 51365, 51332, 51365, 51365, 34981, 5450, 5450, 53512, 53545, 54635, 55692, 53512, 52422, 52422, 52422, 52422, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, +0, 3270, 53512, 52455, 53479, 53545, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 4360, 53512, 52455, 52389, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 52389, 51365, 51365, 19621, 0, 0, 0, 36005, 52389, 51365, 51365, 51365, 52389, 52389, 51365, 51365, 51365, 52389, 52455, 52422, 51365, 18597, 0, 0, 0, 0, 18564, 51365, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 52422, 53512, 52422, 51365, 2213, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 52389, 52389, 51365, 34981, 0, 0, 0, 0, +0, 0, 0, 18564, 36005, 52389, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 34981, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 52389, 51365, 51365, 51365, +51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 2213, 2213, 3270, 2213, 18597, 51365, 51365, 34981, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 5450, 18597, 18597, 2213, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 40398, 40431, 22957, 24014, 41488, 40431, 22957, 22924, 22924, 5483, 0, 0, 0, 0, 0, 0, 0, 6573, 56782, 56815, 56815, 56782, 57872, 56815, 56815, 55725, 56782, 39341, 38251, 20744, 0, +0, 0, 0, 0, 22957, 56782, 56815, 57872, 56782, 56815, 56815, 56815, 56749, 56782, 56749, 56749, 54635, 20744, 0, 0, 0, 7630, 39341, 56782, 56815, 56815, 56815, 56815, 56815, 56815, 55725, 56782, +56782, 56782, 56749, 38251, 5450, 0, 0, 22924, 55692, 55692, 56782, 56815, 56815, 56815, 56782, 56782, 56749, 56815, 56815, 56782, 55692, 54602, 19687, 0, 0, 38251, 54635, 55692, 56782, 56815, 56782, 56782, +56815, 56815, 56815, 56815, 56782, 55692, 53512, 52389, 51365, 2180, 0, 19687, 54635, 55725, 55725, 56749, 55725, 55725, 56782, 56782, 56782, 55725, 55692, 53545, 52422, 51332, 51365, 2213, 0, 21834, 55692, 55725, +55692, 55692, 54635, 54635, 54635, 55692, 55725, 55692, 53545, 52422, 51365, 51365, 34981, 0, 21867, 55692, 55692, 55692, 55692, 54635, 54602, 54602, 54569, 54569, 54635, 54602, 52422, 51365, 51365, 51365, 18597, 0, +20777, 54569, 54602, 54635, 54635, 54635, 54635, 54635, 54635, 54602, 53512, 52422, 51365, 51365, 51365, 51365, 2213, 0, 3270, 36071, 54569, 54635, 54635, 55692, 55692, 55692, 55692, 54602, 52455, 51365, 51365, 51365, +51365, 51365, 2213, 0, 0, 36005, 53479, 54602, 55692, 55692, 55692, 54635, 54635, 53545, 52422, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 18597, 51365, 53479, 54602, 54635, 54602, 54569, 53545, 52422, +51365, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 0, 34981, 51365, 52455, 53512, 53512, 52455, 52422, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 3270, 0, 0, 18597, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 2213, 0, 0, 0, 18597, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18564, 0, 0, +0, 0, 4360, 36005, 34981, 51365, 52389, 51365, 51365, 51365, 52389, 34981, 34981, 18597, 2180, 2213, 0, 0, 0, 0, 0, 0, 0, 19621, 52389, 34981, 18597, 18597, 19621, 2213, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 24014, 24014, +39308, 5483, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22957, 24014, 24047, 24047, 56782, 56782, 56815, 56782, 39341, 6540, 0, 0, 0, 0, 0, 0, 0, 0, 0, +6540, 55725, 56782, 56815, 56815, 56782, 56815, 56782, 56782, 56749, 39308, 5450, 0, 0, 0, 0, 0, 24014, 25104, 25104, 39341, 56782, 56815, 56815, 56782, 56749, 56782, 56749, 56782, 56782, 55725, 38218, +1090, 0, 0, 0, 5450, 39341, 56815, 57872, 56815, 56782, 56782, 56815, 56782, 56749, 55725, 55725, 56749, 56782, 55725, 53512, 18564, 0, 0, 0, 0, 39308, 55725, 56782, 56782, 55725, 56782, 56782, +56782, 56782, 55692, 56749, 56782, 56815, 56782, 53545, 18564, 0, 0, 0, 0, 38251, 55692, 55692, 56749, 56782, 56782, 56782, 56782, 56782, 56782, 56782, 56782, 56782, 56782, 54635, 34981, 0, 0, 0, +3270, 53545, 54602, 55692, 55725, 56782, 56815, 56815, 56815, 56815, 56815, 57872, 56815, 56815, 56782, 54602, 19621, 0, 0, 0, 0, 37128, 53512, 53545, 54635, 55725, 56782, 56815, 56815, 56782, 56815, 56815, +56815, 55725, 55659, 53545, 36005, 2180, 0, 0, 0, 36038, 52422, 52422, 53512, 55692, 55725, 55725, 56749, 55725, 55725, 55692, 55692, 54635, 53512, 52422, 51365, 34981, 2213, 0, 0, 2180, 51365, 52422, +53479, 54635, 55659, 55692, 54635, 54602, 54569, 53545, 53512, 53512, 52422, 51365, 51365, 51365, 34981, 0, 0, 0, 34981, 51332, 53479, 54602, 53545, 53512, 52455, 52422, 52422, 51365, 51365, 52389, 52389, 51365, +51365, 51365, 51365, 19621, 0, 3237, 51365, 52389, 53512, 53545, 52422, 52389, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 34948, 52389, 52455, 52455, 52389, 51365, +51365, 51365, 52389, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 2213, 34981, 52389, 51365, 51365, 51332, 51332, 52422, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 34981, 18597, 0, +0, 0, 0, 2213, 34981, 51365, 51365, 51365, 51365, 52389, 52422, 51365, 51365, 51365, 51365, 51365, 51365, 2213, 0, 0, 0, 0, 0, 0, 2213, 34981, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 34981, 51365, 36005, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 52389, 51365, 51365, 51365, 18597, 0, 2213, 3270, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 3237, 18597, 18597, 34981, 18597, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25104, 40398, 40398, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24047, 56815, +56815, 56815, 56782, 40398, 22957, 6540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24047, 56782, 56782, 56815, 56815, 56815, 56782, 56782, 39341, 3303, 0, 0, 0, 0, 0, +0, 0, 22957, 40431, 40431, 57872, 56782, 56782, 56815, 56815, 56782, 56782, 56782, 55692, 20777, 0, 0, 0, 0, 0, 0, 5450, 39341, 56782, 56782, 56815, 56815, 56815, 56782, 56782, 55725, 55725, +55725, 55725, 38251, 7630, 0, 0, 0, 0, 0, 0, 22957, 56782, 56782, 56815, 56782, 56782, 55725, 55692, 54635, 55692, 56782, 56815, 56815, 40431, 8720, 0, 0, 0, 0, 0, 39308, 56782, +56782, 56749, 54602, 55692, 55725, 55725, 55692, 55725, 55725, 56749, 56749, 56782, 39341, 5450, 0, 0, 0, 24014, 56782, 56815, 56782, 54602, 51332, 52455, 54569, 55692, 55725, 56782, 56815, 56815, 56815, 56782, +55692, 37128, 2180, 0, 6540, 55692, 55725, 55725, 55725, 53545, 52389, 51332, 53512, 55692, 55725, 56782, 56815, 56815, 56782, 55692, 53545, 52422, 34948, 4360, 20744, 54602, 54602, 54602, 54569, 54602, 54635, 54602, +55725, 56782, 56782, 56782, 56782, 56782, 55692, 54602, 52455, 51365, 18597, 0, 36071, 53512, 53512, 52455, 52455, 53545, 54602, 55692, 56749, 56782, 56782, 55725, 54635, 54602, 53479, 52422, 51365, 34981, 0, 0, +19621, 52389, 52389, 51365, 51365, 51365, 52389, 53545, 54635, 54635, 54635, 54635, 53545, 52422, 51365, 51365, 51365, 34981, 0, 0, 0, 18597, 51365, 51365, 51332, 51332, 51332, 52389, 53512, 53545, 53512, 52455, +52422, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 34981, 51365, 51365, 51365, 51365, 51365, 52389, 52389, 52389, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 34981, 51365, +51365, 51365, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 18597, 0, 0, 5450, 18597, 51332, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 51332, +51365, 51365, 3270, 0, 0, 0, 0, 2180, 18597, 34981, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 34981, +34981, 51365, 51365, 51365, 51365, 51365, 34981, 18597, 3237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3270, 18597, 18597, 34981, 18597, 3270, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 5450, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40431, 40431, 40431, 40398, 40398, 40398, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 7630, 56815, 56815, 56815, 56815, 56815, 56782, 56749, 22957, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24014, 56782, 56782, 56782, 56815, +56782, 55725, 55725, 55725, 40398, 41488, 24047, 7630, 0, 0, 0, 0, 0, 0, 0, 0, 6540, 40398, 55725, 55725, 56782, 56815, 56782, 55692, 55692, 56749, 56815, 56815, 56782, 56749, 22957, 0, +0, 0, 0, 0, 0, 24014, 40431, 56815, 55725, 55692, 56782, 56815, 55725, 54635, 54635, 55725, 55725, 56782, 56782, 56782, 54635, 19687, 0, 0, 0, 0, 22924, 56749, 56782, 56815, 56782, 55725, +56815, 56782, 55725, 55692, 54635, 55725, 56782, 56815, 56815, 55725, 54569, 36038, 0, 0, 0, 5450, 54635, 55692, 55725, 55725, 56782, 56782, 56782, 55725, 54635, 54635, 55725, 56782, 56815, 56782, 56782, 54635, +53512, 36005, 0, 0, 0, 21834, 54602, 54602, 55725, 56782, 55725, 56782, 55725, 53512, 52422, 52455, 54602, 55692, 56782, 56782, 55725, 54569, 52422, 51365, 2213, 0, 0, 21834, 54602, 53512, 53545, 55725, +55725, 55725, 55659, 52422, 51332, 52455, 54635, 55725, 55725, 55692, 54602, 52455, 51332, 51365, 18597, 0, 0, 37161, 54602, 53545, 53512, 55692, 55725, 55725, 54635, 52455, 52422, 54602, 55692, 55725, 55692, 54635, +53512, 51365, 51365, 51365, 34981, 0, 2180, 52422, 54635, 55725, 55692, 55692, 54635, 53545, 53479, 52455, 52455, 53545, 54602, 54602, 53545, 53512, 52389, 51365, 51365, 51365, 34981, 0, 0, 18564, 54602, 55692, +54635, 52455, 52422, 52422, 51365, 52389, 52389, 52389, 52422, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 0, 36038, 53512, 52455, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 4360, 34981, 52389, 51365, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, +0, 2213, 34981, 51365, 51365, 51365, 51365, 51332, 51332, 51332, 51365, 51365, 51365, 51365, 51365, 34981, 2213, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 51365, 19621, 0, 0, 0, 0, 0, 0, 0, 0, 2180, 51365, 51332, 51365, 51365, 51365, 34981, 34981, 18597, 51365, 34981, 18597, 18597, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 2213, 2180, 18564, 18597, 2180, 0, 0, 0, 2180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7663, 24014, 24014, 24014, +3270, 0, 8720, 25104, 24014, 24014, 0, 0, 0, 0, 0, 0, 0, 7630, 24047, 40431, 56815, 56815, 56815, 56782, 40398, 40431, 57872, 56815, 56782, 56782, 22924, 0, 0, 0, 0, 0, +0, 40398, 56815, 56815, 56815, 56815, 56815, 56782, 55725, 56782, 56782, 56749, 56782, 56782, 54635, 20777, 0, 0, 0, 0, 5483, 55725, 56782, 56782, 56782, 56782, 56815, 56782, 55725, 55725, 55725, 56815, +56815, 56782, 54635, 37095, 0, 0, 0, 0, 4360, 55659, 56782, 55725, 55725, 56749, 56782, 56815, 55725, 55725, 56782, 56815, 56815, 56782, 55659, 37095, 0, 0, 0, 0, 21801, 55692, 56782, 55725, +55692, 55692, 56782, 56782, 56749, 55692, 55692, 55725, 56782, 55725, 54635, 52455, 2147, 0, 0, 0, 22924, 55725, 56782, 56782, 55725, 55692, 56782, 56782, 55725, 53545, 53512, 54635, 55725, 55725, 54602, 52422, +18597, 0, 0, 5483, 39308, 55725, 55725, 56782, 56782, 56782, 56782, 56782, 55692, 53479, 52455, 55659, 55725, 55692, 53545, 52422, 34981, 3270, 0, 38218, 54635, 54602, 54635, 55692, 55725, 55725, 55725, 55725, +55692, 53512, 53512, 54635, 54635, 53545, 52422, 51365, 51365, 18564, 4360, 54602, 54602, 52455, 52422, 53545, 54635, 55692, 55692, 55725, 55692, 53512, 52455, 53512, 52455, 52422, 51365, 51365, 34981, 3270, 2213, 53512, +53545, 53479, 51365, 51365, 52389, 53512, 55659, 55692, 53545, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 37161, 53545, 53512, 52455, 52422, 52422, 52422, 52422, 53479, 52422, 51365, 51365, 51365, +51365, 51365, 51365, 51365, 2213, 0, 2180, 52455, 53545, 54569, 54602, 54635, 55659, 53545, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 2180, 51365, 52422, 53545, 54635, 55692, +54635, 52455, 51365, 51365, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 18597, 0, 0, 18597, 34981, 51365, 52389, 52455, 53512, 52455, 52389, 51365, 52389, 51365, 51365, 51365, 51365, 18597, 34981, 34981, 3237, +0, 0, 0, 0, 2213, 18597, 34981, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2180, 18597, 51365, 51365, 51365, 51365, +51365, 51365, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51365, 34981, 2213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 18597, 34981, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7663, 24014, 24014, 6540, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7630, 41488, 56815, 56782, 56815, 39308, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 24014, 40431, 24047, 40398, +56815, 56782, 56815, 56815, 56782, 38251, 5450, 0, 0, 0, 0, 0, 0, 7663, 40431, 56815, 56815, 56815, 56815, 56782, 56782, 56815, 56815, 56815, 55725, 38251, 4360, 0, 0, 0, 0, 24014, +56782, 56815, 56815, 56815, 56815, 56815, 56782, 56782, 56782, 56782, 56782, 56782, 55692, 21834, 0, 0, 0, 0, 39341, 56749, 56782, 56782, 56782, 56782, 56782, 56815, 56782, 56782, 55725, 55692, 56782, 55725, +54569, 19654, 0, 0, 3303, 54602, 55692, 56782, 56782, 55725, 55725, 56749, 56815, 56782, 56782, 55725, 55692, 55725, 55659, 53512, 36005, 0, 0, 18597, 53479, 54602, 55725, 56815, 56815, 56782, 56782, 56782, +56749, 55725, 56782, 55692, 54602, 53512, 52422, 34981, 0, 0, 18597, 52422, 54602, 55692, 55725, 56782, 56782, 56782, 56749, 55725, 55725, 56782, 55692, 53479, 52389, 51365, 34981, 0, 0, 18597, 52389, 54602, +54635, 54635, 54602, 54602, 54602, 54635, 55692, 55692, 55692, 53545, 52389, 51365, 51365, 34981, 0, 0, 18564, 52422, 54602, 53545, 52389, 51332, 51365, 51365, 51365, 53545, 54602, 52455, 51365, 51365, 51365, 51365, +51365, 2213, 0, 0, 36071, 54602, 54602, 53512, 52422, 51365, 51365, 52422, 53545, 53512, 51365, 51365, 51365, 51365, 51332, 51365, 34981, 4360, 0, 37128, 54569, 53545, 53545, 53512, 52422, 53512, 54635, 55692, +53512, 51365, 51365, 51365, 51365, 51365, 34981, 19621, 0, 2180, 52389, 53512, 53545, 53545, 54569, 54602, 55692, 55725, 55692, 53512, 51365, 52389, 51365, 34981, 2180, 0, 0, 0, 0, 34948, 52422, 53545, +54569, 54602, 54602, 54635, 54602, 53545, 52422, 51365, 51365, 51365, 18597, 0, 0, 0, 0, 0, 18597, 51365, 52422, 52422, 52422, 52422, 52455, 52422, 52389, 51365, 51365, 51365, 51365, 2180, 0, 0, +0, 0, 0, 0, 18597, 51365, 51365, 51365, 51365, 52389, 51365, 51365, 51365, 51365, 51365, 34981, 0, 0, 0, 0, 0, 0, 0, 18597, 51365, 51365, 51332, 51365, 52389, 51365, 51365, 51365, +51365, 51332, 18564, 0, 0, 0, 0, 0, 0, 0, 0, 2180, 2180, 2147, 3237, 18597, 34981, 34981, 34981, 51332, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 2180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22957, 22957, 5450, 7630, 7630, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 24047, 56782, 56782, 56782, 56815, 40398, 21867, 22924, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24047, 40431, 56815, 57872, 56815, 56815, 56815, 56782, 55725, +55725, 38251, 4360, 0, 0, 0, 0, 0, 7630, 40398, 56815, 56815, 56815, 57872, 57872, 56815, 56782, 56782, 56782, 56815, 55692, 37128, 3303, 0, 0, 0, 7597, 40365, 56815, 56815, 56815, 56815, +56815, 57872, 56815, 56782, 56782, 56782, 56749, 55692, 53545, 19687, 0, 0, 0, 22924, 55692, 55725, 56782, 56815, 56815, 56782, 56782, 55725, 55725, 55725, 55725, 55692, 55692, 53545, 36038, 3270, 0, 0, +38218, 54635, 54635, 55725, 56782, 56782, 56782, 53512, 51332, 52455, 55659, 55692, 55692, 54602, 53545, 52389, 34981, 18597, 0, 36038, 53512, 53545, 54635, 55692, 55725, 55725, 53545, 51332, 52422, 54635, 55692, 55692, +54602, 53512, 51365, 51365, 34981, 3303, 34948, 52389, 53479, 54602, 54635, 55692, 55725, 54602, 52422, 54602, 55725, 56782, 55692, 54602, 53512, 51365, 51365, 19621, 0, 18597, 51365, 51365, 53512, 54635, 55692, 55692, +54635, 54635, 55692, 56815, 56782, 54635, 54569, 52422, 51365, 18597, 0, 0, 18597, 51365, 51365, 53512, 54635, 54635, 54635, 55692, 55692, 55725, 56749, 55692, 54602, 52455, 51365, 34981, 0, 0, 0, 2180, +51365, 51365, 52455, 54602, 54602, 54635, 55692, 55692, 55692, 54635, 53545, 52422, 51365, 51365, 34981, 0, 0, 0, 3237, 51365, 51365, 52422, 53479, 53545, 54635, 54635, 54602, 53545, 52455, 52422, 51365, 51365, +51365, 51365, 2213, 0, 0, 0, 34981, 51365, 51365, 51365, 52422, 53512, 53512, 52422, 52389, 51365, 51365, 51365, 51365, 51365, 51365, 18564, 0, 0, 0, 2213, 34981, 51365, 51332, 51365, 52389, 52389, +51365, 51332, 51365, 52389, 51365, 51365, 51365, 34981, 2180, 0, 0, 0, 0, 2180, 34981, 34981, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 51365, 51365, 18597, 3270, 0, 0, 0, 0, 0, +0, 2180, 0, 18597, 51365, 34981, 2180, 2180, 34981, 34981, 34981, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2213, 18597, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 8753, 41488, 24014, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24014, 40398, +22957, 41488, 56815, 56815, 7630, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7663, 41488, 56815, 56782, 56782, 57872, 56815, 56815, 39341, 7630, 0, 0, 0, 0, 0, 0, 0, +0, 24047, 56815, 57872, 56815, 56782, 56782, 56782, 56782, 56782, 55725, 39308, 20711, 0, 0, 0, 0, 0, 0, 24014, 56782, 57872, 57872, 56815, 56815, 56815, 55725, 56782, 56782, 55725, 55725, 37128, +2213, 0, 0, 0, 0, 24014, 56815, 56815, 56815, 56815, 56815, 56782, 57872, 55725, 56815, 56815, 55725, 55692, 52422, 36005, 19621, 0, 0, 22957, 56782, 56815, 56782, 56782, 56782, 56815, 56782, 56815, +56782, 57872, 56815, 55725, 54635, 52389, 51365, 18597, 0, 6507, 55692, 55725, 55725, 55725, 55725, 55725, 56782, 56782, 56815, 56782, 56815, 56815, 55725, 53545, 51365, 51365, 18564, 0, 5483, 54635, 54635, 54635, +54635, 55692, 55725, 56782, 56782, 56815, 56782, 56782, 56782, 55659, 52422, 51365, 51365, 2213, 0, 3237, 52455, 53545, 53545, 54602, 54635, 55692, 55725, 55725, 56782, 56782, 55725, 55692, 53512, 51332, 51365, 51365, +34981, 2213, 0, 34981, 52422, 52455, 53479, 53545, 54602, 55692, 55725, 55725, 55692, 54635, 53545, 52422, 51365, 51365, 51365, 51365, 18597, 0, 18597, 51365, 51365, 52389, 52422, 53512, 54569, 54635, 54635, 54602, +53512, 52422, 51365, 52389, 51365, 51365, 51365, 18564, 0, 0, 34981, 52389, 51365, 51365, 52422, 52422, 53512, 53479, 52455, 52389, 51332, 51365, 51365, 51365, 51365, 51365, 18564, 0, 0, 2213, 34981, 51365, +51365, 51365, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 3270, 0, 0, 0, 18564, 51332, 51365, 51365, 51365, 51365, 51365, 51332, 51365, 51365, 51365, 51365, 51365, 51365, 18597, +0, 0, 0, 0, 2180, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 34981, 2213, 0, 0, 0, 0, 2213, 51365, 51365, 51365, 51365, 51365, 51365, 51365, 51365, +51365, 51365, 34981, 18597, 2180, 0, 0, 0, 0, 0, 0, 18597, 51332, 51365, 51365, 51365, 19621, 18597, 34981, 34981, 34981, 3270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2180, +34981, 51332, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2180, 18597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +Sprites rock(16, rockPixels, rockOffsets, rockRes, rockPoints, rockPointOffsets, Sprites::PixelFormat::R5G5B4A2); diff --git a/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/VGA3DEngine.ino b/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/VGA3DEngine.ino new file mode 100644 index 0000000..604cc11 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/VGA3DEngine.ino @@ -0,0 +1,93 @@ +//This example displays a 3D model on a VGA screen. Double buffering is used to avoid flickering. +//You need to connect a VGA screen cable and an external DAC (simple R2R does the job) to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +//include libraries +#include +#include + +//include model +#include "thinker.h" +Mesh model(thinker::vertexCount, thinker::vertices, 0, 0, thinker::triangleCount, thinker::triangles, thinker::triangleNormals); + +//pin configuration +const int redPins[] = {2, 4, 12, 13, 14}; +const int greenPins[] = {15, 16, 17, 18, 19}; +const int bluePins[] = {21, 22, 23, 27}; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA14Bit vga; +//3D engine +Engine3D engine(1337); + +//initial setup +void setup() +{ + //need double buffering + vga.setFrameBufferCount(2); + //initializing i2s vga + vga.init(vga.MODE200x150, redPins, greenPins, bluePins, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); +} + +///a colorful triangle shader actually calculated per triangle +VGA14Bit::Color myTriangleShader(int trinangleNo, short *v0, short *v1, short *v2, const signed char *normal, VGA14Bit::Color color) +{ + //normals packed in 1 signed byte per axis + const float scaleN = 1.0f / 127.0f; + const float nx = normal[0] * scaleN; + const float ny = normal[1] * scaleN; + const float nz = normal[2] * scaleN; + //return R5G5B4 color each normal axis controls each color component + return (int(15 * nx + 16)) | (int(15 * nz + 16) << 5) | (int(7 * ny + 8) << 10); +} + +//render 3d model +void drawModel() +{ + //perspective transformation + static Matrix perspective = Matrix::translation(vga.xres / 2, vga.yres / 2, 0) * Matrix::scaling(100 * vga.pixelAspect(), 100, 100) * Matrix::perspective(90, 1, 10); + static float u = 0; + u += 0.02; + //rotate model + Matrix rotation = Matrix::rotation(-1.7, 1, 0, 0) * Matrix::rotation(u, 0, 0, 1); + Matrix m0 = perspective * Matrix::translation(0, 1.7 * 0, 7) * rotation * Matrix::scaling(7); + //transform the vertices and normals + model.transform(m0, rotation); + //begin adding triangles to render pipeline + engine.begin(); + //add this model to the render pipeline. it will sort the triangles from back to front and remove backfaced. The tiangle shader will determine the color of the tirangle. + //the RGB color gien in the second parameter is not used in this case but could be used for calculations in the triangle shader + model.drawTriangles(engine, vga.RGB(128, 70, 20), myTriangleShader); + //render all triangles in the pipeline. if you render multiple models you want to do this once at the end + engine.end(vga); +} + +//just draw each frame +void loop() +{ + //calculate the milliseconds passed from last pass + static int lastMillis = 0; + int t = millis(); + //calculate fps (smooth) + static float oldFps = 0; + float fps = oldFps * 0.9f + 100.f / (t - lastMillis); + oldFps = fps; + lastMillis = t; + //clear the back buffer + vga.clear(0); + //draw the model + drawModel(); + //reset the text cursor + vga.setCursor(0, 0); + //print the stats + vga.print("fps: "); + vga.print(fps, 1, 4); + vga.print(" tris/s: "); + vga.print(int(fps * model.triangleCount)); + vga.show(); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/thinker.h b/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/thinker.h new file mode 100644 index 0000000..12675cc --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGA3DEngine/thinker.h @@ -0,0 +1,417 @@ +namespace thinker +{ +const int vertexCount = 1294; +const int triangleCount = 2616; +const float vertices[][3] = { +0.0765, -0.2322, 0.1462, 0.0503, -0.2366, 0.1479, 0.0820, -0.2773, 0.2119, 0.0340, -0.2306, 0.2923, 0.0458, -0.2237, 0.2850, 0.0596, -0.2562, 0.2855, -0.0730, -0.1915, -0.4763, 0.0295, -0.1985, -0.4769, -0.0251, -0.1703, -0.4739, 0.1022, 0.0365, -0.0443, 0.1027, -0.0583, -0.0810, 0.1019, -0.0767, -0.1003, 0.0895, -0.2869, -0.1174, 0.0727, -0.2850, -0.1157, 0.0895, -0.2460, -0.1532, -0.2106, 0.2105, -0.4743, +-0.2724, 0.2189, -0.4759, -0.2290, 0.1345, -0.4745, -0.0189, -0.3179, 0.3725, -0.0211, -0.2602, 0.3398, -0.0196, -0.2811, 0.3342, 0.0341, -0.1717, 0.0731, 0.0516, -0.1858, 0.0944, 0.0787, -0.1815, 0.0634, 0.0417, -0.1812, -0.0987, 0.0424, -0.1851, -0.0786, 0.0572, -0.1375, -0.1000, -0.0027, -0.0647, -0.3952, -0.0500, -0.0498, -0.3778, -0.0580, -0.0617, -0.3997, -0.0425, -0.0817, -0.4258, -0.1258, -0.0732, -0.3582, +-0.1226, -0.0643, -0.3371, -0.1302, -0.0396, -0.3585, 0.0347, -0.1953, 0.3281, 0.0243, -0.2317, 0.3146, 0.0142, -0.1801, 0.3039, -0.1562, -0.1558, -0.4761, -0.1736, -0.1019, -0.4751, -0.2312, -0.0929, -0.4762, 0.0882, 0.0703, -0.0995, 0.0670, 0.0249, -0.1142, 0.0698, 0.0441, -0.1312, 0.1704, -0.1153, 0.2991, 0.1509, -0.0916, 0.3597, 0.1308, -0.1197, 0.3448, 0.1435, 0.0745, -0.3624, 0.1762, 0.0841, -0.3785, +0.1585, 0.1084, -0.3410, -0.1714, -0.0017, 0.2455, -0.1336, 0.0014, 0.3012, -0.1283, 0.0571, 0.2564, -0.0844, -0.0806, -0.2200, -0.1549, -0.0269, -0.2301, -0.1059, -0.0413, -0.2758, 0.0638, -0.3659, 0.3510, 0.0436, -0.3844, 0.3823, 0.0289, -0.3742, 0.3510, -0.1153, 0.2465, -0.4440, -0.0890, 0.2578, -0.4296, -0.0896, 0.2627, -0.4729, -0.0547, -0.2463, 0.2774, -0.0322, -0.2456, 0.2417, -0.0396, -0.2153, 0.2702, +-0.0553, 0.1617, 0.1143, -0.0287, 0.1546, 0.1828, -0.0257, 0.1688, 0.1071, 0.1038, -0.0988, 0.3573, 0.0651, -0.1168, 0.3753, 0.0630, -0.1108, 0.3514, -0.1781, 0.0176, 0.0714, -0.1946, 0.0563, 0.0923, -0.1908, 0.0731, 0.0591, -0.1216, 0.1447, 0.1045, -0.1631, 0.1144, 0.0976, -0.1713, 0.0829, 0.1385, 0.1541, -0.1853, 0.1469, 0.1510, -0.2312, 0.1111, 0.1716, -0.1912, 0.1348, -0.1228, -0.0772, 0.1326, +-0.1446, -0.1175, 0.1735, -0.1718, -0.1035, 0.1846, -0.1592, -0.0488, 0.1399, -0.1385, -0.3276, -0.1611, -0.1082, -0.3306, -0.1395, -0.1272, -0.3622, -0.0890, 0.0613, -0.3506, 0.3421, 0.0622, -0.3280, 0.3101, 0.0640, -0.3266, 0.3488, -0.0859, -0.1739, -0.2113, -0.0962, -0.1711, -0.1649, -0.0945, -0.1998, -0.1626, 0.0212, -0.0830, -0.1116, -0.0198, -0.0773, -0.1761, 0.0323, -0.0807, -0.1807, -0.0810, -0.2523, -0.0874, +-0.0965, -0.2155, -0.0908, -0.0907, -0.2371, -0.0792, 0.0832, -0.0169, -0.3979, 0.0758, 0.0051, -0.3576, 0.0910, -0.0374, -0.3777, 0.1085, -0.0048, -0.4372, 0.1288, -0.0695, -0.4727, 0.1312, -0.0114, -0.4579, -0.1798, -0.1260, -0.1105, -0.1916, -0.0047, -0.1039, -0.1663, -0.0486, -0.1303, -0.1414, -0.1351, -0.2063, -0.1175, -0.0873, -0.2990, -0.1361, -0.1022, -0.2743, -0.1204, -0.1905, -0.1355, -0.1062, -0.2053, -0.1259, +0.0689, 0.1218, 0.1394, 0.0696, 0.1164, 0.1060, 0.0299, 0.1429, 0.1181, -0.2203, 0.0308, -0.4745, -0.2985, 0.0768, -0.4757, -0.2791, -0.0072, -0.4760, -0.0254, -0.2174, 0.2303, -0.0350, -0.2461, 0.1928, 0.0537, -0.0779, -0.3196, 0.0458, -0.0542, -0.3032, 0.0375, -0.0601, -0.3389, -0.2040, 0.0160, 0.0446, -0.1939, 0.0527, 0.0568, -0.2126, 0.0273, 0.0255, 0.0745, -0.3040, 0.0102, 0.0975, -0.2793, 0.0467, +0.0871, -0.2672, 0.0264, -0.0079, -0.2457, 0.3270, -0.0040, -0.2043, 0.2997, 0.0287, -0.2020, -0.0563, 0.0425, -0.2445, -0.0644, 0.0434, -0.2207, -0.0353, -0.0107, -0.0982, 0.1573, -0.0036, -0.1256, 0.1682, -0.0377, -0.1290, 0.1621, 0.0379, -0.3271, -0.1068, 0.0567, -0.3201, -0.1054, 0.0493, -0.3176, -0.1155, 0.1918, 0.1624, -0.4063, 0.1817, 0.1614, -0.3769, 0.2002, 0.1196, -0.4339, 0.1202, -0.1666, 0.0283, +0.0997, -0.2094, 0.0436, 0.1238, -0.2300, 0.0193, 0.0239, -0.1647, 0.2509, -0.0121, -0.2055, 0.2573, 0.0027, -0.1983, 0.2443, 0.0987, -0.0791, 0.1737, 0.0833, -0.0825, 0.1916, 0.0905, -0.0437, 0.2044, 0.1517, 0.1183, -0.3267, 0.1387, 0.0991, -0.2477, 0.1481, 0.1007, -0.2798, 0.2888, 0.0250, -0.4761, 0.2971, 0.0982, -0.4758, 0.2297, 0.1104, -0.4744, 0.2285, 0.0273, -0.4741, -0.0503, -0.1001, -0.0208, +-0.0413, -0.0761, 0.0021, -0.0634, -0.0418, 0.0082, -0.2056, 0.1301, -0.4491, -0.2162, 0.1250, -0.4612, -0.1989, 0.0771, -0.4278, 0.0958, -0.1199, -0.1107, 0.1226, -0.1335, -0.1025, 0.1149, -0.0972, -0.1370, 0.0227, -0.1393, 0.2191, -0.1377, -0.1089, -0.4460, -0.1085, -0.1492, -0.4759, 0.0938, -0.3185, 0.0116, 0.0305, -0.3217, -0.0487, 0.1675, -0.2439, 0.0476, 0.1702, -0.2163, 0.0404, 0.1828, -0.1604, 0.0785, +0.0537, -0.3412, 0.3051, 0.0499, -0.3249, 0.2994, 0.1021, 0.0863, 0.2161, 0.0809, 0.1061, 0.2268, 0.1165, 0.0465, 0.3292, 0.1005, 0.0022, 0.2082, 0.1072, -0.0145, 0.2432, 0.0941, -0.0184, 0.2056, 0.1692, -0.0676, 0.3485, 0.1347, -0.2024, -0.0050, 0.0558, 0.1458, -0.0710, 0.0886, 0.1180, -0.0674, 0.0681, 0.1190, -0.0985, 0.0312, 0.1430, 0.1838, 0.0880, -0.1242, -0.4573, 0.1066, -0.0876, -0.4302, +0.0830, -0.1369, -0.4419, 0.1070, -0.1646, -0.0647, 0.1229, -0.1696, -0.0445, 0.1230, -0.1870, -0.0618, -0.0885, -0.3316, -0.0697, -0.0898, -0.2993, -0.0354, -0.1022, -0.3477, -0.0499, -0.2065, -0.2419, 0.0121, -0.1887, -0.2976, -0.0030, -0.1844, -0.2519, 0.0241, -0.0604, 0.1793, -0.1089, -0.0042, 0.1737, -0.1196, 0.0013, 0.1751, -0.1266, 0.1294, -0.2212, -0.0634, 0.1391, -0.2018, -0.1372, 0.1345, -0.1633, -0.1001, +-0.0221, -0.2132, 0.0848, -0.0208, -0.1864, 0.0816, -0.0044, -0.2150, 0.0502, -0.0577, -0.1742, -0.4691, -0.1055, -0.0878, 0.3587, -0.0601, -0.1132, 0.3882, -0.0676, -0.0856, 0.3847, -0.1668, 0.1262, -0.1219, -0.1616, 0.1261, -0.1113, -0.1324, 0.1454, -0.1054, 0.0723, 0.0762, 0.0371, 0.0754, 0.1053, 0.0190, 0.0739, 0.1270, 0.0594, -0.1174, 0.1553, 0.0616, -0.0760, 0.1533, 0.0396, -0.1275, 0.1502, 0.0237, +0.0540, -0.2934, 0.0832, 0.0536, -0.2711, 0.0468, 0.0738, -0.2861, 0.0787, 0.0915, -0.0910, -0.4106, -0.0900, -0.0456, 0.0124, -0.1076, -0.0435, 0.0340, -0.0933, -0.0688, 0.0309, 0.0649, -0.3858, 0.3857, 0.0908, -0.3797, 0.3993, 0.0352, -0.3974, 0.3951, 0.0595, 0.0345, 0.0869, 0.0355, 0.0195, 0.0571, 0.0585, 0.0466, 0.0713, 0.1390, -0.1338, -0.1214, -0.1408, -0.0816, -0.1476, -0.1449, -0.0819, -0.1355, +-0.1638, -0.0383, -0.1356, -0.0224, -0.2380, 0.1917, 0.1685, -0.1239, 0.1114, 0.1797, -0.1292, 0.0908, 0.1576, -0.1347, 0.0773, 0.0874, -0.0383, 0.1843, 0.0725, -0.0667, 0.1877, 0.0710, -0.0368, 0.1730, -0.1325, -0.2972, -0.1854, -0.1579, -0.2956, -0.1823, -0.1416, -0.2416, -0.2514, 0.0878, -0.2053, 0.1068, 0.0743, -0.1997, 0.1233, 0.0918, -0.2439, 0.1355, 0.1168, 0.0572, 0.2622, 0.1320, 0.0401, 0.3061, +0.0357, -0.1066, 0.1900, 0.0327, -0.1415, 0.2287, 0.0220, -0.1115, 0.1922, 0.0482, -0.2787, 0.1903, 0.0316, -0.2529, 0.1499, 0.0259, -0.2785, 0.1481, -0.0794, 0.1529, 0.0939, -0.0679, 0.1358, 0.1751, 0.0673, -0.1677, 0.0381, 0.0413, -0.1871, 0.0209, 0.0789, -0.1872, 0.0449, -0.1448, -0.3721, -0.0878, -0.1724, -0.3330, -0.1405, -0.0157, 0.1939, -0.0864, 0.0123, 0.1794, -0.0503, 0.0399, 0.1685, -0.0972, +-0.1636, 0.1352, 0.0140, -0.1434, 0.1478, 0.0446, 0.1111, -0.0905, -0.2442, 0.1381, -0.1199, -0.1880, 0.1420, -0.1671, -0.1714, -0.1229, -0.1183, -0.4414, -0.1223, -0.1053, -0.4016, -0.1112, -0.0794, -0.4196, 0.1707, -0.2282, 0.0894, 0.0214, -0.2503, 0.3037, 0.0508, -0.2370, 0.3277, 0.0432, -0.2453, 0.3099, -0.0815, -0.0397, 0.0447, -0.0306, -0.0208, 0.0457, -0.0330, -0.0097, 0.0634, 0.1458, -0.2617, 0.0174, +0.1463, -0.2290, 0.0194, 0.1251, -0.0807, 0.0141, 0.1014, -0.1449, 0.0407, 0.0786, -0.1924, 0.0795, 0.0865, -0.1992, 0.0609, 0.0590, -0.2168, 0.4559, 0.0359, -0.2542, 0.4909, 0.0548, -0.2719, 0.4964, 0.0715, -0.2541, 0.4770, 0.1109, -0.0024, 0.0095, 0.1038, 0.0106, 0.0202, 0.0773, 0.0042, -0.1212, 0.0769, 0.0034, -0.1125, -0.0568, -0.0952, -0.4207, -0.0697, -0.1160, -0.3806, -0.0407, -0.1114, -0.4316, +-0.1206, -0.3573, -0.0499, -0.1062, -0.3457, -0.0873, -0.0101, -0.2645, 0.2766, 0.0053, -0.2761, 0.2828, 0.0024, -0.2752, 0.2633, 0.0735, -0.1931, 0.1019, 0.0505, -0.2825, 0.2461, 0.0796, -0.2793, 0.2349, 0.0571, -0.2910, 0.2253, 0.0144, 0.0914, 0.3006, -0.0377, 0.0864, 0.2907, -0.0173, 0.0817, 0.3176, 0.1370, -0.2407, -0.0644, 0.1288, -0.2485, -0.1176, -0.1660, -0.0928, 0.3209, -0.0974, -0.1207, 0.3849, +-0.2052, 0.0305, -0.0124, -0.2007, 0.0703, 0.0032, -0.2056, 0.0475, -0.0397, -0.1441, -0.0683, 0.3241, -0.1639, -0.0541, 0.2681, -0.1757, -0.0815, 0.2914, -0.0435, 0.2453, -0.1351, -0.0545, 0.2300, -0.1108, -0.0359, 0.2325, -0.1261, 0.1381, 0.0656, -0.3481, 0.1498, 0.0848, -0.3436, -0.0512, 0.1916, -0.0694, 0.0482, 0.1355, -0.1281, 0.0529, -0.2035, 0.1284, 0.0651, 0.1300, 0.0210, 0.0965, 0.0287, -0.0703, +0.1269, 0.2529, -0.3543, 0.1439, 0.1847, -0.3307, 0.1744, 0.1644, -0.3732, 0.1766, -0.1213, 0.2026, 0.1624, -0.1462, 0.1837, 0.1808, -0.1086, 0.1888, 0.1364, -0.2002, -0.1745, 0.1165, -0.1376, -0.2380, -0.1928, -0.1191, -0.0866, -0.2068, -0.0619, -0.0579, -0.1916, -0.0628, -0.0864, 0.0487, -0.1286, 0.3508, 0.0523, -0.1360, 0.3671, 0.0531, -0.1840, 0.3856, 0.0703, 0.0907, -0.1322, 0.1002, 0.1133, -0.1356, +0.1051, 0.1278, -0.1429, -0.1317, -0.0982, -0.3196, 0.0184, -0.2284, 0.3000, -0.1796, -0.0029, -0.2953, 0.0306, -0.1583, 0.2616, 0.0831, -0.1430, 0.2588, 0.0808, -0.1408, 0.2777, 0.1357, -0.0625, -0.4743, 0.0993, -0.1327, -0.4720, 0.1798, -0.1383, -0.4761, 0.0307, -0.0179, -0.3394, 0.0457, -0.0254, -0.3180, 0.0118, -0.0328, 0.0433, 0.0065, -0.0039, 0.0318, -0.0124, -0.0342, 0.0093, -0.0833, -0.1239, -0.3540, +-0.0935, -0.1482, -0.3009, -0.1072, -0.1760, -0.3046, 0.0089, 0.0028, 0.0407, 0.0390, -0.1840, 0.1414, 0.0016, -0.1615, 0.1774, 0.0147, -0.1866, 0.1918, 0.0480, -0.3284, -0.1238, 0.0689, -0.2801, -0.1293, 0.0595, -0.3250, -0.1205, -0.1645, -0.1947, -0.1307, -0.1809, -0.1805, -0.1550, -0.1881, -0.2339, -0.1187, 0.0021, -0.2598, 0.3100, 0.0214, -0.2611, 0.2945, -0.0070, -0.2450, 0.2942, -0.0204, -0.2039, 0.2323, +-0.0139, -0.1895, 0.2152, -0.0095, -0.1553, 0.1979, -0.0461, -0.1343, -0.4308, -0.0205, -0.0261, 0.0029, -0.0218, -0.0790, -0.0235, 0.0801, -0.1251, 0.3109, 0.0494, -0.1683, 0.3212, 0.0415, -0.1643, 0.2856, -0.0614, -0.0867, -0.0622, -0.0640, -0.0791, -0.0872, -0.0446, -0.0591, -0.0804, -0.1247, 0.1306, 0.1553, 0.0374, 0.1670, -0.0199, -0.0017, -0.2741, 0.3064, -0.1618, 0.2316, -0.4382, -0.1715, 0.2268, -0.4335, +-0.1630, 0.2279, -0.2925, -0.1525, 0.2374, -0.2554, -0.1489, 0.2420, -0.2627, 0.0185, -0.0747, 0.1823, 0.0820, -0.3403, -0.0964, 0.1070, -0.3240, -0.0096, 0.0679, -0.3447, -0.0923, -0.0238, -0.2361, 0.2932, -0.0268, -0.0848, -0.0489, -0.0035, -0.1199, -0.0624, -0.0125, 0.1739, 0.0780, -0.0393, 0.1769, 0.0638, 0.0744, -0.3399, 0.2405, 0.0876, -0.3309, 0.2427, 0.0630, -0.3065, 0.2991, 0.0359, -0.3267, 0.2869, +0.1436, -0.1839, 0.1499, 0.1125, -0.1723, 0.1675, 0.1230, -0.1807, 0.1298, 0.0201, -0.3285, -0.1115, 0.0194, -0.3173, -0.0700, 0.0257, -0.3162, -0.1016, 0.0697, -0.3336, 0.1959, 0.0332, -0.3332, 0.2414, 0.0363, -0.3143, 0.2176, 0.0133, -0.1310, -0.0911, -0.0206, -0.0696, -0.0785, 0.0025, -0.0882, 0.1765, 0.0123, -0.0641, 0.1759, -0.0869, -0.2571, -0.1156, -0.0963, -0.2705, -0.1393, -0.0876, -0.2401, -0.1906, +-0.2228, -0.1377, -0.0399, -0.2148, -0.1799, 0.0116, -0.2106, -0.0869, -0.0097, 0.1716, -0.0678, 0.2484, 0.1691, -0.0626, 0.2211, 0.1049, -0.3341, -0.0885, 0.0932, -0.3228, -0.0981, 0.1216, -0.3150, -0.0085, 0.1251, -0.3041, 0.0232, 0.0818, -0.0211, -0.3161, 0.0977, -0.0611, -0.2556, 0.0948, -0.0357, -0.3185, -0.0783, -0.0971, -0.1086, -0.0214, -0.0806, -0.1993, 0.0470, -0.1950, -0.1247, 0.0277, -0.0292, -0.3618, +0.0297, -0.0476, -0.3914, 0.0323, -0.0579, -0.3899, -0.0790, -0.0388, 0.0041, -0.0795, -0.0812, -0.0166, -0.0690, -0.0862, -0.0341, -0.0943, -0.2480, -0.0631, -0.0699, -0.1493, -0.0673, -0.0644, -0.1607, -0.0418, -0.0737, 0.2616, -0.3836, -0.0297, 0.2653, -0.3849, -0.0433, 0.2587, -0.4239, 0.0550, -0.1654, -0.4430, 0.0335, -0.1669, -0.4449, 0.0375, -0.1504, -0.4650, 0.0957, -0.0874, -0.1752, 0.0598, -0.0105, -0.3140, +0.0808, -0.0821, -0.1680, 0.0600, -0.1438, 0.3229, 0.0604, -0.1098, 0.3450, 0.0211, -0.2895, -0.0713, 0.0396, -0.2895, -0.0222, 0.0103, -0.2776, -0.0709, 0.0060, -0.2159, 0.2847, 0.1025, 0.0674, -0.0741, 0.0958, 0.0852, -0.0429, -0.1492, -0.3653, -0.0581, -0.1659, -0.3346, -0.0287, -0.1860, -0.3424, -0.0481, 0.1731, -0.1266, 0.1389, 0.1696, -0.0956, 0.1824, 0.1737, -0.1267, 0.1627, 0.0268, -0.0366, -0.2569, +0.0471, -0.0466, -0.2521, -0.1725, 0.0927, -0.2296, -0.1554, 0.0441, -0.2003, -0.1610, 0.0692, -0.1848, 0.1081, 0.2712, -0.2976, 0.1039, 0.3023, -0.3707, -0.1472, 0.1808, -0.1238, -0.1710, 0.1699, -0.1894, -0.1712, 0.1426, -0.1315, 0.0422, -0.2641, -0.0356, 0.1174, -0.0687, -0.0651, 0.1285, -0.1273, -0.0469, -0.1860, -0.0115, -0.4688, -0.1879, 0.0241, -0.4145, -0.2054, 0.0167, -0.4701, 0.0980, -0.0411, 0.0400, +-0.0890, -0.2067, -0.0162, -0.0672, -0.1386, -0.0015, -0.1238, -0.0505, -0.3814, -0.1166, -0.0496, -0.4003, 0.1121, 0.2385, -0.2455, 0.1306, 0.1760, -0.2728, -0.0382, -0.2004, 0.3071, -0.0280, -0.2335, 0.3429, -0.0567, -0.2060, 0.3405, -0.2002, -0.1849, 0.0290, -0.1861, -0.0997, 0.0332, 0.1242, 0.1879, -0.2135, 0.1118, 0.1882, -0.1776, 0.1376, 0.1246, -0.1807, 0.1495, 0.1259, -0.2244, -0.1082, 0.1645, -0.0810, +-0.0882, 0.1490, -0.0993, 0.1328, -0.1571, -0.0128, 0.1216, -0.0389, -0.0422, -0.0832, -0.1267, -0.3923, -0.1066, -0.1331, -0.3497, -0.1067, -0.1233, -0.3927, -0.0905, -0.1008, 0.1637, -0.0907, -0.1164, 0.1713, -0.1188, -0.1531, 0.1997, 0.0615, -0.0674, -0.1960, 0.0605, -0.0373, -0.2602, 0.0617, -0.0247, -0.2638, -0.0024, -0.2171, 0.2896, 0.0592, 0.1207, 0.2027, -0.1849, 0.0957, 0.0856, -0.1833, 0.0400, 0.1378, +0.0576, 0.1107, 0.2552, -0.1090, -0.1969, -0.1176, -0.0944, -0.1511, -0.1172, -0.1182, 0.1589, -0.0179, -0.1470, 0.1601, -0.0558, -0.1687, 0.1302, -0.3163, -0.1951, 0.1099, -0.3808, -0.1870, 0.0915, -0.3480, -0.0784, -0.1575, 0.3889, -0.1218, -0.1540, 0.3686, -0.1803, -0.0968, 0.2376, -0.1884, -0.0708, 0.1982, -0.1621, -0.1310, 0.2081, -0.0858, -0.1418, 0.0173, -0.0976, -0.1771, 0.0111, 0.0560, -0.2491, -0.1088, +0.0094, 0.2818, -0.3911, 0.0575, 0.2941, -0.4071, 0.0409, 0.2839, -0.4253, -0.0253, -0.2003, 0.4296, -0.0426, -0.1071, 0.3873, 0.0743, -0.3444, 0.3544, 0.0899, -0.3171, 0.3595, 0.0983, -0.3465, 0.3592, -0.0090, -0.1292, -0.0103, 0.0232, -0.1566, 0.0017, 0.0037, -0.0958, 0.0260, 0.1363, 0.0169, 0.2855, 0.1321, 0.0121, 0.3289, 0.1470, 0.0028, 0.2988, -0.1596, -0.1430, -0.2347, 0.0749, 0.2660, -0.1731, +-0.0534, 0.2520, -0.1620, 0.0213, -0.2719, 0.0553, 0.0278, -0.2861, 0.1001, -0.0003, -0.2706, 0.0800, 0.1264, -0.1075, -0.1750, 0.1256, -0.1039, -0.1594, 0.1455, -0.1509, -0.1433, -0.1815, -0.0017, -0.2703, -0.1877, 0.0341, -0.2544, -0.1848, 0.0182, -0.3797, 0.0839, -0.2856, 0.1085, 0.1036, -0.3111, 0.2125, 0.0954, -0.3217, 0.2018, 0.0871, -0.3634, 0.3873, -0.1599, 0.1621, -0.2594, -0.1514, 0.2347, -0.2005, +0.0336, -0.1008, -0.1069, 0.0169, -0.0853, -0.1028, -0.1950, -0.0707, -0.4734, -0.1498, -0.0876, -0.4279, -0.1801, -0.0530, -0.4388, 0.0754, 0.1093, 0.0792, -0.1905, 0.0745, -0.3020, -0.1798, 0.1028, -0.2500, 0.1144, -0.1718, -0.0745, 0.0997, -0.1640, -0.0855, 0.0491, -0.2705, -0.1023, 0.0623, -0.2699, -0.1218, 0.0591, -0.3063, -0.0884, -0.0700, -0.0209, 0.0788, -0.0209, 0.0000, 0.0797, -0.1979, -0.0265, 0.0316, +-0.1385, -0.0340, 0.0404, -0.1786, 0.0079, 0.0606, -0.2037, 0.0150, -0.0757, -0.1950, -0.0157, -0.0555, 0.0684, -0.2683, 0.3036, 0.0810, -0.2917, 0.2876, 0.0870, -0.2539, 0.2806, 0.0858, -0.2479, 0.4039, 0.0702, -0.2360, 0.3577, 0.0776, -0.2398, 0.3907, -0.0910, -0.0398, 0.0245, -0.1273, -0.0321, 0.0448, 0.0321, -0.0775, -0.3888, 0.0394, -0.1001, -0.3922, 0.0448, -0.0806, -0.3599, 0.0315, -0.1405, 0.4040, +-0.0185, -0.1261, 0.3994, 0.0115, -0.1818, 0.4260, -0.1904, -0.2182, -0.1864, -0.1950, -0.2124, -0.1796, -0.1751, -0.1563, -0.2288, 0.0469, 0.2423, -0.1278, 0.0672, -0.1115, -0.2812, 0.0796, -0.1600, -0.2330, 0.0627, -0.1409, -0.2301, -0.2026, -0.0314, -0.0394, -0.0110, -0.1681, 0.1013, 0.0162, -0.1675, 0.0982, 0.0182, -0.1806, 0.0479, 0.0734, 0.1212, -0.0012, 0.0687, 0.1349, -0.0170, 0.0305, 0.1657, 0.0266, +-0.0780, -0.0938, -0.3364, -0.0917, -0.0756, -0.3319, -0.0771, -0.1129, -0.3145, 0.0716, -0.2992, 0.3206, 0.0701, -0.2541, 0.3196, 0.0700, -0.3094, 0.3271, -0.1034, 0.2469, -0.2184, -0.0423, 0.2539, -0.2033, -0.0716, 0.2439, -0.2258, 0.0783, -0.0277, 0.0449, 0.0355, -0.0189, 0.0454, 0.0426, -0.0687, 0.0424, 0.0597, -0.3421, -0.0950, 0.1471, -0.0568, 0.1918, 0.1582, -0.0806, 0.1725, 0.1476, -0.1021, 0.1376, +0.0056, -0.3206, 0.2510, -0.0043, -0.2866, 0.2495, 0.0121, -0.2995, 0.2270, 0.1106, 0.0339, -0.0152, 0.1806, 0.0648, -0.3953, 0.0900, 0.0156, -0.4134, 0.1874, 0.0343, -0.4293, -0.0859, 0.2561, -0.3182, -0.1209, 0.2502, -0.2953, -0.0895, 0.2498, -0.2734, -0.1416, -0.0248, 0.1057, 0.0682, -0.1065, -0.1249, -0.1368, 0.1546, -0.1156, -0.0900, 0.1484, -0.1116, -0.2071, 0.2091, -0.4708, -0.1848, 0.2361, -0.4741, +0.1056, -0.2522, -0.1502, 0.1047, -0.1469, -0.2451, -0.0453, -0.2321, 0.1176, -0.0079, -0.2412, 0.0487, -0.0138, -0.2544, 0.0831, -0.1527, -0.0890, 0.0426, -0.1742, -0.1798, 0.0365, -0.1196, -0.1204, 0.0345, 0.1261, 0.0536, -0.3561, 0.0850, 0.0167, -0.3043, 0.0679, 0.0242, -0.3361, -0.1533, 0.2511, -0.3703, 0.0880, -0.0191, -0.3509, -0.0018, 0.2786, -0.3790, 0.0583, 0.2911, -0.3686, -0.1014, -0.1271, -0.2425, +-0.0777, -0.0716, 0.1224, -0.1706, -0.3647, -0.0752, -0.2029, -0.3237, -0.0789, -0.1726, 0.1067, -0.1893, -0.1722, 0.1124, -0.1320, -0.1929, 0.0802, -0.4026, 0.0671, 0.0286, -0.3556, -0.1834, -0.0140, -0.4253, 0.0646, 0.3122, -0.4506, 0.0984, 0.3017, -0.4071, 0.1255, -0.1122, 0.1177, 0.0743, 0.0973, 0.0869, 0.0709, 0.0643, 0.0830, -0.0900, -0.0266, -0.3484, -0.1719, 0.0136, -0.3843, -0.1400, -0.0104, -0.3950, +0.0760, -0.0991, -0.1226, 0.0607, -0.0884, -0.1287, 0.0694, -0.0346, -0.1280, 0.0933, -0.0726, -0.1175, 0.0594, -0.1029, -0.1349, 0.0316, -0.1303, -0.1413, 0.1190, -0.3149, -0.0722, 0.1051, -0.3068, -0.0992, 0.1406, -0.2982, -0.0560, -0.1366, 0.2158, -0.1223, -0.1466, -0.1506, -0.1736, 0.0919, -0.2678, 0.1382, 0.0972, -0.2826, 0.1965, 0.0643, -0.3346, -0.0533, 0.0756, -0.0912, 0.0469, 0.1534, -0.1580, 0.0504, +0.1382, -0.1633, 0.0420, -0.0799, 0.1400, 0.1583, -0.0813, 0.0790, 0.2696, -0.0667, 0.0703, 0.2754, 0.0856, 0.0944, 0.1457, 0.1075, -0.1023, 0.3506, 0.1343, -0.0117, 0.3571, 0.0473, -0.1081, -0.2147, 0.2138, 0.0046, -0.4749, 0.1633, 0.0029, -0.4600, 0.0515, 0.1347, 0.1025, 0.0936, -0.0609, -0.3520, -0.1797, 0.2092, -0.3399, -0.1959, 0.2070, -0.4005, -0.0467, -0.0834, -0.0446, -0.0297, -0.0849, -0.0254, +-0.1587, -0.2619, 0.0218, 0.0251, -0.0256, 0.1347, 0.0571, -0.0313, 0.1680, -0.0164, 0.2456, -0.2441, 0.0168, 0.2698, -0.2620, 0.0383, 0.2849, -0.2932, 0.0206, -0.2815, 0.5000, 0.0011, -0.3217, 0.4913, 0.0565, -0.3246, 0.4985, 0.0166, -0.1331, -0.4433, 0.0147, -0.0923, -0.4210, -0.0254, -0.1259, -0.4458, 0.0734, -0.0829, -0.3254, 0.0926, -0.1094, -0.2803, 0.2146, 0.2795, -0.4439, 0.2230, 0.1971, -0.4576, +0.2381, 0.2213, -0.4734, -0.1523, -0.0166, 0.0612, 0.1647, -0.0297, 0.3455, 0.1447, -0.0646, 0.3748, -0.2085, -0.2988, -0.0341, -0.1475, 0.2437, -0.3296, -0.1555, 0.2302, -0.3015, -0.0962, -0.1665, -0.4741, -0.0650, -0.1543, -0.4503, -0.1056, -0.1461, -0.4538, 0.0085, -0.2542, 0.1216, -0.0544, -0.0528, 0.3753, -0.1136, -0.0211, 0.3434, -0.1344, -0.2434, 0.2446, -0.1530, -0.2189, 0.2917, -0.1555, -0.1919, 0.2530, +0.0542, -0.2682, 0.0097, 0.0463, -0.2284, 0.0120, 0.1038, -0.2340, 0.0868, 0.0978, -0.1932, 0.0815, 0.0927, -0.2480, 0.0446, 0.1063, -0.2872, -0.1156, 0.1304, -0.2832, -0.1003, -0.1060, -0.2905, -0.0092, -0.0976, -0.2284, -0.0022, -0.1158, -0.1913, 0.0296, -0.0832, 0.1572, 0.0066, -0.1123, 0.1515, 0.0052, -0.2936, 0.1564, -0.4768, 0.0539, -0.1393, -0.2143, 0.0632, -0.1835, -0.1879, 0.0478, -0.1738, -0.1904, +-0.0934, -0.1952, 0.1513, -0.0709, -0.1964, 0.1317, -0.0869, -0.2192, 0.1609, -0.1222, -0.0965, -0.1441, 0.0171, -0.2458, 0.1453, -0.0006, -0.2464, 0.1205, 0.1137, -0.0587, 0.1864, 0.1057, -0.0312, 0.2249, 0.1165, -0.0226, 0.2421, -0.0649, 0.1697, 0.0048, -0.0397, 0.1816, -0.0031, -0.1317, -0.1038, -0.3728, -0.0176, 0.1867, 0.0347, 0.0435, -0.3380, -0.0793, -0.0493, -0.0347, 0.0184, -0.0259, -0.1683, -0.4613, +-0.0238, -0.1483, -0.4595, -0.0491, -0.0517, 0.1160, -0.0606, 0.1882, -0.1141, 0.0465, -0.2674, 0.2943, -0.1686, -0.0253, 0.1283, -0.1900, 0.0058, 0.1555, -0.1738, 0.0050, 0.1259, 0.0935, -0.1444, 0.2686, 0.1157, -0.1427, 0.2369, 0.1208, -0.1476, 0.2592, 0.0129, -0.3652, 0.3482, 0.0287, -0.3440, 0.3513, 0.0536, -0.3571, 0.3435, -0.0591, -0.1911, 0.3893, -0.1026, -0.1953, 0.3586, -0.1013, -0.2161, 0.3347, +-0.0581, -0.2113, 0.3654, 0.0294, -0.1770, 0.4225, 0.0135, -0.1922, 0.4403, 0.1045, 0.0790, -0.0206, 0.0985, 0.0904, -0.0022, 0.0948, -0.0635, -0.3266, 0.1009, -0.0912, -0.2665, -0.2186, -0.1335, -0.0598, -0.2205, -0.2110, -0.0417, 0.0742, -0.3221, -0.1250, 0.0104, -0.2841, 0.2481, 0.0357, -0.1498, -0.4280, 0.0275, -0.1511, -0.4498, -0.0564, -0.1561, 0.1545, -0.0384, -0.1418, 0.1607, -0.0839, -0.2317, 0.3105, +-0.0599, -0.2063, 0.2949, 0.0106, -0.1553, -0.0651, 0.0448, -0.2756, 0.2911, 0.1403, -0.2671, -0.0894, 0.0431, -0.2251, -0.0593, -0.0159, -0.2934, 0.4793, -0.0144, -0.3400, 0.4685, -0.1039, 0.1465, 0.1390, 0.0985, 0.0065, 0.3615, 0.0206, -0.3074, 0.2857, 0.1299, 0.1137, -0.1440, 0.1292, 0.1379, -0.1577, 0.0807, 0.0458, 0.3478, 0.0539, -0.0663, -0.2182, 0.0312, -0.0736, -0.2074, 0.1290, 0.0902, -0.1439, +0.0888, 0.0666, -0.1434, -0.0132, -0.0937, -0.0219, -0.2254, 0.1294, -0.4719, 0.0771, -0.0094, -0.4194, 0.0724, -0.0021, -0.4009, 0.1023, -0.0662, -0.4386, 0.1816, 0.2584, -0.4244, 0.1901, 0.2165, -0.4318, -0.0874, -0.1867, -0.2411, -0.0909, -0.2050, -0.2077, 0.0948, -0.1159, 0.3346, 0.1221, -0.1396, 0.3145, -0.0335, -0.0622, -0.0888, 0.0603, 0.3264, -0.4745, 0.0147, 0.3974, -0.4756, 0.0145, 0.2878, -0.4742, +0.0834, -0.2530, 0.3514, -0.0146, 0.2538, -0.2374, -0.0607, -0.2631, 0.2236, -0.0749, -0.2584, 0.2751, -0.1233, -0.2651, 0.2575, 0.0457, -0.1592, 0.3951, 0.0553, -0.1902, 0.4095, -0.1150, -0.2604, 0.2335, 0.0751, 0.1245, -0.0341, 0.2456, -0.0713, -0.4762, 0.1813, -0.1753, 0.1257, -0.0181, -0.1451, 0.1712, 0.0562, -0.1423, -0.4427, 0.1924, 0.0556, -0.4154, -0.0962, 0.2371, -0.1505, -0.0899, 0.2433, -0.1822, +-0.1507, 0.2351, -0.1806, 0.0305, -0.1516, -0.1622, 0.0421, -0.1157, -0.1944, 0.0324, -0.1611, -0.1760, 0.0446, -0.0941, -0.2081, 0.0391, -0.1006, -0.1255, 0.0584, -0.2820, 0.2722, 0.0814, 0.3067, -0.3262, 0.0724, 0.2946, -0.3058, 0.0927, 0.2711, -0.2497, 0.0414, 0.1065, 0.2797, -0.1873, 0.2147, -0.4169, 0.0835, -0.0541, 0.4047, 0.0567, -0.1137, 0.3892, 0.1168, -0.0772, 0.3804, -0.0417, -0.0365, 0.0010, +-0.0652, -0.1474, 0.1617, -0.0583, -0.1169, 0.1570, -0.1493, -0.2143, 0.2411, -0.1219, -0.2212, 0.1870, 0.0415, -0.3113, -0.0926, -0.0064, -0.0270, -0.3249, 0.0180, -0.0227, -0.3573, -0.1191, -0.0203, -0.3741, 0.0188, -0.1669, -0.0195, -0.1431, -0.3263, -0.0182, 0.0782, 0.0744, 0.0248, -0.0978, -0.1544, 0.1819, -0.1752, -0.0132, -0.4185, -0.0110, -0.2155, 0.2074, 0.0939, 0.0274, -0.3005, 0.0708, -0.1071, -0.3948, +0.0713, 0.0605, 0.0497, 0.0501, 0.0431, 0.0417, 0.0320, -0.1817, 0.3165, 0.0444, -0.2268, 0.2986, 0.0199, -0.2097, 0.2927, 0.0992, -0.1037, -0.4474, -0.1321, -0.2356, 0.3022, -0.1497, -0.2055, 0.3083, -0.1916, 0.1900, -0.3703, -0.1993, 0.1587, -0.4202, -0.0603, -0.0897, -0.0313, 0.0795, -0.0089, 0.1740, 0.0993, 0.0143, 0.1770, 0.0387, -0.1169, 0.0315, 0.0670, 0.0945, -0.1233, 0.0990, -0.3218, -0.1153, +-0.0824, 0.0055, 0.3333, 0.0947, 0.2214, -0.1427, 0.0813, 0.2391, -0.1381, 0.1106, 0.1683, -0.1453, 0.0952, -0.0270, 0.3956, 0.0732, -0.0189, 0.4029, -0.0164, 0.1848, -0.0271, 0.0084, -0.3128, 0.3204, 0.0076, -0.2883, 0.2971, 0.0272, -0.3186, 0.3087, -0.1730, 0.1988, -0.3114, 0.1446, 0.2901, -0.4224, 0.1774, 0.2969, -0.4365, 0.1316, 0.3339, -0.4710, -0.0807, 0.1601, -0.0982, -0.0733, 0.1777, -0.0727, +-0.0938, -0.2791, -0.0710, -0.0870, -0.2685, -0.0989, -0.0725, -0.2051, 0.3269, 0.0119, -0.2510, 0.0393, 0.0412, -0.2274, 0.0211, -0.0392, -0.0617, 0.3866, 0.1000, -0.3099, 0.4565, 0.0827, -0.2936, 0.4839, 0.0761, -0.3374, 0.4803, -0.0150, -0.1548, -0.4743, -0.0217, 0.2638, -0.3146, -0.0586, 0.2523, -0.2982, -0.0232, 0.2429, -0.2765, -0.1899, -0.0174, -0.4743, 0.0619, -0.0100, 0.1409, 0.0696, 0.0146, 0.1407, +0.0060, -0.2992, 0.2695, 0.0161, -0.3254, 0.2646, -0.0129, -0.0396, 0.1372, 0.0367, -0.1992, 0.0250, -0.0691, -0.2319, 0.1676, 0.1129, -0.1703, 0.1132, 0.1077, -0.1653, 0.1456, 0.1141, -0.1269, 0.1226, -0.1021, -0.2380, -0.2053, -0.0442, 0.2630, -0.3401, 0.0984, -0.3640, 0.4240, 0.1056, -0.3409, 0.4050, 0.0516, 0.0284, 0.3626, 0.0377, 0.0693, 0.3278, 0.0103, 0.0600, 0.3496, -0.1020, 0.0565, 0.2880, +-0.1179, -0.1069, -0.1302, -0.1486, -0.1797, -0.1267, 0.0844, -0.3027, -0.1058, 0.0644, -0.2121, 0.3752, 0.0935, -0.2503, 0.4340, 0.1025, -0.2738, 0.4270, 0.0793, 0.2716, -0.2382, 0.0178, 0.2692, -0.2355, 0.1212, -0.3052, -0.0166, 0.0685, -0.0274, -0.1336, -0.0063, -0.3704, 0.4302, -0.0412, -0.3041, 0.4223, 0.1316, -0.1935, 0.1304, 0.1081, -0.3291, -0.1025, -0.1669, -0.1948, -0.1210, 0.0967, -0.2432, 0.1007, +0.0626, -0.1314, -0.4151, 0.0934, 0.0594, 0.0152, -0.0423, -0.0455, -0.3334, -0.0232, -0.0389, -0.3271, -0.0333, -0.0641, -0.2379, 0.0646, -0.2691, 0.2541, -0.1085, 0.1186, 0.2023, 0.0047, -0.0721, -0.2021, 0.0665, -0.0838, -0.3596, 0.0390, -0.3375, 0.3359, 0.0063, -0.3371, 0.3397, -0.0624, -0.1229, -0.0218, -0.0681, -0.0958, -0.0852, 0.1419, -0.1392, 0.2324, 0.2128, 0.0321, -0.4512, 0.0715, -0.2132, 0.4041, +-0.0820, 0.1730, -0.1110, 0.0178, -0.3630, 0.4713, -0.1318, -0.2666, 0.0131, 0.1225, 0.0405, 0.2231, 0.0499, -0.2031, 0.3441, 0.1253, -0.0353, 0.3785, -0.0464, -0.0204, 0.3567, -0.2141, -0.0355, 0.0062, -0.1256, -0.3294, -0.0173, 0.0919, -0.2975, 0.3488, -0.0556, 0.1788, 0.0404, 0.1094, 0.0381, -0.2230, 0.0970, -0.0007, -0.2164, 0.0850, 0.0066, -0.2633, -0.0611, 0.2347, -0.1117, -0.0954, 0.2240, -0.1033, +-0.0970, -0.0597, -0.3310, -0.0548, -0.2078, 0.1117, 0.1390, -0.0261, 0.2537, 0.0475, 0.0368, 0.0300, -0.0303, -0.2898, 0.4545, -0.0338, 0.0153, 0.3708, -0.0192, 0.0545, 0.3474, -0.0435, 0.0356, 0.3336, -0.1839, 0.0981, 0.0404, -0.1625, 0.0575, -0.1270, 0.0550, -0.2198, -0.1358, 0.0696, -0.3176, -0.1139, 0.0834, -0.3226, -0.1144, 0.0710, -0.2477, 0.2734, -0.1020, -0.0213, 0.0808, -0.1460, -0.0142, 0.0963, +0.0966, -0.2962, 0.3819, 0.0485, 0.0186, 0.0302, 0.0163, -0.2345, 0.1705, -0.0315, -0.0233, 0.0109, -0.0644, -0.2523, 0.1897, -0.0382, -0.2354, 0.1679, 0.0796, -0.2555, 0.0306, 0.1758, -0.0580, 0.2696, 0.0626, -0.0883, -0.1401, 0.0825, 0.0508, 0.1323, 0.0714, -0.3208, -0.0998, -0.0324, 0.2696, -0.4697, 0.0489, -0.0903, -0.1750, 0.0331, -0.1052, -0.1849, -0.2198, 0.0535, -0.4707, 0.0528, -0.2991, -0.0691, +0.0314, -0.2957, -0.0507, 0.0450, -0.2789, -0.0562, 0.0192, 0.0119, 0.0742, 0.0171, 0.0034, 0.0841, 0.1028, -0.0543, -0.4188, -0.1932, 0.0988, -0.0332, -0.1926, -0.2899, -0.1165, -0.1862, -0.2344, -0.1977, -0.2135, -0.2009, -0.0608, -0.2000, -0.2279, -0.0755, -0.2058, -0.2885, -0.0678, -0.0681, -0.0869, -0.2130, 0.1331, -0.0341, 0.2191, 0.0584, -0.2481, 0.0226, -0.1864, 0.0906, -0.1038, -0.1988, 0.0877, -0.0813, +-0.1219, -0.0748, 0.0486, -0.1004, -0.3036, -0.1000, -0.1057, -0.2958, -0.1443, 0.0881, -0.2724, 0.3943, -0.0331, -0.2076, 0.2727, -0.1059, -0.2613, -0.1736, -0.1011, -0.2189, -0.2304, -0.1565, -0.1837, -0.2697, -0.1209, -0.1380, -0.4581, 0.1312, -0.2602, 0.0106, 0.0475, -0.2013, 0.1399, 0.0976, -0.3111, 0.3795, 0.0881, -0.3100, 0.3513, 0.0152, 0.2735, -0.3000, 0.0220, 0.2716, -0.3286, 0.0304, -0.2341, 0.1454, +-0.0017, -0.2571, 0.4789, -0.0350, -0.2121, 0.4226, 0.0960, 0.0413, 0.1539, -0.0917, 0.1581, -0.0382, -0.0150, -0.0447, -0.3610, -0.1717, 0.1669, -0.3312, 0.0372, -0.0376, 0.4062, -0.0571, 0.1363, 0.2000, -0.0419, 0.1290, 0.2252, 0.0288, -0.3329, -0.0721, -0.1741, -0.0573, 0.0408, 0.2331, 0.2711, -0.4758, 0.2067, 0.3113, -0.4764, 0.2239, 0.2810, -0.4738, 0.1606, -0.1297, 0.2663, -0.0335, -0.2393, 0.1466, +-0.0268, -0.3291, 0.3743, -0.1710, 0.0150, -0.1137, -0.1783, 0.1302, -0.0598, -0.1598, 0.1348, -0.0182, 0.0280, -0.0091, 0.1086, -0.1662, 0.1434, -0.0896, -0.2156, -0.0067, -0.0082, -0.0185, -0.0827, 0.1581, -0.0574, 0.0593, 0.2937, 0.0720, -0.1380, -0.4672, -0.1866, -0.3384, -0.1063, 0.0849, 0.2620, -0.1745, -0.1881, -0.0207, 0.2032, -0.1849, -0.0464, 0.2267, -0.1669, 0.0726, -0.1211, 0.0210, -0.1137, -0.4148, +-0.1259, 0.2312, -0.1340, -0.1306, 0.2175, -0.1156, -0.0576, 0.2385, -0.2579, 0.0203, -0.2757, -0.0772, 0.0835, -0.0396, -0.1615, 0.1770, -0.0408, 0.3236, -0.1085, -0.2443, 0.3023, -0.0747, -0.1136, -0.3414, -0.0101, -0.1844, 0.4166, 0.1904, 0.1711, -0.4247, 0.1924, 0.1618, -0.4310, 0.0413, -0.1521, -0.4713, 0.1149, 0.0137, -0.1502, 0.1236, 0.0309, -0.1361, -0.0102, 0.0251, 0.3777, -0.0232, -0.0160, 0.0899, +-0.0174, -0.0233, 0.1237, -0.0933, -0.1545, -0.2546, -0.1699, -0.0322, -0.1467, -0.1602, -0.0403, -0.1833, -0.1027, -0.2397, -0.1118, 0.0810, 0.1754, -0.1396, 0.0270, -0.1081, 0.0316, -0.0219, -0.0506, -0.2445, 0.1265, 0.0006, 0.2591, 0.1124, 0.0173, 0.2100, 0.0981, -0.1209, 0.2360, 0.0867, -0.1078, 0.2132, 0.0842, -0.1249, -0.4259, -0.1364, -0.0883, -0.3632, 0.0093, -0.3848, 0.3862, 0.0490, -0.1788, 0.0414, +-0.0511, -0.0672, -0.0938, 0.0475, 0.3175, -0.4723, -0.0677, -0.0906, -0.3703, -0.0618, 0.1585, 0.0649, 0.0339, -0.2952, 0.1534, 0.0174, -0.2998, 0.2690, 0.0323, -0.1691, 0.0025, 0.0184, -0.1648, 0.2746, 0.0429, 0.1597, 0.0628, -0.0890, -0.0970, -0.2982, 0.1202, -0.1649, 0.0599, 0.1093, -0.1549, 0.0921, -0.1604, -0.1663, 0.2391, 0.0534, 0.1459, -0.1372, 0.0015, 0.2394, -0.1287, -0.0298, -0.2478, 0.3654, +0.1132, 0.0119, -0.1740, 0.1398, 0.0941, -0.2350, 0.2473, 0.2654, -0.5000, 0.1716, 0.3447, -0.4999, 0.2539, 0.2547, -0.4774, 0.2043, 0.3029, -0.4511, -0.1629, -0.0003, -0.3390, -0.1015, -0.0216, -0.3300, -0.1817, 0.0390, -0.2446, -0.1714, 0.0120, -0.1250, 0.0459, -0.3179, 0.1956, 0.1028, 0.0710, 0.1803, 0.0661, -0.1232, 0.2076, 0.0055, -0.3663, 0.3709, -0.0074, -0.3360, 0.3587, -0.1798, -0.2826, -0.1575, +0.1267, -0.1719, 0.1883, 0.1261, 0.0566, -0.1886, -0.1695, -0.1572, 0.2659, -0.1832, -0.1266, 0.2542, -0.0486, -0.1978, 0.2972, 0.2442, 0.2021, -0.4748, -0.1635, -0.0183, -0.2201, -0.1901, 0.0402, 0.1820, 0.2389, 0.1422, -0.4741, 0.0107, -0.1878, 0.2762, -0.1544, 0.2320, -0.1756, -0.0370, -0.2664, 0.4096, -0.0058, -0.3252, 0.3369, -0.0816, -0.2239, -0.1796, -0.0886, -0.2160, -0.1348, 0.2861, 0.1831, -0.4765, +-0.1523, 0.0718, 0.2226, -0.1650, 0.0888, 0.1620, 0.0711, -0.1435, -0.4308, 0.0359, -0.2961, -0.1332, 0.0229, -0.3021, -0.1222, 0.0006, -0.0653, 0.4140, 0.1250, -0.2341, 0.1071, -0.1435, -0.1254, -0.3204, 0.1042, -0.2777, 0.3881, 0.1812, 0.2311, -0.4177, 0.0936, -0.3710, 0.3938, -0.1438, -0.1707, 0.2192, 0.0272, -0.1132, 0.4011, -0.0992, -0.3232, -0.1043, -0.1644, 0.0168, -0.2139, -0.1679, 0.0556, -0.2358, +0.1134, 0.0431, 0.0031, -0.0918, -0.1025, -0.1155, 0.0935, 0.3015, -0.3981, 0.0977, 0.3078, -0.3713, 0.0166, -0.0854, 0.4141, 0.0892, -0.1206, 0.1759, -0.0740, -0.1740, 0.1500, 0.1903, -0.1689, 0.1026, -0.0413, -0.2305, 0.4049, -0.1714, 0.0046, -0.1886, -0.1972, 0.1987, -0.4523, 0.1338, 0.1521, -0.2296, 0.0730, -0.2569, 0.2954, -0.0432, 0.0652, 0.3205, -0.0889, 0.2195, -0.1009, -0.1386, -0.1270, -0.3285, +-0.0956, 0.3829, -0.4764, -0.0924, -0.2927, -0.0666, 0.0368, -0.2901, 0.0863, -0.1944, -0.2388, -0.1088, -0.1938, -0.2755, -0.1058, -0.1326, -0.1210, -0.3492, 0.1612, 0.2368, -0.3877, -0.1741, 0.1264, -0.2067, -0.1266, -0.1958, 0.1766, -0.0713, -0.0353, -0.3638, -0.0778, -0.0359, -0.3630, -0.1278, 0.2083, -0.1089, -0.0533, -0.0511, -0.3101, 0.0974, -0.1596, -0.0755, 0.0507, -0.0099, -0.3054, -0.1492, -0.1939, 0.2106, +-0.1179, -0.1742, 0.1707, 0.1243, 0.3391, -0.4751, -0.1815, -0.1053, 0.2915, -0.1626, -0.1459, 0.3071, -0.0687, -0.1003, -0.0533, 0.1049, 0.3769, -0.4761, -0.0616, -0.0663, -0.3970, -0.0896, -0.2557, -0.0344, -0.0118, -0.2024, 0.2916, 0.0759, -0.3772, 0.4321, 0.1253, 0.1653, -0.2335, 0.0996, -0.2977, 0.2498, 0.0535, -0.0748, 0.1771, 0.1279, 0.2795, -0.3854, 0.0383, -0.2184, -0.0098, 0.0590, -0.0295, -0.2827, +0.0977, -0.3405, 0.3942, 0.1187, 0.0646, -0.2575, 0.1687, -0.1082, 0.2410, -0.1714, 0.0103, 0.1088, 0.0434, 0.2280, -0.1266, -0.1449, -0.1199, -0.3035, -0.1832, -0.1852, -0.1052, -0.1992, -0.1955, -0.1654, -0.1643, -0.1413, -0.1976, -0.1259, -0.1647, -0.3141, -0.1093, -0.0233, 0.0982, -0.2091, -0.3106, -0.0934, 0.1803, -0.0761, 0.2829, -0.0087, -0.2781, 0.2808, -0.0216, 0.1134, 0.2683, -0.0196, -0.0186, 0.3913, +-0.0767, -0.1249, -0.0997, -0.2390, 0.2775, -0.4763, -0.1198, -0.1421, -0.1899, 0.0999, -0.1404, 0.2203, 0.1206, 0.0528, -0.3752, 0.1087, -0.1792, -0.4764, -0.1770, -0.0322, 0.1560, 0.1711, 0.3422, -0.4762, 0.2235, -0.1004, -0.5000, 0.1514, -0.1585, -0.5000, 0.2814, -0.0040, -0.5000, 0.2991, 0.1061, -0.5000, 0.2807, 0.1986, -0.5000, -0.2305, -0.0917, -0.5000, -0.2812, -0.0084, -0.4999, -0.2713, 0.2260, -0.5000, +0.0589, -0.1952, -0.5000, -0.0435, -0.1972, -0.5000, -0.0045, 0.3972, -0.5000, -0.1725, -0.1458, -0.4999, -0.2266, 0.2932, -0.5000, -0.2991, 0.0884, -0.4999, -0.2938, 0.1523, -0.4999, -0.1075, -0.1799, -0.5000, -0.0853, 0.3858, -0.5000, 0.0760, 0.3873, -0.5000, -0.1709, 0.3442, -0.4999, -0.1821, 0.3352, -0.4754, 0.1678, 0.2727, -0.4278, 0.0051, 0.1383, 0.2111, }; +const unsigned short triangles[][3] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, +46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, +92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 90, 110, 111, 112, 113, 114, 115, 116, 117, 118, 62, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 35, 129, 130, 131, 132, 133, 134, 135, 136, +137, 138, 139, 140, 141, 47, 140, 47, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 155, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 146, 148, 168, 169, 38, 170, 171, 126, 172, 173, 174, 175, +87, 176, 177, 178, 179, 180, 181, 182, 183, 44, 43, 184, 143, 145, 185, 186, 187, 188, 65, 189, 66, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 8, 211, 6, 212, 213, 214, +215, 216, 217, 218, 219, 220, 74, 73, 221, 221, 222, 223, 224, 225, 226, 192, 191, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 207, 237, 166, 238, 239, 240, 118, 119, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 0, +251, 0, 253, 254, 180, 255, 256, 257, 258, 259, 260, 261, 64, 262, 263, 264, 265, 266, 267, 268, 83, 269, 270, 271, 223, 272, 273, 274, 275, 276, 277, 278, 279, 78, 77, 280, 281, 282, 283, 284, 285, 286, 287, 288, 174, 143, 289, 290, +23, 291, 292, 293, 294, 295, 293, 295, 296, 297, 298, 289, 299, 41, 300, 301, 302, 303, 198, 304, 305, 306, 307, 308, 22, 309, 291, 310, 311, 312, 313, 314, 315, 316, 317, 206, 212, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, +329, 330, 154, 331, 269, 202, 271, 188, 332, 22, 333, 309, 334, 220, 219, 11, 300, 335, 336, 337, 338, 339, 340, 341, 276, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 109, 108, 353, 281, 283, 354, 355, 54, 53, 356, 357, 358, +359, 360, 361, 362, 122, 363, 364, 365, 366, 367, 368, 369, 285, 370, 235, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 302, 386, 303, 387, 388, 366, 389, 390, 391, 392, 393, 394, 73, 75, 395, 187, 186, 396, +19, 380, 397, 398, 399, 58, 400, 401, 402, 403, 256, 258, 404, 405, 406, 380, 407, 397, 408, 409, 388, 66, 410, 411, 318, 212, 323, 412, 413, 414, 412, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 409, 408, 425, 408, 426, +403, 427, 428, 429, 430, 431, 432, 433, 434, 435, 339, 341, 436, 435, 341, 437, 404, 438, 439, 440, 405, 441, 442, 443, 393, 444, 445, 446, 132, 24, 122, 447, 448, 122, 448, 449, 450, 451, 452, 440, 173, 280, 453, 96, 454, 453, 454, 455, +213, 212, 319, 456, 457, 458, 459, 460, 461, 462, 463, 464, 347, 465, 466, 467, 468, 469, 3, 354, 470, 9, 471, 472, 473, 474, 475, 476, 477, 478, 479, 363, 480, 481, 482, 483, 484, 336, 485, 486, 487, 488, 489, 469, 468, 490, 491, 193, +492, 493, 494, 495, 290, 289, 496, 455, 497, 498, 499, 279, 337, 500, 501, 502, 503, 504, 505, 506, 433, 507, 508, 509, 507, 509, 510, 511, 512, 217, 513, 491, 514, 513, 514, 289, 515, 516, 517, 518, 519, 520, 521, 522, 523, 281, 380, 35, +354, 524, 281, 179, 178, 525, 75, 526, 71, 75, 71, 527, 143, 185, 513, 525, 528, 179, 529, 530, 454, 531, 511, 532, 533, 534, 535, 536, 319, 537, 538, 539, 81, 538, 81, 540, 541, 542, 496, 541, 496, 497, 543, 132, 446, 544, 545, 546, +547, 548, 213, 549, 550, 551, 552, 553, 554, 555, 556, 557, 353, 558, 109, 326, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 55, 551, 573, 401, 574, 575, 92, 576, 577, 578, 38, 579, 578, 579, 580, 218, 220, 581, +582, 568, 583, 584, 166, 585, 586, 587, 588, 589, 286, 590, 591, 592, 593, 322, 594, 595, 596, 597, 598, 599, 600, 601, 602, 284, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 559, 326, 613, 614, 615, 616, 434, 617, 345, 618, 619, 620, +116, 115, 17, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 374, 376, 636, 335, 9, 11, 637, 638, 639, 640, 641, 642, 643, 298, 297, 644, 645, 646, 257, 168, 258, 304, 85, 305, 647, 648, 649, 79, 82, 650, +167, 651, 165, 652, 217, 653, 15, 654, 655, 656, 657, 342, 658, 659, 660, 661, 662, 663, 664, 665, 666, 647, 456, 667, 668, 100, 99, 669, 670, 544, 107, 671, 108, 672, 518, 79, 673, 475, 674, 487, 675, 676, 487, 676, 488, 534, 677, 535, +666, 678, 664, 569, 493, 679, 680, 545, 681, 682, 637, 639, 236, 683, 684, 505, 201, 662, 685, 686, 687, 688, 689, 690, 688, 690, 691, 26, 692, 693, 694, 695, 696, 487, 486, 697, 90, 698, 110, 699, 700, 570, 406, 701, 636, 702, 264, 290, +703, 288, 704, 705, 706, 707, 113, 112, 708, 69, 466, 709, 556, 710, 557, 120, 614, 711, 712, 713, 103, 714, 66, 114, 715, 100, 443, 592, 229, 603, 716, 667, 717, 582, 535, 677, 159, 718, 719, 200, 720, 201, 428, 721, 722, 723, 724, 725, +726, 727, 728, 729, 730, 731, 732, 733, 614, 734, 735, 736, 593, 737, 70, 473, 85, 304, 184, 738, 739, 200, 740, 475, 741, 742, 648, 348, 347, 69, 743, 744, 745, 261, 260, 746, 747, 748, 212, 749, 750, 751, 170, 743, 745, 752, 128, 753, +754, 755, 756, 148, 147, 383, 695, 757, 758, 759, 760, 761, 223, 762, 763, 116, 16, 764, 106, 240, 239, 765, 766, 767, 768, 769, 770, 52, 771, 238, 746, 772, 773, 774, 775, 776, 777, 778, 331, 779, 498, 278, 623, 396, 780, 781, 137, 374, +781, 374, 636, 782, 284, 602, 782, 602, 450, 386, 783, 784, 672, 589, 785, 204, 786, 202, 787, 3, 5, 788, 789, 790, 791, 792, 793, 794, 795, 796, 41, 335, 300, 797, 798, 799, 797, 799, 800, 801, 802, 293, 803, 622, 804, 805, 806, 733, +432, 807, 808, 809, 404, 406, 810, 310, 642, 811, 812, 460, 618, 813, 814, 815, 61, 816, 646, 101, 713, 817, 552, 409, 818, 381, 470, 819, 758, 317, 820, 24, 132, 727, 821, 822, 823, 221, 73, 556, 180, 824, 825, 381, 818, 826, 827, 352, +210, 658, 208, 824, 180, 828, 829, 830, 480, 831, 351, 832, 552, 366, 833, 401, 400, 574, 163, 834, 164, 835, 836, 837, 735, 838, 839, 492, 580, 679, 840, 89, 841, 842, 843, 45, 533, 535, 582, 389, 842, 466, 426, 844, 92, 845, 846, 847, +628, 600, 848, 586, 132, 543, 724, 723, 849, 456, 59, 667, 850, 851, 852, 349, 853, 854, 852, 855, 850, 71, 526, 72, 856, 396, 622, 712, 359, 361, 712, 361, 857, 340, 78, 858, 503, 502, 130, 814, 859, 619, 461, 860, 459, 644, 646, 861, +862, 863, 630, 862, 630, 864, 865, 866, 711, 865, 711, 867, 827, 826, 509, 520, 540, 80, 344, 346, 104, 595, 105, 346, 868, 121, 711, 576, 94, 869, 818, 787, 870, 871, 872, 873, 820, 131, 25, 180, 528, 874, 149, 774, 682, 667, 741, 647, +90, 111, 91, 875, 717, 667, 864, 402, 575, 876, 877, 878, 731, 301, 303, 387, 879, 388, 880, 519, 881, 882, 883, 749, 113, 683, 581, 137, 421, 884, 362, 885, 886, 657, 656, 615, 685, 887, 33, 131, 888, 817, 741, 667, 716, 474, 889, 720, +381, 281, 524, 219, 218, 890, 606, 120, 122, 195, 185, 316, 891, 880, 813, 498, 279, 278, 892, 579, 499, 118, 893, 384, 894, 665, 329, 895, 227, 715, 2, 312, 311, 136, 859, 814, 896, 897, 218, 362, 886, 447, 42, 41, 299, 898, 34, 36, +899, 900, 283, 102, 901, 190, 902, 903, 750, 904, 717, 905, 159, 161, 906, 907, 908, 181, 635, 909, 264, 40, 910, 188, 438, 12, 911, 912, 707, 706, 490, 9, 514, 913, 914, 915, 916, 828, 917, 409, 552, 833, 478, 477, 341, 617, 595, 346, +269, 918, 270, 861, 158, 142, 409, 833, 388, 710, 556, 824, 919, 920, 921, 741, 716, 922, 923, 924, 925, 202, 926, 927, 95, 928, 929, 799, 815, 930, 391, 358, 389, 499, 579, 279, 330, 329, 664, 931, 659, 932, 786, 204, 328, 328, 327, 786, +608, 933, 548, 934, 935, 936, 8, 937, 783, 440, 280, 77, 938, 939, 940, 492, 494, 941, 267, 473, 673, 907, 942, 943, 944, 640, 945, 946, 721, 428, 210, 947, 659, 658, 948, 770, 40, 335, 41, 949, 950, 951, 430, 952, 431, 647, 939, 953, +934, 954, 955, 956, 957, 958, 405, 437, 439, 912, 959, 748, 239, 960, 961, 456, 647, 953, 484, 500, 336, 12, 438, 962, 144, 704, 145, 12, 757, 911, 854, 601, 963, 627, 177, 414, 964, 934, 965, 424, 312, 259, 836, 678, 98, 966, 967, 559, +968, 694, 696, 300, 969, 299, 293, 296, 964, 441, 463, 462, 970, 822, 971, 223, 763, 272, 182, 775, 183, 972, 76, 416, 244, 703, 704, 834, 17, 115, 437, 973, 694, 106, 974, 104, 832, 42, 299, 57, 794, 796, 253, 699, 975, 442, 441, 462, +976, 227, 895, 298, 977, 633, 22, 291, 23, 522, 521, 464, 432, 345, 807, 978, 979, 980, 981, 311, 310, 150, 246, 151, 930, 504, 800, 683, 236, 581, 943, 908, 907, 982, 705, 823, 861, 142, 47, 983, 445, 980, 715, 805, 732, 715, 732, 984, +796, 795, 985, 985, 795, 986, 796, 921, 176, 358, 357, 791, 987, 455, 988, 416, 340, 989, 990, 861, 646, 205, 316, 206, 293, 964, 991, 255, 180, 556, 451, 228, 230, 992, 926, 202, 282, 963, 600, 936, 728, 993, 994, 761, 662, 995, 254, 255, +34, 996, 35, 386, 784, 303, 878, 997, 916, 563, 659, 931, 563, 931, 561, 769, 813, 618, 717, 875, 905, 998, 747, 933, 434, 506, 999, 889, 304, 1000, 628, 848, 1001, 1002, 777, 762, 1003, 1004, 1005, 1006, 1007, 327, 975, 570, 226, 108, 1008, 32, +983, 93, 445, 173, 287, 174, 120, 711, 121, 208, 1009, 209, 555, 557, 1010, 370, 365, 1011, 486, 488, 215, 1012, 971, 822, 440, 171, 405, 472, 471, 187, 48, 338, 152, 1013, 1014, 1015, 124, 1016, 125, 675, 483, 1017, 12, 438, 695, 767, 766, 1018, +875, 667, 399, 984, 732, 120, 984, 120, 606, 962, 1019, 1020, 4, 598, 1021, 392, 718, 452, 9, 335, 471, 880, 136, 814, 830, 94, 983, 123, 125, 999, 1022, 1023, 737, 397, 919, 20, 1001, 848, 1024, 1025, 890, 1011, 893, 1026, 373, 902, 798, 903, +118, 383, 147, 292, 756, 144, 933, 747, 548, 782, 161, 1027, 829, 94, 830, 168, 135, 258, 1028, 948, 1029, 203, 332, 204, 1030, 226, 225, 1010, 1031, 435, 969, 689, 1032, 1033, 684, 683, 1033, 683, 708, 920, 919, 397, 404, 1034, 438, 847, 60, 1035, +643, 472, 803, 656, 758, 757, 102, 359, 103, 621, 804, 622, 693, 1036, 1037, 164, 834, 1038, 389, 358, 842, 1039, 1040, 1041, 898, 390, 34, 718, 159, 906, 136, 880, 881, 1014, 958, 315, 1042, 234, 1043, 985, 921, 796, 1000, 759, 994, 974, 110, 377, +837, 1044, 191, 962, 1020, 12, 1016, 272, 1045, 1046, 610, 1047, 666, 463, 441, 1048, 1049, 1050, 1051, 444, 771, 696, 695, 758, 637, 1052, 436, 128, 1053, 753, 868, 1036, 121, 782, 1027, 285, 1054, 105, 1055, 230, 1056, 541, 1057, 1058, 430, 1057, 430, 929, +848, 599, 1059, 118, 1060, 63, 93, 983, 94, 966, 559, 873, 1061, 1062, 952, 217, 512, 653, 249, 1063, 250, 1064, 277, 169, 1065, 287, 968, 859, 372, 371, 627, 414, 596, 333, 22, 1066, 445, 1051, 980, 1067, 1068, 1001, 810, 870, 310, 194, 193, 491, +953, 1069, 1070, 555, 1010, 776, 260, 333, 1071, 1072, 547, 1073, 1074, 943, 1033, 770, 769, 1009, 1060, 816, 63, 703, 174, 288, 762, 1075, 763, 776, 1052, 774, 1076, 886, 885, 1076, 885, 979, 414, 597, 596, 534, 1077, 904, 67, 878, 877, 876, 917, 1078, +921, 945, 423, 139, 587, 374, 199, 201, 505, 1079, 314, 1080, 171, 172, 1081, 546, 1035, 458, 1082, 592, 591, 1083, 1084, 1085, 843, 1086, 43, 773, 1029, 1087, 868, 711, 866, 432, 434, 345, 1088, 971, 18, 1089, 240, 106, 1090, 1045, 1091, 234, 1092, 1043, +1093, 532, 511, 595, 594, 105, 269, 271, 203, 1094, 434, 999, 881, 519, 1095, 755, 754, 972, 695, 973, 757, 3, 470, 4, 32, 353, 108, 494, 493, 1038, 707, 1096, 263, 259, 312, 2, 860, 461, 1097, 673, 1098, 268, 464, 565, 462, 913, 507, 500, +913, 500, 1099, 1100, 1101, 324, 320, 322, 595, 679, 686, 569, 170, 38, 743, 1089, 1102, 1017, 1103, 605, 604, 14, 543, 1018, 1104, 697, 1105, 658, 210, 659, 248, 250, 369, 723, 1106, 632, 723, 632, 631, 489, 1107, 469, 458, 457, 546, 1032, 521, 1108, +274, 276, 806, 149, 682, 951, 321, 320, 125, 557, 1109, 1031, 660, 659, 563, 815, 1110, 851, 1111, 368, 367, 722, 403, 428, 608, 1112, 609, 913, 915, 827, 1113, 142, 1114, 416, 418, 972, 937, 1115, 729, 1116, 1117, 299, 958, 1014, 1118, 1081, 172, 420, +725, 1070, 1069, 509, 508, 913, 509, 913, 827, 151, 246, 245, 581, 896, 218, 1119, 1092, 1120, 1111, 1121, 368, 238, 1122, 1123, 1124, 96, 95, 252, 251, 309, 341, 340, 478, 1115, 7, 1097, 352, 915, 1125, 1058, 84, 248, 210, 620, 947, 636, 138, 1034, +580, 579, 892, 554, 553, 1126, 619, 371, 21, 705, 263, 262, 550, 88, 1068, 980, 1127, 983, 213, 536, 547, 995, 1128, 1129, 753, 468, 752, 899, 596, 3, 218, 1011, 890, 1130, 1131, 150, 853, 348, 68, 495, 289, 298, 160, 879, 161, 192, 1132, 976, +525, 708, 112, 31, 498, 1133, 568, 582, 677, 1134, 56, 233, 23, 266, 1135, 1, 333, 260, 853, 801, 854, 394, 1136, 844, 308, 641, 306, 880, 891, 520, 1039, 588, 884, 546, 1137, 847, 738, 184, 1109, 402, 742, 400, 411, 780, 1002, 756, 127, 754, +626, 1111, 1138, 999, 591, 123, 905, 534, 904, 885, 362, 479, 1138, 1111, 302, 1124, 95, 929, 1103, 449, 729, 868, 1037, 1036, 190, 1097, 102, 762, 222, 1139, 840, 1121, 89, 865, 693, 1037, 562, 1140, 261, 64, 66, 411, 185, 1065, 316, 308, 1141, 810, +671, 90, 89, 845, 1137, 680, 601, 991, 599, 1142, 264, 909, 518, 80, 79, 575, 402, 401, 391, 1143, 356, 1144, 220, 334, 1144, 334, 623, 624, 626, 1138, 520, 519, 880, 625, 1008, 1145, 1146, 144, 1147, 373, 1071, 1066, 520, 1148, 540, 964, 965, 599, +1149, 1125, 1150, 678, 99, 98, 932, 561, 931, 922, 1077, 574, 503, 19, 1151, 91, 841, 89, 894, 1005, 523, 894, 523, 665, 55, 57, 796, 9, 490, 10, 158, 861, 990, 1152, 1003, 1153, 1084, 1154, 1155, 1084, 1156, 1154, 867, 765, 767, 1012, 821, 1073, +106, 105, 1089, 412, 422, 572, 635, 364, 1126, 734, 736, 1083, 858, 476, 478, 1084, 1157, 734, 1084, 734, 1085, 1016, 72, 526, 86, 796, 176, 359, 712, 103, 1158, 1159, 54, 843, 793, 1086, 616, 615, 766, 1127, 479, 983, 459, 811, 460, 596, 628, 627, +1082, 661, 592, 583, 1160, 481, 384, 372, 385, 557, 710, 738, 785, 1119, 1120, 1076, 448, 886, 272, 763, 531, 1089, 1017, 1161, 507, 913, 508, 439, 437, 694, 332, 1149, 204, 422, 424, 1162, 906, 161, 450, 708, 178, 1163, 865, 1037, 866, 357, 356, 257, +357, 257, 1164, 177, 629, 87, 244, 243, 703, 1069, 940, 723, 794, 1165, 1166, 591, 506, 1082, 588, 1034, 884, 775, 774, 151, 57, 56, 1165, 183, 151, 245, 466, 465, 389, 781, 701, 1081, 45, 878, 709, 880, 814, 813, 625, 1145, 626, 1110, 852, 851, +33, 887, 498, 906, 452, 718, 1047, 1167, 1046, 744, 743, 211, 467, 469, 1107, 1098, 673, 674, 211, 8, 783, 416, 989, 1168, 972, 418, 949, 510, 1169, 1153, 465, 390, 389, 366, 554, 364, 1148, 1170, 1171, 83, 248, 84, 816, 1172, 930, 228, 451, 450, +1173, 1083, 736, 1174, 1123, 1122, 1091, 272, 531, 394, 426, 718, 590, 1043, 1119, 910, 42, 350, 161, 879, 1027, 714, 114, 113, 757, 12, 656, 1100, 49, 1175, 982, 51, 959, 538, 1101, 539, 1176, 1114, 157, 1177, 146, 1143, 635, 1126, 909, 4, 1021, 5, +1178, 864, 575, 835, 645, 836, 22, 21, 1066, 1179, 18, 971, 1180, 1166, 18, 898, 36, 391, 346, 105, 104, 185, 145, 1065, 934, 964, 296, 1181, 841, 1182, 1183, 1083, 1173, 962, 13, 1019, 872, 966, 873, 809, 406, 636, 1184, 982, 395, 144, 290, 266, +227, 976, 1132, 869, 26, 576, 484, 871, 873, 1184, 1185, 1175, 932, 947, 753, 1056, 229, 592, 976, 1186, 192, 702, 495, 633, 421, 1187, 1188, 1119, 1043, 1092, 884, 138, 137, 933, 608, 1189, 1190, 77, 76, 958, 957, 315, 658, 1009, 208, 762, 223, 222, +883, 855, 749, 1191, 610, 612, 961, 960, 530, 1120, 1092, 721, 1059, 1192, 1024, 1059, 965, 1192, 1193, 1113, 839, 1103, 729, 812, 934, 936, 954, 686, 685, 1159, 1017, 1102, 215, 694, 973, 695, 1163, 178, 995, 1017, 215, 676, 954, 232, 1194, 489, 133, 132, +1195, 1148, 520, 1055, 1045, 1090, 853, 877, 607, 584, 193, 195, 877, 1196, 607, 550, 1068, 1067, 877, 876, 1196, 938, 1069, 953, 604, 122, 449, 708, 683, 113, 139, 138, 376, 207, 166, 584, 743, 6, 211, 1058, 1197, 84, 1096, 707, 998, 1198, 1199, 1160, +1105, 1007, 862, 520, 891, 1195, 1010, 1052, 776, 920, 407, 382, 399, 59, 58, 1148, 1195, 751, 1200, 977, 298, 988, 1201, 444, 897, 1011, 218, 666, 99, 678, 1075, 762, 926, 1108, 521, 1004, 973, 911, 757, 1011, 897, 370, 371, 373, 1066, 979, 978, 1076, +1202, 1203, 485, 1189, 608, 1204, 888, 553, 552, 950, 417, 1205, 768, 1206, 769, 67, 69, 709, 18, 20, 1180, 994, 662, 720, 281, 35, 282, 139, 13, 587, 171, 127, 126, 836, 1044, 837, 1177, 147, 146, 870, 810, 1141, 860, 1186, 976, 403, 722, 247, +1024, 1192, 955, 1070, 872, 670, 154, 153, 1153, 858, 78, 1207, 800, 504, 1208, 762, 777, 926, 431, 1181, 429, 1144, 780, 410, 937, 731, 784, 262, 64, 1139, 1198, 1174, 1209, 688, 691, 165, 731, 937, 729, 556, 555, 255, 510, 509, 831, 972, 1190, 76, +398, 655, 1210, 920, 382, 825, 715, 227, 1044, 724, 631, 967, 322, 321, 1045, 190, 1186, 1097, 904, 716, 717, 407, 920, 397, 153, 1211, 1153, 1212, 5, 596, 131, 820, 132, 945, 921, 825, 1141, 308, 944, 315, 314, 1213, 51, 982, 1184, 211, 386, 744, +270, 396, 186, 174, 703, 175, 598, 1212, 596, 202, 269, 203, 86, 88, 549, 775, 182, 776, 1007, 1214, 327, 1191, 353, 1215, 629, 628, 1001, 515, 1111, 367, 522, 464, 463, 846, 1216, 847, 453, 1217, 928, 73, 395, 823, 430, 429, 929, 561, 1218, 562, +335, 40, 471, 1219, 379, 1220, 799, 930, 800, 1221, 779, 517, 532, 1091, 531, 194, 491, 185, 548, 547, 1112, 422, 1218, 224, 338, 1193, 1222, 574, 1223, 487, 828, 313, 957, 590, 1042, 1043, 173, 439, 287, 802, 547, 1072, 643, 297, 9, 706, 959, 912, +49, 51, 1184, 693, 865, 24, 872, 871, 670, 883, 1224, 768, 883, 768, 770, 642, 423, 640, 247, 907, 245, 13, 587, 14, 1225, 685, 1226, 1105, 697, 1227, 689, 651, 1032, 461, 729, 1115, 1177, 1143, 36, 449, 730, 729, 1021, 598, 311, 168, 385, 135, +274, 442, 275, 603, 229, 602, 753, 947, 265, 882, 749, 751, 548, 747, 214, 1169, 1152, 1153, 658, 660, 948, 1183, 1173, 1176, 1228, 52, 54, 615, 614, 657, 440, 439, 173, 1138, 625, 624, 1121, 840, 368, 830, 983, 479, 564, 566, 275, 876, 916, 917, +1026, 241, 772, 1042, 286, 285, 719, 388, 879, 844, 1136, 92, 316, 205, 195, 323, 748, 50, 190, 901, 191, 234, 236, 684, 884, 1034, 138, 425, 426, 577, 165, 1229, 585, 421, 137, 1187, 666, 665, 1230, 1231, 1232, 1224, 1067, 1001, 1024, 700, 699, 253, +146, 168, 356, 78, 340, 76, 821, 727, 726, 1084, 1233, 925, 863, 1007, 1006, 871, 484, 485, 1234, 1235, 318, 665, 664, 329, 746, 773, 660, 1135, 266, 265, 476, 1207, 243, 248, 369, 1062, 746, 260, 772, 1026, 772, 1071, 393, 1236, 988, 634, 1011, 364, +26, 693, 24, 61, 815, 851, 1025, 1011, 634, 1195, 1232, 1231, 93, 1136, 393, 982, 706, 705, 467, 1107, 1041, 1064, 278, 277, 349, 465, 347, 230, 541, 497, 845, 1233, 1237, 381, 524, 470, 824, 828, 916, 564, 275, 442, 922, 742, 741, 110, 529, 111, +104, 1049, 1048, 326, 328, 1150, 685, 32, 1008, 209, 769, 618, 100, 715, 1044, 133, 1142, 553, 131, 133, 553, 1238, 1226, 1138, 1138, 301, 1238, 864, 630, 402, 1073, 547, 536, 806, 276, 343, 1036, 692, 869, 855, 883, 1028, 9, 472, 643, 1229, 11, 10, +464, 167, 565, 563, 746, 660, 241, 119, 1029, 130, 36, 35, 301, 1138, 302, 1215, 779, 1221, 769, 1206, 813, 828, 957, 956, 436, 477, 638, 461, 460, 812, 415, 423, 412, 413, 572, 571, 828, 956, 917, 377, 379, 974, 388, 833, 366, 700, 0, 2, +733, 732, 805, 1213, 314, 1096, 210, 209, 620, 818, 470, 787, 202, 786, 992, 310, 870, 981, 1215, 31, 1133, 127, 171, 1190, 542, 663, 761, 1025, 977, 890, 1187, 137, 419, 197, 1239, 760, 814, 619, 618, 860, 1097, 1186, 689, 969, 690, 193, 584, 585, +193, 585, 1229, 714, 113, 581, 558, 107, 109, 682, 639, 244, 620, 209, 618, 756, 755, 144, 826, 351, 831, 394, 844, 426, 95, 97, 928, 479, 480, 830, 626, 1121, 1111, 900, 470, 354, 897, 235, 370, 1007, 1105, 1227, 232, 231, 1194, 502, 1240, 130, +592, 603, 593, 1122, 240, 1161, 1040, 420, 468, 644, 46, 645, 572, 226, 570, 424, 310, 312, 1118, 1014, 1013, 225, 1218, 561, 759, 761, 994, 1099, 914, 913, 438, 588, 962, 374, 137, 139, 1018, 766, 14, 353, 31, 1215, 1241, 936, 993, 403, 258, 427, +1242, 507, 1211, 1213, 1096, 1015, 414, 413, 597, 902, 1110, 799, 244, 639, 242, 791, 1131, 1130, 1209, 1161, 1017, 1209, 1017, 483, 1094, 999, 125, 197, 196, 1217, 2, 311, 1243, 804, 219, 977, 1070, 725, 872, 1223, 574, 583, 636, 376, 138, 1147, 949, 951, +986, 795, 1166, 1147, 144, 755, 123, 593, 124, 816, 1060, 1172, 824, 916, 997, 1108, 1152, 1116, 902, 750, 852, 247, 1244, 403, 336, 1222, 1245, 269, 331, 918, 87, 629, 88, 277, 279, 169, 468, 126, 128, 443, 100, 668, 1156, 1084, 1083, 489, 1246, 133, +650, 788, 790, 877, 853, 68, 537, 319, 318, 941, 38, 578, 576, 26, 425, 487, 575, 574, 1182, 429, 1181, 83, 249, 248, 776, 1128, 555, 1036, 94, 829, 62, 118, 63, 461, 1115, 1097, 829, 480, 1247, 1131, 246, 150, 309, 333, 252, 450, 161, 782, +1048, 808, 807, 292, 251, 975, 1041, 489, 132, 572, 224, 226, 1032, 1108, 969, 115, 941, 494, 703, 243, 175, 226, 1030, 756, 616, 766, 765, 955, 1248, 1024, 955, 954, 1194, 49, 324, 50, 671, 89, 1121, 777, 1002, 778, 550, 1067, 551, 1057, 305, 1197, +859, 136, 385, 1160, 567, 1198, 792, 791, 1130, 803, 472, 622, 1194, 1248, 955, 617, 1094, 595, 93, 92, 1136, 231, 233, 56, 1249, 894, 329, 500, 873, 1099, 600, 599, 848, 176, 87, 86, 1242, 1211, 153, 748, 747, 912, 1144, 714, 220, 370, 1027, 365, +1143, 146, 356, 435, 1250, 339, 688, 165, 651, 1177, 36, 1240, 56, 1134, 1165, 735, 734, 838, 790, 527, 1251, 517, 278, 1064, 613, 1150, 1252, 629, 177, 627, 142, 1113, 140, 416, 76, 340, 1253, 1191, 612, 1209, 483, 482, 1092, 943, 942, 302, 515, 386, +1219, 1049, 1254, 398, 58, 655, 569, 568, 677, 612, 1255, 1256, 652, 653, 992, 536, 797, 1073, 395, 982, 823, 229, 450, 602, 513, 185, 491, 735, 1176, 1173, 123, 591, 593, 141, 1193, 338, 580, 492, 578, 170, 1064, 169, 261, 746, 563, 263, 1096, 1079, +636, 1034, 1019, 621, 623, 334, 847, 1137, 845, 821, 1072, 1073, 1038, 115, 494, 215, 488, 676, 1012, 1073, 1179, 1248, 573, 551, 263, 705, 707, 745, 1064, 170, 903, 1235, 750, 325, 324, 538, 996, 390, 963, 345, 344, 807, 812, 729, 461, 233, 970, 1134, +1188, 1187, 419, 27, 30, 731, 381, 380, 281, 667, 59, 399, 250, 1257, 369, 1148, 751, 1170, 416, 1168, 417, 689, 688, 651, 13, 375, 587, 628, 282, 600, 1162, 259, 1140, 843, 791, 793, 29, 1225, 1226, 29, 1226, 1238, 568, 1160, 583, 666, 441, 99, +336, 1245, 485, 914, 613, 1252, 127, 756, 128, 745, 517, 1064, 421, 1188, 419, 772, 260, 1071, 1252, 1150, 1125, 145, 288, 1065, 612, 558, 1253, 801, 609, 802, 452, 906, 450, 1173, 736, 735, 1022, 1258, 1023, 660, 773, 1087, 303, 784, 731, 1208, 1179, 1073, +799, 798, 902, 779, 278, 517, 15, 834, 654, 790, 1251, 1023, 825, 921, 920, 1101, 538, 324, 437, 438, 911, 135, 385, 136, 1246, 1142, 133, 446, 865, 867, 30, 29, 301, 765, 867, 711, 1081, 137, 781, 498, 779, 1133, 985, 919, 921, 301, 731, 30, +960, 1201, 530, 943, 1092, 234, 230, 987, 1236, 1021, 981, 5, 947, 1135, 265, 324, 49, 1100, 560, 559, 631, 383, 118, 384, 1259, 1098, 674, 29, 1238, 301, 988, 444, 393, 316, 819, 317, 1254, 1049, 104, 53, 1123, 1174, 675, 487, 1223, 1227, 697, 486, +571, 700, 1243, 311, 598, 1243, 1061, 952, 430, 561, 932, 1053, 256, 1244, 1164, 612, 107, 558, 587, 543, 14, 1228, 1159, 685, 611, 1220, 1255, 209, 1009, 769, 211, 783, 386, 809, 12, 1020, 649, 632, 1106, 1260, 1250, 435, 956, 1078, 917, 891, 813, 1206, +21, 371, 1066, 78, 280, 1207, 871, 485, 1203, 1095, 785, 946, 360, 359, 1097, 589, 1022, 286, 273, 272, 1016, 954, 1241, 232, 639, 638, 477, 2, 1, 259, 859, 385, 372, 1261, 640, 944, 701, 405, 171, 313, 874, 1262, 255, 555, 995, 1103, 604, 449, +19, 503, 129, 495, 298, 633, 657, 733, 343, 740, 433, 808, 464, 1032, 651, 648, 402, 649, 366, 365, 387, 679, 892, 686, 635, 264, 702, 1219, 974, 379, 771, 239, 238, 608, 548, 1112, 99, 441, 668, 1137, 546, 680, 122, 362, 447, 141, 338, 48, +1141, 944, 945, 643, 1200, 298, 300, 690, 969, 853, 349, 348, 791, 357, 1131, 1040, 884, 421, 453, 928, 97, 978, 52, 1228, 512, 1075, 926, 1013, 998, 1263, 644, 861, 47, 1180, 986, 1166, 599, 965, 1059, 681, 1245, 925, 961, 529, 110, 759, 197, 760, +582, 583, 574, 582, 574, 533, 192, 1186, 190, 268, 249, 83, 404, 437, 405, 677, 905, 162, 737, 1251, 70, 836, 98, 1044, 606, 122, 604, 390, 854, 963, 70, 72, 124, 10, 490, 193, 10, 193, 1229, 1066, 1071, 333, 446, 867, 1018, 201, 720, 662, +960, 771, 444, 1264, 1201, 988, 1036, 829, 121, 679, 493, 492, 1194, 231, 573, 557, 1031, 1010, 332, 910, 350, 1019, 13, 809, 294, 726, 295, 1230, 522, 463, 523, 522, 1230, 438, 1034, 588, 826, 352, 351, 517, 516, 1221, 938, 940, 1069, 423, 415, 921, +271, 186, 188, 245, 907, 183, 989, 340, 339, 1087, 948, 660, 257, 356, 168, 1146, 1147, 682, 1135, 21, 23, 77, 1190, 171, 817, 409, 425, 562, 261, 563, 955, 1192, 965, 955, 965, 934, 1258, 1022, 589, 1056, 592, 661, 737, 593, 603, 907, 247, 722, +691, 300, 11, 1249, 1003, 1005, 1249, 1005, 894, 314, 1262, 1080, 160, 719, 879, 453, 496, 760, 838, 1193, 839, 259, 1, 260, 15, 655, 1265, 15, 1265, 16, 466, 842, 709, 1034, 404, 1020, 391, 390, 898, 261, 1140, 259, 0, 700, 253, 46, 644, 47, +1094, 125, 320, 200, 199, 740, 1007, 1227, 1214, 714, 410, 66, 241, 893, 118, 728, 727, 993, 1261, 306, 641, 1210, 162, 905, 1170, 751, 750, 164, 677, 162, 593, 70, 124, 111, 529, 1124, 1046, 1220, 611, 90, 671, 1266, 915, 914, 1125, 43, 1250, 1260, +367, 369, 516, 911, 973, 437, 171, 440, 77, 943, 234, 1033, 622, 472, 856, 545, 1202, 681, 687, 686, 892, 195, 207, 584, 768, 1232, 1206, 1063, 1215, 1221, 306, 1261, 307, 992, 653, 926, 792, 1267, 1168, 5, 899, 3, 176, 921, 177, 1142, 265, 264, +1170, 750, 1235, 405, 701, 406, 418, 417, 950, 1093, 1054, 1055, 266, 292, 144, 375, 374, 587, 605, 895, 984, 605, 984, 606, 554, 1126, 364, 79, 650, 1258, 79, 1258, 672, 578, 492, 941, 1240, 36, 130, 1240, 1172, 1060, 462, 565, 564, 1175, 1185, 75, +573, 1248, 1194, 1168, 1267, 417, 1030, 1053, 128, 19, 129, 380, 365, 364, 1011, 147, 1177, 1060, 1230, 463, 666, 969, 1108, 299, 945, 825, 1141, 1086, 1250, 43, 633, 635, 702, 1236, 393, 392, 927, 331, 202, 1114, 1176, 839, 899, 598, 4, 972, 754, 1190, +452, 451, 1236, 1165, 1088, 18, 137, 1081, 419, 541, 661, 663, 309, 251, 291, 92, 577, 426, 31, 33, 498, 674, 740, 1050, 1062, 841, 1181, 1062, 1181, 431, 12, 14, 656, 59, 1035, 60, 760, 542, 761, 631, 849, 723, 1261, 944, 307, 961, 530, 529, +1165, 1134, 1088, 863, 560, 631, 134, 881, 1095, 698, 1266, 107, 698, 107, 1256, 997, 738, 710, 430, 1058, 1061, 1211, 507, 510, 625, 1226, 1008, 614, 120, 732, 396, 623, 622, 1201, 960, 444, 1156, 1083, 1183, 567, 569, 355, 56, 573, 231, 448, 447, 886, +15, 17, 834, 317, 342, 206, 238, 240, 1122, 1095, 427, 134, 134, 427, 258, 1062, 368, 840, 978, 1228, 1225, 293, 802, 294, 1209, 1174, 1122, 5, 981, 870, 457, 669, 544, 404, 809, 1020, 506, 434, 433, 1149, 1150, 328, 614, 616, 711, 145, 704, 288, +1218, 1140, 562, 981, 1021, 311, 1178, 1104, 864, 262, 1139, 222, 1182, 111, 1124, 60, 847, 655, 1102, 216, 215, 925, 1233, 845, 629, 1068, 88, 1037, 868, 866, 68, 67, 877, 935, 728, 936, 393, 1136, 394, 252, 1, 0, 796, 86, 55, 1105, 862, 1104, +718, 408, 719, 722, 942, 907, 709, 842, 45, 1227, 992, 1214, 514, 491, 490, 287, 439, 968, 473, 304, 889, 473, 889, 474, 613, 326, 1150, 1178, 575, 487, 1065, 696, 316, 139, 376, 13, 837, 901, 102, 347, 466, 69, 549, 551, 55, 559, 1099, 873, +1063, 1047, 610, 637, 436, 638, 631, 724, 849, 25, 131, 817, 1068, 629, 1001, 567, 355, 53, 387, 365, 1027, 1074, 708, 1163, 811, 1103, 812, 1006, 560, 863, 721, 946, 1120, 809, 636, 1019, 1180, 919, 986, 990, 712, 158, 661, 506, 662, 680, 546, 545, +804, 621, 219, 348, 69, 68, 166, 237, 167, 1117, 1152, 1169, 671, 1121, 626, 1142, 909, 553, 330, 46, 48, 832, 299, 1117, 985, 986, 919, 1063, 1221, 1257, 926, 653, 512, 349, 390, 465, 342, 657, 343, 524, 354, 470, 330, 1268, 46, 771, 52, 1051, +791, 842, 358, 993, 970, 233, 153, 152, 501, 1207, 476, 858, 1054, 216, 1102, 1226, 685, 1008, 7, 1269, 360, 1038, 834, 115, 345, 617, 346, 900, 354, 283, 760, 1239, 453, 352, 1125, 1149, 767, 1018, 867, 502, 1172, 1240, 1177, 1240, 1060, 707, 747, 998, +382, 407, 380, 926, 777, 927, 189, 528, 525, 1082, 506, 661, 486, 652, 1227, 1217, 1057, 928, 1033, 708, 1074, 55, 86, 549, 5, 1212, 899, 470, 900, 4, 53, 1174, 567, 227, 1132, 192, 35, 996, 282, 1145, 671, 626, 57, 1165, 794, 599, 991, 964, +786, 327, 1214, 400, 742, 922, 974, 961, 110, 27, 1076, 28, 722, 721, 942, 598, 899, 1212, 811, 605, 1103, 217, 1093, 511, 992, 1227, 652, 952, 1062, 431, 1000, 994, 889, 502, 930, 1172, 753, 1053, 932, 229, 228, 450, 159, 719, 160, 843, 842, 791, +91, 111, 1182, 1030, 128, 756, 854, 991, 601, 1051, 52, 978, 1267, 792, 1130, 900, 899, 4, 565, 167, 237, 1236, 392, 452, 596, 899, 283, 217, 652, 215, 1162, 1140, 422, 758, 656, 317, 1266, 671, 107, 724, 967, 725, 257, 256, 1164, 368, 1062, 369, +1198, 1209, 482, 887, 499, 498, 878, 44, 739, 1079, 65, 64, 1270, 789, 788, 240, 1089, 1161, 949, 418, 950, 787, 354, 3, 749, 855, 852, 1199, 481, 1160, 470, 354, 787, 369, 1257, 516, 1147, 951, 682, 1096, 314, 1079, 785, 589, 1119, 1040, 468, 467, +1089, 105, 1102, 3, 596, 5, 642, 641, 810, 605, 811, 895, 363, 479, 362, 641, 308, 810, 960, 239, 771, 1205, 150, 149, 560, 1006, 326, 1081, 420, 419, 1104, 862, 864, 720, 889, 994, 429, 1124, 929, 839, 1113, 1114, 198, 759, 1000, 97, 96, 453, +1202, 545, 1203, 919, 1180, 20, 196, 305, 1057, 200, 475, 474, 853, 607, 801, 429, 1182, 1124, 621, 334, 219, 1237, 1233, 1271, 1242, 153, 501, 587, 586, 543, 686, 1158, 355, 686, 355, 569, 1272, 1273, 1155, 1274, 1272, 1154, 1275, 1274, 1276, 1277, 1278, 1279, +1280, 1281, 1282, 1283, 1277, 1284, 1273, 1280, 1155, 1278, 1285, 1286, 1281, 1287, 1288, 1155, 1280, 1289, 1288, 1283, 1290, 1287, 1283, 1288, 1288, 1282, 1281, 1279, 1284, 1277, 1284, 1290, 1283, 1282, 1289, 1280, 1286, 1279, 1278, 1155, 1154, 1272, 1154, 1276, 1274, 1183, 1275, 1276, +857, 361, 1272, 37, 1283, 1287, 156, 155, 1275, 1276, 1154, 1156, 16, 1279, 764, 1279, 16, 1265, 764, 1286, 116, 1274, 155, 857, 1281, 1280, 7, 1283, 37, 39, 117, 116, 1278, 1291, 1216, 1290, 1284, 1265, 1291, 1216, 846, 1288, 361, 1269, 1273, 361, 1273, 1272, +39, 117, 1278, 1269, 7, 1280, 846, 1237, 1289, 1155, 1289, 1237, 7, 937, 8, 195, 205, 207, 324, 323, 50, 38, 941, 39, 1291, 655, 847, 870, 825, 818, 226, 756, 975, 639, 477, 476, 1076, 978, 28, 322, 1045, 1055, 415, 177, 921, 493, 677, 164, +310, 424, 642, 1093, 1055, 1090, 792, 1168, 989, 1086, 793, 989, 1217, 453, 1239, 1217, 1239, 197, 566, 206, 276, 1097, 7, 360, 747, 707, 912, 1078, 1189, 1204, 239, 961, 106, 397, 20, 19, 101, 835, 837, 923, 925, 1245, 462, 564, 442, 331, 927, 777, +887, 687, 499, 328, 204, 1149, 838, 1292, 1193, 413, 571, 1243, 1086, 989, 1250, 891, 1206, 1232, 1109, 184, 1260, 517, 744, 515, 850, 119, 62, 841, 1062, 840, 1029, 948, 1087, 874, 828, 180, 14, 615, 656, 189, 1293, 528, 392, 394, 718, 714, 1144, 410, +267, 85, 473, 665, 523, 1230, 285, 235, 1042, 803, 804, 1200, 155, 158, 712, 1129, 908, 1163, 1171, 1234, 538, 225, 1053, 1030, 908, 1074, 1163, 1070, 670, 669, 1056, 661, 541, 725, 966, 872, 1006, 327, 326, 315, 957, 313, 318, 325, 1234, 343, 733, 806, +1167, 1047, 249, 544, 546, 457, 64, 411, 1139, 786, 1214, 992, 1146, 244, 704, 284, 1022, 603, 1220, 1050, 1049, 1166, 795, 794, 179, 528, 180, 221, 273, 74, 905, 677, 534, 1263, 1118, 1013, 1152, 1004, 1003, 181, 183, 907, 436, 1010, 435, 468, 420, 172, +893, 241, 1026, 637, 774, 1052, 875, 1210, 905, 749, 852, 750, 1222, 923, 1245, 270, 186, 271, 792, 989, 793, 13, 376, 375, 675, 1017, 676, 128, 752, 468, 187, 396, 856, 1023, 1258, 650, 1220, 379, 1255, 198, 1000, 304, 850, 855, 1028, 874, 1293, 1262, +860, 976, 811, 434, 1094, 617, 1291, 1265, 655, 851, 850, 62, 531, 763, 1075, 754, 127, 1190, 234, 1042, 235, 198, 305, 196, 273, 1016, 526, 540, 81, 80, 1101, 1100, 539, 772, 241, 773, 386, 515, 744, 594, 1055, 105, 32, 685, 33, 652, 486, 215, +619, 859, 371, 641, 640, 1261, 1088, 970, 971, 247, 246, 1244, 149, 951, 1205, 516, 1257, 1221, 126, 468, 172, 740, 674, 475, 1075, 512, 511, 417, 1267, 1205, 672, 1258, 589, 194, 185, 195, 633, 977, 1025, 1091, 1045, 272, 191, 901, 837, 262, 222, 221, +1225, 29, 28, 944, 308, 307, 1255, 612, 611, 1016, 321, 125, 649, 402, 630, 1210, 655, 654, 961, 974, 106, 1196, 1204, 608, 1047, 1063, 249, 774, 149, 151, 377, 698, 378, 265, 1246, 753, 31, 353, 32, 948, 883, 770, 1046, 1167, 268, 282, 596, 283, +998, 1013, 1015, 1095, 519, 518, 987, 988, 1236, 694, 968, 439, 350, 832, 351, 725, 1069, 723, 1016, 124, 72, 18, 1179, 1151, 18, 1151, 19, 1028, 119, 850, 726, 728, 295, 268, 1098, 1259, 959, 50, 748, 263, 1079, 64, 521, 1032, 464, 425, 25, 817, +1118, 1189, 1078, 411, 410, 780, 700, 2, 1243, 506, 591, 999, 695, 438, 12, 874, 528, 1293, 1041, 132, 1039, 340, 858, 478, 1007, 863, 862, 773, 241, 1029, 1058, 1057, 1197, 181, 908, 1129, 325, 538, 1234, 390, 349, 854, 352, 827, 915, 933, 1189, 1263, +165, 11, 1229, 597, 413, 1243, 1208, 1151, 1179, 589, 590, 1119, 11, 165, 691, 243, 1207, 175, 645, 678, 836, 743, 38, 37, 24, 820, 25, 895, 715, 984, 274, 443, 442, 503, 1151, 504, 696, 1065, 968, 1210, 654, 834, 1027, 879, 387, 1031, 1109, 1260, +88, 550, 549, 82, 1270, 788, 523, 1005, 1004, 936, 1241, 954, 637, 682, 774, 107, 612, 1256, 289, 143, 513, 422, 224, 572, 384, 893, 373, 423, 642, 424, 1014, 1213, 1015, 697, 1104, 1178, 588, 1039, 586, 270, 918, 396, 930, 815, 816, 420, 1040, 421, +82, 81, 539, 1014, 315, 1213, 1078, 1204, 876, 214, 747, 212, 217, 216, 1093, 378, 1255, 379, 181, 1129, 182, 102, 1097, 359, 798, 797, 536, 808, 433, 432, 803, 1200, 643, 342, 317, 656, 27, 731, 730, 433, 740, 199, 579, 169, 279, 80, 518, 520, +290, 144, 143, 1167, 249, 268, 567, 1174, 1198, 314, 313, 1262, 456, 953, 457, 27, 730, 448, 634, 364, 635, 214, 213, 548, 173, 175, 1207, 1118, 1263, 1189, 1002, 762, 1139, 221, 223, 273, 566, 207, 206, 841, 91, 1182, 254, 178, 180, 681, 925, 680, +662, 506, 505, 1218, 422, 1140, 1232, 768, 1224, 685, 1225, 1228, 1063, 610, 1215, 561, 1053, 225, 751, 1231, 882, 1256, 1255, 378, 1080, 65, 1079, 236, 235, 896, 895, 811, 976, 383, 168, 148, 946, 428, 1095, 385, 168, 383, 979, 1127, 980, 53, 52, 1123, +1058, 248, 1061, 1111, 515, 302, 1147, 755, 949, 571, 570, 700, 1256, 378, 698, 116, 1285, 1278, 1216, 1288, 1290, 1280, 1273, 1269, 155, 1274, 1275, 764, 1279, 1286, 760, 496, 542, 1248, 1067, 1024, 455, 987, 497, 647, 649, 939, 246, 1131, 1244, 835, 101, 645, +576, 92, 94, 1033, 234, 684, 497, 987, 230, 570, 975, 699, 596, 282, 628, 110, 698, 377, 1142, 1246, 265, 572, 413, 412, 658, 770, 1009, 776, 182, 1128, 142, 157, 1114, 959, 51, 50, 559, 613, 914, 65, 1080, 1293, 28, 978, 1225, 896, 235, 897, +1025, 634, 633, 1179, 971, 1012, 924, 923, 1292, 1024, 848, 1059, 974, 1254, 104, 719, 408, 388, 237, 566, 565, 1242, 501, 507, 451, 230, 1236, 189, 112, 114, 276, 275, 566, 701, 171, 1081, 454, 96, 529, 1253, 558, 353, 1026, 1071, 373, 1117, 1169, 831, +1077, 922, 904, 567, 1160, 568, 183, 775, 151, 891, 1232, 1195, 806, 443, 274, 220, 714, 581, 398, 1210, 399, 37, 6, 743, 94, 1036, 869, 84, 1197, 85, 664, 678, 1268, 156, 1176, 157, 557, 738, 1109, 517, 745, 744, 1075, 511, 531, 1200, 804, 977, +103, 713, 101, 67, 709, 878, 552, 554, 366, 505, 433, 199, 610, 1191, 1215, 831, 509, 826, 503, 130, 129, 909, 1126, 553, 705, 262, 823, 924, 838, 734, 1292, 923, 1222, 869, 692, 26, 480, 363, 1247, 187, 471, 40, 117, 39, 941, 852, 1110, 902, +533, 574, 1077, 538, 540, 1171, 648, 742, 402, 363, 121, 1247, 1270, 82, 539, 734, 1083, 1085, 780, 778, 1002, 152, 154, 330, 489, 468, 1246, 1220, 1046, 1259, 453, 455, 496, 529, 96, 1124, 254, 995, 178, 1031, 1260, 435, 1271, 1233, 1084, 1108, 1116, 299, +136, 881, 134, 42, 832, 350, 1246, 468, 753, 1202, 485, 1245, 381, 825, 382, 85, 267, 83, 212, 748, 323, 878, 739, 997, 526, 74, 273, 687, 887, 685, 1139, 411, 1002, 252, 333, 1, 620, 21, 1135, 713, 712, 990, 530, 1201, 1264, 1210, 875, 399, +690, 300, 691, 630, 863, 631, 995, 1129, 1163, 1050, 1220, 1259, 44, 878, 45, 674, 1050, 1259, 659, 947, 932, 874, 313, 828, 1046, 611, 610, 1215, 1133, 779, 61, 851, 62, 672, 1095, 518, 1077, 534, 533, 870, 1141, 825, 436, 341, 477, 1183, 1176, 156, +141, 48, 47, 1175, 789, 1100, 1175, 527, 789, 1038, 493, 164, 588, 587, 13, 1248, 551, 1067, 650, 790, 1023, 296, 295, 935, 233, 232, 1241, 98, 100, 1044, 44, 184, 739, 443, 805, 715, 1010, 436, 1052, 135, 134, 258, 1012, 822, 821, 294, 1072, 726, +1171, 540, 1148, 330, 48, 152, 197, 759, 198, 846, 1289, 1282, 37, 1287, 6, 857, 1272, 1274, 1276, 1156, 1183, 1281, 7, 6, 1275, 1183, 156, 1271, 1155, 1237, 1284, 1291, 1290, 780, 918, 778, 935, 295, 728, 221, 823, 262, 1042, 590, 286, 1022, 284, 286, +789, 1270, 1100, 459, 860, 811, 631, 559, 967, 1263, 998, 933, 332, 203, 271, 669, 953, 1070, 85, 1197, 305, 224, 1218, 225, 871, 1203, 670, 356, 358, 391, 75, 1185, 395, 929, 928, 1057, 1231, 751, 1195, 119, 1028, 1029, 541, 663, 542, 673, 473, 475, +481, 483, 675, 178, 708, 525, 883, 1231, 1224, 1267, 1130, 150, 1267, 150, 1205, 207, 566, 237, 93, 393, 445, 782, 285, 284, 727, 822, 993, 1054, 1093, 216, 619, 21, 620, 259, 1162, 424, 930, 502, 504, 701, 781, 636, 1102, 105, 1054, 510, 1153, 1211, +1247, 121, 829, 251, 253, 975, 1039, 132, 586, 474, 720, 200, 741, 648, 647, 710, 824, 997, 481, 1199, 482, 882, 1231, 883, 972, 949, 755, 953, 939, 938, 332, 350, 1149, 191, 1044, 227, 147, 1060, 118, 1076, 27, 448, 1045, 321, 1016, 1127, 979, 885, +716, 904, 922, 918, 331, 778, 268, 267, 673, 1107, 489, 1041, 547, 802, 609, 547, 609, 1112, 1100, 1270, 539, 686, 1159, 1158, 458, 1035, 59, 822, 970, 993, 808, 1048, 1050, 426, 408, 718, 515, 367, 516, 876, 1204, 1196, 838, 924, 1292, 672, 785, 1095, +443, 806, 805, 104, 1048, 344, 476, 242, 639, 504, 1151, 1208, 758, 819, 696, 1292, 1222, 1193, 614, 733, 657, 809, 13, 12, 1235, 537, 318, 552, 817, 888, 559, 914, 1099, 1065, 288, 287, 297, 514, 9, 588, 13, 962, 527, 1175, 75, 1217, 196, 1057, +723, 940, 1106, 1249, 1153, 1003, 423, 422, 412, 24, 865, 446, 352, 1149, 350, 574, 400, 922, 169, 579, 38, 1155, 1271, 1084, 476, 243, 242, 5, 870, 787, 112, 189, 525, 1176, 735, 839, 726, 1072, 821, 532, 1090, 1091, 832, 1117, 831, 1096, 998, 1015, +1131, 1164, 1244, 39, 1277, 1283, 1279, 1265, 1284, 740, 808, 1050, 1049, 1219, 1220, 1078, 956, 1118, 495, 702, 290, 1193, 140, 1113, 1260, 184, 43, 297, 289, 514, 948, 1028, 883, 1293, 1080, 1262, 721, 1092, 942, 640, 423, 945, 951, 950, 1205, 188, 910, 332, +90, 1266, 698, 1235, 1234, 1171, 1235, 1171, 1170, 189, 114, 66, 845, 680, 925, 71, 70, 1251, 71, 1251, 527, 23, 292, 266, 978, 980, 1051, 360, 1269, 361, 790, 789, 527, 163, 1210, 834, 380, 129, 35, 543, 446, 1018, 974, 1219, 1254, 448, 730, 449, +166, 165, 585, 815, 799, 1110, 597, 1243, 598, 600, 963, 601, 583, 481, 675, 630, 632, 649, 671, 1145, 108, 1226, 625, 1138, 276, 206, 342, 765, 711, 616, 1008, 108, 1145, 1259, 1046, 268, 845, 1237, 846, 577, 576, 425, 892, 499, 687, 713, 990, 646, +1268, 678, 645, 537, 798, 536, 158, 157, 142, 1157, 925, 924, 937, 784, 783, 372, 384, 373, 1073, 797, 1208, 484, 873, 500, 1223, 583, 675, 1056, 230, 229, 903, 537, 1235, 995, 555, 1128, 457, 953, 669, 536, 213, 319, 670, 1203, 545, 997, 739, 738, +290, 264, 266, 1023, 1251, 737, 857, 155, 712, 45, 843, 43, 1127, 885, 479, 510, 831, 1169, 40, 188, 187, 338, 1222, 336, 1039, 884, 1040, 162, 1210, 163, 523, 1004, 521, 797, 800, 1208, 173, 1207, 280, 854, 801, 293, 1161, 1209, 1122, 1055, 594, 322, +766, 615, 14, 425, 26, 25, 1094, 320, 595, 1249, 329, 154, 74, 526, 75, 154, 1153, 1249, 908, 943, 1074, 737, 603, 1022, 1116, 1152, 1117, 914, 1252, 1125, 967, 966, 725, 152, 337, 501, 996, 963, 282, 6, 1287, 1281, 1286, 1285, 116, 39, 1278, 1277, +101, 837, 102, 677, 493, 569, 54, 355, 1158, 40, 42, 910, 581, 236, 896, 807, 344, 1048, 939, 1106, 940, 1157, 1084, 925, 428, 427, 1095, 152, 338, 337, 54, 1159, 1228, 16, 116, 17, 623, 780, 1144, 122, 121, 363, 1131, 357, 1164, 323, 325, 318, +1175, 49, 1184, 1120, 946, 785, 414, 177, 415, 187, 856, 472, 1115, 937, 7, 530, 1264, 454, 482, 1199, 1198, 55, 573, 56, 607, 609, 801, 645, 101, 646, 82, 788, 650, 662, 761, 663, 670, 545, 544, 692, 1036, 693, 1244, 256, 403, 316, 696, 819, +649, 1106, 939, 52, 238, 1123, 396, 918, 780, 61, 63, 816, 1146, 704, 144, 982, 959, 706, 330, 664, 1268, 1135, 947, 620, 500, 337, 336, 293, 991, 854, 878, 916, 876, 487, 697, 1178, 1191, 1253, 353, 390, 996, 34, 1027, 370, 285, 1193, 141, 140, +580, 892, 679, 1165, 18, 1166, 1063, 1257, 250, 36, 1143, 391, 934, 296, 935, 1184, 395, 1185, 1245, 681, 1202, 443, 668, 441, 1108, 1004, 1152, 167, 464, 651, 248, 1062, 1061, 1216, 1291, 847, 244, 1146, 682, 802, 1072, 294, 608, 607, 1196, 500, 507, 501, +444, 1051, 445, 467, 1041, 1040, 131, 553, 888, 46, 1268, 645, 1134, 970, 1088, 219, 890, 977, 847, 1035, 546, 233, 1241, 993, 292, 975, 756, 734, 1157, 924, 58, 60, 655, 455, 454, 988, 989, 339, 1250, 1034, 1020, 1019, 251, 292, 291, 1090, 532, 1093, +1264, 988, 454, 846, 1282, 1288, 798, 537, 903, 1129, 1128, 182, 65, 1293, 189, 117, 941, 115, 456, 458, 59, 956, 958, 1118, }; +const float triangleNormals[][3] = { +-0.0979, 0.8167, 0.5688, -0.4293, -0.1976, -0.8813, -0.0014, -0.1062, 0.9943, 0.9968, 0.0329, -0.0733, -0.1503, -0.6511, -0.7439, -0.0264, 0.0034, 0.9996, -0.9975, -0.0597, -0.0371, 0.2753, 0.8903, 0.3628, -0.9364, 0.3361, 0.1012, -0.0875, -0.8599, 0.5028, -0.0907, -0.8158, 0.5712, -0.9717, -0.1255, 0.2002, 0.7684, 0.0179, -0.6397, -0.0241, -0.0275, 0.9993, 0.9069, -0.3458, -0.2408, 0.5955, -0.6641, 0.4520, +0.4879, -0.6103, 0.6242, -0.7474, 0.4572, 0.4821, -0.5406, -0.7714, -0.3357, 0.2128, -0.8947, -0.3927, -0.4406, 0.8914, 0.1064, 0.8143, -0.2800, 0.5084, -0.1902, 0.9663, 0.1736, 0.3063, -0.9166, -0.2570, -0.9328, -0.2678, -0.2412, -0.5787, 0.6978, 0.4221, 0.3107, -0.5964, 0.7402, -0.5273, -0.4478, -0.7221, -0.5120, -0.4701, -0.7189, 0.2356, -0.8613, -0.4501, 0.9939, -0.1014, -0.0431, 0.9744, 0.0728, 0.2125, +-0.0697, -0.9966, -0.0442, 0.8687, 0.3938, 0.3005, 0.9387, 0.3444, -0.0163, 0.6231, -0.2172, 0.7514, -0.7510, -0.0374, -0.6592, -0.1527, 0.8937, 0.4218, 0.2681, 0.6889, 0.6734, 0.5240, 0.8423, -0.1262, -0.0203, -0.0083, 0.9998, 0.9661, -0.2526, -0.0525, -0.8391, -0.4693, 0.2753, -0.8861, 0.0941, 0.4538, -0.8674, 0.0802, 0.4911, -0.0856, -0.5413, -0.8365, -0.8850, -0.3470, 0.3106, 0.1977, -0.3164, -0.9278, +-0.3428, 0.8225, 0.4539, 0.9435, -0.0743, 0.3229, 0.9281, -0.0552, 0.3683, 0.6797, -0.0653, 0.7306, 0.6892, -0.5297, 0.4943, -0.7459, 0.3303, -0.5784, 0.8008, 0.5386, 0.2622, 0.0194, -0.0072, 0.9998, 0.0338, 0.0027, 0.9994, -0.5336, -0.4700, 0.7031, -0.7724, 0.1504, 0.6170, 0.1398, 0.6971, 0.7032, 0.8156, -0.4386, -0.3775, -0.5168, -0.7181, 0.4661, -0.5168, -0.6338, 0.5755, 0.9823, -0.1300, -0.1348, +0.6334, -0.1191, -0.7646, 0.7136, 0.6842, 0.1502, 0.9224, -0.2476, -0.2965, 0.8033, -0.4191, 0.4231, 0.9146, -0.0051, 0.4044, 0.6218, 0.6805, -0.3877, 0.1837, 0.9648, 0.1883, 0.9053, -0.4214, -0.0539, 0.7451, 0.4719, -0.4713, 0.8816, -0.3271, 0.3403, -0.5685, -0.3801, 0.7296, 0.1520, 0.9400, 0.3053, 0.9917, -0.0054, 0.1284, -0.8906, -0.0152, -0.4545, 0.1842, -0.5081, 0.8414, -0.5634, -0.0491, 0.8247, +-0.5681, 0.7707, 0.2885, 0.9954, -0.0637, 0.0717, -0.5983, 0.7897, 0.1357, -0.0214, 0.9917, -0.1270, 0.1906, -0.8360, -0.5145, 0.7362, -0.4692, 0.4877, 0.7441, 0.3507, 0.5686, 0.3121, -0.9344, -0.1718, 0.7913, -0.4572, -0.4059, 0.7133, 0.3356, 0.6153, -0.8736, -0.3775, -0.3073, 0.5189, -0.7371, 0.4330, -0.0644, 0.9570, -0.2828, 0.5092, -0.3587, -0.7824, -0.1263, -0.7669, -0.6292, 0.7684, 0.4033, 0.4969, +0.7176, 0.4613, 0.5218, 0.5408, 0.8301, 0.1360, 0.1602, -0.7391, -0.6543, -0.8713, 0.1633, 0.4628, -0.5092, 0.8247, 0.2461, -0.5388, -0.0264, 0.8421, -0.1850, -0.8327, -0.5219, 0.4210, 0.9063, 0.0374, -0.3435, 0.9265, -0.1538, 0.8785, -0.0910, -0.4689, -0.9369, 0.3363, -0.0956, 0.6275, -0.6089, 0.4853, -0.0982, 0.9163, -0.3883, 0.2844, -0.7941, 0.5372, 0.6480, 0.0382, -0.7606, 0.6211, 0.0938, 0.7781, +0.8983, 0.3624, 0.2485, 0.2871, 0.5530, 0.7821, 0.3195, 0.5453, 0.7750, 0.9049, 0.1859, 0.3828, 0.9029, 0.4232, 0.0761, 0.7628, 0.4398, 0.4740, 0.4577, -0.8837, -0.0981, 0.6136, 0.7874, -0.0593, 0.2857, 0.9534, 0.0966, -0.2625, 0.8628, -0.4319, -0.1439, 0.9507, 0.2746, 0.9598, -0.2579, -0.1107, -0.4961, 0.4629, 0.7345, -0.9941, 0.0845, 0.0686, -0.6708, 0.6261, 0.3975, 0.4114, 0.6772, 0.6100, +0.8264, -0.5409, 0.1565, -0.1815, 0.9501, -0.2539, 0.8289, 0.4796, -0.2880, 0.1399, 0.8808, 0.4523, 0.9020, 0.3937, -0.1771, 0.8432, 0.1864, -0.5043, 0.8316, 0.3555, 0.4267, 0.8985, -0.4235, -0.1157, 0.9086, -0.1178, -0.4007, -0.8824, 0.0205, -0.4701, 0.9658, -0.0787, -0.2472, -0.2899, 0.5055, 0.8127, -0.6174, 0.7866, 0.0089, -0.2981, 0.1156, 0.9475, -0.4194, -0.8921, -0.1683, 0.2850, -0.9474, 0.1452, +0.0513, 0.0065, 0.9987, -0.8341, -0.1265, 0.5369, -0.8136, 0.0816, 0.5756, 0.8980, -0.4390, -0.0300, 0.5160, -0.8560, 0.0328, 0.7155, 0.5896, 0.3747, 0.3339, -0.2488, -0.9092, -0.5317, 0.5241, 0.6653, 0.4312, 0.7698, 0.4706, 0.9002, 0.0979, 0.4243, 0.8608, -0.1877, 0.4731, -0.7528, -0.2798, 0.5959, 0.7642, -0.6002, -0.2362, 0.7366, -0.6220, -0.2655, -0.7323, 0.6663, 0.1405, 0.6510, 0.7550, -0.0784, +-0.7429, 0.3440, -0.5743, -0.2281, 0.8742, 0.4286, -0.7527, 0.6567, 0.0472, -0.0602, -0.2647, -0.9624, 0.3184, -0.9441, 0.0858, -0.5775, -0.0537, 0.8146, -0.7449, -0.6033, 0.2849, 0.0140, 0.9836, 0.1799, -0.5143, 0.3633, 0.7768, 0.4256, -0.7486, 0.5084, 0.3513, -0.7829, 0.5135, -0.2776, -0.9512, 0.1348, -0.8475, 0.5088, -0.1510, -0.6556, -0.5472, -0.5203, -0.8411, -0.4309, -0.3270, -0.8420, -0.3876, -0.3753, +0.4916, -0.2184, -0.8430, 0.8943, -0.4347, -0.1061, -0.9688, 0.0961, 0.2286, 0.9728, -0.0913, 0.2128, 0.9627, 0.2682, -0.0346, 0.3854, -0.3318, -0.8610, 0.4993, -0.8357, 0.2289, 0.7446, 0.6301, 0.2204, 0.6028, -0.7612, 0.2392, -0.9836, -0.0959, -0.1527, -0.9664, -0.2424, 0.0856, -0.9694, -0.2286, 0.0893, 0.7841, -0.2803, 0.5537, 0.7731, -0.6114, 0.1690, 0.9053, -0.2389, -0.3511, 0.9210, -0.2408, -0.3061, +-0.1864, 0.5837, 0.7903, -0.0863, 0.9869, -0.1363, 0.1091, -0.7776, -0.6192, 0.4548, 0.8236, 0.3388, 0.7943, -0.3389, 0.5043, -0.7077, -0.6544, 0.2663, -0.3313, -0.8446, -0.4206, 0.9824, 0.1256, 0.1379, -0.2942, -0.7390, 0.6061, 0.9889, 0.1464, -0.0242, -0.3701, -0.8868, -0.2768, -0.9561, -0.2749, 0.1014, 0.8780, 0.4566, 0.1437, -0.8449, 0.4847, 0.2263, -0.7571, 0.3616, 0.5440, 0.7129, -0.0807, -0.6966, +-0.7871, -0.5282, 0.3185, 0.7127, 0.0280, 0.7009, 0.9040, -0.3444, 0.2533, -0.9349, 0.0558, -0.3504, 0.9535, 0.1736, 0.2465, -0.6003, -0.6654, -0.4437, -0.7504, 0.0923, 0.6545, 0.8881, 0.3455, 0.3030, 0.9091, 0.3268, 0.2584, 0.0230, 0.7761, -0.6302, 0.9953, 0.0806, -0.0531, 0.9953, 0.0963, 0.0104, -0.1364, -0.9660, -0.2196, -0.3218, -0.4120, -0.8524, 0.9910, -0.1149, -0.0683, -0.4827, 0.4984, -0.7201, +-0.4068, 0.0942, 0.9086, 0.6435, 0.7561, -0.1194, -0.9128, 0.2757, 0.3013, -0.9148, 0.2511, 0.3164, 0.9457, 0.0940, 0.3113, 0.4016, 0.8973, 0.1833, 0.8671, -0.2752, -0.4153, -0.0620, 0.9950, 0.0782, -0.9295, 0.1718, 0.3265, -0.4275, -0.1248, 0.8954, -0.9016, -0.4228, -0.0911, -0.9107, -0.4019, -0.0951, 0.7199, -0.3464, 0.6015, 0.6936, -0.3575, 0.6254, -0.9370, -0.2321, -0.2610, -0.3127, 0.9212, -0.2313, +-0.0935, 0.3970, 0.9130, 0.2033, 0.0689, -0.9767, -0.6234, -0.4540, 0.6367, 0.7173, 0.6815, 0.1448, -0.8844, 0.4646, -0.0447, -0.0810, 0.9592, 0.2709, -0.3285, -0.9128, -0.2427, 0.9256, 0.3758, -0.0446, -0.9856, -0.1688, -0.0009, 0.8737, -0.4076, -0.2657, 0.5209, -0.8071, -0.2780, -0.9950, 0.0972, 0.0236, -0.6568, -0.5852, -0.4755, -0.6989, -0.5070, 0.5044, -0.7116, -0.4528, 0.5372, 0.9971, 0.0010, -0.0760, +-0.9837, 0.1034, 0.1468, -0.2432, 0.6057, 0.7576, -0.7480, -0.4119, -0.5204, 0.3746, -0.8922, 0.2522, -0.1915, -0.5686, 0.8000, -0.9733, -0.1935, 0.1233, 0.7797, -0.0057, 0.6262, 0.7785, 0.6191, -0.1031, -0.1636, -0.9832, 0.0807, -0.8745, -0.3372, 0.3487, 0.0483, 0.4523, 0.8906, -0.8671, -0.1165, -0.4844, -0.0075, 0.8874, 0.4609, -0.7160, -0.5754, -0.3954, -0.9902, 0.1398, -0.0056, -0.0518, 0.9660, -0.2535, +-0.0162, -0.0014, 0.9999, 0.7716, 0.5776, 0.2665, 0.7807, 0.4875, 0.3910, 0.9538, 0.0371, 0.2981, -0.0066, 0.9175, -0.3977, -0.0013, -0.0591, 0.9983, 0.3767, -0.7672, -0.5191, 0.9617, 0.1190, -0.2469, 0.4782, 0.6762, -0.5604, -0.8448, -0.2681, -0.4631, 0.9143, 0.2336, 0.3309, 0.2443, -0.6966, 0.6746, 0.4856, -0.6003, -0.6355, 0.6193, -0.7851, -0.0052, -0.0801, 0.9878, 0.1334, -0.5439, -0.5281, -0.6521, +0.0231, 0.7485, 0.6628, 0.0426, 0.7503, 0.6598, -0.4991, 0.4989, 0.7085, 0.4745, -0.5872, -0.6558, -0.7627, -0.5499, -0.3405, 0.1457, -0.1010, 0.9842, 0.3120, -0.8730, -0.3748, -0.1203, 0.9909, 0.0604, 0.8973, 0.4050, -0.1758, -0.2126, 0.9755, 0.0567, 0.2838, 0.8203, 0.4966, -0.0304, -0.8119, -0.5830, -0.7866, -0.6149, 0.0566, -0.9997, 0.0253, 0.0035, -0.9993, 0.0352, 0.0134, -0.9739, -0.1773, 0.1416, +0.3784, -0.8991, -0.2199, -0.9887, -0.1356, 0.0639, -0.1711, 0.9189, 0.3556, 0.2173, 0.7503, -0.6243, 0.6708, 0.0188, -0.7414, -0.2502, -0.1290, 0.9595, -0.5171, -0.8119, 0.2712, 0.3430, -0.0445, -0.9383, 0.3444, -0.0443, -0.9378, -0.6615, 0.5528, 0.5067, 0.7316, -0.4484, -0.5136, -0.9194, 0.2663, 0.2893, 0.1907, 0.7409, 0.6440, 0.9689, -0.2039, -0.1400, -0.3550, -0.9127, 0.2023, 0.0023, -0.1146, 0.9934, +0.3552, 0.3431, -0.8696, 0.3130, 0.8313, 0.4593, 0.8590, 0.5077, -0.0655, 0.1798, -0.9599, -0.2149, 0.8565, 0.4252, 0.2926, -0.7991, -0.5629, -0.2110, 0.2641, -0.4701, 0.8422, 0.4075, 0.9091, 0.0869, 0.9971, 0.0395, -0.0652, -0.1124, -0.7780, 0.6182, -0.7664, 0.6159, 0.1824, -0.9517, 0.3046, 0.0395, 0.4564, -0.7601, -0.4625, -0.1175, -0.4994, 0.8583, 0.4326, -0.7222, -0.5397, -0.5947, 0.8037, -0.0178, +-0.1360, -0.1466, 0.9798, -0.1340, -0.4808, 0.8665, 0.0877, -0.8424, -0.5317, 0.7474, -0.0334, 0.6636, -0.5691, -0.5974, 0.5650, 0.2482, -0.9654, 0.0803, 0.7315, 0.1420, 0.6669, -0.7118, -0.5187, 0.4736, -0.5168, 0.8199, 0.2462, 0.7173, -0.5608, -0.4135, -0.2046, -0.7366, 0.6446, -0.7719, 0.1295, 0.6224, -0.4010, 0.1588, 0.9022, -0.9199, -0.3467, -0.1832, -0.8080, -0.5590, 0.1862, -0.4445, -0.1384, 0.8850, +-0.9528, -0.1027, 0.2857, 0.3118, -0.9359, -0.1638, 0.5050, -0.5727, -0.6457, 0.7533, -0.1740, 0.6342, -0.1874, 0.9785, -0.0856, -0.0988, 0.0198, 0.9949, -0.5800, -0.2508, -0.7751, -0.2029, -0.5319, -0.8222, -0.6057, -0.4293, -0.6699, -0.5207, -0.7459, -0.4153, -0.6114, -0.6512, 0.4496, 0.2103, 0.8164, -0.5378, -0.3651, 0.9090, 0.2011, -0.9454, 0.0903, -0.3132, 0.4099, 0.9080, 0.0862, -0.4938, -0.8383, -0.2313, +-0.4433, -0.8658, -0.2321, 0.1154, -0.9921, -0.0494, 0.1262, -0.9917, 0.0244, 0.7497, -0.1336, 0.6481, 0.2939, -0.6484, -0.7023, 0.2714, 0.4872, 0.8300, -0.4257, -0.1933, -0.8840, -0.8640, -0.1869, -0.4675, -0.1731, -0.9697, -0.1727, -0.1623, 0.2573, -0.9526, 0.7745, 0.3141, -0.5491, -0.3541, -0.7162, 0.6014, -0.3450, -0.7236, 0.5978, 0.1319, 0.6924, 0.7094, 0.8384, 0.5420, -0.0577, 0.9402, -0.2626, -0.2169, +-0.9770, -0.0255, -0.2115, 0.0966, -0.8277, -0.5529, -0.0607, 0.8035, -0.5922, -0.9044, -0.2370, 0.3548, -0.4133, 0.7464, -0.5216, 0.6829, -0.2157, 0.6980, 0.3321, -0.7774, 0.5342, -0.9205, -0.3566, -0.1599, -0.3185, -0.3027, -0.8983, 0.8541, -0.2001, -0.4800, -0.9985, 0.0154, 0.0531, -0.7448, -0.1740, 0.6442, -0.4310, 0.8851, 0.1758, 0.6428, 0.2856, 0.7108, -0.0262, 0.1865, -0.9821, 0.3093, 0.4747, 0.8240, +-0.8525, 0.2657, -0.4502, 0.4032, 0.4590, 0.7917, 0.0349, -0.8604, -0.5084, 0.1248, -0.1926, 0.9733, -0.9857, -0.0673, 0.1545, -0.9639, 0.0817, 0.2535, -0.6949, 0.2244, 0.6831, 0.9210, 0.3805, 0.0833, 0.6089, -0.0193, 0.7930, -0.9934, -0.1011, 0.0541, 0.9872, -0.1585, 0.0187, -0.2454, -0.7781, 0.5782, -0.9227, 0.3791, 0.0693, -0.0474, -0.9214, 0.3856, -0.4009, -0.9055, -0.1391, -0.0031, 0.0129, 0.9999, +0.6918, 0.6521, -0.3101, -0.9292, 0.2095, -0.3044, -0.6699, 0.5504, -0.4983, -0.1362, 0.9900, -0.0357, 0.0946, -0.9888, 0.1152, 0.9565, 0.2915, -0.0118, -0.0885, -0.9710, -0.2221, -0.9706, 0.2403, 0.0124, 0.7155, 0.6833, -0.1455, 0.0179, -0.0130, 0.9998, 0.0131, -0.0118, 0.9998, 0.8599, -0.2853, 0.4232, -0.2197, -0.7466, -0.6280, 0.2449, 0.9532, -0.1774, 0.7703, -0.0307, -0.6370, 0.6004, -0.5310, 0.5979, +-0.1702, 0.9726, 0.1584, -0.1210, 0.9806, 0.1543, -0.9710, 0.1364, -0.1966, -0.9702, 0.1172, -0.2122, 0.9537, 0.1670, 0.2500, -0.4680, -0.7185, -0.5145, -0.8759, 0.0204, -0.4821, -0.9969, -0.0230, -0.0757, -0.9857, -0.1504, -0.0755, -0.6302, -0.7521, -0.1931, -0.6819, 0.3864, -0.6211, -0.1834, 0.8811, 0.4360, -0.6703, -0.3329, -0.6633, 0.3960, 0.8258, 0.4016, -0.4163, 0.6805, -0.6030, -0.2297, 0.9513, 0.2056, +0.9463, 0.0792, 0.3134, -0.7059, 0.7073, -0.0370, -0.8611, 0.5079, 0.0241, 0.3562, -0.3827, 0.8524, 0.7497, 0.6437, 0.1536, 0.1399, -0.4445, 0.8848, -0.4036, -0.0476, -0.9137, -0.8145, -0.4597, -0.3539, 0.9784, 0.0394, 0.2031, 0.2377, 0.6187, -0.7488, 0.2653, -0.9615, 0.0717, -0.0109, -0.6694, -0.7428, -0.3789, 0.7043, 0.6003, -0.9347, -0.3466, 0.0787, -0.7448, 0.6298, 0.2204, -0.1781, -0.5502, 0.8158, +-0.8055, -0.3861, 0.4495, 0.8968, 0.1594, 0.4128, -0.8263, -0.5192, 0.2184, 0.9579, 0.2572, -0.1277, -0.5433, 0.1340, -0.8288, -0.9122, 0.1202, -0.3917, -0.4271, -0.2543, 0.8677, 0.8471, -0.0638, 0.5276, 0.7349, -0.6608, 0.1527, 0.7168, -0.6303, 0.2980, -0.4686, 0.8830, 0.0269, 0.4684, 0.0676, -0.8809, 0.6353, -0.5068, -0.5827, -0.3346, -0.8225, 0.4600, 0.8517, 0.2720, 0.4479, 0.4613, -0.5206, -0.7185, +0.1488, 0.5083, 0.8482, 0.8107, -0.5459, 0.2116, -0.6726, -0.5044, 0.5414, -0.9893, 0.0101, 0.1452, -0.8298, -0.3869, 0.4023, 0.7505, -0.6060, -0.2638, -0.4792, -0.1574, 0.8635, 0.8477, 0.3579, -0.3915, -0.9090, -0.2677, -0.3194, 0.0895, 0.6517, 0.7532, 0.8736, 0.2124, -0.4379, 0.4153, 0.0797, 0.9062, 0.4410, 0.5521, 0.7076, -0.9784, -0.1599, -0.1309, 0.7635, 0.4140, -0.4957, -0.9410, 0.1350, -0.3102, +0.2976, 0.9432, 0.1478, 0.8603, 0.0417, 0.5081, -0.8647, -0.4901, 0.1101, 0.4289, 0.7067, 0.5627, -0.5525, -0.5855, -0.5933, -0.7300, 0.5685, 0.3793, 0.1227, 0.7536, 0.6458, -0.7317, 0.6416, -0.2303, 0.9165, -0.3934, 0.0727, 0.4115, -0.6017, 0.6845, 0.5305, -0.7622, 0.3710, -0.0614, -0.5487, 0.8338, 0.7113, -0.2804, -0.6445, -0.4659, -0.0985, -0.8793, 0.2238, 0.1354, 0.9652, 0.6392, -0.1533, 0.7536, +-0.4190, 0.0449, 0.9069, 0.7856, -0.1679, 0.5956, 0.8327, -0.5375, 0.1331, 0.5696, -0.7071, 0.4190, -0.0627, 0.8762, 0.4779, -0.5469, -0.3436, 0.7634, -0.1850, -0.9636, 0.1932, 0.8545, -0.2689, -0.4445, -0.7924, -0.3907, 0.4684, 0.2333, -0.7461, -0.6237, -0.5541, 0.1239, -0.8232, -0.5023, -0.8315, -0.2373, 0.8194, -0.2103, -0.5333, -0.9838, 0.0645, -0.1675, 0.3300, -0.8350, -0.4403, -0.0359, 0.9709, 0.2370, +0.9705, -0.1441, 0.1931, 0.3043, 0.6754, 0.6717, 0.5181, -0.8499, 0.0958, -0.2541, 0.5868, 0.7688, 0.1080, -0.0933, -0.9898, -0.1329, 0.9894, 0.0580, 0.9046, 0.3888, 0.1745, -0.3649, -0.4709, -0.8032, -0.4795, 0.3745, -0.7936, 0.1075, -0.0303, -0.9937, 0.9080, 0.4182, -0.0263, 0.8063, -0.5779, -0.1259, 0.9003, 0.2369, 0.3652, -0.7501, 0.5421, 0.3789, 0.8050, 0.5305, -0.2656, -0.0336, 0.9960, 0.0828, +0.5464, -0.8283, 0.1241, 0.9619, -0.2730, 0.0133, 0.7554, 0.4951, 0.4292, 0.4349, 0.8306, 0.3478, -0.8805, -0.4439, 0.1661, -0.3280, 0.9222, -0.2047, 0.8930, 0.2928, -0.3419, 0.0073, -0.8994, 0.4371, 0.1016, 0.7438, -0.6606, -0.6287, -0.0529, 0.7759, 0.8139, -0.5807, -0.0208, -0.7482, -0.0387, -0.6624, 0.2593, 0.2815, 0.9238, -0.0381, -0.3579, -0.9330, 0.9899, 0.0204, 0.1401, 0.7368, 0.6369, 0.2269, +0.7308, -0.5654, 0.3825, 0.5179, 0.1936, 0.8333, 0.4540, 0.7385, 0.4984, 0.0759, 0.9031, 0.4227, -0.9716, 0.1645, -0.1701, 0.3821, -0.8996, -0.2113, -0.3204, 0.7107, -0.6263, 0.5464, 0.1679, -0.8205, 0.1201, -0.9722, -0.2008, 0.9488, -0.2298, -0.2166, 0.6690, -0.4987, -0.5511, -0.0735, 0.9148, 0.3972, 0.9161, 0.0039, 0.4009, 0.2344, -0.8663, -0.4411, 0.6662, -0.7385, -0.1044, 0.6579, -0.7442, -0.1154, +-0.5123, -0.6120, -0.6025, -0.0620, -0.9042, -0.4225, -0.6417, -0.7070, -0.2973, -0.2134, -0.9730, 0.0879, 0.9958, -0.0028, -0.0911, 0.6554, -0.6587, 0.3696, 0.5165, -0.5497, 0.6566, 0.9139, 0.3468, 0.2110, 0.7492, 0.6236, 0.2231, 0.8156, 0.3666, 0.4477, 0.9630, 0.0728, 0.2594, -0.2598, 0.6657, 0.6996, 0.7106, 0.5190, -0.4750, 0.1965, -0.6918, 0.6949, 0.0698, -0.2272, 0.9714, 0.8533, 0.5126, -0.0954, +0.7333, 0.0424, -0.6786, 0.7425, -0.1533, 0.6521, 0.4797, -0.0581, 0.8755, -0.5632, -0.6652, -0.4901, -0.5910, -0.6426, -0.4876, -0.5702, 0.5492, -0.6109, -0.9030, 0.2157, -0.3716, -0.2872, 0.5284, 0.7989, -0.8755, -0.1954, 0.4419, -0.1682, -0.7620, 0.6254, 0.9029, 0.1960, -0.3826, -0.5692, 0.8199, -0.0615, 0.8987, -0.3367, -0.2811, 0.1192, 0.3470, 0.9303, 0.8975, -0.3259, -0.2969, -0.2674, 0.8081, 0.5249, +0.3185, -0.9413, 0.1122, 0.8486, -0.2078, -0.4866, -0.9642, -0.2333, -0.1264, -0.6387, -0.0649, -0.7667, 0.8738, 0.3231, -0.3635, 0.6751, -0.6646, 0.3202, -0.6387, 0.2553, 0.7259, -0.9050, -0.1790, 0.3859, 0.2600, -0.8834, 0.3898, 0.9645, 0.2573, 0.0589, 0.9000, -0.0047, 0.4359, -0.6926, 0.5420, 0.4761, -0.9058, 0.3050, 0.2940, -0.9493, -0.3025, -0.0854, -0.4469, 0.3872, 0.8065, -0.3704, -0.7337, -0.5696, +-0.6642, 0.7416, -0.0944, -0.2289, -0.9709, 0.0706, -0.1480, -0.9841, 0.0983, -0.1073, -0.3924, 0.9135, 0.4942, 0.7399, -0.4564, 0.1488, -0.9871, 0.0596, 0.9725, -0.1654, -0.1637, -0.0600, 0.1107, -0.9920, -0.1064, -0.9623, -0.2504, -0.9273, -0.0899, 0.3634, -0.1269, -0.9878, 0.0903, -0.7240, -0.3957, -0.5651, 0.9701, 0.1952, -0.1444, 0.1434, 0.0185, 0.9895, 0.7024, -0.2698, 0.6586, -0.5905, -0.4785, 0.6499, +0.9028, -0.2894, -0.3182, 0.7541, -0.1207, 0.6456, -0.5776, 0.0552, 0.8145, 0.5059, -0.8565, -0.1020, 0.1829, -0.9487, -0.2580, 0.7289, -0.4653, -0.5022, -0.0794, -0.7399, -0.6680, 0.5476, 0.6291, 0.5517, 0.1863, -0.8381, -0.5127, 0.6913, 0.5932, -0.4126, 0.9794, -0.1117, 0.1683, 0.9668, -0.0693, -0.2461, 0.9820, -0.0104, -0.1884, -0.8732, -0.3521, -0.3370, 0.0644, -0.1372, -0.9884, -0.1065, 0.4877, 0.8665, +0.9360, 0.0848, -0.3416, 0.4959, -0.6052, -0.6228, 0.4431, -0.2385, 0.8642, 0.7477, 0.5972, 0.2905, -0.7491, 0.5848, 0.3114, -0.8846, -0.0576, 0.4628, -0.1193, -0.8990, 0.4214, -0.6101, 0.2452, -0.7535, 0.5508, -0.4753, -0.6861, -0.2041, -0.7876, -0.5814, 0.1327, -0.1778, -0.9751, -0.1842, 0.7314, 0.6566, 0.5404, -0.6931, -0.4771, -0.7833, -0.6153, -0.0885, 0.3893, -0.3697, 0.8437, -0.0974, 0.9950, -0.0196, +0.9811, 0.1519, -0.1195, 0.9634, -0.1438, 0.2263, -0.8359, 0.5374, 0.1114, -0.9755, -0.1374, -0.1716, 0.3026, 0.4603, 0.8346, -0.7483, -0.0123, -0.6633, 0.0605, -0.9940, -0.0911, 0.6222, -0.6865, -0.3763, 0.5999, 0.7395, -0.3054, -0.2933, -0.5874, -0.7543, -0.9637, 0.2502, 0.0931, 0.5517, -0.8329, 0.0445, -0.8780, 0.0483, -0.4762, 0.5386, -0.1012, 0.8365, 0.9337, -0.3162, -0.1678, 0.9184, -0.3450, -0.1936, +0.9919, 0.1142, -0.0565, 0.6891, -0.6690, 0.2785, -0.0873, -0.9580, -0.2733, 0.1100, 0.9897, 0.0920, -0.2438, -0.7675, -0.5929, -0.0772, 0.9963, -0.0371, -0.8078, -0.3565, -0.4694, -0.5558, -0.5704, 0.6048, 0.4220, 0.3989, -0.8141, 0.6071, 0.7818, 0.1420, 0.9345, -0.0919, -0.3439, 0.7858, 0.5608, 0.2610, 0.2469, -0.8618, -0.4430, 0.8906, -0.3320, -0.3109, -0.0388, 0.9992, -0.0084, 0.7604, -0.1377, -0.6347, +-0.1130, 0.9894, -0.0910, 0.4479, 0.5494, -0.7054, 0.3522, 0.2423, 0.9040, -0.7404, 0.2384, 0.6285, 0.6960, -0.3895, -0.6033, -0.8120, -0.1496, -0.5642, 0.4846, -0.6116, 0.6254, 0.5398, 0.2899, -0.7903, -0.1945, 0.9793, 0.0563, -0.1579, 0.8331, -0.5301, 0.5107, -0.8142, 0.2763, 0.5314, -0.8041, 0.2664, 0.6284, -0.1776, 0.7573, -0.8952, -0.0197, 0.4452, 0.5159, -0.7515, 0.4113, 0.0565, 0.0674, 0.9961, +-0.1735, -0.9654, 0.1944, -0.9573, 0.2396, -0.1619, -0.5328, -0.1858, 0.8256, -0.4782, 0.7221, 0.5000, -0.3632, -0.8294, 0.4245, -0.2801, 0.9469, 0.1577, -0.1203, 0.1981, 0.9728, 0.5683, 0.3842, 0.7276, 0.4930, -0.8415, 0.2210, -0.0873, -0.9836, 0.1578, -0.9621, -0.0555, -0.2669, -0.9763, 0.2137, 0.0339, -0.7559, 0.4599, -0.4660, -0.9239, 0.0306, -0.3815, -0.7589, 0.5920, 0.2714, 0.5138, -0.6497, -0.5603, +-0.2900, 0.9132, -0.2861, -0.9228, -0.3563, -0.1467, 0.3100, 0.8559, -0.4140, -0.9979, -0.0622, -0.0163, -0.3569, 0.4420, -0.8229, -0.8222, -0.0463, 0.5673, 0.9740, 0.1052, 0.2005, -0.1159, -0.6192, -0.7766, -0.8370, 0.4110, 0.3613, -0.8828, -0.3407, 0.3234, -0.0878, 0.8337, 0.5452, -0.2785, 0.8823, 0.3795, 0.3396, -0.9390, 0.0540, -0.5585, -0.7521, -0.3499, 0.4023, 0.8989, 0.1737, 0.9117, 0.3513, 0.2129, +0.8964, 0.3870, 0.2162, -0.8166, 0.3298, 0.4736, -0.9864, -0.1462, -0.0750, -0.0901, -0.8148, 0.5726, 0.1720, 0.2205, 0.9601, -0.8187, -0.0153, -0.5740, -0.6335, -0.3138, 0.7072, -0.6641, -0.5203, -0.5369, -0.6122, 0.7016, 0.3648, -0.8617, 0.1463, -0.4859, 0.3440, -0.6940, -0.6325, -0.0982, 0.9731, -0.2083, -0.1179, 0.9558, -0.2694, -0.2643, 0.9559, -0.1282, -0.2866, 0.9561, -0.0607, 0.8698, -0.4558, -0.1890, +0.9092, -0.0279, -0.4155, -0.7290, 0.3758, -0.5722, -0.9687, 0.1770, -0.1741, 0.8550, 0.4883, -0.1745, -0.7557, -0.5582, -0.3425, 0.1834, -0.7615, 0.6217, 0.8192, -0.5643, -0.1026, 0.1622, -0.4364, -0.8850, -0.4110, 0.2036, 0.8886, 0.7385, 0.1890, 0.6472, 0.9826, 0.1781, 0.0528, -0.6650, -0.4225, 0.6159, 0.0098, -0.8243, 0.5661, 0.5485, -0.6783, 0.4890, -0.1786, 0.6791, 0.7120, -0.7962, -0.5203, 0.3088, +-0.4034, 0.9020, -0.1539, 0.9237, 0.3696, 0.1006, 0.9245, 0.3592, 0.1279, 0.8425, -0.4720, -0.2597, 0.9970, -0.0760, -0.0155, 0.2152, -0.9463, -0.2412, 0.8359, -0.5434, -0.0771, -0.8572, -0.5022, -0.1144, 0.7843, 0.2776, -0.5548, 0.5867, 0.7796, 0.2189, 0.8927, -0.4455, 0.0681, -0.0898, -0.0959, 0.9913, 0.1979, 0.0331, 0.9797, 0.8329, -0.1317, -0.5375, -0.5838, 0.3324, -0.7407, 0.3158, -0.3698, -0.8738, +-0.6074, -0.2991, 0.7360, -0.4262, -0.4502, 0.7847, 0.4028, 0.9054, 0.1343, 0.0691, 0.9780, 0.1966, 0.4848, -0.6092, -0.6275, 0.4338, -0.6648, -0.6082, -0.4814, 0.2120, 0.8505, 0.9176, -0.2108, -0.3369, -0.8367, -0.1946, 0.5119, 0.4716, 0.1685, -0.8656, 0.7249, -0.5346, 0.4344, 0.7076, -0.4000, -0.5825, 0.8533, -0.4823, -0.1980, 0.7861, 0.1435, 0.6013, 0.3086, 0.0290, 0.9507, 0.4693, -0.7244, 0.5049, +0.8318, 0.5381, 0.1362, -0.8139, 0.4483, 0.3695, -0.9987, -0.0462, 0.0207, -0.0764, -0.6761, -0.7328, 0.2907, 0.9154, -0.2783, -0.3472, 0.4958, 0.7960, 0.5790, 0.4832, 0.6567, 0.1116, -0.8704, 0.4796, -0.0104, 0.7733, -0.6339, -0.5249, 0.5373, -0.6601, -0.1633, 0.9054, -0.3918, -0.6279, 0.6719, 0.3929, 0.8915, 0.1419, 0.4303, -0.4773, 0.8603, -0.1793, -0.1689, 0.9659, 0.1962, -0.9483, -0.1251, 0.2918, +0.9787, -0.1835, 0.0923, -0.7594, -0.2871, 0.5838, -0.9904, 0.0252, 0.1359, 0.1553, -0.9650, -0.2115, 0.9821, -0.1331, 0.1332, 0.7756, 0.1478, -0.6137, -0.9818, 0.1890, 0.0210, -0.6881, 0.7256, 0.0028, 0.7224, -0.6237, -0.2986, -0.6640, 0.7122, 0.2278, 0.9848, 0.1364, -0.1079, -0.9986, 0.0531, -0.0047, -0.9039, -0.4266, 0.0314, -0.1732, 0.9567, 0.2339, 0.9969, -0.0422, 0.0667, 0.8334, 0.5383, 0.1254, +0.8939, 0.4129, 0.1745, -0.4279, 0.7408, 0.5177, 0.8753, 0.3629, -0.3195, -0.6758, -0.2926, 0.6765, 0.1069, -0.7363, -0.6682, -0.7541, 0.6552, 0.0456, 0.1677, -0.9361, -0.3093, 0.7146, 0.6908, -0.1105, 0.7033, 0.7001, -0.1231, 0.8686, 0.3916, 0.3035, -0.5517, -0.2025, -0.8091, 0.9440, 0.3079, 0.1185, -0.7607, 0.5015, -0.4121, 0.7649, -0.3032, 0.5683, -0.4392, -0.6046, -0.6645, 0.8948, 0.4009, -0.1963, +0.1147, -0.0263, 0.9930, 0.9372, 0.3482, -0.0186, -0.0005, -0.6097, -0.7926, -0.9794, -0.1330, 0.1521, -0.9509, -0.2186, -0.2191, 0.9842, -0.1491, 0.0960, 0.7961, -0.5888, -0.1400, 0.7881, -0.5954, -0.1564, 0.1308, -0.5531, -0.8228, 0.7845, 0.2271, -0.5771, 0.7899, -0.1975, 0.5805, 0.7984, -0.5032, -0.3308, 0.7199, 0.6868, -0.1001, 0.7666, 0.6375, 0.0766, -0.7675, -0.2669, -0.5829, -0.8440, 0.1588, 0.5123, +-0.5507, 0.1725, -0.8167, 0.4458, -0.8787, -0.1706, -0.1927, -0.1886, 0.9630, 0.8717, 0.1115, 0.4771, 0.9849, 0.1717, -0.0240, 0.8612, 0.4515, 0.2336, 0.8247, 0.4909, 0.2809, -0.9649, 0.2624, -0.0077, 0.5844, -0.7498, -0.3103, 0.2445, -0.2769, 0.9293, -0.2618, -0.9003, -0.3477, 0.3885, -0.9132, 0.1230, -0.6765, -0.5221, -0.5193, 0.1179, -0.8545, -0.5058, -0.0134, -0.7029, 0.7111, 0.9904, 0.0287, -0.1352, +-0.0195, 0.0446, 0.9988, -0.9309, 0.0590, 0.3606, 0.8882, 0.1104, 0.4461, 0.5700, 0.7596, 0.3134, 0.6785, -0.6851, -0.2652, 0.3821, -0.6796, 0.6262, -0.2283, 0.9088, 0.3492, -0.9814, 0.1908, -0.0227, 0.9360, 0.1442, 0.3212, 0.8185, -0.5736, -0.0331, 0.4209, 0.4702, 0.7757, -0.5474, -0.8305, -0.1033, -0.3101, -0.6173, 0.7231, 0.8942, 0.4172, -0.1624, -0.8188, -0.2140, -0.5326, 0.2316, -0.8716, -0.4320, +0.2212, -0.8825, -0.4150, 0.6652, 0.3333, -0.6681, 0.2252, 0.7186, -0.6580, -0.5974, 0.7997, 0.0609, -0.8317, -0.4735, -0.2898, -0.4108, -0.0458, 0.9106, 0.0097, 0.6239, -0.7815, 0.0895, 0.8200, -0.5653, -0.4012, -0.9095, -0.1092, 0.9738, -0.1289, -0.1873, 0.7113, -0.3901, 0.5848, -0.1937, -0.9411, 0.2770, -0.3342, -0.6673, 0.6655, -0.1431, 0.5385, -0.8304, 0.8745, 0.4386, 0.2071, -0.0314, -0.9025, 0.4296, +-0.9037, 0.2277, -0.3625, -0.1609, -0.8182, -0.5519, -0.8551, -0.3937, -0.3373, -0.2286, -0.7123, 0.6636, -0.4403, -0.3780, -0.8144, -0.7733, -0.6210, -0.1281, 0.1408, -0.9774, 0.1579, 0.4352, -0.7881, 0.4353, -0.8224, -0.5473, 0.1554, 0.9520, -0.3042, 0.0344, 0.6957, -0.3440, 0.6306, -0.8124, -0.0557, 0.5805, -0.8809, -0.4504, -0.1454, 0.4153, -0.6168, -0.6686, 0.5495, -0.7996, 0.2424, 0.6889, -0.3251, 0.6479, +0.4923, 0.0912, 0.8656, -0.9082, -0.3896, -0.1529, -0.4986, 0.8641, -0.0680, -0.2977, -0.7809, -0.5491, 0.1435, -0.5447, -0.8263, 0.9332, -0.0035, 0.3592, 0.3698, -0.7226, 0.5841, 0.5857, 0.6676, 0.4597, 0.0630, -0.7012, -0.7102, -0.9390, 0.0846, 0.3334, -0.5870, 0.6335, 0.5041, -0.9841, -0.0535, 0.1694, 0.6262, -0.1859, 0.7572, 0.9310, -0.2956, 0.2141, -0.1475, -0.2075, 0.9670, -0.6017, -0.2684, -0.7523, +-0.6408, 0.7677, 0.0106, 0.7747, -0.4964, 0.3916, 0.3141, 0.8899, 0.3307, -0.9154, -0.0337, -0.4011, -0.8931, -0.3724, -0.2523, 0.2210, -0.8776, -0.4255, -0.9618, -0.0793, -0.2622, 0.8746, 0.0761, 0.4788, 0.8488, 0.2660, 0.4569, 0.8662, 0.4974, 0.0479, 0.0485, 0.0216, 0.9986, -0.5520, 0.0974, 0.8282, 0.2740, 0.9183, 0.2856, -0.0660, -0.8152, -0.5754, -0.5851, 0.7144, 0.3838, 0.1096, 0.0414, 0.9931, +0.5036, -0.4425, 0.7420, -0.7967, -0.5550, -0.2394, 0.6846, 0.6945, 0.2216, -0.7843, 0.5353, 0.3136, -0.8729, -0.0735, -0.4824, 0.2800, 0.2970, 0.9129, 0.7323, -0.2893, 0.6165, 0.2545, 0.0204, 0.9669, 0.0911, 0.8264, 0.5557, 0.3019, -0.7877, -0.5370, -0.2024, 0.6782, -0.7065, -0.5680, -0.0195, 0.8228, -0.0544, -0.6105, 0.7902, 0.1022, 0.8614, 0.4975, -0.6174, -0.4086, -0.6722, -0.1024, 0.9853, 0.1367, +-0.7220, -0.5785, -0.3795, -0.9468, 0.0148, -0.3213, 0.2467, -0.1502, -0.9574, 0.0898, -0.8465, -0.5248, -0.2202, 0.3903, -0.8940, 0.2794, 0.9567, -0.0812, 0.8803, 0.0801, 0.4676, -0.6577, -0.5726, 0.4894, 0.8252, -0.3082, 0.4733, -0.4313, -0.9019, -0.0250, -0.8085, -0.0066, -0.5885, 0.8943, 0.1847, -0.4075, 0.9119, 0.3830, -0.1474, -0.8170, -0.0933, 0.5690, 0.9346, -0.3488, -0.0697, -0.9996, 0.0272, 0.0082, +-0.7058, 0.2829, -0.6495, -0.9011, 0.4336, 0.0041, 0.5157, -0.0128, 0.8567, 0.7917, 0.5865, 0.1711, 0.3728, 0.1536, 0.9151, 0.9380, -0.1344, -0.3194, 0.3421, -0.3784, 0.8601, -0.3430, 0.8983, 0.2746, -0.9946, 0.0148, 0.1025, 0.9106, 0.3830, -0.1553, 0.5169, 0.8374, -0.1777, 0.6880, 0.3281, 0.6473, -0.1715, -0.2412, 0.9552, 0.9690, -0.0982, -0.2267, -0.7242, 0.3632, 0.5862, -0.7324, 0.2835, 0.6190, +-0.2618, 0.9285, 0.2632, -0.6415, 0.0638, -0.7644, 0.2778, 0.9243, -0.2617, 0.0279, -0.0564, -0.9980, -0.3465, 0.9328, -0.0991, -0.7404, -0.4245, -0.5212, 0.6254, 0.2082, 0.7520, 0.4294, -0.8925, -0.1380, 0.8329, -0.5432, 0.1063, 0.9382, 0.3437, 0.0395, -0.0373, 0.9989, 0.0268, 0.8545, -0.4989, -0.1446, 0.7554, -0.1650, -0.6342, 0.6789, -0.6994, 0.2236, 0.8290, 0.3712, 0.4183, 0.2543, -0.9409, 0.2236, +0.6538, 0.7069, -0.2697, -0.2105, -0.1661, 0.9634, -0.6722, -0.6008, 0.4326, -0.9061, -0.4184, 0.0627, -0.5628, 0.4736, -0.6775, 0.1690, -0.8337, 0.5257, -0.9126, -0.3038, -0.2736, 0.2867, -0.1158, 0.9510, 0.4678, 0.3929, -0.7917, -0.2665, 0.1973, 0.9434, -0.4713, -0.5639, 0.6782, 0.7481, 0.5593, -0.3571, 0.7111, -0.6626, -0.2351, -0.3484, 0.6891, 0.6355, -0.3975, -0.8183, 0.4153, 0.1523, -0.4293, -0.8902, +0.8039, -0.1365, -0.5788, -0.4321, 0.9006, 0.0463, 0.6584, 0.7170, 0.2290, 0.8828, -0.4095, 0.2303, -0.4782, -0.8529, -0.2097, -0.5654, 0.8248, 0.0093, 0.9516, -0.2979, -0.0755, 0.3026, 0.9237, 0.2351, 0.7537, -0.3050, 0.5821, -0.3286, 0.9443, -0.0181, -0.9665, 0.0194, -0.2560, 0.4265, -0.1055, -0.8983, -0.1639, -0.7221, 0.6720, 0.9905, 0.0895, 0.1048, 0.9633, 0.0427, 0.2649, -0.4495, -0.3872, 0.8050, +-0.6864, 0.5227, 0.5056, -0.8413, -0.3122, -0.4413, 0.9568, -0.2711, 0.1052, -0.2335, 0.9720, 0.0282, -0.9716, 0.2361, -0.0142, 0.6826, -0.7250, -0.0917, -0.9485, 0.1998, 0.2460, -0.8530, -0.5177, -0.0656, 0.9934, 0.1062, -0.0428, -0.4371, 0.6339, 0.6380, 0.2011, 0.2459, -0.9482, -0.9664, -0.0506, 0.2520, 0.0518, 0.2576, 0.9649, -0.4381, 0.7689, 0.4657, -0.4370, 0.6699, 0.6002, -0.0145, -0.6907, 0.7230, +0.5636, 0.8150, -0.1349, 0.6713, 0.3078, -0.6742, 0.6844, 0.2246, 0.6937, -0.0138, 0.8518, -0.5237, 0.6429, 0.1350, -0.7539, 0.4811, 0.6270, -0.6127, 0.0734, 0.5404, 0.8382, -0.7672, 0.3488, -0.5382, 0.7254, -0.0945, -0.6818, 0.7304, -0.6810, 0.0532, -0.0493, 0.8939, 0.4456, -0.0086, 0.0129, 0.9999, 0.9885, 0.0531, -0.1413, -0.4858, 0.8495, 0.2058, 0.9336, -0.2978, -0.1994, -0.5955, -0.7864, -0.1643, +0.8491, -0.1789, -0.4970, -0.8293, -0.0578, -0.5558, 0.2817, -0.9254, -0.2536, -0.7406, -0.5303, -0.4127, -0.4647, 0.8004, 0.3786, 0.9092, -0.1438, -0.3907, 0.2747, 0.4392, 0.8554, -0.2360, -0.9253, -0.2969, 0.8272, 0.2512, 0.5027, -0.9761, 0.1360, -0.1694, 0.2374, 0.8433, 0.4822, 0.1401, -0.7680, -0.6249, 0.8393, -0.1247, -0.5292, -0.3275, 0.5382, 0.7766, 0.9398, -0.1862, -0.2865, -0.0441, 0.6548, 0.7545, +-0.6727, 0.3733, 0.6388, -0.9875, -0.0836, 0.1338, -0.6896, 0.7151, 0.1143, -0.5512, -0.3998, -0.7323, -0.5233, -0.4327, -0.7341, -0.4906, -0.5848, -0.6460, 0.5553, -0.1388, -0.8200, -0.3698, -0.5565, -0.7440, 0.1416, -0.7793, 0.6104, -0.7570, 0.2126, 0.6178, 0.9004, 0.4063, 0.1557, -0.7845, -0.5170, -0.3424, 0.9474, -0.3186, -0.0301, -0.4832, -0.4381, 0.7580, 0.4984, 0.7206, -0.4821, 0.6235, -0.6116, -0.4871, +0.9106, 0.3085, -0.2749, 0.4928, 0.0305, 0.8696, -0.7401, 0.0539, -0.6703, -0.8604, -0.4033, 0.3116, 0.1033, 0.2368, 0.9661, 0.9646, -0.2509, 0.0813, -0.7709, -0.5208, -0.3666, 0.0424, 0.0080, 0.9991, -0.4182, -0.8103, -0.4104, 0.0216, -0.7026, -0.7113, 0.7899, -0.6021, 0.1166, 0.7988, 0.5244, 0.2950, 0.9816, 0.1898, 0.0216, -0.1619, -0.7971, -0.5817, 0.8172, 0.5284, 0.2299, 0.3628, 0.1526, 0.9193, +0.2970, -0.8668, 0.4006, 0.2238, -0.8116, 0.5397, 0.1242, -0.4513, 0.8837, 0.0115, -0.7262, 0.6874, -0.0178, 0.0431, -0.9989, -0.7932, 0.2943, 0.5332, 0.8453, -0.5206, 0.1206, 0.8588, -0.3763, -0.3476, 0.1915, 0.9014, -0.3884, -0.5833, -0.1587, -0.7966, 0.9748, 0.1684, 0.1463, 0.6770, 0.5024, 0.5378, 0.5528, -0.6935, -0.4620, -0.8138, 0.1775, -0.5534, 0.9858, 0.0885, -0.1424, 0.9957, -0.0054, -0.0921, +0.8689, -0.3720, -0.3264, 0.2374, -0.6925, 0.6812, -0.5062, 0.0601, 0.8603, 0.2810, 0.8096, 0.5154, -0.2430, 0.9457, 0.2158, 0.6570, 0.7068, 0.2624, -0.8343, -0.1880, 0.5183, 0.6947, -0.7182, 0.0397, -0.6300, -0.6842, 0.3673, 0.2909, 0.8445, -0.4497, 0.9510, 0.2872, 0.1148, 0.7739, -0.5189, -0.3631, -0.5000, -0.7445, 0.4424, 0.6232, -0.7054, 0.3377, 0.9700, -0.2424, -0.0171, -0.2622, 0.3095, 0.9140, +-0.7875, 0.2968, 0.5401, 0.3517, -0.7017, 0.6196, 0.6962, 0.0482, 0.7162, -0.8110, 0.3602, -0.4610, 0.7226, -0.5978, 0.3471, 0.0845, 0.8734, 0.4796, 0.5996, -0.2835, -0.7484, -0.9843, -0.1624, 0.0691, 0.9398, 0.2322, 0.2509, 0.7141, -0.0573, 0.6977, 0.0036, 0.0270, 0.9996, -0.4398, -0.3303, -0.8352, 0.6691, 0.4961, 0.5533, 0.7704, 0.6312, 0.0898, -0.8719, 0.4894, -0.0150, 0.7789, 0.5540, -0.2941, +-0.7852, -0.0562, -0.6167, -0.0444, 0.5595, 0.8276, -0.2824, 0.4908, 0.8242, -0.5503, 0.7313, -0.4030, 0.9476, 0.2773, 0.1587, -0.7315, -0.4989, 0.4648, -0.8763, -0.4162, 0.2427, 0.9349, 0.1292, 0.3305, 0.9773, -0.0123, 0.2114, -0.1819, 0.9802, 0.0780, -0.5988, 0.0191, 0.8007, 0.9437, 0.1305, -0.3041, -0.2186, -0.9458, -0.2400, -0.3721, -0.7364, -0.5650, 0.9557, 0.1333, -0.2625, 0.9986, -0.0517, 0.0071, +0.4439, 0.8946, -0.0524, -0.6715, -0.6293, 0.3912, 0.4755, -0.7853, -0.3965, 0.3690, -0.1186, -0.9218, 0.9605, -0.1675, 0.2222, -0.8056, -0.4646, -0.3676, -0.3734, 0.5540, -0.7441, 0.0010, 0.6481, 0.7615, 0.8417, 0.4159, -0.3443, -0.7416, -0.4389, -0.5073, -0.1559, -0.9765, 0.1489, 0.7848, -0.6197, 0.0110, 0.0004, 0.6487, 0.7610, -0.8633, 0.4542, -0.2202, -0.6883, -0.3300, 0.6460, 0.6332, 0.6616, 0.4016, +0.5281, -0.6148, -0.5858, -0.7630, 0.5851, 0.2747, -0.6373, 0.3718, -0.6750, 0.4948, 0.2311, -0.8377, -0.2219, 0.4888, 0.8437, -0.7351, 0.6453, 0.2081, -0.8023, 0.4229, 0.4213, -0.5519, -0.5275, 0.6459, 0.7174, -0.0099, 0.6966, 0.4247, -0.1406, 0.8944, 0.2433, -0.6189, -0.7469, 0.9691, -0.0095, 0.2463, -0.0700, 0.8997, -0.4309, 0.0395, -0.9883, -0.1470, 0.9284, -0.1462, -0.3417, 0.5575, 0.8118, -0.1737, +0.6176, 0.7616, -0.1962, 0.7146, 0.6990, -0.0290, -0.7029, 0.6601, 0.2651, 0.0206, 0.8808, -0.4731, -0.4949, 0.7465, -0.4448, -0.9769, 0.1748, -0.1228, 0.2736, -0.0035, 0.9618, -0.3846, -0.9088, -0.1619, 0.7139, 0.0834, 0.6953, -0.2526, -0.7856, -0.5649, 0.9659, -0.2417, 0.0928, -0.5795, 0.3404, 0.7405, 0.6486, -0.6537, -0.3897, -0.3671, 0.4689, 0.8033, 0.4842, -0.5775, -0.6573, -0.1328, -0.9864, -0.0973, +-0.2113, -0.5809, 0.7861, -0.9005, -0.2155, 0.3777, -0.9212, 0.3376, 0.1933, 0.3035, -0.7660, 0.5667, 0.8491, -0.4487, -0.2786, -0.5856, 0.6705, -0.4556, -0.4407, 0.5752, 0.6892, -0.1020, -0.8793, -0.4652, 0.4657, -0.2824, 0.8387, 0.7798, 0.4922, 0.3867, -0.4481, -0.4871, -0.7497, -0.7504, 0.4580, -0.4766, -0.6691, -0.5562, -0.4929, -0.9730, 0.2308, 0.0004, 0.2339, -0.7498, 0.6189, 0.4720, -0.1873, -0.8614, +0.8194, 0.4033, -0.4073, -0.8840, 0.1961, 0.4244, 0.7158, -0.4710, 0.5155, -0.2622, -0.6975, 0.6669, 0.3860, 0.8367, -0.3885, 0.1876, -0.8093, -0.5566, -0.9814, 0.1908, -0.0223, -0.9875, 0.1575, 0.0072, -0.9954, -0.0058, 0.0958, 0.9928, 0.1040, -0.0598, 0.6141, 0.7782, 0.1314, 0.7362, 0.2371, 0.6339, -0.3336, 0.9226, -0.1937, -0.9494, 0.3109, -0.0437, 0.9758, 0.1864, 0.1147, -0.9866, -0.0074, 0.1627, +-0.2380, -0.9641, -0.1173, -0.9255, 0.3296, -0.1864, -0.5561, -0.1194, 0.8225, 0.4224, -0.7157, 0.5562, 0.5145, 0.5856, 0.6263, 0.8637, -0.5035, -0.0223, -0.6822, -0.6645, 0.3052, -0.1465, -0.0396, -0.9884, 0.7063, 0.5268, 0.4729, 0.0106, 0.9884, 0.1514, 0.9322, -0.3607, -0.0308, -0.4647, -0.3219, 0.8249, -0.6959, -0.0760, 0.7141, 0.9631, 0.2599, -0.0693, 0.1784, 0.1327, 0.9750, -0.9799, 0.0277, -0.1975, +-0.6300, -0.1702, -0.7577, 0.8354, -0.1326, 0.5334, -0.6527, 0.1123, 0.7493, -0.0919, -0.0083, -0.9957, -0.2343, -0.3220, -0.9173, -0.9500, 0.2560, -0.1786, 0.9853, 0.1039, 0.1356, -0.1350, -0.6034, -0.7860, 0.9440, -0.2784, -0.1772, -0.3032, -0.8181, -0.4887, 0.9373, -0.0887, 0.3370, 0.3288, -0.8426, 0.4265, -0.0349, -0.8665, -0.4979, -0.6074, -0.5596, -0.5638, -0.0969, 0.9505, 0.2951, -0.3108, -0.5195, 0.7960, +-0.9259, -0.0643, -0.3723, 0.9738, 0.2272, 0.0019, -0.9936, -0.0882, -0.0703, 0.2655, -0.9127, -0.3105, 0.8465, -0.1358, 0.5148, -0.5531, -0.3458, 0.7579, 0.6671, 0.3300, -0.6679, 0.7032, -0.6345, -0.3210, -0.8129, -0.4146, -0.4091, 0.7963, -0.1426, -0.5878, 0.9346, -0.3489, -0.0696, -0.8243, 0.1218, 0.5529, 0.9548, 0.2957, -0.0315, -0.4506, 0.8860, -0.1093, 0.8755, 0.2526, -0.4120, 0.9327, -0.0201, 0.3601, +0.6008, 0.7920, 0.1087, -0.9091, -0.2261, 0.3499, -0.6656, -0.5579, -0.4957, 0.8238, 0.5400, -0.1727, 0.8011, -0.1023, -0.5897, -0.9147, 0.0264, -0.4032, -0.2028, -0.9783, 0.0421, 0.0643, -0.7227, -0.6882, 0.7336, -0.6629, 0.1497, 0.8409, 0.5197, 0.1510, 0.7688, 0.6321, -0.0964, 0.8513, -0.4400, -0.2857, 0.7285, 0.2809, -0.6248, -0.1738, 0.5186, 0.8372, 0.7142, 0.6517, 0.2555, 0.0949, -0.8096, 0.5792, +0.5656, -0.8150, 0.1262, 0.9648, -0.1131, 0.2373, 0.3982, -0.1373, -0.9070, 0.6062, -0.7827, 0.1411, -0.0801, -0.6430, -0.7617, 0.5478, -0.0819, 0.8326, -0.9615, -0.1943, -0.1941, -0.7907, -0.5380, 0.2921, -0.0252, 0.0945, 0.9952, 0.8362, -0.3822, -0.3932, 0.9826, 0.1851, 0.0133, 0.1130, -0.6940, 0.7110, -0.9125, 0.3606, -0.1930, -0.8147, 0.1631, -0.5565, 0.6703, -0.2149, -0.7103, 0.2886, -0.8478, 0.4448, +-0.9496, 0.2141, -0.2291, -0.2616, 0.9225, 0.2838, -0.9914, -0.1309, -0.0058, -0.8340, 0.5512, 0.0237, -0.0841, 0.0005, 0.9965, -0.8287, -0.4721, 0.3006, -0.3788, 0.8569, 0.3495, 0.2795, -0.9531, 0.1160, 0.9945, 0.0244, -0.1020, 0.6372, -0.0475, 0.7692, -0.5758, -0.3136, 0.7550, 0.5416, 0.4278, 0.7237, -0.9329, 0.1155, 0.3412, -0.8596, -0.3370, -0.3841, -0.9010, -0.3024, 0.3110, -0.8324, 0.4293, 0.3505, +-0.8322, 0.5073, 0.2240, 0.7182, 0.6959, -0.0048, -0.5463, 0.6412, -0.5389, -0.7084, 0.2842, 0.6461, -0.4045, -0.1427, 0.9033, -0.8930, 0.0240, 0.4494, -0.9887, 0.0010, 0.1497, 0.8924, -0.4423, 0.0892, -0.4303, 0.7535, 0.4970, -0.4994, -0.8463, 0.1857, -0.9210, -0.1926, 0.3386, -0.9269, 0.3742, -0.0281, 0.8298, -0.2975, -0.4721, -0.7501, 0.1410, -0.6461, -0.7946, -0.2877, -0.5346, -0.4680, -0.8753, 0.1217, +-0.5504, -0.2629, -0.7924, -0.3830, -0.5006, 0.7763, -0.4954, 0.5640, -0.6607, -0.3300, 0.9088, -0.2551, 0.0070, -0.6324, -0.7746, -0.9311, -0.2945, -0.2153, -0.2481, -0.9506, 0.1866, 0.3338, -0.0591, -0.9408, 0.8274, 0.5610, -0.0270, 0.5269, 0.7677, -0.3648, -0.9417, 0.0988, 0.3217, -0.0659, -0.9872, 0.1450, 0.1422, -0.7709, 0.6209, 0.7664, -0.2513, 0.5912, -0.8688, 0.0680, 0.4904, 0.9456, 0.3188, 0.0643, +0.7448, 0.4953, 0.4471, 0.2882, 0.0137, 0.9575, -0.9101, -0.1119, 0.3989, -0.5095, -0.8092, 0.2924, -0.9390, 0.2101, 0.2723, -0.1978, 0.2182, 0.9557, -0.1377, -0.3298, 0.9340, -0.0052, 0.2753, -0.9613, -0.6148, 0.7778, 0.1304, -0.1002, 0.7990, 0.5929, -0.4524, -0.6156, 0.6452, 0.6063, 0.1329, 0.7840, -0.2256, -0.9520, -0.2068, -0.0355, -0.9747, 0.2206, 0.3032, -0.4824, 0.8218, -0.9191, -0.0589, 0.3896, +-0.1984, -0.7461, 0.6357, -0.6932, -0.6947, -0.1919, -0.6661, 0.1428, 0.7321, -0.7345, -0.6123, -0.2926, -0.8334, -0.5519, -0.0294, -0.8912, 0.0163, 0.4533, -0.5690, -0.7873, -0.2376, 0.1913, -0.7133, -0.6742, -0.9794, -0.0732, -0.1882, -0.9519, -0.2971, 0.0751, 0.6192, -0.4013, 0.6749, -0.8108, -0.1792, -0.5572, -0.4276, -0.8670, -0.2559, -0.8650, 0.0485, -0.4995, -0.4724, -0.8063, -0.3560, 0.4878, -0.1960, 0.8507, +0.4910, 0.0050, -0.8712, 0.6584, -0.6574, -0.3666, 0.9061, 0.4078, 0.1122, -0.7682, 0.5401, -0.3439, -0.9002, 0.3987, 0.1750, -0.9331, 0.0227, 0.3589, -0.1067, 0.9942, 0.0155, 0.9329, -0.3558, 0.0562, -0.8028, -0.5706, -0.1728, 0.7901, -0.3054, 0.5315, 0.8896, -0.2663, -0.3711, 0.9373, 0.2932, -0.1884, -0.8736, 0.0058, -0.4867, -0.8127, -0.5287, -0.2448, -0.9932, 0.0253, 0.1136, -0.7610, 0.2042, 0.6157, +0.9733, 0.2284, 0.0208, 0.7622, 0.5049, -0.4050, 0.7043, -0.6103, -0.3626, -0.4352, -0.4188, -0.7970, 0.2325, -0.4751, -0.8487, -0.6026, 0.7803, 0.1677, -0.7958, 0.1285, -0.5917, -0.4967, -0.8646, -0.0761, -0.9822, -0.1499, -0.1131, -0.7141, 0.1831, -0.6756, -0.0537, -0.6838, 0.7277, 0.7359, -0.1788, -0.6531, 0.3391, 0.9407, -0.0115, 0.9678, -0.0235, -0.2507, -0.2242, 0.5708, 0.7899, -0.4279, 0.5918, -0.6832, +0.8980, 0.4398, 0.0150, 0.8936, -0.3558, 0.2737, 0.6325, 0.7340, 0.2474, 0.5600, -0.4194, -0.7145, 0.1855, -0.0637, 0.9806, -0.0321, -0.7686, -0.6389, -0.7599, 0.5980, 0.2549, 0.5613, -0.7666, 0.3119, 0.8265, 0.3319, -0.4547, -0.4277, 0.6515, 0.6265, 0.6189, 0.7793, 0.0980, -0.3250, -0.6153, 0.7182, -0.0085, -0.9695, 0.2449, -0.0151, 0.8196, 0.5728, 0.9561, 0.2722, -0.1084, -0.9330, 0.0283, 0.3588, +-0.6486, -0.0775, -0.7571, 0.4100, 0.2865, 0.8659, 0.6782, -0.3748, -0.6321, -0.9779, -0.1177, 0.1728, 0.5905, 0.6167, 0.5205, -0.1000, 0.9818, 0.1617, -0.6826, -0.1089, 0.7226, -0.4162, -0.6967, 0.5843, -0.1946, -0.0912, 0.9766, -0.8423, 0.4373, -0.3151, -0.5855, -0.7805, -0.2192, -0.2806, 0.2348, 0.9307, 0.8566, 0.4832, 0.1811, -0.3932, 0.7727, 0.4983, -0.9662, -0.1516, 0.2083, 0.5257, 0.3072, 0.7933, +0.0283, -0.1704, 0.9850, 0.9579, -0.2872, -0.0043, 0.9610, -0.0825, -0.2638, 0.9283, 0.1482, 0.3409, 0.4073, -0.7386, -0.5371, -0.0784, 0.9276, -0.3652, 0.9796, -0.0337, 0.1982, -0.5183, -0.7433, -0.4229, -0.8126, 0.5681, -0.1301, -0.7911, 0.0381, 0.6105, 0.7286, -0.3479, 0.5900, -0.5951, -0.7017, -0.3918, 0.8935, 0.2324, -0.3842, 0.2262, 0.8173, 0.5299, 0.3784, 0.9212, 0.0905, 0.8103, -0.1734, 0.5598, +-0.9470, 0.3205, 0.0213, -0.9343, 0.3524, -0.0533, -0.2174, -0.7178, -0.6615, -0.3163, -0.6504, -0.6906, 0.2190, -0.9684, 0.1191, -0.9709, -0.0259, 0.2380, -0.7953, -0.6057, 0.0229, 0.8109, 0.5458, -0.2112, -0.7752, -0.5656, 0.2814, 0.9362, 0.3507, 0.0241, -0.6646, 0.0126, 0.7471, 0.7050, -0.0799, -0.7047, 0.7302, -0.0749, -0.6791, 0.8197, -0.3511, 0.4527, -0.9439, -0.3258, 0.0539, 0.0218, -0.1718, 0.9849, +0.4895, -0.6173, -0.6159, 0.7769, 0.0831, -0.6241, -0.8781, 0.4549, 0.1486, -0.9898, -0.1324, 0.0521, 0.4199, -0.3555, -0.8351, 0.6838, 0.5524, -0.4768, 0.8912, 0.4255, 0.1573, -0.9128, 0.0658, -0.4030, 0.0128, 0.3109, 0.9503, 0.4267, 0.8119, 0.3984, 0.9332, -0.1792, -0.3115, 0.1202, 0.6756, -0.7274, 0.2507, 0.3655, 0.8964, -0.4168, -0.8866, -0.2005, -0.3834, 0.8162, 0.4322, -0.4416, -0.8841, 0.1530, +0.8636, 0.4946, -0.0976, 0.9145, 0.1086, -0.3898, 0.6584, -0.6553, 0.3703, -0.0788, -0.9751, 0.2072, 0.2301, -0.8719, -0.4322, -0.7403, 0.5297, -0.4139, 0.1860, 0.9740, -0.1292, -0.0201, -0.7461, 0.6655, -0.8662, -0.3178, -0.3856, -0.5384, -0.8355, 0.1099, 0.9986, 0.0334, 0.0411, 0.9948, 0.0137, 0.1007, 0.0049, -0.9933, -0.1154, -0.2341, 0.0985, 0.9672, -0.6423, -0.6957, -0.3218, 0.3166, -0.0617, -0.9465, +0.8927, 0.2185, -0.3942, 0.8621, -0.4865, -0.1419, 0.8657, -0.4955, -0.0714, -0.5013, 0.7105, 0.4939, 0.9232, 0.0199, 0.3837, 0.9494, -0.2806, 0.1413, 0.8859, 0.0969, 0.4537, -0.4864, 0.6316, 0.6038, -0.0248, 0.0199, 0.9995, -0.0242, 0.0202, 0.9995, 0.0506, -0.7783, 0.6258, 0.7083, 0.4748, 0.5224, 0.6323, -0.7420, -0.2230, -0.8515, -0.2676, 0.4510, 0.7084, 0.6229, 0.3318, 0.4969, -0.5028, 0.7073, +-0.9542, 0.2493, -0.1652, -0.7717, -0.3844, 0.5067, -0.2414, 0.1522, 0.9584, 0.3984, 0.8552, 0.3316, 0.6879, -0.6171, 0.3820, -0.1390, -0.4766, 0.8681, -0.9859, -0.1325, -0.1021, -0.9878, 0.1254, 0.0919, -0.9349, -0.3162, -0.1609, -0.9714, -0.0281, 0.2357, -0.9112, -0.2855, 0.2971, 0.9729, 0.1649, 0.1620, -0.9977, -0.0587, -0.0349, 0.5142, 0.7602, 0.3971, 0.1850, -0.0234, 0.9825, 0.9614, -0.2681, -0.0613, +0.1597, -0.7157, -0.6798, -0.1986, -0.9060, -0.3737, 0.1315, -0.7817, 0.6096, 0.8717, -0.3833, -0.3053, 0.8885, 0.4552, 0.0577, -0.1824, 0.9797, -0.0833, -0.3417, -0.7243, 0.5988, 0.4229, 0.7079, 0.5658, -0.4064, 0.3185, -0.8564, -0.9629, 0.0260, -0.2688, -0.1698, 0.2768, 0.9458, -0.8860, 0.2758, 0.3728, -0.4956, -0.7893, 0.3624, -0.3034, -0.5139, 0.8024, -0.4396, -0.3890, -0.8096, -0.6797, 0.3295, 0.6554, +-0.9636, -0.1702, 0.2063, -0.0124, -0.9685, 0.2485, -0.7372, -0.6733, -0.0558, -0.8173, 0.4202, -0.3944, 0.7255, 0.6701, 0.1568, -0.6104, 0.1758, -0.7723, -0.1430, -0.8329, 0.5346, -0.1323, -0.8386, 0.5285, -0.3318, -0.2101, 0.9196, -0.1747, -0.4973, -0.8498, -0.1284, -0.5095, -0.8508, -0.8556, 0.0910, 0.5095, 0.6307, -0.3549, -0.6902, -0.0686, -0.9401, 0.3339, 0.5416, 0.8237, -0.1681, -0.8636, 0.4921, 0.1096, +0.6562, -0.0806, -0.7503, -0.5007, -0.7885, 0.3572, -0.4576, -0.5459, 0.7019, -0.7776, 0.1755, -0.6038, -0.2696, 0.3127, 0.9108, 0.3587, -0.8564, 0.3714, 0.6704, 0.2807, 0.6868, 0.7096, -0.4201, 0.5657, -0.9800, 0.0156, 0.1981, 0.8985, -0.4318, -0.0794, -0.1104, 0.0362, 0.9932, 0.7715, -0.5821, -0.2569, -0.5135, 0.8460, -0.1439, 0.6899, 0.0417, 0.7227, 0.5910, 0.7712, 0.2364, -0.6635, -0.2871, 0.6909, +0.5207, -0.6842, 0.5107, -0.1577, -0.0478, -0.9863, 0.1087, -0.9851, -0.1336, 0.6247, 0.5053, 0.5953, 0.6052, 0.7791, 0.1634, -0.5807, -0.6709, -0.4612, -0.9690, -0.1285, -0.2110, -0.9940, -0.0522, -0.0963, 0.9273, -0.0862, -0.3643, 0.9266, -0.0923, -0.3646, -0.1140, -0.6543, -0.7476, -0.1246, 0.9858, 0.1123, 0.7633, -0.1616, 0.6255, -0.6412, 0.5628, -0.5216, -0.0468, -0.4998, 0.8648, 0.2016, -0.0579, -0.9777, +-0.6763, -0.5446, -0.4960, -0.2226, 0.9748, -0.0165, -0.0124, 0.0463, -0.9989, -0.0299, 0.9056, 0.4230, -0.0873, 0.9060, 0.4141, 0.5378, 0.4463, 0.7152, 0.9042, -0.2734, -0.3283, 0.8128, 0.4062, -0.4176, 0.7577, 0.2170, 0.6155, -0.3553, -0.7620, -0.5414, 0.7054, 0.6980, 0.1237, 0.5064, -0.1859, -0.8420, 0.1312, 0.0714, 0.9888, -0.9069, -0.4185, -0.0487, 0.1529, -0.0804, -0.9850, -0.3170, -0.8144, 0.4860, +-0.4670, 0.1107, 0.8773, 0.9656, -0.2468, -0.0814, -0.8445, -0.5003, 0.1912, 0.6636, 0.3711, -0.6496, 0.7523, 0.2069, -0.6254, 0.7961, -0.4684, -0.3833, -0.0663, -0.9633, -0.2603, 0.0925, 0.6486, 0.7555, -0.9776, -0.1744, -0.1182, -0.9630, 0.1587, -0.2180, -0.4039, 0.9041, -0.1395, 0.5872, -0.6007, -0.5426, -0.8365, 0.1581, 0.5248, 0.0791, -0.0464, 0.9958, -0.9156, -0.3075, -0.2591, -0.0074, 0.3247, -0.9458, +-0.5099, -0.8558, -0.0875, -0.3571, 0.6811, -0.6392, -0.3801, 0.8828, 0.2761, -0.3206, 0.9471, -0.0126, 0.5847, 0.3574, 0.7283, -0.1430, 0.5509, -0.8223, -0.8991, 0.0089, 0.4376, -0.0971, 0.5304, 0.8422, 0.5430, -0.7243, -0.4250, -0.8236, -0.2315, -0.5177, 0.5440, -0.6857, 0.4835, 0.6169, -0.1565, 0.7713, 0.6983, -0.7090, 0.0983, -0.0413, 0.5741, 0.8177, 0.2390, -0.4560, -0.8573, 0.1930, 0.7617, 0.6185, +-0.0727, -0.9973, -0.0117, 0.7075, -0.6115, -0.3543, -0.2740, -0.6123, 0.7416, -0.1335, -0.2277, 0.9645, 0.9518, 0.0899, -0.2932, 0.9069, -0.2470, -0.3414, -0.0007, -0.7154, 0.6987, -0.0681, -0.7086, 0.7023, -0.0500, 0.5340, 0.8440, -0.9580, 0.2749, -0.0820, 0.9924, 0.0753, 0.0970, 0.3053, -0.2135, 0.9280, 0.7624, -0.2056, 0.6136, 0.7291, -0.5715, -0.3766, 0.2024, 0.0554, -0.9777, 0.3877, 0.9093, 0.1512, +-0.9282, 0.0145, -0.3718, 0.6773, 0.6267, -0.3855, -0.6785, 0.4850, -0.5518, -0.6376, -0.3735, -0.6738, 0.7482, -0.3401, -0.5697, -0.6733, -0.7392, -0.0157, 0.1527, -0.8472, 0.5089, 0.9192, 0.1222, -0.3744, -0.8218, -0.4592, 0.3375, -0.3546, 0.8994, 0.2557, 0.1694, -0.6005, -0.7815, -0.9145, 0.0581, -0.4005, -0.3725, -0.5574, -0.7420, 0.6842, -0.4626, 0.5637, -0.2702, -0.0007, 0.9628, -0.6603, 0.7253, 0.1948, +0.7238, 0.5748, 0.3818, 0.8533, -0.4833, -0.1957, 0.6008, 0.6135, 0.5125, 0.9460, -0.3066, -0.1051, 0.9321, 0.1265, 0.3393, -0.5532, -0.3514, 0.7553, 0.9345, -0.3318, -0.1287, 0.1241, 0.3128, 0.9417, -0.1000, -0.6134, -0.7834, -0.5349, -0.5776, -0.6166, -0.8268, 0.5551, 0.0904, 0.9115, -0.3841, -0.1470, 0.6846, -0.4004, -0.6091, 0.1860, 0.8303, 0.5253, 0.6034, -0.7666, 0.2195, -0.2254, -0.9250, -0.3059, +0.0223, -0.9184, 0.3951, -0.2992, -0.8931, 0.3359, 0.9656, 0.1669, 0.1992, 0.9255, 0.0540, 0.3750, -0.5365, 0.2026, -0.8192, 0.8012, 0.1375, 0.5823, 0.0161, -0.0907, 0.9957, -0.8879, -0.0579, 0.4563, -0.8116, 0.3802, -0.4435, -0.1129, 0.3722, 0.9213, 0.9374, 0.3431, -0.0603, 0.1036, -0.0158, 0.9945, -0.7699, -0.5344, -0.3487, -0.1414, -0.9835, -0.1124, 0.4169, -0.8799, -0.2281, -0.4833, 0.5218, 0.7029, +0.3837, 0.7748, 0.5024, -0.9665, 0.2228, 0.1272, 0.5395, 0.8239, 0.1736, -0.2379, -0.1045, 0.9656, -0.6134, 0.0060, 0.7898, 0.9706, 0.0196, -0.2398, 0.8633, 0.0925, -0.4962, 0.7222, -0.2525, -0.6439, -0.5086, -0.5557, 0.6577, 0.1356, 0.6608, -0.7382, -0.6984, -0.7156, 0.0156, 0.9572, -0.2619, 0.1231, 0.4677, 0.3960, -0.7902, 0.8867, 0.2938, 0.3570, -0.4540, -0.8713, -0.1864, 0.9052, 0.3744, -0.2011, +0.3065, -0.1203, 0.9442, -0.7101, 0.5377, -0.4547, -0.2961, -0.0017, -0.9551, -0.1022, -0.8740, 0.4750, 0.4058, -0.7522, -0.5191, 0.5843, 0.4848, 0.6508, -0.5206, -0.4727, 0.7110, -0.2525, 0.8677, -0.4282, -0.0450, -0.1147, 0.9924, 0.4399, -0.7228, -0.5329, -0.1259, -0.4423, 0.8880, -0.0092, -0.9689, -0.2472, -0.7131, -0.6174, -0.3323, 0.6957, -0.3936, 0.6009, 0.5901, -0.6620, 0.4620, -0.4415, -0.8209, 0.3621, +0.6018, 0.7394, 0.3018, -0.4468, -0.5424, 0.7114, 0.4350, 0.4834, -0.7596, -0.1987, -0.9129, -0.3566, -0.6577, -0.5089, 0.5553, 0.5155, 0.8226, 0.2400, 0.7784, 0.5961, 0.1970, 0.9117, -0.3239, 0.2528, 0.5558, 0.4078, 0.7244, -0.5798, 0.4637, 0.6699, -0.5268, -0.8020, -0.2815, 0.8037, -0.2356, -0.5464, 0.0790, 0.9510, 0.2990, -0.5412, 0.8401, 0.0367, -0.0164, -0.7418, -0.6704, 0.8717, -0.3901, -0.2966, +-0.9327, 0.3498, -0.0876, -0.9155, 0.2229, -0.3350, 0.3503, -0.3955, 0.8490, -0.4118, 0.8762, 0.2504, -0.8862, -0.3089, -0.3453, -0.9839, -0.1638, 0.0718, -0.7136, -0.7006, -0.0063, -0.4590, -0.2035, -0.8648, -0.7219, -0.5866, -0.3670, -0.3418, -0.1979, 0.9187, 0.7914, 0.5919, -0.1530, -0.0899, -0.7204, -0.6877, -0.7942, 0.5047, -0.3383, -0.6956, 0.5559, 0.4551, 0.2363, -0.6789, -0.6951, -0.8747, -0.3554, 0.3295, +-0.3489, -0.0951, -0.9323, 0.7175, 0.6873, 0.1132, -0.1905, 0.7852, -0.5892, -0.0636, -0.5797, 0.8123, -0.1597, -0.9610, -0.2256, -0.1912, 0.8004, -0.5681, -0.5971, -0.7201, -0.3534, -0.7639, 0.1419, -0.6295, -0.1682, 0.9396, 0.2981, -0.8426, -0.5232, 0.1272, -0.1152, 0.9829, 0.1438, 0.1910, -0.4843, 0.8538, 0.7191, 0.5701, 0.3974, 0.9681, 0.0461, 0.2463, 0.5958, -0.4384, 0.6730, 0.7497, 0.4572, 0.4784, +-0.1496, 0.9676, -0.2035, -0.6357, -0.2450, -0.7321, 0.7783, -0.2776, -0.5631, -0.3598, -0.6720, 0.6473, 0.7399, 0.2722, 0.6152, 0.5749, 0.3665, 0.7316, 0.9095, 0.3655, 0.1982, 0.0215, 0.0362, 0.9991, 0.9722, 0.1588, 0.1722, -0.8239, 0.0991, -0.5579, -0.3807, -0.9028, -0.2003, -0.3909, -0.8983, -0.2003, -0.0000, 0.0000, -1.0000, -0.0004, 0.0000, -1.0000, -0.0022, 0.0001, -1.0000, -0.0003, -0.0000, -1.0000, +-0.0000, 0.0000, -1.0000, 0.0000, 0.0000, -1.0000, 0.0000, 0.0000, -1.0000, -0.0035, -0.0003, -1.0000, -0.0000, -0.0000, -1.0000, 0.0000, -0.0000, -1.0000, -0.0000, 0.0000, -1.0000, -0.0000, -0.0000, -1.0000, 0.0000, -0.0000, -1.0000, 0.0001, 0.0000, -1.0000, 0.0000, 0.0000, -1.0000, -0.0000, 0.0000, -1.0000, -0.0002, -0.0000, -1.0000, -0.0000, 0.0000, -1.0000, 0.0002, 0.0001, -1.0000, 0.9763, 0.1942, -0.0955, +0.7008, -0.6868, 0.1929, -0.4634, -0.8846, -0.0524, 0.9926, -0.1127, 0.0449, 0.8931, 0.4471, -0.0502, -0.9465, 0.3190, 0.0485, -0.8639, 0.4927, 0.1046, -0.9981, 0.0621, 0.0009, 0.8914, -0.4008, 0.2116, 0.0187, -0.9927, -0.1187, -0.6381, -0.7607, 0.1189, -0.9699, -0.2241, 0.0953, -0.4794, 0.8718, 0.1008, -0.7096, 0.6987, 0.0904, -0.1307, 0.9893, 0.0650, 0.4929, -0.8587, 0.1402, 0.6251, -0.7757, -0.0873, +-0.8682, -0.4861, 0.1000, 0.2337, -0.9591, 0.1598, 0.2201, 0.9634, 0.1529, 0.4054, 0.9092, -0.0947, 0.0502, -0.0071, 0.9987, 0.9152, 0.1555, 0.3717, -0.8875, 0.2587, 0.3813, -0.0220, -0.0137, 0.9997, -0.0031, 0.0127, 0.9999, -0.5825, 0.5449, -0.6032, 0.8874, -0.4604, -0.0246, 0.6426, 0.6475, -0.4098, 0.1063, -0.9914, 0.0760, -0.9721, 0.2267, 0.0603, -0.1403, -0.9447, 0.2964, -0.9594, -0.1376, 0.2463, +0.0959, 0.6401, -0.7623, -0.8635, 0.4859, -0.1354, 0.2008, -0.8184, 0.5384, 0.4046, -0.9139, 0.0337, 0.9878, 0.0534, -0.1461, 0.9966, -0.0024, -0.0826, 0.9903, -0.1307, -0.0476, 0.2223, -0.3042, 0.9263, 0.1824, 0.6304, 0.7545, 0.1316, 0.1048, 0.9857, -0.3406, -0.0716, -0.9375, -0.8471, 0.0812, -0.5253, 0.4991, -0.0711, 0.8636, 0.5776, 0.6771, 0.4559, 0.5264, 0.8041, 0.2763, -0.5442, 0.8265, 0.1436, +-0.7209, -0.4963, 0.4837, 0.2911, 0.1792, 0.9398, 0.0037, 0.2364, 0.9717, 0.8980, -0.3747, 0.2307, 0.7658, -0.5898, -0.2562, -0.3134, 0.6866, -0.6561, 0.9770, -0.1154, 0.1795, -0.1388, -0.9105, 0.3895, 0.5316, -0.8467, -0.0222, 0.8929, -0.4283, -0.1385, -0.1129, -0.9819, 0.1519, 0.2705, 0.7906, 0.5494, -0.1301, -0.6821, -0.7196, 0.1930, 0.9186, 0.3448, 0.6350, -0.6552, -0.4092, 0.3367, 0.8276, 0.4491, +0.4875, -0.8321, 0.2645, 0.5683, -0.7158, -0.4057, 0.5052, -0.8572, 0.0998, 0.9428, 0.3115, 0.1189, 0.0316, -0.0542, 0.9980, 0.9304, -0.0359, -0.3648, -0.9956, 0.0929, -0.0078, 0.0085, -0.7250, -0.6887, 0.9790, -0.0487, -0.1978, -0.2375, 0.9407, 0.2421, 0.0151, -0.4169, 0.9088, -0.1421, 0.9321, 0.3332, 0.3985, 0.6855, 0.6093, 0.0717, 0.8034, 0.5912, -0.8991, 0.2203, 0.3782, 0.8665, -0.0153, -0.4989, +-0.7705, -0.4649, -0.4361, -0.4112, 0.8511, -0.3264, -0.6334, 0.7731, 0.0330, 0.2590, -0.1665, 0.9514, -0.4589, 0.7963, -0.3941, -0.1508, -0.9123, 0.3808, -0.9576, 0.0565, -0.2824, -0.1437, 0.2443, -0.9590, 0.5144, 0.8067, 0.2908, -0.4599, 0.8186, 0.3441, -0.9968, -0.0799, 0.0066, -0.3939, 0.3495, 0.8501, 0.9087, -0.3345, -0.2496, 0.8625, -0.2084, -0.4612, 0.7894, 0.6122, 0.0464, -0.8902, -0.1120, 0.4416, +0.4078, -0.6535, 0.6377, 0.0586, 0.7823, -0.6201, -0.9636, 0.1959, 0.1817, -0.8657, -0.4931, -0.0857, 0.7071, 0.5276, 0.4709, 0.6588, 0.7190, 0.2212, 0.0895, -0.9685, -0.2323, 0.9027, -0.2578, -0.3444, -0.9847, -0.1729, 0.0228, -0.2917, -0.7289, 0.6194, 0.7650, 0.6159, 0.1884, -0.1339, -0.6884, -0.7129, -0.8765, 0.2485, 0.4122, 0.3552, -0.6834, 0.6378, -0.1000, -0.9517, -0.2903, 0.0247, 0.9121, 0.4092, +0.4720, -0.8518, 0.2272, -0.9354, -0.0522, -0.3497, -0.0297, 0.0135, 0.9995, 0.4183, -0.8871, 0.1952, 0.2175, 0.9444, 0.2465, -0.6095, -0.4762, 0.6338, 0.4748, -0.8779, 0.0615, 0.6855, -0.7191, -0.1139, -0.7755, 0.6311, 0.0167, -0.5508, -0.6462, -0.5282, -0.9915, -0.0062, 0.1299, -0.0943, -0.9916, 0.0882, 0.3445, -0.8011, 0.4894, -0.9112, 0.0297, -0.4110, -0.3660, 0.5427, 0.7559, -0.3428, 0.1563, 0.9263, +0.5686, 0.8225, 0.0151, -0.9573, -0.2834, -0.0570, -0.8836, -0.4678, -0.0208, 0.5644, -0.3411, -0.7518, -0.8823, 0.1799, -0.4351, -0.3036, -0.6249, -0.7192, -0.7132, -0.3146, 0.6264, -0.8535, -0.4938, 0.1667, 0.5556, 0.8265, -0.0905, -0.9091, -0.4151, 0.0341, -0.3301, -0.5859, -0.7401, 0.9744, 0.1611, -0.1571, 0.0257, 0.3183, 0.9476, -0.7104, 0.6979, -0.0911, 0.9101, 0.0217, -0.4138, 0.0523, 0.9986, 0.0115, +-0.2117, -0.8225, 0.5278, 0.9833, 0.1113, -0.1442, -0.9423, 0.0434, -0.3320, -0.9258, 0.3405, 0.1640, -0.1268, 0.9917, 0.0201, -0.5718, 0.5468, 0.6116, -0.2515, -0.0604, -0.9660, 0.0774, -0.3962, 0.9149, -0.6639, -0.4551, -0.5934, -0.1948, 0.6174, -0.7622, -0.2944, 0.7249, 0.6227, -0.9470, -0.1737, 0.2701, -0.9615, 0.2732, 0.0296, -0.3506, -0.7733, -0.5282, -0.9120, -0.2455, -0.3285, 0.4946, 0.6858, -0.5339, +-0.9201, 0.1887, 0.3432, 0.0410, -0.4393, -0.8974, 0.9348, 0.3491, 0.0645, 0.9992, 0.0065, -0.0399, 0.2763, -0.2233, 0.9348, -0.4777, 0.8637, 0.1605, -0.9759, 0.1616, -0.1468, -0.9580, -0.1733, -0.2283, -0.9546, -0.1742, -0.2418, 0.2284, -0.9207, -0.3166, 0.0928, 0.0435, 0.9947, -0.7938, -0.4573, -0.4011, -0.6423, 0.5065, 0.5752, -0.5599, 0.7977, 0.2240, 0.2076, 0.9175, 0.3393, -0.6892, -0.4931, -0.5310, +-0.0769, 0.3639, 0.9282, -0.0452, 0.9572, 0.2857, 0.5039, 0.8368, 0.2142, -0.8183, -0.1199, 0.5621, 0.4469, -0.3872, -0.8065, 0.2273, 0.9241, 0.3073, -0.9308, -0.1467, -0.3349, 0.9196, -0.1371, 0.3681, 0.3391, 0.9079, 0.2464, -0.2256, -0.9731, 0.0458, 0.9908, 0.0864, -0.1040, 0.7752, -0.5784, -0.2541, -0.9718, 0.2353, 0.0153, 0.4329, 0.8812, 0.1899, 0.5307, -0.0201, 0.8473, -0.5498, 0.1606, 0.8197, +0.9826, -0.0999, -0.1569, 0.8016, -0.3876, 0.4552, -0.9654, -0.1440, -0.2172, 0.2478, -0.5443, -0.8015, 0.8929, -0.0170, -0.4499, 0.9551, 0.1856, -0.2308, 0.7736, -0.4780, 0.4160, -0.0396, -0.0325, 0.9987, -0.9993, 0.0015, 0.0377, 0.4546, -0.7157, 0.5302, 0.8782, 0.4562, 0.1440, -0.5906, -0.6574, -0.4681, 0.8481, -0.3997, 0.3479, -0.8224, 0.1815, 0.5392, 0.2897, -0.7068, 0.6454, 0.9795, 0.1847, -0.0804, +0.4008, -0.0553, -0.9145, -0.7904, -0.4938, -0.3624, 0.7719, -0.5682, -0.2851, 0.5630, -0.6380, 0.5254, 0.0562, 0.7917, -0.6083, -0.3552, 0.8846, -0.3023, 0.9565, -0.0065, 0.2915, 0.4585, -0.8149, -0.3546, 0.6342, -0.1558, 0.7573, -0.5788, -0.6000, -0.5523, -0.6422, 0.3147, 0.6989, -0.5763, 0.7086, 0.4071, -0.9272, -0.3289, -0.1790, 0.2931, 0.9455, 0.1417, 0.7793, -0.5369, 0.3232, -0.8074, 0.5730, -0.1408, +-0.8369, -0.2181, -0.5020, -0.3752, 0.6959, 0.6123, 0.0781, 0.1285, 0.9886, -0.5288, 0.4246, 0.7349, -0.3363, 0.7010, -0.6289, -0.6946, 0.4663, 0.5478, 0.7435, -0.5360, -0.4000, 0.6107, -0.4545, 0.6484, -0.5305, -0.2958, 0.7944, -0.9915, -0.0342, 0.1255, 0.9734, 0.1064, -0.2030, 0.6823, -0.5994, -0.4185, 0.0805, -0.6530, 0.7530, -0.9717, -0.1275, 0.1991, -0.0212, -0.6569, 0.7536, 0.0313, -0.5778, -0.8156, +0.5672, 0.0214, 0.8233, -0.7878, -0.2984, -0.5389, -0.9461, -0.0812, 0.3137, -0.1978, 0.6945, 0.6918, -0.0824, 0.9963, 0.0235, 0.2205, -0.5882, 0.7781, -0.0484, -0.0656, 0.9967, 0.0047, 0.1252, 0.9921, 0.9476, -0.0571, -0.3142, -0.2458, 0.3360, 0.9092, -0.7790, 0.5644, 0.2733, -0.2361, 0.9694, -0.0670, 0.9708, -0.0962, 0.2199, 0.9206, 0.3893, 0.0298, 0.8195, 0.5652, 0.0948, -0.1847, 0.9126, 0.3647, +-0.2758, -0.0016, 0.9612, -0.6803, -0.7280, -0.0851, -0.6066, 0.0274, -0.7945, -0.4820, -0.8711, -0.0942, -0.8880, -0.1644, -0.4295, -0.1680, -0.6972, -0.6970, -0.9712, -0.1968, -0.1342, -0.7024, 0.6388, 0.3140, -0.2919, 0.8561, 0.4265, 0.7560, -0.6540, 0.0285, 0.0895, -0.6565, 0.7490, 0.4941, -0.4787, -0.7258, 0.6282, -0.4041, -0.6649, 0.6329, -0.5370, -0.5577, 0.7338, -0.6710, -0.1067, -0.5546, -0.7808, -0.2878, +0.7710, -0.4095, -0.4878, 0.5505, -0.8261, 0.1205, -0.9329, 0.1957, 0.3022, 0.9793, 0.1322, -0.1532, -0.3566, 0.7529, 0.5531, -0.9814, -0.1814, -0.0631, -0.4358, 0.8966, -0.0790, 0.3651, -0.9197, -0.1448, 0.9812, -0.1575, -0.1120, -0.9554, 0.2925, -0.0401, 0.9119, -0.1020, 0.3974, 0.9941, 0.0521, 0.0954, 0.9828, -0.1232, 0.1376, 0.0356, 0.9891, 0.1430, 0.5573, -0.2902, -0.7779, 0.4904, -0.4362, 0.7545, +-0.7920, -0.5926, -0.1467, 0.8734, -0.3768, -0.3084, 0.9177, 0.1777, 0.3552, 0.9504, -0.3015, -0.0763, 0.3333, 0.6839, -0.6490, 0.0117, 0.6948, 0.7191, -0.9413, -0.1858, 0.2818, 0.5712, -0.8175, -0.0738, -0.7128, -0.4194, -0.5622, 0.3143, 0.4663, -0.8269, 0.8155, 0.1112, 0.5680, -0.6548, 0.5096, 0.5582, 0.2425, 0.7893, 0.5641, -0.0154, 0.8578, 0.5138, -0.4769, -0.8634, 0.1649, 0.6689, -0.6487, -0.3630, +0.0826, 0.3501, 0.9331, -0.9528, 0.1853, 0.2403, 0.3550, 0.1902, 0.9153, 0.9469, 0.3189, 0.0409, -0.7054, -0.0236, -0.7084, 0.5825, -0.8098, 0.0701, 0.9237, 0.3824, -0.0229, 0.9917, -0.0516, 0.1176, 0.9621, 0.0252, 0.2715, 0.4805, 0.8769, -0.0117, 0.9893, 0.0335, -0.1421, -0.2198, -0.9186, 0.3284, 0.8460, -0.1588, -0.5090, -0.8581, 0.5133, -0.0104, 0.8587, -0.1841, 0.4782, 0.9959, -0.0901, 0.0033, +-0.9149, -0.2385, 0.3258, -0.6895, -0.3897, 0.6106, 0.8259, -0.0900, -0.5565, -0.5521, 0.7563, -0.3510, 0.8781, -0.2721, -0.3936, 0.8206, 0.4551, 0.3457, -0.5493, 0.7495, -0.3695, -0.0583, -0.1391, 0.9886, -0.4125, -0.8722, -0.2631, 0.7953, -0.6015, -0.0761, 0.3878, -0.9188, -0.0734, 0.0184, -0.0126, 0.9998, 0.7807, 0.6111, 0.1303, -0.2147, -0.9164, 0.3378, 0.1107, 0.9883, 0.1049, 0.6564, 0.2862, 0.6980, +0.3544, -0.7073, 0.6116, 0.2890, -0.7755, 0.5613, -0.8971, -0.1201, 0.4251, -0.7769, -0.0982, 0.6219, -0.8494, -0.2453, -0.4673, 0.9801, -0.0361, 0.1950, -0.7030, -0.3433, -0.6228, -0.3499, -0.4715, 0.8095, 0.1047, 0.9749, 0.1963, 0.3261, 0.3175, 0.8904, 0.5638, 0.5671, 0.6004, -0.3808, -0.6436, -0.6639, 0.4807, -0.8318, -0.2776, 0.8480, 0.2911, -0.4429, -0.0211, -0.0142, 0.9997, -0.3064, -0.8207, 0.4823, +-0.9868, -0.0155, 0.1611, -0.9058, -0.1359, -0.4014, -0.4664, 0.8655, -0.1828, -0.9128, 0.1817, 0.3656, -0.7852, -0.3355, -0.5204, 0.7322, 0.6300, 0.2587, -0.2030, 0.9791, -0.0128, 0.9958, -0.0791, 0.0456, -0.9856, 0.0110, -0.1688, -0.8791, 0.2344, -0.4150, 0.9052, -0.3638, 0.2196, 0.9230, 0.0859, -0.3750, 0.9151, 0.4013, -0.0389, 0.9709, 0.0735, -0.2281, 0.0226, 0.0199, 0.9995, 0.6067, -0.4902, 0.6259, +0.0975, -0.2346, -0.9672, 0.5305, 0.0111, 0.8476, -0.9459, -0.0731, 0.3161, 0.5849, 0.7731, -0.2452, 0.1115, 0.1832, -0.9767, 0.4180, -0.7919, -0.4452, -0.6370, 0.0986, 0.7645, 0.1964, 0.0042, 0.9805, -0.6965, 0.6951, 0.1783, -0.5846, 0.3295, 0.7414, -0.5552, 0.7030, 0.4445, 0.1197, 0.4974, 0.8592, -0.1122, 0.9577, -0.2649, 0.2391, -0.6299, 0.7390, 0.7140, -0.0127, -0.7000, -0.6851, 0.7162, -0.1326, +0.6280, 0.1852, -0.7558, -0.1489, 0.9773, 0.1509, 0.8707, -0.1099, -0.4793, -0.9243, 0.3390, -0.1753, 0.2315, -0.5789, 0.7818, -0.9518, -0.1617, 0.2607, -0.4973, 0.0389, -0.8667, 0.0690, 0.7638, 0.6417, -0.8900, -0.3039, -0.3398, -0.9625, -0.1489, -0.2266, 0.4705, -0.8363, 0.2815, 0.1670, -0.8289, -0.5339, -0.9307, 0.0697, 0.3592, -0.3856, 0.8087, 0.4442, 0.7824, 0.4786, -0.3984, 0.0379, 0.0132, 0.9992, +0.8835, -0.0730, 0.4626, -0.9994, -0.0242, 0.0264, -0.9818, 0.1137, -0.1522, -0.8821, -0.0663, 0.4664, -0.4470, -0.5833, -0.6782, 0.9999, 0.0111, 0.0128, -0.7426, -0.5917, -0.3136, 0.5735, 0.2959, 0.7639, 0.2851, -0.9375, 0.1994, 0.9000, 0.4279, 0.0831, 0.6998, -0.2213, 0.6792, 0.9990, 0.0117, -0.0443, 0.6043, 0.7472, -0.2764, 0.7086, -0.0942, -0.6992, -0.8652, -0.1398, 0.4816, -0.3023, 0.4497, 0.8405, +-0.8617, -0.3574, -0.3602, 0.7356, -0.3368, 0.5877, 0.7339, -0.3601, 0.5759, 0.1208, 0.9869, -0.1068, -0.3913, -0.9120, 0.1229, 0.8461, -0.5083, -0.1601, 0.9100, 0.4100, 0.0620, -0.0670, -0.9860, 0.1528, 0.9841, 0.1289, 0.1224, 0.4618, 0.8812, 0.1009, -0.6752, 0.7367, -0.0372, -0.1695, 0.9850, -0.0333, 0.4416, 0.0506, 0.8958, -0.0404, 0.9920, 0.1199, 0.3084, -0.9005, 0.3065, 0.2721, -0.7857, 0.5555, +-0.9356, -0.3232, -0.1420, 0.5916, -0.0448, 0.8050, -0.1485, 0.9711, 0.1866, -0.7651, 0.2850, 0.5774, 0.3931, 0.6799, -0.6190, -0.1620, 0.9642, 0.2098, 0.5294, -0.6116, -0.5880, -0.2541, -0.8255, -0.5040, -0.3943, 0.9113, -0.1190, 0.4131, -0.8217, -0.3927, -0.7185, 0.6952, 0.0198, 0.9296, -0.3577, 0.0893, -0.9483, 0.2779, -0.1533, 0.2302, -0.8838, -0.4073, 0.3016, -0.2615, 0.9169, -0.3822, -0.8096, 0.4455, +-0.9545, -0.2820, 0.0972, 0.6359, 0.7694, -0.0607, -0.7877, -0.3509, -0.5063, -0.9678, -0.2056, 0.1450, -0.9680, -0.2076, 0.1411, 0.9655, 0.0391, 0.2574, 0.2745, -0.9544, 0.1171, 0.3346, -0.9128, 0.2342, -0.4411, -0.5297, 0.7245, -0.7566, 0.4247, -0.4973, -0.1290, 0.9584, -0.2546, -0.9161, -0.0007, -0.4009, 0.0602, -0.9894, -0.1323, -0.0709, -0.9805, 0.1835, -0.6428, 0.0359, -0.7652, 0.5005, 0.1313, -0.8557, +-0.9176, 0.3719, 0.1406, 0.9688, 0.2038, 0.1410, -0.9824, -0.1813, -0.0456, -0.0013, -0.5711, 0.8209, -0.1908, 0.9809, -0.0375, 0.3702, 0.5444, 0.7527, -0.9460, -0.1642, 0.2796, -0.7952, -0.5030, -0.3386, -0.8003, -0.2360, 0.5512, -0.2254, 0.9595, 0.1689, 0.6725, 0.2695, 0.6892, 0.7956, -0.1123, 0.5954, 0.2858, -0.9212, 0.2639, 0.3343, -0.7558, 0.5631, -0.9123, 0.3916, 0.1197, 0.5826, -0.8034, -0.1233, +-0.9489, 0.1479, 0.2788, 0.0157, 0.9890, 0.1473, -0.4246, -0.8189, -0.3861, 0.4905, 0.7372, -0.4648, -0.3180, 0.7462, 0.5848, -0.3307, 0.7651, 0.5525, -0.9711, 0.0300, -0.2366, -0.2899, -0.9298, -0.2267, -0.0458, 0.9744, 0.2201, -0.5450, -0.7092, 0.4472, -0.9289, -0.0519, -0.3668, -0.1643, -0.8620, -0.4796, 0.3799, -0.9223, 0.0701, 0.2748, -0.3172, 0.9077, 0.0850, 0.3091, 0.9472, 0.2981, -0.6527, -0.6965, +0.9956, 0.0249, -0.0899, -0.8688, 0.0649, -0.4909, 0.6938, 0.7187, -0.0453, -0.8401, -0.5425, -0.0056, 0.8886, -0.3313, -0.3173, 0.8041, 0.3712, 0.4644, 0.0809, -0.6685, -0.7393, -0.0736, 0.2414, -0.9676, -0.8294, -0.1896, 0.5255, -0.8057, -0.5923, -0.0068, 0.3815, 0.7645, 0.5196, 0.4273, 0.0498, -0.9027, 0.9681, 0.2494, 0.0243, -0.3796, -0.6279, -0.6794, -0.9518, 0.2685, -0.1481, 0.9563, 0.1171, -0.2680, +-0.1545, 0.9868, -0.0477, 0.8553, -0.4956, -0.1508, -0.1625, -0.9792, -0.1213, -0.9337, -0.3581, -0.0033, 0.1550, 0.1364, 0.9784, -0.9412, 0.1150, 0.3177, -0.5229, -0.7069, 0.4764, 0.6528, 0.7519, 0.0922, 0.4484, 0.8937, 0.0132, -0.7090, 0.3505, -0.6120, 0.5770, 0.8103, 0.1020, 0.5873, -0.0730, 0.8060, -0.5333, 0.2185, 0.8172, -0.6869, 0.6899, 0.2285, -0.0677, 0.1353, 0.9885, -0.8709, 0.2250, 0.4370, +0.4890, -0.3711, -0.7894, -0.6813, -0.7298, -0.0567, -0.8306, 0.5523, -0.0705, -0.9819, -0.1439, -0.1232, -0.9874, -0.0278, -0.1560, 0.1772, 0.5152, 0.8385, 0.2654, 0.0151, 0.9640, 0.9884, 0.1518, -0.0029, 0.9682, -0.1683, 0.1848, 0.9805, 0.1817, 0.0747, -0.3628, -0.7213, -0.5900, -0.0720, 0.9262, 0.3702, 0.4110, -0.7907, -0.4537, -0.4171, -0.9088, -0.0039, -0.9055, -0.0958, -0.4134, 0.8570, 0.3468, -0.3811, +0.1786, 0.7216, 0.6689, -0.9132, -0.3345, 0.2326, -0.9168, -0.3197, 0.2394, 0.4250, 0.9051, -0.0106, -0.1144, 0.8446, 0.5229, -0.9011, -0.4267, 0.0768, -0.9007, -0.4288, 0.0702, 0.9015, 0.4161, -0.1187, 0.4244, -0.8804, -0.2116, 0.0447, -0.0849, 0.9954, -0.8758, -0.0743, -0.4769, -0.7345, 0.1087, 0.6698, -0.4923, 0.5043, -0.7095, -0.9560, -0.2167, -0.1978, -0.6295, 0.1827, -0.7552, -0.8047, -0.1155, 0.5823, +-0.0135, 0.4954, 0.8686, 0.1826, -0.7607, 0.6229, 0.8925, -0.0569, 0.4475, 0.9230, 0.3464, -0.1678, -0.9121, -0.3875, 0.1340, 0.1116, 0.9905, 0.0802, 0.2638, 0.8225, 0.5038, 0.8598, 0.0924, 0.5021, 0.9838, -0.1611, -0.0790, -0.8666, -0.1895, -0.4615, 0.2066, 0.6716, 0.7116, -0.7973, -0.0393, -0.6023, 0.0101, 0.0213, 0.9997, -0.4002, -0.1985, -0.8947, -0.4878, -0.3975, 0.7772, 0.3681, -0.7790, 0.5075, +0.4911, -0.7871, 0.3734, -0.4146, -0.3917, 0.8214, 0.8075, -0.0099, 0.5898, 0.1614, 0.7723, 0.6144, 0.8256, -0.1322, 0.5486, 0.4618, 0.6108, 0.6431, -0.8486, -0.1856, 0.4955, 0.8139, 0.5182, 0.2627, -0.9928, 0.0279, 0.1165, 0.4914, 0.1727, 0.8536, -0.8313, -0.1693, 0.5294, 0.9538, -0.1422, -0.2647, -0.4151, 0.9054, -0.0892, -0.0974, 0.0564, 0.9936, -0.3846, 0.9197, 0.0786, 0.5901, 0.2943, 0.7518, +-0.2104, 0.2083, 0.9552, -0.6417, -0.7488, 0.1656, 0.0194, -0.0092, 0.9998, 0.5080, -0.7802, 0.3649, 0.2130, -0.9478, -0.2372, 0.9577, -0.2564, 0.1308, 0.7912, 0.3370, -0.5104, 0.7328, 0.2592, 0.6291, -0.1836, 0.9057, -0.3822, -0.7698, 0.1231, 0.6263, 0.7907, -0.5196, -0.3236, -0.9177, -0.3207, 0.2344, 0.9454, -0.3223, 0.0476, 0.5652, 0.7347, 0.3751, -0.9994, -0.0357, 0.0042, -0.9927, 0.0586, -0.1056, +-0.6384, -0.5618, -0.5262, -0.2353, -0.3364, -0.9119, -0.8836, 0.2008, -0.4230, 0.8122, -0.5587, 0.1677, -0.7138, 0.6165, 0.3323, 0.7909, -0.6092, 0.0575, 0.7045, -0.4099, -0.5793, -0.2510, -0.8639, 0.4366, 0.9012, -0.4323, -0.0301, 0.2981, -0.0233, 0.9543, -0.0249, 0.9669, 0.2541, 0.9622, 0.1262, 0.2414, 0.9089, 0.2015, -0.3650, -0.2598, -0.9612, -0.0928, -0.9946, 0.0820, 0.0637, -0.8531, -0.5194, -0.0503, +0.7812, -0.0939, 0.6171, -0.9922, -0.1031, 0.0697, -0.4070, -0.8891, -0.2095, 0.8044, 0.1362, -0.5783, 0.8933, -0.2858, 0.3468, -0.7083, -0.0431, -0.7046, 0.0511, 0.9453, 0.3223, 0.2942, 0.8983, 0.3265, 0.5953, -0.2583, -0.7608, 0.8342, 0.1288, 0.5362, -0.3827, -0.8581, -0.3422, -0.0221, 0.0057, 0.9997, 0.4015, 0.9157, 0.0155, -0.9722, 0.1020, 0.2109, 0.6081, -0.6512, -0.4541, -0.6939, 0.5672, 0.4436, +-0.8058, 0.3563, 0.4731, 0.5599, -0.4319, -0.7071, 0.6252, -0.4549, -0.6341, 0.8746, 0.3991, 0.2754, -0.0479, -0.1067, 0.9931, 0.8597, -0.3040, -0.4106, -0.9232, 0.1223, 0.3644, 0.4454, -0.8664, -0.2256, 0.0514, 0.4510, 0.8910, 0.2421, -0.6356, 0.7330, -0.5360, -0.5351, -0.6530, 0.1046, -0.0629, 0.9925, -0.2222, 0.9718, 0.0791, -0.6928, 0.6151, 0.3764, -0.1405, -0.3031, -0.9425, 0.9960, 0.0696, 0.0559, +0.1781, 0.9324, 0.3146, -0.5803, -0.6715, -0.4607, 0.3159, 0.9485, -0.0229, 0.7192, -0.1970, 0.6663, -0.6291, 0.5063, -0.5898, -0.3158, 0.7565, 0.5726, 0.8178, -0.5345, -0.2133, -0.1899, 0.6817, -0.7066, 0.9415, 0.2927, 0.1668, 0.8202, 0.5225, 0.2331, 0.5949, 0.0133, 0.8037, -0.9190, 0.1665, 0.3573, -0.8965, 0.3941, -0.2025, 0.5165, -0.4651, -0.7190, 0.5170, -0.8547, 0.0470, 0.9249, 0.1967, 0.3254, +-0.5885, -0.3075, 0.7478, -0.8840, -0.4413, -0.1544, -0.6807, -0.3743, -0.6297, 0.2214, -0.8777, -0.4249, 0.8759, 0.3127, 0.3675, -0.6539, 0.6821, 0.3274, 0.4504, 0.8535, 0.2622, 0.7480, 0.6413, 0.1709, 0.8364, -0.5225, -0.1658, -0.0495, 0.8756, 0.4806, 0.8110, -0.4998, -0.3041, -0.0009, 0.0218, 0.9998, -0.2891, 0.7210, -0.6298, -0.2929, 0.5382, 0.7903, -0.0644, 0.0939, 0.9935, 0.9637, 0.2618, 0.0516, +0.1575, -0.9842, -0.0807, 0.5927, -0.6412, -0.4875, -0.6262, -0.6417, 0.4429, 0.5621, -0.7706, 0.3005, -0.8232, -0.5572, -0.1089, 0.6541, 0.1972, 0.7303, -0.3291, 0.9105, 0.2505, 0.0645, -0.9035, 0.4237, 0.9848, 0.1521, -0.0838, 0.3609, 0.4155, 0.8349, -0.2614, 0.9227, 0.2834, 0.9587, -0.1173, -0.2591, 0.6546, -0.6645, 0.3605, 0.3394, 0.9258, 0.1661, 0.7378, 0.6716, 0.0676, -0.6921, 0.7209, 0.0376, +0.9456, -0.1300, -0.2983, -0.1382, 0.9856, 0.0978, -0.6081, -0.4425, 0.6591, 0.7463, -0.5339, -0.3975, 0.1742, 0.9283, 0.3284, -0.0204, -0.0081, 0.9998, -0.0087, 0.9969, -0.0784, 0.1689, 0.5565, 0.8135, }; +}; diff --git a/libraries/bitluni_ESP32Lib/examples/VGACustomResolution/VGACustomResolution.ino b/libraries/bitluni_ESP32Lib/examples/VGACustomResolution/VGACustomResolution.ino new file mode 100644 index 0000000..bde30f9 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGACustomResolution/VGACustomResolution.ino @@ -0,0 +1,102 @@ +//This example shows how a custom VGA resolution can be created for one of the base modes +//You need to connect a VGA screen cable to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +//including the needed header +#include +#include + +//pin configuration +const int redPin = 14; +const int greenPin = 19; +const int bluePin = 27; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device using an interrupt to unpack the pixels from 4bit to 16bit for the I²S +//This takes some CPU time in the background but is able to fit a frame buffer in the memory +VGA3Bit vga; + +void setup() +{ + Serial.begin(115200); + //enabling double buffering + vga.setFrameBufferCount(2); + //Mode::custom(xres, yres, fixedYDivider = 1) calculates the parameters for our custom resolution. + //the y resolution is only scaling integer divisors (yet). + //if you don't like to let it scale automatically pass a fixed parameter with a fixed divider. + Mode myMode = vga.MODE640x480.custom(80, 60); + //print the parameters + myMode.print(Serial); + //use the mode + vga.init(myMode, redPin, greenPin, bluePin, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); +} + +///draws a bouncing balls +void balls() +{ + //some basic gravity physics + static VGA3BitI::Color c[4] = {vga.RGB(0, 255, 0), vga.RGB(0, 255, 255), vga.RGB(255, 0, 255), vga.RGB(255, 255, 0)}; + static float y[4] = {20, 20, 20, 20}; + static float x[4] = {20, 20, 20, 20}; + static float vx[4] = {.01, -0.07, .05, -.03}; + static float vy[4] = {0, 1, 2, 3}; + static unsigned long lastT = 0; + unsigned long t = millis(); + float dt = (t - lastT) * 0.001f; + lastT = t; + const int r = 6; + for (int i = 0; i < 4; i++) + { + int rx = r; + int ry = r; + vy[i] += -9.81f * dt * 100; + x[i] += vx[i]; + y[i] += vy[i] * dt; + //check for boundaries and bounce back + if (y[i] < r && vy[i] < 0) + { + vy[i] = 200 + i * 10; + ry = y[i]; + } + if (x[i] < r && vx[i] < 0) + { + vx[i] = -vx[i]; + rx = x[i]; + } + if (x[i] >= vga.xres - r && vx[i] > 0) + { + vx[i] = -vx[i]; + rx = vga.xres - x[i]; + } + //draw a filled ellipse + vga.fillEllipse(x[i], vga.yres - y[i] - 1, rx, ry, c[i]); + vga.ellipse(x[i], vga.yres - y[i] - 1, rx, ry, 0); + } +} + +//mainloop +void loop() +{ + //draw a background + for (int y = 0; y * 10 < vga.yres; y++) + for (int x = 0; x * 10 < vga.xres; x++) + vga.fillRect(x * 10, y * 10, 10, 10, (x + y) & 1 ? vga.RGB(255, 0, 0) : vga.RGB(255, 255, 255)); + //text position + vga.setCursor(2, 2); + //black text color no background color + vga.setTextColor(vga.RGB(0)); + //show the remaining memory + vga.print(vga.xres); + vga.print("x"); + vga.println(vga.yres); + vga.print("free memory: "); + vga.print((int)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + //draw bouncing balls + balls(); + //show the backbuffer (only needed when using backbuffering) + vga.show(); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGADemo14Bit/VGADemo14Bit.ino b/libraries/bitluni_ESP32Lib/examples/VGADemo14Bit/VGADemo14Bit.ino new file mode 100644 index 0000000..3ac9456 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGADemo14Bit/VGADemo14Bit.ino @@ -0,0 +1,71 @@ +//This example shows how to animate graphics on a VGA screen. No backbuffering is used... just try it. +//You need to connect a VGA screen cable and an external DAC (simple R2R does the job) to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +#include +#include +#include + +//pin configuration +const int redPins[] = {2, 4, 12, 13, 14}; +const int greenPins[] = {15, 16, 17, 18, 19}; +const int bluePins[] = {21, 22, 23, 27}; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA14Bit vga; + +//initial setup +void setup() +{ + //initializing i2s vga (with only one framebuffer) + vga.init(vga.MODE200x150, redPins, greenPins, bluePins, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); +} + +//the loop is done every frame +void loop() +{ + //setting the text cursor to the lower left corner of the screen + vga.setCursor(0, vga.yres - 8); + //setting the text color to white with opaque black background + vga.setTextColor(vga.RGB(0xffffff), vga.RGBA(0, 0, 0, 255)); + //printing the fps + vga.print("fps: "); + static long f = 0; + vga.print(long((f++ * 1000) / millis())); + + //circle parameters + float factors[][2] = {{1, 1.1f}, {0.9f, 1.02f}, {1.1, 0.8}}; + int colors[] = {vga.RGB(0xff0000), vga.RGB(0x00ff00), vga.RGB(0x0000ff)}; + //animate them with milliseconds + float p = millis() * 0.002f; + for (int i = 0; i < 3; i++) + { + //calculate the position + int x = vga.xres / 2 + sin(p * factors[i][0]) * vga.xres / 3; + int y = vga.yres / 2 + cos(p * factors[i][1]) * vga.yres / 3; + //clear the center with a black filled circle + vga.fillCircle(x, y, 8, 0); + //draw the circle with the color from the array + vga.circle(x, y, 10, colors[i]); + } + //render the flame effect + for (int y = 0; y < vga.yres - 9; y++) + for (int x = 1; x < vga.xres - 1; x++) + { + //take the avarage from the surrounding pixels below + int c0 = vga.get(x, y); + int c1 = vga.get(x, y + 1); + int c2 = vga.get(x - 1, y + 1); + int c3 = vga.get(x + 1, y + 1); + int r = ((c0 & 0x1f) + (c1 & 0x1f) + ((c2 & 0x1f) + (c3 & 0x1f)) / 2) / 3; + int g = (((c0 & 0x3e0) + (c1 & 0x3e0) + ((c2 & 0x3e0) + (c3 & 0x3e0)) / 2) / 3) & 0x3e0; + int b = (((c0 & 0x3c00) + (c1 & 0x3c00) + ((c2 & 0x3c00) + (c3 & 0x3c00)) / 2) / 3) & 0x3c00; + //draw the new pixel + vga.dotFast(x, y, r | g | b); + } +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGAFonts/VGAFonts.ino b/libraries/bitluni_ESP32Lib/examples/VGAFonts/VGAFonts.ino new file mode 100644 index 0000000..6ed76a5 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGAFonts/VGAFonts.ino @@ -0,0 +1,71 @@ +//This example shows how to use different fonts on a VGA screen. +//You need to connect a VGA screen cable to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +#include +#include +#include +#include +#include +#include +#include + +//pin configuration +const int redPin = 14; +const int greenPin = 19; +const int bluePin = 27; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA3Bit vga; + +void setup() +{ + //initializing vga at the specified pins + vga.init(vga.MODE640x400, redPin, greenPin, bluePin, hsyncPin, vsyncPin); + //selecting the font + vga.setFont(Font6x8); + //set color + vga.setTextColor(vga.RGB(255, 0, 0), vga.RGB(0, 0, 255)); + //displaying the character set + vga.println("Font6x8"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); + vga.setFont(CodePage437_8x8); + vga.setTextColor(vga.RGB(0, 255, 0), vga.RGB(255, 0, 0)); + vga.println("CodePage437_8x8"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); + vga.setFont(CodePage437_8x14); + vga.setTextColor(vga.RGB(0, 0, 255), vga.RGB(0, 255, 0)); + vga.println("CodePage437_8x14"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); + vga.setFont(CodePage437_8x16); + vga.setTextColor(vga.RGB(255, 255, 0), vga.RGB(0, 255, 255)); + vga.println("CodePage437_8x16"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); + vga.setFont(CodePage437_8x19); + vga.setTextColor(vga.RGB(255, 0, 255), vga.RGB(255, 255, 0)); + vga.println("CodePage437_8x19"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); + vga.setFont(CodePage437_9x16); + vga.setTextColor(vga.RGB(0, 255, 255), vga.RGB(255, 0, 255)); + vga.println("CodePage437_9x16"); + for (int i = 0; i < 256; i++) + vga.print((char)i); + vga.println(); +} + +void loop() +{ +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGAHelloWorld/VGAHelloWorld.ino b/libraries/bitluni_ESP32Lib/examples/VGAHelloWorld/VGAHelloWorld.ino new file mode 100644 index 0000000..cd190c6 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGAHelloWorld/VGAHelloWorld.ino @@ -0,0 +1,31 @@ +//This example shows a simple "Hello world!" on a VGA screen. +//You need to connect a VGA screen cable to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +#include +#include + +//pin configuration +const int redPin = 14; +const int greenPin = 19; +const int bluePin = 27; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA3Bit vga; + +void setup() +{ + //initializing vga at the specified pins + vga.init(vga.MODE320x240, redPin, greenPin, bluePin, hsyncPin, vsyncPin); + //selecting the font + vga.setFont(Font6x8); + //displaying the text + vga.println("Hello World!"); +} + +void loop() +{ +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGAHighRes/VGAHighRes.ino b/libraries/bitluni_ESP32Lib/examples/VGAHighRes/VGAHighRes.ino new file mode 100644 index 0000000..4da014b --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGAHighRes/VGAHighRes.ino @@ -0,0 +1,67 @@ +//This example shows a high VGA resolution 3Bit mode +//The VGA3BitI implementation uses the I²S interrupt to transcode a dense frame buffer to the needed +//8Bit/sample. Using the dense frame buffer allows to fit the big frame buffer in memory at the price of +//a lot cpu performance. +//You need to connect a VGA screen cable to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +//including the needed header +#include +#include + +//pin configuration +const int redPin = 14; +const int greenPin = 19; +const int bluePin = 27; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device using an interrupt to unpack the pixels from 4bit to 8bit for the I²S +//This takes some CPU time in the background but is able to fit a frame buffer in the memory +VGA3BitI vga; + +///draws the bitluni logo +void bitluni(int x, int y, int s) +{ + vga.fillCircle(x + 2 * s, y + 2 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillCircle(x + 22 * s, y + 2 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillCircle(x + 2 * s, y + 22 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillCircle(x + 22 * s, y + 22 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillRect(x, y + 2 * s, 24 * s, 20 * s, vga.RGB(128, 0, 0)); + vga.fillRect(x + 2 * s, y, 20 * s, 24 * s, vga.RGB(128, 0, 0)); + vga.fillCircle(x + 7 * s, y + 4 * s, 2 * s, vga.RGB(255, 255, 255)); + vga.fillCircle(x + 15 * s, y + 6 * s, 2 * s, vga.RGB(255, 255, 255)); + vga.fillCircle(x + 11 * s, y + 16 * s, 6 * s, vga.RGB(255, 255, 255)); + vga.fillCircle(x + 13 * s, y + 16 * s, 6 * s, vga.RGB(255, 255, 255)); + vga.fillCircle(x + 11 * s, y + 16 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillCircle(x + 13 * s, y + 16 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillRect(x + 11 * s, y + 14 * s, 2 * s, 4 * s, vga.RGB(128, 0, 0)); + vga.fillRect(x + 9 * s, y + 14 * s, 2 * s, 2 * s, vga.RGB(128, 0, 0)); + vga.fillRect(x + 5 * s, y + 4 * s, 4 * s, 12 * s, vga.RGB(255, 255, 255)); + vga.fillRect(x + 9 * s, y + 10 * s, 4 * s, s, vga.RGB(255, 255, 255)); +} + +void setup() +{ + //initializing the graphics mode + vga.init(vga.MODE800x600, redPin, greenPin, bluePin, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); + //clearing with white background + vga.clear(vga.RGB(0xffffff)); + //text position + vga.setCursor(10, 10); + //black text color no background color + vga.setTextColor(vga.RGB(0)); + //show the remaining memory + vga.print("free memory: "); + vga.print((int)heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + //draw the logo + bitluni(150, 60, 20); +} + +//mainloop +void loop() +{ +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGANoFrameBuffer/VGANoFrameBuffer.ino b/libraries/bitluni_ESP32Lib/examples/VGANoFrameBuffer/VGANoFrameBuffer.ino new file mode 100644 index 0000000..08783dd --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGANoFrameBuffer/VGANoFrameBuffer.ino @@ -0,0 +1,83 @@ +//This example shows how to draw directly to I2S with no frame buffer. It only allows low computation times per pixels +//to be able to serve I2S fast enough +//You need to connect a VGA screen cable and an external DAC (simple R2R does the job) to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +#include +#include + +//pin configuration +const int redPins[] = {2, 4, 12, 13, 14}; +const int greenPins[] = {15, 16, 17, 18, 19}; +const int bluePins[] = {21, 22, 23, 27}; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//color palette +unsigned long rainbow[256]; +long frameNumber = 0; + +//Our own VGA Device +class MyVGA : public VGA14BitI +{ + protected: + //override frame buffer allocation + virtual Color **allocateFrameBuffer() + { + return 0; + } + + //override the sync callback to count the frames + virtual void vSync() + { + frameNumber++; + } + + //draw each line + void interruptPixelLine(int y, unsigned long *pixels, unsigned long syncBits) + { + for (int x = 0; x < mode.hRes / 2; x++) + { + //writing two pixels improves speed drastically (avoids memory reads) + pixels[x] = syncBits | rainbow[(x - y + frameNumber) & 255]; + } + } +}; + +//get an instance +MyVGA vga; + +//initial setup +void setup() +{ + //color palette calculations + const float cb[][3] = {{1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}}; + for (int i = 0; i < 256; i++) + { + //interpolate the colors from the cb array and calculate the R5G5B5 color + float s = 6.f / 256 * i; + int n = int(s); + float f = s - n; + float fi = (1 - f); + const float *cf = cb[n]; + const float *cfn = cb[(n + 1) % 6]; + int r = int((fi * cf[0] * 0b11111) + (f * cfn[0] * 0b11111)); + int g = int((fi * cf[1] * 0b11111) + (f * cfn[1] * 0b11111)); + int b = int((fi * cf[2] * 0b1111) + (f * cfn[2] * 0b1111)); + int c = r | (g << 5) | (b << 10); + rainbow[i] = c; + } + //prepare writing two pixels at once (we shift the palette by a pixel) + for (int i = 0; i < 256; i++) + rainbow[i] |= rainbow[(i - 1) & 255] << 16; + + //initializing the vga with a high mode where a frambuffer would never fit into the memory + vga.init(vga.MODE500x480, redPins, greenPins, bluePins, hsyncPin, vsyncPin); +} + +//idle, everything is happening in the interrupt +void loop() +{ + delay(10); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGASprites/VGASprites.ino b/libraries/bitluni_ESP32Lib/examples/VGASprites/VGASprites.ino new file mode 100644 index 0000000..5da7ff8 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGASprites/VGASprites.ino @@ -0,0 +1,56 @@ +//This example shows the three methods of how sprites can be rendered on a VGA screen +//You need to connect a VGA screen cable and an external DAC (simple R2R does the job) to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +//include libraries +#include +#include + +//include the sprites converted the SpriteConverter. Check the documentation for further infos. +#include "explosion.h" + +//pin configuration +const int redPins[] = {2, 4, 12, 13, 14}; +const int greenPins[] = {15, 16, 17, 18, 19}; +const int bluePins[] = {21, 22, 23, 27}; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//VGA Device +VGA14Bit vga; + +//initial setup +void setup() +{ + //need double buffering + vga.setFrameBufferCount(2); + //initializing i2s vga + vga.init(vga.MODE200x150, redPins, greenPins, bluePins, hsyncPin, vsyncPin); + //setting the font + vga.setFont(Font6x8); +} + +//just draw each frame +void loop() +{ + //draw a background + for (int y = 0; y < vga.yres / 10; y++) + for (int x = 0; x < vga.xres / 10; x++) + vga.fillRect(x * 10, y * 10, 10, 10, (x + y) & 1 ? vga.RGB(0, 128, 0) : vga.RGB(0, 0, 128)); + //print some labels + vga.setCursor(36, 41); + vga.print("draw drawMix drawAdd"); + //there are 20 sprites for the explosion. The second parameter is the index of the sprite. + //We used the milliseconds to calculate the current index of the animation. + //the last two parameters is the position. During the conversion of the sprite the origin of each sprite is defined. + //check the Utilities folder for the converter + //"draw" draws the sprite opaque ignoring any existing alpha channel + explosion.draw(vga, (millis() / 50) % 20, vga.xres / 4, vga.yres / 2); + //"drawMix" uses the alpha channel + explosion.drawMix(vga, (millis() / 50) % 20, vga.xres / 2, vga.yres / 2); + //"drawAdd" adds the color components of the back ground and the sprite + explosion.drawAdd(vga, (millis() / 50) % 20, vga.xres * 3 / 4, vga.yres / 2); + //swap the frame buffers and show the rendering + vga.show(); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGASprites/explosion.h b/libraries/bitluni_ESP32Lib/examples/VGASprites/explosion.h new file mode 100644 index 0000000..f351c6c --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGASprites/explosion.h @@ -0,0 +1,891 @@ +const int explosionOffsets[] = {0, 882, 2132, 3814, 5926, 8306, 11270, 14550, 17910, 21270, 24630, 27990, 31350, 34710, 38070, 41346, 44538, 47654, 50770, 53730, 56610, }; +const short explosionPointOffsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, }; +const unsigned short explosionRes[][2] = {{21, 21}, {25, 25}, {29, 29}, {32, 33}, {34, 35}, {38, 39}, {40, 41}, {40, 42}, {40, 42}, {40, 42}, {40, 42}, {40, 42}, {40, 42}, {40, 42}, {39, 42}, {38, 42}, {38, 41}, {38, 41}, {37, 40}, {36, 40}, }; +const signed short explosionPoints[][2] = {{11, 10}, {13, 12}, {15, 14}, {16, 16}, {17, 17}, {19, 19}, {20, 20}, {20, 21}, {20, 21}, {20, 21}, {20, 21}, {20, 21}, {20, 21}, {20, 21}, {19, 21}, {19, 21}, {19, 21}, {19, 21}, {19, 20}, {18, 20}, }; +const unsigned short explosionPixels[] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 15360, 10943, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8720, 0, 9916, +9916, 10974, 8827, 8723, 5450, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9949, 10905, 8924, 12127, 31743, 31743, 32767, 29695, 11103, 7704, 7606, 8727, 0, 0, 0, 0, +0, 0, 0, 0, 10015, 32767, 32767, 32767, 32767, 49151, 32767, 32767, 31743, 27487, 25176, 6481, 8827, 0, 0, 0, 0, 0, 0, 0, 9948, 29631, 32767, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 32767, 29695, 27423, 11103, 11263, 0, 0, 0, 0, 8192, 11006, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 32767, 26367, 8731, 3481, 0, 0, 0, +5492, 28575, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 28607, 8894, 6684, 5471, 0, 15391, 13247, 31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 30719, 30719, 27551, 10047, 0, 0, 8727, 29695, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 31743, 29695, 26335, 8890, 0, 8730, 26399, +30719, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 29695, 8860, 5461, 0, 11071, 28671, 31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 30719, 8893, 5440, 8720, 7801, 29695, 32767, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 29695, 25311, 8895, 0, 4338, 25279, 27519, +30719, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 27487, 5624, 4631, 0, 0, 8730, 9951, 29695, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 28639, 28639, 10015, 0, 0, 0, 0, 10239, 29695, 31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 29663, 8927, 11071, 8735, 0, 0, 0, 0, 0, 10079, +28639, 30719, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 46079, 25277, 6578, 0, 0, 0, 0, 0, 0, 0, 0, 7803, 26335, 31743, 32767, 49151, 49151, 49151, 31743, 30719, 29695, 8959, +6678, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 6552, 26431, 30719, 31743, 30719, 29695, 27519, 29695, 30719, 11167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +16, 5526, 26367, 27487, 25311, 11071, 6617, 26399, 11167, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4631, 6348, 7706, 5458, 5450, 0, 6751, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 8828, 8825, +0, 8991, 8761, 0, 31, 8959, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6582, 10972, 11103, 11263, 11167, 29695, 28639, 11135, 29695, 27487, 9950, 7638, 9919, 6742, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5456, 12255, 31743, 30719, 29695, 27519, 31743, 30719, 29695, 31743, 28575, 29663, 27390, 9951, 8760, 0, 0, 0, 0, 0, 0, 0, +0, 8727, 5290, 9981, 30719, 32767, 32767, 32767, 32767, 49151, 32767, 32767, 30719, 28575, 29695, 29663, 9916, 9917, 5120, 0, 0, 0, 0, 0, 0, 4112, 25213, 26334, 28607, 29695, 47103, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 29695, 31743, 30719, 28543, 28607, 12159, 0, 0, 0, 0, 0, 0, 12191, 30719, 31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +47103, 29599, 32767, 28511, 5394, 0, 0, 0, 0, 0, 0, 8891, 30719, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 32767, 32767, 12094, 8889, 7636, +0, 0, 0, 5456, 26367, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 32767, 31743, 30719, 12255, 11263, 0, 31, 9948, 29695, 30719, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 30719, 28607, 28639, 13311, 0, 5461, 7638, 27583, 28511, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 27487, 26301, 8827, 10943, 0, 8727, 25212, 29695, 28639, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 49151, 49151, 49151, 49151, 49151, 49151, +47103, 27423, 29695, 9947, 0, 0, 5120, 10014, 30719, 47103, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 49151, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 28607, 27519, 7736, 5440, 0, 8922, +28639, 27487, 29695, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 32767, 28575, 26431, 24091, 8730, 31, 6547, 10015, 26367, 30719, 46079, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 27551, 25343, 6682, 6553, 0, 0, 9855, 3085, 27519, 29695, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 32767, 28607, 6780, 5461, 31, 0, 0, 0, 12287, 31743, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 30719, 27519, 26367, 6749, +6783, 0, 0, 0, 0, 10111, 29695, 30719, 32767, 47103, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 46079, 28671, 8958, 6680, 5525, 8720, 0, 0, 0, 0, 0, 8893, +25278, 29695, 31743, 47103, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 45055, 45055, 28671, 10047, 0, 16, 0, 0, 0, 0, 0, 0, 0, 5232, 10047, 30719, 30719, 48127, 48127, 49151, +49151, 49151, 30719, 31743, 29695, 27583, 26399, 10047, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8991, 28607, 29695, 46079, 48127, 30719, 30719, 28543, 31743, 30719, 26366, 7737, +5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 26334, 30719, 29695, 27487, 28671, 28639, 7770, 27487, 30719, 27615, 10111, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 6545, 27583, 27583, 27519, 9983, 10047, 8727, 5492, 10047, 8927, 8735, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 9951, 8860, 8827, 5554, 8720, 0, 5450, 6747, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8890, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 11103, 8727, 0, 0, 10933, 8720, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8735, 10943, 0, 0, 0, 12287, 29695, 27391, 6160, 7668, 11005, 5425, 5461, 5120, 7737, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 8208, 8829, 11007, 7737, 6582, 8729, 11071, 31743, 30719, 9951, 29663, 32767, 10013, 6678, 9982, 12159, 9983, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 11103, 30719, 32767, 29695, 26399, 27487, 28543, 49151, 30719, 28575, 32767, 32767, 30719, 28575, 30719, 11039, 8890, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, +8735, 6156, 9919, 29695, 30719, 31743, 31743, 32767, 48127, 49151, 47103, 28543, 48127, 46047, 28607, 28607, 32767, 11038, 7838, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 8828, 25245, 26333, 29663, +31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 49151, 47103, 46079, 48127, 30719, 28575, 27487, 10111, 0, 0, 0, 0, 0, 0, 0, 16383, 30719, 30719, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 29663, 31743, 26399, 8925, 0, 0, 0, 0, 0, 0, 0, 0, 26431, 29695, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 32767, 30719, 29695, 9917, 8991, 0, 0, 0, 0, 0, 11135, 29695, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 32767, 31743, 27551, 13311, 10047, 0, 0, 0, 12287, 29695, 27487, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 32767, 30719, 29695, 31743, 11038, 10933, 0, 0, 6547, 28671, 26367, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 46079, 29695, 29695, 9951, 5813, 0, 8720, 8891, 31743, 27487, 30719, 47103, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 27455, 26367, +9950, 8730, 0, 0, 5461, 6678, 29695, 27455, 30719, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 44959, 26335, 9918, 6742, 0, +0, 0, 7834, 28639, 28671, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 65535, 49151, 46079, 46079, 26335, 26335, 5491, 0, 0, 9819, 11135, +26367, 27519, 46079, 48127, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 46079, 29695, 28607, 28671, 24156, 4534, 0, 9980, 29695, 28607, 31743, 29695, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 29695, 31743, 28607, 25343, 7805, 6783, 0, 0, 10079, 9983, 29695, 26431, 46079, 47103, 49151, +49151, 49151, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 26334, 25245, 4466, 6678, 31, 0, 0, 0, 0, 11135, 29695, 32767, 49151, 49151, 49151, 49151, 49151, +49151, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 29695, 28671, 10111, 0, 0, 0, 0, 0, 8720, 8828, 29695, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 28607, 27551, 27519, 29695, 13311, 0, 0, 0, 0, 0, 5823, 7773, 27519, 31743, 48127, 48127, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +48127, 48127, 46079, 46079, 28639, 25311, 7740, 11167, 9215, 0, 0, 0, 0, 0, 0, 6777, 5399, 26335, 26301, 26399, 28639, 46079, 48127, 49151, 49151, 49151, 49151, 49151, 48127, 47103, 47103, 46079, +45055, 27583, 8927, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4435, 6712, 27519, 28607, 30719, 47103, 48127, 49151, 49151, 49151, 49151, 47103, 29695, 28671, 29695, 28671, 25343, 6679, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 2048, 10014, 29695, 29695, 48127, 48127, 49151, 47103, 48127, 29695, 27519, 31743, 28671, 27583, 27551, 7903, 31, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4108, 26335, 26367, 29695, 28671, 31743, 28639, 30719, 26399, 25311, 31743, 29695, 9982, 7805, 8735, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 7738, 26431, 29695, 27519, 28671, 28671, 26399, 5560, 5591, 27551, 28639, 8926, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 6647, 8925, 8858, 11199, 10047, 6747, 5461, 0, 6713, 8959, 8725, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 6547, 2057, 7837, 6547, 0, 0, 0, 528, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 4398, 10015, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 10972, 29695, 11135, 2048, 0, 8959, 7737, 0, 0, 0, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13311, 0, 0, 5461, +0, 12287, 31743, 31743, 12191, 7571, 28575, 27391, 6550, 0, 8794, 9852, 6580, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8859, 30719, 12191, 5457, 12159, +7735, 11135, 32767, 30719, 11006, 27423, 32767, 30719, 11135, 8826, 28575, 11039, 8759, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6348, 11039, 32767, 32767, 31743, 32767, +30719, 29695, 49151, 31743, 31743, 31743, 32767, 28575, 29695, 29695, 31743, 10079, 8827, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6553, 0, 5120, 9884, 29695, 30719, 32767, 30719, 47103, +47103, 48127, 49151, 48127, 29631, 32767, 49151, 29663, 28639, 30719, 31743, 26367, 7674, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26334, 9981, 25243, 28575, 32767, 47103, 48127, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 46047, 49151, 31743, 29663, 26303, 8828, 0, 0, 0, 0, 0, 0, 0, 0, 12223, 31743, 32767, 48127, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 28575, 30719, 10015, 9949, 8825, 0, 0, 0, 0, 0, 0, 0, 0, 28607, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 49151, 49151, 49151, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 32767, 31743, 29695, 28639, 10047, 0, 0, 0, 0, 0, 5450, 5459, 26335, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, +65535, 49151, 49151, 49151, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 32767, 31743, 28607, 8827, 8794, 0, 0, 0, 0, 8824, 25242, 46079, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, +65535, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 32767, 30719, 28575, 11103, 13311, 8895, 0, 0, 13311, 13311, 29695, 48127, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 49151, 65535, 65535, 65535, 49151, 49151, 49151, 48127, 31743, 31743, 30719, 29695, 31743, 13311, 0, 0, 0, 8793, 8859, 28607, 46079, 48127, 49151, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 49151, 49151, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 48127, 28543, 25245, 10047, 8890, 0, 0, 8857, 27487, 28607, 29695, 29695, 49151, 49151, 49151, 49151, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 44927, 25211, 25177, 6416, 31, 0, 0, 8728, 27519, 26303, 28607, 28607, 49151, 49151, 49151, 49151, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 65535, 65535, 49151, 47103, 44959, 25278, 27551, 11167, 0, 0, 31, 10014, 30719, 29695, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 47103, 28575, 25279, 9983, 0, 0, 0, 6711, 27519, 26399, 45055, 46079, 49151, 49151, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 48127, 46079, 29695, 29695, 31743, 9950, 6553, 10943, 7903, 26334, 28607, 29695, 48127, 49151, 49151, 49151, 49151, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 46079, 46079, 28671, 28671, 25375, 28671, 8923, 8720, 0, 5590, 8891, 29695, 31743, 46079, 48127, 49151, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 46079, 26367, 25276, 27615, 24156, 8891, 6771, 0, 0, 0, 0, 8892, 26332, 30719, 47103, 49151, 49151, 49151, 49151, 49151, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 28607, 10047, 4466, 5461, 0, 0, 0, 0, 0, 4112, 26367, 30719, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 46079, 47103, 29695, 29695, 7899, 528, 0, 0, 0, 0, 0, 10045, 30719, 31743, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 28639, 25375, 8991, 8927, 4534, 5461, 0, 0, 0, 0, 8720, 6647, 12255, 28671, 29663, 45023, 45023, 43967, 47103, 48127, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 47103, 29695, 24223, 4534, 6553, 4375, 0, 0, 0, 0, 0, 0, 0, 11103, 8893, 25247, 24190, 26399, 26495, 46079, 47103, 48127, +49151, 49151, 49151, 49151, 49151, 49151, 48127, 48127, 47103, 47103, 47103, 28639, 24187, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7805, 8895, 24154, 26431, 29695, 46079, 47103, +47103, 49151, 49151, 48127, 46079, 47103, 47103, 47103, 43967, 27551, 27519, 26367, 6648, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6680, 7738, 27551, 31743, 46079, 47103, +48127, 49151, 48127, 49151, 47103, 46079, 30719, 29695, 26463, 26399, 26463, 8893, 3475, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2064, 7803, 28607, 28639, 30719, +30719, 47103, 46079, 46079, 26495, 25375, 29695, 31743, 26399, 6681, 6715, 6553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4206, 25245, 28671, 31743, +27615, 29695, 28671, 25407, 23132, 23067, 28639, 31743, 26399, 7640, 4631, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6646, 27551, 31743, +26431, 29695, 28671, 8959, 4406, 2066, 8861, 28671, 7835, 4368, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8761, 12223, +7768, 11103, 10047, 8735, 0, 0, 0, 8991, 8727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +5120, 7736, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 5120, 10111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8720, 9915, 14335, 11103, 3072, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +31, 0, 0, 0, 0, 8720, 10973, 31743, 30719, 9915, 8720, 5461, 6678, 8823, 0, 0, 0, 8895, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 11071, 10111, 0, 11103, 6547, 0, 11039, 31743, 30719, 10015, 4114, 9918, 28511, 27487, 12159, 5120, 11071, 11135, 9951, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 11039, 29695, 30719, 13311, 30719, 28607, 6650, 27487, 49151, 31743, 12223, 8895, 31743, 32767, 27455, 7806, 11007, 29695, 9884, 8725, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 12223, 31743, 32767, 32767, 31743, 31743, 28671, 30719, 49151, 48127, 30719, 28575, 32767, 32767, 29695, 28671, 29695, 31743, 9915, 8725, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 9951, 28639, 28575, 32767, 49151, 47103, 48127, 48127, 49151, 49151, 49151, 32767, 30719, 49151, 48127, 28607, 45023, 30719, 30719, 8925, 8794, 5130, 0, 0, 0, 0, +0, 0, 0, 0, 5456, 12127, 8989, 8958, 10015, 30719, 29695, 47103, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 48127, 49151, 31743, 29695, 29695, 12287, 0, 0, +0, 0, 0, 0, 0, 10047, 26399, 31743, 30719, 30719, 31743, 32767, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 47103, 49151, 48127, 29663, 29695, 11071, 6578, +9951, 4104, 0, 0, 0, 0, 0, 5450, 8794, 29695, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 32767, +28607, 26399, 27487, 8795, 8735, 0, 0, 0, 0, 0, 7638, 26367, 47103, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, +49151, 49151, 32767, 32767, 30719, 10975, 9819, 0, 0, 0, 0, 11167, 8829, 44959, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +49151, 49151, 49151, 49151, 32767, 32767, 28543, 9884, 11039, 5461, 0, 31, 8891, 28671, 26334, 46079, 49151, 49151, 49151, 49151, 65535, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 49151, 49151, 49151, 48127, 31743, 32767, 29663, 30719, 32767, 9980, 8720, 0, 11071, 29695, 29695, 31743, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 47103, 29695, 32767, 32767, 10047, 5461, 0, 0, 25311, 25309, 29695, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 30719, 27487, 11071, 8727, 0, 0, 8925, 28671, 28671, 30719, 31743, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 47103, 47103, 27487, 27423, 8762, 6578, 8720, 0, 8959, 9951, 26431, 27455, 28511, 47103, 48127, 48127, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 46079, 46079, 43871, 27455, 25247, 7703, 6547, 0, 8959, 25343, 27519, 46079, 49151, 49151, 49151, 49151, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 48127, 46079, 28607, 26335, 8891, 9948, 0, 8727, 25311, 26302, 28575, 46079, 46079, 48127, 48127, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 48127, 46079, 29695, 31743, 30719, 29695, 12287, 13311, 28639, 26367, 26301, 28607, 47103, 49151, +49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 48127, 46079, 28671, 28607, 30719, 27551, 28671, 11167, 0, 9983, 28671, 29695, 31743, +48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 45023, 26399, 28671, 28607, 30719, 26334, 10015, 10943, 0, 3084, 10015, +28671, 28639, 45023, 47103, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 46079, 25309, 8892, 27487, 8825, 8720, 0, 0, +0, 0, 11135, 27583, 29695, 31743, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 48127, 49151, 49151, 29695, 12223, 8959, 0, 0, +0, 0, 0, 0, 7701, 28671, 32767, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 45055, 27551, 29695, 27551, 32767, 13311, +12287, 0, 0, 0, 0, 5461, 8894, 31743, 32767, 49151, 48127, 47103, 46079, 48127, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 46079, 27551, 25343, 6714, +11006, 7770, 8727, 0, 0, 0, 0, 8720, 6481, 11135, 30719, 28543, 27583, 42815, 45055, 47103, 48127, 48127, 49151, 49151, 48127, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 48127, 47103, 47103, 27551, +25311, 8831, 5461, 4631, 0, 0, 0, 0, 0, 0, 8720, 0, 10175, 23003, 25278, 25246, 26399, 47103, 47103, 47103, 49151, 49151, 49151, 49151, 49151, 48127, 49151, 49151, 49151, 47103, 47103, 46079, +48127, 30719, 8925, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 29695, 26367, 24122, 29695, 30719, 47103, 47103, 49151, 49151, 49151, 48127, 46079, 49151, 47103, 46079, 43967, +30719, 28639, 27615, 27551, 8890, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4375, 10079, 6681, 26266, 32767, 30719, 46079, 46079, 49151, 49151, 49151, 49151, 47103, 49151, 46079, +47103, 27615, 28671, 28671, 28671, 10079, 8727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6975, 3475, 7804, 28671, 27519, 29695, 29695, 31743, 48127, 47103, 49151, 28639, +47103, 28639, 31743, 27519, 7803, 8894, 10047, 7871, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4375, 10047, 27551, 29695, 29695, 29695, 30719, 27615, +26463, 23096, 25279, 25310, 31743, 29695, 11135, 4364, 7740, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7869, 27583, 30719, 28671, 26463, +28671, 29695, 10015, 3270, 3544, 5430, 26431, 26463, 8860, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6715, 10015, +7803, 6681, 26399, 8959, 6777, 0, 0, 0, 7740, 7770, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 6553, 3475, 5489, 7739, 6582, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 8727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8626, 9947, 8464, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8735, 8795, 29695, 9885, 8692, 15360, 0, 0, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 8720, 0, 8727, 26333, 31743, 28543, 8761, +8727, 0, 5296, 9948, 4096, 0, 0, 16, 11263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5301, 14335, 10047, 6743, 12159, +8890, 12159, 27423, 49151, 29695, 11038, 10047, 9914, 28543, 29695, 10046, 11103, 10111, 10972, 9983, 11263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +5471, 26367, 31743, 16383, 32767, 32767, 31743, 30719, 30719, 49151, 49151, 31743, 32767, 32767, 32767, 30719, 30719, 30719, 32767, 32767, 27391, 8890, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 11039, 28639, 32767, 32767, 32767, 32767, 30719, 29695, 30719, 49151, 49151, 29695, 29695, 31743, 49151, 29695, 29695, 28575, 31743, 32767, 12063, 8730, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7707, 27455, 32767, 32767, 49151, 48127, 48127, 48127, 49151, 49151, 49151, 49151, 48127, 49151, 49151, 48127, 29695, 44991, 30719, 28511, +9916, 5359, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9980, 10047, 4112, 7737, 9983, 28639, 29695, 29631, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, +49151, 48127, 47103, 48127, 31743, 30719, 29695, 9917, 6553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 28671, 29695, 27487, 27487, 28575, 31743, 31743, 47103, 48127, 49151, 49151, 49151, 65535, +65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 29663, 29695, 9914, 8720, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 8890, 28575, 32767, 49151, 49151, 49151, 49151, 49151, +49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 29695, 9950, 11007, 9885, 7670, 0, 0, 0, 0, 0, 0, 0, 0, 9982, +30719, 48127, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 29695, 30719, 29695, 11071, 10047, 0, 0, +0, 0, 0, 0, 8192, 6648, 28607, 48127, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, +32767, 30719, 11071, 8892, 0, 0, 0, 0, 0, 0, 11071, 26367, 47103, 49151, 49151, 49151, 49151, 65535, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 49151, 49151, 48127, 31743, 30719, 27487, 9919, 11199, 8959, 0, 0, 0, 8720, 7801, 29695, 28607, 29695, 49151, 48127, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 47103, 30719, 31743, 30719, 30719, 31743, 13247, 0, 0, 0, 8720, 5490, 27519, 28671, 28607, 49151, 49151, 49151, 48127, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 48127, 31743, 29695, 29695, 11103, 0, 0, 0, 0, 7801, 28639, 27487, 29695, 32767, +49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 30719, 27487, 10047, 9215, 0, 0, 0, +4352, 8825, 29695, 28639, 28671, 29695, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 47103, 46079, 45023, 27455, +28639, 7802, 0, 0, 0, 0, 5301, 23033, 26367, 26367, 27455, 45023, 48127, 48127, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +64511, 63487, 46079, 46079, 45055, 26367, 30719, 11167, 6553, 0, 0, 0, 8827, 27519, 29695, 47103, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 46079, 28639, 26463, 9982, 10047, 0, 0, 0, 8955, 27583, 27551, 29695, 30719, 47103, 46079, 47103, 64511, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 47103, 47103, 45055, 29695, 28671, 30719, 30719, 9982, 8720, 0, 6678, 26463, 27519, 26399, 27455, 46079, 46079, 48127, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 48127, 47103, 46079, 27455, 29695, 28607, 29695, 30719, 10015, 8720, 0, 5588, 25311, +29695, 30719, 30719, 48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 46079, 30719, 28607, 30719, 29695, 27551, +10111, 9947, 31, 0, 0, 6549, 12223, 30719, 29695, 44991, 48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, +46079, 27487, 25375, 28639, 28671, 7835, 6547, 0, 0, 0, 0, 0, 2052, 24254, 27551, 28639, 44991, 47103, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 49151, 65535, 49151, 49151, 49151, 49151, 28639, 10079, 8893, 6547, 0, 0, 0, 0, 0, 0, 0, 8925, 28671, 29695, 32767, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 48127, 47103, 49151, 49151, 31743, 30719, 8894, 4375, 0, 0, 0, 0, 0, 0, 11263, 12223, 29695, 32767, 49151, 49151, 49151, 49151, +64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 48127, 43903, 27487, 27487, 11039, 11103, 10015, 8959, 0, 0, 0, 0, 0, 0, 10943, 11135, +11135, 32767, 32767, 48127, 48127, 47103, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 48127, 48127, 48127, 45055, 28671, 8991, 4360, 6780, 6712, 8720, 0, 0, +0, 0, 0, 0, 0, 7868, 6647, 28671, 28575, 27487, 26431, 43903, 46079, 48127, 47103, 48127, 65535, 65535, 48127, 65535, 64511, 64511, 48127, 49151, 49151, 49151, 48127, 48127, 48127, 48127, 28671, 8959, +6783, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10111, 4333, 8925, 28671, 27551, 29695, 47103, 46079, 48127, 49151, 49151, 48127, 48127, 47103, 47103, 49151, 48127, 49151, 47103, +47103, 48127, 48127, 32767, 30719, 8991, 4624, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9983, 12287, 29695, 25343, 27551, 31743, 47103, 48127, 48127, 49151, 49151, 49151, +49151, 47103, 49151, 48127, 47103, 46079, 29695, 30719, 29695, 28671, 27551, 8956, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3078, 7803, 10079, 8959, 26431, 32767, +30719, 46079, 46079, 48127, 49151, 48127, 49151, 47103, 49151, 46079, 47103, 30719, 26399, 27551, 30719, 28671, 8991, 8735, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 4631, 0, 6747, 24121, 29695, 28671, 28671, 43999, 47103, 48127, 46079, 47103, 45055, 48127, 43967, 29695, 30719, 8926, 8893, 29695, 28671, 12287, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 8735, 6648, 27551, 27551, 30719, 27583, 31743, 47103, 28671, 25407, 25343, 28671, 25311, 28671, 32767, 26431, 6681, 8861, 8925, 10943, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6879, 11199, 28671, 31743, 26399, 28671, 27583, 30719, 10111, 4565, 10047, 6614, 11071, 31743, 25311, 5692, +8720, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6677, 25278, 29695, 9983, 8894, 26463, 26463, 8927, 0, +0, 16, 0, 11071, 6712, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2058, 10015, +6783, 7742, 8927, 8927, 1023, 0, 0, 0, 0, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 5461, 6681, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8694, 9916, 5296, +5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 8735, 9852, 29695, 9917, 7606, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 11103, 0, 0, 9849, 26269, 30719, 27455, 8793, 10581, 0, 8727, 7737, 3273, 0, 0, 0, 8720, 5450, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8925, 3084, 7833, 13311, 11135, 0, 8796, 27391, 49151, 28511, 8728, 11103, 8892, 27391, 27487, 6549, 5461, 0, +5136, 11005, 6449, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9951, 29695, 12127, 12191, 32767, 31743, 10047, 26399, 27423, 49151, 29695, +27487, 13311, 29695, 31743, 32767, 12191, 8892, 8991, 12159, 32767, 12094, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5450, 9980, 31743, 32767, +29695, 31743, 32767, 29695, 29695, 46015, 49151, 48127, 31743, 31743, 31743, 49151, 31743, 28639, 28575, 29695, 32767, 31743, 12127, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 2048, 12191, 31743, 32767, 49151, 49151, 49151, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 47103, 45023, 46079, 30719, 29695, 10015, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 6777, 8720, 0, 5461, 8464, 11071, 31743, 29663, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 46079, 48127, +28575, 28575, 29695, 10015, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4368, 24155, 11005, 9981, 8858, 10015, 29663, 30719, 45023, 47103, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 49151, +49151, 49151, 49151, 49151, 49151, 48127, 48127, 49151, 32767, 30719, 27487, 7638, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 12287, 28639, 32767, 32767, 31743, 31743, 32767, 31743, 48127, 49151, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 47103, 29663, 8825, 7736, 6412, 6646, 0, 0, 0, 0, 0, 0, 0, 0, 26399, +32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 32767, 27455, 26367, 25179, 8895, 12287, +0, 0, 0, 0, 0, 0, 0, 7736, 29663, 48127, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, +49151, 49151, 49151, 31743, 32767, 29695, 12287, 10239, 0, 0, 0, 0, 0, 8720, 8720, 6613, 28575, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 32767, 32767, 28543, 8892, 13215, 0, 0, 0, 0, 0, 8893, 11135, 9948, 30719, 49151, 49151, 49151, 49151, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 48127, 47103, 29695, 29695, 26334, 28639, 16383, 11007, 8735, 0, 0, 13311, 30719, 32767, 29695, +30719, 48127, 49151, 48127, 49151, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 47103, 46079, 48127, 47103, 30719, 30719, 29695, 31743, 32767, +9983, 8735, 0, 0, 0, 8893, 28575, 26335, 29695, 49151, 49151, 48127, 47103, 63487, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, +49151, 49151, 49151, 49151, 49151, 31743, 29695, 11071, 7704, 5461, 0, 0, 0, 8859, 31743, 29663, 31743, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 30719, 11103, 7640, 5461, 0, 0, 0, 2053, 7769, 29695, 28639, 28575, 46047, 47103, 47103, 48127, 48127, 64511, 64511, +64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 46079, 45023, 46079, 28607, 27551, 11103, 5558, 8720, 0, 0, 0, 8921, 27487, 26367, 26333, +27391, 46079, 47103, 47103, 47103, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 63487, 62463, 46079, 46079, 46079, 29695, 29695, 8861, +6553, 0, 0, 0, 5393, 27423, 29695, 48127, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 48127, 47103, 46079, 28607, 27455, 9950, 11039, 0, 0, 8720, 7834, 29695, 28607, 47103, 47103, 48127, 48127, 46079, 63487, 63487, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 47103, 45055, 27519, 29695, 29695, 29695, 9981, 0, 0, 6646, 28639, 28607, 28575, 27487, 29695, 47103, 46079, 63487, 64511, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 48127, 47103, 46079, 45055, 46047, 28575, 30719, 29695, 30719, 11135, 8720, 31, 7703, 26463, 28671, 28671, +29695, 47103, 48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 47103, 48127, 47103, 29695, 29695, 31743, 28671, +11135, 9947, 0, 0, 4112, 7805, 12223, 27519, 28671, 45023, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, +49151, 48127, 30719, 28639, 28671, 28671, 28575, 27519, 11071, 0, 0, 0, 0, 3283, 8861, 26463, 25311, 26431, 44991, 46079, 46079, 47103, 48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 28607, 7836, 8957, 8760, 7801, 8727, 0, 0, 0, 0, 0, 8827, 27551, 27487, 28671, 29695, 47103, 48127, 49151, 48127, 49151, +64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 49151, 64511, 48127, 49151, 49151, 49151, 29695, 11039, 13, 5450, 0, 0, 0, 0, 0, 0, 8727, 27583, +29695, 29695, 32767, 49151, 49151, 49151, 48127, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 65535, 64511, 48127, 48127, 46079, 43871, 27423, 28607, 27487, 32767, 12062, 0, +0, 0, 0, 0, 0, 0, 10111, 14335, 32767, 32767, 49151, 49151, 48127, 48127, 48127, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 48127, 48127, 49151, +46079, 27615, 25311, 6748, 7805, 12223, 7772, 6777, 0, 0, 0, 0, 0, 0, 5653, 11135, 13311, 31743, 29695, 29695, 43935, 46079, 48127, 47103, 49151, 49151, 65535, 65535, 65535, 64511, 65535, 65535, +64511, 64511, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 45055, 28671, 8894, 0, 9951, 6679, 31, 0, 0, 0, 0, 0, 0, 0, 8959, 3072, 11103, 10079, 28671, 25276, 27519, 46079, 47103, +48127, 47103, 49151, 65535, 64511, 48127, 64511, 64511, 64511, 48127, 48127, 49151, 49151, 48127, 48127, 49151, 49151, 48127, 29695, 8895, 4639, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 6547, 0, 8991, 27551, 28671, 31743, 30719, 47103, 46079, 49151, 49151, 49151, 49151, 48127, 49151, 48127, 49151, 48127, 48127, 47103, 48127, 48127, 48127, 47103, 30719, 28671, 8927, 31, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11263, 11263, 26495, 24253, 28671, 29695, 46079, 45023, 45055, 46079, 48127, 49151, 47103, 48127, 46079, 49151, 48127, 47103, 46079, 28639, 31743, 29695, +30719, 27583, 8926, 8890, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 7805, 8924, 5390, 10047, 30719, 29695, 45055, 43999, 45055, 47103, 47103, 47103, 48127, +46079, 48127, 46079, 46079, 46079, 27615, 27487, 26463, 31743, 9983, 4534, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 31, 5450, 8923, 29695, +27583, 27551, 28671, 45023, 49151, 48127, 46079, 46079, 46079, 46079, 45055, 27583, 30719, 27551, 7771, 9983, 31743, 10015, 6783, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 8823, 28671, 26495, 28671, 30719, 26399, 31743, 29695, 28671, 24287, 25343, 27551, 25343, 27551, 31743, 30719, 11263, 0, 8959, 6680, 8720, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10047, 28671, 29695, 31743, 26463, 28639, 26495, 28671, 6750, 4439, 23065, 21910, 23099, 27551, 29695, 11199, 0, +5461, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 7806, 25311, 29695, 11231, 28671, 26495, 29695, 11199, +5461, 5461, 4368, 2229, 6747, 10047, 8959, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +5461, 5590, 11167, 10111, 12287, 8863, 10111, 5823, 0, 0, 0, 0, 0, 8208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8991, 6679, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 528, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 8720, 6582, 6613, 5457, 6540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10943, 9819, 27391, 8763, 8727, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9951, 8727, 0, 0, 8735, 26236, 29695, 26301, 8762, 8727, 0, 0, 10933, 0, 0, 0, +0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 27455, 28543, 5136, 5358, 9918, 27390, 48127, 28479, +8728, 9849, 8192, 8659, 11006, 6217, 0, 0, 0, 9948, 7736, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4112, 9981, 8828, +29631, 32767, 13247, 9948, 30719, 28447, 49151, 28543, 26201, 11039, 9883, 27455, 30719, 9948, 6547, 0, 10972, 30719, 28575, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 15360, 8595, 8829, 29695, 28575, 30719, 32767, 30719, 27551, 29695, 44895, 49151, 48127, 30719, 31743, 31743, 30719, 32767, 27455, 8826, 7837, 12159, 32767, 31743, 14335, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8991, 28575, 32767, 32767, 49151, 48127, 48127, 47103, 47103, 47103, 49151, 49151, 48127, 49151, 49151, 49151, 48127, 30719, 29695, 29695, +30719, 31743, 29695, 11167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5120, 27487, 32767, 49151, 49151, 49151, 49151, 49151, 48127, 48127, 49151, 49151, +48127, 49151, 49151, 49151, 49151, 48127, 46079, 47103, 48127, 29695, 27391, 7641, 5823, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 9916, 5120, 0, 5461, 5450, 9916, 28639, 46079, 49151, +49151, 49151, 49151, 65535, 65535, 65535, 65535, 49151, 48127, 49151, 49151, 49151, 49151, 48127, 47103, 49151, 48127, 31743, 30719, 27455, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 6650, 28543, +27390, 10014, 8858, 10014, 28575, 29695, 48127, 47103, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 48127, 49151, 49151, 49151, 48127, 48127, 49151, 49151, 47103, 28607, 29695, 9947, 8192, 6553, 4368, +0, 0, 0, 0, 0, 8735, 9916, 31743, 32767, 32767, 48127, 47103, 49151, 48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 65535, 49151, 65535, 65535, 48127, 49151, 49151, +49151, 49151, 31743, 31743, 11135, 9949, 9983, 8894, 4112, 0, 0, 0, 0, 0, 8990, 31743, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 48127, 29695, 28575, 31743, 32767, 14335, 13311, 0, 0, 0, 0, 0, 11039, 30719, 32767, 49151, 49151, 49151, 49151, 49151, 49151, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 48127, 32767, 31743, 31743, 12191, 10933, 0, 0, 0, 0, 8720, 6777, 28671, +48127, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 48127, 31743, 31743, 29695, 11103, 12191, +4096, 0, 0, 0, 0, 8892, 12191, 29695, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +47103, 47103, 46047, 28575, 29695, 30719, 28575, 16383, 11039, 8735, 0, 0, 13311, 30719, 30719, 29695, 48127, 48127, 48127, 47103, 64511, 63487, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 63487, 64511, 47103, 46079, 48127, 49151, 48127, 47103, 46079, 29695, 30719, 11007, 8735, 0, 0, 0, 9884, 28575, 30719, 32767, 49151, 49151, 48127, 47103, 64511, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 49151, 48127, 30719, 12223, 8760, 10581, 0, 0, 16383, 9948, 30719, 28575, +31743, 49151, 49151, 49151, 49151, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49151, 49151, 49151, 49151, 47103, 29695, 11135, +5461, 0, 0, 0, 6348, 8857, 28607, 27519, 27487, 46079, 46079, 47103, 47103, 47103, 63487, 63487, 64511, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 64511, 64511, 64511, 64511, 63487, 64511, +62463, 46047, 44959, 44991, 46079, 29695, 26399, 6582, 8720, 0, 0, 0, 8892, 26431, 27391, 27357, 46047, 48127, 46079, 47103, 47103, 64511, 65535, 64511, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 64511, 64511, 64511, 64511, 65535, 64511, 63487, 46079, 46079, 47103, 31743, 28671, 8861, 8727, 0, 0, 0, 6512, 27423, 47103, 48127, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 47103, 47103, 46079, 27423, 8859, 10015, 0, 0, 8720, 8858, 28639, 30719, 48127, +48127, 48127, 47103, 46079, 62463, 63487, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 47103, 47103, 46079, 43839, 27519, 28639, +28671, 9947, 0, 0, 7701, 27487, 28575, 28575, 46079, 47103, 46079, 46079, 62463, 63487, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 64511, 48127, +47103, 46079, 44991, 46079, 46079, 43871, 29695, 29695, 29695, 11103, 8720, 31, 5556, 25278, 28607, 28671, 47103, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 64511, 47103, 47103, 46079, 47103, 47103, 47103, 44959, 29695, 29695, 27487, 7736, 0, 0, 5461, 8795, 11135, 27551, 46079, 47103, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 49151, 49151, 48127, 47103, 30719, 30719, 29695, 29695, 31743, 29695, 12191, 0, 0, 0, 8464, 7736, 27487, +28607, 43903, 47103, 47103, 46079, 47103, 49151, 65535, 64511, 64511, 65535, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 63487, 49151, 49151, 49151, 49151, 46079, 26399, 8824, 8858, 9917, +9950, 8730, 0, 0, 0, 8720, 8925, 30719, 29663, 44991, 45023, 46079, 47103, 48127, 48127, 48127, 63487, 65535, 65535, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 63487, 63487, +48127, 49151, 49151, 49151, 30719, 27487, 2060, 8727, 6547, 31, 0, 0, 0, 8735, 6716, 28671, 29695, 32767, 32767, 49151, 49151, 47103, 47103, 63487, 63487, 64511, 64511, 64511, 64511, 65535, 65535, 65535, +65535, 64511, 65535, 65535, 65535, 64511, 47103, 47103, 46079, 45055, 44959, 46079, 30719, 32767, 12094, 8720, 0, 0, 0, 0, 0, 0, 7768, 28639, 31743, 32767, 49151, 49151, 49151, 48127, 64511, 64511, +65535, 65535, 65535, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 49151, 65535, 65535, 48127, 49151, 47103, 46079, 26399, 8926, 10079, 27487, 8795, 8727, 0, 0, 0, 0, 0, 0, 5461, 12191, +32767, 32767, 30719, 45023, 47103, 48127, 48127, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 48127, 49151, 49151, 65535, 48127, 48127, 48127, 46079, 28671, 8959, 2057, 9951, 8725, 31, +0, 0, 0, 0, 0, 0, 0, 7740, 27487, 28575, 11135, 26431, 27487, 48127, 47103, 47103, 49151, 64511, 65535, 64511, 63487, 63487, 64511, 64511, 63487, 48127, 48127, 49151, 49151, 49151, 48127, 49151, +49151, 48127, 28671, 29695, 8959, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8927, 7736, 0, 9982, 26399, 30719, 48127, 47103, 47103, 48127, 49151, 65535, 49151, 49151, 65535, 65535, +65535, 48127, 47103, 48127, 48127, 48127, 47103, 47103, 48127, 46079, 28639, 12287, 8891, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9914, 31743, 27583, 28671, 30719, 46079, +46079, 47103, 47103, 49151, 47103, 47103, 47103, 48127, 47103, 47103, 47103, 47103, 47103, 28607, 29695, 31743, 31743, 27551, 9983, 11071, 5813, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 8720, 6681, 28639, 25277, 27583, 26399, 29695, 27551, 45023, 45055, 47103, 48127, 48127, 46079, 47103, 46079, 48127, 48127, 46079, 47103, 28671, 28671, 30719, 32767, 28607, 7737, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9983, 4368, 10079, 8959, 30719, 27615, 43935, 45023, 46079, 48127, 47103, 46079, 46079, 46079, 46079, 46079, 46047, 46079, 29695, 8925, 10015, +30719, 31743, 11103, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 5626, 27551, 26399, 26431, 28671, 29695, 47103, 45055, 28671, 27519, +29695, 27583, 46079, 45055, 28671, 29695, 8958, 4206, 11071, 31743, 11103, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10015, +27551, 28671, 30719, 27519, 48127, 27583, 28671, 25375, 26495, 25343, 27583, 25310, 27519, 31743, 11167, 0, 3184, 10015, 6742, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 3078, 7771, 25246, 28671, 24253, 28671, 26463, 27647, 7902, 6745, 5624, 26495, 7804, 8894, 30719, 11167, 0, 0, 5461, 31, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 6547, 10047, 7836, 27615, 7837, 8959, 8727, 0, 16, 9055, 1023, 3, 10079, 6777, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8991, 6679, 8720, 0, +0, 0, 0, 0, 0, 528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8726, 8694, 9650, 8720, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 0, 0, 5461, 9819, 25080, 27359, 9785, +9750, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9951, +14335, 9951, 8720, 8763, 12159, 27325, 30719, 27391, 9782, 6582, 5823, 15360, 8720, 0, 0, 0, 0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 8890, 10047, 30719, 28575, 6487, 9983, 31743, 28447, 48127, 28447, 26233, 9951, 9918, 8626, 11005, 6345, 8720, 0, 0, 9945, 8761, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11103, 12191, 27487, 30719, 32767, 27455, 26303, 31743, 29567, 49151, 46079, 30719, 32767, 30719, 28447, 30719, 9948, 10047, 0, +9849, 29663, 28543, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9981, 29695, 31743, 31743, 32767, 32767, 31743, 32767, 31743, 46015, 49151, 48127, +47103, 49151, 31743, 47103, 31743, 29631, 29695, 26301, 27423, 30719, 30719, 14335, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8826, 29663, 32767, 49151, +49151, 47103, 47103, 48127, 47103, 47103, 49151, 49151, 47103, 49151, 49151, 48127, 48127, 47103, 47103, 29695, 29695, 30719, 29663, 9916, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 6480, 28575, 48127, 49151, 49151, 49151, 48127, 49151, 48127, 49151, 49151, 49151, 48127, 49151, 48127, 48127, 49151, 49151, 47103, 49151, 48127, 30719, 30719, 26301, 6582, 0, 0, 0, +0, 0, 0, 0, 0, 0, 2053, 8860, 8192, 8720, 8727, 6348, 26333, 30719, 47103, 49151, 49151, 48127, 65535, 65535, 64511, 64511, 65535, 48127, 48127, 48127, 47103, 49151, 49151, 48127, 46079, 49151, +49151, 49151, 48127, 27423, 9884, 5120, 0, 0, 0, 0, 0, 0, 0, 8720, 6616, 27455, 10973, 11005, 25241, 9948, 28543, 48127, 48127, 47103, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, +63487, 47103, 47103, 49151, 49151, 47103, 47103, 49151, 49151, 49151, 48127, 31743, 29695, 11167, 9849, 5461, 0, 0, 0, 0, 0, 8720, 9916, 30719, 31743, 32767, 48127, 47103, 48127, 49151, 49151, 49151, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 63487, 63487, 64511, 49151, 65535, 65535, 63487, 49151, 49151, 49151, 49151, 48127, 31743, 29695, 11071, 9983, 8860, 8464, 0, 0, 0, 0, 0, 9948, 30719, +32767, 49151, 49151, 49151, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 65535, 49151, 49151, 49151, 49151, 49151, 48127, 30719, 30719, 31743, 14335, +11103, 0, 0, 0, 0, 0, 8991, 29695, 48127, 49151, 48127, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 49151, 49151, +48127, 48127, 49151, 47103, 47103, 30719, 30719, 12191, 8735, 0, 0, 0, 0, 8720, 9983, 28639, 47103, 49151, 49151, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 47103, 46079, 30719, 29663, 28607, 9948, 4096, 0, 0, 0, 0, 8760, 11135, 29695, 47103, 49151, 49151, 48127, 48127, 64511, 65535, 49151, +65535, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 64511, 64511, 47103, 46047, 44959, 27455, 28575, 30719, 30719, 29695, 10940, 8720, 0, 0, 11263, 29695, 31743, 31743, +47103, 47103, 47103, 46047, 62463, 62463, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 63487, 63487, 48127, 47103, 47103, 49151, 48127, 48127, 48127, 48127, 30719, +11071, 8727, 0, 0, 0, 9917, 29695, 31743, 49151, 49151, 48127, 47103, 63487, 63487, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 49151, +49151, 49151, 49151, 49151, 49151, 49151, 47103, 28671, 9883, 8720, 0, 0, 15391, 9914, 29663, 28543, 32767, 49151, 49151, 49151, 48127, 47103, 63487, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48127, 49151, 49151, 49151, 48127, 29695, 11135, 10933, 0, 0, 0, 6540, 8824, 27519, 27455, 28639, 46047, 47103, 47103, 47103, 46079, 46079, 62463, +63487, 63487, 63487, 64511, 64511, 64511, 63487, 64511, 63487, 63487, 63487, 63487, 63487, 62463, 62463, 63487, 45023, 44927, 43903, 46079, 46079, 28639, 27487, 6582, 8720, 0, 0, 0, 9851, 9983, 27358, 29631, +47103, 46079, 47103, 48127, 49151, 64511, 64511, 63487, 63487, 63487, 64511, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 64511, 63487, 63487, 63487, 63487, 65535, 64511, 47103, 46079, 46079, 47103, 47103, 31743, 9950, +6547, 0, 0, 0, 7600, 27423, 47103, 49151, 49151, 49151, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 48127, 46079, 48127, 47103, 30719, 11005, 9949, 0, 0, 8720, 8858, 28607, 30719, 47103, 48127, 47103, 46079, 44959, 61375, 62463, 63487, 63487, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 63487, 46079, 46079, 47103, 43871, 27487, 27455, 28575, 9915, 0, 0, 6676, 26399, 27519, 27487, 47103, 47103, 46079, 45055, 61407, 62463, 63487, 64511, +65535, 65535, 64511, 64511, 64511, 65535, 64511, 65535, 64511, 64511, 65535, 64511, 63487, 62463, 63487, 47103, 46079, 45023, 43871, 46079, 46079, 44959, 28671, 28607, 28607, 11039, 8720, 31, 6547, 25212, 27519, 28639, +47103, 48127, 48127, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 65535, 65535, 64511, 64511, 64511, 63487, 47103, 47103, 45055, 45055, 47103, 47103, 46079, 44991, 28607, +26399, 7640, 0, 0, 5450, 8726, 25246, 27519, 46079, 49151, 48127, 64511, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 65535, 65535, 65535, 65535, 64511, 64511, 65535, 65535, 65535, 64511, 63487, 65535, 64511, +48127, 47103, 46079, 29695, 30719, 31743, 29695, 30719, 29695, 12159, 0, 0, 0, 8720, 7703, 27519, 45023, 47103, 47103, 46079, 46079, 47103, 64511, 64511, 62463, 63487, 64511, 63487, 63487, 64511, 65535, 64511, +64511, 64511, 63487, 64511, 65535, 62463, 62463, 47103, 49151, 49151, 49151, 47103, 27423, 26333, 9948, 9916, 9949, 9849, 0, 0, 0, 8720, 8859, 29695, 29663, 46079, 44991, 45023, 46079, 48127, 48127, 63487, +62463, 63487, 63487, 63487, 63487, 64511, 65535, 64511, 65535, 65535, 65535, 64511, 65535, 62463, 62463, 46079, 47103, 49151, 49151, 49151, 48127, 28575, 3082, 8727, 8727, 31, 0, 0, 0, 8735, 6715, 28575, +28607, 31743, 32767, 49151, 47103, 46079, 46079, 62463, 62463, 63487, 63487, 63487, 63487, 64511, 65535, 64511, 64511, 63487, 63487, 64511, 64511, 63487, 62463, 46079, 46079, 44991, 46079, 47103, 47103, 31743, 11037, 8720, +0, 0, 0, 0, 0, 0, 7704, 28543, 30719, 32767, 49151, 49151, 49151, 48127, 63487, 63487, 64511, 64511, 64511, 63487, 64511, 64511, 65535, 64511, 64511, 63487, 47103, 48127, 65535, 65535, 47103, 48127, +46079, 45055, 28639, 25311, 25310, 26335, 8761, 5461, 0, 0, 0, 0, 0, 0, 6348, 11135, 31743, 32767, 30719, 46079, 47103, 48127, 48127, 47103, 65535, 64511, 64511, 63487, 64511, 64511, 65535, 64511, +63487, 64511, 47103, 47103, 49151, 65535, 47103, 47103, 47103, 46079, 29695, 26463, 6713, 8731, 6547, 31, 0, 0, 0, 0, 0, 0, 0, 6553, 26399, 28575, 11103, 26463, 43903, 47103, 46079, 48127, +49151, 64511, 64511, 64511, 64511, 63487, 64511, 64511, 64511, 64511, 47103, 47103, 48127, 49151, 47103, 47103, 48127, 47103, 27551, 27615, 8925, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +8831, 6742, 0, 25243, 25311, 47103, 47103, 48127, 47103, 47103, 48127, 64511, 48127, 48127, 65535, 65535, 64511, 48127, 46079, 47103, 48127, 48127, 48127, 47103, 48127, 47103, 28639, 11231, 8859, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8858, 28671, 27487, 31743, 30719, 47103, 46079, 47103, 47103, 49151, 48127, 48127, 49151, 49151, 48127, 46079, 45055, 47103, 48127, 45023, 46079, 29695, +31743, 30719, 26463, 10047, 5813, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 7804, 28607, 26399, 28639, 26367, 28671, 42847, 45055, 45055, 47103, 47103, 47103, 46079, 47103, +46079, 46079, 47103, 46079, 48127, 43935, 27519, 29695, 31743, 32767, 11006, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 8925, 27551, 10047, 8895, 29695, +27519, 43967, 43999, 46079, 46079, 46079, 45055, 46079, 45055, 45055, 46079, 44991, 46079, 43999, 8859, 9982, 29695, 32767, 11071, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 11263, 6777, 6552, 26367, 25246, 27551, 28671, 30719, 46079, 43935, 44031, 42911, 28671, 26495, 45055, 45055, 45055, 45055, 25278, 5331, 10015, 14335, 11103, 8720, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5450, 8763, 25343, 28671, 29695, 30719, 47103, 42879, 28671, 25375, 28671, 26495, 27615, 26463, 26399, 29695, 25309, 6547, +5230, 10015, 6742, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5120, 6581, 25244, 27519, 26431, 31743, 26463, 27615, 25311, +28671, 26431, 28671, 8925, 24154, 29695, 10047, 8720, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +8720, 6450, 8924, 6614, 27583, 24155, 23165, 5525, 6747, 6714, 28671, 10079, 3078, 10047, 6777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8991, 7637, 4507, 3283, 4368, 0, 10079, 8735, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5450, 9855, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 6553, 8730, 8661, 27423, 8629, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 11103, 0, 8735, 9919, 12095, 27291, 30719, 12062, 8724, 8730, 8890, 11039, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8208, 13311, 11103, 29695, 11103, 8892, 27487, 31743, 27357, 30719, 27389, 25077, 27423, 13279, 14335, 12287, 0, 0, 0, +0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9949, 11007, 15359, 31743, 30719, 30719, 28575, 28575, 31743, 28511, 47103, 28543, +27391, 30719, 30719, 29663, 11039, 7399, 5461, 0, 0, 8760, 8825, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13311, 13311, 29695, 30719, 31743, +32767, 32767, 31743, 31743, 31743, 29663, 49151, 48127, 31743, 31743, 30719, 28479, 29695, 9882, 10973, 2048, 9849, 12127, 28479, 9819, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 10581, 12063, 30719, 31743, 47103, 49151, 49151, 49151, 32767, 30719, 29663, 49151, 48127, 47103, 49151, 31743, 30719, 30719, 29631, 30719, 27356, 27391, 29663, 30719, 12159, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8793, 29599, 32767, 48127, 48127, 48127, 48127, 47103, 47103, 47103, 49151, 48127, 46079, 48127, 48127, 48127, 48127, 48127, 47103, 29663, +30719, 30719, 31743, 30719, 11005, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 0, 0, 12287, 28543, 30719, 47103, 48127, 49151, 48127, 48127, 48127, 48127, 48127, 49151, 48127, +47103, 48127, 47103, 48127, 49151, 49151, 47103, 49151, 48127, 47103, 32767, 30719, 9983, 5120, 0, 0, 0, 0, 0, 0, 0, 0, 6342, 9818, 11035, 8726, 8456, 5130, 26333, 48127, 46079, 48127, +48127, 47103, 48127, 65535, 65535, 64511, 65535, 47103, 47103, 47103, 46079, 48127, 49151, 47103, 46079, 49151, 48127, 49151, 48127, 28511, 27423, 8831, 0, 0, 0, 0, 0, 0, 0, 8720, 8760, 28511, +30719, 28575, 26266, 9915, 28511, 48127, 47103, 46047, 64511, 63487, 65535, 65535, 64511, 64511, 65535, 63487, 63487, 46079, 46079, 48127, 64511, 46079, 47103, 48127, 48127, 49151, 49151, 30719, 29695, 11167, 8727, 5450, +0, 0, 0, 0, 0, 8735, 9916, 29695, 31743, 48127, 47103, 47103, 48127, 48127, 49151, 48127, 65535, 64511, 65535, 65535, 64511, 64511, 64511, 63487, 62463, 47103, 47103, 48127, 64511, 47103, 49151, 48127, +49151, 49151, 48127, 30719, 29695, 11007, 11007, 8792, 5450, 0, 0, 0, 0, 0, 9916, 29695, 30719, 48127, 49151, 48127, 49151, 48127, 48127, 49151, 65535, 65535, 64511, 65535, 65535, 65535, 65535, 64511, +64511, 64511, 64511, 64511, 64511, 64511, 48127, 48127, 49151, 48127, 47103, 30719, 29695, 29695, 31743, 13183, 10047, 0, 0, 0, 0, 0, 10972, 28607, 30719, 47103, 47103, 48127, 65535, 48127, 64511, 65535, +65535, 65535, 64511, 64511, 64511, 64511, 64511, 64511, 64511, 64511, 64511, 65535, 64511, 47103, 48127, 48127, 48127, 48127, 48127, 46079, 46079, 29663, 30719, 12127, 8735, 0, 0, 0, 0, 31, 8860, 27519, +47103, 48127, 48127, 47103, 49151, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 63487, 48127, 47103, 46079, 44991, 29695, 28511, 27487, 9949, +0, 0, 0, 0, 0, 7670, 12159, 29695, 48127, 49151, 48127, 47103, 47103, 64511, 65535, 65535, 64511, 63487, 64511, 64511, 65535, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 63487, 62463, 62463, 62463, +47103, 44959, 43839, 28511, 29695, 29695, 29695, 29695, 9982, 8720, 0, 0, 13119, 29663, 31743, 31743, 47103, 47103, 46079, 44927, 46047, 62463, 63487, 63487, 65535, 64511, 64511, 64511, 65535, 65535, 65535, 65535, +65535, 65535, 65535, 65535, 63487, 62463, 62463, 47103, 47103, 46079, 48127, 47103, 48127, 47103, 48127, 31743, 12191, 8727, 0, 0, 8192, 9916, 29695, 32767, 49151, 48127, 47103, 46079, 46079, 46079, 63487, 63487, +64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 63487, 63487, 63487, 49151, 49151, 49151, 49151, 48127, 49151, 49151, 47103, 28607, 8859, 8720, 0, 0, 0, 9849, 28607, 30719, +32767, 49151, 49151, 48127, 47103, 46079, 63487, 63487, 64511, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 65535, 48127, 47103, 47103, 48127, 49151, 48127, 28607, 11071, +10933, 0, 0, 0, 8456, 8758, 27455, 27455, 28575, 47103, 47103, 46079, 46079, 45055, 45055, 45023, 62463, 62463, 62463, 62463, 62463, 63487, 62463, 62463, 62463, 62463, 61439, 61439, 62463, 62463, 61375, 45023, +44959, 43839, 43807, 44991, 46079, 27519, 26367, 6579, 8720, 0, 0, 0, 8825, 26333, 29695, 47071, 46079, 47103, 47103, 48127, 48127, 63487, 63487, 62463, 62463, 62463, 62463, 64511, 64511, 65535, 63487, 63487, +63487, 63487, 62463, 62463, 62463, 62463, 62463, 64511, 64511, 46079, 45055, 45023, 46079, 46079, 29695, 8860, 8727, 0, 0, 0, 8657, 28447, 48127, 49151, 49151, 49151, 48127, 64511, 64511, 65535, 65535, 65535, +65535, 64511, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 64511, 64511, 64511, 65535, 65535, 65535, 64511, 46079, 45055, 47103, 46079, 29695, 9980, 10969, 0, 0, 8720, 9848, 27487, 47103, 48127, +47103, 47103, 45023, 43839, 43871, 44991, 62463, 62463, 63487, 63487, 63487, 64511, 64511, 65535, 64511, 64511, 64511, 64511, 64511, 63487, 63487, 64511, 63487, 63487, 62463, 62463, 44991, 45055, 47103, 46079, 27423, 26301, +27455, 9850, 15360, 0, 6578, 25212, 27455, 27487, 46079, 47103, 46079, 43871, 44959, 62463, 62463, 63487, 63487, 63487, 63487, 63487, 63487, 64511, 63487, 63487, 63487, 63487, 63487, 62463, 62463, 61439, 46079, 46079, +45023, 43903, 42751, 44991, 45055, 46079, 45023, 28607, 27487, 9916, 8720, 31, 5489, 8795, 27519, 28639, 46079, 47103, 47103, 47103, 64511, 65535, 64511, 64511, 64511, 64511, 63487, 63487, 64511, 64511, 64511, 63487, +63487, 63487, 63487, 63487, 62463, 62463, 62463, 46079, 47103, 45023, 44991, 45055, 45055, 46079, 45055, 28639, 26367, 7637, 0, 0, 0, 8826, 28607, 27519, 46079, 47103, 48127, 63487, 64511, 64511, 64511, 64511, +63487, 63487, 63487, 63487, 64511, 64511, 64511, 63487, 63487, 64511, 64511, 64511, 62463, 62463, 64511, 63487, 47103, 46079, 45023, 28639, 29695, 31743, 30719, 29695, 28639, 12159, 0, 0, 11263, 12191, 30719, 29695, +47103, 47103, 46079, 45055, 46079, 46079, 63487, 63487, 61439, 62463, 62463, 62463, 62463, 63487, 63487, 63487, 63487, 63487, 62463, 63487, 63487, 61375, 62463, 62463, 47103, 49151, 48127, 47103, 27519, 26366, 27551, 9950, +9915, 8727, 0, 0, 11263, 12223, 29695, 29695, 47103, 46079, 43903, 44991, 47103, 47103, 47103, 62463, 61439, 62463, 62463, 63487, 62463, 63487, 64511, 63487, 63487, 63487, 63487, 62463, 64511, 61439, 60351, 45023, +46079, 48127, 49151, 49151, 49151, 28575, 8860, 6777, 10581, 0, 0, 0, 0, 8892, 9950, 26399, 28543, 30719, 30719, 47103, 47103, 45055, 45055, 61439, 61439, 62463, 62463, 62463, 62463, 62463, 63487, 62463, +62463, 62463, 62463, 62463, 63487, 62463, 61439, 43967, 43967, 43935, 45023, 48127, 47103, 30719, 11103, 8720, 0, 0, 0, 0, 0, 0, 8725, 27391, 29695, 32767, 49151, 49151, 48127, 47103, 46079, 62463, +63487, 63487, 63487, 63487, 62463, 62463, 64511, 63487, 63487, 62463, 45055, 47103, 64511, 64511, 47103, 46079, 45055, 45055, 43903, 26399, 27551, 27551, 9950, 8720, 0, 0, 0, 0, 0, 0, 5450, 11135, +30719, 31743, 29695, 46079, 47103, 48127, 46079, 46079, 48127, 63487, 63487, 62463, 63487, 63487, 64511, 63487, 62463, 63487, 46079, 46079, 47103, 64511, 47103, 46079, 47103, 46079, 46079, 29695, 27519, 8894, 8725, 0, +0, 0, 0, 0, 0, 0, 0, 6419, 26367, 27519, 26431, 28639, 43903, 47103, 45023, 47103, 48127, 63487, 63487, 63487, 62463, 62463, 63487, 62463, 63487, 47103, 46079, 46079, 47103, 48127, 47103, 46079, +47103, 47103, 45023, 27583, 25279, 7736, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8764, 6582, 3072, 26300, 25310, 46079, 47103, 47103, 47103, 46079, 47103, 63487, 47103, 47103, 64511, 64511, +47103, 47103, 46079, 46079, 47103, 47103, 47103, 47103, 47103, 46079, 28671, 26463, 7802, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8890, 28671, 28671, 29695, 47103, 47103, +45055, 46079, 46079, 48127, 48127, 47103, 48127, 49151, 47103, 45055, 46079, 46079, 48127, 46079, 46079, 43999, 29695, 29695, 29695, 10015, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 8720, 7771, 27551, 27519, 28639, 26399, 28671, 42815, 43967, 43999, 46079, 46079, 46079, 46079, 47103, 45055, 43999, 45055, 45055, 48127, 46079, 26463, 27519, 29695, 31743, 26431, 6611, 16, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2048, 8924, 27519, 10015, 7804, 28639, 26431, 43903, 43935, 46079, 46079, 46079, 43999, 45055, 43999, 43967, 45055, 43935, 47103, 45055, 25211, 8892, +12287, 30719, 10014, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5120, 13311, 8727, 7574, 25246, 26367, 27551, 28639, 30719, 45055, 43935, 27551, 26495, +27647, 25343, 43935, 43999, 46079, 45023, 25212, 5424, 9982, 13311, 10047, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 6550, +25245, 27583, 27615, 29695, 46079, 42783, 27583, 25279, 27583, 26431, 27551, 26431, 26367, 28639, 25244, 6547, 4237, 9983, 8725, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 11103, 26399, 26431, 26399, 26431, 29695, 26399, 26463, 24189, 28671, 25343, 28639, 8925, 24088, 28639, 8925, 8720, 0, 8720, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6811, 7770, 8828, 6547, 26463, 24121, 24156, 23098, 28671, 24252, 27583, 9023, 4100, 10079, 5813, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8892, 6582, 6551, 4403, +8927, 5557, 8927, 8735, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 6559, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9855, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 9849, 8660, 28415, 8660, 5450, 0, 0, 11039, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11103, 0, 8735, 9820, 12031, 27291, 30719, 12029, +9748, 10778, 12191, 16383, 14335, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8192, 12031, 11071, +12191, 11071, 8764, 27423, 30719, 27356, 29695, 28381, 26133, 27359, 12223, 32767, 14335, 0, 0, 0, 0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 10908, 10973, 14335, 31743, 30719, 30719, 28575, 28575, 31743, 28447, 46079, 29567, 28447, 29695, 29695, 29631, 10973, 6569, 5461, 0, 0, 8723, 9847, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12287, 13247, 28543, 29695, 30719, 31743, 32767, 31743, 31743, 30719, 29631, 48127, 47103, 31743, 31743, 30719, 28415, 28543, 9848, 9948, 5126, +8661, 10909, 12031, 10778, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8704, 12063, 29631, 30719, 47103, 32767, 49151, 48127, 31743, 30719, 29599, 48127, 47103, +46079, 48127, 30719, 29663, 29695, 29599, 30719, 27323, 27423, 29567, 30719, 12191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8759, 28479, 31743, 47103, +48127, 47103, 47103, 47103, 46079, 47103, 48127, 47103, 46079, 48127, 47103, 47103, 47103, 48127, 47103, 29663, 31743, 30719, 31743, 30719, 11004, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +15391, 0, 0, 11263, 27455, 29695, 29663, 47103, 48127, 47103, 47103, 47103, 47103, 47103, 48127, 47103, 47103, 47103, 46079, 47103, 49151, 49151, 47103, 48127, 48127, 47103, 31743, 30719, 11006, 8704, 0, 0, +0, 0, 0, 0, 0, 0, 8456, 9750, 11069, 8595, 5450, 8528, 26332, 47103, 46015, 47103, 47103, 47103, 47103, 64511, 64511, 63487, 64511, 47103, 46079, 46079, 46015, 47103, 48127, 47103, 46015, 48127, +47103, 48127, 47103, 28479, 27391, 9951, 0, 0, 0, 0, 0, 0, 0, 8720, 9816, 27423, 29695, 27423, 26233, 9916, 28479, 47103, 46079, 45983, 63487, 63487, 48127, 64511, 63487, 63487, 64511, 62463, +62463, 46047, 46047, 47103, 47103, 46015, 46079, 47103, 47103, 49151, 48127, 29695, 28607, 11039, 10933, 0, 0, 0, 0, 0, 0, 8735, 10940, 29663, 30719, 47103, 47103, 47103, 47103, 48127, 48127, 47103, +64511, 63487, 64511, 64511, 63487, 63487, 63487, 62463, 62431, 46047, 46079, 46079, 63487, 47103, 48127, 47103, 48127, 48127, 47103, 29695, 28575, 10941, 11005, 8626, 8720, 0, 0, 0, 0, 0, 9818, 28575, +30719, 47103, 48127, 49151, 48127, 47103, 47103, 48127, 65535, 64511, 63487, 64511, 64511, 64511, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 47103, 47103, 47103, 48127, 48127, 48127, 29695, 29631, 28543, 30719, 11006, +8727, 0, 0, 0, 0, 0, 9948, 28511, 29663, 46079, 46079, 48127, 48127, 48127, 48127, 48127, 64511, 64511, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 64511, 63487, 46079, 47103, 46079, +47103, 47103, 48127, 44959, 29631, 28543, 29695, 11069, 15391, 0, 0, 0, 0, 16383, 8729, 27423, 46079, 47103, 47103, 47103, 48127, 48127, 48127, 48127, 64511, 64511, 64511, 64511, 64511, 64511, 64511, 64511, +64511, 64511, 64511, 64511, 64511, 63487, 47103, 46079, 47103, 46079, 46047, 27423, 28543, 27391, 27391, 10941, 5120, 0, 0, 0, 0, 9750, 11071, 29663, 48127, 47103, 47103, 46079, 47103, 47103, 48127, 48127, +64511, 63487, 63487, 63487, 63487, 63487, 63487, 64511, 64511, 64511, 64511, 63487, 62463, 61407, 61407, 46047, 46079, 44895, 43775, 27423, 29663, 28607, 28607, 28639, 9916, 8720, 0, 0, 10047, 12159, 30719, 30719, +46079, 46079, 46015, 44895, 44959, 44991, 62463, 62463, 64511, 63487, 63487, 63487, 63487, 64511, 64511, 64511, 64511, 63487, 63487, 63487, 62463, 61375, 61375, 47103, 46079, 46047, 47103, 46047, 47103, 46079, 47103, 30719, +12127, 10933, 0, 0, 0, 10907, 29663, 32767, 48127, 47103, 47103, 46047, 45023, 46079, 62463, 62463, 63487, 63487, 64511, 64511, 64511, 64511, 65535, 65535, 65535, 65535, 64511, 63487, 62463, 62463, 62463, 48127, +48127, 47103, 48127, 47103, 48127, 47103, 46079, 28543, 9882, 8720, 0, 0, 0, 9815, 28511, 30719, 31743, 47103, 48127, 48127, 46079, 46079, 46079, 62463, 63487, 63487, 63487, 65535, 65535, 65535, 65535, 65535, +64511, 65535, 64511, 63487, 63487, 64511, 64511, 48127, 47103, 46079, 46079, 47103, 48127, 47103, 27519, 9951, 8720, 0, 0, 0, 8720, 9748, 27358, 27423, 27487, 46079, 46079, 46079, 44991, 44959, 44959, 43903, +61343, 61407, 61407, 61439, 61439, 62463, 61407, 61439, 61375, 61407, 61375, 61375, 61343, 61343, 60319, 44959, 43839, 43775, 43774, 43903, 44991, 27455, 26302, 6546, 8720, 0, 0, 0, 8760, 26300, 29695, 30719, +46015, 46079, 47103, 48127, 47103, 46079, 46079, 61407, 61375, 61375, 61439, 62463, 63487, 63487, 62463, 62463, 62463, 62463, 61439, 61375, 61375, 61407, 61439, 47103, 47103, 46079, 44959, 43903, 45055, 44959, 28639, 8826, +8720, 0, 0, 0, 8657, 27422, 48127, 49151, 48127, 48127, 48127, 64511, 63487, 64511, 64511, 64511, 63487, 63487, 63487, 64511, 64511, 65535, 65535, 65535, 65535, 64511, 64511, 63487, 62463, 63487, 63487, 64511, +64511, 63487, 46079, 44959, 46079, 44991, 28607, 9981, 9849, 0, 0, 16383, 9814, 27390, 47103, 47103, 47103, 46079, 44927, 43775, 43807, 43871, 44959, 61407, 61439, 62463, 62463, 63487, 63487, 63487, 63487, 63487, +63487, 63487, 63487, 62463, 62463, 63487, 62463, 62463, 61407, 46079, 43903, 44959, 46079, 44991, 26333, 26234, 26334, 8758, 15360, 0, 8592, 8728, 27423, 27455, 47103, 46079, 44959, 43775, 43839, 61407, 61343, 61439, +61439, 62463, 62463, 62463, 62463, 62463, 62463, 62463, 62463, 61439, 62463, 61439, 60351, 60319, 44991, 44991, 43871, 42783, 42687, 43871, 44927, 44959, 28575, 27487, 26367, 8826, 0, 0, 7636, 25179, 27455, 27455, +46079, 46079, 46079, 46079, 63487, 64511, 62463, 63487, 63487, 62463, 62463, 62463, 63487, 63487, 62463, 62463, 62463, 62463, 62463, 62463, 61439, 61439, 61439, 46079, 47103, 44991, 43903, 43935, 43935, 44991, 45023, 28575, +26269, 6582, 0, 0, 8727, 26334, 28575, 27423, 45023, 47103, 47103, 46079, 46079, 47103, 63487, 63487, 62463, 62463, 62463, 62463, 63487, 62463, 62463, 62463, 62463, 63487, 62463, 63487, 61439, 62463, 62463, 46079, +46079, 43935, 43935, 27551, 29695, 30719, 31743, 30719, 27455, 10943, 0, 0, 10943, 11103, 29695, 29663, 46079, 46079, 44959, 45023, 46079, 45055, 46079, 61439, 60351, 61439, 61439, 61439, 61439, 61439, 62463, 62463, +61439, 62463, 61439, 62463, 62463, 60287, 61439, 46079, 46079, 47103, 47103, 46079, 27455, 26367, 30719, 29695, 10015, 8727, 0, 0, 11263, 11135, 29695, 28607, 30719, 44991, 43839, 46079, 47103, 46079, 46079, 61439, +60351, 61407, 61439, 61439, 61439, 62463, 63487, 62463, 62463, 62463, 62463, 61439, 62463, 60319, 42815, 43903, 45023, 47103, 48127, 49151, 49151, 28543, 8925, 11167, 8890, 0, 0, 0, 0, 8827, 9949, 26335, +28511, 29695, 29695, 48127, 46079, 44991, 43935, 60319, 60351, 61439, 61375, 61375, 61407, 61439, 62463, 62463, 61439, 61439, 44031, 61439, 62463, 61407, 43903, 42783, 42847, 43871, 44959, 46079, 46079, 31743, 9981, 6348, +0, 0, 0, 0, 0, 0, 8758, 26236, 28607, 31743, 49151, 48127, 47103, 46079, 45055, 61439, 61439, 46079, 62463, 62463, 61439, 61439, 63487, 62463, 62463, 61439, 43999, 45055, 46079, 63487, 46079, 45055, +43935, 43935, 27455, 26335, 27487, 31743, 26301, 6547, 0, 0, 0, 0, 0, 0, 8720, 11039, 29695, 31743, 30719, 45055, 46079, 46079, 45055, 45055, 46079, 62463, 62463, 61439, 61439, 62463, 63487, 62463, +61439, 62463, 45055, 45055, 46079, 47103, 46079, 45055, 45055, 45023, 45055, 27583, 26367, 27455, 6647, 4375, 0, 0, 0, 0, 0, 0, 0, 6450, 26302, 27487, 26431, 28607, 46079, 46079, 43935, 46079, +47103, 62463, 62463, 62463, 61439, 61439, 62463, 61439, 61439, 46079, 45055, 45055, 46079, 47103, 46079, 45055, 45055, 46079, 46079, 26431, 24157, 7740, 5461, 31, 0, 0, 0, 0, 0, 0, 0, 0, +8731, 8725, 4096, 26333, 28639, 45055, 46079, 45055, 46079, 46079, 46079, 46079, 46079, 46079, 63487, 47103, 46079, 46079, 46079, 45055, 46079, 46079, 47103, 46079, 46079, 45055, 29695, 26367, 6679, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8889, 28607, 28671, 28671, 45055, 46079, 45023, 46079, 45055, 47103, 47103, 46079, 47103, 47103, 46079, 43967, 45055, 45055, 47103, 45055, 45055, 43903, +28671, 28671, 28671, 8892, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 7737, 27455, 27455, 27519, 25311, 44991, 42751, 43903, 43903, 45055, 45023, 45055, 45055, 46079, +43967, 43903, 43967, 43935, 47103, 46079, 26431, 26431, 27551, 29695, 26366, 6611, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3072, 8859, 11071, 9982, 7705, 26431, +26367, 26431, 42783, 45023, 45055, 45055, 43903, 43967, 42879, 42815, 43999, 42847, 46079, 45055, 24220, 8826, 10015, 28671, 8892, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8192, 10047, 8727, 7604, 8794, 25246, 26399, 26431, 29695, 43967, 42847, 26431, 26431, 27551, 24189, 42847, 43903, 43967, 43967, 24155, 5489, 8859, 12287, 10013, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 7505, 25211, 27519, 26463, 28671, 45055, 41695, 26463, 24190, 26463, 25311, 26463, 25343, 25246, 26463, 7770, 4368, +6342, 9980, 8727, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10047, 26303, 26367, 25214, 26367, 28671, 25279, 25343, 24155, +27647, 25311, 27583, 8893, 7670, 26463, 8827, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +8760, 8825, 7640, 5425, 25311, 23031, 24089, 6680, 27615, 7835, 26463, 8927, 5285, 9951, 5813, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7641, 5461, 6582, 5457, 7806, 6550, 8927, 8735, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4631, 31, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8727, 0, +0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8735, 8727, 8659, 10973, 9747, 5450, 0, 0, 11039, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 9855, 0, 8735, 9787, 11006, 10905, 30687, 12028, 9781, 9849, 12191, 15359, 14335, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12031, 11007, 11007, 10973, 8760, 10974, 30719, 27322, 29599, 27355, 9749, 10941, 12127, 31743, 13311, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8760, 9915, 14335, 30719, 30719, 30719, 28543, 28511, 30719, 28414, 45983, 28479, +28415, 29631, 29663, 29567, 10906, 8714, 10581, 0, 0, 8720, 9849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12031, 12095, 12063, 29663, 29695, +31743, 32767, 30719, 30719, 30719, 29599, 47103, 30719, 30719, 30719, 29663, 27356, 27359, 9750, 9915, 6342, 9650, 10874, 11997, 9849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 15360, 10940, 28415, 29663, 29663, 31743, 48127, 48127, 30719, 30719, 29567, 48127, 47103, 46015, 47103, 29695, 29567, 29599, 28511, 29663, 27322, 27390, 28479, 30719, 12095, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8726, 28415, 30719, 47103, 47103, 47103, 47103, 46079, 46047, 46047, 48127, 46047, 46015, 47103, 46047, 46047, 46079, 47103, 46047, 29599, +31743, 30687, 30719, 29599, 9914, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 0, 0, 10943, 10909, 29599, 28511, 47103, 47103, 46079, 46047, 47103, 46079, 47103, 47103, 45983, +46047, 47103, 45983, 47103, 48127, 48127, 46015, 47103, 47103, 46047, 30719, 29599, 10940, 8704, 0, 0, 0, 0, 0, 0, 0, 0, 5450, 9747, 11004, 6348, 5461, 6540, 26299, 30719, 45951, 47103, +46079, 46047, 47103, 47103, 63487, 62463, 63487, 46015, 46047, 46015, 44927, 47103, 47103, 46047, 44895, 47103, 46015, 47103, 46047, 28413, 27391, 10943, 0, 0, 0, 0, 0, 0, 0, 16383, 9782, 27390, +29695, 27390, 9848, 9882, 28447, 47103, 45983, 44895, 46015, 46015, 47103, 63487, 62463, 62463, 63487, 46015, 46047, 44959, 44927, 46047, 46047, 44927, 46015, 46047, 46015, 48127, 47103, 29599, 28543, 9983, 8720, 0, +0, 0, 0, 0, 0, 8735, 10971, 29599, 30719, 30719, 46047, 29663, 30719, 47103, 47103, 46079, 63487, 62463, 63487, 63487, 62463, 62463, 62463, 44991, 44959, 44959, 46047, 46047, 46015, 46047, 47103, 46079, +47103, 48127, 46079, 28607, 28511, 9851, 10971, 8593, 0, 0, 0, 0, 0, 16352, 10838, 28415, 29663, 47103, 47103, 48127, 48127, 47103, 47103, 47103, 48127, 63487, 62463, 63487, 63487, 63487, 62463, 62463, +62463, 62463, 62463, 62463, 47103, 46079, 46079, 46079, 47103, 47103, 47103, 29599, 28543, 28447, 29663, 11004, 10933, 0, 0, 0, 0, 0, 11995, 27423, 29631, 45983, 46015, 47103, 47103, 47103, 47103, 47103, +47103, 62463, 62463, 62463, 62463, 62463, 62463, 62463, 63487, 62431, 62463, 63487, 62431, 44959, 45023, 44959, 46047, 47103, 47103, 44895, 28511, 27423, 29663, 11037, 15391, 0, 0, 0, 0, 16383, 8760, 27359, +45983, 46079, 46079, 46079, 47103, 47103, 47103, 48127, 63487, 62463, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 46079, 46079, 46079, 46079, 46015, 44959, 27358, 28447, 27325, 10974, 9850, +8192, 0, 0, 0, 0, 10804, 10974, 28543, 46079, 46079, 46047, 46015, 46079, 46079, 47103, 47103, 47103, 62463, 62463, 62463, 62463, 62463, 62463, 63487, 63487, 63487, 63487, 62463, 62463, 43903, 44927, 44959, +46079, 44863, 27357, 27357, 28575, 28543, 28479, 27423, 9785, 15391, 0, 0, 8735, 10975, 29631, 29695, 28575, 45983, 44927, 43807, 44863, 44895, 61343, 62431, 62463, 62463, 62463, 62463, 62463, 63487, 63487, 63487, +63487, 63487, 62463, 62463, 61439, 43871, 44927, 46079, 46079, 44927, 46079, 44927, 46079, 44991, 46015, 29663, 11039, 10933, 0, 0, 0, 9850, 28543, 31743, 30719, 46079, 46079, 44927, 44927, 44959, 45023, 61407, +62463, 62463, 63487, 63487, 62463, 63487, 63487, 64511, 63487, 64511, 63487, 62463, 61407, 61407, 46079, 47103, 47103, 46079, 47103, 46079, 47103, 46079, 44927, 27423, 9849, 15391, 0, 0, 0, 9750, 27358, 29695, +30719, 47103, 47103, 47103, 46079, 44991, 45023, 61407, 62463, 62463, 62463, 63487, 63487, 64511, 64511, 64511, 63487, 63487, 63487, 62463, 63487, 63487, 63487, 47103, 46079, 45023, 44991, 46079, 47103, 46047, 26335, 9886, +8720, 0, 0, 0, 5450, 8722, 26266, 27357, 27391, 44959, 44959, 44927, 44895, 43775, 43839, 43807, 43839, 60255, 60255, 60287, 60255, 61311, 60255, 60287, 60255, 60287, 60255, 60223, 60223, 60255, 43839, 43871, +43775, 42717, 42684, 43807, 27519, 27358, 26235, 6512, 0, 0, 0, 0, 8727, 9883, 29695, 30687, 44927, 46047, 46079, 46079, 46079, 44991, 44959, 43871, 60223, 60255, 60319, 61439, 62463, 62463, 61407, 61439, +61407, 61375, 60287, 60255, 60287, 60255, 44991, 46079, 46079, 44991, 43903, 43807, 44959, 27423, 27519, 7735, 5461, 0, 0, 0, 8654, 28446, 47103, 48127, 47103, 47103, 47103, 47103, 62463, 63487, 63487, 63487, +62463, 62463, 62463, 63487, 63487, 63487, 63487, 63487, 63487, 63487, 62463, 61439, 61439, 62463, 62463, 63487, 62463, 46079, 44991, 43775, 44927, 43807, 27519, 10046, 8727, 0, 0, 16383, 8755, 26267, 29695, 46079, +46015, 44927, 43807, 43742, 43743, 43775, 43807, 43871, 43871, 60319, 61439, 62463, 62463, 62463, 61439, 62463, 62463, 61439, 61439, 61439, 61439, 61439, 45023, 44991, 43903, 44959, 43807, 43839, 44959, 28543, 26332, 26199, +9819, 8757, 15360, 0, 8720, 8692, 27358, 27391, 46047, 44927, 43775, 42652, 43775, 43903, 60191, 60255, 60287, 60319, 61375, 61407, 61407, 61407, 60383, 61439, 60383, 60319, 60351, 60255, 59167, 42751, 43871, 43871, +42751, 42719, 42652, 42719, 43807, 43871, 27487, 27423, 26270, 8759, 15360, 0, 8723, 26202, 27391, 27359, 44959, 46047, 44991, 44991, 46079, 63487, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 61407, 61407, +60383, 60351, 60351, 61375, 60319, 60287, 43935, 45023, 46079, 43935, 27455, 42751, 43839, 43903, 44927, 27423, 25178, 8725, 0, 0, 5461, 8829, 27487, 27359, 44927, 46079, 45023, 45023, 45055, 46079, 46079, 62463, +61439, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 61439, 62463, 61375, 61375, 62463, 45055, 45023, 43871, 27455, 27519, 28671, 29695, 30719, 29695, 9950, 9849, 0, 0, 10943, 9983, 29663, 28543, +44991, 44991, 44927, 44959, 45023, 44959, 45055, 44959, 43871, 43967, 61407, 60319, 60287, 60319, 60351, 60351, 60319, 60351, 60319, 61407, 61439, 43871, 43935, 44991, 45055, 46079, 45055, 44991, 27423, 26334, 29695, 28639, +9949, 5461, 0, 0, 9215, 11071, 28575, 27455, 29695, 28575, 43775, 46047, 46079, 45023, 44991, 43903, 59199, 60287, 60319, 60319, 60319, 60319, 61439, 61407, 61439, 61439, 61439, 60319, 61439, 60223, 42719, 42783, +43935, 46079, 47103, 49151, 48127, 28543, 8893, 11103, 9849, 0, 0, 0, 0, 9819, 9884, 26236, 27423, 29663, 29695, 47103, 45055, 43839, 43839, 43839, 60255, 60319, 60255, 60255, 60287, 60319, 61407, 61407, +60351, 60319, 43935, 43935, 61407, 60287, 42783, 41663, 42719, 43839, 43871, 45055, 44991, 30719, 9981, 4368, 0, 0, 0, 0, 0, 0, 8723, 9818, 27423, 29695, 31743, 47103, 46079, 45055, 43935, 43935, +45023, 45055, 61439, 61375, 60319, 61407, 61439, 61439, 45055, 43967, 42847, 43967, 45055, 46079, 45055, 43935, 42783, 43871, 26335, 25244, 26268, 30719, 25243, 6547, 0, 0, 0, 0, 0, 0, 0, 11007, +28607, 30719, 29695, 44959, 44991, 46079, 43935, 43903, 45055, 45055, 61439, 61375, 60351, 61439, 62463, 61407, 60351, 45055, 43903, 43903, 43967, 46079, 45055, 43903, 45023, 43903, 43935, 26431, 25182, 26335, 6581, 4368, +0, 0, 0, 0, 0, 0, 0, 8528, 26268, 11071, 26399, 28575, 29695, 45055, 43839, 45023, 46079, 45055, 62463, 61439, 45023, 45023, 61407, 60319, 60351, 45055, 43903, 43903, 43967, 46079, 45055, 44991, +44991, 44991, 28607, 26335, 7739, 7640, 4368, 31, 0, 0, 0, 0, 0, 0, 0, 0, 9814, 9619, 5120, 26332, 27551, 45023, 45055, 45023, 45055, 45055, 45055, 46079, 45055, 45055, 46079, 46079, +45055, 45023, 45055, 43935, 45023, 45055, 46079, 45055, 45055, 43903, 28671, 25245, 6582, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8826, 28543, 28607, 28639, 43967, 45023, +43903, 45023, 43967, 46079, 45055, 46079, 46079, 46079, 45023, 42815, 43935, 43935, 46079, 43967, 45023, 26431, 27487, 27487, 28639, 8859, 6547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 31, 6647, 26335, 26367, 27455, 25277, 27551, 26302, 42815, 42751, 43903, 43871, 43935, 43903, 45023, 42847, 42751, 42847, 42815, 46079, 45023, 26367, 26399, 26335, 28639, 25309, 6547, 8208, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6336, 8826, 9951, 9916, 7639, 26303, 25245, 26367, 41663, 43935, 43935, 43967, 42815, 42847, 42783, 41663, 43903, 42783, 43967, 27519, 25178, 8793, +8761, 11135, 8825, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8192, 10047, 5461, 7571, 8727, 25147, 25279, 25247, 28639, 42815, 42719, 25247, 25311, +26431, 24122, 26399, 26431, 27455, 42783, 24089, 6544, 8758, 11103, 8860, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 9650, +25178, 26431, 26367, 27583, 43967, 25246, 26367, 24125, 26399, 25246, 26399, 25311, 25212, 26367, 7736, 4368, 4352, 9948, 5813, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 8727, 24090, 26335, 8795, 25310, 28639, 25246, 25279, 24089, 27551, 24221, 27551, 8925, 7636, 25246, 6680, 0, 0, 31, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7636, 8792, 8725, 6446, 25277, 7638, 24055, 7702, 27519, 7769, 26431, 8895, 8456, 8731, 8720, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8727, 8720, 5461, 5393, +7772, 5589, 8892, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 8727, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 11799, 0, 0, 0, 0, 15391, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10933, 9747, 11963, 9747, 8720, 0, 0, 11005, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11799, 0, 8735, 9752, 11996, 10905, 29567, 13051, +9779, 11799, 12095, 14335, 12287, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8727, 10972, +10973, 11004, 8727, 10940, 13215, 27289, 28447, 27289, 9747, 10908, 12031, 30719, 12287, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 9814, 10938, 12159, 30719, 30719, 30719, 12127, 28479, 29599, 28381, 44863, 28414, 28414, 29567, 29631, 28447, 10840, 9612, 8720, 0, 0, 8720, 11799, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10943, 10910, 10941, 28479, 29663, 30719, 31743, 30719, 29695, 29663, 29567, 46015, 29599, 30719, 29663, 29631, 27323, 27291, 9749, 9914, 7399, +8725, 10839, 11964, 11799, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16352, 10874, 27324, 28479, 29567, 30719, 31743, 47103, 30719, 29663, 28511, 47103, 45983, +28543, 46015, 29599, 28415, 28447, 28479, 29599, 27290, 27356, 28381, 29599, 11039, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9748, 28413, 30719, 46047, +47103, 46047, 46015, 46047, 45983, 45983, 47103, 45951, 44895, 46047, 29599, 45983, 46015, 46047, 29567, 28543, 30719, 29567, 29567, 28383, 9783, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +15360, 0, 0, 8735, 9817, 29535, 28447, 46015, 46015, 46015, 45951, 46047, 46015, 46015, 46079, 44895, 45983, 46015, 44895, 46047, 47103, 47103, 44927, 46079, 30719, 29567, 29631, 28415, 9817, 8704, 0, 0, +0, 0, 0, 0, 0, 0, 8720, 9619, 11036, 8448, 8720, 8720, 26233, 29631, 28479, 45983, 44927, 44927, 45983, 46079, 46047, 62335, 46015, 44927, 44927, 44895, 44863, 46015, 46079, 44927, 44831, 45983, +28511, 46079, 28543, 27291, 27326, 9855, 0, 0, 0, 0, 0, 0, 0, 16383, 10774, 10940, 13247, 10972, 9815, 9849, 28414, 46047, 44863, 44799, 44895, 44863, 46079, 46079, 45983, 62367, 46047, 44927, +44927, 44863, 44863, 44927, 45983, 44831, 44895, 44895, 44895, 47103, 46079, 28447, 27423, 11999, 16383, 0, 0, 0, 0, 0, 0, 8720, 10938, 28479, 29631, 29599, 28543, 29599, 29631, 46047, 46047, 45983, +46015, 45983, 46047, 46079, 62431, 62399, 62399, 44927, 44895, 44927, 44959, 44927, 45983, 45983, 46047, 44991, 46047, 47103, 44959, 27423, 27359, 9752, 10873, 8654, 0, 0, 0, 0, 0, 0, 10804, 27324, +29599, 46047, 46079, 47103, 47103, 46047, 46047, 47103, 47103, 47103, 62431, 62463, 62431, 62463, 62399, 62399, 62431, 61343, 44959, 46015, 46047, 46015, 46015, 46047, 46079, 47103, 46047, 28479, 28447, 27291, 28511, 12029, +8720, 0, 0, 0, 0, 0, 9849, 27291, 29567, 44895, 44895, 46079, 47103, 46079, 46047, 46079, 46079, 46015, 61343, 61375, 61343, 61311, 61311, 61375, 62431, 61311, 61343, 62431, 44927, 44895, 44863, 44863, +44927, 46047, 46047, 27391, 27391, 27293, 12127, 10972, 0, 0, 0, 0, 0, 16383, 9819, 27293, 28479, 44927, 46015, 46047, 46079, 46079, 47103, 47103, 46079, 61375, 62431, 62463, 62463, 62463, 62463, 62463, +62463, 62463, 62463, 62463, 62463, 44991, 44991, 44991, 46015, 44927, 44863, 27292, 27324, 27258, 10907, 9816, 0, 0, 0, 0, 0, 9810, 9851, 28479, 45983, 44927, 44895, 44927, 44959, 46015, 46079, 46079, +46079, 44991, 61311, 44991, 61375, 61375, 61407, 62463, 62463, 62463, 62463, 61407, 44959, 43807, 43839, 44927, 46015, 43775, 27323, 26233, 27455, 28479, 27391, 27359, 9785, 15391, 0, 0, 10933, 10939, 28447, 29631, +28447, 44863, 43807, 43741, 43775, 43775, 43839, 44927, 62431, 61375, 61343, 61343, 61375, 62463, 61407, 61407, 61439, 61407, 61407, 61407, 44927, 43807, 43839, 46047, 46015, 43839, 28543, 27455, 44959, 27455, 28511, 28511, +10972, 8720, 0, 0, 0, 10935, 28479, 30719, 29631, 44895, 44927, 44863, 44863, 44863, 44895, 44927, 61375, 61375, 62463, 62463, 61375, 61439, 62463, 63487, 62463, 63487, 61407, 61343, 61311, 44927, 44991, 46079, +46079, 45023, 46047, 44927, 44991, 44927, 43775, 27326, 9815, 15360, 0, 0, 0, 10773, 10940, 29695, 29663, 29663, 46047, 46047, 44959, 44927, 44927, 44927, 62431, 61375, 61407, 62463, 62463, 62463, 63487, 63487, +62463, 62463, 62463, 62463, 62463, 62463, 62463, 46079, 44959, 44927, 44895, 44991, 46047, 44927, 26270, 8762, 16383, 0, 0, 0, 8720, 9681, 9815, 27356, 27324, 28543, 43775, 43807, 43743, 42653, 43710, 42685, +43775, 42719, 42719, 60191, 59135, 60191, 60159, 59135, 59135, 43807, 43775, 43775, 43775, 43807, 43807, 43807, 42684, 42651, 26234, 26301, 27391, 26267, 26201, 7566, 0, 0, 0, 0, 8725, 10873, 29631, 29663, +43807, 44927, 44927, 46047, 46047, 44895, 43839, 43807, 43743, 43775, 60223, 61343, 61343, 61375, 60255, 60287, 60255, 60255, 59135, 60159, 60191, 43807, 43871, 45023, 45023, 44959, 43807, 42687, 43871, 26269, 27423, 7702, +5461, 0, 0, 0, 9650, 11004, 47103, 47103, 46079, 46079, 46079, 46047, 44991, 46079, 62463, 62463, 61439, 61407, 61375, 62463, 62463, 62463, 62463, 62463, 62463, 62463, 61375, 60287, 61343, 61375, 45055, 46079, +45023, 44959, 43839, 42654, 43807, 26303, 27455, 10013, 8725, 0, 0, 16383, 8753, 9881, 29631, 46015, 44895, 43775, 43742, 42652, 42653, 42685, 42686, 42719, 43775, 43807, 43903, 61343, 61375, 61375, 60319, 60319, +60319, 60319, 60319, 60319, 44959, 44991, 43903, 43871, 42751, 43839, 42686, 43775, 43839, 27455, 26299, 9814, 9784, 8723, 0, 0, 10570, 8690, 26267, 27357, 28543, 27391, 26235, 42617, 43742, 43775, 42654, 42751, +42751, 42751, 60223, 60223, 60223, 59199, 59199, 60255, 59167, 59167, 59167, 59103, 41630, 42654, 42751, 42751, 42653, 42652, 41530, 42621, 42686, 27391, 27391, 27359, 26236, 9750, 15360, 0, 8727, 9816, 26268, 26235, +27423, 44895, 44895, 44895, 44959, 45023, 60287, 61343, 60319, 61343, 60319, 60319, 60319, 60223, 59199, 60255, 59199, 59167, 59167, 60255, 59167, 59167, 43839, 44959, 44991, 27487, 26335, 26302, 27391, 27423, 27423, 26302, +9783, 9619, 0, 0, 8735, 9852, 27423, 26268, 43807, 44927, 44895, 44927, 44959, 44991, 45023, 45055, 43903, 43903, 60287, 60287, 60287, 60255, 60255, 60287, 60287, 60319, 60319, 61375, 60287, 60287, 44991, 44927, +43903, 43807, 27423, 27487, 28575, 28671, 29695, 28607, 9949, 10933, 0, 0, 8735, 9852, 27455, 27423, 44927, 44895, 43839, 43839, 44927, 43839, 44927, 43839, 42751, 42815, 43871, 59167, 59135, 59135, 59167, 59199, +59167, 60255, 59167, 60255, 61343, 42751, 43807, 43871, 44959, 45023, 44959, 27519, 26334, 26300, 28607, 27455, 9851, 8720, 0, 0, 8735, 9887, 27391, 27391, 28607, 28511, 26302, 44959, 44991, 43871, 43839, 42751, +42719, 59167, 60191, 59167, 59167, 59135, 59199, 59199, 59231, 60287, 43903, 59167, 60287, 42751, 42685, 42719, 43871, 45023, 45023, 47103, 30719, 27455, 8859, 8893, 8727, 0, 0, 0, 0, 9814, 9849, 26202, +27325, 28511, 28575, 46079, 43871, 42719, 42751, 42719, 42751, 43839, 59135, 59135, 59167, 59167, 60255, 60255, 42815, 42783, 42783, 42783, 43871, 42783, 42687, 41563, 41564, 42751, 43807, 44959, 27487, 29695, 9981, 8464, +0, 0, 0, 0, 0, 0, 9650, 9816, 26303, 28607, 30719, 46079, 44991, 44959, 43871, 43839, 43903, 44991, 44959, 43903, 43903, 43935, 61407, 43967, 43935, 43871, 42751, 42815, 43935, 43967, 43935, 42783, +41630, 42751, 25212, 25177, 25210, 29695, 9916, 6553, 0, 0, 0, 0, 0, 0, 0, 10940, 27423, 29695, 28575, 43839, 43871, 44991, 43839, 42751, 44959, 43903, 61343, 60287, 60287, 43935, 61407, 60287, +43871, 43903, 42751, 42751, 42815, 45023, 43903, 42815, 43903, 43839, 26399, 25246, 25114, 9950, 7637, 5461, 0, 0, 0, 0, 0, 0, 0, 8720, 9817, 11006, 9949, 27487, 28671, 44991, 42751, 43935, +45055, 44991, 45023, 61375, 43903, 43903, 60287, 43839, 43903, 43935, 42783, 42751, 42815, 45023, 43935, 43871, 43871, 43871, 27519, 25179, 7673, 8727, 5461, 0, 0, 0, 0, 0, 0, 0, 0, 0, +9849, 10581, 5120, 26267, 27519, 27551, 43935, 43903, 43935, 43935, 45023, 45023, 45023, 43967, 45023, 45055, 44991, 43903, 43903, 42783, 43903, 43935, 45023, 44991, 27551, 43839, 28607, 25211, 6580, 31, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8758, 27423, 27487, 27551, 27487, 43903, 43839, 43903, 43871, 44991, 44991, 45023, 44991, 45055, 43903, 42719, 43839, 42815, 45023, 27519, 43935, 26367, +25245, 26302, 27519, 7801, 4368, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 6582, 26237, 26269, 26367, 25243, 27455, 25212, 42719, 42687, 42751, 42751, 43871, 42783, 43871, +42719, 41597, 42751, 42751, 44991, 27551, 26335, 25278, 8827, 27519, 8859, 6545, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4352, 8728, 9819, 8795, 8627, 26235, +25147, 26302, 25180, 26431, 42815, 43871, 42719, 42783, 41695, 40540, 42783, 25278, 42751, 26367, 25145, 8728, 8659, 9885, 7734, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8704, 8735, 5461, 7633, 8692, 25080, 25180, 25179, 27519, 42687, 25246, 25179, 25246, 25311, 24056, 25278, 25278, 26270, 25247, 24055, 8528, 8723, 8795, 9654, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 8720, 8759, 26367, 26303, 27487, 42815, 25180, 25214, 24091, 25311, 24124, 25278, 25212, 25145, 26302, 7703, 8464, +8704, 8727, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 8694, 25244, 8761, 26268, 27487, 25179, 25180, 24055, +25343, 24123, 26431, 8859, 7602, 8794, 7636, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +8592, 8727, 6547, 6474, 25211, 7636, 7637, 7669, 26431, 7703, 8959, 8827, 8704, 9625, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8725, 5461, 6547, 6446, 7737, 6544, 8827, 1023, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5461, 31, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 0, +0, 0, 0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8720, 8720, 9746, 10905, 10768, 8720, 0, 0, 10972, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 10933, 0, 16383, 10807, 10873, 10872, 29503, 11994, 10804, 11799, 12031, 13247, 11103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10581, 11996, 10908, 11996, 9654, 11962, 12095, 10872, 28349, 27255, 9747, 10907, 10941, 13183, 12031, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8727, 11867, 11007, 29663, 29599, 29695, 12095, 28447, 29503, 28347, 44766, 28347, +28380, 28511, 29567, 11997, 10838, 8720, 8720, 0, 0, 8720, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8735, 10906, 10906, 10941, 29567, +30719, 30719, 30719, 29631, 29567, 29535, 45919, 28479, 29663, 29599, 29567, 11929, 10839, 9748, 10905, 6441, 8720, 10805, 10906, 10581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 10872, 28282, 28383, 28447, 29663, 30719, 29695, 29695, 29599, 28447, 45951, 28479, 28447, 44895, 28447, 28350, 27326, 28414, 28511, 27289, 27355, 28346, 13183, 11996, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9748, 28347, 29567, 28511, 45983, 45951, 45951, 45983, 44895, 44895, 46015, 44831, 44831, 45951, 28511, 28511, 28511, 29599, 28447, 28447, +29663, 28479, 28479, 27291, 10806, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 0, 0, 16383, 9782, 28382, 28381, 45919, 44895, 44895, 44831, 45951, 44863, 44895, 44927, 44831, +44863, 44895, 44799, 44927, 46047, 46047, 44863, 29567, 29599, 28447, 28511, 28348, 9815, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 8720, 9979, 5440, 8720, 8720, 26200, 28511, 28414, 44863, +44799, 44831, 44863, 45951, 45951, 44863, 44895, 44831, 44831, 43775, 43743, 44863, 45983, 44831, 43709, 44831, 28414, 29599, 28479, 27256, 10908, 11807, 0, 0, 0, 0, 0, 0, 0, 16383, 10836, 10907, +12127, 9882, 8790, 10840, 27356, 29567, 44798, 44765, 44799, 44799, 45983, 45951, 44863, 44863, 44895, 44831, 44863, 44799, 44799, 44863, 44895, 44766, 44799, 28415, 28447, 46047, 29599, 28380, 27326, 10938, 16383, 0, +0, 0, 0, 0, 0, 8720, 10872, 27357, 28479, 28479, 28447, 28511, 29567, 45951, 45951, 44895, 44895, 44895, 44927, 45983, 44927, 44895, 44895, 44863, 44863, 44863, 44895, 44895, 44895, 44895, 44927, 44895, +44927, 46079, 28447, 27292, 27292, 9815, 10905, 9612, 0, 0, 0, 0, 0, 0, 9747, 10937, 28511, 44927, 45983, 46079, 46079, 45951, 45951, 46047, 46079, 46047, 44927, 61311, 61311, 61311, 61279, 61311, +61311, 44895, 44895, 44927, 45983, 44927, 44927, 44927, 45983, 46015, 29599, 27390, 27357, 27256, 10942, 11996, 16383, 0, 0, 0, 0, 0, 11792, 10872, 28446, 28447, 44799, 46015, 46047, 45983, 44895, 44895, +44895, 44831, 43775, 43775, 43775, 60159, 60159, 61247, 61279, 43807, 44863, 44927, 44831, 43775, 43743, 43742, 44831, 44927, 44927, 27325, 27324, 27258, 10942, 8760, 15360, 0, 0, 0, 0, 0, 8730, 26234, +28447, 44863, 44927, 45983, 45983, 46015, 46015, 46047, 44959, 44895, 61279, 44959, 44927, 61311, 61343, 61375, 61407, 44959, 46047, 62399, 44927, 44895, 44895, 44927, 44895, 44831, 43743, 27258, 27256, 10871, 10840, 9784, +0, 0, 0, 0, 0, 8720, 10838, 27357, 28479, 44831, 43743, 44831, 44863, 44895, 46015, 44959, 44959, 44863, 43807, 43839, 43807, 43839, 60223, 61311, 61343, 44927, 44959, 44927, 43839, 43743, 43775, 44863, +44895, 27357, 27256, 27224, 27326, 27358, 27291, 27326, 9753, 0, 0, 0, 16383, 10935, 28381, 29567, 27357, 43742, 43709, 43675, 43709, 43709, 43743, 43775, 44895, 43839, 60191, 43807, 43839, 61311, 60255, 60255, +60255, 61311, 44895, 43871, 43839, 43775, 43775, 46015, 28543, 27359, 27359, 27390, 27423, 27325, 27358, 27423, 9917, 15391, 0, 0, 0, 9847, 27358, 29631, 28447, 27359, 43743, 27357, 43742, 43743, 43775, 43775, +43839, 60223, 61311, 61343, 61279, 61311, 61375, 62431, 61343, 62463, 61311, 60255, 43871, 44895, 44927, 46079, 46079, 44927, 44895, 43807, 43775, 43775, 27325, 27291, 9847, 0, 0, 0, 0, 10933, 10874, 28543, +28479, 28479, 28479, 44927, 44863, 43775, 43839, 43839, 44927, 60255, 61311, 62463, 62463, 61407, 62463, 62463, 61343, 62431, 61375, 61375, 61375, 61407, 46079, 44991, 43839, 43775, 43775, 43807, 44863, 43711, 26203, 9818, +16383, 0, 0, 0, 8720, 9744, 10838, 27323, 27290, 28479, 27324, 27325, 43676, 42619, 42619, 42618, 43710, 42653, 42621, 42686, 42685, 43742, 42686, 42686, 42686, 43775, 43742, 42717, 43742, 43742, 43774, 43774, +26266, 26233, 26200, 26234, 26268, 26200, 9783, 6540, 0, 0, 0, 0, 8720, 9848, 28543, 29599, 27324, 44863, 44831, 44927, 44895, 43775, 43743, 43742, 43709, 43743, 43775, 43871, 60223, 60223, 60191, 60191, +43807, 43775, 42718, 42685, 42718, 42686, 43807, 44927, 44927, 44863, 42654, 26268, 27391, 26201, 26301, 7603, 8720, 0, 0, 0, 8720, 10972, 29631, 46015, 44895, 44927, 44927, 44927, 44895, 44927, 44959, 44959, +44927, 43871, 60255, 61343, 61375, 61375, 61375, 61375, 61375, 60287, 60255, 60223, 60223, 43839, 44959, 45023, 44927, 43839, 43743, 26235, 26269, 26235, 27423, 9949, 8720, 0, 0, 16383, 9747, 9879, 28511, 28511, +43775, 43742, 43708, 43642, 42651, 42619, 42651, 42685, 42686, 42687, 43775, 43807, 43807, 60191, 59135, 42751, 42751, 42751, 43807, 43839, 43839, 43807, 43807, 43775, 42653, 43743, 42618, 27325, 43775, 27391, 26265, 9749, +9782, 8720, 0, 0, 8720, 8688, 26233, 27324, 28511, 27324, 26200, 26199, 43740, 42653, 42587, 42685, 42686, 42686, 42719, 42719, 42687, 59071, 59071, 59071, 42654, 42686, 42686, 42653, 41563, 42587, 42685, 42718, +42586, 26201, 26167, 26202, 26268, 27325, 26236, 26236, 26202, 9749, 15360, 0, 8720, 9782, 27257, 26199, 27292, 27358, 43742, 43775, 44863, 44895, 43807, 43839, 43839, 43839, 43839, 60223, 60223, 59103, 59103, 59135, +59103, 59103, 42719, 42719, 42686, 42719, 43775, 43871, 44927, 27391, 26301, 26235, 26268, 27325, 27326, 26202, 8725, 8720, 0, 0, 8735, 9851, 27326, 26233, 27292, 43807, 43775, 43807, 43839, 43871, 44927, 44959, +43807, 43807, 43839, 59135, 60191, 59135, 59135, 59135, 59167, 60223, 60223, 43871, 43807, 43807, 43807, 43807, 43839, 26334, 27390, 27455, 27455, 28543, 28575, 27519, 9915, 8720, 0, 0, 8720, 9817, 26235, 27359, +27391, 43807, 43774, 43743, 43807, 43743, 43807, 42719, 42653, 42686, 42719, 42686, 42685, 42686, 42686, 42719, 42719, 42719, 42687, 42751, 43807, 42718, 42718, 43775, 43871, 43903, 43839, 27423, 26300, 26233, 27455, 27359, +9880, 8720, 0, 0, 8735, 9819, 9850, 27292, 27455, 27423, 26268, 44895, 44895, 43775, 43743, 42653, 42653, 42687, 42687, 59038, 42654, 59038, 59039, 42687, 42719, 42719, 42719, 42719, 43807, 42686, 42652, 42653, +42751, 43871, 43903, 46079, 29663, 27423, 8761, 8826, 10933, 0, 0, 0, 0, 8725, 9814, 26199, 26235, 27326, 27423, 44991, 43743, 42653, 42653, 42621, 42719, 43775, 42719, 42718, 42687, 42687, 59135, 42719, +42719, 42687, 42687, 42687, 42751, 42751, 41595, 41496, 41529, 26301, 27391, 27455, 27391, 29695, 9948, 5450, 0, 0, 0, 0, 0, 0, 9619, 9782, 26235, 27455, 28607, 44991, 43839, 43839, 43775, 43775, +43839, 43871, 43871, 43839, 43839, 43839, 43903, 43839, 42783, 42751, 41597, 41631, 42751, 43871, 43839, 42686, 41563, 42621, 25177, 25143, 25209, 28671, 9884, 8727, 0, 0, 0, 0, 0, 0, 0, 10808, +26302, 28511, 27455, 27359, 43775, 43839, 42687, 42687, 43839, 43807, 43839, 43807, 43807, 43839, 43903, 43839, 42783, 42783, 41598, 41565, 42687, 43903, 42783, 42719, 43807, 26334, 26270, 25112, 7639, 9917, 7669, 5461, +0, 0, 0, 0, 0, 0, 0, 10570, 9782, 9915, 9916, 27423, 28607, 43871, 26301, 43871, 44991, 43871, 43903, 43871, 43839, 43839, 43839, 42783, 43839, 43839, 42654, 41597, 42687, 43903, 43839, 42719, +26367, 27423, 27487, 25145, 7638, 6582, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8727, 8720, 6144, 9882, 27455, 27423, 26367, 42751, 43807, 43807, 43871, 43903, 43935, 43903, 43903, 43935, +43903, 42783, 43807, 42654, 42719, 43839, 43903, 43903, 27455, 26335, 27487, 25177, 7636, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8723, 9820, 26335, 27391, 26367, 43807, +42719, 43839, 43807, 43903, 43903, 43935, 43903, 44991, 43839, 41596, 42719, 42719, 43839, 26399, 27455, 25278, 25211, 26235, 26399, 7736, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 31, 7637, 26171, 26202, 25212, 25177, 26335, 25178, 42685, 41564, 42654, 42718, 43839, 42686, 42783, 41629, 41563, 42686, 41629, 42783, 27487, 25244, 25113, 8758, 9884, 8792, 5552, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5440, 8726, 9816, 8760, 7566, 9817, 8729, 25212, 25146, 26335, 26303, 42783, 41629, 42687, 41564, 24090, 41662, 25179, 26269, 26302, 8759, 7636, +8657, 9816, 6578, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8727, 8720, 10570, 8657, 8725, 25146, 25145, 27455, 25179, 25178, 25112, 25179, +24123, 24022, 25146, 25179, 25145, 25178, 7637, 8720, 9650, 8725, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10581, +8759, 26302, 25180, 26335, 42687, 25146, 25115, 24089, 25181, 24090, 24123, 24090, 25110, 25178, 7637, 5461, 8720, 9619, 15391, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 15391, 8691, 25146, 8726, 8827, 26335, 25146, 25081, 24021, 24157, 24089, 26399, 8826, 8657, 8759, 7633, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8726, 8720, 8584, 8760, 6579, 6547, 7635, 25278, 6581, 8829, 6747, 0, 10581, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8720, 8464, 6444, +8726, 5456, 6582, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 15391, 9746, 10872, 9619, 8720, 0, 0, 9852, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 0, 16383, 10837, 11894, 10870, 28446, 11960, 10801, 10933, 11999, +12095, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10581, 9851, 10873, 9979, 8725, 10904, +10941, 10838, 28314, 10870, 9779, 10873, 11930, 11999, 11999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, +9849, 10942, 13183, 28511, 13279, 12063, 11998, 28382, 28314, 28348, 28313, 27323, 28415, 12031, 10907, 9782, 8720, 15391, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 16383, 10839, 10903, 11931, 28479, 30719, 30719, 29695, 29567, 28447, 28447, 28479, 28415, 28543, 28479, 28447, 10905, 10837, 9779, 9847, 8522, 10933, 10805, 10872, 8720, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10805, 11895, 28348, 28381, 29599, 29599, 29631, 29631, 28479, 28413, 29503, 28447, 27325, 28415, 28382, 27324, 27291, 28348, 28414, +10871, 11962, 28312, 12031, 12027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9747, 28314, 28479, 28415, 28415, 28415, 44831, 28447, 27359, 44799, 44863, 28382, +43709, 44831, 28447, 28415, 28415, 28479, 28381, 28382, 29567, 28414, 28383, 10872, 9814, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 0, 0, 16383, 9779, 11930, 28346, 28382, 44766, +44767, 44766, 44831, 44767, 44767, 44799, 43710, 44767, 43743, 27325, 28415, 45951, 45951, 28415, 28511, 28511, 28381, 28415, 11930, 10838, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 10581, 9819, 8704, +0, 5450, 9814, 28383, 27324, 28415, 43709, 43709, 44767, 44799, 44799, 44766, 44766, 43741, 44766, 43677, 43676, 44767, 44831, 43741, 43675, 27325, 28380, 28479, 27358, 10871, 10874, 11799, 0, 0, 0, 0, +0, 0, 16383, 10804, 10872, 10975, 9848, 8788, 9815, 27322, 28447, 28347, 28347, 44732, 44732, 44831, 44799, 44766, 44799, 44799, 44766, 44799, 43708, 43708, 44766, 44799, 44731, 27324, 27291, 27357, 28543, 28511, +27323, 10875, 9849, 16383, 0, 0, 0, 0, 0, 16383, 10838, 27257, 27326, 28415, 28415, 28447, 28479, 44831, 44831, 44799, 44831, 44831, 44799, 44863, 44863, 44831, 44799, 44799, 44799, 44831, 44831, 44831, +44831, 44831, 44863, 28447, 28447, 29599, 27357, 27224, 27257, 10805, 10805, 8720, 0, 0, 0, 0, 0, 9843, 10902, 28382, 28447, 28479, 46015, 45951, 44799, 44799, 44863, 44895, 44895, 44831, 44831, 44831, +44863, 44831, 44863, 44863, 44863, 44863, 44863, 44863, 44863, 44831, 44831, 44863, 44895, 28511, 27291, 27258, 10870, 10907, 10936, 16383, 0, 0, 0, 0, 16352, 10838, 28347, 28380, 28381, 44895, 44895, 44831, +43742, 43743, 43710, 43709, 43677, 43709, 43677, 43709, 43677, 43775, 43775, 43709, 43775, 44863, 43742, 43708, 43675, 43675, 43708, 44863, 44863, 27291, 27258, 27223, 10874, 9814, 15360, 0, 0, 0, 0, 8727, +26233, 28413, 28414, 44863, 44895, 44895, 44895, 44863, 44895, 44863, 43775, 43775, 43807, 43775, 43807, 43807, 44895, 44895, 44863, 44895, 44895, 44831, 43775, 43775, 44831, 43775, 43709, 27291, 27256, 27222, 10837, 10836, +10808, 0, 0, 0, 0, 0, 9750, 27290, 28413, 43709, 43708, 44766, 43775, 44799, 44831, 43807, 44863, 43775, 43742, 43743, 43742, 43710, 43710, 43775, 43807, 43807, 43807, 43775, 43775, 43708, 43709, 43742, +43743, 27258, 26199, 27222, 27258, 27258, 27256, 27293, 9751, 0, 0, 0, 10933, 10938, 28447, 27290, 27291, 27257, 27257, 43642, 43674, 43675, 43709, 43775, 43743, 43775, 43775, 43775, 43807, 43743, 43710, 43775, +43775, 43775, 43775, 43775, 43741, 43743, 28543, 28511, 27292, 27291, 27324, 27325, 27259, 27258, 27358, 9949, 15360, 0, 0, 10943, 10973, 28511, 27358, 27291, 27258, 27290, 27291, 43676, 43710, 43710, 43743, 43743, +44863, 44863, 43807, 43839, 44895, 44863, 43839, 44927, 43807, 43775, 43807, 43807, 43839, 44927, 28575, 27391, 43775, 43709, 43709, 27325, 27291, 27257, 10773, 0, 0, 0, 8720, 10871, 28447, 27358, 27358, 27327, +28447, 43775, 43709, 43710, 43743, 43807, 43743, 43807, 61311, 44927, 61311, 61343, 61279, 43839, 44895, 44895, 61279, 44895, 44895, 44927, 43839, 43775, 42653, 43677, 43710, 43710, 27259, 26200, 9814, 16383, 0, 0, +0, 9836, 9782, 10938, 27256, 28381, 27257, 27290, 27257, 27256, 27224, 26200, 43675, 42619, 42618, 42651, 42651, 42651, 42651, 42619, 42651, 43709, 43708, 42651, 43708, 43708, 43741, 27357, 26233, 26232, 26166, 26200, +26234, 26166, 8725, 6573, 0, 0, 0, 10933, 10807, 27391, 28543, 27290, 28414, 43742, 44831, 43775, 43709, 43676, 43675, 42619, 43709, 42653, 43743, 43743, 43743, 42686, 42686, 42719, 42686, 42620, 42619, 42620, +42620, 43743, 43839, 43775, 27391, 26235, 26202, 26268, 26199, 26202, 7602, 8720, 0, 0, 10933, 11995, 28511, 28511, 44799, 44831, 44831, 44831, 43807, 43807, 43807, 43807, 43775, 43775, 43775, 43775, 43775, 60159, +43775, 43807, 43775, 43775, 43743, 43743, 43743, 43775, 43839, 44927, 43807, 43775, 42619, 26201, 26234, 26200, 27357, 8857, 10581, 0, 0, 10933, 10840, 28415, 27358, 27324, 27292, 27290, 27257, 27258, 27257, 42617, +42618, 42619, 42619, 42685, 42685, 42686, 42686, 42653, 42653, 42653, 42652, 42685, 43775, 42718, 43742, 42685, 43741, 42618, 42619, 26199, 27258, 27324, 27357, 9848, 9782, 9749, 9619, 0, 15360, 8720, 9815, 27289, +28447, 27290, 27222, 27255, 27291, 42617, 42584, 42586, 42618, 42619, 42652, 42652, 42652, 42619, 42619, 42619, 42586, 42619, 42619, 42586, 42584, 42584, 42618, 26299, 26200, 26166, 26165, 26200, 26201, 26266, 26201, 26201, +9784, 9747, 15360, 8720, 9748, 9814, 10804, 27290, 27259, 27258, 43676, 43743, 43743, 43709, 43710, 43710, 43743, 43775, 43807, 43807, 42718, 42685, 42718, 42685, 42685, 42685, 42686, 42684, 42685, 42718, 43807, 27455, +26269, 26235, 26234, 26233, 26233, 26234, 26168, 9749, 10581, 0, 8735, 9817, 9819, 27224, 27257, 27325, 43708, 43710, 43743, 43775, 43775, 44895, 43743, 43743, 43775, 42718, 42718, 42686, 42686, 42686, 42686, 43743, +43775, 43775, 42685, 43742, 42653, 43742, 43775, 26268, 26268, 27391, 27326, 27391, 27455, 27359, 9880, 8720, 0, 8720, 9783, 10873, 27325, 27324, 27390, 27356, 43676, 43709, 42620, 43677, 42652, 42619, 42652, 42717, +42619, 42652, 42652, 42652, 42653, 42653, 42685, 42619, 42653, 42686, 42651, 42619, 42653, 42719, 43775, 42686, 26301, 26266, 9816, 27359, 26236, 9783, 15391, 0, 8735, 9785, 9814, 27290, 27325, 27357, 26266, 28479, +27391, 43709, 43677, 42586, 42587, 42685, 42652, 42618, 42619, 42651, 42619, 42652, 42653, 42653, 42653, 42685, 43743, 42684, 42618, 42619, 42653, 43775, 43775, 44959, 28607, 27390, 8726, 8758, 10581, 0, 0, 0, +9843, 10836, 9813, 27255, 27291, 27292, 27455, 26268, 26234, 42619, 42586, 42685, 43742, 43741, 43709, 42684, 42685, 42653, 42653, 42653, 42652, 42652, 42652, 42653, 42685, 42585, 26134, 25110, 26235, 27357, 27391, 27325, +28575, 9914, 8720, 0, 0, 0, 0, 0, 10581, 10836, 9816, 27292, 27391, 27423, 27327, 43743, 43710, 42686, 43743, 43743, 43775, 43775, 43775, 42751, 43807, 43775, 42719, 42685, 42587, 41563, 42620, 42719, +42687, 42586, 26168, 25146, 26167, 25142, 9848, 28607, 9914, 8727, 0, 0, 0, 0, 0, 0, 9814, 27259, 27327, 27359, 26268, 26301, 43743, 42620, 42620, 43743, 43743, 43743, 42687, 42719, 43775, 43807, +43775, 42719, 42686, 41530, 41530, 42619, 43775, 42718, 42620, 26334, 26203, 26202, 25109, 7637, 9883, 8723, 8720, 0, 0, 0, 0, 0, 0, 8720, 9781, 9849, 9883, 27391, 28511, 27359, 26202, 27359, +43871, 43807, 43775, 43775, 43775, 43775, 43775, 43775, 43775, 43775, 42587, 41530, 42620, 43807, 42719, 26236, 26268, 26301, 27455, 25174, 7637, 6547, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 10933, +8720, 4096, 9849, 27423, 26302, 26237, 42653, 42686, 42686, 42719, 43807, 43839, 43807, 43807, 43839, 43807, 42687, 42686, 42620, 41597, 42719, 43807, 27423, 26303, 26204, 26302, 8759, 7633, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 8725, 9816, 26236, 26301, 26302, 42687, 42653, 43775, 42719, 43807, 43839, 43807, 43839, 43839, 42686, 41561, 42620, 26204, 26303, 26269, 26303, 25179, 26201, 26168, +25147, 6579, 5450, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 9749, 9817, 26167, 25144, 25144, 26269, 25144, 26235, 42554, 42619, 26268, 42719, 42652, 42686, 42586, 25145, 41563, +25146, 42622, 26303, 25178, 8693, 8757, 9785, 8662, 6448, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5450, 8758, 9813, 8758, 8592, 8759, 8726, 25144, 25112, 26236, +26236, 42687, 25179, 41596, 25113, 25112, 25179, 25112, 25145, 25179, 8725, 8723, 9843, 10806, 9619, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, +15391, 15360, 9619, 9748, 25144, 26167, 26302, 25145, 25144, 25078, 25112, 24057, 24021, 25079, 25080, 25110, 25111, 7635, 5461, 8720, 9747, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 9749, 26235, 26170, 26204, 25180, 25112, 25112, 25079, 24057, 24056, 24089, 24056, 25077, 25110, 7635, 5450, 0, 10581, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 8657, 8728, 8726, 8728, 25180, 25111, 25110, 24019, 24089, 24055, 26334, 8761, 8626, 8725, 6578, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 8723, 8720, 8712, 8726, 7633, 7601, 7570, +8729, 6547, 7737, 8725, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 10581, 0, 5450, 6445, 8628, 6540, 8533, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 15360, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 15391, 8720, 10806, 8720, 0, 0, 0, 9851, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 0, 16383, 9843, 11862, 10869, 11964, +11895, 9810, 10933, 10938, 10943, 10943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9819, 10872, +11995, 9619, 10871, 11929, 10868, 27256, 10804, 9746, 10871, 10871, 10942, 10938, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 10933, 11930, 12031, 28447, 13215, 12031, 11965, 28348, 11929, 28314, 28280, 28314, 27293, 11965, 10906, 10805, 15360, 15360, 0, 0, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 10933, 10837, 10872, 28382, 29663, 29663, 29631, 28511, 28382, 28382, 28447, 28381, 28479, 28350, 28350, 11896, 9779, 9747, 9782, 6540, 8720, 9843, 11894, 15360, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11799, 11894, 28346, 28347, 29567, 29535, 29567, 28543, 28415, 28348, 28447, 28414, 27290, 27292, 27292, 27291, 27289, 28347, 28315, +10838, 10905, 10870, 11965, 12921, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9843, 11960, 28414, 28348, 28348, 28348, 44764, 27325, 27292, 28347, 28381, 28347, 27290, +27292, 27291, 27291, 28348, 28447, 28315, 28348, 28479, 28347, 28348, 11927, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9810, 11928, 28312, 28346, 28314, 27291, 44731, +44733, 43676, 43676, 27291, 27291, 43675, 43675, 27291, 28349, 28447, 28479, 28382, 28447, 28415, 28347, 28348, 11928, 9846, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10773, 0, 0, 8720, 9782, +27291, 27290, 28380, 28314, 27290, 44699, 43675, 44700, 44699, 43675, 43674, 43675, 43674, 27290, 43708, 44733, 28347, 27257, 27290, 27290, 28415, 27324, 10838, 10873, 10933, 0, 0, 0, 0, 0, 0, 9810, +10869, 10908, 9783, 9748, 9781, 10905, 28349, 28313, 28313, 28313, 44698, 44700, 43676, 44699, 44732, 44732, 44699, 44765, 27290, 27257, 43674, 43707, 28313, 27257, 27256, 27290, 28415, 28382, 10872, 10873, 8727, 0, +0, 0, 0, 0, 16383, 10837, 10870, 27291, 28348, 28348, 28381, 28382, 28381, 44732, 44732, 44732, 44765, 44733, 44766, 44766, 43709, 43709, 44764, 44766, 28381, 27356, 43708, 44765, 44765, 28414, 27324, 27324, +27358, 27291, 27222, 10839, 10773, 9843, 8720, 0, 0, 0, 0, 8720, 11926, 11964, 28382, 28382, 44895, 44799, 44732, 44699, 44733, 44767, 44799, 44766, 43741, 44766, 44799, 44798, 44831, 44831, 44831, 44831, +44766, 44766, 44766, 27325, 27293, 28381, 28447, 28447, 27288, 10872, 10836, 11896, 10933, 16383, 0, 0, 0, 0, 10900, 11929, 28315, 27290, 44798, 44831, 44764, 43674, 43674, 43674, 43673, 43673, 43673, 43673, +43675, 43674, 43708, 43708, 43675, 43708, 43708, 43674, 27257, 27256, 27256, 27290, 28414, 27357, 27256, 27255, 10837, 10838, 9843, 0, 0, 0, 0, 10933, 10872, 28346, 28347, 28446, 44831, 44799, 44799, 44831, +44831, 44831, 44798, 44766, 43775, 43742, 43742, 43775, 44831, 44799, 44831, 44831, 44799, 43742, 43741, 43741, 43709, 43708, 27291, 27257, 27222, 10869, 10805, 11891, 9814, 0, 0, 0, 0, 9747, 10871, 27290, +27289, 28314, 27291, 43676, 43676, 43710, 43710, 44798, 43708, 43675, 43676, 43676, 43643, 43675, 43709, 43677, 43709, 43709, 43676, 43675, 43641, 43642, 27291, 27324, 27256, 10837, 10836, 27256, 10873, 10871, 27259, 10778, +0, 0, 10933, 11928, 27291, 27255, 27256, 27255, 27256, 27256, 27256, 27257, 43642, 43708, 43708, 43741, 43741, 43741, 43741, 43708, 43674, 43708, 43741, 43741, 43741, 43741, 43708, 43677, 28511, 28415, 27291, 27257, +27257, 27258, 27224, 27255, 27259, 9849, 0, 0, 10933, 10906, 27358, 27290, 27256, 27255, 27255, 27257, 27258, 27291, 27291, 43676, 43676, 43743, 43775, 43743, 43710, 43743, 43775, 43775, 44863, 43742, 43709, 43742, +43742, 43743, 44831, 28447, 27325, 27260, 27258, 27257, 27258, 27256, 10871, 8727, 0, 0, 0, 9848, 27293, 27258, 27291, 27292, 27325, 27324, 27259, 43643, 43675, 43709, 43677, 43710, 43775, 43807, 43775, 44831, +43743, 43743, 43775, 43743, 43743, 43743, 43711, 43775, 43775, 27324, 27258, 27258, 27258, 27258, 27256, 26198, 9750, 0, 0, 0, 0, 9810, 10937, 10805, 27291, 27255, 27256, 27255, 27223, 27222, 27223, 27257, +27256, 27256, 27258, 43641, 43642, 43641, 42585, 26234, 27291, 27291, 27258, 27291, 27258, 27291, 27291, 27256, 27223, 26166, 27222, 27223, 10837, 9748, 8720, 0, 0, 16383, 9747, 10973, 28511, 27288, 27324, 27323, +27324, 27291, 27258, 27258, 27258, 27258, 27259, 42619, 42620, 42620, 43644, 42651, 42620, 42620, 42619, 42586, 42585, 42586, 42586, 42652, 43711, 43710, 27325, 27257, 26200, 26202, 9781, 9815, 7633, 0, 0, 16383, +11003, 28479, 28479, 28381, 44766, 44798, 43741, 43709, 43741, 43709, 43742, 43709, 43709, 43677, 43677, 42653, 42653, 42653, 42653, 42686, 42653, 42620, 43676, 42653, 43709, 43710, 43743, 43709, 43708, 27257, 27223, 26200, +26199, 26202, 8760, 8720, 0, 16383, 10804, 27292, 27324, 27290, 27290, 27257, 27288, 27257, 27256, 27256, 27256, 27225, 26233, 26202, 26201, 42586, 42619, 42618, 42586, 42586, 42586, 42587, 42621, 42619, 26235, 26202, +26234, 26200, 26200, 27222, 27224, 27257, 27258, 9782, 10804, 9779, 8720, 0, 8720, 10805, 27256, 27324, 27256, 10837, 27254, 27257, 27223, 27222, 27255, 27224, 27257, 27258, 26234, 42585, 42585, 42585, 42584, 26200, +26200, 42584, 42584, 26199, 26199, 26200, 26233, 27223, 27188, 27221, 27223, 27224, 27224, 26199, 10840, 10838, 10773, 16383, 9781, 10838, 10836, 27256, 27257, 27256, 27258, 27259, 43643, 43642, 43675, 43643, 43708, 43742, +43742, 43775, 43709, 43708, 43708, 43675, 43675, 43675, 43708, 43707, 43708, 43709, 27391, 27423, 26267, 26200, 27256, 27223, 27224, 27257, 27222, 10772, 8720, 8735, 9816, 9816, 10838, 27255, 27258, 27257, 27258, 27259, +43676, 43677, 43743, 43676, 43676, 43742, 42652, 42652, 42652, 43676, 42620, 42619, 42652, 43709, 43709, 42619, 43708, 42618, 27291, 26269, 26234, 26202, 27292, 26235, 27325, 27423, 27259, 9847, 16383, 16383, 9749, 9815, +27291, 27290, 27258, 27258, 27258, 26234, 27225, 42617, 43642, 26201, 26267, 43708, 42617, 42585, 42618, 42619, 42587, 42586, 42619, 42585, 42586, 42619, 26233, 26200, 42586, 26202, 26268, 26235, 26235, 10904, 9749, 27292, +26200, 9749, 15391, 16383, 9783, 9812, 10838, 27257, 27258, 27256, 28447, 27325, 27259, 27258, 26200, 26233, 42651, 43707, 42584, 42585, 42618, 42617, 42618, 42652, 42619, 42619, 42652, 43709, 42618, 26201, 26201, 26202, +42685, 43742, 28511, 28543, 10973, 9748, 8757, 8720, 0, 0, 8727, 10837, 10836, 10837, 10873, 27258, 27326, 27257, 27257, 26201, 27256, 43708, 43742, 43741, 43741, 43741, 43708, 43708, 42651, 42684, 42683, 26267, +26268, 42651, 42619, 26199, 26165, 26165, 26201, 27324, 26268, 27291, 28479, 9881, 8720, 0, 0, 0, 0, 8720, 9810, 10837, 27289, 27325, 27293, 27259, 27293, 43676, 42619, 42652, 42654, 43677, 43710, 43710, +42653, 43742, 42653, 42620, 42651, 26234, 26201, 42618, 42652, 43709, 26200, 26166, 26168, 26166, 9748, 9847, 28543, 9882, 10933, 0, 0, 0, 0, 0, 11799, 10840, 27291, 27260, 26201, 27258, 43676, 42586, +42586, 43709, 43709, 43709, 42652, 43709, 43709, 43742, 43709, 43677, 42651, 42584, 26167, 42585, 43709, 42652, 26201, 26202, 26168, 26200, 26132, 8723, 9915, 8659, 8720, 0, 0, 0, 0, 0, 8720, 9812, +9781, 9849, 27357, 27391, 27291, 26200, 26268, 43743, 43743, 43742, 43742, 43774, 43741, 43741, 43742, 43742, 43709, 42585, 26168, 26201, 43709, 42620, 26201, 26201, 26202, 27423, 25142, 8659, 8720, 8720, 0, 0, +0, 0, 0, 0, 0, 8720, 16383, 5440, 9848, 27325, 26202, 26203, 26235, 26268, 27292, 42652, 43742, 43775, 43743, 43742, 43775, 42686, 42619, 42587, 26202, 26203, 26269, 27359, 26270, 26268, 26201, 26267, +8757, 6578, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9813, 26201, 26202, 26268, 26268, 26235, 27357, 27325, 43775, 27423, 43775, 43807, 43807, 42619, 26168, 26202, 26202, 26269, +26202, 26203, 26168, 9783, 9815, 26168, 8659, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 9747, 9814, 9782, 9749, 8758, 26170, 26134, 26200, 26168, 26201, 26202, 42620, 26202, +42651, 26201, 26168, 26201, 26168, 26203, 26236, 26168, 8692, 9747, 9782, 8692, 6544, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9747, 10804, 9747, 9612, 9750, 8724, +26166, 26166, 26169, 26202, 26204, 25145, 25145, 25111, 25111, 26168, 25109, 26167, 26169, 8726, 8720, 8720, 9843, 10581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 16383, 0, 0, 8720, 10801, 9749, 9782, 26202, 26167, 26167, 25109, 25077, 25078, 25044, 25077, 25077, 26132, 26133, 8658, 8720, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 9782, 26168, 26167, 26169, 26168, 25142, 26135, 25077, 25078, 25079, 25110, 25110, 9748, 26132, 8624, 8720, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9782, 9748, 8758, 26169, 8725, 9749, 8659, 8694, 7637, 25146, 8727, 9650, 8753, 5461, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 8720, 15391, 10570, 8724, 7569, 7568, 6542, 8660, +6544, 8693, 8727, 0, 15391, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 8720, 0, 8720, 8456, 6578, 5450, 8720, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 15391, 8720, 10836, 8720, 0, 0, 0, 9849, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 0, 16383, 9843, 9843, 9846, 11929, 10933, 10933, 8720, 9849, +10910, 9849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9843, 10838, 9849, 8720, 9847, 11895, +10803, 27254, 10835, 9746, 10806, 9846, 10907, 9849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10806, +10907, 28382, 12095, 11998, 11932, 11962, 11928, 28313, 28279, 11928, 11931, 11964, 11895, 9843, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 10933, 9843, 10836, 11930, 29599, 29631, 29567, 12031, 28348, 28381, 28413, 28348, 28382, 28347, 11964, 11928, 11891, 9810, 9814, 10570, 8720, 10933, 10933, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 11860, 11928, 28312, 29503, 28479, 28447, 28415, 27292, 28314, 28380, 28347, 27288, 27289, 27257, 27256, 10904, 11929, 11928, 10836, 10872, 10869, 11963, +10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 11894, 28346, 28312, 28346, 28313, 28314, 27289, 27257, 27289, 28313, 28313, 27256, 27288, 28280, 27257, 28313, +28380, 28312, 28346, 28383, 28313, 11930, 11894, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10903, 11927, 28311, 28312, 28313, 28312, 28314, 28314, 28313, 28313, +28313, 28313, 28313, 28312, 28315, 28380, 28382, 28348, 28381, 28349, 28313, 28313, 11927, 10773, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 11799, 0, 0, 8720, 9780, 27290, 27256, 28313, 28312, +28311, 28312, 28312, 44697, 44697, 28312, 28311, 28313, 28311, 28311, 28313, 28314, 28312, 27255, 28311, 28312, 28315, 27290, 10869, 10869, 8720, 0, 0, 0, 0, 0, 0, 9843, 10837, 11895, 9782, 9746, +10804, 10871, 28314, 28311, 28311, 28311, 28311, 28313, 28312, 44696, 44697, 28313, 28313, 28380, 28311, 27254, 28279, 28312, 28279, 28278, 27254, 27255, 28316, 27291, 10870, 11894, 10933, 0, 0, 0, 0, 0, +16383, 10805, 11893, 11928, 28313, 28313, 28346, 28314, 28313, 28313, 28312, 28313, 44730, 44698, 44698, 44698, 44698, 28314, 28315, 28380, 28347, 27290, 27289, 28314, 28314, 28314, 27289, 27256, 27290, 27257, 10836, 10870, +11792, 10933, 16383, 0, 0, 0, 0, 16383, 11895, 11930, 28315, 28315, 28381, 28347, 28313, 28312, 28313, 28314, 44699, 43674, 43674, 43675, 44765, 44797, 44799, 44799, 28414, 28413, 28380, 28380, 27290, 27289, +27257, 27257, 28347, 28380, 27254, 10838, 11894, 11894, 11799, 0, 0, 0, 0, 0, 11862, 11927, 28312, 28280, 28346, 28347, 28313, 28312, 28312, 28312, 27288, 27255, 27256, 27255, 27289, 27288, 43674, 43674, +27289, 27290, 28314, 27288, 27255, 27255, 27254, 27255, 28348, 27291, 27255, 11894, 10836, 11891, 10933, 0, 0, 0, 0, 8720, 10870, 28312, 28313, 28348, 28380, 28380, 28380, 28381, 28381, 28380, 28380, 28380, +27324, 27324, 27324, 43708, 43709, 43709, 43709, 43709, 43708, 27323, 27323, 27323, 27291, 27290, 27256, 27254, 10869, 11892, 10804, 10773, 10773, 0, 0, 0, 0, 9843, 10869, 28311, 28311, 28312, 28314, 27290, +28314, 28314, 27291, 28380, 27290, 27289, 27290, 27289, 27256, 27256, 27257, 27256, 27256, 27257, 27256, 27257, 27255, 27256, 27290, 27290, 27255, 10836, 10869, 10870, 10871, 10869, 10873, 9849, 0, 0, 8720, 11926, +11928, 11895, 28311, 27255, 28279, 28279, 27255, 27256, 27256, 27290, 27290, 27323, 28380, 27324, 27324, 27290, 27289, 27290, 27291, 27323, 27324, 27324, 27290, 27291, 28447, 27325, 27289, 27255, 10839, 27255, 10870, 10869, +27224, 9847, 0, 0, 8735, 11929, 28315, 28312, 27254, 27254, 10870, 27255, 27288, 27290, 27290, 27290, 27290, 43675, 43676, 43675, 43675, 43675, 43675, 43675, 43709, 43675, 43675, 43675, 27291, 27324, 27358, 27325, +27258, 27257, 27255, 27255, 27254, 27255, 10870, 10933, 0, 0, 0, 9846, 10907, 10904, 27256, 27289, 28314, 27290, 27256, 27256, 27257, 43675, 43675, 43675, 43709, 43742, 43709, 43709, 43708, 43676, 43677, 43676, +43676, 43676, 43676, 43709, 27325, 27258, 27256, 27255, 27255, 27223, 27254, 10805, 9814, 0, 0, 0, 0, 8720, 10938, 10836, 11929, 11895, 11895, 10870, 10838, 10837, 27222, 27255, 27255, 27223, 27256, 27256, +27256, 27223, 27223, 27256, 27257, 27225, 27224, 27257, 27223, 27225, 27257, 10870, 10838, 10836, 10837, 10837, 10837, 10804, 8720, 0, 0, 0, 8720, 10907, 11997, 27222, 27289, 27289, 27288, 27256, 27256, 27256, +27257, 27256, 27257, 27256, 27257, 27257, 27257, 27257, 27257, 27258, 27224, 27224, 27223, 27224, 27224, 27258, 27292, 27259, 27259, 27256, 27255, 27223, 10836, 9781, 8720, 0, 0, 16383, 11929, 28414, 28413, 28346, +27291, 27323, 27290, 27289, 27290, 27258, 27258, 43642, 43642, 43641, 43641, 43641, 43641, 43641, 43642, 43642, 43609, 43641, 43641, 43641, 27257, 27259, 43643, 43642, 27258, 27256, 27222, 27222, 27221, 10839, 9814, 16383, +0, 0, 9810, 27257, 27289, 28280, 27256, 27256, 27255, 27255, 27255, 27255, 27223, 27223, 27223, 27223, 27223, 27223, 27225, 27224, 26199, 27223, 27223, 26199, 26201, 27224, 27224, 27223, 27223, 26198, 27222, 27222, +27255, 27256, 10840, 9781, 9747, 11858, 8720, 0, 8720, 10836, 10870, 27289, 11895, 10869, 10870, 27255, 27254, 27253, 27254, 27255, 27255, 27224, 27224, 27223, 27223, 27223, 27222, 27221, 27222, 27222, 27222, 27222, +27222, 27223, 27256, 27255, 10803, 10836, 27254, 27222, 27222, 10837, 10871, 10836, 8720, 15391, 9746, 10869, 11892, 27255, 27255, 27255, 27256, 27255, 27256, 27256, 27257, 27257, 27258, 27292, 27324, 43709, 27324, 27291, +43675, 27323, 27291, 27290, 27291, 27258, 27291, 27291, 27324, 28447, 27258, 10871, 10837, 27222, 27254, 27255, 27222, 10837, 16383, 16383, 9782, 9782, 11894, 11926, 27254, 27255, 27255, 27255, 27256, 27257, 27259, 27256, +27257, 27291, 27258, 27258, 27258, 27258, 27257, 27225, 27257, 27258, 27257, 27256, 27258, 27223, 27256, 27258, 27223, 27256, 10873, 10840, 27290, 28414, 27289, 9846, 16383, 16383, 10836, 10838, 11930, 28312, 27256, 27256, +27257, 27223, 27222, 27223, 27223, 27256, 27225, 27291, 27224, 27223, 27224, 27224, 27224, 26200, 26201, 26199, 27223, 27224, 27223, 27222, 27223, 27224, 27258, 27256, 27257, 10904, 10804, 10906, 10870, 9810, 15391, 16383, +10805, 10834, 10838, 10839, 27223, 27255, 28414, 27291, 27256, 27257, 27223, 27256, 26234, 27290, 27224, 27224, 27257, 27225, 27257, 27291, 26234, 26201, 27323, 27291, 26201, 26199, 27223, 27224, 27290, 27291, 28447, 28511, +10971, 10805, 11799, 16383, 0, 0, 8720, 9843, 10867, 10869, 10870, 10871, 27291, 27256, 27255, 27256, 27256, 27357, 27357, 27357, 27325, 27357, 27357, 27323, 27291, 27324, 27324, 27292, 27324, 27291, 27290, 27223, +27220, 27220, 27256, 26201, 27258, 27290, 27390, 10840, 15360, 0, 0, 0, 0, 16383, 8720, 10805, 10870, 27258, 27258, 27257, 27258, 27257, 27256, 27258, 27291, 27291, 27291, 27291, 27258, 27291, 27258, 27258, +27290, 26201, 26200, 26201, 27259, 27259, 27223, 27222, 27222, 9781, 9748, 9848, 27455, 9849, 10933, 0, 0, 0, 0, 0, 10933, 10871, 10872, 27257, 27224, 27256, 27257, 27224, 27224, 27258, 27290, 43643, +43642, 27291, 27291, 27324, 27259, 27258, 27257, 27222, 27222, 27223, 27225, 26201, 27224, 27224, 27222, 27223, 9748, 9714, 10904, 8723, 15391, 0, 0, 0, 0, 0, 16383, 10837, 10804, 9816, 27291, 27357, +27256, 27222, 27226, 27292, 27324, 43708, 43708, 27356, 43740, 43740, 27324, 43708, 27258, 27223, 27222, 27223, 27258, 27225, 26198, 27222, 27255, 27324, 9782, 8659, 10581, 0, 0, 0, 0, 0, 0, 0, +0, 8720, 16383, 8704, 9815, 27259, 27223, 27224, 27224, 27258, 27258, 27258, 27291, 27292, 27292, 27292, 27324, 27259, 27224, 27225, 27224, 26200, 27258, 27291, 27259, 27258, 27223, 27257, 9781, 9619, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10804, 9816, 27222, 26202, 26234, 27225, 27291, 27291, 27389, 27390, 27357, 27390, 27390, 27256, 26198, 26200, 27224, 26202, 26200, 26201, 26166, 9814, +10805, 9782, 8657, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 8720, 9779, 10836, 9780, 8693, 26167, 26164, 26198, 26198, 26199, 26200, 26202, 26200, 27258, 26199, 26199, 26167, +26167, 27225, 26201, 9782, 9779, 10773, 10801, 9746, 8528, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 9843, 8720, 10570, 8757, 9779, 9781, 27189, 26199, 26200, +26202, 26167, 26134, 26134, 26134, 26167, 26132, 26165, 26200, 9749, 10933, 16383, 8720, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +16383, 10768, 9781, 9781, 27222, 26165, 26165, 9748, 25108, 8692, 9715, 26131, 9747, 9779, 26132, 8721, 8720, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 9814, 10838, 9814, 26166, 26166, 26166, 26166, 25108, 8692, 25110, 26134, 9749, 9747, 9746, 8657, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9747, 9747, 9749, 9781, 9780, 9748, 8690, 8658, 8691, 8727, 8726, 9619, 8720, 8720, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10581, 15391, 8720, 9746, 8592, 7566, 6477, 8690, 8656, 8724, 8720, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 0, 8720, +8720, 9619, 8720, 10581, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 8720, 9843, 8720, 0, 0, 0, 11799, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 0, 16383, 10933, 10933, 11799, 11960, 10933, 8720, 8720, 11799, 10907, 11799, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 11894, 9849, 10581, 9843, 11894, 10837, 10869, 9745, 9746, 9779, 10804, 11897, 11799, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 11862, 10905, 11964, 10975, 10939, 11931, 11960, 11928, 28312, 11895, 11895, +11928, 11929, 11895, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 11792, 10804, 11929, 12095, 13151, 12095, +11965, 11962, 11963, 28348, 27291, 10940, 11928, 11961, 10936, 11799, 10933, 9843, 8720, 16383, 16383, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, +11924, 11927, 28279, 28413, 28413, 28381, 27292, 27290, 28313, 28346, 28314, 27255, 28278, 11895, 11895, 10871, 11895, 11893, 10836, 10870, 10867, 11928, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 16383, 10837, 11928, 28311, 28312, 28311, 28310, 27255, 27255, 28311, 28311, 28310, 28310, 28278, 11893, 10870, 11926, 28314, 11926, 11928, 28348, 11895, 11927, 10933, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10870, 11926, 11926, 28310, 28311, 28310, 28312, 28312, 28311, 28311, 28311, 28311, 28278, 28310, 28312, 28346, 28348, 28346, 28347, 28347, +11895, 11926, 11862, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 10933, 0, 0, 15391, 10836, 10904, 10870, 28311, 28311, 28278, 28310, 28310, 28311, 28311, 28310, 28310, 28311, 28278, 28310, +28310, 28311, 28278, 28278, 28278, 11926, 28312, 27255, 11924, 11924, 8720, 0, 0, 0, 0, 0, 0, 10933, 10933, 10837, 9747, 9650, 9810, 10869, 11927, 11894, 28310, 28310, 28310, 28310, 28310, 28311, +28310, 28311, 28312, 28380, 28311, 28277, 28310, 28311, 28310, 11892, 11860, 11893, 28313, 28312, 10870, 11891, 16383, 0, 0, 0, 0, 0, 0, 11792, 11891, 11926, 11926, 11893, 11894, 28278, 28310, 28310, +28310, 28310, 28312, 28311, 28311, 28311, 28311, 28311, 28312, 28346, 28312, 28278, 28310, 28310, 28310, 28310, 28278, 27253, 27255, 10838, 10803, 10867, 8720, 16383, 0, 0, 0, 0, 0, 16383, 12915, 11927, +28312, 28311, 28313, 28312, 28311, 28310, 28311, 28310, 28312, 28312, 28312, 28312, 28315, 28347, 28381, 28380, 28380, 28314, 28313, 28346, 28312, 28279, 28278, 28278, 28313, 27290, 10837, 10803, 10933, 12915, 16383, 0, +0, 0, 0, 0, 11792, 11926, 11926, 28278, 28311, 28313, 28311, 28311, 11927, 11926, 28310, 28278, 28278, 28278, 28279, 28311, 28312, 28311, 28311, 28311, 28311, 28311, 28278, 11894, 10870, 28278, 28314, 27289, +10870, 11893, 11924, 9843, 8720, 0, 0, 0, 0, 16383, 11862, 11926, 28311, 28314, 28314, 28346, 28315, 28314, 28314, 28314, 28314, 28314, 28314, 28314, 28281, 28313, 28313, 27289, 27289, 28313, 27289, 27289, +27257, 27257, 28313, 28312, 28278, 10869, 10867, 11891, 10773, 10933, 11799, 0, 0, 0, 0, 10933, 11860, 11926, 11894, 11926, 11928, 28312, 28312, 28312, 28313, 28314, 28313, 28312, 28314, 28314, 27255, 28311, +28281, 27255, 28279, 27289, 27287, 27256, 27255, 28280, 11930, 28314, 11926, 10805, 10933, 10869, 11926, 11926, 10839, 8727, 0, 0, 16383, 11957, 11894, 11925, 11926, 11894, 11926, 11926, 11926, 28310, 28310, 28312, +28312, 28313, 28314, 27291, 28314, 28313, 28312, 27256, 27289, 27290, 28314, 28314, 28313, 28313, 28347, 28347, 28312, 11926, 11894, 10870, 10837, 10836, 10838, 9843, 0, 0, 16383, 11895, 11929, 11927, 11925, 10868, +10870, 11926, 28279, 28312, 28312, 28313, 28312, 28312, 27289, 27256, 27256, 28313, 27256, 27256, 27258, 27256, 27256, 27289, 27258, 27290, 27291, 28314, 28312, 11927, 11893, 28277, 28310, 10870, 10837, 16383, 0, 0, +0, 9843, 10905, 11895, 11926, 11895, 11927, 28278, 28278, 28278, 28279, 28313, 28312, 27288, 27289, 27290, 27290, 27290, 43674, 27289, 43674, 43673, 43673, 27290, 27289, 27258, 27257, 27255, 11894, 10869, 27254, 28277, +11893, 10837, 10933, 0, 0, 0, 0, 8720, 10938, 11891, 11894, 10869, 11893, 11893, 10869, 10869, 10869, 10870, 27286, 28278, 27254, 27255, 27255, 27254, 27254, 27254, 27254, 27222, 27254, 27254, 27221, 27222, +10839, 10870, 10869, 10867, 11892, 10901, 11924, 10773, 0, 0, 0, 0, 10933, 11895, 11930, 11861, 27255, 28311, 11894, 28278, 28311, 28278, 27255, 28278, 27255, 27254, 27255, 27254, 27254, 27254, 27254, 27254, +27254, 27254, 27253, 27254, 27254, 27256, 27257, 27289, 27288, 28311, 10871, 10837, 10803, 10836, 8720, 0, 0, 0, 11867, 10907, 28346, 28311, 28312, 28312, 28278, 28278, 28311, 27255, 27287, 27255, 27255, 27254, +27254, 27254, 27254, 27254, 27254, 27254, 27254, 27254, 27254, 27254, 27255, 27255, 27255, 27255, 27254, 27254, 10868, 10869, 10837, 10869, 11799, 0, 0, 0, 10933, 11895, 28279, 28278, 27287, 10870, 10869, 10870, +10870, 10870, 11894, 11894, 10869, 10869, 27253, 27254, 27222, 27254, 27254, 27222, 27254, 27222, 27222, 27222, 27254, 10869, 10837, 10838, 10837, 10837, 10868, 10870, 10839, 10804, 9843, 15360, 0, 0, 8720, 9843, +10868, 11894, 11894, 10869, 11893, 10870, 11893, 10869, 11893, 10869, 10869, 10869, 11893, 10869, 27253, 27221, 27221, 10869, 10868, 10837, 27220, 10868, 10869, 10836, 10870, 10902, 10836, 10867, 10868, 10868, 10837, 10835, +11894, 11799, 0, 0, 8720, 9843, 11860, 11926, 11894, 11894, 28310, 28309, 28278, 28279, 28280, 27255, 27256, 27257, 27257, 27257, 27257, 27256, 27256, 27256, 27256, 27256, 27256, 27256, 27257, 27257, 27258, 28381, +10904, 10870, 11893, 11925, 11893, 11862, 10869, 11858, 16383, 16383, 10836, 9782, 10837, 11925, 11893, 11893, 28310, 27254, 27254, 27254, 27256, 27254, 27223, 27256, 27255, 27255, 27255, 27256, 27255, 27223, 27255, 27255, +27254, 27254, 27256, 27254, 27222, 27255, 10869, 10870, 11894, 11925, 11926, 28315, 10904, 10933, 0, 0, 9747, 10902, 11929, 11895, 10870, 27254, 27288, 27253, 27221, 27221, 27254, 27255, 27255, 27256, 27222, 27254, +27254, 27254, 27222, 27255, 27223, 27254, 27254, 27222, 27254, 27221, 27253, 27222, 27255, 27254, 27255, 10903, 10803, 10902, 11862, 11792, 0, 0, 8720, 11792, 11860, 10837, 10837, 10838, 28380, 27289, 27222, 27255, +27254, 27255, 27257, 27256, 27254, 27255, 27256, 27255, 27256, 27257, 27225, 27224, 27290, 27257, 27254, 27221, 27220, 27221, 27255, 27289, 28382, 28447, 10937, 9843, 8720, 16383, 0, 0, 0, 8720, 9843, 9843, +10870, 10838, 27290, 11895, 10869, 27222, 28310, 28381, 27356, 27324, 27291, 27357, 27357, 27323, 27258, 27291, 27291, 26234, 27259, 27258, 27257, 27222, 10836, 11860, 10838, 10838, 27256, 27256, 10875, 9813, 15360, 0, +0, 0, 0, 0, 16383, 9843, 11894, 28311, 27255, 11894, 28278, 27254, 27254, 27256, 27256, 27256, 27257, 27257, 27256, 27257, 27256, 27256, 27257, 26200, 26200, 27224, 27256, 27257, 27222, 10869, 10837, 10836, +10803, 10871, 10941, 10871, 8720, 0, 0, 0, 0, 0, 8720, 10869, 10871, 27255, 27254, 27255, 27255, 27254, 27254, 27256, 27256, 27256, 27256, 27256, 27256, 27257, 27256, 27256, 27255, 27222, 27221, 27222, +27254, 27223, 27222, 27254, 27221, 10870, 9779, 9744, 9815, 8657, 15391, 0, 0, 0, 0, 0, 0, 11858, 11862, 10871, 10873, 27259, 27254, 10837, 27224, 27258, 27290, 27290, 27291, 27323, 27323, 27323, +27291, 27323, 27257, 27254, 27221, 27222, 27223, 27222, 27220, 10837, 10837, 10840, 9748, 8657, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 16383, 0, 9846, 10874, 10838, 27222, 27222, 27223, +27256, 27256, 27256, 27290, 27289, 27256, 27290, 27257, 27223, 27255, 27254, 27222, 27255, 27256, 27257, 27256, 10869, 10871, 9747, 10581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, +9843, 10871, 10838, 27256, 27224, 27222, 27257, 27258, 27323, 27357, 27324, 27356, 28380, 27255, 27222, 27222, 27254, 27256, 27222, 27222, 9781, 10869, 10837, 9813, 8657, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 8720, 9779, 9811, 10804, 9779, 9782, 9779, 10805, 27221, 27222, 27222, 27223, 27254, 27256, 27222, 27222, 27222, 27221, 27223, 27223, 9782, 9747, 10933, 10933, 9650, 5450, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10933, 10933, 8720, 8725, 10773, 10837, 10868, 10870, 10837, 27224, 26165, 26133, 26165, 9781, 27190, 10804, 10804, 10839, 9781, +8720, 0, 16383, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10867, 10837, 10836, 10836, 10837, 9811, 9779, 8723, +9746, 9780, 9747, 10803, 10804, 8657, 15391, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 10869, 10869, +10836, 27221, 10838, 10837, 9747, 8691, 9780, 9781, 9748, 9746, 9779, 9619, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 16383, 9843, 11799, 10773, 9812, 10836, 9779, 8657, 8721, 9748, 9782, 9814, 10933, 8720, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 0, 15360, 8753, 8592, 7502, 6540, 8624, 8654, 9748, 10581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 0, 0, 0, 8720, 0, 8720, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 11792, 0, 0, 0, 0, +10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 8720, +11894, 10933, 15360, 16383, 8727, 11897, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9810, +8727, 10581, 8727, 11891, 10805, 10869, 8657, 9744, 9746, 10805, 10871, 10933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 10933, 10870, 10906, 10909, 10873, 11962, 11928, 11894, 11927, 10870, 11926, 11926, 11959, 12921, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 8720, 10773, 11928, 11965, 11967, 10942, 11931, 11928, 11930, 11963, 11929, 10938, 11927, 11926, 10933, 8720, 8720, 8720, 0, 16383, 16383, 16383, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 10933, 11924, 10870, 11931, 11931, 11931, 10873, 10903, 11895, 11928, 11929, 11894, 11894, 11894, 10837, 11894, 10870, 10836, 10834, 10869, 11828, +10933, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 9810, 11957, 11927, 11926, 11926, 28310, 11893, 11861, 11893, 11926, 11925, 11892, 11893, 11892, 10869, 11925, +11927, 11925, 11927, 11930, 11925, 11926, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 10933, 11858, 11926, 11926, 11925, 11926, 11926, 28310, 11926, 11925, 11958, +11926, 11893, 11957, 11927, 11927, 11929, 11929, 11960, 11927, 11926, 11924, 8720, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 9747, 10870, 10869, 11926, 11926, 11925, 11925, +11925, 28310, 11926, 11926, 11925, 11926, 11925, 11925, 11926, 11926, 11893, 11925, 11958, 11926, 11927, 11925, 10933, 10933, 16383, 0, 0, 0, 0, 0, 16383, 8720, 16383, 16383, 16383, 16383, 10933, 11894, +11925, 11893, 11925, 11893, 11925, 11926, 28310, 28310, 11893, 11926, 28314, 11927, 11925, 11925, 11925, 11924, 11891, 11891, 11893, 11926, 11894, 9843, 10933, 0, 0, 0, 0, 0, 0, 16383, 8720, 10933, +11891, 12915, 11858, 11891, 11893, 11925, 11926, 11926, 11927, 11926, 11893, 11926, 11926, 11926, 11926, 11927, 11894, 11926, 11893, 11925, 11925, 11926, 11893, 10836, 11893, 10836, 10834, 9843, 16383, 0, 0, 0, +0, 0, 0, 10933, 11926, 11894, 11926, 11925, 11926, 11926, 11926, 11958, 11926, 11927, 28311, 28311, 28312, 28313, 28314, 28314, 28314, 28314, 28313, 28312, 28313, 11926, 11925, 11893, 10868, 11895, 11895, 10869, +10933, 16383, 16383, 0, 0, 0, 0, 0, 8720, 11891, 11893, 11924, 11893, 11927, 11926, 11926, 11957, 12918, 11957, 11893, 11925, 11893, 11926, 11894, 28310, 28310, 11926, 11926, 11926, 11926, 11924, 10870, +10837, 10837, 11927, 11927, 11862, 10933, 16383, 16383, 0, 0, 0, 0, 16383, 11858, 11926, 11925, 11894, 11926, 11927, 11927, 11927, 11927, 11926, 11959, 11926, 11926, 11928, 11894, 11894, 11927, 11926, 11926, +11927, 11893, 11927, 11925, 11894, 11927, 11927, 11894, 11860, 10933, 8720, 16383, 16383, 8720, 0, 0, 0, 8720, 11891, 11959, 11925, 11926, 11928, 11895, 11959, 11928, 11928, 11929, 11960, 11928, 11961, 11928, +11894, 11926, 11928, 11927, 11927, 11928, 11927, 11929, 11926, 11928, 11929, 11961, 11926, 10933, 11799, 11924, 11926, 11957, 10869, 10933, 0, 16383, 10933, 12918, 11957, 11957, 11925, 11926, 11893, 11925, 11893, 11926, +11926, 28311, 28311, 28313, 28313, 28312, 28312, 11927, 28312, 28312, 28313, 28313, 28313, 11927, 28311, 11929, 11928, 11926, 11925, 11924, 11893, 11924, 11957, 11860, 8720, 0, 0, 10933, 11927, 11926, 12918, 10867, +11924, 11893, 11925, 11926, 11925, 11926, 28310, 28310, 28278, 28278, 28310, 28310, 28310, 27254, 27255, 27254, 28278, 28311, 27255, 27255, 28311, 11927, 11894, 11925, 11893, 11924, 11892, 11924, 10933, 16383, 0, 0, +11799, 11960, 11926, 12981, 11924, 12918, 11893, 11893, 11892, 11893, 28312, 28311, 28310, 28310, 28311, 28310, 28312, 28313, 28312, 28311, 28311, 28311, 28311, 28279, 27256, 27255, 10870, 10901, 10900, 11893, 11925, 11893, +11891, 16383, 0, 0, 0, 15360, 12921, 9843, 12981, 10933, 12915, 11862, 10805, 10805, 11924, 11925, 11893, 11926, 11893, 11894, 10870, 10870, 11894, 11926, 11893, 10870, 11893, 11893, 10836, 10837, 10837, 11891, +11862, 11862, 11862, 11858, 11862, 10933, 0, 0, 0, 16352, 10933, 11928, 10867, 11893, 11894, 11893, 11957, 11926, 11925, 11927, 11893, 11927, 11894, 11926, 11925, 11892, 11892, 11925, 11892, 11892, 11893, 11892, +11893, 11893, 11894, 11927, 11894, 11894, 11894, 11926, 11894, 9843, 11858, 8720, 0, 0, 12921, 11929, 11961, 11926, 11893, 11925, 11893, 11924, 11893, 11926, 11893, 11925, 11926, 11926, 11925, 11893, 11892, 11893, +11893, 11892, 11893, 11893, 11892, 11893, 11892, 11893, 11925, 11926, 11893, 11924, 10933, 11861, 11924, 10805, 8720, 0, 0, 8720, 10870, 11926, 11893, 10868, 11893, 10870, 11926, 11924, 11893, 11893, 11926, 11924, +11926, 11925, 11893, 11893, 11925, 11892, 11892, 11925, 11893, 11892, 10868, 11925, 11893, 11892, 10836, 10869, 10870, 10933, 10869, 11924, 10773, 10933, 15360, 0, 0, 10933, 10870, 11893, 11862, 12915, 10933, 10870, +11957, 11924, 11861, 11924, 11861, 11924, 10870, 11893, 11925, 10868, 11892, 11860, 11893, 11893, 10868, 10867, 10837, 11924, 10867, 10837, 10933, 12915, 11862, 11891, 10870, 10804, 11894, 8720, 0, 8720, 11792, 11862, +11957, 12981, 11894, 10870, 11893, 11926, 11895, 11927, 11895, 11927, 11894, 27254, 28279, 28279, 28279, 28278, 28278, 27254, 28278, 27254, 28278, 28278, 11894, 10871, 27257, 10871, 11924, 12981, 11957, 11924, 11893, 11860, +11799, 16383, 9846, 9843, 11862, 11862, 11891, 10870, 11893, 10867, 11893, 10869, 11894, 11893, 10870, 10870, 27222, 11894, 11894, 28278, 28277, 27253, 28277, 27254, 27253, 28277, 28278, 10869, 10836, 10838, 10867, 11924, +11891, 10933, 11957, 10904, 10903, 11799, 0, 8720, 10933, 11928, 11893, 11893, 10870, 11894, 10836, 10836, 10869, 11893, 10870, 11893, 10870, 10870, 10870, 11893, 11893, 11893, 10870, 10869, 10869, 10868, 11893, 11893, +10869, 11893, 11893, 11893, 11893, 11927, 11894, 10928, 11926, 10805, 8720, 0, 0, 8720, 11894, 11891, 10804, 9781, 27258, 10838, 10868, 10870, 10870, 10870, 27222, 27254, 10869, 11894, 11894, 11894, 10869, 10839, +10870, 10870, 10870, 10871, 11893, 11861, 10836, 11861, 11893, 11894, 27291, 11932, 10871, 10933, 16383, 0, 0, 0, 16383, 11792, 11792, 12915, 10837, 10872, 11862, 11924, 10838, 10870, 27290, 27258, 27257, 27257, +27258, 27259, 27257, 27256, 27257, 27225, 27224, 27257, 27257, 27255, 10869, 11891, 11891, 11893, 11860, 11895, 11927, 10871, 10805, 0, 0, 0, 0, 0, 16383, 8720, 11926, 10871, 11926, 11894, 11893, 10836, +11892, 11894, 10870, 27254, 27222, 27222, 27222, 27256, 27254, 27254, 27223, 10838, 9814, 10870, 27254, 27255, 10836, 11860, 10867, 9843, 9843, 9847, 10873, 10838, 8720, 0, 0, 0, 0, 16383, 12915, 10869, +10871, 10869, 10869, 28277, 11893, 10869, 11893, 11894, 28311, 28278, 10870, 28278, 10870, 11894, 28278, 11894, 10868, 10868, 10868, 10836, 27221, 10869, 11894, 10869, 10838, 10803, 10933, 8757, 8720, 15391, 0, 0, +0, 0, 0, 16383, 10933, 9843, 10872, 11897, 10870, 10868, 10870, 27287, 28281, 27289, 27289, 27289, 27257, 27257, 28314, 27257, 11896, 11893, 10869, 11893, 11893, 10836, 10804, 10805, 10869, 10838, 9779, 10933, +15360, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10805, 10873, 11860, 10837, 10836, 11894, 10871, 10871, 11894, 10871, 10870, 10870, 10871, 27255, 10870, 10869, 11894, 11894, 10871, 10869, 10872, +11895, 11924, 10869, 10804, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10773, 10838, 10869, 10870, 27222, 10869, 10870, 10872, 11897, 10873, 27257, 27257, 27289, 10871, 10869, 11894, +10870, 10870, 10869, 10836, 10805, 11862, 10933, 10836, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9747, 10836, 10803, 9746, 10804, 10770, 10836, 10837, 10837, 10836, 27221, +27253, 27254, 10837, 10837, 10837, 10869, 11893, 10870, 10803, 11858, 8720, 16383, 8720, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 8720, 8720, 8720, 8720, 11792, +10837, 11924, 11893, 10869, 10838, 10836, 9780, 9781, 9812, 10838, 10803, 10803, 10838, 9843, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 16383, 11862, 11894, 10867, 10837, 11860, 10804, 9779, 8657, 10801, 10836, 10804, 10801, 10803, 8657, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 11862, 11926, 10870, 10868, 10837, 10836, 9779, 8721, 9748, 10836, 10837, 10773, 9843, 8720, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 8720, 8720, 10933, 11891, 9843, 8753, 8657, 9744, 9746, 10803, 8720, 16383, 16383, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15360, 9843, 8720, 6540, 6540, 9650, 9650, 9810, 15391, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 15391, 0, 0, +15391, 0, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 16383, 16383, 16383, 15360, 0, 0, 10933, 10871, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 15360, 9843, 10933, 8720, 10933, 10933, 11858, 10801, 8720, 9650, 9650, 10773, 10870, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 16383, 10933, 11927, 10906, 10838, 10871, 11958, 11925, 11894, 11894, 11925, 11924, 11891, 10933, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 15391, 8720, 11891, 11961, 11931, 10907, 11928, 11927, 11960, 11961, 10903, 11928, 11959, 11894, 10933, 16383, 16383, 16383, 0, 16383, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 9843, 11924, 11960, 11928, 11927, 11894, 11895, 11893, 11926, 11926, 11926, 11893, 12918, 11957, 11926, 10870, 10837, 9843, +11862, 11792, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 12915, 11959, 11926, 11926, 11926, 11924, 10837, 11894, 12981, 11894, 11893, 11893, 11891, 10867, +11957, 11927, 11957, 11957, 12982, 12981, 12023, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10933, 10933, 12981, 11990, 12981, 11957, 11957, 11926, 11990, 12915, +12981, 11858, 12915, 11926, 11959, 11926, 11958, 11959, 12918, 10933, 12023, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 10837, 10867, 11924, 11926, 11862, 11891, 12918, +11957, 12981, 11990, 11891, 11893, 10933, 12915, 11891, 12918, 11894, 11862, 12921, 12915, 12918, 11924, 10933, 11799, 16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 0, 0, 10933, 10805, 10933, 10933, +11891, 12981, 11924, 11957, 11926, 11957, 12918, 12918, 11959, 11861, 11891, 11891, 10837, 11862, 11799, 10933, 11862, 11893, 11925, 9843, 8720, 0, 0, 0, 0, 0, 0, 16383, 16383, 11799, 8720, 10933, +11799, 11891, 12981, 13014, 11957, 11926, 11958, 11893, 11957, 11926, 11957, 11957, 11958, 12918, 11957, 11957, 12918, 11957, 11894, 11894, 11891, 10869, 11860, 9810, 8720, 0, 0, 0, 0, 0, 0, 16383, +8720, 11891, 10933, 11894, 11924, 12918, 12918, 13014, 11957, 11958, 12950, 11926, 11926, 11927, 11926, 11926, 11926, 11927, 11926, 11926, 11927, 11925, 12918, 11957, 10867, 11925, 11926, 9843, 8720, 16383, 16383, 0, +0, 0, 0, 16383, 10933, 10933, 11891, 10867, 11893, 12981, 11891, 11990, 12981, 11990, 12981, 12981, 11894, 11925, 11893, 11893, 11924, 11894, 11894, 11924, 11894, 11891, 12915, 12915, 11862, 11927, 11893, 10805, +8720, 16383, 0, 0, 0, 0, 0, 8720, 11862, 11862, 10933, 10933, 12918, 11891, 11891, 11891, 11891, 11891, 12981, 12981, 12981, 11924, 11924, 11924, 11894, 12981, 12981, 12918, 12981, 11891, 11862, 11862, +11891, 11894, 11891, 10933, 8720, 16383, 16383, 16383, 0, 0, 16383, 10933, 11926, 11957, 11957, 11928, 11926, 11926, 11959, 12982, 11959, 11928, 11958, 11960, 11960, 11926, 11925, 11927, 11927, 11958, 11927, 11958, +11960, 11893, 11928, 11928, 11926, 11990, 8720, 8720, 11891, 12981, 12981, 11891, 8720, 0, 8720, 12915, 12023, 11799, 11799, 12915, 12915, 10933, 12981, 11895, 13014, 11926, 11925, 11927, 11927, 11927, 11927, 11925, +11925, 11894, 11958, 11958, 11958, 11926, 11926, 11893, 11894, 12023, 8720, 10933, 11862, 11862, 10933, 9843, 16383, 0, 10933, 13014, 12981, 10933, 10933, 10933, 10933, 12023, 12915, 10933, 11924, 11925, 11924, 11893, +10868, 10868, 11893, 11925, 11860, 11893, 11892, 11925, 11893, 11893, 10869, 11893, 11924, 12023, 10933, 12023, 10933, 11894, 12915, 16383, 0, 0, 10933, 12920, 12915, 8720, 8720, 10933, 12023, 12023, 12915, 12981, +11926, 11926, 11893, 11893, 11925, 11893, 11926, 28310, 28310, 28310, 28310, 28310, 11926, 11894, 11895, 11893, 11862, 12023, 12023, 12915, 11891, 11894, 10933, 0, 0, 0, 15360, 10933, 8720, 11799, 10933, 10933, +10933, 10933, 12023, 12915, 11861, 11924, 11893, 11893, 11957, 11924, 11924, 11926, 11925, 11924, 11924, 11924, 11893, 10867, 10869, 11891, 11799, 10933, 8720, 10933, 10933, 8720, 16383, 0, 0, 16383, 9843, 11895, +10933, 11957, 11924, 12981, 11894, 11926, 11926, 11926, 11924, 12918, 12918, 11924, 11924, 11893, 11893, 11957, 11924, 12918, 12918, 11924, 11893, 11924, 10870, 11924, 11893, 11894, 12981, 10933, 10933, 8720, 11799, 15360, +0, 10933, 11926, 11926, 12981, 12918, 11891, 10933, 11990, 11990, 11891, 11891, 12981, 12981, 12981, 11894, 11924, 11924, 11894, 11894, 11894, 11894, 11894, 10867, 10867, 12981, 11891, 11894, 12918, 11862, 12915, 11799, +12915, 11792, 11799, 16383, 0, 0, 12915, 12981, 11891, 11891, 11799, 10933, 12023, 12023, 12023, 12023, 12915, 12915, 10933, 10933, 11891, 12981, 11891, 11862, 11862, 11862, 11862, 11858, 11858, 10933, 12915, 12915, +12915, 11799, 10933, 10933, 12915, 11792, 8720, 16383, 0, 15360, 16383, 12915, 11891, 10933, 8720, 10933, 10933, 10933, 10933, 10933, 10933, 11862, 10933, 11891, 11891, 12981, 11957, 12981, 11862, 11891, 11862, 10933, +11862, 10933, 12915, 11792, 10773, 10933, 8720, 8720, 10933, 11799, 10933, 10933, 16383, 8720, 8720, 10933, 10933, 10933, 10933, 12915, 11894, 11924, 11893, 11893, 11893, 11893, 11924, 10869, 11894, 11893, 11925, 11925, +11924, 11893, 11924, 11924, 11924, 11924, 11893, 11893, 10870, 11891, 10933, 8720, 8720, 12915, 10933, 10933, 0, 9843, 10933, 10933, 10933, 10933, 12023, 12915, 12915, 11862, 11924, 11893, 11894, 10867, 10867, 10835, +11860, 11924, 11924, 11891, 10900, 10900, 11893, 11860, 11860, 11862, 11924, 10805, 11891, 10773, 10933, 8720, 16383, 11799, 11863, 11891, 16383, 16383, 8720, 11862, 10933, 11858, 10933, 10870, 10804, 10801, 10837, 10870, +11924, 11924, 11924, 10867, 11924, 11924, 11893, 11924, 11893, 11924, 11861, 11861, 10870, 11924, 11861, 11924, 10805, 10805, 11891, 11895, 12023, 8720, 11799, 10933, 0, 0, 16383, 10933, 10933, 11792, 10805, 10904, +11893, 10805, 11860, 11893, 11892, 10868, 10836, 11860, 11893, 11893, 10867, 11893, 11893, 11893, 11924, 11924, 11893, 11893, 10837, 11924, 11891, 11858, 11895, 11929, 11931, 11895, 16383, 0, 0, 0, 0, 16383, +16383, 16383, 10773, 11894, 11891, 9843, 10869, 11924, 10838, 10870, 10837, 10837, 11927, 10872, 10870, 10837, 10870, 10870, 10837, 10837, 10870, 10837, 10837, 9843, 11792, 10933, 12915, 11894, 11895, 10869, 10773, 0, +0, 0, 0, 0, 16383, 10933, 11924, 10837, 12915, 10933, 10805, 11891, 10870, 10836, 10869, 10837, 10803, 9780, 10868, 10869, 10869, 9781, 9779, 8721, 9779, 11860, 11860, 11891, 11799, 11792, 8720, 11792, +11799, 10838, 10803, 8720, 0, 0, 0, 0, 16383, 11799, 10933, 11862, 11894, 11957, 10933, 10933, 11894, 11957, 11893, 11860, 10870, 10867, 10870, 10870, 11924, 11924, 11858, 10773, 10773, 10805, 11892, 11892, +11895, 11860, 10870, 11894, 8720, 9810, 8720, 15391, 0, 0, 0, 0, 0, 16383, 10933, 11894, 11926, 11891, 11799, 11891, 11926, 11926, 11894, 11926, 11926, 11896, 11896, 11926, 10903, 11894, 11862, 11799, +11799, 10933, 10804, 10801, 10933, 11858, 10867, 10801, 8720, 15391, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 11799, 11864, 11862, 11891, 11891, 11924, 11860, 11893, 10869, 11893, 11924, 11924, 11860, +10868, 11893, 11894, 10933, 10933, 11894, 11924, 11862, 11891, 12915, 11891, 10773, 16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10837, 10805, 10803, 10836, 10867, 10869, 11893, 10869, +11893, 10869, 11893, 11925, 11892, 11893, 11893, 11860, 11894, 10869, 10867, 9843, 11792, 11792, 11858, 8720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8720, 10773, 10773, 9747, 9779, +10773, 10805, 11924, 11924, 10804, 10867, 11924, 10901, 11860, 11860, 10869, 10869, 11893, 10837, 9843, 8720, 16383, 16383, 16383, 15360, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +16383, 16383, 15391, 8720, 8720, 12915, 12915, 11862, 9843, 10837, 9843, 9746, 8753, 9747, 10837, 9843, 9843, 10837, 10768, 15391, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 16383, 10933, 10933, 11799, 10933, 11799, 10933, 10933, 10581, 10933, 11792, 10933, 10933, 10801, 9619, 15360, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 11799, 11862, 11891, 10804, 10805, 9843, 9843, 8720, 9747, 10805, 11858, 10933, 10933, 15391, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16383, 16383, 8720, 9843, 10773, 9843, 9612, 9612, 10768, 10773, 8720, 16383, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10933, 8720, 8720, 8720, 8720, 8720, 10933, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +16383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +Sprites explosion(20, explosionPixels, explosionOffsets, explosionRes, explosionPoints, explosionPointOffsets, Sprites::PixelFormat::R5G5B4A2); diff --git a/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/VGAWiFiTextTerminal.ino b/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/VGAWiFiTextTerminal.ino new file mode 100644 index 0000000..0e728cc --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/VGAWiFiTextTerminal.ino @@ -0,0 +1,116 @@ +//This example acts like a websever. It can create an access point or join an existing WiFI network. +//All text that's entered in the served page will bis displayed on the connected VGA screen. +//You need to connect a VGA screen cable to the pins specified below. +//cc by-sa 4.0 license +//bitluni + +#include +#include +#include +//ESP32Lib headers +#include +#include + +//true: creates an access point, false: connects to an existing wifi +const bool AccessPointMode = true; +//wifi credentials (enter yours if you arne not using the AccessPointMode) +const char *ssid = "VGA"; +const char *password = ""; + +//pin configuration, change if you need to +const int redPin = 14; +const int greenPin = 19; +const int bluePin = 27; +const int hsyncPin = 32; +const int vsyncPin = 33; + +//the webserver at pot 80 +WebServer server(80); + +//The VGA Device +VGA3Bit vga; + +//include html page +const char *page = +#include "page.h" + ; + +///Html page is sent on root +void sendPage() +{ + server.send(200, "text/html", page); +} + +///Received text will be displayed on the screen +void text() +{ + server.send(200, "text/plain", "ok"); + vga.println(server.arg(0).c_str()); +} + +///initialization +void setup() +{ + Serial.begin(115200); + //Handle the WiFi AP or STA mode and display results on the screen + if (AccessPointMode) + { + Serial.println("Creating access point..."); + WiFi.softAP(ssid, password, 6, 0); + } + else + { + Serial.print("Connecting to SSID "); + Serial.println(ssid); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + vga.print("."); + } + } + //start vga on the specified pins + vga.init(vga.MODE400x300, redPin, greenPin, bluePin, hsyncPin, vsyncPin); + //make the background blue + vga.clear(vga.RGBA(0, 0, 255)); + vga.backColor = vga.RGB(0, 0, 255); + //select the font + vga.setFont(Font6x8); + + //send page on http:/// + server.on("/", sendPage); + //receive text on http:///text + server.on("/text", text); + //start the server + server.begin(); + + //display some text header on the screen including the ip + vga.clear(vga.RGBA(0, 0, 255)); + vga.setCursor(0, 0); + vga.println("----------------------"); + vga.println("bitluni's VGA Terminal"); + if (AccessPointMode) + { + vga.print("SSID: "); + vga.println(ssid); + if (strlen(password)) + { + vga.print("password: "); + vga.println(password); + } + vga.println("http://192.168.4.1"); + } + else + { + vga.print("http://"); + vga.println(WiFi.localIP().toString().c_str()); + } + vga.println("----------------------"); +} + +void loop() +{ + //process the server stuff + server.handleClient(); + delay(10); +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/page.h b/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/page.h new file mode 100644 index 0000000..a0f8cbb --- /dev/null +++ b/libraries/bitluni_ESP32Lib/examples/VGAWiFiTextTerminal/page.h @@ -0,0 +1,92 @@ +R"( + + + + bitluni's VGA text terminal + + + + + +

bitluni's VGA text terminal

+ + + + + + + + +
+ +
+ + + + + +)" \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/keywords.txt b/libraries/bitluni_ESP32Lib/keywords.txt new file mode 100644 index 0000000..e69de29 diff --git a/libraries/bitluni_ESP32Lib/library.json b/libraries/bitluni_ESP32Lib/library.json new file mode 100644 index 0000000..1207db2 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/library.json @@ -0,0 +1,22 @@ +{ + "name": "bitluni ESP32Lib", + "keywords": "VGA, audio, I2S, 3d, game, engine, mesh, gamepad, controller, NES, SNES, input, output, graphics, font, arduino, esp32, driver, display, bitluni", + "description": "Provides VGA, Game Controller (NES, SNES), Audio support for the ESP32. The graphics engine supports sprites, animations and 3d meshes.", + "repository": + { + "type": "git", + "url": "https://github.com/bitluni/ESP32Lib.git" + }, + "authors": + [ + { + "name": "bitluni", + "email": "platformio@bitluni.net", + "url": "https://github.com/bitluni", + "maintainer": true + } + ], + "version": "0.2.1", + "frameworks": "arduino", + "platforms": "espressif32" +} \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/library.properties b/libraries/bitluni_ESP32Lib/library.properties new file mode 100644 index 0000000..e6bafde --- /dev/null +++ b/libraries/bitluni_ESP32Lib/library.properties @@ -0,0 +1,10 @@ +name=bitluni ESP32Lib +version=0.2.1 +author=bitluni +maintainer=bitluni +sentence=Multimedia library for the ESP32 +paragraph=Provides VGA, Game Controller (NES, SNES), Audio support for the ESP32. The graphics engine supports sprites, animations and 3d meshes. +url=https://github.com/bitluni/ESP32Lib +architectures=esp32 +includes=ESP32Lib.h +category=Other \ No newline at end of file diff --git a/libraries/bitluni_ESP32Lib/src/Audio/AudioOutput.h b/libraries/bitluni_ESP32Lib/src/Audio/AudioOutput.h new file mode 100644 index 0000000..0cb823c --- /dev/null +++ b/libraries/bitluni_ESP32Lib/src/Audio/AudioOutput.h @@ -0,0 +1,58 @@ +/* + Author: bitluni 2019 + License: + Creative Commons Attribution ShareAlike 4.0 + https://creativecommons.org/licenses/by-sa/4.0/ + + For further details check out: + https://youtube.com/bitlunislab + https://github.com/bitluni + http://bitluni.net +*/ +#include "soc/i2s_reg.h" +#include "soc/timer_group_struct.h" +#include "driver/periph_ctrl.h" +#include "driver/timer.h" +#include "AudioSystem.h" + +class AudioOutput; +void IRAM_ATTR timerInterrupt(AudioOutput *audioOutput); + +class AudioOutput +{ + public: + AudioSystem *audioSystem; + + void init(AudioSystem &audioSystem) + { + this->audioSystem = &audioSystem; + timer_config_t config; + config.alarm_en = 1; + config.auto_reload = 1; + config.counter_dir = TIMER_COUNT_UP; + config.divider = 16; + config.intr_type = TIMER_INTR_LEVEL; + config.counter_en = TIMER_PAUSE; + timer_init((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, &config); + timer_pause((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); + timer_set_counter_value((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, 0x00000000ULL); + timer_set_alarm_value((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, 1.0/audioSystem.samplingRate * TIMER_BASE_CLK / config.divider); + timer_enable_intr((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); + timer_isr_register((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0, (void (*)(void*))timerInterrupt, (void*) this, ESP_INTR_FLAG_IRAM, NULL); + timer_start((timer_group_t)TIMER_GROUP_0, (timer_idx_t)TIMER_0); + } +}; + +void IRAM_ATTR timerInterrupt(AudioOutput *audioOutput) +{ + uint32_t intStatus = TIMERG0.int_st_timers.val; + if(intStatus & BIT(TIMER_0)) + { + TIMERG0.hw_timer[TIMER_0].update = 1; + TIMERG0.int_clr_timers.t0 = 1; + TIMERG0.hw_timer[TIMER_0].config.alarm_en = 1; + + WRITE_PERI_REG(I2S_CONF_SIGLE_DATA_REG(0), audioOutput->audioSystem->nextSample() << 24); + } +} + diff --git a/libraries/bitluni_ESP32Lib/src/Audio/AudioSystem.h b/libraries/bitluni_ESP32Lib/src/Audio/AudioSystem.h new file mode 100644 index 0000000..2355fe2 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/src/Audio/AudioSystem.h @@ -0,0 +1,209 @@ +/* + Author: bitluni 2019 + License: + Creative Commons Attribution ShareAlike 4.0 + https://creativecommons.org/licenses/by-sa/4.0/ + + For further details check out: + https://youtube.com/bitlunislab + https://github.com/bitluni + http://bitluni.net +*/ +#pragma once +#include "../Tools/Log.h" + +class Sound +{ + public: + const signed char *samples; + int sampleCount; + long long position; + int positionIncrement; + int volume; + bool playing; + int id; + bool loop; + Sound *next; + + void remove(Sound **prevNext) + { + *prevNext = next; + delete this; + } + + void insert(Sound **prevNext) + { + next = *prevNext; + *prevNext = this; + } + + void init(const signed char *samples, int sampleCount, float volume = 1, float speed = 1, bool loop = false) + { + next = 0; + id = 0; + this->samples = samples; + this->sampleCount = sampleCount; + this->loop = loop; + position = 0; + positionIncrement = int(65536 * speed); + this->volume = volume * 256; + playing = true; + } + + ///returns the next rendered sample. 16bit since it's scaled by volume + int nextSample() + { + if(!playing) return 0; + int s = samples[position >> 16] * volume; + position += positionIncrement; + if((position >> 16) >= sampleCount) + { + if(loop) + position = 0; + else + playing = false; + } + return s; + } +}; + +class AudioSystem +{ + public: + Sound *sounds; + int samplingRate; + unsigned char *buffer; + int bufferSize; + bool swapped; + int currentId; + volatile int readPosition; + int writePosition; + int volume; + + AudioSystem(int samplingRate, int bufferSize) + { + this->samplingRate = samplingRate; + this->bufferSize = bufferSize; + sounds = 0; + buffer = (unsigned char*)malloc(bufferSize * sizeof(unsigned char)); + if(!buffer) + ERROR("Not enough memory for audio buffer"); + for(int i = 0; i < bufferSize; i++) + buffer[i] = 128; + swapped = true; + currentId = 0; + readPosition = 0; + writePosition = 0; + volume = 256; + } + + ///plays a sound, and returns an idividual id + int play(Sound *sound) + { + sound->id = currentId; + sound->insert(&sounds); + return currentId++; + } + + ///fills the buffer with all saounds that are currently playing + void calcSamples() + { + int samples = readPosition - writePosition; + if(samples < 0) + samples += bufferSize; + for(int i = 0; i < samples; i++) + { + int sample = 0; + Sound **soundp = &sounds; + while(*soundp) + { + sample += (*soundp)->nextSample(); + if(!(*soundp)->playing) + (*soundp)->remove(soundp); + else + soundp = &(*soundp)->next; + } + if(sample >= (1 << 15)) + sample = (1 << 15) - 1; + else if(sample < -(1 << 15)) + sample = -(1 << 15); + sample = ((sample * volume) >> 16) + 128; + buffer[writePosition] = sample; + writePosition++; + if(writePosition >= bufferSize) + writePosition = 0; + } + } + + inline unsigned char nextSample() + { + unsigned char s = buffer[readPosition]; + readPosition++; + if(readPosition >= bufferSize) + readPosition = 0; + return s; + } + + ///stops playing a sound with an individual id + void stop(int id) + { + Sound **soundp = &sounds; + while(*soundp) + { + if((*soundp)->id == id) + { + (*soundp)->remove(soundp); + return; + } + soundp = &(*soundp)->next; + } + } + + ///stops playing all sounds of an specific sample pointer + void stopBySample(const signed char *samples) + { + Sound **soundp = &sounds; + while(*soundp) + { + if((*soundp)->samples == samples) + (*soundp)->remove(soundp); + else + soundp = &(*soundp)->next; + } + } +}; + +class Wavetable +{ + public: + const signed char *samples; + int soundCount; + const int *offsets; + int samplingRate; + + Wavetable(const signed char *samples, int soundCount, const int *offsets, int samplingRate) + { + this->samples = samples; + this->soundCount = soundCount; + this->offsets = offsets; + this->samplingRate = samplingRate; + } + + int play(AudioSystem &audioSystem, int soundNumber, float amplitude = 1, float speed = 1, bool loop = false) + { + Sound *sound = new Sound(); + sound->init(&samples[offsets[soundNumber]], offsets[soundNumber + 1] - offsets[soundNumber], amplitude, speed * samplingRate / audioSystem.samplingRate, loop); + return audioSystem.play(sound); + } + + void stop(AudioSystem &audioSystem, int id) + { + audioSystem.stop(id); + } + + void stop(AudioSystem &audioSystem) + { + for(int i = 0; i < soundCount; i++) + audioSystem.stopBySample(&samples[offsets[i]]); + } +}; diff --git a/libraries/bitluni_ESP32Lib/src/Controller/GameControllers.h b/libraries/bitluni_ESP32Lib/src/Controller/GameControllers.h new file mode 100644 index 0000000..f518ae8 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/src/Controller/GameControllers.h @@ -0,0 +1,128 @@ +/* + Author: bitluni 2019 + License: + Creative Commons Attribution ShareAlike 4.0 + https://creativecommons.org/licenses/by-sa/4.0/ + + For further details check out: + https://youtube.com/bitlunislab + https://github.com/bitluni + http://bitluni.net +*/ + +#pragma once +const int MAX_CONTROLLERS = 4; + +class GameControllers +{ + public: + + enum Type + { + NES = 0, + SNES = 1, + }; + + enum Button + { + B = 0, + Y = 1, + SELECT = 2, + START = 3, + UP = 4, + DOWN = 5, + LEFT = 6, + RIGHT = 7, + A = 8, + X = 9, + L = 10, + R = 11, + }; + + Type types[MAX_CONTROLLERS]; + int latchPin; + int clockPin; + int dataPins[MAX_CONTROLLERS]; + int buttons[MAX_CONTROLLERS][12]; + + ///This has to be initialized once for the shared pins latch and clock + void init(int latch, int clock) + { + latchPin = latch; + clockPin = clock; + pinMode(latchPin, OUTPUT); + digitalWrite(latchPin, LOW); + pinMode(clockPin, OUTPUT); + digitalWrite(clockPin, HIGH); + for(int c = 0; c < MAX_CONTROLLERS; c++) + { + for(int i = 0; i < 12; i++) + buttons[c][i] = -1; + types[c] = NES; + dataPins[c] = -1; + } + } + + ///This sets the controller type and initializes its individual data pin + void setController(int controller, Type type, int dataPin) + { + types[controller] = type; + dataPins[controller] = dataPin; + pinMode(dataPins[controller], INPUT_PULLUP); + } + + void poll() + { + digitalWrite(latchPin, HIGH); + delayMicroseconds(12); + digitalWrite(latchPin, LOW); + delayMicroseconds(6); + for(int i = 0; i < 12; i++) + { + for(int c = 0; c < MAX_CONTROLLERS; c++) + if(dataPins[c] > -1) + { + if(digitalRead(dataPins[c])) + buttons[c][i] = -1; + else + buttons[c][i]++; + } + digitalWrite(clockPin, LOW); + delayMicroseconds(6); + digitalWrite(clockPin, HIGH); + delayMicroseconds(6); + } + } + + int translate(int controller, Button b) const + { + if(types[controller] == SNES) return b; + static const int translateToNES[] = {1, 8, 2, 3, 4, 5, 6, 7, 0, 8, 8, 8}; + return translateToNES[b]; + } + + ///button will be unpressed until released again + void clear(int controller, Button b) + { + buttons[controller][translate(controller, b)] = 0x80000000; + } + + ///returns if button is currently down + bool down(int controller, Button b) const + { + return buttons[controller][translate(controller, b)] >= 0; + } + + ///returns true if button state changed to down since previous poll. repeatAfterTics can be used to repeat after button was hold down for sme time + bool pressed(int controller, Button b, int repeatAfterTics = 0x7fffffff) const + { + return buttons[controller][translate(controller, b)] == 0 || (buttons[controller][translate(controller, b)] >= repeatAfterTics); + } + + ///returns the type of controller configured + Type getType(int controller) + { + return types[controller]; + } +}; + diff --git a/libraries/bitluni_ESP32Lib/src/ESP32Lib.h b/libraries/bitluni_ESP32Lib/src/ESP32Lib.h new file mode 100644 index 0000000..b92a903 --- /dev/null +++ b/libraries/bitluni_ESP32Lib/src/ESP32Lib.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include

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.1 diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/subscriber.py b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/subscriber.py new file mode 100644 index 0000000..b93bdc8 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_arbitrary_data/python_subscriber/subscriber.py @@ -0,0 +1,114 @@ +'''MQTT subscriber for Adafruit MQTT library mqtt_arbitrary_buffer example''' +import paho.mqtt.client as mqtt +import argparse +import struct +import array +import sys + +return_str =[ + "Connection successful", + "incorrect protocol version", + "invalid client identifier", + "server unavailable", + "bad username or password", + "not authorised" + ] + +args = None + +# The callback for when the client receives a CONNACK response from the server. +def on_connect(client, userdata, rc): + """callback function on connect. Subscribes or exits depending on outcome""" + print("MQTT: "), + print(return_str[rc]) + if(rc > 1): + print("Connection refused - " + return_str[rc]) + sys.exit(rc) + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + else: + print(return_str[rc]) + client.subscribe(args.topic) + print("Subscribed to {}".format(args.topic)) + +def on_disconnect(client, userdata, rc): + """Callback for disconnect""" + if rc != 0: + print("Unexpected disconnection.") + client.reconnect() + +# The callback for when a PUBLISH message is received from the server. +def on_message(client, userdata, msg): + try: + pMsg = parseMsg(msg.payload) + print("Received char Array: \"{}\", val1: {}, val2: {}, val3: {}...".format(pMsg[0], pMsg[1], pMsg[2], pMsg[3])) + + except Exception as err: + print err + + + +def argBegin(): + parser = argparse.ArgumentParser(description='MQTT subscriber for Adafruit MQTT library mqtt_arbitrary_buffer example') + parser.add_argument("--host", default="localhost", help='mqtt host to connect to. Defaults to localhost.') + parser.add_argument("-p", "--port", default=1883, help='network port to connect to. Defaults to 1883.') + parser.add_argument("-t", "--topic", nargs='*', default="/feeds/arb_packet", help="mqtt topic to subscribe to. May be repeated multiple times.") + parser.add_argument("-u", "--username", default="testPy", help="provide a username (requires MQTT 3.1 broker)") + parser.add_argument("-P", "--password", default="testPy", help="provide a password (requires MQTT 3.1 broker)") + parser.add_argument("-k", "--keepalive", default=60, help="keep alive in seconds for this client. Defaults to 60.") + + return parser.parse_args() + + +def parseMsg(payload): + """Parses C struct from MQTT publisher Adafruit MQTT client to Python list""" + + arr = array.array('B', payload) #convert python list to C-like array of unsigned char (B) + + parsedStruct = struct.Struct('< 10s h L H') #define struct template (see below) + ''' + Format of Struct from Adafruit MQTT client, Arduino, etc: + + Adafruit MQTT client == Little endian (<) + + Var NAME | C TYPE (python symbol) | size of member x bytes + ------------------------------------------------------------------- + "charAry" | uchar (s) | 10s x 1 = 10 bytes + "val1" | int16 / short (h) | 1h x 2 = 2 bytes + "val2" | unsigned long (L) | 1L x 4 = 4 bytes + "val3" | uint16/unsigned short(H)| 1H x 2 = 2 bytes + ------------------------------------------------------------------ + Total packet size = | 18 bytes | + + See Section 7.3.2 of Python struct module documentation for complete format list + https://docs.python.org/2/library/struct.html + ''' + + charAry, val1, val2, val3 = parsedStruct.unpack_from(arr) #convert byte array to formatted struct + charAry = charAry.rstrip(' \t\r\n\0') #remove trailing white space from buffer + return charAry, val1, val2, val3 + + + + + + + +def main(): + """Wait for incoming message published by Adafruit MQTT client""" + global args + args = argBegin() + client = mqtt.Client() + client.on_connect = on_connect + client.on_message = on_message + client.username_pw_set(args.username, args.password) + client.connect(args.host, args.port, args.keepalive) + + # Blocking call that processes network traffic, dispatches callbacks and + # handles reconnecting. + # Other loop*() functions are available that give a threaded interface and a + # manual interface. + client.loop_forever() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266/mqtt_esp8266.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266/mqtt_esp8266.ino new file mode 100644 index 0000000..a8c3b0e --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266/mqtt_esp8266.ino @@ -0,0 +1,147 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Example + + Must use ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board & Feather + ----> https://www.adafruit.com/product/2471 + ----> https://www.adafruit.com/products/2821 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "...your SSID..." +#define WLAN_PASS "...your password..." + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 // use 8883 for SSL +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + +/************ Global State (you don't need to change this!) ******************/ + +// Create an ESP8266 WiFiClient class to connect to the MQTT server. +WiFiClient client; +// or... use WiFiFlientSecure for SSL +//WiFiClientSecure client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Sketch Code ************************************/ + +// Bug workaround for Arduino 1.6.6, it seems to need a function declaration +// for some reason (only affects ESP8266, likely an arduino-builder bug). +void MQTT_connect(); + +void setup() { + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit MQTT demo")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print("Connecting to "); + Serial.println(WLAN_SSID); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + Serial.println("WiFi connected"); + Serial.println("IP address: "); Serial.println(WiFi.localIP()); + + // Setup MQTT subscription for onoff feed. + mqtt.subscribe(&onoffbutton); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + // try to spend your time here + + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(5000))) { + if (subscription == &onoffbutton) { + Serial.print(F("Got: ")); + Serial.println((char *)onoffbutton.lastread); + } + } + + // Now we can publish stuff! + Serial.print(F("\nSending photocell val ")); + Serial.print(x); + Serial.print("..."); + if (! photocell.publish(x++)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + + // ping the server to keep the mqtt connection alive + // NOT required if you are publishing once every KEEPALIVE seconds + /* + if(! mqtt.ping()) { + mqtt.disconnect(); + } + */ +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266_callback/mqtt_esp8266_callback.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266_callback/mqtt_esp8266_callback.ino new file mode 100644 index 0000000..c089a5a --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_esp8266_callback/mqtt_esp8266_callback.ino @@ -0,0 +1,185 @@ +/*************************************************** + Adafruit MQTT Library ESP8266 Example + + Must use ESP8266 Arduino from: + https://github.com/esp8266/Arduino + + Works great with Adafruit's Huzzah ESP board: + ----> https://www.adafruit.com/product/2471 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* WiFi Access Point *********************************/ + +#define WLAN_SSID "network" +#define WLAN_PASS "password" + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 +#define AIO_USERNAME "user" +#define AIO_KEY "key" + +/************ Global State (you don't need to change this!) ******************/ + +// Create an ESP8266 WiFiClient class to connect to the MQTT server. +WiFiClient client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_USERNAME, AIO_KEY); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'time' for subscribing to current time +Adafruit_MQTT_Subscribe timefeed = Adafruit_MQTT_Subscribe(&mqtt, "time/seconds"); + +// Setup a feed called 'slider' for subscribing to changes on the slider +Adafruit_MQTT_Subscribe slider = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/slider", MQTT_QOS_1); + +// Setup a feed called 'onoff' for subscribing to changes to the button +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff", MQTT_QOS_1); + +/*************************** Sketch Code ************************************/ + +int sec; +int min; +int hour; + +int timeZone = -4; // utc-4 eastern daylight time (nyc) + +void timecallback(uint32_t current) { + + // adjust to local time zone + current += (timeZone * 60 * 60); + + // calculate current time + sec = current % 60; + current /= 60; + min = current % 60; + current /= 60; + hour = current % 24; + + // print hour + if(hour == 0 || hour == 12) + Serial.print("12"); + if(hour < 12) + Serial.print(hour); + else + Serial.print(hour - 12); + + // print mins + Serial.print(":"); + if(min < 10) Serial.print("0"); + Serial.print(min); + + // print seconds + Serial.print(":"); + if(sec < 10) Serial.print("0"); + Serial.print(sec); + + if(hour < 12) + Serial.println(" am"); + else + Serial.println(" pm"); + +} + +void slidercallback(double x) { + Serial.print("Hey we're in a slider callback, the slider value is: "); + Serial.println(x); +} + +void onoffcallback(char *data, uint16_t len) { + Serial.print("Hey we're in a onoff callback, the button value is: "); + Serial.println(data); +} + + +void setup() { + Serial.begin(115200); + delay(10); + + Serial.println(F("Adafruit MQTT demo")); + + // Connect to WiFi access point. + Serial.println(); Serial.println(); + Serial.print("Connecting to "); + Serial.println(WLAN_SSID); + + WiFi.begin(WLAN_SSID, WLAN_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + + Serial.println("WiFi connected"); + Serial.println("IP address: "); Serial.println(WiFi.localIP()); + + timefeed.setCallback(timecallback); + slider.setCallback(slidercallback); + onoffbutton.setCallback(onoffcallback); + + // Setup MQTT subscription for time feed. + mqtt.subscribe(&timefeed); + mqtt.subscribe(&slider); + mqtt.subscribe(&onoffbutton); + +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets and callback em' busy subloop + // try to spend your time here: + mqtt.processPackets(10000); + + // ping the server to keep the mqtt connection alive + // NOT required if you are publishing once every KEEPALIVE seconds + + if(! mqtt.ping()) { + mqtt.disconnect(); + } +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + uint8_t retries = 3; + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 10 seconds..."); + mqtt.disconnect(); + delay(10000); // wait 10 seconds + retries--; + if (retries == 0) { + // basically die and wait for WDT to reset me + while (1); + } + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_ethernet/mqtt_ethernet.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_ethernet/mqtt_ethernet.ino new file mode 100644 index 0000000..a156705 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_ethernet/mqtt_ethernet.ino @@ -0,0 +1,128 @@ +/*************************************************** + Adafruit MQTT Library Ethernet Example + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Alec Moore + Derived from the code written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +#include +#include +#include +#include + +/************************* Ethernet Client Setup *****************************/ +byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; + +//Uncomment the following, and set to a valid ip if you don't have dhcp available. +//IPAddress iotIP (192, 168, 0, 42); +//Uncomment the following, and set to your preference if you don't have automatic dns. +//IPAddress dnsIP (8, 8, 8, 8); +//If you uncommented either of the above lines, make sure to change "Ethernet.begin(mac)" to "Ethernet.begin(mac, iotIP)" or "Ethernet.begin(mac, iotIP, dnsIP)" + + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + + +/************ Global State (you don't need to change this!) ******************/ + +//Set up the ethernet client +EthernetClient client; + +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +// You don't need to change anything below this line! +#define halt(s) { Serial.println(F( s )); while(1); } + + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Sketch Code ************************************/ + +void setup() { + Serial.begin(115200); + + Serial.println(F("Adafruit MQTT demo")); + + // Initialise the Client + Serial.print(F("\nInit the Client...")); + Ethernet.begin(mac); + delay(1000); //give the ethernet a second to initialize + + + mqtt.subscribe(&onoffbutton); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(1000))) { + if (subscription == &onoffbutton) { + Serial.print(F("Got: ")); + Serial.println((char *)onoffbutton.lastread); + } + } + + // Now we can publish stuff! + Serial.print(F("\nSending photocell val ")); + Serial.print(x); + Serial.print("..."); + if (! photocell.publish(x++)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + + // ping the server to keep the mqtt connection alive + if(! mqtt.ping()) { + mqtt.disconnect(); + } + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/fonahelper.cpp b/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/fonahelper.cpp new file mode 100644 index 0000000..822413e --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/fonahelper.cpp @@ -0,0 +1,50 @@ +#include +#include +#include "Adafruit_FONA.h" + +#define halt(s) { Serial.println(F( s )); while(1); } + +extern Adafruit_FONA fona; +extern SoftwareSerial fonaSS; + +boolean FONAconnect(const __FlashStringHelper *apn, const __FlashStringHelper *username, const __FlashStringHelper *password) { + Watchdog.reset(); + + Serial.println(F("Initializing FONA....(May take 3 seconds)")); + + fonaSS.begin(4800); // if you're using software serial + + if (! fona.begin(fonaSS)) { // can also try fona.begin(Serial1) + Serial.println(F("Couldn't find FONA")); + return false; + } + fonaSS.println("AT+CMEE=2"); + Serial.println(F("FONA is OK")); + Watchdog.reset(); + Serial.println(F("Checking for network...")); + while (fona.getNetworkStatus() != 1) { + delay(500); + } + + Watchdog.reset(); + delay(5000); // wait a few seconds to stabilize connection + Watchdog.reset(); + + fona.setGPRSNetworkSettings(apn, username, password); + + Serial.println(F("Disabling GPRS")); + fona.enableGPRS(false); + + Watchdog.reset(); + delay(5000); // wait a few seconds to stabilize connection + Watchdog.reset(); + + Serial.println(F("Enabling GPRS")); + if (!fona.enableGPRS(true)) { + Serial.println(F("Failed to turn GPRS on")); + return false; + } + Watchdog.reset(); + + return true; +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/mqtt_fona.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/mqtt_fona.ino new file mode 100644 index 0000000..5487dfe --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_fona/mqtt_fona.ino @@ -0,0 +1,169 @@ +/*************************************************** + Adafruit MQTT Library FONA Example + + Designed specifically to work with the Adafruit FONA + ----> http://www.adafruit.com/products/1946 + ----> http://www.adafruit.com/products/1963 + ----> http://www.adafruit.com/products/2468 + ----> http://www.adafruit.com/products/2542 + + These cellular modules use TTL Serial 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. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include +#include "Adafruit_FONA.h" +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_FONA.h" + +/*************************** FONA Pins ***********************************/ + +// Default pins for Feather 32u4 FONA +#define FONA_RX 9 +#define FONA_TX 8 +#define FONA_RST 4 +SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX); + +Adafruit_FONA fona = Adafruit_FONA(FONA_RST); + +/************************* WiFi Access Point *********************************/ + + // Optionally configure a GPRS APN, username, and password. + // You might need to do this to access your network's GPRS/data + // network. Contact your provider for the exact APN, username, + // and password values. Username and password are optional and + // can be removed, but APN is required. +#define FONA_APN "" +#define FONA_USERNAME "" +#define FONA_PASSWORD "" + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + +/************ Global State (you don't need to change this!) ******************/ + +// Setup the FONA MQTT class by passing in the FONA class and MQTT server and login details. +Adafruit_MQTT_FONA mqtt(&fona, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +// You don't need to change anything below this line! +#define halt(s) { Serial.println(F( s )); while(1); } + +// FONAconnect is a helper function that sets up the FONA and connects to +// the GPRS network. See the fonahelper.cpp tab above for the source! +boolean FONAconnect(const __FlashStringHelper *apn, const __FlashStringHelper *username, const __FlashStringHelper *password); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Sketch Code ************************************/ + +// How many transmission failures in a row we're willing to be ok with before reset +uint8_t txfailures = 0; +#define MAXTXFAILURES 3 + +void setup() { + while (!Serial); + + // Watchdog is optional! + //Watchdog.enable(8000); + + Serial.begin(115200); + + Serial.println(F("Adafruit FONA MQTT demo")); + + mqtt.subscribe(&onoffbutton); + + Watchdog.reset(); + delay(5000); // wait a few seconds to stabilize connection + Watchdog.reset(); + + // Initialise the FONA module + while (! FONAconnect(F(FONA_APN), F(FONA_USERNAME), F(FONA_PASSWORD))) { + Serial.println("Retrying FONA"); + } + + Serial.println(F("Connected to Cellular!")); + + Watchdog.reset(); + delay(5000); // wait a few seconds to stabilize connection + Watchdog.reset(); +} + +uint32_t x=0; + +void loop() { + // Make sure to reset watchdog every loop iteration! + Watchdog.reset(); + + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + Watchdog.reset(); + // Now we can publish stuff! + Serial.print(F("\nSending photocell val ")); + Serial.print(x); + Serial.print("..."); + if (! photocell.publish(x++)) { + Serial.println(F("Failed")); + txfailures++; + } else { + Serial.println(F("OK!")); + txfailures = 0; + } + + Watchdog.reset(); + // this is our 'wait for incoming subscription packets' busy subloop + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(5000))) { + if (subscription == &onoffbutton) { + Serial.print(F("Got: ")); + Serial.println((char *)onoffbutton.lastread); + } + } + + // ping the server to keep the mqtt connection alive, only needed if we're not publishing + //if(! mqtt.ping()) { + // Serial.println(F("MQTT Ping failed.")); + //} + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_winc1500/mqtt_winc1500.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_winc1500/mqtt_winc1500.ino new file mode 100644 index 0000000..cd962cf --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_winc1500/mqtt_winc1500.ino @@ -0,0 +1,151 @@ +/*************************************************** + Adafruit MQTT Library WINC1500 Example + + 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. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" +#include + +/************************* WiFI Setup *****************************/ +#define WINC_CS 8 +#define WINC_IRQ 7 +#define WINC_RST 4 +#define WINC_EN 2 // or, tie EN to VCC + +char ssid[] = "yournetwork"; // your network SSID (name) +char pass[] = "yourpassword"; // your network password (use for WPA, or use as key for WEP) +int keyIndex = 0; // your network key Index number (needed only for WEP) + +int status = WL_IDLE_STATUS; + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 +#define AIO_USERNAME "adafruitiousername" +#define AIO_KEY "adafruitiokey" + +/************ Global State (you don't need to change this!) ******************/ + +//Set up the wifi client +WiFiClient client; + +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +// You don't need to change anything below this line! +#define halt(s) { Serial.println(F( s )); while(1); } + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Sketch Code ************************************/ + +#define LEDPIN 13 + + +void setup() { + WiFi.setPins(WINC_CS, WINC_IRQ, WINC_RST, WINC_EN); + + while (!Serial); + Serial.begin(115200); + + Serial.println(F("Adafruit MQTT demo for WINC1500")); + + // Initialise the Client + Serial.print(F("\nInit the WiFi module...")); + // check for the presence of the breakout + if (WiFi.status() == WL_NO_SHIELD) { + Serial.println("WINC1500 not present"); + // don't continue: + while (true); + } + Serial.println("ATWINC OK!"); + + pinMode(LEDPIN, OUTPUT); + mqtt.subscribe(&onoffbutton); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(5000))) { + if (subscription == &onoffbutton) { + Serial.print(F("Got: ")); + Serial.println((char *)onoffbutton.lastread); + + if (0 == strcmp((char *)onoffbutton.lastread, "OFF")) { + digitalWrite(LEDPIN, LOW); + } + if (0 == strcmp((char *)onoffbutton.lastread, "ON")) { + digitalWrite(LEDPIN, HIGH); + } + } + } + + // Now we can publish stuff! + Serial.print(F("\nSending photocell val ")); + Serial.print(x); + Serial.print("..."); + if (! photocell.publish(x++)) { + Serial.println(F("Failed")); + } else { + Serial.println(F("OK!")); + } + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // attempt to connect to Wifi network: + while (WiFi.status() != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + status = WiFi.begin(ssid, pass); + + // wait 10 seconds for connection: + uint8_t timeout = 10; + while (timeout && (WiFi.status() != WL_CONNECTED)) { + timeout--; + delay(1000); + } + } + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Serial.print("Connecting to MQTT... "); + + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Serial.println(mqtt.connectErrorString(ret)); + Serial.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + } + Serial.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/examples/mqtt_yun/mqtt_yun.ino b/libraries/Adafruit_MQTT_Library/examples/mqtt_yun/mqtt_yun.ino new file mode 100644 index 0000000..1966629 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/examples/mqtt_yun/mqtt_yun.ino @@ -0,0 +1,116 @@ +/*************************************************** + Adafruit MQTT Library Arduino Yun Example + + Make sure your Arduino Yun is connected to a WiFi access point which + has internet access. Also note this sketch uses the Console class + for debug output so make sure to connect to the Yun over WiFi and + open the serial monitor to see the console output. + + Works great with the Arduino Yun: + ----> https://www.adafruit.com/products/1498 + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Tony DiCola for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ +#include +#include +#include +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" + +/************************* Adafruit.io Setup *********************************/ + +#define AIO_SERVER "io.adafruit.com" +#define AIO_SERVERPORT 1883 +#define AIO_USERNAME "...your AIO username (see https://accounts.adafruit.com)..." +#define AIO_KEY "...your AIO key..." + + +/************ Global State (you don't need to change this!) ******************/ + +// Create a BridgeClient instance to communicate using the Yun's bridge & Linux OS. +BridgeClient client; + +// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details. +Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); + +/****************************** Feeds ***************************************/ + +// Setup a feed called 'photocell' for publishing. +// Notice MQTT paths for AIO follow the form: /feeds/ +Adafruit_MQTT_Publish photocell = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/photocell"); + +// Setup a feed called 'onoff' for subscribing to changes. +Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/onoff"); + +/*************************** Sketch Code ************************************/ + +void setup() { + Bridge.begin(); + Console.begin(); + Console.println(F("Adafruit MQTT demo")); + + // Setup MQTT subscription for onoff feed. + mqtt.subscribe(&onoffbutton); +} + +uint32_t x=0; + +void loop() { + // Ensure the connection to the MQTT server is alive (this will make the first + // connection and automatically reconnect when disconnected). See the MQTT_connect + // function definition further below. + MQTT_connect(); + + // this is our 'wait for incoming subscription packets' busy subloop + Adafruit_MQTT_Subscribe *subscription; + while ((subscription = mqtt.readSubscription(1000))) { + if (subscription == &onoffbutton) { + Console.print(F("Got: ")); + Console.println((char *)onoffbutton.lastread); + } + } + + // Now we can publish stuff! + Console.print(F("\nSending photocell val ")); + Console.print(x); + Console.print("..."); + if (! photocell.publish(x++)) { + Console.println(F("Failed")); + } else { + Console.println(F("OK!")); + } + + // ping the server to keep the mqtt connection alive + if(! mqtt.ping()) { + Console.println(F("MQTT Ping failed.")); + } + + delay(1000); + +} + +// Function to connect and reconnect as necessary to the MQTT server. +// Should be called in the loop function and it will take care if connecting. +void MQTT_connect() { + int8_t ret; + + // Stop if already connected. + if (mqtt.connected()) { + return; + } + + Console.print("Connecting to MQTT... "); + + while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected + Console.println(mqtt.connectErrorString(ret)); + Console.println("Retrying MQTT connection in 5 seconds..."); + mqtt.disconnect(); + delay(5000); // wait 5 seconds + } + Console.println("MQTT Connected!"); +} diff --git a/libraries/Adafruit_MQTT_Library/keywords.txt b/libraries/Adafruit_MQTT_Library/keywords.txt new file mode 100644 index 0000000..22809c7 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/keywords.txt @@ -0,0 +1,20 @@ +Adafruit_MQTT KEYWORD1 +Adafruit_MQTT_FONA KEYWORD1 +Adafruit_MQTT_Client KEYWORD1 +Adafruit_MQTT_Publish KEYWORD1 +Adafruit_MQTT_Subscribe KEYWORD1 +connect KEYWORD2 +connectErrorString KEYWORD2 +disconnect KEYWORD2 +connected KEYWORD2 +will KEYWORD2 +publish KEYWORD2 +subscribe KEYWORD2 +unsubscribe KEYWORD2 +readSubscription KEYWORD2 +ping KEYWORD2 +setCallback KEYWORD2 +connectServer KEYWORD2 +disconnectServer KEYWORD2 +readPacket KEYWORD2 +sendPacket KEYWORD2 diff --git a/libraries/Adafruit_MQTT_Library/library.properties b/libraries/Adafruit_MQTT_Library/library.properties new file mode 100644 index 0000000..5953cd7 --- /dev/null +++ b/libraries/Adafruit_MQTT_Library/library.properties @@ -0,0 +1,9 @@ +name=Adafruit MQTT Library +version=1.0.3 +author=Adafruit +maintainer=Adafruit +sentence=MQTT library that supports the FONA, ESP8266, Yun, and generic Arduino Client hardware. +paragraph=Simple MQTT library that supports the bare minimum to publish and subscribe to topics. +category=Communication +url=https://github.com/adafruit/Adafruit_MQTT_Library +architectures=* diff --git a/libraries/ArduinoOTA/ArduinoOTA.png b/libraries/ArduinoOTA/ArduinoOTA.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa86b1ba1e8895d8ebb1ce03a7b9be081fa6c16 GIT binary patch literal 17226 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 + 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! diff --git a/libraries/ArduinoOTA/README.md b/libraries/ArduinoOTA/README.md new file mode 100644 index 0000000..cb3ad68 --- /dev/null +++ b/libraries/ArduinoOTA/README.md @@ -0,0 +1,108 @@ + +# Arduino library to upload sketch over network to supported Arduino board + +This library allows you to update sketches on your board over WiFi or Ethernet. + +The library is a modification of the Arduino WiFi101OTA library. + +![network port in IDE](ArduinoOTA.png) + +## Supported micro-controllers + +* ATmega AVR with at least 64 kB of flash (Arduino Mega, [MegaCore](https://github.com/MCUdude/MegaCore) MCUs, MightyCore 1284p and 644) +* Arduino SAMD boards like Zero, M0 or MKR +* nRF5 board supported by [nRF5 core](https://github.com/sandeepmistry/arduino-nRF5). +* boards supported by ESP8266 and ESP32 Arduino boards package + +## Supported networking libraries + +* Ethernet library - shields and modules with Wiznet 5100, 5200 and 5500 chips +* WiFi101 - WiFi101 shield and MKR 1000 +* WiFiNINA - MKR 1010, MKR 4000, ESP32 as SPI network adapter with WiFiNINA firmware +* WiFiLink - esp8266 as network adapter with WiFiLink firmware (SPI or Serial) +* UIPEthernet - shields and modules with ENC28j60 chip +* WiFiSpi - esp8266 as SPI network adapter with WiFiSpi library updated with [this PR](https://github.com/JiriBilek/WiFiSpi/pull/12) +* WiFi - Arduino WiFi Shield (not tested) +* WiFi library of ESP8266 and ESP32 Arduino boards package + +UIPEthernet, WiFiSpi and WiFi library don't support UDP multicast for MDNS, so Arduino IDE will not show the network upload port. WiFiLink doesn't support UDP multicast, but the firmware propagates the MDNS record. + +WiFiEsp library for esp8266 with AT firmware failed tests and there is no easy fix. + +## Installation + +The library is in Library Manager of the Arduino IDE. + +Arduino SAMD boards (Zero, M0, MKR) are supported 'out of the box'. + +For nRF5 boards two lines need to be added to platform.txt file of the nRF5 Arduino package (until the PR that adds them is accepted and included in a release). Only nRF51 was tested until now. For details scroll down. + +For ESP8266 and ESP32 boards, platform.local.txt from extras folder need to be copied into boards package installation folder and the bundled ArduinoOTA library must be deleted. For details scroll down. + +ATmega boards require to flash a modified Optiboot bootloader for flash write operations. Details are below. + +## ATmega support + +The size of networking library and SD library limit the use of ArduinoOTA library to ATmega MCUs with at least 64 kB flash memory. + +*There are other network upload options for here excluded ATmega328p: ([Ariadne bootloader](https://github.com/loathingKernel/ariadne-bootloader) for Wiznet chips, [WiFiLink firmware](https://github.com/jandrassy/arduino-firmware-wifilink) for the esp8266).* + +For upload over InternalStorage, Optiboot bootloader with [`copy_flash_pages` function](https://github.com/Optiboot/optiboot/pull/269) is required. Arduino AVR package doesn't use Optiboot for Arduino Mega. For Arduino Mega you can download [my boards definitions](https://github.com/jandrassy/my_boards) and use it [to burn](https://arduino.stackexchange.com/questions/473/how-do-i-burn-the-bootloader) the modified Optiboot and to upload sketches to your Mega over USB and over network. + +For SDStorage a 'SD bootloader' is required to load the uploaded file from the SD card. There is no good SD bootloader. 2boots works only with not available old types of SD cards and zevero/avr_boot doesn't yet support USB upload of sketch. The SDStorage was tested with zevero/avr_boot. The ATmega_SD example shows how to use this ArduinoOTA library with SD bootloader. + +To use remote upload from IDE with SDStorage or InternalStorage, copy platform.local.txt from extras/avr folder, next to platform.txt in the boards package used (Arduino-avr or MCUdude packages). Packages are located in ~/.arduino15/packages/ on Linux and %userprofile%\AppData\Local\Arduino15\packages\ on Windows (AppData is a hidden folder). The platform.local.txt contains a line to create a .bin file and a line to override tools.avrdude.upload.network_pattern, because in platform.txt it is defined for Yun. + +The IDE upload tool is installed with Arduino AVR core package. At least version 1.2 of the arduinoOTA tool is required. For upload from command line without IDE see the command template in extras/avr/platform.local.txt. + +## ESP8266 and ESP32 support + +The ArduinoOTA library bundled with ESP8266 and ESP32 Arduino packages works only with native WiFi libraries. This library allows to upload a sketch to esp8266 or esp32 over Ethernet with Ethernet or UIPEthernet library. Upload over the native WiFi library works too. + +To use this library instead of the bundled library, the bundled library must be removed from the boards package library folder. To override the configuration of OTA upload in platform.txt, copy the platform.local.txt file from extras folder of this library next to platform.txt file in boards package installation folder. Packages are located in ~/.arduino15/packages/ on Linux and %userprofile%\AppData\Local\Arduino15\packages\ on Windows (AppData is a hidden folder). + +The esp8266 boards package has bundled Ethernet library. It is an old version of the Arduino Ethernet library which works only with W5100 chips. It is better to remove it from boards package and install the Ethernet library from Library Manager in IDE. + +This library supports SPIFFS upload to esp8266 and esp32, but the IDE plugins have the network upload tool hardcoded to espota. It can't be changed in configuration. To upload SPIFFS, call the plugin in Tools menu and after it fails to upload over network, go to location of the created bin file and upload the file with arduinoOTA tool from command line. The location of the file is printed in the IDE console window. Upload command example (linux): +``` +~/arduino-1.8.8/hardware/tools/avr/bin/arduinoOTA -address 192.168.1.107 -port 65280 -username arduino -password password -sketch OTEthernet.spiffs.bin -upload /data -b +``` +(the same command can be used to upload the sketch binary, only use `-upload /sketch`) + +## nRF5 support + +Note: Only nRF51 was tested for now + +If SoftDevice is not used, the sketch is written from address 0. For write to address 0 the sketch must be compiled with -fno-delete-null-pointer-checks. + +For SD card update use [SDUnRF5 library](https://github.com/jandrassy/SDUnRF5). + +To use remote upload from IDE, add lines from [this PR](https://github.com/sandeepmistry/arduino-nRF5/pull/327/commits/4b70ae7207124bd92afa14e562e4f0c4d931220d) to sandeepmistry/hardware/nRF5/0.6.0/platform.txt at the end of section "OpenOCD sketch upload": + +If you use SoftDevice, stop BLE before applying update. Use `ArduinoOTA.beforeApply` to register a callback function. For example in setup `ArduinoOTA.beforeApply(shutdown);` and add the function to to sketch: + +``` +void shutdown() { + blePeripheral.end(); +} +``` +## Boards tested + +* SAMD + - Arduino MKR Zero + - Crowduino M0 SD +* nRF5 + - Seeed Arch Link +* ATmega + - Arduino Mega + - Badio 1284p +* esp8266 + - Wemos D1 mini +* esp32 + - ESP32 Dev Module + +## Contribution + +Please report tested boards. + +Other ARM based MCUs could be added with code similar to SAMD and nRF5. diff --git a/libraries/ArduinoOTA/examples/ATmega_SD/ATmega_SD.ino b/libraries/ArduinoOTA/examples/ATmega_SD/ATmega_SD.ino new file mode 100644 index 0000000..4d0acda --- /dev/null +++ b/libraries/ArduinoOTA/examples/ATmega_SD/ATmega_SD.ino @@ -0,0 +1,64 @@ +/* + + This example polls for sketch updates over Ethernet, sketches + can be updated by selecting a network port from within + the Arduino IDE: Tools -> Port -> Network Ports ... + + Circuit: + * W5100, W5200 or W5500 Ethernet shield with SD card attached + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 16 January 2017 + by Sandeep Mistry + ArduinoOTA version Dec 2018 + by Juraj Andrassy + */ + +#include +#include +#include +#include + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +void setup() { + //Initialize serial: + Serial.begin(9600); + while (!Serial); + + // setup SD card + Serial.print("Initializing SD card..."); + if (!SD.begin(SDCARD_SS_PIN)) { + Serial.println("initialization failed!"); + // don't continue: + while (true); + } + Serial.println("initialization done."); + + // start the Ethernet connection: + Serial.println("Initialize Ethernet with DHCP:"); + if (Ethernet.begin(mac) == 0) { + Serial.println("Failed to configure Ethernet using DHCP"); + } else { + Serial.print(" DHCP assigned IP "); + Serial.println(Ethernet.localIP()); + } + + // start the OTEthernet library with SD based storage + SDStorage.setUpdateFileName("FIRMWARE.BIN"); // for https://github.com/zevero/avr_boot/ + SDStorage.clear(); // AVR SD bootloaders don't delete the update file + ArduinoOTA.begin(Ethernet.localIP(), "Arduino", "password", SDStorage); + +} + +void loop() { + // check for updates + ArduinoOTA.poll(); + + // add your normal loop code below ... +} diff --git a/libraries/ArduinoOTA/examples/OTEthernet/OTEthernet.ino b/libraries/ArduinoOTA/examples/OTEthernet/OTEthernet.ino new file mode 100644 index 0000000..88c083d --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTEthernet/OTEthernet.ino @@ -0,0 +1,53 @@ +/* + + This example polls for sketch updates over Ethernet, sketches + can be updated by selecting a network port from within + the Arduino IDE: Tools -> Port -> Network Ports ... + + Circuit: + * W5100, W5200 or W5500 Ethernet shield attached + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 16 January 2017 + by Sandeep Mistry + Ethernet version August 2018 + by Juraj Andrassy + */ + +#include +#include +#include + +//#define Serial SerialUSB + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +void setup() { + //Initialize serial: + Serial.begin(9600); + while (!Serial); + + // start the Ethernet connection: + Serial.println("Initialize Ethernet with DHCP:"); + if (Ethernet.begin(mac) == 0) { + Serial.println("Failed to configure Ethernet using DHCP"); + } else { + Serial.print(" DHCP assigned IP "); + Serial.println(Ethernet.localIP()); + } + + // start the OTEthernet library with internal (flash) based storage + ArduinoOTA.begin(Ethernet.localIP(), "Arduino", "password", InternalStorage); +} + +void loop() { + // check for updates + ArduinoOTA.poll(); + + // add your normal loop code below ... +} diff --git a/libraries/ArduinoOTA/examples/OTEthernet_SD/OTEthernet_SD.ino b/libraries/ArduinoOTA/examples/OTEthernet_SD/OTEthernet_SD.ino new file mode 100644 index 0000000..64bc3ea --- /dev/null +++ b/libraries/ArduinoOTA/examples/OTEthernet_SD/OTEthernet_SD.ino @@ -0,0 +1,65 @@ +/* + + This example polls for sketch updates over Ethernet, sketches + can be updated by selecting a network port from within + the Arduino IDE: Tools -> Port -> Network Ports ... + + Circuit: + * W5100, W5200 or W5500 Ethernet shield with SD card attached + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 16 January 2017 + by Sandeep Mistry + Ethernet version August 2018 + by Juraj Andrassy + */ + +#include +#include +#include +#include +#include + +//#define Serial SerialUSB + +// Enter a MAC address for your controller below. +// Newer Ethernet shields have a MAC address printed on a sticker on the shield +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; + +void setup() { + //Initialize serial: + Serial.begin(9600); + while (!Serial); + + // setup SD card + Serial.print("Initializing SD card..."); + if (!SD.begin(SDCARD_SS_PIN)) { + Serial.println("initialization failed!"); + // don't continue: + while (true); + } + Serial.println("initialization done."); + + // start the Ethernet connection: + Serial.println("Initialize Ethernet with DHCP:"); + if (Ethernet.begin(mac) == 0) { + Serial.println("Failed to configure Ethernet using DHCP"); + } else { + Serial.print(" DHCP assigned IP "); + Serial.println(Ethernet.localIP()); + } + + // start the OTEthernet library with SD based storage + ArduinoOTA.begin(Ethernet.localIP(), "Arduino", "password", SDStorage); + +} + +void loop() { + // check for updates + ArduinoOTA.poll(); + + // add your normal loop code below ... +} diff --git a/libraries/ArduinoOTA/examples/WiFi101_OTA/WiFi101_OTA.ino b/libraries/ArduinoOTA/examples/WiFi101_OTA/WiFi101_OTA.ino new file mode 100644 index 0000000..0a18fa9 --- /dev/null +++ b/libraries/ArduinoOTA/examples/WiFi101_OTA/WiFi101_OTA.ino @@ -0,0 +1,81 @@ +/* + + This example connects to an WPA encrypted WiFi network. + Then it prints the MAC address of the Wifi shield, + the IP address obtained, and other network details. + It then polls for sketch updates over WiFi, sketches + can be updated by selecting a network port from within + the Arduino IDE: Tools -> Port -> Network Ports ... + + Circuit: + * WiFi shield attached + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 16 January 2017 + by Sandeep Mistry + */ + +#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; // your network SSID (name) +char pass[] = SECRET_PASS; // your network password + +int status = WL_IDLE_STATUS; + +void setup() { + //Initialize serial: + Serial.begin(9600); + + // check for the presence of the shield: + if (WiFi.status() == WL_NO_SHIELD) { + Serial.println("WiFi shield not present"); + // don't continue: + while (true); + } + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + status = WiFi.begin(ssid, pass); + } + + // start the WiFi OTA library with internal (flash) based storage + ArduinoOTA.begin(WiFi.localIP(), "Arduino", "password", InternalStorage); + + // you're connected now, so print out the status: + printWifiStatus(); +} + +void loop() { + // check for WiFi OTA updates + ArduinoOTA.poll(); + + // add your normal loop code below ... +} + +void printWifiStatus() { + // 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); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); +} diff --git a/libraries/ArduinoOTA/examples/WiFi101_OTA/arduino_secrets.h b/libraries/ArduinoOTA/examples/WiFi101_OTA/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/libraries/ArduinoOTA/examples/WiFi101_OTA/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/WiFi101_SD_OTA.ino b/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/WiFi101_SD_OTA.ino new file mode 100644 index 0000000..a7a3e33 --- /dev/null +++ b/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/WiFi101_SD_OTA.ino @@ -0,0 +1,93 @@ +/* + + This example connects to an WPA encrypted WiFi network. + Then it prints the MAC address of the Wifi shield, + the IP address obtained, and other network details. + It then polls for sketch updates over WiFi, sketches + can be updated by selecting a network port from within + the Arduino IDE: Tools -> Port -> Network Ports ... + + Circuit: + * WiFi shield attached + * SD shield attached + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 16 January 2017 + by Sandeep Mistry + */ + +#include +#include +#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; // your network SSID (name) +char pass[] = SECRET_PASS; // your network password + +int status = WL_IDLE_STATUS; + +void setup() { + //Initialize serial: + Serial.begin(9600); + + // setup SD card + Serial.print("Initializing SD card..."); + if (!SD.begin(SDCARD_SS_PIN)) { + Serial.println("initialization failed!"); + // don't continue: + while (true); + } + Serial.println("initialization done."); + + // check for the presence of the shield: + if (WiFi.status() == WL_NO_SHIELD) { + Serial.println("WiFi shield not present"); + // don't continue: + while (true); + } + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + status = WiFi.begin(ssid, pass); + } + + // start the WiFi OTA library with SD based storage + ArduinoOTA.begin(WiFi.localIP(), "Arduino", "password", SDStorage); + + // you're connected now, so print out the status: + printWifiStatus(); +} + +void loop() { + // check for WiFi OTA updates + ArduinoOTA.poll(); + + // add your normal loop code below ... +} + +void printWifiStatus() { + // 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); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); +} diff --git a/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/arduino_secrets.h b/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/arduino_secrets.h new file mode 100644 index 0000000..a8ff904 --- /dev/null +++ b/libraries/ArduinoOTA/examples/WiFi101_SD_OTA/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" + diff --git a/libraries/ArduinoOTA/extras/avr/platform.local.txt b/libraries/ArduinoOTA/extras/avr/platform.local.txt new file mode 100644 index 0000000..1794fef --- /dev/null +++ b/libraries/ArduinoOTA/extras/avr/platform.local.txt @@ -0,0 +1,9 @@ + +# This configuration file supports the general ArduinoOTA library https://github.com/jandrassy/ArduinoOTA + +## Create output (bin file) +recipe.objcopy.bin.pattern="{compiler.path}{compiler.elf2hex.cmd}" -O binary {compiler.elf2hex.extra_flags} "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin" + +tools.avrdude.upload.network_pattern="{network_cmd}" -address {serial.port} -port 65280 -username arduino -password "{network.password}" -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b + + diff --git a/libraries/ArduinoOTA/extras/esp32/platform.local.txt b/libraries/ArduinoOTA/extras/esp32/platform.local.txt new file mode 100644 index 0000000..5e0c723 --- /dev/null +++ b/libraries/ArduinoOTA/extras/esp32/platform.local.txt @@ -0,0 +1,6 @@ + +# This configuration file supports the general ArduinoOTA library https://github.com/jandrassy/ArduinoOTA + +tools.esptool_py.network_cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA +tools.esptool_py.upload.network_pattern="{network_cmd}" -address {serial.port} -port 65280 -username arduino -password "{network.password}" -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b + diff --git a/libraries/ArduinoOTA/extras/esp8266/platform.local.txt b/libraries/ArduinoOTA/extras/esp8266/platform.local.txt new file mode 100644 index 0000000..a39c67e --- /dev/null +++ b/libraries/ArduinoOTA/extras/esp8266/platform.local.txt @@ -0,0 +1,6 @@ + +# This configuration file supports the general ArduinoOTA library https://github.com/jandrassy/ArduinoOTA + +tools.esptool.network_cmd={runtime.tools.arduinoOTA.path}/bin/arduinoOTA +tools.esptool.upload.network_pattern="{network_cmd}" -address {serial.port} -port 65280 -username arduino -password "{network.password}" -sketch "{build.path}/{build.project_name}.bin" -upload /sketch -b + diff --git a/libraries/ArduinoOTA/keywords.txt b/libraries/ArduinoOTA/keywords.txt new file mode 100644 index 0000000..bc838be --- /dev/null +++ b/libraries/ArduinoOTA/keywords.txt @@ -0,0 +1,24 @@ +####################################### +# Syntax Coloring Map For WiFi101OTA +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ArduinoOTA KEYWORD1 + +InternalStorage KEYWORD1 +SDStorage KEYWORD1 +SerialFlashStorage KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +poll KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/ArduinoOTA/library.properties b/libraries/ArduinoOTA/library.properties new file mode 100644 index 0000000..3ffe218 --- /dev/null +++ b/libraries/ArduinoOTA/library.properties @@ -0,0 +1,10 @@ +name=ArduinoOTA +version=1.0.1 +author=Arduino,Juraj Andrassy +maintainer=Juraj Andrassy +sentence=Upload sketch over network to Arduino board with WiFi or Ethernet libraries +paragraph=Based on WiFi101OTA library. Uploads over Ethernet, UIPEthernet, WiFi101, WiFiNina, WiFiLink, WiFi to SAMD, nRF5, esp8266, esp32 and to ATmega with more then 64 kB flash memory. +category=Other +url=https://github.com/jandrassy/ArduinoOTA +architectures=* +includes=ArduinoOTA.h diff --git a/libraries/ArduinoOTA/src/ArduinoOTA.h b/libraries/ArduinoOTA/src/ArduinoOTA.h new file mode 100644 index 0000000..dbb4f41 --- /dev/null +++ b/libraries/ArduinoOTA/src/ArduinoOTA.h @@ -0,0 +1,112 @@ +/* + Copyright (c) 2018 Juraj Andrassy + + 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 _ARDUINOOTA_H_ +#define _ARDUINOOTA_H_ + +#include "WiFiOTA.h" +#ifdef __AVR__ +#if FLASHEND >= 0xFFFF +#include "InternalStorageAVR.h" +#endif +#elif defined(ESP8266) || defined(ESP32) +#include "InternalStorageESP.h" +#else +#include "InternalStorage.h" +#endif +#ifdef __SD_H__ +#include "SDStorage.h" +SDStorageClass SDStorage; +#endif +#ifdef SerialFlash_h_ +#include "SerialFlashStorage.h" +SerialFlashStorageClass SerialFlashStorage; +#endif + + +template +class ArduinoOTAClass : public WiFiOTAClass { + +private: + NetServer server; + +public: + ArduinoOTAClass() : server(65280) {}; + + void begin(IPAddress localIP, const char* name, const char* password, OTAStorage& storage) { + WiFiOTAClass::begin(localIP, name, password, storage); + server.begin(); + } + + void poll() { + NetClient client = server.available(); + pollServer(client); + } + + void handle() { // alias + poll(); + } + +}; + +template +class ArduinoOTAMdnsClass : public ArduinoOTAClass { + +private: + NetUDP mdnsSocket; + +public: + ArduinoOTAMdnsClass() {}; + + void begin(IPAddress localIP, const char* name, const char* password, OTAStorage& storage) { + ArduinoOTAClass::begin(localIP, name, password, storage); +#if defined(ESP8266) && !(defined(ethernet_h_) || defined(ethernet_h) || defined(UIPETHERNET_H)) + mdnsSocket.beginMulticast(localIP, IPAddress(224, 0, 0, 251), 5353); +#else + mdnsSocket.beginMulticast(IPAddress(224, 0, 0, 251), 5353); +#endif + } + + void poll() { + ArduinoOTAClass::poll(); + WiFiOTAClass::pollMdns(mdnsSocket); + } + +}; + +#if defined(ethernet_h_) || defined(ethernet_h) // Ethernet library +ArduinoOTAMdnsClass ArduinoOTA; + +#elif defined(UIPETHERNET_H) // no UDP multicast implementation yet +ArduinoOTAClass ArduinoOTA; + +#elif defined(WiFiNINA_h) || defined(WIFI_H) || defined(ESP8266) || defined(ESP32) // NINA, WiFi101 and Espressif WiFi +#include +ArduinoOTAMdnsClass ArduinoOTA; + +#elif defined(WiFi_h) // WiFi and WiFiLink lib (no UDP multicast), for WiFiLink firmware handles mdns +ArduinoOTAClass ArduinoOTA; + +#elif defined(_WIFISPI_H_INCLUDED) // no UDP multicast implementation +ArduinoOTAClass ArduinoOTA; + +#else +#error "Network library not included or not supported" +#endif + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorage.cpp b/libraries/ArduinoOTA/src/InternalStorage.cpp new file mode 100644 index 0000000..e71a09a --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorage.cpp @@ -0,0 +1,159 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 + + WiFi101OTA version Feb 2017 + by Sandeep Mistry (Arduino) + modified for ArduinoOTA Dec 2018 + by Juraj Andrassy +*/ + +#if !defined(__AVR__) && !defined(ESP8266) && !defined(ESP32) + +#include + +#include "InternalStorage.h" + +InternalStorageClass::InternalStorageClass() : + MAX_PARTIONED_SKETCH_SIZE((MAX_FLASH - SKETCH_START_ADDRESS) / 2), + STORAGE_START_ADDRESS(SKETCH_START_ADDRESS + MAX_PARTIONED_SKETCH_SIZE) +{ + _writeIndex = 0; + _writeAddress = nullptr; +} + +extern "C" { + // these functions must be in RAM (.data) and NOT inlined + // as they erase and copy the sketch data in flash + + __attribute__ ((long_call, noinline, section (".data#"))) // + void waitForReady() { +#if defined(ARDUINO_ARCH_SAMD) + while (!NVMCTRL->INTFLAG.bit.READY); +#elif defined(ARDUINO_ARCH_NRF5) + while (NRF_NVMC->READY == NVMC_READY_READY_Busy); +#endif + } + + __attribute__ ((long_call, noinline, section (".data#"))) + static void eraseFlash(int address, int length, int pageSize) + { +#if defined(ARDUINO_ARCH_SAMD) + int rowSize = pageSize * 4; + for (int i = 0; i < length; i += rowSize) { + NVMCTRL->ADDR.reg = ((uint32_t)(address + i)) / 2; + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + + waitForReady(); + } +#elif defined(ARDUINO_ARCH_NRF5) + // Enable erasing flash + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos; + + // Erase page(s) + int end_address = address + length; + while (address < end_address) { + waitForReady(); + // Erase one 1k/4k page + NRF_NVMC->ERASEPAGE = address; + address = address + pageSize; + } + // Disable erasing, enable write + waitForReady(); + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos; + waitForReady(); +#endif + } + + __attribute__ ((long_call, noinline, section (".data#"))) + static void copyFlashAndReset(int dest, int src, int length, int pageSize) + { + uint32_t* d = (uint32_t*)dest; + uint32_t* s = (uint32_t*)src; + + eraseFlash(dest, length, pageSize); + + for (int i = 0; i < length; i += 4) { + *d++ = *s++; + + waitForReady(); + } + + NVIC_SystemReset(); + } +} + +int InternalStorageClass::open(int length) +{ + (void)length; + _writeIndex = 0; + _writeAddress = (uint32_t*)STORAGE_START_ADDRESS; + +#ifdef ARDUINO_ARCH_SAMD + // enable auto page writes + NVMCTRL->CTRLB.bit.MANW = 0; +#endif + + eraseFlash(STORAGE_START_ADDRESS, MAX_PARTIONED_SKETCH_SIZE, PAGE_SIZE); + + return 1; +} + +size_t InternalStorageClass::write(uint8_t b) +{ + _addressData.u8[_writeIndex] = b; + _writeIndex++; + + if (_writeIndex == 4) { + _writeIndex = 0; + + *_writeAddress = _addressData.u32; + + _writeAddress++; + + waitForReady(); + } + + return 1; +} + +void InternalStorageClass::close() +{ + while ((int)_writeAddress % PAGE_SIZE) { + write(0xff); + } +} + +void InternalStorageClass::clear() +{ +} + +void InternalStorageClass::apply() +{ + // disable interrupts, as vector table will be erase during flash sequence + noInterrupts(); + + copyFlashAndReset(SKETCH_START_ADDRESS, STORAGE_START_ADDRESS, MAX_PARTIONED_SKETCH_SIZE, PAGE_SIZE); +} + +long InternalStorageClass::maxSize() +{ + return MAX_PARTIONED_SKETCH_SIZE; +} + +InternalStorageClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorage.h b/libraries/ArduinoOTA/src/InternalStorage.h new file mode 100644 index 0000000..1e055de --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorage.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 + + WiFi101OTA version Feb 2017 + by Sandeep Mistry (Arduino) + modified for ArduinoOTA Dec 2018 + by Juraj Andrassy +*/ + +#ifndef _INTERNAL_STORAGE_H_INCLUDED +#define _INTERNAL_STORAGE_H_INCLUDED + +#include "OTAStorage.h" + +class InternalStorageClass : public OTAStorage { +public: + + InternalStorageClass(); + + virtual int open(int length); + virtual size_t write(uint8_t); + virtual void close(); + virtual void clear(); + virtual void apply(); + virtual long maxSize(); + +private: + const uint32_t MAX_PARTIONED_SKETCH_SIZE, STORAGE_START_ADDRESS; + + union { + uint32_t u32; + uint8_t u8[4]; + } _addressData; + + int _writeIndex; + uint32_t* _writeAddress; +}; + +extern InternalStorageClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorageAVR.cpp b/libraries/ArduinoOTA/src/InternalStorageAVR.cpp new file mode 100644 index 0000000..628c0ea --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorageAVR.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2018 Juraj Andrassy + + 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 + +#if defined(__AVR__) && FLASHEND >= 0xFFFF + +#include "InternalStorageAVR.h" +#include "utility/optiboot.h" + +InternalStorageAVRClass::InternalStorageAVRClass() { + maxSketchSize = (MAX_FLASH - bootloaderSize) / 2; + maxSketchSize = (maxSketchSize / SPM_PAGESIZE) * SPM_PAGESIZE; // align to page + pageAddress = maxSketchSize; + pageIndex = 0; +} + +int InternalStorageAVRClass::open(int length) { + (void)length; + pageAddress = maxSketchSize; + pageIndex = 0; + return 1; +} + +size_t InternalStorageAVRClass::write(uint8_t b) { + if (pageIndex == 0) { + optiboot_page_erase(pageAddress); + } + dataWord.u8[pageIndex % 2] = b; + if (pageIndex % 2) { + optiboot_page_fill(pageAddress + pageIndex, dataWord.u16); + } + pageIndex++; + if (pageIndex == SPM_PAGESIZE) { + optiboot_page_write(pageAddress); + pageIndex = 0; + pageAddress += SPM_PAGESIZE; + } + return 1; +} + +void InternalStorageAVRClass::close() { + if (pageIndex) { + optiboot_page_write(pageAddress); + } + pageIndex = 0; +} + +void InternalStorageAVRClass::clear() { +} + +void InternalStorageAVRClass::apply() { + copy_flash_pages_cli(SKETCH_START_ADDRESS, maxSketchSize, (pageAddress - maxSketchSize) / SPM_PAGESIZE + 1, true); +} + +long InternalStorageAVRClass::maxSize() { + return maxSketchSize; +} + +InternalStorageAVRClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorageAVR.h b/libraries/ArduinoOTA/src/InternalStorageAVR.h new file mode 100644 index 0000000..6cea26a --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorageAVR.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2018 Juraj Andrassy + + 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 _INTERNAL_STORAGE_AVR_H_INCLUDED +#define _INTERNAL_STORAGE_AVR_H_INCLUDED + +#include "OTAStorage.h" + +class InternalStorageAVRClass : public OTAStorage { +public: + + InternalStorageAVRClass(); + + virtual int open(int length); + virtual size_t write(uint8_t); + virtual void close(); + virtual void clear(); + virtual void apply(); + virtual long maxSize(); + +private: + uint32_t maxSketchSize; + uint32_t pageAddress; + uint16_t pageIndex; + + union { + uint16_t u16; + uint8_t u8[2]; + } dataWord; +}; + +extern InternalStorageAVRClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorageESP.cpp b/libraries/ArduinoOTA/src/InternalStorageESP.cpp new file mode 100644 index 0000000..bdb4d04 --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorageESP.cpp @@ -0,0 +1,64 @@ +/* + Copyright (c) 2019 Juraj Andrassy. 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 +*/ + +#if defined(ESP8266) || defined(ESP32) + +#include + +#ifdef ESP32 +#include +#endif + +#include "InternalStorageESP.h" + +InternalStorageESPClass::InternalStorageESPClass() +{ +} + +int InternalStorageESPClass::open(int length, uint8_t command) +{ + return Update.begin(length, command == 0 ? U_FLASH : U_SPIFFS); +} + +size_t InternalStorageESPClass::write(uint8_t b) +{ + return Update.write(&b, 1); +} + +void InternalStorageESPClass::close() +{ + Update.end(false); +} + +void InternalStorageESPClass::clear() +{ +} + +void InternalStorageESPClass::apply() +{ + ESP.restart(); +} + +long InternalStorageESPClass::maxSize() +{ + return ESP.getFlashChipSize() / 2; // Update.begin() in open() does the exact check +} + +InternalStorageESPClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/InternalStorageESP.h b/libraries/ArduinoOTA/src/InternalStorageESP.h new file mode 100644 index 0000000..2c63cc0 --- /dev/null +++ b/libraries/ArduinoOTA/src/InternalStorageESP.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2019 Juraj Andrassy. 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 _INTERNAL_STORAGE_ESP_H_INCLUDED +#define _INTERNAL_STORAGE_ESP_H_INCLUDED + +#include "OTAStorage.h" + +class InternalStorageESPClass : public OTAStorage { +public: + + InternalStorageESPClass(); + + virtual int open(int length) { + return open(length, 0); + } + virtual int open(int length, uint8_t command); + virtual size_t write(uint8_t); + virtual void close(); + virtual void clear(); + virtual void apply(); + virtual long maxSize(); + +private: +}; + +extern InternalStorageESPClass InternalStorage; + +#endif diff --git a/libraries/ArduinoOTA/src/OTAStorage.cpp b/libraries/ArduinoOTA/src/OTAStorage.cpp new file mode 100644 index 0000000..e685c68 --- /dev/null +++ b/libraries/ArduinoOTA/src/OTAStorage.cpp @@ -0,0 +1,58 @@ +#include "OTAStorage.h" + +#if defined(ARDUINO_ARCH_SAMD) +static const uint32_t pageSizes[] = { 8, 16, 32, 64, 128, 256, 512, 1024 }; +extern "C" { +char * __text_start__(); // 0x2000, 0x0 without bootloader and 0x4000 for M0 original bootloader +} +#elif defined(ARDUINO_ARCH_NRF5) +extern "C" { +char * __isr_vector(); +} +#elif defined(__AVR__) +#include +#include +#define MIN_BOOTSZ (4 * SPM_PAGESIZE) +#endif + +OTAStorage::OTAStorage() : +#if defined(ARDUINO_ARCH_SAMD) + SKETCH_START_ADDRESS((uint32_t) __text_start__), + PAGE_SIZE(pageSizes[NVMCTRL->PARAM.bit.PSZ]), + MAX_FLASH(PAGE_SIZE * NVMCTRL->PARAM.bit.NVMP) +#elif defined(ARDUINO_ARCH_NRF5) + SKETCH_START_ADDRESS((uint32_t) __isr_vector), + PAGE_SIZE((size_t) NRF_FICR->CODEPAGESIZE), + MAX_FLASH(PAGE_SIZE * (uint32_t) NRF_FICR->CODESIZE) +#elif defined(__AVR__) + SKETCH_START_ADDRESS(0), + PAGE_SIZE(SPM_PAGESIZE), + MAX_FLASH((uint32_t) FLASHEND + 1) +#elif defined(ESP8266) || defined(ESP32) + SKETCH_START_ADDRESS(0), // not used + PAGE_SIZE(0), // not used + MAX_FLASH(0) // not used +#endif +{ + bootloaderSize = 0; +#ifdef __AVR__ + cli(); + uint8_t highBits = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS); + sei(); + if (!(highBits & bit(FUSE_BOOTRST))) { + byte v = (highBits & ((~FUSE_BOOTSZ1 ) + (~FUSE_BOOTSZ0 ))); + bootloaderSize = MIN_BOOTSZ << ((v >> 1) ^ 3); + } +#endif +} + +void ExternalOTAStorage::apply() { +#ifdef __AVR__ + wdt_enable(WDTO_15MS); + while (true); +#elif defined(ESP8266) || defined(ESP32) + ESP.restart(); +#else + NVIC_SystemReset(); +#endif +} diff --git a/libraries/ArduinoOTA/src/OTAStorage.h b/libraries/ArduinoOTA/src/OTAStorage.h new file mode 100644 index 0000000..ca65394 --- /dev/null +++ b/libraries/ArduinoOTA/src/OTAStorage.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 + + WiFi101OTA version Feb 2017 + by Sandeep Mistry (Arduino) + modified for ArduinoOTA Dec 2018, Apr 2019 + by Juraj Andrassy +*/ + +#ifndef _OTA_STORAGE_H_INCLUDED +#define _OTA_STORAGE_H_INCLUDED + +#include + +class OTAStorage { +public: + + OTAStorage(); + + virtual int open(int length) = 0; + virtual int open(int length, uint8_t command) { + (void) command; + return open(length); + } + virtual size_t write(uint8_t) = 0; + virtual void close() = 0; + virtual void clear() = 0; + virtual void apply() = 0; + + virtual long maxSize() { + return (MAX_FLASH - SKETCH_START_ADDRESS - bootloaderSize); + } + +protected: + const uint32_t SKETCH_START_ADDRESS; + const uint32_t PAGE_SIZE; + const uint32_t MAX_FLASH; + uint32_t bootloaderSize; + +}; + +class ExternalOTAStorage : public OTAStorage { + +protected: + const char* updateFileName = "UPDATE.BIN"; + +public: + void setUpdateFileName(const char* _updateFileName) { + updateFileName = _updateFileName; + } + + virtual void apply(); +}; + +#endif diff --git a/libraries/ArduinoOTA/src/SDStorage.h b/libraries/ArduinoOTA/src/SDStorage.h new file mode 100644 index 0000000..5b51b45 --- /dev/null +++ b/libraries/ArduinoOTA/src/SDStorage.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 _SD_STORAGE_H_INCLUDED +#define _SD_STORAGE_H_INCLUDED + +#include + +#include "OTAStorage.h" + +#ifndef SDCARD_SS_PIN +#define SDCARD_SS_PIN 4 +#endif + +class SDStorageClass : public ExternalOTAStorage { +public: + + virtual int open(int length) { + _file = SD.open(updateFileName, O_CREAT | O_WRITE); + if (!_file) + return 0; + return 1; + } + + virtual size_t write(uint8_t b) { + return _file.write(b); + } + virtual void close() { + _file.close(); + } + + virtual void clear() { + SD.remove(updateFileName); + } + +private: + File _file; +}; + +#endif diff --git a/libraries/ArduinoOTA/src/SerialFlashStorage.h b/libraries/ArduinoOTA/src/SerialFlashStorage.h new file mode 100644 index 0000000..10eec5f --- /dev/null +++ b/libraries/ArduinoOTA/src/SerialFlashStorage.h @@ -0,0 +1,72 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 _SERIALFLASH_STORAGE_H_INCLUDED +#define _SERIALFLASH_STORAGE_H_INCLUDED + +#include + +#include "OTAStorage.h" + +#define SERIAL_FLASH_BUFFER_SIZE 64 +#define SERIAL_FLASH_CS 5 + +class SerialFlashStorageClass : public ExternalOTAStorage { +public: + + virtual int open(int length) { + if (!SerialFlash.begin(SERIAL_FLASH_CS)) { + return 0; + } + + while (!SerialFlash.ready()) {} + + if (SerialFlash.exists(updateFileName)) { + SerialFlash.remove(updateFileName); + } + + if (SerialFlash.create(updateFileName, length)) { + _file = SerialFlash.open(updateFileName); + } + + if (!_file) { + return 0; + } + + return 1; + } + + virtual size_t write(uint8_t b) { + while (!SerialFlash.ready()) {} + int ret = _file.write(&b, 1); + return ret; + } + + virtual void close() { + _file.close(); + } + + virtual void clear() { + SerialFlash.remove(updateFileName); + } + +private: + SerialFlashFile _file; +}; + +#endif diff --git a/libraries/ArduinoOTA/src/WiFiOTA.cpp b/libraries/ArduinoOTA/src/WiFiOTA.cpp new file mode 100644 index 0000000..bf102f0 --- /dev/null +++ b/libraries/ArduinoOTA/src/WiFiOTA.cpp @@ -0,0 +1,358 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 + + WiFi101OTA version Feb 2017 + by Sandeep Mistry (Arduino) + modified for ArduinoOTA Dec 2018, Apr 2019 + by Juraj Andrassy +*/ + +#include + +#include "WiFiOTA.h" + +#define BOARD "arduino" +#define BOARD_LENGTH (sizeof(BOARD) - 1) + +static String base64Encode(const String& in) +{ + static const char* CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + int b; + String out; + out.reserve((in.length()) * 4 / 3); + + for (unsigned int i = 0; i < in.length(); i += 3) { + b = (in.charAt(i) & 0xFC) >> 2; + out += CODES[b]; + + b = (in.charAt(i) & 0x03) << 4; + if (i + 1 < in.length()) { + b |= (in.charAt(i + 1) & 0xF0) >> 4; + out += CODES[b]; + b = (in.charAt(i + 1) & 0x0F) << 2; + if (i + 2 < in.length()) { + b |= (in.charAt(i + 2) & 0xC0) >> 6; + out += CODES[b]; + b = in.charAt(i + 2) & 0x3F; + out += CODES[b]; + } else { + out += CODES[b]; + out += '='; + } + } else { + out += CODES[b]; + out += "=="; + } + } + + return out; +} + +WiFiOTAClass::WiFiOTAClass() : + _storage(NULL), + localIp(0), + _lastMdnsResponseTime(0), + beforeApplyCallback(nullptr) +{ +} + +void WiFiOTAClass::begin(IPAddress& localIP, const char* name, const char* password, OTAStorage& storage) +{ + localIp = localIP; + _name = name; + _expectedAuthorization = "Basic " + base64Encode("arduino:" + String(password)); + _storage = &storage; +} + +void WiFiOTAClass::pollMdns(UDP &_mdnsSocket) +{ + int packetLength = _mdnsSocket.parsePacket(); + + if (packetLength <= 0) { + return; + } + + const byte ARDUINO_SERVICE_REQUEST[37] = { + 0x00, 0x00, // transaction id + 0x00, 0x00, // flags + 0x00, 0x01, // questions + 0x00, 0x00, // answer RRs + 0x00, 0x00, // authority RRs + 0x00, 0x00, // additional RRs + 0x08, + 0x5f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, // _arduino + 0x04, + 0x5f, 0x74, 0x63, 0x70, // _tcp + 0x05, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // local + 0x00, 0x0c, // PTR + 0x00, 0x01 // Class IN + }; + + if (packetLength != sizeof(ARDUINO_SERVICE_REQUEST)) { + while (packetLength) { + if (_mdnsSocket.available()) { + packetLength--; + _mdnsSocket.read(); + } + } + return; + } + + byte request[packetLength]; + + _mdnsSocket.read(request, sizeof(request)); + + if (memcmp(&request[2], &ARDUINO_SERVICE_REQUEST[2], packetLength - 2) != 0) { + return; + } + + if ((millis() - _lastMdnsResponseTime) < 1000) { + // ignore request + return; + } + _lastMdnsResponseTime = millis(); + + _mdnsSocket.beginPacket(IPAddress(224, 0, 0, 251), 5353); + + const byte responseHeader[] = { + 0x00, 0x00, // transaction id + 0x84, 0x00, // flags + 0x00, 0x00, // questions + 0x00, 0x04, // answers RRs + 0x00, 0x00, // authority RRs + 0x00, 0x00 // additional RRS + }; + _mdnsSocket.write(responseHeader, sizeof(responseHeader)); + + const byte ptrRecordStart[] = { + 0x08, + '_', 'a', 'r', 'd', 'u', 'i', 'n', 'o', + + 0x04, + '_', 't', 'c', 'p', + + 0x05, + 'l', 'o', 'c', 'a', 'l', + 0x00, + + 0x00, 0x0c, // PTR + 0x00, 0x01, // class IN + 0x00, 0x00, 0x11, 0x94, // TTL + + 0x00, (byte)(_name.length() + 3), // length + (byte)_name.length() + }; + + const byte ptrRecordEnd[] = { + 0xc0, 0x0c + }; + + _mdnsSocket.write(ptrRecordStart, sizeof(ptrRecordStart)); + _mdnsSocket.write((const byte*) _name.c_str(), _name.length()); + _mdnsSocket.write(ptrRecordEnd, sizeof(ptrRecordEnd)); + + const byte txtRecord[] = { + 0xc0, 0x2b, + 0x00, 0x10, // TXT strings + 0x80, 0x01, // class + 0x00, 0x00, 0x11, 0x94, // TTL + 0x00, (50 + BOARD_LENGTH), + 13, + 's', 's', 'h', '_', 'u', 'p', 'l', 'o', 'a', 'd', '=', 'n', 'o', + 12, + 't', 'c', 'p', '_', 'c', 'h', 'e', 'c', 'k', '=', 'n', 'o', + 15, + 'a', 'u', 't', 'h', '_', 'u', 'p', 'l', 'o', 'a', 'd', '=', 'y', 'e', 's', + (6 + BOARD_LENGTH), + 'b', 'o', 'a', 'r', 'd', '=', + }; + _mdnsSocket.write(txtRecord, sizeof(txtRecord)); + _mdnsSocket.write((byte*)BOARD, BOARD_LENGTH); + + const byte srvRecordStart[] = { + 0xc0, 0x2b, + 0x00, 0x21, // SRV + 0x80, 0x01, // class + 0x00, 0x00, 0x00, 0x78, // TTL + 0x00, (byte)(_name.length() + 9), // length + 0x00, 0x00, + 0x00, 0x00, + 0xff, 0x00, // port + (byte)_name.length() + }; + + const byte srvRecordEnd[] = { + 0xc0, 0x1a + }; + + _mdnsSocket.write(srvRecordStart, sizeof(srvRecordStart)); + _mdnsSocket.write((const byte*) _name.c_str(), _name.length()); + _mdnsSocket.write(srvRecordEnd, sizeof(srvRecordEnd)); + + byte aRecordNameOffset = sizeof(responseHeader) + + sizeof(ptrRecordStart) + _name.length() + sizeof(ptrRecordEnd) + + sizeof(txtRecord) + BOARD_LENGTH + + sizeof(srvRecordStart) - 1; + + byte aRecord[] = { + 0xc0, aRecordNameOffset, + + 0x00, 0x01, // A record + 0x80, 0x01, // class + 0x00, 0x00, 0x00, 0x78, // TTL + 0x00, 0x04, + 0xff, 0xff, 0xff, 0xff // IP + }; + memcpy(&aRecord[sizeof(aRecord) - 4], &localIp, sizeof(localIp)); + _mdnsSocket.write(aRecord, sizeof(aRecord)); + + _mdnsSocket.endPacket(); +} + +void WiFiOTAClass::pollServer(Client& client) +{ + + if (client) { + String request = client.readStringUntil('\n'); + request.trim(); + + String header; + long contentLength = -1; + String authorization; + + do { + header = client.readStringUntil('\n'); + header.trim(); + + if (header.startsWith("Content-Length: ")) { + header.remove(0, 16); + + contentLength = header.toInt(); + } else if (header.startsWith("Authorization: ")) { + header.remove(0, 15); + + authorization = header; + } + } while (header != ""); + + bool dataUpload = false; +#if defined(ESP8266) || defined(ESP32) + if (request == "POST /data HTTP/1.1") { + dataUpload = true; + } else +#endif + if (request != "POST /sketch HTTP/1.1") { + flushRequestBody(client, contentLength); + sendHttpResponse(client, 404, "Not Found"); + return; + } + + if (_expectedAuthorization != authorization) { + flushRequestBody(client, contentLength); + sendHttpResponse(client, 401, "Unauthorized"); + return; + } + + if (contentLength <= 0) { + sendHttpResponse(client, 400, "Bad Request"); + return; + } + + if (_storage == NULL || !_storage->open(contentLength, dataUpload)) { + flushRequestBody(client, contentLength); + sendHttpResponse(client, 500, "Internal Server Error"); + return; + } + + if (contentLength > _storage->maxSize()) { + _storage->close(); + flushRequestBody(client, contentLength); + sendHttpResponse(client, 413, "Payload Too Large"); + return; + } + + long read = 0; + byte buff[64]; + + while (client.connected() && read < contentLength) { + while (client.available()) { + int l = client.read(buff, sizeof(buff)); + for (int i = 0; i < l; i++) { + _storage->write(buff[i]); + } + read += l; + } + } + + _storage->close(); + + if (read == contentLength) { + sendHttpResponse(client, 200, "OK"); + + delay(500); + + if (beforeApplyCallback) { + beforeApplyCallback(); + } + + // apply the update + _storage->apply(); + + while (true); + } else { + + sendHttpResponse(client, 414, "Payload size wrong"); + _storage->clear(); + + delay(500); + + client.stop(); + } + } +} + +void WiFiOTAClass::sendHttpResponse(Client& client, int code, const char* status) +{ + while (client.available()) { + client.read(); + } + + client.print("HTTP/1.1 "); + client.print(code); + client.print(" "); + client.println(status); + client.println("Connection: close"); + client.println(); + delay(500); + client.stop(); +} + +void WiFiOTAClass::flushRequestBody(Client& client, long contentLength) +{ + long read = 0; + + while (client.connected() && read < contentLength) { + if (client.available()) { + read++; + + client.read(); + } + } +} + diff --git a/libraries/ArduinoOTA/src/WiFiOTA.h b/libraries/ArduinoOTA/src/WiFiOTA.h new file mode 100644 index 0000000..d987816 --- /dev/null +++ b/libraries/ArduinoOTA/src/WiFiOTA.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2017 Arduino LLC. 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 + + WiFi101OTA version Feb 2017 + by Sandeep Mistry (Arduino) + modified for ArduinoOTA Dec 2018 + by Juraj Andrassy +*/ + +#ifndef _WIFI_OTA_H_INCLUDED +#define _WIFI_OTA_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "OTAStorage.h" + +class WiFiOTAClass { +protected: + WiFiOTAClass(); + + void begin(IPAddress& localIP, const char* name, const char* password, OTAStorage& storage); + + void pollMdns(UDP &mdnsSocket); + void pollServer(Client& client); + +public: + void beforeApply(void (*fn)(void)) { + beforeApplyCallback = fn; + } + +private: + void sendHttpResponse(Client& client, int code, const char* status); + void flushRequestBody(Client& client, long contentLength); + +private: + String _name; + String _expectedAuthorization; + OTAStorage* _storage; + + uint32_t localIp; + uint32_t _lastMdnsResponseTime; + + void (*beforeApplyCallback)(void); +}; + +#endif diff --git a/libraries/ArduinoOTA/src/utility/optiboot.h b/libraries/ArduinoOTA/src/utility/optiboot.h new file mode 100644 index 0000000..9783b67 --- /dev/null +++ b/libraries/ArduinoOTA/src/utility/optiboot.h @@ -0,0 +1,200 @@ +/*------------------------ Optiboot header file ----------------------------| + | | + | June 2015 by Marek Wodzinski, https://github.com/majekw | + | Modified June 2016 by MCUdude, https://github.com/MCUdude | + | Modified Dec 2018 by Juraj Andrassy, https://github.com/jandrassy | + | Released to public domain | + | | + | This header file gives possibility to use SPM instruction | + | from Optiboot bootloader memory. | + | | + | There are 5 convenient functions available here: | + | * optiboot_page_erase - to erase a FLASH page | + | * optiboot_page_fill - to put words into temporary buffer | + | * optiboot_page_write - to write contents of temporary buffer into FLASH | | + | * optiboot_readPage - higher level function to read a flash page and | + | store it in an array | + | * optiboot_writePage - higher level function to write content to | + | a flash page | + | | + | For some hardcore users, you could use 'do_spm' as raw entry to | + | bootloader spm function. | + |-------------------------------------------------------------------------*/ + +#ifndef _OPTIBOOT_H_ +#define _OPTIBOOT_H_ 1 + +#include +#include "Arduino.h" + + +/* + * Main 'magic' function - enter to bootloader do_spm function + * + * address - address to write (in bytes) but must be even number + * command - one of __BOOT_PAGE_WRITE, __BOOT_PAGE_ERASE or __BOOT_PAGE_FILL + * data - data to write in __BOOT_PAGE_FILL. In __BOOT_PAGE_ERASE or + * __BOOT_PAGE_WRITE it control if boot_rww_enable is run + * (0 = run, !0 = skip running boot_rww_enable) + * + */ + + // 'typedef' (in following line) and 'const' (few lines below) are a way to define external function at some arbitrary address + typedef void (*do_spm_t)(uint16_t address, uint8_t command, uint16_t data); + typedef void (*copy_flash_pages_t)(uint32_t dest, uint32_t src, uint16_t page_count, uint8_t reset); + + +/* + * Devices with more than 64KB of flash: + * - have larger bootloader area (1KB) (they are BIGBOOT targets) + * - have RAMPZ register :-) + * - need larger variable to hold address (pgmspace.h uses uint32_t) + */ +#ifdef RAMPZ + typedef uint32_t optiboot_addr_t; +#else + typedef uint16_t optiboot_addr_t; +#endif + + #if FLASHEND > 65534 + const do_spm_t do_spm = (do_spm_t)((FLASHEND-1023+2)>>1); + const copy_flash_pages_t copy_flash_pages = (copy_flash_pages_t)((FLASHEND-1023+4)>>1); + #else + const do_spm_t do_spm = (do_spm_t)((FLASHEND-511+2)>>1); + #endif + + +/* + * The same as do_spm but with disable/restore interrupts state + * required to succesfull SPM execution + * + * On devices with more than 64kB flash, 16 bit address is not enough, + * so there is also RAMPZ used in that case. + * + * On devices with more than 128kB flash, 16 bit word address is not enough + * for a function call above 0x20000, so there is also EIND used in that case. + */ +void do_spm_cli(optiboot_addr_t address, uint8_t command, uint16_t data) { + uint8_t sreg_save; + + sreg_save = SREG; // save old SREG value + asm volatile("cli"); // disable interrupts +#ifdef RAMPZ + RAMPZ = (address >> 16) & 0xff; // address bits 23-16 goes to RAMPZ +#ifdef EIND + uint8_t eind = EIND; + EIND = FLASHEND / 0x20000; +#endif + do_spm((address & 0xffff), command, data); // do_spm accepts only lower 16 bits of address +#ifdef EIND + EIND = eind; +#endif +#else + do_spm(address, command, data); // 16 bit address - no problems to pass directly +#endif + SREG = sreg_save; // restore last interrupts state +} + +/* + * Copy contents of the flash pages. Addresses must be aligned to page boundary. + * + * On devices with more than 128kB flash, 16 bit word address is not enough + * for a function call above 0x20000, so there is also EIND used in that case. + * + * If reset_mcu is true, watchdog is used to reset the MCU after pages are copied. + * That enables to copy a new version of application from upper half of the flash. + */ +#if FLASHEND > 65534 +void copy_flash_pages_cli(uint32_t dest, uint32_t src, uint16_t page_count, uint8_t reset_mcu) { + uint8_t sreg_save = SREG; // save old SREG value + asm volatile("cli"); // disable interrupts +#ifdef EIND + uint8_t eind = EIND; + EIND = FLASHEND / 0x20000; +#endif + copy_flash_pages(dest, src, page_count, reset_mcu); +#ifdef EIND + EIND = eind; +#endif + SREG = sreg_save; // restore last interrupts state +} +#endif + +// Erase page in FLASH +void optiboot_page_erase(optiboot_addr_t address) { + do_spm_cli(address, __BOOT_PAGE_ERASE, 0); +} + + +// Write word into temporary buffer +void optiboot_page_fill(optiboot_addr_t address, uint16_t data) { + do_spm_cli(address, __BOOT_PAGE_FILL, data); +} + + +//Write temporary buffer into FLASH +void optiboot_page_write(optiboot_addr_t address) { + do_spm_cli(address, __BOOT_PAGE_WRITE, 0); +} + + + +/* + * Higher level functions for reading and writing from flash + * See the examples for more info on how to use these functions + */ + +// Function to read a flash page and store it in an array (storage_array[]) +void optiboot_readPage(const uint8_t allocated_flash_space[], uint8_t storage_array[], uint16_t page, char blank_character) +{ + uint8_t read_character; + for(uint16_t j = 0; j < SPM_PAGESIZE; j++) + { + read_character = pgm_read_byte(&allocated_flash_space[j + SPM_PAGESIZE*(page-1)]); + if(read_character != 0 && read_character != 255) + storage_array[j] = read_character; + else + storage_array[j] = blank_character; + } +} + + +// Function to read a flash page and store it in an array (storage_array[]), but without blank_character +void optiboot_readPage(const uint8_t allocated_flash_space[], uint8_t storage_array[], uint16_t page) +{ + uint8_t read_character; + for(uint16_t j = 0; j < SPM_PAGESIZE; j++) + { + read_character = pgm_read_byte(&allocated_flash_space[j + SPM_PAGESIZE*(page-1)]); + if(read_character != 0 && read_character != 255) + storage_array[j] = read_character; + } +} + + +// Function to write data to a flash page +void optiboot_writePage(const uint8_t allocated_flash_space[], uint8_t data_to_store[], uint16_t page) +{ + uint16_t word_buffer = 0; + + // Erase the flash page + optiboot_page_erase((optiboot_addr_t)(void*) &allocated_flash_space[SPM_PAGESIZE*(page-1)]); + + // Copy ram buffer to temporary flash buffer + for(uint16_t i = 0; i < SPM_PAGESIZE; i++) + { + if(i % 2 == 0) // We must write words + word_buffer = data_to_store[i]; + else + { + word_buffer += (data_to_store[i] << 8); + optiboot_page_fill((optiboot_addr_t)(void*) &allocated_flash_space[i + SPM_PAGESIZE*(page-1)], word_buffer); + } + } + + // Writing temporary buffer to flash + optiboot_page_write((optiboot_addr_t)(void*) &allocated_flash_space[SPM_PAGESIZE*(page-1)]); +} + + +#endif /* _OPTIBOOT_H_ */ diff --git a/libraries/ArduinoThread-master/.gitignore b/libraries/ArduinoThread-master/.gitignore new file mode 100644 index 0000000..620d3dc --- /dev/null +++ b/libraries/ArduinoThread-master/.gitignore @@ -0,0 +1,13 @@ +# Compiled Object files +*.slo +*.lo +*.o + +# Compiled Dynamic libraries +*.so +*.dylib + +# Compiled Static libraries +*.lai +*.la +*.a diff --git a/libraries/ArduinoThread-master/LICENSE.txt b/libraries/ArduinoThread-master/LICENSE.txt new file mode 100644 index 0000000..5c02604 --- /dev/null +++ b/libraries/ArduinoThread-master/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Ivan Seidel + +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/libraries/ArduinoThread-master/README.md b/libraries/ArduinoThread-master/README.md new file mode 100644 index 0000000..b866337 --- /dev/null +++ b/libraries/ArduinoThread-master/README.md @@ -0,0 +1,208 @@ +![ArduinoThread Logo](https://raw.githubusercontent.com/ivanseidel/ArduinoThread/master/extras/ArduinoThread.png) + +## ArduinoThreads Motivation +Arduino does not support isolated parallel tasks ([Threads](https://en.wikipedia.org/wiki/Thread_(computing))), +but we can make the main `loop` switch function execution conditionally and +thus simulate threading with [Protothread](https://en.wikipedia.org/wiki/Protothread) mechanism. +This library implements it and helps you to: + +- schedule, manage and simplify parallel, periodic tasks +- define fixed or variable time between runs +- organize the code in any type of project + - put all sensor readings in a thread + - keep the main loop clean +- hide the complexity of thread management +- run "pseudo-background" tasks using Timer interrupts + +Blinking an LED is often the very first thing an Arduino user learns. +And this demonstrates that periodically performing one single task, like toggling the LED state, is really easy. +However, one may quickly discover that managing multiple periodic tasks is not so simple +if the tasks have different schedule. + +The user defines a Thread object for each of those tasks, then lets the library manage their scheduled execution. + +It should be noted that these are not “threads†in the real computer-science meaning of the term: +tasks are implemented as functions that are run periodically. +On the one hand, this means that the only way a task can *yield* the CPU is by returning to the caller, +and it is thus inadvisable to `delay()` or do long waits inside any task. +On the other hand, this makes ArduinoThreads memory friendly, as no stack need to be allocated per task. + +## Installation + +1. Download [the Master branch](https://github.com/ivanseidel/ArduinoThread/archive/master.zip) from gitHub. +2. Unzip and modify the Folder name to "ArduinoThread" (Remove the '-master' suffix) +3. Paste the modified folder on your Library folder (On your `Libraries` folder inside Sketchbooks or Arduino software). +4. Restart the Arduino IDE + +**If you are here just because another library requires a class from ArduinoThread, then you are done now +.** + + +## Getting Started + +There are many examples showing many ways to use it. We will explain Class itself, +what it does and how it does. + +There are three main classes included in the library: +`Thread`, `ThreadController` and `StaticThreadController` (both controllers inherit from `Thread`). + +- `Thread`: Basic class, witch contains methods to set and run callbacks, + check if the Thread should be run, and also creates a unique ThreadID on the instantiation. + +- `ThreadController`: Responsible for managing multiple Threads. Can also be thought of + as "a group of Threads", and is used to perform `run` in every Thread ONLY when needed. + +- `StaticThreadController`: Slightly faster and smaller version of the `ThreadController`. + It works similar to `ThreadController`, but once constructed it can't add or remove threads to run. + +#### Create Thread instance: + +```c++ +Thread myThread = Thread(); +// or, if initializing a pointer +Thread* myThread = new Thread(); +``` + +#### Setup thread behaviour +You can configure many things: + +```c++ +myThread.enabled = true; // Default enabled value is true +myThread.setInterval(10); // Setts the wanted interval to be 10ms +/* + This is useful for debugging + (Thread Name is disabled by default, to use less memory) + (Enable it by definint USE_THREAD_NAMES on 'Thread.h') +*/ +myThread.ThreadName = "myThread tag"; +// This will set the callback of the Thread: "What should I run"? +myThread.onRun(callback_function); // callback_function is the name of the function +``` + +#### Running threads manually +Ok, creating threads isn't too hard, but what do we do with them? + +```c++ +// First check if our Thread should be run +if(myThread.shouldRun()){ + // Yes, the Thread should run, let's run it + myThread.run(); +} +``` + +#### Running threads via a controller +If you had 3, 5 or 100 threads, managing them manually could become tedious. +That's when `ThreadController` or `StaticThreadController` comes into play and saves you the repetitive thread management parts of code. + +```c++ +// Instantiate new ThreadController +ThreadController controller = ThreadController(); +// Now, put bunch of Threads inside it, FEED it! +controller.add(&myThread); // Notice the '&' sign before the thread, IF it's not instantied as a pointer. +controller.add(&hisThread); +controller.add(&sensorReadings); +... +``` +or +```c++ +// Instantiate a new StaticThreadController with the number of threads to be supplied as template parameter +StaticThreadController<3> controller (&myThread, &hisThread, &sensorReadings); +// You don't need to do anything else, controller now contains all the threads. +... +``` + +You have created, configured, grouped it. What is missing? Yes, whe should RUN it! +The following will run all the threads that NEED to run. + +```c++ +// call run on a Thread, a ThreadController or a StaticThreadController to run it +controller.run(); +``` + +Congratulations, you have learned the basics of the `ArduinoThread` library. If you want to learn more, see bellow. + +### Tips and Warnings + +* `ThreadController` is not of a dynamic size (like a `LinkedList`). The maximum number of threads that it can manage + is defined in `ThreadController.h` (default is 15) + +* ☢ When extending the `Thread` class and overriding the `run()` function, + remember to always call `runned();` at the end, otherwise the thread will hang forever. + +* It's a good idea, to create a `Timer` interrupt and call a `ThreadController.run()` there. +That way, you don't need to worry about reading sensors and doing time-sensitive stuff +in your main code (`loop`). Check `ControllerWithTimer` example. + +* Inheriting from `Thread` or even `ThreadController` is always a good idea. +For example, I always create base classes of sensors that extends `Thread`, +so that I can "register" the sensors inside a `ThreadController`, and forget +about reading sensors, just having the values available in my main code. +Check the `SensorThread` example. + +* Remember that `ThreadController` is in fact, a `Thread` itself. If you want to group threads and +manage them together (enable or disable), think about putting all of them inside a `ThreadController`, +and adding this `ThreadController` to another `ThreadController` (YES! One inside another). +Check `ControllerInController` example. + +* `StaticThreadController` is optimal when you know the exact number of +threads to run. You cannot add or remove threads at runtime, but it +doesn't require additional memory to keep all the treads together, doesn't limit the number of thread +(except for available memory) and the code may be slightly +better optimized because all the threads always exist and no need to do any runtime checks. + +* Check the full example `CustomTimedThread` for a cool application of threads that run +for a period, after a button is pressed. + +* Running tasks on the `Timer` interrupts must be thought though REALLY carefully + + - You mustn't use `sleep()` inside an interrupt, because it would cause an infinite loop. + + - Things execute quickly. Waiting too loooong on a interrupt, means waiting too + loooong on the main code (`loop`) + + - Things might get "scrambled". Since Timers interrupts actually "BREAK" your code in half + and start running the interrupt, you might want to call `noInterrupts` and `interrupts` + on places where cannot be interrupted: + +```c++ +noInterrupts(); +// Put the code that CANNOT be interrupted... +interrupts(); // This will enable the interrupts egain. DO NOT FORGET! +``` +## Library Reference + +### Configuration options +#### Thread +- `bool Thread::enabled` - Enables or disables the thread. (doesn't prevent it from running, but will + return `false` when `shouldRun()` is called) +- `void Thread::setInterval()` - Schedules the thread run interval in milliseconds +- `bool Thread::shouldRun()` - Returns true, if the thread should be run. + (Basically,the logic is: (reached time AND is enabled?). +- `void Thread::onRun()` - The target callback function to be called. +- `void Thread::run()` - Runs the thread (executes the callback function). +- `int Thread::ThreadID` - Theoretically, it's the memory address. It's unique, and can + be used to compare if two threads are identical. +- `int Thread::ThreadName` - A human-readable thread name. + Default is "Thread ThreadID", eg.: "Thread 141515". + Note that to enable this attribute, you must uncomment the line that disables it on `Thread.h` +- protected: `void Thread::runned()` - Used to reset internal timer of the thread. + This is automatically called AFTER a call to `run()`. + +#### ThreadController +- `void ThreadController::run()` - Runs the all threads grouped by the controller, + but only if needed (if `shouldRun()` returns true); +- `bool ThreadController::add(Thread* _thread)` - Adds a the thread to the controller, + and returns `true` if succeeded (returns false if the array is full). +- `void ThreadController::remove(Thread* _thread)` - Removes the thread from the controller +- `void ThreadController::remove(int index)` - Removes the thread at the `index` position +- `void ThreadController::clear()` - Removes ALL threads from the controller +- `int ThreadController::size(bool cached = true)` - Returns number of threads allocated + in the ThreadController. Re-calculates thread count if `cached` is `false` +- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position + +#### StaticThreadController +- `void StaticThreadController::run()` - Runs all the threads within the controller, + but only if needed (if `shouldRun()` returns true); +- `int StaticThreadController::size()` - Returns how many Threads are allocated inside the controller. +- `Thread* ThreadController::get(int index)` - Returns the thread at the `index` position - or `nullptr` if `index` + is out of bounds. diff --git a/libraries/ArduinoThread-master/StaticThreadController.h b/libraries/ArduinoThread-master/StaticThreadController.h new file mode 100644 index 0000000..6e316ea --- /dev/null +++ b/libraries/ArduinoThread-master/StaticThreadController.h @@ -0,0 +1,75 @@ +/* + StaticThreadController.h - Controlls a list of Threads with different timings + + Basicaly, what it does is to keep track of current Threads and run when + necessary. + + StaticThreadController is an extended class of Thread, because of that, + it allows you to add a StaticThreadController inside another kind of ThreadController... + + It works exact as ThreadController except you can't add or remove treads dynamically. + + Created by Alex Eremin, September, 2016. + Released into the public domain. +*/ + +#ifndef StaticThreadController_h +#define StaticThreadController_h + +#include "Thread.h" + +template +class StaticThreadController: public Thread{ +protected: + //since this is a static controller, the pointers themselves can be const + //it should be distinguished from 'const Thread* thread[N]' + Thread * const thread[N]; +public: + template + StaticThreadController(T... params) : + Thread(), + thread{params...} + { + #ifdef USE_THREAD_NAMES + // Overrides name + ThreadName = "StaticThreadController "; + ThreadName = ThreadName + ThreadID; + #endif + }; + + // run() Method is overrided + void run() override + { + // Run this thread before + if(_onRun != nullptr && shouldRun()) + _onRun(); + + for(int i = 0; i < N; i++){ + // Is enabled? Timeout exceeded? + if(thread[i]->shouldRun()){ + thread[i]->run(); + } + } + + // StaticThreadController extends Thread, so we should flag as runned thread + runned(); + } + + // Return the quantity of Threads + static constexpr int size() { return N; }; + + // Return the I Thread on the array + // Returns nullptr if index is out of bounds + Thread* get(int index) { + return (index >= 0 && index < N) ? thread[index] : nullptr; + }; + + // Return the I Thread on the array + // Doesn't perform any bounds checks and behaviour is + // unpredictable in case of index > N + Thread& operator[](int index) { + return *thread[index]; + }; +}; + +#endif diff --git a/libraries/ArduinoThread-master/Thread.cpp b/libraries/ArduinoThread-master/Thread.cpp new file mode 100644 index 0000000..cd29d98 --- /dev/null +++ b/libraries/ArduinoThread-master/Thread.cpp @@ -0,0 +1,52 @@ +#include "Thread.h" + +Thread::Thread(void (*callback)(void), unsigned long _interval){ + enabled = true; + onRun(callback); + _cached_next_run = 0; + last_run = millis(); + + ThreadID = (int)this; + #ifdef USE_THREAD_NAMES + ThreadName = "Thread "; + ThreadName = ThreadName + ThreadID; + #endif + + setInterval(_interval); +}; + +void Thread::runned(unsigned long time){ + // Saves last_run + last_run = time; + + // Cache next run + _cached_next_run = last_run + interval; +} + +void Thread::setInterval(unsigned long _interval){ + // Save interval + interval = _interval; + + // Cache the next run based on the last_run + _cached_next_run = last_run + interval; +} + +bool Thread::shouldRun(unsigned long time){ + // If the "sign" bit is set the signed difference would be negative + bool time_remaining = (time - _cached_next_run) & 0x80000000; + + // Exceeded the time limit, AND is enabled? Then should run... + return !time_remaining && enabled; +} + +void Thread::onRun(void (*callback)(void)){ + _onRun = callback; +} + +void Thread::run(){ + if(_onRun != NULL) + _onRun(); + + // Update last_run and _cached_next_run + runned(); +} diff --git a/libraries/ArduinoThread-master/Thread.h b/libraries/ArduinoThread-master/Thread.h new file mode 100644 index 0000000..0e580a9 --- /dev/null +++ b/libraries/ArduinoThread-master/Thread.h @@ -0,0 +1,89 @@ +/* + Thread.h - An runnable object + + Thread is responsable for holding the "action" for something, + also, it responds if it "should" or "should not" run, based on + the current time; + + For instructions, go to https://github.com/ivanseidel/ArduinoThread + + Created by Ivan Seidel Gomes, March, 2013. + Released into the public domain. +*/ + +#ifndef Thread_h +#define Thread_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include +#else + #include +#endif + +#include + +/* + Uncomment this line to enable ThreadName Strings. + + It might be usefull if you are loging thread with Serial, + or displaying a list of threads... +*/ +// #define USE_THREAD_NAMES 1 + +class Thread{ +protected: + // Desired interval between runs + unsigned long interval; + + // Last runned time in Ms + unsigned long last_run; + + // Scheduled run in Ms (MUST BE CACHED) + unsigned long _cached_next_run; + + /* + IMPORTANT! Run after all calls to run() + Updates last_run and cache next run. + NOTE: This MUST be called if extending + this class and implementing run() method + */ + void runned(unsigned long time); + + // Default is to mark it runned "now" + void runned() { runned(millis()); } + + // Callback for run() if not implemented + void (*_onRun)(void); + +public: + + // If the current Thread is enabled or not + bool enabled; + + // ID of the Thread (initialized from memory adr.) + int ThreadID; + + #ifdef USE_THREAD_NAMES + // Thread Name (used for better UI). + String ThreadName; + #endif + + Thread(void (*callback)(void) = NULL, unsigned long _interval = 0); + + // Set the desired interval for calls, and update _cached_next_run + virtual void setInterval(unsigned long _interval); + + // Return if the Thread should be runned or not + virtual bool shouldRun(unsigned long time); + + // Default is to check whether it should run "now" + bool shouldRun() { return shouldRun(millis()); } + + // Callback set + void onRun(void (*callback)(void)); + + // Runs Thread + virtual void run(); +}; + +#endif diff --git a/libraries/ArduinoThread-master/ThreadController.cpp b/libraries/ArduinoThread-master/ThreadController.cpp new file mode 100644 index 0000000..7d8e41c --- /dev/null +++ b/libraries/ArduinoThread-master/ThreadController.cpp @@ -0,0 +1,114 @@ +#include "Thread.h" +#include "ThreadController.h" + +ThreadController::ThreadController(unsigned long _interval): Thread(){ + cached_size = 0; + + clear(); + setInterval(_interval); + + #ifdef USE_THREAD_NAMES + // Overrides name + ThreadName = "ThreadController "; + ThreadName = ThreadName + ThreadID; + #endif +} + +/* + ThreadController run() (cool stuf) +*/ +void ThreadController::run(){ + // Run this thread before + if(_onRun != NULL) + _onRun(); + + unsigned long time = millis(); + int checks = 0; + for(int i = 0; i < MAX_THREADS && checks < cached_size; i++){ + // Object exists? Is enabled? Timeout exceeded? + if(thread[i]){ + checks++; + if(thread[i]->shouldRun(time)){ + thread[i]->run(); + } + } + } + + // ThreadController extends Thread, so we should flag as runned thread + runned(); +} + + +/* + List controller (boring part) +*/ +bool ThreadController::add(Thread* _thread){ + // Check if the Thread already exists on the array + for(int i = 0; i < MAX_THREADS; i++){ + if(thread[i] != NULL && thread[i]->ThreadID == _thread->ThreadID) + return true; + } + + // Find an empty slot + for(int i = 0; i < MAX_THREADS; i++){ + if(!thread[i]){ + // Found a empty slot, now add Thread + thread[i] = _thread; + cached_size++; + return true; + } + } + + // Array is full + return false; +} + +void ThreadController::remove(int id){ + // Find Threads with the id, and removes + for(int i = 0; i < MAX_THREADS; i++){ + if(thread[i]->ThreadID == id){ + thread[i] = NULL; + cached_size--; + return; + } + } +} + +void ThreadController::remove(Thread* _thread){ + remove(_thread->ThreadID); +} + +void ThreadController::clear(){ + for(int i = 0; i < MAX_THREADS; i++){ + thread[i] = NULL; + } + cached_size = 0; +} + +int ThreadController::size(bool cached){ + if(cached) + return cached_size; + + int size = 0; + for(int i = 0; i < MAX_THREADS; i++){ + if(thread[i]) + size++; + } + cached_size = size; + + return cached_size; +} + +Thread* ThreadController::get(int index){ + int pos = -1; + for(int i = 0; i < MAX_THREADS; i++){ + if(thread[i] != NULL){ + pos++; + + if(pos == index) + return thread[i]; + } + } + + return NULL; +} diff --git a/libraries/ArduinoThread-master/ThreadController.h b/libraries/ArduinoThread-master/ThreadController.h new file mode 100644 index 0000000..8e2888c --- /dev/null +++ b/libraries/ArduinoThread-master/ThreadController.h @@ -0,0 +1,53 @@ +/* + ThreadController.h - Controlls a list of Threads with different timings + + Basicaly, what it does is to keep track of current Threads and run when + necessary. + + ThreadController is an extended class of Thread, because of that, + it allows you to add a ThreadController inside another ThreadController... + + For instructions, go to https://github.com/ivanseidel/ArduinoThread + + Created by Ivan Seidel Gomes, March, 2013. + Released into the public domain. +*/ + +#ifndef ThreadController_h +#define ThreadController_h + +#include "Thread.h" +#include "inttypes.h" + +#define MAX_THREADS 15 + +class ThreadController: public Thread{ +protected: + Thread* thread[MAX_THREADS]; + int cached_size; +public: + ThreadController(unsigned long _interval = 0); + + // run() Method is overrided + void run(); + + // Adds a thread in the first available slot (remove first) + // Returns if the Thread could be added or not + bool add(Thread* _thread); + + // remove the thread (given the Thread* or ThreadID) + void remove(int _id); + void remove(Thread* _thread); + + // Removes all threads + void clear(); + + // Return the quantity of Threads + int size(bool cached = true); + + // Return the I Thread on the array + // Returns NULL if none found + Thread* get(int index); +}; + +#endif diff --git a/libraries/ArduinoThread-master/examples/ControllerInController/ControllerInController.ino b/libraries/ArduinoThread-master/examples/ControllerInController/ControllerInController.ino new file mode 100644 index 0000000..feb8a0a --- /dev/null +++ b/libraries/ArduinoThread-master/examples/ControllerInController/ControllerInController.ino @@ -0,0 +1,78 @@ +#include +#include + +int ledPin = 13; + +// ThreadController that will controll all threads +ThreadController controll = ThreadController(); + +//My Thread +Thread myThread = Thread(); +//His Thread +Thread hisThread = Thread(); +//Blink Led Thread +Thread blinkLedThread = Thread(); +//ThreadController, that will be added to controll +ThreadController groupOfThreads = ThreadController(); + +// callback for myThread +void niceCallback(){ + Serial.print("COOL! I'm running on: "); + Serial.println(millis()); +} + +// callback for hisThread +void boringCallback(){ + Serial.println("BORING..."); +} + +// callback for blinkLedThread +void blinkLed(){ + static bool ledStatus = false; + ledStatus = !ledStatus; + + digitalWrite(ledPin, ledStatus); + + Serial.print("blinking: "); + Serial.println(ledStatus); +} + +void setup(){ + Serial.begin(9600); + + pinMode(ledPin, OUTPUT); + + // Configure myThread + myThread.onRun(niceCallback); + myThread.setInterval(500); + + // Configure hisThread + hisThread.onRun(boringCallback); + hisThread.setInterval(250); + + // Configure blinkLedThread + blinkLedThread.onRun(blinkLed); + blinkLedThread.setInterval(100); + + // Adds myThread to the controll + controll.add(&myThread); + + // Adds hisThread and blinkLedThread to groupOfThreads + groupOfThreads.add(&hisThread); + groupOfThreads.add(&blinkLedThread); + + // Add groupOfThreads to controll + controll.add(&groupOfThreads); + +} + +void loop(){ + // run ThreadController + // this will check every thread inside ThreadController, + // if it should run. If yes, he will run it; + controll.run(); + + // Rest of code + float h = 3.1415; + h/=2; +} \ No newline at end of file diff --git a/libraries/ArduinoThread-master/examples/ControllerWithTimer/ControllerWithTimer.ino b/libraries/ArduinoThread-master/examples/ControllerWithTimer/ControllerWithTimer.ino new file mode 100644 index 0000000..029507f --- /dev/null +++ b/libraries/ArduinoThread-master/examples/ControllerWithTimer/ControllerWithTimer.ino @@ -0,0 +1,100 @@ +#include +#include + +/* + This example, requires a Timer Interrupt Library. + If you are using Arduino NANO, UNO... (with ATmega168/328) + Please go to: http://playground.arduino.cc/code/timer1 + If you are using Arduino DUE, + Please go to: https://github.com/ivanseidel/DueTimer + + Include the library corresponding to your Arduino. +*/ +// #include +// #include + +// ThreadController that will controll all threads +ThreadController controll = ThreadController(); + +//My Thread +Thread myThread = Thread(); +//His Thread +Thread hisThread = Thread(); + +// callback for myThread +void myThreadCallback(){ + Serial.println("myThread\t\tcallback"); +} + +// callback for hisThread +void hisThreadCallback(){ + Serial.println("\thisThread\tcallback"); +} + +// This is the callback for the Timer +void timerCallback(){ + controll.run(); +} + +void setup(){ + Serial.begin(9600); + + // Configure myThread + myThread.onRun(myThreadCallback); + myThread.setInterval(500); + + // Configure myThread + hisThread.onRun(hisThreadCallback); + hisThread.setInterval(200); + + // Adds both threads to the controller + controll.add(&myThread); // & to pass the pointer to it + controll.add(&hisThread); + + /* + If using DueTimer... + */ + // Timer1.attachInterrupt(timerCallback).start(20000); + + /* + If using TimerOne... + */ + // Timer1.initialize(20000); + // Timer1.attachInterrupt(timerCallback); + // Timer1.start(); +} + +void waitSerial(){ + while (!Serial.available()); + delay(10); + while (Serial.available() && Serial.read()); +} + +void loop(){ + while(1){ + noInterrupts(); // Call to disable interrupts + Serial.println("Type anyting to stop myThread!"); + interrupts(); // Call to enable interrupts + waitSerial(); + myThread.enabled = false; + + noInterrupts(); + Serial.println("Type anyting to stop hisThread!"); + interrupts(); + waitSerial(); + hisThread.enabled = false; + + noInterrupts(); + Serial.println("Type anyting to enable myThread!"); + interrupts(); + waitSerial(); + myThread.enabled = true; + + noInterrupts(); + Serial.println("Type anyting to enable hisThread!"); + interrupts(); + waitSerial(); + hisThread.enabled = true; + + } +} \ No newline at end of file diff --git a/libraries/ArduinoThread-master/examples/CustomTimedThread/CustomTimedThread.ino b/libraries/ArduinoThread-master/examples/CustomTimedThread/CustomTimedThread.ino new file mode 100644 index 0000000..146715d --- /dev/null +++ b/libraries/ArduinoThread-master/examples/CustomTimedThread/CustomTimedThread.ino @@ -0,0 +1,257 @@ +/* + This is an example from ArduinoThread. You can find more information + in https://github.com/ivanseidel/ArduinoThread. + + Coded by Ivan Seidel, Jun/2014 - ivanseidel@gmail.com + + Dont be afraid. 90% is commented lines. READ them, they will teach you. +*/ + +#include +#include + +/* + This example provides an object-oriented approach to + develop a custom Thread that overrides the 'shouldRun' + method, to only run the thread after a button was pushed. + + After the push, it should 'keep' running for a desired time. + + It should also provide us, a way to easily implement this + controll multiple times, without trouble. + + We are giving this Custom Thread the name 'ButtonThread'. + + Exemplifying what it does: + + ButtonThread added to our mainController ThreadList + => Instantiated with a custom Pin #, + => and a time duration (in miliseconds) + + + ButtonThread is not running. + + + When the button is pressed: + + Thread will start and keep running. + + If the thread runned for our defined period, + we stop it. + + ================ HOW TO SETUP HARDWARE ================== + In order to make this example work with any arduino, hook up + the pins on the board to 3 buttons. You can change the inputs + if you need below here. + + The Buttons are being SOFTWARE pulled UP (to VCC), and when + pushed, should go LOW. Connect like this: + (Arduino Input) <----> (Btn) <----> GND (-) + + We are using digital pins 9, 10 and 11 as input. + It also uses a LED, but we are using the default one in the board. + + =============== WHAT YO LEARN WITH THIS ================= + 1) Threads are actually running in 'parallel'. + + Since each thread process time is very tiny, they appear + as being runned in parallel. + + Because of that, clicking multiple buttons at any time, + will looks like there is a program for each one of them. + + 2) If you keep the button 'pressed', it will continue to run. + + Since we are 'enabling' the thread, and reseting the timer + flag (_lastButtonPushed) every time the button is pressed, + we should notice that in btn1Callback, where we print this + flag, it will never go beyond 0 if we keep pressing it. + + 3) The LED turns off, only because the Thread runs a last time + with the flag 'enabled' as false. This way, we can turn the + LED off and remain OFF until we press it egain. + + I hope you enjoy, and learn some advanced-cool stuf with this tutorial. + Any feedback is apreciated! +*/ +#define BTN1 9 +#define BTN2 10 +#define BTN3 11 + +#define LED 13 + +// ThreadController that will controll all button threads +ThreadController controll = ThreadController(); + +// Here we implement our custom ButtonThread, that Inherits from Thread +class ButtonThread: public Thread{ +public: + // Our custom thread attributes + int pin; + long duration; + long _lastButtonPushed; + + /* + Our Constructor. This will initialize the thread + with it's corresponding pin and duration after clicked. + */ + ButtonThread(int _pin, long _duration): Thread(){ + // Set our attributes on construct + pin = _pin; + duration = _duration; + _lastButtonPushed = 0; + + // Thread will start disabled + enabled = false; + + // Configure the pin as INPUT and enable pull-up + pinMode(pin, INPUT); + digitalWrite(pin, HIGH); + } + + /* + Override the method responsible for + checking if the thread should run. + + It will first check if the button is pressed. + If so, we enable the thread, and then let the + "Old" default Thread method 'shouldRun' return if + it should run. + */ + bool shouldRun(unsigned long time){ + // Override enabled on thread when pin goes LOW. + if(digitalRead(pin) == LOW){ + enabled = true; + /* + Here, we save the current time in this object, + to compare it later. + + the 'time' parameter in this method, is an override for the + 'millis()' method. It allows who is checking the thread, to + pass a custom time. + + This is sintax for writing an 'inline' if is very usefull, + it's the same as: + if(time > 0){ + _lastButtonPushed = time; + }else{ + _lastButtonPushed = millis(); + } + */ + _lastButtonPushed = (time ? time : millis()); + } + + // Let default method check for it. + return Thread::shouldRun(time); + } + + /* + We 'disable' the thread after the duration on the + 'run' method. + + What we should do here, is check if the time saved + in the _lastButtonPushed variable plus the duration, + is greater than our current time. If that's true, it + means we exceeded the thread time, and that we must + disable it and prevent from running. + */ + void run(){ + // Check if time elapsed since last button push + if(millis() > _lastButtonPushed + duration){ + // It exceeded time. We should disable it. + enabled = false; + } + + /* + Run the thread. + + Note that this method will only get called + from the ThreadList, IF the 'shouldRun' returns true. + + If the thread is not enabled anymore, it will run a 'last' + time with the flag 'enabled' as false, meaning it's the last + run in the period. You can use it for doing something only + before it stops running. + */ + Thread::run(); + } +}; + +/* + ButtonThreads objects instantiation + (we are instantiating 2 as a member, and one + as pointer in the setup, just to show you + different ways of doing it) +*/ + +// Thread 1 will be reading BTN1 pin, and will run for 3 secs +ButtonThread btn1Thread(BTN1, 3000); + +// Thread 2 will be reading BTN1 pin, and will run for 5 secs +ButtonThread btn2Thread = ButtonThread(BTN2, 5000); + +// Thread 3 will be instantiated in the setup() +ButtonThread *btn3Thread; + + +/* + Callback for ButtonThreads +*/ +void btn1Callback(){ + // When it's running, this thread will write to the serial. + /* + This math will print 'how long' the thread has been running, + since the button was/is pressed. + + After pressing it, it should print as 0, and goes up untill + the thread duration (in this case, +-5000ms). + */ + Serial.print("BTN 1 Thread: "); + Serial.println(millis() - btn1Thread._lastButtonPushed); +} + +void btn2Callback(){ + /* + This thread will remain with the LED on pin 13 turned on + while it is running. + + We detect that this method is called for the LAST time, if + the flag 'enabled' is FALSE on the btn2Thread object. + + So, basically: If it's TRUE, we should turn ON the led, if not + we should turn OFF. We can simplify that into one line. + (Same 'inline' sintax as above) + */ + digitalWrite(LED, btn2Thread.enabled ? HIGH : LOW); +} + +void btn3Callback(){ + // When it's running, this thread will also write to the serial + Serial.println("BTN 3 Thread"); +} + +void setup(){ + // Configure serial and output pins + Serial.begin(9600); + pinMode(LED, OUTPUT); + + // Configure btn1Thread callback + // (During the 'enabled' time, it will run every 100ms, aka Interval) + btn1Thread.onRun(btn1Callback); + btn1Thread.setInterval(100); + + // Configure btn2Thread callback and interval + btn2Thread.onRun(btn2Callback); + btn2Thread.setInterval(200); + + // Instantiate btn3Thread + btn3Thread = new ButtonThread(BTN3, 4000); + // Configure btn3Thread callback and interval + btn3Thread->onRun(btn3Callback); + btn3Thread->setInterval(100); + + // Adds all threads to the controller + controll.add(&btn1Thread); // & to pass the pointer to it + controll.add(&btn2Thread); + controll.add(btn3Thread); // Its already a pointer, no need for & +} + +void loop(){ + // Here we just run the main thread controller + controll.run(); +} diff --git a/libraries/ArduinoThread-master/examples/SensorThread/SensorThread.ino b/libraries/ArduinoThread-master/examples/SensorThread/SensorThread.ino new file mode 100644 index 0000000..9c5c91f --- /dev/null +++ b/libraries/ArduinoThread-master/examples/SensorThread/SensorThread.ino @@ -0,0 +1,105 @@ +#include "Thread.h" +#include "ThreadController.h" +/* + This is a more "complex" for of using Threads. + You can also inherit from Thread, and do your entire code on the class. + + This allows you, to create for example: + Sensor Readings (aquire, filter, and save localy values) + Custom Blinks, Beeps... + Anything you can imagine. + + Threads are more "usefull" when used within Timer interrupts + + This way of coding is more "reusable", and "correct" (Object Oriented) +*/ + + +/* + This example, requires a Timer Interrupt Library. + If you are using Arduino NANO, UNO... (with ATmega168/328) + Please go to: http://playground.arduino.cc/code/timer1 + If you are using Arduino DUE, + Please go to: https://github.com/ivanseidel/DueTimer + + Include the library corresponding to your Arduino. +*/ +#include +// #include + +// Create a new Class, called SensorThread, that inherits from Thread +class SensorThread: public Thread +{ +public: + int value; + int pin; + + // No, "run" cannot be anything... + // Because Thread uses the method "run" to run threads, + // we MUST overload this method here. using anything other + // than "run" will not work properly... + void run(){ + // Reads the analog pin, and saves it localy + value = map(analogRead(pin), 0,1023,0,255); + runned(); + } +}; + +// Now, let's use our new class of Thread +SensorThread analog1 = SensorThread(); +SensorThread analog2 = SensorThread(); + +// Instantiate a new ThreadController +ThreadController controller = ThreadController(); + +// This is the callback for the Timer +void timerCallback(){ + controller.run(); +} + +void setup(){ + + Serial.begin(9600); + + // Configures Thread analog1 + analog1.pin = A1; + analog1.setInterval(100); + + // Configures Thread analog2 + analog2.pin = A2; + analog2.setInterval(100); + + // Add the Threads to our ThreadController + controller.add(&analog1); + controller.add(&analog2); + + /* + If using DueTimer... + */ + Timer1.attachInterrupt(timerCallback).start(10000); + + /* + If using TimerOne... + */ + // Timer1.initialize(20000); + // Timer1.attachInterrupt(timerCallback); + // Timer1.start(); + +} + +void loop(){ + // Do complex-crazy-timeconsuming-tasks here + delay(1000); + + // Get the fresh readings + Serial.print("Analog1 Thread: "); + Serial.println(analog1.value); + + Serial.print("Analog2 Thread: "); + Serial.println(analog2.value); + + // Do more complex-crazy-timeconsuming-tasks here + delay(1000); + +} + diff --git a/libraries/ArduinoThread-master/examples/SimpleThread/SimpleThread.ino b/libraries/ArduinoThread-master/examples/SimpleThread/SimpleThread.ino new file mode 100644 index 0000000..e45362a --- /dev/null +++ b/libraries/ArduinoThread-master/examples/SimpleThread/SimpleThread.ino @@ -0,0 +1,35 @@ +#include +int ledPin = 13; + +//My simple Thread +Thread myThread = Thread(); + +// callback for myThread +void niceCallback(){ + static bool ledStatus = false; + ledStatus = !ledStatus; + + digitalWrite(ledPin, ledStatus); + + Serial.print("COOL! I'm running on: "); + Serial.println(millis()); +} + +void setup(){ + Serial.begin(9600); + + pinMode(ledPin, OUTPUT); + + myThread.onRun(niceCallback); + myThread.setInterval(500); +} + +void loop(){ + // checks if thread should run + if(myThread.shouldRun()) + myThread.run(); + + // Other code... + int x = 0; + x = 1 + 2; +} \ No newline at end of file diff --git a/libraries/ArduinoThread-master/examples/SimpleThreadController/SimpleThreadController.ino b/libraries/ArduinoThread-master/examples/SimpleThreadController/SimpleThreadController.ino new file mode 100644 index 0000000..949290b --- /dev/null +++ b/libraries/ArduinoThread-master/examples/SimpleThreadController/SimpleThreadController.ino @@ -0,0 +1,48 @@ +#include +#include + +// ThreadController that will controll all threads +ThreadController controll = ThreadController(); + +//My Thread (as a pointer) +Thread* myThread = new Thread(); +//His Thread (not pointer) +Thread hisThread = Thread(); + +// callback for myThread +void niceCallback(){ + Serial.print("COOL! I'm running on: "); + Serial.println(millis()); +} + +// callback for hisThread +void boringCallback(){ + Serial.println("BORING..."); +} + +void setup(){ + Serial.begin(9600); + + // Configure myThread + myThread->onRun(niceCallback); + myThread->setInterval(500); + + // Configure myThread + hisThread.onRun(boringCallback); + hisThread.setInterval(250); + + // Adds both threads to the controller + controll.add(myThread); + controll.add(&hisThread); // & to pass the pointer to it +} + +void loop(){ + // run ThreadController + // this will check every thread inside ThreadController, + // if it should run. If yes, he will run it; + controll.run(); + + // Rest of code + float h = 3.1415; + h/=2; +} \ No newline at end of file diff --git a/libraries/ArduinoThread-master/examples/StaticThreadController/StaticThreadController.ino b/libraries/ArduinoThread-master/examples/StaticThreadController/StaticThreadController.ino new file mode 100644 index 0000000..bb4155a --- /dev/null +++ b/libraries/ArduinoThread-master/examples/StaticThreadController/StaticThreadController.ino @@ -0,0 +1,56 @@ +#include +#include + +//My Thread (as a pointer) +Thread* myThread = new Thread(); +//His Thread (not pointer) +Thread hisThread = Thread(); + +// callback for myThread +void niceCallback(){ + Serial.print("COOL! I'm running on: "); + Serial.println(millis()); +} + +// callback for hisThread +void boringCallback(){ + Serial.println("BORING..."); +} + +// callback for theThread +void justCallback(){ + Serial.println("executing..."); +} + +//The Thread (as a pointer) with justCallback initialized +Thread* theThread = new Thread(justCallback); + +// StaticThreadController that will controll all threads +// All non-pointers go with '&', but pointers go without '&', +StaticThreadController<3> controll (myThread, &hisThread, theThread); + +void setup(){ + Serial.begin(9600); + + // Configure myThread + myThread->onRun(niceCallback); + myThread->setInterval(500); + + // Configure hisThread + hisThread.onRun(boringCallback); + hisThread.setInterval(250); + + // Set interval for theThread using StaticThreadController interface + controll[3].setInterval(375); +} + +void loop(){ + // run StaticThreadController + // this will check every thread inside ThreadController, + // if it should run. If yes, he will run it; + controll.run(); + + // Rest of code + float h = 3.1415; + h/=2; +} diff --git a/libraries/ArduinoThread-master/extras/ArduinoThread.png b/libraries/ArduinoThread-master/extras/ArduinoThread.png new file mode 100644 index 0000000000000000000000000000000000000000..ef80394cf0d11d35b3ae42fb9103096b5f04b7a5 GIT binary patch literal 8512 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 literal 0 HcmV?d00001 diff --git a/libraries/ArduinoThread-master/keywords.txt b/libraries/ArduinoThread-master/keywords.txt new file mode 100644 index 0000000..09b104d --- /dev/null +++ b/libraries/ArduinoThread-master/keywords.txt @@ -0,0 +1,32 @@ +####################################### +# Syntax Coloring +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Thread KEYWORD1 +ThreadController KEYWORD1 +StaticThreadController KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +runned KEYWORD2 +setInterval KEYWORD2 +shouldRun KEYWORD2 +onRun KEYWORD2 +run KEYWORD2 + +# Specific of ThreadController or StaticThreadController +add KEYWORD2 +remove KEYWORD2 +clear KEYWORD2 +size KEYWORD2 +get KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/ArduinoThread-master/library.json b/libraries/ArduinoThread-master/library.json new file mode 100644 index 0000000..975f790 --- /dev/null +++ b/libraries/ArduinoThread-master/library.json @@ -0,0 +1,12 @@ +{ + "name": "Thread", + "keywords": "thread, task", + "description": "A library for managing the periodic execution of multiple tasks", + "repository": + { + "type": "git", + "url": "https://github.com/ivanseidel/ArduinoThread.git" + }, + "frameworks": "arduino", + "platforms": "atmelavr" +} diff --git a/libraries/ArduinoThread-master/library.properties b/libraries/ArduinoThread-master/library.properties new file mode 100644 index 0000000..59eed7a --- /dev/null +++ b/libraries/ArduinoThread-master/library.properties @@ -0,0 +1,9 @@ +name=ArduinoThread +version=2.1.1 +author=Ivan Seidel +maintainer=Ivan Seidel +sentence=A simple way to run Threads on Arduino +paragraph=This Library helps to maintain organized and to facilitate the use of multiple tasks. We can use Timers Interrupts, and make it really powerfull, running "pseudo-background" tasks on the rug. +category=Timing +url=https://github.com/ivanseidel/ArduinoThread +architectures=* diff --git a/libraries/ButtonDebounce/LICENSE b/libraries/ButtonDebounce/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/libraries/ButtonDebounce/LICENSE @@ -0,0 +1,674 @@ + GNU 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. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/libraries/ButtonDebounce/README.md b/libraries/ButtonDebounce/README.md new file mode 100644 index 0000000..3de18a0 --- /dev/null +++ b/libraries/ButtonDebounce/README.md @@ -0,0 +1,36 @@ +# ButtonDebounce + ButtonDebounce.h - Arduino Library for Button Debounce. + + Usage +============ + +## 1. Buttons definition + + +The button is create with pass pin and time to debounce (ms). + + +Each button is defined in isolation in terms of: + +* a `update function` executed in loop verifying with the debounce and the state of button changed. +* a `state function` return the last state of button changed. +* a `setCallback function` set the callback function that will execute when the state of button changed. +* a `clear function` clear variables of state of button. + +Example: + +``` +void buttonChanged(int state) { + // do something +} +ButtonDebounce button(3, 250); // PIN 3 and debounce 100ms +button.setCallback(buttonChanged); +``` + +## 2. Periodic verification + +Now all you need is to periodically activate the button verification which check the pin to determine if the state changed and fires the corresponding code in callback. The following code goes into the `loop()` function and needs to be executed as often as possible: this means you shouldn't introduce any `delay(...)` statement in your code, otherwise the library will not work as expected: + +``` +button.update(); +``` diff --git a/libraries/ButtonDebounce/examples/simple_btn/simple_btn.ino b/libraries/ButtonDebounce/examples/simple_btn/simple_btn.ino new file mode 100644 index 0000000..bec0db0 --- /dev/null +++ b/libraries/ButtonDebounce/examples/simple_btn/simple_btn.ino @@ -0,0 +1,13 @@ +#include + +ButtonDebounce button(10, 250); +void setup() { + Serial.begin(115200); +} + +void loop() { + button.update(); + if(button.state() == HIGH){ + Serial.println("Clicked"); + } +} diff --git a/libraries/ButtonDebounce/examples/simple_callback/simple_callback.ino b/libraries/ButtonDebounce/examples/simple_callback/simple_callback.ino new file mode 100644 index 0000000..2954998 --- /dev/null +++ b/libraries/ButtonDebounce/examples/simple_callback/simple_callback.ino @@ -0,0 +1,15 @@ +#include + +ButtonDebounce button(10, 250); +void buttonChanged(int state){ + Serial.println("Changed: " + String(state)); +} + +void setup() { + Serial.begin(115200); + button.setCallback(buttonChanged); +} + +void loop() { + button.update(); +} diff --git a/libraries/ButtonDebounce/keywords.txt b/libraries/ButtonDebounce/keywords.txt new file mode 100644 index 0000000..9cef56d --- /dev/null +++ b/libraries/ButtonDebounce/keywords.txt @@ -0,0 +1,24 @@ +####################################### +# ButtonDebounce +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ButtonDebounce KEYWORD1 +Button KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +update KEYWORD2 +state KEYWORD2 +setCallback KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +BTN_CALLBACK LITERAL1 diff --git a/libraries/ButtonDebounce/library.properties b/libraries/ButtonDebounce/library.properties new file mode 100644 index 0000000..47eb9cc --- /dev/null +++ b/libraries/ButtonDebounce/library.properties @@ -0,0 +1,10 @@ +name=ButtonDebounce +version=1.0.1 +author=Maykon L. Capellari +maintainer=Maykon L. Capellari +sentence=A library that makes easy to use button with debounce. +paragraph=Make easy to use button with debounce. +category=Sensors +url=https://github.com/maykon/ButtonDebounce +architectures=* +includes=ButtonDebounce.h \ No newline at end of file diff --git a/libraries/ButtonDebounce/src/ButtonDebounce.cpp b/libraries/ButtonDebounce/src/ButtonDebounce.cpp new file mode 100644 index 0000000..72e69d0 --- /dev/null +++ b/libraries/ButtonDebounce/src/ButtonDebounce.cpp @@ -0,0 +1,31 @@ +#include "Arduino.h" +#include "ButtonDebounce.h" + +ButtonDebounce::ButtonDebounce(int pin, unsigned long delay){ + pinMode(pin, INPUT_PULLUP); + _pin = pin; + _delay = delay; + _lastDebounceTime = 0; + _lastStateBtn = HIGH; +} + +bool ButtonDebounce::isTimeToUpdate(){ + return (millis() - _lastDebounceTime) > _delay; +} + +void ButtonDebounce::update(){ + if(!isTimeToUpdate()) return; + _lastDebounceTime = millis(); + int btnState = digitalRead(_pin); + if(btnState == _lastStateBtn) return; + _lastStateBtn = btnState; + if(this->callback) this->callback(_lastStateBtn); +} + +int ButtonDebounce::state(){ + return _lastStateBtn; +} + +void ButtonDebounce::setCallback(BTN_CALLBACK){ + this->callback = callback; +} \ No newline at end of file diff --git a/libraries/ButtonDebounce/src/ButtonDebounce.h b/libraries/ButtonDebounce/src/ButtonDebounce.h new file mode 100644 index 0000000..f7d09ad --- /dev/null +++ b/libraries/ButtonDebounce/src/ButtonDebounce.h @@ -0,0 +1,28 @@ +/* + ButtonDebounce.h - Library for Button Debounce. + Created by Maykon L. Capellari, September 30, 2017. + Released into the public domain. +*/ +#ifndef ButtonDebounce_h +#define ButtonDebounce_h + +#include "Arduino.h" + +#define BTN_CALLBACK void (*callback)(int) + +class ButtonDebounce{ + private: + int _pin; + unsigned long _delay; + unsigned long _lastDebounceTime; + int _lastStateBtn; + BTN_CALLBACK; + bool isTimeToUpdate(); + public: + ButtonDebounce(int pin, unsigned long delay); + void update(); + int state(); + void setCallback(BTN_CALLBACK); +}; + +#endif diff --git a/libraries/ESP32_BLE_Arduino/README.md b/libraries/ESP32_BLE_Arduino/README.md new file mode 100644 index 0000000..e80fbe0 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/README.md @@ -0,0 +1,15 @@ +# ESP32 BLE for Arduino +The Arduino IDE provides an excellent library package manager where versions of libraries can be downloaded and installed. This Github project provides the repository for the ESP32 BLE support for Arduino. + +The actual source of the project which is being maintained can be found here: + +https://github.com/nkolban/esp32-snippets + +Issues and questions should be raised here: + +https://github.com/nkolban/esp32-snippets/issues + + +Documentation for using the library can be found here: + +https://github.com/nkolban/esp32-snippets/tree/master/Documentation \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_client/BLE_client.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_client/BLE_client.ino new file mode 100644 index 0000000..4c58299 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_client/BLE_client.ino @@ -0,0 +1,160 @@ +/** + * A BLE client example that is rich in capabilities. + * There is a lot new capabilities implemented. + * author unknown + * updated by chegewara + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); + +static boolean doConnect = false; +static boolean connected = false; +static boolean doScan = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; +static BLEAdvertisedDevice* myDevice; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); + Serial.print("data: "); + Serial.println((char*)pData); +} + +class MyClientCallback : public BLEClientCallbacks { + void onConnect(BLEClient* pclient) { + } + + void onDisconnect(BLEClient* pclient) { + connected = false; + Serial.println("onDisconnect"); + } +}; + +bool connectToServer() { + Serial.print("Forming a connection to "); + Serial.println(myDevice->getAddress().toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + pClient->setClientCallbacks(new MyClientCallback()); + + // Connect to the remove BLE Server. + pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private) + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + Serial.println(" - Found our service"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + if(pRemoteCharacteristic->canRead()) { + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + } + + if(pRemoteCharacteristic->canNotify()) + pRemoteCharacteristic->registerForNotify(notifyCallback); + + connected = true; +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + + BLEDevice::getScan()->stop(); + myDevice = new BLEAdvertisedDevice(advertisedDevice); + doConnect = true; + doScan = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + pBLEScan->start(5, false); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer()) { + Serial.println("We are now connected to the BLE Server."); + } else { + Serial.println("We have failed to connect to the server; there is nothin more we will do."); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + }else if(doScan){ + BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_iBeacon/BLE_iBeacon.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_iBeacon/BLE_iBeacon.ino new file mode 100644 index 0000000..e43174d --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_iBeacon/BLE_iBeacon.ino @@ -0,0 +1,103 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by pcbreflux +*/ + + +/* + Create a BLE server that will send periodic iBeacon frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep + +*/ +#include "sys/time.h" + +#include "BLEDevice.h" +#include "BLEUtils.h" +#include "BLEBeacon.h" +#include "esp_sleep.h" + +#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up +RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory +RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory + +#ifdef __cplusplus +extern "C" { +#endif + +uint8_t temprature_sens_read(); +//uint8_t g_phyFuns; + +#ifdef __cplusplus +} +#endif + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +BLEAdvertising *pAdvertising; +struct timeval now; + +#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/) + +void setBeacon() { + + BLEBeacon oBeacon = BLEBeacon(); + oBeacon.setManufacturerId(0x4C00); // fake Apple 0x004C LSB (ENDIAN_CHANGE_U16!) + oBeacon.setProximityUUID(BLEUUID(BEACON_UUID)); + oBeacon.setMajor((bootcount & 0xFFFF0000) >> 16); + oBeacon.setMinor(bootcount&0xFFFF); + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); + + oAdvertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED 0x04 + + std::string strServiceData = ""; + + strServiceData += (char)26; // Len + strServiceData += (char)0xFF; // Type + strServiceData += oBeacon.getData(); + oAdvertisementData.addData(strServiceData); + + pAdvertising->setAdvertisementData(oAdvertisementData); + pAdvertising->setScanResponseData(oScanResponseData); + +} + +void setup() { + + + Serial.begin(115200); + gettimeofday(&now, NULL); + + Serial.printf("start ESP32 %d\n",bootcount++); + + Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n",now.tv_sec,now.tv_sec-last); + + last = now.tv_sec; + + // Create the BLE Device + BLEDevice::init(""); + + // Create the BLE Server + // BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage + + pAdvertising = BLEDevice::getAdvertising(); + + setBeacon(); + // Start advertising + pAdvertising->start(); + Serial.println("Advertizing started..."); + delay(100); + pAdvertising->stop(); + Serial.printf("enter deep sleep\n"); + esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION); + Serial.printf("in deep sleep\n"); +} + +void loop() { +} diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_notify/BLE_notify.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_notify/BLE_notify.ino new file mode 100644 index 0000000..42b9e72 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_notify/BLE_notify.ino @@ -0,0 +1,110 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + updated by chegewara + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + A connect hander associated with the server starts a background task that performs notification + every couple of seconds. +*/ +#include +#include +#include +#include + +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; +bool deviceConnected = false; +bool oldDeviceConnected = false; +uint32_t value = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("ESP32"); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + // Create a BLE Descriptor + pCharacteristic->addDescriptor(new BLE2902()); + + // Start the service + pService->start(); + + // Start advertising + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(false); + pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter + BLEDevice::startAdvertising(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + // notify changed value + if (deviceConnected) { + pCharacteristic->setValue((uint8_t*)&value, 4); + pCharacteristic->notify(); + value++; + delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms + } + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } +} \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_scan/BLE_scan.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_scan/BLE_scan.ino new file mode 100644 index 0000000..094f793 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_scan/BLE_scan.ino @@ -0,0 +1,40 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include +#include + +int scanTime = 5; //In seconds +BLEScan* pBLEScan; + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Scanning..."); + + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); //create new scan + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); // less or equal setInterval value +} + +void loop() { + // put your main code here, to run repeatedly: + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); + Serial.print("Devices found: "); + Serial.println(foundDevices.getCount()); + Serial.println("Scan done!"); + pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory + delay(2000); +} \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_server/BLE_server.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_server/BLE_server.ino new file mode 100644 index 0000000..3f9176a --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_server/BLE_server.ino @@ -0,0 +1,45 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini + updates by chegewara +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("Long name works now"); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_server_multiconnect/BLE_server_multiconnect.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_server_multiconnect/BLE_server_multiconnect.ino new file mode 100644 index 0000000..90704ef --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_server_multiconnect/BLE_server_multiconnect.ino @@ -0,0 +1,111 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + updated by chegewara + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + A connect hander associated with the server starts a background task that performs notification + every couple of seconds. +*/ +#include +#include +#include +#include + +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; +bool deviceConnected = false; +bool oldDeviceConnected = false; +uint32_t value = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + BLEDevice::startAdvertising(); + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("ESP32"); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + // Create a BLE Descriptor + pCharacteristic->addDescriptor(new BLE2902()); + + // Start the service + pService->start(); + + // Start advertising + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(false); + pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter + BLEDevice::startAdvertising(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + // notify changed value + if (deviceConnected) { + pCharacteristic->setValue((uint8_t*)&value, 4); + pCharacteristic->notify(); + value++; + delay(10); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms + } + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } +} diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_uart/BLE_uart.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_uart/BLE_uart.ino new file mode 100644 index 0000000..35b570b --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_uart/BLE_uart.ino @@ -0,0 +1,125 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E + Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" + Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY" + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + In this example rxValue is the data received (only accessible inside that function). + And txValue is the data to be sent, in this example just a byte incremented every second. +*/ +#include +#include +#include +#include + +BLEServer *pServer = NULL; +BLECharacteristic * pTxCharacteristic; +bool deviceConnected = false; +bool oldDeviceConnected = false; +uint8_t txValue = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID +#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string rxValue = pCharacteristic->getValue(); + + if (rxValue.length() > 0) { + Serial.println("*********"); + Serial.print("Received Value: "); + for (int i = 0; i < rxValue.length(); i++) + Serial.print(rxValue[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("UART Service"); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pTxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_TX, + BLECharacteristic::PROPERTY_NOTIFY + ); + + pTxCharacteristic->addDescriptor(new BLE2902()); + + BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE + ); + + pRxCharacteristic->setCallbacks(new MyCallbacks()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + + if (deviceConnected) { + pTxCharacteristic->setValue(&txValue, 1); + pTxCharacteristic->notify(); + txValue++; + delay(10); // bluetooth stack will go into congestion, if too many packets are sent + } + + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } +} diff --git a/libraries/ESP32_BLE_Arduino/examples/BLE_write/BLE_write.ino b/libraries/ESP32_BLE_Arduino/examples/BLE_write/BLE_write.ino new file mode 100644 index 0000000..24a0cd2 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/examples/BLE_write/BLE_write.ino @@ -0,0 +1,65 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include +#include +#include + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + + if (value.length() > 0) { + Serial.println("*********"); + Serial.print("New value: "); + for (int i = 0; i < value.length(); i++) + Serial.print(value[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + +void setup() { + Serial.begin(115200); + + Serial.println("1- Download and install an BLE scanner app in your phone"); + Serial.println("2- Scan for BLE devices in the app"); + Serial.println("3- Connect to MyESP32"); + Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something"); + Serial.println("5- See the magic =)"); + + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); + + BLEService *pService = pServer->createService(SERVICE_UUID); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + pCharacteristic->setValue("Hello World"); + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/library.properties b/libraries/ESP32_BLE_Arduino/library.properties new file mode 100644 index 0000000..8c2a019 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/library.properties @@ -0,0 +1,10 @@ +name=ESP32 BLE Arduino +version=1.0.1 +author=Neil Kolban +maintainer=Dariusz Krempa +sentence=BLE functions for ESP32 +paragraph=This library provides an implementation Bluetooth Low Energy support for the ESP32 using the Arduino platform. +category=Communication +url=https://github.com/nkolban/ESP32_BLE_Arduino +architectures=esp32 +includes=BLEDevice.h, BLEUtils.h, BLEScan.h, BLEAdvertisedDevice.h diff --git a/libraries/ESP32_BLE_Arduino/src/BLE2902.cpp b/libraries/ESP32_BLE_Arduino/src/BLE2902.cpp new file mode 100644 index 0000000..23d9c77 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLE2902.cpp @@ -0,0 +1,62 @@ +/* + * BLE2902.cpp + * + * Created on: Jun 25, 2017 + * Author: kolban + */ + +/* + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLE2902.h" + +BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { + uint8_t data[2] = { 0, 0 }; + setValue(data, 2); +} // BLE2902 + + +/** + * @brief Get the notifications value. + * @return The notifications value. True if notifications are enabled and false if not. + */ +bool BLE2902::getNotifications() { + return (getValue()[0] & (1 << 0)) != 0; +} // getNotifications + + +/** + * @brief Get the indications value. + * @return The indications value. True if indications are enabled and false if not. + */ +bool BLE2902::getIndications() { + return (getValue()[0] & (1 << 1)) != 0; +} // getIndications + + +/** + * @brief Set the indications flag. + * @param [in] flag The indications flag. + */ +void BLE2902::setIndications(bool flag) { + uint8_t *pValue = getValue(); + if (flag) pValue[0] |= 1 << 1; + else pValue[0] &= ~(1 << 1); +} // setIndications + + +/** + * @brief Set the notifications flag. + * @param [in] flag The notifications flag. + */ +void BLE2902::setNotifications(bool flag) { + uint8_t *pValue = getValue(); + if (flag) pValue[0] |= 1 << 0; + else pValue[0] &= ~(1 << 0); +} // setNotifications + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLE2902.h b/libraries/ESP32_BLE_Arduino/src/BLE2902.h new file mode 100644 index 0000000..397360a --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLE2902.h @@ -0,0 +1,34 @@ +/* + * BLE2902.h + * + * Created on: Jun 25, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLE2902_H_ +#define COMPONENTS_CPP_UTILS_BLE2902_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLEDescriptor.h" + +/** + * @brief Descriptor for Client Characteristic Configuration. + * + * This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + */ +class BLE2902: public BLEDescriptor { +public: + BLE2902(); + bool getNotifications(); + bool getIndications(); + void setNotifications(bool flag); + void setIndications(bool flag); + +}; // BLE2902 + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLE2904.cpp b/libraries/ESP32_BLE_Arduino/src/BLE2904.cpp new file mode 100644 index 0000000..02252a1 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLE2904.cpp @@ -0,0 +1,74 @@ +/* + * BLE2904.cpp + * + * Created on: Dec 23, 2017 + * Author: kolban + */ + +/* + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLE2904.h" + + +BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t) 0x2904)) { + m_data.m_format = 0; + m_data.m_exponent = 0; + m_data.m_namespace = 1; // 1 = Bluetooth SIG Assigned Numbers + m_data.m_unit = 0; + m_data.m_description = 0; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} // BLE2902 + + +/** + * @brief Set the description. + */ +void BLE2904::setDescription(uint16_t description) { + m_data.m_description = description; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} + + +/** + * @brief Set the exponent. + */ +void BLE2904::setExponent(int8_t exponent) { + m_data.m_exponent = exponent; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} // setExponent + + +/** + * @brief Set the format. + */ +void BLE2904::setFormat(uint8_t format) { + m_data.m_format = format; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} // setFormat + + +/** + * @brief Set the namespace. + */ +void BLE2904::setNamespace(uint8_t namespace_value) { + m_data.m_namespace = namespace_value; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} // setNamespace + + +/** + * @brief Set the units for this value. It should be one of the encoded values defined here: + * https://www.bluetooth.com/specifications/assigned-numbers/units + * @param [in] unit The type of units of this characteristic as defined by assigned numbers. + */ +void BLE2904::setUnit(uint16_t unit) { + m_data.m_unit = unit; + setValue((uint8_t*) &m_data, sizeof(m_data)); +} // setUnit + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLE2904.h b/libraries/ESP32_BLE_Arduino/src/BLE2904.h new file mode 100644 index 0000000..cb337e2 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLE2904.h @@ -0,0 +1,74 @@ +/* + * BLE2904.h + * + * Created on: Dec 23, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLE2904_H_ +#define COMPONENTS_CPP_UTILS_BLE2904_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLEDescriptor.h" + +struct BLE2904_Data { + uint8_t m_format; + int8_t m_exponent; + uint16_t m_unit; // See https://www.bluetooth.com/specifications/assigned-numbers/units + uint8_t m_namespace; + uint16_t m_description; + +} __attribute__((packed)); + +/** + * @brief Descriptor for Characteristic Presentation Format. + * + * This is a convenience descriptor for the Characteristic Presentation Format which has a UUID of 0x2904. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml + */ +class BLE2904: public BLEDescriptor { +public: + BLE2904(); + static const uint8_t FORMAT_BOOLEAN = 1; + static const uint8_t FORMAT_UINT2 = 2; + static const uint8_t FORMAT_UINT4 = 3; + static const uint8_t FORMAT_UINT8 = 4; + static const uint8_t FORMAT_UINT12 = 5; + static const uint8_t FORMAT_UINT16 = 6; + static const uint8_t FORMAT_UINT24 = 7; + static const uint8_t FORMAT_UINT32 = 8; + static const uint8_t FORMAT_UINT48 = 9; + static const uint8_t FORMAT_UINT64 = 10; + static const uint8_t FORMAT_UINT128 = 11; + static const uint8_t FORMAT_SINT8 = 12; + static const uint8_t FORMAT_SINT12 = 13; + static const uint8_t FORMAT_SINT16 = 14; + static const uint8_t FORMAT_SINT24 = 15; + static const uint8_t FORMAT_SINT32 = 16; + static const uint8_t FORMAT_SINT48 = 17; + static const uint8_t FORMAT_SINT64 = 18; + static const uint8_t FORMAT_SINT128 = 19; + static const uint8_t FORMAT_FLOAT32 = 20; + static const uint8_t FORMAT_FLOAT64 = 21; + static const uint8_t FORMAT_SFLOAT16 = 22; + static const uint8_t FORMAT_SFLOAT32 = 23; + static const uint8_t FORMAT_IEEE20601 = 24; + static const uint8_t FORMAT_UTF8 = 25; + static const uint8_t FORMAT_UTF16 = 26; + static const uint8_t FORMAT_OPAQUE = 27; + + void setDescription(uint16_t); + void setExponent(int8_t exponent); + void setFormat(uint8_t format); + void setNamespace(uint8_t namespace_value); + void setUnit(uint16_t unit); + +private: + BLE2904_Data m_data; +}; // BLE2904 + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLE2904_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAddress.cpp b/libraries/ESP32_BLE_Arduino/src/BLEAddress.cpp new file mode 100644 index 0000000..d688334 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAddress.cpp @@ -0,0 +1,95 @@ +/* + * BLEAddress.cpp + * + * Created on: Jul 2, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLEAddress.h" +#include +#include +#include +#include +#include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + + +/** + * @brief Create an address from the native ESP32 representation. + * @param [in] address The native representation. + */ +BLEAddress::BLEAddress(esp_bd_addr_t address) { + memcpy(m_address, address, ESP_BD_ADDR_LEN); +} // BLEAddress + + +/** + * @brief Create an address from a hex string + * + * A hex string is of the format: + * ``` + * 00:00:00:00:00:00 + * ``` + * which is 17 characters in length. + * + * @param [in] stringAddress The hex representation of the address. + */ +BLEAddress::BLEAddress(std::string stringAddress) { + if (stringAddress.length() != 17) return; + + int data[6]; + sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]); + m_address[0] = (uint8_t) data[0]; + m_address[1] = (uint8_t) data[1]; + m_address[2] = (uint8_t) data[2]; + m_address[3] = (uint8_t) data[3]; + m_address[4] = (uint8_t) data[4]; + m_address[5] = (uint8_t) data[5]; +} // BLEAddress + + +/** + * @brief Determine if this address equals another. + * @param [in] otherAddress The other address to compare against. + * @return True if the addresses are equal. + */ +bool BLEAddress::equals(BLEAddress otherAddress) { + return memcmp(otherAddress.getNative(), m_address, 6) == 0; +} // equals + + +/** + * @brief Return the native representation of the address. + * @return The native representation of the address. + */ +esp_bd_addr_t *BLEAddress::getNative() { + return &m_address; +} // getNative + + +/** + * @brief Convert a BLE address to a string. + * + * A string representation of an address is in the format: + * + * ``` + * xx:xx:xx:xx:xx:xx + * ``` + * + * @return The string representation of the address. + */ +std::string BLEAddress::toString() { + std::stringstream stream; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[0] << ':'; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[1] << ':'; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[2] << ':'; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[3] << ':'; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[4] << ':'; + stream << std::setfill('0') << std::setw(2) << std::hex << (int) ((uint8_t*) (m_address))[5]; + return stream.str(); +} // toString +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAddress.h b/libraries/ESP32_BLE_Arduino/src/BLEAddress.h new file mode 100644 index 0000000..7eff4da --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAddress.h @@ -0,0 +1,34 @@ +/* + * BLEAddress.h + * + * Created on: Jul 2, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEADDRESS_H_ +#define COMPONENTS_CPP_UTILS_BLEADDRESS_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include // ESP32 BLE +#include + + +/** + * @brief A %BLE device address. + * + * Every %BLE device has a unique address which can be used to identify it and form connections. + */ +class BLEAddress { +public: + BLEAddress(esp_bd_addr_t address); + BLEAddress(std::string stringAddress); + bool equals(BLEAddress otherAddress); + esp_bd_addr_t* getNative(); + std::string toString(); + +private: + esp_bd_addr_t m_address; +}; + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEADDRESS_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp b/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp new file mode 100644 index 0000000..3f55e8c --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp @@ -0,0 +1,529 @@ +/* + * BLEAdvertisedDevice.cpp + * + * During the scanning procedure, we will be finding advertised BLE devices. This class + * models a found device. + * + * + * See also: + * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + * + * Created on: Jul 3, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLEAdvertisedDevice.h" +#include "BLEUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG="BLEAdvertisedDevice"; +#endif + +BLEAdvertisedDevice::BLEAdvertisedDevice() { + m_adFlag = 0; + m_appearance = 0; + m_deviceType = 0; + m_manufacturerData = ""; + m_name = ""; + m_rssi = -9999; + m_serviceData = ""; + m_txPower = 0; + m_pScan = nullptr; + + m_haveAppearance = false; + m_haveManufacturerData = false; + m_haveName = false; + m_haveRSSI = false; + m_haveServiceData = false; + m_haveServiceUUID = false; + m_haveTXPower = false; + +} // BLEAdvertisedDevice + + +/** + * @brief Get the address. + * + * Every %BLE device exposes an address that is used to identify it and subsequently connect to it. + * Call this function to obtain the address of the advertised device. + * + * @return The address of the advertised device. + */ +BLEAddress BLEAdvertisedDevice::getAddress() { + return m_address; +} // getAddress + + +/** + * @brief Get the appearance. + * + * A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user + * typcially in the form of an icon. + * + * @return The appearance of the advertised device. + */ +uint16_t BLEAdvertisedDevice::getAppearance() { + return m_appearance; +} // getAppearance + + +/** + * @brief Get the manufacturer data. + * @return The manufacturer data of the advertised device. + */ +std::string BLEAdvertisedDevice::getManufacturerData() { + return m_manufacturerData; +} // getManufacturerData + + +/** + * @brief Get the name. + * @return The name of the advertised device. + */ +std::string BLEAdvertisedDevice::getName() { + return m_name; +} // getName + + +/** + * @brief Get the RSSI. + * @return The RSSI of the advertised device. + */ +int BLEAdvertisedDevice::getRSSI() { + return m_rssi; +} // getRSSI + + +/** + * @brief Get the scan object that created this advertisement. + * @return The scan object. + */ +BLEScan* BLEAdvertisedDevice::getScan() { + return m_pScan; +} // getScan + + +/** + * @brief Get the service data. + * @return The ServiceData of the advertised device. + */ +std::string BLEAdvertisedDevice::getServiceData() { + return m_serviceData; +} //getServiceData + + +/** + * @brief Get the service data UUID. + * @return The service data UUID. + */ +BLEUUID BLEAdvertisedDevice::getServiceDataUUID() { + return m_serviceDataUUID; +} // getServiceDataUUID + + +/** + * @brief Get the Service UUID. + * @return The Service UUID of the advertised device. + */ +BLEUUID BLEAdvertisedDevice::getServiceUUID() { //TODO Remove it eventually, is no longer useful + return m_serviceUUIDs[0]; +} // getServiceUUID + +/** + * @brief Check advertised serviced for existence required UUID + * @return Return true if service is advertised + */ +bool BLEAdvertisedDevice::isAdvertisingService(BLEUUID uuid){ + for (int i = 0; i < m_serviceUUIDs.size(); i++) { + if (m_serviceUUIDs[i].equals(uuid)) return true; + } + return false; +} + +/** + * @brief Get the TX Power. + * @return The TX Power of the advertised device. + */ +int8_t BLEAdvertisedDevice::getTXPower() { + return m_txPower; +} // getTXPower + + + +/** + * @brief Does this advertisement have an appearance value? + * @return True if there is an appearance value present. + */ +bool BLEAdvertisedDevice::haveAppearance() { + return m_haveAppearance; +} // haveAppearance + + +/** + * @brief Does this advertisement have manufacturer data? + * @return True if there is manufacturer data present. + */ +bool BLEAdvertisedDevice::haveManufacturerData() { + return m_haveManufacturerData; +} // haveManufacturerData + + +/** + * @brief Does this advertisement have a name value? + * @return True if there is a name value present. + */ +bool BLEAdvertisedDevice::haveName() { + return m_haveName; +} // haveName + + +/** + * @brief Does this advertisement have a signal strength value? + * @return True if there is a signal strength value present. + */ +bool BLEAdvertisedDevice::haveRSSI() { + return m_haveRSSI; +} // haveRSSI + + +/** + * @brief Does this advertisement have a service data value? + * @return True if there is a service data value present. + */ +bool BLEAdvertisedDevice::haveServiceData() { + return m_haveServiceData; +} // haveServiceData + + +/** + * @brief Does this advertisement have a service UUID value? + * @return True if there is a service UUID value present. + */ +bool BLEAdvertisedDevice::haveServiceUUID() { + return m_haveServiceUUID; +} // haveServiceUUID + + +/** + * @brief Does this advertisement have a transmission power value? + * @return True if there is a transmission power value present. + */ +bool BLEAdvertisedDevice::haveTXPower() { + return m_haveTXPower; +} // haveTXPower + + +/** + * @brief Parse the advertising pay load. + * + * The pay load is a buffer of bytes that is either 31 bytes long or terminated by + * a 0 length value. Each entry in the buffer has the format: + * [length][type][data...] + * + * The length does not include itself but does include everything after it until the next record. A record + * with a length value of 0 indicates a terminator. + * + * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + */ +void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload, size_t total_len) { + uint8_t length; + uint8_t ad_type; + uint8_t sizeConsumed = 0; + bool finished = false; + m_payload = payload; + m_payloadLength = total_len; + + while(!finished) { + length = *payload; // Retrieve the length of the record. + payload++; // Skip to type + sizeConsumed += 1 + length; // increase the size consumed. + + if (length != 0) { // A length of 0 indicates that we have reached the end. + ad_type = *payload; + payload++; + length--; + + char* pHex = BLEUtils::buildHexData(nullptr, payload, length); + ESP_LOGD(LOG_TAG, "Type: 0x%.2x (%s), length: %d, data: %s", + ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); + free(pHex); + + switch(ad_type) { + case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 + setName(std::string(reinterpret_cast(payload), length)); + break; + } // ESP_BLE_AD_TYPE_NAME_CMPL + + case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A + setTXPower(*payload); + break; + } // ESP_BLE_AD_TYPE_TX_PWR + + case ESP_BLE_AD_TYPE_APPEARANCE: { // Adv Data Type: 0x19 + setAppearance(*reinterpret_cast(payload)); + break; + } // ESP_BLE_AD_TYPE_APPEARANCE + + case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01 + setAdFlag(*payload); + break; + } // ESP_BLE_AD_TYPE_FLAG + + case ESP_BLE_AD_TYPE_16SRV_CMPL: + case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 + for (int var = 0; var < length/2; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 2))); + } + break; + } // ESP_BLE_AD_TYPE_16SRV_PART + + case ESP_BLE_AD_TYPE_32SRV_CMPL: + case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 + for (int var = 0; var < length/4; ++var) { + setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 4))); + } + break; + } // ESP_BLE_AD_TYPE_32SRV_PART + + case ESP_BLE_AD_TYPE_128SRV_CMPL: { // Adv Data Type: 0x07 + setServiceUUID(BLEUUID(payload, 16, false)); + break; + } // ESP_BLE_AD_TYPE_128SRV_CMPL + + case ESP_BLE_AD_TYPE_128SRV_PART: { // Adv Data Type: 0x06 + setServiceUUID(BLEUUID(payload, 16, false)); + break; + } // ESP_BLE_AD_TYPE_128SRV_PART + + // See CSS Part A 1.4 Manufacturer Specific Data + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: { + setManufacturerData(std::string(reinterpret_cast(payload), length)); + break; + } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE + + case ESP_BLE_AD_TYPE_SERVICE_DATA: { // Adv Data Type: 0x16 (Service Data) - 2 byte UUID + if (length < 2) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); + break; + } + uint16_t uuid = *(uint16_t*)payload; + setServiceDataUUID(BLEUUID(uuid)); + if (length > 2) { + setServiceData(std::string(reinterpret_cast(payload + 2), length - 2)); + } + break; + } //ESP_BLE_AD_TYPE_SERVICE_DATA + + case ESP_BLE_AD_TYPE_32SERVICE_DATA: { // Adv Data Type: 0x20 (Service Data) - 4 byte UUID + if (length < 4) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA"); + break; + } + uint32_t uuid = *(uint32_t*) payload; + setServiceDataUUID(BLEUUID(uuid)); + if (length > 4) { + setServiceData(std::string(reinterpret_cast(payload + 4), length - 4)); + } + break; + } //ESP_BLE_AD_TYPE_32SERVICE_DATA + + case ESP_BLE_AD_TYPE_128SERVICE_DATA: { // Adv Data Type: 0x21 (Service Data) - 16 byte UUID + if (length < 16) { + ESP_LOGE(LOG_TAG, "Length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA"); + break; + } + + setServiceDataUUID(BLEUUID(payload, (size_t)16, false)); + if (length > 16) { + setServiceData(std::string(reinterpret_cast(payload + 16), length - 16)); + } + break; + } //ESP_BLE_AD_TYPE_32SERVICE_DATA + + default: { + ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); + break; + } + } // switch + payload += length; + } // Length <> 0 + + + if (sizeConsumed >= total_len) + finished = true; + + } // !finished +} // parseAdvertisement + + +/** + * @brief Set the address of the advertised device. + * @param [in] address The address of the advertised device. + */ +void BLEAdvertisedDevice::setAddress(BLEAddress address) { + m_address = address; +} // setAddress + + +/** + * @brief Set the adFlag for this device. + * @param [in] The discovered adFlag. + */ +void BLEAdvertisedDevice::setAdFlag(uint8_t adFlag) { + m_adFlag = adFlag; +} // setAdFlag + + +/** + * @brief Set the appearance for this device. + * @param [in] The discovered appearance. + */ +void BLEAdvertisedDevice::setAppearance(uint16_t appearance) { + m_appearance = appearance; + m_haveAppearance = true; + ESP_LOGD(LOG_TAG, "- appearance: %d", m_appearance); +} // setAppearance + + +/** + * @brief Set the manufacturer data for this device. + * @param [in] The discovered manufacturer data. + */ +void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { + m_manufacturerData = manufacturerData; + m_haveManufacturerData = true; + char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*) m_manufacturerData.data(), (uint8_t) m_manufacturerData.length()); + ESP_LOGD(LOG_TAG, "- manufacturer data: %s", pHex); + free(pHex); +} // setManufacturerData + + +/** + * @brief Set the name for this device. + * @param [in] name The discovered name. + */ +void BLEAdvertisedDevice::setName(std::string name) { + m_name = name; + m_haveName = true; + ESP_LOGD(LOG_TAG, "- setName(): name: %s", m_name.c_str()); +} // setName + + +/** + * @brief Set the RSSI for this device. + * @param [in] rssi The discovered RSSI. + */ +void BLEAdvertisedDevice::setRSSI(int rssi) { + m_rssi = rssi; + m_haveRSSI = true; + ESP_LOGD(LOG_TAG, "- setRSSI(): rssi: %d", m_rssi); +} // setRSSI + + +/** + * @brief Set the Scan that created this advertised device. + * @param pScan The Scan that created this advertised device. + */ +void BLEAdvertisedDevice::setScan(BLEScan* pScan) { + m_pScan = pScan; +} // setScan + + +/** + * @brief Set the Service UUID for this device. + * @param [in] serviceUUID The discovered serviceUUID + */ +void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) { + return setServiceUUID(BLEUUID(serviceUUID)); +} // setServiceUUID + + +/** + * @brief Set the Service UUID for this device. + * @param [in] serviceUUID The discovered serviceUUID + */ +void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { + m_serviceUUIDs.push_back(serviceUUID); + m_haveServiceUUID = true; + ESP_LOGD(LOG_TAG, "- addServiceUUID(): serviceUUID: %s", serviceUUID.toString().c_str()); +} // setServiceUUID + + +/** + * @brief Set the ServiceData value. + * @param [in] data ServiceData value. + */ +void BLEAdvertisedDevice::setServiceData(std::string serviceData) { + m_haveServiceData = true; // Set the flag that indicates we have service data. + m_serviceData = serviceData; // Save the service data that we received. +} //setServiceData + + +/** + * @brief Set the ServiceDataUUID value. + * @param [in] data ServiceDataUUID value. + */ +void BLEAdvertisedDevice::setServiceDataUUID(BLEUUID uuid) { + m_haveServiceData = true; // Set the flag that indicates we have service data. + m_serviceDataUUID = uuid; +} // setServiceDataUUID + + +/** + * @brief Set the power level for this device. + * @param [in] txPower The discovered power level. + */ +void BLEAdvertisedDevice::setTXPower(int8_t txPower) { + m_txPower = txPower; + m_haveTXPower = true; + ESP_LOGD(LOG_TAG, "- txPower: %d", m_txPower); +} // setTXPower + + +/** + * @brief Create a string representation of this device. + * @return A string representation of this device. + */ +std::string BLEAdvertisedDevice::toString() { + std::stringstream ss; + ss << "Name: " << getName() << ", Address: " << getAddress().toString(); + if (haveAppearance()) { + ss << ", appearance: " << getAppearance(); + } + if (haveManufacturerData()) { + char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length()); + ss << ", manufacturer data: " << pHex; + free(pHex); + } + if (haveServiceUUID()) { + ss << ", serviceUUID: " << getServiceUUID().toString(); + } + if (haveTXPower()) { + ss << ", txPower: " << (int)getTXPower(); + } + return ss.str(); +} // toString + +uint8_t* BLEAdvertisedDevice::getPayload() { + return m_payload; +} + +esp_ble_addr_type_t BLEAdvertisedDevice::getAddressType() { + return m_addressType; +} + +void BLEAdvertisedDevice::setAddressType(esp_ble_addr_type_t type) { + m_addressType = type; +} + +size_t BLEAdvertisedDevice::getPayloadLength() { + return m_payloadLength; +} + +#endif /* CONFIG_BT_ENABLED */ + diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h b/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h new file mode 100644 index 0000000..aec8374 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h @@ -0,0 +1,123 @@ +/* + * BLEAdvertisedDevice.h + * + * Created on: Jul 3, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ +#define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +#include + +#include "BLEAddress.h" +#include "BLEScan.h" +#include "BLEUUID.h" + + +class BLEScan; +/** + * @brief A representation of a %BLE advertised device found by a scan. + * + * When we perform a %BLE scan, the result will be a set of devices that are advertising. This + * class provides a model of a detected device. + */ +class BLEAdvertisedDevice { +public: + BLEAdvertisedDevice(); + + BLEAddress getAddress(); + uint16_t getAppearance(); + std::string getManufacturerData(); + std::string getName(); + int getRSSI(); + BLEScan* getScan(); + std::string getServiceData(); + BLEUUID getServiceDataUUID(); + BLEUUID getServiceUUID(); + int8_t getTXPower(); + uint8_t* getPayload(); + size_t getPayloadLength(); + esp_ble_addr_type_t getAddressType(); + void setAddressType(esp_ble_addr_type_t type); + + + bool isAdvertisingService(BLEUUID uuid); + bool haveAppearance(); + bool haveManufacturerData(); + bool haveName(); + bool haveRSSI(); + bool haveServiceData(); + bool haveServiceUUID(); + bool haveTXPower(); + + std::string toString(); + +private: + friend class BLEScan; + + void parseAdvertisement(uint8_t* payload, size_t total_len=62); + void setAddress(BLEAddress address); + void setAdFlag(uint8_t adFlag); + void setAdvertizementResult(uint8_t* payload); + void setAppearance(uint16_t appearance); + void setManufacturerData(std::string manufacturerData); + void setName(std::string name); + void setRSSI(int rssi); + void setScan(BLEScan* pScan); + void setServiceData(std::string data); + void setServiceDataUUID(BLEUUID uuid); + void setServiceUUID(const char* serviceUUID); + void setServiceUUID(BLEUUID serviceUUID); + void setTXPower(int8_t txPower); + + bool m_haveAppearance; + bool m_haveManufacturerData; + bool m_haveName; + bool m_haveRSSI; + bool m_haveServiceData; + bool m_haveServiceUUID; + bool m_haveTXPower; + + + BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); + uint8_t m_adFlag; + uint16_t m_appearance; + int m_deviceType; + std::string m_manufacturerData; + std::string m_name; + BLEScan* m_pScan; + int m_rssi; + std::vector m_serviceUUIDs; + int8_t m_txPower; + std::string m_serviceData; + BLEUUID m_serviceDataUUID; + uint8_t* m_payload; + size_t m_payloadLength = 0; + esp_ble_addr_type_t m_addressType; +}; + +/** + * @brief A callback handler for callbacks associated device scanning. + * + * When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising + * has been found. This class can be sub-classed and registered such that when a scan is performed and + * a new advertised device has been found, we will be called back to be notified. + */ +class BLEAdvertisedDeviceCallbacks { +public: + virtual ~BLEAdvertisedDeviceCallbacks() {} + /** + * @brief Called when a new scan result is detected. + * + * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the + * device that was found. During any individual scan, a device will only be detected one time. + */ + virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0; +}; + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.cpp b/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.cpp new file mode 100644 index 0000000..230d77c --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.cpp @@ -0,0 +1,505 @@ +/* + * BLEAdvertising.cpp + * + * This class encapsulates advertising a BLE Server. + * Created on: Jun 21, 2017 + * Author: kolban + * + * The ESP-IDF provides a framework for BLE advertising. It has determined that there are a common set + * of properties that are advertised and has built a data structure that can be populated by the programmer. + * This means that the programmer doesn't have to "mess with" the low level construction of a low level + * BLE advertising frame. Many of the fields are determined for us while others we can set before starting + * to advertise. + * + * Should we wish to construct our own payload, we can use the BLEAdvertisementData class and call the setters + * upon it. Once it is populated, we can then associate it with the advertising and what ever the programmer + * set in the data will be advertised. + * + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include "BLEAdvertising.h" +#include +#include "BLEUtils.h" +#include "GeneralUtils.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEAdvertising"; +#endif + + + +/** + * @brief Construct a default advertising object. + * + */ +BLEAdvertising::BLEAdvertising() { + m_advData.set_scan_rsp = false; + m_advData.include_name = true; + m_advData.include_txpower = true; + m_advData.min_interval = 0x20; + m_advData.max_interval = 0x40; + m_advData.appearance = 0x00; + m_advData.manufacturer_len = 0; + m_advData.p_manufacturer_data = nullptr; + m_advData.service_data_len = 0; + m_advData.p_service_data = nullptr; + m_advData.service_uuid_len = 0; + m_advData.p_service_uuid = nullptr; + m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); + + m_advParams.adv_int_min = 0x20; + m_advParams.adv_int_max = 0x40; + m_advParams.adv_type = ADV_TYPE_IND; + m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + m_advParams.channel_map = ADV_CHNL_ALL; + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; + m_advParams.peer_addr_type = BLE_ADDR_TYPE_PUBLIC; + + m_customAdvData = false; // No custom advertising data + m_customScanResponseData = false; // No custom scan response data +} // BLEAdvertising + + +/** + * @brief Add a service uuid to exposed list of services. + * @param [in] serviceUUID The UUID of the service to expose. + */ +void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) { + m_serviceUUIDs.push_back(serviceUUID); +} // addServiceUUID + + +/** + * @brief Add a service uuid to exposed list of services. + * @param [in] serviceUUID The string representation of the service to expose. + */ +void BLEAdvertising::addServiceUUID(const char* serviceUUID) { + addServiceUUID(BLEUUID(serviceUUID)); +} // addServiceUUID + + +/** + * @brief Set the device appearance in the advertising data. + * The appearance attribute is of type 0x19. The codes for distinct appearances can be found here: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml. + * @param [in] appearance The appearance of the device in the advertising data. + * @return N/A. + */ +void BLEAdvertising::setAppearance(uint16_t appearance) { + m_advData.appearance = appearance; +} // setAppearance + +void BLEAdvertising::setMinInterval(uint16_t mininterval) { + m_advParams.adv_int_min = mininterval; +} // setMinInterval + +void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { + m_advParams.adv_int_max = maxinterval; +} // setMaxInterval + +void BLEAdvertising::setMinPreferred(uint16_t mininterval) { + m_advData.min_interval = mininterval; +} // + +void BLEAdvertising::setMaxPreferred(uint16_t maxinterval) { + m_advData.max_interval = maxinterval; +} // + +void BLEAdvertising::setScanResponse(bool set) { + m_scanResp = set; +} + +/** + * @brief Set the filtering for the scan filter. + * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. + * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. + */ +void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { + ESP_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); + if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (scanRequestWhitelistOnly && !connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (!scanRequestWhitelistOnly && connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } + if (scanRequestWhitelistOnly && connectWhitelistOnly) { + m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST; + ESP_LOGD(LOG_TAG, "<< setScanFilter"); + return; + } +} // setScanFilter + + +/** + * @brief Set the advertisement data that is to be published in a regular advertisement. + * @param [in] advertisementData The data to be advertised. + */ +void BLEAdvertising::setAdvertisementData(BLEAdvertisementData& advertisementData) { + ESP_LOGD(LOG_TAG, ">> setAdvertisementData"); + esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw( + (uint8_t*)advertisementData.getPayload().data(), + advertisementData.getPayload().length()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_config_adv_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. + ESP_LOGD(LOG_TAG, "<< setAdvertisementData"); +} // setAdvertisementData + + +/** + * @brief Set the advertisement data that is to be published in a scan response. + * @param [in] advertisementData The data to be advertised. + */ +void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData) { + ESP_LOGD(LOG_TAG, ">> setScanResponseData"); + esp_err_t errRc = ::esp_ble_gap_config_scan_rsp_data_raw( + (uint8_t*)advertisementData.getPayload().data(), + advertisementData.getPayload().length()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_config_scan_rsp_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. + ESP_LOGD(LOG_TAG, "<< setScanResponseData"); +} // setScanResponseData + +/** + * @brief Start advertising. + * Start advertising. + * @return N/A. + */ +void BLEAdvertising::start() { + ESP_LOGD(LOG_TAG, ">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); + + // We have a vector of service UUIDs that we wish to advertise. In order to use the + // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) + // representations. If we have 1 or more services to advertise then we allocate enough + // storage to host them and then copy them in one at a time into the contiguous storage. + int numServices = m_serviceUUIDs.size(); + if (numServices > 0) { + m_advData.service_uuid_len = 16 * numServices; + m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; + uint8_t* p = m_advData.p_service_uuid; + for (int i = 0; i < numServices; i++) { + ESP_LOGD(LOG_TAG, "- advertising service: %s", m_serviceUUIDs[i].toString().c_str()); + BLEUUID serviceUUID128 = m_serviceUUIDs[i].to128(); + memcpy(p, serviceUUID128.getNative()->uuid.uuid128, 16); + p += 16; + } + } else { + m_advData.service_uuid_len = 0; + ESP_LOGD(LOG_TAG, "- no services advertised"); + } + + esp_err_t errRc; + + if (!m_customAdvData) { + // Set the configuration for advertising. + m_advData.set_scan_rsp = false; + m_advData.include_name = !m_scanResp; + m_advData.include_txpower = !m_scanResp; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + if (!m_customScanResponseData && m_scanResp) { + m_advData.set_scan_rsp = true; + m_advData.include_name = m_scanResp; + m_advData.include_txpower = m_scanResp; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + // If we had services to advertise then we previously allocated some storage for them. + // Here we release that storage. + if (m_advData.service_uuid_len > 0) { + delete[] m_advData.p_service_uuid; + m_advData.p_service_uuid = nullptr; + } + + // Start advertising. + errRc = ::esp_ble_gap_start_advertising(&m_advParams); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + ESP_LOGD(LOG_TAG, "<< start"); +} // start + + +/** + * @brief Stop advertising. + * Stop advertising. + * @return N/A. + */ +void BLEAdvertising::stop() { + ESP_LOGD(LOG_TAG, ">> stop"); + esp_err_t errRc = ::esp_ble_gap_stop_advertising(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + ESP_LOGD(LOG_TAG, "<< stop"); +} // stop + +/** + * @brief Add data to the payload to be advertised. + * @param [in] data The data to be added to the payload. + */ +void BLEAdvertisementData::addData(std::string data) { + if ((m_payload.length() + data.length()) > ESP_BLE_ADV_DATA_LEN_MAX) { + return; + } + m_payload.append(data); +} // addData + + +/** + * @brief Set the appearance. + * @param [in] appearance The appearance code value. + * + * See also: + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml + */ +void BLEAdvertisementData::setAppearance(uint16_t appearance) { + char cdata[2]; + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 + addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); +} // setAppearance + + +/** + * @brief Set the complete services. + * @param [in] uuid The single service to advertise. + */ +void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { + char cdata[2]; + switch (uuid.bitSize()) { + case 16: { + // [Len] [0x02] [LL] [HH] + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2)); + break; + } + + case 32: { + // [Len] [0x04] [LL] [LL] [HH] [HH] + cdata[0] = 5; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4)); + break; + } + + case 128: { + // [Len] [0x04] [0] [1] ... [15] + cdata[0] = 17; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 + addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->uuid.uuid128, 16)); + break; + } + + default: + return; + } +} // setCompleteServices + + +/** + * @brief Set the advertisement flags. + * @param [in] The flags to be set in the advertisement. + * + * * ESP_BLE_ADV_FLAG_LIMIT_DISC + * * ESP_BLE_ADV_FLAG_GEN_DISC + * * ESP_BLE_ADV_FLAG_BREDR_NOT_SPT + * * ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT + * * ESP_BLE_ADV_FLAG_DMT_HOST_SPT + * * ESP_BLE_ADV_FLAG_NON_LIMIT_DISC + */ +void BLEAdvertisementData::setFlags(uint8_t flag) { + char cdata[3]; + cdata[0] = 2; + cdata[1] = ESP_BLE_AD_TYPE_FLAG; // 0x01 + cdata[2] = flag; + addData(std::string(cdata, 3)); +} // setFlag + + + +/** + * @brief Set manufacturer specific data. + * @param [in] data Manufacturer data. + */ +void BLEAdvertisementData::setManufacturerData(std::string data) { + ESP_LOGD("BLEAdvertisementData", ">> setManufacturerData"); + char cdata[2]; + cdata[0] = data.length() + 1; + cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff + addData(std::string(cdata, 2) + data); + ESP_LOGD("BLEAdvertisementData", "<< setManufacturerData"); +} // setManufacturerData + + +/** + * @brief Set the name. + * @param [in] The complete name of the device. + */ +void BLEAdvertisementData::setName(std::string name) { + ESP_LOGD("BLEAdvertisementData", ">> setName: %s", name.c_str()); + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 + addData(std::string(cdata, 2) + name); + ESP_LOGD("BLEAdvertisementData", "<< setName"); +} // setName + + +/** + * @brief Set the partial services. + * @param [in] uuid The single service to advertise. + */ +void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { + char cdata[2]; + switch (uuid.bitSize()) { + case 16: { + // [Len] [0x02] [LL] [HH] + cdata[0] = 3; + cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid16, 2)); + break; + } + + case 32: { + // [Len] [0x04] [LL] [LL] [HH] [HH] + cdata[0] = 5; + cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid32, 4)); + break; + } + + case 128: { + // [Len] [0x04] [0] [1] ... [15] + cdata[0] = 17; + cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 + addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid128, 16)); + break; + } + + default: + return; + } +} // setPartialServices + + +/** + * @brief Set the service data (UUID + data) + * @param [in] uuid The UUID to set with the service data. Size of UUID will be used. + * @param [in] data The data to be associated with the service data advert. + */ +void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { + char cdata[2]; + switch (uuid.bitSize()) { + case 16: { + // [Len] [0x16] [UUID16] data + cdata[0] = data.length() + 3; + cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2) + data); + break; + } + + case 32: { + // [Len] [0x20] [UUID32] data + cdata[0] = data.length() + 5; + cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4) + data); + break; + } + + case 128: { + // [Len] [0x21] [UUID128] data + cdata[0] = data.length() + 17; + cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 + addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16) + data); + break; + } + + default: + return; + } +} // setServiceData + + +/** + * @brief Set the short name. + * @param [in] The short name of the device. + */ +void BLEAdvertisementData::setShortName(std::string name) { + ESP_LOGD("BLEAdvertisementData", ">> setShortName: %s", name.c_str()); + char cdata[2]; + cdata[0] = name.length() + 1; + cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 + addData(std::string(cdata, 2) + name); + ESP_LOGD("BLEAdvertisementData", "<< setShortName"); +} // setShortName + + +/** + * @brief Retrieve the payload that is to be advertised. + * @return The payload that is to be advertised. + */ +std::string BLEAdvertisementData::getPayload() { + return m_payload; +} // getPayload + +void BLEAdvertising::handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + + ESP_LOGD(LOG_TAG, "handleGAPEvent [event no: %d]", (int)event); + + switch(event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { + // m_semaphoreSetAdv.give(); + break; + } + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { + ESP_LOGI(LOG_TAG, "STOP advertising"); + start(); + break; + } + default: + break; + } +} + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.h b/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.h new file mode 100644 index 0000000..3128b50 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEAdvertising.h @@ -0,0 +1,78 @@ +/* + * BLEAdvertising.h + * + * Created on: Jun 21, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ +#define COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLEUUID.h" +#include +#include "FreeRTOS.h" + +/** + * @brief Advertisement data set by the programmer to be published by the %BLE server. + */ +class BLEAdvertisementData { + // Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will + // be exposed on demand/request or as time permits. + // +public: + void setAppearance(uint16_t appearance); + void setCompleteServices(BLEUUID uuid); + void setFlags(uint8_t); + void setManufacturerData(std::string data); + void setName(std::string name); + void setPartialServices(BLEUUID uuid); + void setServiceData(BLEUUID uuid, std::string data); + void setShortName(std::string name); + void addData(std::string data); // Add data to the payload. + std::string getPayload(); // Retrieve the current advert payload. + +private: + friend class BLEAdvertising; + std::string m_payload; // The payload of the advertisement. +}; // BLEAdvertisementData + + +/** + * @brief Perform and manage %BLE advertising. + * + * A %BLE server will want to perform advertising in order to make itself known to %BLE clients. + */ +class BLEAdvertising { +public: + BLEAdvertising(); + void addServiceUUID(BLEUUID serviceUUID); + void addServiceUUID(const char* serviceUUID); + void start(); + void stop(); + void setAppearance(uint16_t appearance); + void setMaxInterval(uint16_t maxinterval); + void setMinInterval(uint16_t mininterval); + void setAdvertisementData(BLEAdvertisementData& advertisementData); + void setScanFilter(bool scanRequertWhitelistOnly, bool connectWhitelistOnly); + void setScanResponseData(BLEAdvertisementData& advertisementData); + void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); + + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); + void setMinPreferred(uint16_t); + void setMaxPreferred(uint16_t); + void setScanResponse(bool); + +private: + esp_ble_adv_data_t m_advData; + esp_ble_adv_params_t m_advParams; + std::vector m_serviceUUIDs; + bool m_customAdvData = false; // Are we using custom advertising data? + bool m_customScanResponseData = false; // Are we using custom scan response data? + FreeRTOS::Semaphore m_semaphoreSetAdv = FreeRTOS::Semaphore("startAdvert"); + bool m_scanResp = true; + +}; +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ \ No newline at end of file diff --git a/libraries/ESP32_BLE_Arduino/src/BLEBeacon.cpp b/libraries/ESP32_BLE_Arduino/src/BLEBeacon.cpp new file mode 100644 index 0000000..68f8d8e --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEBeacon.cpp @@ -0,0 +1,89 @@ +/* + * BLEBeacon.cpp + * + * Created on: Jan 4, 2018 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLEBeacon.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEBeacon"; +#endif + +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) + + +BLEBeacon::BLEBeacon() { + m_beaconData.manufacturerId = 0x4c00; + m_beaconData.subType = 0x02; + m_beaconData.subTypeLength = 0x15; + m_beaconData.major = 0; + m_beaconData.minor = 0; + m_beaconData.signalPower = 0; + memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); +} // BLEBeacon + +std::string BLEBeacon::getData() { + return std::string((char*) &m_beaconData, sizeof(m_beaconData)); +} // getData + +uint16_t BLEBeacon::getMajor() { + return m_beaconData.major; +} + +uint16_t BLEBeacon::getManufacturerId() { + return m_beaconData.manufacturerId; +} + +uint16_t BLEBeacon::getMinor() { + return m_beaconData.minor; +} + +BLEUUID BLEBeacon::getProximityUUID() { + return BLEUUID(m_beaconData.proximityUUID, 16, false); +} + +int8_t BLEBeacon::getSignalPower() { + return m_beaconData.signalPower; +} + +/** + * Set the raw data for the beacon record. + */ +void BLEBeacon::setData(std::string data) { + if (data.length() != sizeof(m_beaconData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData)); + return; + } + memcpy(&m_beaconData, data.data(), sizeof(m_beaconData)); +} // setData + +void BLEBeacon::setMajor(uint16_t major) { + m_beaconData.major = ENDIAN_CHANGE_U16(major); +} // setMajor + +void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { + m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId); +} // setManufacturerId + +void BLEBeacon::setMinor(uint16_t minor) { + m_beaconData.minor = ENDIAN_CHANGE_U16(minor); +} // setMinior + +void BLEBeacon::setProximityUUID(BLEUUID uuid) { + uuid = uuid.to128(); + memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); +} // setProximityUUID + +void BLEBeacon::setSignalPower(int8_t signalPower) { + m_beaconData.signalPower = signalPower; +} // setSignalPower + + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLEBeacon.h b/libraries/ESP32_BLE_Arduino/src/BLEBeacon.h new file mode 100644 index 0000000..277bd67 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEBeacon.h @@ -0,0 +1,43 @@ +/* + * BLEBeacon2.h + * + * Created on: Jan 4, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEBEACON_H_ +#define COMPONENTS_CPP_UTILS_BLEBEACON_H_ +#include "BLEUUID.h" +/** + * @brief Representation of a beacon. + * See: + * * https://en.wikipedia.org/wiki/IBeacon + */ +class BLEBeacon { +private: + struct { + uint16_t manufacturerId; + uint8_t subType; + uint8_t subTypeLength; + uint8_t proximityUUID[16]; + uint16_t major; + uint16_t minor; + int8_t signalPower; + } __attribute__((packed)) m_beaconData; +public: + BLEBeacon(); + std::string getData(); + uint16_t getMajor(); + uint16_t getMinor(); + uint16_t getManufacturerId(); + BLEUUID getProximityUUID(); + int8_t getSignalPower(); + void setData(std::string data); + void setMajor(uint16_t major); + void setMinor(uint16_t minor); + void setManufacturerId(uint16_t manufacturerId); + void setProximityUUID(BLEUUID uuid); + void setSignalPower(int8_t signalPower); +}; // BLEBeacon + +#endif /* COMPONENTS_CPP_UTILS_BLEBEACON_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.cpp b/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.cpp new file mode 100644 index 0000000..e340287 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.cpp @@ -0,0 +1,760 @@ +/* + * BLECharacteristic.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include "sdkconfig.h" +#include +#include "BLECharacteristic.h" +#include "BLEService.h" +#include "BLEDevice.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include "GeneralUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLECharacteristic"; +#endif + +#define NULL_HANDLE (0xffff) + + +/** + * @brief Construct a characteristic + * @param [in] uuid - UUID (const char*) for the characteristic. + * @param [in] properties - Properties for the characteristic. + */ +BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BLECharacteristic(BLEUUID(uuid), properties) { +} + +/** + * @brief Construct a characteristic + * @param [in] uuid - UUID for the characteristic. + * @param [in] properties - Properties for the characteristic. + */ +BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { + m_bleUUID = uuid; + m_handle = NULL_HANDLE; + m_properties = (esp_gatt_char_prop_t)0; + m_pCallbacks = nullptr; + + setBroadcastProperty((properties & PROPERTY_BROADCAST) != 0); + setReadProperty((properties & PROPERTY_READ) != 0); + setWriteProperty((properties & PROPERTY_WRITE) != 0); + setNotifyProperty((properties & PROPERTY_NOTIFY) != 0); + setIndicateProperty((properties & PROPERTY_INDICATE) != 0); + setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) != 0); +} // BLECharacteristic + +/** + * @brief Destructor. + */ +BLECharacteristic::~BLECharacteristic() { + //free(m_value.attr_value); // Release the storage for the value. +} // ~BLECharacteristic + + +/** + * @brief Associate a descriptor with this characteristic. + * @param [in] pDescriptor + * @return N/A. + */ +void BLECharacteristic::addDescriptor(BLEDescriptor* pDescriptor) { + ESP_LOGD(LOG_TAG, ">> addDescriptor(): Adding %s to %s", pDescriptor->toString().c_str(), toString().c_str()); + m_descriptorMap.setByUUID(pDescriptor->getUUID(), pDescriptor); + ESP_LOGD(LOG_TAG, "<< addDescriptor()"); +} // addDescriptor + + +/** + * @brief Register a new characteristic with the ESP runtime. + * @param [in] pService The service with which to associate this characteristic. + */ +void BLECharacteristic::executeCreate(BLEService* pService) { + ESP_LOGD(LOG_TAG, ">> executeCreate()"); + + if (m_handle != NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "Characteristic already has a handle."); + return; + } + + m_pService = pService; // Save the service to which this characteristic belongs. + + ESP_LOGD(LOG_TAG, "Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s", + getUUID().toString().c_str(), + m_pService->toString().c_str()); + + esp_attr_control_t control; + control.auto_rsp = ESP_GATT_RSP_BY_APP; + + m_semaphoreCreateEvt.take("executeCreate"); + esp_err_t errRc = ::esp_ble_gatts_add_char( + m_pService->getHandle(), + getUUID().getNative(), + static_cast(m_permissions), + getProperties(), + nullptr, + &control); // Whether to auto respond or not. + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreCreateEvt.wait("executeCreate"); + + BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + pDescriptor->executeCreate(this); + pDescriptor = m_descriptorMap.getNext(); + } // End while + + ESP_LOGD(LOG_TAG, "<< executeCreate"); +} // executeCreate + + +/** + * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. + * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. + * @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned. + */ +BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) { + return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID)); +} // getDescriptorByUUID + + +/** + * @brief Return the BLE Descriptor for the given UUID if associated with this characteristic. + * @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve. + * @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned. + */ +BLEDescriptor* BLECharacteristic::getDescriptorByUUID(BLEUUID descriptorUUID) { + return m_descriptorMap.getByUUID(descriptorUUID); +} // getDescriptorByUUID + + +/** + * @brief Get the handle of the characteristic. + * @return The handle of the characteristic. + */ +uint16_t BLECharacteristic::getHandle() { + return m_handle; +} // getHandle + +void BLECharacteristic::setAccessPermissions(esp_gatt_perm_t perm) { + m_permissions = perm; +} + +esp_gatt_char_prop_t BLECharacteristic::getProperties() { + return m_properties; +} // getProperties + + +/** + * @brief Get the service associated with this characteristic. + */ +BLEService* BLECharacteristic::getService() { + return m_pService; +} // getService + + +/** + * @brief Get the UUID of the characteristic. + * @return The UUID of the characteristic. + */ +BLEUUID BLECharacteristic::getUUID() { + return m_bleUUID; +} // getUUID + + +/** + * @brief Retrieve the current value of the characteristic. + * @return A pointer to storage containing the current characteristic value. + */ +std::string BLECharacteristic::getValue() { + return m_value.getValue(); +} // getValue + +/** + * @brief Retrieve the current raw data of the characteristic. + * @return A pointer to storage containing the current characteristic data. + */ +uint8_t* BLECharacteristic::getData() { + return m_value.getData(); +} // getData + + +/** + * Handle a GATT server event. + */ +void BLECharacteristic::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param) { + ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); + + switch(event) { + // Events handled: + // + // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_CONF_EVT + // ESP_GATTS_CONNECT_EVT + // ESP_GATTS_DISCONNECT_EVT + // ESP_GATTS_EXEC_WRITE_EVT + // ESP_GATTS_READ_EVT + // ESP_GATTS_WRITE_EVT + + // + // ESP_GATTS_EXEC_WRITE_EVT + // When we receive this event it is an indication that a previous write long needs to be committed. + // + // exec_write: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint8_t exec_write_flag - Either ESP_GATT_PREP_WRITE_EXEC or ESP_GATT_PREP_WRITE_CANCEL + // + case ESP_GATTS_EXEC_WRITE_EVT: { + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + m_value.commit(); + if (m_pCallbacks != nullptr) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } + } else { + m_value.cancel(); + } +// ??? + esp_err_t errRc = ::esp_ble_gatts_send_response( + gatts_if, + param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, nullptr); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + break; + } // ESP_GATTS_EXEC_WRITE_EVT + + + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + case ESP_GATTS_ADD_CHAR_EVT: { + if (getHandle() == param->add_char.attr_handle) { + // we have created characteristic, now we can create descriptors + // BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + // while (pDescriptor != nullptr) { + // pDescriptor->executeCreate(this); + // pDescriptor = m_descriptorMap.getNext(); + // } // End while + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_ADD_CHAR_EVT + + + // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. + // + // write: + // - uint16_t conn_id + // - uint16_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool need_rsp + // - bool is_prep + // - uint16_t len + // - uint8_t *value + // + case ESP_GATTS_WRITE_EVT: { +// We check if this write request is for us by comparing the handles in the event. If it is for us +// we save the new value. Next we look at the need_rsp flag which indicates whether or not we need +// to send a response. If we do, then we formulate a response and send it. + if (param->write.handle == m_handle) { + if (param->write.is_prep) { + m_value.addPart(param->write.value, param->write.len); + } else { + setValue(param->write.value, param->write.len); + } + + ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s", + getHandle(), getUUID().toString().c_str()); + + char* pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len); + ESP_LOGD(LOG_TAG, " - Data: length: %d, data: %s", param->write.len, pHexData); + free(pHexData); + + if (param->write.need_rsp) { + esp_gatt_rsp_t rsp; + + rsp.attr_value.len = param->write.len; + rsp.attr_value.handle = m_handle; + rsp.attr_value.offset = param->write.offset; + rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; + memcpy(rsp.attr_value.value, param->write.value, param->write.len); + + esp_err_t errRc = ::esp_ble_gatts_send_response( + gatts_if, + param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, &rsp); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } // Response needed + + if (m_pCallbacks != nullptr && param->write.is_prep != true) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } + } // Match on handles. + break; + } // ESP_GATTS_WRITE_EVT + + + // ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived. + // + // read: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool is_long + // - bool need_rsp + // + case ESP_GATTS_READ_EVT: { + if (param->read.handle == m_handle) { + + + +// Here's an interesting thing. The read request has the option of saying whether we need a response +// or not. What would it "mean" to receive a read request and NOT send a response back? That feels like +// a very strange read. +// +// We have to handle the case where the data we wish to send back to the client is greater than the maximum +// packet size of 22 bytes. In this case, we become responsible for chunking the data into units of 22 bytes. +// The apparent algorithm is as follows: +// +// If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes. +// If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than +// 22 bytes, then we "just" send it and thats the end of the story. +// If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request. +// If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request. +// Because of follow on request processing, we need to maintain an offset of how much data we have already sent +// so that when a follow on request arrives, we know where to start in the data to send the next sequence. +// Note that the indication that the client will send a follow on request is that we sent exactly 22 bytes as a response. +// If our payload is divisible by 22 then the last response will be a response of 0 bytes in length. +// +// The following code has deliberately not been factored to make it fewer statements because this would cloud the +// the logic flow comprehension. +// + + // get mtu for peer device that we are sending read request to + uint16_t maxOffset = getService()->getServer()->getPeerMTU(param->read.conn_id) - 1; + ESP_LOGD(LOG_TAG, "mtu value: %d", maxOffset); + if (param->read.need_rsp) { + ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); + esp_gatt_rsp_t rsp; + + if (param->read.is_long) { + std::string value = m_value.getValue(); + + if (value.length() - m_value.getReadOffset() < maxOffset) { + // This is the last in the chain + rsp.attr_value.len = value.length() - m_value.getReadOffset(); + rsp.attr_value.offset = m_value.getReadOffset(); + memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(0); + } else { + // There will be more to come. + rsp.attr_value.len = maxOffset; + rsp.attr_value.offset = m_value.getReadOffset(); + memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(rsp.attr_value.offset + maxOffset); + } + } else { // read.is_long == false + + std::string value = m_value.getValue(); + + if (value.length() + 1 > maxOffset) { + // Too big for a single shot entry. + m_value.setReadOffset(maxOffset); + rsp.attr_value.len = maxOffset; + rsp.attr_value.offset = 0; + memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); + } else { + // Will fit in a single packet with no callbacks required. + rsp.attr_value.len = value.length(); + rsp.attr_value.offset = 0; + memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len); + } + + if (m_pCallbacks != nullptr) { // If is.long is false then this is the first (or only) request to read data, so invoke the callback + m_pCallbacks->onRead(this); // Invoke the read callback. + } + } + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; + + char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len); + ESP_LOGD(LOG_TAG, " - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset); + free(pHexData); + + esp_err_t errRc = ::esp_ble_gatts_send_response( + gatts_if, param->read.conn_id, + param->read.trans_id, + ESP_GATT_OK, + &rsp); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } // Response needed + } // Handle matches this characteristic. + break; + } // ESP_GATTS_READ_EVT + + + // ESP_GATTS_CONF_EVT + // + // conf: + // - esp_gatt_status_t status – The status code. + // - uint16_t conn_id – The connection used. + // + case ESP_GATTS_CONF_EVT: { + // ESP_LOGD(LOG_TAG, "m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); + if(param->conf.conn_id == getService()->getServer()->getConnId()) // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet + m_semaphoreConfEvt.give(param->conf.status); + break; + } + + case ESP_GATTS_CONNECT_EVT: { + break; + } + + case ESP_GATTS_DISCONNECT_EVT: { + m_semaphoreConfEvt.give(); + break; + } + + default: { + break; + } // default + + } // switch event + + // Give each of the descriptors associated with this characteristic the opportunity to handle the + // event. + + m_descriptorMap.handleGATTServerEvent(event, gatts_if, param); + ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); +} // handleGATTServerEvent + + +/** + * @brief Send an indication. + * An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication + * will block waiting a positive confirmation from the client. + * @return N/A + */ +void BLECharacteristic::indicate() { + + ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length()); + notify(false); + ESP_LOGD(LOG_TAG, "<< indicate"); +} // indicate + + +/** + * @brief Send a notify. + * A notification is a transmission of up to the first 20 bytes of the characteristic value. An notification + * will not block; it is a fire and forget. + * @return N/A. + */ +void BLECharacteristic::notify(bool is_notification) { + ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length()); + + assert(getService() != nullptr); + assert(getService()->getServer() != nullptr); + + GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length()); + + if (getService()->getServer()->getConnectedCount() == 0) { + ESP_LOGD(LOG_TAG, "<< notify: No connected clients."); + return; + } + + // Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled + // and, if not, prevent the notification. + + BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if(is_notification) { + if (p2902 != nullptr && !p2902->getNotifications()) { + ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); + return; + } + } + else{ + if (p2902 != nullptr && !p2902->getIndications()) { + ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); + return; + } + } + for (auto &myPair : getService()->getServer()->getPeerDevices(false)) { + uint16_t _mtu = (myPair.second.mtu); + if (m_value.getValue().length() > _mtu - 3) { + ESP_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3); + } + + size_t length = m_value.getValue().length(); + if(!is_notification) + m_semaphoreConfEvt.take("indicate"); + esp_err_t errRc = ::esp_ble_gatts_send_indicate( + getService()->getServer()->getGattsIf(), + myPair.first, + getHandle(), length, (uint8_t*)m_value.getValue().data(), !is_notification); // The need_confirm = false makes this a notify. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_ %s: rc=%d %s",is_notification?"notify":"indicate", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreConfEvt.give(); + return; + } + if(!is_notification) + m_semaphoreConfEvt.wait("indicate"); + } + ESP_LOGD(LOG_TAG, "<< notify"); +} // Notify + + +/** + * @brief Set the permission to broadcast. + * A characteristics has properties associated with it which define what it is capable of doing. + * One of these is the broadcast flag. + * @param [in] value The flag value of the property. + * @return N/A + */ +void BLECharacteristic::setBroadcastProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + } +} // setBroadcastProperty + + +/** + * @brief Set the callback handlers for this characteristic. + * @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic. + */ +void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) { + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks); + m_pCallbacks = pCallbacks; + ESP_LOGD(LOG_TAG, "<< setCallbacks"); +} // setCallbacks + + +/** + * @brief Set the BLE handle associated with this characteristic. + * A user program will request that a characteristic be created against a service. When the characteristic has been + * registered, the service will be given a "handle" that it knows the characteristic as. This handle is unique to the + * server/service but it is told to the service, not the characteristic associated with the service. This internally + * exposed function can be invoked by the service against this model of the characteristic to allow the characteristic + * to learn its own handle. Once the characteristic knows its own handle, it will be able to see incoming GATT events + * that will be propagated down to it which contain a handle value and now know that the event is destined for it. + * @param [in] handle The handle associated with this characteristic. + */ +void BLECharacteristic::setHandle(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str()); + m_handle = handle; + ESP_LOGD(LOG_TAG, "<< setHandle"); +} // setHandle + + +/** + * @brief Set the Indicate property value. + * @param [in] value Set to true if we are to allow indicate messages. + */ +void BLECharacteristic::setIndicateProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + } +} // setIndicateProperty + + +/** + * @brief Set the Notify property value. + * @param [in] value Set to true if we are to allow notification messages. + */ +void BLECharacteristic::setNotifyProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + } +} // setNotifyProperty + + +/** + * @brief Set the Read property value. + * @param [in] value Set to true if we are to allow reads. + */ +void BLECharacteristic::setReadProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setReadProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ); + } +} // setReadProperty + + +/** + * @brief Set the value of the characteristic. + * @param [in] data The data to set for the characteristic. + * @param [in] length The length of the data in bytes. + */ +void BLECharacteristic::setValue(uint8_t* data, size_t length) { + char* pHex = BLEUtils::buildHexData(nullptr, data, length); + ESP_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str()); + free(pHex); + if (length > ESP_GATT_MAX_ATTR_LEN) { + ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN); + return; + } + m_value.setValue(data, length); + ESP_LOGD(LOG_TAG, "<< setValue"); +} // setValue + + +/** + * @brief Set the value of the characteristic from string data. + * We set the value of the characteristic from the bytes contained in the + * string. + * @param [in] Set the value of the characteristic. + * @return N/A. + */ +void BLECharacteristic::setValue(std::string value) { + setValue((uint8_t*)(value.data()), value.length()); +} // setValue + +void BLECharacteristic::setValue(uint16_t& data16) { + uint8_t temp[2]; + temp[0] = data16; + temp[1] = data16 >> 8; + setValue(temp, 2); +} // setValue + +void BLECharacteristic::setValue(uint32_t& data32) { + uint8_t temp[4]; + temp[0] = data32; + temp[1] = data32 >> 8; + temp[2] = data32 >> 16; + temp[3] = data32 >> 24; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(int& data32) { + uint8_t temp[4]; + temp[0] = data32; + temp[1] = data32 >> 8; + temp[2] = data32 >> 16; + temp[3] = data32 >> 24; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(float& data32) { + uint8_t temp[4]; + *((float*)temp) = data32; + setValue(temp, 4); +} // setValue + +void BLECharacteristic::setValue(double& data64) { + uint8_t temp[8]; + *((double*)temp) = data64; + setValue(temp, 8); +} // setValue + + +/** + * @brief Set the Write No Response property value. + * @param [in] value Set to true if we are to allow writes with no response. + */ +void BLECharacteristic::setWriteNoResponseProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + } +} // setWriteNoResponseProperty + + +/** + * @brief Set the Write property value. + * @param [in] value Set to true if we are to allow writes. + */ +void BLECharacteristic::setWriteProperty(bool value) { + //ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", value); + if (value) { + m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE); + } else { + m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + } +} // setWriteProperty + + +/** + * @brief Return a string representation of the characteristic. + * @return A string representation of the characteristic. + */ +std::string BLECharacteristic::toString() { + std::stringstream stringstream; + stringstream << std::hex << std::setfill('0'); + stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle; + stringstream << " " << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_READ) ? "Read " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE) ? "Write " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) ? "WriteNoResponse " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_BROADCAST) ? "Broadcast " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) ? "Notify " : "") << + ((m_properties & ESP_GATT_CHAR_PROP_BIT_INDICATE) ? "Indicate " : ""); + return stringstream.str(); +} // toString + + +BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} + + +/** + * @brief Callback function to support a read request. + * @param [in] pCharacteristic The characteristic that is the source of the event. + */ +void BLECharacteristicCallbacks::onRead(BLECharacteristic* pCharacteristic) { + ESP_LOGD("BLECharacteristicCallbacks", ">> onRead: default"); + ESP_LOGD("BLECharacteristicCallbacks", "<< onRead"); +} // onRead + + +/** + * @brief Callback function to support a write request. + * @param [in] pCharacteristic The characteristic that is the source of the event. + */ +void BLECharacteristicCallbacks::onWrite(BLECharacteristic* pCharacteristic) { + ESP_LOGD("BLECharacteristicCallbacks", ">> onWrite: default"); + ESP_LOGD("BLECharacteristicCallbacks", "<< onWrite"); +} // onWrite + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.h b/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.h new file mode 100644 index 0000000..5eb1e8d --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLECharacteristic.h @@ -0,0 +1,137 @@ +/* + * BLECharacteristic.h + * + * Created on: Jun 22, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ +#define COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEUUID.h" +#include +#include +#include "BLEDescriptor.h" +#include "BLEValue.h" +#include "FreeRTOS.h" + +class BLEService; +class BLEDescriptor; +class BLECharacteristicCallbacks; + +/** + * @brief A management structure for %BLE descriptors. + */ +class BLEDescriptorMap { +public: + void setByUUID(const char* uuid, BLEDescriptor* pDescriptor); + void setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor); + void setByHandle(uint16_t handle, BLEDescriptor* pDescriptor); + BLEDescriptor* getByUUID(const char* uuid); + BLEDescriptor* getByUUID(BLEUUID uuid); + BLEDescriptor* getByHandle(uint16_t handle); + std::string toString(); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + BLEDescriptor* getFirst(); + BLEDescriptor* getNext(); +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + + +/** + * @brief The model of a %BLE Characteristic. + * + * A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE server and + * can be read and written to by a %BLE client. + */ +class BLECharacteristic { +public: + BLECharacteristic(const char* uuid, uint32_t properties = 0); + BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); + virtual ~BLECharacteristic(); + + void addDescriptor(BLEDescriptor* pDescriptor); + BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID); + BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); + BLEUUID getUUID(); + std::string getValue(); + uint8_t* getData(); + + void indicate(); + void notify(bool is_notification = true); + void setBroadcastProperty(bool value); + void setCallbacks(BLECharacteristicCallbacks* pCallbacks); + void setIndicateProperty(bool value); + void setNotifyProperty(bool value); + void setReadProperty(bool value); + void setValue(uint8_t* data, size_t size); + void setValue(std::string value); + void setValue(uint16_t& data16); + void setValue(uint32_t& data32); + void setValue(int& data32); + void setValue(float& data32); + void setValue(double& data64); + void setWriteProperty(bool value); + void setWriteNoResponseProperty(bool value); + std::string toString(); + uint16_t getHandle(); + void setAccessPermissions(esp_gatt_perm_t perm); + + static const uint32_t PROPERTY_READ = 1<<0; + static const uint32_t PROPERTY_WRITE = 1<<1; + static const uint32_t PROPERTY_NOTIFY = 1<<2; + static const uint32_t PROPERTY_BROADCAST = 1<<3; + static const uint32_t PROPERTY_INDICATE = 1<<4; + static const uint32_t PROPERTY_WRITE_NR = 1<<5; + +private: + + friend class BLEServer; + friend class BLEService; + friend class BLEDescriptor; + friend class BLECharacteristicMap; + + BLEUUID m_bleUUID; + BLEDescriptorMap m_descriptorMap; + uint16_t m_handle; + esp_gatt_char_prop_t m_properties; + BLECharacteristicCallbacks* m_pCallbacks; + BLEService* m_pService; + BLEValue m_value; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + + void executeCreate(BLEService* pService); + esp_gatt_char_prop_t getProperties(); + BLEService* getService(); + void setHandle(uint16_t handle); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); +}; // BLECharacteristic + + +/** + * @brief Callbacks that can be associated with a %BLE characteristic to inform of events. + * + * When a server application creates a %BLE characteristic, we may wish to be informed when there is either + * a read or write request to the characteristic's value. An application can register a + * sub-classed instance of this class and will be notified when such an event happens. + */ +class BLECharacteristicCallbacks { +public: + virtual ~BLECharacteristicCallbacks(); + virtual void onRead(BLECharacteristic* pCharacteristic); + virtual void onWrite(BLECharacteristic* pCharacteristic); +}; +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp b/libraries/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp new file mode 100644 index 0000000..d73aae9 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp @@ -0,0 +1,133 @@ +/* + * BLECharacteristicMap.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEService.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + + +/** + * @brief Return the characteristic by handle. + * @param [in] handle The handle to look up the characteristic. + * @return The characteristic. + */ +BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + +/** + * @brief Return the characteristic by UUID. + * @param [in] UUID The UUID to look up the characteristic. + * @return The characteristic. + */ +BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + + +/** + * @brief Return the characteristic by UUID. + * @param [in] UUID The UUID to look up the characteristic. + * @return The characteristic. + */ +BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { + for (auto &myPair : m_uuidMap) { + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; + } + } + //return m_uuidMap.at(uuid.toString()); + return nullptr; +} // getByUUID + + +/** + * @brief Get the first characteristic in the map. + * @return The first characteristic in the map. + */ +BLECharacteristic* BLECharacteristicMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) return nullptr; + BLECharacteristic* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + + +/** + * @brief Get the next characteristic in the map. + * @return The next characteristic in the map. + */ +BLECharacteristic* BLECharacteristicMap::getNext() { + if (m_iterator == m_uuidMap.end()) return nullptr; + BLECharacteristic* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext + + +/** + * @brief Pass the GATT server event onwards to each of the characteristics found in the mapping + * @param [in] event + * @param [in] gatts_if + * @param [in] param + */ +void BLECharacteristicMap::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + // Invoke the handler for every Service we have. + for (auto& myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(event, gatts_if, param); + } +} // handleGATTServerEvent + + +/** + * @brief Set the characteristic by handle. + * @param [in] handle The handle of the characteristic. + * @param [in] characteristic The characteristic to cache. + * @return N/A. + */ +void BLECharacteristicMap::setByHandle(uint16_t handle, BLECharacteristic* characteristic) { + m_handleMap.insert(std::pair(handle, characteristic)); +} // setByHandle + + +/** + * @brief Set the characteristic by UUID. + * @param [in] uuid The uuid of the characteristic. + * @param [in] characteristic The characteristic to cache. + * @return N/A. + */ +void BLECharacteristicMap::setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid) { + m_uuidMap.insert(std::pair(pCharacteristic, uuid.toString())); +} // setByUUID + + +/** + * @brief Return a string representation of the characteristic map. + * @return A string representation of the characteristic map. + */ +std::string BLECharacteristicMap::toString() { + std::stringstream stringStream; + stringStream << std::hex << std::setfill('0'); + int count = 0; + for (auto &myPair: m_uuidMap) { + if (count > 0) { + stringStream << "\n"; + } + count++; + stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString(); + } + return stringStream.str(); +} // toString + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEClient.cpp b/libraries/ESP32_BLE_Arduino/src/BLEClient.cpp new file mode 100644 index 0000000..0e552ec --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEClient.cpp @@ -0,0 +1,536 @@ +/* + * BLEDevice.cpp + * + * Created on: Mar 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include "BLEClient.h" +#include "BLEUtils.h" +#include "BLEService.h" +#include "GeneralUtils.h" +#include +#include +#include +#include "BLEDevice.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEClient"; +#endif + + +/* + * Design + * ------ + * When we perform a searchService() requests, we are asking the BLE server to return each of the services + * that it exposes. For each service, we received an ESP_GATTC_SEARCH_RES_EVT event which contains details + * of the exposed service including its UUID. + * + * The objects we will invent for a BLEClient will be as follows: + * * BLERemoteService - A model of a remote service. + * * BLERemoteCharacteristic - A model of a remote characteristic + * * BLERemoteDescriptor - A model of a remote descriptor. + * + * Since there is a hierarchical relationship here, we will have the idea that from a BLERemoteService will own + * zero or more remote characteristics and a BLERemoteCharacteristic will own zero or more remote BLEDescriptors. + * + * We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics + * and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors. + * + * + */ + +BLEClient::BLEClient() { + m_pClientCallbacks = nullptr; + m_conn_id = ESP_GATT_IF_NONE; + m_gattc_if = ESP_GATT_IF_NONE; + m_haveServices = false; + m_isConnected = false; // Initially, we are flagged as not connected. +} // BLEClient + + +/** + * @brief Destructor. + */ +BLEClient::~BLEClient() { + // We may have allocated service references associated with this client. Before we are finished + // with the client, we must release resources. + for (auto &myPair : m_servicesMap) { + delete myPair.second; + } + m_servicesMap.clear(); +} // ~BLEClient + + +/** + * @brief Clear any existing services. + * + */ +void BLEClient::clearServices() { + ESP_LOGD(LOG_TAG, ">> clearServices"); + // Delete all the services. + for (auto &myPair : m_servicesMap) { + delete myPair.second; + } + m_servicesMap.clear(); + m_haveServices = false; + ESP_LOGD(LOG_TAG, "<< clearServices"); +} // clearServices + +/** + * Add overloaded function to ease connect to peer device with not public address + */ +bool BLEClient::connect(BLEAdvertisedDevice* device) { + BLEAddress address = device->getAddress(); + esp_ble_addr_type_t type = device->getAddressType(); + return connect(address, type); +} + +/** + * @brief Connect to the partner (BLE Server). + * @param [in] address The address of the partner. + * @return True on success. + */ +bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type) { + ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str()); + +// We need the connection handle that we get from registering the application. We register the app +// and then block on its completion. When the event has arrived, we will have the handle. + m_appId = BLEDevice::m_appId++; + BLEDevice::addPeerDevice(this, true, m_appId); + m_semaphoreRegEvt.take("connect"); + + // clearServices(); // we dont need to delete services since every client is unique? + esp_err_t errRc = ::esp_ble_gattc_app_register(m_appId); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + + m_semaphoreRegEvt.wait("connect"); + + m_peerAddress = address; + + // Perform the open connection request against the target BLE Server. + m_semaphoreOpenEvt.take("connect"); + errRc = ::esp_ble_gattc_open( + m_gattc_if, + *getPeerAddress().getNative(), // address + type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. + 1 // direct connection <-- maybe needs to be changed in case of direct indirect connection??? + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. + ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK); + return rc == ESP_GATT_OK; +} // connect + + +/** + * @brief Disconnect from the peer. + * @return N/A. + */ +void BLEClient::disconnect() { + ESP_LOGD(LOG_TAG, ">> disconnect()"); + esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + ESP_LOGD(LOG_TAG, "<< disconnect()"); +} // disconnect + + +/** + * @brief Handle GATT Client events + */ +void BLEClient::gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + + // Execute handler code based on the type of event received. + switch(event) { + + case ESP_GATTC_SRVC_CHG_EVT: + ESP_LOGI(LOG_TAG, "SERVICE CHANGED"); + break; + + case ESP_GATTC_CLOSE_EVT: { + // esp_ble_gattc_app_unregister(m_appId); + // BLEDevice::removePeerDevice(m_gattc_if, true); + break; + } + + // + // ESP_GATTC_DISCONNECT_EVT + // + // disconnect: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + case ESP_GATTC_DISCONNECT_EVT: { + // If we receive a disconnect event, set the class flag that indicates that we are + // no longer connected. + m_isConnected = false; + if (m_pClientCallbacks != nullptr) { + m_pClientCallbacks->onDisconnect(this); + } + BLEDevice::removePeerDevice(m_appId, true); + esp_ble_gattc_app_unregister(m_gattc_if); + m_semaphoreRssiCmplEvt.give(); + m_semaphoreSearchCmplEvt.give(1); + break; + } // ESP_GATTC_DISCONNECT_EVT + + // + // ESP_GATTC_OPEN_EVT + // + // open: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // + case ESP_GATTC_OPEN_EVT: { + m_conn_id = evtParam->open.conn_id; + if (m_pClientCallbacks != nullptr) { + m_pClientCallbacks->onConnect(this); + } + if (evtParam->open.status == ESP_GATT_OK) { + m_isConnected = true; // Flag us as connected. + } + m_semaphoreOpenEvt.give(evtParam->open.status); + break; + } // ESP_GATTC_OPEN_EVT + + + // + // ESP_GATTC_REG_EVT + // + // reg: + // esp_gatt_status_t status + // uint16_t app_id + // + case ESP_GATTC_REG_EVT: { + m_gattc_if = gattc_if; + m_semaphoreRegEvt.give(); + break; + } // ESP_GATTC_REG_EVT + + case ESP_GATTC_CFG_MTU_EVT: + if(evtParam->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG,"Config mtu failed"); + } + m_mtu = evtParam->cfg_mtu.mtu; + break; + + case ESP_GATTC_CONNECT_EVT: { + BLEDevice::updatePeerDevice(this, true, m_gattc_if); + esp_err_t errRc = esp_ble_gattc_send_mtu_req(gattc_if, evtParam->connect.conn_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_send_mtu_req: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(evtParam->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTC_CONNECT_EVT + + // + // ESP_GATTC_SEARCH_CMPL_EVT + // + // search_cmpl: + // - esp_gatt_status_t status + // - uint16_t conn_id + // + case ESP_GATTC_SEARCH_CMPL_EVT: { + esp_ble_gattc_cb_param_t* p_data = (esp_ble_gattc_cb_param_t*)evtParam; + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(LOG_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } +#ifndef ARDUINO_ARCH_ESP32 +// commented out just for now to keep backward compatibility + // if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + // ESP_LOGI(LOG_TAG, "Get service information from remote device"); + // } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + // ESP_LOGI(LOG_TAG, "Get service information from flash"); + // } else { + // ESP_LOGI(LOG_TAG, "unknown service source"); + // } +#endif + m_semaphoreSearchCmplEvt.give(0); + break; + } // ESP_GATTC_SEARCH_CMPL_EVT + + + // + // ESP_GATTC_SEARCH_RES_EVT + // + // search_res: + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id + // + case ESP_GATTC_SEARCH_RES_EVT: { + BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); + BLERemoteService* pRemoteService = new BLERemoteService( + evtParam->search_res.srvc_id, + this, + evtParam->search_res.start_handle, + evtParam->search_res.end_handle + ); + m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); + m_servicesMapByInstID.insert(std::pair(pRemoteService, evtParam->search_res.srvc_id.inst_id)); + break; + } // ESP_GATTC_SEARCH_RES_EVT + + + default: { + break; + } + } // Switch + + // Pass the request on to all services. + for (auto &myPair : m_servicesMap) { + myPair.second->gattClientEventHandler(event, gattc_if, evtParam); + } + +} // gattClientEventHandler + + +uint16_t BLEClient::getConnId() { + return m_conn_id; +} // getConnId + + + +esp_gatt_if_t BLEClient::getGattcIf() { + return m_gattc_if; +} // getGattcIf + + +/** + * @brief Retrieve the address of the peer. + * + * Returns the Bluetooth device address of the %BLE peer to which this client is connected. + */ +BLEAddress BLEClient::getPeerAddress() { + return m_peerAddress; +} // getAddress + + +/** + * @brief Ask the BLE server for the RSSI value. + * @return The RSSI value. + */ +int BLEClient::getRssi() { + ESP_LOGD(LOG_TAG, ">> getRssi()"); + if (!isConnected()) { + ESP_LOGD(LOG_TAG, "<< getRssi(): Not connected"); + return 0; + } + // We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive + // an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion. + // + m_semaphoreRssiCmplEvt.take("getRssi"); + esp_err_t rc = ::esp_ble_gap_read_rssi(*getPeerAddress().getNative()); + if (rc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + return 0; + } + int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi"); + ESP_LOGD(LOG_TAG, "<< getRssi(): %d", rssiValue); + return rssiValue; +} // getRssi + + +/** + * @brief Get the service BLE Remote Service instance corresponding to the uuid. + * @param [in] uuid The UUID of the service being sought. + * @return A reference to the Service or nullptr if don't know about it. + */ +BLERemoteService* BLEClient::getService(const char* uuid) { + return getService(BLEUUID(uuid)); +} // getService + + +/** + * @brief Get the service object corresponding to the uuid. + * @param [in] uuid The UUID of the service being sought. + * @return A reference to the Service or nullptr if don't know about it. + * @throws BLEUuidNotFound + */ +BLERemoteService* BLEClient::getService(BLEUUID uuid) { + ESP_LOGD(LOG_TAG, ">> getService: uuid: %s", uuid.toString().c_str()); +// Design +// ------ +// We wish to retrieve the service given its UUID. It is possible that we have not yet asked the +// device what services it has in which case we have nothing to match against. If we have not +// asked the device about its services, then we do that now. Once we get the results we can then +// examine the services map to see if it has the service we are looking for. + if (!m_haveServices) { + getServices(); + } + std::string uuidStr = uuid.toString(); + for (auto &myPair : m_servicesMap) { + if (myPair.first == uuidStr) { + ESP_LOGD(LOG_TAG, "<< getService: found the service with uuid: %s", uuid.toString().c_str()); + return myPair.second; + } + } // End of each of the services. + ESP_LOGD(LOG_TAG, "<< getService: not found"); + return nullptr; +} // getService + + +/** + * @brief Ask the remote %BLE server for its services. + * A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of + * services and wait until we have received them all. + * @return N/A + */ +std::map* BLEClient::getServices() { +/* + * Design + * ------ + * We invoke esp_ble_gattc_search_service. This will request a list of the service exposed by the + * peer BLE partner to be returned as events. Each event will be an an instance of ESP_GATTC_SEARCH_RES_EVT + * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received. + */ + ESP_LOGD(LOG_TAG, ">> getServices"); +// TODO implement retrieving services from cache + clearServices(); // Clear any services that may exist. + + esp_err_t errRc = esp_ble_gattc_search_service( + getGattcIf(), + getConnId(), + NULL // Filter UUID + ); + + m_semaphoreSearchCmplEvt.take("getServices"); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return &m_servicesMap; + } + // If sucessfull, remember that we now have services. + m_haveServices = (m_semaphoreSearchCmplEvt.wait("getServices") == 0); + ESP_LOGD(LOG_TAG, "<< getServices"); + return &m_servicesMap; +} // getServices + + +/** + * @brief Get the value of a specific characteristic associated with a specific service. + * @param [in] serviceUUID The service that owns the characteristic. + * @param [in] characteristicUUID The characteristic whose value we wish to read. + * @throws BLEUuidNotFound + */ +std::string BLEClient::getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + std::string ret = getService(serviceUUID)->getCharacteristic(characteristicUUID)->readValue(); + ESP_LOGD(LOG_TAG, "<read_rssi_cmpl.rssi); + break; + } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + + default: + break; + } +} // handleGAPEvent + + +/** + * @brief Are we connected to a partner? + * @return True if we are connected and false if we are not connected. + */ +bool BLEClient::isConnected() { + return m_isConnected; +} // isConnected + + + + +/** + * @brief Set the callbacks that will be invoked. + */ +void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) { + m_pClientCallbacks = pClientCallbacks; +} // setClientCallbacks + + +/** + * @brief Set the value of a specific characteristic associated with a specific service. + * @param [in] serviceUUID The service that owns the characteristic. + * @param [in] characteristicUUID The characteristic whose value we wish to write. + * @throws BLEUuidNotFound + */ +void BLEClient::setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + getService(serviceUUID)->getCharacteristic(characteristicUUID)->writeValue(value); + ESP_LOGD(LOG_TAG, "<< setValue"); +} // setValue + +uint16_t BLEClient::getMTU() { + return m_mtu; +} + +/** + * @brief Return a string representation of this client. + * @return A string representation of this client. + */ +std::string BLEClient::toString() { + std::ostringstream ss; + ss << "peer address: " << m_peerAddress.toString(); + ss << "\nServices:\n"; + for (auto &myPair : m_servicesMap) { + ss << myPair.second->toString() << "\n"; + // myPair.second is the value + } + return ss.str(); +} // toString + + +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEClient.h b/libraries/ESP32_BLE_Arduino/src/BLEClient.h new file mode 100644 index 0000000..1b8144d --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEClient.h @@ -0,0 +1,103 @@ +/* + * BLEDevice.h + * + * Created on: Mar 22, 2017 + * Author: kolban + */ + +#ifndef MAIN_BLEDEVICE_H_ +#define MAIN_BLEDEVICE_H_ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include +#include +#include +#include +#include "BLEExceptions.h" +#include "BLERemoteService.h" +#include "BLEService.h" +#include "BLEAddress.h" +#include "BLEAdvertisedDevice.h" + +class BLERemoteService; +class BLEClientCallbacks; +class BLEAdvertisedDevice; + +/** + * @brief A model of a %BLE client. + */ +class BLEClient { +public: + BLEClient(); + ~BLEClient(); + + bool connect(BLEAdvertisedDevice* device); + bool connect(BLEAddress address, esp_ble_addr_type_t type = BLE_ADDR_TYPE_PUBLIC); // Connect to the remote BLE Server + void disconnect(); // Disconnect from the remote BLE Server + BLEAddress getPeerAddress(); // Get the address of the remote BLE Server + int getRssi(); // Get the RSSI of the remote BLE Server + std::map* getServices(); // Get a map of the services offered by the remote BLE Server + BLERemoteService* getService(const char* uuid); // Get a reference to a specified service offered by the remote BLE server. + BLERemoteService* getService(BLEUUID uuid); // Get a reference to a specified service offered by the remote BLE server. + std::string getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a given characteristic at a given service. + + + void handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + + bool isConnected(); // Return true if we are connected. + + void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); + void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a given characteristic at a given service. + + std::string toString(); // Return a string representation of this client. + uint16_t getConnId(); + esp_gatt_if_t getGattcIf(); + uint16_t getMTU(); + +uint16_t m_appId; +private: + friend class BLEDevice; + friend class BLERemoteService; + friend class BLERemoteCharacteristic; + friend class BLERemoteDescriptor; + + void gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param); + + BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); // The BD address of the remote server. + uint16_t m_conn_id; +// int m_deviceType; + esp_gatt_if_t m_gattc_if; + bool m_haveServices = false; // Have we previously obtain the set of services from the remote server. + bool m_isConnected = false; // Are we currently connected. + + BLEClientCallbacks* m_pClientCallbacks; + FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); + FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); + FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt"); + FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); + std::map m_servicesMap; + std::map m_servicesMapByInstID; + void clearServices(); // Clear any existing services. + uint16_t m_mtu = 23; +}; // class BLEDevice + + +/** + * @brief Callbacks associated with a %BLE client. + */ +class BLEClientCallbacks { +public: + virtual ~BLEClientCallbacks() {}; + virtual void onConnect(BLEClient *pClient) = 0; + virtual void onDisconnect(BLEClient *pClient) = 0; +}; + +#endif // CONFIG_BT_ENABLED +#endif /* MAIN_BLEDEVICE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.cpp b/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.cpp new file mode 100644 index 0000000..ba5753d --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.cpp @@ -0,0 +1,296 @@ +/* + * BLEDescriptor.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include "sdkconfig.h" +#include +#include "BLEService.h" +#include "BLEDescriptor.h" +#include "GeneralUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEDescriptor"; +#endif + + + + +#define NULL_HANDLE (0xffff) + + +/** + * @brief BLEDescriptor constructor. + */ +BLEDescriptor::BLEDescriptor(const char* uuid, uint16_t len) : BLEDescriptor(BLEUUID(uuid), len) { +} + +/** + * @brief BLEDescriptor constructor. + */ +BLEDescriptor::BLEDescriptor(BLEUUID uuid, uint16_t max_len) { + m_bleUUID = uuid; + m_value.attr_len = 0; // Initial length is 0. + m_value.attr_max_len = max_len; // Maximum length of the data. + m_handle = NULL_HANDLE; // Handle is initially unknown. + m_pCharacteristic = nullptr; // No initial characteristic. + m_pCallback = nullptr; // No initial callback. + + m_value.attr_value = (uint8_t*) malloc(max_len); // Allocate storage for the value. +} // BLEDescriptor + + +/** + * @brief BLEDescriptor destructor. + */ +BLEDescriptor::~BLEDescriptor() { + free(m_value.attr_value); // Release the storage we created in the constructor. +} // ~BLEDescriptor + + +/** + * @brief Execute the creation of the descriptor with the BLE runtime in ESP. + * @param [in] pCharacteristic The characteristic to which to register this descriptor. + */ +void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { + ESP_LOGD(LOG_TAG, ">> executeCreate(): %s", toString().c_str()); + + if (m_handle != NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "Descriptor already has a handle."); + return; + } + + m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service. + + esp_attr_control_t control; + control.auto_rsp = ESP_GATT_AUTO_RSP; + m_semaphoreCreateEvt.take("executeCreate"); + esp_err_t errRc = ::esp_ble_gatts_add_char_descr( + pCharacteristic->getService()->getHandle(), + getUUID().getNative(), + (esp_gatt_perm_t)m_permissions, + &m_value, + &control); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char_descr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreCreateEvt.wait("executeCreate"); + ESP_LOGD(LOG_TAG, "<< executeCreate"); +} // executeCreate + + +/** + * @brief Get the BLE handle for this descriptor. + * @return The handle for this descriptor. + */ +uint16_t BLEDescriptor::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Get the length of the value of this descriptor. + * @return The length (in bytes) of the value of this descriptor. + */ +size_t BLEDescriptor::getLength() { + return m_value.attr_len; +} // getLength + + +/** + * @brief Get the UUID of the descriptor. + */ +BLEUUID BLEDescriptor::getUUID() { + return m_bleUUID; +} // getUUID + + + +/** + * @brief Get the value of this descriptor. + * @return A pointer to the value of this descriptor. + */ +uint8_t* BLEDescriptor::getValue() { + return m_value.attr_value; +} // getValue + + +/** + * @brief Handle GATT server events for the descripttor. + * @param [in] event + * @param [in] gatts_if + * @param [in] param + */ +void BLEDescriptor::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param) { + switch (event) { + // ESP_GATTS_ADD_CHAR_DESCR_EVT + // + // add_char_descr: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + case ESP_GATTS_ADD_CHAR_DESCR_EVT: { + if (m_pCharacteristic != nullptr && + m_bleUUID.equals(BLEUUID(param->add_char_descr.descr_uuid)) && + m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle && + m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) { + setHandle(param->add_char_descr.attr_handle); + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_ADD_CHAR_DESCR_EVT + + // ESP_GATTS_WRITE_EVT - A request to write the value of a descriptor has arrived. + // + // write: + // - uint16_t conn_id + // - uint16_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool need_rsp + // - bool is_prep + // - uint16_t len + // - uint8_t *value + case ESP_GATTS_WRITE_EVT: { + if (param->write.handle == m_handle) { + setValue(param->write.value, param->write.len); // Set the value of the descriptor. + + if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now. + m_pCallback->onWrite(this); // Invoke the onWrite callback handler. + } + } // End of ... this is our handle. + + break; + } // ESP_GATTS_WRITE_EVT + + // ESP_GATTS_READ_EVT - A request to read the value of a descriptor has arrived. + // + // read: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool is_long + // - bool need_rsp + // + case ESP_GATTS_READ_EVT: { + if (param->read.handle == m_handle) { // If this event is for this descriptor ... process it + + if (m_pCallback != nullptr) { // If we have a user supplied callback, invoke it now. + m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler. + } + + } // End of this is our handle + break; + } // ESP_GATTS_READ_EVT + + default: + break; + } // switch event +} // handleGATTServerEvent + + +/** + * @brief Set the callback handlers for this descriptor. + * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. + */ +void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks* pCallback) { + ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t) pCallback); + m_pCallback = pCallback; + ESP_LOGD(LOG_TAG, "<< setCallbacks"); +} // setCallbacks + + +/** + * @brief Set the handle of this descriptor. + * Set the handle of this descriptor to be the supplied value. + * @param [in] handle The handle to be associated with this descriptor. + * @return N/A. + */ +void BLEDescriptor::setHandle(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle); + m_handle = handle; + ESP_LOGD(LOG_TAG, "<< setHandle()"); +} // setHandle + + +/** + * @brief Set the value of the descriptor. + * @param [in] data The data to set for the descriptor. + * @param [in] length The length of the data in bytes. + */ +void BLEDescriptor::setValue(uint8_t* data, size_t length) { + if (length > ESP_GATT_MAX_ATTR_LEN) { + ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN); + return; + } + m_value.attr_len = length; + memcpy(m_value.attr_value, data, length); +} // setValue + + +/** + * @brief Set the value of the descriptor. + * @param [in] value The value of the descriptor in string form. + */ +void BLEDescriptor::setValue(std::string value) { + setValue((uint8_t*) value.data(), value.length()); +} // setValue + +void BLEDescriptor::setAccessPermissions(esp_gatt_perm_t perm) { + m_permissions = perm; +} + +/** + * @brief Return a string representation of the descriptor. + * @return A string representation of the descriptor. + */ +std::string BLEDescriptor::toString() { + std::stringstream stringstream; + stringstream << std::hex << std::setfill('0'); + stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle; + return stringstream.str(); +} // toString + + +BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {} + +/** + * @brief Callback function to support a read request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onRead(BLEDescriptor* pDescriptor) { + ESP_LOGD("BLEDescriptorCallbacks", ">> onRead: default"); + ESP_LOGD("BLEDescriptorCallbacks", "<< onRead"); +} // onRead + + +/** + * @brief Callback function to support a write request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onWrite(BLEDescriptor* pDescriptor) { + ESP_LOGD("BLEDescriptorCallbacks", ">> onWrite: default"); + ESP_LOGD("BLEDescriptorCallbacks", "<< onWrite"); +} // onWrite + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.h b/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.h new file mode 100644 index 0000000..03cc579 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEDescriptor.h @@ -0,0 +1,77 @@ +/* + * BLEDescriptor.h + * + * Created on: Jun 22, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ +#define COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLEUUID.h" +#include "BLECharacteristic.h" +#include +#include "FreeRTOS.h" + +class BLEService; +class BLECharacteristic; +class BLEDescriptorCallbacks; + +/** + * @brief A model of a %BLE descriptor. + */ +class BLEDescriptor { +public: + BLEDescriptor(const char* uuid, uint16_t max_len = 100); + BLEDescriptor(BLEUUID uuid, uint16_t max_len = 100); + virtual ~BLEDescriptor(); + + uint16_t getHandle(); // Get the handle of the descriptor. + size_t getLength(); // Get the length of the value of the descriptor. + BLEUUID getUUID(); // Get the UUID of the descriptor. + uint8_t* getValue(); // Get a pointer to the value of the descriptor. + void handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + + void setAccessPermissions(esp_gatt_perm_t perm); // Set the permissions of the descriptor. + void setCallbacks(BLEDescriptorCallbacks* pCallbacks); // Set callbacks to be invoked for the descriptor. + void setValue(uint8_t* data, size_t size); // Set the value of the descriptor as a pointer to data. + void setValue(std::string value); // Set the value of the descriptor as a data buffer. + + std::string toString(); // Convert the descriptor to a string representation. + +private: + friend class BLEDescriptorMap; + friend class BLECharacteristic; + BLEUUID m_bleUUID; + uint16_t m_handle; + BLEDescriptorCallbacks* m_pCallback; + BLECharacteristic* m_pCharacteristic; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + esp_attr_value_t m_value; + + void executeCreate(BLECharacteristic* pCharacteristic); + void setHandle(uint16_t handle); +}; // BLEDescriptor + + +/** + * @brief Callbacks that can be associated with a %BLE descriptors to inform of events. + * + * When a server application creates a %BLE descriptor, we may wish to be informed when there is either + * a read or write request to the descriptors value. An application can register a + * sub-classed instance of this class and will be notified when such an event happens. + */ +class BLEDescriptorCallbacks { +public: + virtual ~BLEDescriptorCallbacks(); + virtual void onRead(BLEDescriptor* pDescriptor); + virtual void onWrite(BLEDescriptor* pDescriptor); +}; +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp b/libraries/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp new file mode 100644 index 0000000..6b84583 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp @@ -0,0 +1,147 @@ +/* + * BLEDescriptorMap.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLECharacteristic.h" +#include "BLEDescriptor.h" +#include // ESP32 BLE +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +/** + * @brief Return the descriptor by UUID. + * @param [in] UUID The UUID to look up the descriptor. + * @return The descriptor. If not present, then nullptr is returned. + */ +BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + + +/** + * @brief Return the descriptor by UUID. + * @param [in] UUID The UUID to look up the descriptor. + * @return The descriptor. If not present, then nullptr is returned. + */ +BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) { + for (auto &myPair : m_uuidMap) { + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; + } + } + //return m_uuidMap.at(uuid.toString()); + return nullptr; +} // getByUUID + + +/** + * @brief Return the descriptor by handle. + * @param [in] handle The handle to look up the descriptor. + * @return The descriptor. + */ +BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + +/** + * @brief Set the descriptor by UUID. + * @param [in] uuid The uuid of the descriptor. + * @param [in] characteristic The descriptor to cache. + * @return N/A. + */ +void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor* pDescriptor){ + m_uuidMap.insert(std::pair(pDescriptor, uuid)); +} // setByUUID + + + +/** + * @brief Set the descriptor by UUID. + * @param [in] uuid The uuid of the descriptor. + * @param [in] characteristic The descriptor to cache. + * @return N/A. + */ +void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor) { + m_uuidMap.insert(std::pair(pDescriptor, uuid.toString())); +} // setByUUID + + +/** + * @brief Set the descriptor by handle. + * @param [in] handle The handle of the descriptor. + * @param [in] descriptor The descriptor to cache. + * @return N/A. + */ +void BLEDescriptorMap::setByHandle(uint16_t handle, BLEDescriptor* pDescriptor) { + m_handleMap.insert(std::pair(handle, pDescriptor)); +} // setByHandle + + +/** + * @brief Return a string representation of the descriptor map. + * @return A string representation of the descriptor map. + */ +std::string BLEDescriptorMap::toString() { + std::stringstream stringStream; + stringStream << std::hex << std::setfill('0'); + int count = 0; + for (auto &myPair : m_uuidMap) { + if (count > 0) { + stringStream << "\n"; + } + count++; + stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString(); + } + return stringStream.str(); +} // toString + + +/** + * @breif Pass the GATT server event onwards to each of the descriptors found in the mapping + * @param [in] event + * @param [in] gatts_if + * @param [in] param + */ +void BLEDescriptorMap::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param) { + // Invoke the handler for every descriptor we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(event, gatts_if, param); + } +} // handleGATTServerEvent + + +/** + * @brief Get the first descriptor in the map. + * @return The first descriptor in the map. + */ +BLEDescriptor* BLEDescriptorMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + + +/** + * @brief Get the next descriptor in the map. + * @return The next descriptor in the map. + */ +BLEDescriptor* BLEDescriptorMap::getNext() { + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEDevice.cpp b/libraries/ESP32_BLE_Arduino/src/BLEDevice.cpp new file mode 100644 index 0000000..1f39caf --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEDevice.cpp @@ -0,0 +1,646 @@ +/* + * BLE.cpp + * + * Created on: Mar 16, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 ESP-IDF +#include // Part of C++ Standard library +#include // Part of C++ Standard library +#include // Part of C++ Standard library + +#include "BLEDevice.h" +#include "BLEClient.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#include "esp32-hal-bt.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEDevice"; +#endif + + +/** + * Singletons for the BLEDevice. + */ +BLEServer* BLEDevice::m_pServer = nullptr; +BLEScan* BLEDevice::m_pScan = nullptr; +BLEClient* BLEDevice::m_pClient = nullptr; +bool initialized = false; +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +BLESecurityCallbacks* BLEDevice::m_securityCallbacks = nullptr; +uint16_t BLEDevice::m_localMTU = 23; // not sure if this variable is useful +BLEAdvertising* BLEDevice::m_bleAdvertising = nullptr; +uint16_t BLEDevice::m_appId = 0; +std::map BLEDevice::m_connectedClientsMap; +gap_event_handler BLEDevice::m_customGapHandler = nullptr; +gattc_event_handler BLEDevice::m_customGattcHandler = nullptr; +gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; + +/** + * @brief Create a new instance of a client. + * @return A new instance of the client. + */ +/* STATIC */ BLEClient* BLEDevice::createClient() { + ESP_LOGD(LOG_TAG, ">> createClient"); +#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTC_ENABLE + m_pClient = new BLEClient(); + ESP_LOGD(LOG_TAG, "<< createClient"); + return m_pClient; +} // createClient + + +/** + * @brief Create a new instance of a server. + * @return A new instance of the server. + */ +/* STATIC */ BLEServer* BLEDevice::createServer() { + ESP_LOGD(LOG_TAG, ">> createServer"); +#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig + ESP_LOGE(LOG_TAG, "BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); + abort(); +#endif // CONFIG_GATTS_ENABLE + m_pServer = new BLEServer(); + m_pServer->createApp(m_appId++); + ESP_LOGD(LOG_TAG, "<< createServer"); + return m_pServer; +} // createServer + + +/** + * @brief Handle GATT server events. + * + * @param [in] event The event that has been newly received. + * @param [in] gatts_if The connection to the GATT interface. + * @param [in] param Parameters for the event. + */ +/* STATIC */ void BLEDevice::gattServerEventHandler( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param +) { + ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s", + gatts_if, + BLEUtils::gattServerEventTypeToString(event).c_str()); + + BLEUtils::dumpGattServerEvent(event, gatts_if, param); + + switch (event) { + case ESP_GATTS_CONNECT_EVT: { +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + default: { + break; + } + } // switch + + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); + } + + if(m_customGattsHandler != nullptr) { + m_customGattsHandler(event, gatts_if, param); + } + +} // gattServerEventHandler + + +/** + * @brief Handle GATT client events. + * + * Handler for the GATT client events. + * + * @param [in] event + * @param [in] gattc_if + * @param [in] param + */ +/* STATIC */ void BLEDevice::gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param) { + + ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s", + gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + BLEUtils::dumpGattClientEvent(event, gattc_if, param); + + switch(event) { + case ESP_GATTC_CONNECT_EVT: { +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityLevel){ + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + default: + break; + } // switch + for(auto &myPair : BLEDevice::getPeerDevices(true)) { + conn_status_t conn_status = (conn_status_t)myPair.second; + if(((BLEClient*)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient*)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE || gattc_if == ESP_GATT_IF_NONE){ + ((BLEClient*)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); + } + } + + if(m_customGattcHandler != nullptr) { + m_customGattcHandler(event, gattc_if, param); + } + + +} // gattClientEventHandler + + +/** + * @brief Handle GAP events. + */ +/* STATIC */ void BLEDevice::gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t *param) { + + BLEUtils::dumpGapEvent(event, param); + + switch(event) { + + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(LOG_TAG, m_remote_bda, sizeof(m_remote_bda)); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks!=nullptr){ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); + } + else{ + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: //the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + //display the passkey number to the user to input it in the peer deivce within 30 seconds + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "passKey = %d", param->ble_security.key_notif.passkey); + if(BLEDevice::m_securityCallbacks!=nullptr){ + BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + ESP_LOGD(LOG_TAG, "ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + ESP_LOGI(LOG_TAG, "key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if(BLEDevice::m_securityCallbacks != nullptr){ + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + default: { + break; + } + } // switch + + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->handleGAPEvent(event, param); + } + + if(m_bleAdvertising != nullptr) { + BLEDevice::getAdvertising()->handleGAPEvent(event, param); + } + + if(m_customGapHandler != nullptr) { + BLEDevice::m_customGapHandler(event, param); + } + +} // gapEventHandler + + +/** + * @brief Get the BLE device address. + * @return The BLE device address. + */ +/* STATIC*/ BLEAddress BLEDevice::getAddress() { + const uint8_t* bdAddr = esp_bt_dev_get_address(); + esp_bd_addr_t addr; + memcpy(addr, bdAddr, sizeof(addr)); + return BLEAddress(addr); +} // getAddress + + +/** + * @brief Retrieve the Scan object that we use for scanning. + * @return The scanning object reference. This is a singleton object. The caller should not + * try and release/delete it. + */ +/* STATIC */ BLEScan* BLEDevice::getScan() { + //ESP_LOGD(LOG_TAG, ">> getScan"); + if (m_pScan == nullptr) { + m_pScan = new BLEScan(); + //ESP_LOGD(LOG_TAG, " - creating a new scan object"); + } + //ESP_LOGD(LOG_TAG, "<< getScan: Returning object at 0x%x", (uint32_t)m_pScan); + return m_pScan; +} // getScan + + +/** + * @brief Get the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ std::string BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { + ESP_LOGD(LOG_TAG, ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient* pClient = createClient(); + pClient->connect(bdAddress); + std::string ret = pClient->getValue(serviceUUID, characteristicUUID); + pClient->disconnect(); + ESP_LOGD(LOG_TAG, "<< getValue"); + return ret; +} // getValue + + +/** + * @brief Initialize the %BLE environment. + * @param deviceName The device name of the device. + */ +/* STATIC */ void BLEDevice::init(std::string deviceName) { + if(!initialized){ + initialized = true; // Set the initialization flag to ensure we are only initialized once. + + esp_err_t errRc = ESP_OK; +#ifdef ARDUINO_ARCH_ESP32 + if (!btStart()) { + errRc = ESP_FAIL; + return; + } +#else + errRc = ::nvs_flash_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifndef CLASSIC_BT_ENABLED + esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); +#endif + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + errRc = esp_bt_controller_init(&bt_cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifndef CLASSIC_BT_ENABLED + errRc = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#else + errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif +#endif + + esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); + if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED) { + errRc = esp_bluedroid_init(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + if (bt_state != ESP_BLUEDROID_STATUS_ENABLED) { + errRc = esp_bluedroid_enable(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + } + + errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + +#ifdef CONFIG_GATTC_ENABLE // Check that BLE client is configured in make menuconfig + errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTC_ENABLE + +#ifdef CONFIG_GATTS_ENABLE // Check that BLE server is configured in make menuconfig + errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } +#endif // CONFIG_GATTS_ENABLE + + errRc = ::esp_ble_gap_set_device_name(deviceName.c_str()); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; + +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; + errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + }; +#endif // CONFIG_BLE_SMP_ENABLE + } + vTaskDelay(200 / portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. +} // init + + +/** + * @brief Set the transmission power. + * The power level can be one of: + * * ESP_PWR_LVL_N14 + * * ESP_PWR_LVL_N11 + * * ESP_PWR_LVL_N8 + * * ESP_PWR_LVL_N5 + * * ESP_PWR_LVL_N2 + * * ESP_PWR_LVL_P1 + * * ESP_PWR_LVL_P4 + * * ESP_PWR_LVL_P7 + * @param [in] powerLevel. + */ +/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel) { + ESP_LOGD(LOG_TAG, ">> setPower: %d", powerLevel); + esp_err_t errRc = ::esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, powerLevel); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_tx_power_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + }; + ESP_LOGD(LOG_TAG, "<< setPower"); +} // setPower + + +/** + * @brief Set the value of a characteristic of a service on a remote device. + * @param [in] bdAddress + * @param [in] serviceUUID + * @param [in] characteristicUUID + */ +/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + BLEClient* pClient = createClient(); + pClient->connect(bdAddress); + pClient->setValue(serviceUUID, characteristicUUID, value); + pClient->disconnect(); +} // setValue + + +/** + * @brief Return a string representation of the nature of this device. + * @return A string representation of the nature of this device. + */ +/* STATIC */ std::string BLEDevice::toString() { + std::ostringstream oss; + oss << "BD Address: " << getAddress().toString(); + return oss.str(); +} // toString + + +/** + * @brief Add an entry to the BLE white list. + * @param [in] address The address to add to the white list. + */ +void BLEDevice::whiteListAdd(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListAdd: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListAdd"); +} // whiteListAdd + + +/** + * @brief Remove an entry from the BLE white list. + * @param [in] address The address to remove from the white list. + */ +void BLEDevice::whiteListRemove(BLEAddress address) { + ESP_LOGD(LOG_TAG, ">> whiteListRemove: %s", address.toString().c_str()); + esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListRemove"); +} // whiteListRemove + +/* + * @brief Set encryption level that will be negotiated with peer device durng connection + * @param [in] level Requested encryption level + */ +void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { + BLEDevice::m_securityLevel = level; +} + +/* + * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events + * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback + */ +void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks* callbacks) { + BLEDevice::m_securityCallbacks = callbacks; +} + +/* + * @brief Setup local mtu that will be used to negotiate mtu during request from client peer + * @param [in] mtu Value to set local mtu, should be larger than 23 and lower or equal to 517 + */ +esp_err_t BLEDevice::setMTU(uint16_t mtu) { + ESP_LOGD(LOG_TAG, ">> setLocalMTU: %d", mtu); + esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); + if (err == ESP_OK) { + m_localMTU = mtu; + } else { + ESP_LOGE(LOG_TAG, "can't set local mtu value: %d", mtu); + } + ESP_LOGD(LOG_TAG, "<< setLocalMTU"); + return err; +} + +/* + * @brief Get local MTU value set during mtu request or default value + */ +uint16_t BLEDevice::getMTU() { + return m_localMTU; +} + +bool BLEDevice::getInitialized() { + return initialized; +} + +BLEAdvertising* BLEDevice::getAdvertising() { + if(m_bleAdvertising == nullptr) { + m_bleAdvertising = new BLEAdvertising(); + ESP_LOGI(LOG_TAG, "create advertising"); + } + ESP_LOGD(LOG_TAG, "get advertising"); + return m_bleAdvertising; +} + +void BLEDevice::startAdvertising() { + ESP_LOGD(LOG_TAG, ">> startAdvertising"); + getAdvertising()->start(); + ESP_LOGD(LOG_TAG, "<< startAdvertising"); +} // startAdvertising + +/* multi connect support */ +/* requires a little more work */ +std::map BLEDevice::getPeerDevices(bool _client) { + return m_connectedClientsMap; +} + +BLEClient* BLEDevice::getClientByGattIf(uint16_t conn_id) { + return (BLEClient*)m_connectedClientsMap.find(conn_id)->second.peer_device; +} + +void BLEDevice::updatePeerDevice(void* peer, bool _client, uint16_t conn_id) { + ESP_LOGD(LOG_TAG, "update conn_id: %d, GATT role: %s", conn_id, _client? "client":"server"); + std::map::iterator it = m_connectedClientsMap.find(ESP_GATT_IF_NONE); + if (it != m_connectedClientsMap.end()) { + std::swap(m_connectedClientsMap[conn_id], it->second); + m_connectedClientsMap.erase(it); + }else{ + it = m_connectedClientsMap.find(conn_id); + if (it != m_connectedClientsMap.end()) { + conn_status_t _st = it->second; + _st.peer_device = peer; + std::swap(m_connectedClientsMap[conn_id], _st); + } + } +} + +void BLEDevice::addPeerDevice(void* peer, bool _client, uint16_t conn_id) { + ESP_LOGI(LOG_TAG, "add conn_id: %d, GATT role: %s", conn_id, _client? "client":"server"); + conn_status_t status = { + .peer_device = peer, + .connected = true, + .mtu = 23 + }; + + m_connectedClientsMap.insert(std::pair(conn_id, status)); +} + +void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { + ESP_LOGI(LOG_TAG, "remove: %d, GATT role %s", conn_id, _client?"client":"server"); + if(m_connectedClientsMap.find(conn_id) != m_connectedClientsMap.end()) + m_connectedClientsMap.erase(conn_id); +} + +/* multi connect support */ + +/** + * @brief de-Initialize the %BLE environment. + * @param release_memory release the internal BT stack memory + */ +/* STATIC */ void BLEDevice::deinit(bool release_memory) { + if (!initialized) return; + + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); +#ifndef ARDUINO_ARCH_ESP32 + if (release_memory) { + esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); // <-- require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) + } else { + initialized = false; + } +#endif +} + +void BLEDevice::setCustomGapHandler(gap_event_handler handler) { + m_customGapHandler = handler; +} + +void BLEDevice::setCustomGattcHandler(gattc_event_handler handler) { + m_customGattcHandler = handler; +} + +void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) { + m_customGattsHandler = handler; +} + +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEDevice.h b/libraries/ESP32_BLE_Arduino/src/BLEDevice.h new file mode 100644 index 0000000..e9cd40a --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEDevice.h @@ -0,0 +1,99 @@ +/* + * BLEDevice.h + * + * Created on: Mar 16, 2017 + * Author: kolban + */ + +#ifndef MAIN_BLEDevice_H_ +#define MAIN_BLEDevice_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include // ESP32 BLE +#include // ESP32 BLE +#include // Part of C++ STL +#include +#include + +#include "BLEServer.h" +#include "BLEClient.h" +#include "BLEUtils.h" +#include "BLEScan.h" +#include "BLEAddress.h" + +/** + * @brief BLE functions. + */ +typedef void (*gap_event_handler)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); +typedef void (*gattc_event_handler)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param); +typedef void (*gatts_event_handler)(esp_gatts_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gatts_cb_param_t* param); + +class BLEDevice { +public: + + static BLEClient* createClient(); // Create a new BLE client. + static BLEServer* createServer(); // Cretae a new BLE server. + static BLEAddress getAddress(); // Retrieve our own local BD address. + static BLEScan* getScan(); // Get the scan object + static std::string getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a characteristic of a service on a server. + static void init(std::string deviceName); // Initialize the local BLE environment. + static void setPower(esp_power_level_t powerLevel); // Set our power level. + static void setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, std::string value); // Set the value of a characteristic on a service on a server. + static std::string toString(); // Return a string representation of our device. + static void whiteListAdd(BLEAddress address); // Add an entry to the BLE white list. + static void whiteListRemove(BLEAddress address); // Remove an entry from the BLE white list. + static void setEncryptionLevel(esp_ble_sec_act_t level); + static void setSecurityCallbacks(BLESecurityCallbacks* pCallbacks); + static esp_err_t setMTU(uint16_t mtu); + static uint16_t getMTU(); + static bool getInitialized(); // Returns the state of the device, is it initialized or not? + /* move advertising to BLEDevice for saving ram and flash in beacons */ + static BLEAdvertising* getAdvertising(); + static void startAdvertising(); + static uint16_t m_appId; + /* multi connect */ + static std::map getPeerDevices(bool client); + static void addPeerDevice(void* peer, bool is_client, uint16_t conn_id); + static void updatePeerDevice(void* peer, bool _client, uint16_t conn_id); + static void removePeerDevice(uint16_t conn_id, bool client); + static BLEClient* getClientByGattIf(uint16_t conn_id); + static void setCustomGapHandler(gap_event_handler handler); + static void setCustomGattcHandler(gattc_event_handler handler); + static void setCustomGattsHandler(gatts_event_handler handler); + static void deinit(bool release_memory = false); + static uint16_t m_localMTU; + static esp_ble_sec_act_t m_securityLevel; + +private: + static BLEServer* m_pServer; + static BLEScan* m_pScan; + static BLEClient* m_pClient; + static BLESecurityCallbacks* m_securityCallbacks; + static BLEAdvertising* m_bleAdvertising; + static esp_gatt_if_t getGattcIF(); + static std::map m_connectedClientsMap; + + static void gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* param); + + static void gattServerEventHandler( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param); + + static void gapEventHandler( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + +public: +/* custom gap and gatt handlers for flexibility */ + static gap_event_handler m_customGapHandler; + static gattc_event_handler m_customGattcHandler; + static gatts_event_handler m_customGattsHandler; + +}; // class BLE + +#endif // CONFIG_BT_ENABLED +#endif /* MAIN_BLEDevice_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.cpp b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.cpp new file mode 100644 index 0000000..a92bcdb --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.cpp @@ -0,0 +1,150 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include "BLEEddystoneTLM.h" + +static const char LOG_TAG[] = "BLEEddystoneTLM"; +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) +#define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24)) + +BLEEddystoneTLM::BLEEddystoneTLM() { + beaconUUID = 0xFEAA; + m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; + m_eddystoneData.version = 0; + m_eddystoneData.volt = 3300; // 3300mV = 3.3V + m_eddystoneData.temp = (uint16_t) ((float) 23.00); + m_eddystoneData.advCount = 0; + m_eddystoneData.tmil = 0; +} // BLEEddystoneTLM + +std::string BLEEddystoneTLM::getData() { + return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneTLM::getUUID() { + return BLEUUID(beaconUUID); +} // getUUID + +uint8_t BLEEddystoneTLM::getVersion() { + return m_eddystoneData.version; +} // getVersion + +uint16_t BLEEddystoneTLM::getVolt() { + return m_eddystoneData.volt; +} // getVolt + +float BLEEddystoneTLM::getTemp() { + return (float)m_eddystoneData.temp; +} // getTemp + +uint32_t BLEEddystoneTLM::getCount() { + return m_eddystoneData.advCount; +} // getCount + +uint32_t BLEEddystoneTLM::getTime() { + return m_eddystoneData.tmil; +} // getTime + +std::string BLEEddystoneTLM::toString() { + std::stringstream ss; + std::string out = ""; + uint32_t rawsec; + ss << "Version "; + ss << std::dec << m_eddystoneData.version; + ss << "\n"; + + ss << "Battery Voltage "; + ss << std::dec << ENDIAN_CHANGE_U16(m_eddystoneData.volt); + ss << " mV\n"; + + ss << "Temperature "; + ss << (float) m_eddystoneData.temp; + ss << " °C\n"; + + ss << "Adv. Count "; + ss << std::dec << ENDIAN_CHANGE_U32(m_eddystoneData.advCount); + + ss << "\n"; + + ss << "Time "; + + rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); + std::stringstream buffstream; + buffstream << "0000"; + buffstream << std::dec << rawsec / 864000; + std::string buff = buffstream.str(); + + ss << buff.substr(buff.length() - 4, buff.length()); + ss << "."; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec / 36000) % 24; + buff = buffstream.str(); + ss << buff.substr(buff.length()-2, buff.length()); + ss << ":"; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec / 600) % 60; + buff = buffstream.str(); + ss << buff.substr(buff.length() - 2, buff.length()); + ss << ":"; + + buffstream.str(""); + buffstream.clear(); + buffstream << "00"; + buffstream << std::dec << (rawsec / 10) % 60; + buff = buffstream.str(); + ss << buff.substr(buff.length() - 2, buff.length()); + ss << "\n"; + + return ss.str(); +} // toString + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneTLM::setData(std::string data) { + if (data.length() != sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memcpy(&m_eddystoneData, data.data(), data.length()); +} // setData + +void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) { + beaconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneTLM::setVersion(uint8_t version) { + m_eddystoneData.version = version; +} // setVersion + +void BLEEddystoneTLM::setVolt(uint16_t volt) { + m_eddystoneData.volt = volt; +} // setVolt + +void BLEEddystoneTLM::setTemp(float temp) { + m_eddystoneData.temp = (uint16_t)temp; +} // setTemp + +void BLEEddystoneTLM::setCount(uint32_t advCount) { + m_eddystoneData.advCount = advCount; +} // setCount + +void BLEEddystoneTLM::setTime(uint32_t tmil) { + m_eddystoneData.tmil = tmil; +} // setTime + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.h b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.h new file mode 100644 index 0000000..a93e224 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneTLM.h @@ -0,0 +1,51 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneTLM_H_ +#define _BLEEddystoneTLM_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_TLM_FRAME_TYPE 0x20 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneTLM { +public: + BLEEddystoneTLM(); + std::string getData(); + BLEUUID getUUID(); + uint8_t getVersion(); + uint16_t getVolt(); + float getTemp(); + uint32_t getCount(); + uint32_t getTime(); + std::string toString(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setVersion(uint8_t version); + void setVolt(uint16_t volt); + void setTemp(float temp); + void setCount(uint32_t advCount); + void setTime(uint32_t tmil); + +private: + uint16_t beaconUUID; + struct { + uint8_t frameType; + uint8_t version; + uint16_t volt; + uint16_t temp; + uint32_t advCount; + uint32_t tmil; + } __attribute__((packed)) m_eddystoneData; + +}; // BLEEddystoneTLM + +#endif /* _BLEEddystoneTLM_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.cpp b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.cpp new file mode 100644 index 0000000..af3b674 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.cpp @@ -0,0 +1,148 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEEddystoneURL.h" + +static const char LOG_TAG[] = "BLEEddystoneURL"; + +BLEEddystoneURL::BLEEddystoneURL() { + beaconUUID = 0xFEAA; + lengthURL = 0; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); +} // BLEEddystoneURL + +std::string BLEEddystoneURL::getData() { + return std::string((char*) &m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneURL::getUUID() { + return BLEUUID(beaconUUID); +} // getUUID + +int8_t BLEEddystoneURL::getPower() { + return m_eddystoneData.advertisedTxPower; +} // getPower + +std::string BLEEddystoneURL::getURL() { + return std::string((char*) &m_eddystoneData.url, sizeof(m_eddystoneData.url)); +} // getURL + +std::string BLEEddystoneURL::getDecodedURL() { + std::string decodedURL = ""; + + switch (m_eddystoneData.url[0]) { + case 0x00: + decodedURL += "http://www."; + break; + case 0x01: + decodedURL += "https://www."; + break; + case 0x02: + decodedURL += "http://"; + break; + case 0x03: + decodedURL += "https://"; + break; + default: + decodedURL += m_eddystoneData.url[0]; + } + + for (int i = 1; i < lengthURL; i++) { + if (m_eddystoneData.url[i] > 33 && m_eddystoneData.url[i] < 127) { + decodedURL += m_eddystoneData.url[i]; + } else { + switch (m_eddystoneData.url[i]) { + case 0x00: + decodedURL += ".com/"; + break; + case 0x01: + decodedURL += ".org/"; + break; + case 0x02: + decodedURL += ".edu/"; + break; + case 0x03: + decodedURL += ".net/"; + break; + case 0x04: + decodedURL += ".info/"; + break; + case 0x05: + decodedURL += ".biz/"; + break; + case 0x06: + decodedURL += ".gov/"; + break; + case 0x07: + decodedURL += ".com"; + break; + case 0x08: + decodedURL += ".org"; + break; + case 0x09: + decodedURL += ".edu"; + break; + case 0x0A: + decodedURL += ".net"; + break; + case 0x0B: + decodedURL += ".info"; + break; + case 0x0C: + decodedURL += ".biz"; + break; + case 0x0D: + decodedURL += ".gov"; + break; + default: + break; + } + } + } + return decodedURL; +} // getDecodedURL + + + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneURL::setData(std::string data) { + if (data.length() > sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memset(&m_eddystoneData, 0, sizeof(m_eddystoneData)); + memcpy(&m_eddystoneData, data.data(), data.length()); + lengthURL = data.length() - (sizeof(m_eddystoneData) - sizeof(m_eddystoneData.url)); +} // setData + +void BLEEddystoneURL::setUUID(BLEUUID l_uuid) { + beaconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { + m_eddystoneData.advertisedTxPower = advertisedTxPower; +} // setPower + +void BLEEddystoneURL::setURL(std::string url) { + if (url.length() > sizeof(m_eddystoneData.url)) { + ESP_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); + return; + } + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + memcpy(m_eddystoneData.url, url.data(), url.length()); + lengthURL = url.length(); +} // setURL + + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.h b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.h new file mode 100644 index 0000000..0b538c0 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEEddystoneURL.h @@ -0,0 +1,43 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneURL_H_ +#define _BLEEddystoneURL_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_URL_FRAME_TYPE 0x10 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneURL { +public: + BLEEddystoneURL(); + std::string getData(); + BLEUUID getUUID(); + int8_t getPower(); + std::string getURL(); + std::string getDecodedURL(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setPower(int8_t advertisedTxPower); + void setURL(std::string url); + +private: + uint16_t beaconUUID; + uint8_t lengthURL; + struct { + uint8_t frameType; + int8_t advertisedTxPower; + uint8_t url[16]; + } __attribute__((packed)) m_eddystoneData; + +}; // BLEEddystoneURL + +#endif /* _BLEEddystoneURL_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEExceptions.cpp b/libraries/ESP32_BLE_Arduino/src/BLEExceptions.cpp new file mode 100644 index 0000000..b6adfd8 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEExceptions.cpp @@ -0,0 +1,9 @@ +/* + * BLExceptions.cpp + * + * Created on: Nov 27, 2017 + * Author: kolban + */ + +#include "BLEExceptions.h" + diff --git a/libraries/ESP32_BLE_Arduino/src/BLEExceptions.h b/libraries/ESP32_BLE_Arduino/src/BLEExceptions.h new file mode 100644 index 0000000..ea9db85 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEExceptions.h @@ -0,0 +1,31 @@ +/* + * BLExceptions.h + * + * Created on: Nov 27, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ +#define COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ +#include "sdkconfig.h" + +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + +#include + + +class BLEDisconnectedException : public std::exception { + const char* what() const throw () { + return "BLE Disconnected"; + } +}; + +class BLEUuidNotFoundException : public std::exception { + const char* what() const throw () { + return "No such UUID"; + } +}; + +#endif /* COMPONENTS_CPP_UTILS_BLEEXCEPTIONS_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.cpp b/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.cpp new file mode 100644 index 0000000..69e18be --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.cpp @@ -0,0 +1,243 @@ +/* + * BLEHIDDevice.cpp + * + * Created on: Jan 03, 2018 + * Author: chegewara + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLEHIDDevice.h" +#include "BLE2904.h" + + +BLEHIDDevice::BLEHIDDevice(BLEServer* server) { + /* + * Here we create mandatory services described in bluetooth specification + */ + m_deviceInfoService = server->createService(BLEUUID((uint16_t) 0x180a)); + m_hidService = server->createService(BLEUUID((uint16_t) 0x1812), 40); + m_batteryService = server->createService(BLEUUID((uint16_t) 0x180f)); + + /* + * Mandatory characteristic for device info service + */ + m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a50, BLECharacteristic::PROPERTY_READ); + + /* + * Mandatory characteristics for HID service + */ + m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4a, BLECharacteristic::PROPERTY_READ); + m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4b, BLECharacteristic::PROPERTY_READ); + m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4c, BLECharacteristic::PROPERTY_WRITE_NR); + m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4e, BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_READ); + + /* + * Mandatory battery level characteristic with notification and presence descriptor + */ + BLE2904* batteryLevelDescriptor = new BLE2904(); + batteryLevelDescriptor->setFormat(BLE2904::FORMAT_UINT8); + batteryLevelDescriptor->setNamespace(1); + batteryLevelDescriptor->setUnit(0x27ad); + + m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t) 0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + m_batteryLevelCharacteristic->addDescriptor(batteryLevelDescriptor); + m_batteryLevelCharacteristic->addDescriptor(new BLE2902()); + + /* + * This value is setup here because its default value in most usage cases, its very rare to use boot mode + * and we want to simplify library using as much as possible + */ + const uint8_t pMode[] = { 0x01 }; + protocolMode()->setValue((uint8_t*) pMode, 1); +} + +BLEHIDDevice::~BLEHIDDevice() { +} + +/* + * @brief + */ +void BLEHIDDevice::reportMap(uint8_t* map, uint16_t size) { + m_reportMapCharacteristic->setValue(map, size); +} + +/* + * @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service + */ +void BLEHIDDevice::startServices() { + m_deviceInfoService->start(); + m_hidService->start(); + m_batteryService->start(); +} + +/* + * @brief Create manufacturer characteristic (this characteristic is optional) + */ +BLECharacteristic* BLEHIDDevice::manufacturer() { + m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); + return m_manufacturerCharacteristic; +} + +/* + * @brief Set manufacturer name + * @param [in] name manufacturer name + */ +void BLEHIDDevice::manufacturer(std::string name) { + m_manufacturerCharacteristic->setValue(name); +} + +/* + * @brief + */ +void BLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; + m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); +} + +/* + * @brief + */ +void BLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { + uint8_t info[] = { 0x11, 0x1, country, flags }; + m_hidInfoCharacteristic->setValue(info, sizeof(info)); +} + +/* + * @brief Create input report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID input report ID, the same as in report map for input object related to created characteristic + * @return pointer to new input report characteristic + */ +BLECharacteristic* BLEHIDDevice::inputReport(uint8_t reportID) { + BLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor* inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); + BLE2902* p2902 = new BLE2902(); + inputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + inputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + p2902->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + uint8_t desc1_val[] = { reportID, 0x01 }; + inputReportDescriptor->setValue((uint8_t*) desc1_val, 2); + inputReportCharacteristic->addDescriptor(p2902); + inputReportCharacteristic->addDescriptor(inputReportDescriptor); + + return inputReportCharacteristic; +} + +/* + * @brief Create output report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic + * @return Pointer to new output report characteristic + */ +BLECharacteristic* BLEHIDDevice::outputReport(uint8_t reportID) { + BLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); + BLEDescriptor* outputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); + outputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + outputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + uint8_t desc1_val[] = { reportID, 0x02 }; + outputReportDescriptor->setValue((uint8_t*) desc1_val, 2); + outputReportCharacteristic->addDescriptor(outputReportDescriptor); + + return outputReportCharacteristic; +} + +/* + * @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used + * @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic + * @return Pointer to new feature report characteristic + */ +BLECharacteristic* BLEHIDDevice::featureReport(uint8_t reportID) { + BLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE); + BLEDescriptor* featureReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t) 0x2908)); + + featureReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + featureReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + uint8_t desc1_val[] = { reportID, 0x03 }; + featureReportDescriptor->setValue((uint8_t*) desc1_val, 2); + featureReportCharacteristic->addDescriptor(featureReportDescriptor); + + return featureReportCharacteristic; +} + +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::bootInput() { + BLECharacteristic* bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a22, BLECharacteristic::PROPERTY_NOTIFY); + bootInputCharacteristic->addDescriptor(new BLE2902()); + + return bootInputCharacteristic; +} + +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::bootOutput() { + return m_hidService->createCharacteristic((uint16_t) 0x2a32, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR); +} + +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::hidControl() { + return m_hidControlCharacteristic; +} + +/* + * @brief + */ +BLECharacteristic* BLEHIDDevice::protocolMode() { + return m_protocolModeCharacteristic; +} + +void BLEHIDDevice::setBatteryLevel(uint8_t level) { + m_batteryLevelCharacteristic->setValue(&level, 1); +} +/* + * @brief Returns battery level characteristic + * @ return battery level characteristic + *//* +BLECharacteristic* BLEHIDDevice::batteryLevel() { + return m_batteryLevelCharacteristic; +} + + + +BLECharacteristic* BLEHIDDevice::reportMap() { + return m_reportMapCharacteristic; +} + +BLECharacteristic* BLEHIDDevice::pnp() { + return m_pnpCharacteristic; +} + + +BLECharacteristic* BLEHIDDevice::hidInfo() { + return m_hidInfoCharacteristic; +} +*/ +/* + * @brief + */ +BLEService* BLEHIDDevice::deviceInfo() { + return m_deviceInfoService; +} + +/* + * @brief + */ +BLEService* BLEHIDDevice::hidService() { + return m_hidService; +} + +/* + * @brief + */ +BLEService* BLEHIDDevice::batteryService() { + return m_batteryService; +} + +#endif // CONFIG_BT_ENABLED + diff --git a/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.h b/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.h new file mode 100644 index 0000000..33e6b46 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEHIDDevice.h @@ -0,0 +1,75 @@ +/* + * BLEHIDDevice.h + * + * Created on: Jan 03, 2018 + * Author: chegewara + */ + +#ifndef _BLEHIDDEVICE_H_ +#define _BLEHIDDEVICE_H_ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include "BLECharacteristic.h" +#include "BLEService.h" +#include "BLEDescriptor.h" +#include "BLE2902.h" +#include "HIDTypes.h" + +#define GENERIC_HID 0x03C0 +#define HID_KEYBOARD 0x03C1 +#define HID_MOUSE 0x03C2 +#define HID_JOYSTICK 0x03C3 +#define HID_GAMEPAD 0x03C4 +#define HID_TABLET 0x03C5 +#define HID_CARD_READER 0x03C6 +#define HID_DIGITAL_PEN 0x03C7 +#define HID_BARCODE 0x03C8 + +class BLEHIDDevice { +public: + BLEHIDDevice(BLEServer*); + virtual ~BLEHIDDevice(); + + void reportMap(uint8_t* map, uint16_t); + void startServices(); + + BLEService* deviceInfo(); + BLEService* hidService(); + BLEService* batteryService(); + + BLECharacteristic* manufacturer(); + void manufacturer(std::string name); + //BLECharacteristic* pnp(); + void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version); + //BLECharacteristic* hidInfo(); + void hidInfo(uint8_t country, uint8_t flags); + //BLECharacteristic* batteryLevel(); + void setBatteryLevel(uint8_t level); + + + //BLECharacteristic* reportMap(); + BLECharacteristic* hidControl(); + BLECharacteristic* inputReport(uint8_t reportID); + BLECharacteristic* outputReport(uint8_t reportID); + BLECharacteristic* featureReport(uint8_t reportID); + BLECharacteristic* protocolMode(); + BLECharacteristic* bootInput(); + BLECharacteristic* bootOutput(); + +private: + BLEService* m_deviceInfoService; //0x180a + BLEService* m_hidService; //0x1812 + BLEService* m_batteryService = 0; //0x180f + + BLECharacteristic* m_manufacturerCharacteristic; //0x2a29 + BLECharacteristic* m_pnpCharacteristic; //0x2a50 + BLECharacteristic* m_hidInfoCharacteristic; //0x2a4a + BLECharacteristic* m_reportMapCharacteristic; //0x2a4b + BLECharacteristic* m_hidControlCharacteristic; //0x2a4c + BLECharacteristic* m_protocolModeCharacteristic; //0x2a4e + BLECharacteristic* m_batteryLevelCharacteristic; //0x2a19 +}; +#endif // CONFIG_BT_ENABLED +#endif /* _BLEHIDDEVICE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp b/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp new file mode 100644 index 0000000..b6d36d8 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp @@ -0,0 +1,588 @@ +/* + * BLERemoteCharacteristic.cpp + * + * Created on: Jul 8, 2017 + * Author: kolban + */ + +#include "BLERemoteCharacteristic.h" + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include +#include + +#include +#include "BLEExceptions.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#include "BLERemoteDescriptor.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteCharacteristic"; // The logging tag for this class. +#endif + + + +/** + * @brief Constructor. + * @param [in] handle The BLE server side handle of this characteristic. + * @param [in] uuid The UUID of this characteristic. + * @param [in] charProp The properties of this characteristic. + * @param [in] pRemoteService A reference to the remote service to which this remote characteristic pertains. + */ +BLERemoteCharacteristic::BLERemoteCharacteristic( + uint16_t handle, + BLEUUID uuid, + esp_gatt_char_prop_t charProp, + BLERemoteService* pRemoteService) { + ESP_LOGD(LOG_TAG, ">> BLERemoteCharacteristic: handle: %d 0x%d, uuid: %s", handle, handle, uuid.toString().c_str()); + m_handle = handle; + m_uuid = uuid; + m_charProp = charProp; + m_pRemoteService = pRemoteService; + m_notifyCallback = nullptr; + + retrieveDescriptors(); // Get the descriptors for this characteristic + ESP_LOGD(LOG_TAG, "<< BLERemoteCharacteristic"); +} // BLERemoteCharacteristic + + +/** + *@brief Destructor. + */ +BLERemoteCharacteristic::~BLERemoteCharacteristic() { + removeDescriptors(); // Release resources for any descriptor information we may have allocated. +} // ~BLERemoteCharacteristic + + +/** + * @brief Does the characteristic support broadcasting? + * @return True if the characteristic supports broadcasting. + */ +bool BLERemoteCharacteristic::canBroadcast() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_BROADCAST) != 0; +} // canBroadcast + + +/** + * @brief Does the characteristic support indications? + * @return True if the characteristic supports indications. + */ +bool BLERemoteCharacteristic::canIndicate() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_INDICATE) != 0; +} // canIndicate + + +/** + * @brief Does the characteristic support notifications? + * @return True if the characteristic supports notifications. + */ +bool BLERemoteCharacteristic::canNotify() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0; +} // canNotify + + +/** + * @brief Does the characteristic support reading? + * @return True if the characteristic supports reading. + */ +bool BLERemoteCharacteristic::canRead() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_READ) != 0; +} // canRead + + +/** + * @brief Does the characteristic support writing? + * @return True if the characteristic supports writing. + */ +bool BLERemoteCharacteristic::canWrite() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE) != 0; +} // canWrite + + +/** + * @brief Does the characteristic support writing with no response? + * @return True if the characteristic supports writing with no response. + */ +bool BLERemoteCharacteristic::canWriteNoResponse() { + return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) != 0; +} // canWriteNoResponse + + +/* +static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { + if (id1.id.inst_id != id2.id.inst_id) { + return false; + } + if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) { + return false; + } + return true; +} // compareSrvcId +*/ + +/* +static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { + if (id1.inst_id != id2.inst_id) { + return false; + } + if (!BLEUUID(id1.uuid).equals(BLEUUID(id2.uuid))) { + return false; + } + return true; +} // compareCharId +*/ + + +/** + * @brief Handle GATT Client events. + * When an event arrives for a GATT client we give this characteristic the opportunity to + * take a look at it to see if there is interest in it. + * @param [in] event The type of event. + * @param [in] gattc_if The interface on which the event was received. + * @param [in] evtParam Payload data for the event. + * @returns N/A + */ +void BLERemoteCharacteristic::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam) { + switch(event) { + // ESP_GATTC_NOTIFY_EVT + // + // notify + // - uint16_t conn_id - The connection identifier of the server. + // - esp_bd_addr_t remote_bda - The device address of the BLE server. + // - uint16_t handle - The handle of the characteristic for which the event is being received. + // - uint16_t value_len - The length of the received data. + // - uint8_t* value - The received data. + // - bool is_notify - True if this is a notify, false if it is an indicate. + // + // We have received a notification event which means that the server wishes us to know about a notification + // piece of data. What we must now do is find the characteristic with the associated handle and then + // invoke its notification callback (if it has one). + case ESP_GATTC_NOTIFY_EVT: { + if (evtParam->notify.handle != getHandle()) break; + if (m_notifyCallback != nullptr) { + ESP_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", toString().c_str()); + m_notifyCallback(this, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); + } // End we have a callback function ... + break; + } // ESP_GATTC_NOTIFY_EVT + + // ESP_GATTC_READ_CHAR_EVT + // This event indicates that the server has responded to the read request. + // + // read: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // - uint8_t* value + // - uint16_t value_len + case ESP_GATTC_READ_CHAR_EVT: { + // If this event is not for us, then nothing further to do. + if (evtParam->read.handle != getHandle()) break; + + // At this point, we have determined that the event is for us, so now we save the value + // and unlock the semaphore to ensure that the requestor of the data can continue. + if (evtParam->read.status == ESP_GATT_OK) { + m_value = std::string((char*) evtParam->read.value, evtParam->read.value_len); + if(m_rawData != nullptr) free(m_rawData); + m_rawData = (uint8_t*) calloc(evtParam->read.value_len, sizeof(uint8_t)); + memcpy(m_rawData, evtParam->read.value, evtParam->read.value_len); + } else { + m_value = ""; + } + + m_semaphoreReadCharEvt.give(); + break; + } // ESP_GATTC_READ_CHAR_EVT + + // ESP_GATTC_REG_FOR_NOTIFY_EVT + // + // reg_for_notify: + // - esp_gatt_status_t status + // - uint16_t handle + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + // If the request is not for this BLERemoteCharacteristic then move on to the next. + if (evtParam->reg_for_notify.handle != getHandle()) break; + + // We have processed the notify registration and can unlock the semaphore. + m_semaphoreRegForNotifyEvt.give(); + break; + } // ESP_GATTC_REG_FOR_NOTIFY_EVT + + // ESP_GATTC_UNREG_FOR_NOTIFY_EVT + // + // unreg_for_notify: + // - esp_gatt_status_t status + // - uint16_t handle + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { + if (evtParam->unreg_for_notify.handle != getHandle()) break; + // We have processed the notify un-registration and can unlock the semaphore. + m_semaphoreRegForNotifyEvt.give(); + break; + } // ESP_GATTC_UNREG_FOR_NOTIFY_EVT: + + // ESP_GATTC_WRITE_CHAR_EVT + // + // write: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + case ESP_GATTC_WRITE_CHAR_EVT: { + // Determine if this event is for us and, if not, pass onwards. + if (evtParam->write.handle != getHandle()) break; + + // There is nothing further we need to do here. This is merely an indication + // that the write has completed and we can unlock the caller. + m_semaphoreWriteCharEvt.give(); + break; + } // ESP_GATTC_WRITE_CHAR_EVT + + + default: + break; + } // End switch +}; // gattClientEventHandler + + +/** + * @brief Populate the descriptors (if any) for this characteristic. + */ +void BLERemoteCharacteristic::retrieveDescriptors() { + ESP_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); + + removeDescriptors(); // Remove any existing descriptors. + + // Loop over each of the descriptors within the service associated with this characteristic. + // For each descriptor we find, create a BLERemoteDescriptor instance. + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + while(true) { + uint16_t count = 10; + esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( + getRemoteService()->getClient()->getGattcIf(), + getRemoteService()->getClient()->getConnId(), + getHandle(), + &result, + &count, + offset + ); + + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_descr: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + + if (count == 0) break; + + ESP_LOGD(LOG_TAG, "Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteDescriptor* pNewRemoteDescriptor = new BLERemoteDescriptor( + result.handle, + BLEUUID(result.uuid), + this + ); + + m_descriptorMap.insert(std::pair(pNewRemoteDescriptor->getUUID().toString(), pNewRemoteDescriptor)); + + offset++; + } // while true + //m_haveCharacteristics = true; // Remember that we have received the characteristics. + ESP_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", offset); +} // getDescriptors + + +/** + * @brief Retrieve the map of descriptors keyed by UUID. + */ +std::map* BLERemoteCharacteristic::getDescriptors() { + return &m_descriptorMap; +} // getDescriptors + + +/** + * @brief Get the handle for this characteristic. + * @return The handle for this characteristic. + */ +uint16_t BLERemoteCharacteristic::getHandle() { + //ESP_LOGD(LOG_TAG, ">> getHandle: Characteristic: %s", getUUID().toString().c_str()); + //ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", m_handle, m_handle); + return m_handle; +} // getHandle + + +/** + * @brief Get the descriptor instance with the given UUID that belongs to this characteristic. + * @param [in] uuid The UUID of the descriptor to find. + * @return The Remote descriptor (if present) or null if not present. + */ +BLERemoteDescriptor* BLERemoteCharacteristic::getDescriptor(BLEUUID uuid) { + ESP_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str()); + std::string v = uuid.toString(); + for (auto &myPair : m_descriptorMap) { + if (myPair.first == v) { + ESP_LOGD(LOG_TAG, "<< getDescriptor: found"); + return myPair.second; + } + } + ESP_LOGD(LOG_TAG, "<< getDescriptor: Not found"); + return nullptr; +} // getDescriptor + + +/** + * @brief Get the remote service associated with this characteristic. + * @return The remote service associated with this characteristic. + */ +BLERemoteService* BLERemoteCharacteristic::getRemoteService() { + return m_pRemoteService; +} // getRemoteService + + +/** + * @brief Get the UUID for this characteristic. + * @return The UUID for this characteristic. + */ +BLEUUID BLERemoteCharacteristic::getUUID() { + return m_uuid; +} // getUUID + + +/** + * @brief Read an unsigned 16 bit value + * @return The unsigned 16 bit value. + */ +uint16_t BLERemoteCharacteristic::readUInt16() { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*)(value.data()); + } + return 0; +} // readUInt16 + + +/** + * @brief Read an unsigned 32 bit value. + * @return the unsigned 32 bit value. + */ +uint32_t BLERemoteCharacteristic::readUInt32() { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*)(value.data()); + } + return 0; +} // readUInt32 + + +/** + * @brief Read a byte value + * @return The value as a byte + */ +uint8_t BLERemoteCharacteristic::readUInt8() { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } + return 0; +} // readUInt8 + + +/** + * @brief Read the value of the remote characteristic. + * @return The value of the remote characteristic. + */ +std::string BLERemoteCharacteristic::readValue() { + ESP_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); + + // Check to see that we are connected. + if (!getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + m_semaphoreReadCharEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + // This is an asynchronous request which means that we must block waiting for the response + // to become available. + esp_err_t errRc = ::esp_ble_gattc_read_char( + m_pRemoteService->getClient()->getGattcIf(), + m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return ""; + } + + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadCharEvt.wait("readValue"); + + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); + return m_value; +} // readValue + + +/** + * @brief Register for notifications. + * @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we are + * unregistering a notification. + * @return N/A. + */ +void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, bool notifications) { + ESP_LOGD(LOG_TAG, ">> registerForNotify(): %s", toString().c_str()); + + m_notifyCallback = notifyCallback; // Save the notification callback. + + m_semaphoreRegForNotifyEvt.take("registerForNotify"); + + if (notifyCallback != nullptr) { // If we have a callback function, then this is a registration. + esp_err_t errRc = ::esp_ble_gattc_register_for_notify( + m_pRemoteService->getClient()->getGattcIf(), + *m_pRemoteService->getClient()->getPeerAddress().getNative(), + getHandle() + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + uint8_t val[] = {0x01, 0x00}; + if(!notifications) val[0] = 0x02; + BLERemoteDescriptor* desc = getDescriptor(BLEUUID((uint16_t)0x2902)); + desc->writeValue(val, 2); + } // End Register + else { // If we weren't passed a callback function, then this is an unregistration. + esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( + m_pRemoteService->getClient()->getGattcIf(), + *m_pRemoteService->getClient()->getPeerAddress().getNative(), + getHandle() + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_unregister_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + uint8_t val[] = {0x00, 0x00}; + BLERemoteDescriptor* desc = getDescriptor((uint16_t)0x2902); + desc->writeValue(val, 2); + } // End Unregister + + m_semaphoreRegForNotifyEvt.wait("registerForNotify"); + + ESP_LOGD(LOG_TAG, "<< registerForNotify()"); +} // registerForNotify + + +/** + * @brief Delete the descriptors in the descriptor map. + * We maintain a map called m_descriptorMap that contains pointers to BLERemoteDescriptors + * object references. Since we allocated these in this class, we are also responsible for deleteing + * them. This method does just that. + * @return N/A. + */ +void BLERemoteCharacteristic::removeDescriptors() { + // Iterate through all the descriptors releasing their storage and erasing them from the map. + for (auto &myPair : m_descriptorMap) { + m_descriptorMap.erase(myPair.first); + delete myPair.second; + } + m_descriptorMap.clear(); // Technically not neeeded, but just to be sure. +} // removeCharacteristics + + +/** + * @brief Convert a BLERemoteCharacteristic to a string representation; + * @return a String representation. + */ +std::string BLERemoteCharacteristic::toString() { + std::ostringstream ss; + ss << "Characteristic: uuid: " << m_uuid.toString() << + ", handle: " << getHandle() << " 0x" << std::hex << getHandle() << + ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp); + return ss.str(); +} // toString + + +/** + * @brief Write the new value for the characteristic. + * @param [in] newValue The new value to write. + * @param [in] response Do we expect a response? + * @return N/A. + */ +void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { + writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response); +} // writeValue + + +/** + * @brief Write the new value for the characteristic. + * + * This is a convenience function. Many BLE characteristics are a single byte of data. + * @param [in] newValue The new byte value to write. + * @param [in] response Whether we require a response from the write. + * @return N/A. + */ +void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { + writeValue(&newValue, 1, response); +} // writeValue + + +/** + * @brief Write the new value for the characteristic from a data buffer. + * @param [in] data A pointer to a data buffer. + * @param [in] length The length of the data in the data buffer. + * @param [in] response Whether we require a response from the write. + */ +void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) { + // writeValue(std::string((char*)data, length), response); + ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", length); + + // Check to see that we are connected. + if (!getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + m_semaphoreWriteCharEvt.take("writeValue"); + // Invoke the ESP-IDF API to perform the write. + esp_err_t errRc = ::esp_ble_gattc_write_char( + m_pRemoteService->getClient()->getGattcIf(), + m_pRemoteService->getClient()->getConnId(), + getHandle(), + length, + data, + response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE + ); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreWriteCharEvt.wait("writeValue"); + + ESP_LOGD(LOG_TAG, "<< writeValue"); +} // writeValue + +/** + * @brief Read raw data from remote characteristic as hex bytes + * @return return pointer data read + */ +uint8_t* BLERemoteCharacteristic::readRawData() { + return m_rawData; +} + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h b/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h new file mode 100644 index 0000000..fbcafe8 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h @@ -0,0 +1,84 @@ +/* + * BLERemoteCharacteristic.h + * + * Created on: Jul 8, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ +#define COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +#include + +#include "BLERemoteService.h" +#include "BLERemoteDescriptor.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLERemoteService; +class BLERemoteDescriptor; +typedef void (*notify_callback)(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); + +/** + * @brief A model of a remote %BLE characteristic. + */ +class BLERemoteCharacteristic { +public: + ~BLERemoteCharacteristic(); + + // Public member functions + bool canBroadcast(); + bool canIndicate(); + bool canNotify(); + bool canRead(); + bool canWrite(); + bool canWriteNoResponse(); + BLERemoteDescriptor* getDescriptor(BLEUUID uuid); + std::map* getDescriptors(); + uint16_t getHandle(); + BLEUUID getUUID(); + std::string readValue(); + uint8_t readUInt8(); + uint16_t readUInt16(); + uint32_t readUInt32(); + void registerForNotify(notify_callback _callback, bool notifications = true); + void writeValue(uint8_t* data, size_t length, bool response = false); + void writeValue(std::string newValue, bool response = false); + void writeValue(uint8_t newValue, bool response = false); + std::string toString(); + uint8_t* readRawData(); + +private: + BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); + friend class BLEClient; + friend class BLERemoteService; + friend class BLERemoteDescriptor; + + // Private member functions + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); + + BLERemoteService* getRemoteService(); + void removeDescriptors(); + void retrieveDescriptors(); + + // Private properties + BLEUUID m_uuid; + esp_gatt_char_prop_t m_charProp; + uint16_t m_handle; + BLERemoteService* m_pRemoteService; + FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); + FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt"); + FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt"); + std::string m_value; + uint8_t *m_rawData; + notify_callback m_notifyCallback; + + // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. + std::map m_descriptorMap; +}; // BLERemoteCharacteristic +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp b/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp new file mode 100644 index 0000000..96a8a57 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp @@ -0,0 +1,181 @@ +/* + * BLERemoteDescriptor.cpp + * + * Created on: Jul 8, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include "BLERemoteDescriptor.h" +#include "GeneralUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteDescriptor"; +#endif + + + + +BLERemoteDescriptor::BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic) { + + m_handle = handle; + m_uuid = uuid; + m_pRemoteCharacteristic = pRemoteCharacteristic; +} + + +/** + * @brief Retrieve the handle associated with this remote descriptor. + * @return The handle associated with this remote descriptor. + */ +uint16_t BLERemoteDescriptor::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Get the characteristic that owns this descriptor. + * @return The characteristic that owns this descriptor. + */ +BLERemoteCharacteristic* BLERemoteDescriptor::getRemoteCharacteristic() { + return m_pRemoteCharacteristic; +} // getRemoteCharacteristic + + +/** + * @brief Retrieve the UUID associated this remote descriptor. + * @return The UUID associated this remote descriptor. + */ +BLEUUID BLERemoteDescriptor::getUUID() { + return m_uuid; +} // getUUID + + +std::string BLERemoteDescriptor::readValue() { + ESP_LOGD(LOG_TAG, ">> readValue: %s", toString().c_str()); + + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + m_semaphoreReadDescrEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + esp_err_t errRc = ::esp_ble_gattc_read_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + ESP_GATT_AUTH_REQ_NONE); // Security + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return ""; + } + + // Block waiting for the event that indicates that the read has completed. When it has, the std::string found + // in m_value will contain our data. + m_semaphoreReadDescrEvt.wait("readValue"); + + ESP_LOGD(LOG_TAG, "<< readValue(): length: %d", m_value.length()); + return m_value; +} // readValue + + +uint8_t BLERemoteDescriptor::readUInt8() { + std::string value = readValue(); + if (value.length() >= 1) { + return (uint8_t) value[0]; + } + return 0; +} // readUInt8 + + +uint16_t BLERemoteDescriptor::readUInt16() { + std::string value = readValue(); + if (value.length() >= 2) { + return *(uint16_t*) value.data(); + } + return 0; +} // readUInt16 + + +uint32_t BLERemoteDescriptor::readUInt32() { + std::string value = readValue(); + if (value.length() >= 4) { + return *(uint32_t*) value.data(); + } + return 0; +} // readUInt32 + + +/** + * @brief Return a string representation of this BLE Remote Descriptor. + * @retun A string representation of this BLE Remote Descriptor. + */ +std::string BLERemoteDescriptor::toString() { + std::stringstream ss; + ss << "handle: " << getHandle() << ", uuid: " << getUUID().toString(); + return ss.str(); +} // toString + + +/** + * @brief Write data to the BLE Remote Descriptor. + * @param [in] data The data to send to the remote descriptor. + * @param [in] length The length of the data to send. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue(uint8_t* data, size_t length, bool response) { + ESP_LOGD(LOG_TAG, ">> writeValue: %s", toString().c_str()); + // Check to see that we are connected. + if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { + ESP_LOGE(LOG_TAG, "Disconnected"); + throw BLEDisconnectedException(); + } + + esp_err_t errRc = ::esp_ble_gattc_write_char_descr( + m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), + m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), + getHandle(), + length, // Data length + data, // Data + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char_descr: %d", errRc); + } + ESP_LOGD(LOG_TAG, "<< writeValue"); +} // writeValue + + +/** + * @brief Write data represented as a string to the BLE Remote Descriptor. + * @param [in] newValue The data to send to the remote descriptor. + * @param [in] response True if we expect a response. + */ +void BLERemoteDescriptor::writeValue(std::string newValue, bool response) { + writeValue((uint8_t*) newValue.data(), newValue.length(), response); +} // writeValue + + +/** + * @brief Write a byte value to the Descriptor. + * @param [in] The single byte to write. + * @param [in] True if we expect a response. + */ +void BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { + writeValue(&newValue, 1, response); +} // writeValue + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h b/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h new file mode 100644 index 0000000..7bbc48f --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h @@ -0,0 +1,55 @@ +/* + * BLERemoteDescriptor.h + * + * Created on: Jul 8, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ +#define COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +#include + +#include "BLERemoteCharacteristic.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLERemoteCharacteristic; +/** + * @brief A model of remote %BLE descriptor. + */ +class BLERemoteDescriptor { +public: + uint16_t getHandle(); + BLERemoteCharacteristic* getRemoteCharacteristic(); + BLEUUID getUUID(); + std::string readValue(void); + uint8_t readUInt8(void); + uint16_t readUInt16(void); + uint32_t readUInt32(void); + std::string toString(void); + void writeValue(uint8_t* data, size_t length, bool response = false); + void writeValue(std::string newValue, bool response = false); + void writeValue(uint8_t newValue, bool response = false); + + +private: + friend class BLERemoteCharacteristic; + BLERemoteDescriptor( + uint16_t handle, + BLEUUID uuid, + BLERemoteCharacteristic* pRemoteCharacteristic + ); + uint16_t m_handle; // Server handle of this descriptor. + BLEUUID m_uuid; // UUID of this descriptor. + std::string m_value; // Last received value of the descriptor. + BLERemoteCharacteristic* m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated. + FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt"); + + +}; +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteService.cpp b/libraries/ESP32_BLE_Arduino/src/BLERemoteService.cpp new file mode 100644 index 0000000..c2b7d34 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteService.cpp @@ -0,0 +1,340 @@ +/* + * BLERemoteService.cpp + * + * Created on: Jul 8, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include +#include "BLERemoteService.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#include +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLERemoteService"; +#endif + + + +BLERemoteService::BLERemoteService( + esp_gatt_id_t srvcId, + BLEClient* pClient, + uint16_t startHandle, + uint16_t endHandle + ) { + + ESP_LOGD(LOG_TAG, ">> BLERemoteService()"); + m_srvcId = srvcId; + m_pClient = pClient; + m_uuid = BLEUUID(m_srvcId); + m_haveCharacteristics = false; + m_startHandle = startHandle; + m_endHandle = endHandle; + + ESP_LOGD(LOG_TAG, "<< BLERemoteService()"); +} + + +BLERemoteService::~BLERemoteService() { + removeCharacteristics(); +} + +/* +static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { + if (id1.id.inst_id != id2.id.inst_id) { + return false; + } + if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) { + return false; + } + return true; +} // compareSrvcId +*/ + +/** + * @brief Handle GATT Client events + */ +void BLERemoteService::gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam) { + switch (event) { + // + // ESP_GATTC_GET_CHAR_EVT + // + // get_char: + // - esp_gatt_status_t status + // - uin1t6_t conn_id + // - esp_gatt_srvc_id_t srvc_id + // - esp_gatt_id_t char_id + // - esp_gatt_char_prop_t char_prop + // + /* + case ESP_GATTC_GET_CHAR_EVT: { + // Is this event for this service? If yes, then the local srvc_id and the event srvc_id will be + // the same. + if (compareSrvcId(m_srvcId, evtParam->get_char.srvc_id) == false) { + break; + } + + // If the status is NOT OK then we have a problem and continue. + if (evtParam->get_char.status != ESP_GATT_OK) { + m_semaphoreGetCharEvt.give(); + break; + } + + // This is an indication that we now have the characteristic details for a characteristic owned + // by this service so remember it. + m_characteristicMap.insert(std::pair( + BLEUUID(evtParam->get_char.char_id.uuid).toString(), + new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) )); + + + // Now that we have received a characteristic, lets ask for the next one. + esp_err_t errRc = ::esp_ble_gattc_get_characteristic( + m_pClient->getGattcIf(), + m_pClient->getConnId(), + &m_srvcId, + &evtParam->get_char.char_id); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + break; + } + + //m_semaphoreGetCharEvt.give(); + break; + } // ESP_GATTC_GET_CHAR_EVT +*/ + default: + break; + } // switch + + // Send the event to each of the characteristics owned by this service. + for (auto &myPair : m_characteristicMapByHandle) { + myPair.second->gattClientEventHandler(event, gattc_if, evtParam); + } +} // gattClientEventHandler + + +/** + * @brief Get the remote characteristic object for the characteristic UUID. + * @param [in] uuid Remote characteristic uuid. + * @return Reference to the remote characteristic object. + * @throws BLEUuidNotFoundException + */ +BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} // getCharacteristic + +/** + * @brief Get the characteristic object for the UUID. + * @param [in] uuid Characteristic uuid. + * @return Reference to the characteristic object. + * @throws BLEUuidNotFoundException + */ +BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { +// Design +// ------ +// We wish to retrieve the characteristic given its UUID. It is possible that we have not yet asked the +// device what characteristics it has in which case we have nothing to match against. If we have not +// asked the device about its characteristics, then we do that now. Once we get the results we can then +// examine the characteristics map to see if it has the characteristic we are looking for. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } + std::string v = uuid.toString(); + for (auto &myPair : m_characteristicMap) { + if (myPair.first == v) { + return myPair.second; + } + } + // throw new BLEUuidNotFoundException(); // <-- we dont want exception here, which will cause app crash, we want to search if any characteristic can be found one after another + return nullptr; +} // getCharacteristic + + +/** + * @brief Retrieve all the characteristics for this service. + * This function will not return until we have all the characteristics. + * @return N/A + */ +void BLERemoteService::retrieveCharacteristics() { + ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); + + removeCharacteristics(); // Forget any previous characteristics. + + uint16_t offset = 0; + esp_gattc_char_elem_t result; + while (true) { + uint16_t count = 10; // this value is used as in parameter that allows to search max 10 chars with the same uuid + esp_gatt_status_t status = ::esp_ble_gattc_get_all_char( + getClient()->getGattcIf(), + getClient()->getConnId(), + m_startHandle, + m_endHandle, + &result, + &count, + offset + ); + + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { // If we got an error, end. + ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + + if (count == 0) { // If we failed to get any new records, end. + break; + } + + ESP_LOGD(LOG_TAG, "Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteCharacteristic *pNewRemoteCharacteristic = new BLERemoteCharacteristic( + result.char_handle, + BLEUUID(result.uuid), + result.properties, + this + ); + + m_characteristicMap.insert(std::pair(pNewRemoteCharacteristic->getUUID().toString(), pNewRemoteCharacteristic)); + m_characteristicMapByHandle.insert(std::pair(result.char_handle, pNewRemoteCharacteristic)); + offset++; // Increment our count of number of descriptors found. + } // Loop forever (until we break inside the loop). + + m_haveCharacteristics = true; // Remember that we have received the characteristics. + ESP_LOGD(LOG_TAG, "<< getCharacteristics()"); +} // getCharacteristics + + +/** + * @brief Retrieve a map of all the characteristics of this service. + * @return A map of all the characteristics of this service. + */ +std::map* BLERemoteService::getCharacteristics() { + ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); + // If is possible that we have not read the characteristics associated with the service so do that + // now. The request to retrieve the characteristics by calling "retrieveCharacteristics" is a blocking + // call and does not return until all the characteristics are available. + if (!m_haveCharacteristics) { + retrieveCharacteristics(); + } + ESP_LOGD(LOG_TAG, "<< getCharacteristics() for service: %s", getUUID().toString().c_str()); + return &m_characteristicMap; +} // getCharacteristics + +/** + * @brief This function is designed to get characteristics map when we have multiple characteristics with the same UUID + */ +void BLERemoteService::getCharacteristics(std::map* pCharacteristicMap) { +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" + pCharacteristicMap = &m_characteristicMapByHandle; +} // Get the characteristics map. + +/** + * @brief Get the client associated with this service. + * @return A reference to the client associated with this service. + */ +BLEClient* BLERemoteService::getClient() { + return m_pClient; +} // getClient + + +uint16_t BLERemoteService::getEndHandle() { + return m_endHandle; +} // getEndHandle + + +esp_gatt_id_t* BLERemoteService::getSrvcId() { + return &m_srvcId; +} // getSrvcId + + +uint16_t BLERemoteService::getStartHandle() { + return m_startHandle; +} // getStartHandle + + +uint16_t BLERemoteService::getHandle() { + ESP_LOGD(LOG_TAG, ">> getHandle: service: %s", getUUID().toString().c_str()); + ESP_LOGD(LOG_TAG, "<< getHandle: %d 0x%.2x", getStartHandle(), getStartHandle()); + return getStartHandle(); +} // getHandle + + +BLEUUID BLERemoteService::getUUID() { + return m_uuid; +} + +/** + * @brief Read the value of a characteristic associated with this service. + */ +std::string BLERemoteService::getValue(BLEUUID characteristicUuid) { + ESP_LOGD(LOG_TAG, ">> readValue: uuid: %s", characteristicUuid.toString().c_str()); + std::string ret = getCharacteristic(characteristicUuid)->readValue(); + ESP_LOGD(LOG_TAG, "<< readValue"); + return ret; +} // readValue + + + +/** + * @brief Delete the characteristics in the characteristics map. + * We maintain a map called m_characteristicsMap that contains pointers to BLERemoteCharacteristic + * object references. Since we allocated these in this class, we are also responsible for deleteing + * them. This method does just that. + * @return N/A. + */ +void BLERemoteService::removeCharacteristics() { + for (auto &myPair : m_characteristicMap) { + delete myPair.second; + //m_characteristicMap.erase(myPair.first); // Should be no need to delete as it will be deleted by the clear + } + m_characteristicMap.clear(); // Clear the map + for (auto &myPair : m_characteristicMapByHandle) { + delete myPair.second; + } + m_characteristicMapByHandle.clear(); // Clear the map +} // removeCharacteristics + + +/** + * @brief Set the value of a characteristic. + * @param [in] characteristicUuid The characteristic to set. + * @param [in] value The value to set. + * @throws BLEUuidNotFound + */ +void BLERemoteService::setValue(BLEUUID characteristicUuid, std::string value) { + ESP_LOGD(LOG_TAG, ">> setValue: uuid: %s", characteristicUuid.toString().c_str()); + getCharacteristic(characteristicUuid)->writeValue(value); + ESP_LOGD(LOG_TAG, "<< setValue"); +} // setValue + + +/** + * @brief Create a string representation of this remote service. + * @return A string representation of this remote service. + */ +std::string BLERemoteService::toString() { + std::ostringstream ss; + ss << "Service: uuid: " + m_uuid.toString(); + ss << ", start_handle: " << std::dec << m_startHandle << " 0x" << std::hex << m_startHandle << + ", end_handle: " << std::dec << m_endHandle << " 0x" << std::hex << m_endHandle; + for (auto &myPair : m_characteristicMap) { + ss << "\n" << myPair.second->toString(); + // myPair.second is the value + } + return ss.str(); +} // toString + + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLERemoteService.h b/libraries/ESP32_BLE_Arduino/src/BLERemoteService.h new file mode 100644 index 0000000..2ab8673 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLERemoteService.h @@ -0,0 +1,85 @@ +/* + * BLERemoteService.h + * + * Created on: Jul 8, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ +#define COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +#include "BLEClient.h" +#include "BLERemoteCharacteristic.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLEClient; +class BLERemoteCharacteristic; + + +/** + * @brief A model of a remote %BLE service. + */ +class BLERemoteService { +public: + virtual ~BLERemoteService(); + + // Public methods + BLERemoteCharacteristic* getCharacteristic(const char* uuid); // Get the specified characteristic reference. + BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); // Get the specified characteristic reference. + BLERemoteCharacteristic* getCharacteristic(uint16_t uuid); // Get the specified characteristic reference. + std::map* getCharacteristics(); + std::map* getCharacteristicsByHandle(); // Get the characteristics map. + void getCharacteristics(std::map* pCharacteristicMap); + + BLEClient* getClient(void); // Get a reference to the client associated with this service. + uint16_t getHandle(); // Get the handle of this service. + BLEUUID getUUID(void); // Get the UUID of this service. + std::string getValue(BLEUUID characteristicUuid); // Get the value of a characteristic. + void setValue(BLEUUID characteristicUuid, std::string value); // Set the value of a characteristic. + std::string toString(void); + +private: + // Private constructor ... never meant to be created by a user application. + BLERemoteService(esp_gatt_id_t srvcId, BLEClient* pClient, uint16_t startHandle, uint16_t endHandle); + + // Friends + friend class BLEClient; + friend class BLERemoteCharacteristic; + + // Private methods + void retrieveCharacteristics(void); // Retrieve the characteristics from the BLE Server. + esp_gatt_id_t* getSrvcId(void); + uint16_t getStartHandle(); // Get the start handle for this service. + uint16_t getEndHandle(); // Get the end handle for this service. + + void gattClientEventHandler( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam); + + void removeCharacteristics(); + + // Properties + + // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. + std::map m_characteristicMap; + + // We maintain a map of characteristics owned by this service keyed by a handle. + std::map m_characteristicMapByHandle; + + bool m_haveCharacteristics; // Have we previously obtained the characteristics. + BLEClient* m_pClient; + FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt"); + esp_gatt_id_t m_srvcId; + BLEUUID m_uuid; // The UUID of this service. + uint16_t m_startHandle; // The starting handle of this service. + uint16_t m_endHandle; // The ending handle of this service. +}; // BLERemoteService + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEScan.cpp b/libraries/ESP32_BLE_Arduino/src/BLEScan.cpp new file mode 100644 index 0000000..d851a47 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEScan.cpp @@ -0,0 +1,331 @@ +/* + * BLEScan.cpp + * + * Created on: Jul 1, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + + +#include + +#include + +#include "BLEAdvertisedDevice.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEScan"; +#endif + + + + +/** + * Constructor + */ +BLEScan::BLEScan() { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. + m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; + m_pAdvertisedDeviceCallbacks = nullptr; + m_stopped = true; + m_wantDuplicates = false; + setInterval(100); + setWindow(100); +} // BLEScan + + +/** + * @brief Handle GAP events related to scans. + * @param [in] event The event type for this event. + * @param [in] param Parameter data for this event. + */ +void BLEScan::handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + + switch(event) { + + // --------------------------- + // scan_rst: + // esp_gap_search_evt_t search_evt + // esp_bd_addr_t bda + // esp_bt_dev_type_t dev_type + // esp_ble_addr_type_t ble_addr_type + // esp_ble_evt_type_t ble_evt_type + // int rssi + // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] + // int flag + // int num_resps + // uint8_t adv_data_len + // uint8_t scan_rsp_len + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + + switch(param->scan_rst.search_evt) { + // + // ESP_GAP_SEARCH_INQ_CMPL_EVT + // + // Event that indicates that the duration allowed for the search has completed or that we have been + // asked to stop. + case ESP_GAP_SEARCH_INQ_CMPL_EVT: { + ESP_LOGW(LOG_TAG, "ESP_GAP_SEARCH_INQ_CMPL_EVT"); + m_stopped = true; + m_semaphoreScanEnd.give(); + if (m_scanCompleteCB != nullptr) { + m_scanCompleteCB(m_scanResults); + } + break; + } // ESP_GAP_SEARCH_INQ_CMPL_EVT + + // + // ESP_GAP_SEARCH_INQ_RES_EVT + // + // Result that has arrived back from a Scan inquiry. + case ESP_GAP_SEARCH_INQ_RES_EVT: { + if (m_stopped) { // If we are not scanning, nothing to do with the extra results. + break; + } + +// Examine our list of previously scanned addresses and, if we found this one already, +// ignore it. + BLEAddress advertisedAddress(param->scan_rst.bda); + bool found = false; + + if (m_scanResults.m_vectorAdvertisedDevices.count(advertisedAddress.toString()) != 0) { + found = true; + } + + if (found && !m_wantDuplicates) { // If we found a previous entry AND we don't want duplicates, then we are done. + ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str()); + vTaskDelay(1); // <--- allow to switch task in case we scan infinity and dont have new devices to report, or we are blocked here + break; + } + + // We now construct a model of the advertised device that we have just found for the first + // time. + // ESP_LOG_BUFFER_HEXDUMP(LOG_TAG, (uint8_t*)param->scan_rst.ble_adv, param->scan_rst.adv_data_len + param->scan_rst.scan_rsp_len, ESP_LOG_DEBUG); + // ESP_LOGW(LOG_TAG, "bytes length: %d + %d, addr type: %d", param->scan_rst.adv_data_len, param->scan_rst.scan_rsp_len, param->scan_rst.ble_addr_type); + BLEAdvertisedDevice *advertisedDevice = new BLEAdvertisedDevice(); + advertisedDevice->setAddress(advertisedAddress); + advertisedDevice->setRSSI(param->scan_rst.rssi); + advertisedDevice->setAdFlag(param->scan_rst.flag); + advertisedDevice->parseAdvertisement((uint8_t*)param->scan_rst.ble_adv, param->scan_rst.adv_data_len + param->scan_rst.scan_rsp_len); + advertisedDevice->setScan(this); + advertisedDevice->setAddressType(param->scan_rst.ble_addr_type); + + if (!found) { // If we have previously seen this device, don't record it again. + m_scanResults.m_vectorAdvertisedDevices.insert(std::pair(advertisedAddress.toString(), advertisedDevice)); + } + + if (m_pAdvertisedDeviceCallbacks) { + m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); + } + if(found) + delete advertisedDevice; + + break; + } // ESP_GAP_SEARCH_INQ_RES_EVT + + default: { + break; + } + } // switch - search_evt + + + break; + } // ESP_GAP_BLE_SCAN_RESULT_EVT + + default: { + break; + } // default + } // End switch +} // gapEventHandler + + +/** + * @brief Should we perform an active or passive scan? + * The default is a passive scan. An active scan means that we will wish a scan response. + * @param [in] active If true, we perform an active scan otherwise a passive scan. + * @return N/A. + */ +void BLEScan::setActiveScan(bool active) { + if (active) { + m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; + } else { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; + } +} // setActiveScan + + +/** + * @brief Set the call backs to be invoked. + * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. + * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. + */ +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { + m_wantDuplicates = wantDuplicates; + m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; +} // setAdvertisedDeviceCallbacks + + +/** + * @brief Set the interval to scan. + * @param [in] The interval in msecs. + */ +void BLEScan::setInterval(uint16_t intervalMSecs) { + m_scan_params.scan_interval = intervalMSecs / 0.625; +} // setInterval + + +/** + * @brief Set the window to actively scan. + * @param [in] windowMSecs How long to actively scan. + */ +void BLEScan::setWindow(uint16_t windowMSecs) { + m_scan_params.scan_window = windowMSecs / 0.625; +} // setWindow + + +/** + * @brief Start scanning. + * @param [in] duration The duration in seconds for which to scan. + * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @param [in] are we continue scan (true) or we want to clear stored devices (false) + * @return True if scan started or false if there was an error. + */ +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue) { + ESP_LOGD(LOG_TAG, ">> start(duration=%d)", duration); + + m_semaphoreScanEnd.take(std::string("start")); + m_scanCompleteCB = scanCompleteCB; // Save the callback to be invoked when the scan completes. + + // if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals + // then we should not clear map or we will connect the same device few times + if(!is_continue) { + for(auto _dev : m_scanResults.m_vectorAdvertisedDevices){ + delete _dev.second; + } + m_scanResults.m_vectorAdvertisedDevices.clear(); + } + + esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + errRc = ::esp_ble_gap_start_scanning(duration); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreScanEnd.give(); + return false; + } + + m_stopped = false; + + ESP_LOGD(LOG_TAG, "<< start()"); + return true; +} // start + + +/** + * @brief Start scanning and block until scanning has been completed. + * @param [in] duration The duration in seconds for which to scan. + * @return The BLEScanResults. + */ +BLEScanResults BLEScan::start(uint32_t duration, bool is_continue) { + if(start(duration, nullptr, is_continue)) { + m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. + } + return m_scanResults; +} // start + + +/** + * @brief Stop an in progress scan. + * @return N/A. + */ +void BLEScan::stop() { + ESP_LOGD(LOG_TAG, ">> stop()"); + + esp_err_t errRc = ::esp_ble_gap_stop_scanning(); + + m_stopped = true; + m_semaphoreScanEnd.give(); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // stop + +// delete peer device from cache after disconnecting, it is required in case we are connecting to devices with not public address +void BLEScan::erase(BLEAddress address) { + ESP_LOGI(LOG_TAG, "erase device: %s", address.toString().c_str()); + BLEAdvertisedDevice *advertisedDevice = m_scanResults.m_vectorAdvertisedDevices.find(address.toString())->second; + m_scanResults.m_vectorAdvertisedDevices.erase(address.toString()); + delete advertisedDevice; +} + + +/** + * @brief Dump the scan results to the log. + */ +void BLEScanResults::dump() { + ESP_LOGD(LOG_TAG, ">> Dump scan results:"); + for (int i=0; isecond; + for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) { + dev = *it->second; + if (x==i) break; + x++; + } + return dev; +} + +BLEScanResults BLEScan::getResults() { + return m_scanResults; +} + +void BLEScan::clearResults() { + for(auto _dev : m_scanResults.m_vectorAdvertisedDevices){ + delete _dev.second; + } + m_scanResults.m_vectorAdvertisedDevices.clear(); +} + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEScan.h b/libraries/ESP32_BLE_Arduino/src/BLEScan.h new file mode 100644 index 0000000..2f71a72 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEScan.h @@ -0,0 +1,83 @@ +/* + * BLEScan.h + * + * Created on: Jul 1, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_ +#define COMPONENTS_CPP_UTILS_BLESCAN_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +// #include +#include +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "FreeRTOS.h" + +class BLEAdvertisedDevice; +class BLEAdvertisedDeviceCallbacks; +class BLEClient; +class BLEScan; + + +/** + * @brief The result of having performed a scan. + * When a scan completes, we have a set of found devices. Each device is described + * by a BLEAdvertisedDevice object. The number of items in the set is given by + * getCount(). We can retrieve a device by calling getDevice() passing in the + * index (starting at 0) of the desired device. + */ +class BLEScanResults { +public: + void dump(); + int getCount(); + BLEAdvertisedDevice getDevice(uint32_t i); + +private: + friend BLEScan; + std::map m_vectorAdvertisedDevices; +}; + +/** + * @brief Perform and manage %BLE scans. + * + * Scanning is associated with a %BLE client that is attempting to locate BLE servers. + */ +class BLEScan { +public: + void setActiveScan(bool active); + void setAdvertisedDeviceCallbacks( + BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, + bool wantDuplicates = false); + void setInterval(uint16_t intervalMSecs); + void setWindow(uint16_t windowMSecs); + bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue = false); + BLEScanResults start(uint32_t duration, bool is_continue = false); + void stop(); + void erase(BLEAddress address); + BLEScanResults getResults(); + void clearResults(); + +private: + BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. + friend class BLEDevice; + void handleGAPEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload); + + + esp_ble_scan_params_t m_scan_params; + BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr; + bool m_stopped = true; + FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); + BLEScanResults m_scanResults; + bool m_wantDuplicates; + void (*m_scanCompleteCB)(BLEScanResults scanResults); +}; // BLEScan + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLESecurity.cpp b/libraries/ESP32_BLE_Arduino/src/BLESecurity.cpp new file mode 100644 index 0000000..921f542 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLESecurity.cpp @@ -0,0 +1,104 @@ +/* + * BLESecurity.cpp + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#include "BLESecurity.h" +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +BLESecurity::BLESecurity() { +} + +BLESecurity::~BLESecurity() { +} +/* + * @brief Set requested authentication mode + */ +void BLESecurity::setAuthenticationMode(esp_ble_auth_req_t auth_req) { + m_authReq = auth_req; + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); // <--- setup requested authentication mode +} + +/** + * @brief Set our device IO capability to let end user perform authorization + * either by displaying or entering generated 6-digits pin code + */ +void BLESecurity::setCapability(esp_ble_io_cap_t iocap) { + m_iocap = iocap; + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); +} // setCapability + + +/** + * @brief Init encryption key by server + * @param key_size is value between 7 and 16 + */ +void BLESecurity::setInitEncryptionKey(uint8_t init_key) { + m_initKey = init_key; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); +} // setInitEncryptionKey + + +/** + * @brief Init encryption key by client + * @param key_size is value between 7 and 16 + */ +void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { + m_respKey = resp_key; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); +} // setRespEncryptionKey + + +/** + * + * + */ +void BLESecurity::setKeySize(uint8_t key_size) { + m_keySize = key_size; + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); +} //setKeySize + + +/** + * @brief Debug function to display what keys are exchanged by peers + */ +char* BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { + char* key_str = nullptr; + switch (key_type) { + case ESP_LE_KEY_NONE: + key_str = (char*) "ESP_LE_KEY_NONE"; + break; + case ESP_LE_KEY_PENC: + key_str = (char*) "ESP_LE_KEY_PENC"; + break; + case ESP_LE_KEY_PID: + key_str = (char*) "ESP_LE_KEY_PID"; + break; + case ESP_LE_KEY_PCSRK: + key_str = (char*) "ESP_LE_KEY_PCSRK"; + break; + case ESP_LE_KEY_PLK: + key_str = (char*) "ESP_LE_KEY_PLK"; + break; + case ESP_LE_KEY_LLK: + key_str = (char*) "ESP_LE_KEY_LLK"; + break; + case ESP_LE_KEY_LENC: + key_str = (char*) "ESP_LE_KEY_LENC"; + break; + case ESP_LE_KEY_LID: + key_str = (char*) "ESP_LE_KEY_LID"; + break; + case ESP_LE_KEY_LCSRK: + key_str = (char*) "ESP_LE_KEY_LCSRK"; + break; + default: + key_str = (char*) "INVALID BLE KEY TYPE"; + break; + } + return key_str; +} // esp_key_type_to_str +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLESecurity.h b/libraries/ESP32_BLE_Arduino/src/BLESecurity.h new file mode 100644 index 0000000..48d09d2 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLESecurity.h @@ -0,0 +1,72 @@ +/* + * BLESecurity.h + * + * Created on: Dec 17, 2017 + * Author: chegewara + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#define COMPONENTS_CPP_UTILS_BLESECURITY_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +class BLESecurity { +public: + BLESecurity(); + virtual ~BLESecurity(); + void setAuthenticationMode(esp_ble_auth_req_t auth_req); + void setCapability(esp_ble_io_cap_t iocap); + void setInitEncryptionKey(uint8_t init_key); + void setRespEncryptionKey(uint8_t resp_key); + void setKeySize(uint8_t key_size = 16); + static char* esp_key_type_to_str(esp_ble_key_type_t key_type); + +private: + esp_ble_auth_req_t m_authReq; + esp_ble_io_cap_t m_iocap; + uint8_t m_initKey; + uint8_t m_respKey; + uint8_t m_keySize; + +}; // BLESecurity + + +/* + * @brief Callbacks to handle GAP events related to authorization + */ +class BLESecurityCallbacks { +public: + virtual ~BLESecurityCallbacks() {}; + + /** + * @brief Its request from peer device to input authentication pin code displayed on peer device. + * It requires that our device is capable to input 6-digits code by end user + * @return Return 6-digits integer value from input device + */ + virtual uint32_t onPassKeyRequest() = 0; + + /** + * @brief Provide us 6-digits code to perform authentication. + * It requires that our device is capable to display this code to end user + * @param + */ + virtual void onPassKeyNotify(uint32_t pass_key) = 0; + + /** + * @brief Here we can make decision if we want to let negotiate authorization with peer device or not + * return Return true if we accept this peer device request + */ + + virtual bool onSecurityRequest() = 0 ; + /** + * Provide us information when authentication process is completed + */ + virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; + + virtual bool onConfirmPIN(uint32_t pin) = 0; +}; // BLESecurityCallbacks + +#endif // CONFIG_BT_ENABLED +#endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEServer.cpp b/libraries/ESP32_BLE_Arduino/src/BLEServer.cpp new file mode 100644 index 0000000..6a780aa --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEServer.cpp @@ -0,0 +1,424 @@ +/* + * BLEServer.cpp + * + * Created on: Apr 16, 2017 + * Author: kolban + */ + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "GeneralUtils.h" +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEService.h" +#include "BLEUtils.h" +#include +#include +#include +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEServer"; +#endif + + + + +/** + * @brief Construct a %BLE Server + * + * This class is not designed to be individually instantiated. Instead one should create a server by asking + * the BLEDevice class. + */ +BLEServer::BLEServer() { + m_appId = ESP_GATT_IF_NONE; + m_gatts_if = ESP_GATT_IF_NONE; + m_connectedCount = 0; + m_connId = ESP_GATT_IF_NONE; + m_pServerCallbacks = nullptr; +} // BLEServer + + +void BLEServer::createApp(uint16_t appId) { + m_appId = appId; + registerApp(appId); +} // createApp + + +/** + * @brief Create a %BLE Service. + * + * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition + * of a new service. Every service must have a unique UUID. + * @param [in] uuid The UUID of the new service. + * @return A reference to the new service object. + */ +BLEService* BLEServer::createService(const char* uuid) { + return createService(BLEUUID(uuid)); +} + + +/** + * @brief Create a %BLE Service. + * + * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition + * of a new service. Every service must have a unique UUID. + * @param [in] uuid The UUID of the new service. + * @param [in] numHandles The maximum number of handles associated with this service. + * @param [in] inst_id With multiple services with the same UUID we need to provide inst_id value different for each service. + * @return A reference to the new service object. + */ +BLEService* BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t inst_id) { + ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str()); + m_semaphoreCreateEvt.take("createService"); + + // Check that a service with the supplied UUID does not already exist. + if (m_serviceMap.getByUUID(uuid) != nullptr) { + ESP_LOGW(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", + uuid.toString().c_str()); + } + + BLEService* pService = new BLEService(uuid, numHandles); + pService->m_instId = inst_id; + m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server. + pService->executeCreate(this); // Perform the API calls to actually create the service. + + m_semaphoreCreateEvt.wait("createService"); + + ESP_LOGD(LOG_TAG, "<< createService"); + return pService; +} // createService + + +/** + * @brief Get a %BLE Service by its UUID + * @param [in] uuid The UUID of the new service. + * @return A reference to the service object. + */ +BLEService* BLEServer::getServiceByUUID(const char* uuid) { + return m_serviceMap.getByUUID(uuid); +} + +/** + * @brief Get a %BLE Service by its UUID + * @param [in] uuid The UUID of the new service. + * @return A reference to the service object. + */ +BLEService* BLEServer::getServiceByUUID(BLEUUID uuid) { + return m_serviceMap.getByUUID(uuid); +} + +/** + * @brief Retrieve the advertising object that can be used to advertise the existence of the server. + * + * @return An advertising object. + */ +BLEAdvertising* BLEServer::getAdvertising() { + return BLEDevice::getAdvertising(); +} + +uint16_t BLEServer::getConnId() { + return m_connId; +} + + +/** + * @brief Return the number of connected clients. + * @return The number of connected clients. + */ +uint32_t BLEServer::getConnectedCount() { + return m_connectedCount; +} // getConnectedCount + + +uint16_t BLEServer::getGattsIf() { + return m_gatts_if; +} + + +/** + * @brief Handle a GATT Server Event. + * + * @param [in] event + * @param [in] gatts_if + * @param [in] param + * + */ +void BLEServer::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s", + BLEUtils::gattServerEventTypeToString(event).c_str()); + + switch(event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + // + case ESP_GATTS_ADD_CHAR_EVT: { + break; + } // ESP_GATTS_ADD_CHAR_EVT + + case ESP_GATTS_MTU_EVT: + updatePeerMTU(param->mtu.conn_id, param->mtu.mtu); + break; + + // ESP_GATTS_CONNECT_EVT + // connect: + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // + case ESP_GATTS_CONNECT_EVT: { + m_connId = param->connect.conn_id; + addPeerDevice((void*)this, false, m_connId); + if (m_pServerCallbacks != nullptr) { + m_pServerCallbacks->onConnect(this); + m_pServerCallbacks->onConnect(this, param); + } + m_connectedCount++; // Increment the number of connected devices count. + break; + } // ESP_GATTS_CONNECT_EVT + + + // ESP_GATTS_CREATE_EVT + // Called when a new service is registered as having been created. + // + // create: + // * esp_gatt_status_t status + // * uint16_t service_handle + // * esp_gatt_srvc_id_t service_id + // + case ESP_GATTS_CREATE_EVT: { + BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid, param->create.service_id.id.inst_id); // <--- very big bug for multi services with the same uuid + m_serviceMap.setByHandle(param->create.service_handle, pService); + m_semaphoreCreateEvt.give(); + break; + } // ESP_GATTS_CREATE_EVT + + + // ESP_GATTS_DISCONNECT_EVT + // + // disconnect + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - esp_gatt_conn_reason_t reason + // + // If we receive a disconnect event then invoke the callback for disconnects (if one is present). + // we also want to start advertising again. + case ESP_GATTS_DISCONNECT_EVT: { + m_connectedCount--; // Decrement the number of connected devices count. + if (m_pServerCallbacks != nullptr) { // If we have callbacks, call now. + m_pServerCallbacks->onDisconnect(this); + } + startAdvertising(); //- do this with some delay from the loop() + removePeerDevice(param->disconnect.conn_id, false); + break; + } // ESP_GATTS_DISCONNECT_EVT + + + // ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived. + // + // read: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool is_long + // - bool need_rsp + // + case ESP_GATTS_READ_EVT: { + break; + } // ESP_GATTS_READ_EVT + + + // ESP_GATTS_REG_EVT + // reg: + // - esp_gatt_status_t status + // - uint16_t app_id + // + case ESP_GATTS_REG_EVT: { + m_gatts_if = gatts_if; + m_semaphoreRegisterAppEvt.give(); // Unlock the mutex waiting for the registration of the app. + break; + } // ESP_GATTS_REG_EVT + + + // ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived. + // + // write: + // - uint16_t conn_id + // - uint16_t trans_id + // - esp_bd_addr_t bda + // - uint16_t handle + // - uint16_t offset + // - bool need_rsp + // - bool is_prep + // - uint16_t len + // - uint8_t* value + // + case ESP_GATTS_WRITE_EVT: { + break; + } + + case ESP_GATTS_OPEN_EVT: + m_semaphoreOpenEvt.give(param->open.status); + break; + + default: + break; + } + + // Invoke the handler for every Service we have. + m_serviceMap.handleGATTServerEvent(event, gatts_if, param); + + ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); +} // handleGATTServerEvent + + +/** + * @brief Register the app. + * + * @return N/A + */ +void BLEServer::registerApp(uint16_t m_appId) { + ESP_LOGD(LOG_TAG, ">> registerApp - %d", m_appId); + m_semaphoreRegisterAppEvt.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event. + ::esp_ble_gatts_app_register(m_appId); + m_semaphoreRegisterAppEvt.wait("registerApp"); + ESP_LOGD(LOG_TAG, "<< registerApp"); +} // registerApp + + +/** + * @brief Set the server callbacks. + * + * As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client + * disconnecting. This function can be called to register a callback handler that will be invoked when these + * events are detected. + * + * @param [in] pCallbacks The callbacks to be invoked. + */ +void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { + m_pServerCallbacks = pCallbacks; +} // setCallbacks + +/* + * Remove service + */ +void BLEServer::removeService(BLEService* service) { + service->stop(); + service->executeDelete(); + m_serviceMap.removeService(service); +} + +/** + * @brief Start advertising. + * + * Start the server advertising its existence. This is a convenience function and is equivalent to + * retrieving the advertising object and invoking start upon it. + */ +void BLEServer::startAdvertising() { + ESP_LOGD(LOG_TAG, ">> startAdvertising"); + BLEDevice::startAdvertising(); + ESP_LOGD(LOG_TAG, "<< startAdvertising"); +} // startAdvertising + +/** + * Allow to connect GATT server to peer device + * Probably can be used in ANCS for iPhone + */ +bool BLEServer::connect(BLEAddress address) { + esp_bd_addr_t addr; + memcpy(&addr, address.getNative(), 6); + // Perform the open connection request against the target BLE Server. + m_semaphoreOpenEvt.take("connect"); + esp_err_t errRc = ::esp_ble_gatts_open( + getGattsIf(), + addr, // address + 1 // direct connection + ); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. + ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK); + return rc == ESP_GATT_OK; +} // connect + + + +void BLEServerCallbacks::onConnect(BLEServer* pServer) { + ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + +void BLEServerCallbacks::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { + ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + + +void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { + ESP_LOGD("BLEServerCallbacks", ">> onDisconnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + ESP_LOGD("BLEServerCallbacks", "<< onDisconnect()"); +} // onDisconnect + +/* multi connect support */ +/* TODO do some more tweaks */ +void BLEServer::updatePeerMTU(uint16_t conn_id, uint16_t mtu) { + // set mtu in conn_status_t + const std::map::iterator it = m_connectedServersMap.find(conn_id); + if (it != m_connectedServersMap.end()) { + it->second.mtu = mtu; + std::swap(m_connectedServersMap[conn_id], it->second); + } +} + +std::map BLEServer::getPeerDevices(bool _client) { + return m_connectedServersMap; +} + + +uint16_t BLEServer::getPeerMTU(uint16_t conn_id) { + return m_connectedServersMap.find(conn_id)->second.mtu; +} + +void BLEServer::addPeerDevice(void* peer, bool _client, uint16_t conn_id) { + conn_status_t status = { + .peer_device = peer, + .connected = true, + .mtu = 23 + }; + + m_connectedServersMap.insert(std::pair(conn_id, status)); +} + +void BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { + m_connectedServersMap.erase(conn_id); +} +/* multi connect support */ + +/** + * Update connection parameters can be called only after connection has been established + */ +void BLEServer::updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) { + esp_ble_conn_update_params_t conn_params; + memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = latency; + conn_params.max_int = maxInterval; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = minInterval; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = timeout; // timeout = 400*10ms = 4000ms + esp_ble_gap_update_conn_params(&conn_params); +} +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEServer.h b/libraries/ESP32_BLE_Arduino/src/BLEServer.h new file mode 100644 index 0000000..d39d8bf --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEServer.h @@ -0,0 +1,140 @@ +/* + * BLEServer.h + * + * Created on: Apr 16, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESERVER_H_ +#define COMPONENTS_CPP_UTILS_BLESERVER_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +#include +#include +// #include "BLEDevice.h" + +#include "BLEUUID.h" +#include "BLEAdvertising.h" +#include "BLECharacteristic.h" +#include "BLEService.h" +#include "BLESecurity.h" +#include "FreeRTOS.h" +#include "BLEAddress.h" + +class BLEServerCallbacks; +/* TODO possibly refactor this struct */ +typedef struct { + void *peer_device; // peer device BLEClient or BLEServer - maybe its better to have 2 structures or union here + bool connected; // do we need it? + uint16_t mtu; // every peer device negotiate own mtu +} conn_status_t; + + +/** + * @brief A data structure that manages the %BLE servers owned by a BLE server. + */ +class BLEServiceMap { +public: + BLEService* getByHandle(uint16_t handle); + BLEService* getByUUID(const char* uuid); + BLEService* getByUUID(BLEUUID uuid, uint8_t inst_id = 0); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + void setByHandle(uint16_t handle, BLEService* service); + void setByUUID(const char* uuid, BLEService* service); + void setByUUID(BLEUUID uuid, BLEService* service); + std::string toString(); + BLEService* getFirst(); + BLEService* getNext(); + void removeService(BLEService *service); + int getRegisteredServiceCount(); + +private: + std::map m_handleMap; + std::map m_uuidMap; + std::map::iterator m_iterator; +}; + + +/** + * @brief The model of a %BLE server. + */ +class BLEServer { +public: + uint32_t getConnectedCount(); + BLEService* createService(const char* uuid); + BLEService* createService(BLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0); + BLEAdvertising* getAdvertising(); + void setCallbacks(BLEServerCallbacks* pCallbacks); + void startAdvertising(); + void removeService(BLEService* service); + BLEService* getServiceByUUID(const char* uuid); + BLEService* getServiceByUUID(BLEUUID uuid); + bool connect(BLEAddress address); + uint16_t m_appId; + void updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + + /* multi connection support */ + std::map getPeerDevices(bool client); + void addPeerDevice(void* peer, bool is_client, uint16_t conn_id); + void removePeerDevice(uint16_t conn_id, bool client); + BLEServer* getServerByConnId(uint16_t conn_id); + void updatePeerMTU(uint16_t connId, uint16_t mtu); + uint16_t getPeerMTU(uint16_t conn_id); + uint16_t getConnId(); + + +private: + BLEServer(); + friend class BLEService; + friend class BLECharacteristic; + friend class BLEDevice; + esp_ble_adv_data_t m_adv_data; + // BLEAdvertising m_bleAdvertising; + uint16_t m_connId; + uint32_t m_connectedCount; + uint16_t m_gatts_if; + std::map m_connectedServersMap; + + FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); + BLEServiceMap m_serviceMap; + BLEServerCallbacks* m_pServerCallbacks = nullptr; + + void createApp(uint16_t appId); + uint16_t getGattsIf(); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + void registerApp(uint16_t); +}; // BLEServer + + +/** + * @brief Callbacks associated with the operation of a %BLE server. + */ +class BLEServerCallbacks { +public: + virtual ~BLEServerCallbacks() {}; + /** + * @brief Handle a new client connection. + * + * When a new client connects, we are invoked. + * + * @param [in] pServer A reference to the %BLE server that received the client connection. + */ + virtual void onConnect(BLEServer* pServer); + virtual void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param); + /** + * @brief Handle an existing client disconnection. + * + * When an existing client disconnects, we are invoked. + * + * @param [in] pServer A reference to the %BLE server that received the existing client disconnection. + */ + virtual void onDisconnect(BLEServer* pServer); +}; // BLEServerCallbacks + + +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEService.cpp b/libraries/ESP32_BLE_Arduino/src/BLEService.cpp new file mode 100644 index 0000000..3034cf1 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEService.cpp @@ -0,0 +1,418 @@ +/* + * BLEService.cpp + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +// A service is identified by a UUID. A service is also the container for one or more characteristics. + +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include + +#include +#include +#include + +#include "BLEServer.h" +#include "BLEService.h" +#include "BLEUtils.h" +#include "GeneralUtils.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEService"; // Tag for logging. +#endif + +#define NULL_HANDLE (0xffff) + + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(const char* uuid, uint16_t numHandles) : BLEService(BLEUUID(uuid), numHandles) { +} + + +/** + * @brief Construct an instance of the BLEService + * @param [in] uuid The UUID of the service. + * @param [in] numHandles The maximum number of handles associated with the service. + */ +BLEService::BLEService(BLEUUID uuid, uint16_t numHandles) { + m_uuid = uuid; + m_handle = NULL_HANDLE; + m_pServer = nullptr; + //m_serializeMutex.setName("BLEService"); + m_lastCreatedCharacteristic = nullptr; + m_numHandles = numHandles; +} // BLEService + + +/** + * @brief Create the service. + * Create the service. + * @param [in] gatts_if The handle of the GATT server interface. + * @return N/A. + */ + +void BLEService::executeCreate(BLEServer* pServer) { + ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); + m_pServer = pServer; + m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT + + esp_gatt_srvc_id_t srvc_id; + srvc_id.is_primary = true; + srvc_id.id.inst_id = m_instId; + srvc_id.id.uuid = *m_uuid.getNative(); + esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, m_numHandles); // The maximum number of handles associated with the service. + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreCreateEvt.wait("executeCreate"); + ESP_LOGD(LOG_TAG, "<< executeCreate"); +} // executeCreate + + +/** + * @brief Delete the service. + * Delete the service. + * @return N/A. + */ + +void BLEService::executeDelete() { + ESP_LOGD(LOG_TAG, ">> executeDelete()"); + m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT + + esp_err_t errRc = ::esp_ble_gatts_delete_service(getHandle()); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_ble_gatts_delete_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + m_semaphoreDeleteEvt.wait("executeDelete"); + ESP_LOGD(LOG_TAG, "<< executeDelete"); +} // executeDelete + + +/** + * @brief Dump details of this BLE GATT service. + * @return N/A. + */ +void BLEService::dump() { + ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x", + m_uuid.toString().c_str(), + m_handle); + ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); +} // dump + + +/** + * @brief Get the UUID of the service. + * @return the UUID of the service. + */ +BLEUUID BLEService::getUUID() { + return m_uuid; +} // getUUID + + +/** + * @brief Start the service. + * Here we wish to start the service which means that we will respond to partner requests about it. + * Starting a service also means that we can create the corresponding characteristics. + * @return Start the service. + */ +void BLEService::start() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). +// + ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!"); + return; + } + + BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); + + while (pCharacteristic != nullptr) { + m_lastCreatedCharacteristic = pCharacteristic; + pCharacteristic->executeCreate(this); + + pCharacteristic = m_characteristicMap.getNext(); + } + // Start each of the characteristics ... these are found in the m_characteristicMap. + + m_semaphoreStartEvt.take("start"); + esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStartEvt.wait("start"); + + ESP_LOGD(LOG_TAG, "<< start()"); +} // start + + +/** + * @brief Stop the service. + */ +void BLEService::stop() { +// We ask the BLE runtime to start the service and then create each of the characteristics. +// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event +// obtained as a result of calling esp_ble_gatts_create_service(). + ESP_LOGD(LOG_TAG, ">> stop(): Stopping service (esp_ble_gatts_stop_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "<< !!! We attempted to stop a service but don't know its handle!"); + return; + } + + m_semaphoreStopEvt.take("stop"); + esp_err_t errRc = ::esp_ble_gatts_stop_service(m_handle); + + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_stop_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreStopEvt.wait("stop"); + + ESP_LOGD(LOG_TAG, "<< stop()"); +} // start + + +/** + * @brief Set the handle associated with this service. + * @param [in] handle The handle associated with the service. + */ +void BLEService::setHandle(uint16_t handle) { + ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); + if (m_handle != NULL_HANDLE) { + ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle); + return; + } + m_handle = handle; + ESP_LOGD(LOG_TAG, "<< setHandle"); +} // setHandle + + +/** + * @brief Get the handle associated with this service. + * @return The handle associated with this service. + */ +uint16_t BLEService::getHandle() { + return m_handle; +} // getHandle + + +/** + * @brief Add a characteristic to the service. + * @param [in] pCharacteristic A pointer to the characteristic to be added. + */ +void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) { + // We maintain a mapping of characteristics owned by this service. These are managed by the + // BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic + // to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF). + + ESP_LOGD(LOG_TAG, ">> addCharacteristic()"); + ESP_LOGD(LOG_TAG, "Adding characteristic: uuid=%s to service: %s", + pCharacteristic->getUUID().toString().c_str(), + toString().c_str()); + + // Check that we don't add the same characteristic twice. + if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) { + ESP_LOGW(LOG_TAG, "<< Adding a new characteristic with the same UUID as a previous one"); + //return; + } + + // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID + // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT. + m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); + + ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); +} // addCharacteristic + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) { + return createCharacteristic(BLEUUID(uuid), properties); +} + + +/** + * @brief Create a new BLE Characteristic associated with this service. + * @param [in] uuid - The UUID of the characteristic. + * @param [in] properties - The properties of the characteristic. + * @return The new BLE characteristic. + */ +BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { + BLECharacteristic* pCharacteristic = new BLECharacteristic(uuid, properties); + addCharacteristic(pCharacteristic); + return pCharacteristic; +} // createCharacteristic + + +/** + * @brief Handle a GATTS server event. + */ +void BLEService::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param) { + switch (event) { + // ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service. + // add_char: + // - esp_gatt_status_t status + // - uint16_t attr_handle + // - uint16_t service_handle + // - esp_bt_uuid_t char_uuid + + // If we have reached the correct service, then locate the characteristic and remember the handle + // for that characteristic. + case ESP_GATTS_ADD_CHAR_EVT: { + if (m_handle == param->add_char.service_handle) { + BLECharacteristic *pCharacteristic = getLastCreatedCharacteristic(); + if (pCharacteristic == nullptr) { + ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!", + BLEUUID(param->add_char.char_uuid).toString().c_str()); + dump(); + break; + } + pCharacteristic->setHandle(param->add_char.attr_handle); + m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); + break; + } // Reached the correct service. + break; + } // ESP_GATTS_ADD_CHAR_EVT + + + // ESP_GATTS_START_EVT + // + // start: + // esp_gatt_status_t status + // uint16_t service_handle + case ESP_GATTS_START_EVT: { + if (param->start.service_handle == getHandle()) { + m_semaphoreStartEvt.give(); + } + break; + } // ESP_GATTS_START_EVT + + // ESP_GATTS_STOP_EVT + // + // stop: + // esp_gatt_status_t status + // uint16_t service_handle + // + case ESP_GATTS_STOP_EVT: { + if (param->stop.service_handle == getHandle()) { + m_semaphoreStopEvt.give(); + } + break; + } // ESP_GATTS_STOP_EVT + + + // ESP_GATTS_CREATE_EVT + // Called when a new service is registered as having been created. + // + // create: + // * esp_gatt_status_t status + // * uint16_t service_handle + // * esp_gatt_srvc_id_t service_id + // * - esp_gatt_id id + // * - esp_bt_uuid uuid + // * - uint8_t inst_id + // * - bool is_primary + // + case ESP_GATTS_CREATE_EVT: { + if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid)) && m_instId == param->create.service_id.id.inst_id) { + setHandle(param->create.service_handle); + m_semaphoreCreateEvt.give(); + } + break; + } // ESP_GATTS_CREATE_EVT + + + // ESP_GATTS_DELETE_EVT + // Called when a service is deleted. + // + // delete: + // * esp_gatt_status_t status + // * uint16_t service_handle + // + case ESP_GATTS_DELETE_EVT: { + if (param->del.service_handle == getHandle()) { + m_semaphoreDeleteEvt.give(); + } + break; + } // ESP_GATTS_DELETE_EVT + + default: + break; + } // Switch + + // Invoke the GATTS handler in each of the associated characteristics. + m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); +} // handleGATTServerEvent + + +BLECharacteristic* BLEService::getCharacteristic(const char* uuid) { + return getCharacteristic(BLEUUID(uuid)); +} + + +BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) { + return m_characteristicMap.getByUUID(uuid); +} + + +/** + * @brief Return a string representation of this service. + * A service is defined by: + * * Its UUID + * * Its handle + * @return A string representation of this service. + */ +std::string BLEService::toString() { + std::stringstream stringStream; + stringStream << "UUID: " << getUUID().toString() << + ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle(); + return stringStream.str(); +} // toString + + +/** + * @brief Get the last created characteristic. + * It is lamentable that this function has to exist. It returns the last created characteristic. + * We need this because the descriptor API is built around the notion that a new descriptor, when created, + * is associated with the last characteristics created and we need that information. + * @return The last created characteristic. + */ +BLECharacteristic* BLEService::getLastCreatedCharacteristic() { + return m_lastCreatedCharacteristic; +} // getLastCreatedCharacteristic + + +/** + * @brief Get the BLE server associated with this service. + * @return The BLEServer associated with this service. + */ +BLEServer* BLEService::getServer() { + return m_pServer; +} // getServer + +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEService.h b/libraries/ESP32_BLE_Arduino/src/BLEService.h new file mode 100644 index 0000000..b42d57f --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEService.h @@ -0,0 +1,97 @@ +/* + * BLEService.h + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#define COMPONENTS_CPP_UTILS_BLESERVICE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#include + +#include "BLECharacteristic.h" +#include "BLEServer.h" +#include "BLEUUID.h" +#include "FreeRTOS.h" + +class BLEServer; + +/** + * @brief A data mapping used to manage the set of %BLE characteristics known to the server. + */ +class BLECharacteristicMap { +public: + void setByUUID(BLECharacteristic* pCharacteristic, const char* uuid); + void setByUUID(BLECharacteristic* pCharacteristic, BLEUUID uuid); + void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic); + BLECharacteristic* getByUUID(const char* uuid); + BLECharacteristic* getByUUID(BLEUUID uuid); + BLECharacteristic* getByHandle(uint16_t handle); + BLECharacteristic* getFirst(); + BLECharacteristic* getNext(); + std::string toString(); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + +private: + std::map m_uuidMap; + std::map m_handleMap; + std::map::iterator m_iterator; +}; + + +/** + * @brief The model of a %BLE service. + * + */ +class BLEService { +public: + void addCharacteristic(BLECharacteristic* pCharacteristic); + BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties); + BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties); + void dump(); + void executeCreate(BLEServer* pServer); + void executeDelete(); + BLECharacteristic* getCharacteristic(const char* uuid); + BLECharacteristic* getCharacteristic(BLEUUID uuid); + BLEUUID getUUID(); + BLEServer* getServer(); + void start(); + void stop(); + std::string toString(); + uint16_t getHandle(); + uint8_t m_instId = 0; + +private: + BLEService(const char* uuid, uint16_t numHandles); + BLEService(BLEUUID uuid, uint16_t numHandles); + friend class BLEServer; + friend class BLEServiceMap; + friend class BLEDescriptor; + friend class BLECharacteristic; + friend class BLEDevice; + + BLECharacteristicMap m_characteristicMap; + uint16_t m_handle; + BLECharacteristic* m_lastCreatedCharacteristic = nullptr; + BLEServer* m_pServer = nullptr; + BLEUUID m_uuid; + + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); + + uint16_t m_numHandles; + + BLECharacteristic* getLastCreatedCharacteristic(); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param); + void setHandle(uint16_t handle); + //void setService(esp_gatt_srvc_id_t srvc_id); +}; // BLEService + + +#endif // CONFIG_BT_ENABLED +#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEServiceMap.cpp b/libraries/ESP32_BLE_Arduino/src/BLEServiceMap.cpp new file mode 100644 index 0000000..cf4f75f --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEServiceMap.cpp @@ -0,0 +1,134 @@ +/* + * BLEServiceMap.cpp + * + * Created on: Jun 22, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEService.h" + + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(const char* uuid) { + return getByUUID(BLEUUID(uuid)); +} + +/** + * @brief Return the service by UUID. + * @param [in] UUID The UUID to look up the service. + * @return The characteristic. + */ +BLEService* BLEServiceMap::getByUUID(BLEUUID uuid, uint8_t inst_id) { + for (auto &myPair : m_uuidMap) { + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; + } + } + //return m_uuidMap.at(uuid.toString()); + return nullptr; +} // getByUUID + + +/** + * @brief Return the service by handle. + * @param [in] handle The handle to look up the service. + * @return The service. + */ +BLEService* BLEServiceMap::getByHandle(uint16_t handle) { + return m_handleMap.at(handle); +} // getByHandle + + +/** + * @brief Set the service by UUID. + * @param [in] uuid The uuid of the service. + * @param [in] characteristic The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByUUID(BLEUUID uuid, BLEService* service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); +} // setByUUID + + +/** + * @brief Set the service by handle. + * @param [in] handle The handle of the service. + * @param [in] service The service to cache. + * @return N/A. + */ +void BLEServiceMap::setByHandle(uint16_t handle, BLEService* service) { + m_handleMap.insert(std::pair(handle, service)); +} // setByHandle + + +/** + * @brief Return a string representation of the service map. + * @return A string representation of the service map. + */ +std::string BLEServiceMap::toString() { + std::stringstream stringStream; + stringStream << std::hex << std::setfill('0'); + for (auto &myPair: m_handleMap) { + stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n"; + } + return stringStream.str(); +} // toString + +void BLEServiceMap::handleGATTServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* param) { + // Invoke the handler for every Service we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(event, gatts_if, param); + } +} + +/** + * @brief Get the first service in the map. + * @return The first service in the map. + */ +BLEService* BLEServiceMap::getFirst() { + m_iterator = m_uuidMap.begin(); + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getFirst + +/** + * @brief Get the next service in the map. + * @return The next service in the map. + */ +BLEService* BLEServiceMap::getNext() { + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEService* pRet = m_iterator->first; + m_iterator++; + return pRet; +} // getNext + +/** + * @brief Removes service from maps. + * @return N/A. + */ +void BLEServiceMap::removeService(BLEService* service) { + m_handleMap.erase(service->getHandle()); + m_uuidMap.erase(service); +} // removeService + +/** + * @brief Returns the amount of registered services + * @return amount of registered services + */ +int BLEServiceMap::getRegisteredServiceCount(){ + return m_handleMap.size(); +} + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEUUID.cpp b/libraries/ESP32_BLE_Arduino/src/BLEUUID.cpp new file mode 100644 index 0000000..4ddf8fc --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEUUID.cpp @@ -0,0 +1,407 @@ +/* + * BLEUUID.cpp + * + * Created on: Jun 21, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include +#include +#include +#include +#include "BLEUUID.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEUUID"; +#endif + + +/** + * @brief Copy memory from source to target but in reverse order. + * + * When we move memory from one location it is normally: + * + * ``` + * [0][1][2]...[n] -> [0][1][2]...[n] + * ``` + * + * with this function, it is: + * + * ``` + * [0][1][2]...[n] -> [n][n-1][n-2]...[0] + * ``` + * + * @param [in] target The target of the copy + * @param [in] source The source of the copy + * @param [in] size The number of bytes to copy + */ +static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { + assert(size > 0); + target += (size - 1); // Point target to the last byte of the target data + while (size > 0) { + *target = *source; + target--; + source++; + size--; + } +} // memrcpy + + +/** + * @brief Create a UUID from a string. + * + * Create a UUID from a string. There will be two possible stories here. Either the string represents + * a binary data field or the string represents a hex encoding of a UUID. + * For the hex encoding, here is an example: + * + * ``` + * "beb5483e-36e1-4688-b7f5-ea07361b26a8" + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * 12345678-90ab-cdef-1234-567890abcdef + * ``` + * + * This has a length of 36 characters. We need to parse this into 16 bytes. + * + * @param [in] value The string to build a UUID from. + */ +BLEUUID::BLEUUID(std::string value) { + m_valueSet = true; + if (value.length() == 4) { + m_uuid.len = ESP_UUID_LEN_16; + m_uuid.uuid.uuid16 = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid16 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(2-i)*4; + i+=2; + } + } + else if (value.length() == 8) { + m_uuid.len = ESP_UUID_LEN_32; + m_uuid.uuid.uuid32 = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid32 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4; + i+=2; + } + } + else if (value.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be investigated (lack of time) + m_uuid.len = ESP_UUID_LEN_128; + memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16); + } + else if (value.length() == 36) { + // If the length of the string is 36 bytes then we will assume it is a long hex string in + // UUID format. + m_uuid.len = ESP_UUID_LEN_128; + int n = 0; + for(int i=0;i '9') MSB -= 7; + if(LSB > '9') LSB -= 7; + m_uuid.uuid.uuid128[15-n++] = ((MSB&0x0F) <<4) | (LSB & 0x0F); + i+=2; + } + } + else { + ESP_LOGE(LOG_TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes"); + m_valueSet = false; + } +} //BLEUUID(std::string) + + +/** + * @brief Create a UUID from 16 bytes of memory. + * + * @param [in] pData The pointer to the start of the UUID. + * @param [in] size The size of the data. + * @param [in] msbFirst Is the MSB first in pData memory? + */ +BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) { + if (size != 16) { + ESP_LOGE(LOG_TAG, "ERROR: UUID length not 16 bytes"); + return; + } + m_uuid.len = ESP_UUID_LEN_128; + if (msbFirst) { + memrcpy(m_uuid.uuid.uuid128, pData, 16); + } else { + memcpy(m_uuid.uuid.uuid128, pData, 16); + } + m_valueSet = true; +} // BLEUUID + + +/** + * @brief Create a UUID from the 16bit value. + * + * @param [in] uuid The 16bit short form UUID. + */ +BLEUUID::BLEUUID(uint16_t uuid) { + m_uuid.len = ESP_UUID_LEN_16; + m_uuid.uuid.uuid16 = uuid; + m_valueSet = true; +} // BLEUUID + + +/** + * @brief Create a UUID from the 32bit value. + * + * @param [in] uuid The 32bit short form UUID. + */ +BLEUUID::BLEUUID(uint32_t uuid) { + m_uuid.len = ESP_UUID_LEN_32; + m_uuid.uuid.uuid32 = uuid; + m_valueSet = true; +} // BLEUUID + + +/** + * @brief Create a UUID from the native UUID. + * + * @param [in] uuid The native UUID. + */ +BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { + m_uuid = uuid; + m_valueSet = true; +} // BLEUUID + + +/** + * @brief Create a UUID from the ESP32 esp_gat_id_t. + * + * @param [in] gattId The data to create the UUID from. + */ +BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) { +} // BLEUUID + + +BLEUUID::BLEUUID() { + m_valueSet = false; +} // BLEUUID + + +/** + * @brief Get the number of bits in this uuid. + * @return The number of bits in the UUID. One of 16, 32 or 128. + */ +uint8_t BLEUUID::bitSize() { + if (!m_valueSet) return 0; + switch (m_uuid.len) { + case ESP_UUID_LEN_16: + return 16; + case ESP_UUID_LEN_32: + return 32; + case ESP_UUID_LEN_128: + return 128; + default: + ESP_LOGE(LOG_TAG, "Unknown UUID length: %d", m_uuid.len); + return 0; + } // End of switch +} // bitSize + + +/** + * @brief Compare a UUID against this UUID. + * + * @param [in] uuid The UUID to compare against. + * @return True if the UUIDs are equal and false otherwise. + */ +bool BLEUUID::equals(BLEUUID uuid) { + //ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str()); + if (!m_valueSet || !uuid.m_valueSet) return false; + + if (uuid.m_uuid.len != m_uuid.len) { + return uuid.toString() == toString(); + } + + if (uuid.m_uuid.len == ESP_UUID_LEN_16) { + return uuid.m_uuid.uuid.uuid16 == m_uuid.uuid.uuid16; + } + + if (uuid.m_uuid.len == ESP_UUID_LEN_32) { + return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32; + } + + return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0; +} // equals + + +/** + * Create a BLEUUID from a string of the form: + * 0xNNNN + * 0xNNNNNNNN + * 0x + * NNNN + * NNNNNNNN + * + */ +BLEUUID BLEUUID::fromString(std::string _uuid) { + uint8_t start = 0; + if (strstr(_uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters. + start = 2; + } + uint8_t len = _uuid.length() - start; // Calculate the length of the string we are going to use. + + if(len == 4) { + uint16_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + } else if (len == 8) { + uint32_t x = strtoul(_uuid.substr(start, len).c_str(), NULL, 16); + return BLEUUID(x); + } else if (len == 36) { + return BLEUUID(_uuid); + } + return BLEUUID(); +} // fromString + + +/** + * @brief Get the native UUID value. + * + * @return The native UUID value or NULL if not set. + */ +esp_bt_uuid_t* BLEUUID::getNative() { + //ESP_LOGD(TAG, ">> getNative()") + if (m_valueSet == false) { + ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!"); + return nullptr; + } + //ESP_LOGD(TAG, "<< getNative()"); + return &m_uuid; +} // getNative + + +/** + * @brief Convert a UUID to its 128 bit representation. + * + * A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method + * will convert 16 or 32 bit representations to the full 128bit. + */ +BLEUUID BLEUUID::to128() { + //ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str()); + + // If we either don't have a value or are already a 128 bit UUID, nothing further to do. + if (!m_valueSet || m_uuid.len == ESP_UUID_LEN_128) { + return *this; + } + + // If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID. + if (m_uuid.len == ESP_UUID_LEN_16) { + uint16_t temp = m_uuid.uuid.uuid16; + m_uuid.uuid.uuid128[15] = 0; + m_uuid.uuid.uuid128[14] = 0; + m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff; + m_uuid.uuid.uuid128[12] = temp & 0xff; + + } + else if (m_uuid.len == ESP_UUID_LEN_32) { + uint32_t temp = m_uuid.uuid.uuid32; + m_uuid.uuid.uuid128[15] = (temp >> 24) & 0xff; + m_uuid.uuid.uuid128[14] = (temp >> 16) & 0xff; + m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff; + m_uuid.uuid.uuid128[12] = temp & 0xff; + } + + // Set the fixed parts of the UUID. + m_uuid.uuid.uuid128[11] = 0x00; + m_uuid.uuid.uuid128[10] = 0x00; + + m_uuid.uuid.uuid128[9] = 0x10; + m_uuid.uuid.uuid128[8] = 0x00; + + m_uuid.uuid.uuid128[7] = 0x80; + m_uuid.uuid.uuid128[6] = 0x00; + + m_uuid.uuid.uuid128[5] = 0x00; + m_uuid.uuid.uuid128[4] = 0x80; + m_uuid.uuid.uuid128[3] = 0x5f; + m_uuid.uuid.uuid128[2] = 0x9b; + m_uuid.uuid.uuid128[1] = 0x34; + m_uuid.uuid.uuid128[0] = 0xfb; + + m_uuid.len = ESP_UUID_LEN_128; + //ESP_LOGD(TAG, "<< toFull <- %s", toString().c_str()); + return *this; +} // to128 + + + + +/** + * @brief Get a string representation of the UUID. + * + * The format of a string is: + * 01234567 8901 2345 6789 012345678901 + * 0000180d-0000-1000-8000-00805f9b34fb + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * + * @return A string representation of the UUID. + */ +std::string BLEUUID::toString() { + if (!m_valueSet) return ""; // If we have no value, nothing to format. + + // If the UUIDs are 16 or 32 bit, pad correctly. + std::stringstream ss; + + if (m_uuid.len == ESP_UUID_LEN_16) { // If the UUID is 16bit, pad correctly. + ss << "0000" << + std::hex << + std::setfill('0') << + std::setw(4) << + m_uuid.uuid.uuid16 << + "-0000-1000-8000-00805f9b34fb"; + return ss.str(); // Return the string + } // End 16bit UUID + + if (m_uuid.len == ESP_UUID_LEN_32) { // If the UUID is 32bit, pad correctly. + ss << std::hex << + std::setfill('0') << + std::setw(8) << + m_uuid.uuid.uuid32 << + "-0000-1000-8000-00805f9b34fb"; + return ss.str(); // return the string + } // End 32bit UUID + + // The UUID is not 16bit or 32bit which means that it is 128bit. + // + // UUID string format: + // AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP + ss << std::hex << std::setfill('0') << + std::setw(2) << (int) m_uuid.uuid.uuid128[15] << + std::setw(2) << (int) m_uuid.uuid.uuid128[14] << + std::setw(2) << (int) m_uuid.uuid.uuid128[13] << + std::setw(2) << (int) m_uuid.uuid.uuid128[12] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[11] << + std::setw(2) << (int) m_uuid.uuid.uuid128[10] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[9] << + std::setw(2) << (int) m_uuid.uuid.uuid128[8] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[7] << + std::setw(2) << (int) m_uuid.uuid.uuid128[6] << "-" << + std::setw(2) << (int) m_uuid.uuid.uuid128[5] << + std::setw(2) << (int) m_uuid.uuid.uuid128[4] << + std::setw(2) << (int) m_uuid.uuid.uuid128[3] << + std::setw(2) << (int) m_uuid.uuid.uuid128[2] << + std::setw(2) << (int) m_uuid.uuid.uuid128[1] << + std::setw(2) << (int) m_uuid.uuid.uuid128[0]; + return ss.str(); +} // toString + +#endif /* CONFIG_BT_ENABLED */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEUUID.h b/libraries/ESP32_BLE_Arduino/src/BLEUUID.h new file mode 100644 index 0000000..700739b --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEUUID.h @@ -0,0 +1,39 @@ +/* + * BLEUUID.h + * + * Created on: Jun 21, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEUUID_H_ +#define COMPONENTS_CPP_UTILS_BLEUUID_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include + +/** + * @brief A model of a %BLE UUID. + */ +class BLEUUID { +public: + BLEUUID(std::string uuid); + BLEUUID(uint16_t uuid); + BLEUUID(uint32_t uuid); + BLEUUID(esp_bt_uuid_t uuid); + BLEUUID(uint8_t* pData, size_t size, bool msbFirst); + BLEUUID(esp_gatt_id_t gattId); + BLEUUID(); + uint8_t bitSize(); // Get the number of bits in this uuid. + bool equals(BLEUUID uuid); + esp_bt_uuid_t* getNative(); + BLEUUID to128(); + std::string toString(); + static BLEUUID fromString(std::string uuid); // Create a BLEUUID from a string + +private: + esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. + bool m_valueSet = false; // Is there a value set for this instance. +}; // BLEUUID +#endif /* CONFIG_BT_ENABLED */ +#endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEUtils.cpp b/libraries/ESP32_BLE_Arduino/src/BLEUtils.cpp new file mode 100644 index 0000000..5cd55f9 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEUtils.cpp @@ -0,0 +1,2033 @@ +/* + * BLEUtils.cpp + * + * Created on: Mar 25, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include "BLEAddress.h" +#include "BLEClient.h" +#include "BLEUtils.h" +#include "BLEUUID.h" +#include "GeneralUtils.h" + +#include +#include +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 ESP-IDF +#include // Part of C++ STL +#include +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "BLEUtils"; // Tag for logging. +#endif + + +/* +static std::map g_addressMap; +static std::map g_connIdMap; +*/ + +typedef struct { + uint32_t assignedNumber; + const char* name; +} member_t; + +static const member_t members_ids[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + {0xFE08, "Microsoft"}, + {0xFE09, "Pillsy, Inc."}, + {0xFE0A, "ruwido austria gmbh"}, + {0xFE0B, "ruwido austria gmbh"}, + {0xFE0C, "Procter & Gamble"}, + {0xFE0D, "Procter & Gamble"}, + {0xFE0E, "Setec Pty Ltd"}, + {0xFE0F, "Philips Lighting B.V."}, + {0xFE10, "Lapis Semiconductor Co., Ltd."}, + {0xFE11, "GMC-I Messtechnik GmbH"}, + {0xFE12, "M-Way Solutions GmbH"}, + {0xFE13, "Apple Inc."}, + {0xFE14, "Flextronics International USA Inc."}, + {0xFE15, "Amazon Fulfillment Services, Inc."}, + {0xFE16, "Footmarks, Inc."}, + {0xFE17, "Telit Wireless Solutions GmbH"}, + {0xFE18, "Runtime, Inc."}, + {0xFE19, "Google Inc."}, + {0xFE1A, "Tyto Life LLC"}, + {0xFE1B, "Tyto Life LLC"}, + {0xFE1C, "NetMedia, Inc."}, + {0xFE1D, "Illuminati Instrument Corporation"}, + {0xFE1E, "Smart Innovations Co., Ltd"}, + {0xFE1F, "Garmin International, Inc."}, + {0xFE20, "Emerson"}, + {0xFE21, "Bose Corporation"}, + {0xFE22, "Zoll Medical Corporation"}, + {0xFE23, "Zoll Medical Corporation"}, + {0xFE24, "August Home Inc"}, + {0xFE25, "Apple, Inc. "}, + {0xFE26, "Google Inc."}, + {0xFE27, "Google Inc."}, + {0xFE28, "Ayla Networks"}, + {0xFE29, "Gibson Innovations"}, + {0xFE2A, "DaisyWorks, Inc."}, + {0xFE2B, "ITT Industries"}, + {0xFE2C, "Google Inc."}, + {0xFE2D, "SMART INNOVATION Co.,Ltd"}, + {0xFE2E, "ERi,Inc."}, + {0xFE2F, "CRESCO Wireless, Inc"}, + {0xFE30, "Volkswagen AG"}, + {0xFE31, "Volkswagen AG"}, + {0xFE32, "Pro-Mark, Inc."}, + {0xFE33, "CHIPOLO d.o.o."}, + {0xFE34, "SmallLoop LLC"}, + {0xFE35, "HUAWEI Technologies Co., Ltd"}, + {0xFE36, "HUAWEI Technologies Co., Ltd"}, + {0xFE37, "Spaceek LTD"}, + {0xFE38, "Spaceek LTD"}, + {0xFE39, "TTS Tooltechnic Systems AG & Co. KG"}, + {0xFE3A, "TTS Tooltechnic Systems AG & Co. KG"}, + {0xFE3B, "Dolby Laboratories"}, + {0xFE3C, "Alibaba"}, + {0xFE3D, "BD Medical"}, + {0xFE3E, "BD Medical"}, + {0xFE3F, "Friday Labs Limited"}, + {0xFE40, "Inugo Systems Limited"}, + {0xFE41, "Inugo Systems Limited"}, + {0xFE42, "Nets A/S "}, + {0xFE43, "Andreas Stihl AG & Co. KG"}, + {0xFE44, "SK Telecom "}, + {0xFE45, "Snapchat Inc"}, + {0xFE46, "B&O Play A/S "}, + {0xFE47, "General Motors"}, + {0xFE48, "General Motors"}, + {0xFE49, "SenionLab AB"}, + {0xFE4A, "OMRON HEALTHCARE Co., Ltd."}, + {0xFE4B, "Philips Lighting B.V."}, + {0xFE4C, "Volkswagen AG"}, + {0xFE4D, "Casambi Technologies Oy"}, + {0xFE4E, "NTT docomo"}, + {0xFE4F, "Molekule, Inc."}, + {0xFE50, "Google Inc."}, + {0xFE51, "SRAM"}, + {0xFE52, "SetPoint Medical"}, + {0xFE53, "3M"}, + {0xFE54, "Motiv, Inc."}, + {0xFE55, "Google Inc."}, + {0xFE56, "Google Inc."}, + {0xFE57, "Dotted Labs"}, + {0xFE58, "Nordic Semiconductor ASA"}, + {0xFE59, "Nordic Semiconductor ASA"}, + {0xFE5A, "Chronologics Corporation"}, + {0xFE5B, "GT-tronics HK Ltd"}, + {0xFE5C, "million hunters GmbH"}, + {0xFE5D, "Grundfos A/S"}, + {0xFE5E, "Plastc Corporation"}, + {0xFE5F, "Eyefi, Inc."}, + {0xFE60, "Lierda Science & Technology Group Co., Ltd."}, + {0xFE61, "Logitech International SA"}, + {0xFE62, "Indagem Tech LLC"}, + {0xFE63, "Connected Yard, Inc."}, + {0xFE64, "Siemens AG"}, + {0xFE65, "CHIPOLO d.o.o."}, + {0xFE66, "Intel Corporation"}, + {0xFE67, "Lab Sensor Solutions"}, + {0xFE68, "Qualcomm Life Inc"}, + {0xFE69, "Qualcomm Life Inc"}, + {0xFE6A, "Kontakt Micro-Location Sp. z o.o."}, + {0xFE6B, "TASER International, Inc."}, + {0xFE6C, "TASER International, Inc."}, + {0xFE6D, "The University of Tokyo"}, + {0xFE6E, "The University of Tokyo"}, + {0xFE6F, "LINE Corporation"}, + {0xFE70, "Beijing Jingdong Century Trading Co., Ltd."}, + {0xFE71, "Plume Design Inc"}, + {0xFE72, "St. Jude Medical, Inc."}, + {0xFE73, "St. Jude Medical, Inc."}, + {0xFE74, "unwire"}, + {0xFE75, "TangoMe"}, + {0xFE76, "TangoMe"}, + {0xFE77, "Hewlett-Packard Company"}, + {0xFE78, "Hewlett-Packard Company"}, + {0xFE79, "Zebra Technologies"}, + {0xFE7A, "Bragi GmbH"}, + {0xFE7B, "Orion Labs, Inc."}, + {0xFE7C, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)"}, + {0xFE7D, "Aterica Health Inc."}, + {0xFE7E, "Awear Solutions Ltd"}, + {0xFE7F, "Doppler Lab"}, + {0xFE80, "Doppler Lab"}, + {0xFE81, "Medtronic Inc."}, + {0xFE82, "Medtronic Inc."}, + {0xFE83, "Blue Bite"}, + {0xFE84, "RF Digital Corp"}, + {0xFE85, "RF Digital Corp"}, + {0xFE86, "HUAWEI Technologies Co., Ltd. ( )"}, + {0xFE87, "Qingdao Yeelink Information Technology Co., Ltd. ( )"}, + {0xFE88, "SALTO SYSTEMS S.L."}, + {0xFE89, "B&O Play A/S"}, + {0xFE8A, "Apple, Inc."}, + {0xFE8B, "Apple, Inc."}, + {0xFE8C, "TRON Forum"}, + {0xFE8D, "Interaxon Inc."}, + {0xFE8E, "ARM Ltd"}, + {0xFE8F, "CSR"}, + {0xFE90, "JUMA"}, + {0xFE91, "Shanghai Imilab Technology Co.,Ltd"}, + {0xFE92, "Jarden Safety & Security"}, + {0xFE93, "OttoQ Inc."}, + {0xFE94, "OttoQ Inc."}, + {0xFE95, "Xiaomi Inc."}, + {0xFE96, "Tesla Motor Inc."}, + {0xFE97, "Tesla Motor Inc."}, + {0xFE98, "Currant, Inc."}, + {0xFE99, "Currant, Inc."}, + {0xFE9A, "Estimote"}, + {0xFE9B, "Samsara Networks, Inc"}, + {0xFE9C, "GSI Laboratories, Inc."}, + {0xFE9D, "Mobiquity Networks Inc"}, + {0xFE9E, "Dialog Semiconductor B.V."}, + {0xFE9F, "Google Inc."}, + {0xFEA0, "Google Inc."}, + {0xFEA1, "Intrepid Control Systems, Inc."}, + {0xFEA2, "Intrepid Control Systems, Inc."}, + {0xFEA3, "ITT Industries"}, + {0xFEA4, "Paxton Access Ltd"}, + {0xFEA5, "GoPro, Inc."}, + {0xFEA6, "GoPro, Inc."}, + {0xFEA7, "UTC Fire and Security"}, + {0xFEA8, "Savant Systems LLC"}, + {0xFEA9, "Savant Systems LLC"}, + {0xFEAA, "Google Inc."}, + {0xFEAB, "Nokia Corporation"}, + {0xFEAC, "Nokia Corporation"}, + {0xFEAD, "Nokia Corporation"}, + {0xFEAE, "Nokia Corporation"}, + {0xFEAF, "Nest Labs Inc."}, + {0xFEB0, "Nest Labs Inc."}, + {0xFEB1, "Electronics Tomorrow Limited"}, + {0xFEB2, "Microsoft Corporation"}, + {0xFEB3, "Taobao"}, + {0xFEB4, "WiSilica Inc."}, + {0xFEB5, "WiSilica Inc."}, + {0xFEB6, "Vencer Co, Ltd"}, + {0xFEB7, "Facebook, Inc."}, + {0xFEB8, "Facebook, Inc."}, + {0xFEB9, "LG Electronics"}, + {0xFEBA, "Tencent Holdings Limited"}, + {0xFEBB, "adafruit industries"}, + {0xFEBC, "Dexcom, Inc. "}, + {0xFEBD, "Clover Network, Inc."}, + {0xFEBE, "Bose Corporation"}, + {0xFEBF, "Nod, Inc."}, + {0xFEC0, "KDDI Corporation"}, + {0xFEC1, "KDDI Corporation"}, + {0xFEC2, "Blue Spark Technologies, Inc."}, + {0xFEC3, "360fly, Inc."}, + {0xFEC4, "PLUS Location Systems"}, + {0xFEC5, "Realtek Semiconductor Corp."}, + {0xFEC6, "Kocomojo, LLC"}, + {0xFEC7, "Apple, Inc."}, + {0xFEC8, "Apple, Inc."}, + {0xFEC9, "Apple, Inc."}, + {0xFECA, "Apple, Inc."}, + {0xFECB, "Apple, Inc."}, + {0xFECC, "Apple, Inc."}, + {0xFECD, "Apple, Inc."}, + {0xFECE, "Apple, Inc."}, + {0xFECF, "Apple, Inc."}, + {0xFED0, "Apple, Inc."}, + {0xFED1, "Apple, Inc."}, + {0xFED2, "Apple, Inc."}, + {0xFED3, "Apple, Inc."}, + {0xFED4, "Apple, Inc."}, + {0xFED5, "Plantronics Inc."}, + {0xFED6, "Broadcom Corporation"}, + {0xFED7, "Broadcom Corporation"}, + {0xFED8, "Google Inc."}, + {0xFED9, "Pebble Technology Corporation"}, + {0xFEDA, "ISSC Technologies Corporation"}, + {0xFEDB, "Perka, Inc."}, + {0xFEDC, "Jawbone"}, + {0xFEDD, "Jawbone"}, + {0xFEDE, "Coin, Inc."}, + {0xFEDF, "Design SHIFT"}, + {0xFEE0, "Anhui Huami Information Technology Co."}, + {0xFEE1, "Anhui Huami Information Technology Co."}, + {0xFEE2, "Anki, Inc."}, + {0xFEE3, "Anki, Inc."}, + {0xFEE4, "Nordic Semiconductor ASA"}, + {0xFEE5, "Nordic Semiconductor ASA"}, + {0xFEE6, "Silvair, Inc."}, + {0xFEE7, "Tencent Holdings Limited"}, + {0xFEE8, "Quintic Corp."}, + {0xFEE9, "Quintic Corp."}, + {0xFEEA, "Swirl Networks, Inc."}, + {0xFEEB, "Swirl Networks, Inc."}, + {0xFEEC, "Tile, Inc."}, + {0xFEED, "Tile, Inc."}, + {0xFEEE, "Polar Electro Oy"}, + {0xFEEF, "Polar Electro Oy"}, + {0xFEF0, "Intel"}, + {0xFEF1, "CSR"}, + {0xFEF2, "CSR"}, + {0xFEF3, "Google Inc."}, + {0xFEF4, "Google Inc."}, + {0xFEF5, "Dialog Semiconductor GmbH"}, + {0xFEF6, "Wicentric, Inc."}, + {0xFEF7, "Aplix Corporation"}, + {0xFEF8, "Aplix Corporation"}, + {0xFEF9, "PayPal, Inc."}, + {0xFEFA, "PayPal, Inc."}, + {0xFEFB, "Telit Wireless Solutions (Formerly Stollmann E+V GmbH)"}, + {0xFEFC, "Gimbal, Inc."}, + {0xFEFD, "Gimbal, Inc."}, + {0xFEFE, "GN ReSound A/S"}, + {0xFEFF, "GN Netcom"}, + {0xFFFF, "Reserved"}, /*for testing purposes only*/ +#endif + {0, "" } +}; + +typedef struct { + uint32_t assignedNumber; + const char* name; +} gattdescriptor_t; + +static const gattdescriptor_t g_descriptor_ids[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + {0x2905,"Characteristic Aggregate Format"}, + {0x2900,"Characteristic Extended Properties"}, + {0x2904,"Characteristic Presentation Format"}, + {0x2901,"Characteristic User Description"}, + {0x2902,"Client Characteristic Configuration"}, + {0x290B,"Environmental Sensing Configuration"}, + {0x290C,"Environmental Sensing Measurement"}, + {0x290D,"Environmental Sensing Trigger Setting"}, + {0x2907,"External Report Reference"}, + {0x2909,"Number of Digitals"}, + {0x2908,"Report Reference"}, + {0x2903,"Server Characteristic Configuration"}, + {0x290E,"Time Trigger Setting"}, + {0x2906,"Valid Range"}, + {0x290A,"Value Trigger Setting"}, +#endif + { 0, "" } +}; + +typedef struct { + uint32_t assignedNumber; + const char* name; +} characteristicMap_t; + +static const characteristicMap_t g_characteristicsMappings[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + {0x2A7E,"Aerobic Heart Rate Lower Limit"}, + {0x2A84,"Aerobic Heart Rate Upper Limit"}, + {0x2A7F,"Aerobic Threshold"}, + {0x2A80,"Age"}, + {0x2A5A,"Aggregate"}, + {0x2A43,"Alert Category ID"}, + {0x2A42,"Alert Category ID Bit Mask"}, + {0x2A06,"Alert Level"}, + {0x2A44,"Alert Notification Control Point"}, + {0x2A3F,"Alert Status"}, + {0x2AB3,"Altitude"}, + {0x2A81,"Anaerobic Heart Rate Lower Limit"}, + {0x2A82,"Anaerobic Heart Rate Upper Limit"}, + {0x2A83,"Anaerobic Threshold"}, + {0x2A58,"Analog"}, + {0x2A59,"Analog Output"}, + {0x2A73,"Apparent Wind Direction"}, + {0x2A72,"Apparent Wind Speed"}, + {0x2A01,"Appearance"}, + {0x2AA3,"Barometric Pressure Trend"}, + {0x2A19,"Battery Level"}, + {0x2A1B,"Battery Level State"}, + {0x2A1A,"Battery Power State"}, + {0x2A49,"Blood Pressure Feature"}, + {0x2A35,"Blood Pressure Measurement"}, + {0x2A9B,"Body Composition Feature"}, + {0x2A9C,"Body Composition Measurement"}, + {0x2A38,"Body Sensor Location"}, + {0x2AA4,"Bond Management Control Point"}, + {0x2AA5,"Bond Management Features"}, + {0x2A22,"Boot Keyboard Input Report"}, + {0x2A32,"Boot Keyboard Output Report"}, + {0x2A33,"Boot Mouse Input Report"}, + {0x2AA6,"Central Address Resolution"}, + {0x2AA8,"CGM Feature"}, + {0x2AA7,"CGM Measurement"}, + {0x2AAB,"CGM Session Run Time"}, + {0x2AAA,"CGM Session Start Time"}, + {0x2AAC,"CGM Specific Ops Control Point"}, + {0x2AA9,"CGM Status"}, + {0x2ACE,"Cross Trainer Data"}, + {0x2A5C,"CSC Feature"}, + {0x2A5B,"CSC Measurement"}, + {0x2A2B,"Current Time"}, + {0x2A66,"Cycling Power Control Point"}, + {0x2A66,"Cycling Power Control Point"}, + {0x2A65,"Cycling Power Feature"}, + {0x2A65,"Cycling Power Feature"}, + {0x2A63,"Cycling Power Measurement"}, + {0x2A64,"Cycling Power Vector"}, + {0x2A99,"Database Change Increment"}, + {0x2A85,"Date of Birth"}, + {0x2A86,"Date of Threshold Assessment"}, + {0x2A08,"Date Time"}, + {0x2A0A,"Day Date Time"}, + {0x2A09,"Day of Week"}, + {0x2A7D,"Descriptor Value Changed"}, + {0x2A00,"Device Name"}, + {0x2A7B,"Dew Point"}, + {0x2A56,"Digital"}, + {0x2A57,"Digital Output"}, + {0x2A0D,"DST Offset"}, + {0x2A6C,"Elevation"}, + {0x2A87,"Email Address"}, + {0x2A0B,"Exact Time 100"}, + {0x2A0C,"Exact Time 256"}, + {0x2A88,"Fat Burn Heart Rate Lower Limit"}, + {0x2A89,"Fat Burn Heart Rate Upper Limit"}, + {0x2A26,"Firmware Revision String"}, + {0x2A8A,"First Name"}, + {0x2AD9,"Fitness Machine Control Point"}, + {0x2ACC,"Fitness Machine Feature"}, + {0x2ADA,"Fitness Machine Status"}, + {0x2A8B,"Five Zone Heart Rate Limits"}, + {0x2AB2,"Floor Number"}, + {0x2A8C,"Gender"}, + {0x2A51,"Glucose Feature"}, + {0x2A18,"Glucose Measurement"}, + {0x2A34,"Glucose Measurement Context"}, + {0x2A74,"Gust Factor"}, + {0x2A27,"Hardware Revision String"}, + {0x2A39,"Heart Rate Control Point"}, + {0x2A8D,"Heart Rate Max"}, + {0x2A37,"Heart Rate Measurement"}, + {0x2A7A,"Heat Index"}, + {0x2A8E,"Height"}, + {0x2A4C,"HID Control Point"}, + {0x2A4A,"HID Information"}, + {0x2A8F,"Hip Circumference"}, + {0x2ABA,"HTTP Control Point"}, + {0x2AB9,"HTTP Entity Body"}, + {0x2AB7,"HTTP Headers"}, + {0x2AB8,"HTTP Status Code"}, + {0x2ABB,"HTTPS Security"}, + {0x2A6F,"Humidity"}, + {0x2A2A,"IEEE 11073-20601 Regulatory Certification Data List"}, + {0x2AD2,"Indoor Bike Data"}, + {0x2AAD,"Indoor Positioning Configuration"}, + {0x2A36,"Intermediate Cuff Pressure"}, + {0x2A1E,"Intermediate Temperature"}, + {0x2A77,"Irradiance"}, + {0x2AA2,"Language"}, + {0x2A90,"Last Name"}, + {0x2AAE,"Latitude"}, + {0x2A6B,"LN Control Point"}, + {0x2A6A,"LN Feature"}, + {0x2AB1,"Local East Coordinate"}, + {0x2AB0,"Local North Coordinate"}, + {0x2A0F,"Local Time Information"}, + {0x2A67,"Location and Speed Characteristic"}, + {0x2AB5,"Location Name"}, + {0x2AAF,"Longitude"}, + {0x2A2C,"Magnetic Declination"}, + {0x2AA0,"Magnetic Flux Density - 2D"}, + {0x2AA1,"Magnetic Flux Density - 3D"}, + {0x2A29,"Manufacturer Name String"}, + {0x2A91,"Maximum Recommended Heart Rate"}, + {0x2A21,"Measurement Interval"}, + {0x2A24,"Model Number String"}, + {0x2A68,"Navigation"}, + {0x2A3E,"Network Availability"}, + {0x2A46,"New Alert"}, + {0x2AC5,"Object Action Control Point"}, + {0x2AC8,"Object Changed"}, + {0x2AC1,"Object First-Created"}, + {0x2AC3,"Object ID"}, + {0x2AC2,"Object Last-Modified"}, + {0x2AC6,"Object List Control Point"}, + {0x2AC7,"Object List Filter"}, + {0x2ABE,"Object Name"}, + {0x2AC4,"Object Properties"}, + {0x2AC0,"Object Size"}, + {0x2ABF,"Object Type"}, + {0x2ABD,"OTS Feature"}, + {0x2A04,"Peripheral Preferred Connection Parameters"}, + {0x2A02,"Peripheral Privacy Flag"}, + {0x2A5F,"PLX Continuous Measurement Characteristic"}, + {0x2A60,"PLX Features"}, + {0x2A5E,"PLX Spot-Check Measurement"}, + {0x2A50,"PnP ID"}, + {0x2A75,"Pollen Concentration"}, + {0x2A2F,"Position 2D"}, + {0x2A30,"Position 3D"}, + {0x2A69,"Position Quality"}, + {0x2A6D,"Pressure"}, + {0x2A4E,"Protocol Mode"}, + {0x2A62,"Pulse Oximetry Control Point"}, + {0x2A60,"Pulse Oximetry Pulsatile Event Characteristic"}, + {0x2A78,"Rainfall"}, + {0x2A03,"Reconnection Address"}, + {0x2A52,"Record Access Control Point"}, + {0x2A14,"Reference Time Information"}, + {0x2A3A,"Removable"}, + {0x2A4D,"Report"}, + {0x2A4B,"Report Map"}, + {0x2AC9,"Resolvable Private Address Only"}, + {0x2A92,"Resting Heart Rate"}, + {0x2A40,"Ringer Control point"}, + {0x2A41,"Ringer Setting"}, + {0x2AD1,"Rower Data"}, + {0x2A54,"RSC Feature"}, + {0x2A53,"RSC Measurement"}, + {0x2A55,"SC Control Point"}, + {0x2A4F,"Scan Interval Window"}, + {0x2A31,"Scan Refresh"}, + {0x2A3C,"Scientific Temperature Celsius"}, + {0x2A10,"Secondary Time Zone"}, + {0x2A5D,"Sensor Location"}, + {0x2A25,"Serial Number String"}, + {0x2A05,"Service Changed"}, + {0x2A3B,"Service Required"}, + {0x2A28,"Software Revision String"}, + {0x2A93,"Sport Type for Aerobic and Anaerobic Thresholds"}, + {0x2AD0,"Stair Climber Data"}, + {0x2ACF,"Step Climber Data"}, + {0x2A3D,"String"}, + {0x2AD7,"Supported Heart Rate Range"}, + {0x2AD5,"Supported Inclination Range"}, + {0x2A47,"Supported New Alert Category"}, + {0x2AD8,"Supported Power Range"}, + {0x2AD6,"Supported Resistance Level Range"}, + {0x2AD4,"Supported Speed Range"}, + {0x2A48,"Supported Unread Alert Category"}, + {0x2A23,"System ID"}, + {0x2ABC,"TDS Control Point"}, + {0x2A6E,"Temperature"}, + {0x2A1F,"Temperature Celsius"}, + {0x2A20,"Temperature Fahrenheit"}, + {0x2A1C,"Temperature Measurement"}, + {0x2A1D,"Temperature Type"}, + {0x2A94,"Three Zone Heart Rate Limits"}, + {0x2A12,"Time Accuracy"}, + {0x2A15,"Time Broadcast"}, + {0x2A13,"Time Source"}, + {0x2A16,"Time Update Control Point"}, + {0x2A17,"Time Update State"}, + {0x2A11,"Time with DST"}, + {0x2A0E,"Time Zone"}, + {0x2AD3,"Training Status"}, + {0x2ACD,"Treadmill Data"}, + {0x2A71,"True Wind Direction"}, + {0x2A70,"True Wind Speed"}, + {0x2A95,"Two Zone Heart Rate Limit"}, + {0x2A07,"Tx Power Level"}, + {0x2AB4,"Uncertainty"}, + {0x2A45,"Unread Alert Status"}, + {0x2AB6,"URI"}, + {0x2A9F,"User Control Point"}, + {0x2A9A,"User Index"}, + {0x2A76,"UV Index"}, + {0x2A96,"VO2 Max"}, + {0x2A97,"Waist Circumference"}, + {0x2A98,"Weight"}, + {0x2A9D,"Weight Measurement"}, + {0x2A9E,"Weight Scale Feature"}, + {0x2A79,"Wind Chill"}, +#endif + {0, ""} +}; + +/** + * @brief Mapping from service ids to names + */ +typedef struct { + const char* name; + const char* type; + uint32_t assignedNumber; +} gattService_t; + + +/** + * Definition of the service ids to names that we know about. + */ +static const gattService_t g_gattServices[] = { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + {"Alert Notification Service", "org.bluetooth.service.alert_notification", 0x1811}, + {"Automation IO", "org.bluetooth.service.automation_io", 0x1815 }, + {"Battery Service","org.bluetooth.service.battery_service", 0x180F}, + {"Blood Pressure", "org.bluetooth.service.blood_pressure", 0x1810}, + {"Body Composition", "org.bluetooth.service.body_composition", 0x181B}, + {"Bond Management", "org.bluetooth.service.bond_management", 0x181E}, + {"Continuous Glucose Monitoring", "org.bluetooth.service.continuous_glucose_monitoring", 0x181F}, + {"Current Time Service", "org.bluetooth.service.current_time", 0x1805}, + {"Cycling Power", "org.bluetooth.service.cycling_power", 0x1818}, + {"Cycling Speed and Cadence", "org.bluetooth.service.cycling_speed_and_cadence", 0x1816}, + {"Device Information", "org.bluetooth.service.device_information", 0x180A}, + {"Environmental Sensing", "org.bluetooth.service.environmental_sensing", 0x181A}, + {"Generic Access", "org.bluetooth.service.generic_access", 0x1800}, + {"Generic Attribute", "org.bluetooth.service.generic_attribute", 0x1801}, + {"Glucose", "org.bluetooth.service.glucose", 0x1808}, + {"Health Thermometer", "org.bluetooth.service.health_thermometer", 0x1809}, + {"Heart Rate", "org.bluetooth.service.heart_rate", 0x180D}, + {"HTTP Proxy", "org.bluetooth.service.http_proxy", 0x1823}, + {"Human Interface Device", "org.bluetooth.service.human_interface_device", 0x1812}, + {"Immediate Alert", "org.bluetooth.service.immediate_alert", 0x1802}, + {"Indoor Positioning", "org.bluetooth.service.indoor_positioning", 0x1821}, + {"Internet Protocol Support", "org.bluetooth.service.internet_protocol_support", 0x1820}, + {"Link Loss", "org.bluetooth.service.link_loss", 0x1803}, + {"Location and Navigation", "org.bluetooth.service.location_and_navigation", 0x1819}, + {"Next DST Change Service", "org.bluetooth.service.next_dst_change", 0x1807}, + {"Object Transfer", "org.bluetooth.service.object_transfer", 0x1825}, + {"Phone Alert Status Service", "org.bluetooth.service.phone_alert_status", 0x180E}, + {"Pulse Oximeter", "org.bluetooth.service.pulse_oximeter", 0x1822}, + {"Reference Time Update Service", "org.bluetooth.service.reference_time_update", 0x1806}, + {"Running Speed and Cadence", "org.bluetooth.service.running_speed_and_cadence", 0x1814}, + {"Scan Parameters", "org.bluetooth.service.scan_parameters", 0x1813}, + {"Transport Discovery", "org.bluetooth.service.transport_discovery", 0x1824}, + {"Tx Power", "org.bluetooth.service.tx_power", 0x1804}, + {"User Data", "org.bluetooth.service.user_data", 0x181C}, + {"Weight Scale", "org.bluetooth.service.weight_scale", 0x181D}, +#endif + {"", "", 0 } +}; + + +/** + * @brief Convert characteristic properties into a string representation. + * @param [in] prop Characteristic properties. + * @return A string representation of characteristic properties. + */ +std::string BLEUtils::characteristicPropertiesToString(esp_gatt_char_prop_t prop) { + std::stringstream stream; + stream << + "broadcast: " << ((prop & ESP_GATT_CHAR_PROP_BIT_BROADCAST)?"1":"0") << + ", read: " << ((prop & ESP_GATT_CHAR_PROP_BIT_READ)?"1":"0") << + ", write_nr: " << ((prop & ESP_GATT_CHAR_PROP_BIT_WRITE_NR)?"1":"0") << + ", write: " << ((prop & ESP_GATT_CHAR_PROP_BIT_WRITE)?"1":"0") << + ", notify: " << ((prop & ESP_GATT_CHAR_PROP_BIT_NOTIFY)?"1":"0") << + ", indicate: " << ((prop & ESP_GATT_CHAR_PROP_BIT_INDICATE)?"1":"0") << + ", auth: " << ((prop & ESP_GATT_CHAR_PROP_BIT_AUTH)?"1":"0"); + return stream.str(); +} // characteristicPropertiesToString + +/** + * @brief Convert an esp_gatt_id_t to a string. + */ +static std::string gattIdToString(esp_gatt_id_t gattId) { + std::stringstream stream; + stream << "uuid: " << BLEUUID(gattId.uuid).toString() << ", inst_id: " << (int)gattId.inst_id; + //sprintf(buffer, "uuid: %s, inst_id: %d", uuidToString(gattId.uuid).c_str(), gattId.inst_id); + return stream.str(); +} // gattIdToString + + +/** + * @brief Convert an esp_ble_addr_type_t to a string representation. + */ +const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { + switch (type) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case BLE_ADDR_TYPE_PUBLIC: + return "BLE_ADDR_TYPE_PUBLIC"; + case BLE_ADDR_TYPE_RANDOM: + return "BLE_ADDR_TYPE_RANDOM"; + case BLE_ADDR_TYPE_RPA_PUBLIC: + return "BLE_ADDR_TYPE_RPA_PUBLIC"; + case BLE_ADDR_TYPE_RPA_RANDOM: + return "BLE_ADDR_TYPE_RPA_RANDOM"; +#endif + default: + return " esp_ble_addr_type_t"; + } +} // addressTypeToString + + +/** + * @brief Convert the BLE Advertising Data flags to a string. + * @param adFlags The flags to convert + * @return std::string A string representation of the advertising flags. + */ +std::string BLEUtils::adFlagsToString(uint8_t adFlags) { + std::stringstream ss; + if (adFlags & (1 << 0)) { + ss << "[LE Limited Discoverable Mode] "; + } + if (adFlags & (1 << 1)) { + ss << "[LE General Discoverable Mode] "; + } + if (adFlags & (1 << 2)) { + ss << "[BR/EDR Not Supported] "; + } + if (adFlags & (1 << 3)) { + ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Controller)] "; + } + if (adFlags & (1 << 4)) { + ss << "[Simultaneous LE and BR/EDR to Same Device Capable (Host)] "; + } + return ss.str(); +} // adFlagsToString + + +/** + * @brief Given an advertising type, return a string representation of the type. + * + * For details see ... + * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + * + * @return A string representation of the type. + */ +const char* BLEUtils::advTypeToString(uint8_t advType) { + switch (advType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_BLE_AD_TYPE_FLAG: // 0x01 + return "ESP_BLE_AD_TYPE_FLAG"; + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 + return "ESP_BLE_AD_TYPE_16SRV_PART"; + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 + return "ESP_BLE_AD_TYPE_16SRV_CMPL"; + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 + return "ESP_BLE_AD_TYPE_32SRV_PART"; + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 + return "ESP_BLE_AD_TYPE_32SRV_CMPL"; + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 + return "ESP_BLE_AD_TYPE_128SRV_PART"; + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 + return "ESP_BLE_AD_TYPE_128SRV_CMPL"; + case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 + return "ESP_BLE_AD_TYPE_NAME_SHORT"; + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 + return "ESP_BLE_AD_TYPE_NAME_CMPL"; + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a + return "ESP_BLE_AD_TYPE_TX_PWR"; + case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b + return "ESP_BLE_AD_TYPE_DEV_CLASS"; + case ESP_BLE_AD_TYPE_SM_TK: // 0x10 + return "ESP_BLE_AD_TYPE_SM_TK"; + case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 + return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; + case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 + return "ESP_BLE_AD_TYPE_INT_RANGE"; + case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 + return "ESP_BLE_AD_TYPE_SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 + return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 + return "ESP_BLE_AD_TYPE_SERVICE_DATA"; + case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 + return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; + case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 + return "ESP_BLE_AD_TYPE_RANDOM_TARGET"; + case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 + return "ESP_BLE_AD_TYPE_APPEARANCE"; + case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a + return "ESP_BLE_AD_TYPE_ADV_INT"; + case ESP_BLE_AD_TYPE_32SOL_SRV_UUID: + return "ESP_BLE_AD_TYPE_32SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 + return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 + return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff + return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; +#endif + default: + ESP_LOGV(LOG_TAG, " adv data type: 0x%x", advType); + return ""; + } // End switch +} // advTypeToString + + +esp_gatt_id_t BLEUtils::buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id) { + esp_gatt_id_t retGattId; + retGattId.uuid = uuid; + retGattId.inst_id = inst_id; + return retGattId; +} + +esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary) { + esp_gatt_srvc_id_t retSrvcId; + retSrvcId.id = gattId; + retSrvcId.is_primary = is_primary; + return retSrvcId; +} + +/** + * @brief Create a hex representation of data. + * + * @param [in] target Where to write the hex string. If this is null, we malloc storage. + * @param [in] source The start of the binary data. + * @param [in] length The length of the data to convert. + * @return A pointer to the formatted buffer. + */ +char* BLEUtils::buildHexData(uint8_t* target, uint8_t* source, uint8_t length) { + // Guard against too much data. + if (length > 100) length = 100; + + if (target == nullptr) { + target = (uint8_t*) malloc(length * 2 + 1); + if (target == nullptr) { + ESP_LOGE(LOG_TAG, "buildHexData: malloc failed"); + return nullptr; + } + } + char* startOfData = (char*) target; + + for (int i = 0; i < length; i++) { + sprintf((char*) target, "%.2x", (char) *source); + source++; + target += 2; + } + + // Handle the special case where there was no data. + if (length == 0) { + *startOfData = 0; + } + + return startOfData; +} // buildHexData + + +/** + * @brief Build a printable string of memory range. + * Create a string representation of a piece of memory. Only printable characters will be included + * while those that are not printable will be replaced with '.'. + * @param [in] source Start of memory. + * @param [in] length Length of memory. + * @return A string representation of a piece of memory. + */ +std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) { + std::ostringstream ss; + for (int i = 0; i < length; i++) { + char c = *source; + ss << (isprint(c) ? c : '.'); + source++; + } + return ss.str(); +} // buildPrintData + + +/** + * @brief Convert a close/disconnect reason to a string. + * @param [in] reason The close reason. + * @return A string representation of the reason. + */ +std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) { + switch (reason) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GATT_CONN_UNKNOWN: { + return "ESP_GATT_CONN_UNKNOWN"; + } + case ESP_GATT_CONN_L2C_FAILURE: { + return "ESP_GATT_CONN_L2C_FAILURE"; + } + case ESP_GATT_CONN_TIMEOUT: { + return "ESP_GATT_CONN_TIMEOUT"; + } + case ESP_GATT_CONN_TERMINATE_PEER_USER: { + return "ESP_GATT_CONN_TERMINATE_PEER_USER"; + } + case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: { + return "ESP_GATT_CONN_TERMINATE_LOCAL_HOST"; + } + case ESP_GATT_CONN_FAIL_ESTABLISH: { + return "ESP_GATT_CONN_FAIL_ESTABLISH"; + } + case ESP_GATT_CONN_LMP_TIMEOUT: { + return "ESP_GATT_CONN_LMP_TIMEOUT"; + } + case ESP_GATT_CONN_CONN_CANCEL: { + return "ESP_GATT_CONN_CONN_CANCEL"; + } + case ESP_GATT_CONN_NONE: { + return "ESP_GATT_CONN_NONE"; + } +#endif + default: { + return "Unknown"; + } + } +} // gattCloseReasonToString + + +std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType) { + switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GATTC_ACL_EVT: + return "ESP_GATTC_ACL_EVT"; + case ESP_GATTC_ADV_DATA_EVT: + return "ESP_GATTC_ADV_DATA_EVT"; + case ESP_GATTC_ADV_VSC_EVT: + return "ESP_GATTC_ADV_VSC_EVT"; + case ESP_GATTC_BTH_SCAN_CFG_EVT: + return "ESP_GATTC_BTH_SCAN_CFG_EVT"; + case ESP_GATTC_BTH_SCAN_DIS_EVT: + return "ESP_GATTC_BTH_SCAN_DIS_EVT"; + case ESP_GATTC_BTH_SCAN_ENB_EVT: + return "ESP_GATTC_BTH_SCAN_ENB_EVT"; + case ESP_GATTC_BTH_SCAN_PARAM_EVT: + return "ESP_GATTC_BTH_SCAN_PARAM_EVT"; + case ESP_GATTC_BTH_SCAN_RD_EVT: + return "ESP_GATTC_BTH_SCAN_RD_EVT"; + case ESP_GATTC_BTH_SCAN_THR_EVT: + return "ESP_GATTC_BTH_SCAN_THR_EVT"; + case ESP_GATTC_CANCEL_OPEN_EVT: + return "ESP_GATTC_CANCEL_OPEN_EVT"; + case ESP_GATTC_CFG_MTU_EVT: + return "ESP_GATTC_CFG_MTU_EVT"; + case ESP_GATTC_CLOSE_EVT: + return "ESP_GATTC_CLOSE_EVT"; + case ESP_GATTC_CONGEST_EVT: + return "ESP_GATTC_CONGEST_EVT"; + case ESP_GATTC_CONNECT_EVT: + return "ESP_GATTC_CONNECT_EVT"; + case ESP_GATTC_DISCONNECT_EVT: + return "ESP_GATTC_DISCONNECT_EVT"; + case ESP_GATTC_ENC_CMPL_CB_EVT: + return "ESP_GATTC_ENC_CMPL_CB_EVT"; + case ESP_GATTC_EXEC_EVT: + return "ESP_GATTC_EXEC_EVT"; + //case ESP_GATTC_GET_CHAR_EVT: +// return "ESP_GATTC_GET_CHAR_EVT"; + //case ESP_GATTC_GET_DESCR_EVT: +// return "ESP_GATTC_GET_DESCR_EVT"; + //case ESP_GATTC_GET_INCL_SRVC_EVT: +// return "ESP_GATTC_GET_INCL_SRVC_EVT"; + case ESP_GATTC_MULT_ADV_DATA_EVT: + return "ESP_GATTC_MULT_ADV_DATA_EVT"; + case ESP_GATTC_MULT_ADV_DIS_EVT: + return "ESP_GATTC_MULT_ADV_DIS_EVT"; + case ESP_GATTC_MULT_ADV_ENB_EVT: + return "ESP_GATTC_MULT_ADV_ENB_EVT"; + case ESP_GATTC_MULT_ADV_UPD_EVT: + return "ESP_GATTC_MULT_ADV_UPD_EVT"; + case ESP_GATTC_NOTIFY_EVT: + return "ESP_GATTC_NOTIFY_EVT"; + case ESP_GATTC_OPEN_EVT: + return "ESP_GATTC_OPEN_EVT"; + case ESP_GATTC_PREP_WRITE_EVT: + return "ESP_GATTC_PREP_WRITE_EVT"; + case ESP_GATTC_READ_CHAR_EVT: + return "ESP_GATTC_READ_CHAR_EVT"; + case ESP_GATTC_REG_EVT: + return "ESP_GATTC_REG_EVT"; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: + return "ESP_GATTC_REG_FOR_NOTIFY_EVT"; + case ESP_GATTC_SCAN_FLT_CFG_EVT: + return "ESP_GATTC_SCAN_FLT_CFG_EVT"; + case ESP_GATTC_SCAN_FLT_PARAM_EVT: + return "ESP_GATTC_SCAN_FLT_PARAM_EVT"; + case ESP_GATTC_SCAN_FLT_STATUS_EVT: + return "ESP_GATTC_SCAN_FLT_STATUS_EVT"; + case ESP_GATTC_SEARCH_CMPL_EVT: + return "ESP_GATTC_SEARCH_CMPL_EVT"; + case ESP_GATTC_SEARCH_RES_EVT: + return "ESP_GATTC_SEARCH_RES_EVT"; + case ESP_GATTC_SRVC_CHG_EVT: + return "ESP_GATTC_SRVC_CHG_EVT"; + case ESP_GATTC_READ_DESCR_EVT: + return "ESP_GATTC_READ_DESCR_EVT"; + case ESP_GATTC_UNREG_EVT: + return "ESP_GATTC_UNREG_EVT"; + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: + return "ESP_GATTC_UNREG_FOR_NOTIFY_EVT"; + case ESP_GATTC_WRITE_CHAR_EVT: + return "ESP_GATTC_WRITE_CHAR_EVT"; + case ESP_GATTC_WRITE_DESCR_EVT: + return "ESP_GATTC_WRITE_DESCR_EVT"; +#endif + default: + ESP_LOGV(LOG_TAG, "Unknown GATT Client event type: %d", eventType); + return "Unknown"; + } +} // gattClientEventTypeToString + + +/** + * @brief Return a string representation of a GATT server event code. + * @param [in] eventType A GATT server event code. + * @return A string representation of the GATT server event code. + */ +std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType) { + switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GATTS_REG_EVT: + return "ESP_GATTS_REG_EVT"; + case ESP_GATTS_READ_EVT: + return "ESP_GATTS_READ_EVT"; + case ESP_GATTS_WRITE_EVT: + return "ESP_GATTS_WRITE_EVT"; + case ESP_GATTS_EXEC_WRITE_EVT: + return "ESP_GATTS_EXEC_WRITE_EVT"; + case ESP_GATTS_MTU_EVT: + return "ESP_GATTS_MTU_EVT"; + case ESP_GATTS_CONF_EVT: + return "ESP_GATTS_CONF_EVT"; + case ESP_GATTS_UNREG_EVT: + return "ESP_GATTS_UNREG_EVT"; + case ESP_GATTS_CREATE_EVT: + return "ESP_GATTS_CREATE_EVT"; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + return "ESP_GATTS_ADD_INCL_SRVC_EVT"; + case ESP_GATTS_ADD_CHAR_EVT: + return "ESP_GATTS_ADD_CHAR_EVT"; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + return "ESP_GATTS_ADD_CHAR_DESCR_EVT"; + case ESP_GATTS_DELETE_EVT: + return "ESP_GATTS_DELETE_EVT"; + case ESP_GATTS_START_EVT: + return "ESP_GATTS_START_EVT"; + case ESP_GATTS_STOP_EVT: + return "ESP_GATTS_STOP_EVT"; + case ESP_GATTS_CONNECT_EVT: + return "ESP_GATTS_CONNECT_EVT"; + case ESP_GATTS_DISCONNECT_EVT: + return "ESP_GATTS_DISCONNECT_EVT"; + case ESP_GATTS_OPEN_EVT: + return "ESP_GATTS_OPEN_EVT"; + case ESP_GATTS_CANCEL_OPEN_EVT: + return "ESP_GATTS_CANCEL_OPEN_EVT"; + case ESP_GATTS_CLOSE_EVT: + return "ESP_GATTS_CLOSE_EVT"; + case ESP_GATTS_LISTEN_EVT: + return "ESP_GATTS_LISTEN_EVT"; + case ESP_GATTS_CONGEST_EVT: + return "ESP_GATTS_CONGEST_EVT"; + case ESP_GATTS_RESPONSE_EVT: + return "ESP_GATTS_RESPONSE_EVT"; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: + return "ESP_GATTS_CREAT_ATTR_TAB_EVT"; + case ESP_GATTS_SET_ATTR_VAL_EVT: + return "ESP_GATTS_SET_ATTR_VAL_EVT"; + case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: + return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; +#endif + default: + return "Unknown"; + } +} // gattServerEventTypeToString + + + +/** + * @brief Convert a BLE device type to a string. + * @param [in] type The device type. + */ +const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) { + switch (type) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_BT_DEVICE_TYPE_BREDR: + return "ESP_BT_DEVICE_TYPE_BREDR"; + case ESP_BT_DEVICE_TYPE_BLE: + return "ESP_BT_DEVICE_TYPE_BLE"; + case ESP_BT_DEVICE_TYPE_DUMO: + return "ESP_BT_DEVICE_TYPE_DUMO"; +#endif + default: + return "Unknown"; + } +} // devTypeToString + + +/** + * @brief Dump the GAP event to the log. + */ +void BLEUtils::dumpGapEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param) { + ESP_LOGV(LOG_TAG, "Received a GAP event: %s", gapEventToString(event)); + switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT + // adv_data_cmpl + // - esp_bt_status_t + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_data_cmpl.status); + break; + } // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT + + // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT + // + // adv_data_raw_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_data_raw_cmpl.status); + break; + } // ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT + + // ESP_GAP_BLE_ADV_START_COMPLETE_EVT + // + // adv_start_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_start_cmpl.status); + break; + } // ESP_GAP_BLE_ADV_START_COMPLETE_EVT + + // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT + // + // adv_stop_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->adv_stop_cmpl.status); + break; + } // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT + + // ESP_GAP_BLE_AUTH_CMPL_EVT + // + // auth_cmpl + // - esp_bd_addr_t bd_addr + // - bool key_present + // - esp_link_key key + // - bool success + // - uint8_t fail_reason + // - esp_bd_addr_type_t addr_type + // - esp_bt_dev_type_t dev_type + case ESP_GAP_BLE_AUTH_CMPL_EVT: { + ESP_LOGV(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", + BLEAddress(param->ble_security.auth_cmpl.bd_addr).toString().c_str(), + param->ble_security.auth_cmpl.key_present, + param->ble_security.auth_cmpl.key_type, + param->ble_security.auth_cmpl.success, + param->ble_security.auth_cmpl.fail_reason, + BLEUtils::devTypeToString(param->ble_security.auth_cmpl.dev_type) + ); + break; + } // ESP_GAP_BLE_AUTH_CMPL_EVT + + // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT + // + // clear_bond_dev_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->clear_bond_dev_cmpl.status); + break; + } // ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT + + // ESP_GAP_BLE_LOCAL_IR_EVT + case ESP_GAP_BLE_LOCAL_IR_EVT: { + break; + } // ESP_GAP_BLE_LOCAL_IR_EVT + + // ESP_GAP_BLE_LOCAL_ER_EVT + case ESP_GAP_BLE_LOCAL_ER_EVT: { + break; + } // ESP_GAP_BLE_LOCAL_ER_EVT + + // ESP_GAP_BLE_NC_REQ_EVT + case ESP_GAP_BLE_NC_REQ_EVT: { + ESP_LOGV(LOG_TAG, "[bd_addr: %s, passkey: %d]", + BLEAddress(param->ble_security.key_notif.bd_addr).toString().c_str(), + param->ble_security.key_notif.passkey); + break; + } // ESP_GAP_BLE_NC_REQ_EVT + + // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + // + // read_rssi_cmpl + // - esp_bt_status_t status + // - int8_t rssi + // - esp_bd_addr_t remote_addr + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d, rssi: %d, remote_addr: %s]", + param->read_rssi_cmpl.status, + param->read_rssi_cmpl.rssi, + BLEAddress(param->read_rssi_cmpl.remote_addr).toString().c_str() + ); + break; + } // ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT + + // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT + // + // scan_param_cmpl. + // - esp_bt_status_t status + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT + + // ESP_GAP_BLE_SCAN_RESULT_EVT + // + // scan_rst: + // - search_evt + // - bda + // - dev_type + // - ble_addr_type + // - ble_evt_type + // - rssi + // - ble_adv + // - flag + // - num_resps + // - adv_data_len + // - scan_rsp_len + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + switch (param->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: { + ESP_LOGV(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d (%s), num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", + searchEventTypeToString(param->scan_rst.search_evt), + BLEAddress(param->scan_rst.bda).toString().c_str(), + devTypeToString(param->scan_rst.dev_type), + addressTypeToString(param->scan_rst.ble_addr_type), + eventTypeToString(param->scan_rst.ble_evt_type), + param->scan_rst.rssi, + param->scan_rst.flag, + adFlagsToString(param->scan_rst.flag).c_str(), + param->scan_rst.num_resps, + param->scan_rst.adv_data_len, + param->scan_rst.scan_rsp_len + ); + break; + } // ESP_GAP_SEARCH_INQ_RES_EVT + + default: { + ESP_LOGV(LOG_TAG, "search_evt: %s",searchEventTypeToString(param->scan_rst.search_evt)); + break; + } + } + break; + } // ESP_GAP_BLE_SCAN_RESULT_EVT + + // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT + // + // scan_rsp_data_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT + + // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_rsp_data_raw_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT + + // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT + // + // scan_start_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT + + // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT + // + // scan_stop_cmpl + // - esp_bt_status_t status + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); + break; + } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT + + // ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT + // + // update_conn_params + // - esp_bt_status_t status + // - esp_bd_addr_t bda + // - uint16_t min_int + // - uint16_t max_int + // - uint16_t latency + // - uint16_t conn_int + // - uint16_t timeout + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: { + ESP_LOGV(LOG_TAG, "[status: %d, bd_addr: %s, min_int: %d, max_int: %d, latency: %d, conn_int: %d, timeout: %d]", + param->update_conn_params.status, + BLEAddress(param->update_conn_params.bda).toString().c_str(), + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.latency, + param->update_conn_params.conn_int, + param->update_conn_params.timeout + ); + break; + } // ESP_GAP_BLE_SCAN_UPDATE_CONN_PARAMS_EVT + + // ESP_GAP_BLE_SEC_REQ_EVT + case ESP_GAP_BLE_SEC_REQ_EVT: { + ESP_LOGV(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); + break; + } // ESP_GAP_BLE_SEC_REQ_EVT +#endif + default: { + ESP_LOGV(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); + break; + } // default + } // switch +} // dumpGapEvent + + +/** + * @brief Decode and dump a GATT client event + * + * @param [in] event The type of event received. + * @param [in] evtParam The data associated with the event. + */ +void BLEUtils::dumpGattClientEvent( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam) { + + //esp_ble_gattc_cb_param_t* evtParam = (esp_ble_gattc_cb_param_t*) param; + ESP_LOGV(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); + switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + // ESP_GATTC_CLOSE_EVT + // + // close: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - esp_gatt_conn_reason_t reason + case ESP_GATTC_CLOSE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]", + BLEUtils::gattStatusToString(evtParam->close.status).c_str(), + BLEUtils::gattCloseReasonToString(evtParam->close.reason).c_str(), + evtParam->close.conn_id); + break; + } + + // ESP_GATTC_CONNECT_EVT + // + // connect: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + evtParam->connect.conn_id, + BLEAddress(evtParam->connect.remote_bda).toString().c_str() + ); + break; + } + + // ESP_GATTC_DISCONNECT_EVT + // + // disconnect: + // - esp_gatt_conn_reason_t reason + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGV(LOG_TAG, "[reason: %s, conn_id: %d, remote_bda: %s]", + BLEUtils::gattCloseReasonToString(evtParam->disconnect.reason).c_str(), + evtParam->disconnect.conn_id, + BLEAddress(evtParam->disconnect.remote_bda).toString().c_str() + ); + break; + } // ESP_GATTC_DISCONNECT_EVT + + // ESP_GATTC_GET_CHAR_EVT + // + // get_char: + // - esp_gatt_status_t status + // - uin1t6_t conn_id + // - esp_gatt_srvc_id_t srvc_id + // - esp_gatt_id_t char_id + // - esp_gatt_char_prop_t char_prop + /* + case ESP_GATTC_GET_CHAR_EVT: { + + // If the status of the event shows that we have a value other than ESP_GATT_OK then the + // characteristic fields are not set to a usable value .. so don't try and log them. + if (evtParam->get_char.status == ESP_GATT_OK) { + std::string description = "Unknown"; + if (evtParam->get_char.char_id.uuid.len == ESP_UUID_LEN_16) { + description = BLEUtils::gattCharacteristicUUIDToString(evtParam->get_char.char_id.uuid.uuid.uuid16); + } + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s, char_id: %s [description: %s]\nchar_prop: %s]", + BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(), + evtParam->get_char.conn_id, + BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str(), + gattIdToString(evtParam->get_char.char_id).c_str(), + description.c_str(), + BLEUtils::characteristicPropertiesToString(evtParam->get_char.char_prop).c_str() + ); + } else { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s]", + BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(), + evtParam->get_char.conn_id, + BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str() + ); + } + break; + } // ESP_GATTC_GET_CHAR_EVT + */ + + // ESP_GATTC_NOTIFY_EVT + // + // notify + // uint16_t conn_id + // esp_bd_addr_t remote_bda + // handle handle + // uint16_t value_len + // uint8_t* value + // bool is_notify + // + case ESP_GATTC_NOTIFY_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s, handle: %d 0x%.2x, value_len: %d, is_notify: %d]", + evtParam->notify.conn_id, + BLEAddress(evtParam->notify.remote_bda).toString().c_str(), + evtParam->notify.handle, + evtParam->notify.handle, + evtParam->notify.value_len, + evtParam->notify.is_notify + ); + break; + } + + // ESP_GATTC_OPEN_EVT + // + // open: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda + // - uint16_t mtu + // + case ESP_GATTC_OPEN_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, remote_bda: %s, mtu: %d]", + BLEUtils::gattStatusToString(evtParam->open.status).c_str(), + evtParam->open.conn_id, + BLEAddress(evtParam->open.remote_bda).toString().c_str(), + evtParam->open.mtu); + break; + } // ESP_GATTC_OPEN_EVT + + // ESP_GATTC_READ_CHAR_EVT + // + // Callback to indicate that requested data that we wanted to read is now available. + // + // read: + // esp_gatt_status_t status + // uint16_t conn_id + // uint16_t handle + // uint8_t* value + // uint16_t value_type + // uint16_t value_len + case ESP_GATTC_READ_CHAR_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, value_len: %d]", + BLEUtils::gattStatusToString(evtParam->read.status).c_str(), + evtParam->read.conn_id, + evtParam->read.handle, + evtParam->read.handle, + evtParam->read.value_len + ); + if (evtParam->read.status == ESP_GATT_OK) { + GeneralUtils::hexDump(evtParam->read.value, evtParam->read.value_len); + /* + char* pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len); + ESP_LOGV(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); + free(pHexData); + */ + } + break; + } // ESP_GATTC_READ_CHAR_EVT + + // ESP_GATTC_REG_EVT + // + // reg: + // - esp_gatt_status_t status + // - uint16_t app_id + case ESP_GATTC_REG_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, app_id: 0x%x]", + BLEUtils::gattStatusToString(evtParam->reg.status).c_str(), + evtParam->reg.app_id); + break; + } // ESP_GATTC_REG_EVT + + // ESP_GATTC_REG_FOR_NOTIFY_EVT + // + // reg_for_notify: + // - esp_gatt_status_t status + // - uint16_t handle + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, handle: %d 0x%.2x]", + BLEUtils::gattStatusToString(evtParam->reg_for_notify.status).c_str(), + evtParam->reg_for_notify.handle, + evtParam->reg_for_notify.handle + ); + break; + } // ESP_GATTC_REG_FOR_NOTIFY_EVT + + // ESP_GATTC_SEARCH_CMPL_EVT + // + // search_cmpl: + // - esp_gatt_status_t status + // - uint16_t conn_id + case ESP_GATTC_SEARCH_CMPL_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d]", + BLEUtils::gattStatusToString(evtParam->search_cmpl.status).c_str(), + evtParam->search_cmpl.conn_id); + break; + } // ESP_GATTC_SEARCH_CMPL_EVT + + // ESP_GATTC_SEARCH_RES_EVT + // + // search_res: + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, start_handle: %d 0x%.2x, end_handle: %d 0x%.2x, srvc_id: %s", + evtParam->search_res.conn_id, + evtParam->search_res.start_handle, + evtParam->search_res.start_handle, + evtParam->search_res.end_handle, + evtParam->search_res.end_handle, + gattIdToString(evtParam->search_res.srvc_id).c_str()); + break; + } // ESP_GATTC_SEARCH_RES_EVT + + // ESP_GATTC_WRITE_CHAR_EVT + // + // write: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // - uint16_t offset + case ESP_GATTC_WRITE_CHAR_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: %d, handle: %d 0x%.2x, offset: %d]", + BLEUtils::gattStatusToString(evtParam->write.status).c_str(), + evtParam->write.conn_id, + evtParam->write.handle, + evtParam->write.handle, + evtParam->write.offset + ); + break; + } // ESP_GATTC_WRITE_CHAR_EVT +#endif + default: + break; + } +} // dumpGattClientEvent + + +/** + * @brief Dump the details of a GATT server event. + * A GATT Server event is a callback received from the BLE subsystem when we are acting as a BLE + * server. The callback indicates the type of event in the `event` field. The `evtParam` is a + * union of structures where we can use the `event` to indicate which of the structures has been + * populated and hence is valid. + * + * @param [in] event The event type that was posted. + * @param [in] evtParam A union of structures only one of which is populated. + */ +void BLEUtils::dumpGattServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* evtParam) { + ESP_LOGV(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); + switch (event) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + + case ESP_GATTS_ADD_CHAR_DESCR_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + gattStatusToString(evtParam->add_char_descr.status).c_str(), + evtParam->add_char_descr.attr_handle, + evtParam->add_char_descr.attr_handle, + evtParam->add_char_descr.service_handle, + evtParam->add_char_descr.service_handle, + BLEUUID(evtParam->add_char_descr.descr_uuid).toString().c_str()); + break; + } // ESP_GATTS_ADD_CHAR_DESCR_EVT + + case ESP_GATTS_ADD_CHAR_EVT: { + if (evtParam->add_char.status == ESP_GATT_OK) { + ESP_LOGV(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + gattStatusToString(evtParam->add_char.status).c_str(), + evtParam->add_char.attr_handle, + evtParam->add_char.attr_handle, + evtParam->add_char.service_handle, + evtParam->add_char.service_handle, + BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "[status: %s, attr_handle: %d 0x%.2x, service_handle: %d 0x%.2x, char_uuid: %s]", + gattStatusToString(evtParam->add_char.status).c_str(), + evtParam->add_char.attr_handle, + evtParam->add_char.attr_handle, + evtParam->add_char.service_handle, + evtParam->add_char.service_handle, + BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + } + break; + } // ESP_GATTS_ADD_CHAR_EVT + + + // ESP_GATTS_CONF_EVT + // + // conf: + // - esp_gatt_status_t status – The status code. + // - uint16_t conn_id – The connection used. + case ESP_GATTS_CONF_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", + gattStatusToString(evtParam->conf.status).c_str(), + evtParam->conf.conn_id); + break; + } // ESP_GATTS_CONF_EVT + + + case ESP_GATTS_CONGEST_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, congested: %d]", + evtParam->congest.conn_id, + evtParam->congest.congested); + break; + } // ESP_GATTS_CONGEST_EVT + + case ESP_GATTS_CONNECT_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + evtParam->connect.conn_id, + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); + break; + } // ESP_GATTS_CONNECT_EVT + + case ESP_GATTS_CREATE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, service_handle: %d 0x%.2x, service_id: [%s]]", + gattStatusToString(evtParam->create.status).c_str(), + evtParam->create.service_handle, + evtParam->create.service_handle, + gattServiceIdToString(evtParam->create.service_id).c_str()); + break; + } // ESP_GATTS_CREATE_EVT + + case ESP_GATTS_DISCONNECT_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", + evtParam->connect.conn_id, + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); + break; + } // ESP_GATTS_DISCONNECT_EVT + + + // ESP_GATTS_EXEC_WRITE_EVT + // exec_write: + // - uint16_t conn_id + // - uint32_t trans_id + // - esp_bd_addr_t bda + // - uint8_t exec_write_flag + case ESP_GATTS_EXEC_WRITE_EVT: { + char* pWriteFlagText; + switch (evtParam->exec_write.exec_write_flag) { + case ESP_GATT_PREP_WRITE_EXEC: { + pWriteFlagText = (char*) "WRITE"; + break; + } + + case ESP_GATT_PREP_WRITE_CANCEL: { + pWriteFlagText = (char*) "CANCEL"; + break; + } + + default: + pWriteFlagText = (char*) ""; + break; + } + + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]", + evtParam->exec_write.conn_id, + evtParam->exec_write.trans_id, + BLEAddress(evtParam->exec_write.bda).toString().c_str(), + evtParam->exec_write.exec_write_flag, + pWriteFlagText); + break; + } // ESP_GATTS_DISCONNECT_EVT + + + case ESP_GATTS_MTU_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, mtu: %d]", + evtParam->mtu.conn_id, + evtParam->mtu.mtu); + break; + } // ESP_GATTS_MTU_EVT + + case ESP_GATTS_READ_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, is_long: %d, need_rsp:%d]", + evtParam->read.conn_id, + evtParam->read.trans_id, + BLEAddress(evtParam->read.bda).toString().c_str(), + evtParam->read.handle, + evtParam->read.is_long, + evtParam->read.need_rsp); + break; + } // ESP_GATTS_READ_EVT + + case ESP_GATTS_RESPONSE_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, handle: 0x%.2x]", + gattStatusToString(evtParam->rsp.status).c_str(), + evtParam->rsp.handle); + break; + } // ESP_GATTS_RESPONSE_EVT + + case ESP_GATTS_REG_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, app_id: %d]", + gattStatusToString(evtParam->reg.status).c_str(), + evtParam->reg.app_id); + break; + } // ESP_GATTS_REG_EVT + + + // ESP_GATTS_START_EVT + // + // start: + // - esp_gatt_status_t status + // - uint16_t service_handle + case ESP_GATTS_START_EVT: { + ESP_LOGV(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", + gattStatusToString(evtParam->start.status).c_str(), + evtParam->start.service_handle); + break; + } // ESP_GATTS_START_EVT + + + // ESP_GATTS_WRITE_EVT + // + // write: + // - uint16_t conn_id – The connection id. + // - uint16_t trans_id – The transfer id. + // - esp_bd_addr_t bda – The address of the partner. + // - uint16_t handle – The attribute handle. + // - uint16_t offset – The offset of the currently received within the whole value. + // - bool need_rsp – Do we need a response? + // - bool is_prep – Is this a write prepare? If set, then this is to be considered part of the received value and not the whole value. A subsequent ESP_GATTS_EXEC_WRITE will mark the total. + // - uint16_t len – The length of the incoming value part. + // - uint8_t* value – The data for this value part. + case ESP_GATTS_WRITE_EVT: { + ESP_LOGV(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", + evtParam->write.conn_id, + evtParam->write.trans_id, + BLEAddress(evtParam->write.bda).toString().c_str(), + evtParam->write.handle, + evtParam->write.offset, + evtParam->write.need_rsp, + evtParam->write.is_prep, + evtParam->write.len); + char* pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); + ESP_LOGV(LOG_TAG, "[Data: %s]", pHex); + free(pHex); + break; + } // ESP_GATTS_WRITE_EVT +#endif + default: + ESP_LOGV(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); + break; + } +} // dumpGattServerEvent + + +/** + * @brief Convert a BLE event type to a string. + * @param [in] eventType The event type. + * @return The event type as a string. + */ +const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { + switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_BLE_EVT_CONN_ADV: + return "ESP_BLE_EVT_CONN_ADV"; + case ESP_BLE_EVT_CONN_DIR_ADV: + return "ESP_BLE_EVT_CONN_DIR_ADV"; + case ESP_BLE_EVT_DISC_ADV: + return "ESP_BLE_EVT_DISC_ADV"; + case ESP_BLE_EVT_NON_CONN_ADV: + return "ESP_BLE_EVT_NON_CONN_ADV"; + case ESP_BLE_EVT_SCAN_RSP: + return "ESP_BLE_EVT_SCAN_RSP"; +#endif + default: + ESP_LOGV(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); + return "*** Unknown ***"; + } +} // eventTypeToString + + + +/** + * @brief Convert a BT GAP event type to a string representation. + * @param [in] eventType The type of event. + * @return A string representation of the event type. + */ +const char* BLEUtils::gapEventToString(uint32_t eventType) { + switch (eventType) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + return "ESP_GAP_BLE_ADV_START_COMPLETE_EVT"; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /* !< When stop adv complete, the event comes */ + return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */ + return "ESP_GAP_BLE_AUTH_CMPL_EVT"; + case ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ + return "ESP_GAP_BLE_KEY_EVT"; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + return "ESP_GAP_BLE_LOCAL_IR_EVT"; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + return "ESP_GAP_BLE_LOCAL_ER_EVT"; + case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */ + return "ESP_GAP_BLE_NC_REQ_EVT"; + case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */ + return "ESP_GAP_BLE_OOB_REQ_EVT"; + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */ + return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT"; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + return "ESP_GAP_BLE_PASSKEY_REQ_EVT"; + case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: + return "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT"; + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: + return "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RESULT_EVT: + return "ESP_GAP_BLE_SCAN_RESULT_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT"; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT"; + case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ + return "ESP_GAP_BLE_SEC_REQ_EVT"; + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: + return "ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT"; + case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: + return "ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT"; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + return "ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT"; +#endif + default: + ESP_LOGV(LOG_TAG, "gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); + return "Unknown event type"; + } +} // gapEventToString + + +std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID) { + const characteristicMap_t* p = g_characteristicsMappings; + while (strlen(p->name) > 0) { + if (p->assignedNumber == characteristicUUID) { + return std::string(p->name); + } + p++; + } + return "Unknown"; +} // gattCharacteristicUUIDToString + + +/** + * @brief Given the UUID for a BLE defined descriptor, return its string representation. + * @param [in] descriptorUUID UUID of the descriptor to be returned as a string. + * @return The string representation of a descriptor UUID. + */ +std::string BLEUtils::gattDescriptorUUIDToString(uint32_t descriptorUUID) { + gattdescriptor_t* p = (gattdescriptor_t*) g_descriptor_ids; + while (strlen(p->name) > 0) { + if (p->assignedNumber == descriptorUUID) { + return std::string(p->name); + } + p++; + } + return ""; +} // gattDescriptorUUIDToString + + +/** + * @brief Return a string representation of an esp_gattc_service_elem_t. + * @return A string representation of an esp_gattc_service_elem_t. + */ +std::string BLEUtils::gattcServiceElementToString(esp_gattc_service_elem_t* pGATTCServiceElement) { + std::stringstream ss; + + ss << "[uuid: " << BLEUUID(pGATTCServiceElement->uuid).toString() << + ", start_handle: " << pGATTCServiceElement->start_handle << + " 0x" << std::hex << pGATTCServiceElement->start_handle << + ", end_handle: " << std::dec << pGATTCServiceElement->end_handle << + " 0x" << std::hex << pGATTCServiceElement->end_handle << "]"; + return ss.str(); +} // gattcServiceElementToString + + +/** + * @brief Convert an esp_gatt_srvc_id_t to a string. + */ +std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) { + return gattIdToString(srvcId.id); +} // gattServiceIdToString + + +std::string BLEUtils::gattServiceToString(uint32_t serviceId) { + gattService_t* p = (gattService_t*) g_gattServices; + while (strlen(p->name) > 0) { + if (p->assignedNumber == serviceId) { + return std::string(p->name); + } + p++; + } + return "Unknown"; +} // gattServiceToString + + +/** + * @brief Convert a GATT status to a string. + * + * @param [in] status The status to convert. + * @return A string representation of the status. + */ +std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { + switch (status) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GATT_OK: + return "ESP_GATT_OK"; + case ESP_GATT_INVALID_HANDLE: + return "ESP_GATT_INVALID_HANDLE"; + case ESP_GATT_READ_NOT_PERMIT: + return "ESP_GATT_READ_NOT_PERMIT"; + case ESP_GATT_WRITE_NOT_PERMIT: + return "ESP_GATT_WRITE_NOT_PERMIT"; + case ESP_GATT_INVALID_PDU: + return "ESP_GATT_INVALID_PDU"; + case ESP_GATT_INSUF_AUTHENTICATION: + return "ESP_GATT_INSUF_AUTHENTICATION"; + case ESP_GATT_REQ_NOT_SUPPORTED: + return "ESP_GATT_REQ_NOT_SUPPORTED"; + case ESP_GATT_INVALID_OFFSET: + return "ESP_GATT_INVALID_OFFSET"; + case ESP_GATT_INSUF_AUTHORIZATION: + return "ESP_GATT_INSUF_AUTHORIZATION"; + case ESP_GATT_PREPARE_Q_FULL: + return "ESP_GATT_PREPARE_Q_FULL"; + case ESP_GATT_NOT_FOUND: + return "ESP_GATT_NOT_FOUND"; + case ESP_GATT_NOT_LONG: + return "ESP_GATT_NOT_LONG"; + case ESP_GATT_INSUF_KEY_SIZE: + return "ESP_GATT_INSUF_KEY_SIZE"; + case ESP_GATT_INVALID_ATTR_LEN: + return "ESP_GATT_INVALID_ATTR_LEN"; + case ESP_GATT_ERR_UNLIKELY: + return "ESP_GATT_ERR_UNLIKELY"; + case ESP_GATT_INSUF_ENCRYPTION: + return "ESP_GATT_INSUF_ENCRYPTION"; + case ESP_GATT_UNSUPPORT_GRP_TYPE: + return "ESP_GATT_UNSUPPORT_GRP_TYPE"; + case ESP_GATT_INSUF_RESOURCE: + return "ESP_GATT_INSUF_RESOURCE"; + case ESP_GATT_NO_RESOURCES: + return "ESP_GATT_NO_RESOURCES"; + case ESP_GATT_INTERNAL_ERROR: + return "ESP_GATT_INTERNAL_ERROR"; + case ESP_GATT_WRONG_STATE: + return "ESP_GATT_WRONG_STATE"; + case ESP_GATT_DB_FULL: + return "ESP_GATT_DB_FULL"; + case ESP_GATT_BUSY: + return "ESP_GATT_BUSY"; + case ESP_GATT_ERROR: + return "ESP_GATT_ERROR"; + case ESP_GATT_CMD_STARTED: + return "ESP_GATT_CMD_STARTED"; + case ESP_GATT_ILLEGAL_PARAMETER: + return "ESP_GATT_ILLEGAL_PARAMETER"; + case ESP_GATT_PENDING: + return "ESP_GATT_PENDING"; + case ESP_GATT_AUTH_FAIL: + return "ESP_GATT_AUTH_FAIL"; + case ESP_GATT_MORE: + return "ESP_GATT_MORE"; + case ESP_GATT_INVALID_CFG: + return "ESP_GATT_INVALID_CFG"; + case ESP_GATT_SERVICE_STARTED: + return "ESP_GATT_SERVICE_STARTED"; + case ESP_GATT_ENCRYPED_NO_MITM: + return "ESP_GATT_ENCRYPED_NO_MITM"; + case ESP_GATT_NOT_ENCRYPTED: + return "ESP_GATT_NOT_ENCRYPTED"; + case ESP_GATT_CONGESTED: + return "ESP_GATT_CONGESTED"; + case ESP_GATT_DUP_REG: + return "ESP_GATT_DUP_REG"; + case ESP_GATT_ALREADY_OPEN: + return "ESP_GATT_ALREADY_OPEN"; + case ESP_GATT_CANCEL: + return "ESP_GATT_CANCEL"; + case ESP_GATT_STACK_RSP: + return "ESP_GATT_STACK_RSP"; + case ESP_GATT_APP_RSP: + return "ESP_GATT_APP_RSP"; + case ESP_GATT_UNKNOWN_ERROR: + return "ESP_GATT_UNKNOWN_ERROR"; + case ESP_GATT_CCC_CFG_ERR: + return "ESP_GATT_CCC_CFG_ERR"; + case ESP_GATT_PRC_IN_PROGRESS: + return "ESP_GATT_PRC_IN_PROGRESS"; + case ESP_GATT_OUT_OF_RANGE: + return "ESP_GATT_OUT_OF_RANGE"; +#endif + default: + return "Unknown"; + } +} // gattStatusToString + + + +std::string BLEUtils::getMember(uint32_t memberId) { + member_t* p = (member_t*) members_ids; + + while (strlen(p->name) > 0) { + if (p->assignedNumber == memberId) { + return std::string(p->name); + } + p++; + } + return "Unknown"; +} + +/** + * @brief convert a GAP search event to a string. + * @param [in] searchEvt + * @return The search event type as a string. + */ +const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { + switch (searchEvt) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_GAP_SEARCH_INQ_RES_EVT: + return "ESP_GAP_SEARCH_INQ_RES_EVT"; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + return "ESP_GAP_SEARCH_INQ_CMPL_EVT"; + case ESP_GAP_SEARCH_DISC_RES_EVT: + return "ESP_GAP_SEARCH_DISC_RES_EVT"; + case ESP_GAP_SEARCH_DISC_BLE_RES_EVT: + return "ESP_GAP_SEARCH_DISC_BLE_RES_EVT"; + case ESP_GAP_SEARCH_DISC_CMPL_EVT: + return "ESP_GAP_SEARCH_DISC_CMPL_EVT"; + case ESP_GAP_SEARCH_DI_DISC_CMPL_EVT: + return "ESP_GAP_SEARCH_DI_DISC_CMPL_EVT"; + case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT: + return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT"; +#endif + default: + ESP_LOGV(LOG_TAG, "Unknown event type: 0x%x", searchEvt); + return "Unknown event type"; + } +} // searchEventTypeToString + +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEUtils.h b/libraries/ESP32_BLE_Arduino/src/BLEUtils.h new file mode 100644 index 0000000..7981691 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEUtils.h @@ -0,0 +1,63 @@ +/* + * BLEUtils.h + * + * Created on: Mar 25, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEUTILS_H_ +#define COMPONENTS_CPP_UTILS_BLEUTILS_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include // ESP32 BLE +#include // ESP32 BLE +#include // ESP32 BLE +#include +#include "BLEClient.h" + +/** + * @brief A set of general %BLE utilities. + */ +class BLEUtils { +public: + static const char* addressTypeToString(esp_ble_addr_type_t type); + static std::string adFlagsToString(uint8_t adFlags); + static const char* advTypeToString(uint8_t advType); + static char* buildHexData(uint8_t* target, uint8_t* source, uint8_t length); + static std::string buildPrintData(uint8_t* source, size_t length); + static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); + static const char* devTypeToString(esp_bt_dev_type_t type); + static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id = 0); + static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary = true); + static void dumpGapEvent( + esp_gap_ble_cb_event_t event, + esp_ble_gap_cb_param_t* param); + static void dumpGattClientEvent( + esp_gattc_cb_event_t event, + esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t* evtParam); + static void dumpGattServerEvent( + esp_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t* evtParam); + static const char* eventTypeToString(esp_ble_evt_type_t eventType); + static BLEClient* findByAddress(BLEAddress address); + static BLEClient* findByConnId(uint16_t conn_id); + static const char* gapEventToString(uint32_t eventType); + static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); + static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); + static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); + static std::string gattcServiceElementToString(esp_gattc_service_elem_t* pGATTCServiceElement); + static std::string gattDescriptorUUIDToString(uint32_t descriptorUUID); + static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); + static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); + static std::string gattServiceToString(uint32_t serviceId); + static std::string gattStatusToString(esp_gatt_status_t status); + static std::string getMember(uint32_t memberId); + static void registerByAddress(BLEAddress address, BLEClient* pDevice); + static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); + static const char* searchEventTypeToString(esp_gap_search_evt_t searchEvt); +}; + +#endif // CONFIG_BT_ENABLED +#endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/BLEValue.cpp b/libraries/ESP32_BLE_Arduino/src/BLEValue.cpp new file mode 100644 index 0000000..ec1e61f --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEValue.cpp @@ -0,0 +1,139 @@ +/* + * BLEValue.cpp + * + * Created on: Jul 17, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include "BLEValue.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG="BLEValue"; +#endif + + + +BLEValue::BLEValue() { + m_accumulation = ""; + m_value = ""; + m_readOffset = 0; +} // BLEValue + + +/** + * @brief Add a message part to the accumulation. + * The accumulation is a growing set of data that is added to until a commit or cancel. + * @param [in] part A message part being added. + */ +void BLEValue::addPart(std::string part) { + ESP_LOGD(LOG_TAG, ">> addPart: length=%d", part.length()); + m_accumulation += part; +} // addPart + + +/** + * @brief Add a message part to the accumulation. + * The accumulation is a growing set of data that is added to until a commit or cancel. + * @param [in] pData A message part being added. + * @param [in] length The number of bytes being added. + */ +void BLEValue::addPart(uint8_t* pData, size_t length) { + ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length); + m_accumulation += std::string((char*) pData, length); +} // addPart + + +/** + * @brief Cancel the current accumulation. + */ +void BLEValue::cancel() { + ESP_LOGD(LOG_TAG, ">> cancel"); + m_accumulation = ""; + m_readOffset = 0; +} // cancel + + +/** + * @brief Commit the current accumulation. + * When writing a value, we may find that we write it in "parts" meaning that the writes come in in pieces + * of the overall message. After the last part has been received, we may perform a commit which means that + * we now have the complete message and commit the change as a unit. + */ +void BLEValue::commit() { + ESP_LOGD(LOG_TAG, ">> commit"); + // If there is nothing to commit, do nothing. + if (m_accumulation.length() == 0) return; + setValue(m_accumulation); + m_accumulation = ""; + m_readOffset = 0; +} // commit + + +/** + * @brief Get a pointer to the data. + * @return A pointer to the data. + */ +uint8_t* BLEValue::getData() { + return (uint8_t*) m_value.data(); +} + + +/** + * @brief Get the length of the data in bytes. + * @return The length of the data in bytes. + */ +size_t BLEValue::getLength() { + return m_value.length(); +} // getLength + + +/** + * @brief Get the read offset. + * @return The read offset into the read. + */ +uint16_t BLEValue::getReadOffset() { + return m_readOffset; +} // getReadOffset + + +/** + * @brief Get the current value. + */ +std::string BLEValue::getValue() { + return m_value; +} // getValue + + +/** + * @brief Set the read offset + * @param [in] readOffset The offset into the read. + */ +void BLEValue::setReadOffset(uint16_t readOffset) { + m_readOffset = readOffset; +} // setReadOffset + + +/** + * @brief Set the current value. + */ +void BLEValue::setValue(std::string value) { + m_value = value; +} // setValue + + +/** + * @brief Set the current value. + * @param [in] pData The data for the current value. + * @param [in] The length of the new current value. + */ +void BLEValue::setValue(uint8_t* pData, size_t length) { + m_value = std::string((char*) pData, length); +} // setValue + + +#endif // CONFIG_BT_ENABLED diff --git a/libraries/ESP32_BLE_Arduino/src/BLEValue.h b/libraries/ESP32_BLE_Arduino/src/BLEValue.h new file mode 100644 index 0000000..5df904c --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/BLEValue.h @@ -0,0 +1,39 @@ +/* + * BLEValue.h + * + * Created on: Jul 17, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#define COMPONENTS_CPP_UTILS_BLEVALUE_H_ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include + +/** + * @brief The model of a %BLE value. + */ +class BLEValue { +public: + BLEValue(); + void addPart(std::string part); + void addPart(uint8_t* pData, size_t length); + void cancel(); + void commit(); + uint8_t* getData(); + size_t getLength(); + uint16_t getReadOffset(); + std::string getValue(); + void setReadOffset(uint16_t readOffset); + void setValue(std::string value); + void setValue(uint8_t* pData, size_t length); + +private: + std::string m_accumulation; + uint16_t m_readOffset; + std::string m_value; + +}; +#endif // CONFIG_BT_ENABLED +#endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/FreeRTOS.cpp b/libraries/ESP32_BLE_Arduino/src/FreeRTOS.cpp new file mode 100644 index 0000000..1f12c88 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/FreeRTOS.cpp @@ -0,0 +1,274 @@ +/* + * FreeRTOS.cpp + * + * Created on: Feb 24, 2017 + * Author: kolban + */ +#include // Include the base FreeRTOS definitions +#include // Include the task definitions +#include // Include the semaphore definitions +#include +#include +#include +#include "FreeRTOS.h" +#include "sdkconfig.h" +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "FreeRTOS"; +#endif + + +/** + * Sleep for the specified number of milliseconds. + * @param[in] ms The period in milliseconds for which to sleep. + */ +void FreeRTOS::sleep(uint32_t ms) { + ::vTaskDelay(ms / portTICK_PERIOD_MS); +} // sleep + + +/** + * Start a new task. + * @param[in] task The function pointer to the function to be run in the task. + * @param[in] taskName A string identifier for the task. + * @param[in] param An optional parameter to be passed to the started task. + * @param[in] stackSize An optional paremeter supplying the size of the stack in which to run the task. + */ +void FreeRTOS::startTask(void task(void*), std::string taskName, void* param, uint32_t stackSize) { + ::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL); +} // startTask + + +/** + * Delete the task. + * @param[in] pTask An optional handle to the task to be deleted. If not supplied the calling task will be deleted. + */ +void FreeRTOS::deleteTask(TaskHandle_t pTask) { + ::vTaskDelete(pTask); +} // deleteTask + + +/** + * Get the time in milliseconds since the %FreeRTOS scheduler started. + * @return The time in milliseconds since the %FreeRTOS scheduler started. + */ +uint32_t FreeRTOS::getTimeSinceStart() { + return (uint32_t) (xTaskGetTickCount() * portTICK_PERIOD_MS); +} // getTimeSinceStart + + +/** + * @brief Wait for a semaphore to be released by trying to take it and + * then releasing it again. + * @param [in] owner A debug tag. + * @return The value associated with the semaphore. + */ +uint32_t FreeRTOS::Semaphore::wait(std::string owner) { + ESP_LOGV(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + + if (m_usePthreads) { + pthread_mutex_lock(&m_pthread_mutex); + } else { + xSemaphoreTake(m_semaphore, portMAX_DELAY); + } + + m_owner = owner; + + if (m_usePthreads) { + pthread_mutex_unlock(&m_pthread_mutex); + } else { + xSemaphoreGive(m_semaphore); + } + + ESP_LOGV(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str()); + m_owner = std::string(""); + return m_value; +} // wait + + +FreeRTOS::Semaphore::Semaphore(std::string name) { + m_usePthreads = false; // Are we using pThreads or FreeRTOS? + if (m_usePthreads) { + pthread_mutex_init(&m_pthread_mutex, nullptr); + } else { + m_semaphore = xSemaphoreCreateMutex(); + } + + m_name = name; + m_owner = std::string(""); + m_value = 0; +} + + +FreeRTOS::Semaphore::~Semaphore() { + if (m_usePthreads) { + pthread_mutex_destroy(&m_pthread_mutex); + } else { + vSemaphoreDelete(m_semaphore); + } +} + + +/** + * @brief Give a semaphore. + * The Semaphore is given. + */ +void FreeRTOS::Semaphore::give() { + ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); + if (m_usePthreads) { + pthread_mutex_unlock(&m_pthread_mutex); + } else { + xSemaphoreGive(m_semaphore); + } +// #ifdef ARDUINO_ARCH_ESP32 +// FreeRTOS::sleep(10); +// #endif + + m_owner = std::string(""); +} // Semaphore::give + + +/** + * @brief Give a semaphore. + * The Semaphore is given with an associated value. + * @param [in] value The value to associate with the semaphore. + */ +void FreeRTOS::Semaphore::give(uint32_t value) { + m_value = value; + give(); +} // give + + +/** + * @brief Give a semaphore from an ISR. + */ +void FreeRTOS::Semaphore::giveFromISR() { + BaseType_t higherPriorityTaskWoken; + if (m_usePthreads) { + assert(false); + } else { + xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken); + } +} // giveFromISR + + +/** + * @brief Take a semaphore. + * Take a semaphore and wait indefinitely. + * @param [in] owner The new owner (for debugging) + * @return True if we took the semaphore. + */ +bool FreeRTOS::Semaphore::take(std::string owner) { + ESP_LOGD(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + bool rc = false; + if (m_usePthreads) { + pthread_mutex_lock(&m_pthread_mutex); + } else { + rc = ::xSemaphoreTake(m_semaphore, portMAX_DELAY) == pdTRUE; + } + m_owner = owner; + if (rc) { + ESP_LOGD(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str()); + } + return rc; +} // Semaphore::take + + +/** + * @brief Take a semaphore. + * Take a semaphore but return if we haven't obtained it in the given period of milliseconds. + * @param [in] timeoutMs Timeout in milliseconds. + * @param [in] owner The new owner (for debugging) + * @return True if we took the semaphore. + */ +bool FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { + ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); + bool rc = false; + if (m_usePthreads) { + assert(false); // We apparently don't have a timed wait for pthreads. + } else { + rc = ::xSemaphoreTake(m_semaphore, timeoutMs / portTICK_PERIOD_MS) == pdTRUE; + } + m_owner = owner; + if (rc) { + ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + } else { + ESP_LOGE(LOG_TAG, "Semaphore NOT taken: %s", toString().c_str()); + } + return rc; +} // Semaphore::take + + + +/** + * @brief Create a string representation of the semaphore. + * @return A string representation of the semaphore. + */ +std::string FreeRTOS::Semaphore::toString() { + std::stringstream stringStream; + stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner; + return stringStream.str(); +} // toString + + +/** + * @brief Set the name of the semaphore. + * @param [in] name The name of the semaphore. + */ +void FreeRTOS::Semaphore::setName(std::string name) { + m_name = name; +} // setName + + +/** + * @brief Create a ring buffer. + * @param [in] length The amount of storage to allocate for the ring buffer. + * @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF. + */ +Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) { + m_handle = ::xRingbufferCreate(length, type); +} // Ringbuffer + + +Ringbuffer::~Ringbuffer() { + ::vRingbufferDelete(m_handle); +} // ~Ringbuffer + + +/** + * @brief Receive data from the buffer. + * @param [out] size On return, the size of data returned. + * @param [in] wait How long to wait. + * @return A pointer to the storage retrieved. + */ +void* Ringbuffer::receive(size_t* size, TickType_t wait) { + return ::xRingbufferReceive(m_handle, size, wait); +} // receive + + +/** + * @brief Return an item. + * @param [in] item The item to be returned/released. + */ +void Ringbuffer::returnItem(void* item) { + ::vRingbufferReturnItem(m_handle, item); +} // returnItem + + +/** + * @brief Send data to the buffer. + * @param [in] data The data to place into the buffer. + * @param [in] length The length of data to place into the buffer. + * @param [in] wait How long to wait before giving up. The default is to wait indefinitely. + * @return + */ +bool Ringbuffer::send(void* data, size_t length, TickType_t wait) { + return ::xRingbufferSend(m_handle, data, length, wait) == pdTRUE; +} // send + + diff --git a/libraries/ESP32_BLE_Arduino/src/FreeRTOS.h b/libraries/ESP32_BLE_Arduino/src/FreeRTOS.h new file mode 100644 index 0000000..b861c87 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/FreeRTOS.h @@ -0,0 +1,71 @@ +/* + * FreeRTOS.h + * + * Created on: Feb 24, 2017 + * Author: kolban + */ + +#ifndef MAIN_FREERTOS_H_ +#define MAIN_FREERTOS_H_ +#include +#include +#include + +#include // Include the base FreeRTOS definitions. +#include // Include the task definitions. +#include // Include the semaphore definitions. +#include // Include the ringbuffer definitions. + + +/** + * @brief Interface to %FreeRTOS functions. + */ +class FreeRTOS { +public: + static void sleep(uint32_t ms); + static void startTask(void task(void*), std::string taskName, void* param = nullptr, uint32_t stackSize = 2048); + static void deleteTask(TaskHandle_t pTask = nullptr); + + static uint32_t getTimeSinceStart(); + + class Semaphore { + public: + Semaphore(std::string owner = ""); + ~Semaphore(); + void give(); + void give(uint32_t value); + void giveFromISR(); + void setName(std::string name); + bool take(std::string owner = ""); + bool take(uint32_t timeoutMs, std::string owner = ""); + std::string toString(); + uint32_t wait(std::string owner = ""); + + private: + SemaphoreHandle_t m_semaphore; + pthread_mutex_t m_pthread_mutex; + std::string m_name; + std::string m_owner; + uint32_t m_value; + bool m_usePthreads; + + }; +}; + + +/** + * @brief Ringbuffer. + */ +class Ringbuffer { +public: + Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT); + ~Ringbuffer(); + + void* receive(size_t* size, TickType_t wait = portMAX_DELAY); + void returnItem(void* item); + bool send(void* data, size_t length, TickType_t wait = portMAX_DELAY); +private: + RingbufHandle_t m_handle; +}; + +#endif /* MAIN_FREERTOS_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/GeneralUtils.cpp b/libraries/ESP32_BLE_Arduino/src/GeneralUtils.cpp new file mode 100644 index 0000000..019c81b --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/GeneralUtils.cpp @@ -0,0 +1,544 @@ +/* + * GeneralUtils.cpp + * + * Created on: May 20, 2017 + * Author: kolban + */ + +#include "GeneralUtils.h" +#include +#include +#include +#include +#include +#include +#include "FreeRTOS.h" +#include +#include +#include +#include +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" +static const char* LOG_TAG = "GeneralUtils"; +#endif + + +static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static int base64EncodedLength(size_t length) { + return (length + 2 - ((length + 2) % 3)) / 3 * 4; +} // base64EncodedLength + + +static int base64EncodedLength(const std::string& in) { + return base64EncodedLength(in.length()); +} // base64EncodedLength + + +static void a3_to_a4(unsigned char* a4, unsigned char* a3) { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = (a3[2] & 0x3f); +} // a3_to_a4 + + +static void a4_to_a3(unsigned char* a3, unsigned char* a4) { + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; +} // a4_to_a3 + + +/** + * @brief Encode a string into base 64. + * @param [in] in + * @param [out] out + */ +bool GeneralUtils::base64Encode(const std::string& in, std::string* out) { + int i = 0, j = 0; + size_t enc_len = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + out->resize(base64EncodedLength(in)); + + int input_len = in.size(); + std::string::const_iterator input = in.begin(); + + while (input_len--) { + a3[i++] = *(input++); + if (i == 3) { + a3_to_a4(a4, a3); + + for (i = 0; i < 4; i++) { + (*out)[enc_len++] = kBase64Alphabet[a4[i]]; + } + + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) { + a3[j] = '\0'; + } + + a3_to_a4(a4, a3); + + for (j = 0; j < i + 1; j++) { + (*out)[enc_len++] = kBase64Alphabet[a4[j]]; + } + + while ((i++ < 3)) { + (*out)[enc_len++] = '='; + } + } + + return (enc_len == out->size()); +} // base64Encode + + +/** + * @brief Dump general info to the log. + * Data includes: + * * Amount of free RAM + */ +void GeneralUtils::dumpInfo() { + size_t freeHeap = heap_caps_get_free_size(MALLOC_CAP_8BIT); + esp_chip_info_t chipInfo; + esp_chip_info(&chipInfo); + ESP_LOGV(LOG_TAG, "--- dumpInfo ---"); + ESP_LOGV(LOG_TAG, "Free heap: %d", freeHeap); + ESP_LOGV(LOG_TAG, "Chip Info: Model: %d, cores: %d, revision: %d", chipInfo.model, chipInfo.cores, chipInfo.revision); + ESP_LOGV(LOG_TAG, "ESP-IDF version: %s", esp_get_idf_version()); + ESP_LOGV(LOG_TAG, "---"); +} // dumpInfo + + +/** + * @brief Does the string end with a specific character? + * @param [in] str The string to examine. + * @param [in] c The character to look form. + * @return True if the string ends with the given character. + */ +bool GeneralUtils::endsWith(std::string str, char c) { + if (str.empty()) { + return false; + } + if (str.at(str.length() - 1) == c) { + return true; + } + return false; +} // endsWidth + + +static int DecodedLength(const std::string& in) { + int numEq = 0; + int n = (int) in.size(); + + for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) { + ++numEq; + } + return ((6 * n) / 8) - numEq; +} // DecodedLength + + +static unsigned char b64_lookup(unsigned char c) { + if(c >='A' && c <='Z') return c - 'A'; + if(c >='a' && c <='z') return c - 71; + if(c >='0' && c <='9') return c + 4; + if(c == '+') return 62; + if(c == '/') return 63; + return 255; +}; // b64_lookup + + +/** + * @brief Decode a chunk of data that is base64 encoded. + * @param [in] in The string to be decoded. + * @param [out] out The resulting data. + */ +bool GeneralUtils::base64Decode(const std::string& in, std::string* out) { + int i = 0, j = 0; + size_t dec_len = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + int input_len = in.size(); + std::string::const_iterator input = in.begin(); + + out->resize(DecodedLength(in)); + + while (input_len--) { + if (*input == '=') { + break; + } + + a4[i++] = *(input++); + if (i == 4) { + for (i = 0; i <4; i++) { + a4[i] = b64_lookup(a4[i]); + } + + a4_to_a3(a3,a4); + + for (i = 0; i < 3; i++) { + (*out)[dec_len++] = a3[i]; + } + + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + a4[j] = '\0'; + } + + for (j = 0; j < 4; j++) { + a4[j] = b64_lookup(a4[j]); + } + + a4_to_a3(a3,a4); + + for (j = 0; j < i - 1; j++) { + (*out)[dec_len++] = a3[j]; + } + } + + return (dec_len == out->size()); + } // base64Decode + +/* +void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { + uint32_t index=0; + std::stringstream ascii; + std::stringstream hex; + char asciiBuf[80]; + char hexBuf[80]; + hex.str(""); + ascii.str(""); + while(index < length) { + hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' '; + if (std::isprint(pData[index])) { + ascii << pData[index]; + } else { + ascii << '.'; + } + index++; + if (index % 16 == 0) { + strcpy(hexBuf, hex.str().c_str()); + strcpy(asciiBuf, ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hexBuf, asciiBuf); + hex.str(""); + ascii.str(""); + } + } + if (index %16 != 0) { + while(index % 16 != 0) { + hex << " "; + index++; + } + strcpy(hexBuf, hex.str().c_str()); + strcpy(asciiBuf, ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hexBuf, asciiBuf); + //ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + } + FreeRTOS::sleep(1000); +} +*/ + +/* +void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { + uint32_t index=0; + static std::stringstream ascii; + static std::stringstream hex; + hex.str(""); + ascii.str(""); + while(index < length) { + hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' '; + if (std::isprint(pData[index])) { + ascii << pData[index]; + } else { + ascii << '.'; + } + index++; + if (index % 16 == 0) { + ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + hex.str(""); + ascii.str(""); + } + } + if (index %16 != 0) { + while(index % 16 != 0) { + hex << " "; + index++; + } + ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + } + FreeRTOS::sleep(1000); +} +*/ + + +/** + * @brief Dump a representation of binary data to the console. + * + * @param [in] pData Pointer to the start of data to be logged. + * @param [in] length Length of the data (in bytes) to be logged. + * @return N/A. + */ +void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { + char ascii[80]; + char hex[80]; + char tempBuf[80]; + uint32_t lineNumber = 0; + + ESP_LOGV(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"); + ESP_LOGV(LOG_TAG, " -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); + strcpy(ascii, ""); + strcpy(hex, ""); + uint32_t index = 0; + while (index < length) { + sprintf(tempBuf, "%.2x ", pData[index]); + strcat(hex, tempBuf); + if (isprint(pData[index])) { + sprintf(tempBuf, "%c", pData[index]); + } else { + sprintf(tempBuf, "."); + } + strcat(ascii, tempBuf); + index++; + if (index % 16 == 0) { + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber * 16, hex, ascii); + strcpy(ascii, ""); + strcpy(hex, ""); + lineNumber++; + } + } + if (index %16 != 0) { + while (index % 16 != 0) { + strcat(hex, " "); + index++; + } + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber * 16, hex, ascii); + } +} // hexDump + + +/** + * @brief Convert an IP address to string. + * @param ip The 4 byte IP address. + * @return A string representation of the IP address. + */ +std::string GeneralUtils::ipToString(uint8_t *ip) { + std::stringstream s; + s << (int) ip[0] << '.' << (int) ip[1] << '.' << (int) ip[2] << '.' << (int) ip[3]; + return s.str(); +} // ipToString + + +/** + * @brief Split a string into parts based on a delimiter. + * @param [in] source The source string to split. + * @param [in] delimiter The delimiter characters. + * @return A vector of strings that are the split of the input. + */ +std::vector GeneralUtils::split(std::string source, char delimiter) { + // See also: https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector strings; + std::istringstream iss(source); + std::string s; + while (std::getline(iss, s, delimiter)) { + strings.push_back(trim(s)); + } + return strings; +} // split + + +/** + * @brief Convert an ESP error code to a string. + * @param [in] errCode The errCode to be converted. + * @return A string representation of the error code. + */ +const char* GeneralUtils::errorToString(esp_err_t errCode) { + switch (errCode) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case ESP_OK: + return "ESP_OK"; + case ESP_FAIL: + return "ESP_FAIL"; + case ESP_ERR_NO_MEM: + return "ESP_ERR_NO_MEM"; + case ESP_ERR_INVALID_ARG: + return "ESP_ERR_INVALID_ARG"; + case ESP_ERR_INVALID_SIZE: + return "ESP_ERR_INVALID_SIZE"; + case ESP_ERR_INVALID_STATE: + return "ESP_ERR_INVALID_STATE"; + case ESP_ERR_NOT_FOUND: + return "ESP_ERR_NOT_FOUND"; + case ESP_ERR_NOT_SUPPORTED: + return "ESP_ERR_NOT_SUPPORTED"; + case ESP_ERR_TIMEOUT: + return "ESP_ERR_TIMEOUT"; + case ESP_ERR_NVS_NOT_INITIALIZED: + return "ESP_ERR_NVS_NOT_INITIALIZED"; + case ESP_ERR_NVS_NOT_FOUND: + return "ESP_ERR_NVS_NOT_FOUND"; + case ESP_ERR_NVS_TYPE_MISMATCH: + return "ESP_ERR_NVS_TYPE_MISMATCH"; + case ESP_ERR_NVS_READ_ONLY: + return "ESP_ERR_NVS_READ_ONLY"; + case ESP_ERR_NVS_NOT_ENOUGH_SPACE: + return "ESP_ERR_NVS_NOT_ENOUGH_SPACE"; + case ESP_ERR_NVS_INVALID_NAME: + return "ESP_ERR_NVS_INVALID_NAME"; + case ESP_ERR_NVS_INVALID_HANDLE: + return "ESP_ERR_NVS_INVALID_HANDLE"; + case ESP_ERR_NVS_REMOVE_FAILED: + return "ESP_ERR_NVS_REMOVE_FAILED"; + case ESP_ERR_NVS_KEY_TOO_LONG: + return "ESP_ERR_NVS_KEY_TOO_LONG"; + case ESP_ERR_NVS_PAGE_FULL: + return "ESP_ERR_NVS_PAGE_FULL"; + case ESP_ERR_NVS_INVALID_STATE: + return "ESP_ERR_NVS_INVALID_STATE"; + case ESP_ERR_NVS_INVALID_LENGTH: + return "ESP_ERR_NVS_INVALID_LENGTH"; + case ESP_ERR_WIFI_NOT_INIT: + return "ESP_ERR_WIFI_NOT_INIT"; + //case ESP_ERR_WIFI_NOT_START: + // return "ESP_ERR_WIFI_NOT_START"; + case ESP_ERR_WIFI_IF: + return "ESP_ERR_WIFI_IF"; + case ESP_ERR_WIFI_MODE: + return "ESP_ERR_WIFI_MODE"; + case ESP_ERR_WIFI_STATE: + return "ESP_ERR_WIFI_STATE"; + case ESP_ERR_WIFI_CONN: + return "ESP_ERR_WIFI_CONN"; + case ESP_ERR_WIFI_NVS: + return "ESP_ERR_WIFI_NVS"; + case ESP_ERR_WIFI_MAC: + return "ESP_ERR_WIFI_MAC"; + case ESP_ERR_WIFI_SSID: + return "ESP_ERR_WIFI_SSID"; + case ESP_ERR_WIFI_PASSWORD: + return "ESP_ERR_WIFI_PASSWORD"; + case ESP_ERR_WIFI_TIMEOUT: + return "ESP_ERR_WIFI_TIMEOUT"; + case ESP_ERR_WIFI_WAKE_FAIL: + return "ESP_ERR_WIFI_WAKE_FAIL"; +#endif + default: + return "Unknown ESP_ERR error"; + } +} // errorToString + +/** + * @brief Convert a wifi_err_reason_t code to a string. + * @param [in] errCode The errCode to be converted. + * @return A string representation of the error code. + * + * @note: wifi_err_reason_t values as of April 2018 are: (1-24, 200-204) and are defined in ~/esp-idf/components/esp32/include/esp_wifi_types.h. + */ +const char* GeneralUtils::wifiErrorToString(uint8_t errCode) { + if (errCode == ESP_OK) return "ESP_OK (received SYSTEM_EVENT_STA_GOT_IP event)"; + if (errCode == UINT8_MAX) return "Not Connected (default value)"; + + switch ((wifi_err_reason_t) errCode) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 + case WIFI_REASON_UNSPECIFIED: + return "WIFI_REASON_UNSPECIFIED"; + case WIFI_REASON_AUTH_EXPIRE: + return "WIFI_REASON_AUTH_EXPIRE"; + case WIFI_REASON_AUTH_LEAVE: + return "WIFI_REASON_AUTH_LEAVE"; + case WIFI_REASON_ASSOC_EXPIRE: + return "WIFI_REASON_ASSOC_EXPIRE"; + case WIFI_REASON_ASSOC_TOOMANY: + return "WIFI_REASON_ASSOC_TOOMANY"; + case WIFI_REASON_NOT_AUTHED: + return "WIFI_REASON_NOT_AUTHED"; + case WIFI_REASON_NOT_ASSOCED: + return "WIFI_REASON_NOT_ASSOCED"; + case WIFI_REASON_ASSOC_LEAVE: + return "WIFI_REASON_ASSOC_LEAVE"; + case WIFI_REASON_ASSOC_NOT_AUTHED: + return "WIFI_REASON_ASSOC_NOT_AUTHED"; + case WIFI_REASON_DISASSOC_PWRCAP_BAD: + return "WIFI_REASON_DISASSOC_PWRCAP_BAD"; + case WIFI_REASON_DISASSOC_SUPCHAN_BAD: + return "WIFI_REASON_DISASSOC_SUPCHAN_BAD"; + case WIFI_REASON_IE_INVALID: + return "WIFI_REASON_IE_INVALID"; + case WIFI_REASON_MIC_FAILURE: + return "WIFI_REASON_MIC_FAILURE"; + case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT"; + case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: + return "WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT"; + case WIFI_REASON_IE_IN_4WAY_DIFFERS: + return "WIFI_REASON_IE_IN_4WAY_DIFFERS"; + case WIFI_REASON_GROUP_CIPHER_INVALID: + return "WIFI_REASON_GROUP_CIPHER_INVALID"; + case WIFI_REASON_PAIRWISE_CIPHER_INVALID: + return "WIFI_REASON_PAIRWISE_CIPHER_INVALID"; + case WIFI_REASON_AKMP_INVALID: + return "WIFI_REASON_AKMP_INVALID"; + case WIFI_REASON_UNSUPP_RSN_IE_VERSION: + return "WIFI_REASON_UNSUPP_RSN_IE_VERSION"; + case WIFI_REASON_INVALID_RSN_IE_CAP: + return "WIFI_REASON_INVALID_RSN_IE_CAP"; + case WIFI_REASON_802_1X_AUTH_FAILED: + return "WIFI_REASON_802_1X_AUTH_FAILED"; + case WIFI_REASON_CIPHER_SUITE_REJECTED: + return "WIFI_REASON_CIPHER_SUITE_REJECTED"; + case WIFI_REASON_BEACON_TIMEOUT: + return "WIFI_REASON_BEACON_TIMEOUT"; + case WIFI_REASON_NO_AP_FOUND: + return "WIFI_REASON_NO_AP_FOUND"; + case WIFI_REASON_AUTH_FAIL: + return "WIFI_REASON_AUTH_FAIL"; + case WIFI_REASON_ASSOC_FAIL: + return "WIFI_REASON_ASSOC_FAIL"; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + return "WIFI_REASON_HANDSHAKE_TIMEOUT"; +#endif + default: + return "Unknown ESP_ERR error"; + } +} // wifiErrorToString + + +/** + * @brief Convert a string to lower case. + * @param [in] value The string to convert to lower case. + * @return A lower case representation of the string. + */ +std::string GeneralUtils::toLower(std::string& value) { + // Question: Could this be improved with a signature of: + // std::string& GeneralUtils::toLower(std::string& value) + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + return value; +} // toLower + + +/** + * @brief Remove white space from a string. + */ +std::string GeneralUtils::trim(const std::string& str) { + size_t first = str.find_first_not_of(' '); + if (std::string::npos == first) return str; + size_t last = str.find_last_not_of(' '); + return str.substr(first, (last - first + 1)); +} // trim diff --git a/libraries/ESP32_BLE_Arduino/src/GeneralUtils.h b/libraries/ESP32_BLE_Arduino/src/GeneralUtils.h new file mode 100644 index 0000000..8eecbd4 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/GeneralUtils.h @@ -0,0 +1,35 @@ +/* + * GeneralUtils.h + * + * Created on: May 20, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_GENERALUTILS_H_ +#define COMPONENTS_CPP_UTILS_GENERALUTILS_H_ +#include +#include +#include +#include +#include + +/** + * @brief General utilities. + */ +class GeneralUtils { +public: + static bool base64Decode(const std::string& in, std::string* out); + static bool base64Encode(const std::string& in, std::string* out); + static void dumpInfo(); + static bool endsWith(std::string str, char c); + static const char* errorToString(esp_err_t errCode); + static const char* wifiErrorToString(uint8_t value); + static void hexDump(const uint8_t* pData, uint32_t length); + static std::string ipToString(uint8_t* ip); + static std::vector split(std::string source, char delimiter); + static std::string toLower(std::string& value); + static std::string trim(const std::string& str); + +}; + +#endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */ diff --git a/libraries/ESP32_BLE_Arduino/src/HIDKeyboardTypes.h b/libraries/ESP32_BLE_Arduino/src/HIDKeyboardTypes.h new file mode 100644 index 0000000..4e221d5 --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/HIDKeyboardTypes.h @@ -0,0 +1,402 @@ +/* Copyright (c) 2015 mbed.org, MIT License + * + * 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. + * + * Note: this file was pulled from different parts of the USBHID library, in mbed SDK + */ + +#ifndef KEYBOARD_DEFS_H +#define KEYBOARD_DEFS_H + +#define REPORT_ID_KEYBOARD 1 +#define REPORT_ID_VOLUME 3 + +/* Modifiers */ +enum MODIFIER_KEY { + KEY_CTRL = 1, + KEY_SHIFT = 2, + KEY_ALT = 4, +}; + + +enum MEDIA_KEY { + KEY_NEXT_TRACK, /*!< next Track Button */ + KEY_PREVIOUS_TRACK, /*!< Previous track Button */ + KEY_STOP, /*!< Stop Button */ + KEY_PLAY_PAUSE, /*!< Play/Pause Button */ + KEY_MUTE, /*!< Mute Button */ + KEY_VOLUME_UP, /*!< Volume Up Button */ + KEY_VOLUME_DOWN, /*!< Volume Down Button */ +}; + +enum FUNCTION_KEY { + KEY_F1 = 128, /* F1 key */ + KEY_F2, /* F2 key */ + KEY_F3, /* F3 key */ + KEY_F4, /* F4 key */ + KEY_F5, /* F5 key */ + KEY_F6, /* F6 key */ + KEY_F7, /* F7 key */ + KEY_F8, /* F8 key */ + KEY_F9, /* F9 key */ + KEY_F10, /* F10 key */ + KEY_F11, /* F11 key */ + KEY_F12, /* F12 key */ + + KEY_PRINT_SCREEN, /* Print Screen key */ + KEY_SCROLL_LOCK, /* Scroll lock */ + KEY_CAPS_LOCK, /* caps lock */ + KEY_NUM_LOCK, /* num lock */ + KEY_INSERT, /* Insert key */ + KEY_HOME, /* Home key */ + KEY_PAGE_UP, /* Page Up key */ + KEY_PAGE_DOWN, /* Page Down key */ + + RIGHT_ARROW, /* Right arrow */ + LEFT_ARROW, /* Left arrow */ + DOWN_ARROW, /* Down arrow */ + UP_ARROW, /* Up arrow */ +}; + +typedef struct { + unsigned char usage; + unsigned char modifier; +} KEYMAP; + +#ifdef US_KEYBOARD +/* US keyboard (as HID standard) */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x34, KEY_SHIFT}, /* " */ + {0x20, KEY_SHIFT}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x1f, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x31, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x31, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x35, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; + +#else +/* UK keyboard */ +#define KEYMAP_SIZE (152) +const KEYMAP keymap[KEYMAP_SIZE] = { + {0, 0}, /* NUL */ + {0, 0}, /* SOH */ + {0, 0}, /* STX */ + {0, 0}, /* ETX */ + {0, 0}, /* EOT */ + {0, 0}, /* ENQ */ + {0, 0}, /* ACK */ + {0, 0}, /* BEL */ + {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ + {0x2b, 0}, /* TAB */ /* Keyboard Tab */ + {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ + {0, 0}, /* VT */ + {0, 0}, /* FF */ + {0, 0}, /* CR */ + {0, 0}, /* SO */ + {0, 0}, /* SI */ + {0, 0}, /* DEL */ + {0, 0}, /* DC1 */ + {0, 0}, /* DC2 */ + {0, 0}, /* DC3 */ + {0, 0}, /* DC4 */ + {0, 0}, /* NAK */ + {0, 0}, /* SYN */ + {0, 0}, /* ETB */ + {0, 0}, /* CAN */ + {0, 0}, /* EM */ + {0, 0}, /* SUB */ + {0, 0}, /* ESC */ + {0, 0}, /* FS */ + {0, 0}, /* GS */ + {0, 0}, /* RS */ + {0, 0}, /* US */ + {0x2c, 0}, /* */ + {0x1e, KEY_SHIFT}, /* ! */ + {0x1f, KEY_SHIFT}, /* " */ + {0x32, 0}, /* # */ + {0x21, KEY_SHIFT}, /* $ */ + {0x22, KEY_SHIFT}, /* % */ + {0x24, KEY_SHIFT}, /* & */ + {0x34, 0}, /* ' */ + {0x26, KEY_SHIFT}, /* ( */ + {0x27, KEY_SHIFT}, /* ) */ + {0x25, KEY_SHIFT}, /* * */ + {0x2e, KEY_SHIFT}, /* + */ + {0x36, 0}, /* , */ + {0x2d, 0}, /* - */ + {0x37, 0}, /* . */ + {0x38, 0}, /* / */ + {0x27, 0}, /* 0 */ + {0x1e, 0}, /* 1 */ + {0x1f, 0}, /* 2 */ + {0x20, 0}, /* 3 */ + {0x21, 0}, /* 4 */ + {0x22, 0}, /* 5 */ + {0x23, 0}, /* 6 */ + {0x24, 0}, /* 7 */ + {0x25, 0}, /* 8 */ + {0x26, 0}, /* 9 */ + {0x33, KEY_SHIFT}, /* : */ + {0x33, 0}, /* ; */ + {0x36, KEY_SHIFT}, /* < */ + {0x2e, 0}, /* = */ + {0x37, KEY_SHIFT}, /* > */ + {0x38, KEY_SHIFT}, /* ? */ + {0x34, KEY_SHIFT}, /* @ */ + {0x04, KEY_SHIFT}, /* A */ + {0x05, KEY_SHIFT}, /* B */ + {0x06, KEY_SHIFT}, /* C */ + {0x07, KEY_SHIFT}, /* D */ + {0x08, KEY_SHIFT}, /* E */ + {0x09, KEY_SHIFT}, /* F */ + {0x0a, KEY_SHIFT}, /* G */ + {0x0b, KEY_SHIFT}, /* H */ + {0x0c, KEY_SHIFT}, /* I */ + {0x0d, KEY_SHIFT}, /* J */ + {0x0e, KEY_SHIFT}, /* K */ + {0x0f, KEY_SHIFT}, /* L */ + {0x10, KEY_SHIFT}, /* M */ + {0x11, KEY_SHIFT}, /* N */ + {0x12, KEY_SHIFT}, /* O */ + {0x13, KEY_SHIFT}, /* P */ + {0x14, KEY_SHIFT}, /* Q */ + {0x15, KEY_SHIFT}, /* R */ + {0x16, KEY_SHIFT}, /* S */ + {0x17, KEY_SHIFT}, /* T */ + {0x18, KEY_SHIFT}, /* U */ + {0x19, KEY_SHIFT}, /* V */ + {0x1a, KEY_SHIFT}, /* W */ + {0x1b, KEY_SHIFT}, /* X */ + {0x1c, KEY_SHIFT}, /* Y */ + {0x1d, KEY_SHIFT}, /* Z */ + {0x2f, 0}, /* [ */ + {0x64, 0}, /* \ */ + {0x30, 0}, /* ] */ + {0x23, KEY_SHIFT}, /* ^ */ + {0x2d, KEY_SHIFT}, /* _ */ + {0x35, 0}, /* ` */ + {0x04, 0}, /* a */ + {0x05, 0}, /* b */ + {0x06, 0}, /* c */ + {0x07, 0}, /* d */ + {0x08, 0}, /* e */ + {0x09, 0}, /* f */ + {0x0a, 0}, /* g */ + {0x0b, 0}, /* h */ + {0x0c, 0}, /* i */ + {0x0d, 0}, /* j */ + {0x0e, 0}, /* k */ + {0x0f, 0}, /* l */ + {0x10, 0}, /* m */ + {0x11, 0}, /* n */ + {0x12, 0}, /* o */ + {0x13, 0}, /* p */ + {0x14, 0}, /* q */ + {0x15, 0}, /* r */ + {0x16, 0}, /* s */ + {0x17, 0}, /* t */ + {0x18, 0}, /* u */ + {0x19, 0}, /* v */ + {0x1a, 0}, /* w */ + {0x1b, 0}, /* x */ + {0x1c, 0}, /* y */ + {0x1d, 0}, /* z */ + {0x2f, KEY_SHIFT}, /* { */ + {0x64, KEY_SHIFT}, /* | */ + {0x30, KEY_SHIFT}, /* } */ + {0x32, KEY_SHIFT}, /* ~ */ + {0,0}, /* DEL */ + + {0x3a, 0}, /* F1 */ + {0x3b, 0}, /* F2 */ + {0x3c, 0}, /* F3 */ + {0x3d, 0}, /* F4 */ + {0x3e, 0}, /* F5 */ + {0x3f, 0}, /* F6 */ + {0x40, 0}, /* F7 */ + {0x41, 0}, /* F8 */ + {0x42, 0}, /* F9 */ + {0x43, 0}, /* F10 */ + {0x44, 0}, /* F11 */ + {0x45, 0}, /* F12 */ + + {0x46, 0}, /* PRINT_SCREEN */ + {0x47, 0}, /* SCROLL_LOCK */ + {0x39, 0}, /* CAPS_LOCK */ + {0x53, 0}, /* NUM_LOCK */ + {0x49, 0}, /* INSERT */ + {0x4a, 0}, /* HOME */ + {0x4b, 0}, /* PAGE_UP */ + {0x4e, 0}, /* PAGE_DOWN */ + + {0x4f, 0}, /* RIGHT_ARROW */ + {0x50, 0}, /* LEFT_ARROW */ + {0x51, 0}, /* DOWN_ARROW */ + {0x52, 0}, /* UP_ARROW */ +}; +#endif + +#endif diff --git a/libraries/ESP32_BLE_Arduino/src/HIDTypes.h b/libraries/ESP32_BLE_Arduino/src/HIDTypes.h new file mode 100644 index 0000000..64850ef --- /dev/null +++ b/libraries/ESP32_BLE_Arduino/src/HIDTypes.h @@ -0,0 +1,96 @@ +/* Copyright (c) 2010-2011 mbed.org, MIT License +* +* 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 USBCLASS_HID_TYPES +#define USBCLASS_HID_TYPES + +#include + +/* */ +#define HID_VERSION_1_11 (0x0111) + +/* HID Class */ +#define HID_CLASS (3) +#define HID_SUBCLASS_NONE (0) +#define HID_PROTOCOL_NONE (0) + +/* Descriptors */ +#define HID_DESCRIPTOR (33) +#define HID_DESCRIPTOR_LENGTH (0x09) +#define REPORT_DESCRIPTOR (34) + +/* Class requests */ +#define GET_REPORT (0x1) +#define GET_IDLE (0x2) +#define SET_REPORT (0x9) +#define SET_IDLE (0xa) + +/* HID Class Report Descriptor */ +/* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */ +/* of data as per HID Class standard */ + +/* Main items */ +#ifdef ARDUINO_ARCH_ESP32 +#define HIDINPUT(size) (0x80 | size) +#define HIDOUTPUT(size) (0x90 | size) +#else +#define INPUT(size) (0x80 | size) +#define OUTPUT(size) (0x90 | size) +#endif +#define FEATURE(size) (0xb0 | size) +#define COLLECTION(size) (0xa0 | size) +#define END_COLLECTION(size) (0xc0 | size) + +/* Global items */ +#define USAGE_PAGE(size) (0x04 | size) +#define LOGICAL_MINIMUM(size) (0x14 | size) +#define LOGICAL_MAXIMUM(size) (0x24 | size) +#define PHYSICAL_MINIMUM(size) (0x34 | size) +#define PHYSICAL_MAXIMUM(size) (0x44 | size) +#define UNIT_EXPONENT(size) (0x54 | size) +#define UNIT(size) (0x64 | size) +#define REPORT_SIZE(size) (0x74 | size) //bits +#define REPORT_ID(size) (0x84 | size) +#define REPORT_COUNT(size) (0x94 | size) //bytes +#define PUSH(size) (0xa4 | size) +#define POP(size) (0xb4 | size) + +/* Local items */ +#define USAGE(size) (0x08 | size) +#define USAGE_MINIMUM(size) (0x18 | size) +#define USAGE_MAXIMUM(size) (0x28 | size) +#define DESIGNATOR_INDEX(size) (0x38 | size) +#define DESIGNATOR_MINIMUM(size) (0x48 | size) +#define DESIGNATOR_MAXIMUM(size) (0x58 | size) +#define STRING_INDEX(size) (0x78 | size) +#define STRING_MINIMUM(size) (0x88 | size) +#define STRING_MAXIMUM(size) (0x98 | size) +#define DELIMITER(size) (0xa8 | size) + +/* HID Report */ +/* Where report IDs are used the first byte of 'data' will be the */ +/* report ID and 'length' will include this report ID byte. */ + +#define MAX_HID_REPORT_SIZE (64) + +typedef struct { + uint32_t length; + uint8_t data[MAX_HID_REPORT_SIZE]; +} HID_REPORT; + +#endif diff --git a/libraries/ESP32_Rest_Client/LICENSE b/libraries/ESP32_Rest_Client/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/libraries/ESP32_Rest_Client/LICENSE @@ -0,0 +1,674 @@ + GNU 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. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state 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 program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/libraries/ESP32_Rest_Client/README.md b/libraries/ESP32_Rest_Client/README.md new file mode 100644 index 0000000..a62e655 --- /dev/null +++ b/libraries/ESP32_Rest_Client/README.md @@ -0,0 +1,2 @@ +# Arduino ESP32 Rest Client +ESP32 Rest Client for Arduino diff --git a/libraries/ESP32_Rest_Client/RestClient.cpp b/libraries/ESP32_Rest_Client/RestClient.cpp new file mode 100644 index 0000000..9fc0d67 --- /dev/null +++ b/libraries/ESP32_Rest_Client/RestClient.cpp @@ -0,0 +1,170 @@ +#include "RestClient.h" + +#define ESP32_RESTCLIENT_DEBUG + +#ifdef ESP32_RESTCLIENT_DEBUG +#define DEBUG_PRINT(x) Serial.print(x); +#else +#define DEBUG_PRINT(x) +#endif +//TODO: Add Timeout to send and read requests + +const char *contentType = "text/plain"; + +RestClient::RestClient(const char *_host, const int _port) +{ + port = _port; + host = _host; + num_headers = 0; +} + +int RestClient::begin(const char *ssid, const char *pass) +{ + WiFi.begin(ssid, pass); + DEBUG_PRINT("\n[Connecting] ["); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + DEBUG_PRINT("-"); + } + + DEBUG_PRINT("]\n"); + DEBUG_PRINT("[Connected to: "); + DEBUG_PRINT(ssid); + DEBUG_PRINT("]\n"); + DEBUG_PRINT("[IP address: "); + DEBUG_PRINT(WiFi.localIP()); + DEBUG_PRINT("]\n"); + return WiFi.status(); +} + +int RestClient::get(const char *path) +{ + return request("GET", path, NULL); +} + +int RestClient::post(const char *path, const char *body) +{ + return request("POST", path, body); +} + +void RestClient::setHeader(const char *header) +{ + headers[num_headers] = header; + num_headers++; +} + +void RestClient::setContentType(const char *contentTypeValue) +{ + contentType = contentTypeValue; +} + +void RestClient::write(const char *string) +{ + DEBUG_PRINT(string); + client_s.print(string); +} + +void RestClient::writeHeaders() +{ + for (int i = 0; i < num_headers; i++) + { + write(headers[i]); + write("\r\n"); + } +} + +void RestClient::writeBody(const char *body) +{ + if (body != NULL) + { + char contentLength[30]; + sprintf(contentLength, "Content-Length: %d\r\n", strlen(body)); + write(contentLength); + + write("Content-Type: "); + write(contentType); + write("\r\n"); + } + + write("\r\n"); + + if (body != NULL) + { + write(body); + write("\r\n"); + write("\r\n"); + } +} + +int RestClient::request(const char *method, const char *path, const char *body) +{ + int statusCode = -1; + if (!client_s.connect(host, port)) + { + DEBUG_PRINT("[Connection failed]\n"); + return 0; + } + DEBUG_PRINT("[Connected to: "); + DEBUG_PRINT(host); + DEBUG_PRINT("]\n"); + DEBUG_PRINT("[Request: \n"); + + write(method); + write(" "); + write(path); + write(" "); + write("HTTP/1.1\r\n"); + writeHeaders(); + write("Host: "); + write(host); + write("\r\n"); + write("Connection: close\r\n"); + writeBody(body); + + DEBUG_PRINT("][End Request]\n"); + delay(100); + DEBUG_PRINT("[Reading Response]\n"); + statusCode = readResponse(); + DEBUG_PRINT("[End Read Response]\n"); + DEBUG_PRINT("[Stoping client]\n"); + num_headers = 0; + client_s.stop(); + delay(50); + DEBUG_PRINT("[Client stopped]\n"); + return statusCode; +} + +int RestClient::readResponse() +{ + boolean inStatus = false; + char statusCode[4]; + int i = 0; + int code = 0; + + while (client_s.connected() && client_s.available()) + { + if (client_s.available()) + { + char c = client_s.read(); + DEBUG_PRINT(c); + + if (c == ' ' && !inStatus) + { + inStatus = true; + } + + if (inStatus && i < 3 && c != ' ') + { + statusCode[i] = c; + i++; + } + if (i == 3) + { + statusCode[i] = '\0'; + code = atoi(statusCode); + } + } + } + return code; +} diff --git a/libraries/ESP32_Rest_Client/RestClient.h b/libraries/ESP32_Rest_Client/RestClient.h new file mode 100644 index 0000000..f1554b6 --- /dev/null +++ b/libraries/ESP32_Rest_Client/RestClient.h @@ -0,0 +1,32 @@ +#include +#include +#include + +class RestClient +{ +public: + RestClient(const char *host, const int port); + int begin(const char *ssid, const char *pass); + IPAddress getIpAddress(); + + int get(const char *); + int post(const char *path, const char *body); + + void setHeader(const char *header); + void setContentType(const char *contentTypeValue); + void write(const char *string); + int request(const char *method, const char *path, const char *body); + int readResponse(); + +private: + int port; + int num_headers; + const char *host; + const char *headers[10]; + const char *contentType; + WiFiClient client; + WiFiClientSecure client_s; + + void writeHeaders(); + void writeBody(const char *body); +}; \ No newline at end of file diff --git a/libraries/ESP32_Rest_Client/examples/SimpleGet/simpleget.ino b/libraries/ESP32_Rest_Client/examples/SimpleGet/simpleget.ino new file mode 100644 index 0000000..b3d2fe4 --- /dev/null +++ b/libraries/ESP32_Rest_Client/examples/SimpleGet/simpleget.ino @@ -0,0 +1,20 @@ +#include + +RestClient client = RestClient("google.es", 443); + +void setup() +{ + Serial.begin(9600); + delay(1000); + + client.begin("ssid", "password"); + + int statusCode = client.get("/"); + Serial.print("Status code from server: "); + Serial.println(statusCode); +} + +void loop() +{ + +} diff --git a/libraries/ESP32_Rest_Client/library.json b/libraries/ESP32_Rest_Client/library.json new file mode 100644 index 0000000..d32ec45 --- /dev/null +++ b/libraries/ESP32_Rest_Client/library.json @@ -0,0 +1,18 @@ +{ + "name": "Arduino-ESP32-Rest-Client", + "description": "Arduino WIFI Rest Client for ESP32", + "keywords": "esp32,wifi,rest,client", + "repository": { + "type": "git", + "url": "https://github.com/eduardomarcos/arduino-esp32-restclient.git" + }, + "authors": [ + { + "name": "Eduardo Marcos", + "email": "dudy1984@gmail.com", + "maintainer": true + } + ], + "frameworks": "espidf", + "platforms": "*" +} \ No newline at end of file diff --git a/libraries/ESP32_Rest_Client/library.properties b/libraries/ESP32_Rest_Client/library.properties new file mode 100644 index 0000000..eb7b87c --- /dev/null +++ b/libraries/ESP32_Rest_Client/library.properties @@ -0,0 +1,9 @@ +name=ESP32 Rest Client +version=1.0.0 +author=Eduardo Marcos +maintainer=Eduardo Marcos +sentence=Arduino WIFI Rest Client for ESP32 +paragraph=Exposes REST methods to communicate with a host, like get and post in an easier way than using the WIFI library +category=Communication +url=https://github.com/eduardomarcos/arduino-esp32-restclient +architectures=* \ No newline at end of file diff --git a/libraries/ESP8266_MQTT_Mesh/LICENSE b/libraries/ESP8266_MQTT_Mesh/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/LICENSE @@ -0,0 +1,674 @@ + GNU 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. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state 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 program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/libraries/ESP8266_MQTT_Mesh/README.md b/libraries/ESP8266_MQTT_Mesh/README.md new file mode 100644 index 0000000..e50eb22 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/README.md @@ -0,0 +1,159 @@ +# ESP8266MQTTMesh +Self-assembling mesh network built around the MQTT protocol for the ESP8266 with OTA support + +## Overview +This code provides a library that can build a mesh network between ESP8266 devices that will allow all nodes to communicate +with an MQTT broker. At least one node must be able to see a wiFi router, and there must me a host on the WiFi network running +the MQTT broker. +Nodes cannot (generally) communicate between themselves, but instead forward all messages through the broker. +Each node will expose a hidden AP which can be connected to from any other node on the network. Note: hiding the AP does not provide +any additional security, but does minimize the clutter of other WiFi clients in the area. + +Additionally the library provides an OTA mechanism using the MQTT pathway which can update any/all nodes on the mesh. + +This code was developed primarily for the Sonoff line of relays, but should work with any ESP8266 board with sufficient flash memory + +Further information about the mesh topology can be found [here](docs/MeshTopology.md) + +### Note for version >= 1.0 +As of version 1.0 the nodes no longer need to connect to the broker once before use. Nodes can self-identify other nodes automatically +and do not store any needed state on the broker. Nodes use the MAC address to identify other nodes, and the ESP8266MQTTMesh code +will change the node's MAC address to match the required pattern. The MAC addresses are based on both the node's chipID as well as the mesh password, ensuring that each node will have a unique MAC address as well as that multiple meshes can run in the same area independently. + +Also, he ESP8266MQTTMesh code no longer uses the SPIFFS file-system. If you need it, you'll need to initialize it yourself. If +the mesh is configured to use SSL between nodes, the SSL certs need to be provided during initalization + +## OTA +While all nodes must run the same version of the ESP8622MQTTMesh library, each node may run a unique firmware with independent purposes. +The main purpose behind this library was to provide a backbone on which several home-automation sensors could be built. As such +each node may need different code to achieve its purpose. Because firmwares are large, and memory is limited on the ESP8266 platform, +there is only a single memory area to hold the incoming firmware. To ensure that a given firmware is only consumed by the proper nodes, +The firmware defines a unique identifier that distinguishes itself from other code. A given firmware is broadcast from the MQTT +broker to all nodes, but only nodes with a matching ID will update. + +## Using the Library +### Prerequisites +This library has been converted to use Asynchronous communication for imroved reliability. It requires the following libraries to be installed +* AsyncMqttClient +* ESPAsyncTCP +* Arduino ESP8266 Core version 2.4 + +PlatformIO is strongly recommended, and installation instructions can be found [here](http://docs.platformio.org/en/latest/platforms/espressif8266.html)). + +**NOTE:** Enabling SSL will add ~70kB to the firmware size, and may make it impossible to use OTA updates depending on firmware and flash size. + +If OTA support is desired, the esp8266 module must have at least 1M or Flash (configured as >=784k ROM). The OTA image is stored +between the end of the firmware image and the beginning of the filesystem (i.e. not in the filesystem itself) or the end of flash if +no filesystem is present. Thus, ithe maximum firmware size is ~ 1/2 the available Flash. + +### Library initialization +The ESP8266MQTTMesh only requires 2 parameters to initialize, but there are many additional optional parameters: +``` +ESP8266MQTTMesh mesh = ESP8266MQTTMesh::Builder(networks, mqtt_server, mqtt_port).build(); +``` +- `wifi_conn networks[]` *Required*: A list of SSIDs/BSSIDs/passwords to search for to connect to the wireless network. The list should be terminated with a NULL element. +- `const char *mqtt_server` *Required*: Host which runs the MQTT broker +- `int mqtt_port` *Optional*: Port which the MQTT broker is running on. Defaults to 1883 if MQTT SSL is not enabled. Defaults to 8883 is MQTT SSL is enabled + +Additional Parameters can be enabled via the *Builder* for example: +``` +ESP8266MQTTMesh mesh = ESP8266MQTTMesh::Builder(networks, mqtt_server, mqtt_port) + .setVersion(firmware_ver, firmware_id) + .setMeshPassword(password) + .build(); +``` +These additional parameters are specified before calling build(), and as few or as many can be used as neeeded. + +``` +setVersion(firmware_ver, firmware_id) +``` +- `const char *firmware_ver`: This is a string that idenitfies the firmware. It can be whatever you like. It will be broadcast to the MQTT broker on successful connection +- `int firmware_id`: This identifies a specific node codebase. Each unique firmware should have its own id, and it should not be changed between revisions of the code + +``` +setMqttAuth(username, password) +``` +- `const char *username`: The username used to login to the MQTT broker +- `const char *password`: The password used to login to the MQTT broker + +``` +setMeshPassword(password) +``` +- `const char *password`: The password to use when a node connects to another node on the mesh. Default: `ESP8266MQTTMesh` + +``` +setMeshSSID(ssid) +``` +- `const char *ssid`: The SSID used by mesh nodes. All nodes will use the same SSID, though this isn't noticeable due to nodes using hidden networks. Default: `esp8266_mqtt_mesh + +``` +setMeshPort(port) +``` +- `int port`: Port for mesh nodes to listen on for message parsing. Default: `1884` + +``` +setTopic(in_topic, out_topic) +``` +- `const char *in_topic`: MQTT topic prefix for messages sent to the node. Default: `esp8266-in/` +- `const char *out_topic`: MQTT topic prefix for messages from the node. Default: `esp8266-out/` + +If SSL support is enabled, the following optional parameters are available: +``` +setMqttSSL(enable, fingerprint) +``` +- `bool enable`: Enable MQTT SSL support. Default: `false` +- `const uint8_t *fingerprint`: Fingerprint to verify MQTT certificate (prevent man-in-the-middle attacks) + +``` +setMeshSSL(cert, cert_len, key, key_len, fingerprint) +``` +- `const uint8_t *cert`: Certificate +- `uint32_t cert_len`: Certificate length +- `const uint8_t *key`: Certificate key +- `uint32_t key_len`: Certificate key length +- `uint8_t *fingerprint`: Certificate fingerprint (SHA1 of certificate) + +### Interacting with the mesh +Besides the constructor, the code must call the `begin()` method during setup, and the `loop()` method in the main loop + +If messages need to be received by the node, execute the `callback()` function during setup with a function pointer +(prototype: `void callback(const char *topic, const char *payload)`) + +To send messages to the MQTT broker, use one of the publish methods: +``` +publish(topic, payload, msgCmd) +publish_node(topic, payload, msgCmd) +``` +The `publish` function sends messages with the `out_topic` prefix, and will not be relayed to any nodes. The `publish_nodes` +function will send messages with the `in_topic` prefix and will be relayed back to all nodes + +- `const char *topic`: the message topic (will be appended to the topic-prefix) +- `const char *payload`: The message to send (must be less than 1152 bytes in length) +- `enum MSG_TYPE msgCmd`: The MQTT Retail/QoS partameters (optional). Must be one of: `MSG_TYPE_NONE`, + `MSG_TYPE_QOS_0`, `MSG_TYPE_QOS_1`, `MSG_TYPE_QOS_2`, `MSG_TYPE_RETAIN_QOS_0`, MSG_TYPE_RETAIN_QOS_1`, + `MSG_TYPE_RETAIN_QOS_2`. Default: `MSG_TYPE_NONE` + +### SSL support +SSL support is enabled by defining `ASYNC_TCP_SSL_ENABLED=1`. This must be done globally during build. + +Once enabled, SSL can be optionally enabled between the node and the MQTT broker or between mesh nodes (or both). + +#### Using SSL with the MQTT Broker +**WARNING:** Make sure you do not use SHA512 certificate signatures on your MQTT broker. They are not suppoorted by ESP8266 properly +Using an optional fingerprint ensures that the MQTT Broker is the one you expect. Specifying a fingerprint is useful to prevent man-in-the-middle attacks. +The fingerprint can be generated by running: +``` +utils/get_mqtt_fingerprint.py --host --post +``` +This will also check the signature to make sure it is compatible with ESP8266 + +#### Using SSL between mesh nodes +**NOTE:** Enabling SSL between mesh nodes should not provide additional security, since the mesh connections are already secured via WPA, so enabling this is not recommended + +Generate the certificate, key and fingerprint: +``` +utils/gen_server_cert.sh +``` + +Add the resulting ssl_cert.h to your project. then add to the Builder `setMeshSSL(ssl_cert, ssl_cert_len, ssl_key, ssl_key_len, ssl_fingerprint)` + diff --git a/libraries/ESP8266_MQTT_Mesh/docs/MeshTopology.md b/libraries/ESP8266_MQTT_Mesh/docs/MeshTopology.md new file mode 100644 index 0000000..53e29cb --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/docs/MeshTopology.md @@ -0,0 +1,25 @@ +# Mesh Construction and Communication + +![Mesh Topology](images/MeshTopology.png) + +Nodes always power up in Station mode. Each node in the mesh will 1st try to connect to one of the configured WiFi access-points.i +If the node cannot see any APs, it will next attempt to connect to any nearby nodes. +A potential node is identified by having a hidden SSID and having a specific MAC address. +Each node constructs its MAC address by combining its chipId (upper 3 octets) with the mesh password and generating the lower 3 octets. +The connecting node will check each hidden network's MAC address by calculating the lower 3 octets and verifying that they match the +AP. All matching nodes are sorted by signal strength, and the node will attempt to connect to each in order. + +Once connected, the connecting node will then open a RCP connection on the specified port, and send the mesh password to the AP. Next the +node will broadcast a MQTT message about its availability (ex `esp8266-out/bssid/aabbcc=00:11:22:33:44:55`). This message is sent with the +retain bit set to preserve it on the broker, but it is never consumed by any other nodes. Lastly, the node will start its own AP and begin +listening for connections from other nodes. + +Each node can only handle 4 incoming commections, and will refuse connections once it has reached the limit. While the max connection +count is limited by the ESP8266 core code. The ESP8266MQTTMesh library also assumes this limit. + +If a node loses its connection to the AP, it will also shutdown its own AP. This will result in the subsection of the mesh reorganizing +and rebuilding automatically. It is required that at least one node can see the WiFi AP at any time as the broker must be available +to handle message passing. + +Mesh nodes cannot generally communicate directly with each other. Instead all messages are passed through the MQTT broker. Nodes +automatically subscribe to all topics beginning with the `in_topic` value (typically `esp8266-in/`). diff --git a/libraries/ESP8266_MQTT_Mesh/docs/images/MeshTopology.png b/libraries/ESP8266_MQTT_Mesh/docs/images/MeshTopology.png new file mode 100644 index 0000000000000000000000000000000000000000..49728de4a7eb01e90727df6a8bb3f94d7e4d5fb9 GIT binary patch literal 81450 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∨GS}ns`tUB+ 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 literal 0 HcmV?d00001 diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/README.md b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/README.md new file mode 100644 index 0000000..f0fc252 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/README.md @@ -0,0 +1,34 @@ +# ESP8266MeshHelloWorld + +This is a trivial node that can be used to test the mesh network. + +## Configuration +a sample configuration is provided in `src/credentials.h.example`. + +1) First copy `src/credentials.h.example` to `src/credentials.h` +2) Edit `credentials.h` and modify the configuration to suit your environment + +The following variables should be set: + - *NETWORK_PASSWORD* : Specifies the password of your wireless network + - *NETWORK_LIST* : Specifies the SSIDs of your wireless network + - *MQTT_SERVER* : Specify the IP address of your MQTT broker + - *MQTT_PORT* : Specify the port of your MQTT broker + +The following can optionally be changed: + - *MESH_PASSWORD* : A string used by all nodes to prevent unauthorized access to the mesh network + - *MESH_PORT* : The port that the mesh nodes listen on + +These options are only relevant if the node is compiled with SSL enabled: + - *MESH_SECURE* : Enable SSL bewteen mesh nodes. This requires that the `ssl_cert.h` file is present (Use [this](https://github.com/marvinroger/async-mqtt-client/blob/master/scripts/gen_server_cert.sh) script to generate this header) + - *MQTT_SECURE* : Enable SSL connection to the MQTT broker. + - *MQTT_FINGERPRINT* : a 20-byte string that uniquely identifies your MQTTbroker. A script to retrieve the fingerprint can be found [here](https://github.com/marvinroger/async-mqtt-client/blob/master/scripts/get-fingerprint/get-fingerprint.py) + +## Compiling and uploading +This example has been designed to use platformio for building and install +Assuming you have already setup a platformio environment: + +### Non-SSL +`platformio run --target upload` + +### SSL (Still experimental) +`platformio run -e ssl --target upload` diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/fingerprint b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/fingerprint new file mode 100644 index 0000000..7a96d84 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/fingerprint @@ -0,0 +1 @@ +Èÿøиçð‡,WNÝÓH,5d \ No newline at end of file diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/server.cer b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/data/ssl/server.cer new file mode 100644 index 0000000000000000000000000000000000000000..fa21cf1134da937dafe5101b01be3f3f0041bd2b GIT binary patch literal 587 zcmXqLVsbZVV$^5iWVj^iGBfPthl>WhY@Awc9&O)w85vnw84P3$r3@t4m_u2(cm!RG z3yM;Ui!;*{f-8$lQge$9A4GoM9j17&V#CeT@Od|s*7bqraAP&(jkXRAo z6ReP*r{EeKU}0otW+-ML0+M3p;Y}?r0Ey`3TQ7&OjDb_63U19M|9gF#~_Q)45; zvZpVke*ci#ZgPIc1c9msSsDA~vkpv;J#_xUwR5S0;aj~F8{^XUu@>C<9MkI{#H_TY zRBaNA{K2{T8GZ#Xoj+dqq@Qtb>8xGfP6aRN^t>@AwT7vB|DUw5WudNx-G=sW7Nl~Q zU1*`Ga~~dvh#pp#N5Ql$Pn+pELiqX zjSla<5Z1Mc_beZEuXN`PKYTz`f@goBQB+?>O4Xvr>Safi#0;xMu4{|QP0`N`{QqE2 z%k}KQgUemz^0u`3-(?pq@)oLmZ+P#?ykq&d%uYl~XW4zYcfIA@^L3H?w&`xT+|qw- zQNI2>rD)m2^V2pbi6tNJT5+Q0!PXh;gf^~w`d?m(SF}d$gXDgpg3X#A7l@<;afEg3 z4>=#>5}>;C%VACHO*2kB+F4e?ce^jB+qZh^?b9zZ(yjAn^4;-moV;oA%u9RZw)oFI zAoyP3$_k@5kN>R*3ioHR6`%dlMsl?{b6Vzvu4>NvKP#Rcd&?rJ-Erk%Lx9fE^q^r<%Z*UBb`M*3M-SSG7tZ zfnH|51#sN+T#Y~y10t+-CXxgn!JThtPH^c%@zC-wXx^ooy7tObq=`w`oMn6hd%yf< zR;E=&af>lO?4V@}c5mefXWxpRi$*D9afIzx!n`+mqCfgM)9Sx^%Yy;~0RRC4fq)^) zFQPXd*O~nhgWR{-b`x&m+_w`16EYnRHc)t4c%AmLt`$X0c%hHGpCjV4LiIxJk|?d)Ea+gWDr^Q)CUJ@&x;6F7Js$l?gBJEb4UWYW^aKGDES z!pK&X!R4V`SAJGr!83q=2Q%0U)xiQm0MnHe;lZo#8D?z$+p~4h>KrT=DnD&`%Bg~X z8JNHymI`Zz0j^H!nMh?}$g#oPAph<23Hc~&a6RiIUb8mSi@sI=>V&h} z_}OiF40bYYrtvG@&VK?y0EyNMw2~9rxKo9%jL7rI46FY535x>NA|@JvX#^A|V&7v6 zF~?s9dS8Eb@CPlf;3(iuk3Eb-L(h2y__Kob7@)E4)5isM%+a?!evBG%pM|BaYX;LG@D>qBK#kDIk literal 0 HcmV?d00001 diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/platformio.ini b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/platformio.ini new file mode 100644 index 0000000..f21710a --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/platformio.ini @@ -0,0 +1,39 @@ +# +# Example PlatformIO configuration file for SSL and non-SSL builds. +# +# Before you will be able to build the SSL version of this project, you will +# need to explicitly install the espressif8266_stage platform. +# +# To perform this installation, refer to step 1 of: +# http://docs.platformio.org/en/latest/platforms/espressif8266.html#using-arduino-framework-with-staging-version + +[platformio] +env_default = nossl + +[common] +framework = arduino +lib_deps = ESP8266MQTTMesh + +[env:nossl] +#platform = https://github.com/platformio/platform-espressif8266.git#feature/stage +platform = espressif8266@~1.6.0 +board = esp01_1m +framework = ${common.framework} +lib_deps = ${common.lib_deps} +#build_flags = -DLED_PIN=2 -g + +[env:ssl] +platform = espressif8266@~1.6.0 +board = esp01_1m +#build_flags = -DASYNC_TCP_SSL_ENABLED=1 -DGATEWAY_ID=10499051 -DLED_PIN=2 -g +build_flags = -DASYNC_TCP_SSL_ENABLED=1 +framework = ${common.framework} +lib_deps = ${common.lib_deps} + +[env:esp32_nossl] +platform = espressif32 +board = esp32dev +framework = ${common.framework} +lib_deps = ${common.lib_deps} +build_flags = -DLED_PIN=2 -g + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ESP8266MeshHelloWorld.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ESP8266MeshHelloWorld.ino new file mode 100644 index 0000000..4c71170 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ESP8266MeshHelloWorld.ino @@ -0,0 +1,98 @@ +#include "credentials.h" +#include +#include + + +#ifndef LED_PIN + #define LED_PIN LED_BUILTIN +#endif + + +#define FIRMWARE_ID 0x1337 +#define FIRMWARE_VER "0.1" +wifi_conn networks[] = NETWORK_LIST; +const char* mesh_password = MESH_PASSWORD; +const char* mqtt_server = MQTT_SERVER; +const int mqtt_port = MQTT_PORT; +#if ASYNC_TCP_SSL_ENABLED +const uint8_t *mqtt_fingerprint = MQTT_FINGERPRINT; +bool mqtt_secure = MQTT_SECURE; + #if MESH_SECURE + #include "ssl_cert.h" + #endif +#endif + +#ifdef ESP32 +String ID = String((unsigned long)ESP.getEfuseMac()); +#else +String ID = String(ESP.getChipId()); +#endif + + + + +unsigned long previousMillis = 0; +const long interval = 5000; +int cnt = 0; + +// Note: All of the '.set' options below are optional. The default values can be +// found in ESP8266MQTTMeshBuilder.h +ESP8266MQTTMesh mesh = ESP8266MQTTMesh::Builder(networks, mqtt_server, mqtt_port) + .setVersion(FIRMWARE_VER, FIRMWARE_ID) + .setMeshPassword(mesh_password) +#if ASYNC_TCP_SSL_ENABLED + .setMqttSSL(mqtt_secure, mqtt_fingerprint) +#if MESH_SECURE + .setMeshSSL(ssl_cert, ssl_cert_len, ssl_key, ssl_key_len, ssl_fingerprint) +#endif //MESH_SECURE +#endif //ASYNC_TCP_SSL_ENABLED + .build(); + +void callback(const char *topic, const char *msg); + + + +void setup() { + + Serial.begin(115200); + delay(1000); //This is only here to make it easier to catch the startup messages. It isn't required + mesh.setCallback(callback); + mesh.begin(); + pinMode(LED_PIN, OUTPUT); + +} + + +void loop() { + + + if (! mesh.connected()) + return; + + unsigned long currentMillis = millis(); + + if (currentMillis - previousMillis >= interval) { + + String cntStr = String(cnt); + String msg = "hello from " + ID + " cnt: " + cntStr; + mesh.publish(ID.c_str(), msg.c_str()); + previousMillis = currentMillis; + cnt++; + + } + +} + + + +void callback(const char *topic, const char *msg) { + + + if (0 == strcmp(topic, (const char*) ID.c_str())) { + if(String(msg) == "0") { + digitalWrite(LED_PIN, HIGH); + }else{ + digitalWrite(LED_PIN, LOW); + } + } +} diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/credentials.h.example b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/credentials.h.example new file mode 100644 index 0000000..361d75b --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/credentials.h.example @@ -0,0 +1,16 @@ +#define NETWORK_PASSWORD "network password" +#define NETWORK_LIST { \ + WIFI_CONN("ssid 1", NETWORK_PASSWORD, NULL, 0), \ + WIFI_CONN("ssid 2", NETWORK_PASSWORD, NULL, 0), \ + NULL, \ + } +#define MESH_PASSWORD "esp8266_sensor_mesh" +#define MQTT_SERVER "MQTT Server IP Address" +#define MQTT_PORT 1883 + +/* Only used if SSL is enabled */ +#define MESH_SECURE true +#define MQTT_SECURE false +#define MQTT_FINGERPRINT NULL +//const uint8_t MQTT_FINGERPRINT[] = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff,0x00,0x11,0x22,0x33}; + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ssl_cert.h b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ssl_cert.h new file mode 100644 index 0000000..bb07361 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshHelloWorld/src/ssl_cert.h @@ -0,0 +1,87 @@ +const uint8_t ssl_key[] = + "\x30\x82\x02\x5D\x02\x01\x00\x02\x81\x81\x00\xA6\xE5\xE8\x1A\xFB" + "\xF8\x1A\xB7\x34\xCF\x98\x90\x10\x7A\x80\x1D\x1C\x3F\xA7\x9A\xC0" + "\x97\x5D\xC2\xCF\xD0\xD6\xCE\x65\x11\x57\xB5\x4A\x21\x81\x5E\x66" + "\xBE\x05\x70\xDC\xF3\x5C\x8D\x40\x12\x03\x22\xAC\x75\x26\x92\x04" + "\x1F\xC1\x9D\x6F\x68\x4E\x70\xE9\x43\xF1\xD0\xF2\x2F\x68\xDE\xA5" + "\x9A\xBA\xF6\xCA\x53\xA4\x89\x49\xD8\x9C\x65\x7C\x02\x7B\xBF\xFC" + "\x66\x56\xA6\x55\x45\x71\x8B\x31\x3F\xEC\xA0\x65\x0B\x76\x6F\xE5" + "\x08\x67\xDF\x8A\x9E\x8B\x46\x29\x63\x71\x84\xED\x57\xC2\xBC\x37" + "\x79\xA2\x3F\xFA\x39\xD3\xEA\xBF\x7A\xCB\x83\x02\x03\x01\x00\x01" + "\x02\x81\x80\x21\xCB\x2F\xA2\x37\x1E\xD7\x99\xFD\x11\x83\xDC\xB7" + "\xD9\x76\x13\x6E\xE2\xDC\xB7\x13\x04\x13\x32\x1D\x0E\x36\x50\x78" + "\x5A\x78\x9D\xF6\xB2\xAE\x15\x45\x4C\x78\xA1\x8F\xBB\x9F\x23\xE2" + "\xB3\x42\xFB\x44\x5C\x3C\x41\x18\xA0\xAD\x7D\x89\x4F\x5F\x82\xB1" + "\x58\xD6\x9F\x9A\x8F\x3A\x2C\x5C\xE4\xED\xEC\x5E\x95\xDB\x59\x66" + "\xEE\xF3\xAB\x95\x3E\x3D\xF6\xC0\xFD\x13\x38\x78\x1D\xC8\xE2\x08" + "\xAC\x3B\xA5\x1F\xC8\x64\xD2\xD2\xC4\x3E\xD1\xC0\x4B\xC2\xC8\x56" + "\x94\xC1\xE5\xA1\x5C\x57\x7E\x56\x5E\xC1\x33\x80\x7F\x07\x33\xD8" + "\x0B\xD5\xC1\x02\x41\x00\xD3\x95\x14\xE1\xC1\xAB\xEF\x19\x66\x6C" + "\xFE\xDB\xB3\x75\xD0\xEA\x1C\x2C\x17\x2A\x3F\x6D\x79\xCA\xA9\x82" + "\x7F\x19\x98\xC0\x1F\x96\x0A\x6B\x86\x01\xAE\x4E\xEA\x99\x48\x65" + "\x61\xC8\xB1\xC1\xDC\x20\xFF\xEE\x3A\xB3\x10\xCE\x4B\xEF\x6C\xD0" + "\x60\x44\x68\x33\x45\x13\x02\x41\x00\xC9\xEF\x63\xDA\x7D\xF8\x8A" + "\xC9\xD1\x96\xBE\x13\xB6\x3F\xB6\x04\xF1\xD7\x06\x89\xCC\x2D\x5E" + "\xDA\x9A\x78\xA7\xAC\x40\x37\x2E\x5C\x1F\x7C\x64\x6D\x4B\xB1\x78" + "\xD6\x62\x5F\x60\x95\xC9\x7B\xD7\x03\x54\x46\x98\xD8\x4E\xA8\xB8" + "\xC6\x32\xD8\xC5\x32\xAE\xCA\xDD\xD1\x02\x40\x57\x37\x61\xF7\x39" + "\x85\x6D\x37\x14\x30\xA3\xD1\xDE\xA5\x17\x2C\x19\xD6\xD6\xE9\xB4" + "\x61\xA5\x4D\xB4\x18\x35\xDA\x50\x4C\x09\xF9\x28\x6C\x70\x3D\xEB" + "\x23\x5E\xB3\x36\xD3\x8B\xBE\x55\xFF\xEA\x84\xB3\xDA\xF8\xD9\x6D" + "\x79\x0C\x76\x32\x6D\xA6\xF1\x2B\xDE\xCE\x7F\x02\x41\x00\x89\xD6" + "\x0B\xB4\x92\x13\xDA\xB8\x53\x85\xAF\x8C\xC8\xF3\xC8\x0C\xAB\xFE" + "\xF8\x09\x8B\x02\xD5\x22\x26\x1A\x81\x69\x04\x14\x26\x62\xDF\x63" + "\x0B\x31\xC7\x5F\x06\x7A\x5F\x7F\x76\xF0\x07\x2D\xAE\xE0\x28\xE0" + "\x5F\x68\x16\x98\xF8\x36\xE1\x72\x31\x78\x9C\xF3\x00\x61\x02\x41" + "\x00\xB5\x07\xCF\xC3\x62\x65\xD3\xD4\x97\x2B\xCA\x6B\x66\x75\x5C" + "\xE1\x38\xFD\xAF\xC2\xA9\xCE\x21\x3F\x09\xDC\xEA\xEE\xD2\x09\xF8" + "\xCF\xF6\x8D\xEC\x95\xF2\x12\xAC\xE7\x11\x30\xE3\xC6\xDB\x26\x17" + "\x64\xB1\xC2\x78\xF0\x47\x75\x11\xA9\x69\x52\x23\xC3\x2B\x37\x4B" + "\xBB" +; +const uint32_t ssl_key_len = 609; +const uint8_t ssl_cert[] = + "\x30\x82\x02\x47\x30\x82\x01\x2F\x02\x09\x00\xD2\x15\x44\x99\x56" + "\xC9\xF0\xD1\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05" + "\x05\x00\x30\x1C\x31\x1A\x30\x18\x06\x03\x55\x04\x0A\x0C\x11\x45" + "\x73\x70\x72\x65\x73\x73\x69\x66\x20\x53\x79\x73\x74\x65\x6D\x73" + "\x30\x1E\x17\x0D\x31\x37\x30\x37\x31\x31\x30\x33\x30\x33\x31\x32" + "\x5A\x17\x0D\x33\x31\x30\x33\x32\x30\x30\x33\x30\x33\x31\x32\x5A" + "\x30\x33\x31\x19\x30\x17\x06\x03\x55\x04\x0A\x0C\x10\x61\x78\x54" + "\x4C\x53\x20\x6F\x6E\x20\x45\x53\x50\x38\x32\x36\x36\x31\x16\x30" + "\x14\x06\x03\x55\x04\x03\x0C\x0D\x65\x73\x70\x38\x32\x36\x36\x2E" + "\x6C\x6F\x63\x61\x6C\x30\x81\x9F\x30\x0D\x06\x09\x2A\x86\x48\x86" + "\xF7\x0D\x01\x01\x01\x05\x00\x03\x81\x8D\x00\x30\x81\x89\x02\x81" + "\x81\x00\xA6\xE5\xE8\x1A\xFB\xF8\x1A\xB7\x34\xCF\x98\x90\x10\x7A" + "\x80\x1D\x1C\x3F\xA7\x9A\xC0\x97\x5D\xC2\xCF\xD0\xD6\xCE\x65\x11" + "\x57\xB5\x4A\x21\x81\x5E\x66\xBE\x05\x70\xDC\xF3\x5C\x8D\x40\x12" + "\x03\x22\xAC\x75\x26\x92\x04\x1F\xC1\x9D\x6F\x68\x4E\x70\xE9\x43" + "\xF1\xD0\xF2\x2F\x68\xDE\xA5\x9A\xBA\xF6\xCA\x53\xA4\x89\x49\xD8" + "\x9C\x65\x7C\x02\x7B\xBF\xFC\x66\x56\xA6\x55\x45\x71\x8B\x31\x3F" + "\xEC\xA0\x65\x0B\x76\x6F\xE5\x08\x67\xDF\x8A\x9E\x8B\x46\x29\x63" + "\x71\x84\xED\x57\xC2\xBC\x37\x79\xA2\x3F\xFA\x39\xD3\xEA\xBF\x7A" + "\xCB\x83\x02\x03\x01\x00\x01\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7" + "\x0D\x01\x01\x05\x05\x00\x03\x82\x01\x01\x00\x5F\x4F\xA6\x53\x1D" + "\xC2\x7C\x2C\x0D\xDE\x54\x05\xAD\x61\xDE\x39\xE2\x8B\xA9\x47\x0D" + "\x57\xC3\xC0\x15\x18\x0C\xBF\x71\x32\x5A\x8E\x68\x64\x7A\xA2\xE3" + "\x27\x76\xC4\x22\x16\x31\x7A\x14\xD7\x2B\x16\x1E\x94\x2F\x69\x51" + "\xFF\xE0\xBC\x84\xD7\x6B\x51\xC1\xA7\x45\x1E\x6E\xB4\x86\x4F\xDD" + "\x07\x15\x72\x4B\x12\x79\xEF\x31\xDE\xE4\x9E\xC6\x6F\xDA\x36\xC8" + "\x59\x1B\x6A\x3E\xF0\xDE\xD7\x84\xCE\xE7\xAE\x59\xBE\xB6\x2D\xB0" + "\xD3\x84\x8F\xD6\xA2\x6F\x2F\x9E\x22\x5B\x1D\x61\xCF\x96\xB3\x62" + "\x16\x63\xC7\x8A\xA8\xC8\x7C\xE0\xB5\x98\xAE\x12\xB1\xAE\xE5\xFF" + "\x1F\x1A\x0D\x15\x7C\x26\xF0\x19\xBF\x12\x70\xB3\x29\xF1\xA0\x14" + "\x64\x52\x08\x56\x88\xBF\x54\xCF\x5C\x44\x50\x25\xB9\xF4\xC3\x29" + "\x3B\xB2\x98\xC8\xE2\xB9\x76\x78\x0E\xDB\x8E\x52\x8B\x4D\x7B\x95" + "\xDB\xCB\xE8\x68\x67\x3B\x6F\x99\x0E\xDC\x4D\x81\x93\xB2\xA3\x99" + "\xD2\xBC\x1E\xB4\x4F\x9D\xC0\x11\xEF\x10\xD4\xA8\x32\xEC\xE3\xFE" + "\xA8\x52\x57\x4F\x04\x3D\x17\x9B\xF1\x3C\x19\xAB\x17\x03\x66\x69" + "\x90\x8A\x7B\x09\xDF\xF9\x78\xE6\xC6\xED\x04\x19\x2B\x88\xD4\xE1" + "\x80\x88\x1A\x98\x6F\x9C\x6D\x37\x18\x55\x36" +; +const uint32_t ssl_cert_len = 587; + +const uint8_t ssl_fingerprint[] = + "\xC8\x03\xFF\xF8\xD0\x8D\xB8\xE7\x11\xF0\x87\x2C\x57\x4E\xDD\xD3" + "\x48\x2C\x35\x64" +; diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.brd b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.brd new file mode 100644 index 0000000..be8c552 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.brd @@ -0,0 +1,1932 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +IRRemote 0.9 +Rx +Tx + + + +<b>Common Voltage Regulators</b> +<p>Created by Michael Shimniok <a href="http://www.bot-thoughts.com/">www.bot-thoughts.com</a></b> + + +<B>TO220BV</B> from transistor-power.lbr + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>LEDs</b><p> +<author>Created by librarian@cadsoft.de</author><br> +Extended by Federico Battaglin <author>&lt;federico.rd@fdpinternational.com&gt;</author> with DUOLED + + +<B>LED</B><p> +5 mm, round + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +2 +1 +3 + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<map name="nav_main"> +<area shape="rect" coords="0,1,140,23" href="../military_specs.asp" title=""> +<area shape="rect" coords="0,24,140,51" href="../about.asp" title=""> +<area shape="rect" coords="1,52,140,77" href="../rfq.asp" title=""> +<area shape="rect" coords="0,78,139,103" href="../products.asp" title=""> +<area shape="rect" coords="1,102,138,128" href="../excess_inventory.asp" title=""> +<area shape="rect" coords="1,129,138,150" href="../edge.asp" title=""> +<area shape="rect" coords="1,151,139,178" href="../industry_links.asp" title=""> +<area shape="rect" coords="0,179,139,201" href="../comments.asp" title=""> +<area shape="rect" coords="1,203,138,231" href="../directory.asp" title=""> +<area shape="default" nohref> +</map> + +<html> + +<title></title> + + <LINK REL="StyleSheet" TYPE="text/css" HREF="style-sheet.css"> + +<body bgcolor="#ffffff" text="#000000" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"> +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0 height="55%"> +<tr valign="top"> + +</td> +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> +</BODY></HTML> + + +<b>RESISTOR</b><p> +type 0309, grid 10mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<b>Omron Switches</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>OMRON SWITCH</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 +2 +3 +4 + + + + + + + + + + + + + + + + + + +>Name + + + + +<b>Connectors from Cypress Industries</b><p> +www.cypressindustries.com<br> +<author>Created by librarian@cadsoft.de</author> + + +<b>MINI USB-B R/A DIP</b><p> +Source: http://www.cypressindustries.com/pdf/32005-601.pdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>Laen's PCB Order Design Rules</b> +<p> +Please make sure your boards conform to these design rules. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.pdf b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/IRRemote.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3221f22e1d48147a59e33521ab6c66218a710e8 GIT binary patch literal 14893 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Connectors from Cypress Industries</b><p> +www.cypressindustries.com<br> +<author>Created by librarian@cadsoft.de</author> + + +<b>MINI USB-B R/A DIP</b><p> +Source: http://www.cypressindustries.com/pdf/32005-601.pdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +>NAME + + + + + + +MINI USB-B R/A DIP</b> 5pol.<p> +Source: www.cypressindustries.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>LEDs</b><p> +<author>Created by librarian@cadsoft.de</author><br> +Extended by Federico Battaglin <author>&lt;federico.rd@fdpinternational.com&gt;</author> with DUOLED + + +<b>CHICAGO MINIATURE LAMP, INC.</b><p> +7022X Series SMT LEDs 1206 Package Size + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + +<B>LED</B><p> +5 mm, square, Siemens + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<B>LED</B><p> +2 x 5 mm, rectangle + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + +<B>LED</B><p> +3 mm, round + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<B>LED</B><p> +5 mm, round + + + + + + + + + + + +>NAME +>VALUE + + +<B>LED</B><p> +1 mm, round, Siemens + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<B>LED BLOCK</B><p> +1 LED, Siemens + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>LED HOLDER</b><p> +Siemens + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>LED HOLDER</b><p> +Siemens + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>LED HOLDER</b><p> +Siemens + + + + + + + + + + + + + + + + + +A+ +K- +>NAME +>VALUE + + + + + +<b>LED HOLDER</b><p> +Siemens + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE ++ +- + + +<B>IR LED</B><p> +infrared emitting diode, Infineon +TO-18, lead spacing 2.54 mm, cathode marking<p> +Inifineon + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<B>IR LED</B><p> +infrared emitting diode, Infineon +TO-18, lead spacing 2.54 mm, cathode marking<p> +Inifineon + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<B>LED</B><p> +rectangle, 5.7 x 3.2 mm + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<B>IR LED</B><p> +IR transmitter Siemens + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>TOPLED® High-optical Power LED (HOP)</b><p> +Source: http://www.osram.convergy.de/ ... ls_t675.pdf + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +A +C + + + + + + + +<b>BLUE LINETM Hyper Mini TOPLED® Hyper-Bright LED</b><p> +Source: http://www.osram.convergy.de/ ... LB M676.pdf + + + + + + + + + + + + + + +A +C +>NAME +>VALUE + + + + + + + +<b>Super SIDELED® High-Current LED</b><p> +LG A672, LP A672 <br> +Source: http://www.osram.convergy.de/ ... LG_LP_A672.pdf (2004.05.13) + + + + + + + + + + + + + + + + + + + +C +A +>NAME +>VALUE + + + + + + + +<b>SmartLEDTM Hyper-Bright LED</b><p> +Source: http://www.osram.convergy.de/ ... LA_LO_LS_LY L896.pdf + + + + + + + + +>NAME +>VALUE + + + + + +<b>Hyper TOPLED® RG Hyper-Bright LED</b><p> +Source: http://www.osram.convergy.de/ ... LA_LO_LS_LY T776.pdf + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +A +C + + + + + + + + + + +<b>Hyper Micro SIDELED®</b><p> +Source: http://www.osram.convergy.de/ ... LA_LO_LS_LY Y876.pdf + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Power TOPLED®</b><p> +Source: http://www.osram.convergy.de/ ... LA_LO_LA_LY E67B.pdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +C +A +C +C + + + + + + + + + + + +<b>Hyper CHIPLED Hyper-Bright LED</b><p> +LB Q993<br> +Source: http://www.osram.convergy.de/ ... Lb_q993.pdf + + + + +>NAME +>VALUE + + + + + + + +<b>Hyper CHIPLED Hyper-Bright LED</b><p> +LB R99A<br> +Source: http://www.osram.convergy.de/ ... lb_r99a.pdf + + + + +>NAME +>VALUE + + + + + + + +<b>Mini TOPLED Santana®</b><p> +Source: http://www.osram.convergy.de/ ... LG M470.pdf + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +<b>CHIPLED</b><p> +Source: http://www.osram.convergy.de/ ... LG_R971.pdf + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + +<b>CHIPLED</b><p> +Source: http://www.osram.convergy.de/ ... LG_LY N971.pdf + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>CHIPLED</b><p> +Source: http://www.osram.convergy.de/ ... LG_LY Q971.pdf + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>CHIPLED-0603</b><p> +Recommended Solder Pad useable for SmartLEDTM and Chipled - Package 0603<br> +Package able to withstand TTW-soldering heat<br> +Package suitable for TTW-soldering<br> +Source: http://www.osram.convergy.de/ ... LO_LS_LY L89K.pdf + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + +<b>SmartLED TTW</b><p> +Recommended Solder Pad useable for SmartLEDTM and Chipled - Package 0603<br> +Package able to withstand TTW-soldering heat<br> +Package suitable for TTW-soldering<br> +Source: http://www.osram.convergy.de/ ... LO_LS_LY L89K.pdf + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + +<b>Lumileds Lighting. LUXEON®</b> with cool pad<p> +Source: K2.pdf + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Lumileds Lighting. LUXEON®</b> without cool pad<p> +Source: K2.pdf + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + +<B>LED</B><p> +10 mm, round + + + + + + + + + + + + + +>NAME +>VALUE + + +<b>SURFACE MOUNT LED LAMP</b> 3.5x2.8mm<p> +Source: http://www.kingbright.com/manager/upload/pdf/KA-3528ASYC(Ver1189474662.1) + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + +<b>SML0805-2CW-TR (0805 PROFILE)</b> COOL WHITE<p> +Source: http://www.ledtronics.com/ds/smd-0603/Dstr0093.pdf + + + + + + + + + + + +>NAME +>VALUE + + +<b>SML10XXKH-TR (HIGH INTENSITY) LED</b><p> +<table> +<tr><td>SML10R3KH-TR</td><td>ULTRA RED</td></tr> +<tr><td>SML10E3KH-TR</td><td>SUPER REDSUPER BLUE</td></tr> +<tr><td>SML10O3KH-TR</td><td>SUPER ORANGE</td></tr> +<tr><td>SML10PY3KH-TR</td><td>PURE YELLOW</td></tr> +<tr><td>SML10OY3KH-TR</td><td>ULTRA YELLOW</td></tr> +<tr><td>SML10AG3KH-TR</td><td>AQUA GREEN</td></tr> +<tr><td>SML10BG3KH-TR</td><td>BLUE GREEN</td></tr> +<tr><td>SML10PB1KH-TR</td><td>SUPER BLUE</td></tr> +<tr><td>SML10CW1KH-TR</td><td>WHITE</td></tr> +</table> + +Source: http://www.ledtronics.com/ds/smd-1206/dstr0094.PDF + + + + + + + +>NAME +>VALUE + + + + + + + + + +<b>SML0603-XXX (HIGH INTENSITY) LED</b><p> +<table> +<tr><td>AG3K</td><td>AQUA GREEN</td></tr> +<tr><td>B1K</td><td>SUPER BLUE</td></tr> +<tr><td>R1K</td><td>SUPER RED</td></tr> +<tr><td>R3K</td><td>ULTRA RED</td></tr> +<tr><td>O3K</td><td>SUPER ORANGE</td></tr> +<tr><td>O3KH</td><td>SOFT ORANGE</td></tr> +<tr><td>Y3KH</td><td>SUPER YELLOW</td></tr> +<tr><td>Y3K</td><td>SUPER YELLOW</td></tr> +<tr><td>2CW</td><td>WHITE</td></tr> +</table> +Source: http://www.ledtronics.com/ds/smd-0603/Dstr0092.pdf + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + +<b>LED</b><p> +<u>OSRAM</u>:<br> + +- <u>CHIPLED</u><br> +LG R971, LG N971, LY N971, LG Q971, LY Q971, LO R971, LY R971 +LH N974, LH R974<br> +LS Q976, LO Q976, LY Q976<br> +LO Q996<br> + +- <u>Hyper CHIPLED</u><br> +LW Q18S<br> +LB Q993, LB Q99A, LB R99A<br> + +- <u>SideLED</u><br> +LS A670, LO A670, LY A670, LG A670, LP A670<br> +LB A673, LV A673, LT A673, LW A673<br> +LH A674<br> +LY A675<br> +LS A676, LA A676, LO A676, LY A676, LW A676<br> +LS A679, LY A679, LG A679<br> + +- <u>Hyper Micro SIDELED®</u><br> +LS Y876, LA Y876, LO Y876, LY Y876<br> +LT Y87S<br> + +- <u>SmartLED</u><br> +LW L88C, LW L88S<br> +LB L89C, LB L89S, LG L890<br> +LS L89K, LO L89K, LY L89K<br> +LS L896, LA L896, LO L896, LY L896<br> + +- <u>TOPLED</u><br> +LS T670, LO T670, LY T670, LG T670, LP T670<br> +LSG T670, LSP T670, LSY T670, LOP T670, LYG T670<br> +LG T671, LOG T671, LSG T671<br> +LB T673, LV T673, LT T673, LW T673<br> +LH T674<br> +LS T676, LA T676, LO T676, LY T676, LB T676, LH T676, LSB T676, LW T676<br> +LB T67C, LV T67C, LT T67C, LS T67K, LO T67K, LY T67K, LW E67C<br> +LS E67B, LA E67B, LO E67B, LY E67B, LB E67C, LV E67C, LT E67C<br> +LW T67C<br> +LS T679, LY T679, LG T679<br> +LS T770, LO T770, LY T770, LG T770, LP T770<br> +LB T773, LV T773, LT T773, LW T773<br> +LH T774<br> +LS E675, LA E675, LY E675, LS T675<br> +LS T776, LA T776, LO T776, LY T776, LB T776<br> +LHGB T686<br> +LT T68C, LB T68C<br> + +- <u>Hyper Mini TOPLED®</u><br> +LB M676<br> + +- <u>Mini TOPLED Santana®</u><br> +LG M470<br> +LS M47K, LO M47K, LY M47K +<p> +Source: http://www.osram.convergy.de<p> + +<u>LUXEON:</u><br> +- <u>LUMILED®</u><br> +LXK2-PW12-R00, LXK2-PW12-S00, LXK2-PW14-U00, LXK2-PW14-V00<br> +LXK2-PM12-R00, LXK2-PM12-S00, LXK2-PM14-U00<br> +LXK2-PE12-Q00, LXK2-PE12-R00, LXK2-PE12-S00, LXK2-PE14-T00, LXK2-PE14-U00<br> +LXK2-PB12-K00, LXK2-PB12-L00, LXK2-PB12-M00, LXK2-PB14-N00, LXK2-PB14-P00, LXK2-PB14-Q00<br> +LXK2-PR12-L00, LXK2-PR12-M00, LXK2-PR14-Q00, LXK2-PR14-R00<br> +LXK2-PD12-Q00, LXK2-PD12-R00, LXK2-PD12-S00<br> +LXK2-PH12-R00, LXK2-PH12-S00<br> +LXK2-PL12-P00, LXK2-PL12-Q00, LXK2-PL12-R00 +<p> +Source: www.luxeon.com<p> + +<u>KINGBRIGHT:</U><p> +KA-3528ASYC<br> +Source: www.kingbright.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SOT-23, 3-pin + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + +>NAME +>VALUE +2 +1 +3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +D +G +S +>NAME +>VALUE + + + + + + + +<h3>2N7000, 2N7002, NDS7002A</h3> + +<p>N-channel enhancement mode MOSFET</p> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Common Voltage Regulators</b> +<p>Created by Michael Shimniok <a href="http://www.bot-thoughts.com/">www.bot-thoughts.com</a></b> + + +<B>TO220BV</B> from transistor-power.lbr + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +3-pin voltage regulator + + + + + + + +>NAME +>VALUE +GND +IN +OUT + + + + + + + + + + + + + + + + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<map name="nav_main"> +<area shape="rect" coords="0,1,140,23" href="../military_specs.asp" title=""> +<area shape="rect" coords="0,24,140,51" href="../about.asp" title=""> +<area shape="rect" coords="1,52,140,77" href="../rfq.asp" title=""> +<area shape="rect" coords="0,78,139,103" href="../products.asp" title=""> +<area shape="rect" coords="1,102,138,128" href="../excess_inventory.asp" title=""> +<area shape="rect" coords="1,129,138,150" href="../edge.asp" title=""> +<area shape="rect" coords="1,151,139,178" href="../industry_links.asp" title=""> +<area shape="rect" coords="0,179,139,201" href="../comments.asp" title=""> +<area shape="rect" coords="1,203,138,231" href="../directory.asp" title=""> +<area shape="default" nohref> +</map> + +<html> + +<title></title> + + <LINK REL="StyleSheet" TYPE="text/css" HREF="style-sheet.css"> + +<body bgcolor="#ffffff" text="#000000" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"> +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0 height="55%"> +<tr valign="top"> + +</td> +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> +</BODY></HTML> + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> wave soldering<p> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +wave soldering + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +Source: http://download.siliconexpert.com/pdfs/2005/02/24/Semi_Ap/2/VSH/Resistor/dcrcwfre.pdf + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> wave soldering<p> +Source: http://download.siliconexpert.com/pdfs/2005/02/24/Semi_Ap/2/VSH/Resistor/dcrcwfre.pdf + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.10 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.25 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.12 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.10 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.25 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.25 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.12 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +MELF 0.25 W + + + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b><p> +type 0204, grid 5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0204, grid 7.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0207, grid 10 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0207, grid 12 mm + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<b>RESISTOR</b><p> +type 0207, grid 15mm + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<b>RESISTOR</b><p> +type 0207, grid 2.5 mm + + + + + + + +>NAME +>VALUE + + +<b>RESISTOR</b><p> +type 0207, grid 5 mm + + + + + + + +>NAME +>VALUE + + +<b>RESISTOR</b><p> +type 0207, grid 7.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0309, grid 10mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0309, grid 12.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0411, grid 12.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0411, grid 15 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0411, grid 3.81 mm + + + + + + +>NAME +>VALUE + + + +<b>RESISTOR</b><p> +type 0414, grid 15 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0414, grid 5 mm + + + + + + +>NAME +>VALUE + + + +<b>RESISTOR</b><p> +type 0617, grid 17.5 mm + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0617, grid 22.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0617, grid 5 mm + + + + + + +>NAME +>VALUE + + + +<b>RESISTOR</b><p> +type 0922, grid 22.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<b>RESISTOR</b><p> +type 0613, grid 5 mm + + + + + + +>NAME +>VALUE + + + +<b>RESISTOR</b><p> +type 0613, grid 15 mm + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type 0817, grid 22.5 mm + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +0817 + + + + +<b>RESISTOR</b><p> +type 0817, grid 6.35 mm + + + + + + +>NAME +>VALUE +0817 + + + +<b>RESISTOR</b><p> +type V234, grid 12.5 mm + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type V235, grid 17.78 mm + + + + + + + + + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b><p> +type V526-0, grid 2.5 mm + + + + + + + + + + +>NAME +>VALUE + + +<b>Mini MELF 0102 Axial</b> + + + + +>NAME +>VALUE + + + +<b>RESISTOR</b><p> +type 0922, grid 7.5 mm + + + + + + +>NAME +>VALUE +0922 + + + +<b>CECC Size RC2211</b> Reflow Soldering<p> +source Beyschlag + + + + + + +>NAME +>VALUE + + +<b>CECC Size RC2211</b> Wave Soldering<p> +source Beyschlag + + + + + + +>NAME +>VALUE + + +<b>CECC Size RC3715</b> Reflow Soldering<p> +source Beyschlag + + + + + + + + +>NAME +>VALUE + + +<b>CECC Size RC3715</b> Wave Soldering<p> +source Beyschlag + + + + + + + + +>NAME +>VALUE + + +<b>CECC Size RC6123</b> Reflow Soldering<p> +source Beyschlag + + + + + + + + +>NAME +>VALUE + + +<b>CECC Size RC6123</b> Wave Soldering<p> +source Beyschlag + + + + + + + + +>NAME +>VALUE + + +<b>RESISTOR</b><p> +type RDH, grid 15 mm + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +RDH + + + + +<b>RESISTOR</b><p> +type 0204, grid 2.5 mm + + + + + + +>NAME +>VALUE + + +<b>RESISTOR</b><p> +type 0309, grid 2.5 mm + + + + + + +>NAME +>VALUE + + + + + +<b>RESISTOR</b> chip<p> +Source: http://www.vishay.com/docs/20008/dcrcw.pdf + + +>NAME +>VALUE + + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RNC55<br> +Source: VISHAY .. vta56.pdf + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RNC60<br> +Source: VISHAY .. vta56.pdf + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RBR52<br> +Source: VISHAY .. vta56.pdf + + + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RBR53<br> +Source: VISHAY .. vta56.pdf + + + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RBR54<br> +Source: VISHAY .. vta56.pdf + + + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RBR55<br> +Source: VISHAY .. vta56.pdf + + + + + + + + + + +>NAME +>VALUE + + + + +<b>Bulk Metal® Foil Technology</b>, Tubular Axial Lead Resistors, Meets or Exceeds MIL-R-39005 Requirements<p> +MIL SIZE RBR56<br> +Source: VISHAY .. vta56.pdf + + + + + + + + + + +>NAME +>VALUE + + + + +<b>Package 4527</b><p> +Source: http://www.vishay.com/docs/31059/wsrhigh.pdf + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + +>NAME +>VALUE + + +<b>Wirewound Resistors, Precision Power</b><p> +Source: VISHAY wscwsn.pdf + + + + + + +>NAME +>VALUE + + +<b>CRCW1218 Thick Film, Rectangular Chip Resistors</b><p> +Source: http://www.vishay.com .. dcrcw.pdf + + + + +>NAME +>VALUE + + + + +<b>Chip Monolithic Ceramic Capacitors</b> Medium Voltage High Capacitance for General Use<p> +Source: http://www.murata.com .. GRM43DR72E224KW01.pdf + + + + + + +>NAME +>VALUE + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<B>RESISTOR</B>, American symbol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Supply Symbols</b><p> +GND, VCC, 0V, +5V, -5V, etc.<p> +Please keep in mind, that these devices are necessary for the +automatic wiring of the supply signals.<p> +The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> +In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> +<author>Created by librarian@cadsoft.de</author> + + + + + + + +>VALUE + + + + + +<b>SUPPLY SYMBOL</b> + + + + + + + + + + + + + + +<b>Supply Symbols</b><p> + GND, VCC, 0V, +5V, -5V, etc.<p> + Please keep in mind, that these devices are necessary for the + automatic wiring of the supply signals.<p> + The pin name defined in the symbol is identical to the net which is to be wired automatically.<p> + In this library the device names are the same as the pin names of the symbols, therefore the correct signal names appear next to the supply symbols in the schematic.<p> + <author>Created by librarian@cadsoft.de</author> + + + + + + +>VALUE + + + + + +>VALUE + + + + + +<b>SUPPLY SYMBOL</b> + + + + + + + + + + + + +<b>SUPPLY SYMBOL</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>Name + + +ESP8266 Module 01 + + + + + + + + + + + + + + + +ESP-01 +>Name +>Value + + + + +ESP8266 Wifi module 01 + + + + + + + + + + + + +>Name +>Value + + + + +ESP8266 Wifi module 01 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Omron Switches</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>OMRON SWITCH</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 +2 +3 +4 + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>OMRON SWITCH</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/bt_regulator.lbr b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/bt_regulator.lbr new file mode 100644 index 0000000..ad73447 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/bt_regulator.lbr @@ -0,0 +1,1248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Common Voltage Regulators</b> +<p>Created by Michael Shimniok <a href="http://www.bot-thoughts.com/">www.bot-thoughts.com</a></b> + + +<B>SOT23-6 package</B> + +<P>This is the DBV6 package from Texas Instruments. It differs from the one in the Eagle libraries (there are two versions in ref-packages.lbr) in that the pad dimensions are 0.5mm x 1.4mm, as in the TI recommendation. +<P>The pin spacing is 0.95mm, and the package body has nominal dimensions 2.9mm long by 1.6mm wide. + + + + + + + + + + + + + +>VALUE +>NAME + + + + + + + + +<b>TO-263</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + +<b>TO220-5</b> + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +<b>SMALL OUTLINE INTEGRATED CIRCUIT</b><p> +body 3.9 mm/JEDEC MS-012AA + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +<b>Small Outline Transistor</b> + + + + + +>VALUE + + + + +>NAME + + + + + + + + +<B>D-PAK SMT Package</B> +<P>Similar to TO-252, but pad size and spacing as suggested by ST Microelectronics LD1117 devices</B> + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +<b>VOLTAGE REGULATOR</b> + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +A15,2mm +1 +2 +3 + + + + + + + + + + +<b>VOLTAGE REGULATOR</b> + + + + + + + + + +>NAME +>VALUE +1 +2 +3 + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + +<b>D2PACK</b><p> +Source: INTERNATIONAL RECTIFIER, irg4bc15ud-s.pdf + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + +<B>TO220BV</B> from transistor-power.lbr + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +<b>TO220 Horizontal</b><p> +From transistor-power.lbr + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE +A17,5mm + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + +>NAME +>VALUE + + + + + + + +3-pin voltage regulator + + + + + + + +>NAME +>VALUE +GND +IN +OUT + + + + + + + + + +>NAME +>VALUE +GND +IN +OUT + + + + + + + + + + +>NAME +>VALUE +GND +IN +OUT + + + + + + + +>NAME +>VALUE + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + +>NAME +>VALUE +GND +IN +OUT + + + + + + + +<b>MIC5320 Dual ULDO Voltage Regulator</b> +<p>The MIC5320 is a tiny Dual Ultra Low-Dropout (ULDO(TM)) linear regulator ideally suited for portable electronics. + +<p>Reference: <a href="http://www.micrel.com/_PDF/mic5320.pdf">http://www.micrel.com/_PDF/mic5320.pdf</a></p> + + + + + + + + + + + + + + + + + + + + +<b><a href=http://www.national.com>National Semiconductor</a> Low Dropout Adjustable Regulator</b><p> +Vout 3-24V, Vin up to 26V, Shutdown Pin, Reverse Batterie/Load Dump/Short Circuit/Overload Protection, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b><a href=http://www.national.com>National Semiconductor</a> Micropower Linear Regulator</b><p> +50mA regulated output, Vin 16V max., ultra-low dropout of 120mV @ 50mA, 1µA standby current + + + + + + + + + + + + + + + + + + +<b>LP2980IM5-ADJ</b> + +<p>50mA adjustable, ultra low drop out regulator in SOT-23-5 package</p> + + + + + + + + + + + + + + + + + + + +<B>TLV00XX, TLV01XX LDO Voltage Regulator</b> +<p>The TLV700xx series of low-dropout (LDO) linear regulators are low quiescent current devices with excellent line and load transient performance. These LDOs are designed for power-sensitive applications.</p> +<p>Datasheet: <a href="http://www.ti.com/lit/ds/symlink/tlv70025.pdf">tlv70025.pdf</a> + + + + + + + + + + + + + + + + + + +<B>MCP1824 LDO voltage regulator</B> +<p>The MCP1824/MCP1824S is a 300 mA Low Dropout +(LDO) linear regulator that provides high current and +low output voltages. The MCP1824 comes in a fixed or +adjustable output voltage version, with an output +voltage range of 0.8V to 5.0V.</P> +<p><a href="http://ww1.microchip.com/downloads/en/devicedoc/22070a.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + + + +<b>Microchip MCP1801 LDO Regulator</b> + +<p>The MCP1801 is a family of CMOS low dropout (LDO) +voltage regulators that can deliver up to 150 mA of +current while consuming only 25 μA of quiescent +current (typical) with input operating range from 2.0V to 10.0V.</p> + +<p><a href="http://ww1.microchip.com/downloads/en/DeviceDoc/22051D.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + + + +<B>STM LD29150 LDO Voltage Regulator</b> +<p>The LD29150xx is a high current, high accuracy, +low-dropout voltage regulator series. These +regulators feature 400 mV dropout voltage and +very low ground current. Designed for high +current loads, these devices are also used in +lower current, extremely low dropout-critical +systems, where their tiny dropout voltage and +ground current values are important attributes. </p> +<p><a href="http://www.st.com/web/en/resource/technical/document/datasheet/CD00003403.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + +<b>Microchip MCP1824 LDO Voltage Regulator</b> +<p>The MCP1825/MCP1825S is a 500 mA Low Dropout +(LDO) linear regulator that provides high current and +low output voltages.</p> + +<p><a href="http://ww1.microchip.com/downloads/en/DeviceDoc/22056b.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + + +<b>5V LDO Voltage Regulator</b> +<p>The LFxxAB/LFxxC are very low drop regulators +available in PENTAWATT, TO-220, TO-220FP, +DPAK and PPAK package and in a wide range of +output voltages.</p><p>The very low drop voltage (0.45 +V) and the very low quiescent current make them +particularly suitable for low noise, low power +applications and specially in battery powered +systems</p> +<p><a href="http://www.st.com/web/en/resource/technical/document/datasheet/CD00000546.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + +<b>LM2940 LDO Voltage Regulator</b> + +<p>The LM2940-N/LM2940C positive voltage regulator features the ability to source 1A of output current with a dropout voltage of typically 0.5V and a maximum of 1V over the entire temperature range. Furthermore, a quiescent current reduction circuit has been included which reduces the ground current when the differential between the input voltage and the output +exceeds approximately 3V.</p> + +<p>Designed also for vehicular applications, the LM2940-N/LM2940C and all regulated circuitry are protected from reverse battery installations or 2-battery jumps. During line transients, such as load dump when the +input voltage can momentarily exceed the specified maximum operating voltage, the regulator will automatically shut down to protect both the internal circuits and the load. The LM2940/LM2940C cannot be harmed by temporary mirror-image insertion. +Familiar regulator features such as short circuit and thermal overload protection are also provided.</p> + +<p><a href="http://www.ti.com/lit/ds/symlink/lm2940-n.pdf">Datasheet.pdf</a></p. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Microchip MCP1755 LDO Voltage Regulator</b> +<p>The MCP1755/1755S is a family of CMOS low-dropout +(LDO) voltage regulators that can deliver up to 300 mA +of current while consuming only 68.0 μA of quiescent +current (typical). The input operating range is specified +from 3.6V to 16.0V, making it an ideal choice for four to +six primary cell battery-powered applications, 12V +mobile applications and one to three cell Li-Ion- +powered applications.</p> +<a href="http://ww1.microchip.com/downloads/en/DeviceDoc/25160A.pdf">datasheet.pdf</a> + + + + + + + + + + + + + + + + + + + +<b>NCP5500, NCV5500, NCP5501, NCV5501 LDO Voltage Regulator</b> +<p>These linear low drop voltage regulators provide up to 500 mA over +a user−adjustable output range of 1.25 V to 5.0 V, or at a fixed output +voltage of 1.5 V, 3.3 V or 5.0 V, with typical output voltage accuracy +better than 3%. Reverse bias protection, thermal shutdown, and short +circuit protection. Dropout is 230 mV at 500 mA</p> + + + + + + + + + + + + + + + + + +<b>Microchip TC1262 LDO Regulator</b> +<p> +The TC1262 is a fixed output, high accuracy (typically +±0.5%) CMOS low dropout regulator. Designed specif- +ically for battery-operated systems, the TC1262’s +CMOS construction eliminates wasted ground current, +significantly extending battery life. Total supply current +is typically 80ï­A at full load (20 to 60 times lower than +in bipolar regulators).</p> +<p>TC1262 key features include ultra low noise operation, +very low dropout voltage (typically 350mV at full load), +and fast response to step changes in load.</p> +<p><a href="http://ww1.microchip.com/downloads/en/DeviceDoc/21373C.pdf">Datasheet.pdf</a></p> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>ULDO Voltage regulator</b> +<p>The MIC5205 is an efficient linear voltage regulator with ultra low-noise output, very low dropout voltage (typically 17mV at light loads and 165mV at 150mA), and very low ground current (600⎧A at 100mA output). The MIC5205 offers better than 1% initial accuracy.</p> +<p>Designed especially for hand-held, battery-powered devices, the MIC5205 includes a CMOS or TTL compatible enable/shutdown control input. When shut down, power consumption drops nearly to zero. Regulator ground current increases only slightly in dropout, further prolonging battery life.</p> +<p>Key MIC5205 features include a reference bypass pin to improve its already excellent low-noise performance, reversed-battery protection, current limiting, and overtemperature shutdown.</p> +<p><a href="http://www.micrel.com/_PDF/mic5205.pdf">datasheet.pdf</a> + + + + + + + + + + + + + + + + + + + +<B>LD4941 LDO 1A Regulator</B> +<p>ST Microelectronics</p> +<p>The L4941 is a three terminal 5V positive +regulators available in TO-220 and DPAK +packages, making it useful in a wide range of +industrial and consumer applications. Thanks to +its very low input/output voltage drop, these +devices are particularly suitable for battery +powered equipments, reducing consumption and +prolonging battery life. It employs internal current +limiting, antisaturation circuit, thermal shut-down +and safe area protection.</p> +<p>Reference: <a href="http://www.st.com/stonline/books/pdf/docs/2142.pdf">http://www.st.com/stonline/books/pdf/docs/2142.pdf</a></p> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>LDO Voltage Regulator</b> + +<p>NCP1117 series are low dropout positive voltage regulators that are capable of providing an output current that is in excess of 1.0 A with a maximum dropout voltage of 1.2 V at 800 mA over +temperature. This series contains nine fixed output voltages of 1.5 V, 1.8 V, 1.9 V, 2.0 V, 2.5 V, 2.85 V, 3.3 V, 5.0 V, and 12 V that have no minimum load requirement to maintain regulation. Also included is an adjustable output version that can be programmed from 1.25 V to 18.8 V with two external resistors. <a href="http://www.onsemi.com/pub_link/Collateral/NCP1117-D.PDF">datasheet.pdf</a> + +<p>LM1117-N is a series of low dropout voltage regulators of 1.2V at 800mA of load current. It has the same pin-out as Texas Instruments' industry standard LM317. The LM1117-N is available in an adjustable version, available in five fixed voltages, 1.8V, 2.5V, 2.85V, 3.3V, and 5V. The LM1117-N offers current limiting and thermal shutdown. Its circuit includes a zener trimmed to within ±1%. +<a href="http://www.ti.com/lit/ds/symlink/lm1117-n.pdf">datasheet.pdf</a> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/esp8266modules.lbr b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/esp8266modules.lbr new file mode 100644 index 0000000..8d4e5a8 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/esp8266modules.lbr @@ -0,0 +1,1308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Module 01 + + + + + + + + + + + + + + + +ESP-01 +>Name +>Value + + +ESP8266 Module 05 - 4 pins + + + + + + + + + + + + + + +Esp-05 +>Name +>Value + + +ESP8266 Module 05 - 5 pins + + + + + + + + + + + + + + + +ESP05-5pin +>Name +>Value + + +ESP8266 Module 07 & 08 + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP-07 + + +ESP-08 +>Name +>Value + + +ESP8266 Module 02 + + + + + + + + + + + + + + + + + +ESP2 +>Value +>Name + + +ESP8266 Modules 3 & 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP03 +ESP04 + + +>Name +>Value + + +ESP8266 Module 06 + + + + + + + + + + + + + + + + + + + + +ESP06 + + + + +>Value +>Name + + +ESP8266 Module 11 + + + + + + + + + + + + + +ESP-11 + + + + +>Name +ESP11 + + +ESP8266 Module 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP - 12 +>Value +>Name + + +ESP8266 Module 09 + + + + + + + + + + + + + + + + + + + + + + + + +>Name +>Value + + +ESP8266 Module 10 + + + + + + + + + +ESP10 +>Name +>Value + + +ESP8266 Module DWA8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>Name +>Value +ESP8266 - DWA8 + + +ESP8266 Wifi module DWA8-mini + + + + + + + + + + + + + + +>Name +>Value + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Module 12 SMD pads + + + + + + + + + + + + + + + + + + + + +ESP - 12 +>Value +>Name + + + + + + + + + + + + + + + + + + +ESP8266 Module 12E + + + + + + + + + + + + + + + + + + + +ESP - 12E +>Value +>Name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>Name + + + + +ESP8266 Wifi module 01 + + + + + + + + + + + + +>Name +>Value + + + + + + + + + + +>Name +>Value + + + + + + + + + + + +>Name +>Value + + +ESP8266 Wifi module 07 & 08 + + + + + + + + + + + + + + + + + + +>Name +>Value + + +ESP8266 Wifi module 03 & 04 + + + + + + + + + + + + + + + + + + +>Value +>Name + + +ESP8266 Wifi module 02 + + + + + + + + + + + + +>Name +>Value + + + + + + + + + + + + + + + + + + + + + + + + + + +>Value +>Name + + + + + + + + + + + + + + +>Name +ESP11 + + + + + + + + + + + + + + + + + + + + + + +>Name +>Value + + + + + + + + + + + + + + + + + + + + + + + + + + +>Value +>Name + + + + + + + + + + + +>Name +>Value + + +ESP8266 Wifi module DWA8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>Name +>Value + + + + + + + + + + + + + + + + +>Name +>Value + + +ESP8266-12E Module + + + + + + + + + + + + + + + + + + + + +>Name +>Value + + + + + + +* Can only be used on the ESP12-D +(Untested. Info from esp8266 wiki) + + + + +ESP8266 Wifi module 01 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 05-4pin + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 05 - 5pin + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 07 & 08 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 03 & 04 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 02 + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 06 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 11 + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 09 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module 10 + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module DWA8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ESP8266 Wifi module DWA8-MINI + + + + + + + + + + + + + + + + + + + + + + + + +ESP12E Module + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/transistors.lbr b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/IRBlaster/transistors.lbr new file mode 100644 index 0000000000000000000000000000000000000000..1619623b6de722599955167d3beb98115b50d4ab GIT binary patch literal 12360 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/dev/null || echo Unknown) +time_string = $(shell date +$(1)) + +# ESP Arduino directories +ifndef ESP_ROOT + # Location not defined, find and use possible version in the Arduino IDE installation + OS ?= $(shell uname -s) + ifeq ($(OS), Windows_NT) + ARDUINO_DIR = $(shell cygpath -m $(LOCALAPPDATA)/Arduino15/packages/$(CHIP)) + else ifeq ($(OS), Darwin) + ARDUINO_DIR = $(HOME)/Library/Arduino15/packages/$(CHIP) + else + ARDUINO_DIR = $(HOME)/.arduino15/packages/$(CHIP) + endif + ESP_ROOT := $(lastword $(wildcard $(ARDUINO_DIR)/hardware/$(CHIP)/*)) + ifeq ($(ESP_ROOT),) + $(error No installed version of $(CHIP) Arduino found) + endif + ESP_ARDUINO_VERSION := $(notdir $(ESP_ROOT)) + # Find used version of compiler and tools + COMP_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/xtensa-lx106-elf-gcc/*)) + ESPTOOL_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/esptool/*)) + MKSPIFFS_PATH := $(lastword $(wildcard $(ARDUINO_DIR)/tools/mkspiffs/*)) +else + # Location defined, assume it is a git clone + ESP_ARDUINO_VERSION = $(call git_description,$(ESP_ROOT)) +endif +ESP_LIBS = $(ESP_ROOT)/libraries +SDK_ROOT = $(ESP_ROOT)/tools/sdk +TOOLS_ROOT = $(ESP_ROOT)/tools + +ifeq ($(wildcard $(ESP_ROOT)/cores/$(CHIP)),) + $(error $(ESP_ROOT) is not a vaild directory for $(CHIP)) +endif + +ESPTOOL_PY = esptool.py --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) + +# Search for sketch if not defined +SKETCH := $(realpath $(firstword \ + $(SKETCH) \ + $(wildcard *.ino) \ + $(if $(filter $(CHIP), esp32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WebServer/examples/HelloServer/HelloServer.ino) \ + ) \ +) +ifeq ($(wildcard $(SKETCH)),) + $(error Sketch $(SKETCH) not found) +endif + +# Main output definitions +MAIN_NAME := $(basename $(notdir $(SKETCH))) +MAIN_EXE = $(BUILD_DIR)/$(MAIN_NAME).bin +FS_IMAGE = $(BUILD_DIR)/FS.spiffs + +ifeq ($(OS), Windows_NT) + # Adjust critical paths + BUILD_DIR := $(shell cygpath -m $(BUILD_DIR)) + SKETCH := $(shell cygpath -m $(SKETCH)) +endif + +# Build file extensions +OBJ_EXT = .o +DEP_EXT = .d + +# Special tool definitions +OTA_TOOL ?= $(TOOLS_ROOT)/espota.py +HTTP_TOOL ?= curl + +# Core source files +CORE_DIR = $(ESP_ROOT)/cores/$(CHIP) +CORE_SRC := $(shell find $(CORE_DIR) -name "*.S" -o -name "*.c" -o -name "*.cpp") +CORE_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(CORE_SRC))) +CORE_LIB = $(BUILD_DIR)/arduino.ar + +# User defined compilation units and directories +ifeq ($(LIBS),) + # Automatically find directories with header files used by the sketch + FINDCMD := perl -e 'use File::Find;@d = split(" ", shift);while (<>) {$$f{"$$1"} = 1 if /^\s*\#include\s+[<"]([^>"]+)/;}find({follow => 1, wanted => sub {return if($$File::Find::dir =~ /examples|tests/);print $$File::Find::dir," " if $$f{$$_}}}, @d);' + LIBS := $(shell $(FINDCMD) "$(ESP_LIBS) $(HOME)/Arduino/libraries" $(SKETCH) ../../src/*.cpp ../../src/*.h) + ifeq ($(LIBS),) + # No dependencies found + LIBS = /dev/null + endif +endif +IGNORE_PATTERN := $(foreach dir,$(EXCLUDE_DIRS),$(dir)/%) +SKETCH_DIR = $(dir $(SKETCH)) +USER_INC := $(filter-out $(IGNORE_PATTERN),$(shell find -L $(SKETCH_DIR) $(LIBS) -name "*.h")) +USER_SRC := $(SKETCH) $(filter-out $(IGNORE_PATTERN),$(shell find -L $(SKETCH_DIR) $(LIBS) -name "*.S" -o -name "*.c" -o -name "*.cpp")) +# Object file suffix seems to be significant for the linker... +USER_OBJ := $(subst .ino,_.cpp,$(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC)))) +USER_DIRS := $(sort $(dir $(USER_SRC))) +USER_INC_DIRS := $(sort $(dir $(USER_INC))) + +# Use first flash definition for the board as default +FLASH_DEF ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.menu\.FlashSize\.([^\.]+)=/){ print "$$1"; exit;}} print "NA";') + +# The actual build commands are to be extracted from the Arduino description files +ARDUINO_MK = $(BUILD_DIR)/arduino.mk +ARDUINO_DESC := $(shell find $(ESP_ROOT) -maxdepth 1 -name "*.txt" | sort) +$(ARDUINO_MK): $(ARDUINO_DESC) $(MAKEFILE_LIST) | $(BUILD_DIR) + perl -e "$$PARSE_ARDUINO" $(BOARD) $(FLASH_DEF) $(ARDUINO_EXTRA_DESC) $(ARDUINO_DESC) >$(ARDUINO_MK) + +-include $(ARDUINO_MK) + +# Compilation directories and path +INCLUDE_DIRS += $(CORE_DIR) $(ESP_ROOT)/variants/$(INCLUDE_VARIANT) $(BUILD_DIR) +C_INCLUDES := $(foreach dir,$(INCLUDE_DIRS) $(USER_INC_DIRS),-I$(dir)) +VPATH += $(shell find $(CORE_DIR) -type d) $(USER_DIRS) + +# Automatically generated build information data +# Makes the build date and git descriptions at the actual build event available as string constants in the program +BUILD_INFO_H = $(BUILD_DIR)/buildinfo.h +BUILD_INFO_CPP = $(BUILD_DIR)/buildinfo.c++ +BUILD_INFO_OBJ = $(BUILD_INFO_CPP)$(OBJ_EXT) + +$(BUILD_INFO_H): | $(BUILD_DIR) + echo "typedef struct { const char *date, *time, *src_version, *env_version;} _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ + +# Build rules for the different source file types +$(BUILD_DIR)/%.cpp$(OBJ_EXT): %.cpp $(BUILD_INFO_H) $(ARDUINO_MK) + echo $(' >$(BUILD_INFO_CPP) + echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) + $(CPP_COM) $(BUILD_INFO_CPP) -o $(BUILD_INFO_OBJ) + $(LD_COM) + $(GEN_PART_COM) + $(ELF2BIN_COM) + $(SIZE_COM) | perl -e "$$MEM_USAGE" "$(MEM_FLASH)" "$(MEM_RAM)" +ifneq ($(FLASH_INFO),) + printf "Flash size: $(FLASH_INFO)\n\n" +endif + perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' + +upload flash: all + $(UPLOAD_COM) + +ota: all + $(OTA_TOOL) -i $(ESP_ADDR) -p $(ESP_PORT) -a $(ESP_PWD) -f $(MAIN_EXE) + +http: all + $(HTTP_TOOL) --verbose -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) + echo "\n" + +$(FS_IMAGE): $(wildcard $(FS_DIR)/*) +ifneq ($(CHIP),esp32) + echo Generating filesystem image: $(FS_IMAGE) + $(MKSPIFFS_COM) +else + echo No SPIFFS function available for $(CHIP) + exit 1 +endif + +fs: $(FS_IMAGE) + +upload_fs flash_fs: $(FS_IMAGE) + $(FS_UPLOAD_COM) + +FLASH_FILE ?= esp_flash.bin +dump_flash: + echo Dumping flash memory to file: $(FLASH_FILE) + $(ESPTOOL_PY) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) + +restore_flash: + echo Restoring flash memory from file: $(FLASH_FILE) + $(ESPTOOL_PY) write_flash -fs $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";print ($$2 eq "K" ? 2 : $$1*8);' $(FLASH_DEF))m -fm $(FLASH_MODE) -ff $(FLASH_SPEED)m 0 $(FLASH_FILE) + +clean: + echo Removing all build files + rm -rf $(BUILD_DIR)/* + +list_boards: + echo === Available boards === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^(\w+)\.name=(.+)/){ print sprintf("%-20s %s\n", $$1,$$2);} }' + +list_lib: + echo === User specific libraries === + perl -e 'foreach (@ARGV) {print "$$_\n"}' "* Include directories:" $(USER_INC_DIRS) "* Library source files:" $(USER_SRC) "Foo" $(IGNORE_PATTERN) + +list_flash_defs: + echo === Memory configurations for board: $(BOARD) === + cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.menu\.FlashSize.([^\.]+)=(.+)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + +help: + echo + echo "Generic makefile for building Arduino esp8266 and esp32 projects" + echo "This file can either be used directly or included from another makefile" + echo "" + echo "The following targets are available:" + echo " all (default) Build the project application" + echo " clean Remove all intermediate build files" + echo " flash Build and and flash the project application" + echo " flash_fs Build and and flash file system (when applicable)" + echo " ota Build and and flash via OTA" + echo " Params: ESP_ADDR, ESP_PORT and ESP_PWD" + echo " http Build and and flash via http (curl)" + echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" + echo " dump_flash Dump the whole board flash memory to a file" + echo " restore_flash Restore flash memory from a previously dumped file" + echo " list_lib Show a list of used library files and include paths" + echo "Configurable parameters:" + echo " SKETCH Main source file" + echo " If not specified the first sketch in current" + echo " directory will be used. If none is found there," + echo " a demo example will be used instead." + echo " LIBS Includes in the sketch file of libraries from within" + echo " the ESP Arduino directories are automatically" + echo " detected. If this is not enough, define this" + echo " variable with all libraries or directories needed." + echo " USER_LIBS Path to user installed Arduino libraries" + echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" + echo " BOARD Name of the target board. Default: '$(BOARD)'" + echo " Use 'list_boards' to get list of available ones" + echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" + echo " Use 'list_flash_defs' to get list of available ones" + echo " BUILD_DIR Directory for intermediate build files." + echo " Default '$(BUILD_DIR)'" + echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" + echo " FS_DIR File system root directory" + echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" + echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" + echo " FLASH_FILE File name for dump and restore flash operations" + echo " Default: '$(FLASH_FILE)'" + echo " VERBOSE Set to 1 to get full printout of the build" + echo " SINGLE_THREAD Use only one build thread" + echo + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +.PHONY: all +all: $(BUILD_DIR) $(ARDUINO_MK) $(BUILD_INFO_H) prebuild $(MAIN_EXE) + +prebuild: +ifdef USE_PREBUILD + $(PREBUILD_COM) +endif + +# Include all available dependencies +-include $(wildcard $(BUILD_DIR)/*$(DEP_EXT)) + +.DEFAULT_GOAL = all + +ifndef SINGLE_THREAD + # Use multithreaded builds by default + MAKEFLAGS += -j +endif + +ifndef VERBOSE + # Set silent mode as default + MAKEFLAGS += --silent +endif + +# Inline Perl scripts + +# Parse Arduino definitions and build commands from the descriptions +define PARSE_ARDUINO +my $$board = shift; +my $$flashSize = shift; +my %v; + +sub def_var { + my ($$name, $$var) = @_; + print "$$var ?= $$v{$$name}\n"; + $$v{$$name} = "\$$($$var)"; +} + +$$v{'runtime.platform.path'} = '$$(ESP_ROOT)'; +$$v{'includes'} = '$$(C_INCLUDES)'; +$$v{'runtime.ide.version'} = '10605'; +$$v{'build.arch'} = '$$(CHIP)'; +$$v{'build.project_name'} = '$$(MAIN_NAME)'; +$$v{'build.path'} = '$$(BUILD_DIR)'; +$$v{'object_files'} = '$$^ $$(BUILD_INFO_OBJ)'; + +foreach my $$fn (@ARGV) { + open($$f, $$fn) || die "Failed to open: $$fn\n"; + while (<$$f>) { + next unless /^(\w[\w\-\.]+)=(.*)/; + my ($$key, $$val) =($$1, $$2); + $$board_defined = 1 if $$key eq "$$board.name"; + $$key =~ s/$$board\.menu\.FlashSize\.$$flashSize\.//; + $$key =~ s/$$board\.menu\.FlashFreq\.[^\.]+\.//; + $$key =~ s/$$board\.menu\.UploadSpeed\.[^\.]+\.//; + $$key =~ s/^$$board\.//; + $$v{$$key} ||= $$val; + } + close($$f); +} +$$v{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$$(COMP_PATH)'; +$$v{'runtime.tools.esptool.path'} ||= '$$(ESPTOOL_PATH)'; +$$v{'runtime.tools.mkspiffs.path'} ||= '$$(MKSPIFFS_PATH)'; + +die "* Uknown board $$board\n" unless $$board_defined; + +print "# Board definitions\n"; +def_var('build.f_cpu', 'F_CPU'); +def_var('build.flash_mode', 'FLASH_MODE'); +def_var('build.flash_freq', 'FLASH_SPEED'); +def_var('upload.resetmethod', 'UPLOAD_RESET'); +def_var('upload.speed', 'UPLOAD_SPEED'); +def_var('compiler.warning_flags', 'COMP_WARNINGS'); +$$v{'upload.verbose'} = '$$(UPLOAD_VERB)'; +$$v{'serial.port'} = '$$(UPLOAD_PORT)'; +$$v{'recipe.objcopy.hex.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; +$$v{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$$1\}/g; +$$v{'compiler.cpreprocessor.flags'} .= " \$$(C_PRE_PROC_FLAGS)"; +$$v{'build.extra_flags'} .= " \$$(BUILD_EXTRA_FLAGS)"; + +foreach my $$key (sort keys %v) { + while ($$v{$$key} =~/\{/) { + $$v{$$key} =~ s/\{([\w\-\.]+)\}/$$v{$$1}/; + $$v{$$key} =~ s/""//; + } + $$v{$$key} =~ s/ -o $$//; + $$v{$$key} =~ s/(-D\w+=)"([^"]+)"/$$1\\"$$2\\"/g; +} + +print "INCLUDE_VARIANT = $$v{'build.variant'}\n"; +print "# Commands\n"; +print "C_COM=$$v{'recipe.c.o.pattern'}\n"; +print "CPP_COM=$$v{'recipe.cpp.o.pattern'}\n"; +print "S_COM=$$v{'recipe.S.o.pattern'}\n"; +print "AR_COM=$$v{'recipe.ar.pattern'}\n"; +print "LD_COM=$$v{'recipe.c.combine.pattern'}\n"; +print "GEN_PART_COM=$$v{'recipe.objcopy.eep.pattern'}\n"; +print "ELF2BIN_COM=$$v{'recipe.objcopy.hex.pattern'}\n"; +print "SIZE_COM=$$v{'recipe.size.pattern'}\n"; +my $$flash_size = sprintf("0x%X", hex($$v{'build.spiffs_end'})-hex($$v{'build.spiffs_start'})); +print "MKSPIFFS_COM=$$v{'tools.mkspiffs.path'}/$$v{'tools.mkspiffs.cmd'} -b $$v{'build.spiffs_blocksize'} -s $$flash_size -c \$$(FS_DIR) \$$(FS_IMAGE)\n"; +print "UPLOAD_COM=$$v{'tools.esptool.upload.pattern'}\n"; +my $$fs_upload_com = $$v{'tools.esptool.upload.pattern'}; +$$fs_upload_com =~ s/(.+ -ca) .+/$$1 $$v{'build.spiffs_start'} -cf \$$(FS_IMAGE)/; +print "FS_UPLOAD_COM=$$fs_upload_com\n"; +my $$val = $$v{'recipe.hooks.core.prebuild.1.pattern'}; +$$val =~ s/bash -c "(.+)"/$$1/; +$$val =~ s/(#define .+0x)(\`)/"\\$$1\"$$2/; +$$val =~ s/(\\)//; +print "PREBUILD_COM=$$val\n"; +print "MEM_FLASH=$$v{'recipe.size.regex'}\n"; +print "MEM_RAM=$$v{'recipe.size.regex.data'}\n"; +print "FLASH_INFO=$$v{'menu.FlashSize.' . $$flashSize}\n" +endef +export PARSE_ARDUINO + +# Convert memory information +define MEM_USAGE +$$fp = shift; +$$rp = shift; +while (<>) { + $$r += $$1 if /$$rp/; + $$f += $$1 if /$$fp/; +} +print "\nMemory usage\n"; +print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "Ram:", $$r, "Flash:", $$f); +endef +export MEM_USAGE diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/README.md b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/README.md new file mode 100644 index 0000000..25554fc --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/README.md @@ -0,0 +1,34 @@ +# ESP8266MeshIRRemote + +This is an example of an IR Blaster controlled via MQTT commands + +The blaster uses Pronto codes (though it should be easy to add support for all protocols handled by the IRRemote library). +The Pronto protocol is very flexible, and should be able to handle virtually all IR codes. + +Codes can be saved in a file on the device to make it easy to send frequently used codes + +## Hardware + +The IRBlaster directory contains the Eagle libraries for the IRBlaster used in this example. + +The board is quite basic. It uses an ESP01 module along with a 2n7000 to drive an IR LED. A LM1117-3.3 module is connected +to a mini-usb to provide power. + +This was built before Eagle +was moved to a subscription plan. I have since moved to using KiCad, but there was no reason to redesign the schematics. + +A PDF of the schematic is provided. Boards can be ordered from OshPark here: +Order from OSH Park + +## MQTT Commands + +| Topic | Message | Description | +|-------|---------|-------------| +| \/send | code=\ | send specified code one time | +| \/send | repeat=5,code=\ | send specified code 5 times | +| \/send | repeat=5,code=\,repeat=3,pronto=\ | send code1 5 times followed by sending code2 3 times | +| \/send | repeat=5,file=\,file=\,... | send pronto code previously saved in 'filename1' 5 times, followed by the contents of 'filename2' | +| \/list | "" | returns a list of all saved files | +| \/debug | \<1|0> | enable/disable debugging messages over MQTT | +| \/save/\ | code=\ | save specified pronto code to 'filename' on esp8266 device | +| \/read/\ | "" | return contents of 'flename' via MQTT | diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/config.mk b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/config.mk new file mode 100644 index 0000000..6e35453 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/config.mk @@ -0,0 +1,4 @@ +FLASH_DEF = 1M256 +USER_LIBS = ${HOME}/Arduino/libraries/ +CPP_EXTRA = -Wall +BUILD_EXTRA_FLAGS = "-DMQTT_MAX_PACKET_SIZE=1152" diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/Contributors.md b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/Contributors.md new file mode 100644 index 0000000..67e85ba --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/Contributors.md @@ -0,0 +1,20 @@ +## Contributors of this project +- [Mark Szabo](https://github.com/markszabo/) : IR sending on ESP8266 +- [Sébastien Warin](https://github.com/sebastienwarin/) (http://sebastien.warin.fr) : IR receiving on ESP8266 + +## Contributors of the original project (https://github.com/shirriff/Arduino-IRremote/) +These are the active contributors of this project that you may contact if there is anything you need help with or if you have suggestions. + +- [z3t0](https://github.com/z3t0) : Active Contributor and currently also the main contributor. + * Email: zetoslab@gmail.com + * Skype: polarised16 +- [shirriff](https://github.com/shirriff) : Owner of repository and creator of library. +- [Informatic](https://github.com/Informatic) : Active contributor +- [fmeschia](https://github.com/fmeschia) : Active contributor +- [PaulStoffregen](https://github.com/paulstroffregen) : Active contributor +- [crash7](https://github.com/crash7) : Active contributor +- [Neco777](https://github.com/neco777) : Active contributor + +Note: This list is being updated constantly so please let [z3t0](https://github.com/z3t0) know if you have been missed. + + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.cpp b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.cpp new file mode 100644 index 0000000..55cf41a --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.cpp @@ -0,0 +1,1400 @@ + /*************************************************** + * IRremote for ESP8266 + * + * Based on the IRremote library for Arduino by Ken Shirriff + * Version 0.11 August, 2009 + * Copyright 2009 Ken Shirriff + * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html + * + * Modified by Paul Stoffregen to support other boards and timers + * Modified by Mitra Ardron + * Added Sanyo and Mitsubishi controllers + * Modified Sony to spot the repeat codes that some Sony's send + * + * Interrupt code based on NECIRrcv by Joe Knapp + * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 + * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/ + * + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + * LG added by Darryl Smith (based on the JVC protocol) + * Whynter A/C ARC-110WD added by Francesco Meschia + * + * Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for sending IR code on ESP8266 + * Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code on ESP8266 + * + * GPL license, all text above must be included in any redistribution + ****************************************************/ + +#include "IRremoteESP8266.h" +#include "IRremoteInt.h" + +// These versions of MATCH, MATCH_MARK, and MATCH_SPACE are only for debugging. +// To use them, set DEBUG in IRremoteInt.h +// Normally macros are used for efficiency +#ifdef DEBUG +int MATCH(int measured, int desired) { + Serial.print("Testing: "); + Serial.print(TICKS_LOW(desired), DEC); + Serial.print(" <= "); + Serial.print(measured, DEC); + Serial.print(" <= "); + Serial.println(TICKS_HIGH(desired), DEC); + return measured >= TICKS_LOW(desired) && measured <= TICKS_HIGH(desired); +} + +int MATCH_MARK(int measured_ticks, int desired_us) { + Serial.print("Testing mark "); + Serial.print(measured_ticks * USECPERTICK, DEC); + Serial.print(" vs "); + Serial.print(desired_us, DEC); + Serial.print(": "); + Serial.print(TICKS_LOW(desired_us + MARK_EXCESS), DEC); + Serial.print(" <= "); + Serial.print(measured_ticks, DEC); + Serial.print(" <= "); + Serial.println(TICKS_HIGH(desired_us + MARK_EXCESS), DEC); + return measured_ticks >= TICKS_LOW(desired_us + MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us + MARK_EXCESS); +} + +int MATCH_SPACE(int measured_ticks, int desired_us) { + Serial.print("Testing space "); + Serial.print(measured_ticks * USECPERTICK, DEC); + Serial.print(" vs "); + Serial.print(desired_us, DEC); + Serial.print(": "); + Serial.print(TICKS_LOW(desired_us - MARK_EXCESS), DEC); + Serial.print(" <= "); + Serial.print(measured_ticks, DEC); + Serial.print(" <= "); + Serial.println(TICKS_HIGH(desired_us - MARK_EXCESS), DEC); + return measured_ticks >= TICKS_LOW(desired_us - MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us - MARK_EXCESS); +} +#else +int MATCH(int measured, int desired) {return measured >= TICKS_LOW(desired) && measured <= TICKS_HIGH(desired);} +int MATCH_MARK(int measured_ticks, int desired_us) {return MATCH(measured_ticks, (desired_us + MARK_EXCESS));} +int MATCH_SPACE(int measured_ticks, int desired_us) {return MATCH(measured_ticks, (desired_us - MARK_EXCESS));} +// Debugging versions are in IRremote.cpp +#endif + +// IRsend ----------------------------------------------------------------------------------- + +IRsend::IRsend(int IRsendPin) +{ + IRpin = IRsendPin; +} + +void IRsend::begin() +{ + pinMode(IRpin, OUTPUT); +} + +void IRsend::sendNEC(unsigned long data, int nbits) +{ + enableIROut(38); + mark(NEC_HDR_MARK); + space(NEC_HDR_SPACE); + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + mark(NEC_BIT_MARK); + space(NEC_ONE_SPACE); + } + else { + mark(NEC_BIT_MARK); + space(NEC_ZERO_SPACE); + } + data <<= 1; + } + mark(NEC_BIT_MARK); + space(0); +} + +void IRsend::sendLG (unsigned long data, int nbits) +{ + enableIROut(38); + mark(LG_HDR_MARK); + space(LG_HDR_SPACE); + mark(LG_BIT_MARK); + for (unsigned long mask = 1UL << (nbits - 1); mask; mask >>= 1) { + if (data & mask) { + space(LG_ONE_SPACE); + mark(LG_BIT_MARK); + } else { + space(LG_ZERO_SPACE); + mark(LG_BIT_MARK); + } + } + space(0); +} + +void IRsend::sendWhynter(unsigned long data, int nbits) { + enableIROut(38); + mark(WHYNTER_ZERO_MARK); + space(WHYNTER_ZERO_SPACE); + mark(WHYNTER_HDR_MARK); + space(WHYNTER_HDR_SPACE); + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + mark(WHYNTER_ONE_MARK); + space(WHYNTER_ONE_SPACE); + } + else { + mark(WHYNTER_ZERO_MARK); + space(WHYNTER_ZERO_SPACE); + } + data <<= 1; + } + mark(WHYNTER_ZERO_MARK); + space(WHYNTER_ZERO_SPACE); +} + +void IRsend::sendSony(unsigned long data, int nbits) { + enableIROut(40); + mark(SONY_HDR_MARK); + space(SONY_HDR_SPACE); + data = data << (32 - nbits); + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + mark(SONY_ONE_MARK); + space(SONY_HDR_SPACE); + } + else { + mark(SONY_ZERO_MARK); + space(SONY_HDR_SPACE); + } + data <<= 1; + } +} + +void IRsend::sendRaw(unsigned int buf[], int len, int hz) +{ + enableIROut(hz); + for (int i = 0; i < len; i++) { + if (i & 1) { + space(buf[i]); + } + else { + mark(buf[i]); + } + } + space(0); // Just to be sure +} + +// Note: first bit must be a one (start bit) +void IRsend::sendRC5(unsigned long data, int nbits) +{ + enableIROut(36); + data = data << (32 - nbits); + mark(RC5_T1); // First start bit + space(RC5_T1); // Second start bit + mark(RC5_T1); // Second start bit + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + space(RC5_T1); // 1 is space, then mark + mark(RC5_T1); + } + else { + mark(RC5_T1); + space(RC5_T1); + } + data <<= 1; + } + space(0); // Turn off at end +} + +// Caller needs to take care of flipping the toggle bit +void IRsend::sendRC6(unsigned long data, int nbits) +{ + enableIROut(36); + data = data << (32 - nbits); + mark(RC6_HDR_MARK); + space(RC6_HDR_SPACE); + mark(RC6_T1); // start bit + space(RC6_T1); + int t; + for (int i = 0; i < nbits; i++) { + if (i == 3) { + // double-wide trailer bit + t = 2 * RC6_T1; + } + else { + t = RC6_T1; + } + if (data & TOPBIT) { + mark(t); + space(t); + } + else { + space(t); + mark(t); + } + + data <<= 1; + } + space(0); // Turn off at end +} +void IRsend::sendPanasonic(unsigned int address, unsigned long data) { + enableIROut(35); + mark(PANASONIC_HDR_MARK); + space(PANASONIC_HDR_SPACE); + + for(int i=0;i<16;i++) + { + mark(PANASONIC_BIT_MARK); + if (address & 0x8000) { + space(PANASONIC_ONE_SPACE); + } else { + space(PANASONIC_ZERO_SPACE); + } + address <<= 1; + } + for (int i=0; i < 32; i++) { + mark(PANASONIC_BIT_MARK); + if (data & TOPBIT) { + space(PANASONIC_ONE_SPACE); + } else { + space(PANASONIC_ZERO_SPACE); + } + data <<= 1; + } + mark(PANASONIC_BIT_MARK); + space(0); +} +void IRsend::sendJVC(unsigned long data, int nbits, int repeat) +{ + enableIROut(38); + data = data << (32 - nbits); + if (!repeat){ + mark(JVC_HDR_MARK); + space(JVC_HDR_SPACE); + } + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + mark(JVC_BIT_MARK); + space(JVC_ONE_SPACE); + } + else { + mark(JVC_BIT_MARK); + space(JVC_ZERO_SPACE); + } + data <<= 1; + } + mark(JVC_BIT_MARK); + space(0); +} + +void IRsend::sendSAMSUNG(unsigned long data, int nbits) +{ + enableIROut(38); + mark(SAMSUNG_HDR_MARK); + space(SAMSUNG_HDR_SPACE); + for (int i = 0; i < nbits; i++) { + if (data & TOPBIT) { + mark(SAMSUNG_BIT_MARK); + space(SAMSUNG_ONE_SPACE); + } + else { + mark(SAMSUNG_BIT_MARK); + space(SAMSUNG_ZERO_SPACE); + } + data <<= 1; + } + mark(SAMSUNG_BIT_MARK); + space(0); +} +void IRsend::sendDenon (unsigned long data, int nbits) +{ + // Set IR carrier frequency + enableIROut(38); + + // Header + mark (DENON_HDR_MARK); + space(DENON_HDR_SPACE); + + // Data + for (unsigned long mask = 1UL << (nbits - 1); mask; mask >>= 1) { + if (data & mask) { + mark (DENON_BIT_MARK); + space(DENON_ONE_SPACE); + } else { + mark (DENON_BIT_MARK); + space(DENON_ZERO_SPACE); + } + } + + // Footer + mark(DENON_BIT_MARK); + space(0); // Always end with the LED off +} +//+============================================================================= +// Check for a valid hex digit +// +bool ishex (char ch) +{ + return ( ((ch >= '0') && (ch <= '9')) || + ((ch >= 'A') && (ch <= 'F')) || + ((ch >= 'a') && (ch <= 'f')) ) ? true : false ; +} + +//+============================================================================= +// Check for a valid "blank" ... '\0' is a valid "blank" +// +bool isblank (char ch) +{ + return ((ch == ' ') || (ch == '\t') || (ch == '\0')) ? true : false ; +} + +//+============================================================================= +// Bypass spaces +// +void byp (const char** pcp) +{ + while (1) { + if(isblank(**pcp)) { + (*pcp)++; + } else if(**pcp == '%' && *(*pcp+1) == '2' && *(*pcp+2) == '0') { + (*pcp)+=3; + } else { + return; + } + } +} + +//+============================================================================= +// Hex-to-Byte : Decode a hex digit +// We assume the character has already been validated +// +uint8_t htob (char ch) +{ + if ((ch >= '0') && (ch <= '9')) return ch - '0' ; + if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10 ; + if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10 ; +} + +//+============================================================================= +// Hex-to-Word : Decode a block of 4 hex digits +// We assume the string has already been validated +// and the pointer being passed points at the start of a block of 4 hex digits +// +uint16_t htow (const char* cp) +{ + return ( (htob(cp[0]) << 12) | (htob(cp[1]) << 8) | + (htob(cp[2]) << 4) | (htob(cp[3]) ) ) ; +} + +//+============================================================================= +// +bool IRsend::sendPronto (const char* s, bool repeat, bool fallback) +{ + int i; + int hex_count = 0; + int len; + int skip; + const char* cp; + uint16_t freq; // Frequency in KHz + uint8_t usec; // pronto uSec/tick + uint8_t once; + uint8_t rpt; + + // Validate the string + for (cp = s; *cp; cp += 4) { + byp(&cp); + if ( !ishex(cp[0]) || !ishex(cp[1]) || + !ishex(cp[2]) || !ishex(cp[3])) + { + Serial.println("Error at:" + String(cp - s)); + for (int i = 0; i < 4; i++) {Serial.print(cp[i], HEX); Serial.print(" ");} Serial.println(""); + return false ; + } + hex_count++; + } + + // We will use cp to traverse the string + cp = s; + + // Check mode = Oscillated/Learned + byp(&cp); + if (htow(cp) != 0000) { + Serial.print("Error Bad Header: "); + Serial.println(htow(cp), HEX); + return false; + } + cp += 4; + + // Extract & set frequency + byp(&cp); + freq = (int)(1000000 / (htow(cp) * 0.241246)); // Rounding errors will occur, tolerance is +/- 10% + usec = (int)(((1.0 / freq) * 1000000) + 0.5); // Another rounding error, thank Cod for analogue electronics + freq /= 1000; // This will introduce a(nother) rounding error which we do not want in the usec calcualtion + cp += 4; + //Serial.println("Frequency: " + String(freq); + + // Get length of "once" code + byp(&cp); + once = htow(cp); + cp += 4; + + // Get length of "repeat" code + byp(&cp); + rpt = htow(cp); + cp += 4; + if (hex_count != 4 + (2 * (once+rpt))) { + Serial.println("Got unexpected data length: " + String(hex_count) + " != 4 + " + String(2*once) + " + " + String(2*rpt)); + return false; + } + + // Which code are we sending? + if (fallback) { // fallback on the "other" code if "this" code is not present + if (!repeat) { // requested 'once' + if (once) len = once * 2, skip = 0 ; // if once exists send it + else len = rpt * 2, skip = 0 ; // else send repeat code + } else { // requested 'repeat' + if (rpt) len = rpt * 2, skip = once *2; // if rpt exists send it + else len = once * 2, skip = 0 ; // else send once code + } + } else { // Send what we asked for, do not fallback if the code is empty! + if (!repeat) len = once * 2, skip = 0 ; // 'once' starts at 0 + else len = rpt * 2, skip = once *2; // 'repeat' starts where 'once' ends + } + //Serial.print("Skip: " + String(skip)); + //Serial.println(" Len: " + String(len)); + //Serial.println("Hex count: " + String(hex_count)); + // Skip to start of code + for (i = 0; i < skip; i++, cp += 4) byp(&cp) ; + + // Send code + enableIROut(freq); + for (i = 0; i < len; i++) { + byp(&cp); + if (i & 1) space(htow(cp) * usec); + else mark (htow(cp) * usec); + cp += 4; + } +} + +void IRsend::mark(int time) { + // Sends an IR mark for the specified number of microseconds. + // The mark output is modulated at the PWM frequency. + long beginning = micros(); + while(micros() - beginning < time){ + digitalWrite(IRpin, HIGH); + delayMicroseconds(halfPeriodicTime); + digitalWrite(IRpin, LOW); + delayMicroseconds(halfPeriodicTime); //38 kHz -> T = 26.31 microsec (periodic time), half of it is 13 + } +} + +/* Leave pin off for time (given in microseconds) */ +void IRsend::space(int time) { + // Sends an IR space for the specified number of microseconds. + // A space is no output, so the PWM output is disabled. + digitalWrite(IRpin, LOW); + if (time > 0) delayMicroseconds(time); +} + +void IRsend::enableIROut(int khz) { + // Enables IR output. The khz value controls the modulation frequency in kilohertz. + halfPeriodicTime = 500/khz; // T = 1/f but we need T/2 in microsecond and f is in kHz +} + + +/* Sharp and DISH support by Todd Treece ( http://unionbridge.org/design/ircommand ) + +The Dish send function needs to be repeated 4 times, and the Sharp function +has the necessary repeat built in because of the need to invert the signal. + +Sharp protocol documentation: +http://www.sbprojects.com/knowledge/ir/sharp.htm + +Here are the LIRC files that I found that seem to match the remote codes +from the oscilloscope: + +Sharp LCD TV: +http://lirc.sourceforge.net/remotes/sharp/GA538WJSA + +DISH NETWORK (echostar 301): +http://lirc.sourceforge.net/remotes/echostar/301_501_3100_5100_58xx_59xx + +For the DISH codes, only send the last for characters of the hex. +i.e. use 0x1C10 instead of 0x0000000000001C10 which is listed in the +linked LIRC file. +*/ + +void IRsend::sendSharpRaw(unsigned long data, int nbits) { + enableIROut(38); + + // Sending codes in bursts of 3 (normal, inverted, normal) makes transmission + // much more reliable. That's the exact behaviour of CD-S6470 remote control. + for (int n = 0; n < 3; n++) { + for (int i = 1 << (nbits-1); i > 0; i>>=1) { + if (data & i) { + mark(SHARP_BIT_MARK); + space(SHARP_ONE_SPACE); + } + else { + mark(SHARP_BIT_MARK); + space(SHARP_ZERO_SPACE); + } + } + + mark(SHARP_BIT_MARK); + space(SHARP_ZERO_SPACE); + delay(40); + + data = data ^ SHARP_TOGGLE_MASK; + } +} + +// Sharp send compatible with data obtained through decodeSharp +void IRsend::sendSharp(unsigned int address, unsigned int command) { + sendSharpRaw((address << 10) | (command << 2) | 2, 15); +} + +void IRsend::sendDISH(unsigned long data, int nbits) { + enableIROut(56); + mark(DISH_HDR_MARK); + space(DISH_HDR_SPACE); + for (int i = 0; i < nbits; i++) { + if (data & DISH_TOP_BIT) { + mark(DISH_BIT_MARK); + space(DISH_ONE_SPACE); + } + else { + mark(DISH_BIT_MARK); + space(DISH_ZERO_SPACE); + } + data <<= 1; + } +} + +// --------------------------------------------------------------- + + +//IRRecv------------------------------------------------------ + +extern "C" { + #include "user_interface.h" + #include "gpio.h" +} + +static ETSTimer timer; +volatile irparams_t irparams; + +static void ICACHE_FLASH_ATTR read_timeout(void *arg) { + os_intr_lock(); + if (irparams.rawlen) { + irparams.rcvstate = STATE_STOP; + } + os_intr_unlock(); +} + +static void ICACHE_FLASH_ATTR gpio_intr(void *arg) { + uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status); + + if (irparams.rcvstate == STATE_STOP) { + return; + } + static uint32_t start = 0; + uint32_t now = system_get_time(); + if (irparams.rcvstate == STATE_IDLE) { + irparams.rcvstate = STATE_MARK; + irparams.rawbuf[irparams.rawlen++] = 20; + } + else if (irparams.rawlen < RAWBUF) { + irparams.rawbuf[irparams.rawlen++] = (now - start) / USECPERTICK + 1; + } + start = now; + + os_timer_disarm(&timer); + os_timer_arm(&timer, 15, 0); +} + +IRrecv::IRrecv(int recvpin) { + irparams.recvpin = recvpin; +} + +// initialization +void IRrecv::enableIRIn() { + + // initialize state machine variables + irparams.rcvstate = STATE_IDLE; + irparams.rawlen = 0; + + // set pin modes + //PIN_FUNC_SELECT(IR_IN_MUX, IR_IN_FUNC); + GPIO_DIS_OUTPUT(irparams.recvpin); + + // Initialize timer + os_timer_disarm(&timer); + os_timer_setfn(&timer, (os_timer_func_t *)read_timeout, &timer); + + // ESP Attach Interrupt + ETS_GPIO_INTR_DISABLE(); + ETS_GPIO_INTR_ATTACH(gpio_intr, NULL); + gpio_pin_intr_state_set(GPIO_ID_PIN(irparams.recvpin), GPIO_PIN_INTR_ANYEDGE); + ETS_GPIO_INTR_ENABLE(); + //ETS_INTR_UNLOCK(); + + //attachInterrupt(irparams.recvpin, readIR, CHANGE); + //irReadTimer.initializeUs(USECPERTICK, readIR).start(); + //os_timer_arm_us(&irReadTimer, USECPERTICK, 1); + //ets_timer_arm_new(&irReadTimer, USECPERTICK, 1, 0); +} + +void IRrecv::disableIRIn() { + //irReadTimer.stop(); + //os_timer_disarm(&irReadTimer); + ETS_INTR_LOCK(); + ETS_GPIO_INTR_DISABLE(); +} + +void IRrecv::resume() { + irparams.rcvstate = STATE_IDLE; + irparams.rawlen = 0; +} + +// Decodes the received IR message +// Returns 0 if no data ready, 1 if data ready. +// Results of decoding are stored in results +int IRrecv::decode(decode_results *results) { + results->rawbuf = irparams.rawbuf; + results->rawlen = irparams.rawlen; + if (irparams.rcvstate != STATE_STOP) { + return ERR; + } +#ifdef DEBUG + Serial.println("Attempting NEC decode"); +#endif + if (decodeNEC(results)) { + return DECODED; + } + +#ifdef DEBUG + Serial.println("Attempting Sony decode"); +#endif + if (decodeSony(results)) { + return DECODED; + } + /* +#ifdef DEBUG + Serial.println("Attempting Sanyo decode"); +#endif + if (decodeSanyo(results)) { + return DECODED; + }*/ +#ifdef DEBUG + Serial.println("Attempting Mitsubishi decode"); +#endif + if (decodeMitsubishi(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting RC5 decode"); +#endif + if (decodeRC5(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting RC6 decode"); +#endif + if (decodeRC6(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting Panasonic decode"); +#endif + if (decodePanasonic(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting LG decode"); +#endif + if (decodeLG(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting JVC decode"); +#endif + if (decodeJVC(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting SAMSUNG decode"); +#endif + if (decodeSAMSUNG(results)) { + return DECODED; + } +#ifdef DEBUG + Serial.println("Attempting Whynter decode"); +#endif + if (decodeWhynter(results)) { + return DECODED; + } + // decodeHash returns a hash on any input. + // Thus, it needs to be last in the list. + // If you add any decodes, add them before this. + if (decodeHash(results)) { + return DECODED; + } + // Throw away and start over + resume(); + return ERR; +} + +// NECs have a repeat only 4 items long +long IRrecv::decodeNEC(decode_results *results) { + long data = 0; + int offset = 1; // Skip initial space + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], NEC_HDR_MARK)) { + return ERR; + } + offset++; + // Check for repeat + if (irparams.rawlen == 4 && + MATCH_SPACE(results->rawbuf[offset], NEC_RPT_SPACE) && + MATCH_MARK(results->rawbuf[offset+1], NEC_BIT_MARK)) { + results->bits = 0; + results->value = REPEAT; + results->decode_type = NEC; + return DECODED; + } + if (irparams.rawlen < 2 * NEC_BITS + 4) { + return ERR; + } + // Initial space + if (!MATCH_SPACE(results->rawbuf[offset], NEC_HDR_SPACE)) { + return ERR; + } + offset++; + for (int i = 0; i < NEC_BITS; i++) { + if (!MATCH_MARK(results->rawbuf[offset], NEC_BIT_MARK)) { + return ERR; + } + offset++; + if (MATCH_SPACE(results->rawbuf[offset], NEC_ONE_SPACE)) { + data = (data << 1) | 1; + } + else if (MATCH_SPACE(results->rawbuf[offset], NEC_ZERO_SPACE)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + // Success + results->bits = NEC_BITS; + results->value = data; + results->decode_type = NEC; + return DECODED; +} + +long IRrecv::decodeSony(decode_results *results) { + long data = 0; + if (irparams.rawlen < 2 * SONY_BITS + 2) { + return ERR; + } + int offset = 0; // Dont skip first space, check its size + + /* + // Some Sony's deliver repeats fast after first + // unfortunately can't spot difference from of repeat from two fast clicks + if (results->rawbuf[offset] < SONY_DOUBLE_SPACE_USECS) { + // Serial.print("IR Gap found: "); + results->bits = 0; + results->value = REPEAT; + results->decode_type = SANYO; + return DECODED; + }*/ + offset++; + + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], SONY_HDR_MARK)) { + return ERR; + } + offset++; + + while (offset + 1 < irparams.rawlen) { + if (!MATCH_SPACE(results->rawbuf[offset], SONY_HDR_SPACE)) { + break; + } + offset++; + if (MATCH_MARK(results->rawbuf[offset], SONY_ONE_MARK)) { + data = (data << 1) | 1; + } + else if (MATCH_MARK(results->rawbuf[offset], SONY_ZERO_MARK)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + + // Success + results->bits = (offset - 1) / 2; + if (results->bits < 12) { + results->bits = 0; + return ERR; + } + results->value = data; + results->decode_type = SONY; + return DECODED; +} + +long IRrecv::decodeWhynter(decode_results *results) { + long data = 0; + + if (irparams.rawlen < 2 * WHYNTER_BITS + 6) { + return ERR; + } + + int offset = 1; // Skip first space + + + // sequence begins with a bit mark and a zero space + if (!MATCH_MARK(results->rawbuf[offset], WHYNTER_BIT_MARK)) { + return ERR; + } + offset++; + if (!MATCH_SPACE(results->rawbuf[offset], WHYNTER_ZERO_SPACE)) { + return ERR; + } + offset++; + + // header mark and space + if (!MATCH_MARK(results->rawbuf[offset], WHYNTER_HDR_MARK)) { + return ERR; + } + offset++; + if (!MATCH_SPACE(results->rawbuf[offset], WHYNTER_HDR_SPACE)) { + return ERR; + } + offset++; + + // data bits + for (int i = 0; i < WHYNTER_BITS; i++) { + if (!MATCH_MARK(results->rawbuf[offset], WHYNTER_BIT_MARK)) { + return ERR; + } + offset++; + if (MATCH_SPACE(results->rawbuf[offset], WHYNTER_ONE_SPACE)) { + data = (data << 1) | 1; + } + else if (MATCH_SPACE(results->rawbuf[offset],WHYNTER_ZERO_SPACE)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + + // trailing mark + if (!MATCH_MARK(results->rawbuf[offset], WHYNTER_BIT_MARK)) { + return ERR; + } + // Success + results->bits = WHYNTER_BITS; + results->value = data; + results->decode_type = WHYNTER; + return DECODED; +} + +// I think this is a Sanyo decoder - serial = SA 8650B +// Looks like Sony except for timings, 48 chars of data and time/space different +long IRrecv::decodeSanyo(decode_results *results) { + long data = 0; + if (irparams.rawlen < 2 * SANYO_BITS + 2) { + return ERR; + } + int offset = 1; // Skip first space + + + // Initial space + /* Put this back in for debugging - note can't use #DEBUG as if Debug on we don't see the repeat cos of the delay + Serial.print("IR Gap: "); + Serial.println( results->rawbuf[offset]); + Serial.println( "test against:"); + Serial.println(results->rawbuf[offset]); + */ + + if (results->rawbuf[offset] < SANYO_DOUBLE_SPACE_USECS) { + // Serial.print("IR Gap found: "); + results->bits = 0; + results->value = REPEAT; + results->decode_type = SANYO; + return DECODED; + } + offset++; + + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], SANYO_HDR_MARK)) { + return ERR; + } + offset++; + + // Skip Second Mark + if (!MATCH_MARK(results->rawbuf[offset], SANYO_HDR_MARK)) { + return ERR; + } + offset++; + + while (offset + 1 < irparams.rawlen) { + if (!MATCH_SPACE(results->rawbuf[offset], SANYO_HDR_SPACE)) { + break; + } + offset++; + if (MATCH_MARK(results->rawbuf[offset], SANYO_ONE_MARK)) { + data = (data << 1) | 1; + } + else if (MATCH_MARK(results->rawbuf[offset], SANYO_ZERO_MARK)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + + // Success + results->bits = (offset - 1) / 2; + if (results->bits < 12) { + results->bits = 0; + return ERR; + } + results->value = data; + results->decode_type = SANYO; + return DECODED; +} + +// Looks like Sony except for timings, 48 chars of data and time/space different +long IRrecv::decodeMitsubishi(decode_results *results) { + // Serial.print("?!? decoding Mitsubishi:");Serial.print(irparams.rawlen); Serial.print(" want "); Serial.println( 2 * MITSUBISHI_BITS + 2); + long data = 0; + if (irparams.rawlen < 2 * MITSUBISHI_BITS + 2) { + return ERR; + } + int offset = 1; // Skip first space + // Initial space + /* Put this back in for debugging - note can't use #DEBUG as if Debug on we don't see the repeat cos of the delay + Serial.print("IR Gap: "); + Serial.println( results->rawbuf[offset]); + Serial.println( "test against:"); + Serial.println(results->rawbuf[offset]); + */ + /* Not seeing double keys from Mitsubishi + if (results->rawbuf[offset] < MITSUBISHI_DOUBLE_SPACE_USECS) { + // Serial.print("IR Gap found: "); + results->bits = 0; + results->value = REPEAT; + results->decode_type = MITSUBISHI; + return DECODED; + } + */ + + offset++; + + // Typical + // 14200 7 41 7 42 7 42 7 17 7 17 7 18 7 41 7 18 7 17 7 17 7 18 7 41 8 17 7 17 7 18 7 17 7 + + // Initial Space + if (!MATCH_MARK(results->rawbuf[offset], MITSUBISHI_HDR_SPACE)) { + return ERR; + } + offset++; + while (offset + 1 < irparams.rawlen) { + if (MATCH_MARK(results->rawbuf[offset], MITSUBISHI_ONE_MARK)) { + data = (data << 1) | 1; + } + else if (MATCH_MARK(results->rawbuf[offset], MITSUBISHI_ZERO_MARK)) { + data <<= 1; + } + else { + // Serial.println("A"); Serial.println(offset); Serial.println(results->rawbuf[offset]); + return ERR; + } + offset++; + if (!MATCH_SPACE(results->rawbuf[offset], MITSUBISHI_HDR_SPACE)) { + // Serial.println("B"); Serial.println(offset); Serial.println(results->rawbuf[offset]); + break; + } + offset++; + } + + // Success + results->bits = (offset - 1) / 2; + if (results->bits < MITSUBISHI_BITS) { + results->bits = 0; + return ERR; + } + results->value = data; + results->decode_type = MITSUBISHI; + return DECODED; +} + +// Gets one undecoded level at a time from the raw buffer. +// The RC5/6 decoding is easier if the data is broken into time intervals. +// E.g. if the buffer has MARK for 2 time intervals and SPACE for 1, +// successive calls to getRClevel will return MARK, MARK, SPACE. +// offset and used are updated to keep track of the current position. +// t1 is the time interval for a single bit in microseconds. +// Returns -1 for error (measured time interval is not a multiple of t1). +int IRrecv::getRClevel(decode_results *results, int *offset, int *used, int t1) { + if (*offset >= results->rawlen) { + // After end of recorded buffer, assume SPACE. + return SPACE; + } + int width = results->rawbuf[*offset]; + int val = ((*offset) % 2) ? MARK : SPACE; + int correction = (val == MARK) ? MARK_EXCESS : - MARK_EXCESS; + + int avail; + if (MATCH(width, t1 + correction)) { + avail = 1; + } + else if (MATCH(width, 2*t1 + correction)) { + avail = 2; + } + else if (MATCH(width, 3*t1 + correction)) { + avail = 3; + } + else { + return -1; + } + + (*used)++; + if (*used >= avail) { + *used = 0; + (*offset)++; + } +#ifdef DEBUG + if (val == MARK) { + Serial.println("MARK"); + } + else { + Serial.println("SPACE"); + } +#endif + return val; +} + +long IRrecv::decodeRC5(decode_results *results) { + if (irparams.rawlen < MIN_RC5_SAMPLES + 2) { + return ERR; + } + int offset = 1; // Skip gap space + long data = 0; + int used = 0; + // Get start bits + if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR; + if (getRClevel(results, &offset, &used, RC5_T1) != SPACE) return ERR; + if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR; + int nbits; + for (nbits = 0; offset < irparams.rawlen; nbits++) { + int levelA = getRClevel(results, &offset, &used, RC5_T1); + int levelB = getRClevel(results, &offset, &used, RC5_T1); + if (levelA == SPACE && levelB == MARK) { + // 1 bit + data = (data << 1) | 1; + } + else if (levelA == MARK && levelB == SPACE) { + // zero bit + data <<= 1; + } + else { + return ERR; + } + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = RC5; + return DECODED; +} + +long IRrecv::decodeRC6(decode_results *results) { + if (results->rawlen < MIN_RC6_SAMPLES) { + return ERR; + } + int offset = 1; // Skip first space + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], RC6_HDR_MARK)) { + return ERR; + } + offset++; + if (!MATCH_SPACE(results->rawbuf[offset], RC6_HDR_SPACE)) { + return ERR; + } + offset++; + long data = 0; + int used = 0; + // Get start bit (1) + if (getRClevel(results, &offset, &used, RC6_T1) != MARK) return ERR; + if (getRClevel(results, &offset, &used, RC6_T1) != SPACE) return ERR; + int nbits; + for (nbits = 0; offset < results->rawlen; nbits++) { + int levelA, levelB; // Next two levels + levelA = getRClevel(results, &offset, &used, RC6_T1); + if (nbits == 3) { + // T bit is double wide; make sure second half matches + if (levelA != getRClevel(results, &offset, &used, RC6_T1)) return ERR; + } + levelB = getRClevel(results, &offset, &used, RC6_T1); + if (nbits == 3) { + // T bit is double wide; make sure second half matches + if (levelB != getRClevel(results, &offset, &used, RC6_T1)) return ERR; + } + if (levelA == MARK && levelB == SPACE) { // reversed compared to RC5 + // 1 bit + data = (data << 1) | 1; + } + else if (levelA == SPACE && levelB == MARK) { + // zero bit + data <<= 1; + } + else { + return ERR; // Error + } + } + // Success + results->bits = nbits; + results->value = data; + results->decode_type = RC6; + return DECODED; +} + +long IRrecv::decodePanasonic(decode_results *results) { + unsigned long long data = 0; + int offset = 1; // Dont skip first space + /*if (!MATCH_MARK(results->rawbuf[offset], PANASONIC_HDR_MARK)) { + return ERR; + } + offset++;*/ + if (!MATCH_MARK(results->rawbuf[offset], PANASONIC_HDR_SPACE)) { + return ERR; + } + offset++; + // decode address + for (int i = 0; i < PANASONIC_BITS; i++) { + if (!MATCH(results->rawbuf[offset++], PANASONIC_BIT_MARK)) { + return ERR; + } + if (MATCH(results->rawbuf[offset],PANASONIC_ONE_SPACE)) { + data = (data << 1) | 1; + } else if (MATCH(results->rawbuf[offset],PANASONIC_ZERO_SPACE)) { + data <<= 1; + } else { + return ERR; + } + offset++; + } + results->value = (unsigned long)data; + results->panasonicAddress = (unsigned int)(data >> 32); + results->decode_type = PANASONIC; + results->bits = PANASONIC_BITS; + return DECODED; +} + +long IRrecv::decodeLG(decode_results *results) { + long data = 0; + int offset = 1; // Skip first space + + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], LG_HDR_MARK)) { + return ERR; + } + offset++; + if (irparams.rawlen < 2 * LG_BITS + 1 ) { + return ERR; + } + // Initial space + if (!MATCH_SPACE(results->rawbuf[offset], LG_HDR_SPACE)) { + return ERR; + } + offset++; + for (int i = 0; i < LG_BITS; i++) { + if (!MATCH_MARK(results->rawbuf[offset], LG_BIT_MARK)) { + return ERR; + } + offset++; + if (MATCH_SPACE(results->rawbuf[offset], LG_ONE_SPACE)) { + data = (data << 1) | 1; + } + else if (MATCH_SPACE(results->rawbuf[offset], LG_ZERO_SPACE)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + //Stop bit + if (!MATCH_MARK(results->rawbuf[offset], LG_BIT_MARK)){ + return ERR; + } + // Success + results->bits = LG_BITS; + results->value = data; + results->decode_type = LG; + return DECODED; +} + +long IRrecv::decodeJVC(decode_results *results) { + long data = 0; + int offset = 1; // Skip first space + // Check for repeat + if (irparams.rawlen - 1 == 33 && + MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK) && + MATCH_MARK(results->rawbuf[irparams.rawlen-1], JVC_BIT_MARK)) { + results->bits = 0; + results->value = REPEAT; + results->decode_type = JVC; + return DECODED; + } + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], JVC_HDR_MARK)) { + return ERR; + } + offset++; + if (irparams.rawlen < 2 * JVC_BITS + 1 ) { + return ERR; + } + // Initial space + if (!MATCH_SPACE(results->rawbuf[offset], JVC_HDR_SPACE)) { + return ERR; + } + offset++; + for (int i = 0; i < JVC_BITS; i++) { + if (!MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK)) { + return ERR; + } + offset++; + if (MATCH_SPACE(results->rawbuf[offset], JVC_ONE_SPACE)) { + data = (data << 1) | 1; + } + else if (MATCH_SPACE(results->rawbuf[offset], JVC_ZERO_SPACE)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + //Stop bit + if (!MATCH_MARK(results->rawbuf[offset], JVC_BIT_MARK)){ + return ERR; + } + // Success + results->bits = JVC_BITS; + results->value = data; + results->decode_type = JVC; + return DECODED; +} + +// SAMSUNGs have a repeat only 4 items long +long IRrecv::decodeSAMSUNG(decode_results *results) { + long data = 0; + int offset = 0; // Dont skip first space + // Initial mark + if (!MATCH_MARK(results->rawbuf[offset], SAMSUNG_HDR_MARK)) { + return ERR; + } + offset++; + // Check for repeat + if (irparams.rawlen == 4 && + MATCH_SPACE(results->rawbuf[offset], SAMSUNG_RPT_SPACE) && + MATCH_MARK(results->rawbuf[offset+1], SAMSUNG_BIT_MARK)) { + results->bits = 0; + results->value = REPEAT; + results->decode_type = SAMSUNG; + return DECODED; + } + if (irparams.rawlen < 2 * SAMSUNG_BITS + 2) { + return ERR; + } + // Initial space + if (!MATCH_SPACE(results->rawbuf[offset], SAMSUNG_HDR_SPACE)) { + return ERR; + } + offset++; + for (int i = 0; i < SAMSUNG_BITS; i++) { + if (!MATCH_MARK(results->rawbuf[offset], SAMSUNG_BIT_MARK)) { + return ERR; + } + offset++; + if (MATCH_SPACE(results->rawbuf[offset], SAMSUNG_ONE_SPACE)) { + data = (data << 1) | 1; + } + else if (MATCH_SPACE(results->rawbuf[offset], SAMSUNG_ZERO_SPACE)) { + data <<= 1; + } + else { + return ERR; + } + offset++; + } + // Success + results->bits = SAMSUNG_BITS; + results->value = data; + results->decode_type = SAMSUNG; + return DECODED; +} + +/* ----------------------------------------------------------------------- + * hashdecode - decode an arbitrary IR code. + * Instead of decoding using a standard encoding scheme + * (e.g. Sony, NEC, RC5), the code is hashed to a 32-bit value. + * + * The algorithm: look at the sequence of MARK signals, and see if each one + * is shorter (0), the same length (1), or longer (2) than the previous. + * Do the same with the SPACE signals. Hszh the resulting sequence of 0's, + * 1's, and 2's to a 32-bit value. This will give a unique value for each + * different code (probably), for most code systems. + * + * http://arcfn.com/2010/01/using-arbitrary-remotes-with-arduino.html + */ + +// Compare two tick values, returning 0 if newval is shorter, +// 1 if newval is equal, and 2 if newval is longer +// Use a tolerance of 20% +int IRrecv::compare(unsigned int oldval, unsigned int newval) { + if (newval < oldval * .8) { + return 0; + } + else if (oldval < newval * .8) { + return 2; + } + else { + return 1; + } +} + +// Use FNV hash algorithm: http://isthe.com/chongo/tech/comp/fnv/#FNV-param +#define FNV_PRIME_32 16777619 +#define FNV_BASIS_32 2166136261 + +/* Converts the raw code values into a 32-bit hash code. + * Hopefully this code is unique for each button. + * This isn't a "real" decoding, just an arbitrary value. + */ +long IRrecv::decodeHash(decode_results *results) { + // Require at least 6 samples to prevent triggering on noise + if (results->rawlen < 6) { + return ERR; + } + long hash = FNV_BASIS_32; + for (int i = 1; i+2 < results->rawlen; i++) { + int value = compare(results->rawbuf[i], results->rawbuf[i+2]); + // Add value into the hash + hash = (hash * FNV_PRIME_32) ^ value; + } + results->value = hash; + results->bits = 32; + results->decode_type = UNKNOWN; + return DECODED; +} + +// --------------------------------------------------------------- diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.h b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.h new file mode 100644 index 0000000..584f73c --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteESP8266.h @@ -0,0 +1,160 @@ + /*************************************************** + * IRremote for ESP8266 + * + * Based on the IRremote library for Arduino by Ken Shirriff + * Version 0.11 August, 2009 + * Copyright 2009 Ken Shirriff + * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html + * + * Edited by Mitra to add new controller SANYO + * + * Interrupt code based on NECIRrcv by Joe Knapp + * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 + * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/ + * + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + * LG added by Darryl Smith (based on the JVC protocol) + * Whynter A/C ARC-110WD added by Francesco Meschia + * + * Updated by markszabo (https://github.com/markszabo/IRremoteESP8266) for sending IR code on ESP8266 + * Updated by Sebastien Warin (http://sebastien.warin.fr) for receiving IR code on ESP8266 + * + * GPL license, all text above must be included in any redistribution + ****************************************************/ + +#ifndef IRremote_h +#define IRremote_h + +// The following are compile-time library options. +// If you change them, recompile the library. +// If DEBUG is defined, a lot of debugging output will be printed during decoding. +// TEST must be defined for the IRtest unittests to work. It will make some +// methods virtual, which will be slightly slower, which is why it is optional. +//#define DEBUG +//#define TEST + +enum decode_type_t { + NEC = 1, + SONY = 2, + RC5 = 3, + RC6 = 4, + DISH = 5, + SHARP = 6, + PANASONIC = 7, + JVC = 8, + SANYO = 9, + MITSUBISHI = 10, + SAMSUNG = 11, + LG = 12, + WHYNTER = 13, + AIWA_RC_T501 = 14, + + UNKNOWN = -1 +}; + +// Results returned from the decoder +class decode_results { +public: + int decode_type; // NEC, SONY, RC5, UNKNOWN + union { // This is used for decoding Panasonic and Sharp data + unsigned int panasonicAddress; + unsigned int sharpAddress; + }; + unsigned long value; // Decoded value + int bits; // Number of bits in decoded value + volatile unsigned int *rawbuf; // Raw intervals in .5 us ticks + int rawlen; // Number of records in rawbuf. +}; + +// Values for decode_type +#define NEC 1 +#define SONY 2 +#define RC5 3 +#define RC6 4 +#define DISH 5 +#define SHARP 6 +#define PANASONIC 7 +#define JVC 8 +#define SANYO 9 +#define MITSUBISHI 10 +#define SAMSUNG 11 +#define LG 12 +#define WHYNTER 13 +#define UNKNOWN -1 + +// Decoded value for NEC when a repeat code is received +#define REPEAT 0xffffffff + +// main class for receiving IR +class IRrecv +{ +public: + IRrecv(int recvpin); + int decode(decode_results *results); + void enableIRIn(); + void disableIRIn(); + void resume(); + private: + // These are called by decode + int getRClevel(decode_results *results, int *offset, int *used, int t1); + long decodeNEC(decode_results *results); + long decodeSony(decode_results *results); + long decodeSanyo(decode_results *results); + long decodeMitsubishi(decode_results *results); + long decodeRC5(decode_results *results); + long decodeRC6(decode_results *results); + long decodePanasonic(decode_results *results); + long decodeLG(decode_results *results); + long decodeJVC(decode_results *results); + long decodeSAMSUNG(decode_results *results); + long decodeWhynter(decode_results *results); + long decodeHash(decode_results *results); + int compare(unsigned int oldval, unsigned int newval); +}; + +// Only used for testing; can remove virtual for shorter code +#ifdef TEST +#define VIRTUAL virtual +#else +#define VIRTUAL +#endif +class IRsend +{ +public: + IRsend(int IRsendPin); + void begin(); + void sendWhynter(unsigned long data, int nbits); + void sendNEC(unsigned long data, int nbits); + void sendLG(unsigned long data, int nbits); + void sendSony(unsigned long data, int nbits); + // Neither Sanyo nor Mitsubishi send is implemented yet + // void sendSanyo(unsigned long data, int nbits); + // void sendMitsubishi(unsigned long data, int nbits); + void sendRaw(unsigned int buf[], int len, int hz); + void sendRC5(unsigned long data, int nbits); + void sendRC6(unsigned long data, int nbits); + void sendDISH(unsigned long data, int nbits); + void sendSharp(unsigned int address, unsigned int command); + void sendSharpRaw(unsigned long data, int nbits); + void sendPanasonic(unsigned int address, unsigned long data); + void sendJVC(unsigned long data, int nbits, int repeat); // *Note instead of sending the REPEAT constant if you want the JVC repeat signal sent, send the original code value and change the repeat argument from 0 to 1. JVC protocol repeats by skipping the header NOT by sending a separate code value like NEC does. + void sendSAMSUNG(unsigned long data, int nbits); + void sendDenon (unsigned long data, int nbits); + bool sendPronto (const char* s, bool repeat, bool fallback); + void enableIROut(int khz); + VIRTUAL void mark(int usec); + VIRTUAL void space(int usec); +private: + int halfPeriodicTime; + int IRpin; +} ; + +// Some useful constants +#define USECPERTICK 50 // microseconds per clock interrupt tick +#define RAWBUF 100 // Length of raw duration buffer + +// Marks tend to be 100us too long, and spaces 100us too short +// when received due to sensor lag. +#define MARK_EXCESS 100 + +#endif diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteInt.h b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteInt.h new file mode 100644 index 0000000..56d11fe --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/IRremoteInt.h @@ -0,0 +1,198 @@ + /*************************************************** + * IRremote for ESP8266 + * + * Based on the IRremote library for Arduino by Ken Shirriff + * Version 0.11 August, 2009 + * Copyright 2009 Ken Shirriff + * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.html + * + * Modified by Paul Stoffregen to support other boards and timers + * + * Interrupt code based on NECIRrcv by Joe Knapp + * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 + * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-an-arduino/ + * + * JVC and Panasonic protocol added by Kristian Lauszus (Thanks to zenwheel and other people at the original blog post) + * Whynter A/C ARC-110WD added by Francesco Meschia + * + * 09/23/2015 : Samsung pulse parameters updated by Sebastien Warin to be compatible with EUxxD6200 + * + * GPL license, all text above must be included in any redistribution + ****************************************************/ + +#ifndef IRremoteint_h +#define IRremoteint_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include +#else +#include +#endif + +// Pulse parms are *50-100 for the Mark and *50+100 for the space +// First MARK is the one after the long gap +// pulse parameters in usec +#define WHYNTER_HDR_MARK 2850 +#define WHYNTER_HDR_SPACE 2850 +#define WHYNTER_BIT_MARK 750 +#define WHYNTER_ONE_MARK 750 +#define WHYNTER_ONE_SPACE 2150 +#define WHYNTER_ZERO_MARK 750 +#define WHYNTER_ZERO_SPACE 750 + +#define NEC_HDR_MARK 9000 +#define NEC_HDR_SPACE 4500 +#define NEC_BIT_MARK 560 +#define NEC_ONE_SPACE 1690 +#define NEC_ZERO_SPACE 560 +#define NEC_RPT_SPACE 2250 + +#define SONY_HDR_MARK 2400 +#define SONY_HDR_SPACE 600 +#define SONY_ONE_MARK 1200 +#define SONY_ZERO_MARK 600 +#define SONY_RPT_LENGTH 45000 +#define SONY_DOUBLE_SPACE_USECS 500 // usually ssee 713 - not using ticks as get number wrapround + +// SA 8650B +#define SANYO_HDR_MARK 3500 // seen range 3500 +#define SANYO_HDR_SPACE 950 // seen 950 +#define SANYO_ONE_MARK 2400 // seen 2400 +#define SANYO_ZERO_MARK 700 // seen 700 +#define SANYO_DOUBLE_SPACE_USECS 800 // usually ssee 713 - not using ticks as get number wrapround +#define SANYO_RPT_LENGTH 45000 + +// Mitsubishi RM 75501 +// 14200 7 41 7 42 7 42 7 17 7 17 7 18 7 41 7 18 7 17 7 17 7 18 7 41 8 17 7 17 7 18 7 17 7 + +// #define MITSUBISHI_HDR_MARK 250 // seen range 3500 +#define MITSUBISHI_HDR_SPACE 350 // 7*50+100 +#define MITSUBISHI_ONE_MARK 1950 // 41*50-100 +#define MITSUBISHI_ZERO_MARK 750 // 17*50-100 +// #define MITSUBISHI_DOUBLE_SPACE_USECS 800 // usually ssee 713 - not using ticks as get number wrapround +// #define MITSUBISHI_RPT_LENGTH 45000 + + +#define RC5_T1 889 +#define RC5_RPT_LENGTH 46000 + +#define RC6_HDR_MARK 2666 +#define RC6_HDR_SPACE 889 +#define RC6_T1 444 +#define RC6_RPT_LENGTH 46000 + +#define SHARP_BIT_MARK 245 +#define SHARP_ONE_SPACE 1805 +#define SHARP_ZERO_SPACE 795 +#define SHARP_GAP 600000 +#define SHARP_TOGGLE_MASK 0x3FF +#define SHARP_RPT_SPACE 3000 + +#define DISH_HDR_MARK 400 +#define DISH_HDR_SPACE 6100 +#define DISH_BIT_MARK 400 +#define DISH_ONE_SPACE 1700 +#define DISH_ZERO_SPACE 2800 +#define DISH_RPT_SPACE 6200 +#define DISH_TOP_BIT 0x8000 + +#define PANASONIC_HDR_MARK 3502 +#define PANASONIC_HDR_SPACE 1750 +#define PANASONIC_BIT_MARK 502 +#define PANASONIC_ONE_SPACE 1244 +#define PANASONIC_ZERO_SPACE 400 + +#define JVC_HDR_MARK 8000 +#define JVC_HDR_SPACE 4000 +#define JVC_BIT_MARK 600 +#define JVC_ONE_SPACE 1600 +#define JVC_ZERO_SPACE 550 +#define JVC_RPT_LENGTH 60000 + +#define LG_HDR_MARK 8000 +#define LG_HDR_SPACE 4000 +#define LG_BIT_MARK 600 +#define LG_ONE_SPACE 1600 +#define LG_ZERO_SPACE 550 +#define LG_RPT_LENGTH 60000 + +/* +#define SAMSUNG_HDR_MARK 5000 +#define SAMSUNG_HDR_SPACE 5000 +#define SAMSUNG_BIT_MARK 560 +#define SAMSUNG_ONE_SPACE 1600 +#define SAMSUNG_ZERO_SPACE 560 +#define SAMSUNG_RPT_SPACE 2250 +*/ + +// Update by Sebastien Warin for my EU46D6200 +#define SAMSUNG_HDR_MARK 4500 +#define SAMSUNG_HDR_SPACE 4500 +#define SAMSUNG_BIT_MARK 590 +#define SAMSUNG_ONE_SPACE 1690 +#define SAMSUNG_ZERO_SPACE 590 +#define SAMSUNG_RPT_SPACE 2250 + +#define SHARP_BITS 15 +#define DISH_BITS 16 + +#define DENON_BITS 14 // The number of bits in the command + +#define DENON_HDR_MARK 300 // The length of the Header:Mark +#define DENON_HDR_SPACE 750 // The lenght of the Header:Space + +#define DENON_BIT_MARK 300 // The length of a Bit:Mark +#define DENON_ONE_SPACE 1800 // The length of a Bit:Space for 1's +#define DENON_ZERO_SPACE 750 // The length of a Bit:Space for 0's + +#define TOLERANCE 25 // percent tolerance in measurements +#define LTOL (1.0 - TOLERANCE/100.) +#define UTOL (1.0 + TOLERANCE/100.) + +#define _GAP 5000 // Minimum map between transmissions +#define GAP_TICKS (_GAP/USECPERTICK) + +#define TICKS_LOW(us) (int) (((us)*LTOL/USECPERTICK)) +#define TICKS_HIGH(us) (int) (((us)*UTOL/USECPERTICK + 1)) + +// receiver states +#define STATE_IDLE 2 +#define STATE_MARK 3 +#define STATE_SPACE 4 +#define STATE_STOP 5 + +#define ERR 0 +#define DECODED 1 + +// information for the interrupt handler +typedef struct { + uint8_t recvpin; // pin for IR data from detector + uint8_t rcvstate; // state machine + unsigned int timer; // state timer, counts 50uS ticks. + unsigned int rawbuf[RAWBUF]; // raw data + uint8_t rawlen; // counter of entries in rawbuf +} +irparams_t; + +// Defined in IRremote.cpp +extern volatile irparams_t irparams; + +// IR detector output is active low +#define MARK 0 +#define SPACE 1 + +#define TOPBIT 0x80000000 + +#define NEC_BITS 32 +#define SONY_BITS 12 +#define SANYO_BITS 12 +#define MITSUBISHI_BITS 16 +#define MIN_RC5_SAMPLES 11 +#define MIN_RC6_SAMPLES 1 +#define PANASONIC_BITS 48 +#define JVC_BITS 16 +#define LG_BITS 28 +#define SAMSUNG_BITS 32 +#define WHYNTER_BITS 32 + +#endif diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/LICENSE.txt b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/LICENSE.txt new file mode 100644 index 0000000..77cec6d --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/LICENSE.txt @@ -0,0 +1,458 @@ + + 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. + diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/README.md b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/README.md new file mode 100644 index 0000000..6864df4 --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/README.md @@ -0,0 +1,27 @@ +# IRremote ESP8266 Library + +This library enables you to **send and receive** infra-red signals on an ESP8266 using Arduino framework (https://github.com/esp8266/Arduino) + +This library is based on Ken Shirriff's work (https://github.com/shirriff/Arduino-IRremote/) + +[Mark Szabo](https://github.com/markszabo/IRremoteESP8266) has updated the IRsend class to work on ESP8266 and [Sebastien Warin](https://github.com/sebastienwarin/IRremoteESP8266) the receiving & decoding part (IRrecv class). + +Seb's notes : I also changed the pulse parameters for Samsung, update the Panasonic and Samsung decoders and remove the SANYO decoders. The IR decoder was successfully tested with Panasonic and Samsung remote controls. + +## Installation +1. Click "Download ZIP" +2. Extract the downloaded zip file +3. Rename the extracted folder to "IRremoteESP8266" +4. Move this folder to your libraries directory (under windows: C:\Users\YOURNAME\Documents\Arduino\libraries\) +5. Restart your Arduino ide +6. Check out the examples + +## Contributing +If you want to contribute to this project: +- Report bugs and errors +- Ask for enhancements +- Create issues and pull requests +- Tell other people about this library + +## Contributors +Check [here](Contributors.md) diff --git a/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRServer/IRServer.ino b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRServer/IRServer.ino new file mode 100644 index 0000000..b51ad2c --- /dev/null +++ b/libraries/ESP8266_MQTT_Mesh/examples/ESP8266MeshIRRemote/lib/IRremoteESP8266/examples/IRServer/IRServer.ino @@ -0,0 +1,88 @@ +/* + * IRremoteESP8266: IRServer - demonstrates sending IR codes controlled from a webserver + * An IR LED must be connected to ESP8266 pin 0. + * Version 0.1 June, 2015 + */ + +#include +#include +#include +#include +#include + +const char* ssid = "....."; +const char* password = "....."; +MDNSResponder mdns; + +ESP8266WebServer server(80); + +IRsend irsend(0); + +void handleRoot() { + server.send(200, "text/html", " ESP8266 Demo

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