diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..a1b16285 Binary files /dev/null and b/.DS_Store differ diff --git a/.cproject b/.cproject index 379a8d68..54db1eb5 100644 --- a/.cproject +++ b/.cproject @@ -26,15 +26,46 @@ + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..15d2ae94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.project +.cproject +.settings/ + +# IDEs +.vs/ +.idea/ + +# CMake +cmake-build-debug/ diff --git a/Documentation/BLE C++ Guide.pdf b/Documentation/BLE C++ Guide.pdf old mode 100644 new mode 100755 index e68e0398..cc4ca4bb Binary files a/Documentation/BLE C++ Guide.pdf and b/Documentation/BLE C++ Guide.pdf differ diff --git a/Documentation/C++ HTTP Server.pdf b/Documentation/C++ HTTP Server.pdf new file mode 100755 index 00000000..ee5ab104 Binary files /dev/null and b/Documentation/C++ HTTP Server.pdf differ diff --git a/Documentation/ideas/FastFlash/README.md b/Documentation/ideas/FastFlash/README.md new file mode 100644 index 00000000..d599b4aa --- /dev/null +++ b/Documentation/ideas/FastFlash/README.md @@ -0,0 +1,8 @@ +# FastFlash +As we build ESP32 applications, we typically perform a compile, flash, test loop cycle. We compile an app, we flash the ESP32 with that app and then we test whether it works. We perform these actions over and over again. When we look at the time taken in each step, we see there is compile time on our PC and then the time taken to flash the PC. This story talks about the time taken to flash the PC. + +The ESP32 is typically configured to flash at 115200 kbps. This is 115200 bits per second. If we think that a typical ESP32 application is 800KBytes then this requires a transmission of: + +800000 * 9 = 7.2 million bits = 62.5 seconds + +we can increase our baud rate up to 921600 = 7.8 seconds \ No newline at end of file diff --git a/Documentation/images/arduino_debug.png b/Documentation/images/arduino_debug.png new file mode 100755 index 00000000..0cccfb5f Binary files /dev/null and b/Documentation/images/arduino_debug.png differ diff --git a/Kconfig b/Kconfig new file mode 100644 index 00000000..36b07fb6 --- /dev/null +++ b/Kconfig @@ -0,0 +1,23 @@ +menu "nkolban's c++ wrappers" + +menuconfig NKOLBAN + bool "nkolban's c++ wrappers" + default y + help + Select this option to use nkolban's c++ wrappers. + +menuconfig NKOLBAN_BLE + bool "bluetooth wrappers" + depends on BT_ENABLED && NKOLBAN + default y + help + Select this option to use nkolban's c++ bluetooth ble wrappers. + +menuconfig NKOLBAN_BLE2902 + bool "BLE2902" + depends on NKOLBAN_BLE + default y + help + Select this option to use nkolban's c++ bluetooth BLE2902 wrappers. + +endmenu \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8dada3ed..4558f79f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2017 Neil Kolban Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index c785b3c6..fbea27ae 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,2 @@ # ESP32 Snippets -This repository hosts a set of ESP32 snippets that are in different -stages of completeness. They are made available in the hope that there -may be something of value to you and under the notion that something -is better than nothing. The samples are being categorized. - -* vfs - Virtual File System -* wifi - WiFi access -* curl - Examples of curl usage +This repository is no longer being actively maintained. It was previously archived to make it read-only but, by request, has been un-archived so that others may continue to post comments and issues. However, please understand that issues raised are unlikely to be resolved against this repository. diff --git a/VisualStudioCode/README.md b/VisualStudioCode/README.md new file mode 100644 index 00000000..ff5ab2f2 --- /dev/null +++ b/VisualStudioCode/README.md @@ -0,0 +1,4 @@ +These are file for Microsoft Visual Studio Code and can be copied into your `.vscode` project folder. For more information on this area, see: + +* [VSCode JTAG Debugging of ESP32 - Part 2](https://gojimmypi.blogspot.com/2017/05/vscode-remote-jtag-debugging-of-esp32.html) +* [Deous/VSC-Guide-for-esp32](https://github.com/Deous/VSC-Guide-for-esp32) \ No newline at end of file diff --git a/VisualStudioCode/c_cpp_properties.json b/VisualStudioCode/c_cpp_properties.json new file mode 100644 index 00000000..9ef83b14 --- /dev/null +++ b/VisualStudioCode/c_cpp_properties.json @@ -0,0 +1,266 @@ +{ + "configurations": [ + { + "name": "ESP32-Linux", + "includePath": [ + "${workspaceRoot}", + "${workspaceRoot}/components", + "${workspaceRoot}/build", + "${workspaceRoot}/build/include", + "${env:IDF_PATH}/components/bt/bluedroid/utils/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/sdp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/rfcomm/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/l2cap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gatt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avrc/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avdt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avct/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/include", + "${env:IDF_PATH}/components/bt/bluedroid/osi/include", + "${env:IDF_PATH}/components/bt/bluedroid/hci/include", + "${env:IDF_PATH}/components/bt/bluedroid/gki/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/encoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/decoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/device/include", + "${env:IDF_PATH}/components/bt/bluedroid/btcore/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/hid/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/dis/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/battery/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/blufi/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/sys/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/include", + "${env:IDF_PATH}/components/bt/bluedroid/api/include", + "${env:IDF_PATH}/components/bt/bluedroid/include", + "${env:IDF_PATH}/components/aws_iot/include", + "${env:IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include", + "${env:IDF_PATH}/components/app_trace/include", + "${env:IDF_PATH}/components/app_update/include", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "${env:IDF_PATH}/components/bootloader_support/include", + "${env:IDF_PATH}/components/bootloader_support/include_priv", + "${env:IDF_PATH}/components/bt/include", + "${env:IDF_PATH}/components/coap/port/include", + "${env:IDF_PATH}/components/coap/port/include/coap", + "${env:IDF_PATH}/components/coap/libcoap/include", + "${env:IDF_PATH}/components/coap/libcoap/include/coap", + "${env:IDF_PATH}/components/cxx/include", + "${env:IDF_PATH}/components/driver/include", + "${env:IDF_PATH}/components/driver/include/driver", + "${env:IDF_PATH}/components/esp32/include", + "${env:IDF_PATH}/components/ethernet/include", + "${env:IDF_PATH}/components/expat/include/expat", + "${env:IDF_PATH}/components/expat/port/include", + "${env:IDF_PATH}/components/fatfs/src", + "${env:IDF_PATH}/components/freertos/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/jsmn/include", + "${env:IDF_PATH}/components/json/include", + "${env:IDF_PATH}/components/json/port/include", + "${env:IDF_PATH}/components/json/cJSON", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include/sodium", + "${env:IDF_PATH}/components/log/include", + "${env:IDF_PATH}/components/lwip/include/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/apps/ping", + "${env:IDF_PATH}/components/lwip/include/lwip/apps", + "${env:IDF_PATH}/components/lwip/include/lwip/apps/sntp", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip/priv", + "${env:IDF_PATH}/components/lwip/include/lwip/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp/polarssl", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arch", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arpa", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netinet", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/include/lwip/posix/sys", + "${env:IDF_PATH}/components/mbedtls/port/include", + "${env:IDF_PATH}/components/mbedtls/mbedtls/include", + "${env:IDF_PATH}/components/mbedtls/port/include/mbedtls", + "${env:IDF_PATH}/components/mdns/include", + "${env:IDF_PATH}/components/micro-ecc/micro-ecc", + "${env:IDF_PATH}/components/newlib/include", + "${env:IDF_PATH}/components/newlib/include/sys", + "${env:IDF_PATH}/components/newlib/platform_include", + "${env:IDF_PATH}/components/nghttp/include", + "${env:IDF_PATH}/components/nghttp/port/include", + "${env:IDF_PATH}/components/nvs_flash/include", + "${env:IDF_PATH}/components/openssl/include", + "${env:IDF_PATH}/components/openssl/include/internal", + "${env:IDF_PATH}/components/openssl/include/platform", + "${env:IDF_PATH}/components/openssl/include/openssl", + "${env:IDF_PATH}/components/pthread/include", + "${env:IDF_PATH}/components/sdmmc/include", + "${env:IDF_PATH}/components/spi_flash/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/soc/esp32/include", + "${env:IDF_PATH}/components/soc/include", + "${env:IDF_PATH}/components/soc/esp32/include/soc", + "${env:IDF_PATH}/components/spi_flash", + "${env:IDF_PATH}/components/spiffs/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/ulp/include", + "${env:IDF_PATH}/components/ulp/include/esp32", + "${env:IDF_PATH}/components/vfs/include", + "${env:IDF_PATH}/components/vfs/include/sys", + "${env:IDF_PATH}/components/wear_levelling/include", + "${env:IDF_PATH}/components/wpa_supplicant/include", + "${env:IDF_PATH}/components/wpa_supplicant/port/include", + "${env:IDF_PATH}/components/wpa_supplicant/include/crypto", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/eap_peer", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/tls", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/utils", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "C:/Program Files/Espressif/ESP-IDF Tools/toolchain/lib/gcc/xtensa-esp32-elf/5.2.0/include" + ], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ + "${workspaceRoot}", + "${workspaceRoot}/components", + "${workspaceRoot}/build", + "${workspaceRoot}/build/include", + "${env:IDF_PATH}/components/bt/bluedroid/utils/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/sdp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/rfcomm/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/l2cap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gatt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/gap/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avrc/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avdt/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/avct/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/stack/include", + "${env:IDF_PATH}/components/bt/bluedroid/osi/include", + "${env:IDF_PATH}/components/bt/bluedroid/hci/include", + "${env:IDF_PATH}/components/bt/bluedroid/gki/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/encoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/external/sbc/decoder/include", + "${env:IDF_PATH}/components/bt/bluedroid/device/include", + "${env:IDF_PATH}/components/bt/bluedroid/btcore/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/smp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/hid/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/dis/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/battery/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/a2dp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/std/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/blufi/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/profile/esp/include", + "${env:IDF_PATH}/components/bt/bluedroid/btc/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/sys/include", + "${env:IDF_PATH}/components/bt/bluedroid/bta/include", + "${env:IDF_PATH}/components/bt/bluedroid/api/include", + "${env:IDF_PATH}/components/bt/bluedroid/include", + "${env:IDF_PATH}/components/aws_iot/include", + "${env:IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include", + "${env:IDF_PATH}/components/app_trace/include", + "${env:IDF_PATH}/components/app_update/include", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "${env:IDF_PATH}/components/bootloader_support/include", + "${env:IDF_PATH}/components/bootloader_support/include_priv", + "${env:IDF_PATH}/components/bt/include", + "${env:IDF_PATH}/components/coap/port/include", + "${env:IDF_PATH}/components/coap/port/include/coap", + "${env:IDF_PATH}/components/coap/libcoap/include", + "${env:IDF_PATH}/components/coap/libcoap/include/coap", + "${env:IDF_PATH}/components/cxx/include", + "${env:IDF_PATH}/components/driver/include", + "${env:IDF_PATH}/components/driver/include/driver", + "${env:IDF_PATH}/components/esp32/include", + "${env:IDF_PATH}/components/ethernet/include", + "${env:IDF_PATH}/components/expat/include/expat", + "${env:IDF_PATH}/components/expat/port/include", + "${env:IDF_PATH}/components/fatfs/src", + "${env:IDF_PATH}/components/freertos/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/jsmn/include", + "${env:IDF_PATH}/components/json/include", + "${env:IDF_PATH}/components/json/port/include", + "${env:IDF_PATH}/components/json/cJSON", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include", + "${env:IDF_PATH}/components/libsodium/libsodium/src/libsodium/include/sodium", + "${env:IDF_PATH}/components/log/include", + "${env:IDF_PATH}/components/lwip/include/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/apps/ping", + "${env:IDF_PATH}/components/lwip/include/lwip/apps", + "${env:IDF_PATH}/components/lwip/include/lwip/apps/sntp", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip", + "${env:IDF_PATH}/components/lwip/include/lwip/lwip/priv", + "${env:IDF_PATH}/components/lwip/include/lwip/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp", + "${env:IDF_PATH}/components/lwip/include/lwip/netif/ppp/polarssl", + "${env:IDF_PATH}/components/lwip/include/lwip/port", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arch", + "${env:IDF_PATH}/components/lwip/include/lwip/port/arpa", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netif", + "${env:IDF_PATH}/components/lwip/include/lwip/port/netinet", + "${env:IDF_PATH}/components/lwip/include/lwip/posix", + "${env:IDF_PATH}/components/lwip/include/lwip/posix/sys", + "${env:IDF_PATH}/components/mbedtls/port/include", + "${env:IDF_PATH}/components/mbedtls/include", + "${env:IDF_PATH}/components/mbedtls/port/include/mbedtls", + "${env:IDF_PATH}/components/mdns/include", + "${env:IDF_PATH}/components/micro-ecc/micro-ecc", + "${env:IDF_PATH}/components/newlib/include", + "${env:IDF_PATH}/components/newlib/include/sys", + "${env:IDF_PATH}/components/newlib/platform_include", + "${env:IDF_PATH}/components/nghttp/include", + "${env:IDF_PATH}/components/nghttp/port/include", + "${env:IDF_PATH}/components/nvs_flash/include", + "${env:IDF_PATH}/components/openssl/include", + "${env:IDF_PATH}/components/openssl/include/internal", + "${env:IDF_PATH}/components/openssl/include/platform", + "${env:IDF_PATH}/components/openssl/include/openssl", + "${env:IDF_PATH}/components/pthread/include", + "${env:IDF_PATH}/components/sdmmc/include", + "${env:IDF_PATH}/components/spi_flash/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/soc/esp32/include", + "${env:IDF_PATH}/components/soc/include", + "${env:IDF_PATH}/components/soc/esp32/include/soc", + "${env:IDF_PATH}/components/spi_flash", + "${env:IDF_PATH}/components/spiffs/include", + "${env:IDF_PATH}/components/tcpip_adapter/include", + "${env:IDF_PATH}/components/heap/include", + "${env:IDF_PATH}/components/ulp/include", + "${env:IDF_PATH}/components/ulp/include/esp32", + "${env:IDF_PATH}/components/vfs/include", + "${env:IDF_PATH}/components/vfs/include/sys", + "${env:IDF_PATH}/components/wear_levelling/include", + "${env:IDF_PATH}/components/wpa_supplicant/include", + "${env:IDF_PATH}/components/wpa_supplicant/port/include", + "${env:IDF_PATH}/components/wpa_supplicant/include/crypto", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/eap_peer", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/tls", + "${env:IDF_PATH}/components/wpa_supplicant/include/wpa2/utils", + "${env:IDF_PATH}/components/xtensa-debug-module/include", + "C:/Program Files/Espressif/ESP-IDF Tools/toolchain/lib/gcc/xtensa-esp32-elf/5.2.0/include" + + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "${workspaceRoot}/.vscode/browse.vc.db" + }, + "cStandard": "c11", + "cppStandard": "c++17" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/VisualStudioCode/launch.json b/VisualStudioCode/launch.json new file mode 100644 index 00000000..257de4a0 --- /dev/null +++ b/VisualStudioCode/launch.json @@ -0,0 +1,43 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "ESP32 OpenOCD launch", + "type": "cppdbg", + "request": "launch", + "program": "./build/app-template.elf", + "args": [], + "stopAtEntry": true, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/opt/xtensa-esp32-elf/bin/xtensa-esp32-elf-gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "text": "target remote localhost:3333" + }, + { + "text": "monitor reset halt" + }, + { + "text": "flushregs" + }, + { + "text": "thb app_main" + } + ], + "logging": { + "trace": true, + "traceResponse": true, + "engineLogging": true + } + } + ] +} \ No newline at end of file diff --git a/VisualStudioCode/settings.json b/VisualStudioCode/settings.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/VisualStudioCode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/VisualStudioCode/tasks.json b/VisualStudioCode/tasks.json new file mode 100644 index 00000000..d989b0f2 --- /dev/null +++ b/VisualStudioCode/tasks.json @@ -0,0 +1,41 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "menuconfig", + "type": "shell", + "command": "make menuconfig", + "problemMatcher": [] + }, + { + "label": "make", + "type": "shell", + "command": "make -j5", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "flash", + "type": "shell", + "command": "make flash monitor", + "problemMatcher": [] + }, + { + "label": "monitor", + "type": "shell", + "command": "make monitor", + "problemMatcher": [] + }, + { + "label": "clean", + "type": "shell", + "command": "make clean", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/cloud/GCP/JWT/base64url.cpp b/cloud/GCP/JWT/base64url.cpp new file mode 100644 index 00000000..94221181 --- /dev/null +++ b/cloud/GCP/JWT/base64url.cpp @@ -0,0 +1,136 @@ +// https://raw.githubusercontent.com/zhicheng/base64/master/base64.c +/* This is a public domain base64 implementation written by WEI Zhicheng. */ + +#include + +#include "base64url.h" + +/* BASE 64 encode table */ +static const char base64en[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_', +}; + +#define BASE64_PAD '=' + + +#define BASE64DE_FIRST '+' +#define BASE64DE_LAST 'z' +/* ASCII order for BASE 64 decode, -1 in unused character */ +static const signed char base64de[] = { + /* '+', ',', '-', '.', '/', '0', '1', '2', */ + 62, -1, -1, -1, 63, 52, 53, 54, + + /* '3', '4', '5', '6', '7', '8', '9', ':', */ + 55, 56, 57, 58, 59, 60, 61, -1, + + /* ';', '<', '=', '>', '?', '@', 'A', 'B', */ + -1, -1, -1, -1, -1, -1, 0, 1, + + /* 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', */ + 2, 3, 4, 5, 6, 7, 8, 9, + + /* 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', */ + 10, 11, 12, 13, 14, 15, 16, 17, + + /* 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', */ + 18, 19, 20, 21, 22, 23, 24, 25, + + /* '[', '\', ']', '^', '_', '`', 'a', 'b', */ + -1, -1, -1, -1, -1, -1, 26, 27, + + /* 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', */ + 28, 29, 30, 31, 32, 33, 34, 35, + + /* 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', */ + 36, 37, 38, 39, 40, 41, 42, 43, + + /* 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', */ + 44, 45, 46, 47, 48, 49, 50, 51, +}; + +int base64url_encode(const unsigned char *in, unsigned int inlen, char *out) +{ + unsigned int i, j; + + for (i = j = 0; i < inlen; i++) { + int s = i % 3; /* from 6/gcd(6, 8) */ + + switch (s) { + case 0: + out[j++] = base64en[(in[i] >> 2) & 0x3F]; + continue; + case 1: + out[j++] = base64en[((in[i-1] & 0x3) << 4) + ((in[i] >> 4) & 0xF)]; + continue; + case 2: + out[j++] = base64en[((in[i-1] & 0xF) << 2) + ((in[i] >> 6) & 0x3)]; + out[j++] = base64en[in[i] & 0x3F]; + } + } + + /* move back */ + i -= 1; + + /* check the last and add padding */ + + if ((i % 3) == 0) { + out[j++] = base64en[(in[i] & 0x3) << 4]; + //out[j++] = BASE64_PAD; + //out[j++] = BASE64_PAD; + } else if ((i % 3) == 1) { + out[j++] = base64en[(in[i] & 0xF) << 2]; + //out[j++] = BASE64_PAD; + } + + out[j++] = 0; + + return BASE64_OK; +} + +int base64url_decode(const char *in, unsigned int inlen, unsigned char *out) +{ + unsigned int i, j; + + for (i = j = 0; i < inlen; i++) { + int c; + int s = i % 4; /* from 8/gcd(6, 8) */ + + if (in[i] == '=') + return BASE64_OK; + + if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST || + (c = base64de[in[i] - BASE64DE_FIRST]) == -1) + return BASE64_INVALID; + + switch (s) { + case 0: + out[j] = ((unsigned int)c << 2) & 0xFF; + continue; + case 1: + out[j++] += ((unsigned int)c >> 4) & 0x3; + + /* if not last char with padding */ + if (i < (inlen - 3) || in[inlen - 2] != '=') + out[j] = ((unsigned int)c & 0xF) << 4; + continue; + case 2: + out[j++] += ((unsigned int)c >> 2) & 0xF; + + /* if not last char with padding */ + if (i < (inlen - 2) || in[inlen - 1] != '=') + out[j] = ((unsigned int)c & 0x3) << 6; + continue; + case 3: + out[j++] += (unsigned char)c; + } + } + + return BASE64_OK; +} diff --git a/cloud/GCP/JWT/base64url.h b/cloud/GCP/JWT/base64url.h new file mode 100644 index 00000000..7a8c80ca --- /dev/null +++ b/cloud/GCP/JWT/base64url.h @@ -0,0 +1,15 @@ +// https://raw.githubusercontent.com/zhicheng/base64/master/base64.h +#ifndef __BASE64URL_H__ +#define __BASE64URL_H__ + +enum {BASE64_OK = 0, BASE64_INVALID}; + +#define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4) +#define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3) + +int base64url_encode(const unsigned char *in, unsigned int inlen, char *out); + +int base64url_decode(const char *in, unsigned int inlen, unsigned char *out); + + +#endif /* __BASE64URL_H__ */ diff --git a/cloud/GCP/JWT/main.cpp b/cloud/GCP/JWT/main.cpp new file mode 100644 index 00000000..fcf8ec8b --- /dev/null +++ b/cloud/GCP/JWT/main.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passwords.h" +#include "base64url.h" + +// This is an "xxd" file of the PEM of the private key. +#include "device1_private_pem.h" + + +extern "C" { + void app_main(); +} + +/** + * Return a string representation of an mbedtls error code + */ +static char* mbedtlsError(int errnum) { + static char buffer[200]; + mbedtls_strerror(errnum, buffer, sizeof(buffer)); + return buffer; +} // mbedtlsError + + +/** + * Create a JWT token for GCP. + * For full details, perform a Google search on JWT. However, in summary, we build two strings. One that represents the + * header and one that represents the payload. Both are JSON and are as described in the GCP and JWT documentation. Next + * we base64url encode both strings. Note that is distinct from normal/simple base64 encoding. Once we have a string for + * the base64url encoding of both header and payload, we concatenate both strings together separated by a ".". This resulting + * string is then signed using RSASSA which basically produces an SHA256 message digest that is then signed. The resulting + * binary is then itself converted into base64url and concatenated with the previously built base64url combined header and + * payload and that is our resulting JWT token. + * @param projectId The GCP project. + * @param privateKey The PEM or DER of the private key. + * @param privateKeySize The size in bytes of the private key. + * @returns A JWT token for transmission to GCP. + */ +char* createGCPJWT(const char* projectId, uint8_t* privateKey, size_t privateKeySize) { + char base64Header[100]; + const char header[] = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"; + base64url_encode( + (unsigned char *)header, // Data to encode. + strlen(header), // Length of data to encode. + base64Header); // Base64 encoded data. + + time_t now; + time(&now); + uint32_t iat = now; // Set the time now. + uint32_t exp = iat + 60*60; // Set the expiry time. + + char payload[100]; + sprintf(payload, "{\"iat\":%d,\"exp\":%d,\"aud\":\"%s\"}", iat, exp, projectId); + + char base64Payload[100]; + base64url_encode( + (unsigned char *)payload, // Data to encode. + strlen(payload), // Length of data to encode. + base64Payload); // Base64 encoded data. + + uint8_t headerAndPayload[800]; + sprintf((char*)headerAndPayload, "%s.%s", base64Header, base64Payload); + + // At this point we have created the header and payload parts, converted both to base64 and concatenated them + // together as a single string. Now we need to sign them using RSASSA + + mbedtls_pk_context pk_context; + mbedtls_pk_init(&pk_context); + int rc = mbedtls_pk_parse_key(&pk_context, privateKey, privateKeySize, NULL, 0); + if (rc != 0) { + printf("Failed to mbedtls_pk_parse_key: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + uint8_t oBuf[5000]; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + + const char* pers="MyEntropy"; + + mbedtls_ctr_drbg_seed( + &ctr_drbg, + mbedtls_entropy_func, + &entropy, + (const unsigned char*)pers, + strlen(pers)); + + + uint8_t digest[32]; + rc = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), headerAndPayload, strlen((char*)headerAndPayload), digest); + if (rc != 0) { + printf("Failed to mbedtls_md: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + size_t retSize; + rc = mbedtls_pk_sign(&pk_context, MBEDTLS_MD_SHA256, digest, sizeof(digest), oBuf, &retSize, mbedtls_ctr_drbg_random, &ctr_drbg); + if (rc != 0) { + printf("Failed to mbedtls_pk_sign: %d (-0x%x): %s\n", rc, -rc, mbedtlsError(rc)); + return nullptr; + } + + + char base64Signature[600]; + base64url_encode((unsigned char *)oBuf, retSize, base64Signature); + + char* retData = (char*)malloc(strlen((char*)headerAndPayload) + 1 + strlen((char*)base64Signature) + 1); + + sprintf(retData, "%s.%s", headerAndPayload, base64Signature); + + mbedtls_pk_free(&pk_context); + return retData; +} + +void run(void *) { + printf("Task starting!\n"); + const char* projectId = "test-214415"; + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "time-a-g.nist.gov"); + sntp_init(); + // https://www.epochconverter.com/ + time_t now = 0; + time(&now); + while(now < 5000) { + vTaskDelay(1000 * portTICK_RATE_MS); + time(&now); + } + + char* jwt = createGCPJWT(projectId, device1_private_pem, device1_private_pem_len); + if (jwt != nullptr) { + printf("JWT: %s\n", jwt); + free(jwt); + } + vTaskDelete(nullptr); +} + +esp_err_t event_handler(void *ctx, system_event_t *event) +{ + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { + printf("Our IP address is " IPSTR "\n", + IP2STR(&event->event_info.got_ip.ip_info.ip)); + printf("We have now connected to a station and can do things...\n"); + xTaskCreate(run, "run", 16000, nullptr, 0, nullptr); + + } + + if (event->event_id == SYSTEM_EVENT_STA_START) { + ESP_ERROR_CHECK(esp_wifi_connect()); + } + return ESP_OK; +} + +void app_main(void) +{ + printf("Starting\n"); + nvs_flash_init(); + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + wifi_config_t sta_config; + memset(&sta_config, 0, sizeof(sta_config)); + strcpy((char*)sta_config.sta.ssid, SSID); + strcpy((char*)sta_config.sta.password, SSID_PASSWORD); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + diff --git a/component.mk b/component.mk new file mode 100644 index 00000000..03383fab --- /dev/null +++ b/component.mk @@ -0,0 +1,35 @@ +c := cpp_utils +COMPONENT_EXTRA_INCLUDES := $(realpath $c) +COMPONENT_ADD_INCLUDEDIRS := $c +COMPONENT_SRCDIRS := $c + +$(call compile_only_if,$(CONFIG_NKOLBAN), \ +$c/Task.o \ +$c/FreeRTOS.o \ +$c/GeneralUtils.o \ +) + +$(call compile_only_if,$(CONFIG_NKOLBAN_BLE), \ +$c/BLEAddress.o \ +$c/BLEAdvertisedDevice.o \ +$c/BLEAdvertising.o \ +$c/BLECharacteristic.o \ +$c/BLECharacteristicCallbacks.o \ +$c/BLECharacteristicMap.o \ +$c/BLEClient.o \ +$c/BLEDescriptor.o \ +$c/BLEDescriptorMap.o \ +$c/BLEDevice.o \ +$c/BLERemoteCharacteristic.o \ +$c/BLERemoteDescriptor.o \ +$c/BLERemoteService.o \ +$c/BLEScan.o \ +$c/BLEServer.o \ +$c/BLEService.o \ +$c/BLEServiceMap.o \ +$c/BLEUUID.o \ +$c/BLEUtils.o \ +$c/BLEValue.o \ +) + +$(call compile_only_if,$(CONFIG_NKOLBAN_BLE2902),$c/BLE2902.o) diff --git a/console/main.cpp b/console/main.cpp new file mode 100644 index 00000000..f5715f5d --- /dev/null +++ b/console/main.cpp @@ -0,0 +1,14 @@ +extern "C" { + void app_main(void); +} + +extern void test_linenoise(); +extern void test_argtable(); +extern void test_console(); + +void app_main(void) +{ + //test_linenoise(); + //test_argtable(); + test_console(); +} diff --git a/console/test_argtable.cpp b/console/test_argtable.cpp new file mode 100644 index 00000000..160b728c --- /dev/null +++ b/console/test_argtable.cpp @@ -0,0 +1,69 @@ +/** + * Test of argtable + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +void test_argtable(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + struct arg_lit* help = arg_lit0("h", "help", "Generate some help!"); + struct arg_lit* test = arg_lit1("t", "test", "Generate some help!"); + struct arg_end* end = arg_end(20); + + void *argtable[] = { + help, + test, + end + }; + + char *argv[10]; + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + size_t argc = esp_console_split_argv(line, argv, 10); + printf("Parsed to %d argc count\n", argc); + for (int i=0; i0) { + printf("Number of errors: %d\n", numErrors); + arg_print_errors(stdout, end, "myprog"); + arg_print_syntaxv(stdout, argtable, "Here:"); + } + else { + if (help->count > 0) { + printf("Found help!\n"); + } + } + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/console/test_console.cpp b/console/test_console.cpp new file mode 100644 index 00000000..20c240ad --- /dev/null +++ b/console/test_console.cpp @@ -0,0 +1,95 @@ +/** + * Test of console + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + + +struct arg_lit* show_arg_show = arg_lit0("s", "show", "generate show"); +struct arg_end* show_arg_end = arg_end(10); +void *show_argtable[] = { + show_arg_show, + show_arg_end +}; + +int runShow(int argc, char *argv[]) { + printf("Found show!\n"); + return 0; +} + +struct arg_str* greet_arg_name = arg_str1("n", "name", "", "generate help"); +struct arg_end* greet_arg_end = arg_end(10); +void *greet_argtable[] = { + greet_arg_name, + greet_arg_end +}; + +int runGreet(int argc, char *argv[]) { + printf("Found Greet!\n"); + int numErrors = arg_parse(argc, argv, greet_argtable); + if (numErrors > 0) { + arg_print_errors(stdout, greet_arg_end, "greet"); + } else { + printf("Hello %s\n", greet_arg_name->sval[0]); + } + return 0; +} + +void test_console(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + esp_console_config_t consoleConfig; + consoleConfig.max_cmdline_args = 5; + consoleConfig.max_cmdline_length = 100; + + esp_console_init(&consoleConfig); + esp_console_register_help_command(); + + esp_console_cmd_t consoleCmd; + consoleCmd.command = "show"; + consoleCmd.func = runShow; + consoleCmd.help = "Show something"; + consoleCmd.argtable = show_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "greet"; + consoleCmd.func = runGreet; + consoleCmd.help = "Greet someone"; + consoleCmd.argtable = greet_argtable; + esp_console_cmd_register(&consoleCmd); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + int ret; + esp_console_run(line, &ret); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/console/test_linenoise.cpp b/console/test_linenoise.cpp new file mode 100644 index 00000000..b2d36cf4 --- /dev/null +++ b/console/test_linenoise.cpp @@ -0,0 +1,40 @@ +/** + * Test of linenoise + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +void test_linenoise(void) +{ + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + + linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + printf("Got: %s\n", line); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/cpp_utils/.gitignore b/cpp_utils/.gitignore index 190415da..6e684992 100644 --- a/cpp_utils/.gitignore +++ b/cpp_utils/.gitignore @@ -1,2 +1 @@ /docs/ -/Arduino/ diff --git a/cpp_utils/AWS.cpp b/cpp_utils/AWS.cpp index 561162c3..7687be21 100644 --- a/cpp_utils/AWS.cpp +++ b/cpp_utils/AWS.cpp @@ -19,6 +19,129 @@ AWS::AWS() { AWS::~AWS() { } +/** + * Convert an AWS IoT error code to a string representation. + * @param err The error code to be mapped. + * @return A string representation of the error code. + */ +/* static */ std::string AWS::errorToString(IoT_Error_t err) { + switch(err) { + case NETWORK_PHYSICAL_LAYER_CONNECTED : + return "NETWORK_PHYSICAL_LAYER_CONNECTED"; + case NETWORK_MANUALLY_DISCONNECTED : + return "NETWORK_MANUALLY_DISCONNECTED"; + case NETWORK_ATTEMPTING_RECONNECT: + return "NETWORK_ATTEMPTING_RECONNECT"; + case NETWORK_RECONNECTED: + return "NETWORK_RECONNECTED"; + case MQTT_NOTHING_TO_READ : + return "MQTT_NOTHING_TO_READ"; + case MQTT_CONNACK_CONNECTION_ACCEPTED: + return "MQTT_CONNACK_CONNECTION_ACCEPTED"; + case SUCCESS : + return "SUCCESS"; + case FAILURE: + return "FAILURE"; + case NULL_VALUE_ERROR : + return "NULL_VALUE_ERROR"; + case TCP_CONNECTION_ERROR : + return "TCP_CONNECTION_ERROR"; + case SSL_CONNECTION_ERROR: + return "SSL_CONNECTION_ERROR"; + case TCP_SETUP_ERROR : + return "TCP_SETUP_ERROR"; + case NETWORK_SSL_CONNECT_TIMEOUT_ERROR : + return "NETWORK_SSL_CONNECT_TIMEOUT_ERROR"; + case NETWORK_SSL_WRITE_ERROR : + return "NETWORK_SSL_WRITE_ERROR"; + case NETWORK_SSL_INIT_ERROR : + return "NETWORK_SSL_INIT_ERROR"; + case NETWORK_SSL_CERT_ERROR : + return "NETWORK_SSL_CERT_ERROR"; + case NETWORK_SSL_WRITE_TIMEOUT_ERROR : + return "NETWORK_SSL_WRITE_TIMEOUT_ERROR"; + case NETWORK_SSL_READ_TIMEOUT_ERROR : + return "NETWORK_SSL_READ_TIMEOUT_ERROR"; + case NETWORK_SSL_READ_ERROR : + return "NETWORK_SSL_READ_ERROR"; + case NETWORK_DISCONNECTED_ERROR : + return "NETWORK_DISCONNECTED_ERROR"; + case NETWORK_RECONNECT_TIMED_OUT_ERROR: + return "NETWORK_RECONNECT_TIMED_OUT_ERROR"; + case NETWORK_ALREADY_CONNECTED_ERROR : + return "NETWORK_ALREADY_CONNECTED_ERROR"; + case NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED : + return "NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED"; + case NETWORK_SSL_UNKNOWN_ERROR : + return "NETWORK_SSL_UNKNOWN_ERROR"; + case NETWORK_PHYSICAL_LAYER_DISCONNECTED : + return "NETWORK_PHYSICAL_LAYER_DISCONNECTED"; + case NETWORK_X509_ROOT_CRT_PARSE_ERROR : + return "NETWORK_X509_ROOT_CRT_PARSE_ERROR"; + case NETWORK_X509_DEVICE_CRT_PARSE_ERROR : + return "NETWORK_X509_DEVICE_CRT_PARSE_ERROR"; + case NETWORK_PK_PRIVATE_KEY_PARSE_ERROR : + return "NETWORK_PK_PRIVATE_KEY_PARSE_ERROR"; + case NETWORK_ERR_NET_SOCKET_FAILED : + return "NETWORK_ERR_NET_SOCKET_FAILED"; + case NETWORK_ERR_NET_UNKNOWN_HOST : + return "NETWORK_ERR_NET_UNKNOWN_HOST"; + case NETWORK_ERR_NET_CONNECT_FAILED : + return "NETWORK_ERR_NET_CONNECT_FAILED"; + case NETWORK_SSL_NOTHING_TO_READ : + return "NETWORK_SSL_NOTHING_TO_READ"; + case MQTT_CONNECTION_ERROR : + return "MQTT_CONNECTION_ERROR"; + case MQTT_CONNECT_TIMEOUT_ERROR : + return "MQTT_CONNECT_TIMEOUT_ERROR"; + case MQTT_REQUEST_TIMEOUT_ERROR: + return "MQTT_REQUEST_TIMEOUT_ERROR"; + case MQTT_UNEXPECTED_CLIENT_STATE_ERROR : + return "MQTT_UNEXPECTED_CLIENT_STATE_ERROR"; + case MQTT_CLIENT_NOT_IDLE_ERROR : + return "MQTT_CLIENT_NOT_IDLE_ERROR"; + case MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR : + return "MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR"; + case MQTT_RX_BUFFER_TOO_SHORT_ERROR : + return "MQTT_RX_BUFFER_TOO_SHORT_ERROR"; + case MQTT_TX_BUFFER_TOO_SHORT_ERROR : + return "MQTT_TX_BUFFER_TOO_SHORT_ERROR"; + case MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR : + return "MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR"; + case MQTT_DECODE_REMAINING_LENGTH_ERROR : + return "MQTT_DECODE_REMAINING_LENGTH_ERROR"; + case MQTT_CONNACK_UNKNOWN_ERROR : + return "MQTT_CONNACK_UNKNOWN_ERROR"; + case MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR : + return "MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR"; + case MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR: + return "MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR"; + case MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR : + return "MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR"; + case MQTT_CONNACK_BAD_USERDATA_ERROR: + return "MQTT_CONNACK_BAD_USERDATA_ERROR"; + case MQTT_CONNACK_NOT_AUTHORIZED_ERROR : + return "MQTT_CONNACK_NOT_AUTHORIZED_ERROR"; + case JSON_PARSE_ERROR : + return "JSON_PARSE_ERROR"; + case SHADOW_WAIT_FOR_PUBLISH : + return "SHADOW_WAIT_FOR_PUBLISH"; + case SHADOW_JSON_BUFFER_TRUNCATED : + return "SHADOW_JSON_BUFFER_TRUNCATED"; + case SHADOW_JSON_ERROR : + return "SHADOW_JSON_ERROR"; + case MUTEX_INIT_ERROR : + return "MUTEX_INIT_ERROR"; + case MUTEX_LOCK_ERROR: + return "MUTEX_LOCK_ERROR"; + case MUTEX_UNLOCK_ERROR : + return "MUTEX_UNLOCK_ERROR"; + case MUTEX_DESTROY_ERROR : + return "MUTEX_DESTROY_ERROR"; + default: + return "Unknown error!"; + } +} // AWS#errorToString /** * @brief Connect to the AWS IoT service. diff --git a/cpp_utils/AWS.h b/cpp_utils/AWS.h index 5d2c891c..abbd0613 100644 --- a/cpp_utils/AWS.h +++ b/cpp_utils/AWS.h @@ -24,6 +24,7 @@ class AWS { void connect(std::string clientId); void disconnect(); + static std::string errorToString(IoT_Error_t err); // Convert an AWS IoT error code to a string representation. void init(std::string host=CONFIG_AWS_IOT_MQTT_HOST, uint16_t port=CONFIG_AWS_IOT_MQTT_PORT); void publish(std::string topic, std::string payload, QoS qos = QOS0); void subscribe(std::string topic); diff --git a/cpp_utils/Apa102.cpp b/cpp_utils/Apa102.cpp new file mode 100644 index 00000000..c35c9150 --- /dev/null +++ b/cpp_utils/Apa102.cpp @@ -0,0 +1,46 @@ +/* + * Apa102.cpp + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#include "Apa102.h" + +Apa102::Apa102() { +} + +Apa102::~Apa102() { + +} + + +void Apa102::init() { + mySPI.init(); +} + +/** + * @brief Show the pixels on an APA102 device. + * The pixels that have been set are pushed to the APA102 devices. + */ +void Apa102::show() { + // We follow the data sheet for the APA102. To signify a new stream of data + // we send 32bits of 0 value. Following that are 4 bytes of color data. The + // data is 0b111 nnnnn where `nnnnn` is the brightness of the pixels. + // The following data is 8 bits for blue, 8 bits for green and 8 bits for red. + + // Send APA102 start. + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + mySPI.transferByte(0x0); + + double brigthnessScale = getBrightness() / 100.0; + // Loop over all the pixels in the pixels array to set the colors. + for (uint16_t i = 0; i < m_pixelCount; i++) { + mySPI.transferByte(0xff); // Maximum brightness + mySPI.transferByte(m_pixels[i].blue * brigthnessScale); + mySPI.transferByte(m_pixels[i].green * brigthnessScale); + mySPI.transferByte(m_pixels[i].red * brigthnessScale); + } // End loop over all the pixels. +} // show diff --git a/cpp_utils/Apa102.h b/cpp_utils/Apa102.h new file mode 100644 index 00000000..19808e35 --- /dev/null +++ b/cpp_utils/Apa102.h @@ -0,0 +1,24 @@ +/* + * Apa102.h + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_APA102_H_ +#define COMPONENTS_CPP_UTILS_APA102_H_ + +#include "SmartLED.h" +#include "SPI.h" + +class Apa102: public SmartLED { +public: + Apa102(); + virtual ~Apa102(); + void init(); + void show(); +private: + SPI mySPI; +}; + +#endif /* COMPONENTS_CPP_UTILS_APA102_H_ */ diff --git a/cpp_utils/ArduinoBLE.md b/cpp_utils/ArduinoBLE.md index c37ed58c..fa5bda38 100644 --- a/cpp_utils/ArduinoBLE.md +++ b/cpp_utils/ArduinoBLE.md @@ -1,15 +1,17 @@ # Arduino BLE Support -As part of this Github project, we provide libraries for Bluetooth Low Energy (BLE) for the ESP32 Arduino environment. Support for this capability is still in the process of being cooked (as of August 2017). As such you should not build product based on these functions as changes to the API and implementation are extremely likely over the next couple of months. +As part of this Github project, we provide libraries for Bluetooth Low Energy (BLE) for the ESP32 Arduino environment. Support for this capability is still in the process of being cooked (as of November 2017). As such you should not build product based on these functions as changes to the API and implementation are extremely likely over the next couple of months. That said, we now have the ability to produce a driver you can use for testing. This will give you early access to the function while give those who choose to build and maintain the code early feedback on what works, what doesn't and what can be improved from a usage perspective. When complete, the BLE support for ESP32 Arduino will be available as a single ZIP file. The file will be called **ESP32_BLE.zip**. It is this file that will be able to be imported into the Arduino IDE from the `Sketch -> Include Library -> Add .ZIP library`. When initial development of the library has been completed, this ZIP will be placed under some form of release control so that an ordinary Arduino IDE user can simply download this as a unit and install. -However, today, we are choosing **not** to distribute the BLE support as a downloadable library. The reason for this is that we believe that the code base for the BLE support is too fluid and it is being actively worked upon. Each day there will be changes and that means that for any given instance of the ZIP you get, if it is more than a day old, it is likely out of date. +Update: As of 2017-11, the BLE support has been included with the Arduino ESP32 base package. -Instead of giving you a fish (a new ESP32_BLE.zip) we are going to teach you to fish so that you yourself can build the very latest ESP32_BLE.zip yourself. Hopefully you will find this quite easy to accomplish. +A build of the BLE support for Arduino can be found through the Arduino IDE. Visit `Sketch -> Include Library -> Manage Libraries`. In the library filter, enter "esp32 ble arduino". The search will narrow and you should see "ESP32 BLE Arduino" available for installation or upgrade. -Here is the recipe. + + +Should you wish to build your own instance of the ZIP from the source, here is the recipe. 1. Create a new directory called `build` 2. Enter that directory and run `git clone https://github.com/nkolban/esp32-snippets.git` @@ -20,17 +22,28 @@ Here is the recipe. And here you will find the `ESP32_BLE.zip` that is build from the latest source. You can then install this into your Arduino IDE environment are you are ready to go. -## Modifying the Arduino environment for BLE -The Arduino environment supplied for the ESP32 is **not** sufficient for full BLE support as supplied. A modification must be made. +## Installing a new version +If you have previously installed a version of the Arduino BLE Support and need to install a new one, follow the steps above to build yourself a new instance of the `ESP32_BLE.zip` that is ready for installation. I recommend removing the old one before installing the new one. To remove the old one, find the directory where the Arduino IDE stores your libraries (on Linux this is commonly `$HOME/Arduino`). In there you will find a directory called `libraries` and, if you have previously installed the BLE support, you will find a directory called `ESP32_BLE`. Remove that directory. + +## Replacing the version that comes with Arduino-ESP32 +From October 2017 onwards, a build of the BLE libraries is supplied with the Arduino-ESP32 distribition which means that you should just be able to use the function without performing any additional steps. The intent is to keep the BLE libraries in this project completely in synch with the Arduino-ESP32 distribution. However, there may be times when a bug fix is needed which you may wish to try before an official distribution in Arduino-ESP32. That should be extremely rare. However, just in case it is needed, here is the recipe: + +1. Go to `/hardware/espressif/esp32/libraries` +2. Delete the directory called `BLE` +3. Go to your `/libraries` folder +4. Extract the `ESP32_BLE.zip` file there + +## Switching on debugging +The BLE support contains extensive internal diagnostics which can be switched on through the `Tools > Core Debug Level` setting: + +![](https://github.com/nkolban/esp32-snippets/blob/master/Documentation/images/arduino_debug.png) -Here are the instructions -1. Find the directory where your Arduino IDE is installed -2. Navigate relative from there to `/hardware/espressif/esp32/tools/sdk/include/config` -3. Edit the file called `sdkconfig.h` -4. Find the line which reads `#define CONFIG_BTC_TASK_STACK_SIZE 2048` and change to `#define CONFIG_BTC_TASK_STACK_SIZE 8000` +## Decoding an exception stack trace +While using the BLE C++ classes there is always the unfortunate possibility that something will go wrong and your application crash. Fortunately, this results in some debug information being logged to the console. This is known as a *stack trace*. Included in the stack trace are a sequence of hexadecimal numbers known as the *back trace* which are the list of addresses of functions that were executed just before the crash was detected. If we could decode these we would have a lot of great information that could be used to aid in the resolution. Fortunately there is a fantastic project that makes decoding this information very easy indeed. -You are now ready to build (re-build) your BLE based applications. +This project can be found here: -A request has been made to he owners of the Arduino on ESP32 project to see if we can't make this the default or otherwise supply an easier story for modification see [arduino-esp32 issue 567](https://github.com/espressif/arduino-esp32/issues/567). +https://github.com/me-no-dev/EspExceptionDecoder +If you start to encounter crashes in your BLE C++ applications and wish to report these through Github issues, please help us to help you by installing this tool int your Arduino IDE and including the decoded information in the issue report. diff --git a/cpp_utils/Arduino_ESP32_BLE.library.properties b/cpp_utils/Arduino_ESP32_BLE.library.properties index e8fe2808..fdb01e2f 100644 --- a/cpp_utils/Arduino_ESP32_BLE.library.properties +++ b/cpp_utils/Arduino_ESP32_BLE.library.properties @@ -1,10 +1,10 @@ -name=ESP32_BLE -version=1.0 +name=ESP32 BLE Arduino +version=0.4.3 author=Neil Kolban maintainer=Neil Kolban sentence=BLE functions for ESP32 -paragraph=BLE functions for ESP32 +paragraph=This library provides an implementation Bluetooth Low Energy support for the ESP32 using the Arduino platform. category=Communication -url=http://example.com/ +url=https://github.com/nkolban/ESP32_BLE_Arduino architectures=esp32 -includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h \ No newline at end of file +includes=BLE.h, BLEUtils.h, BLEScan.h, BLEAdvertisedDevice.h \ No newline at end of file diff --git a/cpp_utils/BLE2902.cpp b/cpp_utils/BLE2902.cpp index 8984da81..23d9c77c 100644 --- a/cpp_utils/BLE2902.cpp +++ b/cpp_utils/BLE2902.cpp @@ -15,7 +15,7 @@ #include "BLE2902.h" BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) { - uint8_t data[2] = {0,0}; + uint8_t data[2] = { 0, 0 }; setValue(data, 2); } // BLE2902 @@ -44,11 +44,8 @@ bool BLE2902::getIndications() { */ void BLE2902::setIndications(bool flag) { uint8_t *pValue = getValue(); - if (flag) { - pValue[0] |= 1<<1; - } else { - pValue[0] &= ~(1<<1); - } + if (flag) pValue[0] |= 1 << 1; + else pValue[0] &= ~(1 << 1); } // setIndications @@ -58,12 +55,8 @@ void BLE2902::setIndications(bool flag) { */ void BLE2902::setNotifications(bool flag) { uint8_t *pValue = getValue(); - if (flag) { - pValue[0] |= 1<<0; - } else { - pValue[0] &= ~(1<<0); - } + if (flag) pValue[0] |= 1 << 0; + else pValue[0] &= ~(1 << 0); } // setNotifications - #endif diff --git a/cpp_utils/BLE2904.cpp b/cpp_utils/BLE2904.cpp new file mode 100644 index 00000000..02252a1d --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLE2904.h b/cpp_utils/BLE2904.h new file mode 100644 index 00000000..cb337e22 --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEAddress.cpp b/cpp_utils/BLEAddress.cpp index d2f7f8b0..d6883340 100644 --- a/cpp_utils/BLEAddress.cpp +++ b/cpp_utils/BLEAddress.cpp @@ -13,6 +13,9 @@ #include #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif /** @@ -36,17 +39,16 @@ BLEAddress::BLEAddress(esp_bd_addr_t address) { * @param [in] stringAddress The hex representation of the address. */ BLEAddress::BLEAddress(std::string stringAddress) { - if (stringAddress.length() != 17) { - return; - } + 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]; + 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 @@ -82,12 +84,12 @@ esp_bd_addr_t *BLEAddress::getNative() { */ 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]; + 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/cpp_utils/BLEAdvertisedDevice.cpp b/cpp_utils/BLEAdvertisedDevice.cpp index 97ec1e33..d3e60bdd 100644 --- a/cpp_utils/BLEAdvertisedDevice.cpp +++ b/cpp_utils/BLEAdvertisedDevice.cpp @@ -13,11 +13,16 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #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; @@ -26,6 +31,7 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { m_manufacturerData = ""; m_name = ""; m_rssi = -9999; + m_serviceData = ""; m_txPower = 0; m_pScan = nullptr; @@ -33,6 +39,7 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { m_haveManufacturerData = false; m_haveName = false; m_haveRSSI = false; + m_haveServiceData = false; m_haveServiceUUID = false; m_haveTXPower = false; @@ -60,9 +67,9 @@ BLEAddress BLEAdvertisedDevice::getAddress() { * * @return The appearance of the advertised device. */ -uint16_t BLEAdvertisedDevice::getApperance() { +uint16_t BLEAdvertisedDevice::getAppearance() { return m_appearance; -} +} // getAppearance /** @@ -71,7 +78,7 @@ uint16_t BLEAdvertisedDevice::getApperance() { */ std::string BLEAdvertisedDevice::getManufacturerData() { return m_manufacturerData; -} +} // getManufacturerData /** @@ -101,14 +108,42 @@ BLEScan* BLEAdvertisedDevice::getScan() { } // 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() { - return m_serviceUUID; +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. @@ -118,6 +153,8 @@ 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. @@ -154,6 +191,15 @@ bool BLEAdvertisedDevice::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. @@ -184,18 +230,20 @@ bool BLEAdvertisedDevice::haveTXPower() { * * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile */ -void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { +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 + 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 indicate that we have reached the end. + if (length != 0) { // A length of 0 indicates that we have reached the end. ad_type = *payload; payload++; length--; @@ -205,15 +253,13 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); free(pHex); - - switch(ad_type) { - case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09 + 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 + case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A setTXPower(*payload); break; } // ESP_BLE_AD_TYPE_TX_PWR @@ -223,28 +269,24 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { break; } // ESP_BLE_AD_TYPE_APPEARANCE - case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01 + 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: { // Adv Data Type: 0x03 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); - break; - } // ESP_BLE_AD_TYPE_16SRV_CMPL - - case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); + 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: { // Adv Data Type: 0x05 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); - break; - } // ESP_BLE_AD_TYPE_32SRV_CMPL - - case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04 - setServiceUUID(BLEUUID(*reinterpret_cast(payload))); + 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 @@ -264,8 +306,47 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { 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"); + ESP_LOGD(LOG_TAG, "Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); break; } } // switch @@ -273,9 +354,9 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) { } // Length <> 0 - if (sizeConsumed >=31 || length == 0) { + if (sizeConsumed >= total_len) finished = true; - } + } // !finished } // parseAdvertisement @@ -316,7 +397,7 @@ void BLEAdvertisedDevice::setAppearance(uint16_t appearance) { 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()); + 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 @@ -329,7 +410,7 @@ void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) { void BLEAdvertisedDevice::setName(std::string name) { m_name = name; m_haveName = true; - ESP_LOGD(LOG_TAG, "- name: %s", m_name.c_str()); + ESP_LOGD(LOG_TAG, "- setName(): name: %s", m_name.c_str()); } // setName @@ -340,7 +421,7 @@ void BLEAdvertisedDevice::setName(std::string name) { void BLEAdvertisedDevice::setRSSI(int rssi) { m_rssi = rssi; m_haveRSSI = true; - ESP_LOGD(LOG_TAG, "- rssi: %d", m_rssi); + ESP_LOGD(LOG_TAG, "- setRSSI(): rssi: %d", m_rssi); } // setRSSI @@ -352,23 +433,45 @@ 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)); -} // setRSSI +} // setServiceUUID + /** * @brief Set the Service UUID for this device. * @param [in] serviceUUID The discovered serviceUUID */ void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) { - m_serviceUUID = serviceUUID; + m_serviceUUIDs.push_back(serviceUUID); m_haveServiceUUID = true; - ESP_LOGD(LOG_TAG, "- serviceUUID: %s", serviceUUID.toString().c_str()); -} // setRSSI + 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 /** @@ -390,7 +493,7 @@ std::string BLEAdvertisedDevice::toString() { std::stringstream ss; ss << "Name: " << getName() << ", Address: " << getAddress().toString(); if (haveAppearance()) { - ss << ", appearance: " << getApperance(); + ss << ", appearance: " << getAppearance(); } if (haveManufacturerData()) { char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length()); @@ -406,5 +509,24 @@ std::string BLEAdvertisedDevice::toString() { 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; +} + +void BLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice dev) {} +void BLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice* dev) {} + #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertisedDevice.h b/cpp_utils/BLEAdvertisedDevice.h index 2fb26522..cd206d4a 100644 --- a/cpp_utils/BLEAdvertisedDevice.h +++ b/cpp_utils/BLEAdvertisedDevice.h @@ -30,18 +30,27 @@ class BLEAdvertisedDevice { BLEAdvertisedDevice(); BLEAddress getAddress(); - uint16_t getApperance(); + 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(); @@ -50,7 +59,7 @@ class BLEAdvertisedDevice { private: friend class BLEScan; - void parseAdvertisement(uint8_t* payload); + void parseAdvertisement(uint8_t* payload, size_t total_len=62); void setAddress(BLEAddress address); void setAdFlag(uint8_t adFlag); void setAdvertizementResult(uint8_t* payload); @@ -59,6 +68,8 @@ class BLEAdvertisedDevice { 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); @@ -67,6 +78,7 @@ class BLEAdvertisedDevice { bool m_haveManufacturerData; bool m_haveName; bool m_haveRSSI; + bool m_haveServiceData; bool m_haveServiceUUID; bool m_haveTXPower; @@ -79,8 +91,13 @@ class BLEAdvertisedDevice { std::string m_name; BLEScan* m_pScan; int m_rssi; - BLEUUID m_serviceUUID; + std::vector m_serviceUUIDs; int8_t m_txPower; + std::string m_serviceData; + BLEUUID m_serviceDataUUID; + uint8_t* m_payload = nullptr; + size_t m_payloadLength = 0; + esp_ble_addr_type_t m_addressType; }; /** @@ -99,7 +116,8 @@ class BLEAdvertisedDeviceCallbacks { * 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; + virtual void onResult(BLEAdvertisedDevice advertisedDevice); + virtual void onResult(BLEAdvertisedDevice* advertisedDevice); }; #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEAdvertising.cpp b/cpp_utils/BLEAdvertising.cpp index ad076ddd..230d77cb 100644 --- a/cpp_utils/BLEAdvertising.cpp +++ b/cpp_utils/BLEAdvertising.cpp @@ -4,16 +4,33 @@ * 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 #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 + /** @@ -41,9 +58,31 @@ BLEAdvertising::BLEAdvertising() { 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: @@ -55,52 +94,88 @@ 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 service UUID. - * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the - * ESP-IDF advertising functions. In this method, we see two fields within that structure - * namely service_uuid_len and p_service_uuid to be the information supplied in the passed - * in service uuid. - * @param [in] uuid The UUID of the service. - * @return N/A. + * @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::setServiceUUID(const char* serviceUUID) { - return setServiceUUID(BLEUUID(serviceUUID)); -} +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 service UUID. - * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the - * ESP-IDF advertising functions. In this method, we see two fields within that structure - * namely service_uuid_len and p_service_uuid to be the information supplied in the passed - * in service uuid. - * @param [in] uuid The UUID of the service. - * @return N/A. + * @brief Set the advertisement data that is to be published in a regular advertisement. + * @param [in] advertisementData The data to be advertised. */ -void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { - ESP_LOGD(LOG_TAG, ">> setServiceUUID - %s", serviceUUID.toString().c_str()); - m_serviceUUID = serviceUUID; // Save the new service UUID - esp_bt_uuid_t* espUUID = m_serviceUUID.getNative(); - switch(espUUID->len) { - case ESP_UUID_LEN_16: { - m_advData.service_uuid_len = 2; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid16); - break; - } - case ESP_UUID_LEN_32: { - m_advData.service_uuid_len = 4; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid32); - break; - } - case ESP_UUID_LEN_128: { - m_advData.service_uuid_len = 16; - m_advData.p_service_uuid = reinterpret_cast(&espUUID->uuid.uuid128); - break; - } - } // switch - ESP_LOGD(LOG_TAG, "<< setServiceUUID"); -} // setServiceUUID +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. @@ -108,24 +183,58 @@ void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) { * @return N/A. */ void BLEAdvertising::start() { - ESP_LOGD(LOG_TAG, ">> 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) { - uint8_t hexData[16*2+1]; - BLEUtils::buildHexData(hexData, m_advData.p_service_uuid, m_advData.service_uuid_len); - ESP_LOGD(LOG_TAG, " - Service: service_uuid_len=%d, p_service_uuid=0x%x (data=%s)", - m_advData.service_uuid_len, - (uint32_t)m_advData.p_service_uuid, - (m_advData.service_uuid_len > 0?(char *)hexData:"N/A") - ); - } // We have a service to advertise - - - // Set the configuration for advertising. - esp_err_t 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; + delete[] m_advData.p_service_uuid; + m_advData.p_service_uuid = nullptr; } // Start advertising. @@ -152,4 +261,245 @@ void BLEAdvertising::stop() { } 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/cpp_utils/BLEAdvertising.h b/cpp_utils/BLEAdvertising.h index 2d0b51c2..3128b50f 100644 --- a/cpp_utils/BLEAdvertising.h +++ b/cpp_utils/BLEAdvertising.h @@ -11,6 +11,33 @@ #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. @@ -20,15 +47,32 @@ class BLEAdvertising { public: BLEAdvertising(); + void addServiceUUID(BLEUUID serviceUUID); + void addServiceUUID(const char* serviceUUID); void start(); void stop(); void setAppearance(uint16_t appearance); - void setServiceUUID(const char* serviceUUID); - void setServiceUUID(BLEUUID serviceUUID); + 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; - BLEUUID m_serviceUUID; + 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_ */ +#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ \ No newline at end of file diff --git a/cpp_utils/BLEBeacon.cpp b/cpp_utils/BLEBeacon.cpp new file mode 100644 index 00000000..68f8d8ed --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEBeacon.h b/cpp_utils/BLEBeacon.h new file mode 100644 index 00000000..277bd670 --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLECharacteristic.cpp b/cpp_utils/BLECharacteristic.cpp index f5b82006..2868ee26 100644 --- a/cpp_utils/BLECharacteristic.cpp +++ b/cpp_utils/BLECharacteristic.cpp @@ -11,15 +11,20 @@ #include #include #include "sdkconfig.h" -#include #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) @@ -43,12 +48,12 @@ BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { 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); + 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 /** @@ -83,7 +88,7 @@ void BLECharacteristic::executeCreate(BLEService* pService) { return; } - m_pService = pService; // Save the service for to which this characteristic belongs. + 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(), @@ -93,35 +98,21 @@ void BLECharacteristic::executeCreate(BLEService* pService) { control.auto_rsp = ESP_GATT_RSP_BY_APP; m_semaphoreCreateEvt.take("executeCreate"); - - std::string strValue = m_value.getValue(); - - esp_attr_value_t value; - value.attr_len = strValue.length(); - value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - value.attr_value = (uint8_t*)strValue.data(); - esp_err_t errRc = ::esp_ble_gatts_add_char( m_pService->getHandle(), getUUID().getNative(), - static_cast(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), + static_cast(m_permissions), getProperties(), - &value, + 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"); - // Now that we have registered the characteristic, we must also register all the descriptors associated with this - // characteristic. We iterate through each of those and invoke the registration call to register them with the - // ESP environment. - BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); - while (pDescriptor != nullptr) { pDescriptor->executeCreate(this); pDescriptor = m_descriptorMap.getNext(); @@ -131,7 +122,6 @@ void BLECharacteristic::executeCreate(BLEService* pService) { } // 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. @@ -141,6 +131,7 @@ 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. @@ -159,6 +150,9 @@ 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; @@ -190,18 +184,36 @@ 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_WRITE_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. // @@ -209,24 +221,27 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t conn_id // - uint32_t trans_id // - esp_bd_addr_t bda - // - uint8_t exec_write_flag + // - 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. + if(m_writeEvt){ + m_writeEvt = false; + 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)); } - } 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 @@ -239,8 +254,13 @@ void BLECharacteristic::handleGATTServerEvent( // - uint16_t service_handle // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_EVT: { - if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) && - getService()->getHandle()==param->add_char.service_handle) { + 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; @@ -267,14 +287,18 @@ void BLECharacteristic::handleGATTServerEvent( if (param->write.handle == m_handle) { if (param->write.is_prep) { m_value.addPart(param->write.value, param->write.len); + m_writeEvt = true; } else { setValue(param->write.value, param->write.len); + if (m_pCallbacks != nullptr && param->write.is_prep != true) { + m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler. + } } 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); + 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); @@ -296,9 +320,6 @@ void BLECharacteristic::handleGATTServerEvent( } } // 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 @@ -316,20 +337,18 @@ void BLECharacteristic::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle); if (param->read.handle == m_handle) { - if (m_pCallbacks != nullptr) { - m_pCallbacks->onRead(this); // Invoke the read callback. - } + // 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 uints of 22 bytes. -// The apparent algorithm is as follows. +// 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. @@ -343,12 +362,18 @@ void BLECharacteristic::handleGATTServerEvent( // 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; - std::string value = m_value.getValue(); + if (param->read.is_long) { - if (value.length() - m_value.getReadOffset() < 22) { + 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(); @@ -356,16 +381,22 @@ void BLECharacteristic::handleGATTServerEvent( m_value.setReadOffset(0); } else { // There will be more to come. - rsp.attr_value.len = 22; + 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 + 22); + m_value.setReadOffset(rsp.attr_value.offset + maxOffset); + } + } else { // read.is_long == false + + 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. } - } else { - if (value.length() > 21) { + std::string value = m_value.getValue(); + + if (value.length() + 1 > maxOffset) { // Too big for a single shot entry. - m_value.setReadOffset(22); - rsp.attr_value.len = 22; + 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 { @@ -374,6 +405,10 @@ void BLECharacteristic::handleGATTServerEvent( 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; @@ -395,6 +430,7 @@ void BLECharacteristic::handleGATTServerEvent( break; } // ESP_GATTS_READ_EVT + // ESP_GATTS_CONF_EVT // // conf: @@ -402,6 +438,17 @@ void BLECharacteristic::handleGATTServerEvent( // - 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; } @@ -414,14 +461,12 @@ void BLECharacteristic::handleGATTServerEvent( // Give each of the descriptors associated with this characteristic the opportunity to handle the // event. - BLEDescriptor *pDescriptor = m_descriptorMap.getFirst(); - while(pDescriptor != nullptr) { - pDescriptor->handleGATTServerEvent(event, gatts_if, param); - pDescriptor = m_descriptorMap.getNext(); - } + 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 @@ -431,48 +476,7 @@ void BLECharacteristic::handleGATTServerEvent( void BLECharacteristic::indicate() { ESP_LOGD(LOG_TAG, ">> indicate: 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, "<< indicate: No connected clients."); - return; - } - - // Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled - // and, if not, prevent the indication. - - BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); - if (p2902 != nullptr && !p2902->getIndications()) { - ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring"); - return; - } - - if (m_value.getValue().length() > 20) { - ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); - } - - size_t length = m_value.getValue().length(); - if (length > 20) { - length = 20; - } - - m_semaphoreConfEvt.take("indicate"); - - esp_err_t errRc = ::esp_ble_gatts_send_indicate( - getService()->getServer()->getGattsIf(), - getService()->getServer()->getConnId(), - getHandle(), length, (uint8_t*)m_value.getValue().data(), true); // The need_confirm = true makes this an indication. - - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - - m_semaphoreConfEvt.wait("indicate"); + notify(false); ESP_LOGD(LOG_TAG, "<< indicate"); } // indicate @@ -483,14 +487,12 @@ void BLECharacteristic::indicate() { * will not block; it is a fire and forget. * @return N/A. */ -void BLECharacteristic::notify() { +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) { @@ -501,30 +503,44 @@ void BLECharacteristic::notify() { // 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 (p2902 != nullptr && !p2902->getNotifications()) { - ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); + BLE2902* p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902); + if(p2902 == nullptr){ + ESP_LOGE(LOG_TAG, "Characteristic without 0x2902 descriptor"); return; } - - if (m_value.getValue().length() > 20) { - ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)"); + if(is_notification) { + if (p2902 != nullptr && !p2902->getNotifications()) { + ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring"); + return; + } } - - size_t length = m_value.getValue().length(); - if (length > 20) { - length = 20; + 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); + } - esp_err_t errRc = ::esp_ble_gatts_send_indicate( - getService()->getServer()->getGattsIf(), - getService()->getServer()->getConnId(), - getHandle(), length, (uint8_t*)m_value.getValue().data(), false); // The need_confirm = false makes this a notify. - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + 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 @@ -551,7 +567,9 @@ void BLECharacteristic::setBroadcastProperty(bool value) { * @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 @@ -620,7 +638,7 @@ void BLECharacteristic::setReadProperty(bool value) { * @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); + 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) { @@ -643,6 +661,43 @@ 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. @@ -681,13 +736,36 @@ std::string BLECharacteristic::toString() { 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 ":""); + ((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/cpp_utils/BLECharacteristic.h b/cpp_utils/BLECharacteristic.h index b39c0226..82a8d7d0 100644 --- a/cpp_utils/BLECharacteristic.h +++ b/cpp_utils/BLECharacteristic.h @@ -13,6 +13,7 @@ #include #include "BLEUUID.h" #include +#include #include "BLEDescriptor.h" #include "BLEValue.h" #include "FreeRTOS.h" @@ -26,30 +27,27 @@ class BLECharacteristicCallbacks; */ 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(); + 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; + 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 + * 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 { @@ -61,12 +59,12 @@ class BLECharacteristic { void addDescriptor(BLEDescriptor* pDescriptor); BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID); BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID); - //size_t getLength(); BLEUUID getUUID(); std::string getValue(); + uint8_t* getData(); void indicate(); - void notify(); + void notify(bool is_notification = true); void setBroadcastProperty(bool value); void setCallbacks(BLECharacteristicCallbacks* pCallbacks); void setIndicateProperty(bool value); @@ -74,10 +72,16 @@ class BLECharacteristic { 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; @@ -87,6 +91,7 @@ class BLECharacteristic { static const uint32_t PROPERTY_WRITE_NR = 1<<5; private: + friend class BLEServer; friend class BLEService; friend class BLEDescriptor; @@ -99,6 +104,8 @@ class BLECharacteristic { BLECharacteristicCallbacks* m_pCallbacks; BLEService* m_pService; BLEValue m_value; + esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + bool m_writeEvt = false; void handleGATTServerEvent( esp_gatts_cb_event_t event, @@ -106,7 +113,6 @@ class BLECharacteristic { esp_ble_gatts_cb_param_t* param); void executeCreate(BLEService* pService); - uint16_t getHandle(); esp_gatt_char_prop_t getProperties(); BLEService* getService(); void setHandle(uint16_t handle); @@ -119,7 +125,7 @@ class 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 + * 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 { diff --git a/cpp_utils/BLECharacteristicCallbacks.cpp b/cpp_utils/BLECharacteristicCallbacks.cpp deleted file mode 100644 index b7338659..00000000 --- a/cpp_utils/BLECharacteristicCallbacks.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * BLECharacteristicCallbacks.cpp - * - * Created on: Jul 2, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLECharacteristic.h" -#include -static const char* LOG_TAG = "BLECharacteristicCallbacks"; - - -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(LOG_TAG, ">> onRead: default"); - ESP_LOGD(LOG_TAG, "<< 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(LOG_TAG, ">> onWrite: default"); - ESP_LOGD(LOG_TAG, "<< onWrite"); -} // onWrite -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLECharacteristicMap.cpp b/cpp_utils/BLECharacteristicMap.cpp index f475e835..d73aae99 100644 --- a/cpp_utils/BLECharacteristicMap.cpp +++ b/cpp_utils/BLECharacteristicMap.cpp @@ -9,6 +9,20 @@ #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. @@ -19,6 +33,7 @@ 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. @@ -26,8 +41,8 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) { */ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -36,26 +51,42 @@ BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) { /** - * @brief Return the characteristic by handle. - * @param [in] handle The handle to look up the characteristic. - * @return The characteristic. + * @brief Get the first characteristic in the map. + * @return The first characteristic in the map. */ -BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) { - return m_handleMap.at(handle); -} // getByHandle +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 Set the characteristic by UUID. - * @param [in] uuid The uuid of the characteristic. - * @param [in] characteristic The characteristic to cache. - * @return N/A. + * @brief Get the next characteristic in the map. + * @return The next characteristic in the map. */ -void BLECharacteristicMap::setByUUID( - BLEUUID uuid, - BLECharacteristic *pCharacteristic) { - m_uuidMap.insert(std::pair(uuid.toString(), pCharacteristic)); -} // setByUUID +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 /** @@ -64,12 +95,22 @@ void BLECharacteristicMap::setByUUID( * @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)); +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. @@ -77,60 +118,16 @@ void BLECharacteristicMap::setByHandle(uint16_t handle, std::string BLECharacteristicMap::toString() { std::stringstream stringStream; stringStream << std::hex << std::setfill('0'); - int count=0; + int count = 0; for (auto &myPair: m_uuidMap) { if (count > 0) { stringStream << "\n"; } count++; - stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString(); + 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 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.second->handleGATTServerEvent(event, gatts_if, param); - } -} // handleGATTServerEvent - - -/** - * @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->second; - 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->second; - m_iterator++; - return pRet; -} // getNext #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEClient.cpp b/cpp_utils/BLEClient.cpp index 27086e85..d6e75988 100644 --- a/cpp_utils/BLEClient.cpp +++ b/cpp_utils/BLEClient.cpp @@ -6,8 +6,7 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include -#include +#include #include #include #include @@ -18,6 +17,15 @@ #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 @@ -39,45 +47,96 @@ * * */ -static const char* LOG_TAG = "BLEClient"; BLEClient::BLEClient() { m_pClientCallbacks = nullptr; - m_conn_id = 0; - m_gattc_if = 0; + m_conn_id = ESP_GATT_IF_NONE; + m_gattc_if = ESP_GATT_IF_NONE; m_haveServices = false; -} // BLEClient + m_isConnected = false; // Initially, we are flagged as not connected. -/** - * @brief Connect to the partner. - * @param [in] address The address of the partner. - */ -bool BLEClient::connect(BLEAddress address) { - 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++; + m_appId = m_appId%100; + BLEDevice::addPeerDevice(this, true, m_appId); m_semaphoreRegEvt.take("connect"); - esp_err_t errRc = esp_ble_gattc_app_register(0); + + 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; + return; } + m_semaphoreRegEvt.wait("connect"); +} // 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. + clearServices(); + esp_ble_gattc_app_unregister(m_gattc_if); + BLEDevice::removePeerDevice(m_appId, true); + if(m_deleteCallbacks) + delete m_pClientCallbacks; + +} // ~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_servicesMapByInstID.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()); + + clearServices(); m_peerAddress = address; + // Perform the open connection request against the target BLE Server. m_semaphoreOpenEvt.take("connect"); - errRc = ::esp_ble_gattc_open( - getGattcIf(), + esp_err_t errRc = ::esp_ble_gattc_open( + m_gattc_if, *getPeerAddress().getNative(), // address - 1 // direct connection + 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"); + 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 @@ -89,12 +148,12 @@ bool BLEClient::connect(BLEAddress address) { */ void BLEClient::disconnect() { ESP_LOGD(LOG_TAG, ">> disconnect()"); + // ESP_LOGW(__func__, "gattIf: %d, connId: %d", getGattcIf(), getConnId()); 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; } - m_peerAddress = BLEAddress("00:00:00:00:00:00"); ESP_LOGD(LOG_TAG, "<< disconnect()"); } // disconnect @@ -107,8 +166,46 @@ void BLEClient::gattClientEventHandler( 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: + if(m_gattc_if != gattc_if) + break; + + ESP_LOGI(LOG_TAG, "SERVICE CHANGED"); + break; + + case ESP_GATTC_CLOSE_EVT: + 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: { + ESP_LOGE(__func__, "disconnect event, reason: %d, connId: %d, my connId: %d, my IF: %d, gattc_if: %d", (int)evtParam->disconnect.reason, evtParam->disconnect.conn_id, getConnId(), getGattcIf(), gattc_if); + if(m_gattc_if != gattc_if) + break; + m_semaphoreOpenEvt.give(evtParam->disconnect.reason); + if(!m_isConnected) + break; + // If we receive a disconnect event, set the class flag that indicates that we are + // no longer connected. + esp_ble_gattc_close(m_gattc_if, m_conn_id); + m_isConnected = false; + if (m_pClientCallbacks != nullptr) { + m_pClientCallbacks->onDisconnect(this); + } + break; + } // ESP_GATTC_DISCONNECT_EVT + // // ESP_GATTC_OPEN_EVT // @@ -116,14 +213,19 @@ void BLEClient::gattClientEventHandler( // - esp_gatt_status_t status // - uint16_t conn_id // - esp_bd_addr_t remote_bda - // - uint16_t mtu // case ESP_GATTC_OPEN_EVT: { + if(m_gattc_if != gattc_if) + break; m_conn_id = evtParam->open.conn_id; if (m_pClientCallbacks != nullptr) { m_pClientCallbacks->onConnect(this); } - m_semaphoreOpenEvt.give(); + if (evtParam->open.status == ESP_GATT_OK) { + m_isConnected = true; // Flag us as connected. + m_mtu = evtParam->open.mtu; + } + m_semaphoreOpenEvt.give(evtParam->open.status); break; } // ESP_GATTC_OPEN_EVT @@ -136,11 +238,38 @@ void BLEClient::gattClientEventHandler( // uint16_t app_id // case ESP_GATTC_REG_EVT: { - m_gattc_if = gattc_if; - m_semaphoreRegEvt.give(); + if(m_appId == evtParam->reg.app_id){ + ESP_LOGI(__func__, "register app id: %d, %d, gattc_if: %d", m_appId, evtParam->reg.app_id, gattc_if); + 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"); + } + else + m_mtu = evtParam->cfg_mtu.mtu; + break; + + case ESP_GATTC_CONNECT_EVT: { + if(m_gattc_if != gattc_if) + break; + m_conn_id = evtParam->connect.conn_id; + 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 @@ -150,7 +279,22 @@ void BLEClient::gattClientEventHandler( // - uint16_t conn_id // case ESP_GATTC_SEARCH_CMPL_EVT: { - m_semaphoreSearchCmplEvt.give(); + if(m_gattc_if != gattc_if) + break; + if (evtParam->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(LOG_TAG, "search service failed, error status = %x", evtParam->search_cmpl.status); + } +#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(evtParam->search_cmpl.status); break; } // ESP_GATTC_SEARCH_CMPL_EVT @@ -159,13 +303,24 @@ void BLEClient::gattClientEventHandler( // ESP_GATTC_SEARCH_RES_EVT // // search_res: - // - uint16_t conn_id - // - esp_gatt_srvc_id_t srvc_id + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id // case ESP_GATTC_SEARCH_RES_EVT: { + if(m_gattc_if != gattc_if) + break; + BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id); - BLERemoteService* pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this); - m_servicesMap.insert(std::pair(uuid.toString(), pRemoteService)); + 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 @@ -175,6 +330,7 @@ void BLEClient::gattClientEventHandler( } } // Switch + // Pass the request on to all services. for (auto &myPair : m_servicesMap) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } @@ -182,42 +338,70 @@ void BLEClient::gattClientEventHandler( } // gattClientEventHandler -/** - * @brief Retrieve the address of the peer. - * - * Returns the address of the %BLE peer to which this client is connected. - */ -BLEAddress BLEClient::getPeerAddress() { - return m_peerAddress; -} // getAddress - - 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 object corresponding to the uuid. + * @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 @@ -227,12 +411,14 @@ BLERemoteService* BLEClient::getService(BLEUUID uuid) { if (!m_haveServices) { getServices(); } - std::string v = uuid.toString(); + std::string uuidStr = uuid.toString(); for (auto &myPair : m_servicesMap) { - if (myPair.first == v) { + 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 @@ -252,32 +438,106 @@ std::map* BLEClient::getServices() { * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received. */ ESP_LOGD(LOG_TAG, ">> getServices"); - m_servicesMap.empty(); +// 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 + 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; } - m_semaphoreSearchCmplEvt.wait("getServices"); - m_haveServices = true; // Remember that we now have services. + // 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) { +void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks, bool deleteCallbacks) { m_pClientCallbacks = pClientCallbacks; + m_deleteCallbacks = deleteCallbacks; } // 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. @@ -289,9 +549,10 @@ std::string BLEClient::toString() { ss << "\nServices:\n"; for (auto &myPair : m_servicesMap) { ss << myPair.second->toString() << "\n"; - // myPair.second is the value + // myPair.second is the value } return ss.str(); } // toString + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEClient.h b/cpp_utils/BLEClient.h index 6a74ea2c..4b3c7eea 100644 --- a/cpp_utils/BLEClient.h +++ b/cpp_utils/BLEClient.h @@ -15,12 +15,15 @@ #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. @@ -28,38 +31,58 @@ class BLEClientCallbacks; class BLEClient { public: BLEClient(); - bool connect(BLEAddress address); - void disconnect(); - BLEAddress getPeerAddress(); - std::map* getServices(); - BLERemoteService* getService(const char* uuid); - BLERemoteService* getService(BLEUUID uuid); - void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); - std::string toString(); + ~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, bool deleteCallbacks = true); + 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 BLERemoteCharacteristic; 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); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param); - uint16_t getConnId(); - esp_gatt_if_t getGattcIf(); - BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0"); + 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. + bool m_deleteCallbacks = true; - BLEClientCallbacks* m_pClientCallbacks; - FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); - FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); + BLEClientCallbacks* m_pClientCallbacks = nullptr; + 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; - bool m_haveServices; // Have we previously obtain the set of services. + std::map m_servicesMapByInstID; + void clearServices(); // Clear any existing services. + uint16_t m_mtu = 23; }; // class BLEDevice @@ -70,6 +93,7 @@ class BLEClientCallbacks { public: virtual ~BLEClientCallbacks() {}; virtual void onConnect(BLEClient *pClient) = 0; + virtual void onDisconnect(BLEClient *pClient) = 0; }; #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEDescriptor.cpp b/cpp_utils/BLEDescriptor.cpp index 4a7fda60..ba5753de 100644 --- a/cpp_utils/BLEDescriptor.cpp +++ b/cpp_utils/BLEDescriptor.cpp @@ -11,13 +11,19 @@ #include #include #include "sdkconfig.h" -#include #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) @@ -26,20 +32,21 @@ static const char* LOG_TAG = "BLEDescriptor"; /** * @brief BLEDescriptor constructor. */ -BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) { +BLEDescriptor::BLEDescriptor(const char* uuid, uint16_t len) : BLEDescriptor(BLEUUID(uuid), len) { } /** * @brief BLEDescriptor constructor. */ -BLEDescriptor::BLEDescriptor(BLEUUID uuid) { +BLEDescriptor::BLEDescriptor(BLEUUID uuid, uint16_t max_len) { m_bleUUID = uuid; - m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN); // Allocate storage for the value. - m_value.attr_len = 0; - m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN; - m_handle = NULL_HANDLE; - m_pCharacteristic = nullptr; // No initial characteristic. + 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 @@ -47,7 +54,7 @@ BLEDescriptor::BLEDescriptor(BLEUUID uuid) { * @brief BLEDescriptor destructor. */ BLEDescriptor::~BLEDescriptor() { - free(m_value.attr_value); + free(m_value.attr_value); // Release the storage we created in the constructor. } // ~BLEDescriptor @@ -66,12 +73,12 @@ void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) { m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service. esp_attr_control_t control; - control.auto_rsp = ESP_GATT_RSP_BY_APP; + 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)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), + (esp_gatt_perm_t)m_permissions, &m_value, &control); if (errRc != ESP_OK) { @@ -129,8 +136,8 @@ uint8_t* BLEDescriptor::getValue() { 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_ble_gatts_cb_param_t* param) { + switch (event) { // ESP_GATTS_ADD_CHAR_DESCR_EVT // // add_char_descr: @@ -139,19 +146,8 @@ void BLEDescriptor::handleGATTServerEvent( // - uint16_t service_handle // - esp_bt_uuid_t char_uuid case ESP_GATTS_ADD_CHAR_DESCR_EVT: { - /* - ESP_LOGD(LOG_TAG, "DEBUG: m_pCharacteristic: %x", (uint32_t)m_pCharacteristic); - ESP_LOGD(LOG_TAG, "DEBUG: m_bleUUID: %s, add_char_descr.char_uuid: %s, equals: %d", - m_bleUUID.toString().c_str(), - BLEUUID(param->add_char_descr.char_uuid).toString().c_str(), - m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid))); - ESP_LOGD(LOG_TAG, "DEBUG: service->getHandle: %x, add_char_descr.service_handle: %x", - m_pCharacteristic->getService()->getHandle(), param->add_char_descr.service_handle); - ESP_LOGD(LOG_TAG, "DEBUG: service->lastCharacteristic: %x", - (uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic()); - */ if (m_pCharacteristic != nullptr && - m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)) && + 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); @@ -174,19 +170,13 @@ void BLEDescriptor::handleGATTServerEvent( // - uint8_t *value case ESP_GATTS_WRITE_EVT: { if (param->write.handle == m_handle) { - setValue(param->write.value, param->write.len); - esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); - rsp.attr_value.handle = m_handle; - rsp.attr_value.offset = 0; - rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.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)); + 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 @@ -202,32 +192,33 @@ void BLEDescriptor::handleGATTServerEvent( // - bool need_rsp // case ESP_GATTS_READ_EVT: { - ESP_LOGD(LOG_TAG, "- Testing: Sought handle: 0x%.2x == descriptor handle: 0x%.2x ?", param->read.handle, m_handle); - if (param->read.handle == m_handle) { - ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)"); - if (param->read.need_rsp) { - esp_gatt_rsp_t rsp; - rsp.attr_value.len = getLength(); - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.offset = 0; - rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(rsp.attr_value.value, getValue(), rsp.attr_value.len); - 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)); - } + 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. } - } // ESP_GATTS_READ_EVT + + } // End of this is our handle break; } // ESP_GATTS_READ_EVT - default: { + + default: break; - } - }// switch event + } // 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. @@ -261,9 +252,12 @@ void BLEDescriptor::setValue(uint8_t* data, size_t length) { * @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((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. @@ -275,4 +269,28 @@ std::string BLEDescriptor::toString() { 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/cpp_utils/BLEDescriptor.h b/cpp_utils/BLEDescriptor.h index 1d32d500..03cc5791 100644 --- a/cpp_utils/BLEDescriptor.h +++ b/cpp_utils/BLEDescriptor.h @@ -17,38 +17,61 @@ class BLEService; class BLECharacteristic; +class BLEDescriptorCallbacks; /** * @brief A model of a %BLE descriptor. */ class BLEDescriptor { public: - BLEDescriptor(const char* uuid); - BLEDescriptor(BLEUUID uuid); + BLEDescriptor(const char* uuid, uint16_t max_len = 100); + BLEDescriptor(BLEUUID uuid, uint16_t max_len = 100); virtual ~BLEDescriptor(); - size_t getLength(); - BLEUUID getUUID(); - uint8_t* getValue(); + 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 setValue(uint8_t* data, size_t size); - void setValue(std::string value); - std::string toString(); + + 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; - esp_attr_value_t m_value; - uint16_t m_handle; - BLECharacteristic* m_pCharacteristic; + 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); - uint16_t getHandle(); void setHandle(uint16_t handle); - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); +}; // 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/cpp_utils/BLEDescriptorMap.cpp b/cpp_utils/BLEDescriptorMap.cpp index b2116521..6b845833 100644 --- a/cpp_utils/BLEDescriptorMap.cpp +++ b/cpp_utils/BLEDescriptorMap.cpp @@ -11,6 +11,9 @@ #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. @@ -18,7 +21,7 @@ * @return The descriptor. If not present, then nullptr is returned. */ BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(uuid)); + return getByUUID(BLEUUID(uuid)); } @@ -29,8 +32,8 @@ BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) { */ BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -54,8 +57,8 @@ BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) { * @param [in] characteristic The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){ - m_uuidMap.insert(std::pair(uuid, pDescriptor)); +void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor* pDescriptor){ + m_uuidMap.insert(std::pair(pDescriptor, uuid)); } // setByUUID @@ -66,8 +69,8 @@ void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){ * @param [in] characteristic The descriptor to cache. * @return N/A. */ -void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) { - m_uuidMap.insert(std::pair(uuid.toString(), pDescriptor)); +void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor* pDescriptor) { + m_uuidMap.insert(std::pair(pDescriptor, uuid.toString())); } // setByUUID @@ -77,9 +80,8 @@ void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) { * @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)); +void BLEDescriptorMap::setByHandle(uint16_t handle, BLEDescriptor* pDescriptor) { + m_handleMap.insert(std::pair(handle, pDescriptor)); } // setByHandle @@ -90,13 +92,13 @@ void BLEDescriptorMap::setByHandle(uint16_t handle, std::string BLEDescriptorMap::toString() { std::stringstream stringStream; stringStream << std::hex << std::setfill('0'); - int count=0; - for (auto &myPair: m_uuidMap) { + int count = 0; + for (auto &myPair : m_uuidMap) { if (count > 0) { stringStream << "\n"; } count++; - stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString(); + stringStream << "handle: 0x" << std::setw(2) << myPair.first->getHandle() << ", uuid: " + myPair.first->getUUID().toString(); } return stringStream.str(); } // toString @@ -111,10 +113,10 @@ std::string BLEDescriptorMap::toString() { void BLEDescriptorMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every descriptor we have. for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); + myPair.first->handleGATTServerEvent(event, gatts_if, param); } } // handleGATTServerEvent @@ -125,10 +127,8 @@ void BLEDescriptorMap::handleGATTServerEvent( */ BLEDescriptor* BLEDescriptorMap::getFirst() { m_iterator = m_uuidMap.begin(); - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEDescriptor *pRet = m_iterator->second; + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->first; m_iterator++; return pRet; } // getFirst @@ -139,10 +139,8 @@ BLEDescriptor* BLEDescriptorMap::getFirst() { * @return The next descriptor in the map. */ BLEDescriptor* BLEDescriptorMap::getNext() { - if (m_iterator == m_uuidMap.end()) { - return nullptr; - } - BLEDescriptor *pRet = m_iterator->second; + if (m_iterator == m_uuidMap.end()) return nullptr; + BLEDescriptor* pRet = m_iterator->first; m_iterator++; return pRet; } // getNext diff --git a/cpp_utils/BLEDevice.cpp b/cpp_utils/BLEDevice.cpp index 04cae14c..78bd13d3 100644 --- a/cpp_utils/BLEDevice.cpp +++ b/cpp_utils/BLEDevice.cpp @@ -7,61 +7,130 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include +#include #include #include #include -#include -#include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -// ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // ESP32 ESP-IDF -#include // Part of C++ STL -#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(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define LOG_TAG "" +#else +#include "esp_log.h" static const char* LOG_TAG = "BLEDevice"; +#endif -BLEServer *BLEDevice::m_bleServer = nullptr; -BLEScan *BLEDevice::m_pScan = nullptr; -BLEClient *BLEDevice::m_pClient = nullptr; - -#include +#if defined(ARDUINO_ARCH_ESP32) +#include "esp32-hal-bt.h" +#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; -BLEClient* BLEDevice::createClient() { - m_pClient = new BLEClient(); +/** + * @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 + BLEClient* 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 + BLEDevice::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 - * @param [in] gatts_if - * @param [in] param + * @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. */ -void BLEDevice::gattServerEventHandler( +/* STATIC */ void BLEDevice::gattServerEventHandler( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param + 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); - if (BLEDevice::m_bleServer != nullptr) { - BLEDevice::m_bleServer->handleGATTServerEvent(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 @@ -69,154 +138,507 @@ void BLEDevice::gattServerEventHandler( * @brief Handle GATT client events. * * Handler for the GATT client events. - * * `ESP_GATTC_OPEN_EVT` – Invoked when a connection is opened. - * * `ESP_GATTC_PREP_WRITE_EVT` – Response to write a characteristic. - * * `ESP_GATTC_READ_CHAR_EVT` – Response to read a characteristic. - * * `ESP_GATTC_REG_EVT` – Invoked when a GATT client has been registered. * * @param [in] event * @param [in] gattc_if * @param [in] param */ -void BLEDevice::gattClientEventHandler( - esp_gattc_cb_event_t event, - esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *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) { - default: { + 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 we have a client registered, call it. - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param); + if(m_customGattcHandler != nullptr) { + m_customGattcHandler(event, gattc_if, param); } + } // gattClientEventHandler /** * @brief Handle GAP events. */ -void BLEDevice::gapEventHandler( +/* 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_SEC_REQ_EVT: { - esp_err_t errRc = ::esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gap_security_rsp: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + + 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_bleServer != nullptr) { - BLEDevice::m_bleServer->handleGAPEvent(event, param); + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->handleGAPEvent(event, param); } - if (BLEDevice::m_pScan != nullptr) { - BLEDevice::getScan()->gapEventHandler(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. */ -void BLEDevice::init(std::string deviceName) { - esp_err_t errRc = ::nvs_flash_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } +/* 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; + } - 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 + 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; + } - 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; - } +#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 - errRc = esp_bluedroid_init(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + 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; + } + } - errRc = esp_bluedroid_enable(); - if (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: 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; + 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 + - errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler); +/** + * @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_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + 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 - errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler); + +/** + * @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_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } + ESP_LOGD(LOG_TAG, "<< whiteListAdd"); +} // whiteListAdd - 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; - }; - 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)); +/** + * @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_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - }; + ESP_LOGE(LOG_TAG, "esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ESP_LOGD(LOG_TAG, "<< whiteListRemove"); +} // whiteListRemove - vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. -} // init +/* + * @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 Retrieve the Scan object that we use for scanning. - * @return The scanning object reference. +/* + * @brief Get local MTU value set during mtu request or default value */ -BLEScan* BLEDevice::getScan() { - if (m_pScan == nullptr) { - m_pScan = new BLEScan(); +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"); } - return m_pScan; -} // getScan + 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/cpp_utils/BLEDevice.h b/cpp_utils/BLEDevice.h index 9d767c1e..b01f09be 100644 --- a/cpp_utils/BLEDevice.h +++ b/cpp_utils/BLEDevice.h @@ -13,41 +13,86 @@ #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. + * @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 void dumpDevices(); - static BLEClient *createClient(); - static void init(std::string deviceName); - //static void scan(int duration, esp_ble_scan_type_t scan_type = BLE_SCAN_TYPE_PASSIVE); - static BLEScan *getScan(); - static BLEServer *m_bleServer; - static BLEScan *m_pScan; - static BLEClient *m_pClient; + 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); + 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); + esp_ble_gatts_cb_param_t* param); + static void gapEventHandler( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t *param); + 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 diff --git a/cpp_utils/BLEEddystoneTLM.cpp b/cpp_utils/BLEEddystoneTLM.cpp new file mode 100755 index 00000000..a92bcdb2 --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEEddystoneTLM.h b/cpp_utils/BLEEddystoneTLM.h new file mode 100755 index 00000000..a93e224f --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEEddystoneURL.cpp b/cpp_utils/BLEEddystoneURL.cpp new file mode 100755 index 00000000..af3b674c --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEEddystoneURL.h b/cpp_utils/BLEEddystoneURL.h new file mode 100755 index 00000000..0b538c07 --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEExceptions.cpp b/cpp_utils/BLEExceptions.cpp new file mode 100644 index 00000000..b6adfd82 --- /dev/null +++ b/cpp_utils/BLEExceptions.cpp @@ -0,0 +1,9 @@ +/* + * BLExceptions.cpp + * + * Created on: Nov 27, 2017 + * Author: kolban + */ + +#include "BLEExceptions.h" + diff --git a/cpp_utils/BLEExceptions.h b/cpp_utils/BLEExceptions.h new file mode 100644 index 00000000..ea9db855 --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLEHIDDevice.cpp b/cpp_utils/BLEHIDDevice.cpp new file mode 100644 index 00000000..46770b74 --- /dev/null +++ b/cpp_utils/BLEHIDDevice.cpp @@ -0,0 +1,244 @@ +/* + * 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); + m_batteryLevelCharacteristic->notify(); +} +/* + * @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/cpp_utils/BLEHIDDevice.h b/cpp_utils/BLEHIDDevice.h new file mode 100644 index 00000000..33e6b46c --- /dev/null +++ b/cpp_utils/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/cpp_utils/BLERemoteCharacteristic.cpp b/cpp_utils/BLERemoteCharacteristic.cpp index c1f826bd..ef16da7b 100644 --- a/cpp_utils/BLERemoteCharacteristic.cpp +++ b/cpp_utils/BLERemoteCharacteristic.cpp @@ -6,31 +6,116 @@ */ #include "BLERemoteCharacteristic.h" + #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) #include -#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 -static const char* LOG_TAG = "BLERemoteCharacteristic"; +/** + * @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( - esp_gatt_id_t charId, + uint16_t handle, + BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService) { - m_charId = charId; + 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. + if(m_rawData != nullptr) free(m_rawData); +} // ~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; @@ -40,8 +125,9 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { } 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; @@ -51,6 +137,7 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { } return true; } // compareCharId +*/ /** @@ -62,39 +149,50 @@ static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) { * @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) { +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 - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id - // uint8_t* value - // uint16_t value_type - // uint16_t value_len + // - 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 (compareSrvcId(evtParam->read.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - - if (evtParam->read.conn_id != m_pRemoteService->getClient()->getConnId()) { - break; - } - - if (compareGattId(evtParam->read.char_id, m_charId) == false) { - break; - } + // 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); + 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 = ""; } @@ -103,62 +201,168 @@ void BLERemoteCharacteristic::gattClientEventHandler( break; } // ESP_GATTC_READ_CHAR_EVT - - // // ESP_GATTC_REG_FOR_NOTIFY_EVT // // reg_for_notify: // - esp_gatt_status_t status - // - esp_gatt_srvc_id_t srvc_id - // - esp_gatt_id_t char_id + // - uint16_t handle case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - if (compareSrvcId(evtParam->reg_for_notify.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - if (compareGattId(evtParam->reg_for_notify.char_id, m_charId) == false) { - break; - } + // 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 - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle case ESP_GATTC_WRITE_CHAR_EVT: { - if (compareSrvcId(evtParam->write.srvc_id, *m_pRemoteService->getSrvcId()) == false) { - break; - } - if (evtParam->write.conn_id != m_pRemoteService->getClient()->getConnId()) { - break; - } - if (compareGattId(evtParam->write.char_id, m_charId) == false) { - break; - } + // 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: { + 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 = 1; + 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 || status == ESP_GATT_NOT_FOUND) { // 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; + } } -}; // gattClientEventHandler + 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(void) { +uint16_t BLERemoteCharacteristic::readUInt16() { std::string value = readValue(); if (value.length() >= 2) { return *(uint16_t*)(value.data()); @@ -171,7 +375,7 @@ uint16_t BLERemoteCharacteristic::readUInt16(void) { * @brief Read an unsigned 32 bit value. * @return the unsigned 32 bit value. */ -uint32_t BLERemoteCharacteristic::readUInt32(void) { +uint32_t BLERemoteCharacteristic::readUInt32() { std::string value = readValue(); if (value.length() >= 4) { return *(uint32_t*)(value.data()); @@ -184,7 +388,7 @@ uint32_t BLERemoteCharacteristic::readUInt32(void) { * @brief Read a byte value * @return The value as a byte */ -uint8_t BLERemoteCharacteristic::readUInt8(void) { +uint8_t BLERemoteCharacteristic::readUInt8() { std::string value = readValue(); if (value.length() >= 1) { return (uint8_t)value[0]; @@ -198,17 +402,24 @@ uint8_t BLERemoteCharacteristic::readUInt8(void) { * @return The value of the remote characteristic. */ std::string BLERemoteCharacteristic::readValue() { - ESP_LOGD(LOG_TAG, ">> 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(), - m_pRemoteService->getSrvcId(), - &m_charId, - ESP_GATT_AUTH_REQ_NONE); + 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)); @@ -219,30 +430,57 @@ std::string BLERemoteCharacteristic::readValue() { // in m_value will contain our data. m_semaphoreReadCharEvt.wait("readValue"); - ESP_LOGD(LOG_TAG, "<< 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() { - ESP_LOGD(LOG_TAG, ">> registerForNotify()"); +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"); - esp_err_t errRc = ::esp_ble_gattc_register_for_notify( - m_pRemoteService->getClient()->getGattcIf(), - *m_pRemoteService->getClient()->getPeerAddress().getNative(), - m_pRemoteService->getSrvcId(), - &m_charId); + 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)); - return; - } + 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)); + if(desc != nullptr) + 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); + if(desc != nullptr) + desc->writeValue(val, 2); + } // End Unregister m_semaphoreRegForNotifyEvt.wait("registerForNotify"); @@ -250,15 +488,32 @@ void BLERemoteCharacteristic::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: " << BLEUUID(m_charId.uuid).toString() << - ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp) << - ", inst_id: " << (int)m_charId.inst_id; + ss << "Characteristic: uuid: " << m_uuid.toString() << + ", handle: " << getHandle() << " 0x" << std::hex << getHandle() << + ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp); return ss.str(); } // toString @@ -266,43 +521,73 @@ std::string BLERemoteCharacteristic::toString() { /** * @brief Write the new value for the characteristic. * @param [in] newValue The new value to write. - * @return N/A. + * @param [in] response Do we expect a response? + * @return false if not connected or cant perform write for some reason. */ -void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { - ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length()); +bool BLERemoteCharacteristic::writeValue(std::string newValue, bool response) { + return writeValue((uint8_t*)newValue.c_str(), strlen(newValue.c_str()), response); +} // writeValue - m_semaphoreWriteCharEvt.take("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 false if not connected or cant perform write for some reason. + */ +bool BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { + return 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. + * @return false if not connected or cant perform write for some reason. + */ +bool 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"); + return false; + } + + 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(), - m_pRemoteService->getSrvcId(), - &m_charId, - newValue.length(), - (uint8_t*)newValue.data(), + 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; + return false; } m_semaphoreWriteCharEvt.wait("writeValue"); ESP_LOGD(LOG_TAG, "<< writeValue"); + return true; } // writeValue - /** - * @brief Write the new value for the characteristic. - * @param [in] newValue The new value to write. - * @return N/A. + * @brief Read raw data from remote characteristic as hex bytes + * @return return pointer data read */ -void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { - writeValue(std::string(reinterpret_cast(&newValue), 1), response); -} // writeValue - +uint8_t* BLERemoteCharacteristic::readRawData() { + return m_rawData; +} #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteCharacteristic.h b/cpp_utils/BLERemoteCharacteristic.h index 02fcf9b8..910b11a4 100644 --- a/cpp_utils/BLERemoteCharacteristic.h +++ b/cpp_utils/BLERemoteCharacteristic.h @@ -15,44 +15,70 @@ #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(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService); + ~BLERemoteCharacteristic(); // Public member functions - std::string readValue(void); - uint8_t readUInt8(void); - uint16_t readUInt16(void); - uint32_t readUInt32(void); - void registerForNotify(void); - void writeValue(std::string newValue, bool response = false); - void writeValue(uint8_t newValue, bool response = false); - std::string toString(void); + 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); + bool writeValue(uint8_t* data, size_t length, bool response = false); + bool writeValue(std::string newValue, bool response = false); + bool writeValue(uint8_t newValue, bool response = false); + std::string toString(); + uint8_t* readRawData(); + BLERemoteService* getRemoteService(); 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); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* evtParam); + + void removeDescriptors(); + void retrieveDescriptors(); // Private properties - esp_gatt_id_t m_charId; + 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; + std::string m_value; + uint8_t *m_rawData = nullptr; + notify_callback m_notifyCallback = nullptr; + + // 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/cpp_utils/BLERemoteDescriptor.cpp b/cpp_utils/BLERemoteDescriptor.cpp index 2be312ae..96a8a577 100644 --- a/cpp_utils/BLERemoteDescriptor.cpp +++ b/cpp_utils/BLERemoteDescriptor.cpp @@ -6,6 +6,176 @@ */ #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/cpp_utils/BLERemoteDescriptor.h b/cpp_utils/BLERemoteDescriptor.h index a8d944d4..7bbc48f1 100644 --- a/cpp_utils/BLERemoteDescriptor.h +++ b/cpp_utils/BLERemoteDescriptor.h @@ -9,12 +9,47 @@ #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/cpp_utils/BLERemoteService.cpp b/cpp_utils/BLERemoteService.cpp index dbfe27b5..91089eab 100644 --- a/cpp_utils/BLERemoteService.cpp +++ b/cpp_utils/BLERemoteService.cpp @@ -11,19 +11,33 @@ #include "BLERemoteService.h" #include "BLEUtils.h" #include "GeneralUtils.h" -#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 = "BLERemoteService"; +#endif + + BLERemoteService::BLERemoteService( - esp_gatt_srvc_id_t srvcId, - BLEClient *pClient) { + 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()"); } @@ -31,6 +45,7 @@ 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; @@ -40,7 +55,7 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { } return true; } // compareSrvcId - +*/ /** * @brief Handle GATT Client events @@ -48,83 +63,44 @@ static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) { 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 (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) )); - - /* - ::esp_ble_gattc_get_characteristic( - m_pClient->getGattcIf(), - m_pClient->getConnId(), - &m_srvcId, - &evtParam->get_char.char_id); - */ - m_semaphoreGetCharEvt.give(); - break; - } // ESP_GATTC_GET_CHAR_EVT - - default: { + esp_ble_gattc_cb_param_t* evtParam) { + switch (event) { + default: break; - } } // switch // Send the event to each of the characteristics owned by this service. - for (auto &myPair : m_characteristicMap) { + for (auto &myPair : m_characteristicMapByHandle) { myPair.second->gattClientEventHandler(event, gattc_if, evtParam); } } // gattClientEventHandler /** - * @brief Get the characteristic object for the UUID. - * @param [in] uuid Characteristic uuid. - * @return Reference to the characteristic object. + * @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 characteritic given its UUID. It is possible that we have not yet asked the +// 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) { - getCharacteristics(); + retrieveCharacteristics(); } std::string v = uuid.toString(); for (auto &myPair : m_characteristicMap) { @@ -132,52 +108,154 @@ BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) { 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::getCharacteristics() { - - ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str()); +void BLERemoteService::retrieveCharacteristics() { + ESP_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); removeCharacteristics(); // Forget any previous characteristics. - m_semaphoreGetCharEvt.take("getCharacteristics"); + uint16_t offset = 0; + esp_gattc_char_elem_t result; + while (true) { + uint16_t count = 1; // 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 || status == ESP_GATT_NOT_FOUND) { // We have reached the end of the entries. + break; + } - esp_err_t errRc = ::esp_ble_gattc_get_characteristic( - m_pClient->getGattcIf(), - m_pClient->getConnId(), - &m_srvcId, - nullptr); + 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 (errRc != ESP_OK) { - ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } + 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()); - m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available. + // 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()"); + ESP_LOGD(LOG_TAG, "<< retrieveCharacteristics()"); +} // retrieveCharacteristics + + +/** + * @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 Retrieve a map of all the characteristics of this service. + * @return A map of all the characteristics of this service. + */ +std::map* BLERemoteService::getCharacteristicsByHandle() { + ESP_LOGD(LOG_TAG, ">> getCharacteristicsByHandle() 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, "<< getCharacteristicsByHandle() for service: %s", getUUID().toString().c_str()); + return &m_characteristicMapByHandle; +} // getCharacteristicsByHandle + +/** + * @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_srvc_id_t* BLERemoteService::getSrvcId() { +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. @@ -187,13 +265,26 @@ BLEUUID BLERemoteService::getUUID() { * @return N/A. */ void BLERemoteService::removeCharacteristics() { - for (auto &myPair : m_characteristicMap) { + m_characteristicMap.clear(); // Clear the map + for (auto &myPair : m_characteristicMapByHandle) { delete myPair.second; } - m_characteristicMap.empty(); + 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. @@ -202,6 +293,8 @@ void BLERemoteService::removeCharacteristics() { 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 @@ -210,5 +303,4 @@ std::string BLERemoteService::toString() { } // toString - #endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLERemoteService.h b/cpp_utils/BLERemoteService.h index 4393fbcf..2ab86738 100644 --- a/cpp_utils/BLERemoteService.h +++ b/cpp_utils/BLERemoteService.h @@ -26,37 +26,59 @@ class BLERemoteCharacteristic; */ class BLERemoteService { public: - BLERemoteService(esp_gatt_srvc_id_t srvcId, BLEClient* pClient); virtual ~BLERemoteService(); // Public methods - BLERemoteCharacteristic* getCharacteristic(const char* uuid); - BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid); - void getCharacteristics(void); - BLEClient* getClient(void); - BLEUUID getUUID(void); + 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 - esp_gatt_srvc_id_t* getSrvcId(void); + 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 - std::map m_characteristicMap; + + // 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_srvc_id_t m_srvcId; - BLEUUID m_uuid; + 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 */ diff --git a/cpp_utils/BLEScan.cpp b/cpp_utils/BLEScan.cpp index 9fdcffdf..a96e6df6 100644 --- a/cpp_utils/BLEScan.cpp +++ b/cpp_utils/BLEScan.cpp @@ -8,7 +8,6 @@ #if defined(CONFIG_BT_ENABLED) -#include #include #include @@ -17,70 +16,78 @@ #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); - m_pAdvertisedDeviceCallbacks = nullptr; - m_stopped = true; } // BLEScan - -/** - * @brief Clear the history of previously detected advertised devices. - * @return N/A - */ -/* -void BLEScan::clearAdvertisedDevices() { - for (int i=0; iscan_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; @@ -90,40 +97,39 @@ void BLEScan::gapEventHandler( // ignore it. BLEAddress advertisedAddress(param->scan_rst.bda); bool found = false; - /* - for (int i=0; igetAddress().equals(advertisedAddress)) { - found = true; - break; - } - } - */ - for (int i=0; iscan_rst.rssi); - advertisedDevice.setAdFlag(param->scan_rst.flag); - advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv); - advertisedDevice.setScan(this); - - //m_vectorAvdertisedDevices.push_back(pAdvertisedDevice); + // 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); + m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); } - - m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice); + if(found) + delete advertisedDevice; break; } // ESP_GAP_SEARCH_INQ_RES_EVT @@ -144,14 +150,6 @@ void BLEScan::gapEventHandler( } // gapEventHandler -/* -void BLEScan::onResults() { - ESP_LOGD(LOG_TAG, ">> onResults: default"); - ESP_LOGD(LOG_TAG, "<< onResults"); -} // onResults -*/ - - /** * @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. @@ -170,8 +168,10 @@ void BLEScan::setActiveScan(bool active) { /** * @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) { +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, bool wantDuplicates) { + m_wantDuplicates = wantDuplicates; m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; } // setAdvertisedDeviceCallbacks @@ -197,21 +197,31 @@ void BLEScan::setWindow(uint16_t windowMSecs) { /** * @brief Start scanning. * @param [in] duration The duration in seconds for which to scan. - * @return N/A. + * @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. */ -BLEScanResults BLEScan::start(uint32_t duration) { - ESP_LOGD(LOG_TAG, ">> start(%d)", duration); - - m_semaphoreScanEnd.take("start"); - - m_scanResults.m_vectorAdvertisedDevices.empty(); +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 m_scanResults; + return false; } errRc = ::esp_ble_gap_start_scanning(duration); @@ -219,15 +229,25 @@ BLEScanResults BLEScan::start(uint32_t 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 m_scanResults; + return false; } m_stopped = false; - m_semaphoreScanEnd.take("start"); - m_semaphoreScanEnd.give(); - 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 @@ -242,17 +262,35 @@ void BLEScan::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; } - m_semaphoreScanEnd.give(); - 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/cpp_utils/BLEScan.h b/cpp_utils/BLEScan.h index f9575eac..2f71a727 100644 --- a/cpp_utils/BLEScan.h +++ b/cpp_utils/BLEScan.h @@ -11,7 +11,8 @@ #if defined(CONFIG_BT_ENABLED) #include -#include +// #include +#include #include "BLEAdvertisedDevice.h" #include "BLEClient.h" #include "FreeRTOS.h" @@ -21,13 +22,23 @@ 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: - int getCount(); + void dump(); + int getCount(); BLEAdvertisedDevice getDevice(uint32_t i); + private: friend BLEScan; - std::vector m_vectorAdvertisedDevices; + std::map m_vectorAdvertisedDevices; }; /** @@ -37,30 +48,35 @@ class BLEScanResults { */ class BLEScan { public: - BLEScan(); - - //virtual void onResults(); void setActiveScan(bool active); - void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks); + void setAdvertisedDeviceCallbacks( + BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks, + bool wantDuplicates = false); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); - BLEScanResults start(uint32_t duration); + 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 gapEventHandler( + 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; - bool m_stopped; - FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd"); - //std::vector m_vectorAvdertisedDevices; - BLEScanResults m_scanResults; + 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 */ diff --git a/cpp_utils/BLESecurity.cpp b/cpp_utils/BLESecurity.cpp new file mode 100644 index 00000000..f3b2cd3c --- /dev/null +++ b/cpp_utils/BLESecurity.cpp @@ -0,0 +1,115 @@ +/* + * 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 + +/** + * Setup for static PIN connection, call it first and then call setAuthenticationMode eventually to change it + */ +void BLESecurity::setStaticPIN(uint32_t pin){ + uint32_t passkey = pin; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + setCapability(ESP_IO_CAP_OUT); + setKeySize(); + setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); +} + +/** + * @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/cpp_utils/BLESecurity.h b/cpp_utils/BLESecurity.h new file mode 100644 index 00000000..dc6d6d71 --- /dev/null +++ b/cpp_utils/BLESecurity.h @@ -0,0 +1,73 @@ +/* + * 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); + void setStaticPIN(uint32_t pin); + 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/cpp_utils/BLEServer.cpp b/cpp_utils/BLEServer.cpp old mode 100644 new mode 100755 index be9773d9..83497331 --- a/cpp_utils/BLEServer.cpp +++ b/cpp_utils/BLEServer.cpp @@ -7,21 +7,26 @@ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include -#include +#include #include -#include #include +#include "GeneralUtils.h" #include "BLEDevice.h" #include "BLEServer.h" #include "BLEService.h" #include "BLEUtils.h" #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 = "BLEServer"; +#endif + + /** @@ -31,21 +36,18 @@ static const char* LOG_TAG = "BLEServer"; * the BLEDevice class. */ BLEServer::BLEServer() { - m_appId = -1; - m_gatts_if = -1; + m_appId = ESP_GATT_IF_NONE; + m_gatts_if = ESP_GATT_IF_NONE; m_connectedCount = 0; - m_connId = -1; - BLEDevice::m_bleServer = this; + m_connId = ESP_GATT_IF_NONE; m_pServerCallbacks = nullptr; - - createApp(0); } // BLEServer void BLEServer::createApp(uint16_t appId) { m_appId = appId; - registerApp(); -} + registerApp(appId); +} // createApp /** @@ -67,21 +69,22 @@ BLEService* BLEServer::createService(const char* uuid) { * 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) { +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_LOGE(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.", + 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()); - m_semaphoreCreateEvt.give(); - return nullptr; } - BLEService* pService = new BLEService(uuid); + 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. @@ -92,13 +95,31 @@ BLEService* BLEServer::createService(BLEUUID uuid) { } // 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 &m_bleAdvertising; + return BLEDevice::getAdvertising(); } uint16_t BLEServer::getConnId() { @@ -119,41 +140,6 @@ uint16_t BLEServer::getGattsIf() { return m_gatts_if; } -/** - * @brief Handle a receiver GAP event. - * - * @param [in] event - * @param [in] param - */ -void BLEServer::handleGAPEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param) { - ESP_LOGD(LOG_TAG, "BLEServer ... handling GAP event!"); - switch(event) { - case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { - /* - esp_ble_adv_params_t adv_params; - adv_params.adv_int_min = 0x20; - adv_params.adv_int_max = 0x40; - adv_params.adv_type = ADV_TYPE_IND; - adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - adv_params.channel_map = ADV_CHNL_ALL; - adv_params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; - ESP_LOGD(tag, "Starting advertising"); - esp_err_t errRc = ::esp_ble_gap_start_advertising(&adv_params); - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gap_start_advertising: rc=%d %s", errRc, espToString(errRc)); - return; - } - */ - break; - } - default: - break; - } -} // handleGAPEvent - - /** * @brief Handle a GATT Server Event. @@ -163,125 +149,135 @@ void BLEServer::handleGAPEvent( * @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) { - +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()); - - // Invoke the handler for every Service we have. - m_serviceMap.handleGATTServerEvent(event, gatts_if, param); + 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 + // - uint16_t conn_id // - esp_bd_addr_t remote_bda - // - bool is_connected + // case ESP_GATTS_CONNECT_EVT: { - m_connId = param->connect.conn_id; // Save the connection id. + 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++; + m_connectedCount++; // Increment the number of connected devices count. break; } // ESP_GATTS_CONNECT_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(); - break; - } // ESP_GATTS_REG_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_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); + 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); - //pService->setHandle(param->create.service_handle); 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 + // - 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 + // - 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 + // - 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 - + // - uint16_t handle + // - uint16_t offset + // - bool need_rsp + // - bool is_prep + // - uint16_t len + // - uint8_t* value + // case ESP_GATTS_WRITE_EVT: { break; } - // ESP_GATTS_DISCONNECT_EVT - // 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--; - if (m_pServerCallbacks != nullptr) { - m_pServerCallbacks->onDisconnect(this); - } - startAdvertising(); + case ESP_GATTS_OPEN_EVT: + m_semaphoreOpenEvt.give(param->open.status); break; - } // ESP_GATTS_DISCONNECT_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: { + default: break; - } // ESP_GATTS_ADD_CHAR_EVT + } + // Invoke the handler for every Service we have. + m_serviceMap.handleGATTServerEvent(event, gatts_if, param); - default: { - break; - } - } ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent"); } // handleGATTServerEvent @@ -291,7 +287,7 @@ void BLEServer::handleGATTServerEvent( * * @return N/A */ -void BLEServer::registerApp() { +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); @@ -301,7 +297,7 @@ void BLEServer::registerApp() { /** - * @brief Set the callbacks. + * @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 @@ -313,6 +309,14 @@ 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. @@ -322,32 +326,105 @@ void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) { */ void BLEServer::startAdvertising() { ESP_LOGD(LOG_TAG, ">> startAdvertising"); - m_bleAdvertising.start(); + 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 BLEServer::addCharacteristic(BLECharacteristic *characteristic, BLEService *pService) { - ESP_LOGD(tag, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s, serviceHandle=0x%.2x", - characteristic->m_bleUUID.toString().c_str(), - pService->getHandle()); - m_characteristicMap.setByUUID(characteristic->m_bleUUID, characteristic); - esp_err_t errRc = ::esp_ble_gatts_add_char( - pService->getHandle(), - characteristic->getUUID().getNative(), - (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE), - characteristic->getProperties(), - &characteristic->m_value, - NULL); +void BLEServerCallbacks::onConnect(BLEServer* pServer) { + ESP_LOGD("BLEServerCallbacks", ">> onConnect(): Default"); + ESP_LOGD("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + ESP_LOGD("BLEServerCallbacks", "<< onConnect()"); +} // onConnect - if (errRc != ESP_OK) { - ESP_LOGE(tag, "esp_ble_gatts_add_char: rc=%d %s", errRc, espToString(errRc)); - return; +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); +} + +void BLEServer::disconnect(uint16_t connId){ + esp_ble_gatts_close(m_gatts_if, connId); +} #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEServer.h b/cpp_utils/BLEServer.h old mode 100644 new mode 100755 index c6307bc6..d2f8038d --- a/cpp_utils/BLEServer.h +++ b/cpp_utils/BLEServer.h @@ -13,14 +13,23 @@ #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; /** @@ -30,19 +39,21 @@ class BLEServiceMap { public: BLEService* getByHandle(uint16_t handle); BLEService* getByUUID(const char* uuid); - BLEService* getByUUID(BLEUUID uuid); - void handleGATTServerEvent( - esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t* param); + 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_uuidMap; std::map m_handleMap; + std::map m_uuidMap; + std::map::iterator m_iterator; }; @@ -51,38 +62,52 @@ class BLEServiceMap { */ class BLEServer { public: - BLEServer(); - - uint32_t getConnectedCount(); BLEService* createService(const char* uuid); - BLEService* createService(BLEUUID uuid); + BLEService* createService(BLEUUID uuid, uint32_t numHandles=15, uint8_t inst_id=0); BLEAdvertising* getAdvertising(); - void setCallbacks(BLEServerCallbacks *pCallbacks); + void setCallbacks(BLEServerCallbacks* pCallbacks); void startAdvertising(); + void removeService(BLEService* service); + BLEService* getServiceByUUID(const char* uuid); + BLEService* getServiceByUUID(BLEUUID uuid); + bool connect(BLEAddress address); + void disconnect(uint16_t connId); + 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; - uint16_t m_appId; - BLEAdvertising m_bleAdvertising; - uint16_t m_connId; - uint32_t m_connectedCount; - uint16_t m_gatts_if; - FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); + // 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; + BLEServerCallbacks* m_pServerCallbacks = nullptr; void createApp(uint16_t appId); - uint16_t getConnId(); uint16_t getGattsIf(); - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void registerApp(); + void registerApp(uint16_t); }; // BLEServer @@ -100,7 +125,7 @@ class BLEServerCallbacks { * @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. * @@ -112,6 +137,5 @@ class BLEServerCallbacks { }; // BLEServerCallbacks - #endif /* CONFIG_BT_ENABLED */ #endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */ diff --git a/cpp_utils/BLEServerCallbacks.cpp b/cpp_utils/BLEServerCallbacks.cpp deleted file mode 100644 index 88087209..00000000 --- a/cpp_utils/BLEServerCallbacks.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * BLEServerCallbacks.cpp - * - * Created on: Jul 4, 2017 - * Author: kolban - */ -#include "sdkconfig.h" -#if defined(CONFIG_BT_ENABLED) -#include "BLEServer.h" -#include -static const char* LOG_TAG = "BLEServerCallbacks"; - -void BLEServerCallbacks::onConnect(BLEServer* pServer) { - ESP_LOGD(LOG_TAG, ">> onConnect(): Default"); - ESP_LOGD(LOG_TAG, "<< onConnect()"); -} - -void BLEServerCallbacks::onDisconnect(BLEServer* pServer) { - ESP_LOGD(LOG_TAG, ">> onDisconnect(): Default"); - ESP_LOGD(LOG_TAG, "<< onDisconnect()"); -} -#endif /* CONFIG_BT_ENABLED */ diff --git a/cpp_utils/BLEService.cpp b/cpp_utils/BLEService.cpp index ec16db88..3034cf18 100644 --- a/cpp_utils/BLEService.cpp +++ b/cpp_utils/BLEService.cpp @@ -11,7 +11,6 @@ #if defined(CONFIG_BT_ENABLED) #include #include -#include #include #include @@ -22,28 +21,38 @@ #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) -static const char* LOG_TAG = "BLEService"; // Tag for logging. /** * @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) : BLEService(BLEUUID(uuid)) { +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) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pServer = nullptr; +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 @@ -53,17 +62,17 @@ BLEService::BLEService(BLEUUID uuid) { * @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()); +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; - esp_gatt_srvc_id_t srvc_id; - srvc_id.id.inst_id = 0; - srvc_id.id.uuid = *m_uuid.getNative(); - m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT - esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10); + 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)); @@ -71,11 +80,32 @@ void BLEService::executeCreate(BLEServer *pServer) { } 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. @@ -87,18 +117,6 @@ void BLEService::dump() { ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str()); } // dump -/* -void BLEService::setService(esp_gatt_srvc_id_t srvc_id) { - m_srvc_id = srvc_id; -} -*/ - -/* -esp_gatt_srvc_id_t BLEService::getService() { - return m_srvc_id; -} -*/ - /** * @brief Get the UUID of the service. @@ -126,10 +144,9 @@ void BLEService::start() { return; } - BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst(); - while(pCharacteristic != nullptr) { + while (pCharacteristic != nullptr) { m_lastCreatedCharacteristic = pCharacteristic; pCharacteristic->executeCreate(this); @@ -150,6 +167,32 @@ void BLEService::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. @@ -179,24 +222,24 @@ uint16_t BLEService::getHandle() { * @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). -// + // 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 (esp_ble_gatts_add_char): uuid=%s to service: %s", + 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_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID"); - return; + 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->getUUID(), pCharacteristic); + m_characteristicMap.setByUUID(pCharacteristic, pCharacteristic->getUUID()); ESP_LOGD(LOG_TAG, "<< addCharacteristic()"); } // addCharacteristic @@ -212,6 +255,7 @@ BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t p return createCharacteristic(BLEUUID(uuid), properties); } + /** * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. @@ -219,7 +263,7 @@ BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t p * @return The new BLE characteristic. */ BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) { - BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties); + BLECharacteristic* pCharacteristic = new BLECharacteristic(uuid, properties); addCharacteristic(pCharacteristic); return pCharacteristic; } // createCharacteristic @@ -228,14 +272,9 @@ BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t prope /** * @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. +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 @@ -246,23 +285,21 @@ void BLEService::handleGATTServerEvent( // for that characteristic. case ESP_GATTS_ADD_CHAR_EVT: { if (m_handle == param->add_char.service_handle) { - BLECharacteristic *pCharacteristic = getCharacteristic(BLEUUID(param->add_char.char_uuid)); + 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(); - m_semaphoreAddCharEvt.give(); break; } pCharacteristic->setHandle(param->add_char.attr_handle); m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic); - //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str()); - m_semaphoreAddCharEvt.give(); break; } // Reached the correct service. break; } // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_START_EVT // // start: @@ -275,6 +312,19 @@ void BLEService::handleGATTServerEvent( 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. @@ -289,18 +339,33 @@ void BLEService::handleGATTServerEvent( // * - bool is_primary // case ESP_GATTS_CREATE_EVT: { - if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid))) { + 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 - default: { + + // 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; - } // Default } // Switch + // Invoke the GATTS handler in each of the associated characteristics. m_characteristicMap.handleGATTServerEvent(event, gatts_if, param); } // handleGATTServerEvent diff --git a/cpp_utils/BLEService.h b/cpp_utils/BLEService.h index 86d0776b..b42d57f2 100644 --- a/cpp_utils/BLEService.h +++ b/cpp_utils/BLEService.h @@ -24,8 +24,8 @@ class BLEServer; */ class BLECharacteristicMap { public: - void setByUUID(const char* uuid, BLECharacteristic* pCharacteristic); - void setByUUID(BLEUUID uuid, BLECharacteristic* pCharacteristic); + 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); @@ -33,16 +33,12 @@ class BLECharacteristicMap { 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); - + 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_uuidMap; std::map m_handleMap; - std::map::iterator m_iterator; + std::map::iterator m_iterator; }; @@ -52,22 +48,25 @@ class BLECharacteristicMap { */ class BLEService { public: - BLEService(const char* uuid); - BLEService(BLEUUID uuid); - 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; @@ -76,20 +75,19 @@ class BLEService { BLECharacteristicMap m_characteristicMap; uint16_t m_handle; - BLECharacteristic* m_lastCreatedCharacteristic; - BLEServer* m_pServer; - //FreeRTOS::Semaphore m_serializeMutex; - FreeRTOS::Semaphore m_semaphoreAddCharEvt = FreeRTOS::Semaphore("AddCharEvt"); - FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + BLECharacteristic* m_lastCreatedCharacteristic = nullptr; + BLEServer* m_pServer = nullptr; BLEUUID m_uuid; - uint16_t getHandle(); + 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 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 diff --git a/cpp_utils/BLEServiceMap.cpp b/cpp_utils/BLEServiceMap.cpp old mode 100644 new mode 100755 index 8fdbd5ac..cf4f75f4 --- a/cpp_utils/BLEServiceMap.cpp +++ b/cpp_utils/BLEServiceMap.cpp @@ -17,18 +17,18 @@ * @return The characteristic. */ BLEService* BLEServiceMap::getByUUID(const char* uuid) { - return getByUUID(BLEUUID(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) { +BLEService* BLEServiceMap::getByUUID(BLEUUID uuid, uint8_t inst_id) { for (auto &myPair : m_uuidMap) { - if (myPair.second->getUUID().equals(uuid)) { - return myPair.second; + if (myPair.first->getUUID().equals(uuid)) { + return myPair.first; } } //return m_uuidMap.at(uuid.toString()); @@ -52,9 +52,8 @@ BLEService* BLEServiceMap::getByHandle(uint16_t handle) { * @param [in] characteristic The service to cache. * @return N/A. */ -void BLEServiceMap::setByUUID(BLEUUID uuid, - BLEService *service) { - m_uuidMap.insert(std::pair(uuid.toString(), service)); +void BLEServiceMap::setByUUID(BLEUUID uuid, BLEService* service) { + m_uuidMap.insert(std::pair(service, uuid.toString())); } // setByUUID @@ -64,9 +63,8 @@ void BLEServiceMap::setByUUID(BLEUUID uuid, * @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)); +void BLEServiceMap::setByHandle(uint16_t handle, BLEService* service) { + m_handleMap.insert(std::pair(handle, service)); } // setByHandle @@ -86,10 +84,51 @@ std::string BLEServiceMap::toString() { void BLEServiceMap::handleGATTServerEvent( esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) { + esp_ble_gatts_cb_param_t* param) { // Invoke the handler for every Service we have. for (auto &myPair : m_uuidMap) { - myPair.second->handleGATTServerEvent(event, gatts_if, param); + 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/cpp_utils/BLEUUID.cpp b/cpp_utils/BLEUUID.cpp index 9a4fe451..4ddf8fc2 100644 --- a/cpp_utils/BLEUUID.cpp +++ b/cpp_utils/BLEUUID.cpp @@ -6,13 +6,21 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) -#include #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 /** @@ -35,7 +43,8 @@ static const char* LOG_TAG = "BLEUUID"; * @param [in] size The number of bytes to copy */ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { - target+=(size-1); // Point target to the last byte of the target data + assert(size > 0); + target += (size - 1); // Point target to the last byte of the target data while (size > 0) { *target = *source; target--; @@ -64,42 +73,51 @@ static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) { */ BLEUUID::BLEUUID(std::string value) { m_valueSet = true; - if (value.length() == 2) { - m_uuid.len = ESP_UUID_LEN_16; - m_uuid.uuid.uuid16 = value[0] | (value[1] << 8); - } else if (value.length() == 4) { - m_uuid.len = ESP_UUID_LEN_32; - m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); - } else if (value.length() == 16) { + 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. + } + 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 vals[16]; - sscanf(value.c_str(), "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x", - &vals[15], - &vals[14], - &vals[13], - &vals[12], - &vals[11], - &vals[10], - &vals[9], - &vals[8], - &vals[7], - &vals[6], - &vals[5], - &vals[4], - &vals[3], - &vals[2], - &vals[1], - &vals[0] - ); - - int i; - for (i=0; i<16; i++) { - m_uuid.uuid.uuid128[i] = vals[i]; + 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 { @@ -127,9 +145,10 @@ BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) { } else { memcpy(m_uuid.uuid.uuid128, pData, 16); } - m_valueSet = true; + m_valueSet = true; } // BLEUUID + /** * @brief Create a UUID from the 16bit value. * @@ -166,11 +185,11 @@ BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { /** - * @brief Create a UUID from the ESP32 esp_gatt_srvc_id_t. + * @brief Create a UUID from the ESP32 esp_gat_id_t. * - * @param [in] srvcId The data to create the UUID from. + * @param [in] gattId The data to create the UUID from. */ -BLEUUID::BLEUUID(esp_gatt_srvc_id_t srcvId) : BLEUUID(srcvId.id.uuid) { +BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) { } // BLEUUID @@ -179,6 +198,26 @@ BLEUUID::BLEUUID() { } // 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. * @@ -187,9 +226,7 @@ BLEUUID::BLEUUID() { */ bool BLEUUID::equals(BLEUUID uuid) { //ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str()); - if (m_valueSet == false || uuid.m_valueSet == false) { - return false; - } + if (!m_valueSet || !uuid.m_valueSet) return false; if (uuid.m_uuid.len != m_uuid.len) { return uuid.toString() == toString(); @@ -207,6 +244,35 @@ bool BLEUUID::equals(BLEUUID uuid) { } // 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. * @@ -233,7 +299,7 @@ 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 == false || m_uuid.len == ESP_UUID_LEN_128) { + if (!m_valueSet || m_uuid.len == ESP_UUID_LEN_128) { return *this; } @@ -277,50 +343,65 @@ BLEUUID BLEUUID::to128() { } // to128 -//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 + /** * @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 == false) { - return ""; - } - - if (m_uuid.len == ESP_UUID_LEN_16) { - std::stringstream ss; - ss << "0000" << std::hex << std::setfill('0') << std::setw(4) << m_uuid.uuid.uuid16 << "-0000-1000-8000-00805f9b34fb"; - return ss.str(); - } - - if (m_uuid.len == ESP_UUID_LEN_32) { - std::stringstream ss; - ss << std::hex << std::setfill('0') << std::setw(8) << m_uuid.uuid.uuid32 << "-0000-1000-8000-00805f9b34fb"; - return ss.str(); - } + 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]; + 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/cpp_utils/BLEUUID.h b/cpp_utils/BLEUUID.h index c3647965..700739be 100644 --- a/cpp_utils/BLEUUID.h +++ b/cpp_utils/BLEUUID.h @@ -22,16 +22,18 @@ class BLEUUID { BLEUUID(uint32_t uuid); BLEUUID(esp_bt_uuid_t uuid); BLEUUID(uint8_t* pData, size_t size, bool msbFirst); - BLEUUID(esp_gatt_srvc_id_t srcvId); + 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; - bool m_valueSet; + 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/cpp_utils/BLEUtils.cpp b/cpp_utils/BLEUtils.cpp index c061658b..a9b735df 100644 --- a/cpp_utils/BLEUtils.cpp +++ b/cpp_utils/BLEUtils.cpp @@ -6,15 +6,15 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) +#include "BLEAddress.h" +#include "BLEClient.h" #include "BLEUtils.h" #include "BLEUUID.h" -#include "BLEClient.h" -#include "BLEAddress.h" #include "GeneralUtils.h" #include #include -#include // ESP32 BLE +#include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE #include // ESP32 BLE @@ -24,21 +24,527 @@ #include #include -static const char* LOG_TAG = "BLEUtils"; +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-log.h" +#endif + +static const char* LOG_TAG = "BLEUtils"; // Tag for logging. /* -static std::map g_addressMap; -static std::map g_connIdMap; +static std::map g_addressMap; +static std::map g_connIdMap; */ typedef struct { - uint32_t assignedNumber; - std::string name; + 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 characteristicMap_t g_characteristicsMappings[] = { - {0x2a00, "Device Name"}, - {0x2a01, "Appearance"}, +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, ""} }; @@ -46,9 +552,9 @@ static characteristicMap_t g_characteristicsMappings[] = { * @brief Mapping from service ids to names */ typedef struct { - std::string name; - std::string type; - uint32_t assignedNumber; + const char* name; + const char* type; + uint32_t assignedNumber; } gattService_t; @@ -56,6 +562,7 @@ typedef struct { * 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}, @@ -91,9 +598,11 @@ static const gattService_t g_gattServices[] = { {"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. @@ -127,7 +636,7 @@ static std::string gattIdToString(esp_gatt_id_t gattId) { * @brief Convert an esp_ble_addr_type_t to a string representation. */ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { - switch(type) { + switch (type) { case BLE_ADDR_TYPE_PUBLIC: return "BLE_ADDR_TYPE_PUBLIC"; case BLE_ADDR_TYPE_RANDOM: @@ -137,11 +646,37 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { case BLE_ADDR_TYPE_RPA_RANDOM: return "BLE_ADDR_TYPE_RPA_RANDOM"; default: - return "Unknown addr_t"; + 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. * @@ -151,60 +686,60 @@ const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) { * @return A string representation of the type. */ const char* BLEUtils::advTypeToString(uint8_t advType) { - switch(advType) { - case ESP_BLE_AD_TYPE_FLAG: + switch (advType) { + case ESP_BLE_AD_TYPE_FLAG: // 0x01 return "ESP_BLE_AD_TYPE_FLAG"; - case ESP_BLE_AD_TYPE_16SRV_PART: + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 return "ESP_BLE_AD_TYPE_16SRV_PART"; - case ESP_BLE_AD_TYPE_16SRV_CMPL: + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 return "ESP_BLE_AD_TYPE_16SRV_CMPL"; - case ESP_BLE_AD_TYPE_32SRV_PART: + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 return "ESP_BLE_AD_TYPE_32SRV_PART"; - case ESP_BLE_AD_TYPE_32SRV_CMPL: + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 return "ESP_BLE_AD_TYPE_32SRV_CMPL"; - case ESP_BLE_AD_TYPE_128SRV_PART: + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 return "ESP_BLE_AD_TYPE_128SRV_PART"; - case ESP_BLE_AD_TYPE_128SRV_CMPL: + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 return "ESP_BLE_AD_TYPE_128SRV_CMPL"; - case ESP_BLE_AD_TYPE_NAME_SHORT: + case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 return "ESP_BLE_AD_TYPE_NAME_SHORT"; - case ESP_BLE_AD_TYPE_NAME_CMPL: + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 return "ESP_BLE_AD_TYPE_NAME_CMPL"; - case ESP_BLE_AD_TYPE_TX_PWR: + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a return "ESP_BLE_AD_TYPE_TX_PWR"; - case ESP_BLE_AD_TYPE_DEV_CLASS: + case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b return "ESP_BLE_AD_TYPE_DEV_CLASS"; - case ESP_BLE_AD_TYPE_SM_TK: + case ESP_BLE_AD_TYPE_SM_TK: // 0x10 return "ESP_BLE_AD_TYPE_SM_TK"; - case ESP_BLE_AD_TYPE_SM_OOB_FLAG: + case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; - case ESP_BLE_AD_TYPE_INT_RANGE: + case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 return "ESP_BLE_AD_TYPE_INT_RANGE"; - case ESP_BLE_AD_TYPE_SOL_SRV_UUID: + 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: + case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_SERVICE_DATA: + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 return "ESP_BLE_AD_TYPE_SERVICE_DATA"; - case ESP_BLE_AD_TYPE_PUBLIC_TARGET: + case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; - case ESP_BLE_AD_TYPE_RANDOM_TARGET: - return "ESP_BLE_Amap1D_TYPE_RANDOM_TARGET"; - case ESP_BLE_AD_TYPE_APPEARANCE: + 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: + 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: + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; - case ESP_BLE_AD_TYPE_128SERVICE_DATA: + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; default: - ESP_LOGD(LOG_TAG, "Unknown adv data type: 0x%x", advType); - return "Unknown"; + ESP_LOGV(LOG_TAG, " adv data type: 0x%x", advType); + return ""; } // End switch } // advTypeToString @@ -216,8 +751,7 @@ esp_gatt_id_t BLEUtils::buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id) { return retGattId; } -esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, - bool is_primary) { +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; @@ -232,30 +766,26 @@ esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, * @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; - } - +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); + target = (uint8_t*) malloc(length * 2 + 1); if (target == nullptr) { ESP_LOGE(LOG_TAG, "buildHexData: malloc failed"); return nullptr; } } - char *startOfData = (char *)target; + char* startOfData = (char*) target; - int i; - for (i=0; i 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_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status); + 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_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); + 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_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); + 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_LOGD(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]", + 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, @@ -533,43 +1093,57 @@ void BLEUtils::dumpGapEvent( 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_LOGD(LOG_TAG, "[bd_addr: %s, passkey: %d]", + 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_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status); + 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: @@ -582,10 +1156,12 @@ void BLEUtils::dumpGapEvent( // - ble_adv // - flag // - num_resps + // - adv_data_len + // - scan_rsp_len case ESP_GAP_BLE_SCAN_RESULT_EVT: { - switch(param->scan_rst.search_evt) { + switch (param->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: { - ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d, num_resps: %d, adv_data_len: %d, scan_rsp_len: %d", + 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), @@ -593,6 +1169,7 @@ void BLEUtils::dumpGapEvent( 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 @@ -601,42 +1178,77 @@ void BLEUtils::dumpGapEvent( } // ESP_GAP_SEARCH_INQ_RES_EVT default: { - ESP_LOGD(LOG_TAG, "search_evt: %s",searchEventTypeToString(param->scan_rst.search_evt)); + 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 // - // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT - // - case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status); + // 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_START_COMPLETE_EVT + } // 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 // - // ESP_GAP_BLE_SEC_REQ_EVT - // - case ESP_GAP_BLE_SEC_REQ_EVT: { - ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str()); + // 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_SEC_REQ_EVT - + } // 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_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status); + 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_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); + ESP_LOGV(LOG_TAG, "*** dumpGapEvent: Logger not coded ***"); break; } // default } // switch @@ -650,14 +1262,14 @@ void BLEUtils::dumpGapEvent( * @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_LOGD(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str()); - switch(event) { - // + 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: @@ -665,16 +1277,14 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda // - esp_gatt_conn_reason_t reason - // case ESP_GATTC_CLOSE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]", + 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: @@ -682,31 +1292,28 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t conn_id // - esp_bd_addr_t remote_bda case ESP_GATTC_CONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]", - BLEUtils::gattStatusToString(evtParam->connect.status).c_str(), + 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_status_t status - // - uint16_t conn_id - // - esp_bd_addr_t remote_bda + // - esp_gatt_conn_reason_t reason + // - uint16_t conn_id + // - esp_bd_addr_t remote_bda case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]", - BLEUtils::gattStatusToString(evtParam->disconnect.status).c_str(), + 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: @@ -715,7 +1322,7 @@ void BLEUtils::dumpGattClientEvent( // - 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 @@ -725,7 +1332,7 @@ void BLEUtils::dumpGattClientEvent( if (evtParam->get_char.char_id.uuid.len == ESP_UUID_LEN_16) { description = BLEUtils::gattCharacteristicUUIDToString(evtParam->get_char.char_id.uuid.uuid.uuid16); } - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s, char_id: %s [description: %s]\nchar_prop: %s]", + 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(), @@ -734,7 +1341,7 @@ void BLEUtils::dumpGattClientEvent( BLEUtils::characteristicPropertiesToString(evtParam->get_char.char_prop).c_str() ); } else { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s]", + 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() @@ -742,9 +1349,30 @@ void BLEUtils::dumpGattClientEvent( } 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: @@ -754,7 +1382,7 @@ void BLEUtils::dumpGattClientEvent( // - uint16_t mtu // case ESP_GATTC_OPEN_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, remote_bda: %s, mtu: %d]", + 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(), @@ -762,8 +1390,6 @@ void BLEUtils::dumpGattClientEvent( break; } // ESP_GATTC_OPEN_EVT - - // // ESP_GATTC_READ_CHAR_EVT // // Callback to indicate that requested data that we wanted to read is now available. @@ -771,125 +1397,103 @@ void BLEUtils::dumpGattClientEvent( // read: // esp_gatt_status_t status // uint16_t conn_id - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // uint16_t handle // uint8_t* value // uint16_t value_type // uint16_t value_len case ESP_GATTC_READ_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_type: 0x%x, value_len: %d]", + 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, - BLEUtils::gattServiceIdToString(evtParam->read.srvc_id).c_str(), - gattIdToString(evtParam->read.char_id).c_str(), - gattIdToString(evtParam->read.descr_id).c_str(), - evtParam->read.value_type, + 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_LOGD(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str()); + 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_LOGD(LOG_TAG, "[status: %s, app_id: 0x%x]", + 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 - // - esp_gatt_srvc_id_t srvc_id - // - esp_gatt_id_t char_id + // - uint16_t handle case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, srvc_id: <%s>, char_id: <%s>]", + ESP_LOGV(LOG_TAG, "[status: %s, handle: %d 0x%.2x]", BLEUtils::gattStatusToString(evtParam->reg_for_notify.status).c_str(), - BLEUtils::gattServiceIdToString(evtParam->reg_for_notify.srvc_id).c_str(), - gattIdToString(evtParam->reg_for_notify.char_id).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_LOGD(LOG_TAG, "[status: %s, conn_id: %d]", + 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 - // - esp_gatt_srvc_id_t srvc_id - // + // - uint16_t conn_id + // - uint16_t start_handle + // - uint16_t end_handle + // - esp_gatt_id_t srvc_id case ESP_GATTC_SEARCH_RES_EVT: { - std::string name = ""; - if (evtParam->search_res.srvc_id.id.uuid.len == ESP_UUID_LEN_16) { - name = BLEUtils::gattServiceToString(evtParam->search_res.srvc_id.id.uuid.uuid.uuid16); - } - if (name.length() == 0) { - name = ""; - } - - ESP_LOGD(LOG_TAG, "[srvc_id: %s [%s], instanceId: 0x%.2x conn_id: %d]", - BLEUtils::gattServiceIdToString(evtParam->search_res.srvc_id).c_str(), - name.c_str(), - evtParam->search_res.srvc_id.id.inst_id, - evtParam->search_res.conn_id); + 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 - // esp_gatt_srvc_id_t srvc_id - // esp_gatt_id_t char_id - // esp_gatt_id_t descr_id + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + // - uint16_t offset case ESP_GATTC_WRITE_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>]", + 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, - BLEUtils::gattServiceIdToString(evtParam->write.srvc_id).c_str(), - gattIdToString(evtParam->write.char_id).c_str(), - gattIdToString(evtParam->write.descr_id).c_str() + evtParam->write.handle, + evtParam->write.handle, + evtParam->write.offset ); break; - } - + } // ESP_GATTC_WRITE_CHAR_EVT +#endif default: break; } @@ -907,27 +1511,42 @@ void BLEUtils::dumpGattClientEvent( * @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_LOGD(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); - switch(event) { + 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_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]", + 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.char_uuid).toString().c_str()); + BLEUUID(evtParam->add_char_descr.descr_uuid).toString().c_str()); break; } // ESP_GATTS_ADD_CHAR_DESCR_EVT case ESP_GATTS_ADD_CHAR_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]", - gattStatusToString(evtParam->add_char.status).c_str(), - evtParam->add_char.attr_handle, - evtParam->add_char.service_handle, - BLEUUID(evtParam->add_char.char_uuid).toString().c_str()); + 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 @@ -937,9 +1556,8 @@ void BLEUtils::dumpGattServerEvent( // 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, "[status: %s, conn_id: 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, conn_id: 0x%.2x]", gattStatusToString(evtParam->conf.status).c_str(), evtParam->conf.conn_id); break; @@ -947,33 +1565,32 @@ void BLEUtils::dumpGattServerEvent( case ESP_GATTS_CONGEST_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, congested: %d]", + 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_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, - BLEAddress(evtParam->connect.remote_bda).toString().c_str(), - evtParam->connect.is_connected); + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; } // ESP_GATTS_CONNECT_EVT case ESP_GATTS_CREATE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x, service_id: [%s]]", + 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_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]", + ESP_LOGV(LOG_TAG, "[conn_id: %d, remote_bda: %s]", evtParam->connect.conn_id, - BLEAddress(evtParam->connect.remote_bda).toString().c_str(), - evtParam->connect.is_connected); + BLEAddress(evtParam->connect.remote_bda).toString().c_str()); break; } // ESP_GATTS_DISCONNECT_EVT @@ -984,26 +1601,25 @@ void BLEUtils::dumpGattServerEvent( // - 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) { + switch (evtParam->exec_write.exec_write_flag) { case ESP_GATT_PREP_WRITE_EXEC: { - pWriteFlagText = (char*)"WRITE"; + pWriteFlagText = (char*) "WRITE"; break; } case ESP_GATT_PREP_WRITE_CANCEL: { - pWriteFlagText = (char*)"CANCEL"; + pWriteFlagText = (char*) "CANCEL"; break; } default: - pWriteFlagText = (char*)""; + pWriteFlagText = (char*) ""; break; } - ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]", + 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(), @@ -1014,14 +1630,14 @@ void BLEUtils::dumpGattServerEvent( case ESP_GATTS_MTU_EVT: { - ESP_LOGD(LOG_TAG, "[conn_id: %d, mtu: %d]", + 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_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, is_long: %d, need_rsp:%d]", + 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(), @@ -1032,14 +1648,14 @@ void BLEUtils::dumpGattServerEvent( } // ESP_GATTS_READ_EVT case ESP_GATTS_RESPONSE_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, handle: 0x%.2x]", + 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_LOGD(LOG_TAG, "[status: %s, app_id: %d]", + ESP_LOGV(LOG_TAG, "[status: %s, app_id: %d]", gattStatusToString(evtParam->reg.status).c_str(), evtParam->reg.app_id); break; @@ -1051,9 +1667,8 @@ void BLEUtils::dumpGattServerEvent( // start: // - esp_gatt_status_t status // - uint16_t service_handle - // case ESP_GATTS_START_EVT: { - ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", + ESP_LOGV(LOG_TAG, "[status: %s, service_handle: 0x%.2x]", gattStatusToString(evtParam->start.status).c_str(), evtParam->start.service_handle); break; @@ -1072,9 +1687,8 @@ void BLEUtils::dumpGattServerEvent( // - 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_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]", + 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(), @@ -1083,14 +1697,14 @@ void BLEUtils::dumpGattServerEvent( evtParam->write.need_rsp, evtParam->write.is_prep, evtParam->write.len); - char *pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len); - ESP_LOGD(LOG_TAG, "[Data: %s]", pHex); + 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_LOGD(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); + ESP_LOGV(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***"); break; } } // dumpGattServerEvent @@ -1102,7 +1716,7 @@ void BLEUtils::dumpGattServerEvent( * @return The event type as a string. */ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { - switch(eventType) { + switch (eventType) { case ESP_BLE_EVT_CONN_ADV: return "ESP_BLE_EVT_CONN_ADV"; case ESP_BLE_EVT_CONN_DIR_ADV: @@ -1114,7 +1728,7 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { case ESP_BLE_EVT_SCAN_RSP: return "ESP_BLE_EVT_SCAN_RSP"; default: - ESP_LOGD(LOG_TAG, "Unknown esp_ble_evt_type_t: %d", eventType); + ESP_LOGV(LOG_TAG, "Unknown esp_ble_evt_type_t: %d (0x%.2x)", eventType, eventType); return "*** Unknown ***"; } } // eventTypeToString @@ -1127,13 +1741,39 @@ const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) { * @return A string representation of the event type. */ const char* BLEUtils::gapEventToString(uint32_t eventType) { - switch(eventType) { - case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"; + switch (eventType) { 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: @@ -1144,40 +1784,30 @@ const char* BLEUtils::gapEventToString(uint32_t eventType) { 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_AUTH_CMPL_EVT: /* Authentication complete indication. */ - return "ESP_GAP_BLE_AUTH_CMPL_EVT"; - case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */ - return "ESP_GAP_BLE_KEY_EVT"; - case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */ - return "ESP_GAP_BLE_SEC_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_OOB_REQ_EVT: /* OOB request event */ - return "ESP_GAP_BLE_OOB_REQ_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_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */ - return "ESP_GAP_BLE_ADV_STOP_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"; default: - ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type 0x%x", eventType); + 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) { - characteristicMap_t *p = g_characteristicsMappings; - while (p->name.length() > 0) { + const characteristicMap_t* p = g_characteristicsMappings; + while (strlen(p->name) > 0) { if (p->assignedNumber == characteristicUUID) { - return p->name; + return std::string(p->name); } p++; } @@ -1185,6 +1815,39 @@ std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID } // 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. */ @@ -1194,10 +1857,10 @@ std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) { std::string BLEUtils::gattServiceToString(uint32_t serviceId) { - gattService_t* p = (gattService_t *)g_gattServices; - while (p->name.length() > 0) { + gattService_t* p = (gattService_t*) g_gattServices; + while (strlen(p->name) > 0) { if (p->assignedNumber == serviceId) { - return p->name; + return std::string(p->name); } p++; } @@ -1212,7 +1875,7 @@ std::string BLEUtils::gattServiceToString(uint32_t serviceId) { * @return A string representation of the status. */ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { - switch(status) { + switch (status) { case ESP_GATT_OK: return "ESP_GATT_OK"; case ESP_GATT_INVALID_HANDLE: @@ -1287,23 +1950,44 @@ std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) { 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"; 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) { + switch (searchEvt) { case ESP_GAP_SEARCH_INQ_RES_EVT: return "ESP_GAP_SEARCH_INQ_RES_EVT"; case ESP_GAP_SEARCH_INQ_CMPL_EVT: @@ -1319,7 +2003,7 @@ const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT: return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT"; default: - ESP_LOGD(LOG_TAG, "Unknown event type: 0x%x", searchEvt); + ESP_LOGV(LOG_TAG, "Unknown event type: 0x%x", searchEvt); return "Unknown event type"; } } // searchEventTypeToString diff --git a/cpp_utils/BLEUtils.h b/cpp_utils/BLEUtils.h index 28c7a7ef..7981691c 100644 --- a/cpp_utils/BLEUtils.h +++ b/cpp_utils/BLEUtils.h @@ -20,40 +20,43 @@ */ class BLEUtils { public: - static const char* advTypeToString(uint8_t advType); - 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 std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop); - static char* buildHexData(uint8_t *target, uint8_t *source, uint8_t length); - static BLEClient* findByConnId(uint16_t conn_id); - static BLEClient* findByAddress(BLEAddress address); - static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType); - static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType); - static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId); - static std::string gattStatusToString(esp_gatt_status_t status); - static std::string gattServiceToString(uint32_t serviceId); - static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason); - static void registerByAddress(BLEAddress address, BLEClient* pDevice); - static void registerByConnId(uint16_t conn_id, BLEClient* pDevice); - static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID); - static std::string buildPrintData(uint8_t* source, size_t length); + 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_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_gatts_cb_event_t event, + esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* evtParam); - static const char* devTypeToString(esp_bt_dev_type_t type); - static void dumpGapEvent( - esp_gap_ble_cb_event_t event, - esp_ble_gap_cb_param_t* param); + 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); - static const char* addressTypeToString(esp_ble_addr_type_t type); - static const char *eventTypeToString(esp_ble_evt_type_t eventType); }; #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEValue.cpp b/cpp_utils/BLEValue.cpp index 1989993a..ec1e61f5 100644 --- a/cpp_utils/BLEValue.cpp +++ b/cpp_utils/BLEValue.cpp @@ -6,12 +6,17 @@ */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) - -#include - #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 = ""; @@ -39,7 +44,7 @@ void BLEValue::addPart(std::string part) { */ void BLEValue::addPart(uint8_t* pData, size_t length) { ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length); - m_accumulation += std::string((char *)pData, length); + m_accumulation += std::string((char*) pData, length); } // addPart @@ -62,15 +67,31 @@ void BLEValue::cancel() { void BLEValue::commit() { ESP_LOGD(LOG_TAG, ">> commit"); // If there is nothing to commit, do nothing. - if (m_accumulation.length() == 0) { - return; - } + 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. @@ -111,6 +132,8 @@ void BLEValue::setValue(std::string 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); + m_value = std::string((char*) pData, length); } // setValue + + #endif // CONFIG_BT_ENABLED diff --git a/cpp_utils/BLEValue.h b/cpp_utils/BLEValue.h index a292c6e1..5df904c1 100644 --- a/cpp_utils/BLEValue.h +++ b/cpp_utils/BLEValue.h @@ -17,11 +17,13 @@ class BLEValue { public: BLEValue(); - void addPart(std::string part); - void addPart(uint8_t* pData, size_t length); - void cancel(); - void commit(); - uint16_t getReadOffset(); + 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); @@ -31,6 +33,7 @@ class BLEValue { 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/cpp_utils/BLEXML/Characteristics/code/run.sh b/cpp_utils/BLEXML/Characteristics/code/run.sh index 77f193fe..f0c7930a 100755 --- a/cpp_utils/BLEXML/Characteristics/code/run.sh +++ b/cpp_utils/BLEXML/Characteristics/code/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" RESULT=characteristics.json COUNT=0 @@ -17,4 +17,4 @@ do COUNT=$(expr ${COUNT} + 1) done echo -e "\n]\n" >> ${RESULT} -echo "done" \ No newline at end of file +echo "done" diff --git a/cpp_utils/BLEXML/Services/code/run.sh b/cpp_utils/BLEXML/Services/code/run.sh index bfa084d0..6716a53a 100755 --- a/cpp_utils/BLEXML/Services/code/run.sh +++ b/cpp_utils/BLEXML/Services/code/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/bash BASE_URL="https://www.bluetooth.com/api/gatt/XmlFile?xmlFileName=" RESULT=services.json COUNT=0 @@ -17,4 +17,4 @@ do COUNT=$(expr ${COUNT} + 1) done echo -e "\n]\n" >> ${RESULT} -echo "done" \ No newline at end of file +echo "done" diff --git a/cpp_utils/CMakeLists.txt b/cpp_utils/CMakeLists.txt new file mode 100644 index 00000000..cb9b7267 --- /dev/null +++ b/cpp_utils/CMakeLists.txt @@ -0,0 +1,20 @@ +# Edit following two lines to set component requirements (see docs) +set(COMPONENT_REQUIRES + "console" + "fatfs" + "json" + "mdns" + "nvs_flash" +) +set(COMPONENT_PRIV_REQUIRES ) + +file(GLOB COMPONENT_SRCS + LIST_DIRECTORIES false + "*.h" + "*.cpp" + "*.c" + "*.S" +) +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/cpp_utils/CPPNVS.cpp b/cpp_utils/CPPNVS.cpp index 734cc170..18914ab5 100644 --- a/cpp_utils/CPPNVS.cpp +++ b/cpp_utils/CPPNVS.cpp @@ -6,23 +6,35 @@ */ #include "CPPNVS.h" - +#include "GeneralUtils.h" +#include +#include +#include #include +static const char LOG_TAG[] = "CPPNVS"; + /** * @brief Constructor. * * @param [in] name The namespace to open for access. - * @param [in] openMode + * @param [in] openMode The open mode. One of NVS_READWRITE (default) or NVS_READONLY. */ NVS::NVS(std::string name, nvs_open_mode openMode) { + esp_err_t errRc = ::nvs_flash_init(); // Initialize flash + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } m_name = name; - nvs_open(name.c_str(), openMode, &m_handle); + ::nvs_open(name.c_str(), openMode, &m_handle); } // NVS +/** + * @brief Desctructor + */ NVS::~NVS() { - nvs_close(m_handle); + ::nvs_close(m_handle); } // ~NVS @@ -30,7 +42,7 @@ NVS::~NVS() { * @brief Commit any work performed in the namespace. */ void NVS::commit() { - nvs_commit(m_handle); + ::nvs_commit(m_handle); } // commit @@ -38,7 +50,7 @@ void NVS::commit() { * @brief Erase ALL the keys in the namespace. */ void NVS::erase() { - nvs_erase_all(m_handle); + ::nvs_erase_all(m_handle); } // erase @@ -48,32 +60,89 @@ void NVS::erase() { * @param [in] key The key to erase from the namespace. */ void NVS::erase(std::string key) { - nvs_erase_key(m_handle, key.c_str()); + ::nvs_erase_key(m_handle, key.c_str()); } // erase /** - * @brief Retrieve a string value by key. + * @brief Retrieve a string/blob value by key. * * @param [in] key The key to read from the namespace. * @param [out] result The string read from the %NVS storage. */ -void NVS::get(std::string key, std::string* result) { +int NVS::get(std::string key, std::string* result, bool isBlob) { size_t length; - nvs_get_str(m_handle, key.c_str(), NULL, &length); - char *data = (char *)malloc(length); - nvs_get_str(m_handle, key.c_str(), data, &length); + if (isBlob) { + esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), NULL, &length); + if (rc != ESP_OK) { + ESP_LOGI(LOG_TAG, "Error getting key: %i", rc); + return rc; + } + } else { + esp_err_t rc = ::nvs_get_str(m_handle, key.c_str(), NULL, &length); + if (rc != ESP_OK) { + ESP_LOGI(LOG_TAG, "Error getting key: %i", rc); + return rc; + } + } + char* data = (char*) malloc(length); + if (isBlob) { + ::nvs_get_blob(m_handle, key.c_str(), data, &length); + } else { + ::nvs_get_str(m_handle, key.c_str(), data, &length); + } *result = std::string(data); free(data); + return ESP_OK; } // get +int NVS::get(std::string key, uint32_t& value) { + return ::nvs_get_u32(m_handle, key.c_str(), &value); +} // get - uint32_t + + +int NVS::get(std::string key, uint8_t* result, size_t& length) { + ESP_LOGD(LOG_TAG, ">> get: key: %s, blob: inputSize: %d", key.c_str(), length); + esp_err_t rc = ::nvs_get_blob(m_handle, key.c_str(), result, &length); + if (rc != ESP_OK) { + ESP_LOGD(LOG_TAG, "nvs_get_blob: %d", rc); + } + ESP_LOGD(LOG_TAG, "<< get: outputSize: %d", length); + return rc; +} // get - blob + + + /** - * @brief Set the string value by key. + * @brief Set the string/blob value by key. * * @param [in] key The key to set from the namespace. * @param [in] data The value to set for the key. */ -void NVS::set(std::string key, std::string data) { - nvs_set_str(m_handle, key.c_str(), data.c_str()); +void NVS::set(std::string key, std::string data, bool isBlob) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, string: value=%s", key.c_str(), data.c_str()); + if (isBlob) { + ::nvs_set_blob(m_handle, key.c_str(), data.data(), data.length()); + } else { + ::nvs_set_str(m_handle, key.c_str(), data.c_str()); + } + ESP_LOGD(LOG_TAG, "<< set"); } // set + + +void NVS::set(std::string key, uint32_t value) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, u32: value=%d", key.c_str(), value); + ::nvs_set_u32(m_handle, key.c_str(), value); + ESP_LOGD(LOG_TAG, "<< set"); +} // set - uint32_t + + +void NVS::set(std::string key, uint8_t* data, size_t length) { + ESP_LOGD(LOG_TAG, ">> set: key: %s, blob: length=%d", key.c_str(), length); + esp_err_t rc = ::nvs_set_blob(m_handle, key.c_str(), data, length); + if (rc != ESP_OK) { + ESP_LOGD(LOG_TAG, "nvs_get_blob: %d", rc); + } + ESP_LOGD(LOG_TAG, "<< set"); +} // set (BLOB) diff --git a/cpp_utils/CPPNVS.h b/cpp_utils/CPPNVS.h index 6802ff99..9155891b 100644 --- a/cpp_utils/CPPNVS.h +++ b/cpp_utils/CPPNVS.h @@ -21,11 +21,17 @@ class NVS { void erase(); void erase(std::string key); - void get(std::string key, std::string *result); - void set(std::string key, std::string data); + int get(std::string key, std::string* result, bool isBlob = false); + int get(std::string key, uint8_t* result, size_t& length); + int get(std::string key, uint32_t& value); + void set(std::string key, std::string data, bool isBlob = false); + void set(std::string key, uint32_t value); + void set(std::string key, uint8_t* data, size_t length); + private: std::string m_name; nvs_handle m_handle; + }; #endif /* COMPONENTS_CPP_UTILS_CPPNVS_H_ */ diff --git a/cpp_utils/Console.cpp b/cpp_utils/Console.cpp new file mode 100644 index 00000000..c0f5655f --- /dev/null +++ b/cpp_utils/Console.cpp @@ -0,0 +1,211 @@ +/* + * Console.cpp + * + * Created on: Jun 15, 2018 + * Author: kolban + */ + +/** + * Example: + * Argtable argtable; + * argtable.addString("myparam", "l", "list", "Get Listings"); + * argtable.parse(argc, argv); + * ArgTableEntry_String* pStr = (ArgTableEntry_String*)argTable.get("myparam"); + * pStr->getValue(); + */ +#include "Console.h" + +/** + * Argtable instance constructor. + */ +ArgTable::ArgTable() { + m_argtable = nullptr; + m_argEnd = nullptr; +} // ArgTable#ArgTable + + +/** + * Argtable instance destructor. + */ +ArgTable::~ArgTable() { + freeArgtable(); // Release any resources associated with the argtable. +} // ArgTable#~ArgTable + + +/** + * Build the ArgTable that will be used for parsing. + */ +/* private */ void ArgTable::build() { + /* + * The m_argTableEntries is a std::list that contains the argtable entries in the form of a std::pair. We + * allocate storage for the ArgTable and then populate it. The last entry of the argtable must be an end marker. + */ + int size = m_argTableEntries.size(); + m_argtable = new void*[size + 1]; + int i = 0; + for (auto it = m_argTableEntries.begin(); it != m_argTableEntries.end(); ++it) { + m_argtable[i] = it->second->getEntry(); + i++; + } + m_argEnd = arg_end(10); + m_argtable[i] = m_argEnd; +} // ArgTable#build + + +ArgTableEntry_Date ArgTable::addDate(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Date* pDate = new ArgTableEntry_Date(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pDate)); + return *pDate; +} // ArgTable#addDate + + +ArgTableEntry_Double ArgTable::addDouble(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Double* pDouble = new ArgTableEntry_Double(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pDouble)); + return *pDouble; +} // ArgTable#addDouble + + +ArgTableEntry_File ArgTable::addFile(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_File* pFile = new ArgTableEntry_File(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pFile)); + return *pFile; +} // ArgTable#addFile + + +ArgTableEntry_Int ArgTable::addInt(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Int* pInt = new ArgTableEntry_Int(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pInt)); + return *pInt; +} // ArgTable#addInt + + +ArgTableEntry_Lit ArgTable::addLit(std::string name, std::string shortopts, std::string longopts, std::string glossary) { + ArgTableEntry_Lit* pLit = new ArgTableEntry_Lit(shortopts, longopts, glossary); + m_argTableEntries.push_back(std::make_pair(name, pLit)); + return *pLit; +} // ArgTable#addLit + + +ArgTableEntry_String ArgTable::addString(std::string name, std::string shortopts, std::string longopts, std::string glossary, int min, int max) { + ArgTableEntry_String* pStr = new ArgTableEntry_String(shortopts, longopts, glossary, min, max); + m_argTableEntries.push_back(std::make_pair(name, pStr)); + return *pStr; +} // ArgTable#addString + + +/** + * Parse the input and output parameters against this argtable. + * @param argc A count of the number of parameters. + * @param argv An array of string parameters. + * @return The number of errors detected. + */ +int ArgTable::parse(int argc, char* argv[]) { + if (m_argtable == nullptr) { // If we don't have an argtable, build it. + build(); + } + int nErrors = arg_parse(argc, argv, m_argtable); + return nErrors; +} // ArgTable#parse + + +/** + * Print any errors associated with the parsing. + */ +void ArgTable::printErrors(FILE* fp, std::string progName) { + if (m_argEnd != nullptr) { + arg_print_errors(fp, m_argEnd, progName.c_str()); + } +} // ArgTable#printErrors + + +/** + * Release the argtable data. + */ +/* private */void ArgTable::freeArgtable() { + if (m_argtable != nullptr) { + arg_free(m_argtable); + m_argtable = nullptr; + m_argEnd = nullptr; + } +} // ArgTable#freeArgtable + + +ArgTableEntry_Date::ArgTableEntry_Date(std::string shortopts, std::string longopts, std::string glossary) { + m_argDate = arg_daten(shortopts.c_str(), longopts.c_str(), "", "", 0, 1, glossary.c_str()); + m_type = ArgType_t::DATE; +} // ArgTableEntry_Date#ArgTableEntry_Date + + +int ArgTableEntry_Date::getCount() { + return m_argDate->count; +} // ArgTableEntry_Date#getCount + + +ArgTableEntry_Double::ArgTableEntry_Double(std::string shortopts, std::string longopts, std::string glossary) { + m_argDbl = arg_dbln(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::DBL; +} + + +int ArgTableEntry_Double::getCount() { + return m_argDbl->count; +} + + +ArgTableEntry_File::ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary) { + m_argFile = arg_filen(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::FILE; +} + + +int ArgTableEntry_File::getCount() { + return m_argFile->count; +} + + +ArgTableEntry_Int::ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary) { + m_argInt = arg_intn(shortopts.c_str(), longopts.c_str(), "", 0, 1, glossary.c_str()); + m_type = ArgType_t::INT; +} + + +int ArgTableEntry_Int::getCount() { + return m_argInt->count; +} + + +ArgTableEntry_Lit::ArgTableEntry_Lit(std::string shortopts, std::string longopts, std::string glossary) { + m_argLit = arg_litn(shortopts.c_str(), longopts.c_str(), 0, 1, glossary.c_str()); + m_type = ArgType_t::LIT; +} + + +int ArgTableEntry_Lit::getCount() { + return m_argLit->count; +} + + +int ArgTableEntry_Regex::getCount() { + return m_argRex->count; +} + + +ArgTableEntry_String::ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max) { + m_argStr = arg_strn(shortopts.c_str(), longopts.c_str(), "", min, max, glossary.c_str()); + m_type = ArgType_t::STR; +} + + +int ArgTableEntry_String::getCount() { + return m_argStr->count; +} + + +Console::Console() { +} + + +Console::~Console() { +} + diff --git a/cpp_utils/Console.h b/cpp_utils/Console.h new file mode 100644 index 00000000..a94f63a1 --- /dev/null +++ b/cpp_utils/Console.h @@ -0,0 +1,149 @@ +/* + * Console.h + * + * Created on: Jun 15, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_CONSOLE_H_ +#define COMPONENTS_CPP_UTILS_CONSOLE_H_ + +#include +#include +#include +#include + +class Console { +public: + Console(); + virtual ~Console(); +}; + + +enum class ArgType_t { LIT, INT, DBL, STR, REX, FILE, DATE }; + + +class ArgTableEntry_Generic { +protected: + ArgType_t m_type; +public: + virtual int getCount(); + bool hasValue() { + return getCount() > 0; + } + virtual void* getEntry() = 0; +}; + + +class ArgTableEntry_Lit : public ArgTableEntry_Generic { +private: + struct arg_lit* m_argLit; +public: + int getCount(); + ArgTableEntry_Lit(std::string shortopts, std::string longopts, std::string glossary); + void* getEntry() { + return m_argLit; + } +}; + +class ArgTableEntry_Int : public ArgTableEntry_Generic { +private: + struct arg_int* m_argInt; +public: + ArgTableEntry_Int(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + int getValue(int index = 0); + void* getEntry() { + return m_argInt; + } +}; + + +class ArgTableEntry_Double : public ArgTableEntry_Generic { +private: + struct arg_dbl* m_argDbl; +public: + ArgTableEntry_Double(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + double getValue(int index = 0); + void* getEntry() { + return m_argDbl; + } +}; + + +class ArgTableEntry_String : public ArgTableEntry_Generic { +private: + struct arg_str* m_argStr; +public: + ArgTableEntry_String(std::string shortopts, std::string longopts, std::string glossary, int min, int max); + int getCount(); + std::string getValue(int index = 0); + void* getEntry() { + return m_argStr; + } +}; + + +class ArgTableEntry_Regex : public ArgTableEntry_Generic { +private: + struct arg_rex* m_argRex; +public: + int getCount(); + std::string getValue(int index = 0); + void* getEntry() { + return m_argRex; + } +}; + + +class ArgTableEntry_File : public ArgTableEntry_Generic { +private: + struct arg_file* m_argFile; +public: + ArgTableEntry_File(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + std::string getFilename(int index = 0); + std::string getBasename(int index = 0); + std::string getExtension(int index = 0); + void* getEntry() { + return m_argFile; + } +}; + + +class ArgTableEntry_Date : public ArgTableEntry_Generic { +private: + struct arg_date* m_argDate; +public: + ArgTableEntry_Date(std::string shortopts, std::string longopts, std::string glossary); + int getCount(); + struct tm* getValue(int index = 0); + void* getEntry() { + return m_argDate; + } +}; + + +class ArgTable { +private: + void** m_argtable; + struct arg_end* m_argEnd; + std::list> m_argTableEntries; + void build(); + void freeArgtable(); + +public: + ArgTable(); + ~ArgTable(); + ArgTableEntry_Date addDate(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Double addDouble(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_File addFile(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Int addInt(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_Lit addLit(std::string name, std::string shortopts, std::string longopts, std::string glossary); + ArgTableEntry_String addString(std::string name, std::string shortopts, std::string longopts, std::string glossary, int min, int max); + int parse(int argc, char* argv[]); + void printErrors(FILE* fp, std::string progName=""); +}; + +#endif /* COMPONENTS_CPP_UTILS_CONSOLE_H_ */ diff --git a/cpp_utils/DesignNotes/BLECPP.md b/cpp_utils/DesignNotes/BLECPP.md new file mode 100644 index 00000000..64e7a04c --- /dev/null +++ b/cpp_utils/DesignNotes/BLECPP.md @@ -0,0 +1,69 @@ +# BLE C++ classes + +# BLE Server +## BLE Characteristic callbacks +When a client connects to us as a BLE Server, it can change a characteristics value or read from a characteristic. The BLECharacteristicCallbacks class provides a handler for such. +# BLE Client +# Advertising +When we are being a BLE server we have the opportunity to perform advertising. Advertising means that we broadcast information about ourselves such that interested parties (presumably clients) can receive that information. The BLE advertising is described in the BLE specifications. + +Some key facts: + +* Advertisements can be sent periodically with the period being from 20ms to 10.24 seconds in steps of 0.625 ms. +* An advertisement payload can be up to 31 bytes in length. +* A payload can contain a sequence of records where a record is composed of a length, type and data. + +We can build a BLE advertising structure and specify that it should be sent for an advertisement using `esp_ble_gap_config_adv_data_raw` or sent using a scan response message using `esp_ble_gap_config_scan_rsp_data_raw`. + +Looking at the data structure for advertising we find it consists of records where each record is: + +* 1 byte length +* 1 byte record type +* length-1 bytes of data + +Trailing data (assuming we don't use all 31 bytes) shall be zeros. The advert types that include payload data are: + +* ADV_IND +* ADV\_NONCONN\_IND +* ADV\_SCAN\_IND + +The following advertising types are supported: + +| Type | Description | +|--------|------ +|0x01| Flags | +|0x02|Incomplete list of 16 bit service UUIDs +|0x03|Complete list of 16 bit service UUIDs +|0x04|Incomplete list of 32 bit service UUIDs +|0x05|Complete list of 32 bit UUIDs +|0x06|Incomplete list of 128 bit service UUIDs +|0x07|Complete list of 128 bit service UUIDs +|0x08|Shortened local name +|0x09|Complete local name +|0x16|Service data (16 bit) +|0x19|Appearance +|0x20|Service data (32 bit) +|0x21|Service data (128 bit) +|0xFF|Manufacturer data + + + +See also: + +* BLE Spec 4.2 Vol 3 Part C Chapter 11 - Advertising and Scan response data format. +* [Advertisement record types](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) +* [A BLE Advertising Primer](http://www.argenox.com/a-ble-advertising-primer/) + +## Exposed functions +|Type|Function +|-|- +|0x01|`setFlags(uint8_t flag)` +|0x02, 0x04, 0x06|`setPartialServices(vector)` +|0x02, 0x04, 0x06|`setPartialServices(BLEUUID)` +|0x03, 0x05, 0x07|`setCompleteServices(vector)` +|0x03, 0x05, 0x07|`setCompleteServices(BLEUUID)` +|0x08|`setShortName(std::string)` +|0x09|`setName(std::string)` +|0x16, 0x20, 0x21|`setServiceData(BLEUUID, std::string)` +|0x19|`setAppearance(uint16_t)` +|0xFF|`setManufacturerData(std::string)` \ No newline at end of file diff --git a/cpp_utils/DesignNotes/README.md b/cpp_utils/DesignNotes/README.md new file mode 100644 index 00000000..b28c9078 --- /dev/null +++ b/cpp_utils/DesignNotes/README.md @@ -0,0 +1,2 @@ +# Design Notes +Here is where we keep some of our design documents. \ No newline at end of file diff --git a/cpp_utils/DesignNotes/WebSockets.md b/cpp_utils/DesignNotes/WebSockets.md new file mode 100644 index 00000000..0a5ef897 --- /dev/null +++ b/cpp_utils/DesignNotes/WebSockets.md @@ -0,0 +1,64 @@ +#WebSockets +The WebSocket implementation attempts to implement [RFC 6455 - The WebSocket Protocol](https://tools.ietf.org/html/rfc6455). + + + +When an HTTP request arrives, we examine the request to see if it is a request to open a new WebSocket connection. If it is, we respond with the acknowledgement that we are prepared to be a WebSocket partner. Once done, the socket that was once used to handle the REST request now becomes the one used to handle bi-directional communications between the ESP32 (original WebSocket server) and the client (browser/Node.js). + +Since we don't know when a client may transmit, it could actually transmit at any time. As such we want to register an asynchronous callback handler that will be invoked when the client sends in some WebSocket data. To handle this, we then need to `select()` on the WebSocket socket and, when it wakes, invoke the callback handler. We must **not** issue a new `select()` until the WebSocket message has been consumed. + +It is anticipated that a WebSocket message could be large. The first use case I have come across for using WebSockets is for file transfer and since we have 4MBytes of flash and only 512K of RAM (of which likely only 100K or so may be available) we are likely going to run out of RAM if we wish to write a large file to flash file systems. What this means is that we likely can't buffer a large WebSocket data message in RAM. + +For example, imagine a WebSocket client wants to send a file of 1MByte to the ESP32. That is more data than we have RAM so we can't hold that message in its entirety in RAM. What we need to do is "read some" and then "write some" and repeat until all has been consumed. This sounds like the concept of a stream. We would have an input stream (data coming into the ESP32) associated with the WebSocket and an output stream (data going out from the ESP32) being written to the file. We could thus read a small section of the input, write it to the output and continue while we have new data in the input. + +This sounds workable ... so let us now think about how we might go about creating an input stream for a WebSocket message. Each WebSocket message starts with a WebSocket frame which contains, amongst other things, the length of the payload data. This means that we know up front how much of the remaining data is payload. This becomes essential as we can't rely on an "end of file" marker in the input stream to indicate the end of the WebSocket payload. The reason for this is that the WebSocket is a TCP stream that will be used to carry multiple sequential messages. + +Let us now invent a new class. Let us call it a SocketInputRecordStreambuf. +It will have a constructor of the form: + +``` +SocketInputRecordStreambuf(Socket &socket, size_t dataLength, size_t bufferSize=512) +``` + +The `socket` is the TCP/IP socket that we are going to read data from. The `dataLength` is the size of the data we wish to read. The class will extend `std::streambuf`. It will internally maintain a data buffer of size `bufferSize`. Initially, the buffer will be empty. When a read is performed on the stream, a call to `underflow()` will be made (this is a `std::streambuf` virtual function). + +Our rules for this class include: + +* We must **not** read more the `dataLength` bytes from the socket. +* We must **indicate** and `EOF` once we have had `dataLength` bytes consumed by a stream reader. +* The class must implement a `discard()` method that will discard further bytes from the socket such that the total number of bytes read from the socket will equal `dataLength`. +* Deleting an instance of the class must invoke `discard()`. + +## File transfer +WebSockets make a great file transfer technology. While this is more an application utilization of the technology than the design of the framework, we'll capture it here. Let us first set the scene. We have a client application that wishes to transmit a file. We will assume that the file is composed of three logical components: + +* The file name +* The file length +* The content of the file + +It would be wrong to expect the client to send the file as one continuous unit in one WebSocket message. The reason for this is that the client would have to have loaded the complete file into its memory buffers in order to send it. As such, we should assume that the client will send the files as one or more "parts" where each part represents a piece of the file. + +We thus invent the following protocol: + +For the first message we have: + +``` ++-----------------------+-----------+----+--------------------+-----------+ +| transferId (32bit/LE) | file name | \0 | length (32bits/LE) | Data .... | ++-----------------------+-----------+----+--------------------+-----------+ +``` + +For subsequent messages we have: + +``` ++-----------------------+-----------+ +| transferId (32bit/LE) | Data .... | ++-----------------------+-----------+ +``` +Let us look at these. + +* `transferId` - An Id that is randomly generated by the client. This is used to associate multiple messages for the same file together. +* `file name` - The name of the file that the client wishes to send. Can include paths. This will be used to determine where on the file system the file will be written. +* `length` - The size of the file in bytes. Knowing the size will allow us to know when the whole file has been received. + +We will create an encapsulation class called `WebSocketFileTransfer`. \ No newline at end of file diff --git a/cpp_utils/Doxyfile b/cpp_utils/Doxyfile index 5ac15096..cd186876 100644 --- a/cpp_utils/Doxyfile +++ b/cpp_utils/Doxyfile @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /home/kolban/esp32/esptest/apps/workspace/esp32-snippets/cpp_utils/docs +OUTPUT_DIRECTORY = docs # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and diff --git a/cpp_utils/FATFS_VFS.cpp b/cpp_utils/FATFS_VFS.cpp index 3b0e5c06..30af8e4d 100644 --- a/cpp_utils/FATFS_VFS.cpp +++ b/cpp_utils/FATFS_VFS.cpp @@ -7,9 +7,9 @@ #include "FATFS_VFS.h" #include -extern "C" { + #include -} + /** * @brief Constructor. @@ -26,10 +26,10 @@ extern "C" { * @param [in] partitionName The name of the partition used to store the FAT file system. */ FATFS_VFS::FATFS_VFS(std::string mountPath, std::string partitionName) { - m_mountPath = mountPath; + m_mountPath = mountPath; m_partitionName = partitionName; - m_maxFiles = 4; - m_wl_handle = WL_INVALID_HANDLE; + m_maxFiles = 4; + m_wl_handle = WL_INVALID_HANDLE; } // FATFS_VFS diff --git a/cpp_utils/FATFS_VFS.h b/cpp_utils/FATFS_VFS.h index fe9a7e57..24ce4928 100644 --- a/cpp_utils/FATFS_VFS.h +++ b/cpp_utils/FATFS_VFS.h @@ -10,6 +10,7 @@ #include extern "C" { #include +#include } /** * @brief Provide access to the FAT file system on %SPI flash. @@ -42,6 +43,7 @@ class FATFS_VFS { public: FATFS_VFS(std::string mountPath, std::string partitionName); virtual ~FATFS_VFS(); + void mount(); void setMaxFiles(int maxFiles); void unmount(); @@ -49,7 +51,7 @@ class FATFS_VFS { wl_handle_t m_wl_handle; std::string m_mountPath; std::string m_partitionName; - int m_maxFiles; + int m_maxFiles; }; #endif /* COMPONENTS_CPP_UTILS_FATFS_VFS_H_ */ diff --git a/cpp_utils/FTPCallbacks.cpp b/cpp_utils/FTPCallbacks.cpp new file mode 100644 index 00000000..69e462c9 --- /dev/null +++ b/cpp_utils/FTPCallbacks.cpp @@ -0,0 +1,152 @@ +#include "FTPServer.h" +#include +#include +#include +#include + +static const char* LOG_TAG = "FTPCallbacks"; +/** + * Called at the start of a STOR request. The file name is the name of the file the client would like to + * save. + */ +void FTPFileCallbacks::onStoreStart(std::string fileName) { + ESP_LOGD(LOG_TAG, ">> FTPFileCallbacks::onStoreStart: fileName=%s", fileName.c_str()); + m_storeFile.open(fileName, std::ios::binary); // Open the file for writing. + if (m_storeFile.fail()) { + throw FTPServer::FileException(); + } + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreStart"); +} // FTPFileCallbacks#onStoreStart + + +/** + * Called when the client presents a new chunk of data to be saved. + */ +size_t FTPFileCallbacks::onStoreData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onStoreData: size=%d", size); + m_storeFile.write((char*) data, size); // Store data received. + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreData: size=%d", size); + return size; +} // FTPFileCallbacks#onStoreData + + +/** + * Called at the end of a STOR request. This indicates that the client has completed its transmission of the + * file. + */ +void FTPFileCallbacks::onStoreEnd() { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onStoreEnd"); + m_storeFile.close(); // Close the open file. + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onStoreEnd"); +} // FTPFileCallbacks#onStoreEnd + + +/** + * Called when the client requests retrieval of a file. + */ +void FTPFileCallbacks::onRetrieveStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveStart: fileName=%s", fileName.c_str()); + m_byteCount = 0; + m_retrieveFile.open(fileName, std::ios::binary); + if (m_retrieveFile.fail()) { + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveStart: ***FileException***"); + throw FTPServer::FileException(); + } + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveStart"); +} // FTPFileCallbacks#onRetrieveStart + + +/** + * Called when the client is ready to receive the next piece of the file. To indicate that there + * is no more data to send, return a size of 0. + * @param data The data buffer that we can fill to return data back to the client. + * @param size The maximum size of the data buffer that we can populate. + * @return The size of data being returned. Return 0 to indicate that there is no more data to return. + */ +size_t FTPFileCallbacks::onRetrieveData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveData"); + m_retrieveFile.read((char*) data, size); + size_t readSize = m_retrieveFile.gcount(); + m_byteCount += readSize; + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveData: sizeRead=%d", readSize); + return m_retrieveFile.gcount(); // Return the number of bytes read. +} // FTPFileCallbacks#onRetrieveData + + +/** + * Called when the retrieval has been completed. + */ +void FTPFileCallbacks::onRetrieveEnd() { + ESP_LOGD(LOG_TAG,">> FTPFileCallbacks::onRetrieveEnd"); + m_retrieveFile.close(); + ESP_LOGD(LOG_TAG,"<< FTPFileCallbacks::onRetrieveEnd: bytesTransmitted=%d", m_byteCount); +} // FTPFileCallbacks#onRetrieveEnd + + +/** + * Return a list of files in the file system. + * @return a list of files in the file system. + */ +std::string FTPFileCallbacks::onDir() { + DIR* dir = opendir(FTPServer::getCurrentDirectory().c_str()); + std::stringstream ss; + while (true) { + struct dirent* pDirentry = readdir(dir); + if (pDirentry == nullptr) break; + ss << pDirentry->d_name << "\r\n"; + } + closedir(dir); + return ss.str(); +} // FTPFileCallbacks#onDir + + +/// ---- END OF FTPFileCallbacks + + +void FTPCallbacks::onStoreStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreStart: fileName=%s", fileName.c_str()); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreStart"); +} // FTPCallbacks#onStoreStart + + +size_t FTPCallbacks::onStoreData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreData: size=%d", size); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreData"); + return 0; +} // FTPCallbacks#onStoreData + + +void FTPCallbacks::onStoreEnd() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onStoreEnd"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onStoreEnd"); +} // FTPCallbacks#onStoreEnd + + +void FTPCallbacks::onRetrieveStart(std::string fileName) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveStart"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveStart"); +} // FTPCallbacks#onRetrieveStart + + +size_t FTPCallbacks::onRetrieveData(uint8_t* data, size_t size) { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveData"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveData: 0"); + return 0; +} // FTPCallbacks#onRetrieveData + + +void FTPCallbacks::onRetrieveEnd() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onRetrieveEnd"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onRetrieveEnd"); +} // FTPCallbacks#onRetrieveEnd + + +std::string FTPCallbacks::onDir() { + ESP_LOGD(LOG_TAG,">> FTPCallbacks::onDir"); + ESP_LOGD(LOG_TAG,"<< FTPCallbacks::onDir"); + return ""; +} // FTPCallbacks#onDir + +FTPCallbacks::~FTPCallbacks() { + +} // FTPCallbacks#~FTPCallbacks diff --git a/cpp_utils/FTPServer.cpp b/cpp_utils/FTPServer.cpp new file mode 100644 index 00000000..63cf6a9c --- /dev/null +++ b/cpp_utils/FTPServer.cpp @@ -0,0 +1,805 @@ +/* + * FTPServer.cpp + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#include "FTPServer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* LOG_TAG = "FTPServer"; + +// trim from start (in place) +static void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} // ltrim + + +// trim from end (in place) +static void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} // rtrim + + +// trim from both ends (in place) +static void trim(std::string &s) { + ltrim(s); + rtrim(s); +} // trim + + +FTPServer::FTPServer() { + ESP_LOGD(LOG_TAG,">> FTPServer()"); + + m_dataSocket = -1; + m_clientSocket = -1; + m_dataPort = -1; + m_dataIp = -1; + m_passiveSocket = -1; + m_serverSocket = -1; + + m_callbacks = nullptr; + m_isPassive = false; + m_isImage = true; + m_chunkSize = 4096; + m_port = 21; // The default Server-PI port + m_loginRequired = false; + m_isAuthenticated = false; + m_userid = ""; + m_password = ""; + + ESP_LOGD(LOG_TAG,"<< FTPServer()"); +} // FTPServer#FTPServer + + +FTPServer::~FTPServer() { + // Nothing to do here by default. +} // FTPServer#~FTPServer + + +/** + * Close the connection to the FTP client. + */ +void FTPServer::closeConnection() { + ESP_LOGD(LOG_TAG,">> closeConnection"); + close(m_clientSocket); + ESP_LOGD(LOG_TAG,"<< closeConnection"); +} // FTPServer#closeConnection + + +/** + * Close a previously opened data connection. + */ +void FTPServer::closeData() { + ESP_LOGD(LOG_TAG,">> closeData"); + close(m_dataSocket); + m_dataSocket = -1; + ESP_LOGD(LOG_TAG,"<< closeData"); +} // FTPServer#closeData + + +/** + * Close the passive listening socket that was opened by listenPassive. + */ +void FTPServer::closePassive() { + ESP_LOGD(LOG_TAG,">> closePassive"); + close(m_passiveSocket); + m_passiveSocket = -1; + ESP_LOGD(LOG_TAG, "<< closePassive"); +} // FTPServer#closePassive + + +/** + * Retrieve the current directory. + */ +/* STATIC */ std::string FTPServer::getCurrentDirectory() { + char maxDirectory[256]; + std::string currentDirectory = getcwd(maxDirectory, sizeof(maxDirectory)); + return currentDirectory; +} // FTPServer#getCurrentDirectory + + +/** + * Create a listening socket for the new passive connection. + * @return a String for the passive parameters. + */ +std::string FTPServer::listenPassive() { + ESP_LOGD(LOG_TAG, ">> listenPassive"); + + m_passiveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_passiveSocket == -1) { + ESP_LOGD(LOG_TAG, "socket: %s", strerror(errno)); + } + + struct sockaddr_in clientAddrInfo; + unsigned int addrInfoSize = sizeof(clientAddrInfo); + getsockname(m_clientSocket, (struct sockaddr*) &clientAddrInfo, &addrInfoSize); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(0); + int rc = bind(m_passiveSocket, (struct sockaddr*) &serverAddress, sizeof(serverAddress)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "bind: %s", strerror(errno)); + } + + rc = listen(m_passiveSocket, 5); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "listen: %s", strerror(errno)); + } + + unsigned int addrLen = sizeof(serverAddress); + rc = getsockname(m_passiveSocket, (struct sockaddr*)&serverAddress, &addrLen); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "getsockname: %s", strerror(errno)); + } + + std::stringstream ss; + ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 16) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 24) & 0xff) << + "," << ((serverAddress.sin_port >> 0) & 0xff) << + "," << ((serverAddress.sin_port >> 8) & 0xff); + std::string retStr = ss.str(); + + ESP_LOGD(LOG_TAG, "<< listenPassive: %s", retStr.c_str()); + return retStr; +} // FTPServer#listenPassive + + +/** + * Handle the AUTH command. + */ +void FTPServer::onAuth(std::istringstream& ss) { + std::string param; + ss >> param; + ESP_LOGD(LOG_TAG, ">> onAuth: %s", param.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + ESP_LOGD(LOG_TAG, "<< onAuth"); +} // FTPServer#onAuth + + +/** + * Change the current working directory. + * @param ss A string stream where the first parameter is the directory to change to. + */ +void FTPServer::onCwd(std::istringstream& ss) { + std::string path; + ss >> path; + ESP_LOGD(LOG_TAG, ">> onCwd: path=%s", path.c_str()); + chdir(path.c_str()); + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); + ESP_LOGD(LOG_TAG, "<< onCwd"); +} // FTPServer#onCwd + + +/** + * Process the client transmitted LIST request. + */ +void FTPServer::onList(std::istringstream& ss) { + std::string directory; + ss >> directory; + ESP_LOGD(LOG_TAG, ">> onList: directory=%s", directory.c_str()); + + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + if (m_callbacks != nullptr) { + std::string dirString = m_callbacks->onDir(); + sendData((uint8_t*) dirString.data(), dirString.length()); + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + ESP_LOGD(LOG_TAG, "<< onList"); +} // FTPServer#onList + + +void FTPServer::onMkd(std::istringstream& ss) { + std::string path; + ss >> path; + ESP_LOGD(LOG_TAG, ">> onMkd: path=%s", path.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onMkd"); +} // FTPServer#onMkd + + +/** + * Process a NOOP operation. + */ +void FTPServer::onNoop(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onNoop"); + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + ESP_LOGD(LOG_TAG, "<< onNoop"); +} // FTPServer#onNoop + + +/** + * Process PORT request. The information provided is encoded in the parameter as + * h1,h2,h3,h4,p1,p2 where h1,h2,h3,h4 is the IP address we should connect to + * and p1,p2 is the port number. The data is MSB + * + * Our logic does not form any connection but remembers the ip address and port number + * to be used for a subsequence data connection. + * + * Possible responses: + * 200 + * 500, 501, 421, 530 + */ +void FTPServer::onPort(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPort"); + char c; + uint16_t h1, h2, h3, h4, p1, p2; + ss >> h1 >> c >> h2 >> c >> h3 >> c >> h4 >> c >> p1 >> c >> p2; + m_dataPort = p1 * 256 + p2; + ESP_LOGD(LOG_TAG, "%d.%d.%d.%d %d", h1, h2, h3, h4, m_dataPort); + m_dataIp = h1 << 24 | h2 << 16 | h3 << 8 | h4; + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + m_isPassive = false; + + ESP_LOGD(LOG_TAG, "<< onPort"); +} // FTPServer#onPort + + +/** + * Process the PASS command. + * Possible responses: + * 230 + * 202 + * 530 + * 500, 501, 503, 421 + * 332 + */ +void FTPServer::onPass(std::istringstream& ss) { + std::string password; + ss >> password; + ESP_LOGD(LOG_TAG, ">> onPass: password=%s", password.c_str()); + + // If the immediate last command wasn't USER then don't try and process PASS. + if (m_lastCommand != "USER") { + sendResponse(RESPONSE_503_BAD_SEQUENCE); + ESP_LOGD(LOG_TAG, "<< onPass"); + return; + } + + // Compare the supplied userid and passwords. + if (m_userid == m_suppliedUserid && password == m_password) { + sendResponse(RESPONSE_230_USER_LOGGED_IN); + m_isAuthenticated = true; + } else { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + closeConnection(); + m_isAuthenticated = false; + } + ESP_LOGD(LOG_TAG, "<< onPass"); +} // FTPServer#onPass + + +/** + * Process the PASV command. + * Possible responses: + * 227 + * 500, 501, 502, 421, 530 + */ +void FTPServer::onPasv(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPasv"); + std::string ipInfo = listenPassive(); + std::ostringstream responseTextSS; + responseTextSS << "Entering Passive Mode (" << ipInfo << ")."; + std::string responseText; + responseText = responseTextSS.str(); + sendResponse(RESPONSE_227_ENTERING_PASSIVE_MODE, responseText.c_str()); + m_isPassive = true; + + ESP_LOGD(LOG_TAG, "<< onPasv"); +} // FTPServer#onPasv + + +/** + * Process the PWD command to determine our current working directory. + * Possible responses: + * 257 + * 500, 501, 502, 421, 550 + */ +void FTPServer::onPWD(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onPWD"); + sendResponse(257, "\"" + getCurrentDirectory() + "\""); + ESP_LOGD(LOG_TAG, "<< onPWD: %s", getCurrentDirectory().c_str()); +} // FTPServer#onPWD + + +/** + * Possible responses: + * 221 + * 500 + */ +void FTPServer::onQuit(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onQuit"); + sendResponse(FTPServer::RESPONSE_221_CLOSING_CONTROL_CONNECTION); // Service closing control connection. + closeConnection(); // Close the connection to the client. + ESP_LOGD(LOG_TAG, "<< onQuit"); +} // FTPServer#onQuit + + +/** + * Process a RETR command. The client sends this command to retrieve the content of a file. + * The name of the file is the first parameter in the input stream. + * + * Possible responses: + * 125, 150 + * (110) + * 226, 250 + * 425, 426, 451 + * 450, 550 + * 500, 501, 421, 530 + * @param ss The parameter stream. + */ +void FTPServer::onRetr(std::istringstream& ss) { + // We open a data connection back to the client. We then invoke the callback to indicate that we have + // started a retrieve operation. We call the retrieve callback to request the next chunk of data and + // transmit this down the data connection. We repeat this until there is no more data to send at which + // point we close the data connection and we are done. + ESP_LOGD(LOG_TAG, ">> onRetr"); + std::string fileName; + + ss >> fileName; + uint8_t data[m_chunkSize]; + + if (m_callbacks != nullptr) { + try { + m_callbacks->onRetrieveStart(fileName); + } catch (FTPServer::FileException& e) { + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + ESP_LOGD(LOG_TAG, "<< onRetr: Returned 550 to client."); + return; + } + } + + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + openData(); + if (m_callbacks != nullptr) { + int readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + while (readSize > 0) { + sendData(data, readSize); + readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + } + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + ESP_LOGD(LOG_TAG, "<< onRetr"); +} // FTPServer#onRetr + + +void FTPServer::onRmd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onRmd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onRmd"); +} // FTPServer#onRmd + + +/** + * Called to process a STOR request. This means that the client wishes to store a file + * on the server. The name of the file is found in the parameter. + */ +void FTPServer::onStor(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onStor"); + std::string fileName; + ss >> fileName; + + receiveFile(fileName); + ESP_LOGD(LOG_TAG, "<< onStor"); +} // FTPServer#onStor + + +void FTPServer::onSyst(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onSyst"); + sendResponse(215, "UNIX Type: L8"); + ESP_LOGD(LOG_TAG, "<< onSyst"); +} // FTPServer#onSyst + + +/** + * Process a TYPE request. The parameter that follows is the type of transfer we wish + * to process. Types include: + * I and A. + * + * Possible responses: + * 200 + * 500, 501, 504, 421, 530 + */ +void FTPServer::onType(std::istringstream& ss) { + ESP_LOGD(LOG_TAG, ">> onType"); + std::string type; + ss >> type; + m_isImage = (type.compare("I") == 0); + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + ESP_LOGD(LOG_TAG, "<< onType: isImage=%d", m_isImage); +} // FTPServer#onType + + +/** + * Process a USER request. The parameter that follows is the identity of the user. + * + * Possible responses: + * 230 + * 530 + * 500, 501, 421 + * 331, 332 + * + */ +void FTPServer::onUser(std::istringstream& ss) { + // When we receive a user command, we next want to know if we should ask for a password. If the m_loginRequired + // flag is set then we do indeed want a password and will send the response that we wish one. + std::string userName; + ss >> userName; + ESP_LOGD(LOG_TAG, ">> onUser: userName=%s", userName.c_str()); + sendResponse(m_loginRequired ? FTPServer::RESPONSE_331_PASSWORD_REQUIRED : FTPServer::RESPONSE_200_COMMAND_OK); + m_suppliedUserid = userName; // Save the username that was supplied. + ESP_LOGD(LOG_TAG, "<< onUser"); +} // FTPServer#onUser + + +void FTPServer::onXmkd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onXmkd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onXmkd"); +} // FTPServer#onXmkd + + +void FTPServer::onXrmd(std::istringstream &ss) { + ESP_LOGD(LOG_TAG, ">> onXrmd"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + ESP_LOGD(LOG_TAG, "<< onXrmd"); +} // FTPServer#onXrmd + + +/** + * Open a data connection with the client. + * We will use closeData() to close the connection. + * @return True if the data connection succeeded. + */ +bool FTPServer::openData() { + if (m_isPassive) { + // Handle a passive connection ... here we receive a connection from the client from the passive socket. + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_dataSocket = accept(m_passiveSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + if (m_dataSocket == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::openData: accept(): %s", strerror(errno)); + closePassive(); + return false; + } + closePassive(); + } else { + // Handle an active connection ... here we connect to the client. + m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(m_dataIp); + serverAddress.sin_port = htons(m_dataPort); + + int rc = connect(m_dataSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::openData: connect(): %s", strerror(errno)); + return false; + } + } + return true; +} // FTPServer#openData + + +/** + * Process commands received from the client. + */ +void FTPServer::processCommand() { + sendResponse(FTPServer::RESPONSE_220_SERVICE_READY); // Service ready. + ESP_LOGD(LOG_TAG, ">> FTPServer::processCommand"); + m_lastCommand = ""; + while (true) { + std::string line = ""; + char currentChar; + char lastChar = '\0'; + int rc = recv(m_clientSocket, ¤tChar, 1, 0); + while (rc != -1 && rc != 0) { + line += currentChar; + if (lastChar == '\r' && currentChar == '\n') break; +// printf("%c\n", currentChar); + lastChar = currentChar; + rc = recv(m_clientSocket, ¤tChar, 1, 0); + } // End while we are waiting for a line. + + if (rc == 0 || rc == -1) break; // If we didn't get a line or an error, then we have finished processing commands. + + std::string command; + std::istringstream ss(line); + getline(ss, command, ' '); + trim(command); + + // We now have a command to process. + + ESP_LOGD(LOG_TAG, "Command: \"%s\"", command.c_str()); + if (command.compare("USER") == 0) { + onUser(ss); + } + else if (command.compare("PASS") == 0) { + onPass(ss); + } + else if (m_loginRequired && !m_isAuthenticated) { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + } + else if (command.compare("PASV") == 0) { + onPasv(ss); + } + else if (command.compare("SYST") == 0) { + onSyst(ss); + } + else if (command.compare("PORT") == 0) { + onPort(ss); + } + else if (command.compare("LIST") == 0) { + onList(ss); + } + else if (command.compare("TYPE") == 0) { + onType(ss); + } + else if (command.compare("RETR") == 0) { + onRetr(ss); + } + else if (command.compare("QUIT") == 0) { + onQuit(ss); + } + else if (command.compare("AUTH") == 0) { + onAuth(ss); + } + else if (command.compare("STOR") == 0) { + onStor(ss); + } + else if (command.compare("PWD") == 0) { + onPWD(ss); + } + else if (command.compare("MKD") == 0) { + onMkd(ss); + } + else if (command.compare("XMKD") == 0) { + onXmkd(ss); + } + else if (command.compare("RMD") == 0) { + onRmd(ss); + } + else if (command.compare("XRMD") == 0) { + onXrmd(ss); + } + else if (command.compare("CWD") == 0) { + onCwd(ss); + } + else { + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + } + m_lastCommand = command; + } // End loop processing commands. + + close(m_clientSocket); // We won't be processing any further commands from this client. + ESP_LOGD(LOG_TAG, "<< FTPServer::processCommand"); +} // FTPServer::processCommand + + +/** + * Receive a file from the FTP client (STOR). The name of the file to be created is passed as a + * parameter. + */ +void FTPServer::receiveFile(std::string fileName) { + ESP_LOGD(LOG_TAG, ">> receiveFile: %s", fileName.c_str()); + if (m_callbacks != nullptr) { + try { + m_callbacks->onStoreStart(fileName); + } catch(FTPServer::FileException& e) { + ESP_LOGD(LOG_TAG, "Caught a file exception!"); + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + return; + } + } + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + uint8_t buf[m_chunkSize]; + uint32_t totalSizeRead = 0; + while (true) { + int rc = recv(m_dataSocket, &buf, m_chunkSize, 0); + if (rc <= 0) break; + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveData(buf, rc); + } + totalSizeRead += rc; + } + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + closeData(); + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + ESP_LOGD(LOG_TAG, "<< receiveFile: totalSizeRead=%d", totalSizeRead); +} // FTPServer#receiveFile + + +/** + * Send data to the client over the data connection previously opened with a call to openData(). + * @param pData A pointer to the data to send. + * @param size The number of bytes to send. + */ +void FTPServer::sendData(uint8_t* pData, uint32_t size) { + ESP_LOGD(LOG_TAG, ">> FTPServer::sendData: size=%d", size); + int rc = send(m_dataSocket, pData, size, 0); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "FTPServer::sendData: send(): %s", strerror(errno)); + } + ESP_LOGD(LOG_TAG, "<< FTPServer::sendData"); +} // FTPServer#sendData + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. + */ +void FTPServer::sendResponse(int code, std::string text) { + ESP_LOGD(LOG_TAG, ">> sendResponse: (%d) %s", code, text.c_str()); + std::ostringstream ss; + ss << code << " " << text << "\r\n"; + int rc = send(m_clientSocket, ss.str().data(), ss.str().length(), 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG,"send: %s", strerror(errno)); + } + ESP_LOGD(LOG_TAG, "<< sendResponse"); +} // FTPServer#sendResponse + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. In this function, a standard piece of text is used based on + * the code. + */ +void FTPServer::sendResponse(int code) { + std::string text = "unknown"; + + switch(code) { // Map the code to a text string. + case RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION: + text = "File status okay; about to open data connection."; + break; + case RESPONSE_200_COMMAND_OK: + text = "Command okay."; + break; + case RESPONSE_220_SERVICE_READY: + text = "Service ready."; + break; + case RESPONSE_221_CLOSING_CONTROL_CONNECTION: + text = "Service closing control connection."; + break; + case RESPONSE_226_CLOSING_DATA_CONNECTION: + text = "Closing data connection."; + break; + case RESPONSE_230_USER_LOGGED_IN: + text = "User logged in, proceed."; + break; + case RESPONSE_331_PASSWORD_REQUIRED: + text = "Password required."; + break; + case RESPONSE_500_COMMAND_UNRECOGNIZED: + text = "Syntax error, command unrecognized."; + break; + case RESPONSE_502_COMMAND_NOT_IMPLEMENTED: + text = "Command not implemented."; + break; + case RESPONSE_503_BAD_SEQUENCE: + text = "Bad sequence of commands."; + break; + case RESPONSE_530_NOT_LOGGED_IN: + text = "Not logged in."; + break; + case RESPONSE_550_ACTION_NOT_TAKEN: + text = "Requested action not taken."; + break; + default: + break; + } + sendResponse(code, text); // Send the code AND the text to the FTP client. +} // FTPServer#sendResponse + + +/** + * Set the callbacks that are to be invoked to perform work. + * @param pCallbacks An instance of an FTPCallbacks based class. + */ +void FTPServer::setCallbacks(FTPCallbacks* pCallbacks) { + m_callbacks = pCallbacks; +} // FTPServer#setCallbacks + + +void FTPServer::setCredentials(std::string userid, std::string password) { + ESP_LOGD(LOG_TAG, ">> setCredentials: userid=%s", userid.c_str()); + m_loginRequired = true; + m_userid = userid; + m_password = password; + ESP_LOGD(LOG_TAG, "<< setCredentials"); +} // FTPServer#setCredentials + + +/** + * Set the TCP port we should listen on for FTP client requests. + */ +void FTPServer::setPort(uint16_t port) { + m_port = port; +} // FTPServer#setPort + + +/** + * Start being an FTP Server. + */ +void FTPServer::start() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_serverSocket == -1) { + ESP_LOGD(LOG_TAG, "socket: %s", strerror(errno)); + } + + int enable = 1; + setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(m_port); + int rc = bind(m_serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "bind: %s", strerror(errno)); + } + rc = listen(m_serverSocket, 5); + if (rc == -1) { + ESP_LOGD(LOG_TAG, "listen: %s", strerror(errno)); + } + while (true) { + waitForFTPClient(); + processCommand(); + } +} // FTPServer#start + + +/** + * Wait for a new client to connect. + */ +int FTPServer::waitForFTPClient() { + ESP_LOGD(LOG_TAG, ">> FTPServer::waitForFTPClient"); + + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_clientSocket = accept(m_serverSocket, (struct sockaddr*) &clientAddress, &clientAddressLength); + + char ipAddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &clientAddress.sin_addr, ipAddr, sizeof(ipAddr)); + ESP_LOGD(LOG_TAG, "Received connection from %s [%d]", ipAddr, clientAddress.sin_port); + + struct sockaddr_in socketAddressInfo; + unsigned int socketAddressInfoSize = sizeof(socketAddressInfo); + getsockname(m_clientSocket, (struct sockaddr*) &socketAddressInfo, &socketAddressInfoSize); + + inet_ntop(AF_INET, &socketAddressInfo.sin_addr, ipAddr, sizeof(ipAddr)); + ESP_LOGD(LOG_TAG, "Connected at %s [%d]", ipAddr, socketAddressInfo.sin_port); + ESP_LOGD(LOG_TAG, "<< FTPServer::waitForFTPClient: fd=%d\n", m_clientSocket); + + return m_clientSocket; +} // FTPServer::waitForFTPClient + diff --git a/cpp_utils/FTPServer.h b/cpp_utils/FTPServer.h new file mode 100644 index 00000000..5736201d --- /dev/null +++ b/cpp_utils/FTPServer.h @@ -0,0 +1,138 @@ +/* + * FTPServer.h + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#ifndef NETWORKING_FTPSERVER_FTPSERVER_H_ +#define NETWORKING_FTPSERVER_FTPSERVER_H_ +#include +#include +#include +#include +#include + + +class FTPCallbacks { +public: + virtual void onStoreStart(std::string fileName); + virtual size_t onStoreData(uint8_t* data, size_t size); + virtual void onStoreEnd(); + virtual void onRetrieveStart(std::string fileName); + virtual size_t onRetrieveData(uint8_t *data, size_t size); + virtual void onRetrieveEnd(); + virtual std::string onDir(); + virtual ~FTPCallbacks(); + +}; + +/** + * An implementation of FTPCallbacks that uses Posix File I/O to perform file access. + */ +class FTPFileCallbacks : public FTPCallbacks { +public: + void onStoreStart(std::string fileName) override; // Called for a STOR request. + size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. + void onStoreEnd() override; // Called at the end of a STOR request. + void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. + size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. + void onRetrieveEnd() override; // Called when we have retrieved all the data. + std::string onDir() override; // Called to retrieve all the directory entries. + +private: + std::ofstream m_storeFile; // File used to store data from the client. + std::ifstream m_retrieveFile; // File used to retrieve data for the client. + uint32_t m_byteCount; // Count of bytes sent over wire. + +}; + + +class FTPServer { +public: + FTPServer(); + virtual ~FTPServer(); + void setCredentials(std::string userid, std::string password); + void start(); + void setPort(uint16_t port); + void setCallbacks(FTPCallbacks* pFTPCallbacks); + static std::string getCurrentDirectory(); + class FileException: public std::exception { + }; + + // Response codes. + static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; + static const int RESPONSE_200_COMMAND_OK = 200; + static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; + static const int RESPONSE_212_DIRECTORY_STATUS = 212; + static const int RESPONSE_213_FILE_STATUS = 213; + static const int RESPONSE_214_HELP_MESSAGE = 214; + static const int RESPONSE_220_SERVICE_READY = 220; + static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; + static const int RESPONSE_230_USER_LOGGED_IN = 230; + static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; + static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; + static const int RESPONSE_331_PASSWORD_REQUIRED = 331; + static const int RESPONSE_332_NEED_ACCOUNT = 332; + static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; + static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; + static const int RESPONSE_503_BAD_SEQUENCE = 503; + static const int RESPONSE_530_NOT_LOGGED_IN = 530; + static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; + static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; + +private: + int m_serverSocket; // The socket the FTP server is listening on. + int m_clientSocket; // The current client socket. + int m_dataSocket; // The data socket. + int m_passiveSocket; // The socket on which the server is listening for passive FTP connections. + uint16_t m_port; // The port the FTP server will use. + uint16_t m_dataPort; // The port for data connections. + uint32_t m_dataIp; // The ip address for data connections. + bool m_isPassive; // Are we in passive mode? If not, then we are in active mode. + bool m_isImage; // Are we in image mode? + size_t m_chunkSize; // The maximum chunk size. + std::string m_userid; // The required userid. + std::string m_password; // The required password. + std::string m_suppliedUserid; // The userid supplied from the USER command. + bool m_loginRequired; // Do we required a login? + bool m_isAuthenticated; // Have we authenticated? + std::string m_lastCommand; // The last command that was processed. + + FTPCallbacks* m_callbacks; // The callbacks for processing. + + void closeConnection(); + void closeData(); + void closePassive(); + void onAuth(std::istringstream& ss); + void onCwd(std::istringstream& ss); + void onList(std::istringstream& ss); + void onMkd(std::istringstream& ss); + void onNoop(std::istringstream& ss); + void onPass(std::istringstream& ss); + void onPasv(std::istringstream& ss); + void onPort(std::istringstream& ss); + void onPWD(std::istringstream& ss); + void onQuit(std::istringstream& ss); + void onRetr(std::istringstream& ss); + void onRmd(std::istringstream& ss); + void onStor(std::istringstream& ss); + void onSyst(std::istringstream& ss); + void onType(std::istringstream& ss); + void onUser(std::istringstream& ss); + void onXmkd(std::istringstream& ss); + void onXrmd(std::istringstream& ss); + + bool openData(); + + void receiveFile(std::string fileName); + void sendResponse(int code); + void sendResponse(int code, std::string text); + void sendData(uint8_t* pData, uint32_t size); + std::string listenPassive(); + int waitForFTPClient(); + void processCommand(); + +}; + +#endif /* NETWORKING_FTPSERVER_FTPSERVER_H_ */ diff --git a/cpp_utils/File.cpp b/cpp_utils/File.cpp index 6b0627af..3ac4672f 100644 --- a/cpp_utils/File.cpp +++ b/cpp_utils/File.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include "GeneralUtils.h" static const char* LOG_TAG = "File"; /** @@ -19,8 +19,8 @@ static const char* LOG_TAG = "File"; * @param [in] name The name of the file. * @param [in] type The type of the file (DT_REGULAR, DT_DIRECTORY or DT_UNKNOWN). */ -File::File(std::string name, uint8_t type) { - m_name = name; +File::File(std::string path, uint8_t type) { + m_path = path; m_type = type; } // File @@ -32,16 +32,14 @@ File::File(std::string name, uint8_t type) { */ std::string File::getContent(bool base64Encode) { uint32_t size = length(); - ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, length=%d", m_name.c_str(), size); - if (size == 0) { - return ""; - } - uint8_t *pData = (uint8_t *)malloc(size); + ESP_LOGD(LOG_TAG, "File:: getContent(), path=%s, length=%d", m_path.c_str(), size); + if (size == 0) return ""; + uint8_t* pData = (uint8_t*) malloc(size); if (pData == nullptr) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_name.c_str(), "r"); + FILE* file = fopen(m_path.c_str(), "r"); fread(pData, size, 1, file); fclose(file); std::string ret((char *)pData, size); @@ -64,31 +62,34 @@ std::string File::getContent(bool base64Encode) { std::string File::getContent(uint32_t offset, uint32_t readSize) { uint32_t fileSize = length(); ESP_LOGD(LOG_TAG, "File:: getContent(), name=%s, fileSize=%d, offset=%d, readSize=%d", - m_name.c_str(), fileSize, offset, readSize); - if (fileSize == 0 || offset > fileSize) { - return ""; - } - uint8_t *pData = (uint8_t *)malloc(readSize); + m_path.c_str(), fileSize, offset, readSize); + if (fileSize == 0 || offset > fileSize) return ""; + uint8_t* pData = (uint8_t*) malloc(readSize); if (pData == nullptr) { ESP_LOGE(LOG_TAG, "getContent: Failed to allocate memory"); return ""; } - FILE *file = fopen(m_name.c_str(), "r"); + FILE* file = fopen(m_path.c_str(), "r"); fseek(file, offset, SEEK_SET); size_t bytesRead = fread(pData, 1, readSize, file); fclose(file); - std::string ret((char *)pData, bytesRead); + std::string ret((char*) pData, bytesRead); free(pData); return ret; } // getContent +std::string File::getPath() { + return m_path; +} /** * @brief Get the name of the file. * @return The name of the file. */ std::string File::getName() { - return m_name; + size_t pos = m_path.find_last_of('/'); + if (pos == std::string::npos) return m_path; + return m_path.substr(pos + 1); } // getName @@ -108,11 +109,9 @@ uint8_t File::getType() { */ uint32_t File::length() { struct stat buf; - int rc = stat(m_name.c_str(), &buf); - if (rc != 0) { - return 0; - } - return buf.st_size; + int rc = stat(m_path.c_str(), &buf); + if (rc != 0 || S_ISDIR(buf.st_mode)) return 0; + return (uint32_t) buf.st_size; } // length @@ -122,12 +121,7 @@ uint32_t File::length() { */ bool File::isDirectory() { struct stat buf; - int rc = stat(m_name.c_str(), &buf); - if (rc != 0) { - return false; - } - if (S_ISDIR(buf.st_mode)) { - return true; - } - return false; + int rc = stat(m_path.c_str(), &buf); + if (rc != 0) return false; + return S_ISDIR(buf.st_mode); } // isDirectory diff --git a/cpp_utils/File.h b/cpp_utils/File.h index cd64435f..c1c18014 100644 --- a/cpp_utils/File.h +++ b/cpp_utils/File.h @@ -17,15 +17,16 @@ class File { public: File(std::string name, uint8_t type = DT_UNKNOWN); - std::string getContent(bool base64Encode=false); + std::string getContent(bool base64Encode = false); std::string getContent(uint32_t offset, uint32_t size); std::string getName(); + std::string getPath(); uint8_t getType(); bool isDirectory(); uint32_t length(); private: - std::string m_name; + std::string m_path; uint8_t m_type; }; diff --git a/cpp_utils/FileSystem.cpp b/cpp_utils/FileSystem.cpp index d0240745..1920235c 100644 --- a/cpp_utils/FileSystem.cpp +++ b/cpp_utils/FileSystem.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -25,25 +26,28 @@ static const char* LOG_TAG = "FileSystem"; * @return N/A. */ void FileSystem::dumpDirectory(std::string path) { - DIR *pDir = ::opendir(path.c_str()); + DIR* pDir = ::opendir(path.c_str()); if (pDir == nullptr) { ESP_LOGD(LOG_TAG, "Unable to open directory: %s [errno=%d]", path.c_str(), errno); return; } - struct dirent *pDirent; + struct dirent* pDirent; ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); - while((pDirent = readdir(pDir)) != nullptr) { + while ((pDirent = readdir(pDir)) != nullptr) { std::string type; - switch(pDirent->d_type) { - case DT_UNKNOWN: - type = "Unknown"; - break; - case DT_REG: - type = "Regular"; - break; - case DT_DIR: - type = "Directory"; - break; + switch (pDirent->d_type) { + case DT_UNKNOWN: + type = "Unknown"; + break; + case DT_REG: + type = "Regular"; + break; + case DT_DIR: + type = "Directory"; + break; + default: + type = "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Entry: d_ino: %d, d_name: %s, d_type: %s", pDirent->d_ino, pDirent->d_name, type.c_str()); } @@ -58,15 +62,15 @@ void FileSystem::dumpDirectory(std::string path) { */ std::vector FileSystem::getDirectoryContents(std::string path) { std::vector ret; - DIR *pDir = ::opendir(path.c_str()); + DIR* pDir = ::opendir(path.c_str()); if (pDir == nullptr) { ESP_LOGE(LOG_TAG, "getDirectoryContents:: Unable to open directory: %s [errno=%d]", path.c_str(), errno); return ret; } - struct dirent *pDirent; + struct dirent* pDirent; ESP_LOGD(LOG_TAG, "Directory dump of %s", path.c_str()); - while((pDirent = readdir(pDir)) != nullptr) { - File file(std::string(pDirent->d_name), pDirent->d_type); + while ((pDirent = readdir(pDir)) != nullptr) { + File file(path +"/" + std::string(pDirent->d_name), pDirent->d_type); ret.push_back(file); } ::closedir(pDir); @@ -74,15 +78,29 @@ std::vector FileSystem::getDirectoryContents(std::string path) { } // getDirectoryContents +/** + * @brief Does the path refer to a directory? + * @param [in] path The path to the directory. + */ +bool FileSystem::isDirectory(std::string path) { + struct stat statBuf; + int rc = stat(path.c_str(), &statBuf); + if (rc != 0) return false; + return S_ISDIR(statBuf.st_mode); +} // isDirectory + + + /** * @brief Create a directory * @param [in] path The directory to create. * @return N/A. */ int FileSystem::mkdir(std::string path) { + ESP_LOGD(LOG_TAG, ">> mkdir: %s", path.c_str()); int rc = ::mkdir(path.c_str(), 0); if (rc != 0) { - ESP_LOGE(LOG_TAG, "mkdir: errno=%d", errno); + ESP_LOGE(LOG_TAG, "mkdir: errno=%d %s", errno, strerror(errno)); rc = errno; } return rc; @@ -112,11 +130,11 @@ std::vector FileSystem::pathSplit(std::string path) { std::istringstream stream(path); std::vector ret; std::string pathPart; - while(std::getline(stream, pathPart, '/')) { + while (std::getline(stream, pathPart, '/')) { ret.push_back(pathPart); } // Debug - for (int i=0; i getDirectoryContents(std:: string path); static void dumpDirectory(std::string path); + static bool isDirectory(std::string path); static int mkdir(std::string path); static std::vector pathSplit(std::string path); static int remove(std::string path); diff --git a/cpp_utils/FreeRTOS.cpp b/cpp_utils/FreeRTOS.cpp index 663128d7..1920fa43 100644 --- a/cpp_utils/FreeRTOS.cpp +++ b/cpp_utils/FreeRTOS.cpp @@ -4,23 +4,28 @@ * Created on: Feb 24, 2017 * Author: kolban */ -#include -#include +#include // Include the base FreeRTOS definitions +#include // Include the task definitions +#include // Include the semaphore definitions #include #include #include #include "FreeRTOS.h" -#include #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); + ::vTaskDelay(ms / portTICK_PERIOD_MS); } // sleep @@ -31,7 +36,7 @@ void FreeRTOS::sleep(uint32_t ms) { * @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, int stackSize) { +void FreeRTOS::startTask(void task(void*), std::string taskName, void* param, uint32_t stackSize) { ::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL); } // startTask @@ -50,23 +55,9 @@ void FreeRTOS::deleteTask(TaskHandle_t pTask) { * @return The time in milliseconds since the %FreeRTOS scheduler started. */ uint32_t FreeRTOS::getTimeSinceStart() { - return (uint32_t)(xTaskGetTickCount()*portTICK_PERIOD_MS); + return (uint32_t) (xTaskGetTickCount() * portTICK_PERIOD_MS); } // getTimeSinceStart -/* - * public: - Semaphore(std::string = ""); - ~Semaphore(); - void give(); - void take(std::string owner=""); - void take(uint32_t timeoutMs, std::string owner=""); - private: - SemaphoreHandle_t m_semaphore; - std::string m_name; - std::string m_owner; - }; - * - */ /** * @brief Wait for a semaphore to be released by trying to take it and @@ -75,24 +66,48 @@ uint32_t FreeRTOS::getTimeSinceStart() { * @return The value associated with the semaphore. */ uint32_t FreeRTOS::Semaphore::wait(std::string owner) { - ESP_LOGV(LOG_TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); - xSemaphoreTake(m_semaphore, portMAX_DELAY); + ESP_LOGV(LOG_TAG, ">> wait: Semaphore waiting: %s for %s", toString().c_str(), owner.c_str()); + m_owner = owner; - xSemaphoreGive(m_semaphore); - ESP_LOGV(LOG_TAG, "Semaphore released: %s", toString().c_str()); - m_owner = ""; + + if (m_usePthreads) { + pthread_mutex_lock(&m_pthread_mutex); + } else { + xSemaphoreTake(m_semaphore, portMAX_DELAY); + } + + if (m_usePthreads) { + pthread_mutex_unlock(&m_pthread_mutex); + } else { + xSemaphoreGive(m_semaphore); + } + + ESP_LOGV(LOG_TAG, "<< wait: Semaphore released: %s", toString().c_str()); return m_value; } // wait + FreeRTOS::Semaphore::Semaphore(std::string name) { - m_semaphore = xSemaphoreCreateMutex(); + m_usePthreads = false; // Are we using pThreads or FreeRTOS? + if (m_usePthreads) { + pthread_mutex_init(&m_pthread_mutex, nullptr); + } else { + m_semaphore = xSemaphoreCreateBinary(); + xSemaphoreGive(m_semaphore); + } + m_name = name; - m_owner = ""; + m_owner = std::string(""); m_value = 0; } + FreeRTOS::Semaphore::~Semaphore() { - vSemaphoreDelete(m_semaphore); + if (m_usePthreads) { + pthread_mutex_destroy(&m_pthread_mutex); + } else { + vSemaphoreDelete(m_semaphore); + } } @@ -101,9 +116,17 @@ FreeRTOS::Semaphore::~Semaphore() { * The Semaphore is given. */ void FreeRTOS::Semaphore::give() { - xSemaphoreGive(m_semaphore); ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str()); - m_owner = ""; + 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 @@ -115,7 +138,7 @@ void FreeRTOS::Semaphore::give() { void FreeRTOS::Semaphore::give(uint32_t value) { m_value = value; give(); -} +} // give /** @@ -123,21 +146,35 @@ void FreeRTOS::Semaphore::give(uint32_t value) { */ void FreeRTOS::Semaphore::giveFromISR() { BaseType_t higherPriorityTaskWoken; - xSemaphoreGiveFromISR(m_semaphore, &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. */ -void FreeRTOS::Semaphore::take(std::string owner) -{ - - ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str()); - xSemaphoreTake(m_semaphore, portMAX_DELAY); +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; - ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + 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 @@ -145,21 +182,92 @@ void FreeRTOS::Semaphore::take(std::string owner) * @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. */ -void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) { +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; - xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS); - ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str()); + 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/cpp_utils/FreeRTOS.h b/cpp_utils/FreeRTOS.h index 320f4cc6..b861c875 100644 --- a/cpp_utils/FreeRTOS.h +++ b/cpp_utils/FreeRTOS.h @@ -9,10 +9,12 @@ #define MAIN_FREERTOS_H_ #include #include +#include -#include -#include -#include +#include // Include the base FreeRTOS definitions. +#include // Include the task definitions. +#include // Include the semaphore definitions. +#include // Include the ringbuffer definitions. /** @@ -21,7 +23,7 @@ class FreeRTOS { public: static void sleep(uint32_t ms); - static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048); + 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(); @@ -30,20 +32,40 @@ class FreeRTOS { public: Semaphore(std::string owner = ""); ~Semaphore(); - void give(); - void giveFromISR(); - void give(uint32_t value); - void setName(std::string name); - void take(std::string owner=""); - void take(uint32_t timeoutMs, std::string owner=""); - uint32_t wait(std::string owner=""); + 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; - std::string m_name; - std::string m_owner; - uint32_t m_value; + 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/cpp_utils/FreeRTOSTimer.cpp b/cpp_utils/FreeRTOSTimer.cpp index 4baec3e2..4b096000 100644 --- a/cpp_utils/FreeRTOSTimer.cpp +++ b/cpp_utils/FreeRTOSTimer.cpp @@ -11,10 +11,10 @@ #include "FreeRTOSTimer.h" -static std::map timersMap; +static std::map timersMap; void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { - FreeRTOSTimer *timer = timersMap.at(xTimer); + FreeRTOSTimer* timer = timersMap.at(xTimer); timer->callback(timer); } @@ -33,7 +33,7 @@ void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { * * @code{.cpp} * void callback(FreeRTOSTimer *pTimer) { - * // Callback code here ... + * // Callback code here ... * } * @endcode * @@ -43,12 +43,7 @@ void FreeRTOSTimer::internalCallback(TimerHandle_t xTimer) { * @param [in] data Data to be passed to the callback. * @param [in] callback Callback function to be fired when the timer expires. */ -FreeRTOSTimer::FreeRTOSTimer( - char *name, - TickType_t period, - UBaseType_t reload, - void *data, - void (*callback)(FreeRTOSTimer *pTimer)) { +FreeRTOSTimer::FreeRTOSTimer(char* name, TickType_t period, UBaseType_t reload, void* data, void (*callback)(FreeRTOSTimer* pTimer)) { /* * The callback function to actually be called is saved as member data in the object and * a static callback function is called. This will be passed the FreeRTOS timer handle @@ -124,7 +119,7 @@ void FreeRTOSTimer::changePeriod(TickType_t newPeriod, TickType_t blockTime) { * * @return The name of the timer. */ -const char *FreeRTOSTimer::getName() { +const char* FreeRTOSTimer::getName() { return ::pcTimerGetTimerName(timerHandle); } // getName @@ -134,6 +129,6 @@ const char *FreeRTOSTimer::getName() { * * @return The user supplied data associated with the timer. */ -void *FreeRTOSTimer::getData() { +void* FreeRTOSTimer::getData() { return ::pvTimerGetTimerID(timerHandle); } // getData diff --git a/cpp_utils/FreeRTOSTimer.h b/cpp_utils/FreeRTOSTimer.h index 8556ea3f..5c71a0e7 100644 --- a/cpp_utils/FreeRTOSTimer.h +++ b/cpp_utils/FreeRTOSTimer.h @@ -15,21 +15,22 @@ */ class FreeRTOSTimer { public: - FreeRTOSTimer(char *name, TickType_t period, UBaseType_t reload, void *data, void (*callback)(FreeRTOSTimer *pTimer)); + FreeRTOSTimer(char* name, TickType_t period, UBaseType_t reload, void* data, void (*callback)(FreeRTOSTimer* pTimer)); virtual ~FreeRTOSTimer(); - void changePeriod(TickType_t newPeriod, TickType_t blockTime=portMAX_DELAY); - void *getData(); - const char *getName(); + void changePeriod(TickType_t newPeriod, TickType_t blockTime = portMAX_DELAY); + void* getData(); + const char* getName(); TickType_t getPeriod(); - void reset(TickType_t blockTime=portMAX_DELAY); - void start(TickType_t blockTime=portMAX_DELAY); - void stop(TickType_t blockTime=portMAX_DELAY); + void reset(TickType_t blockTime = portMAX_DELAY); + void start(TickType_t blockTime = portMAX_DELAY); + void stop(TickType_t blockTime = portMAX_DELAY); private: TimerHandle_t timerHandle; TickType_t period; - void (*callback)(FreeRTOSTimer *pTimer); + void (*callback)(FreeRTOSTimer* pTimer); static void internalCallback(TimerHandle_t xTimer); + }; #endif /* COMPONENTS_CPP_UTILS_FREERTOSTIMER_H_ */ diff --git a/cpp_utils/GPIO.cpp b/cpp_utils/GPIO.cpp index 4188d307..63941306 100644 --- a/cpp_utils/GPIO.cpp +++ b/cpp_utils/GPIO.cpp @@ -9,6 +9,7 @@ #include #include "sdkconfig.h" #include +#include #include "GeneralUtils.h" static const char* LOG_TAG = "GPIO"; @@ -21,15 +22,11 @@ static bool g_isrServiceInstalled = false; * @param [in] handler The function to be invoked when the interrupt is detected. * @param [in] pArgs Optional arguments to pass to the handler. */ -void ESP32CPP::GPIO::addISRHandler( - gpio_num_t pin, - gpio_isr_t handler, - void* pArgs) { - +void ESP32CPP::GPIO::addISRHandler(gpio_num_t pin, gpio_isr_t handler, void* pArgs) { ESP_LOGD(LOG_TAG, ">> addISRHandler: pin=%d", pin); // If we have not yet installed the ISR service handler, install it now. - if (g_isrServiceInstalled == false) { + if (!g_isrServiceInstalled) { ESP_LOGD(LOG_TAG, "Installing the global ISR service"); esp_err_t errRc = ::gpio_install_isr_service(0); if (errRc != ESP_OK) { @@ -69,10 +66,7 @@ void ESP32CPP::GPIO::high(gpio_num_t pin) { * @return The value of true if the pin is valid and false otherwise. */ bool ESP32CPP::GPIO::inRange(gpio_num_t pin) { - if (pin>=0 && pin<=39) { - return true; - } - return false; + return (pin >= 0 && pin <= 39); } // inRange @@ -123,7 +117,7 @@ void ESP32CPP::GPIO::low(gpio_num_t pin) { * @return True if the pin is high, false if the pin is low. */ bool ESP32CPP::GPIO::read(gpio_num_t pin) { - return ::gpio_get_level(pin); + return ::gpio_get_level(pin) == 1; } // read @@ -154,14 +148,11 @@ void ESP32CPP::GPIO::setInput(gpio_num_t pin) { * @param [in] intrType The type of interrupt. * @return N/A. */ -void ESP32CPP::GPIO::setInterruptType( - gpio_num_t pin, - gpio_int_type_t intrType) { +void ESP32CPP::GPIO::setInterruptType(gpio_num_t pin, gpio_int_type_t intrType) { esp_err_t rc = ::gpio_set_intr_type(pin, intrType); if (rc != ESP_OK) { ESP_LOGE(LOG_TAG, "setInterruptType: %d", rc); } - } // setInterruptType @@ -188,7 +179,7 @@ void ESP32CPP::GPIO::setOutput(gpio_num_t pin) { */ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { //ESP_LOGD(LOG_TAG, ">> write: pin: %d, value: %d", pin, value); - esp_err_t errRc = ::gpio_set_level(pin, value); + esp_err_t errRc = ::gpio_set_level(pin, value ? 1 : 0); if (errRc != ESP_OK) { ESP_LOGE(LOG_TAG, "<< gpio_set_level: pin=%d, rc=%d %s", pin, errRc, GeneralUtils::errorToString(errRc)); } @@ -203,9 +194,9 @@ void ESP32CPP::GPIO::write(gpio_num_t pin, bool value) { */ void ESP32CPP::GPIO::writeByte(gpio_num_t pins[], uint8_t value, int bits) { ESP_LOGD(LOG_TAG, ">> writeByte: value: %.2x, bits: %d", value, bits); - for (int i=0; i -namespace ESP32CPP -{ +namespace ESP32CPP { /** * @brief Interface to %GPIO functions. * diff --git a/cpp_utils/GeneralUtils.cpp b/cpp_utils/GeneralUtils.cpp index c3504b8e..019c81bd 100644 --- a/cpp_utils/GeneralUtils.cpp +++ b/cpp_utils/GeneralUtils.cpp @@ -6,43 +6,43 @@ */ #include "GeneralUtils.h" -#include +#include #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+/"; - -GeneralUtils::GeneralUtils() { - // TODO Auto-generated constructor stub -} - -GeneralUtils::~GeneralUtils() { - // TODO Auto-generated destructor stub -} +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) { +static int base64EncodedLength(const std::string& in) { return base64EncodedLength(in.length()); } // base64EncodedLength -static void a3_to_a4(unsigned char * a4, unsigned char * a3) { +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); @@ -50,7 +50,7 @@ static void a3_to_a4(unsigned char * a4, unsigned char * a3) { } // a3_to_a4 -static void a4_to_a3(unsigned char * a3, unsigned char * 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]; @@ -62,7 +62,7 @@ static void a4_to_a3(unsigned char * a3, unsigned char * a4) { * @param [in] in * @param [out] out */ -bool GeneralUtils::base64Encode(const std::string &in, std::string *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]; @@ -106,9 +106,43 @@ bool GeneralUtils::base64Encode(const std::string &in, std::string *out) { } // base64Encode -static int DecodedLength(const std::string &in) { +/** + * @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 = in.size(); + int n = (int) in.size(); for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) { ++numEq; @@ -132,7 +166,7 @@ static unsigned char b64_lookup(unsigned char c) { * @param [in] in The string to be decoded. * @param [out] out The resulting data. */ -bool GeneralUtils::base64Decode(const std::string &in, std::string *out) { +bool GeneralUtils::base64Decode(const std::string& in, std::string* out) { int i = 0, j = 0; size_t dec_len = 0; unsigned char a3[3]; @@ -203,7 +237,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { if (index % 16 == 0) { strcpy(hexBuf, hex.str().c_str()); strcpy(asciiBuf, ascii.str().c_str()); - ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf); + ESP_LOGV(tag, "%s %s", hexBuf, asciiBuf); hex.str(""); ascii.str(""); } @@ -215,8 +249,8 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { } strcpy(hexBuf, hex.str().c_str()); strcpy(asciiBuf, ascii.str().c_str()); - ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf); - //ESP_LOGD(tag, "%s %s", hex.str().c_str(), 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); } @@ -238,7 +272,7 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { } index++; if (index % 16 == 0) { - ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + ESP_LOGV(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); hex.str(""); ascii.str(""); } @@ -248,12 +282,13 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { hex << " "; index++; } - ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str()); + 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. * @@ -261,17 +296,18 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { * @param [in] length Length of the data (in bytes) to be logged. * @return N/A. */ -void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { +void GeneralUtils::hexDump(const uint8_t* pData, uint32_t length) { char ascii[80]; char hex[80]; char tempBuf[80]; uint32_t lineNumber = 0; - ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ----------------"); + 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) { + uint32_t index = 0; + while (index < length) { sprintf(tempBuf, "%.2x ", pData[index]); strcat(hex, tempBuf); if (isprint(pData[index])) { @@ -282,21 +318,22 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { strcat(ascii, tempBuf); index++; if (index % 16 == 0) { - ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + ESP_LOGV(LOG_TAG, "%.4x %s %s", lineNumber * 16, hex, ascii); strcpy(ascii, ""); strcpy(hex, ""); lineNumber++; } } if (index %16 != 0) { - while(index % 16 != 0) { + while (index % 16 != 0) { strcat(hex, " "); index++; } - ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii); + 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. @@ -304,35 +341,55 @@ void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) { */ std::string GeneralUtils::ipToString(uint8_t *ip) { std::stringstream s; - s << (int)ip[0] << '.' << (int)ip[1] << '.' << (int)ip[2] << '.' << (int)ip[3]; + 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) { + switch (errCode) { +#if CONFIG_LOG_DEFAULT_LEVEL > 4 case ESP_OK: - return "OK"; + return "ESP_OK"; case ESP_FAIL: - return "Fail"; + return "ESP_FAIL"; case ESP_ERR_NO_MEM: - return "No memory"; + return "ESP_ERR_NO_MEM"; case ESP_ERR_INVALID_ARG: - return "Invalid argument"; + return "ESP_ERR_INVALID_ARG"; case ESP_ERR_INVALID_SIZE: - return "Invalid state"; + return "ESP_ERR_INVALID_SIZE"; case ESP_ERR_INVALID_STATE: - return "Invalid state"; + return "ESP_ERR_INVALID_STATE"; case ESP_ERR_NOT_FOUND: - return "Not found"; + return "ESP_ERR_NOT_FOUND"; case ESP_ERR_NOT_SUPPORTED: - return "Not supported"; + return "ESP_ERR_NOT_SUPPORTED"; case ESP_ERR_TIMEOUT: - return "Timeout"; + return "ESP_ERR_TIMEOUT"; case ESP_ERR_NVS_NOT_INITIALIZED: return "ESP_ERR_NVS_NOT_INITIALIZED"; case ESP_ERR_NVS_NOT_FOUND: @@ -381,7 +438,107 @@ const char* GeneralUtils::errorToString(esp_err_t errCode) { return "ESP_ERR_WIFI_TIMEOUT"; case ESP_ERR_WIFI_WAKE_FAIL: return "ESP_ERR_WIFI_WAKE_FAIL"; +#endif + default: + return "Unknown ESP_ERR error"; } - 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/cpp_utils/GeneralUtils.h b/cpp_utils/GeneralUtils.h index 152abca7..8eecbd4d 100644 --- a/cpp_utils/GeneralUtils.h +++ b/cpp_utils/GeneralUtils.h @@ -10,19 +10,26 @@ #include #include #include +#include +#include /** * @brief General utilities. */ class GeneralUtils { public: - GeneralUtils(); - virtual ~GeneralUtils(); - static void hexDump(uint8_t *pData, uint32_t length); - static std::string ipToString(uint8_t *ip); - static bool base64Encode(const std::string &in, std::string *out); - static bool base64Decode(const std::string &in, std::string *out); - static const char *errorToString(esp_err_t errCode); + 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/cpp_utils/HIDKeyboardTypes.h b/cpp_utils/HIDKeyboardTypes.h new file mode 100644 index 00000000..4e221d57 --- /dev/null +++ b/cpp_utils/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/cpp_utils/HIDTypes.h b/cpp_utils/HIDTypes.h new file mode 100644 index 00000000..64850ef8 --- /dev/null +++ b/cpp_utils/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/cpp_utils/HttpParser.cpp b/cpp_utils/HttpParser.cpp new file mode 100644 index 00000000..a6bbbc34 --- /dev/null +++ b/cpp_utils/HttpParser.cpp @@ -0,0 +1,309 @@ +/* + * HttpParser.cpp + * + * Created on: Aug 28, 2017 + * Author: kolban + */ + +#include +#include +#include +#include "HttpParser.h" +#include "HttpRequest.h" +#include "GeneralUtils.h" + +#include + +#undef close +/** + * RFC7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing + * RFC3986 - URI + * + * + *
+ *
+ * ... + *
+ * + * + * + * Example: + * GET /hello.txt HTTP/1.1 + */ + +static const char* LOG_TAG = "HttpParser"; + +static std::string lineTerminator = "\r\n"; + + +/** + * @brief Parse an incoming line of text until we reach a delimiter. + * @param [in/out] it The current iterator in the text input. + * @param [in] str The string we are parsing. + * @param [in] token The token delimiter. + */ +static std::string toStringToken(std::string::iterator& it, std::string& str, std::string& token) { + std::string ret; + std::string part; + auto itToken = token.begin(); + for (; it != str.end(); ++it) { + if ((*it) == (*itToken)) { + part += (*itToken); + ++itToken; + if (itToken == token.end()) { + ++it; + break; + } + } else { + if (part.empty()) { + ret += part; + part.clear(); + itToken = token.begin(); + } + ret += *it; + } + } // for + return ret; +} // toStringToken + + +/** + * @brief Parse a string until the given token is found. + * @param [in] it The current iterator. + * @param [in] str The string being parsed. + * @param [in] token The token terminating the parse. + * @return The parsed string token. + */ +static std::string toCharToken(std::string::iterator& it, std::string& str, char token) { + std::string ret; + for(; it != str.end(); ++it) { + if ((*it) == token) { + ++it; + break; + } + ret += *it; + } + return ret; +} // toCharToken + + +/** + * @brief Parse a header line. + * An HTTP Header is of the form: + * + * Name":" Value + * + * We parse this and return a pair. + * @param [in] line The line of text to parse. + * @return A pair of the form name/value. + */ +std::pair parseHeader(std::string& line) { + auto it = line.begin(); + std::string name = toCharToken(it, line, ':'); // Parse the line until we find a ':' + // We normalize the header name to be lower case. + GeneralUtils::toLower(name); + auto value = GeneralUtils::trim(toStringToken(it, line, lineTerminator)); + return std::pair(name, value); +} // parseHeader + + +HttpParser::HttpParser() { +} + +HttpParser::~HttpParser() { +} + + +/** + * @brief Dump the outcome of the parse. + * + */ +void HttpParser::dump() { + ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", m_method.c_str(), m_url.c_str(), m_version.c_str()); + auto it2 = m_headers.begin(); + for (; it2 != m_headers.end(); ++it2) { + ESP_LOGD(LOG_TAG, "name=\"%s\", value=\"%s\"", it2->first.c_str(), it2->second.c_str()); + } + ESP_LOGD(LOG_TAG, "Body: \"%s\"", m_body.c_str()); +} // dump + + +std::string HttpParser::getBody() { + return m_body; +} + + +/** + * @brief Retrieve the value of the named header. + * @param [in] name The name of the header to retrieve. + * @return The value of the named header or null if not present. + */ +std::string HttpParser::getHeader(const std::string& name) { + // We normalize the header name to be lower case. + std::string localName = name; + GeneralUtils::toLower(localName); + if (!hasHeader(localName)) return ""; + return m_headers.at(localName); +} // getHeader + + +std::map HttpParser::getHeaders() { + return m_headers; +} // getHeaders + + +std::string HttpParser::getMethod() { + return m_method; +} // getMethod + + +std::string HttpParser::getURL() { + return m_url; +} // getURL + + +std::string HttpParser::getVersion() { + return m_version; +} // getVersion + +std::string HttpParser::getStatus() { + return m_status; +} // getStatus + +std::string HttpParser::getReason() { + return m_reason; +} // getReason + +/** + * @brief Determine if we have a header of the given name. + * @param [in] name The name of the header to find. + * @return True if the header is present and false otherwise. + */ +bool HttpParser::hasHeader(const std::string& name) { + // We normalize the header name to be lower case. + std::string localName = name; + return m_headers.find(GeneralUtils::toLower(localName)) != m_headers.end(); +} // hasHeader + + +/** + * @brief Parse socket data. + * @param [in] s The socket from which to retrieve data. + */ +void HttpParser::parse(Socket s) { + ESP_LOGD(LOG_TAG, ">> parse: socket: %s", s.toString().c_str()); + std::string line; + line = s.readToDelim(lineTerminator); + parseRequestLine(line); + line = s.readToDelim(lineTerminator); + while (!line.empty()) { + m_headers.insert(parseHeader(line)); + line = s.readToDelim(lineTerminator); + } + // Only PUT and POST requests have a body + if (getMethod() != "POST" && getMethod() != "PUT") { + ESP_LOGD(LOG_TAG, "<< parse"); + return; + } + + // We have now parsed up to and including the separator ... we are now at the point where we + // want to read the body. There are two stories here. The first is that we know the exact length + // of the body or we read until we can't read anymore. + if (hasHeader(HttpRequest::HTTP_HEADER_CONTENT_LENGTH)) { + std::string val = getHeader(HttpRequest::HTTP_HEADER_CONTENT_LENGTH); + int length = std::atoi(val.c_str()); + uint8_t data[length]; + s.receive(data, length, true); + m_body = std::string((char*) data, length); + } else { + uint8_t data[512]; + int rc = s.receive(data, sizeof(data)); + if (rc > 0) { + m_body = std::string((char*) data, rc); + } + } + ESP_LOGD(LOG_TAG, "<< parse: Size of body: %d", m_body.length()); +} // parse + + +/** + * @brief Parse a string message. + * @param [in] message The HTTP message to parse. + */ +/* +void HttpParser::parse(std::string message) { + auto it = message.begin(); + auto line = toStringToken(it, message, lineTerminator); + parseRequestLine(line); + + line = toStringToken(it, message, lineTerminator); + while(!line.empty()) { + //ESP_LOGD(LOG_TAG, "Header: \"%s\"", line.c_str()); + m_headers.insert(parseHeader(line)); + line = toStringToken(it, message, lineTerminator); + } + + m_body = message.substr(std::distance(message.begin(), it)); +} // parse +*/ + +/** + * @brief Parse A request line. + * @param [in] line The request line to parse. + */ +void HttpParser::parseRequestLine(std::string& line) { + // A request Line is built from: + // + ESP_LOGD(LOG_TAG, ">> parseRequestLine: \"%s\" [%d]", line.c_str(), line.length()); + std::string::iterator it = line.begin(); + + // Get the method + m_method = toCharToken(it, line, ' '); + + // Get the url + m_url = toCharToken(it, line, ' '); + + // Get the version + m_version = toCharToken(it, line, ' '); + ESP_LOGD(LOG_TAG, "<< parseRequestLine: method: %s, url: %s, version: %s", m_method.c_str(), m_url.c_str(), m_version.c_str()); +} // parseRequestLine + +/** + * @brief Parse a response message. + * @param [in] line The response to parse. + */ +void HttpParser::parseResponse(std::string message) { + // A response is built from: + // A status line, any number of header lines, a body + auto it = message.begin(); + auto line = toStringToken(it, message, lineTerminator); + parseStatusLine(line); + + line = toStringToken(it, message, lineTerminator); + while (!line.empty()) { + ESP_LOGD(LOG_TAG, "Header: \"%s\"", line.c_str()); + m_headers.insert(parseHeader(line)); + line = toStringToken(it, message, lineTerminator); + } + + m_body = message.substr(std::distance(message.begin(), it)); +} // parse + +/** + * @brief Parse A status line. + * @param [in] line The status line to parse. + */ +void HttpParser::parseStatusLine(std::string& line) { + // A status Line is built from: + // + ESP_LOGD(LOG_TAG, ">> ParseStatusLine: \"%s\" [%d]", line.c_str(), line.length()); + std::string::iterator it = line.begin(); + // Get the version + m_version = toCharToken(it, line, ' '); + // Get the version + m_status = toCharToken(it, line, ' '); + // Get the status code + m_reason = toStringToken(it, line, lineTerminator); + + ESP_LOGD(LOG_TAG, "<< ParseStatusLine: method: %s, version: %s, status: %s", m_method.c_str(), m_version.c_str(), m_status.c_str()); +} // parseRequestLine diff --git a/cpp_utils/HttpParser.h b/cpp_utils/HttpParser.h new file mode 100644 index 00000000..06485293 --- /dev/null +++ b/cpp_utils/HttpParser.h @@ -0,0 +1,45 @@ +/* + * HttpParser.h + * + * Created on: Aug 28, 2017 + * Author: kolban + */ + +#ifndef CPP_UTILS_HTTPPARSER_H_ +#define CPP_UTILS_HTTPPARSER_H_ +#include +#include +#include "Socket.h" + +class HttpParser { +public: + HttpParser(); + virtual ~HttpParser(); + std::string getBody(); + std::string getHeader(const std::string& name); + std::map getHeaders(); + std::string getMethod(); + std::string getURL(); + std::string getVersion(); + std::string getStatus(); + std::string getReason(); + bool hasHeader(const std::string& name); + void parse(std::string message); + void parse(Socket s); + void parseResponse(std::string message); + +private: + std::string m_method; + std::string m_url; + std::string m_version; + std::string m_body; + std::string m_status; + std::string m_reason; + std::map m_headers; + void dump(); + void parseRequestLine(std::string& line); + void parseStatusLine(std::string& line); + +}; + +#endif /* CPP_UTILS_HTTPPARSER_H_ */ diff --git a/cpp_utils/HttpRequest.cpp b/cpp_utils/HttpRequest.cpp new file mode 100644 index 00000000..ff9336fb --- /dev/null +++ b/cpp_utils/HttpRequest.cpp @@ -0,0 +1,387 @@ +/* + * HTTPRequest.cpp + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +/* + * A Websocket hand shake request looks like: + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 13 + * + * + * A corresponding hand shake response looks like: + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= + * Sec-WebSocket-Protocol: chat + * + * The server key returned in Sec-WebSocket-Accept is the value of Sec-WebSocket-Key passed in the + * request concatenated with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" and then take the SHA-1 hash + * of the result to give a 20 byte value which is then base64() encoded. + */ + +#include +#include +#include +#include "HttpResponse.h" +#include "HttpRequest.h" +#include "GeneralUtils.h" + +#include +#include + +#define STATE_NAME 0 +#define STATE_VALUE 1 + +static const char* LOG_TAG="HttpRequest"; + +//static std::string lineTerminator = "\r\n"; + +const char HttpRequest::HTTP_HEADER_ACCEPT[] = "Accept"; +const char HttpRequest::HTTP_HEADER_ALLOW[] = "Allow"; +const char HttpRequest::HTTP_HEADER_CONNECTION[] = "Connection"; +const char HttpRequest::HTTP_HEADER_CONTENT_LENGTH[] = "Content-Length"; +const char HttpRequest::HTTP_HEADER_CONTENT_TYPE[] = "Content-Type"; +const char HttpRequest::HTTP_HEADER_COOKIE[] = "Cookie"; +const char HttpRequest::HTTP_HEADER_HOST[] = "Host"; +const char HttpRequest::HTTP_HEADER_LAST_MODIFIED[] = "Last-Modified"; +const char HttpRequest::HTTP_HEADER_ORIGIN[] = "Origin"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_ACCEPT[] = "Sec-WebSocket-Accept"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL[] = "Sec-WebSocket-Protocol"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_KEY[] = "Sec-WebSocket-Key"; +const char HttpRequest::HTTP_HEADER_SEC_WEBSOCKET_VERSION[] = "Sec-WebSocket-Version"; +const char HttpRequest::HTTP_HEADER_UPGRADE[] = "Upgrade"; +const char HttpRequest::HTTP_HEADER_USER_AGENT[] = "User-Agent"; + +const char HttpRequest::HTTP_METHOD_CONNECT[] = "CONNECT"; +const char HttpRequest::HTTP_METHOD_DELETE[] = "DELETE"; +const char HttpRequest::HTTP_METHOD_GET[] = "GET"; +const char HttpRequest::HTTP_METHOD_HEAD[] = "HEAD"; +const char HttpRequest::HTTP_METHOD_OPTIONS[] = "OPTIONS"; +const char HttpRequest::HTTP_METHOD_PATCH[] = "PATCH"; +const char HttpRequest::HTTP_METHOD_POST[] = "POST"; +const char HttpRequest::HTTP_METHOD_PUT[] = "PUT"; + + + +/** + * @brief Build a WebSockey response has. + */ +std::string buildWebsocketKeyResponseHash(std::string requestKey) { + std::string newKey = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + uint8_t shaData[20]; + esp_sha(SHA1, (uint8_t*) newKey.data(), newKey.length(), shaData); + //GeneralUtils::hexDump(shaData, 20); + std::string retStr; + GeneralUtils::base64Encode(std::string((char*) shaData, sizeof(shaData)), &retStr); + return retStr; +} // buildWebsocketKeyResponseHash + + +/** + * @brief Create an HTTP Request instance. + */ +HttpRequest::HttpRequest(Socket clientSocket) { + m_clientSocket = clientSocket; + m_pWebSocket = nullptr; + m_isClosed = false; + + m_parser.parse(clientSocket); // Parse the socket stream to build the HTTP data. + + // We have to take some special action on the Connection header. We want to know if it contains "Upgrade" + // however it has come to light that the Connection header can contain multiple parts. For example, it has + // been reported that it can contain "keep-alive,Upgrade". Because of this we can't simply examine the string + // to see if it equals "Upgrade". Our solution is to get the value of Connection string, split it by "," as + // a delimiter and then examine each of the parts to see if any of those are "Upgrade". + std::vector parts = GeneralUtils::split(getHeader(HTTP_HEADER_CONNECTION), ','); + bool upgradeFound = false; + if (std::find(parts.begin(), parts.end(), "Upgrade") != parts.end()) { + upgradeFound = true; + } + + // Is this a Web Socket? + if (getMethod() == HTTP_METHOD_GET && + !getHeader(HTTP_HEADER_HOST).empty() && + getHeader(HTTP_HEADER_UPGRADE) == "websocket" && + //getHeader(HTTP_HEADER_CONNECTION) == "Upgrade" && + upgradeFound && + !getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY).empty() && + !getHeader(HTTP_HEADER_SEC_WEBSOCKET_VERSION).empty()) { + ESP_LOGD(LOG_TAG, "Websocket detected!"); + // do something + // Process the web socket request + + // Build and send the response HTTP message to switch to being a Web Socket + HttpResponse response(this); + + response.setStatus(HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL, "Switching Protocols"); + response.addHeader(HTTP_HEADER_UPGRADE, "websocket"); + response.addHeader(HTTP_HEADER_CONNECTION, "Upgrade"); + response.addHeader(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + buildWebsocketKeyResponseHash(getHeader(HTTP_HEADER_SEC_WEBSOCKET_KEY))); + response.sendData(""); + + // Now that we have converted the request into a WebSocket, create the new WebSocket entry. + m_pWebSocket = new WebSocket(clientSocket); + } // if this is a web socket ... +} // HttpRequest + + +HttpRequest::~HttpRequest() { +} // ~HttpRequest + + +/** + * @brief Close the HttpRequest + */ +void HttpRequest::close() { + if (isWebsocket()) { + ESP_LOGW(LOG_TAG, "Request to close an HTTP Request but we think it is a web socket!"); + } + m_clientSocket.close(); + m_isClosed = true; +} // close_cpp + + +/** + * @brief Dump the HttpRequest for debugging purposes. + */ +void HttpRequest::dump() { + ESP_LOGD(LOG_TAG, "Method: %s, URL: \"%s\", Version: %s", getMethod().c_str(), getPath().c_str(), getVersion().c_str()); + auto headers = getHeaders(); + auto it2 = headers.begin(); + for (; it2 != headers.end(); ++it2) { + ESP_LOGD(LOG_TAG, "name=\"%s\", value=\"%s\"", it2->first.c_str(), it2->second.c_str()); + } + ESP_LOGD(LOG_TAG, "Body: \"%s\"", getBody().c_str()); +} // dump + + +/** + * @brief Get the body of the HttpRequest. + */ +std::string HttpRequest::getBody() { + return m_parser.getBody(); +} // getBody + + +/** + * @brief Get the named header. + * @param [in] name The name of the header field to retrieve. + * @return The value of the header field. + */ +std::string HttpRequest::getHeader(std::string name) { + return m_parser.getHeader(name); +} // getHeader + + +std::map HttpRequest::getHeaders() { + return m_parser.getHeaders(); +} // getHeaders + + +std::string HttpRequest::getMethod() { + return m_parser.getMethod(); +} // getMethod + + +std::string HttpRequest::getPath() { + return m_parser.getURL(); +} // getPath + + +/** + * @brief Get the query part of the request. + * The query is a set of name = value pairs. The return is a map keyed by the name items. + * + * @return The query part of the request. + */ +std::map HttpRequest::getQuery() { + // Walk through all the characters in the query string maintaining a simple state machine + // that lets us know what we are parsing. + std::map queryMap; + + std::string possibleQueryString = getPath(); + int qindex = possibleQueryString.find_first_of("?"); + if (qindex < 0) { + ESP_LOGD(LOG_TAG, "No query string present"); + return queryMap ; + } + std::string queryString = possibleQueryString.substr(qindex + 1, -1) ; + ESP_LOGD(LOG_TAG, "query string: %s", queryString.c_str()) ; + + /* + * We maintain a simple state machine with states of: + * * STATE_NAME - We are parsing a name. + * * STATE_VALUE - We are parsing a value. + */ + int state = STATE_NAME; + std::string name = ""; + std::string value; + // Loop through each character in the query string. + for (int i = 0; i < queryString.length(); i++) { + char currentChar = queryString[i]; + if (state == STATE_NAME) { + if (currentChar != '=') { + name += currentChar; + } else { + state = STATE_VALUE; + value = ""; + } + } // End state = STATE_NAME + else if (state == STATE_VALUE) { + if (currentChar != '&') { + value += currentChar; + } else { + //ESP_LOGD(tag, "name=%s, value=%s", name.c_str(), value.c_str()); + queryMap[name] = value; + state = STATE_NAME; + name = ""; + } + } // End state = STATE_VALUE + } // End for loop + if (state == STATE_VALUE) { + //ESP_LOGD(tag, "name=%s, value=%s", name.c_str(), value.c_str()); + queryMap[name] = value; + } + return queryMap; +} // getQuery + + +/** + * @brief Get the underlying socket. + * @return The underlying socket. + */ +Socket HttpRequest::getSocket() { + return m_clientSocket; +} // getSocket + + +std::string HttpRequest::getVersion() { + return m_parser.getVersion(); +} // getVersion + + +WebSocket* HttpRequest::getWebSocket() { + return m_pWebSocket; +} // getWebSocket + + +/** + * @brief Determine if the request is closed. + * @return Returns true if the request is closed. + */ +bool HttpRequest::isClosed() { + return m_isClosed; +} // isClosed + + +/** + * @brief Determine if this request represents a WebSocket + * @return True if the request creates a web socket. + */ +bool HttpRequest::isWebsocket() { + return m_pWebSocket != nullptr; +} // isWebsocket + + +/** + * @brief Parse the request message as a form. + * @return A map containing the names/values of the form elements that were found. + */ +std::map HttpRequest::parseForm() { + ESP_LOGD(LOG_TAG, ">> parseForm"); + std::map map; + // A form is composed of name=value pairs where each pair is separated with an "&" character. + // Our algorithm is to split all the pairs by "&" and then split all the name/value pairs by "=". + std::istringstream ss(getBody()); // Get the body of the request. + std::string currentEntry; + while (std::getline(ss, currentEntry, '&')) { // For each form entry. + ESP_LOGD(LOG_TAG, "Processing: %s", currentEntry.c_str()); // Debug + std::istringstream currentPair(currentEntry); // Prepare to parse the name=value string. + std::string name; // Declare the name variable. + std::string value; // Declare the value variable. + + std::getline(currentPair, name, '='); // Parse the current form entry into name/value. + currentPair >> value; // The value is what remains. + map[name] = urlDecode(value); // Decode the field which may have been encoded. + ESP_LOGD(LOG_TAG, " %s = \"%s\"", name.c_str(), map[name].c_str()); // Debug + // Add the form entry into the map. + } // Processed all form entries. + ESP_LOGD(LOG_TAG, "<< parseForm"); // Debug + return map; // Return the map of form entries. +} // parseForm + + +/** + * @brief Return the constituent parts of the path. + * If we imagine a path as composed of parts separated by slashes, then this function + * returns a vector composed of the parts. For example: + * + * ``` + * /x/y/z + * ``` + * will break out to: + * + * ``` + * path[0] = "" + * path[1] = "x" + * path[2] = "y" + * path[3] = "z" + * ``` + * + * @return A vector of the constituent parts of the path. + */ +std::vector HttpRequest::pathSplit() { + std::istringstream stream(getPath()); + std::vector ret; + std::string pathPart; + while (std::getline(stream, pathPart, '/')) { + ret.push_back(pathPart); + } + // Debug + for (int i = 0; i < ret.size(); i++) { + ESP_LOGD(LOG_TAG, "part[%d]: %s", i, ret[i].c_str()); + } + return ret; +} // pathSplit + + +/** + * @brief Decode a URL/form + * @param [in] str + * @return The decoded string. + */ +std::string HttpRequest::urlDecode(std::string str) { + // https://stackoverflow.com/questions/154536/encode-decode-urls-in-c + std::string ret; + char ch; + int ii, len = str.length(); + + for (int i = 0; i < len; i++) { + if (str[i] != '%'){ + if (str[i] == '+') { + ret += ' '; + } else { + ret += str[i]; + } + } else { + sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii); + ch = static_cast(ii); + ret += ch; + i = i + 2; + } + } + return ret; +} // urlDecode diff --git a/cpp_utils/HttpRequest.h b/cpp_utils/HttpRequest.h new file mode 100644 index 00000000..5984ed4f --- /dev/null +++ b/cpp_utils/HttpRequest.h @@ -0,0 +1,72 @@ +/* + * HTTPRequest.h + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ +#define COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ +#include +#include +#include +#include "Socket.h" +#include "WebSocket.h" +#include "HttpParser.h" + +#undef close + +class HttpRequest { +public: + HttpRequest(Socket s); + virtual ~HttpRequest(); + static const char HTTP_HEADER_ACCEPT[]; + static const char HTTP_HEADER_ALLOW[]; + static const char HTTP_HEADER_CONNECTION[]; + static const char HTTP_HEADER_CONTENT_LENGTH[]; + static const char HTTP_HEADER_CONTENT_TYPE[]; + static const char HTTP_HEADER_COOKIE[]; + static const char HTTP_HEADER_HOST[]; + static const char HTTP_HEADER_LAST_MODIFIED[]; + static const char HTTP_HEADER_ORIGIN[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_ACCEPT[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_KEY[]; + static const char HTTP_HEADER_SEC_WEBSOCKET_VERSION[]; + static const char HTTP_HEADER_UPGRADE[]; + static const char HTTP_HEADER_USER_AGENT[]; + + static const char HTTP_METHOD_CONNECT[]; + static const char HTTP_METHOD_DELETE[]; + static const char HTTP_METHOD_GET[]; + static const char HTTP_METHOD_HEAD[]; + static const char HTTP_METHOD_OPTIONS[]; + static const char HTTP_METHOD_PATCH[]; + static const char HTTP_METHOD_POST[]; + static const char HTTP_METHOD_PUT[]; + + void close(); // Close the connection to the client. + void dump(); // Diagnostic dump of the Http request. + std::string getBody(); // Get the body of the request. + std::string getHeader(std::string name); // Get the value of a named header. + std::map getHeaders(); // Get all the headers. + std::string getMethod(); // Get the request method. + std::string getPath(); // Get the request path. + std::map getQuery(); // Get the query part of the request. + Socket getSocket(); // Get the underlying TCP/IP socket. + std::string getVersion(); // Get the HTTP version. + WebSocket* getWebSocket(); // Get the WebSocket reference if this is a web socket. + bool isClosed(); // Has the connection been closed? + bool isWebsocket(); // Is this request to create a web socket? + std::map parseForm(); // Parse the body as a form. + std::vector pathSplit(); + std::string urlDecode(std::string str); // Decode a URL. +private: + Socket m_clientSocket; // The socket connected to the client. + bool m_isClosed; // Is the client connection closed? + HttpParser m_parser; // The parse to parse HTTP data. + WebSocket* m_pWebSocket; // A possible reference to a WebSocket object instance. + +}; + +#endif /* COMPONENTS_CPP_UTILS_HTTPREQUEST_H_ */ diff --git a/cpp_utils/HttpResponse.cpp b/cpp_utils/HttpResponse.cpp new file mode 100644 index 00000000..46f3e9bd --- /dev/null +++ b/cpp_utils/HttpResponse.cpp @@ -0,0 +1,186 @@ +/* + * HttpResponse.cpp + * + * Created on: Sep 2, 2017 + * Author: kolban + */ +#include +#include +#include "HttpRequest.h" +#include "HttpResponse.h" +#include + +static const char* LOG_TAG = "HttpResponse"; + +const int HttpResponse::HTTP_STATUS_CONTINUE = 100; +const int HttpResponse::HTTP_STATUS_SWITCHING_PROTOCOL = 101; +const int HttpResponse::HTTP_STATUS_OK = 200; +const int HttpResponse::HTTP_STATUS_MOVED_PERMANENTLY = 301; +const int HttpResponse::HTTP_STATUS_BAD_REQUEST = 400; +const int HttpResponse::HTTP_STATUS_UNAUTHORIZED = 401; +const int HttpResponse::HTTP_STATUS_FORBIDDEN = 403; +const int HttpResponse::HTTP_STATUS_NOT_FOUND = 404; +const int HttpResponse::HTTP_STATUS_METHOD_NOT_ALLOWED = 405; +const int HttpResponse::HTTP_STATUS_INTERNAL_SERVER_ERROR = 500; +const int HttpResponse::HTTP_STATUS_NOT_IMPLEMENTED = 501; +const int HttpResponse::HTTP_STATUS_SERVICE_UNAVAILABLE = 503; + +static std::string lineTerminator = "\r\n"; +HttpResponse::HttpResponse(HttpRequest* request) { + m_request = request; + m_status = 200; + m_headerCommitted = false; // We have not yet sent a header. +} + + +HttpResponse::~HttpResponse() { +} + + +/** + * @brief Add a header to the response message. + * If the response has already been committed then ignore this request. + * @param [in] name The name of the header. + * @param [in] value The value of the header. + */ +void HttpResponse::addHeader(const std::string name, const std::string value) { + if (m_headerCommitted) return; + m_responseHeaders.insert(std::pair(name, value)); +} // addHeader + + +/** + * @brief Close the response. + * We close the response. If we haven't yet sent the header, we send that now and then close + * the socket. + */ +void HttpResponse::close() { + // If we haven't yet sent the header of the data, send that now. + if (!m_headerCommitted) { + sendHeader(); + } + m_request->close(); +} // close + + +/** + * @brief Get the value of the named header. + * @param [in] name The name of the header for which the value is to be returned. + * @return The value of the named header. + */ +std::string HttpResponse::getHeader(std::string name) { + if (m_responseHeaders.find(name) == m_responseHeaders.end()) return ""; + return m_responseHeaders.at(name); +} // getHeader + + +std::map HttpResponse::getHeaders() { + return m_responseHeaders; +} // getHeaders + + +/** + * @brief Send data to the partner. + * Send some data to the partner. If we haven't yet sent the HTTP header then send that now. We can call this function + * any number of times before calling close. After calling close, we may not call this function again. + * @param [in] data The data to send to the partner. + */ +void HttpResponse::sendData(std::string data) { + ESP_LOGD(LOG_TAG, ">> sendData"); + // If the request is already closed, nothing further to do. + if (m_request->isClosed()) { + ESP_LOGE(LOG_TAG, "<< sendData: Request to send more data but the request/response is already closed"); + return; + } + + // If we haven't yet sent the header of the data, send that now. + if (!m_headerCommitted) { + sendHeader(); + } + + // Send the payload data. + m_request->getSocket().send(data); + ESP_LOGD(LOG_TAG, "<< sendData"); +} // sendData + +void HttpResponse::sendData(uint8_t* pData, size_t size) { + ESP_LOGD(LOG_TAG, ">> sendData: 0x%x, size: %d", (uint32_t) pData, size); + // If the request is already closed, nothing further to do. + if (m_request->isClosed()) { + ESP_LOGE(LOG_TAG, "<< sendData: Request to send more data but the request/response is already closed"); + return; + } + + // If we haven't yet sent the header of the data, send that now. + if (!m_headerCommitted) { + sendHeader(); + } + + // Send the payload data. + m_request->getSocket().send(pData, size); + ESP_LOGD(LOG_TAG, "<< sendData"); +} // sendData + +void HttpResponse::sendFile(std::string fileName, size_t bufSize) { + ESP_LOGI(LOG_TAG, "Opening file: %s", fileName.c_str()); + std::ifstream ifStream; + ifStream.open(fileName, std::ifstream::in | std::ifstream::binary); // Attempt to open the file for reading. + + // If we failed to open the requested file, then it probably didn't exist so return a not found. + if (!ifStream.is_open()) { + ESP_LOGE(LOG_TAG, "Unable to open file %s for reading", fileName.c_str()); + setStatus(HttpResponse::HTTP_STATUS_NOT_FOUND, "Not Found"); + addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/plain"); + sendData("Not Found"); + close(); + return; // Since we failed to open the file, no further work to be done. + } + + // We now have an open file and want to push the content of that file through to the browser. + // because of defect #252 we have to do some pretty important re-work here. Specifically, we can't host the whole file in + // RAM at one time. Instead what we have to do is ensure that we only have enough data in RAM to be sent. + + setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + uint8_t *pData = new uint8_t[bufSize]; + while (!ifStream.eof()) { + ifStream.read((char*) pData, bufSize); + sendData(pData, ifStream.gcount()); + } + delete[] pData; + ifStream.close(); + close(); +} // sendFile + +/** + * @brief Send the header + */ +void HttpResponse::sendHeader() { + // If we haven't yet sent the header of the data, send that now. + if (!m_headerCommitted) { + std::ostringstream oss; + oss << m_request->getVersion() << " " << m_status << " " << m_statusMessage << lineTerminator; + for (auto it = m_responseHeaders.begin(); it != m_responseHeaders.end(); ++it) { + oss << it->first.c_str() << ": " << it->second.c_str() << lineTerminator; + } + oss << lineTerminator; + m_headerCommitted = true; + m_request->getSocket().send(oss.str()); + } +} // sendHeader + + +/** + * @brief Set the status code that is to be sent back to the client. + * When a client makes a request, the response contains a status. This call sets the status that + * will be sent back to the client. + * @param [in] status The status code to be sent back to the caller. + * @param [in] message The message to be sent with the status code. + */ +void HttpResponse::setStatus(const int status, const std::string message) { + if (m_headerCommitted) { // If the header has already been sent, don't set the new values. + ESP_LOGW(LOG_TAG, "Attempt to set a new status(%d)/message(%s) but header has already been committed.", status, message.c_str()); + return; + } + m_status = status; + m_statusMessage = message; +} // setStatus diff --git a/cpp_utils/HttpResponse.h b/cpp_utils/HttpResponse.h new file mode 100644 index 00000000..5dfaa976 --- /dev/null +++ b/cpp_utils/HttpResponse.h @@ -0,0 +1,54 @@ +/* + * HttpResponse.h + * + * Encapsulate a response to be sent to the Http client. + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ +#define COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ +#include +#include +#include "HttpRequest.h" + +class HttpResponse { +public: + static const int HTTP_STATUS_CONTINUE; + static const int HTTP_STATUS_SWITCHING_PROTOCOL; + static const int HTTP_STATUS_OK; + static const int HTTP_STATUS_MOVED_PERMANENTLY; + static const int HTTP_STATUS_BAD_REQUEST; + static const int HTTP_STATUS_UNAUTHORIZED; + static const int HTTP_STATUS_FORBIDDEN; + static const int HTTP_STATUS_NOT_FOUND; + static const int HTTP_STATUS_METHOD_NOT_ALLOWED; + static const int HTTP_STATUS_INTERNAL_SERVER_ERROR; + static const int HTTP_STATUS_NOT_IMPLEMENTED; + static const int HTTP_STATUS_SERVICE_UNAVAILABLE; + + HttpResponse(HttpRequest* httpRequest); + virtual ~HttpResponse(); + + void addHeader(std::string name, std::string value); // Add a header to be sent to the client. + void close(); // Close the request/response. + std::string getHeader(std::string name); // Get a named header. + std::map getHeaders(); // Get all headers. + void sendData(std::string data); // Send data to the client. + void sendData(uint8_t* pData, size_t size); // Send data to the client. + void setStatus(int status, std::string message); // Set the response status. + void sendFile(std::string fileName, size_t bufSize = 4 * 1024); // Send file contents if exists. + +private: + bool m_headerCommitted; // Has the header been sent? + HttpRequest* m_request; // The request associated with this response. + std::map m_responseHeaders; // The headers to be sent with the response. + int m_status; // The status to be sent with the response. + std::string m_statusMessage; // The status message to be sent with the response. + + void sendHeader(); // Send the header to the client. + +}; + +#endif /* COMPONENTS_CPP_UTILS_HTTPRESPONSE_H_ */ diff --git a/cpp_utils/HttpServer.cpp b/cpp_utils/HttpServer.cpp new file mode 100644 index 00000000..fb90fcc9 --- /dev/null +++ b/cpp_utils/HttpServer.cpp @@ -0,0 +1,491 @@ +/* + * HttpServer.cpp + * Design: + * This class represents an HTTP server. We create an instance of the class and then it is configured. + * When the user has completed the configuration, they execute the start() method which starts it running. + * A subsequent call to stop() will stop it. When start() is called, a new task is created which listens + * for incoming messages. + * + * Created on: Aug 30, 2017 + * Author: kolban + */ + +#include +#include "HttpServer.h" +#include "SockServ.h" +#include "Task.h" +#include +#include "HttpRequest.h" +#include "HttpResponse.h" +#include "FileSystem.h" +#include "WebSocket.h" +#include "GeneralUtils.h" +#include "Memory.h" +static const char* LOG_TAG = "HttpServer"; + +#undef close + + +/** + * Constructor for HTTP Server + */ +HttpServer::HttpServer() { + m_portNumber = 80; // The default port number. + m_clientTimeout = 5; // The default timeout 5 seconds. + m_rootPath = ""; // The default path. + m_useSSL = false; // Default SSL is no. + setDirectoryListing(false); // Default directory listing is disabled. + m_fileBufferSize = 4 * 1024; // Default size of the file buffer. +} // HttpServer + + +HttpServer::~HttpServer() { + ESP_LOGD(LOG_TAG, "~HttpServer"); +} + + +/** + * @brief Be an HTTP server task. + * Here we define a Task that will be run when the HTTP server starts. It is this task + * that executes the majority of the passive work of the server. It listens for incoming + * connections and processes them when they arrive. + */ +class HttpServerTask: public Task { +public: + HttpServerTask(std::string name): Task(name, 16 * 1024) { + m_pHttpServer = nullptr; + }; + +private: + HttpServer* m_pHttpServer; // Reference to the HTTP Server + + /** + * @brief Process an incoming HTTP Request + * + * We examine each of the path handlers to see if we have a match for the method/path pair. If we do, + * we invoke the handler callback passing in both the request and response. + * + * If we didn't find a handler, then we are going to behave as a Web Server and try and serve up the + * content from the file on the "file system". + * + * @param [in] request The HTTP request to process. + */ + void processRequest(HttpRequest& request) { + ESP_LOGD("HttpServerTask", ">> processRequest: Method: %s, Path: %s", + request.getMethod().c_str(), request.getPath().c_str()); + + // Loop over all the path handlers we have looking for the first one that matches. Note that none of them + // need to match. If we find one that does, then invoke the handler and that is the end of processing. + for (auto pathHandlerIterartor = m_pHttpServer->m_pathHandlers.begin(); + pathHandlerIterartor != m_pHttpServer->m_pathHandlers.end(); + ++pathHandlerIterartor) { + if (pathHandlerIterartor->match(request.getMethod(), request.getPath())) { // Did we match the handler? + ESP_LOGD("HttpServerTask", "Found a path handler match!!"); + if (request.isWebsocket()) { // Is this handler to be invoked for a web socket? + pathHandlerIterartor->invokePathHandler(&request, nullptr); // Invoke the handler. + request.getWebSocket()->startReader(); + } else { + HttpResponse response(&request); + pathHandlerIterartor->invokePathHandler(&request, &response); // Invoke the handler. + } + return; // End of processing the request + } // Path handler match + } // For each path handler + + ESP_LOGD("HttpServerTask", "No Path handler found"); + // If we reach here, then we did not find a handler for the request. + + + if (request.isWebsocket()) { // Check to see if we have an un-handled WebSocket + request.getWebSocket()->close(); // If we do, close the socket as there is nothing further to do. + return; + } + + // Serve up the content from the file on the file system ... if found ... + + std::string fileName = m_pHttpServer->getRootPath() + request.getPath(); // Build the absolute file name to read. + + // If the file name ends with a '/' then remove it ... we are normalizing to NO trailing slashes. + if (GeneralUtils::endsWith(fileName, '/')) { + fileName = fileName.substr(0, fileName.length() - 1); + } + + HttpResponse response(&request); + // Test if the path is a directory. + if (FileSystem::isDirectory(fileName)) { + ESP_LOGD(LOG_TAG, "Path %s is a directory", fileName.c_str()); + m_pHttpServer->listDirectory(fileName, response); // List the contents of the directory. + return; + } // Path was a directory. + + response.sendFile(fileName, m_pHttpServer->getFileBufferSize()); + } // processRequest + + + /** + * @brief Perform the task handling for server. + * We loop forever waiting for new client connections to arrive. When they do, we parse the + * content and look for a handler for that content. + * @param [in] data A reference to the HttpServer. + */ + void run(void* data) { + m_pHttpServer = (HttpServer*) data; // The passed in data is an instance of an HttpServer. + m_pHttpServer->m_socket.setSSL(m_pHttpServer->m_useSSL); + m_pHttpServer->m_socket.listen(m_pHttpServer->m_portNumber, false /* is datagram */, true /* Allow address reuse */); + ESP_LOGD("HttpServerTask", "Listening on port %d", m_pHttpServer->getPort()); + Socket clientSocket; + while (true) { // Loop forever. + ESP_LOGD("HttpServerTask", "Waiting for new peer client"); + + try { + clientSocket = m_pHttpServer->m_socket.accept(); // Block waiting for a new external client connection. + clientSocket.setTimeout(m_pHttpServer->getClientTimeout()); + } catch (std::exception& e) { + ESP_LOGE("HttpServerTask", "Caught an exception waiting for new client!"); + m_pHttpServer->m_semaphoreServerStarted.give(); // Release the semaphore .. we are now no longer running. + return; + } + + ESP_LOGD("HttpServerTask", "HttpServer that was listening on port %d has received a new client connection; sockFd=%d", m_pHttpServer->getPort(), clientSocket.getFD()); + + HttpRequest request(clientSocket); // Build the HTTP Request from the socket. + if (request.isWebsocket()) { // If this is a WebSocket + clientSocket.setTimeout(0); // Clear the timeout. + } + request.dump(); // debug. + processRequest(request); // Process the request. + if (!request.isWebsocket()) { // If this is NOT a WebSocket, then close it as the request + request.close(); // has been completed. + } + } // while + } // run +}; // HttpServerTask + + +/** + * @brief Register a handler for a path. + * + * When a browser request arrives, the request will contain a method (GET, POST, etc) and a path + * to be accessed. Using this method we can register a regular expression and, if the incoming method + * and path match the expression, the corresponding handler will be called. + * + * Example: + * @code{.cpp} + * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { + * ... + * } + * + * webServer.addPathHandler("GET", "\\/ESP32\\/WiFi", handle_REST_WiFi); + * @endcode + * + * @param [in] method The method being used for access ("GET", "POST" etc). + * @param [in] pathExpr The path being accessed. + * @param [in] handler The callback function to be invoked when a request arrives. + */ +void HttpServer::addPathHandler( + std::string method, + std::regex* pathExpr, + void (*handler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)) { + + // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. + m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); +} // addPathHandler + + +/** + * @brief Register a handler for a path. + * + * When a browser request arrives, the request will contain a method (GET, POST, etc) and a path + * to be accessed. Using this method we can register a regular expression and, if the incoming method + * and path match the expression, the corresponding handler will be called. + * + * Example: + * @code{.cpp} + * static void handle_REST_WiFi(WebServer::HttpRequest *pRequest, WebServer::HttpResponse *pResponse) { + * ... + * } + * + * webServer.addPathHandler("GET", "/ESP32/WiFi", handle_REST_WiFi); + * @endcode + * + * @param [in] method The method being used for access ("GET", "POST" etc). + * @param [in] path The plain path being accessed. + * @param [in] handler The callback function to be invoked when a request arrives. + */ +void HttpServer::addPathHandler( + std::string method, + std::string path, + void (*handler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse)) { + + // We are maintaining a C++ vector of PathHandler objects. We add a new entry into that vector. + m_pathHandlers.push_back(PathHandler(method, path, handler)); +} // addPathHandler + + +/** + * @brief Get the size of the file buffer. + * When serving up a file from the file system, we can't afford to read the whole file into RAM before + * sending it. As such, we must read the file in chunks. The buffer size is the size of a chunk to be + * transmitted before the next chunk is read. + * @return The file buffer size. + */ +size_t HttpServer::getFileBufferSize() { + return m_fileBufferSize; +} // getFileBufferSize + + +/** + * @brief Get the port number on which the HTTP Server is listening. + * @return The port number on which the HTTP server is listening. + */ +uint16_t HttpServer::getPort() { + return m_portNumber; +} // getPort + + +/** + * @brief Get the current root path. + * @return The current root path. + */ +std::string HttpServer::getRootPath() { + return m_rootPath; +} // getRootPath + + +/** + * @brief Return whether or not we are using SSL. + * @return True if we are using SSL. + */ +bool HttpServer::getSSL() { + return m_useSSL; +} // getSSL + + +/** + * Send a directory listing back to the browser. + * @param [in] path The path of the directory to list. + * @param [in] response The response object to use to send data back to the browser. + */ +void HttpServer::listDirectory(std::string path, HttpResponse& response) { + // If path ends with a "/" then remove it. + if (GeneralUtils::endsWith(path, '/')) { + path = path.substr(0, path.length()-1); + } + + // The url path may not be the same as path which is the path on the file system. + // They will differ if we are using a root path. We check to see if the file system path + // starts with the root path, if it does, we remove the root path from the url path. + std::string urlPath = path; + if (urlPath.compare(0, getRootPath().length(), getRootPath()) == 0) { + urlPath = urlPath.substr(getRootPath().length()); + } + + response.addHeader("Content-Type", "text/html"); + response.setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + response.sendData(""); + if (!GeneralUtils::endsWith(path, '/')) { + response.sendData(""); + } + response.sendData(""); + response.sendData("

" + path + "

"); + response.sendData("
"); + response.sendData("

[To Parent Directory]

"); + response.sendData(""); + auto files = FileSystem::getDirectoryContents(path); + for (auto it = files.begin(); it != files.end(); ++it) { + std::stringstream ss; + ss << ""; + if (it->isDirectory()) { + ss << ""; + } else { + ss << ""; + } + + ss << ""; + response.sendData(ss.str()); + ESP_LOGD(LOG_TAG, "file: %s", ss.str().c_str()); + } + response.sendData("
" << it->getName() << "<dir>" << it->length() << "
"); + response.sendData("
"); + response.sendData(""); + response.close(); +} // listDirectory + + +/** + * @brief Set different socket timeout for new connections. + * @param [in] use Set to true to enable directory listing. + */ +void HttpServer::setClientTimeout(uint32_t timeout) { + m_clientTimeout = timeout; +} + + +/** + * @brief Get current socket's timeout for new connections. + * @param [in] use Set to true to enable directory listing. + */ +uint32_t HttpServer::getClientTimeout() { + return m_clientTimeout; +} + + +/** + * @brief Set whether or not we will list directories. + * @param [in] use Set to true to enable directory listing. + */ +void HttpServer::setDirectoryListing(bool use) { + m_directoryListing = use; +} // setDirectoryListening + + +/** + * @brief Set the size of the file buffer. + * When serving up a file from the file system, we can't afford to read the whole file into RAM before + * sending it. As such, we must read the file in chunks. The buffer size is the size of a chunk to be + * transmitted before the next chunk is read. + * @param [in] fileBufferSize How large should the file buffer size be? + */ +void HttpServer::setFileBufferSize(size_t fileBufferSize) { + m_fileBufferSize = fileBufferSize; +} // setFileBufferSize + + +/** + * @brief Set the root path for URL file mapping. + * + * When a browser requests a file, it uses the address form: + * + * @code{.unparsed} + * http://:/ + * @endcode + * + * The path part can be considered the path to where the file should be retrieved on the + * file system available to the web server. Typically, we want a directory structure on the file + * system to host the web served files and not expose the whole file system. Using this method + * we specify the root directory from which the files will be served. + * + * @param [in] path The root path on the file system. + * @return N/A. + */ +void HttpServer::setRootPath(std::string path) { + // Should the user have supplied a path that ends in a "/" remove the trailing slash. This also + // means that "/" becomes "". + if (GeneralUtils::endsWith(path, '/')) { + path = path.substr(0, path.length()-1); + } + m_rootPath = path; +} // setRootPath + + +/** + * @brief Start the HTTP server listening. + * We start an instance of the HTTP server listening. A new task is spawned to perform this work in the + * back ground. + * @param [in] portNumber The port number on which the HTTP server should listen. + * @param [in] useSSL Should we use SSL? + */ +void HttpServer::start(uint16_t portNumber, bool useSSL) { + // Design: + // The start of the HTTP server should be as fast as possible. + ESP_LOGD(LOG_TAG, ">> start: port: %d, useSSL: %d", portNumber, useSSL); + + // Take the semaphore that says that we are now running. If we are already running, then end here as + // there is nothing further to do. + if (!m_semaphoreServerStarted.take(100, "start")) { + ESP_LOGD(LOG_TAG, "<< start: Already running"); + return; + } + + m_useSSL = useSSL; + m_portNumber = portNumber; + + HttpServerTask* pHttpServerTask = new HttpServerTask("HttpServerTask"); + pHttpServerTask->start(this); + ESP_LOGD(LOG_TAG, "<< start"); +} // start + + +/** + * @brief Shutdown the HTTP server. + */ +void HttpServer::stop() { + // Shutdown the HTTP Server. The high level is that we will stop the server socket + // that is listening for incoming connections. That will then shutdown all the other + // activities. + ESP_LOGD(LOG_TAG, ">> stop"); + m_socket.close(); // Close the socket that is being used to watch for incoming requests. + m_semaphoreServerStarted.wait("stop"); // Wait for the server to stop. + ESP_LOGD(LOG_TAG, "<< stop"); +} // stop + + +/** + * @brief Construct an instance of a PathHandler. + * + * @param [in] method The method to be matched. + * @param [in] pathPattern The path pattern to be matched. + * @param [in] webServerRequestHandler The request handler to be called. + */ +PathHandler::PathHandler(std::string method, std::regex* pRegex, + void (*pWebServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ) { + m_method = method; // Save the method we are looking for. + m_pRegex = pRegex; // Save the Regex + m_textPattern = ""; // The plain text of the regex pattern. + m_isRegex = true; + m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. +} // PathHandler + + +/** + * @brief Construct an instance of a PathHandler. + * + * @param [in] method The method to be matched. + * @param [in] pathPattern The path to be matched. Must be an exact match. + * @param [in] webServerRequestHandler The request handler to be called. + */ +PathHandler::PathHandler(std::string method, std::string matchPath, + void (*pWebServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ) { + m_method = method; // Save the method we are looking for. + m_textPattern = matchPath; + m_isRegex = false; + m_pRegex = nullptr; + m_pRequestHandler = pWebServerRequestHandler; // The handler to be invoked if the pattern matches. +} // PathHandler + + +/** + * @brief Determine if the path matches. + * + * @param [in] method The method to be matched. + * @param [in] path The path to be matched. + * @return True if the path matches. + */ +bool PathHandler::match(std::string method, std::string path) { + if (method != m_method) return false; + if (m_isRegex) { + ESP_LOGD("PathHandler", "regex matching: %s with %s", m_textPattern.c_str(), path.c_str()); + return std::regex_search(path, *m_pRegex); + } + ESP_LOGD("PathHandler", "plain matching: %s with %s", m_textPattern.c_str(), path.c_str()); + return m_textPattern.compare(0, m_textPattern.length(), path) ==0; +} // match + + +/** + * @brief Invoke the handler. + * @param [in] request An object representing the request. + * @param [in] response An object representing the response. + * @return N/A. + */ +void PathHandler::invokePathHandler(HttpRequest* request, HttpResponse* response) { + m_pRequestHandler(request, response); +} // invokePathHandler diff --git a/cpp_utils/HttpServer.h b/cpp_utils/HttpServer.h new file mode 100644 index 00000000..8baa7bbb --- /dev/null +++ b/cpp_utils/HttpServer.h @@ -0,0 +1,104 @@ +/* + * HttpServer.h + * + * Created on: Aug 30, 2017 + * Author: kolban + * + * Implementation of an HTTP server for the ESP32. + * + */ + +#ifndef COMPONENTS_CPP_UTILS_HTTPSERVER_H_ +#define COMPONENTS_CPP_UTILS_HTTPSERVER_H_ +#include + +#include +#include "SockServ.h" +#include "HttpRequest.h" +#include "HttpResponse.h" +#include "FreeRTOS.h" +#include + +class HttpServerTask; + +/** + * @brief Handle path matching for an incoming HTTP request. + */ +class PathHandler { + public: + PathHandler( + std::string method, // The method in the request to be matched. + std::string pathPattern, // The pattern in the request to be matched + void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + PathHandler( + std::string method, // The method in the request to be matched. + std::regex* pathPattern, // The pattern in the request to be matched (regex) + void (*pWebServerRequestHandler) // The handler function to be invoked upon a match. + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + bool match(std::string method, std::string path); // Does the request method and pattern match? + void invokePathHandler(HttpRequest* request, HttpResponse* response); + private: + std::string m_method; + std::regex* m_pRegex; + bool m_isRegex; + std::string m_textPattern; + void (*m_pRequestHandler)(HttpRequest* pHttpRequest, HttpResponse* pHttpResponse); +}; // PathHandler + + +class HttpServer { +public: + HttpServer(); + virtual ~HttpServer(); + + void addPathHandler( + std::string method, + std::string pathExpr, + void (*webServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + void addPathHandler( + std::string method, + std::regex* pRegex, + void (*webServerRequestHandler) + ( + HttpRequest* pHttpRequest, + HttpResponse* pHttpResponse) + ); + uint32_t getClientTimeout(); // Get client's socket timeout + size_t getFileBufferSize(); // Get the current size of the file buffer. + uint16_t getPort(); // Get the port on which the Http server is listening. + std::string getRootPath(); // Get the root of the file system path. + bool getSSL(); // Are we using SSL? + void setClientTimeout(uint32_t timeout); // Set client's socket timeout + void setDirectoryListing(bool use); // Should we list the content of directories? + void setFileBufferSize(size_t fileBufferSize); // Set the size of the file buffer + void setRootPath(std::string path); // Set the root of the file system path. + void start(uint16_t portNumber, bool useSSL = false); + void stop(); // Stop a previously started server. + +private: + friend class HttpServerTask; + friend class WebSocket; + void listDirectory(std::string path, HttpResponse& response); + size_t m_fileBufferSize; // Size of the file buffer. + bool m_directoryListing; // Should we list directory content? + std::vector m_pathHandlers; // Vector of path handlers. + uint16_t m_portNumber; // Port number on which server is listening. + std::string m_rootPath; // Root path into the file system. + Socket m_socket; + bool m_useSSL; // Is this server listening on an HTTPS port? + uint32_t m_clientTimeout; // Default Timeout + FreeRTOS::Semaphore m_semaphoreServerStarted = FreeRTOS::Semaphore("ServerStarted"); +}; // HttpServer + +#endif /* COMPONENTS_CPP_UTILS_HTTPSERVER_H_ */ diff --git a/cpp_utils/I2C.cpp b/cpp_utils/I2C.cpp index b3a19923..d2413bae 100644 --- a/cpp_utils/I2C.cpp +++ b/cpp_utils/I2C.cpp @@ -12,6 +12,7 @@ #include "I2C.h" #include "sdkconfig.h" #include +#include "GeneralUtils.h" static const char* LOG_TAG = "I2C"; @@ -22,11 +23,12 @@ static bool debug = false; * @return N/A. */ I2C::I2C() { - directionKnown = false; - address = 0; - cmd = 0; - sdaPin = DEFAULT_SDA_PIN; - sclPin = DEFAULT_SDA_PIN; + m_directionKnown = false; + m_address = 0; + m_cmd = 0; + m_sdaPin = DEFAULT_SDA_PIN; + m_sclPin = DEFAULT_CLK_PIN; + m_portNum = I2C_NUM_0; } // I2C @@ -40,9 +42,12 @@ void I2C::beginTransaction() { if (debug) { ESP_LOGD(LOG_TAG, "beginTransaction()"); } - cmd = ::i2c_cmd_link_create(); - ESP_ERROR_CHECK(::i2c_master_start(cmd)); - directionKnown = false; + m_cmd = ::i2c_cmd_link_create(); + esp_err_t errRc = ::i2c_master_start(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_directionKnown = false; } // beginTransaction @@ -57,13 +62,30 @@ void I2C::endTransaction() { if (debug) { ESP_LOGD(LOG_TAG, "endTransaction()"); } - ESP_ERROR_CHECK(::i2c_master_stop(cmd)); - ESP_ERROR_CHECK(::i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS)); - ::i2c_cmd_link_delete(cmd); - directionKnown = false; + esp_err_t errRc = ::i2c_master_stop(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + errRc = ::i2c_master_cmd_begin(m_portNum, m_cmd, 1000 / portTICK_PERIOD_MS); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_cmd_begin: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + ::i2c_cmd_link_delete(m_cmd); + m_directionKnown = false; } // endTransaction +/** + * @brief Get the address of the %I2C slave against which we are working. + * + * @return The address of the %I2C slave. + */ +uint8_t I2C::getAddress() const { + return m_address; +} + + /** * @brief Initialize the I2C interface. * @@ -72,21 +94,30 @@ void I2C::endTransaction() { * @param [in] sclPin The pin to use for SCL clock. * @return N/A. */ -void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin) { - ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d", address, sdaPin, sclPin); - this->sdaPin = sdaPin; - this->sclPin = sclPin; - this->address = address; +void I2C::init(uint8_t address, gpio_num_t sdaPin, gpio_num_t sclPin, uint32_t clockSpeed, i2c_port_t portNum, bool pullup) { + ESP_LOGD(LOG_TAG, ">> I2c::init. address=%d, sda=%d, scl=%d, clockSpeed=%d, portNum=%d", address, sdaPin, sclPin, clockSpeed, portNum); + assert(portNum < I2C_NUM_MAX); + m_portNum = portNum; + m_sdaPin = sdaPin; + m_sclPin = sclPin; + m_address = address; + i2c_config_t conf; - conf.mode = I2C_MODE_MASTER; + conf.mode = I2C_MODE_MASTER; conf.sda_io_num = sdaPin; conf.scl_io_num = sclPin; - conf.sda_pullup_en = GPIO_PULLUP_ENABLE; - conf.scl_pullup_en = GPIO_PULLUP_ENABLE; - conf.master.clk_speed = 100000; - ESP_ERROR_CHECK(::i2c_param_config(I2C_NUM_0, &conf)); + conf.sda_pullup_en = pullup ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.scl_pullup_en = pullup ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE; + conf.master.clk_speed = clockSpeed; + esp_err_t errRc = ::i2c_param_config(m_portNum, &conf); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_param_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } if (!driverInstalled) { - ESP_ERROR_CHECK(::i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0)); + errRc = ::i2c_driver_install(m_portNum, I2C_MODE_MASTER, 0, 0, 0); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_driver_install: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } driverInstalled = true; } } // init @@ -104,11 +135,17 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=%d, ack=%d)", length, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, !ack)); + if (!m_directionKnown) { + m_directionKnown = true; + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_read(m_cmd, bytes, length, ack?I2C_MASTER_ACK:I2C_MASTER_NACK); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_read: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_read(cmd, bytes, length, !ack)); } // read @@ -119,15 +156,18 @@ void I2C::read(uint8_t* bytes, size_t length, bool ack) { * @param [in] ack Whether or not we should send an ACK to the slave after reading a byte. * @return N/A. */ -void I2C::read(uint8_t *byte, bool ack) { +void I2C::read(uint8_t* byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "read(size=1, ack=%d)", ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, !ack)); + if (!m_directionKnown) { + m_directionKnown = true; + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_READ, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } } - ESP_ERROR_CHECK(::i2c_master_read_byte(cmd, byte, !ack)); + ESP_ERROR_CHECK(::i2c_master_read_byte(m_cmd, byte, ack?I2C_MASTER_ACK:I2C_MASTER_NACK)); } // readByte @@ -139,33 +179,33 @@ void I2C::read(uint8_t *byte, bool ack) { * @return N/A. */ void I2C::scan() { - int i; - esp_err_t espRc; - printf("Data Pin: %d, Clock Pin: %d\n", this->sdaPin, this->sclPin); + printf("Data Pin: %d, Clock Pin: %d\n", this->m_sdaPin, this->m_sclPin); printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); printf("00: "); - for (i=3; i< 0x78; i++) { - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); - i2c_master_stop(cmd); - - espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 100/portTICK_PERIOD_MS); - if (i%16 == 0) { + for (uint8_t i = 3; i < 0x78; i++) { + if (i % 16 == 0) { printf("\n%.2x:", i); } - if (espRc == 0) { + if (slavePresent(i)) { printf(" %.2x", i); } else { printf(" --"); } - //ESP_LOGD(tag, "i=%d, rc=%d (0x%x)", i, espRc, espRc); - i2c_cmd_link_delete(cmd); } printf("\n"); } // scan +/** + * @brief Set the address of the %I2C slave against which we will be working. + * + * @param [in] address The address of the %I2C slave. + */ +void I2C::setAddress(uint8_t address) { + this->m_address = address; +} // setAddress + + /** * @brief enable or disable debugging. * @param [in] enabled Should debugging be enabled or disabled. @@ -176,6 +216,23 @@ void I2C::setDebug(bool enabled) { } // setDebug +/** + * @brief Determine if the slave is present and responding. + * @param [in] The address of the slave. + * @return True if the slave is present and false otherwise. + */ +bool I2C::slavePresent(uint8_t address) { + i2c_cmd_handle_t cmd = ::i2c_cmd_link_create(); + ::i2c_master_start(cmd); + ::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, 1 /* expect ack */); + ::i2c_master_stop(cmd); + + esp_err_t espRc = ::i2c_master_cmd_begin(m_portNum, cmd, 100 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return espRc == 0; // Return true if the slave is present and false otherwise. +} // slavePresent + + /** * @brief Add an %I2C start request to the command stream. * @return N/A. @@ -184,7 +241,11 @@ void I2C::start() { if (debug) { ESP_LOGD(LOG_TAG, "start()"); } - ESP_ERROR_CHECK(::i2c_master_start(cmd)); + esp_err_t errRc = ::i2c_master_start(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_directionKnown = false; } // start @@ -196,8 +257,11 @@ void I2C::stop() { if (debug) { ESP_LOGD(LOG_TAG, "stop()"); } - ESP_ERROR_CHECK(::i2c_master_stop(cmd)); - directionKnown = false; + esp_err_t errRc = ::i2c_master_stop(m_cmd); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_stop: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + m_directionKnown = false; } // stop @@ -212,11 +276,17 @@ void I2C::write(uint8_t byte, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(val=0x%.2x, ack=%d)", byte, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, !ack)); + if (!m_directionKnown) { + m_directionKnown = true; + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, byte, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, byte, !ack)); } // write @@ -232,11 +302,15 @@ void I2C::write(uint8_t *bytes, size_t length, bool ack) { if (debug) { ESP_LOGD(LOG_TAG, "write(length=%d, ack=%d)", length, ack); } - if (directionKnown == false) { - directionKnown = true; - ESP_ERROR_CHECK(::i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, !ack)); + if (!m_directionKnown) { + m_directionKnown = true; + esp_err_t errRc = ::i2c_master_write_byte(m_cmd, (m_address << 1) | I2C_MASTER_WRITE, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write_byte: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } + esp_err_t errRc = ::i2c_master_write(m_cmd, bytes, length, !ack); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "i2c_master_write: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } - ESP_ERROR_CHECK(::i2c_master_write(cmd, bytes, length, !ack)); } // write - - diff --git a/cpp_utils/I2C.h b/cpp_utils/I2C.h index 5142723c..64b63427 100644 --- a/cpp_utils/I2C.h +++ b/cpp_utils/I2C.h @@ -17,55 +17,46 @@ * @brief Interface to %I2C functions. */ class I2C { -private: - uint8_t address; - i2c_cmd_handle_t cmd; - bool directionKnown; - gpio_num_t sdaPin; - gpio_num_t sclPin; - public: - I2C(); - void beginTransaction(); - void endTransaction(); /** - * @brief Get the address of the %I2C slave against which we are working. - * - * @return The address of the %I2C slave. + * @brief The default SDA pin. */ - uint8_t getAddress() const - { - return address; - } + static const gpio_num_t DEFAULT_SDA_PIN = GPIO_NUM_25; - void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN); - void read(uint8_t *bytes, size_t length, bool ack=true); - void read(uint8_t *byte, bool ack=true); + /** + * @brief The default Clock pin. + */ + static const gpio_num_t DEFAULT_CLK_PIN = GPIO_NUM_26; /** - * @brief Set the address of the %I2C slave against which we will be working. - * - * @param[in] address The address of the %I2C slave. + * @brief The default Clock speed. */ - void setAddress(uint8_t address) - { - this->address = address; - } + static const uint32_t DEFAULT_CLK_SPEED = 100000; - void setDebug(bool enabled); + I2C(); + void beginTransaction(); + void endTransaction(); + uint8_t getAddress() const; + void init(uint8_t address, gpio_num_t sdaPin = DEFAULT_SDA_PIN, gpio_num_t sclPin = DEFAULT_CLK_PIN, uint32_t clkSpeed = DEFAULT_CLK_SPEED, i2c_port_t portNum = I2C_NUM_0, bool pullup = true); + void read(uint8_t* bytes, size_t length, bool ack = true); + void read(uint8_t* byte, bool ack = true); void scan(); + void setAddress(uint8_t address); + void setDebug(bool enabled); + bool slavePresent(uint8_t address); void start(); void stop(); - void write(uint8_t byte, bool ack=true); - void write(uint8_t *bytes, size_t length, bool ack=true); - /** - * @brief The default SDA pin. - */ - static const gpio_num_t DEFAULT_SDA_PIN = GPIO_NUM_25; - /** - * @brief The default Clock pin. - */ - static const gpio_num_t DEFAULT_CLK_PIN = GPIO_NUM_26; + void write(uint8_t byte, bool ack = true); + void write(uint8_t* bytes, size_t length, bool ack = true); + +private: + uint8_t m_address; + i2c_cmd_handle_t m_cmd; + bool m_directionKnown; + gpio_num_t m_sdaPin; + gpio_num_t m_sclPin; + i2c_port_t m_portNum; + }; #endif /* MAIN_I2C_H_ */ diff --git a/cpp_utils/I2S.cpp b/cpp_utils/I2S.cpp index c5bcb997..15ba2fa7 100644 --- a/cpp_utils/I2S.cpp +++ b/cpp_utils/I2S.cpp @@ -27,7 +27,6 @@ static const char* LOG_TAG = "I2S"; static intr_handle_t i2s_intr_handle; - /** * A representation of a DMA buffer. */ @@ -67,6 +66,7 @@ DMABuffer::~DMABuffer() { delete[] m_desc.buf; } // ~DMABuffer + /** * @brief Dump the state of the buffer. * @return N/A @@ -74,7 +74,7 @@ DMABuffer::~DMABuffer() { void DMABuffer::dump() { std::ostringstream ss; ss << "size: " << m_desc.size; - ss << ", buf: 0x" << std::hex << (uint32_t)m_desc.buf << std::dec; + ss << ", buf: 0x" << std::hex << (uint32_t) m_desc.buf << std::dec; ss << ", length: " << m_desc.length; ss << ", offset: " << m_desc.offset; ss << ", sosf: " << m_desc.sosf; @@ -82,10 +82,8 @@ void DMABuffer::dump() { ss << ", owner: " << m_desc.owner; ESP_LOGD(LOG_TAG, "Desc: %s", ss.str().c_str()); int length = 100; - if (length > m_desc.length) { - length = m_desc.length; - } - GeneralUtils::hexDump((uint8_t*)m_desc.buf, length); + if (length > m_desc.length) length = m_desc.length; + GeneralUtils::hexDump((uint8_t*) m_desc.buf, length); } /** @@ -95,23 +93,15 @@ void DMABuffer::dump() { * @return The number of bytes actually copied. */ uint32_t DMABuffer::getData(uint8_t* pData, uint32_t length) { - uint8_t* pBuf = (uint8_t*)m_desc.buf; - if (length > getLength()) { - length = getLength(); - } + uint8_t* pBuf = (uint8_t*) m_desc.buf; + if (length > getLength()) length = getLength(); uint32_t i; - // // The descriptor buffer is filled with data that contains: - // // b1 00 b0 00 b3 00 b2 00 b5 00 b4 00 b7 00 b6 00 - // // Our goal is to populate the passed in buffer with data of the form: - // // b0 b1 b2 b3 b4 b5 b6 b7 ... - // // The following alogrithm does that. - // - for (i=0; igetDesc()); - pCurrentDMABuffer = pCurrentDMABuffer->getNext(); - I2S0.int_clr.val = I2S0.int_raw.val; - pI2S->m_dmaSemaphore.giveFromISR(); +static void IRAM_ATTR i2s_isr(void* arg) { + I2S* pI2S = (I2S*) arg; + //ESP_EARLY_LOGV(LOG_TAG, "I2S isr"); + pLastDMABuffer = pCurrentDMABuffer; + //logI2SIntr(); + //logDesc(pCurrentDMABuffer->getDesc()); + pCurrentDMABuffer = pCurrentDMABuffer->getNext(); + I2S0.int_clr.val = I2S0.int_raw.val; + pI2S->m_dmaSemaphore.giveFromISR(); } /** @@ -261,20 +246,20 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { const uint32_t const_high = 0x38; - gpio_matrix_in(config.pin_d0, I2S0I_DATA_IN0_IDX, false); - gpio_matrix_in(config.pin_d1, I2S0I_DATA_IN1_IDX, false); - gpio_matrix_in(config.pin_d2, I2S0I_DATA_IN2_IDX, false); - gpio_matrix_in(config.pin_d3, I2S0I_DATA_IN3_IDX, false); - gpio_matrix_in(config.pin_d4, I2S0I_DATA_IN4_IDX, false); - gpio_matrix_in(config.pin_d5, I2S0I_DATA_IN5_IDX, false); - gpio_matrix_in(config.pin_d6, I2S0I_DATA_IN6_IDX, false); - gpio_matrix_in(config.pin_d7, I2S0I_DATA_IN7_IDX, false); - gpio_matrix_in(config.pin_vsync, I2S0I_V_SYNC_IDX, true); - gpio_matrix_in(config.pin_href, I2S0I_H_SYNC_IDX, false); - //gpio_matrix_in(const_high, I2S0I_V_SYNC_IDX, false); - //gpio_matrix_in(const_high, I2S0I_H_SYNC_IDX, false); - gpio_matrix_in(const_high, I2S0I_H_ENABLE_IDX, false); - gpio_matrix_in(config.pin_pclk, I2S0I_WS_IN_IDX, false); + gpio_matrix_in(config.pin_d0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(config.pin_d1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(config.pin_d2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(config.pin_d3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(config.pin_d4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(config.pin_d5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(config.pin_d6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(config.pin_d7, I2S0I_DATA_IN7_IDX, false); + gpio_matrix_in(config.pin_vsync, I2S0I_V_SYNC_IDX, true); + gpio_matrix_in(config.pin_href, I2S0I_H_SYNC_IDX, false); +// gpio_matrix_in(const_high, I2S0I_V_SYNC_IDX, false); +// gpio_matrix_in(const_high, I2S0I_H_SYNC_IDX, false); + gpio_matrix_in(const_high, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(config.pin_pclk, I2S0I_WS_IN_IDX, false); // Enable and configure I2S peripheral periph_module_enable(PERIPH_I2S0_MODULE); @@ -345,10 +330,10 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { ESP_LOGD(LOG_TAG, "Initializing %d descriptors", desc_count); - DMABuffer *pFirst = new DMABuffer(); - DMABuffer *pLast = pFirst; - for (int i=1; isetNext(pNewDMABuffer); pLast = pNewDMABuffer; } @@ -356,45 +341,45 @@ void I2S::cameraMode(dma_config_t config, int desc_count, int sample_count) { pCurrentDMABuffer = pFirst; // I2S_RX_EOF_NUM_REG - I2S0.rx_eof_num = sample_count; - - // I2S_IN_LINK_REG -> I2S_INLINK_ADDR - I2S0.in_link.addr = (uint32_t) pFirst; - - // I2S_IN_LINK_REG -> I2S_INLINK_START - I2S0.in_link.start = 1; - - I2S0.int_clr.val = I2S0.int_raw.val; - I2S0.int_ena.val = 0; - I2S0.int_ena.in_done = 1; - - // Register the interrupt handler. - esp_intr_alloc( - ETS_I2S0_INTR_SOURCE, - ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, - &i2s_isr, - this, - &i2s_intr_handle); - - m_dmaSemaphore.take(); - // Start the interrupt handler - esp_intr_enable(i2s_intr_handle); - - I2S0.conf.rx_start = 1; - - /* - while(1) { - m_dmaSemaphore.wait(); - uint32_t dataLength = pLastDMABuffer->getLength(); - ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dataLength); - //pLastDMABuffer->dump(); - uint8_t *pData = new uint8_t[dataLength]; - pLastDMABuffer->getData(pData, dataLength); + I2S0.rx_eof_num = sample_count; + + // I2S_IN_LINK_REG -> I2S_INLINK_ADDR + I2S0.in_link.addr = (uint32_t) pFirst; + + // I2S_IN_LINK_REG -> I2S_INLINK_START + I2S0.in_link.start = 1; + + I2S0.int_clr.val = I2S0.int_raw.val; + I2S0.int_ena.val = 0; + I2S0.int_ena.in_done = 1; + + // Register the interrupt handler. + esp_intr_alloc( + ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, + &i2s_isr, + this, + &i2s_intr_handle); + + m_dmaSemaphore.take(); + // Start the interrupt handler + esp_intr_enable(i2s_intr_handle); + + I2S0.conf.rx_start = 1; + + /* + while(1) { + m_dmaSemaphore.wait(); + uint32_t dataLength = pLastDMABuffer->getLength(); + ESP_LOGD(LOG_TAG, "Got a DMA buffer; length=%d", dataLength); + //pLastDMABuffer->dump(); + uint8_t *pData = new uint8_t[dataLength]; + pLastDMABuffer->getData(pData, dataLength); GeneralUtils::hexDump(pData, dataLength); delete[] pData; m_dmaSemaphore.take(); - } - */ + } + */ ESP_LOGD(LOG_TAG, "<< cameraMode"); } diff --git a/cpp_utils/IFTTT.cpp b/cpp_utils/IFTTT.cpp index 0cf7a6d9..6af2279d 100644 --- a/cpp_utils/IFTTT.cpp +++ b/cpp_utils/IFTTT.cpp @@ -37,7 +37,7 @@ void IFTTT::trigger( std::string value2, std::string value3) { m_restClient.setURL("https://maker.ifttt.com/trigger/" + event + "/with/key/" + m_key); - cJSON *root; + cJSON* root; root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "value1", value1.c_str()); diff --git a/cpp_utils/JSON.cpp b/cpp_utils/JSON.cpp index 4bebda0a..31f8cdb1 100644 --- a/cpp_utils/JSON.cpp +++ b/cpp_utils/JSON.cpp @@ -6,6 +6,8 @@ */ +// See: https://github.com/DaveGamble/cJSON + #include #include #include "JSON.h" @@ -95,7 +97,7 @@ void JsonArray::addDouble(double value) { * @param [in] value The int value to add to the array. */ void JsonArray::addInt(int value) { - cJSON_AddItemToArray(m_node, cJSON_CreateDouble((double)value, value)); + cJSON_AddItemToArray(m_node, cJSON_CreateNumber((double)value)); } // addInt @@ -123,11 +125,8 @@ void JsonArray::addString(std::string value) { * @return The boolean value at the given index. */ bool JsonArray::getBoolean(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); - if (node->valueint == 0) { - return false; - } - return true; + cJSON* node = cJSON_GetArrayItem(m_node, item); + return (node->valueint != 0); } // getBoolean @@ -137,7 +136,7 @@ bool JsonArray::getBoolean(int item) { * @return The double value at the given index. */ double JsonArray::getDouble(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return node->valuedouble; } // getDouble @@ -148,7 +147,7 @@ double JsonArray::getDouble(int item) { * @return The int value at the given index. */ int JsonArray::getInt(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return node->valueint; } // getInt @@ -159,7 +158,7 @@ int JsonArray::getInt(int item) { * @return The object value at the given index. */ JsonObject JsonArray::getObject(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return JsonObject(node); } // getObject @@ -170,7 +169,7 @@ JsonObject JsonArray::getObject(int item) { * @return The object value at the given index. */ std::string JsonArray::getString(int item) { - cJSON *node = cJSON_GetArrayItem(m_node, item); + cJSON* node = cJSON_GetArrayItem(m_node, item); return std::string(node->valuestring); } // getString @@ -180,13 +179,25 @@ std::string JsonArray::getString(int item) { * @return A JSON string representation of the array. */ std::string JsonArray::toString() { - char *data = cJSON_Print(m_node); + char* data = cJSON_Print(m_node); std::string ret(data); free(data); return ret; } // toString +/** + * @brief Build an unformatted string representation. + * @return A string representation. + */ +std::string JsonArray::toStringUnformatted() { + char* data = cJSON_PrintUnformatted(m_node); + std::string ret(data); + free(data); + return ret; +} // toStringUnformatted + + /** * @brief Get the number of elements from the array. * @return The int value that represents the number of elements. @@ -195,22 +206,28 @@ std::size_t JsonArray::size() { return cJSON_GetArraySize(m_node); } // size - +/** + * @brief Constructor + */ JsonObject::JsonObject(cJSON* node) { m_node = node; +} // JsonObject + +JsonArray JsonObject::getArray(std::string name) { + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + return JsonArray(node); } + /** * @brief Get the named boolean value from the object. * @param [in] name The name of the object property. * @return The boolean value from the object. */ bool JsonObject::getBoolean(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); - if (node->valueint == 0) { - return false; - } - return true; + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return false; + return cJSON_IsTrue(node); } // getBoolean @@ -220,7 +237,8 @@ bool JsonObject::getBoolean(std::string name) { * @return The double value from the object. */ double JsonObject::getDouble(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return 0.0; return node->valuedouble; } // getDouble @@ -231,7 +249,8 @@ double JsonObject::getDouble(std::string name) { * @return The int value from the object. */ int JsonObject::getInt(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return 0; return node->valueint; } // getInt @@ -242,7 +261,7 @@ int JsonObject::getInt(std::string name) { * @return The object value from the object. */ JsonObject JsonObject::getObject(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); return JsonObject(node); } // getObject @@ -250,14 +269,33 @@ JsonObject JsonObject::getObject(std::string name) { /** * @brief Get the named string value from the object. * @param [in] name The name of the object property. - * @return The string value from the object. + * @return The string value from the object. A zero length string is returned when the object is not present. */ std::string JsonObject::getString(std::string name) { - cJSON *node = cJSON_GetObjectItem(m_node, name.c_str()); + cJSON* node = cJSON_GetObjectItem(m_node, name.c_str()); + if (node == nullptr) return ""; return std::string(node->valuestring); } // getString +/** + * @brief Determine if the object has the specified item. + * @param [in] name The name of the property to check for presence. + * @return True if the object contains this property. + */ +bool JsonObject::hasItem(std::string name) { + return cJSON_GetObjectItem(m_node, name.c_str()) != nullptr; +} // hasItem + + +/** + * @brief Determine if this represents a valid JSON node. + * @return True if this is a valid node and false otherwise. + */ +bool JsonObject::isValid() { + return m_node != nullptr; +} // isValid + /** * @brief Set the named array property. * @param [in] name The name of the property to add. @@ -276,7 +314,7 @@ void JsonObject::setArray(std::string name, JsonArray array) { * @return N/A. */ void JsonObject::setBoolean(std::string name, bool value) { - cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateBool(value)); + cJSON_AddItemToObject(m_node, name.c_str(), value ? cJSON_CreateTrue() : cJSON_CreateFalse()); } // setBoolean @@ -298,7 +336,7 @@ void JsonObject::setDouble(std::string name, double value) { * @return N/A. */ void JsonObject::setInt(std::string name, int value) { - cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateDouble((double)value, value)); + cJSON_AddItemToObject(m_node, name.c_str(), cJSON_CreateNumber((double) value)); } // setInt @@ -329,8 +367,20 @@ void JsonObject::setString(std::string name, std::string value) { * @return A JSON string representation of the object. */ std::string JsonObject::toString() { - char *data = cJSON_Print(m_node); + char* data = cJSON_Print(m_node); std::string ret(data); free(data); return ret; } // toString + + +/** + * @brief Build an unformatted string representation. + * @return A string representation. + */ +std::string JsonObject::toStringUnformatted() { + char* data = cJSON_PrintUnformatted(m_node); + std::string ret(data); + free(data); + return ret; +} // toStringUnformatted diff --git a/cpp_utils/JSON.h b/cpp_utils/JSON.h index 08915104..f132a0a7 100644 --- a/cpp_utils/JSON.h +++ b/cpp_utils/JSON.h @@ -20,11 +20,12 @@ class JsonArray; class JSON { public: static JsonObject createObject(); - static JsonArray createArray(); - static void deleteObject(JsonObject jsonObject); - static void deleteArray(JsonArray jsonArray); + static JsonArray createArray(); + static void deleteObject(JsonObject jsonObject); + static void deleteArray(JsonArray jsonArray); static JsonObject parseObject(std::string text); - static JsonArray parseArray(std::string text); + static JsonArray parseArray(std::string text); + }; // JSON @@ -33,23 +34,29 @@ class JSON { */ class JsonArray { public: - JsonArray(cJSON *node); - int getInt(int item); - JsonObject getObject(int item); + int getInt(int item); + JsonObject getObject(int item); std::string getString(int item); - bool getBoolean(int item); - double getDouble(int item); - void addBoolean(bool value); - void addDouble(double value); - void addInt(int value); - void addObject(JsonObject value); - void addString(std::string value); + bool getBoolean(int item); + double getDouble(int item); + void addBoolean(bool value); + void addDouble(double value); + void addInt(int value); + void addObject(JsonObject value); + void addString(std::string value); std::string toString(); + std::string toStringUnformatted(); std::size_t size(); + +private: + JsonArray(cJSON* node); + friend class JSON; + friend class JsonObject; /** * @brief The underlying cJSON node. */ - cJSON *m_node; + cJSON* m_node; + }; // JsonArray @@ -58,23 +65,32 @@ class JsonArray { */ class JsonObject { public: - JsonObject(cJSON *node); - int getInt(std::string name); - JsonObject getObject(std::string name); + JsonArray getArray(std::string name); + bool getBoolean(std::string name); + double getDouble(std::string name); + int getInt(std::string name); + JsonObject getObject(std::string name); std::string getString(std::string name); - bool getBoolean(std::string name); - double getDouble(std::string name); - void setArray(std::string name, JsonArray array); - void setBoolean(std::string name, bool value); - void setDouble(std::string name, double value); - void setInt(std::string name, int value); - void setObject(std::string name, JsonObject value); - void setString(std::string name, std::string value); + bool hasItem(std::string name); + bool isValid(); + void setArray(std::string name, JsonArray array); + void setBoolean(std::string name, bool value); + void setDouble(std::string name, double value); + void setInt(std::string name, int value); + void setObject(std::string name, JsonObject value); + void setString(std::string name, std::string value); std::string toString(); + std::string toStringUnformatted(); + +private: + JsonObject(cJSON* node); + friend class JSON; + friend class JsonArray; /** * @brief The underlying cJSON node. */ - cJSON *m_node; + cJSON* m_node; + }; // JsonObject diff --git a/cpp_utils/MAX7219.cpp b/cpp_utils/MAX7219.cpp index d83615d9..4cac347d 100644 --- a/cpp_utils/MAX7219.cpp +++ b/cpp_utils/MAX7219.cpp @@ -76,7 +76,7 @@ const static uint8_t charTable[] = { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }; -MAX7219::MAX7219(SPI *spi, int numDevices) { +MAX7219::MAX7219(SPI* spi, int numDevices) { assert(spi != nullptr); this->spi = spi; if (numDevices <= 0 || numDevices > 8) { @@ -84,7 +84,7 @@ MAX7219::MAX7219(SPI *spi, int numDevices) { } maxDevices = numDevices; - for (int i = 0; i < 64; i++) { + for (uint8_t i = 0; i < 64; i++) { status[i] = 0x00; } for (int i = 0; i < maxDevices; i++) { @@ -106,21 +106,18 @@ int MAX7219::getDeviceCount() { void MAX7219::shutdown(bool b, int addr) { - if (addr < 0 || addr >= maxDevices) - return; + if (addr < 0 || addr >= maxDevices) return; + if (b) { spiTransfer(addr, OP_SHUTDOWN, 0); - } - else { + } else { spiTransfer(addr, OP_SHUTDOWN, 1); } } void MAX7219::setScanLimit(int limit, int addr) { - if (addr < 0 || addr >= maxDevices) { - return; - } + if (addr < 0 || addr >= maxDevices) return; if (limit >= 0 && limit < 8) { spiTransfer(addr, OP_SCANLIMIT, limit); } @@ -128,9 +125,7 @@ void MAX7219::setScanLimit(int limit, int addr) { void MAX7219::setIntensity(int intensity, int addr) { - if (addr < 0 || addr >= maxDevices) { - return; - } + if (addr < 0 || addr >= maxDevices) return; if (intensity >= 0 && intensity < 16) { spiTransfer(addr, OP_INTENSITY, intensity); } @@ -138,13 +133,11 @@ void MAX7219::setIntensity(int intensity, int addr) { void MAX7219::clearDisplay(int addr) { - int offset; + if (addr < 0 || addr >= maxDevices) return; - if (addr < 0 || addr >= maxDevices) { - return; - } + int offset; offset = addr * 8; - for (int i = 0; i < 8; i++) { + for (uint8_t i = 0; i < 8; i++) { status[offset + i] = 0; spiTransfer(addr, i + 1, status[offset + i]); } @@ -152,12 +145,11 @@ void MAX7219::clearDisplay(int addr) { void MAX7219::setLed(int row, int column, bool state, int addr) { + if (addr < 0 || addr >= maxDevices) return; + int offset; - uint8_t val = 0x00; + uint8_t val = 0; - if (addr < 0 || addr >= maxDevices) { - return; - } if (row < 0 || row > 7 || column < 0 || column > 7) { return; } @@ -165,8 +157,7 @@ void MAX7219::setLed(int row, int column, bool state, int addr) { val = 0b10000000 >> column; if (state) { status[offset + row] = status[offset + row] | val; - } - else { + } else { val = ~val; status[offset + row] = status[offset + row] & val; } @@ -175,28 +166,20 @@ void MAX7219::setLed(int row, int column, bool state, int addr) { void MAX7219::setRow(int row, uint8_t value, int addr) { - int offset; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (row < 0 || row > 7) { - return; - } - offset = addr * 8; + if (addr < 0 || addr >= maxDevices) return; + if (row < 0 || row > 7) return; + + int offset = addr * 8; status[offset + row] = value; spiTransfer(addr, row + 1, status[offset + row]); } void MAX7219::setColumn(int col, uint8_t value, int addr) { - uint8_t val; + if (addr < 0 || addr >= maxDevices) return; + if (col < 0 || col > 7) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (col < 0 || col > 7) { - return; - } + uint8_t val; for (int row = 0; row < 8; row++) { val = value >> (7 - row); val = val & 0x01; @@ -206,17 +189,11 @@ void MAX7219::setColumn(int col, uint8_t value, int addr) { void MAX7219::setDigit(int digit, uint8_t value, bool dp, int addr) { - int offset; - uint8_t v; + if (addr < 0 || addr >= maxDevices) return; + if (digit < 0 || digit > 7 || value > 15) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (digit < 0 || digit > 7 || value > 15) { - return; - } - offset = addr * 8; - v = charTable[value]; + int offset = addr * 8; + uint8_t v = charTable[value]; if (dp) { v |= 0b10000000; } @@ -226,22 +203,13 @@ void MAX7219::setDigit(int digit, uint8_t value, bool dp, int addr) { void MAX7219::setChar(int digit, char value, bool dp, int addr) { - int offset; - uint8_t index, v; + if (addr < 0 || addr >= maxDevices) return; + if (digit < 0 || digit > 7) return; - if (addr < 0 || addr >= maxDevices) { - return; - } - if (digit < 0 || digit > 7) { - return; - } - offset = addr * 8; - index = (uint8_t) value; - if (index > 127) { - //no defined beyond index 127, so we use the space char - index = 32; - } - v = charTable[index]; + int offset = addr * 8; + uint8_t index = (uint8_t) value; + if (index > 127) index = 32; // not defined beyond index 127, so we use the space char + uint8_t v = charTable[index]; if (dp) { v |= 0b10000000; } @@ -250,7 +218,7 @@ void MAX7219::setChar(int digit, char value, bool dp, int addr) { } -void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t data) { +void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t data) { //Create an array with the data to shift out int offset = addr * 2; int maxbytes = maxDevices * 2; @@ -268,13 +236,13 @@ void MAX7219::spiTransfer(int addr, volatile uint8_t opcode, volatile uint8_t da } void MAX7219::setNumber(uint32_t number, int addr) { - //number = number % (uint32_t)pow(10, maxDevices); - for (auto i=0; i<8; i++) { - if (number == 0 && i > 0){ + // number = number % (uint32_t) pow(10, maxDevices); + for (uint8_t i = 0; i < 8; i++) { + if (number == 0 && i > 0) { setChar(i, ' ', addr); } else { - setDigit(i, number%10, addr); - number = number/10; + setDigit(i, number % 10, addr); + number = number / 10; } } } diff --git a/cpp_utils/MAX7219.h b/cpp_utils/MAX7219.h index b5ae41f4..d3faf353 100644 --- a/cpp_utils/MAX7219.h +++ b/cpp_utils/MAX7219.h @@ -50,18 +50,6 @@ * and setColumn(). */ class MAX7219 { -private: - - /* Send out a single command to the device */ - void spiTransfer(int addr, uint8_t opcode, uint8_t data); - - /* We keep track of the led-status for all 8 devices in this array */ - uint8_t status[64]; - - /* The maximum number of devices we use */ - int maxDevices; - SPI *spi; - public: /** * @brief Create a new %MAX7219 controller @@ -70,7 +58,7 @@ class MAX7219 { * @param [in] numDevices maximum number of devices that can be controlled that are * daisy chained together. */ - MAX7219(SPI *spi, int numDevices = 1); + MAX7219(SPI* spi, int numDevices = 1); /** @@ -101,7 +89,7 @@ class MAX7219 { * @param [in] dp Sets the decimal point. * @param [in] addr Address of the display. */ - void setChar(int digit, char value, bool dp=false, int addr=0); + void setChar(int digit, char value, bool dp=false, int addr = 0); /** @@ -111,7 +99,7 @@ class MAX7219 { * @param [in] value each bit set to 1 will light up the corresponding Led. * @param [in] addr address of the display. */ - void setColumn(int col, uint8_t value, int addr=0); + void setColumn(int col, uint8_t value, int addr = 0); /** @@ -122,7 +110,7 @@ class MAX7219 { * @param [in] dp Sets the decimal point. * @param [in] addr Address of the display. */ - void setDigit(int digit, uint8_t value, bool dp=false, int addr=0); + void setDigit(int digit, uint8_t value, bool dp=false, int addr = 0); /** @@ -131,7 +119,7 @@ class MAX7219 { * @param [in] intensity the brightness of the display. (0..15). * @param [in] addr The address of the display to control. */ - void setIntensity(int intensity, int addr=0); + void setIntensity(int intensity, int addr = 0); /** @@ -142,7 +130,7 @@ class MAX7219 { * @param [in] state If true the led is switched on, if false it is switched off. * @param [in] addr Address of the display. */ - void setLed(int row, int col, bool state, int addr=0); + void setLed(int row, int col, bool state, int addr = 0); /** @@ -153,7 +141,7 @@ class MAX7219 { * @param [in] number The number to display. * @param [in] addr Address of the display. */ - void setNumber(uint32_t number, int addr=0); + void setNumber(uint32_t number, int addr = 0); /** @@ -163,7 +151,7 @@ class MAX7219 { * @param [in] value Each bit set to 1 will light up the corresponding Led. * @param [in] addr Address of the display. */ - void setRow(int row, uint8_t value, int addr=0); + void setRow(int row, uint8_t value, int addr = 0); /** @@ -175,7 +163,7 @@ class MAX7219 { * @param [in] limit Number of digits to be displayed (1..8). * @param [in] addr Address of the display to control. */ - void setScanLimit(int limit, int addr=0); + void setScanLimit(int limit, int addr = 0); /** @@ -185,7 +173,18 @@ class MAX7219 { * for normal operation. * @param [in] addr The address of the display to control. */ - void shutdown(bool status, int addr=0); + void shutdown(bool status, int addr = 0); + +private: + /* Send out a single command to the device */ + void spiTransfer(int addr, uint8_t opcode, uint8_t data); + + /* We keep track of the led-status for all 8 devices in this array */ + uint8_t status[64]; + + /* The maximum number of devices we use */ + int maxDevices; + SPI* spi; }; diff --git a/cpp_utils/MFRC522.cpp b/cpp_utils/MFRC522.cpp index a860e9ba..940c170c 100644 --- a/cpp_utils/MFRC522.cpp +++ b/cpp_utils/MFRC522.cpp @@ -18,8 +18,8 @@ #include "MFRC522.h" #include "MFRC522Debug.h" -#include -#include +#include "FreeRTOS.h" +#include "GPIO.h" #include #include #include @@ -41,29 +41,37 @@ static const char LOG_TAG[] = "MFRC522"; /** * Writes a byte to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to write to. One of the PCD_Register enums. + * @param value The value to write. */ -void MFRC522::PCD_WriteRegister( - PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. - byte value ///< The value to write. - ) { +void MFRC522::PCD_WriteRegister(PCD_Register reg, byte value) { uint8_t data[2]; data[0] = reg; data[1] = value; + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transfer(data, 2); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + } // End PCD_WriteRegister() + /** * Writes a number of bytes to the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to write to. One of the PCD_Register enums. + * @param count The number of bytes to write to the register + * @param values The values to write. Byte array. */ -void MFRC522::PCD_WriteRegister( PCD_Register reg, ///< The register to write to. One of the PCD_Register enums. - byte count, ///< The number of bytes to write to the register - byte *values ///< The values to write. Byte array. - ) { - uint8_t* pData = new uint8_t[count+1]; +void MFRC522::PCD_WriteRegister(PCD_Register reg, byte count, byte* values) { + uint8_t* pData = new uint8_t[count + 1]; pData[0] = reg; - memcpy(pData+1, values, count); - m_spi.transfer(pData, count+1); + memcpy(pData + 1, values, count); + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave + m_spi.transfer(pData, count + 1); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + delete[] pData; } // End PCD_WriteRegister() @@ -71,32 +79,38 @@ void MFRC522::PCD_WriteRegister( PCD_Register reg, ///< The register to write to /** * Reads a byte from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to read from. One of the PCD_Register enums. */ -byte MFRC522::PCD_ReadRegister( PCD_Register reg ///< The register to read from. One of the PCD_Register enums. - ) { +byte MFRC522::PCD_ReadRegister(PCD_Register reg) { uint8_t data[2]; data[0] = reg | 0x80; data[1] = 0; + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transfer(data, 2); + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave + return data[1]; } // End PCD_ReadRegister() + /** * Reads a number of bytes from the specified register in the MFRC522 chip. * The interface is described in the datasheet section 8.1.2. + * @param reg The register to read from. One of the PCD_Register enums. + * @param count The number of bytes to read + * @param values Byte array to store the values in. + * @param rxAlign Only bit positions rxAlign..7 in values[0] are updated. */ -void MFRC522::PCD_ReadRegister( PCD_Register reg, ///< The register to read from. One of the PCD_Register enums. - byte count, ///< The number of bytes to read - byte *values, ///< Byte array to store the values in. - byte rxAlign ///< Only bit positions rxAlign..7 in values[0] are updated. - ) { - if (count == 0) { - return; - } +void MFRC522::PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte rxAlign) { + if (count == 0) return; + //Serial.print(F("Reading ")); Serial.print(count); Serial.println(F(" bytes from register.")); byte address = 0x80 | reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. byte index = 0; // Index in values array. count--; // One read is performed outside of the loop + + ESP32CPP::GPIO::low((gpio_num_t)_chipSelectPin); // Select slave m_spi.transferByte(address); // Tell MFRC522 which address we want to read if (rxAlign) { // Only update bit positions rxAlign..7 in values[0] // Create bit mask for bit positions rxAlign..7 @@ -112,25 +126,28 @@ void MFRC522::PCD_ReadRegister( PCD_Register reg, ///< The register to read from index++; } values[index] = m_spi.transferByte(0); // Read the final byte. Send 0 to stop reading. + ESP32CPP::GPIO::high((gpio_num_t)_chipSelectPin); // Release slave } // End PCD_ReadRegister() + /** * Sets the bits given in mask in register reg. + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to set. */ -void MFRC522::PCD_SetRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. - byte mask ///< The bits to set. - ) { +void MFRC522::PCD_SetRegisterBitMask(PCD_Register reg, byte mask) { byte tmp; tmp = PCD_ReadRegister(reg); - PCD_WriteRegister(reg, tmp | mask); // set bit mask + PCD_WriteRegister(reg, tmp | mask); // set bit mask } // End PCD_SetRegisterBitMask() + /** * Clears the bits given in mask from register reg. + * @param reg The register to update. One of the PCD_Register enums. + * @param mask The bits to clear. */ -void MFRC522::PCD_ClearRegisterBitMask( PCD_Register reg, ///< The register to update. One of the PCD_Register enums. - byte mask ///< The bits to clear. - ) { +void MFRC522::PCD_ClearRegisterBitMask(PCD_Register reg, byte mask) { byte tmp; tmp = PCD_ReadRegister(reg); PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask @@ -139,13 +156,12 @@ void MFRC522::PCD_ClearRegisterBitMask( PCD_Register reg, ///< The register to u /** * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. - * + * @param data In: Pointer to the data to transfer to the FIFO for CRC calculation. + * @param length In: The number of bytes to transfer. + * @param result Out: Pointer to result buffer. Result is written to result[0..1], low byte first. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - byte length, ///< In: The number of bytes to transfer. - byte *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low byte first. - ) { +MFRC522::StatusCode MFRC522::PCD_CalculateCRC(byte* data, byte length, byte* result) { PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization @@ -159,7 +175,7 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to for (uint16_t i = 5000; i > 0; i--) { // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved byte n = PCD_ReadRegister(DivIrqReg); - if (n & 0x04) { // CRCIRq bit set - calculation done + if (n & 0x04) { // CRCIRq bit set - calculation done PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. // Transfer the result from the registers to the result buffer result[0] = PCD_ReadRegister(CRCResultRegL); @@ -172,32 +188,31 @@ MFRC522::StatusCode MFRC522::PCD_CalculateCRC( byte *data, ///< In: Pointer to } // End PCD_CalculateCRC() -///////////////////////////////////////////////////////////////////////////////////// // Functions for manipulating the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// + /** * Initializes the MFRC522 chip. */ void MFRC522::PCD_Init() { //m_spi.setHost(VSPI_HOST); + + ESP32CPP::GPIO::setOutput((gpio_num_t)_chipSelectPin); m_spi.init(); bool hardReset = false; - // Set the chipSelectPin as digital output, do not select the slave yet -/* - pinMode(_chipSelectPin, OUTPUT); - digitalWrite(_chipSelectPin, HIGH); -*/ + // pinMode(_chipSelectPin, OUTPUT); + // digitalWrite(_chipSelectPin, HIGH); + // If a valid pin number has been set, pull device out of power down / reset state. if (_resetPowerDownPin != UNUSED_PIN) { // Set the resetPowerDownPin as digital output, do not reset or power down. //pinMode(_resetPowerDownPin, OUTPUT); ESP32CPP::GPIO::setInput((gpio_num_t)_resetPowerDownPin); - if (ESP32CPP::GPIO::read((gpio_num_t)_resetPowerDownPin) == false) { // The MFRC522 chip is in power down mode. + if (!ESP32CPP::GPIO::read((gpio_num_t)_resetPowerDownPin)) { // The MFRC522 chip is in power down mode. ESP32CPP::GPIO::setOutput((gpio_num_t)_resetPowerDownPin); ESP32CPP::GPIO::high((gpio_num_t)_resetPowerDownPin); // Exit power down mode. This triggers a hard reset. // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. @@ -221,27 +236,30 @@ void MFRC522::PCD_Init() { // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds - PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. PCD_WriteRegister(TReloadRegL, 0xE8); - PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) - PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) + PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) } // End PCD_Init() + /** * Initializes the MFRC522 chip. + * @param chipSelectPin Pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) + * @param resetPowerDownPin Pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) */ -void MFRC522::PCD_Init( byte chipSelectPin, ///< Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) - byte resetPowerDownPin ///< Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) - ) { +void MFRC522::PCD_Init(byte chipSelectPin, byte resetPowerDownPin) { _chipSelectPin = chipSelectPin; _resetPowerDownPin = resetPowerDownPin; // Set the chipSelectPin as digital output, do not select the slave yet PCD_Init(); } // End PCD_Init() + /** * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. */ @@ -252,11 +270,12 @@ void MFRC522::PCD_Reset() { // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. FreeRTOS::sleep(50); // Wait for the PowerDown bit in CommandReg to be cleared - while (PCD_ReadRegister(CommandReg) & (1<<4)) { + while (PCD_ReadRegister(CommandReg) & (1 << 4)) { // PCD still restarting - unlikely after waiting 50ms, but better safe than sorry. } } // End PCD_Reset() + /** * Turns the antenna on by enabling pins TX1 and TX2. * After a reset these pins are disabled. @@ -268,6 +287,7 @@ void MFRC522::PCD_AntennaOn() { } } // End PCD_AntennaOn() + /** * Turns the antenna off by disabling pins TX1 and TX2. */ @@ -275,6 +295,7 @@ void MFRC522::PCD_AntennaOff() { PCD_ClearRegisterBitMask(TxControlReg, 0x03); } // End PCD_AntennaOff() + /** * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf @@ -283,21 +304,23 @@ void MFRC522::PCD_AntennaOff() { * @return Value of the RxGain, scrubbed to the 3 bits used. */ byte MFRC522::PCD_GetAntennaGain() { - return PCD_ReadRegister(RFCfgReg) & (0x07<<4); + return PCD_ReadRegister(RFCfgReg) & (0x07 << 4); } // End PCD_GetAntennaGain() + /** * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. */ void MFRC522::PCD_SetAntennaGain(byte mask) { - if (PCD_GetAntennaGain() != mask) { // only bother if there is a change - PCD_ClearRegisterBitMask(RFCfgReg, (0x07<<4)); // clear needed to allow 000 pattern + if (PCD_GetAntennaGain() != mask) { // only bother if there is a change + PCD_ClearRegisterBitMask(RFCfgReg, (0x07 << 4)); // clear needed to allow 000 pattern PCD_SetRegisterBitMask(RFCfgReg, mask & (0x07<<4)); // only set RxGain[2:0] bits } } // End PCD_SetAntennaGain() + /** * Performs a self-test of the MFRC522 * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf @@ -335,9 +358,7 @@ bool MFRC522::PCD_PerformSelfTest() { // It is reported that some devices does not trigger CRCIRq flag // during selftest. n = PCD_ReadRegister(FIFOLevelReg); - if (n >= 64) { - break; - } + if (n >= 64) break; } PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. @@ -373,9 +394,7 @@ bool MFRC522::PCD_PerformSelfTest() { // Verify that the results match up to our expectations for (uint8_t i = 0; i < 64; i++) { - if (result[i] != (reference[i])) { - return false; - } + if (result[i] != (reference[i])) return false; } // Test passed; all is good. @@ -383,45 +402,47 @@ bool MFRC522::PCD_PerformSelfTest() { } // End PCD_PerformSelfTest() ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with PICCs ///////////////////////////////////////////////////////////////////////////////////// + /** * Executes the Transceive command. * CRC validation can only be done if backData and backLen are specified. - * + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData nullptr or pointer to buffer if data should be read back after executing the command. + * @param backLen In: Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. + * @param rxAlign In: Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_TransceiveData( byte *sendData, ///< Pointer to the data to transfer to the FIFO. - byte sendLen, ///< Number of bytes to transfer to the FIFO. - byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. - byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. - byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. - ) { +MFRC522::StatusCode MFRC522::PCD_TransceiveData(byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { byte waitIRq = 0x30; // RxIRq and IdleIRq return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); } // End PCD_TransceiveData() + /** * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. * CRC validation can only be done if backData and backLen are specified. * + * @param command The command to execute. One of the PCD_Command enums. + * @param waitIRq The bits in the ComIrqReg register that signals successful completion of the command. + * @param sendData Pointer to the data to transfer to the FIFO. + * @param sendLen Number of bytes to transfer to the FIFO. + * @param backData nullptr or pointer to buffer if data should be read back after executing the command. + * @param backLen In: Max number of bytes to write to *backData. Out: The number of bytes returned. + * @param validBits In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. + * @param rxAlign In: Defines the bit position in backData[0] for the first bit received. Default 0. + * @param checkCRC In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The command to execute. One of the PCD_Command enums. - byte waitIRq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - byte *sendData, ///< Pointer to the data to transfer to the FIFO. - byte sendLen, ///< Number of bytes to transfer to the FIFO. - byte *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. - byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. - byte rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. - ) { +MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC(byte command, byte waitIRq, byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits, byte rxAlign, bool checkCRC) { // Prepare values for BitFramingReg - byte txLastBits = validBits ? *validBits : 0; + byte txLastBits = validBits ? *validBits : (byte) 0; byte bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. @@ -449,9 +470,7 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co } } // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) { - return STATUS_TIMEOUT; - } + if (i == 0) return STATUS_TIMEOUT; // Stop now if any errors except collisions were detected. byte errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr @@ -464,10 +483,8 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co // If the caller wants data back, get it from the MFRC522. if (backData && backLen) { byte n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO - if (n > *backLen) { - return STATUS_NO_ROOM; - } - *backLen = n; // Number of bytes returned + if (n > *backLen) return STATUS_NO_ROOM; + *backLen = n; // Number of bytes returned PCD_ReadRegister(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. if (validBits) { @@ -483,19 +500,13 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co // Perform CRC_A validation if requested. if (backData && backLen && checkCRC) { // In this case a MIFARE Classic NAK is not OK. - if (*backLen == 1 && _validBits == 4) { - return STATUS_MIFARE_NACK; - } + if (*backLen == 1 && _validBits == 4) return STATUS_MIFARE_NACK; // We need at least the CRC_A value and all 8 bits of the last byte must be received. - if (*backLen < 2 || _validBits != 0) { - return STATUS_CRC_WRONG; - } + if (*backLen < 2 || _validBits != 0) return STATUS_CRC_WRONG; // Verify CRC_A - do our own calculation and store the control in controlBuffer. byte controlBuffer[2]; MFRC522::StatusCode status = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); - if (status != STATUS_OK) { - return status; - } + if (status != STATUS_OK) return status; if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) { return STATUS_CRC_WRONG; } @@ -504,58 +515,57 @@ MFRC522::StatusCode MFRC522::PCD_CommunicateWithPICC( byte command, ///< The co return STATUS_OK; } // End PCD_CommunicateWithPICC() + /** * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_RequestA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_RequestA(byte* bufferATQA, byte* bufferSize) { return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); } // End PICC_RequestA() + /** * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_WakeupA( byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_WakeupA(byte* bufferATQA, byte* bufferSize) { return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); } // End PICC_WakeupA() + /** * Transmits REQA or WUPA commands. * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. * + * @param command The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + * @param bufferATQA The buffer to store the ATQA (Answer to request) in + * @param bufferSize Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA( byte command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA - byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA(byte command, byte* bufferATQA, byte* bufferSize) { byte validBits; MFRC522::StatusCode status; - if (bufferATQA == nullptr || *bufferSize < 2) { // The ATQA response is 2 bytes long. - return STATUS_NO_ROOM; - } PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. validBits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] + if (bufferATQA == nullptr || *bufferSize < 2) return STATUS_NO_ROOM; // The ATQA response is 2 bytes long. status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits); - if (status != STATUS_OK) { - return status; - } - if (*bufferSize != 2 || validBits != 0) { // ATQA must be exactly 16 bits. - return STATUS_ERROR; - } + if (status != STATUS_OK) return status; + + if (*bufferSize != 2 || validBits != 0) return STATUS_ERROR; // ATQA must be exactly 16 bits. return STATUS_OK; } // End PICC_REQA_or_WUPA() + /** * Transmits SELECT/ANTICOLLISION commands to select a single PICC. * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). @@ -573,9 +583,7 @@ MFRC522::StatusCode MFRC522::PICC_REQA_or_WUPA( byte command, ///< The command * * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - byte validBits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. - ) { +MFRC522::StatusCode MFRC522::PICC_Select(Uid* uid, byte validBits) { bool uidComplete; bool selectDone; bool useCascadeTag; @@ -589,7 +597,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct byte bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. byte rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. byte txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. - byte *responseBuffer; + byte* responseBuffer; byte responseLength; // Description of buffer structure: @@ -615,9 +623,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct // 3 uid6 uid7 uid8 uid9 // Sanity checks - if (validBits > 80) { - return STATUS_INVALID; - } + if (validBits > 80) return STATUS_INVALID; // Prepare MFRC522 PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. @@ -632,22 +638,18 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct uidIndex = 0; useCascadeTag = validBits && uid->size > 4; // When we know that the UID has more than 4 bytes break; - case 2: buffer[0] = PICC_CMD_SEL_CL2; uidIndex = 3; useCascadeTag = validBits && uid->size > 7; // When we know that the UID has more than 7 bytes break; - case 3: buffer[0] = PICC_CMD_SEL_CL3; uidIndex = 6; useCascadeTag = false; // Never used in CL3. break; - default: return STATUS_INTERNAL_ERROR; - break; } // How many UID bits are known in this Cascade Level? @@ -686,16 +688,13 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 7, &buffer[7]); - if (result != STATUS_OK) { - return result; - } txLastBits = 0; // 0 => All 8 bits are valid. bufferUsed = 9; + if (result != STATUS_OK) return result; // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) responseBuffer = &buffer[6]; responseLength = 3; - } - else { // This is an ANTICOLLISION. + } else { // This is an ANTICOLLISION. //Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); txLastBits = currentLevelKnownBits % 8; count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. @@ -715,31 +714,25 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign); if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. byte valueOfCollReg = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (valueOfCollReg & 0x20) { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } + if (valueOfCollReg & 0x20) return STATUS_COLLISION; // CollPosNotValid, Without a valid collision position we cannot continue byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32. if (collisionPos == 0) { collisionPos = 32; } - if (collisionPos <= currentLevelKnownBits) { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } + if (collisionPos <= currentLevelKnownBits) return STATUS_INTERNAL_ERROR; // No progress - should not happen + // Choose the PICC with the bit set. currentLevelKnownBits = collisionPos; count = (currentLevelKnownBits - 1) % 8; // The bit to modify index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. buffer[index] |= (1 << count); } - else if (result != STATUS_OK) { - return result; - } + else if (result != STATUS_OK) return result; else { // STATUS_OK if (currentLevelKnownBits >= 32) { // This was a SELECT. selectDone = true; // No more anticollision // We continue below outside the while. - } - else { // This was an ANTICOLLISION. + } else { // This was an ANTICOLLISION. // We now have all 32 bits of the UID in this Cascade Level currentLevelKnownBits = 32; // Run loop again to do the SELECT. @@ -757,21 +750,18 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct } // Check response SAK (Select Acknowledge) - if (responseLength != 3 || txLastBits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). - return STATUS_ERROR; - } + if (responseLength != 3 || txLastBits != 0) return STATUS_ERROR; // SAK must be exactly 24 bits (1 byte + CRC_A). + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; + if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) { return STATUS_CRC_WRONG; } if (responseBuffer[0] & 0x04) { // Cascade bit set - UID not complete yes cascadeLevel++; - } - else { + } else { uidComplete = true; uid->sak = responseBuffer[0]; } @@ -783,6 +773,7 @@ MFRC522::StatusCode MFRC522::PICC_Select( Uid *uid, ///< Pointer to Uid struct return STATUS_OK; } // End PICC_Select() + /** * Instructs a PICC in state ACTIVE(*) to go to state HALT. * @@ -797,9 +788,7 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { buffer[1] = 0; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Send the command. // The standard says: @@ -807,12 +796,10 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { // HLTA command, this response shall be interpreted as 'not acknowledge'. // We interpret that this way: Only STATUS_TIMEOUT is a success. result = PCD_TransceiveData(buffer, sizeof(buffer), nullptr, 0); - if (result == STATUS_TIMEOUT) { - return STATUS_OK; - } - if (result == STATUS_OK) { // That is ironically NOT ok in this case ;-) - return STATUS_ERROR; - } + + if (result == STATUS_TIMEOUT) return STATUS_OK; + if (result == STATUS_OK) return STATUS_ERROR; // That is ironically NOT ok in this case ;-) + return result; } // End PICC_HaltA() @@ -820,6 +807,7 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { // Functions for communicating with MIFARE PICCs ///////////////////////////////////////////////////////////////////////////////////// + /** * Executes the MFRC522 MFAuthent command. * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. @@ -830,13 +818,13 @@ MFRC522::StatusCode MFRC522::PICC_HaltA() { * * All keys are set to FFFFFFFFFFFFh at chip delivery. * + * @param command PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + * @param blockAddr The block number. See numbering in the comments in the .h file. + * @param key Pointer to the Crypteo1 key to use (6 bytes) + * @param uid Pointer to Uid struct. The first 4 bytes of the UID is used. * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. */ -MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B - byte blockAddr, ///< The block number. See numbering in the comments in the .h file. - MIFARE_Key *key, ///< Pointer to the Crypteo1 key to use (6 bytes) - Uid *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. - ) { +MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key* key, Uid* uid) { byte waitIRq = 0x10; // IdleIRq // Build command buffer @@ -844,20 +832,21 @@ MFRC522::StatusCode MFRC522::PCD_Authenticate(byte command, ///< PICC_CMD_MF_AU sendData[0] = command; sendData[1] = blockAddr; for (byte i = 0; i < MF_KEY_SIZE; i++) { // 6 key bytes - sendData[2+i] = key->keyByte[i]; + sendData[2 + i] = key->keyByte[i]; } // Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf // section 3.2.5 "MIFARE Classic Authentication". // The only missed case is the MF1Sxxxx shortcut activation, // but it requires cascade tag (CT) byte, that is not part of uid. - for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID - sendData[8+i] = uid->uidByte[i+uid->size-4]; + for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID + sendData[8 + i] = uid->uidByte[i + uid->size - 4]; } // Start the authentication. return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData)); } // End PCD_Authenticate() + /** * Used to exit the PCD from its authenticated state. * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. @@ -867,6 +856,7 @@ void MFRC522::PCD_StopCrypto1() { PCD_ClearRegisterBitMask(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] } // End PCD_StopCrypto1() + /** * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. * @@ -881,32 +871,29 @@ void MFRC522::PCD_StopCrypto1() { * The buffer must be at least 18 bytes because a CRC_A is also returned. * Checks the CRC_A before returning STATUS_OK. * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. + * @param buffer The buffer to store the data in + * @param bufferSize Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Read( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. - byte *buffer, ///< The buffer to store the data in - byte *bufferSize ///< Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Read(byte blockAddr, byte* buffer, byte* bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || *bufferSize < 18) { - return STATUS_NO_ROOM; - } + if (buffer == nullptr || *bufferSize < 18) return STATUS_NO_ROOM; // Build command buffer buffer[0] = PICC_CMD_MF_READ; buffer[1] = blockAddr; // Calculate CRC_A result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Transmit the buffer and receive the response, validate CRC_A. return PCD_TransceiveData(buffer, 4, buffer, bufferSize, nullptr, 0, true); } // End MIFARE_Read() + /** * Writes 16 bytes to the active PICC. * @@ -915,19 +902,16 @@ MFRC522::StatusCode MFRC522::MIFARE_Read( byte blockAddr, ///< MIFARE Classic: * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. - * * + * @param blockAddr MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. + * @param buffer The 16 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Write( byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. - byte *buffer, ///< The 16 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Write(byte blockAddr, byte* buffer, byte bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || bufferSize < 16) { - return STATUS_INVALID; - } + if (buffer == nullptr || bufferSize < 16) return STATUS_INVALID; // Mifare Classic protocol requires two communications to perform a write. // Step 1: Tell the PICC we want to write to block blockAddr. @@ -935,34 +919,28 @@ MFRC522::StatusCode MFRC522::MIFARE_Write( byte blockAddr, ///< MIFARE Classic: cmdBuffer[0] = PICC_CMD_MF_WRITE; cmdBuffer[1] = blockAddr; result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; // Step 2: Transfer the data result = PCD_MIFARE_Transceive(buffer, bufferSize); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_Write() + /** * Writes a 4 byte page to the active MIFARE Ultralight PICC. - * + * @param page The page (2-15) to write to. + * @param buffer The 4 bytes to write to the PICC + * @param bufferSize Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write( byte page, ///< The page (2-15) to write to. - byte *buffer, ///< The 4 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write(byte page, byte* buffer, byte bufferSize) { MFRC522::StatusCode result; // Sanity check - if (buffer == nullptr || bufferSize < 4) { - return STATUS_INVALID; - } + if (buffer == nullptr || bufferSize < 4) return STATUS_INVALID; // Build commmand buffer byte cmdBuffer[6]; @@ -972,106 +950,105 @@ MFRC522::StatusCode MFRC522::MIFARE_Ultralight_Write( byte page, ///< The page // Perform the write result = PCD_MIFARE_Transceive(cmdBuffer, 6); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; + return STATUS_OK; } // End MIFARE_Ultralight_Write() + /** * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. + * @param blockAddr The block (0-0xff) number. + * @param delta This number is subtracted from the value of block blockAddr. * * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Decrement( byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is subtracted from the value of block blockAddr. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Decrement(byte blockAddr, int32_t delta) { return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); } // End MIFARE_Decrement() + /** * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. * + * @param blockAddr The block (0-0xff) number. + * @param delta This number is added to the value of block blockAddr. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Increment( byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is added to the value of block blockAddr. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Increment(byte blockAddr, int32_t delta) { return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); } // End MIFARE_Increment() + /** * MIFARE Restore copies the value of the addressed block into a volatile memory. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * Use MIFARE_Transfer() to store the result in a block. * + * @param blockAddr The block (0-0xff) number. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Restore( byte blockAddr ///< The block (0-0xff) number. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Restore(byte blockAddr) { // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. // Doing only a single step does not work, so I chose to transfer 0L in step two. return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); } // End MIFARE_Restore() + /** * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. * + * @param command The command to use + * @param blockAddr The block (0-0xff) number. + * @param data The data to transfer in step 2 * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_TwoStepHelper( byte command, ///< The command to use - byte blockAddr, ///< The block (0-0xff) number. - int32_t data ///< The data to transfer in step 2 - ) { +MFRC522::StatusCode MFRC522::MIFARE_TwoStepHelper(byte command, byte blockAddr, int32_t data) { MFRC522::StatusCode result; byte cmdBuffer[2]; // We only need room for 2 bytes. // Step 1: Tell the PICC the command and block address cmdBuffer[0] = command; cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) return result; // Step 2: Transfer the data - result = PCD_MIFARE_Transceive( (byte *)&data, 4, true); // Adds CRC_A and accept timeout as success. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive((byte*) &data, 4, true); // Adds CRC_A and accept timeout as success. + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_TwoStepHelper() + /** * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. * + * @param blockAddr The block (0-0xff) number. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_Transfer( byte blockAddr ///< The block (0-0xff) number. - ) { +MFRC522::StatusCode MFRC522::MIFARE_Transfer(byte blockAddr) { MFRC522::StatusCode result; byte cmdBuffer[2]; // We only need room for 2 bytes. // Tell the PICC we want to transfer the result into block blockAddr. cmdBuffer[0] = PICC_CMD_MF_TRANSFER; cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive( cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) { - return result; - } + result = PCD_MIFARE_Transceive(cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) return result; return STATUS_OK; } // End MIFARE_Transfer() + /** * Helper routine to read the current value from a Value Block. * @@ -1083,7 +1060,7 @@ MFRC522::StatusCode MFRC522::MIFARE_Transfer( byte blockAddr ///< The block (0-0 * @param[out] value Current value of the Value Block. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t *value) { +MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t* value) { MFRC522::StatusCode status; byte buffer[18]; byte size = sizeof(buffer); @@ -1092,11 +1069,12 @@ MFRC522::StatusCode MFRC522::MIFARE_GetValue(byte blockAddr, int32_t *value) { status = MIFARE_Read(blockAddr, buffer, &size); if (status == STATUS_OK) { // Extract the value - *value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); + *value = (int32_t(buffer[3]) << 24) | (int32_t(buffer[2]) << 16) | (int32_t(buffer[1]) << 8) | int32_t(buffer[0]); } return status; } // End MIFARE_GetValue() + /** * Helper routine to write a specific value into a Value Block. * @@ -1129,6 +1107,7 @@ MFRC522::StatusCode MFRC522::MIFARE_SetValue(byte blockAddr, int32_t value) { return MIFARE_Write(blockAddr, buffer, 16); } // End MIFARE_SetValue() + /** * Authenticate with a NTAG216. * @@ -1138,8 +1117,7 @@ MFRC522::StatusCode MFRC522::MIFARE_SetValue(byte blockAddr, int32_t value) { * @param[in] pACK result success???. * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Authenticate with 32bit password -{ +MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) { //Authenticate with 32bit password // TODO: Fix cmdBuffer length and rxlength. They really should match. // (Better still, rxlength should not even be necessary.) @@ -1148,14 +1126,13 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut cmdBuffer[0] = 0x1B; //Comando de autentificacion - for (byte i = 0; i<4; i++) - cmdBuffer[i+1] = passWord[i]; + for (byte i = 0; i < 4; i++) { + cmdBuffer[i + 1] = passWord[i]; + } result = PCD_CalculateCRC(cmdBuffer, 5, &cmdBuffer[5]); - if (result!=STATUS_OK) { - return result; - } + if (result!=STATUS_OK) return result; // Transceive the data, store the reply in cmdBuffer[] byte waitIRq = 0x30; // RxIRq and IdleIRq @@ -1167,9 +1144,7 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut pACK[0] = cmdBuffer[0]; pACK[1] = cmdBuffer[1]; - if (result!=STATUS_OK) { - return result; - } + if (result!=STATUS_OK) return result; return STATUS_OK; } // End PCD_NTAG216_AUTH() @@ -1179,30 +1154,27 @@ MFRC522::StatusCode MFRC522::PCD_NTAG216_AUTH(byte* passWord, byte pACK[]) //Aut // Support functions ///////////////////////////////////////////////////////////////////////////////////// + /** * Wrapper for MIFARE protocol communication. * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. * + * @param sendData Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. + * @param sendLen Number of bytes in sendData. + * @param acceptTimeout True => A timeout is also success * @return STATUS_OK on success, STATUS_??? otherwise. */ -MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. - byte sendLen, ///< Number of bytes in sendData. - bool acceptTimeout ///< True => A timeout is also success - ) { +MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive(byte* sendData, byte sendLen, bool acceptTimeout) { MFRC522::StatusCode result; byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. // Sanity check - if (sendData == nullptr || sendLen > 16) { - return STATUS_INVALID; - } + if (sendData == nullptr || sendLen > 16) return STATUS_INVALID; // Copy sendData[] to cmdBuffer[] and add CRC_A memcpy(cmdBuffer, sendData, sendLen); result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); - if (result != STATUS_OK) { - return result; - } + if (result != STATUS_OK) return result; sendLen += 2; // Transceive the data, store the reply in cmdBuffer[] @@ -1210,19 +1182,11 @@ MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointe byte cmdBufferSize = sizeof(cmdBuffer); byte validBits = 0; result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits); - if (acceptTimeout && result == STATUS_TIMEOUT) { - return STATUS_OK; - } - if (result != STATUS_OK) { - return result; - } + if (acceptTimeout && result == STATUS_TIMEOUT) return STATUS_OK; + if (result != STATUS_OK) return result; // The PICC must reply with a 4 bit ACK - if (cmdBufferSize != 1 || validBits != 4) { - return STATUS_ERROR; - } - if (cmdBuffer[0] != MF_ACK) { - return STATUS_MIFARE_NACK; - } + if (cmdBufferSize != 1 || validBits != 4) return STATUS_ERROR; + if (cmdBuffer[0] != MF_ACK) return STATUS_MIFARE_NACK; return STATUS_OK; } // End PCD_MIFARE_Transceive() @@ -1230,10 +1194,10 @@ MFRC522::StatusCode MFRC522::PCD_MIFARE_Transceive( byte *sendData, ///< Pointe /** * Translates the SAK (Select Acknowledge) to a PICC type. * + * @param sak The SAK byte returned from PICC_Select(). * @return PICC_Type */ -MFRC522::PICC_Type MFRC522::PICC_GetType(byte sak ///< The SAK byte returned from PICC_Select(). - ) { +MFRC522::PICC_Type MFRC522::PICC_GetType(byte sak) { // http://www.nxp.com/documents/application_note/AN10833.pdf // 3.2 Coding of Select Acknowledge (SAK) // ignore 8-bit (iso14443 starts with LSBit = bit 1) @@ -1276,19 +1240,21 @@ void MFRC522::PCD_DumpVersionToSerial() { ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); // When 0x00 or 0xFF is returned, communication probably failed - if ((v == 0x00) || (v == 0xFF)) + if ((v == 0x00) || (v == 0xFF)) { ESP_LOGD(LOG_TAG, "WARNING: Communication failure, is the MFRC522 properly connected?"); + } } // End PCD_DumpVersionToSerial() + /** * Dumps debug info about the selected PICC to Serial. * On success the PICC is halted after dumping the data. * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). * @DEPRECATED Kept for bakward compatibility */ -void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). - ) { +void MFRC522::PICC_DumpToSerial(Uid* uid) { MIFARE_Key key; // Dump UID, SAK and Type @@ -1325,17 +1291,17 @@ void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned fro break; // No memory dump here } - ESP_LOGD(LOG_TAG,""); PICC_HaltA(); // Already done if it was a MIFARE Classic PICC. } // End PICC_DumpToSerial() + /** * Dumps card info (UID,SAK,Type) about the selected PICC to Serial. * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). * @DEPRECATED kept for backward compatibility */ -void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). - ) { +void MFRC522::PICC_DumpDetailsToSerial(Uid* uid) { // UID std::ostringstream oss; oss << std::hex << std::setfill('0'); @@ -1351,7 +1317,6 @@ void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct retur oss << "" << "Card SAK: " << std::setw(2) << (int)uid->sak; ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); - // (suggested) PICC type PICC_Type piccType = PICC_GetType(uid->sak); oss.str(""); @@ -1359,31 +1324,30 @@ void MFRC522::PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct retur ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } // End PICC_DumpDetailsToSerial() + /** * Dumps memory contents of a MIFARE Classic PICC. * On success the PICC is halted after dumping the data. + * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). + * @param piccType One of the PICC_Type enums. + * @param key Key A used for all sectors. */ -void MFRC522::PICC_DumpMifareClassicToSerial( Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - PICC_Type piccType, ///< One of the PICC_Type enums. - MIFARE_Key *key ///< Key A used for all sectors. - ) { +void MFRC522::PICC_DumpMifareClassicToSerial(Uid* uid, PICC_Type piccType, MIFARE_Key* key) { byte no_of_sectors = 0; switch (piccType) { case PICC_TYPE_MIFARE_MINI: // Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. no_of_sectors = 5; break; - case PICC_TYPE_MIFARE_1K: // Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. no_of_sectors = 16; break; - case PICC_TYPE_MIFARE_4K: // Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. no_of_sectors = 40; break; - default: // Should not happen. Ignore. break; } @@ -1399,15 +1363,17 @@ void MFRC522::PICC_DumpMifareClassicToSerial( Uid *uid, ///< Pointer to Uid st PCD_StopCrypto1(); } // End PICC_DumpMifareClassicToSerial() + /** * Dumps memory contents of a sector of a MIFARE Classic PICC. * Uses PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. * Always uses PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the sector trailer access bits. + * + * @param uid Pointer to Uid struct returned from a successful PICC_Select(). + * @param key Key A for the sector. + * @param sector The sector to dump, 0..39. */ -void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - MIFARE_Key *key, ///< Key A for the sector. - byte sector ///< The sector to dump, 0..39. - ) { +void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid* uid, MIFARE_Key* key, byte sector) { MFRC522::StatusCode status; byte firstBlock; // Address of lowest address to dump actually last block dumped) byte no_of_blocks; // Number of blocks in sector @@ -1436,8 +1402,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U else if (sector < 40) { // Sectors 32-39 has 16 blocks each no_of_blocks = 16; firstBlock = 128 + (sector - 32) * no_of_blocks; - } - else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. + } else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. return; } @@ -1452,28 +1417,23 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U // Sector number - only on first line std::ostringstream oss; if (isSectorTrailer) { - if(sector < 10) { + if (sector < 10) { oss << " "; // Pad with spaces - } - else { + } else { oss << " "; // Pad with spaces } - oss << (int)sector; - + oss << (int) sector; oss << " "; - } - else { oss << " "; + } else { } // Block number - if(blockAddr < 10) { + if (blockAddr < 10) { oss << " "; // Pad with spaces - } - else { - if(blockAddr < 100) { + } else { + if (blockAddr < 100) { oss << " "; // Pad with spaces - } - else { + } else { oss << " "; // Pad with spaces } } @@ -1502,7 +1462,6 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U oss << std::hex << std::setfill('0'); for (byte index = 0; index < 16; index++) { oss << " " << std::setw(2) << (int)buffer[index]; - if ((index % 4) == 3) { oss << " "; } @@ -1527,8 +1486,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U if (no_of_blocks == 4) { group = blockOffset; firstInGroup = true; - } - else { + } else { group = blockOffset / 5; firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5); } @@ -1536,15 +1494,15 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U if (firstInGroup) { // Print access bits oss << std::dec; - oss << " [ " << (int)((g[group] >> 2) & 1) << " " << (int)((g[group] >> 1) & 1) << " " << (int) ((g[group] >> 0) & 1) << " ] "; + oss << " [ " << ((g[group] >> 2) & 1) << " " << ((g[group] >> 1) & 1) << " " << ((g[group] >> 0) & 1) << " ] "; if (invertedError) { oss << " Inverted access bits did not match! "; } } if (group != 3 && (g[group] == 1 || g[group] == 6)) { // Not a sector trailer, a value block - int32_t value = (int32_t(buffer[3])<<24) | (int32_t(buffer[2])<<16) | (int32_t(buffer[1])<<8) | int32_t(buffer[0]); - oss << " Value=0x" << std::hex << value << " Adr=0x" << (int)buffer[12]; + int32_t value = (int32_t(buffer[3]) << 24) | (int32_t(buffer[2]) << 16) | (int32_t(buffer[1]) << 8) | int32_t(buffer[0]); + oss << " Value=0x" << std::hex << value << " Adr=0x" << (int) buffer[12]; } ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } @@ -1552,6 +1510,7 @@ void MFRC522::PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to U return; } // End PICC_DumpMifareClassicSectorToSerial() + /** * Dumps memory contents of a MIFARE Ultralight PICC. */ @@ -1564,7 +1523,7 @@ void MFRC522::PICC_DumpMifareUltralightToSerial() { std::ostringstream oss; oss << "Page 0 1 2 3"; // Try the mpages of the original Ultralight. Ultralight C has more pages. - for (byte page = 0; page < 16; page +=4) { // Read returns data for 4 pages at a time. + for (byte page = 0; page < 16; page += 4) { // Read returns data for 4 pages at a time. // Read pages byteCount = sizeof(buffer); status = MIFARE_Read(page, buffer, &byteCount); @@ -1582,22 +1541,24 @@ void MFRC522::PICC_DumpMifareUltralightToSerial() { for (byte index = 0; index < 4; index++) { i = 4 * offset + index; - oss << std::hex << std::setw(2) << (int)buffer[i]; + oss << std::hex << std::setw(2) << (int) buffer[i]; } ESP_LOGD(LOG_TAG, "%s", oss.str().c_str()); } } } // End PICC_DumpMifareUltralightToSerial() + /** * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). + * + * @param accessBitBuffer Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. + * @param g0 Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + * @param g1 Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + * @param g2 Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + * @param g3 Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) */ -void MFRC522::MIFARE_SetAccessBits( byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. - byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) - byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) - byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) - byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) - ) { +void MFRC522::MIFARE_SetAccessBits(byte* accessBitBuffer, byte g0, byte g1, byte g2, byte g3) { byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); @@ -1611,6 +1572,7 @@ void MFRC522::MIFARE_SetAccessBits( byte *accessBitBuffer, ///< Pointer to byte // Convenience functions - does not add extra functionality ///////////////////////////////////////////////////////////////////////////////////// + /** * Returns true if a PICC responds to PICC_CMD_REQA. * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. @@ -1631,6 +1593,7 @@ bool MFRC522::PICC_IsNewCardPresent() { return (result == STATUS_OK || result == STATUS_COLLISION); } // End PICC_IsNewCardPresent() + /** * Simple wrapper around PICC_Select. * Returns true if a UID could be read. diff --git a/cpp_utils/MFRC522.h b/cpp_utils/MFRC522.h index 2e060712..9e03ba4f 100644 --- a/cpp_utils/MFRC522.h +++ b/cpp_utils/MFRC522.h @@ -76,7 +76,6 @@ #define MFRC522_h - #include #include @@ -258,7 +257,6 @@ class MFRC522 { PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. - PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. // The read/write commands can also be used for MIFARE Ultralight. @@ -329,18 +327,20 @@ class MFRC522 { ///////////////////////////////////////////////////////////////////////////////////// // Functions for setting up the Arduino ///////////////////////////////////////////////////////////////////////////////////// + //MFRC522(); ///////////////////////////////////////////////////////////////////////////////////// // Basic interface functions for communicating with the MFRC522 ///////////////////////////////////////////////////////////////////////////////////// + void PCD_WriteRegister(PCD_Register reg, byte value); - void PCD_WriteRegister(PCD_Register reg, byte count, byte *values); + void PCD_WriteRegister(PCD_Register reg, byte count, byte* values); byte PCD_ReadRegister(PCD_Register reg); - void PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign = 0); + void PCD_ReadRegister(PCD_Register reg, byte count, byte* values, byte rxAlign = 0); void PCD_SetRegisterBitMask(PCD_Register reg, byte mask); void PCD_ClearRegisterBitMask(PCD_Register reg, byte mask); - StatusCode PCD_CalculateCRC(byte *data, byte length, byte *result); + StatusCode PCD_CalculateCRC(byte* data, byte length, byte* result); ///////////////////////////////////////////////////////////////////////////////////// // Functions for manipulating the MFRC522 @@ -357,46 +357,46 @@ class MFRC522 { ///////////////////////////////////////////////////////////////////////////////////// // Functions for communicating with PICCs ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_TransceiveData(byte *sendData, byte sendLen, byte *backData, byte *backLen, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); - StatusCode PCD_CommunicateWithPICC(byte command, byte waitIRq, byte *sendData, byte sendLen, byte *backData = nullptr, byte *backLen = nullptr, byte *validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); - StatusCode PICC_RequestA(byte *bufferATQA, byte *bufferSize); - StatusCode PICC_WakeupA(byte *bufferATQA, byte *bufferSize); - StatusCode PICC_REQA_or_WUPA(byte command, byte *bufferATQA, byte *bufferSize); - virtual StatusCode PICC_Select(Uid *uid, byte validBits = 0); + StatusCode PCD_TransceiveData(byte* sendData, byte sendLen, byte* backData, byte* backLen, byte* validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PCD_CommunicateWithPICC(byte command, byte waitIRq, byte* sendData, byte sendLen, byte* backData = nullptr, byte* backLen = nullptr, byte* validBits = nullptr, byte rxAlign = 0, bool checkCRC = false); + StatusCode PICC_RequestA(byte* bufferATQA, byte* bufferSize); + StatusCode PICC_WakeupA(byte* bufferATQA, byte* bufferSize); + StatusCode PICC_REQA_or_WUPA(byte command, byte* bufferATQA, byte* bufferSize); + virtual StatusCode PICC_Select(Uid* uid, byte validBits = 0); StatusCode PICC_HaltA(); ///////////////////////////////////////////////////////////////////////////////////// // Functions for communicating with MIFARE PICCs ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid); + StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key* key, Uid* uid); void PCD_StopCrypto1(); - StatusCode MIFARE_Read(byte blockAddr, byte *buffer, byte *bufferSize); - StatusCode MIFARE_Write(byte blockAddr, byte *buffer, byte bufferSize); - StatusCode MIFARE_Ultralight_Write(byte page, byte *buffer, byte bufferSize); + StatusCode MIFARE_Read(byte blockAddr, byte* buffer, byte* bufferSize); + StatusCode MIFARE_Write(byte blockAddr, byte* buffer, byte bufferSize); + StatusCode MIFARE_Ultralight_Write(byte page, byte* buffer, byte bufferSize); StatusCode MIFARE_Decrement(byte blockAddr, int32_t delta); StatusCode MIFARE_Increment(byte blockAddr, int32_t delta); StatusCode MIFARE_Restore(byte blockAddr); StatusCode MIFARE_Transfer(byte blockAddr); - StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value); + StatusCode MIFARE_GetValue(byte blockAddr, int32_t* value); StatusCode MIFARE_SetValue(byte blockAddr, int32_t value); - StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]); + StatusCode PCD_NTAG216_AUTH(byte* passWord, byte pACK[]); ///////////////////////////////////////////////////////////////////////////////////// // Support functions ///////////////////////////////////////////////////////////////////////////////////// - StatusCode PCD_MIFARE_Transceive(byte *sendData, byte sendLen, bool acceptTimeout = false); + StatusCode PCD_MIFARE_Transceive(byte* sendData, byte sendLen, bool acceptTimeout = false); static PICC_Type PICC_GetType(byte sak); // Support functions for debuging void PCD_DumpVersionToSerial(); - void PICC_DumpToSerial(Uid *uid); - void PICC_DumpDetailsToSerial(Uid *uid); - void PICC_DumpMifareClassicToSerial(Uid *uid, PICC_Type piccType, MIFARE_Key *key); - void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, byte sector); + void PICC_DumpToSerial(Uid* uid); + void PICC_DumpDetailsToSerial(Uid* uid); + void PICC_DumpMifareClassicToSerial(Uid* uid, PICC_Type piccType, MIFARE_Key* key); + void PICC_DumpMifareClassicSectorToSerial(Uid* uid, MIFARE_Key* key, byte sector); void PICC_DumpMifareUltralightToSerial(); // Advanced functions for MIFARE - void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, byte g3); + void MIFARE_SetAccessBits(byte* accessBitBuffer, byte g0, byte g1, byte g2, byte g3); ///////////////////////////////////////////////////////////////////////////////////// // Convenience functions - does not add extra functionality diff --git a/cpp_utils/MFRC522Debug.h b/cpp_utils/MFRC522Debug.h index 6c1c9ac6..e8033495 100644 --- a/cpp_utils/MFRC522Debug.h +++ b/cpp_utils/MFRC522Debug.h @@ -4,11 +4,10 @@ #define MFRC522Debug_h class MFRC522Debug { -private: - public: // Get human readable code and type static const char* PICC_GetTypeName(MFRC522::PICC_Type type); static const char* GetStatusCodeName(MFRC522::StatusCode code); + }; #endif // MFRC522Debug_h diff --git a/cpp_utils/MMU.cpp b/cpp_utils/MMU.cpp new file mode 100644 index 00000000..ee7adb59 --- /dev/null +++ b/cpp_utils/MMU.cpp @@ -0,0 +1,126 @@ +/* + * MMU.cpp + * + * Created on: Jun 30, 2018 + * Author: kolban + */ + +#include "MMU.h" +#include +#include +#include + +// The following functions are provided by spi_flash.h +extern "C" { + extern void spi_flash_disable_interrupts_caches_and_other_cpu(); + + // Enable cache, enable interrupts (to be added in future), resume scheduler + extern void spi_flash_enable_interrupts_caches_and_other_cpu(); +} + + +typedef struct { + uint32_t low; + uint32_t high; +} addressRange_t; + +static addressRange_t entryNumberToAddressRange(uint32_t entryNumber) { + addressRange_t ret; + if (entryNumber < 64) { + ret.low = 0x3F400000 + 64 * 1024 * entryNumber; + ret.high = 0x3F400000 + 64 * 1024 * (entryNumber + 1) - 1; + return ret; + } + ret.low = 0x40000000 + 64 * 1024 * (entryNumber - 64); + ret.high = 0x40000000 + 64 * 1024 * (entryNumber + 1 - 64) - 1; + return ret; +} + + +static uint32_t flashPageToOffset(uint32_t page) { + return page * 64 * 1024; +} + + + +/* static */ void MMU::dump() { + const uint32_t mappingInvalid = 1 << 8; + + printf("PRO CPU MMU\n"); + for (uint16_t i = 0; i < 256; i++) { + if (!(DPORT_PRO_FLASH_MMU_TABLE[i] & mappingInvalid)) { + addressRange_t addressRange = entryNumberToAddressRange(i); + printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", + i, + addressRange.low, addressRange.high, + DPORT_PRO_FLASH_MMU_TABLE[i] & 0xff, + flashPageToOffset(DPORT_PRO_FLASH_MMU_TABLE[i] & 0xff)); + } + } + printf("\n"); + printf("APP CPU MMU\n"); + for (uint16_t i = 0; i < 256; i++) { + if (!(DPORT_APP_FLASH_MMU_TABLE[i] & mappingInvalid)) { + addressRange_t addressRange = entryNumberToAddressRange(i); + printf("Entry: %2d (0x%8.8x - 0x%8.8x), Page: %d - offset: 0x%x\n", + i, + addressRange.low, addressRange.high, + DPORT_APP_FLASH_MMU_TABLE[i] & 0xff, + flashPageToOffset(DPORT_APP_FLASH_MMU_TABLE[i] & 0xff)); + } + } +} // MMU#dumpMMU + +extern "C" { + static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, size_t size); +} + +static void IRAM_ATTR mapFlashToVMA_Internal(uint32_t flashOffset, void* vma, size_t size) { + printf(">> MMU::mapFlashToVMA: flash offset: 0x%x, VMA: 0x%x, size: %d\n", flashOffset, (uint32_t)vma, size); + uint32_t mmuEntryStart; // The MMU table entry to start mapping. + uint32_t mmuEntryEnd; // The MMU table entry to end mapping. + + if ((uint32_t) vma >= 0x40000000 && (uint32_t) vma < 0x40C00000) { + mmuEntryStart = (((uint32_t) vma - 0x40000000) / (64 * 1024)) + 64; + mmuEntryEnd = (((uint32_t) vma - 0x40000000 + size) / (64 * 1024)) + 64; + } + else if ((uint32_t) vma >= 0x3F400000 && (uint32_t) vma < 0x3F800000) { + mmuEntryStart = (((uint32_t) vma - 0x3F400000) / (64 * 1024)); + mmuEntryEnd = (((uint32_t) vma - 0x3F400000 + size) / (64 * 1024)); + } else { + printf(" - Unable to map from flash to VMA."); + return; + } + + // At this point we have populated mmuEntryStart and mmuEntryEnd which are the MMU table entries. + uint32_t pFlashStart = flashOffset; + uint32_t pFlashEnd = flashOffset + size; + + printf(" - Mapping flash to VMA via MMU. MMU entries start: %d, end: %d, mapping flash 0x%x (flash page: %d) to 0x%x (flash page: %d)\n", + mmuEntryStart, mmuEntryEnd, pFlashStart, pFlashStart/(64 * 1024), pFlashEnd, pFlashEnd / (64 * 1024)); + + uint32_t flashRegion = pFlashStart / (64 * 1024); // Determine the 64K chunk of flash to be mapped (we map in units of 64K). + + spi_flash_disable_interrupts_caches_and_other_cpu(); + + // For each of the mapping entries, map it to the corresponding flash region. + for (uint32_t i = mmuEntryStart; i <= mmuEntryEnd; i++) { + DPORT_PRO_FLASH_MMU_TABLE[i] = flashRegion; // There are two tables. One for the PRO CPU and one for the APP CPU. + DPORT_APP_FLASH_MMU_TABLE[i] = flashRegion; // Map both of them to the flash region. + flashRegion++; + } + + Cache_Flush(0); + Cache_Flush(1); + spi_flash_enable_interrupts_caches_and_other_cpu(); +} // mapFlashToVMA + +/** + * Map an area of flash memory into VMA. + * @param flashOffset The offset in flash of the start of data. + * @param vma Where in VMA the data should appear. + * @size How much data to map. + */ +void IRAM_ATTR MMU::mapFlashToVMA(uint32_t flashOffset, void* vma, size_t size) { + mapFlashToVMA_Internal(flashOffset, vma, size); +} diff --git a/cpp_utils/MMU.h b/cpp_utils/MMU.h new file mode 100644 index 00000000..d8ba6b64 --- /dev/null +++ b/cpp_utils/MMU.h @@ -0,0 +1,22 @@ +/* + * MMU.h + * + * Created on: Jun 30, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_MMU_H_ +#define COMPONENTS_CPP_UTILS_MMU_H_ +#include +#include +#include + +class MMU { +public: + MMU(); + virtual ~MMU(); + static void dump(); + static void mapFlashToVMA(uint32_t flashOffset, void* vma, size_t size); +}; + +#endif /* COMPONENTS_CPP_UTILS_MMU_H_ */ diff --git a/cpp_utils/MPU6050.cpp b/cpp_utils/MPU6050.cpp index 5bcf5bb9..0f6931ae 100644 --- a/cpp_utils/MPU6050.cpp +++ b/cpp_utils/MPU6050.cpp @@ -9,14 +9,15 @@ #define MPU6050_GYRO_XOUT_H 0x43 #define MPU6050_PWR_MGMT_1 0x6B + /** * @brief Construct an %MPU6050 handler. */ MPU6050::MPU6050() { accel_x = accel_y = accel_z = 0; gyro_x = gyro_y = gyro_z = 0; - i2c=nullptr; - inited=false; + i2c = nullptr; + inited = false; } @@ -43,7 +44,7 @@ void MPU6050::readAccel() { uint8_t data[6]; i2c->beginTransaction(); i2c->read(data, 5, true); - i2c->read(data+5, false); + i2c->read(data + 5, false); i2c->endTransaction(); accel_x = (data[0] << 8) | data[1]; @@ -51,6 +52,7 @@ void MPU6050::readAccel() { accel_z = (data[4] << 8) | data[5]; } // readAccel + /** * @brief Read the gyroscopic values from the device. * @@ -66,7 +68,7 @@ void MPU6050::readGyro() { uint8_t data[6]; i2c->beginTransaction(); i2c->read(data, 5, true); - i2c->read(data+5, false); + i2c->read(data + 5, false); i2c->endTransaction(); gyro_x = (data[0] << 8) | data[1]; @@ -74,6 +76,7 @@ void MPU6050::readGyro() { gyro_z = (data[4] << 8) | data[5]; } // readGyro + /** * @brief Initialize the %MPU6050. * @param [in] sdaPin The %GPIO pin to use for %I2C SDA. @@ -94,5 +97,5 @@ void MPU6050::init(gpio_num_t sdaPin, gpio_num_t clkPin) { i2c->write(MPU6050_PWR_MGMT_1); i2c->write(0); i2c->endTransaction(); - inited=true; + inited = true; } diff --git a/cpp_utils/MPU6050.h b/cpp_utils/MPU6050.h index fe69193d..8207f6d6 100644 --- a/cpp_utils/MPU6050.h +++ b/cpp_utils/MPU6050.h @@ -16,61 +16,49 @@ * A call to init() must precede all other API calls. */ class MPU6050 { -private: - I2C *i2c; - short accel_x, accel_y, accel_z; - short gyro_x, gyro_y, gyro_z; - bool inited; public: MPU6050(); virtual ~MPU6050(); - /** * @brief Get the X acceleration value. */ - short getAccelX() const - { + short getAccelX() const { return accel_x; } /** * @brief Get the Y acceleration value. */ - short getAccelY() const - { + short getAccelY() const { return accel_y; } /** * @brief Get the Z acceleration value. */ - short getAccelZ() const - { + short getAccelZ() const { return accel_z; } /** * @brief Get the X gyroscopic value. */ - short getGyroX() const - { + short getGyroX() const { return gyro_x; } /** * @brief Get the Y gyroscopic value. */ - short getGyroY() const - { + short getGyroY() const { return gyro_y; } /** * @brief Get the Z gyroscopic value. */ - short getGyroZ() const - { + short getGyroZ() const { return gyro_z; } @@ -86,10 +74,16 @@ class MPU6050 { return sqrt(accel_x * accel_x + accel_y * accel_y + accel_z * accel_z); } - void init(gpio_num_t sdaPin=I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin=I2C::DEFAULT_CLK_PIN); + void init(gpio_num_t sdaPin = I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin = I2C::DEFAULT_CLK_PIN); void readAccel(); - void readGyro(); + +private: + I2C* i2c; + short accel_x, accel_y, accel_z; + short gyro_x, gyro_y, gyro_z; + bool inited; + }; #endif /* MAIN_MPU6050_H_ */ diff --git a/cpp_utils/MRFC522Debug.cpp b/cpp_utils/MRFC522Debug.cpp index 82cd031d..56ba91d0 100644 --- a/cpp_utils/MRFC522Debug.cpp +++ b/cpp_utils/MRFC522Debug.cpp @@ -1,46 +1,48 @@ #include "MFRC522Debug.h" + /** * Returns a __FlashStringHelper pointer to the PICC type name. * + * @param piccType One of the PICC_Type enums. * @return const __FlashStringHelper * */ -const char* MFRC522Debug::PICC_GetTypeName(MFRC522::PICC_Type piccType ///< One of the PICC_Type enums. -) { +const char* MFRC522Debug::PICC_GetTypeName(MFRC522::PICC_Type piccType) { switch (piccType) { - case MFRC522::PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; + case MFRC522::PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; case MFRC522::PICC_TYPE_ISO_18092: return "PICC compliant with ISO/IEC 18092 (NFC)"; - case MFRC522::PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; + case MFRC522::PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; case MFRC522::PICC_TYPE_MIFARE_1K: return "MIFARE 1KB"; case MFRC522::PICC_TYPE_MIFARE_4K: return "MIFARE 4KB"; case MFRC522::PICC_TYPE_MIFARE_UL: return "MIFARE Ultralight or Ultralight C"; - case MFRC522::PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; - case MFRC522::PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; - case MFRC522::PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; - case MFRC522::PICC_TYPE_NOT_COMPLETE: return "SAK indicates UID is not complete."; + case MFRC522::PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; + case MFRC522::PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; + case MFRC522::PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; + case MFRC522::PICC_TYPE_NOT_COMPLETE: return "SAK indicates UID is not complete."; case MFRC522::PICC_TYPE_UNKNOWN: - default: return "Unknown type"; + default: return "Unknown type"; } } // End PICC_GetTypeName() + /** * Returns a __FlashStringHelper pointer to a status code name. * + * @param code One of the StatusCode enums. * @return const __FlashStringHelper * */ -const char *MFRC522Debug::GetStatusCodeName(MFRC522::StatusCode code ///< One of the StatusCode enums. -) { +const char *MFRC522Debug::GetStatusCodeName(MFRC522::StatusCode code) { switch (code) { - case MFRC522::STATUS_OK: return "Success."; + case MFRC522::STATUS_OK: return "Success."; case MFRC522::STATUS_ERROR: return "Error in communication."; - case MFRC522::STATUS_COLLISION: return "Collission detected."; - case MFRC522::STATUS_TIMEOUT: return "Timeout in communication."; - case MFRC522::STATUS_NO_ROOM: return "A buffer is not big enough."; - case MFRC522::STATUS_INTERNAL_ERROR: return "Internal error in the code. Should not happen."; - case MFRC522::STATUS_INVALID: return "Invalid argument."; + case MFRC522::STATUS_COLLISION: return "Collision detected."; + case MFRC522::STATUS_TIMEOUT: return "Timeout in communication."; + case MFRC522::STATUS_NO_ROOM: return "A buffer is not big enough."; + case MFRC522::STATUS_INTERNAL_ERROR: return "Internal error in the code. Should not happen."; + case MFRC522::STATUS_INVALID: return "Invalid argument."; case MFRC522::STATUS_CRC_WRONG: return "The CRC_A does not match."; - case MFRC522::STATUS_MIFARE_NACK: return "A MIFARE PICC responded with NAK."; - default: return "Unknown error"; + case MFRC522::STATUS_MIFARE_NACK: return "A MIFARE PICC responded with NAK."; + default: return "Unknown error"; } } // End GetStatusCodeName() diff --git a/cpp_utils/Makefile.arduino b/cpp_utils/Makefile.arduino index c14be636..5638b266 100644 --- a/cpp_utils/Makefile.arduino +++ b/cpp_utils/Makefile.arduino @@ -10,15 +10,18 @@ BLE_FILES= \ BLE2902.cpp \ BLE2902.h \ + BLE2904.cpp \ + BLE2904.h \ BLEAddress.cpp \ BLEAddress.h \ BLEAdvertisedDevice.cpp \ BLEAdvertisedDevice.h \ BLEAdvertising.cpp \ BLEAdvertising.h \ + BLEBeacon.cpp \ + BLEBeacon.h \ BLECharacteristic.cpp \ BLECharacteristic.h \ - BLECharacteristicCallbacks.cpp \ BLECharacteristicMap.cpp \ BLEClient.cpp \ BLEClient.h \ @@ -27,6 +30,10 @@ BLE_FILES= \ BLEDescriptorMap.cpp \ BLEDevice.cpp \ BLEDevice.h \ + BLEExceptions.cpp \ + BLEExceptions.h \ + BLEHIDDevice.cpp \ + BLEHIDDevice.h \ BLERemoteCharacteristic.cpp \ BLERemoteCharacteristic.h \ BLERemoteDescriptor.cpp \ @@ -35,12 +42,13 @@ BLE_FILES= \ BLERemoteService.h \ BLEScan.cpp \ BLEScan.h \ - BLEServerCallbacks.cpp \ BLEServer.cpp \ BLEServer.h \ BLEService.cpp \ BLEService.h \ BLEServiceMap.cpp \ + BLESecurity.cpp \ + BLESecurity.h \ BLEUtils.cpp \ BLEUtils.h \ BLEUUID.cpp \ @@ -50,8 +58,11 @@ BLE_FILES= \ FreeRTOS.h \ FreeRTOS.cpp \ GeneralUtils.h \ - GeneralUtils.cpp + GeneralUtils.cpp \ + HIDTypes.h \ + HIDKeyboardTypes.h +ARDUINO_LIBS=$(HOME)/Arduino/libraries build_ble: rm -rf Arduino/ESP32_BLE @@ -59,9 +70,13 @@ build_ble: cp $(BLE_FILES) Arduino/ESP32_BLE/src cp Arduino_ESP32_BLE.library.properties Arduino/ESP32_BLE/library.properties mkdir -p Arduino/ESP32_BLE/examples - cp --recursive tests/BLETests/Arduino Arduino/ESP32_BLE/examples + cp -R tests/BLETests/Arduino Arduino/ESP32_BLE/examples rm -f Arduino/ESP32_BLE.zip cd Arduino; zip -r ESP32_BLE.zip ESP32_BLE rm -rf Arduino/ESP32_BLE @echo "---------------------------------------" @echo "ESP32_BLE.zip Arduino library now built" + +install: build_ble + rm -rf ${ARDUINO_LIBS}/ESP32_BLE + unzip Arduino/ESP32_BLE.zip -d $(ARDUINO_LIBS) diff --git a/cpp_utils/Memory.cpp b/cpp_utils/Memory.cpp new file mode 100644 index 00000000..33e5ae80 --- /dev/null +++ b/cpp_utils/Memory.cpp @@ -0,0 +1,163 @@ +/* + * Memory.cpp + * + * Created on: Oct 24, 2017 + * Author: kolban + */ +#include "sdkconfig.h" +#ifdef CONFIG_HEAP_TRACING +#include "Memory.h" + +#include +#include "GeneralUtils.h" +extern "C" { + #include + #include +} +#include +#include + +static const char* LOG_TAG = "Memory"; + +heap_trace_record_t* Memory::m_pRecords = nullptr; +size_t Memory::m_lastHeapSize = 0; + + +/** + * @brief Check the integrity of memory. + * @return True if all integrity checks passed + */ +/* STATIC */ bool Memory::checkIntegrity() { + bool rc = ::heap_caps_check_integrity_all(true); + if (!rc && m_pRecords != nullptr) { + dumpRanges(); + abort(); + } + return rc; +} // checkIntegrity + + +/** + * @brief Dump the trace records from the heap. + */ +/* STATIC */ void Memory::dump() { + ::heap_trace_dump(); +} // dump + + +/* STATIC */ void Memory::dumpHeapChange(std::string tag) { + size_t currentUsage = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t diff = currentUsage - m_lastHeapSize; + ESP_LOGD(LOG_TAG, "%s: Heap changed by %d bytes (%d to %d)", tag.c_str(), diff, m_lastHeapSize, currentUsage); + m_lastHeapSize = currentUsage; +} // dumpHeapChange + + +/** + * @brief Dump the ranges of allocations found. + */ +/* STATIC */ void Memory::dumpRanges() { + // Each record contained in the Heap trace has the following format: + // * uint32_t ccount – Timestamp of record. + // * void* address – Address that was allocated or released. + // * size_t size – Size of the block that was requested and allocated. + // * void* alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of allocator + // * void* freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH] – Call stack of releasor + if (m_pRecords == nullptr) return; + + esp_log_level_set("*", ESP_LOG_NONE); + size_t count = (size_t) heap_trace_get_count(); + heap_trace_record_t record; + printf(">>> dumpRanges\n"); + for (size_t i = 0; i < count; i++) { + esp_err_t errRc = heap_trace_get(i, &record); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_get: %d", errRc); + } + printf("0x%x:0x%x:%d:", (uint32_t) record.address, ((uint32_t) record.address) + record.size, record.size); + for (size_t j = 0; j < CONFIG_HEAP_TRACING_STACK_DEPTH; j++) { + printf("%x ", (uint32_t) record.alloced_by[j]); + } + printf(":"); + for (size_t j = 0; j < CONFIG_HEAP_TRACING_STACK_DEPTH; j++) { + printf("%x ", (uint32_t) record.freed_by[j]); + } + printf("\n"); + } + printf("<<< dumpRanges\n"); + esp_log_level_set("*", ESP_LOG_VERBOSE); +} // dumpRanges + + +/** + * @brief Initialize heap recording. + * @param [in] recordCount The maximum number of records to be recorded. + */ +/* STATIC */ void Memory::init(uint32_t recordCount) { + assert(recordCount > 0); + if (m_pRecords != nullptr) { + ESP_LOGE(LOG_TAG, "Already initialized"); + return; + } + + m_pRecords = new heap_trace_record_t[recordCount]; // Allocate the maximum number of records to be recorded. + if (m_pRecords == nullptr) { + ESP_LOGE(LOG_TAG, "Unable to create %d heap records", recordCount); + } + + esp_err_t errRc = ::heap_trace_init_standalone(m_pRecords, recordCount); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_init_standalone: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // init + + +/** + * @brief Resume previously paused trace. + */ +/* STATIC */ void Memory::resumeTrace() { + esp_err_t errRc = ::heap_trace_resume(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_resume: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // resumeTrace + + +/** + * @brief Start tracing all allocate and free calls. + */ +/* STATIC */ void Memory::startTraceAll() { + esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_ALL); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // startTraceAll + + +/** + * Start tracing leaks. Matched allocate and free calls are removed. + */ +/* STATIC */ void Memory::startTraceLeaks() { + esp_err_t errRc = ::heap_trace_start(HEAP_TRACE_LEAKS); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // startTraceLeaks + + +/** + * @brief Stop recording heap trace. + */ +/* STATIC */ void Memory::stopTrace() { + esp_err_t errRc = ::heap_trace_stop(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "heap_trace_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // stopTrace + +#endif diff --git a/cpp_utils/Memory.h b/cpp_utils/Memory.h new file mode 100644 index 00000000..25da1231 --- /dev/null +++ b/cpp_utils/Memory.h @@ -0,0 +1,34 @@ +/* + * Memory.h + * + * Created on: Oct 24, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_MEMORY_H_ +#define COMPONENTS_CPP_UTILS_MEMORY_H_ +#include "sdkconfig.h" +#ifdef CONFIG_HEAP_TRACING +#include +extern "C" { +#include +} + +class Memory { +public: + static bool checkIntegrity(); + static void dump(); + static void dumpRanges(); + static void dumpHeapChange(std::string tag); + static void init(uint32_t recordCount); + static void resumeTrace(); + static void startTraceAll(); + static void startTraceLeaks(); + static void stopTrace(); + +private: + static heap_trace_record_t* m_pRecords; + static size_t m_lastHeapSize; // Size of last heap recorded. +}; +#endif +#endif /* COMPONENTS_CPP_UTILS_MEMORY_H_ */ diff --git a/cpp_utils/NeoPixelWiFiEventHandler.cpp b/cpp_utils/NeoPixelWiFiEventHandler.cpp index 9206b567..229fd7ff 100644 --- a/cpp_utils/NeoPixelWiFiEventHandler.cpp +++ b/cpp_utils/NeoPixelWiFiEventHandler.cpp @@ -5,8 +5,11 @@ * Author: kolban */ #include +#include #include "NeoPixelWiFiEventHandler.h" +static const char* LOG_TAG = "NeoPixelWiFiEventHandler"; + NeoPixelWiFiEventHandler::NeoPixelWiFiEventHandler(gpio_num_t gpioPin) { this->gpioPin = gpioPin; ws2812 = new WS2812(gpioPin, 8); @@ -17,42 +20,42 @@ NeoPixelWiFiEventHandler::~NeoPixelWiFiEventHandler() { } esp_err_t NeoPixelWiFiEventHandler::apStart() { - printf("XXX apStart\n"); + ESP_LOGD(LOG_TAG, "XXX apStart"); ws2812->setPixel(0, 0, 00, 64); ws2812->show(); return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staConnected() { - printf("XXX staConnected\n"); +esp_err_t NeoPixelWiFiEventHandler::staConnected(system_event_sta_connected_t info) { + ESP_LOGD(LOG_TAG, "XXX staConnected"); ws2812->setPixel(0, 57, 89, 66); ws2812->show(); return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staDisconnected() { - printf("XXX staDisconnected\n"); +esp_err_t NeoPixelWiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { + ESP_LOGD(LOG_TAG, "XXX staDisconnected"); ws2812->setPixel(0, 64, 0, 0); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::staStart() { - printf("XXX staStart\n"); + ESP_LOGD(LOG_TAG, "XXX staStart"); ws2812->setPixel(0, 64, 64, 0); ws2812->show(); return ESP_OK; } -esp_err_t NeoPixelWiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - printf("XXX staGotIp\n"); +esp_err_t NeoPixelWiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { + ESP_LOGD(LOG_TAG, "XXX staGotIp"); ws2812->setPixel(0, 0, 64, 0); ws2812->show(); return ESP_OK; } esp_err_t NeoPixelWiFiEventHandler::wifiReady() { - printf("XXX wifiReady\n"); + ESP_LOGD(LOG_TAG, "XXX wifiReady"); ws2812->setPixel(0, 64, 64, 0); ws2812->show(); return ESP_OK; diff --git a/cpp_utils/NeoPixelWiFiEventHandler.h b/cpp_utils/NeoPixelWiFiEventHandler.h index c271e3e3..d59cafff 100644 --- a/cpp_utils/NeoPixelWiFiEventHandler.h +++ b/cpp_utils/NeoPixelWiFiEventHandler.h @@ -23,14 +23,15 @@ class NeoPixelWiFiEventHandler: public WiFiEventHandler { virtual ~NeoPixelWiFiEventHandler(); esp_err_t apStart() override; - esp_err_t staConnected() override; - esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) override; - esp_err_t staDisconnected() override; + esp_err_t staConnected(system_event_sta_connected_t info) override; + esp_err_t staGotIp(system_event_sta_got_ip_t info) override; + esp_err_t staDisconnected(system_event_sta_disconnected_t info) override; esp_err_t wifiReady() override; esp_err_t staStart() override; + private: gpio_num_t gpioPin; - WS2812 *ws2812; + WS2812* ws2812; }; #endif /* MAIN_NEOPIXELWIFIEVENTHANDLER_H_ */ diff --git a/cpp_utils/OV7670.cpp b/cpp_utils/OV7670.cpp index c7e395ce..a3c3811d 100644 --- a/cpp_utils/OV7670.cpp +++ b/cpp_utils/OV7670.cpp @@ -14,31 +14,34 @@ #include "GeneralUtils.h" #include "sdkconfig.h" #include + +#define OV7670_I2C_ADDR (0x21) + extern "C" { #include #include } -static const char *LOG_TAG="OV7670"; +static const char* LOG_TAG = "OV7670"; static bool getBit(uint8_t value, uint8_t bitNum) { - return (value & (1<> 3) << 3; uint8_t green = (((byte1 & 0b111) << 3) | ((byte2 & 0b11100000) >> 5)) << 2; uint8_t blue = (byte2 & 0b11111) << 3; - *pLine = (red + green + blue)/3; + *pLine = (red + green + blue) / 3; pLine++; - i+=2; + i += 2; } pLine = pLineSave; - GeneralUtils::hexDump(pLine, length/2); + GeneralUtils::hexDump(pLine, length / 2); } void OV7670::setFormat(uint8_t value) { @@ -120,7 +123,6 @@ void OV7670::setTestPattern(uint8_t value) { * */ -#define OV7670_I2C_ADDR (0x21) /* static void IRAM_ATTR isr_vsync(void* arg) { ESP_EARLY_LOGD(LOG_TAG, "VSYNC"); @@ -183,20 +185,19 @@ OV7670::~OV7670() { } -static esp_err_t camera_enable_out_clock(camera_config_t* config) -{ +static esp_err_t camera_enable_out_clock(camera_config_t* config) { ESP_LOGD(LOG_TAG, ">> camera_enable_out_clock: freq_hz=%d, pin=%d", config->xclk_freq_hz, config->pin_xclk); periph_module_enable(PERIPH_LEDC_MODULE); ledc_timer_config_t timer_conf; - timer_conf.bit_num = (ledc_timer_bit_t)1; timer_conf.freq_hz = config->xclk_freq_hz; + timer_conf.duty_resolution = (ledc_timer_bit_t) 1; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = config->ledc_timer; esp_err_t err = ledc_timer_config(&timer_conf); if (err != ESP_OK) { - ESP_LOGE(LOG_TAG, "ledc_timer_config failed, rc=%x", err); - return err; + ESP_LOGE(LOG_TAG, "ledc_timer_config failed, rc=%x", err); + return err; } ledc_channel_config_t ch_conf; @@ -209,8 +210,8 @@ static esp_err_t camera_enable_out_clock(camera_config_t* config) err = ledc_channel_config(&ch_conf); if (err != ESP_OK) { - ESP_LOGE(LOG_TAG, "ledc_channel_config failed, rc=%x", err); - return err; + ESP_LOGE(LOG_TAG, "ledc_channel_config failed, rc=%x", err); + return err; } ESP_LOGD(LOG_TAG, "<< camera_enable_out_clock"); return ESP_OK; @@ -274,90 +275,94 @@ void OV7670::dump() { uint32_t outputFormat = getBit(com7, 2) << 1 | getBit(com7, 0); //uint32_t outputFormat = (com7 & (1<<2)) >> 1 | (com7 & (1<<0)); std::string outputFormatString; - switch(outputFormat) { - case 0b00: - outputFormatString = "YUV"; - break; - case 0b10: - outputFormatString = "RGB"; - break; - case 0b01: - outputFormatString = "Raw Bayer RGB"; - break; - case 0b11: - outputFormatString = "Process Bayer RGB"; - break; - default: - outputFormatString = "Unknown"; - break; - } - ESP_LOGD(LOG_TAG, "Output format: %s", outputFormatString.c_str()); - if (outputFormat == 0b10) { - uint8_t com15 = readRegister(OV7670_REG_COM15); - uint8_t rgbType = getBit(com15, 5) << 1 | getBit(com15,4); - char *rgbTypeString; - switch(rgbType) { + switch (outputFormat) { case 0b00: + outputFormatString = "YUV"; + break; case 0b10: - rgbTypeString = (char*)"Normal RGB Output"; + outputFormatString = "RGB"; break; case 0b01: - rgbTypeString = (char*)"RGB 565"; + outputFormatString = "Raw Bayer RGB"; break; case 0b11: - rgbTypeString = (char*)"RGB 555"; + outputFormatString = "Process Bayer RGB"; break; default: - rgbTypeString = (char*)"Unknown"; + outputFormatString = "Unknown"; break; + } + ESP_LOGD(LOG_TAG, "Output format: %s", outputFormatString.c_str()); + if (outputFormat == 0b10) { + uint8_t com15 = readRegister(OV7670_REG_COM15); + uint8_t rgbType = getBit(com15, 5) << 1 | getBit(com15, 4); + char* rgbTypeString; + switch (rgbType) { + case 0b00: + case 0b10: + rgbTypeString = (char*) "Normal RGB Output"; + break; + case 0b01: + rgbTypeString = (char*) "RGB 565"; + break; + case 0b11: + rgbTypeString = (char*) "RGB 555"; + break; + default: + rgbTypeString = (char*) "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Rgb Type: %s", rgbTypeString); } - ESP_LOGD(LOG_TAG, "Color bar: %s", getBit(com7, 1)?"Enabled":"Disabled"); + ESP_LOGD(LOG_TAG, "Color bar: %s", getBit(com7, 1) ? "Enabled" : "Disabled"); uint8_t scaling_xsc = readRegister(OV7670_REG_SCALING_XSC); uint8_t scaling_ysc = readRegister(OV7670_REG_SCALING_YSC); uint32_t testPattern = getBit(scaling_xsc, 7) << 1 | getBit(scaling_ysc, 7); - char *testPatternString; - switch(testPattern) { - case 0b00: - testPatternString = (char*)"No test output"; - break; - case 0b01: - testPatternString = (char*)"Shifting 1"; - break; - case 0b10: - testPatternString = (char*)"8-bar color bar"; - break; - case 0b11: - testPatternString = (char*)"Fade to gray color bar"; - break; - default: - testPatternString = (char*)"Unknown"; - break; + char* testPatternString; + switch (testPattern) { + case 0b00: + testPatternString = (char*) "No test output"; + break; + case 0b01: + testPatternString = (char*) "Shifting 1"; + break; + case 0b10: + testPatternString = (char*) "8-bar color bar"; + break; + case 0b11: + testPatternString = (char*) "Fade to gray color bar"; + break; + default: + testPatternString = (char*) "Unknown"; + break; } ESP_LOGD(LOG_TAG, "Test pattern: %s", testPatternString); ESP_LOGD(LOG_TAG, "Horizontal scale factor: %d", scaling_xsc & 0x3f); ESP_LOGD(LOG_TAG, "Vertical scale factor: %d", scaling_ysc & 0x3f); uint8_t com15 = readRegister(OV7670_REG_COM15); - switch((com15 & 0b11000000) >> 6) { - case 0b00: - case 0b01: - ESP_LOGD(LOG_TAG, "Output range: 0x10 to 0xf0"); - break; - case 0b10: - ESP_LOGD(LOG_TAG, "Output range: 0x01 to 0xfe"); - break; - case 0b11: - ESP_LOGD(LOG_TAG, "Output range: 0x00 to 0xff"); - break; + switch ((com15 & 0b11000000) >> 6) { + case 0b00: + case 0b01: + ESP_LOGD(LOG_TAG, "Output range: 0x10 to 0xf0"); + break; + case 0b10: + ESP_LOGD(LOG_TAG, "Output range: 0x01 to 0xfe"); + break; + case 0b11: + ESP_LOGD(LOG_TAG, "Output range: 0x00 to 0xff"); + break; + default: + break; } } // dump -static void log(char *marker) { +/* +static void log(char* marker) { ESP_LOGD(LOG_TAG, "%s", marker); FreeRTOS::sleep(100); } +*/ /** * @brief Initialize the camera. @@ -392,7 +397,7 @@ void OV7670::init(camera_config_t cameraConfig) { // Create the I2C interface. m_i2c = new I2C(); - m_i2c->init(OV7670_I2C_ADDR, (gpio_num_t)m_cameraConfig.pin_sscb_sda, (gpio_num_t)m_cameraConfig.pin_sscb_scl); + m_i2c->init(OV7670_I2C_ADDR, (gpio_num_t) m_cameraConfig.pin_sscb_sda, (gpio_num_t) m_cameraConfig.pin_sscb_scl); m_i2c->scan(); ESP_LOGD(LOG_TAG, "Do you see 0x21 listed?"); resetCamera(); @@ -428,46 +433,42 @@ void OV7670::init(camera_config_t cameraConfig) { } */ - I2S i2s; - dma_config_t dmaConfig; - dmaConfig.pin_d0 = cameraConfig.pin_d0; - dmaConfig.pin_d1 = cameraConfig.pin_d1; - dmaConfig.pin_d2 = cameraConfig.pin_d2; - dmaConfig.pin_d3 = cameraConfig.pin_d3; - dmaConfig.pin_d4 = cameraConfig.pin_d4; - dmaConfig.pin_d5 = cameraConfig.pin_d5; - dmaConfig.pin_d6 = cameraConfig.pin_d6; - dmaConfig.pin_d7 = cameraConfig.pin_d7; - dmaConfig.pin_href = cameraConfig.pin_href; - dmaConfig.pin_pclk = cameraConfig.pin_pclk; - dmaConfig.pin_vsync = cameraConfig.pin_vsync; - i2s.cameraMode(dmaConfig, 50, 360*2); - ESP_LOGD(LOG_TAG, "Waiting for data!"); - while(1) { - DMAData dmaData = i2s.waitForData(); - //GeneralUtils::hexDump(dmaData.getData(), dmaData.getLength()); - toGrayscale(dmaData.getData(), dmaData.getLength()); - dmaData.free(); - } - - ESP_LOGD(LOG_TAG, "Waiting for positive edge on VSYNC"); - while (gpio_get_level(m_cameraConfig.pin_vsync) == 0) { - ; - } - while (gpio_get_level(m_cameraConfig.pin_vsync) != 0) { - ; - } - - - ESP_LOGD(LOG_TAG, "Got VSYNC"); - - - - - while(1) { - FreeRTOS::sleep(1000); - ESP_LOGD(LOG_TAG, "VSYNC Counter: %d, lastHref=%d, pclk=%d", vsyncCounter, lastHref, pclkCounter); - } + I2S i2s; + dma_config_t dmaConfig; + dmaConfig.pin_d0 = cameraConfig.pin_d0; + dmaConfig.pin_d1 = cameraConfig.pin_d1; + dmaConfig.pin_d2 = cameraConfig.pin_d2; + dmaConfig.pin_d3 = cameraConfig.pin_d3; + dmaConfig.pin_d4 = cameraConfig.pin_d4; + dmaConfig.pin_d5 = cameraConfig.pin_d5; + dmaConfig.pin_d6 = cameraConfig.pin_d6; + dmaConfig.pin_d7 = cameraConfig.pin_d7; + dmaConfig.pin_href = cameraConfig.pin_href; + dmaConfig.pin_pclk = cameraConfig.pin_pclk; + dmaConfig.pin_vsync = cameraConfig.pin_vsync; + i2s.cameraMode(dmaConfig, 50, 360 * 2); + ESP_LOGD(LOG_TAG, "Waiting for data!"); + while (true) { + DMAData dmaData = i2s.waitForData(); +// GeneralUtils::hexDump(dmaData.getData(), dmaData.getLength()); + toGrayscale(dmaData.getData(), dmaData.getLength()); + dmaData.free(); + } + + ESP_LOGD(LOG_TAG, "Waiting for positive edge on VSYNC"); + while (gpio_get_level(m_cameraConfig.pin_vsync) == 0) { + ; + } + while (gpio_get_level(m_cameraConfig.pin_vsync) != 0) { + ; + } + + ESP_LOGD(LOG_TAG, "Got VSYNC"); + + while (true) { + FreeRTOS::sleep(1000); + ESP_LOGD(LOG_TAG, "VSYNC Counter: %d, lastHref=%d, pclk=%d", vsyncCounter, lastHref, pclkCounter); + } ESP_LOGD(LOG_TAG, "<< init"); } // init diff --git a/cpp_utils/OV7670.h b/cpp_utils/OV7670.h index b7baf1f9..0fb4fa19 100644 --- a/cpp_utils/OV7670.h +++ b/cpp_utils/OV7670.h @@ -198,11 +198,13 @@ class OV7670 { void setRGBFormat(uint8_t value); void setTestPattern(uint8_t value); void resetCamera(); + private: uint8_t readRegister(uint8_t reg); void writeRegister(uint8_t reg, uint8_t value); camera_config_t m_cameraConfig; - I2C *m_i2c; + I2C* m_i2c; + }; #endif /* CPP_UTILS_OV7670_H_ */ diff --git a/cpp_utils/PCF8574.cpp b/cpp_utils/PCF8574.cpp index 4d850a8b..d246b7a6 100644 --- a/cpp_utils/PCF8574.cpp +++ b/cpp_utils/PCF8574.cpp @@ -18,14 +18,17 @@ * @param [in] address The %I2C address of the device on the %I2C bus. */ PCF8574::PCF8574(uint8_t address) { - i2c.setAddress(address); + i2c = new I2C(); + i2c->setAddress(address); lastWrite = 0; } + /** * @brief Class instance destructor. */ PCF8574::~PCF8574() { + delete i2c; } @@ -35,9 +38,9 @@ PCF8574::~PCF8574() { */ uint8_t PCF8574::read() { uint8_t value; - i2c.beginTransaction(); - i2c.read(&value,true); - i2c.endTransaction(); + i2c->beginTransaction(); + i2c->read(&value,true); + i2c->endTransaction(); return value; } // read @@ -49,11 +52,9 @@ uint8_t PCF8574::read() { * @return True if the pin is high, false otherwise. Undefined if there is no signal on the pin. */ bool PCF8574::readBit(uint8_t bit) { - if (bit > 7) { - return false; - } + if (bit > 7) return false; uint8_t value = read(); - return (value & (1<beginTransaction(); + i2c->write(value, true); + i2c->endTransaction(); lastWrite = value; } // write @@ -83,9 +84,7 @@ void PCF8574::write(uint8_t value) { * @param [in] value The logic level to appear on the identified output pin. */ void PCF8574::writeBit(uint8_t bit, bool value) { - if (bit > 7) { - return; - } + if (bit > 7) return; if (invert) { value = !value; } @@ -117,5 +116,6 @@ void PCF8574::setInvert(bool value) { * @param [in] clkPin The pin to use for the %I2C CLK functions. */ void PCF8574::init(gpio_num_t sdaPin, gpio_num_t clkPin) { - i2c.init(0, sdaPin, clkPin); + uint8_t addr = i2c->getAddress(); + i2c->init(addr, sdaPin, clkPin); } // init diff --git a/cpp_utils/PCF8574.h b/cpp_utils/PCF8574.h index 15b11da9..b8c287f4 100644 --- a/cpp_utils/PCF8574.h +++ b/cpp_utils/PCF8574.h @@ -21,7 +21,7 @@ class PCF8574 { public: PCF8574(uint8_t address); virtual ~PCF8574(); - void init(gpio_num_t sdaPin=I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin=I2C::DEFAULT_CLK_PIN); + void init(gpio_num_t sdaPin = I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin = I2C::DEFAULT_CLK_PIN); uint8_t read(); bool readBit(uint8_t bit); void setInvert(bool value); @@ -29,9 +29,10 @@ class PCF8574 { void writeBit(uint8_t bit, bool value); private: - I2C i2c = I2C(); + I2C* i2c; uint8_t lastWrite; bool invert = false; + }; #endif /* COMPONENTS_CPP_UTILS_PCF8574_H_ */ diff --git a/cpp_utils/PCF8575.cpp b/cpp_utils/PCF8575.cpp new file mode 100644 index 00000000..ea322ef2 --- /dev/null +++ b/cpp_utils/PCF8575.cpp @@ -0,0 +1,123 @@ +/* + * PCF8575.cpp + * + * Created on: Jan 27, 2018 + * Author: kolban + */ + +#include "PCF8575.h" +#include "I2C.h" + + +/** + * @brief Class constructor. + * + * The address is the address of the device on the %I2C bus. This is the value 0x20 plus + * the value of the device input pins `A0`, `A1` and `A2`. This means that the address should + * be between 0x20 and 0x27. + * + * @param [in] address The %I2C address of the device on the %I2C bus. + */ +PCF8575::PCF8575(uint8_t address) { + i2c = new I2C(); + i2c->setAddress(address); + m_lastWrite = 0; +} + + +/** + * @brief Class instance destructor. + */ +PCF8575::~PCF8575() { + delete i2c; +} + + +/** + * @brief Read all the input bits from the device. + * @return A 16 bit value representing the values on each of the input pins. + */ +uint16_t PCF8575::read() { + uint16_t value; + i2c->beginTransaction(); + i2c->read((uint8_t*) &value, true); + i2c->read(((uint8_t*) &value) + 1, true); + i2c->endTransaction(); + return value; +} // read + + +/** + * @brief Read the logic level on a given pin. + * + * @param [in] bit The input pin of the device to read. Values are 0-15. + * @return True if the pin is high, false otherwise. Undefined if there is no signal on the pin. + */ +bool PCF8575::readBit(uint16_t bit) { + if (bit > 7) return false; + uint16_t value = read(); + return (value & (1 << bit)) != 0; +} // readBit + + +/** + * @brief Set the output values of the device. + * + * @param [in] value The bit pattern to set on the output. + */ +void PCF8575::write(uint16_t value) { + if (invert) { + value = ~value; + } + i2c->beginTransaction(); + i2c->write(value & 0xff, true); + i2c->write((value >> 8) & 0xff, true); + i2c->endTransaction(); + m_lastWrite = value; +} // write + + +/** + * @brief Change the output value of a specific pin. + * + * The other bits beyond the one setting retain their values from the previous call to write() or + * previous calls to writeBit(). + * + * @param [in] bit The pin to have its value changed. The pin may be 0-15. + * @param [in] value The logic level to appear on the identified output pin. + */ +void PCF8575::writeBit(uint16_t bit, bool value) { + if (bit > 15) return; + if (invert) { + value = !value; + } + if (value) { + m_lastWrite |= (1 << bit); + } else { + m_lastWrite &= ~(1 << bit); + } + write(m_lastWrite); +} // writeBit + + +/** + * @brief Invert the bit values. + * Normally setting a pin's value to 1 means that a high signal is generated and a 0 means a low + * signal is generated. Setting the inversion to true, inverts that meaning. + * + * @param [in] value True if we wish to invert the signals and false otherwise. + */ +void PCF8575::setInvert(bool value) { + this->invert = value; +} // setInvert + + +/** + * @brief Initialize the PCF8575 device. + * + * @param [in] sdaPin The pin to use for the %I2C SDA functions. + * @param [in] clkPin The pin to use for the %I2C CLK functions. + */ +void PCF8575::init(gpio_num_t sdaPin, gpio_num_t clkPin) { + i2c->init(0, sdaPin, clkPin); +} // init diff --git a/cpp_utils/PCF8575.h b/cpp_utils/PCF8575.h new file mode 100644 index 00000000..0153124c --- /dev/null +++ b/cpp_utils/PCF8575.h @@ -0,0 +1,37 @@ +/* + * PCF8575.h + * + * Created on: Jan 27, 2018 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_PCF8575_H_ +#define COMPONENTS_CPP_UTILS_PCF8575_H_ +#include "I2C.h" + +/** + * @brief Encapsulate a %PCF8575 device. + * + * The %PCF8575 is a 16 bit %GPIO expander attached to %I2C. It can read and write 16 bits of data + * and hence has 16 pins that can be used for input or output. + * + * @see [PCF8575 home page](http://www.ti.com/product/PCF8575) + */ +class PCF8575 { +public: + PCF8575(uint8_t address); + virtual ~PCF8575(); + void init(gpio_num_t sdaPin = I2C::DEFAULT_SDA_PIN, gpio_num_t clkPin = I2C::DEFAULT_CLK_PIN); + uint16_t read(); + bool readBit(uint16_t bit); + void setInvert(bool value); + void write(uint16_t value); + void writeBit(uint16_t bit, bool value); + +private: + I2C* i2c; + uint16_t m_lastWrite; + bool invert = false; +}; + +#endif /* COMPONENTS_CPP_UTILS_PCF8575_H_ */ diff --git a/cpp_utils/PWM.cpp b/cpp_utils/PWM.cpp index 3155b42f..e67bd4fd 100644 --- a/cpp_utils/PWM.cpp +++ b/cpp_utils/PWM.cpp @@ -24,16 +24,16 @@ * * @param [in] gpioNum The GPIO pin to use for output. * @param [in] frequency The frequency of the period. - * @param [in] bitSize The size in bits of the timer. Allowed values are LEDC_TIMER_10_BIT, + * @param [in] dutyResolution The size in bits of the timer. Allowed values are LEDC_TIMER_10_BIT, * LEDC_TIMER_11_BIT, LEDC_TIMER_12_BIT, LEDC_TIMER_13_BIT, LEDC_TIMER_14_BIT, LEDC_TIMER_15_BIT. * @param [in] timer The timer to use. A value of LEDC_TIMER0, LEDC_TIMER1, LEDC_TIMER2 or LEDC_TIMER3. * @param [in] channel The channel to use. A value from LEDC_CHANNEL0 to LEDC_CHANNEL1. * @return N/A. */ -PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t bitSize, ledc_timer_t timer, ledc_channel_t channel) { +PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t dutyResolution, ledc_timer_t timer, ledc_channel_t channel) { ledc_timer_config_t timer_conf; - timer_conf.bit_num = bitSize; + timer_conf.duty_resolution = dutyResolution; timer_conf.freq_hz = frequency; timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE; timer_conf.timer_num = timer; @@ -48,9 +48,9 @@ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t bitSize, ledc_timer_t ledc_conf.timer_sel = timer; ESP_ERROR_CHECK(::ledc_channel_config(&ledc_conf)); - this->channel = channel; - this->timer = timer; - this->bitSize = bitSize; + this->m_channel = channel; + this->m_timer = timer; + this->m_dutyResolution = dutyResolution; } // PWM @@ -60,7 +60,7 @@ PWM::PWM(int gpioNum, uint32_t frequency, ledc_timer_bit_t bitSize, ledc_timer_t * @return The duty cycle value. */ uint32_t PWM::getDuty() { - return ::ledc_get_duty(LEDC_HIGH_SPEED_MODE, channel); + return ::ledc_get_duty(LEDC_HIGH_SPEED_MODE, m_channel); } // getDuty @@ -70,7 +70,7 @@ uint32_t PWM::getDuty() { * @return The frequency/period in Hz. */ uint32_t PWM::getFrequency() { - return ::ledc_get_freq(LEDC_HIGH_SPEED_MODE, timer); + return ::ledc_get_freq(LEDC_HIGH_SPEED_MODE, m_timer); } // getFrequency @@ -84,8 +84,8 @@ uint32_t PWM::getFrequency() { * @return N/A. */ void PWM::setDuty(uint32_t duty) { - ESP_ERROR_CHECK(::ledc_set_duty(LEDC_HIGH_SPEED_MODE, channel, duty)); - ESP_ERROR_CHECK(::ledc_update_duty(LEDC_HIGH_SPEED_MODE, channel)); + ESP_ERROR_CHECK(::ledc_set_duty(LEDC_HIGH_SPEED_MODE, m_channel, duty)); + ESP_ERROR_CHECK(::ledc_update_duty(LEDC_HIGH_SPEED_MODE, m_channel)); } // setDuty @@ -97,7 +97,7 @@ void PWM::setDuty(uint32_t duty) { */ void PWM::setDutyPercentage(uint8_t percent) { uint32_t max; - switch(bitSize) { + switch (m_dutyResolution) { case LEDC_TIMER_10_BIT: max = 1 << 10; break; @@ -120,15 +120,10 @@ void PWM::setDutyPercentage(uint8_t percent) { max = 1 << 10; break; } - if (percent > 100) { - percent = 100; - } + if (percent > 100) percent = 100; uint32_t value = max * percent / 100; - if (value >= max) { - value = max-1; - } + if (value >= max) value = max - 1; setDuty(value); - } // setDutyPercentage @@ -139,7 +134,7 @@ void PWM::setDutyPercentage(uint8_t percent) { * @return N/A. */ void PWM::setFrequency(uint32_t freq) { - ESP_ERROR_CHECK(::ledc_set_freq(LEDC_HIGH_SPEED_MODE, timer, freq)); + ESP_ERROR_CHECK(::ledc_set_freq(LEDC_HIGH_SPEED_MODE, m_timer, freq)); } // setFrequency @@ -150,5 +145,5 @@ void PWM::setFrequency(uint32_t freq) { * @return N/A. */ void PWM::stop(bool idleLevel) { - ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, channel, idleLevel)); + ESP_ERROR_CHECK(::ledc_stop(LEDC_HIGH_SPEED_MODE, m_channel, idleLevel ? 1 : 0)); } // stop diff --git a/cpp_utils/PWM.h b/cpp_utils/PWM.h index a048b81b..8cc1ce39 100644 --- a/cpp_utils/PWM.h +++ b/cpp_utils/PWM.h @@ -22,21 +22,23 @@ class PWM { public: PWM( int gpioNum, - uint32_t frequency = 100, + uint32_t frequency = 100, ledc_timer_bit_t bitSize = LEDC_TIMER_10_BIT, - ledc_timer_t timer = LEDC_TIMER_0, - ledc_channel_t channel = LEDC_CHANNEL_0); + ledc_timer_t timer = LEDC_TIMER_0, + ledc_channel_t channel = LEDC_CHANNEL_0); uint32_t getDuty(); uint32_t getFrequency(); - void setDuty(uint32_t duty); - void setDutyPercentage(uint8_t percent); - void setFrequency(uint32_t freq); - void stop(bool idleLevel=false); + void setDuty(uint32_t duty); + void setDutyPercentage(uint8_t percent); + void setFrequency(uint32_t freq); + void stop(bool idleLevel = false); + private: - ledc_channel_t channel; - ledc_timer_t timer; - ledc_timer_bit_t bitSize; // Bit size of timer. + ledc_channel_t m_channel; + ledc_timer_t m_timer; + ledc_timer_bit_t m_dutyResolution; // Bit size of timer. + }; #endif /* COMPONENTS_CPP_UTILS_PWM_H_ */ diff --git a/cpp_utils/PubSubClient.cpp b/cpp_utils/PubSubClient.cpp new file mode 100644 index 00000000..ae95bb33 --- /dev/null +++ b/cpp_utils/PubSubClient.cpp @@ -0,0 +1,852 @@ +/* + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net + + edit by marcel.seerig + */ +#include "esp_log.h" + +#include "PubSubClient.h" +#include "Task.h" +#include "FreeRTOS.h" +#include "FreeRTOSTimer.h" + +#include "sdkconfig.h" + +static const char* TAG = "PubSubClient"; + +#define pgm_read_byte_near(x) *(x) + +/** + * @brief A task that will handle the PubSubClient. + * + * This Task is started, when we have a valid connection. + * If the connection breaks, we stop this Task. + */ +class PubSubClientTask: public Task { +public: + PubSubClientTask(std::string name) : + Task(name, 16 * 1024) { + taskName = name; + }; + +private: + std::string taskName; + /** + * @brief Loop over the PubSubClient. + * @param [in] data A pointer to an instance of the PubSubClient. + */ + void run(void* data) { + PubSubClient* pPubSubClient = (PubSubClient*) data; + ESP_LOGD("PubSubClientTask", "PubSubClientTask Task started!"); + + while (true) { + if (pPubSubClient->connected()) { + uint16_t len = pPubSubClient->readPacket(); + + if (len > 0) { // if there was data + + pPubSubClient->keepAliveTimer->reset(0); //lastInActivity = t; + + mqtt_message* msg = new mqtt_message; + pPubSubClient->parseData(msg, len); + + //pPubSubClient->dumpData(msg); + ESP_LOGD(TAG, "Message type (%s)!", pPubSubClient->messageType_toString(msg->type).c_str()); + + if (msg->type == PUBLISH) { + if (pPubSubClient->callback) { + if (msg->qos == QOS0) { + pPubSubClient->callback(msg->topic, msg->payload); + } else if (msg->qos == QOS1) { + pPubSubClient->callback(msg->topic, msg->payload); + + pPubSubClient->buffer[0] = PUBACK; + pPubSubClient->buffer[1] = 2; + pPubSubClient->buffer[2] = (msg->msgId >> 8); + pPubSubClient->buffer[3] = (msg->msgId & 0xFF); + + int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 4); + if (rc < 0) pPubSubClient->_state = CONNECTION_LOST; + + pPubSubClient->keepAliveTimer->reset(0); //lastOutActivity = t; + } else if(msg->qos == QOS2) { + ESP_LOGD(TAG, "QOS2 is not supported!"); + } else { + ESP_LOGD(TAG, "QOS-Level unkonwon yet!"); + } + } + } else if (msg->type == PINGREQ) { + pPubSubClient->buffer[0] = PINGRESP; + pPubSubClient->buffer[1] = 0; + int rc = pPubSubClient->_client->send(pPubSubClient->buffer, 2); + if (rc < 0) pPubSubClient->_state = CONNECTION_LOST; + + } else if (msg->type == PINGRESP) { + pPubSubClient->PING_outstanding = false; + } else if (msg->type == SUBACK) { + pPubSubClient->SUBACK_outstanding = false; + pPubSubClient->timeoutTimer->stop(0); + } else if (msg->type == UNSUBACK) { + pPubSubClient->UNSUBACK_Outstanding = false; + pPubSubClient->timeoutTimer->stop(0); + } + + delete(msg); + } + } + } // while (true) + } // run +}; // PubSubClientTask + + +PubSubClient::PubSubClient() { + setup(); + this->_state = DISCONNECTED; + this->_client = new Socket; + setCallback(NULL); +} + + +PubSubClient::PubSubClient(Socket& client) { + setup(); + this->_state = DISCONNECTED; + setClient(client); +} + + +PubSubClient::PubSubClient(std::string addr, uint16_t port) { + setup(); + this->_state = DISCONNECTED; + this->_client = new Socket; + setServer(addr, port); +} + + +PubSubClient::PubSubClient(std::string addr, uint16_t port, Socket& client) { + setup(); + this->_state = DISCONNECTED; + setServer(addr, port); + setClient(client); +} + + +PubSubClient::PubSubClient(std::string addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Socket& client) { + setup(); + this->_state = DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); +} + + +PubSubClient::~PubSubClient() { + _client->close(); + keepAliveTimer->stop(0); + timeoutTimer->stop(0); + m_task->stop(); + delete (_client); + delete (keepAliveTimer); + delete (timeoutTimer); + delete (m_task); +} + + +/** + * @brief This is a Timer called routine mapping routine, which calls + * the PubSubClient member function keepAliveChecker. + * @param The FreeRTOSTimer root instance for this callback function. + * @return N/A. + */ +void keepAliveTimerMapper(FreeRTOSTimer* pTimer) { + PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); + m_pubSubClient->keepAliveChecker(); +} //keepAliveChecker + + +/** + * @brief This is a Timer called routine mapping routine, which calls + * the PubSubClient member function timeoutChecker. + * @param The FreeRTOSTimer root instance for this callback function. + * @return N/A. + */ +void timeoutTimerMapper(FreeRTOSTimer* pTimer) { + PubSubClient* m_pubSubClient = (PubSubClient*) pTimer->getData(); + m_pubSubClient->timeoutChecker(); +} //keepAliveChecker + + +/** + * @brief This is a internal setup routine for the PubSubClient. + * @param N/A. + * @return N/A. + */ +void PubSubClient::setup() { + PING_outstanding = false; + SUBACK_outstanding = false; + UNSUBACK_Outstanding = false; + + keepAliveTimer = new FreeRTOSTimer((char*) "keepAliveTimer", + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, pdTRUE, this, + keepAliveTimerMapper); + timeoutTimer = new FreeRTOSTimer((char*) "timeoutTimer", + (MQTT_KEEPALIVE * 1000) / portTICK_PERIOD_MS, pdTRUE, this, + timeoutTimerMapper); + m_task = new PubSubClientTask("PubSubClientTask"); +} // setup + +/** + * @brief This is a Timer called routine, which checks the PING_outstanding flag. + * This flag is set in this function. We send a Keep alive message to the + * server here. If there is a data in, or output, or we receive the MQTT + * ping request, the flag will be set to false by other functions. + * This function is called every MQTT_KEEPALIVE interval. If the flag is + * still true, we have a error with the connection. + * @param N/A. + * @return N/A. + */ + +void PubSubClient::keepAliveChecker() { + if (PING_outstanding && connected()) { + _state = CONNECTION_TIMEOUT; + //_client->close(); + ESP_LOGD(TAG, "KeepAlive TIMEOUT!"); + } else { + buffer[0] = PINGREQ; + buffer[1] = 0; + int rc = _client->send(buffer, 2); + if (rc < 0) _state = CONNECTION_LOST; + ESP_LOGD(TAG, "send KeepAlive REQUEST!"); + PING_outstanding = true; + } +} //keepAliveChecker + +/** + * @brief This is a Timer called routine, which is called, when we reach the timeout. + * Used is this function for all ACK commands, which comes over MQTT. Notice, + * that the KeepAlivePing has his own Timer, to prevent conflicts. Basically, if + * this function is called, we have detected a MQTT timeout! + * @param N/A. + * @return N/A. + */ +void PubSubClient::timeoutChecker() { + if (connected() && (SUBACK_outstanding || UNSUBACK_Outstanding)) { + _state = CONNECTION_TIMEOUT; + //_client->close(); + ESP_LOGD(TAG, "MQTT TIMEOUT!"); + } +} //keepAliveChecker + + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char* id) { + return connect(id, NULL, NULL, 0, 0, 0, 0); +} + + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] my user name. + * [in] my password. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char* id, const char* user, const char* pass) { + return connect(id, user, pass, 0, 0, 0, 0); +} + + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] last will: topic name. + * [in] last will: qos-level. + * [in] last will: as retained message. + * [in] last will: payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { + return connect(id, NULL, NULL, willTopic, willQos, willRetain, willMessage); +} + + +/** + * @brief Connect to a MQTT server. + * @param [in] Device id to identify this device. + * [in] my user name. + * [in] my password. + * [in] last will: topic name. + * [in] last will: qos-level. + * [in] last will: as retained message. + * [in] last will: payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage) { + _config.id = id; + + _config.user = user; + _config.pass = pass; + + _config.willTopic = willTopic; + _config.willQos = willQos; + _config.willRetain = willRetain; + _config.willMessage = willMessage; + + return connect(); +} + + +/** + * @brief Connect to a MQTT server with the with the previous settings. + * Note: do not call this function without settings, this will not work! + * For the very first connect process, use the connect function with parameters. + * @param N/A + * @return success (true), or no success (false). + */ +bool PubSubClient::connect() { + if (!connected()) { + ESP_LOGD(TAG, "Connect to mqtt server..."); + ESP_LOGD(TAG, "ip: %s port: %d", _config.ip.c_str(), _config.port); + int result = _client->connect((char *)_config.ip.c_str(), _config.port); + + if (result == 0) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = { 0x00, 0x06, 'M', 'Q', 'I', 's', 'd', 'p', MQTT_VERSION }; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = { 0x00, 0x04, 'M', 'Q', 'T', 'T', MQTT_VERSION }; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (unsigned int j = 0; j < MQTT_HEADER_VERSION_LENGTH; j++) { + buffer[length++] = d[j]; + } + + uint8_t v; + if (_config.willTopic) { + v = 0x06 | (_config.willQos << 3) | (_config.willRetain << 5); + } else { + v = 0x02; + } + + if (_config.user != NULL) { + v = v | 0x80; + + if (_config.pass != NULL) { + v = v | (0x80 >> 1); + } + } + + buffer[length++] = v; + + buffer[length++] = ((MQTT_KEEPALIVE) >> 8); + buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF); + length = writeString(_config.id, buffer, length); + if (_config.willTopic) { + length = writeString(_config.willTopic, buffer, length); + length = writeString(_config.willMessage, buffer, length); + } + + if (_config.user != NULL) { + length = writeString(_config.user, buffer, length); + if (_config.pass != NULL) { + length = writeString(_config.pass, buffer, length); + } + } + + write(CONNECT, buffer, length - 5); + + // start keepAliveTimer in 1ms... + keepAliveTimer->start(0); //lastInActivity = lastOutActivity = millis(); + + readPacket(); + uint8_t type = buffer[0] & 0xF0; + + if (type == CONNACK) { + ESP_LOGD(TAG, "Connected to mqtt server!"); + + keepAliveTimer->reset(0); //lastInActivity = millis(); + PING_outstanding = false; + _state = CONNECTED; + + m_task->start(this); + return true; + } else { + _state = (mqtt_state) buffer[3]; + ESP_LOGD(TAG, "Error: %d", _state); + } + + } else { + keepAliveTimer->stop(0); + _state = CONNECT_FAILED; + } + return false; + } + return true; +} + + +/** + * @brief Receiving a MQTT packet and store it in the buffer to parse it. + * @param N/A. + * @return Number of received bytes. + */ +size_t PubSubClient::readPacket() { + size_t res = _client->receive(buffer, MQTT_MAX_PACKET_SIZE); + + if (res > MQTT_MAX_PACKET_SIZE) { + res = 0; // This will cause the packet to be ignored. + } + + return (uint16_t) res; +} + + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const char* payload) { + return publish(topic, (const uint8_t*) payload, strlen(payload), false); +} + + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] is this a retained message (true/false) + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const char* payload, bool retained) { + return publish(topic, (const uint8_t*) payload, strlen(payload), retained); +} + + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] length of the message + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + + +/** + * @brief Publish a MQTT message. + * @param [in] my topic. + * [in] my payload. + * [in] length of the message + * [in] is this a retained message (true/false) + * @return success (true), or no success (false). + */ +bool PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, bool retained) { + if (connected()) { + if (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength) { + // Too long + return false; + } + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + length = writeString(topic, buffer, length); + uint16_t i; + for (i = 0; i < plength; i++) { + buffer[length++] = payload[i]; + } + uint8_t header = PUBLISH; + if (retained) { + header |= 1; + } + return write(header, buffer, length - 5); + } + return false; +} + + +//bool PubSubClient::publish_P(const char* topic, const uint8_t* payload, +// unsigned int plength, bool retained) { +// uint8_t llen = 0; +// uint8_t digit; +// unsigned int rc = 0; +// uint16_t tlen; +// unsigned int pos = 0; +// unsigned int i; +// uint8_t header; +// unsigned int len; +// +// if (!connected()) { +// return false; +// } +// +// tlen = strlen(topic); +// +// header = PUBLISH; +// if (retained) { +// header |= 1; +// } +// buffer[pos++] = header; +// len = plength + 2 + tlen; +// do { +// digit = len % 128; +// len = len / 128; +// if (len > 0) { +// digit |= 0x80; +// } +// buffer[pos++] = digit; +// llen++; +// } while (len > 0); +// +// pos = writeString(topic, buffer, pos); +// +// rc += _client->send(buffer, pos); +// +// for (i = 0; i < plength; i++) { +// //rc += _client->send((char)pgm_read_byte_near(payload + i)); +// //rc += _client->send((uint8_t*) pgm_read_byte_near(payload + i), 1); +// } +// +// keepAliveTimer->reset(0); //lastOutActivity = millis(); +// +// return rc == tlen + 4 + plength; +//} + + +/** + * @brief Send a MQTT message over socket. + * @param [in] MQTT header. + * [in] Message buffer. + * [in] total length. + * @return success (true), or no success (false). + */ +bool PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + int rc; + uint16_t len = length; + do { + digit = len % 128; + len = len / 128; + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while (len > 0); + + buf[4 - llen] = header; + for (int i = 0; i < llen; i++) { + buf[5 - llen + i] = lenBuf[i]; + } + +//#ifdef MQTT_MAX_TRANSFER_SIZE +// uint8_t* writeBuf = buf + (4 - llen); +// uint16_t bytesRemaining = length + 1 + llen; //Match the length type +// uint8_t bytesToWrite; +// bool result = true; +// while ((bytesRemaining > 0) && result) { +// bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE) ? MQTT_MAX_TRANSFER_SIZE : bytesRemaining; +// rc = _client->write(writeBuf, bytesToWrite); +// result = (rc == bytesToWrite); +// bytesRemaining -= rc; +// writeBuf += rc; +// } +// return result; +//#else + rc = _client->send(buf + (4 - llen), length + 1 + llen); + if(rc < 0) _state = CONNECTION_LOST; + keepAliveTimer->reset(0); //lastOutActivity = millis(); + return (rc == 1 + llen + length); +//#endif +} + + +/** + * @brief Subscribe a MQTT topic. + * @param [in] my topic + * [in] qos of subscription + * @return request transmitted with success (true), or no success (false). + */ +bool PubSubClient::subscribe(const char* topic, bool ack) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) return false; // Too long + + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer, length); + buffer[length++] = QOS1; + + if (write(SUBSCRIBE | QOS1, buffer, length - 5)) { + SUBACK_outstanding = true; + if (ack) timeoutTimer->start(0); + return true; + } + } + return false; +} + + +/** + * @brief Check the state of subscription. If there was received a subscription + * ACK, we return a true here. + * @return Is subscription validated with ACK (true/false) + */ +bool PubSubClient::isSubscribeDone() { + return !SUBACK_outstanding; +} + + +/** + * @brief Unsubscribe a MQTT topic. + * @param [in] my topic + * [in] qos of unsubscription + * @return request transmitted with success (true), or no success (false). + */ +bool PubSubClient::unsubscribe(const char* topic, bool ack) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) return false; // Too long + + if (connected()) { + uint16_t length = 5; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer, length); + + if (write(UNSUBSCRIBE | QOS1, buffer, length - 5)) { + UNSUBACK_Outstanding = true; + if (ack) timeoutTimer->start(0); + return true; + } + } + return false; +} + + +/** + * @brief Check the state of unsubscription. If there was received a unsubscription + * ACK, we return a true here. + * @return Is unsubscription validated with ACK (true/false) + */ +bool PubSubClient::isUnsubscribeDone() { + return !UNSUBACK_Outstanding; +} + + +/** + * @brief Disconnect form MQTT server and close the socket. + * @return N/A. + */ +void PubSubClient::disconnect() { + buffer[0] = DISCONNECT; + buffer[1] = 0; + _client->send(buffer, 2); + _state = DISCONNECTED; + _client->close(); + keepAliveTimer->stop(0); //lastInActivity = lastOutActivity = millis(); + timeoutTimer->stop(0); +} + + +/** + * @brief calculation help to send a string. + */ +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp) { + buf[pos++] = *idp++; + i++; + } + buf[pos - i - 2] = (i >> 8); + buf[pos - i - 1] = (i & 0xFF); + return pos; +} + + +/** + * @brief Check the connection to the MQTT server. + * @return connected (true/false) + */ +bool PubSubClient::connected() { + if (this->_state != CONNECTED) return false; + bool rc = true; + + if (_client == nullptr) { + rc = false; + } else if (!_client->isValid()) { + rc = false; + + this->_state = CONNECTION_LOST; + + if (_client->isValid()) _client->close(); + keepAliveTimer->stop(0); + timeoutTimer->stop(0); + } + return rc; +} + + +/** + * @brief Set server ip and Port of my MQTT server. + * @param [in] ip of the distant MQTT server. + * [in] the port of the distant MQTT port. + * @return My instance. + */ +PubSubClient& PubSubClient::setServer(std::string ip, uint16_t port) { + _config.ip = ip; + _config.port = port; + return *this; +} + + +/** + * @brief Set the callback function for incoming data. + * @param [in] callback function + * @return My instance. + */ +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + + +/** + * @brief Set the socket, which we want to use for our MQTT communication. + * @param [in] the new socket instance + * @return My instance. + */ +PubSubClient& PubSubClient::setClient(Socket& client) { + this->_client = &client; + return *this; +} + + +/** + * @brief Get the current MYTT state form the instance. + * @param N/A. + * @return current MQTT state. + */ +int PubSubClient::state() { + return this->_state; +} + + +/** + * @brief Parsing the received data in to the internal message struct. + */ +void PubSubClient::parseData(mqtt_message* msg, uint16_t len) { + /********* Parse Fixed header *********/ + msg->type = buffer[0] & 0xF0; + + /* read DUP-Flag */ + if (msg->type == PUBLISH) { + msg->dup = (bool) (buffer[0] & 0x18) >> 3; + } + + /* read QoS-Level */ + if(msg->type == PUBLISH) { + msg->qos = (buffer[0] & 0x06); + } + + /* read RETAIN-Frag */ + if(msg->type == PUBLISH) { + msg->retained = (bool) (buffer[0] & 0x01); + } + + uint8_t remainingLength = buffer[1]; + + /********* Parse Variable header *********/ + int pos = 2; + + /* read topic name */ + if (msg->type == PUBLISH) { + uint16_t topicLen = (buffer[2] << 8) + buffer[3]; + + msg->topic = ""; + for (int i = 4; i < (topicLen + 4); i++) { + msg->topic += (char) buffer[i]; + pos++; + } + } + + /* read Message ID */ + if (msg->type == PUBLISH || msg->type == PUBACK || msg->type == PUBREC || msg->type == PUBCOMP || msg->type == SUBACK || msg->type == UNSUBACK) { + msg->msgId = (buffer[pos] << 8) + (buffer[pos + 1]); + pos += 2; + } + + /********* read Payload *********/ + if (msg->type == PUBLISH) { + msg->payload = ""; + for (int i = pos; i < remainingLength + 2; i++) { + msg->payload += (char) buffer[i]; + } + } +} + + +/** + * @brief Dump the message struct. + */ +void PubSubClient::dumpData(mqtt_message* msg) { + ESP_LOGD(TAG, "mqtt_message_type: %s", messageType_toString(msg->type).c_str()); + ESP_LOGD(TAG, "mqtt_qos: %d", msg->qos); + ESP_LOGD(TAG, "retained: %d", msg->retained); + ESP_LOGD(TAG, "dup: %d", msg->dup); + ESP_LOGD(TAG, "topic: %s", msg->topic.c_str()); + ESP_LOGD(TAG, "payload: %s", msg->payload.c_str()); + ESP_LOGD(TAG, "msgId: %d", msg->msgId); +} + + +/** + * @brief Convert the MQTT message type to string. + * @param [in] message type byte. + * @return message type as std::string. + */ +std::string PubSubClient::messageType_toString(uint8_t type) { + std::string str = "Not in list!"; + switch (type) { + case CONNECT : str = "CONNECT"; break; + case CONNACK : str = "CONNACK"; break; + case PUBLISH : str = "PUBLISH"; break; + case PUBACK : str = "PUBACK"; break; + case PUBREC : str = "PUBREC"; break; + case PUBREL : str = "PUBREL"; break; + case PUBCOMP : str = "PUBCOMP"; break; + case SUBSCRIBE : str = "SUBSCRIBE"; break; + case SUBACK : str = "SUBACK"; break; + case UNSUBSCRIBE: str = "UNSUBSCRIBE"; break; + case UNSUBACK : str = "UNSUBACK"; break; + case PINGREQ : str = "PINGREQ"; break; + case PINGRESP : str = "PINGRESP"; break; + case DISCONNECT : str = "DISCONNECT"; break; + case Reserved : str = "Reserved"; break; + default : break; + } + return str; +} diff --git a/cpp_utils/PubSubClient.h b/cpp_utils/PubSubClient.h new file mode 100644 index 00000000..aad1103e --- /dev/null +++ b/cpp_utils/PubSubClient.h @@ -0,0 +1,172 @@ +/* + PubSubClient.h - A simple client for MQTT. + Nick O'Leary + http://knolleary.net + + edit by marcel.seerig +*/ + +#ifndef PubSubClient_h +#define PubSubClient_h + +#include +#include "Socket.h" +#include "FreeRTOSTimer.h" + +#define MQTT_VERSION_3_1 3 +#define MQTT_VERSION_3_1_1 4 + +// MQTT_VERSION : Pick the version +//#define MQTT_VERSION MQTT_VERSION_3_1 +#ifndef MQTT_VERSION +#define MQTT_VERSION MQTT_VERSION_3_1_1 +#endif + +// MQTT_MAX_PACKET_SIZE : Maximum packet size +#ifndef MQTT_MAX_PACKET_SIZE +#define MQTT_MAX_PACKET_SIZE 128 +#endif + +// MQTT_KEEPALIVE : keepAlive interval in Seconds +#ifndef MQTT_KEEPALIVE +#define MQTT_KEEPALIVE 15 +#endif + +// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds +#ifndef MQTT_SOCKET_TIMEOUT +#define MQTT_SOCKET_TIMEOUT 15 +#endif + +// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client +// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to +// pass the entire MQTT packet in each write call. +//#define MQTT_MAX_TRANSFER_SIZE 80 + +// Possible values for client.state() + +struct mqtt_InitTypeDef{ + std::string ip; + uint16_t port; + + const char* user; + const char* pass; + + const char* id; + const char* willTopic; + uint8_t willQos; + bool willRetain; + const char* willMessage; +}; + +typedef enum { + CONNECTION_TIMEOUT = -4, + CONNECTION_LOST = -3, + CONNECT_FAILED = -2, + DISCONNECTED = -1, + CONNECTED = 0, + CONNECT_BAD_PROTOCOL = 1, + CONNECT_BAD_CLIENT_ID = 2, + CONNECT_UNAVAILABLE = 3, + CONNECT_BAD_CREDENTIALS = 4, + CONNECT_UNAUTHORIZED = 5, +} mqtt_state; + +typedef enum { + CONNECT = 1 << 4, // Client request to connect to Server + CONNACK = 2 << 4, // Connect Acknowledgment + PUBLISH = 3 << 4, // Publish message + PUBACK = 4 << 4, // Publish Acknowledgment + PUBREC = 5 << 4, // Publish Received (assured delivery part 1) + PUBREL = 6 << 4, // Publish Release (assured delivery part 2) + PUBCOMP = 7 << 4, // Publish Complete (assured delivery part 3) + SUBSCRIBE = 8 << 4, // Client Subscribe request + SUBACK = 9 << 4, // Subscribe Acknowledgment + UNSUBSCRIBE = 10 << 4, // Client Unsubscribe request + UNSUBACK = 11 << 4, // Unsubscribe Acknowledgment + PINGREQ = 12 << 4, // PING Request + PINGRESP = 13 << 4, // PING Response + DISCONNECT = 14 << 4, // Client is Disconnecting + Reserved = 15 << 4, // Reserved +} mqtt_message_type; + +typedef enum { + QOS0 = (0 << 1), + QOS1 = (1 << 1), + QOS2 = (2 << 1), +} mqtt_qos; + +struct mqtt_message { + uint8_t type; + uint8_t qos; + bool retained; + bool dup; + std::string topic; + std::string payload; + uint16_t msgId; +}; + +#define MQTT_CALLBACK_SIGNATURE void (*callback) (std::string, std::string) + +class PubSubClientTask; + +class PubSubClient { +public: + PubSubClient(); + PubSubClient(Socket& client); + PubSubClient(std::string ip, uint16_t port); + PubSubClient(std::string ip, uint16_t port, Socket& client); + PubSubClient(std::string ip, uint16_t port, MQTT_CALLBACK_SIGNATURE,Socket& client); + ~PubSubClient(); + + PubSubClient& setServer(std::string ip, uint16_t port); + PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE); + PubSubClient& setClient(Socket& client); + + bool connect(const char* id); + bool connect(const char* id, const char* user, const char* pass); + bool connect(const char* id, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + bool connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, bool willRetain, const char* willMessage); + bool connect(); + void disconnect(); + bool publish(const char* topic, const char* payload); + bool publish(const char* topic, const char* payload, bool retained); + bool publish(const char* topic, const uint8_t* payload, unsigned int plength); + bool publish(const char* topic, const uint8_t* payload, unsigned int plength, bool retained); + //bool publish_P(const char* topic, const uint8_t * payload, unsigned int plength, bool retained); + + bool subscribe(const char* topic, bool ack = false); + bool unsubscribe(const char* topic, bool ack = false); + bool isSubscribeDone(); + bool isUnsubscribeDone(); + + bool connected(); + int state(); + void keepAliveChecker(); + void timeoutChecker(); + +private: + friend class PubSubClientTask; + PubSubClientTask* m_task; + Socket* _client; + mqtt_InitTypeDef _config; + mqtt_state _state; + uint8_t buffer[MQTT_MAX_PACKET_SIZE]; + uint16_t nextMsgId; + bool PING_outstanding; + bool SUBACK_outstanding; + bool UNSUBACK_Outstanding; + FreeRTOSTimer* keepAliveTimer; + FreeRTOSTimer* timeoutTimer; + + MQTT_CALLBACK_SIGNATURE; + void setup(); + size_t readPacket(); + bool write(uint8_t header, uint8_t* buf, uint16_t length); + uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos); + void parseData(mqtt_message* msg, uint16_t len); + void dumpData(mqtt_message* msg); + std::string messageType_toString(uint8_t type); + +}; + +#endif diff --git a/cpp_utils/RESTClient.cpp b/cpp_utils/RESTClient.cpp index 5cc259c9..a72f80b5 100644 --- a/cpp_utils/RESTClient.cpp +++ b/cpp_utils/RESTClient.cpp @@ -4,7 +4,9 @@ * Created on: Mar 12, 2017 * Author: kolban */ -#if defined(ESP_HAVE_CURL) + +#include "sdkconfig.h" +#if defined(CONFIG_LIBCURL_PRESENT) #define _GLIBCXX_USE_C99 // Needed for std::string -> to_string inclusion. @@ -15,7 +17,7 @@ #include "RESTClient.h" -static char tag[] = "RESTClient"; +static const char* LOG_TAG = "RESTClient"; RESTClient::RESTClient() { @@ -34,13 +36,16 @@ RESTClient::~RESTClient() { /** * @brief Perform an HTTP GET request. */ -void RESTClient::get() { +long RESTClient::get() { + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_HTTPGET, 1); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { - ESP_LOGE(tag, "get(): %s", getErrorMessage().c_str()); + ESP_LOGE(LOG_TAG, "get(): %s", getErrorMessage().c_str()); } + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code); // Added return response_code 2018_4_12 + return response_code; // Added return response_code 2018_4_12 } // get @@ -50,13 +55,16 @@ void RESTClient::get() { * @param [in] body The body of the payload to send with the post request. * */ -void RESTClient::post(std::string body) { +long RESTClient::post(std::string body) { + long response_code; // Added return response_code 2018_4_12 prepForCall(); ::curl_easy_setopt(m_curlHandle, CURLOPT_POSTFIELDS, body.c_str()); int rc = ::curl_easy_perform(m_curlHandle); if (rc != CURLE_OK) { - ESP_LOGE(tag, "post(): %s", getErrorMessage().c_str()); + ESP_LOGE(LOG_TAG, "post(): %s", getErrorMessage().c_str()); } + curl_easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &response_code);// Added return response_code 2018_4_12 + return response_code;// Added return response_code 2018_4_12 } // post @@ -82,10 +90,10 @@ std::string RESTClient::getErrorMessage() { * * @return The number of bytes of data processed. */ -size_t RESTClient::handleData(void *buffer, size_t size, size_t nmemb, void *userp) { +size_t RESTClient::handleData(void* buffer, size_t size, size_t nmemb, void* userp) { //printf("handleData: size: %d, num: %d\n", size, nmemb); - RESTClient *pClient = (RESTClient *)userp; - pClient->m_response.append((const char *)buffer, size*nmemb); + RESTClient* pClient = (RESTClient*) userp; + pClient->m_response.append((const char*) buffer, size * nmemb); return size * nmemb; } // handleData @@ -131,7 +139,7 @@ void RESTClient::prepForCall() { } // prepForCall -RESTTimings::RESTTimings(RESTClient *client) { +RESTTimings::RESTTimings(RESTClient* client) { this->client = client; } @@ -163,4 +171,4 @@ std::string RESTTimings::toString() { "\nTotal: " + std::to_string(m_total); return ret; } // toString -#endif // ESP_HAVE_CURL +#endif // CONFIG_LIBCURL_PRESENT diff --git a/cpp_utils/RESTClient.h b/cpp_utils/RESTClient.h index d9c4e04e..f44612ac 100644 --- a/cpp_utils/RESTClient.h +++ b/cpp_utils/RESTClient.h @@ -7,7 +7,8 @@ #ifndef MAIN_RESTCLIENT_H_ #define MAIN_RESTCLIENT_H_ -#if defined(ESP_HAVE_CURL) +#include "sdkconfig.h" +#if defined(CONFIG_LIBCURL_PRESENT) #include #include @@ -18,9 +19,10 @@ class RESTClient; */ class RESTTimings { public: - RESTTimings(RESTClient *client); + RESTTimings(RESTClient* client); void refresh(); std::string toString(); + private: double m_namelookup = 0; double m_connect = 0; @@ -28,7 +30,8 @@ class RESTTimings { double m_pretransfer = 0; double m_starttransfer = 0; double m_total = 0; - RESTClient *client = nullptr; + RESTClient* client = nullptr; + }; /** @@ -67,7 +70,7 @@ class RESTClient { RESTClient(); virtual ~RESTClient(); void addHeader(std::string name, std::string value); - void get(); + long get(); // Added return response_code 2018_4_12 std::string getErrorMessage(); /** * @brief Get the response payload data from the last REST call. @@ -85,7 +88,7 @@ class RESTClient { return m_timings; } - void post(std::string body); + long post(std::string body); // Added return response_code 2018_4_12 /** * @brief Set the URL for the target. @@ -106,19 +109,18 @@ class RESTClient { m_verbose = value; }; - - private: - CURL *m_curlHandle; + CURL* m_curlHandle; std::string m_url; char m_errbuf[CURL_ERROR_SIZE]; - struct curl_slist *m_headers = nullptr; + struct curl_slist* m_headers = nullptr; bool m_verbose = false; friend class RESTTimings; - RESTTimings *m_timings; + RESTTimings* m_timings; std::string m_response; - static size_t handleData(void *buffer, size_t size, size_t nmemb, void *userp); + static size_t handleData(void* buffer, size_t size, size_t nmemb, void* userp); void prepForCall(); + }; -#endif /* ESP_HAVE_CURL */ +#endif /* CONFIG_LIBCURL_PRESENT */ #endif /* MAIN_RESTCLIENT_H_ */ diff --git a/cpp_utils/RMT.cpp b/cpp_utils/RMT.cpp index 750da1a6..9f9a5950 100644 --- a/cpp_utils/RMT.cpp +++ b/cpp_utils/RMT.cpp @@ -8,6 +8,7 @@ #include #include "RMT.h" + //static char tag[] = "RMT"; /** * @brief Create a class instance. @@ -22,7 +23,7 @@ RMT::RMT(gpio_num_t pin, rmt_channel_t channel) { config.rmt_mode = RMT_MODE_TX; config.channel = channel; config.gpio_num = pin; - config.mem_block_num = 8-this->channel; + config.mem_block_num = 8 - this->channel; config.clk_div = 8; config.tx_config.loop_en = 0; config.tx_config.carrier_en = 0; @@ -32,7 +33,6 @@ RMT::RMT(gpio_num_t pin, rmt_channel_t channel) { config.tx_config.carrier_level = (rmt_carrier_level_t)1; config.tx_config.carrier_duty_percent = 50; - ESP_ERROR_CHECK(rmt_config(&config)); ESP_ERROR_CHECK(rmt_driver_install(this->channel, 0, 0)); } @@ -45,6 +45,7 @@ RMT::~RMT() { ESP_ERROR_CHECK(::rmt_driver_uninstall(this->channel)); } + /** * @brief Start receiving. */ @@ -52,6 +53,7 @@ void RMT::rxStart() { ESP_ERROR_CHECK(::rmt_rx_start(this->channel, true)); } + /** * @brief Stop receiving. */ @@ -59,6 +61,7 @@ void RMT::rxStop() { ESP_ERROR_CHECK(::rmt_rx_stop(this->channel)); } + /** * @brief Start transmitting. */ @@ -66,6 +69,7 @@ void RMT::txStart() { ESP_ERROR_CHECK(::rmt_tx_start(this->channel, true)); } + /** * @brief Stop transmitting. */ @@ -73,6 +77,7 @@ void RMT::txStop() { ESP_ERROR_CHECK(::rmt_tx_stop(this->channel)); } + /** * @brief Write the items out through the RMT. * @@ -81,7 +86,7 @@ void RMT::txStop() { * is cleared. */ void RMT::write() { - add(false,0); + add(false, 0); ESP_ERROR_CHECK(::rmt_write_items(this->channel, &items[0], items.size(), true)); clear(); } @@ -100,12 +105,13 @@ void RMT::add(bool level, uint32_t duration) { item.duration0 = duration; items.push_back(item); } else { - items.at(bitCount/2).level1 = level; - items.at(bitCount/2).duration1 = duration; + items.at(bitCount / 2).level1 = level; + items.at(bitCount / 2).duration1 = duration; } bitCount++; } + /** * @brief Clear any previously written level/duration pairs that have not been sent. */ diff --git a/cpp_utils/RMT.h b/cpp_utils/RMT.h index 90e59007..8c079f80 100644 --- a/cpp_utils/RMT.h +++ b/cpp_utils/RMT.h @@ -15,7 +15,7 @@ */ class RMT { public: - RMT(gpio_num_t pin, rmt_channel_t channel=RMT_CHANNEL_0); + RMT(gpio_num_t pin, rmt_channel_t channel = RMT_CHANNEL_0); virtual ~RMT(); void add(bool level, uint32_t duration); void clear(); @@ -25,11 +25,11 @@ class RMT { void txStop(); void write(); - private: rmt_channel_t channel; std::vector items; int bitCount = 0; + }; #endif /* COMPONENTS_CPP_UTILS_RMT_H_ */ diff --git a/cpp_utils/SOC.cpp b/cpp_utils/SOC.cpp index 2f4d3a9a..fa12d4e6 100644 --- a/cpp_utils/SOC.cpp +++ b/cpp_utils/SOC.cpp @@ -34,9 +34,9 @@ void SOC::I2S::dump() { I2S0.clkm_conf.clka_en); uint32_t clockSpeed; if (I2S0.clkm_conf.clkm_div_a == 0) { - clockSpeed = 160000000/I2S0.clkm_conf.clkm_div_num; + clockSpeed = 160000000 / I2S0.clkm_conf.clkm_div_num; } else { - clockSpeed = 160000000/(I2S0.clkm_conf.clkm_div_num + I2S0.clkm_conf.clkm_div_b/I2S0.clkm_conf.clkm_div_a); + clockSpeed = 160000000 / (I2S0.clkm_conf.clkm_div_num + I2S0.clkm_conf.clkm_div_b / I2S0.clkm_conf.clkm_div_a); } printf("Clock speed: %d\n", clockSpeed); printf("\n"); @@ -44,8 +44,8 @@ void SOC::I2S::dump() { printf("I2S_CONF_REG\n"); printf("------------\n"); printf("tx_slave_mod: %s, rx_slave_mod: %s, rx_msb_right: %d, rx_right_first: %d\n", - I2S0.conf.tx_slave_mod==0?"Master":"Slave", - I2S0.conf.rx_slave_mod==0?"Master":"Slave", + (I2S0.conf.tx_slave_mod == 0) ? "Master" : "Slave", + (I2S0.conf.rx_slave_mod == 0) ? "Master" : "Slave", I2S0.conf.rx_msb_right, I2S0.conf.rx_right_first); printf("\n"); diff --git a/cpp_utils/SPI.cpp b/cpp_utils/SPI.cpp index 3b644002..1312e706 100644 --- a/cpp_utils/SPI.cpp +++ b/cpp_utils/SPI.cpp @@ -12,7 +12,7 @@ //#define DEBUG 1 -static char tag[] = "SPI"; +static const char* LOG_TAG = "SPI"; /** * @brief Construct an instance of the class. * @@ -23,14 +23,15 @@ SPI::SPI() { m_host = HSPI_HOST; } + /** * @brief Class instance destructor. */ SPI::~SPI() { - ESP_LOGI(tag, "... Removing device."); + ESP_LOGI(LOG_TAG, "... Removing device."); ESP_ERROR_CHECK(::spi_bus_remove_device(m_handle)); - ESP_LOGI(tag, "... Freeing bus."); + ESP_LOGI(LOG_TAG, "... Freeing bus."); ESP_ERROR_CHECK(::spi_bus_free(m_host)); } @@ -44,7 +45,7 @@ SPI::~SPI() { * @return N/A. */ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { - ESP_LOGD(tag, "init: mosi=%d, miso=%d, clk=%d, cs=%d", mosiPin, misoPin, clkPin, csPin); + ESP_LOGD(LOG_TAG, "init: mosi=%d, miso=%d, clk=%d, cs=%d", mosiPin, misoPin, clkPin, csPin); spi_bus_config_t bus_config; bus_config.sclk_io_num = clkPin; // CLK @@ -53,8 +54,9 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { bus_config.quadwp_io_num = -1; // Not used bus_config.quadhd_io_num = -1; // Not used bus_config.max_transfer_sz = 0; // 0 means use default. + bus_config.flags = (SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MISO); - ESP_LOGI(tag, "... Initializing bus; host=%d", m_host); + ESP_LOGI(LOG_TAG, "... Initializing bus; host=%d", m_host); esp_err_t errRc = ::spi_bus_initialize( m_host, @@ -63,7 +65,7 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { ); if (errRc != ESP_OK) { - ESP_LOGE(tag, "spi_bus_initialize(): rc=%d", errRc); + ESP_LOGE(LOG_TAG, "spi_bus_initialize(): rc=%d", errRc); abort(); } @@ -77,14 +79,14 @@ void SPI::init(int mosiPin, int misoPin, int clkPin, int csPin) { dev_config.cs_ena_pretrans = 0; dev_config.clock_speed_hz = 100000; dev_config.spics_io_num = csPin; - dev_config.flags = 0; + dev_config.flags = SPI_DEVICE_NO_DUMMY; dev_config.queue_size = 1; dev_config.pre_cb = NULL; dev_config.post_cb = NULL; - ESP_LOGI(tag, "... Adding device bus."); + ESP_LOGI(LOG_TAG, "... Adding device bus."); errRc = ::spi_bus_add_device(m_host, &dev_config, &m_handle); if (errRc != ESP_OK) { - ESP_LOGE(tag, "spi_bus_add_device(): rc=%d", errRc); + ESP_LOGE(LOG_TAG, "spi_bus_add_device(): rc=%d", errRc); abort(); } } // init @@ -99,6 +101,7 @@ void SPI::setHost(spi_host_device_t host) { m_host = host; } // setHost + /** * @brief Send and receive data through %SPI. This is a blocking call. * @@ -109,13 +112,13 @@ void SPI::transfer(uint8_t* data, size_t dataLen) { assert(data != nullptr); assert(dataLen > 0); #ifdef DEBUG - for (auto i=0; i %2d %.2x", i, data[i]); + for (auto i = 0; i < dataLen; i++) { + ESP_LOGD(LOG_TAG, "> %2d %.2x", i, data[i]); } #endif spi_transaction_t trans_desc; - trans_desc.address = 0; - trans_desc.command = 0; + //trans_desc.address = 0; + //trans_desc.command = 0; trans_desc.flags = 0; trans_desc.length = dataLen * 8; trans_desc.rxlength = 0; @@ -125,7 +128,7 @@ void SPI::transfer(uint8_t* data, size_t dataLen) { //ESP_LOGI(tag, "... Transferring"); esp_err_t rc = ::spi_device_transmit(m_handle, &trans_desc); if (rc != ESP_OK) { - ESP_LOGE(tag, "transfer:spi_device_transmit: %d", rc); + ESP_LOGE(LOG_TAG, "transfer:spi_device_transmit: %d", rc); } } // transmit @@ -139,5 +142,3 @@ uint8_t SPI::transferByte(uint8_t value) { transfer(&value, 1); return value; } // transferByte - - diff --git a/cpp_utils/SPI.h b/cpp_utils/SPI.h index d2de5d99..6ecf64e1 100644 --- a/cpp_utils/SPI.h +++ b/cpp_utils/SPI.h @@ -22,32 +22,33 @@ class SPI { int clkPin = DEFAULT_CLK_PIN, int csPin = DEFAULT_CS_PIN); void setHost(spi_host_device_t host); - void transfer(uint8_t *data, size_t dataLen); + void transfer(uint8_t* data, size_t dataLen); uint8_t transferByte(uint8_t value); + /** * @brief The default MOSI pin. */ - static const int DEFAULT_MOSI_PIN = GPIO_NUM_13; - - /** - * @brief The default MISO pin. - */ - static const int DEFAULT_MISO_PIN = GPIO_NUM_12; - - /** - * @brief The default CLK pin. - */ - static const int DEFAULT_CLK_PIN = GPIO_NUM_14; - - /** - * @brief The default CS pin. - */ - static const int DEFAULT_CS_PIN = GPIO_NUM_15; - - /** - * @brief Value of unset pin. - */ - static const int PIN_NOT_SET = -1; + static const int DEFAULT_MOSI_PIN = GPIO_NUM_13; + + /** + * @brief The default MISO pin. + */ + static const int DEFAULT_MISO_PIN = GPIO_NUM_12; + + /** + * @brief The default CLK pin. + */ + static const int DEFAULT_CLK_PIN = GPIO_NUM_14; + + /** + * @brief The default CS pin. + */ + static const int DEFAULT_CS_PIN = GPIO_NUM_15; + + /** + * @brief Value of unset pin. + */ + static const int PIN_NOT_SET = -1; private: spi_device_handle_t m_handle; diff --git a/cpp_utils/SSLUtils.cpp b/cpp_utils/SSLUtils.cpp new file mode 100644 index 00000000..36621b3e --- /dev/null +++ b/cpp_utils/SSLUtils.cpp @@ -0,0 +1,41 @@ +/* + * SSLUtils.cpp + * + * Created on: Sep 16, 2017 + * Author: kolban + */ + +#include "SSLUtils.h" +#include +#include + +char* SSLUtils::m_certificate = nullptr; +char* SSLUtils::m_key = nullptr; + +SSLUtils::SSLUtils() { +} + +SSLUtils::~SSLUtils() { +} + +void SSLUtils::setCertificate(std::string certificate) { + size_t len = certificate.length(); + m_certificate = (char*) malloc(len + 1); + memcpy(m_certificate, certificate.data(), len); + m_certificate[len] = '\0'; +} + +char* SSLUtils::getCertificate() { + return m_certificate; +} + +void SSLUtils::setKey(std::string key) { + size_t len = key.length(); + m_key = (char*) malloc(len + 1); + memcpy(m_key, key.data(), len); + m_key[len] = '\0'; +} + +char* SSLUtils::getKey() { + return m_key; +} diff --git a/cpp_utils/SSLUtils.h b/cpp_utils/SSLUtils.h new file mode 100644 index 00000000..90546b83 --- /dev/null +++ b/cpp_utils/SSLUtils.h @@ -0,0 +1,24 @@ +/* + * SSLUtils.h + * + * Created on: Sep 16, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_SSLUTILS_H_ +#define COMPONENTS_CPP_UTILS_SSLUTILS_H_ +#include +class SSLUtils { +private: + static char* m_certificate; + static char* m_key; +public: + SSLUtils(); + virtual ~SSLUtils(); + static void setCertificate(std::string certificate); + static char* getCertificate(); + static void setKey(std::string key); + static char* getKey(); +}; + +#endif /* COMPONENTS_CPP_UTILS_SSLUTILS_H_ */ diff --git a/cpp_utils/SmartLED.cpp b/cpp_utils/SmartLED.cpp new file mode 100644 index 00000000..430c6317 --- /dev/null +++ b/cpp_utils/SmartLED.cpp @@ -0,0 +1,214 @@ +/* + * SmartLED.cpp + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#include "SmartLED.h" +#include "string.h" +#include + +static const char* LOG_TAG = "SmartLED"; + +SmartLED::SmartLED() { + m_brightness = 100; + m_pixelCount = 0; + m_pixels = nullptr; + m_colorOrder = (char*) "GRB"; +} // SmartLED + + +SmartLED::~SmartLED() { + if (m_pixels != nullptr) { + delete[] m_pixels; // Delete the allocated storage for the pixels. + } +} // ~SmartLED + +/** + * @brief Clear all the pixel colors. + * + * This sets all the pixels to off which is no brightness for all of the color channels. + * The LEDs are not actually updated until a call to show() is subsequently made. + */ +void SmartLED::clear() { + for (auto i = 0; i < this->m_pixelCount; i++) { + m_pixels[i].red = 0; + m_pixels[i].green = 0; + m_pixels[i].blue = 0; + } // End loop over all the pixel +} // clear + + +/** + * @brief Get the brightness as a percentage. + * @return The brightness as a percentage. + */ +uint32_t SmartLED::getBrightness() { + return m_brightness; +} // getBrightness + + +/** + * @brief Return the number of pixels in the chain. + * @return The number of pixels in the chain as previously set by setPixelCount(). + */ +uint16_t SmartLED::getPixelCount() { + return m_pixelCount; +} // getPixelCount + + +/** + * @brief Set the brightness as a percentage. + * @param [in] percent The brightness. + */ +void SmartLED::setBrightness(uint32_t percent) { + m_brightness = percent; +} // setBrightness + + +/** + * @brief Set the color order of data sent to the LEDs. + * + * Data is sent to the WS2812s in a serial fashion. There are 8 bits of data for each of the three + * channel colors (red, green and blue). The WS2812 LEDs typically expect the data to arrive in the + * order of "green" then "red" then "blue". However, this has been found to vary between some + * models and manufacturers. What this means is that some want "red", "green", "blue" and still others + * have their own orders. This function can be called to override the default ordering of "GRB". + * We can specify + * an alternate order by supply an alternate three character string made up of 'R', 'G' and 'B' + * for example "RGB". + */ +void SmartLED::setColorOrder(char* colorOrder) { + if (colorOrder != nullptr && strlen(colorOrder) == 3) { + m_colorOrder = colorOrder; + } +} // setColorOrder + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show() is subsequently made. + * + * @param [in] index The pixel that is to have its color set. + * @param [in] red The amount of red in the pixel. + * @param [in] green The amount of green in the pixel. + * @param [in] blue The amount of blue in the pixel. + */ +void SmartLED::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { + //assert(index < m_pixelCount); + m_pixels[index].red = red; + m_pixels[index].green = green; + m_pixels[index].blue = blue; +} // setPixel + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] pixel The color value of the pixel. + */ +void SmartLED::setPixel(uint16_t index, pixel_t pixel) { + //assert(index < m_pixelCount); + m_pixels[index] = pixel; +} // setPixel + + +/** + * @brief Set the given pixel to the specified color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] pixel The color value of the pixel. + */ +void SmartLED::setPixel(uint16_t index, uint32_t pixel) { + //assert(index < m_pixelCount); + m_pixels[index].red = pixel & 0xff; + m_pixels[index].green = (pixel & 0xff00) >> 8; + m_pixels[index].blue = (pixel & 0xff0000) >> 16; +} // setPixel + + +void SmartLED::setPixelCount(uint16_t pixelCount) { + ESP_LOGD(LOG_TAG, ">> setPixelCount: %d", pixelCount); + if (m_pixels != nullptr) { + delete[] m_pixels; + } + m_pixelCount = pixelCount; + m_pixels = new pixel_t[pixelCount]; // Allocate the storage for the pixels. + ESP_LOGD(LOG_TAG, "<< setPixelCount"); +} + + +/** + * @brief Set the given pixel to the specified HSB color. + * + * The LEDs are not actually updated until a call to show(). + * + * @param [in] index The pixel that is to have its color set. + * @param [in] hue The amount of hue in the pixel (0-360). + * @param [in] saturation The amount of saturation in the pixel (0-255). + * @param [in] brightness The amount of brightness in the pixel (0-255). + */ +void SmartLED::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { + double sat_red; + double sat_green; + double sat_blue; + double ctmp_red; + double ctmp_green; + double ctmp_blue; + double new_red; + double new_green; + double new_blue; + double dSaturation=(double) saturation / 255; + double dBrightness=(double) brightness / 255; + + //assert(index < pixelCount); + + if (hue < 120) { + sat_red = (120 - hue) / 60.0; + sat_green = hue / 60.0; + sat_blue = 0; + } else if (hue < 240) { + sat_red = 0; + sat_green = (240 - hue) / 60.0; + sat_blue = (hue - 120) / 60.0; + } else { + sat_red = (hue - 240) / 60.0; + sat_green = 0; + sat_blue = (360 - hue) / 60.0; + } + + if (sat_red>1.0) { + sat_red = 1.0; + } + if (sat_green>1.0) { + sat_green = 1.0; + } + if (sat_blue>1.0) { + sat_blue = 1.0; + } + + ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); + ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); + ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); + + if (dBrightness < 0.5) { + new_red = dBrightness * ctmp_red; + new_green = dBrightness * ctmp_green; + new_blue = dBrightness * ctmp_blue; + } else { + new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; + new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; + new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; + } + + m_pixels[index].red = (uint8_t)(new_red * 255); + m_pixels[index].green = (uint8_t)(new_green * 255); + m_pixels[index].blue = (uint8_t)(new_blue * 255); +} // setHSBPixel diff --git a/cpp_utils/SmartLED.h b/cpp_utils/SmartLED.h new file mode 100644 index 00000000..7903f697 --- /dev/null +++ b/cpp_utils/SmartLED.h @@ -0,0 +1,54 @@ +/* + * SmartLED.h + * + * Created on: Oct 22, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_SMARTLED_H_ +#define COMPONENTS_SMARTLED_H_ +#include +/** + * @brief A data type representing the color of a pixel. + */ +typedef struct { + /** + * @brief The red component of the pixel. + */ + uint8_t red; + /** + * @brief The green component of the pixel. + */ + uint8_t green; + /** + * @brief The blue component of the pixel. + */ + uint8_t blue; +} pixel_t; + + +class SmartLED { +public: + SmartLED(); + virtual ~SmartLED(); + uint32_t getBrightness(); + uint16_t getPixelCount(); + virtual void init() = 0; + virtual void show() = 0; + void setBrightness(uint32_t percent); + void setColorOrder(char* order); + void setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue); + void setPixel(uint16_t index, pixel_t pixel); + void setPixel(uint16_t index, uint32_t pixel); + void setPixelCount(uint16_t pixelCount); + void setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness); + void clear(); + +protected: + uint32_t m_brightness; + char* m_colorOrder; + uint16_t m_pixelCount; + pixel_t* m_pixels; +}; + +#endif /* COMPONENTS_SMARTLED_H_ */ diff --git a/cpp_utils/SockServ.cpp b/cpp_utils/SockServ.cpp index 60cd4ef7..020ed096 100644 --- a/cpp_utils/SockServ.cpp +++ b/cpp_utils/SockServ.cpp @@ -7,15 +7,17 @@ #include #include -#include -#include - +#include +#include #include + #include #include #include "sdkconfig.h" +#include "FreeRTOS.h" #include "SockServ.h" +#include "Socket.h" static const char* LOG_TAG = "SockServ"; @@ -26,39 +28,54 @@ static const char* LOG_TAG = "SockServ"; * We won't actually start listening for clients until after the start() method has been called. * @param [in] port The TCP/IP port number on which we will listen for incoming connection requests. */ -SockServ::SockServ(uint16_t port) { +SockServ::SockServ(uint16_t port) : SockServ() { this->m_port = port; - m_clientSock = -1; - m_sock = -1; - m_clientSemaphore.take("SockServ"); } // SockServ /** - * @brief Accept an incoming connection. - * - * Block waiting for an incoming connection and accept it when it arrives. + * Constructor */ -void SockServ::acceptTask(void *data) { +SockServ::SockServ() { + m_port = 0; // Unknown port. + m_acceptQueue = xQueueCreate(1, sizeof(Socket)); + m_useSSL = false; + m_clientSemaphore.take("SockServ"); // Create the queue; deleted in the destructor. +} // SockServ - SockServ* pSockServ = (SockServ*)data; - struct sockaddr_in clientAddress; - while(1) { - socklen_t clientAddressLength = sizeof(clientAddress); - int tempSock = ::accept(pSockServ->m_sock, (struct sockaddr *)&clientAddress, &clientAddressLength); - if (tempSock == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); - } - ESP_LOGD(LOG_TAG, "accept() - New socket"); - if (pSockServ->m_clientSock != -1) { - int rc = ::close(pSockServ->m_clientSock); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); - } +/** + * Destructor + */ +SockServ::~SockServ() { + vQueueDelete(m_acceptQueue); // Delete the queue created in the constructor. +} // ~SockServ + + +/** + * @brief Accept an incoming connection. + * @private + * + * Block waiting for an incoming connection and accept it when it arrives. The new + * socket is placed on a queue and a semaphore signaled that a new client is available. + */ +/* static */ void SockServ::acceptTask(void* data) { + SockServ* pSockServ = (SockServ*) data; + while (true) { + try { + ESP_LOGD(LOG_TAG, "Waiting on accept"); + Socket tempSock = pSockServ->m_serverSocket.accept(); + if (!tempSock.isValid()) continue; + + pSockServ->m_clientSet.insert(tempSock); + xQueueSendToBack(pSockServ->m_acceptQueue, &tempSock, portMAX_DELAY); + pSockServ->m_clientSemaphore.give(); + } catch (std::exception e) { + ESP_LOGD(LOG_TAG, "acceptTask ending"); + pSockServ->m_clientSemaphore.give(); // Wake up any waiting clients. + FreeRTOS::deleteTask(); + break; } - pSockServ->m_clientSock = tempSock; - pSockServ->m_clientSemaphore.give(); } } // acceptTask @@ -69,39 +86,35 @@ void SockServ::acceptTask(void *data) { * @return The number of connected partners. */ int SockServ::connectedCount() { - if (m_clientSock == -1) { - return 0; - } - return 1; + return m_clientSet.size(); } // connectedCount /** * @brief Disconnect any connected partners. */ -void SockServ::disconnect() { - if (m_clientSock != -1) { - int rc = ::close(m_clientSock); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); - } - m_clientSock = -1; - m_clientSemaphore.take("disconnect"); - } +void SockServ::disconnect(Socket s) { + auto search = m_clientSet.find(s); + m_clientSet.erase(search); } // disconnect +/** + * Get the SSL status. + */ +bool SockServ::getSSL() { + return m_useSSL; +} // getSSL + + /** * @brief Wait for data * @param [in] pData Pointer to buffer to hold the data. * @param [in] maxData Maximum size of the data to receive. * @return The amount of data returned or 0 if there was an error. */ -size_t SockServ::receiveData(void* pData, size_t maxData) { - if (m_clientSock == -1) { - return 0; - } - int rc = ::recv(m_clientSock, pData, maxData, 0); +size_t SockServ::receiveData(Socket s, void* pData, size_t maxData) { + size_t rc = s.receive((uint8_t*) pData, maxData); if (rc == -1) { ESP_LOGE(LOG_TAG, "recv(): %s", strerror(errno)); return 0; @@ -116,7 +129,7 @@ size_t SockServ::receiveData(void* pData, size_t maxData) { * @param[in] str A string from which sequence of bytes will be used to send to the partner. */ void SockServ::sendData(std::string str) { - sendData((uint8_t *)str.data(), str.size()); + sendData((uint8_t*) str.data(), str.size()); } // sendData @@ -127,40 +140,37 @@ void SockServ::sendData(std::string str) { * @param[in] length The length of the sequence of bytes to send to the partner. */ void SockServ::sendData(uint8_t* data, size_t length) { - if (connectedCount() == 0) { - return; - } - int rc = ::send(m_clientSock, data, length, 0); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "send(): %s", strerror(errno)); - } + for (auto it = m_clientSet.begin(); it != m_clientSet.end(); ++it) { + (*it).send(data, length); + } } // sendData +/** + * @brief Set the port number to use. + * @param port The port number to use. + */ +void SockServ::setPort(uint16_t port) { + m_port = port; +} // setPort + + +void SockServ::setSSL(bool use) { + m_useSSL = use; +} // setSSL + + /** * @brief Start listening for new partner connections. * * The port number on which we will listen is the one defined when the class was created. */ void SockServ::start() { - m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (m_sock == -1) { - ESP_LOGE(LOG_TAG, "socket(): %s", strerror(errno)); - } - struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; - serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); - serverAddress.sin_port = htons(m_port); - int rc = ::bind(m_sock, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "bind(): %s", strerror(errno)); - } - rc = ::listen(m_sock, 5); - if (rc == -1) { - ESP_LOGE(LOG_TAG, "listen(): %s", strerror(errno)); - } + assert(m_port != 0); + //m_serverSocket.setSSL(m_useSSL); + m_serverSocket.listen(m_port); // Create a socket and start listening on it. ESP_LOGD(LOG_TAG, "Now listening on port %d", m_port); - FreeRTOS::startTask(acceptTask, "acceptTask", this); + FreeRTOS::startTask(acceptTask, "acceptTask", this, 8 * 1024); } // start @@ -168,11 +178,46 @@ void SockServ::start() { * @brief Stop listening for new partner connections. */ void SockServ::stop() { - int rc = ::close(m_sock); + ESP_LOGD(LOG_TAG, ">> stop"); + // By closing the server socket, the task watching on accept() on that socket + // will throw an exception which will propagate a clean ending. + m_serverSocket.close(); // Close the server socket. + ESP_LOGD(LOG_TAG, "<< stop"); +} // stop + + +Socket SockServ::waitForData(std::set& socketSet) { + fd_set readSet; + int maxFd = -1; + + for (auto it = socketSet.begin(); it != socketSet.end(); ++it) { + FD_SET(it->getFD(), &readSet); + if (it->getFD() > maxFd) { + maxFd = it->getFD(); + } + } // End for + + int rc = ::select( + maxFd+1, // Number of sockets to scan + &readSet, // Set of read sockets + nullptr, // Set of write sockets + nullptr, // Set of exception sockets + nullptr // Timeout + ); if (rc == -1) { - ESP_LOGE(LOG_TAG, "close(): %s", strerror(errno)); + ESP_LOGE(LOG_TAG, "Error with select"); + Socket s; + return s; } -} // stop + + for (auto it = socketSet.begin(); it != socketSet.end(); ++it) { + if (FD_ISSET(it->getFD(), &readSet)) { + return *it; + } + } // End for + Socket s; + return s; +} /** @@ -180,6 +225,16 @@ void SockServ::stop() { * Returns when a client connection is present. This can block until a client connects * or can return immediately is there is already a client connection in existence. */ -void SockServ::waitForClient() { - m_clientSemaphore.wait("waitForClient"); -} // waitForClient +Socket SockServ::waitForNewClient() { + ESP_LOGD(LOG_TAG, ">> waitForNewClient"); + m_clientSemaphore.wait("waitForNewClient"); // Unlocked in acceptTask. + m_clientSemaphore.take("waitForNewClient"); + Socket tempSocket; + BaseType_t rc = xQueueReceive(m_acceptQueue, &tempSocket, 0); // Read the socket from the queue. + if (rc != pdPASS) { + ESP_LOGE(LOG_TAG, "No new client from SockServ!"); + throw new SocketException(0); + } + ESP_LOGD(LOG_TAG, "<< waitForNewClient"); + return tempSocket; +} // waitForNewClient diff --git a/cpp_utils/SockServ.h b/cpp_utils/SockServ.h index 8de7bd28..770ec954 100644 --- a/cpp_utils/SockServ.h +++ b/cpp_utils/SockServ.h @@ -4,7 +4,11 @@ #define MAIN_SOCKSERV_H_ #include #include +#include +#include "Socket.h" #include "FreeRTOS.h" +#include +#include /** @@ -29,20 +33,30 @@ class SockServ { private: static void acceptTask(void*); - uint16_t m_port; - int m_sock; - int m_clientSock; - FreeRTOS::Semaphore m_clientSemaphore; + uint16_t m_port; + Socket m_serverSocket; + FreeRTOS::Semaphore m_clientSemaphore = FreeRTOS::Semaphore("clientSemaphore"); + std::set m_clientSet; + QueueHandle_t m_acceptQueue; + bool m_useSSL; + public: SockServ(uint16_t port); + SockServ(); + ~SockServ(); int connectedCount(); - void disconnect(); - size_t receiveData(void* pData, size_t maxData); - void sendData(uint8_t *data, size_t length); + void disconnect(Socket s); + bool getSSL(); + size_t receiveData(Socket s, void* pData, size_t maxData); + void sendData(uint8_t* data, size_t length); void sendData(std::string str); + void setPort(uint16_t port); + void setSSL(bool use = true); void start(); void stop(); - void waitForClient(); + Socket waitForData(std::set& socketSet); + Socket waitForNewClient(); + }; #endif /* MAIN_SOCKSERV_H_ */ diff --git a/cpp_utils/Socket.cpp b/cpp_utils/Socket.cpp index 115ce599..db5991ab 100644 --- a/cpp_utils/Socket.cpp +++ b/cpp_utils/Socket.cpp @@ -4,35 +4,93 @@ * Created on: Mar 5, 2017 * Author: kolban */ +#include +#include +#include +#include +#include +#include + +#include #include #include #include #include #include -#include +#include +#include "GeneralUtils.h" +#include "SSLUtils.h" #include "sdkconfig.h" #include "Socket.h" -static char tag[] = "Socket"; +static const char* LOG_TAG = "Socket"; + +#undef bind + +static void my_debug( + void* ctx, + int level, + const char* file, + int line, + const char* str) { + + ((void) level); + ((void) ctx); + printf("%s:%04d: %s", file, line, str); +} Socket::Socket() { - m_sock = -1; + m_sock = -1; + m_useSSL = false; } Socket::~Socket() { - close_cpp(); // When the class instance has ended, delete the socket. + //close_cpp(); // When the class instance has ended, delete the socket. } +/** + * @brief Accept a new socket. + */ +Socket Socket::accept() { + struct sockaddr addr; + getBind(&addr); + ESP_LOGD(LOG_TAG, ">> accept: Accepting on %s; sockFd: %d, using SSL: %d", addressToString(&addr).c_str(), m_sock, getSSL()); + struct sockaddr_in client_addr; + socklen_t sin_size; + int clientSockFD = ::lwip_accept_r(m_sock, (struct sockaddr*) &client_addr, &sin_size); + //printf("------> new connection client %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + if (clientSockFD == -1) { + SocketException se(errno); + ESP_LOGE(LOG_TAG, "accept(): %s, m_sock=%d", strerror(errno), m_sock); + throw se; + } + + ESP_LOGD(LOG_TAG, " - accept: Received new client!: sockFd: %d", clientSockFD); + Socket newSocket; + newSocket.m_sock = clientSockFD; + if (getSSL()) { + newSocket.setSSL(true); + newSocket.m_sslSock.fd = clientSockFD; + newSocket.sslHandshake(); + ESP_LOGD(LOG_TAG, "DEBUG DEBUG "); + uint8_t x; // What is going on here??? + newSocket.receive(&x, 1, 0); // FIX FIX FIX + } + ESP_LOGD(LOG_TAG, "<< accept: sockFd: %d", clientSockFD); + return newSocket; +} // accept + + /** * @brief Convert a socket address to a string representation. * @param [in] addr The address to parse. * @return A string representation of the address. */ std::string Socket::addressToString(struct sockaddr* addr) { - struct sockaddr_in *pInAddr = (struct sockaddr_in *)addr; + struct sockaddr_in* pInAddr = (struct sockaddr_in*) addr; char temp[30]; char ip[20]; inet_ntop(AF_INET, &pInAddr->sin_addr, ip, sizeof(ip)); @@ -47,35 +105,53 @@ std::string Socket::addressToString(struct sockaddr* addr) { * Specify an address of INADDR_ANY to use the local server IP. * @param [in] port Port number to bind. * @param [in] address Address to bind. - * @return N/A + * @return Returns 0 on success. */ -void Socket::bind_cpp(uint16_t port, uint32_t address) { +int Socket::bind(uint16_t port, uint32_t address) { + ESP_LOGD(LOG_TAG, ">> bind: port=%d, address=0x%x", port, address); + if (m_sock == -1) { - ESP_LOGE(tag, "bind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "bind: Socket is not initialized."); } struct sockaddr_in serverAddress; - serverAddress.sin_family = AF_INET; + serverAddress.sin_family = AF_INET; serverAddress.sin_addr.s_addr = htonl(address); - serverAddress.sin_port = htons(port); - int rc = ::bind(m_sock, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); - if (rc == -1) { - ESP_LOGE(tag, "bind_cpp: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); - return; + serverAddress.sin_port = htons(port); + int rc = ::lwip_bind_r(m_sock, (struct sockaddr*) &serverAddress, sizeof(serverAddress)); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "<< bind: bind[socket=%d]: %d: %s", m_sock, errno, strerror(errno)); + return rc; } -} // bind_cpp + ESP_LOGD(LOG_TAG, "<< bind"); + return rc; +} // bind /** * @brief Close the socket. * - * @return N/A. + * @return Returns 0 on success. */ -void Socket::close_cpp() { +int Socket::close() { + ESP_LOGD(LOG_TAG, "close: m_sock=%d, ssl: %d", m_sock, getSSL()); + int rc; + if (getSSL()) { + rc = mbedtls_ssl_close_notify(&m_sslContext); + if (rc < 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_close_notify: %d", rc); + } + } + rc = 0; if (m_sock != -1) { - ::close(m_sock); + ESP_LOGD(LOG_TAG, "Calling lwip_close on %d", m_sock); + rc = ::lwip_close_r(m_sock); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "Error with lwip_close: %d", rc); + } } m_sock = -1; -} // close_cpp + return rc; +} // close /** @@ -85,22 +161,22 @@ void Socket::close_cpp() { * @param [in] port The port number of the partner. * @return Success or failure of the connection. */ -int Socket::connect_cpp(struct in_addr address, uint16_t port) { +int Socket::connect(struct in_addr address, uint16_t port) { struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_addr = address; serverAddress.sin_port = htons(port); char msg[50]; inet_ntop(AF_INET, &address, msg, sizeof(msg)); - ESP_LOGD(tag, "Connecting to %s:[%d]", msg, port); - createSocket_cpp(); - int rc = ::connect(m_sock, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + ESP_LOGD(LOG_TAG, "Connecting to %s:[%d]", msg, port); + createSocket(); + int rc = ::lwip_connect_r(m_sock, (struct sockaddr*) &serverAddress, sizeof(struct sockaddr_in)); if (rc == -1) { - ESP_LOGE(tag, "connect_cpp: Error: %s", strerror(errno)); - close_cpp(); + ESP_LOGE(LOG_TAG, "connect_cpp: Error: %s", strerror(errno)); + close(); return -1; } else { - ESP_LOGD(tag, "Connected to partner"); + ESP_LOGD(LOG_TAG, "Connected to partner"); return 0; } } // connect_cpp @@ -113,10 +189,10 @@ int Socket::connect_cpp(struct in_addr address, uint16_t port) { * @param [in] port The port number of the partner. * @return Success or failure of the connection. */ -int Socket::connect_cpp(char* strAddress, uint16_t port) { +int Socket::connect(char* strAddress, uint16_t port) { struct in_addr address; - inet_pton(AF_INET, (char *)strAddress, &address); - return connect_cpp(address, port); + inet_pton(AF_INET, strAddress, &address); + return connect(address, port); } @@ -125,19 +201,20 @@ int Socket::connect_cpp(char* strAddress, uint16_t port) { * @param [in] isDatagram Set to true to create a datagram socket. Default is false. * @return The socket descriptor. */ -int Socket::createSocket_cpp(bool isDatagram) { +int Socket::createSocket(bool isDatagram) { + ESP_LOGD(LOG_TAG, ">> createSocket: isDatagram: %d", isDatagram); if (isDatagram) { m_sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - } - else { + } else { m_sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (m_sock == -1) { - ESP_LOGE(tag, "createSocket_cpp: socket: %d", errno); + ESP_LOGE(LOG_TAG, "<< createSocket: socket: %d", errno); return m_sock; } + ESP_LOGD(LOG_TAG, "<< createSocket: sockFd: %d", m_sock); return m_sock; -} // createSocket_cpp +} // createSocket /** @@ -145,39 +222,171 @@ int Socket::createSocket_cpp(bool isDatagram) { * @param [out] pAddr The storage to hold the address. * @return N/A. */ -void Socket::getBind_cpp(struct sockaddr *pAddr) { +void Socket::getBind(struct sockaddr* pAddr) { if (m_sock == -1) { - ESP_LOGE(tag, "getBind_cpp: Socket is not initialized."); + ESP_LOGE(LOG_TAG, "getBind: Socket is not initialized."); } socklen_t nameLen = sizeof(struct sockaddr); - ::getsockname(m_sock, pAddr, &nameLen); -} // getBind_cpp + int rc = ::getsockname(m_sock, pAddr, &nameLen); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "Error with getsockname in getBind: %s", strerror(errno)); + } +} // getBind +/** + * @brief Get the underlying socket file descriptor. + * @return The underlying socket file descriptor. + */ +int Socket::getFD() const { + return m_sock; +} // getFD + +bool Socket::getSSL() const { + return m_useSSL; +} + +bool Socket::isValid() { + return m_sock != -1; +} // isValid + /** * @brief Create a listening socket. * @param [in] port The port number to listen upon. - * @param [in] isDatagram True if we are listening on a datagram. + * @param [in] isDatagram True if we are listening on a datagram. The default is false. + * @return Returns 0 on success. */ -void Socket::listen_cpp(uint16_t port, bool isDatagram) { - createSocket_cpp(isDatagram); - bind_cpp(port, INADDR_ANY); -} // listen_cpp +int Socket::listen(uint16_t port, bool isDatagram, bool reuseAddress) { + ESP_LOGD(LOG_TAG, ">> listen: port: %d, isDatagram: %d", port, isDatagram); + createSocket(isDatagram); + setReuseAddress(reuseAddress); + int rc = bind(port, 0); + if (rc != 0) { + ESP_LOGE(LOG_TAG, "<< listen: Error in bind: %s", strerror(errno)); + return rc; + } + // For a datagram socket, we don't execute a listen call. That is is only for connection oriented + // sockets. + if (!isDatagram) { + rc = ::lwip_listen_r(m_sock, 5); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "<< listen: %s", strerror(errno)); + return rc; + } + } + ESP_LOGD(LOG_TAG, "<< listen"); + return 0; +} // listen + + +bool Socket::operator <(const Socket& other) const { + return m_sock < other.m_sock; +} + + +/** + * @brief Set the socket option. + */ +int Socket::setSocketOption(int option, void* value, size_t len) { + int res = ::setsockopt(m_sock, SOL_SOCKET, option, value, len); + if (res < 0) { + ESP_LOGE(LOG_TAG, "%X : %d", option, errno); + } + return res; +} // setSocketOption + + +/** + * @brief Socket timeout. + * @param [in] seconds to wait. + */ +int Socket::setTimeout(uint32_t seconds) { + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if (setSocketOption(SO_RCVTIMEO, (char*) &tv, sizeof(struct timeval)) < 0) { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char*) &tv, sizeof(struct timeval)); +} + + +std::string Socket::readToDelim(std::string delim) { + std::string ret; + std::string part; + auto it = delim.begin(); + while (true) { + uint8_t val; + int rc = receive(&val, 1); + if (rc == -1) return ""; + if (rc == 0) return ret + part; + if (*it == val) { + part+= val; + ++it; + if (it == delim.end()) return ret; + } else { + if (part.empty()) { + ret += part; + part.clear(); + it = delim.begin(); + } + ret += val; + } + } // While +} // readToDelim /** * @brief Receive data from the partner. - * + * Receive data from the socket partner. If exact = false, we read as much data as + * is available without blocking up to length. If exact = true, we will block until + * we have received exactly length bytes or there are no more bytes to read. * @param [in] data The buffer into which the received data will be stored. * @param [in] length The size of the buffer. + * @param [in] exact Read exactly this amount. * @return The length of the data received or -1 on an error. */ -int Socket::receive_cpp(uint8_t* data, size_t length) { - int rc = ::recv(m_sock, data, length, 0); - if (rc == -1) { - ESP_LOGE(tag, "receive_cpp: %s", strerror(errno)); +size_t Socket::receive(uint8_t* data, size_t length, bool exact) { + //ESP_LOGD(LOG_TAG, ">> receive: sockFd: %d, length: %d, exact: %d", m_sock, length, exact); + if (!exact) { + int rc; + if (getSSL()) { + do { + rc = mbedtls_ssl_read(&m_sslContext, data, length); + ESP_LOGD(LOG_TAG, "rc=%d, MBEDTLS_ERR_SSL_WANT_READ=%d", rc, MBEDTLS_ERR_SSL_WANT_READ); + } while (rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } else { + rc = ::lwip_recv_r(m_sock, data, length, 0); + if (rc == -1) { + ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); + } + } + //GeneralUtils::hexDump(data, rc); + //ESP_LOGD(LOG_TAG, "<< receive: rc: %d", rc); + return (size_t) rc; + } // Read what we can, doesn't need to be an exact amount. + + size_t amountToRead = length; + int rc; + while (amountToRead > 0) { + if (getSSL()) { + do { + rc = mbedtls_ssl_read(&m_sslContext, data, amountToRead); + } while (rc == MBEDTLS_ERR_SSL_WANT_WRITE || rc == MBEDTLS_ERR_SSL_WANT_READ); + } else { + rc = ::lwip_recv_r(m_sock, data, amountToRead, 0); + } + if (rc == -1) { + ESP_LOGE(LOG_TAG, "receive: %s", strerror(errno)); + return 0; + } + if (rc == 0) break; + amountToRead -= rc; + data += rc; } - return rc; + //GeneralUtils::hexDump(data, length); + //ESP_LOGD(LOG_TAG, "<< receive: %d", length); + return length; } // receive_cpp @@ -188,11 +397,11 @@ int Socket::receive_cpp(uint8_t* data, size_t length) { * @param [in] pAddr An area into which we can store the address of the partner. * @return The length of the data received. */ -int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr) { +int Socket::receiveFrom(uint8_t* data, size_t length, struct sockaddr *pAddr) { socklen_t addrLen = sizeof(struct sockaddr); int rc = ::recvfrom(m_sock, data, length, 0, pAddr, &addrLen); return rc; -} // receiveFrom_cpp +} // receiveFrom /** @@ -203,12 +412,40 @@ int Socket::receiveFrom_cpp(uint8_t* data, size_t length, struct sockaddr *pAddr * @return N/A. * */ -void Socket::send_cpp(const uint8_t* data, size_t length) { - int rc = ::send(m_sock, data, length, 0); - if (rc == -1) { - ESP_LOGE(tag, "send: socket=%d, %s", m_sock, strerror(errno)); +int Socket::send(const uint8_t* data, size_t length) const { + ESP_LOGD(LOG_TAG, "send: Raw binary of length: %d", length); + //GeneralUtils::hexDump(data, length); + int rc = ERR_OK; + while (length > 0) { + if (getSSL()) { + rc = mbedtls_ssl_write((mbedtls_ssl_context*)&m_sslContext, data, length); + // retry with same parameters if MBEDTLS_ERR_SSL_WANT_WRITE or MBEDTLS_ERR_SSL_WANT_READ + if ((rc != MBEDTLS_ERR_SSL_WANT_WRITE) && (rc != MBEDTLS_ERR_SSL_WANT_READ)) { + if (rc < 0) { + // no cure for other errors - log and exit + ESP_LOGE(LOG_TAG, "send: SSL write error %d", rc); + return rc; + } else { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } + } else { + rc = ::lwip_send_r(m_sock, data, length, 0); + if ((rc < 0) && (errno != EAGAIN)) { + // no cure for errors other than EAGAIN - log and exit + ESP_LOGE(LOG_TAG, "send: socket=%d, %s", m_sock, strerror(errno)); + return rc; + } else if (rc > 0) { + // not all data was written, try again for the remainder + length -= rc; + data += rc; + } + } } -} // send_cpp + return rc; +} // send /** @@ -217,20 +454,209 @@ void Socket::send_cpp(const uint8_t* data, size_t length) { * @param [in] value The string to send to the partner. * @return N/A. */ -void Socket::send_cpp(std::string value) { - send_cpp((uint8_t *)value.data(), value.size()); +int Socket::send(std::string value) const { + ESP_LOGD(LOG_TAG, "send: Binary of length: %d", value.length()); + return send((uint8_t*) value.data(), value.size()); +} // send + + +int Socket::send(uint16_t value) { + ESP_LOGD(LOG_TAG, "send: 16bit value: %.2x", value); + return send((uint8_t*) &value, sizeof(value)); } // send_cpp +int Socket::send(uint32_t value) { + ESP_LOGD(LOG_TAG, "send: 32bit value: %.2x", value); + return send((uint8_t*) &value, sizeof(value)); +} // send + + /** * @brief Send data to a specific address. * @param [in] data The data to send. * @param [in] length The length of the data to send/ * @param [in] pAddr The address to send the data. */ -void Socket::sendTo_cpp(const uint8_t* data, size_t length, struct sockaddr* pAddr) { - int rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); - if (rc == -1) { - ESP_LOGE(tag, "sendto_cpp: socket=%d %s", m_sock, strerror(errno)); +void Socket::sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr) { + int rc; + if (getSSL()) { + rc = mbedtls_ssl_write(&m_sslContext, data, length); + } else { + rc = ::sendto(m_sock, data, length, 0, pAddr, sizeof(struct sockaddr)); + } + if (rc < 0) { + ESP_LOGE(LOG_TAG, "sendto: socket=%d %s", m_sock, strerror(errno)); } -} // sendTo_cpp +} // sendTo + + +/** + * @brief Flag the socket address as re-usable. + * @param [in] value True to mark the address as re-usable, false otherwise. + */ +void Socket::setReuseAddress(bool value) { + ESP_LOGD(LOG_TAG, ">> setReuseAddress: %d", value); + int val = value ? 1 : 0; + setSocketOption(SO_REUSEADDR, &val, sizeof(val)); + ESP_LOGD(LOG_TAG, "<< setReuseAddress"); +} // setReuseAddress + + +/** + * @brief Flag the socket as using SSL + * @param [in] sslValue True if we wish to use SSL. + */ +void Socket::setSSL(bool sslValue) { + const char* pers = "ssl_server"; + ESP_LOGD(LOG_TAG, ">> setSSL: %s", sslValue?"Yes":"No"); + m_useSSL = sslValue; + + if (sslValue) { + char *pvtKey = SSLUtils::getKey(); + char *certificate = SSLUtils::getCertificate(); + if (pvtKey == nullptr) { + ESP_LOGE(LOG_TAG, "No private key file"); + return; + } + if (certificate == nullptr) { + ESP_LOGE(LOG_TAG, "No certificate file"); + return; + } + + mbedtls_net_init(&m_sslSock); + mbedtls_ssl_init(&m_sslContext); + mbedtls_ssl_config_init(&m_conf); + mbedtls_x509_crt_init(&m_srvcert); + mbedtls_pk_init(&m_pkey); + mbedtls_entropy_init(&m_entropy); + mbedtls_ctr_drbg_init(&m_ctr_drbg); + + int ret = mbedtls_x509_crt_parse(&m_srvcert, (unsigned char *) certificate, strlen(certificate) + 1); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_x509_crt_parse returned 0x%x", -ret); + goto exit; + } + + ret = mbedtls_pk_parse_key(&m_pkey, (unsigned char *) pvtKey, strlen(pvtKey) + 1, NULL, 0); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_pk_parse_key returned 0x%x", -ret); + goto exit; + } + + ret = mbedtls_ctr_drbg_seed(&m_ctr_drbg, mbedtls_entropy_func, &m_entropy, (const unsigned char*) pers, strlen(pers)); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "! mbedtls_ctr_drbg_seed returned %d\n", ret); + goto exit; + } + + ret = mbedtls_ssl_config_defaults(&m_conf, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_config_defaults returned %d\n\n", ret); + goto exit; + } + + mbedtls_ssl_conf_authmode(&m_conf, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_conf_rng(&m_conf, mbedtls_ctr_drbg_random, &m_ctr_drbg); + +// mbedtls_ssl_conf_ca_chain( &m_conf, m_srvcert.next, NULL); + ret = mbedtls_ssl_conf_own_cert(&m_conf, &m_srvcert, &m_pkey); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_conf_own_cert returned %d\n\n", ret); + goto exit; + } + + mbedtls_ssl_conf_dbg(&m_conf, my_debug, nullptr); +#ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_debug_set_threshold(4); +#endif + + ret = mbedtls_ssl_setup(&m_sslContext, &m_conf); + if (ret != 0) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_setup returned %d\n\n", ret); + goto exit; + } + } +exit: + return; +} // setSSL + + +/** + * @brief perform the SSL handshake + */ +void Socket::sslHandshake() { + ESP_LOGD(LOG_TAG, ">> sslHandshake: sock: %d", m_sslSock.fd); + mbedtls_ssl_session_reset(&m_sslContext); + ESP_LOGD(LOG_TAG, " - Reset complete"); + mbedtls_ssl_set_bio(&m_sslContext, &m_sslSock, mbedtls_net_send, mbedtls_net_recv, NULL); + + while (true) { + int ret = mbedtls_ssl_handshake(&m_sslContext); + if (ret == 0) break; + + if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGD(LOG_TAG, "mbedtls_ssl_handshake returned %d\n\n", ret); + return; + } + } // End while + ESP_LOGD(LOG_TAG, "<< sslHandshake"); +} // sslHandshake + + +/** + * @brief Get the string representation of this socket + * @return the string representation of the socket. + */ +std::string Socket::toString() { + std::ostringstream oss; + oss << "fd: " << m_sock; + return oss.str(); +} // toString + + +/** + * @brief Create a socket input record streambuf + * @param [in] socket The socket we will be reading from. + * @param [in] dataLength The size of a record. + * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. + */ +SocketInputRecordStreambuf::SocketInputRecordStreambuf( + Socket socket, + size_t dataLength, + size_t bufferSize) { + m_socket = socket; // The socket we will be reading from + m_dataLength = dataLength; // The size of the record we wish to read. + m_bufferSize = bufferSize; // The size of the buffer used to hold data + m_sizeRead = 0; // The size of data read from the socket + m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. + + setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. +} // SocketInputRecordStreambuf + + +SocketInputRecordStreambuf::~SocketInputRecordStreambuf() { + delete[] m_buffer; +} // ~SocketInputRecordStreambuf + + +/** + * @brief Handle the request to read data from the stream but we need more data from the source. + * + */ +SocketInputRecordStreambuf::int_type SocketInputRecordStreambuf::underflow() { + if (m_sizeRead >= m_dataLength) return EOF; + int bytesRead = m_socket.receive((uint8_t*) m_buffer, m_bufferSize, true); + if (bytesRead == 0) return EOF; + + m_sizeRead += bytesRead; + setg(m_buffer, m_buffer, m_buffer + bytesRead); + return traits_type::to_int_type(*gptr()); +} // underflow + +SocketException::SocketException(int myErrno) { + m_errno = myErrno; +} diff --git a/cpp_utils/Socket.h b/cpp_utils/Socket.h index 5885ba51..f9a2bac6 100644 --- a/cpp_utils/Socket.h +++ b/cpp_utils/Socket.h @@ -7,9 +7,50 @@ #ifndef COMPONENTS_CPP_UTILS_SOCKET_H_ #define COMPONENTS_CPP_UTILS_SOCKET_H_ +#include "sdkconfig.h" +#include + +#include +#include +#include +#include +#include +#include + #include #include + +#undef accept +#undef bind +#undef close +#undef connect +#undef listen +#undef read +#undef recv +#undef send +#undef write + #include +#include +#include +#include +#include +#include + + +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + +class SocketException: public std::exception { +public: + SocketException(int myErrno); + +private: + int m_errno; + +}; + /** * @brief Encapsulate a socket. @@ -23,21 +64,61 @@ class Socket { Socket(); virtual ~Socket(); - void send_cpp(std::string value); - static std::string addressToString(struct sockaddr *addr); - void bind_cpp(uint16_t port, uint32_t address); - void close_cpp(); - int connect_cpp(struct in_addr address, uint16_t port); - int connect_cpp(char *address, uint16_t port); - int createSocket_cpp(bool isDatagram = false); - void getBind_cpp(struct sockaddr *pAddr); - void listen_cpp(uint16_t port, bool isDatagram); - int receive_cpp(uint8_t *data, size_t length); - int receiveFrom_cpp(uint8_t *data, size_t length, struct sockaddr *pAddr); - void send_cpp(const uint8_t *data, size_t length); - void sendTo_cpp(const uint8_t *data, size_t length, struct sockaddr *pAddr); + Socket accept(); + static std::string addressToString(struct sockaddr* addr); + int bind(uint16_t port, uint32_t address); + int close(); + int connect(struct in_addr address, uint16_t port); + int connect(char* address, uint16_t port); + int createSocket(bool isDatagram = false); + void setReuseAddress(bool value); + int setSocketOption(int option, void* value, size_t len); + int setTimeout(uint32_t seconds); + void getBind(struct sockaddr* pAddr); + int getFD() const; + bool getSSL() const; + bool isValid(); + int listen(uint16_t port, bool isDatagram = false, bool reuseAddress = false); + bool operator<(const Socket& other) const; + std::string readToDelim(std::string delim); + size_t receive(uint8_t* data, size_t length, bool exact = false); + int receiveFrom(uint8_t* data, size_t length, struct sockaddr* pAddr); + int send(std::string value) const; + int send(const uint8_t* data, size_t length) const; + int send(uint16_t value); + int send(uint32_t value); + void sendTo(const uint8_t* data, size_t length, struct sockaddr* pAddr); + void setSSL(bool sslValue = true); + std::string toString(); + private: - int m_sock; + int m_sock; // The underlying TCP/IP socket + bool m_useSSL; // Should we use SSL + mbedtls_net_context m_sslSock; + mbedtls_entropy_context m_entropy; + mbedtls_ctr_drbg_context m_ctr_drbg; + mbedtls_ssl_context m_sslContext; + mbedtls_ssl_config m_conf; + mbedtls_x509_crt m_srvcert; + mbedtls_pk_context m_pkey; + void sslHandshake(); + }; +class SocketInputRecordStreambuf : public std::streambuf { +public: + SocketInputRecordStreambuf(Socket socket, size_t dataLength, size_t bufferSize = 512); + ~SocketInputRecordStreambuf(); + int_type underflow(); + +private: + char* m_buffer; + Socket m_socket; + size_t m_dataLength; + size_t m_bufferSize; + size_t m_sizeRead; + +}; + + #endif /* COMPONENTS_CPP_UTILS_SOCKET_H_ */ diff --git a/cpp_utils/System.cpp b/cpp_utils/System.cpp index 21a0b9bc..2e24f88b 100644 --- a/cpp_utils/System.cpp +++ b/cpp_utils/System.cpp @@ -7,6 +7,126 @@ #include "System.h" #include +#include +#include + +extern "C" { +#include +} + +typedef volatile struct { + union { + struct { + uint32_t mcu_oe: 1; + uint32_t slp_sel: 1; + uint32_t mcu_wpd: 1; + uint32_t mcu_wpu: 1; + uint32_t mcu_ie: 1; + uint32_t mcu_drb: 2; + uint32_t func_wpd: 1; + uint32_t func_wpu: 1; + uint32_t func_ie: 1; + uint32_t func_drv: 2; + uint32_t mcu_sel: 3; + uint32_t reserved15: 17; + }; + uint32_t val; + }; +} io_mux_reg_t; + + +typedef volatile struct { + union { + struct { + uint32_t clk1: 4; + uint32_t clk2: 4; + uint32_t clk3: 4; + uint32_t reserved12: 20; + }; + uint32_t val; + } pin_ctrl; + + // The 36 exposed pads. + io_mux_reg_t pad_gpio36; // GPIO36 + io_mux_reg_t pad_gpio37; // GPIO37 + io_mux_reg_t pad_gpio38; // GPIO38 + io_mux_reg_t pad_gpio39; // GPIO39 + io_mux_reg_t pad_gpio34; // GPIO34 + io_mux_reg_t pad_gpio35; // GPIO35 + io_mux_reg_t pad_gpio32; // GPIO32 + io_mux_reg_t pad_gpio33; // GPIO33 + io_mux_reg_t pad_gpio25; // GPIO25 + io_mux_reg_t pad_gpio26; // GPIO26 + io_mux_reg_t pad_gpio27; // GPIO27 + io_mux_reg_t pad_mtms; // GPIO14 + io_mux_reg_t pad_mtdi; // GPIO12 + io_mux_reg_t pad_mtck; // GPIO13 + io_mux_reg_t pad_mtdo; // GPIO15 + io_mux_reg_t pad_gpio2; // GPIO2 + io_mux_reg_t pad_gpio0; // GPIO0 + io_mux_reg_t pad_gpio4; // GPIO4 + io_mux_reg_t pad_gpio16; // GPIO16 + io_mux_reg_t pad_gpio17; // GPIO17 + io_mux_reg_t pad_sd_data2; // GPIO9 + io_mux_reg_t pad_sd_data3; // GPIO10 + io_mux_reg_t pad_sd_cmd; // GPIO11 + io_mux_reg_t pad_sd_clk; // GPIO6 + io_mux_reg_t pad_sd_data0; // GPIO7 + io_mux_reg_t pad_sd_data1; // GPIO8 + io_mux_reg_t pad_gpio5; // GPIO5 + io_mux_reg_t pad_gpio18; // GPIO18 + io_mux_reg_t pad_gpio19; // GPIO19 + io_mux_reg_t pad_gpio20; // GPIO20 + io_mux_reg_t pad_gpio21; // GPIO21 + io_mux_reg_t pad_gpio22; // GPIO22 + io_mux_reg_t pad_u0rxd; // GPIO3 + io_mux_reg_t pad_u0txd; // GPIO1 + io_mux_reg_t pad_gpio23; // GPIO23 + io_mux_reg_t pad_gpio24; // GPIO24 +} io_mux_dev_t; + +static io_mux_dev_t* IO_MUX = (io_mux_dev_t*) 0x3ff49000; + +static const io_mux_reg_t* io_mux_translate[] = { + &IO_MUX->pad_gpio0, // 0 + &IO_MUX->pad_u0txd, // 1 + &IO_MUX->pad_gpio2, // 2 + &IO_MUX->pad_u0rxd, // 3 + &IO_MUX->pad_gpio4, // 4 + &IO_MUX->pad_gpio5, // 5 + &IO_MUX->pad_sd_clk, // 6 + &IO_MUX->pad_sd_data0, // 7 + &IO_MUX->pad_sd_data1, // 8 + &IO_MUX->pad_sd_data2, // 9 + &IO_MUX->pad_sd_data3, // 10 + &IO_MUX->pad_sd_cmd, // 11 + &IO_MUX->pad_mtdi, // 12 + &IO_MUX->pad_mtck, // 13 + &IO_MUX->pad_mtms, // 14 + &IO_MUX->pad_mtdo, // 15 + &IO_MUX->pad_gpio16, // 16 + &IO_MUX->pad_gpio17, // 17 + &IO_MUX->pad_gpio18, // 18 + &IO_MUX->pad_gpio19, // 19 + &IO_MUX->pad_gpio20, // 20 + &IO_MUX->pad_gpio21, // 21 + nullptr, // 28 + nullptr, // 29 + nullptr, // 30 + nullptr, // 31 + &IO_MUX->pad_gpio32, // 32 + &IO_MUX->pad_gpio33, // 33 + &IO_MUX->pad_gpio34, // 34 + &IO_MUX->pad_gpio35, // 35 + &IO_MUX->pad_gpio36, // 36 + &IO_MUX->pad_gpio37, // 37 + &IO_MUX->pad_gpio38, // 38 + &IO_MUX->pad_gpio39 // 39 +}; + +static const io_mux_reg_t* gpioToIoMux(int gpio) { + return io_mux_translate[gpio]; +} System::System() { // TODO Auto-generated constructor stub @@ -17,12 +137,323 @@ System::~System() { // TODO Auto-generated destructor stub } +const static char* outSignalStrings[] = { + "SPICLK_out", // 0 + "SPIQ_out", // 1 + "SPID_out", // 2 + "SPIHD_out", //3 + "SPIWP_out", // 4 + "SPICS0_out", // 5 + "SPICS1_out", // 6 + "SPICS2_out", // 7 + "HSPICLK_out", // 8 + "HSPIQ_out", // 9 + "HSPID_out", // 10 + "HSPICS0_out", // 11 + "HSPIHD_out", // 12 + "HSPIWP_out", // 13 + "U0TXD_out", // 14 + "U0RTS_out", // 15 + "U0DTR_out", // 16 + "U1TXD_out", // 17 + "U1RTS_out", // 18 + "", // 19 + "", // 20 + "", // 21 + "", // 22 + "I2S0O_BCK_out", // 23 + "I2S1O_BCK_out", // 24 + "I2S0O_WS_out", // 25 + "I2S1O_WS_out", // 26 + "I2S0I_BCK_out", // 27 + "I2S0I_WS_out", // 28 + "I2CEXT0_SCL_out", // 29 + "I2CEXT0_SDA_out", // 30 + "sdio_tohost_int_out", // 31 + "pwm0_out0a", // 32 + "pwm0_out0b", // 33 + "pwm0_out1a", // 34 + "pwm0_out1b", // 35 + "pwm0_out2a", // 36 + "pwm0_out2b", //37 + "", // 38 + "", // 39 + "", // 40 + "", // 41 + "", // 42 + "", // 43 + "", // 44 + "", // 45 + "", // 46 + "", // 47 + "", // 48 + "", // 49 + "", // 50 + "", // 51 + "", // 52 + "", // 53 + "", // 54 + "", // 55 + "", // 56 + "", // 57 + "", // 58 + "", // 59 + "", // 60 + "HSPICS1_out", // 61 + "HSPICS2_out", // 62 + "VSPICLK_out_mux", // 63 + "VSPIQ_out", // 64 + "VSPID_out", // 65 + "VSPIHD_out", // 66 + "VSPIWP_out", // 67 + "VSPICS0_out", // 68 + "VSPICS1_out", // 69 + "VSPICS2_out", // 70 + "ledc_hs_sig_out0", // 71 + "ledc_hs_sig_out1", // 72 + "ledc_hs_sig_out2", // 73 + "ledc_hs_sig_out3", // 74 + "ledc_hs_sig_out4", // 75 + "ledc_hs_sig_out5", // 76 + "ledc_hs_sig_out6", // 77 + "ledc_hs_sig_out7", // 78 + "edc_ls_sig_out0", // 79 + "ledc_ls_sig_out1", // 80 + "ledc_ls_sig_out2", // 81 + "ledc_ls_sig_out3", // 82 + "ledc_ls_sig_out4", // 83 + "ledc_ls_sig_out5", // 84 + "ledc_ls_sig_out6", // 85 + "ledc_ls_sig_out7", // 86 + "rmt_sig_out0", // 87 + "rmt_sig_out1", // 88 + "rmt_sig_out2", // 89 + "rmt_sig_out3", // 90 + "rmt_sig_out4", // 91 + "rmt_sig_out5", // 92 + "rmt_sig_out6", // 93 + "rmt_sig_out7", // 94 + "I2CEXT1_SCL_out", // 95 + "I2CEXT1_SCL_out", // 96 + "host_ccmd_od_pullup_en_n", // 97 + "host_rst_n_1", // 98 + "host_rst_n_2", // 99 + "gpio_sd0_out", // 100 + "gpio_sd1_out", // 101 + "gpio_sd2_out", // 102 + "gpio_sd3_out", // 103 + "gpio_sd4_out", // 104 + "gpio_sd5_out", // 105 + "gpio_sd6_out", // 106 + "gpio_sd7_out", // 107 + "pwm1_out0a", // 108 + "pwm1_out0b", // 109 + "pwm1_out1a", // 110 + "pwm1_out1b", // 111 + "pwm1_out2a", // 112 + "pwm1_out2b", // 113 + "pwm2_out1h", // 114 + "pwm2_out1l", // 115 + "pwm2_out2h", // 116 + "pwm2_out2l", // 117 + "pwm2_out3h", // 118 + "pwm2_out3l", // 119 + "pwm2_out4h", // 120 + "pwm2_out4l", // 121 + "", // 122 + "", // 123 + "", // 124 + "", // 125 + "", // 126 + "", // 127 + "", // 128 + "", // 129 + "", // 130 + "", // 131 + "", // 132 + "", // 133 + "", // 134 + "", // 135 + "", // 136 + "", // 137 + "", // 138 + "", // 139 + "I2S0O_DATA_out0", // 140 + "I2S0O_DATA_out1", // 141 + "I2S0O_DATA_out2", // 142 + "I2S0O_DATA_out3", // 143 + "I2S0O_DATA_out4", // 144 + "I2S0O_DATA_out5", // 145 + "I2S0O_DATA_out6", // 146 + "I2S0O_DATA_out7", // 147 + "I2S0O_DATA_out8", // 148 + "I2S0O_DATA_out9", // 149 + "I2S0O_DATA_out10", // 150 + "I2S0O_DATA_out11", // 151 + "I2S0O_DATA_out12", // 152 + "I2S0O_DATA_out13", // 153 + "I2S0O_DATA_out14", // 154 + "I2S0O_DATA_out15", // 155 + "I2S0O_DATA_out16", // 156 + "I2S0O_DATA_out17", // 157 + "I2S0O_DATA_out18", // 158 + "I2S0O_DATA_out19", // 159 + "I2S0O_DATA_out20", // 160 + "I2S0O_DATA_out21", // 161 + "I2S0O_DATA_out22", // 162 + "I2S0O_DATA_out23", // 163 + "I2S1I_BCK_out", // 164 + "I2S1I_WS_out", // 165 + "I2S1O_DATA_out0", // 166 + "I2S1O_DATA_out1", // 167 + "I2S1O_DATA_out2", // 168 + "I2S1O_DATA_out3", // 169 + "I2S1O_DATA_out4", // 170 + "I2S1O_DATA_out5", // 171 + "I2S1O_DATA_out6", // 172 + "I2S1O_DATA_out7", // 173 + "I2S1O_DATA_out8", // 174 + "I2S1O_DATA_out9", // 175 + "I2S1O_DATA_out10", // 176 + "I2S1O_DATA_out11", // 177 + "I2S1O_DATA_out12", // 178 + "I2S1O_DATA_out13", // 179 + "I2S1O_DATA_out14", // 180 + "I2S1O_DATA_out15", // 181 + "I2S1O_DATA_out16", // 182 + "I2S1O_DATA_out17", // 183 + "I2S1O_DATA_out18", // 184 + "I2S1O_DATA_out19", // 185 + "I2S1O_DATA_out20", // 186 + "I2S1O_DATA_out21", // 187 + "I2S1O_DATA_out22", // 188 + "I2S1O_DATA_out23", // 189 + "pwm3_out1h", // 190 + "pwm3_out1l", // 191 + "pwm3_out2h", // 192 + "pwm3_out2l", // 193 + "pwm3_out3h", // 194 + "pwm3_out3l", // 195 + "pwm3_out4h", // 196 + "pwm3_out4l", // 197 + "U2TXD_out", // 198 + "U2RTS_out", // 199 + "emac_mdc_o", // 200 + "emac_mdo_o", // 201 + "emac_crs_o", // 202 + "emac_col_o", // 203 + "bt_audio0_irq", // 204 + "bt_audio1_irq", // 205 + "bt_audio2_irq", // 206 + "ble_audio0_irq", // 207 + "ble_audio1_irq", // 208 + "ble_audio2_irq", // 209 + "pcmfsync_out", // 210 + "pcmclk_out", // 211 + "pcmdout", // 212 + "ble_audio_sync0_p", // 213 + "ble_audio_sync1_p", // 214 + "ble_audio_sync2_p", // 215 + "", // 216 + "", // 217 + "", // 218 + "", // 219 + "", // 220 + "", // 221 + "", // 222 + "", // 223 + "sig_in_func224", // 224 + "sig_in_func225", // 225 + "sig_in_func226", // 226 + "sig_in_func227", // 227 + "sig_in_func228", // 228 + "", // 229 + "", // 230 + "", // 231 + "", // 232 + "", // 233 + "", // 234 + "", // 235 + "", // 236 + "", // 237 + "", // 238 + "", // 239 + "", // 240 + "", // 241 + "", // 242 + "", // 243 + "", // 244 + "", // 245 + "", // 246 + "", // 247 + "", // 248 + "", // 249 + "", // 250 + "", // 251 + "", // 252 + "", // 253 + "", // 254 + "", // 255 +}; + + +/** + * Dump the mappings for GPIO pins. + */ +/* static */ void System::dumpPinMapping() { + const int numPins = 40; + printf("GPIO_FUNCn_OUT_SEL_CFG_REG\n"); + printf("--------------------------\n"); + printf("%3s %4s\n", "Pin", "Func"); + for (uint8_t i = 0; i < numPins; i++) { + const char *signal; + if (GPIO.func_out_sel_cfg[i].func_sel == 256) { + signal = (char*) "[GPIO]"; + } else if (GPIO.func_out_sel_cfg[i].func_sel == 257) { + signal = (char*) "N/A"; + } else { + signal = outSignalStrings[GPIO.func_out_sel_cfg[i].func_sel]; + } + printf("%2d %4d %s\n", i, GPIO.func_out_sel_cfg[i].func_sel, signal); + const io_mux_reg_t* io_mux = gpioToIoMux(i); + if (GPIO.func_out_sel_cfg[i].func_sel == 256 && io_mux != nullptr) { + printf("0x%x - function: %d, ie: %d\n", (uint32_t) io_mux, io_mux->mcu_sel + 1, io_mux->func_ie); + } + } + +} // System#dumpPinMapping + + +/** + * Dump the storage stats for the heap. + */ +/* static */ void System::dumpHeapInfo() { + multi_heap_info_t heapInfo; + + printf(" %10s %10s %10s %10s %13s %11s %12s\n", "Free", "Allocated", "Largest", "Minimum", "Alloc Blocks", "Free Blocks", "Total Blocks"); + heap_caps_get_info(&heapInfo, MALLOC_CAP_EXEC); + printf("EXEC %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_32BIT); + printf("32BIT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_8BIT); + printf("8BIT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_DMA); + printf("DMA %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_SPIRAM); + printf("SPISRAM %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_INTERNAL); + printf("INTERNAL %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); + heap_caps_get_info(&heapInfo, MALLOC_CAP_DEFAULT); + printf("DEFAULT %10d %10d %10d %10d %13d %11d %12d\n", heapInfo.total_free_bytes, heapInfo.total_allocated_bytes, heapInfo.largest_free_block, heapInfo.minimum_free_bytes, heapInfo.allocated_blocks, heapInfo.free_blocks, heapInfo.total_blocks); +} // System#dumpHeapInfo + + /** * @brief Get the information about the device. * @param [out] info The structure to be populated on return. * @return N/A. */ -void System::getChipInfo(esp_chip_info_t *info) { +void System::getChipInfo(esp_chip_info_t* info) { ::esp_chip_info(info); } // getChipInfo @@ -31,8 +462,8 @@ void System::getChipInfo(esp_chip_info_t *info) { * @brief Retrieve the system wide free heap size. * @return The system wide free heap size. */ -uint32_t System::getFreeHeapSize() { - return esp_get_free_heap_size(); +size_t System::getFreeHeapSize() { + return heap_caps_get_free_size(MALLOC_CAP_8BIT); } // getFreeHeapSize @@ -46,3 +477,18 @@ std::string System::getIDFVersion() { } // getIDFVersion +/** + * @brief Get the smallest heap size seen. + * @return The smallest heap size seen. + */ +size_t System::getMinimumFreeHeapSize() { + return heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT); +} // getMinimumFreeHeapSize + + +/** + * @brief Restart the ESP32. + */ +void System::restart() { + esp_restart(); +} // restart diff --git a/cpp_utils/System.h b/cpp_utils/System.h index 4c084300..a3bed334 100644 --- a/cpp_utils/System.h +++ b/cpp_utils/System.h @@ -18,9 +18,13 @@ class System { public: System(); virtual ~System(); - static void getChipInfo(esp_chip_info_t *info); - static uint32_t getFreeHeapSize(); + static void dumpPinMapping(); // Dump the mappings of pins to functions. + static void dumpHeapInfo(); + static void getChipInfo(esp_chip_info_t* info); + static size_t getFreeHeapSize(); static std::string getIDFVersion(); + static size_t getMinimumFreeHeapSize(); + static void restart(); }; #endif /* COMPONENTS_CPP_UTILS_SYSTEM_H_ */ diff --git a/cpp_utils/TFTP.cpp b/cpp_utils/TFTP.cpp index afd5cffe..b1591a9f 100644 --- a/cpp_utils/TFTP.cpp +++ b/cpp_utils/TFTP.cpp @@ -9,17 +9,24 @@ #include "TFTP.h" #include -#include -#include +#include "FreeRTOS.h" +#include "GeneralUtils.h" #include #include #include #include -#include +#include "Socket.h" #include "sdkconfig.h" -static char tag[] = "TFTP"; +extern "C" { + extern uint16_t lwip_ntohs(uint16_t); + extern uint32_t lwip_ntohl(uint32_t); + extern uint16_t lwip_htons(uint16_t); + extern uint32_t lwip_htonl(uint32_t); +} + +static const char* LOG_TAG = "TFTP"; enum opcode { TFTP_OPCODE_RRQ = 1, // Read request @@ -43,7 +50,7 @@ enum ERRORCODE { /** * Size of the TFTP data payload. */ -const int TFTP_DATA_SIZE=512; +const int TFTP_DATA_SIZE = 512; struct data_packet { uint16_t blockNumber; @@ -84,15 +91,15 @@ void TFTP::TFTP_Transaction::processRRQ() { * ---------------------------------- * */ - FILE *file; + FILE* file; bool finished = false; - ESP_LOGD(tag, "Reading TFTP data from file: %s", m_filename.c_str()); + ESP_LOGD(LOG_TAG, "Reading TFTP data from file: %s", m_filename.c_str()); std::string tmpName = m_baseDir + "/" + m_filename; /* struct stat buf; if (stat(tmpName.c_str(), &buf) != 0) { - ESP_LOGE(tag, "Stat file: %s: %s", tmpName.c_str(), strerror(errno)); + ESP_LOGE(LOG_TAG, "Stat file: %s: %s", tmpName.c_str(), strerror(errno)); return; } int length = buf.st_size; @@ -102,23 +109,28 @@ void TFTP::TFTP_Transaction::processRRQ() { file = fopen(tmpName.c_str(), "r"); if (file == nullptr) { - ESP_LOGE(tag, "Failed to open file for reading: %s: %s", tmpName.c_str(), strerror(errno)); + ESP_LOGE(LOG_TAG, "Failed to open file for reading: %s: %s", tmpName.c_str(), strerror(errno)); sendError(ERROR_CODE_FILE_NOT_FOUND, tmpName); return; } - uint8_t buf[TFTP_DATA_SIZE + 2 + 2]; // Buffer data size is packet size (512) + 2 bytes for opcode + 2 bytes for blocknumber. + struct { + uint16_t opCode; + uint16_t blockNumber; + uint8_t buf[TFTP_DATA_SIZE]; + } record; - *(uint16_t *)(&buf[0]) = htons(TFTP_OPCODE_DATA); // Set the op code to be DATA. - while(!finished) { - *(uint16_t *)(&buf[2]) = htons(blockNumber); + record.opCode = htons(TFTP_OPCODE_DATA); // Set the op code to be DATA. - int sizeRead = fread(&buf[4], 1, TFTP_DATA_SIZE, file); + while (!finished) { + record.blockNumber = htons(blockNumber); - ESP_LOGD(tag, "Sending data to %s, blockNumber=%d, size=%d", + int sizeRead = fread(record.buf, 1, TFTP_DATA_SIZE, file); + + ESP_LOGD(LOG_TAG, "Sending data to %s, blockNumber=%d, size=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber, sizeRead); - m_partnerSocket.sendTo_cpp(buf, sizeRead+4, &m_partnerAddress); + m_partnerSocket.sendTo((uint8_t*) &record, sizeRead + 4, &m_partnerAddress); if (sizeRead < TFTP_DATA_SIZE) { @@ -128,7 +140,7 @@ void TFTP::TFTP_Transaction::processRRQ() { } blockNumber++; // Increment the block number. } - ESP_LOGD(tag, "File sent"); + ESP_LOGD(LOG_TAG, "File sent"); } // processRRQ @@ -154,33 +166,33 @@ void TFTP::TFTP_Transaction::processWRQ() { uint8_t dataBuffer[TFTP_DATA_SIZE + 2 + 2]; bool finished = false; - FILE *file; + FILE* file; - ESP_LOGD(tag, "Writing TFTP data to file: %s", m_filename.c_str()); + ESP_LOGD(LOG_TAG, "Writing TFTP data to file: %s", m_filename.c_str()); std::string tmpName = m_baseDir + "/" + m_filename; file = fopen(tmpName.c_str(), "w"); if (file == nullptr) { - ESP_LOGE(tag, "Failed to open file for writing: %s: %s", tmpName.c_str(), strerror(errno)); + ESP_LOGE(LOG_TAG, "Failed to open file for writing: %s: %s", tmpName.c_str(), strerror(errno)); return; } while(!finished) { - pRecv_data = (struct recv_data *)dataBuffer; - int receivedSize = m_partnerSocket.receiveFrom_cpp(dataBuffer, sizeof(dataBuffer), &recvAddr); + pRecv_data = (struct recv_data*) dataBuffer; + int receivedSize = m_partnerSocket.receiveFrom(dataBuffer, sizeof(dataBuffer), &recvAddr); if (receivedSize == -1) { - ESP_LOGE(tag, "rc == -1 from receive_from"); + ESP_LOGE(LOG_TAG, "rc == -1 from receive_from"); } struct data_packet dp; dp.blockNumber = ntohs(pRecv_data->blockNumber); - dp.data = std::string((char *)&pRecv_data->data, receivedSize-4); + dp.data = std::string((char*) &pRecv_data->data, receivedSize - 4); fwrite(dp.data.data(), dp.data.length(), 1, file); sendAck(dp.blockNumber); - ESP_LOGD(tag, "Block size: %d", dp.data.length()); + ESP_LOGD(LOG_TAG, "Block size: %d", dp.data.length()); if (dp.data.length() < TFTP_DATA_SIZE) { finished = true; } } // Finished fclose(file); - m_partnerSocket.close_cpp(); + m_partnerSocket.close(); } // process @@ -200,8 +212,8 @@ void TFTP::TFTP_Transaction::sendAck(uint16_t blockNumber) { ackData.opCode = htons(TFTP_OPCODE_ACK); ackData.blockNumber = htons(blockNumber); - ESP_LOGD(tag, "Sending ack to %s, blockNumber=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber); - m_partnerSocket.sendTo_cpp((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); + ESP_LOGD(LOG_TAG, "Sending ack to %s, blockNumber=%d", Socket::addressToString(&m_partnerAddress).c_str(), blockNumber); + m_partnerSocket.sendTo((uint8_t*) &ackData, sizeof(ackData), &m_partnerAddress); } // sendAck @@ -220,26 +232,29 @@ void TFTP::start(uint16_t port) { * or write a file to the server. Once we have received a request we then call the appropriate * handler to handle that type of request. When the request has been completed, we start again. */ - ESP_LOGD(tag, "Starting TFTP::start() on port %d", port); + ESP_LOGD(LOG_TAG, "Starting TFTP::start() on port %d", port); Socket serverSocket; - serverSocket.listen_cpp(port, true); // Create a listening socket that is a datagram. - while(true) { + serverSocket.listen(port, true); // Create a listening socket that is a datagram. + while (true) { // This would be a good place to start a transaction in the background. - TFTP_Transaction *pTFTPTransaction = new TFTP_Transaction(); + TFTP_Transaction* pTFTPTransaction = new TFTP_Transaction(); pTFTPTransaction->setBaseDir(m_baseDir); uint16_t receivedOpCode = pTFTPTransaction->waitForRequest(&serverSocket); - switch(receivedOpCode) { - // Handle the write request (client file upload) + switch (receivedOpCode) { + // Handle the write request (client file upload) case opcode::TFTP_OPCODE_WRQ: { pTFTPTransaction->processWRQ(); break; } - // Handle the read request (server file download) + // Handle the read request (server file download) case opcode::TFTP_OPCODE_RRQ: { pTFTPTransaction->processRRQ(); break; } + default: + ESP_LOGE(LOG_TAG, "Unknown opcode: %d", receivedOpCode); + break; } delete pTFTPTransaction; } // End while loop @@ -279,12 +294,12 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { uint16_t blockNumber; } ackData; - ESP_LOGD(tag, "TFTP: Waiting for an acknowledgment request"); - int sizeRead = m_partnerSocket.receiveFrom_cpp((uint8_t *)&ackData, sizeof(ackData), &m_partnerAddress); - ESP_LOGD(tag, "TFTP: Received some data."); + ESP_LOGD(LOG_TAG, "TFTP: Waiting for an acknowledgment request"); + int sizeRead = m_partnerSocket.receiveFrom((uint8_t*) &ackData, sizeof(ackData), &m_partnerAddress); + ESP_LOGD(LOG_TAG, "TFTP: Received some data."); if (sizeRead != sizeof(ackData)) { - ESP_LOGE(tag, "waitForAck: Received %d but expected %d", sizeRead, sizeof(ackData)); + ESP_LOGE(LOG_TAG, "waitForAck: Received %d but expected %d", sizeRead, sizeof(ackData)); sendError(ERROR_CODE_NOTDEFINED, "Ack not correct size"); return; } @@ -293,12 +308,12 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { ackData.blockNumber = ntohs(ackData.blockNumber); if (ackData.opCode != opcode::TFTP_OPCODE_ACK) { - ESP_LOGE(tag, "waitForAck: Received opcode %d but expected %d", ackData.opCode, opcode::TFTP_OPCODE_ACK); + ESP_LOGE(LOG_TAG, "waitForAck: Received opcode %d but expected %d", ackData.opCode, opcode::TFTP_OPCODE_ACK); return; } if (ackData.blockNumber != blockNumber) { - ESP_LOGE(tag, "waitForAck: Blocknumber received %d but expected %d", ackData.blockNumber, blockNumber); + ESP_LOGE(LOG_TAG, "waitForAck: Blocknumber received %d but expected %d", ackData.blockNumber, blockNumber); return; } } // waitForAck @@ -313,44 +328,45 @@ void TFTP::TFTP_Transaction::waitForAck(uint16_t blockNumber) { * @param pServerSocket The server socket on which to listen for client requests. * @return The op code received. */ -uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket *pServerSocket) { /* * 2 bytes string 1 byte string 1 byte * ----------------------------------------------- * RRQ/ | 01/02 | Filename | 0 | Mode | 0 | * WRQ ----------------------------------------------- */ - uint8_t buf[TFTP_DATA_SIZE]; +uint16_t TFTP::TFTP_Transaction::waitForRequest(Socket* pServerSocket) { + union { + uint8_t buf[TFTP_DATA_SIZE]; + uint16_t opCode; + } record; size_t length = 100; - ESP_LOGD(tag, "TFTP: Waiting for a request"); - pServerSocket->receiveFrom_cpp(buf, length, &m_partnerAddress); + ESP_LOGD(LOG_TAG, "TFTP: Waiting for a request"); + pServerSocket->receiveFrom(record.buf, length, &m_partnerAddress); // Save the filename, mode and op code. - m_filename = std::string((char *)(buf+2)); - m_mode = std::string((char *)(buf + 3 + m_filename.length())); - m_opCode = ntohs(*(uint16_t *)buf); - switch(m_opCode) { - + m_filename = std::string((char*) (record.buf + 2)); + m_mode = std::string((char*) (record.buf + 3 + m_filename.length())); + m_opCode = ntohs(record.opCode); + switch (m_opCode) { // Handle the Write Request command. case TFTP_OPCODE_WRQ: { - m_partnerSocket.createSocket_cpp(true); - m_partnerSocket.bind_cpp(0, INADDR_ANY); + m_partnerSocket.createSocket(true); + m_partnerSocket.bind(0, INADDR_ANY); sendAck(0); break; } - // Handle the Read request command. case TFTP_OPCODE_RRQ: { - m_partnerSocket.createSocket_cpp(true); - m_partnerSocket.bind_cpp(0, INADDR_ANY); + m_partnerSocket.createSocket(true); + m_partnerSocket.bind(0, INADDR_ANY); break; } default: { - ESP_LOGD(tag, "Un-handled opcode: %d", m_opCode); + ESP_LOGD(LOG_TAG, "Un-handled opcode: %d", m_opCode); break; } } @@ -371,10 +387,10 @@ void TFTP::TFTP_Transaction::sendError(uint16_t code, std::string message) { * ----------------------------------------- */ int size = 2 + 2 + message.length() + 1; - uint8_t *buf = (uint8_t *)malloc(size); - *(uint16_t *)(&buf[0]) = htons(opcode::TFTP_OPCODE_ERROR); - *(uint16_t *)(&buf[2]) = htons(code); - strcpy((char *)(&buf[4]), message.c_str()); - m_partnerSocket.sendTo_cpp(buf, size, &m_partnerAddress); + uint8_t* buf = (uint8_t*) malloc(size); + *(uint16_t*) (&buf[0]) = htons(opcode::TFTP_OPCODE_ERROR); + *(uint16_t*) (&buf[2]) = htons(code); + strcpy((char*) (&buf[4]), message.c_str()); + m_partnerSocket.sendTo(buf, size, &m_partnerAddress); free(buf); } // sendError diff --git a/cpp_utils/TFTP.h b/cpp_utils/TFTP.h index 3f40bd2b..56733598 100644 --- a/cpp_utils/TFTP.h +++ b/cpp_utils/TFTP.h @@ -36,7 +36,7 @@ class TFTP { public: TFTP(); virtual ~TFTP(); - void start(uint16_t port=TFTP_DEFAULT_PORT); + void start(uint16_t port = TFTP_DEFAULT_PORT); void setBaseDir(std::string baseDir); /** * @brief Internal class for %TFTP processing. @@ -50,7 +50,8 @@ class TFTP { void sendError(uint16_t code, std::string message); void setBaseDir(std::string baseDir); void waitForAck(uint16_t blockNumber); - uint16_t waitForRequest(Socket *pServerSocket); + uint16_t waitForRequest(Socket* pServerSocket); + private: /** * Socket on which the server will communicate with the client.. @@ -61,9 +62,12 @@ class TFTP { std::string m_filename; // The name of the file. std::string m_mode; std::string m_baseDir; // The base directory. + }; + private: std::string m_baseDir; + }; #endif /* COMPONENTS_CPP_UTILS_TFTP_H_ */ diff --git a/cpp_utils/Task.cpp b/cpp_utils/Task.cpp index dba7a760..43f587e2 100644 --- a/cpp_utils/Task.cpp +++ b/cpp_utils/Task.cpp @@ -14,7 +14,7 @@ #include "Task.h" #include "sdkconfig.h" -static char tag[] = "Task"; +static const char* LOG_TAG = "Task"; /** @@ -24,10 +24,13 @@ static char tag[] = "Task"; * @param [in] stackSize The size of the stack. * @return N/A. */ -Task::Task(std::string taskName, uint16_t stackSize) { +Task::Task(std::string taskName, uint16_t stackSize, uint8_t priority) { + m_taskName = taskName; m_stackSize = stackSize; + m_priority = priority; m_taskData = nullptr; m_handle = nullptr; + m_coreId = tskNO_AFFINITY; } // Task Task::~Task() { @@ -40,8 +43,8 @@ Task::~Task() { * @return N/A. */ -void Task::delay(int ms) { - ::vTaskDelay(ms/portTICK_PERIOD_MS); +/* static */ void Task::delay(int ms) { + ::vTaskDelay(ms / portTICK_PERIOD_MS); } // delay /** @@ -51,9 +54,10 @@ void Task::delay(int ms) { * @param [in] pTaskInstance The task to run. */ void Task::runTask(void* pTaskInstance) { - ESP_LOGD(tag, ">> runTask"); - Task* pTask = (Task*)pTaskInstance; + Task* pTask = (Task*) pTaskInstance; + ESP_LOGD(LOG_TAG, ">> runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->run(pTask->m_taskData); + ESP_LOGD(LOG_TAG, "<< runTask: taskName=%s", pTask->m_taskName.c_str()); pTask->stop(); } // runTask @@ -65,10 +69,10 @@ void Task::runTask(void* pTaskInstance) { */ void Task::start(void* taskData) { if (m_handle != nullptr) { - ESP_LOGW(tag, "Task::start - There might be a task already running!"); + ESP_LOGW(LOG_TAG, "Task::start - There might be a task already running!"); } m_taskData = taskData; - ::xTaskCreate(&runTask, m_taskName.c_str(), m_stackSize, this, 5, &m_handle); + ::xTaskCreatePinnedToCore(&runTask, m_taskName.c_str(), m_stackSize, this, m_priority, &m_handle, m_coreId); } // start @@ -78,9 +82,7 @@ void Task::start(void* taskData) { * @return N/A. */ void Task::stop() { - if (m_handle == nullptr) { - return; - } + if (m_handle == nullptr) return; xTaskHandle temp = m_handle; m_handle = nullptr; ::vTaskDelete(temp); @@ -95,3 +97,34 @@ void Task::stop() { void Task::setStackSize(uint16_t stackSize) { m_stackSize = stackSize; } // setStackSize + +/** + * @brief Set the priority of the task. + * + * @param [in] priority The priority for the task. + * @return N/A. + */ +void Task::setPriority(uint8_t priority) { + m_priority = priority; +} // setPriority + +/** + * @brief Set the name of the task. + * + * @param [in] name The name for the task. + * @return N/A. + */ +void Task::setName(std::string name) { + m_taskName = name; +} // setName + +/** + * @brief Set the core number the task has to be executed on. + * If the core number is not set, tskNO_AFFINITY will be used + * + * @param [in] coreId The id of the core. + * @return N/A. + */ +void Task::setCore(BaseType_t coreId) { + m_coreId = coreId; +} diff --git a/cpp_utils/Task.h b/cpp_utils/Task.h index 51e18fc8..5eb4966b 100644 --- a/cpp_utils/Task.h +++ b/cpp_utils/Task.h @@ -33,10 +33,13 @@ */ class Task { public: - Task(std::string taskName="Task", uint16_t stackSize=10000); + Task(std::string taskName = "Task", uint16_t stackSize = 10000, uint8_t priority = 5); virtual ~Task(); void setStackSize(uint16_t stackSize); - void start(void* taskData=nullptr); + void setPriority(uint8_t priority); + void setName(std::string name); + void setCore(BaseType_t coreId); + void start(void* taskData = nullptr); void stop(); /** * @brief Body of the task to execute. @@ -47,15 +50,18 @@ class Task { * * @param [in] data The data passed in to the newly started task. */ - virtual void run(void *data) = 0; // Make run pure virtual - void delay(int ms); + virtual void run(void* data) = 0; // Make run pure virtual + static void delay(int ms); private: xTaskHandle m_handle; void* m_taskData; - static void runTask(void *data); + static void runTask(void* data); std::string m_taskName; uint16_t m_stackSize; + uint8_t m_priority; + BaseType_t m_coreId; + }; #endif /* COMPONENTS_CPP_UTILS_TASK_H_ */ diff --git a/cpp_utils/U8G2.h b/cpp_utils/U8G2.h index db57ba64..386272fc 100644 --- a/cpp_utils/U8G2.h +++ b/cpp_utils/U8G2.h @@ -24,7 +24,7 @@ class U8G2 { u8g2_ClearBuffer(&m_u8g2); } - void drawBitmap(uint32_t x, uint32_t y, uint32_t cnt, uint32_t h, const uint8_t *bitmap) { + void drawBitmap(uint32_t x, uint32_t y, uint32_t cnt, uint32_t h, const uint8_t* bitmap) { u8g2_DrawBitmap(&m_u8g2, x, y, cnt, h, bitmap); } @@ -75,7 +75,6 @@ class U8G2 { u8g2_DrawRFrame(&m_u8g2, x, y, w, h, r); } - uint32_t drawStr(uint32_t x, uint32_t y, std::string s) { return u8g2_DrawStr(&m_u8g2, x, y, s.c_str()); } @@ -92,11 +91,11 @@ class U8G2 { u8g2_DrawVLine(&m_u8g2, x, y, h); } - int8_t getAscent(void) { + int8_t getAscent() { return u8g2_GetAscent(&m_u8g2); } - int8_t getDescent(void) { + int8_t getDescent() { return u8g2_GetDescent(&m_u8g2); } @@ -107,19 +106,23 @@ class U8G2 { void initDisplay() { u8g2_InitDisplay(&m_u8g2); } + void sendBuffer() { u8g2_SendBuffer(&m_u8g2); } - void setFont(const uint8_t *font) { + void setFont(const uint8_t* font) { u8g2_SetFont(&m_u8g2, font); } + void setPowerSave(uint8_t is_enable) { u8g2_SetPowerSave(&m_u8g2, is_enable); // wake up display } private: u8g2_t m_u8g2; + }; + #endif // CONFIG_U8G2_PRESENT #endif /* COMPONENTS_CPP_UTILS_U8G2_H_ */ diff --git a/cpp_utils/WS2812.cpp b/cpp_utils/WS2812.cpp index 64f3c825..af4bc512 100644 --- a/cpp_utils/WS2812.cpp +++ b/cpp_utils/WS2812.cpp @@ -10,7 +10,12 @@ #include "sdkconfig.h" #include "WS2812.h" -static char tag[] = "WS2812"; +#if CONFIG_CXX_EXCEPTIONS != 1 +#error "C++ exception handling must be enabled within make menuconfig. See Compiler Options > Enable C++ Exceptions." +#endif + + +static const char* LOG_TAG = "WS2812"; /** * A NeoPixel is defined by 3 bytes ... red, green and blue. @@ -28,7 +33,7 @@ static char tag[] = "WS2812"; * a logic 1 for 0.7us * a logic 0 for 0.6us */ -static void setItem1(rmt_item32_t *pItem) { +static void setItem1(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 1; pItem->duration0 = 10; @@ -44,7 +49,7 @@ static void setItem1(rmt_item32_t *pItem) { * a logic 1 for 0.35us * a logic 0 for 0.8us */ -static void setItem0(rmt_item32_t *pItem) { +static void setItem0(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 1; pItem->duration0 = 4; @@ -56,7 +61,7 @@ static void setItem0(rmt_item32_t *pItem) { /** * Add an RMT terminator into the RMT data. */ -static void setTerminator(rmt_item32_t *pItem) { +static void setTerminator(rmt_item32_t* pItem) { assert(pItem != nullptr); pItem->level0 = 0; pItem->duration0 = 0; @@ -69,7 +74,7 @@ static void setTerminator(rmt_item32_t *pItem) { * type which should be one of 'R', 'G' or 'B'. */ static uint8_t getChannelValueByType(char type, pixel_t pixel) { - switch(type) { + switch (type) { case 'r': case 'R': return pixel.red; @@ -79,13 +84,13 @@ static uint8_t getChannelValueByType(char type, pixel_t pixel) { case 'g': case 'G': return pixel.green; + default: + ESP_LOGW(LOG_TAG, "Unknown color channel 0x%2x", type); + return 0; } - ESP_LOGW(tag, "Unknown color channel 0x%2x", type); - return 0; } // getChannelValueByType - /** * @brief Construct a wrapper for the pixels. * @@ -108,7 +113,7 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { assert(ESP32CPP::GPIO::inRange(dinPin)); this->pixelCount = pixelCount; - this->channel = (rmt_channel_t)channel; + this->channel = (rmt_channel_t) channel; // The number of items is number of pixels * 24 bits per pixel + the terminator. // Remember that an item is TWO RMT output bits ... for NeoPixels this is correct because @@ -116,24 +121,23 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { this->items = new rmt_item32_t[pixelCount * 24 + 1]; this->pixels = new pixel_t[pixelCount]; - this->colorOrder = (char *)"GRB"; + this->colorOrder = (char*) "GRB"; clear(); rmt_config_t config; config.rmt_mode = RMT_MODE_TX; config.channel = this->channel; config.gpio_num = dinPin; - config.mem_block_num = 8-this->channel; + config.mem_block_num = 8 - this->channel; config.clk_div = 8; config.tx_config.loop_en = 0; config.tx_config.carrier_en = 0; config.tx_config.idle_output_en = 1; - config.tx_config.idle_level = (rmt_idle_level_t)0; + config.tx_config.idle_level = (rmt_idle_level_t) 0; config.tx_config.carrier_freq_hz = 10000; config.tx_config.carrier_level = (rmt_carrier_level_t)1; config.tx_config.carrier_duty_percent = 50; - ESP_ERROR_CHECK(rmt_config(&config)); ESP_ERROR_CHECK(rmt_driver_install(this->channel, 0, 0)); } // WS2812 @@ -147,19 +151,19 @@ WS2812::WS2812(gpio_num_t dinPin, uint16_t pixelCount, int channel) { void WS2812::show() { auto pCurrentItem = this->items; - for (auto i=0; ipixelCount; i++) { + for (uint16_t i = 0; i < this->pixelCount; i++) { uint32_t currentPixel = (getChannelValueByType(this->colorOrder[0], this->pixels[i]) << 16) | (getChannelValueByType(this->colorOrder[1], this->pixels[i]) << 8) | (getChannelValueByType(this->colorOrder[2], this->pixels[i])); - ESP_LOGD(tag, "Pixel value: %x", currentPixel); - for (int j=23; j>=0; j--) { + ESP_LOGD(LOG_TAG, "Pixel value: %x", currentPixel); + for (int8_t j = 23; j >= 0; j--) { // We have 24 bits of data representing the red, green amd blue channels. The value of the // 24 bits to output is in the variable current_pixel. We now need to stream this value // through RMT in most significant bit first. To do this, we iterate through each of the 24 // bits from MSB to LSB. - if (currentPixel & (1<channel, this->items, this->pixelCount*24, 1 /* wait till done */)); + ESP_ERROR_CHECK(rmt_write_items(this->channel, this->items, this->pixelCount * 24, 1 /* wait till done */)); } // show @@ -186,7 +190,7 @@ void WS2812::show() { * an alternate order by supply an alternate three character string made up of 'R', 'G' and 'B' * for example "RGB". */ -void WS2812::setColorOrder(char *colorOrder) { +void WS2812::setColorOrder(char* colorOrder) { if (colorOrder != nullptr && strlen(colorOrder) == 3) { this->colorOrder = colorOrder; } @@ -203,14 +207,14 @@ void WS2812::setColorOrder(char *colorOrder) { * @param [in] green The amount of green in the pixel. * @param [in] blue The amount of blue in the pixel. */ -void WS2812::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { +void WS2812::setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue) { assert(index < pixelCount); - this->pixels[index].red = red; this->pixels[index].green = green; this->pixels[index].blue = blue; } // setPixel + /** * @brief Set the given pixel to the specified color. * @@ -235,7 +239,6 @@ void WS2812::setPixel(uint16_t index, pixel_t pixel) { */ void WS2812::setPixel(uint16_t index, uint32_t pixel) { assert(index < pixelCount); - this->pixels[index].red = pixel & 0xff; this->pixels[index].green = (pixel & 0xff00) >> 8; this->pixels[index].blue = (pixel & 0xff0000) >> 16; @@ -251,64 +254,65 @@ void WS2812::setPixel(uint16_t index, uint32_t pixel) { * @param [in] saturation The amount of saturation in the pixel (0-255). * @param [in] brightness The amount of brightness in the pixel (0-255). */ -void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { - double sat_red; - double sat_green; - double sat_blue; - double ctmp_red; - double ctmp_green; - double ctmp_blue; - double new_red; - double new_green; - double new_blue; - double dSaturation=(double)saturation/255; - double dBrightness=(double)brightness/255; - - assert(index < pixelCount); - - if (hue < 120) { - sat_red = (120 - hue) / 60.0; - sat_green = hue / 60.0; - sat_blue = 0; - } else if (hue < 240) { - sat_red = 0; - sat_green = (240 - hue) / 60.0; - sat_blue = (hue - 120) / 60.0; - } else { - sat_red = (hue - 240) / 60.0; - sat_green = 0; - sat_blue = (360 - hue) / 60.0; - } - - if (sat_red>1.0) { - sat_red=1.0; - } - if (sat_green>1.0) { - sat_green=1.0; - } - if (sat_blue>1.0) { - sat_blue=1.0; - } - - ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); - ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); - ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); - - if (dBrightness < 0.5) { - new_red = dBrightness * ctmp_red; - new_green = dBrightness * ctmp_green; - new_blue = dBrightness * ctmp_blue; - } else { - new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; - new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; - new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; - } - - this->pixels[index].red = (uint8_t)(new_red*255); - this->pixels[index].green = (uint8_t)(new_green*255); - this->pixels[index].blue = (uint8_t)(new_blue*255); +void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness) { + double sat_red; + double sat_green; + double sat_blue; + double ctmp_red; + double ctmp_green; + double ctmp_blue; + double new_red; + double new_green; + double new_blue; + double dSaturation = (double) saturation / 255; + double dBrightness = (double) brightness / 255; + + assert(index < pixelCount); + + if (hue < 120) { + sat_red = (120 - hue) / 60.0; + sat_green = hue / 60.0; + sat_blue = 0; + } else if (hue < 240) { + sat_red = 0; + sat_green = (240 - hue) / 60.0; + sat_blue = (hue - 120) / 60.0; + } else { + sat_red = (hue - 240) / 60.0; + sat_green = 0; + sat_blue = (360 - hue) / 60.0; + } + + if (sat_red > 1.0) { + sat_red = 1.0; + } + if (sat_green > 1.0) { + sat_green = 1.0; + } + if (sat_blue > 1.0) { + sat_blue = 1.0; + } + + ctmp_red = 2 * dSaturation * sat_red + (1 - dSaturation); + ctmp_green = 2 * dSaturation * sat_green + (1 - dSaturation); + ctmp_blue = 2 * dSaturation * sat_blue + (1 - dSaturation); + + if (dBrightness < 0.5) { + new_red = dBrightness * ctmp_red; + new_green = dBrightness * ctmp_green; + new_blue = dBrightness * ctmp_blue; + } else { + new_red = (1 - dBrightness) * ctmp_red + 2 * dBrightness - 1; + new_green = (1 - dBrightness) * ctmp_green + 2 * dBrightness - 1; + new_blue = (1 - dBrightness) * ctmp_blue + 2 * dBrightness - 1; + } + + this->pixels[index].red = (uint8_t)(new_red * 255); + this->pixels[index].green = (uint8_t)(new_green * 255); + this->pixels[index].blue = (uint8_t)(new_blue * 255); } // setHSBPixel + /** * @brief Clear all the pixel colors. * @@ -316,13 +320,14 @@ void WS2812::setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8 * The LEDs are not actually updated until a call to show(). */ void WS2812::clear() { - for (auto i=0; ipixelCount; i++) { + for (uint16_t i = 0; i < this->pixelCount; i++) { this->pixels[i].red = 0; this->pixels[i].green = 0; this->pixels[i].blue = 0; } } // clear + /** * @brief Class instance destructor. */ diff --git a/cpp_utils/WS2812.h b/cpp_utils/WS2812.h index f599c1b7..f11cbc89 100644 --- a/cpp_utils/WS2812.h +++ b/cpp_utils/WS2812.h @@ -48,21 +48,23 @@ typedef struct { */ class WS2812 { public: - WS2812(gpio_num_t gpioNum, uint16_t pixelCount, int channel=RMT_CHANNEL_0); + WS2812(gpio_num_t gpioNum, uint16_t pixelCount, int channel = RMT_CHANNEL_0); void show(); - void setColorOrder(char *order); + void setColorOrder(char* order); void setPixel(uint16_t index, uint8_t red, uint8_t green, uint8_t blue); void setPixel(uint16_t index, pixel_t pixel); void setPixel(uint16_t index, uint32_t pixel); void setHSBPixel(uint16_t index, uint16_t hue, uint8_t saturation, uint8_t brightness); void clear(); virtual ~WS2812(); + private: - char *colorOrder; + char* colorOrder; uint16_t pixelCount; rmt_channel_t channel; - rmt_item32_t *items; - pixel_t *pixels; + rmt_item32_t* items; + pixel_t* pixels; + }; #endif /* MAIN_WS2812_H_ */ diff --git a/cpp_utils/WebServer.cpp b/cpp_utils/WebServer.cpp index f4d50f79..0fc666af 100644 --- a/cpp_utils/WebServer.cpp +++ b/cpp_utils/WebServer.cpp @@ -18,16 +18,19 @@ #include #include +#define STATE_NAME 0 +#define STATE_VALUE 1 -static char tag[] = "WebServer"; +static const char* LOG_TAG = "WebServer"; struct WebServerUserData { - WebServer *pWebServer; - WebServer::HTTPMultiPart *pMultiPart; - WebServer::WebSocketHandler *pWebSocketHandler; - void *originalUserData; + WebServer* pWebServer; + WebServer::HTTPMultiPart* pMultiPart; + WebServer::WebSocketHandler* pWebSocketHandler; + void* originalUserData; }; + /** * @brief Convert a Mongoose event type to a string. * @param [in] event The received event type. @@ -35,100 +38,93 @@ struct WebServerUserData { */ static std::string mongoose_eventToString(int event) { switch (event) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_POLL: - return "MG_EV_POLL"; - case MG_EV_TIMER: - return "MG_EV_TIMER"; - case MG_EV_HTTP_PART_DATA: - return "MG_EV_HTTP_PART_DATA"; - case MG_EV_HTTP_MULTIPART_REQUEST: - return "MG_EV_HTTP_MULTIPART_REQUEST"; - case MG_EV_HTTP_PART_BEGIN: - return "MG_EV_HTTP_PART_BEGIN"; - case MG_EV_HTTP_PART_END: - return "MG_EV_HTTP_PART_END"; - case MG_EV_HTTP_MULTIPART_REQUEST_END: - return "MG_EV_HTTP_MULTIPART_REQUEST_END"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_HTTP_REPLY: - return "MG_EV_HTTP_REPLY"; - case MG_EV_HTTP_CHUNK: - return "MG_EV_HTTP_CHUNK"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - case MG_EV_WEBSOCKET_CONTROL_FRAME: - return "MG_EV_WEBSOCKET_CONTROL_FRAME"; + case MG_EV_CONNECT: + return "MG_EV_CONNECT"; + case MG_EV_ACCEPT: + return "MG_EV_ACCEPT"; + case MG_EV_CLOSE: + return "MG_EV_CLOSE"; + case MG_EV_SEND: + return "MG_EV_SEND"; + case MG_EV_RECV: + return "MG_EV_RECV"; + case MG_EV_POLL: + return "MG_EV_POLL"; + case MG_EV_TIMER: + return "MG_EV_TIMER"; + case MG_EV_HTTP_PART_DATA: + return "MG_EV_HTTP_PART_DATA"; + case MG_EV_HTTP_MULTIPART_REQUEST: + return "MG_EV_HTTP_MULTIPART_REQUEST"; + case MG_EV_HTTP_PART_BEGIN: + return "MG_EV_HTTP_PART_BEGIN"; + case MG_EV_HTTP_PART_END: + return "MG_EV_HTTP_PART_END"; + case MG_EV_HTTP_MULTIPART_REQUEST_END: + return "MG_EV_HTTP_MULTIPART_REQUEST_END"; + case MG_EV_HTTP_REQUEST: + return "MG_EV_HTTP_REQUEST"; + case MG_EV_HTTP_REPLY: + return "MG_EV_HTTP_REPLY"; + case MG_EV_HTTP_CHUNK: + return "MG_EV_HTTP_CHUNK"; + case MG_EV_MQTT_CONNACK: + return "MG_EV_MQTT_CONNACK"; + case MG_EV_MQTT_CONNECT: + return "MG_EV_MQTT_CONNECT"; + case MG_EV_MQTT_DISCONNECT: + return "MG_EV_MQTT_DISCONNECT"; + case MG_EV_MQTT_PINGREQ: + return "MG_EV_MQTT_PINGREQ"; + case MG_EV_MQTT_PINGRESP: + return "MG_EV_MQTT_PINGRESP"; + case MG_EV_MQTT_PUBACK: + return "MG_EV_MQTT_PUBACK"; + case MG_EV_MQTT_PUBCOMP: + return "MG_EV_MQTT_PUBCOMP"; + case MG_EV_MQTT_PUBLISH: + return "MG_EV_MQTT_PUBLISH"; + case MG_EV_MQTT_PUBREC: + return "MG_EV_MQTT_PUBREC"; + case MG_EV_MQTT_PUBREL: + return "MG_EV_MQTT_PUBREL"; + case MG_EV_MQTT_SUBACK: + return "MG_EV_MQTT_SUBACK"; + case MG_EV_MQTT_SUBSCRIBE: + return "MG_EV_MQTT_SUBSCRIBE"; + case MG_EV_MQTT_UNSUBACK: + return "MG_EV_MQTT_UNSUBACK"; + case MG_EV_MQTT_UNSUBSCRIBE: + return "MG_EV_MQTT_UNSUBSCRIBE"; + case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: + return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: + return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; + case MG_EV_WEBSOCKET_FRAME: + return "MG_EV_WEBSOCKET_FRAME"; + case MG_EV_WEBSOCKET_CONTROL_FRAME: + return "MG_EV_WEBSOCKET_CONTROL_FRAME"; } - std::ostringstream s; - s << "Unknown event: " << event; - return s.str(); + std::string s; + s += "Unknown event: "; + s += event; + return s; } //eventToString -/** - * @brief Convert a Mongoose string type to a string. - * @param [in] mgStr The Mongoose string. - * @return A std::string representation of the Mongoose string. - */ -static std::string mgStrToString(struct mg_str mgStr) { - return std::string(mgStr.p, mgStr.len); -} // mgStrToStr -static void dumpHttpMessage(struct http_message *pHttpMessage) { - ESP_LOGD(tag, "HTTP Message"); - ESP_LOGD(tag, "Message: %s", mgStrToString(pHttpMessage->message).c_str()); - ESP_LOGD(tag, "URI: %s", mgStrToString(pHttpMessage->uri).c_str()); +static void dumpHttpMessage(struct http_message* pHttpMessage) { + ESP_LOGD(LOG_TAG, "HTTP Message"); + ESP_LOGD(LOG_TAG, "Message: %.*s", (int) pHttpMessage->message.len, pHttpMessage->message.p); } /* -static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, struct mg_str fname) { - ESP_LOGD(tag, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); +static struct mg_str uploadFileNameHandler(struct mg_connection* mgConnection, struct mg_str fname) { + ESP_LOGD(LOG_TAG, "uploadFileNameHandler: %s", mgStrToString(fname).c_str()); return fname; } */ + /** * @brief Mongoose event handler. * The event handler is called when an event occurs associated with the WebServer @@ -139,48 +135,47 @@ static struct mg_str uploadFileNameHandler(struct mg_connection *mgConnection, s * @param [in] eventData Data associated with the event. * @return N/A. */ -static void mongoose_event_handler_web_server( - struct mg_connection *mgConnection, // The network connection associated with the event. - int event, // The type of event. - void *eventData // Data associated with the event. -) { - if (event == MG_EV_POLL) { - return; - } - ESP_LOGD(tag, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); +static void mongoose_event_handler_web_server(struct mg_connection* mgConnection, int event, void* eventData) { + if (event == MG_EV_POLL) return; + ESP_LOGD(LOG_TAG, "Event: %s [%d]", mongoose_eventToString(event).c_str(), mgConnection->sock); switch (event) { + case MG_EV_SEND: { + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; + pWebServer->continueConnection(mgConnection); + break; + } + case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) eventData; + struct http_message* message = (struct http_message*) eventData; dumpHttpMessage(message); - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; pWebServer->processRequest(mgConnection, message); break; } // MG_EV_HTTP_REQUEST case MG_EV_HTTP_MULTIPART_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - ESP_LOGD(tag, "User_data address 0x%d", (uint32_t)pWebServerUserData); - WebServer *pWebServer = pWebServerUserData->pWebServer; - if (pWebServer->m_pMultiPartFactory == nullptr) { - return; - } - WebServer::HTTPMultiPart *pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pMultiPart = pMultiPart; + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + ESP_LOGD(LOG_TAG, "User_data address 0x%d", (uint32_t) pWebServerUserData); + WebServer* pWebServer = pWebServerUserData->pWebServer; + if (pWebServer->m_pMultiPartFactory == nullptr) return; + WebServer::HTTPMultiPart* pMultiPart = pWebServer->m_pMultiPartFactory->newInstance(); + struct WebServerUserData* p2 = new WebServerUserData(); + ESP_LOGD(LOG_TAG, "New User_data address 0x%d", (uint32_t) p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pMultiPart = pMultiPart; p2->pWebSocketHandler = nullptr; mgConnection->user_data = p2; - //struct http_message *message = (struct http_message *) eventData; + //struct http_message* message = (struct http_message*) eventData; //dumpHttpMessage(message); break; } // MG_EV_HTTP_MULTIPART_REQUEST case MG_EV_HTTP_MULTIPART_REQUEST_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; if (pWebServerUserData->pMultiPart != nullptr) { delete pWebServerUserData->pMultiPart; pWebServerUserData->pMultiPart = nullptr; @@ -194,10 +189,10 @@ static void mongoose_event_handler_web_server( } // MG_EV_HTTP_MULTIPART_REQUEST_END case MG_EV_HTTP_PART_BEGIN: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t) part->user_data); if (pWebServerUserData->pMultiPart != nullptr) { pWebServerUserData->pMultiPart->begin(std::string(part->var_name), std::string(part->file_name)); } @@ -205,20 +200,20 @@ static void mongoose_event_handler_web_server( } // MG_EV_HTTP_PART_BEGIN case MG_EV_HTTP_PART_DATA: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", - part->file_name, part->var_name, part->status, (uint32_t)part->user_data); + struct WebServerUserData *pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + part->file_name, part->var_name, part->status, (uint32_t) part->user_data); if (pWebServerUserData->pMultiPart != nullptr) { - pWebServerUserData->pMultiPart->data(mgStrToString(part->data)); + pWebServerUserData->pMultiPart->data(std::string(part->data.p, part->data.len)); } break; } // MG_EV_HTTP_PART_DATA case MG_EV_HTTP_PART_END: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - struct mg_http_multipart_part *part = (struct mg_http_multipart_part *)eventData; - ESP_LOGD(tag, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + struct mg_http_multipart_part* part = (struct mg_http_multipart_part*) eventData; + ESP_LOGD(LOG_TAG, "file_name: \"%s\", var_name: \"%s\", status: %d, user_data: 0x%d", part->file_name, part->var_name, part->status, (uint32_t)part->user_data); if (pWebServerUserData->pMultiPart != nullptr) { pWebServerUserData->pMultiPart->end(); @@ -227,28 +222,28 @@ static void mongoose_event_handler_web_server( } // MG_EV_HTTP_PART_END case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; - WebServer *pWebServer = pWebServerUserData->pWebServer; + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; + WebServer* pWebServer = pWebServerUserData->pWebServer; if (pWebServer->m_pWebSocketHandlerFactory != nullptr) { if (pWebServerUserData->pWebSocketHandler != nullptr) { - ESP_LOGD(tag, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); + ESP_LOGD(LOG_TAG, "Warning: MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: pWebSocketHandler was NOT null"); } - struct WebServerUserData *p2 = new WebServerUserData(); - ESP_LOGD(tag, "New User_data address 0x%d", (uint32_t)p2); - p2->originalUserData = pWebServerUserData; - p2->pWebServer = pWebServerUserData->pWebServer; - p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); + struct WebServerUserData* p2 = new WebServerUserData(); + ESP_LOGD(LOG_TAG, "New User_data address 0x%d", (uint32_t) p2); + p2->originalUserData = pWebServerUserData; + p2->pWebServer = pWebServerUserData->pWebServer; + p2->pWebSocketHandler = pWebServer->m_pWebSocketHandlerFactory->newInstance(); mgConnection->user_data = p2; } else { - ESP_LOGD(tag, "We received a WebSocket request but we have no handler factory!"); + ESP_LOGD(LOG_TAG, "We received a WebSocket request but we have no handler factory!"); } break; } // MG_EV_WEBSOCKET_HANDSHAKE_REQUEST case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + ESP_LOGE(LOG_TAG, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); return; } pWebServerUserData->pWebSocketHandler->onCreated(); @@ -261,14 +256,14 @@ static void mongoose_event_handler_web_server( * Our goal will be to send this to the web socket handler (if one exists). */ case MG_EV_WEBSOCKET_FRAME: { - struct WebServerUserData *pWebServerUserData = (struct WebServerUserData *)mgConnection->user_data; + struct WebServerUserData* pWebServerUserData = (struct WebServerUserData*) mgConnection->user_data; if (pWebServerUserData->pWebSocketHandler == nullptr) { - ESP_LOGE(tag, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); + ESP_LOGE(LOG_TAG, "Error: MG_EV_WEBSOCKET_FRAME: pWebSocketHandler is null"); return; } - struct websocket_message *ws_message = (websocket_message *)eventData; - ESP_LOGD(tag, "Received data length: %d", ws_message->size); - pWebServerUserData->pWebSocketHandler->onMessage(std::string((char *)ws_message->data, ws_message->size)); + struct websocket_message* ws_message = (websocket_message*) eventData; + ESP_LOGD(LOG_TAG, "Received data length: %d", ws_message->size); + pWebServerUserData->pWebSocketHandler->onMessage(std::string((char*) ws_message->data, ws_message->size)); break; } // MG_EV_WEBSOCKET_FRAME @@ -280,8 +275,8 @@ static void mongoose_event_handler_web_server( * @brief Constructor. */ WebServer::WebServer() { - m_rootPath = ""; - m_pMultiPartFactory = nullptr; + m_rootPath = ""; + m_pMultiPartFactory = nullptr; m_pWebSocketHandlerFactory = nullptr; } // WebServer @@ -294,7 +289,7 @@ WebServer::~WebServer() { * @brief Get the current root path. * @return The current root path. */ -std::string WebServer::getRootPath() { +const std::string& WebServer::getRootPath() { return m_rootPath; } // getRootPath @@ -319,11 +314,18 @@ std::string WebServer::getRootPath() { * @param [in] pathExpr The path being accessed. * @param [in] handler The callback function to be invoked when a request arrives. */ -void WebServer::addPathHandler(std::string method, std::string pathExpr, void (*handler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)) { +void WebServer::addPathHandler(const std::string& method, const std::string& pathExpr, + void (*handler)(WebServer::HTTPRequest* pHttpRequest, + WebServer::HTTPResponse* pHttpResponse)) { m_pathHandlers.push_back(PathHandler(method, pathExpr, handler)); } // addPathHandler +void WebServer::addPathHandler(std::string&& method, const std::string& pathExpr, void (*handler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_pathHandlers.push_back(PathHandler(std::move(method), pathExpr, handler)); +} // addPathHandler + + /** * @brief Run the web server listening at the given port. * @@ -333,7 +335,7 @@ void WebServer::addPathHandler(std::string method, std::string pathExpr, void (* * @return N/A. */ void WebServer::start(uint16_t port) { - ESP_LOGD(tag, "WebServer task starting"); + ESP_LOGD(LOG_TAG, "WebServer task starting"); struct mg_mgr mgr; mg_mgr_init(&mgr, NULL); @@ -342,20 +344,20 @@ void WebServer::start(uint16_t port) { struct mg_connection *mgConnection = mg_bind(&mgr, stringStream.str().c_str(), mongoose_event_handler_web_server); if (mgConnection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()"); + ESP_LOGE(LOG_TAG, "No connection from the mg_bind()"); vTaskDelete(NULL); return; } - struct WebServerUserData *pWebServerUserData = new WebServerUserData(); + struct WebServerUserData* pWebServerUserData = new WebServerUserData(); pWebServerUserData->pWebServer = this; pWebServerUserData->pMultiPart = nullptr; - mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. - ESP_LOGD(tag, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); + mgConnection->user_data = pWebServerUserData; // Save the WebServer instance reference in user_data. + ESP_LOGD(LOG_TAG, "start: User_data address 0x%d", (uint32_t)pWebServerUserData); mg_set_protocol_http_websocket(mgConnection); - ESP_LOGD(tag, "WebServer listening on port %d", port); - while (1) { + ESP_LOGD(LOG_TAG, "WebServer listening on port %d", port); + while (true) { mg_mgr_poll(&mgr, 2000); } } // run @@ -387,11 +389,16 @@ void WebServer::setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory) { * @param [in] path The root path on the file system. * @return N/A. */ -void WebServer::setRootPath(std::string path) { +void WebServer::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath +void WebServer::setRootPath(std::string&& path) { + m_rootPath = std::move(path); +} // setRootPath + + /** * @brief Register the factory for creating web socket handlers. * @@ -408,8 +415,8 @@ void WebServer::setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHa * @param [in] nc The network connection for the response. */ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { - m_nc = nc; - m_status = 200; + m_nc = nc; + m_status = 200; m_dataSent = false; } // HTTPResponse @@ -419,10 +426,45 @@ WebServer::HTTPResponse::HTTPResponse(struct mg_connection* nc) { * @param [in] name The name of the header. * @param [in] value The value of the header. */ -void WebServer::HTTPResponse::addHeader(std::string name, std::string value) { +void WebServer::HTTPResponse::addHeader(const std::string& name, const std::string& value) { m_headers[name] = value; } // addHeader +void WebServer::HTTPResponse::addHeader(std::string&& name, std::string&& value) { + m_headers[std::move(name)] = std::move(value); +} // addHeader + + +/** + * @brief Build a string representation of the headers. + * @return A string representation of the headers. + */ +std::string WebServer::HTTPResponse::buildHeaders() { + std::string headers; + unsigned long headers_len = 0; + + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if (iter != m_headers.begin()) { + headers_len += 2; + } + headers_len += iter->first.length(); + headers_len += 2; + headers_len += iter->second.length(); + } + headers_len += 1; + headers.resize(headers_len); // Will not have to resize and recopy during the next loop, we have 2 loops but it still ends up being faster + + for (auto iter = m_headers.begin(); iter != m_headers.end(); iter++) { + if (iter != m_headers.begin()) { + headers += "\r\n"; + } + headers += iter->first; + headers += ": "; + headers += iter->second; + } + return headers; +} // buildHeaders + /** * @brief Send data to the HTTP caller. @@ -430,8 +472,8 @@ void WebServer::HTTPResponse::addHeader(std::string name, std::string value) { * @param [in] data The data to be sent to the HTTP caller. * @return N/A. */ -void WebServer::HTTPResponse::sendData(std::string data) { - sendData((uint8_t *)data.data(), data.length()); +void WebServer::HTTPResponse::sendData(const std::string& data) { + sendData((uint8_t*) data.data(), data.length()); } // sendData @@ -442,44 +484,75 @@ void WebServer::HTTPResponse::sendData(std::string data) { * @param [in] length The length of the data to be sent. * @return N/A. */ -void WebServer::HTTPResponse::sendData(uint8_t *pData, size_t length) { +void WebServer::HTTPResponse::sendData(const uint8_t* pData, size_t length) { if (m_dataSent) { - ESP_LOGE(tag, "HTTPResponse: Data already sent! Attempt to send again/more."); + ESP_LOGE(LOG_TAG, "HTTPResponse: Data already sent! Attempt to send again/more."); return; } m_dataSent = true; - std::map::iterator iter; - std::string headers; - - for (iter = m_headers.begin(); iter != m_headers.end(); iter++) { - if (headers.length() == 0) { - headers = iter->first + ": " + iter->second; - } else { - headers = "; " + iter->first + "=" + iter->second; - } - } - mg_send_head(m_nc, m_status, length, headers.c_str()); + mg_send_head(m_nc, m_status, length, buildHeaders().c_str()); mg_send(m_nc, pData, length); m_nc->flags |= MG_F_SEND_AND_CLOSE; } // sendData +/** + * + */ +void WebServer::HTTPResponse::sendData(const char* pData, size_t length) { + sendData((uint8_t*) pData, length); +} // sendData + + +/** + * + */ +void WebServer::HTTPResponse::sendChunkHead() { + if (m_dataSent) { + ESP_LOGE(LOG_TAG, "HTTPResponse: Chunk headers already sent! Attempt to send again/more."); + } + m_dataSent = true; + mg_send_head(m_nc, m_status, -1, buildHeaders().c_str()); +} // sendChunkHead + + +/** + * + */ +void WebServer::HTTPResponse::sendChunk(const char* pData, size_t length) { + mg_send_http_chunk(m_nc, pData, length); +} // sendChunkHead + + +/** + * + */ +void WebServer::HTTPResponse::closeConnection() { + m_nc->flags |= MG_F_SEND_AND_CLOSE; +} // closeConnection + + /** * @brief Set the headers to be sent in the HTTP response. * @param [in] headers The complete set of headers to send to the caller. * @return N/A. */ -void WebServer::HTTPResponse::setHeaders(std::map headers) { +void WebServer::HTTPResponse::setHeaders(const std::map& headers) { m_headers = headers; } // setHeaders +void WebServer::HTTPResponse::setHeaders(std::map&& headers) { + m_headers = std::move(headers); +} // setHeaders + + /** * @brief Get the current root path. * @return The current root path. */ -std::string WebServer::HTTPResponse::getRootPath() { +const std::string& WebServer::HTTPResponse::getRootPath() const { return m_rootPath; } // getRootPath @@ -489,11 +562,16 @@ std::string WebServer::HTTPResponse::getRootPath() { * @param [in] path The root path on the file system. * @return N/A. */ -void WebServer::HTTPResponse::setRootPath(std::string path) { +void WebServer::HTTPResponse::setRootPath(const std::string& path) { m_rootPath = path; } // setRootPath +void WebServer::HTTPResponse::setRootPath(std::string&& path) { + m_rootPath = std::move(path); +} // setRootPath + + /** * @brief Set the status value in the HTTP response. * @@ -516,38 +594,48 @@ void WebServer::HTTPResponse::setStatus(int status) { * @param [in] mgConnection The network connection on which the request was received. * @param [in] message The message representing the request. */ -void WebServer::processRequest(struct mg_connection *mgConnection, struct http_message* message) { - std::string uri = mgStrToString(message->uri); - ESP_LOGD(tag, "WebServer::processRequest: Matching: %s", uri.c_str()); +void WebServer::processRequest(struct mg_connection* mgConnection, struct http_message* message) { + ESP_LOGD(LOG_TAG, "WebServer::processRequest: Matching: %.*s", (int) message->uri.len, message->uri.p); HTTPResponse httpResponse = HTTPResponse(mgConnection); - httpResponse.setRootPath(getRootPath()); /* * Iterate through each of the path handlers looking for a match with the method and specified path. */ std::vector::iterator it; - for (it = m_pathHandlers.begin(); it < m_pathHandlers.end(); it++) { - if ((*it).match(mgStrToString(message->method), uri)) { + for (it = m_pathHandlers.begin(); it != m_pathHandlers.end(); ++it) { + if ((*it).match(message->method.p, message->method.len, message->uri.p)) { HTTPRequest httpRequest(message); (*it).invoke(&httpRequest, &httpResponse); - ESP_LOGD(tag, "Found a match!!"); + ESP_LOGD(LOG_TAG, "Found a match!!"); return; } } // End of examine path handlers. // Because we reached here, it means that we did NOT match a handler. Now we want to attempt // to retrieve the corresponding file content. - std::string filePath = httpResponse.getRootPath() + uri; - ESP_LOGD(tag, "Opening file: %s", filePath.c_str()); - FILE *file = fopen(filePath.c_str(), "r"); + std::string filePath; + filePath.reserve(httpResponse.getRootPath().length() + message->uri.len + 1); + filePath += httpResponse.getRootPath(); + filePath.append(message->uri.p, message->uri.len); + ESP_LOGD(LOG_TAG, "Opening file: %s", filePath.c_str()); + FILE* file = nullptr; + + if (strcmp(filePath.c_str(), "/") != 0) { + file = fopen(filePath.c_str(), "rb"); + } if (file != nullptr) { - fseek(file, 0L, SEEK_END); - size_t length = ftell(file); - fseek(file, 0L, SEEK_SET); - uint8_t *pData = (uint8_t *)malloc(length); - fread(pData, length, 1, file); - fclose(file); - httpResponse.sendData(pData, length); + auto pData = (uint8_t*)malloc(MAX_CHUNK_LENGTH); + size_t read = fread(pData, 1, MAX_CHUNK_LENGTH, file); + + if (read >= MAX_CHUNK_LENGTH) { + httpResponse.sendChunkHead(); + httpResponse.sendChunk((char*) pData, read); + fclose(unfinishedConnection[mgConnection->sock]); + unfinishedConnection[mgConnection->sock] = file; + } else { + fclose(file); + httpResponse.sendData(pData, read); + } free(pData); } else { // Handle unable to open file @@ -556,6 +644,25 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m } } // processRequest +void WebServer::continueConnection(struct mg_connection* mgConnection) { + if (unfinishedConnection.count(mgConnection->sock) == 0) return; + + HTTPResponse httpResponse = HTTPResponse(mgConnection); + + FILE* file = unfinishedConnection[mgConnection->sock]; + auto pData = (char*) malloc(MAX_CHUNK_LENGTH); + size_t length = fread(pData, 1, MAX_CHUNK_LENGTH, file); + + httpResponse.sendChunk(pData, length); + if (length < MAX_CHUNK_LENGTH) { + fclose(file); + httpResponse.closeConnection(); + unfinishedConnection.erase(mgConnection->sock); + httpResponse.sendChunk("", 0); + } + free(pData); +} + /** * @brief Construct an instance of a PathHandler. @@ -564,9 +671,15 @@ void WebServer::processRequest(struct mg_connection *mgConnection, struct http_m * @param [in] pathPattern The path pattern to be matched. * @param [in] webServerRequestHandler The request handler to be called. */ -WebServer::PathHandler::PathHandler(std::string method, std::string pathPattern, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)) { - m_method = method; - m_pattern = std::regex(pathPattern); +WebServer::PathHandler::PathHandler(const std::string& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_method = method; + m_pattern = std::regex(pathPattern); + m_requestHandler = webServerRequestHandler; +} // PathHandler + +WebServer::PathHandler::PathHandler(std::string&& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)) { + m_method = std::move(method); + m_pattern = std::regex(pathPattern); m_requestHandler = webServerRequestHandler; } // PathHandler @@ -575,12 +688,13 @@ WebServer::PathHandler::PathHandler(std::string method, std::string pathPattern, * @brief Determine if the path matches. * * @param [in] method The method to be matched. + * @param [in] method_len The method's length * @param [in] path The path to be matched. * @return True if the path matches. */ -bool WebServer::PathHandler::match(std::string method, std::string path) { - //ESP_LOGD(tag, "match: %s with %s", m_pattern.c_str(), path.c_str()); - if (method != m_method) { +bool WebServer::PathHandler::match(const char* method, size_t method_len, const char* path) { + //ESP_LOGD(LOG_TAG, "match: %s with %s", m_pattern.c_str(), path.c_str()); + if (method_len != m_method.length() || strncmp(method, m_method.c_str(), method_len) != 0) { return false; } return std::regex_search(path, m_pattern); @@ -593,7 +707,7 @@ bool WebServer::PathHandler::match(std::string method, std::string path) { * @param [in] response An object representing the response. * @return N/A. */ -void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse *response) { +void WebServer::PathHandler::invoke(WebServer::HTTPRequest* request, WebServer::HTTPResponse* response) { m_requestHandler(request, response); } // invoke @@ -612,48 +726,84 @@ WebServer::HTTPRequest::HTTPRequest(struct http_message* message) { /** * @brief Get the body of the request. * When an HTTP request is either PUT or POST then it may contain a payload that is also - * known as the body. This method returns that payload (if it exists). + * known as the body. This method returns that payload (if it exists). Careful, because it's not a standard string + * that is terminated by a null character, use the getBodyLen() function to determine the body length * @return The body of the request. */ -std::string WebServer::HTTPRequest::getBody() { - return mgStrToString(m_message->body); +const char* WebServer::HTTPRequest::getBody() const { + return m_message->body.p; } // getBody +/** + * @brief Get the length of the body of the request. + * When an HTTP request is either PUT or POST then it may contain a payload that is also + * known as the body. This method returns that payload (if it exists). + * @return The length of the body of the request. + */ +size_t WebServer::HTTPRequest::getBodyLen() const { + return m_message->body.len; +} // getBodyLen + + /** * @brief Get the method of the request. * An HTTP request contains a request method which is one of GET, PUT, POST, etc. - * @return The method of the request. + * @return The method of the request. Careful, because it's not a standard string + * that is terminated by a null character, use the getMethodLen() function to determine the method length + * @return The body of the request. */ -std::string WebServer::HTTPRequest::getMethod() { - return mgStrToString(m_message->method); +const char* WebServer::HTTPRequest::getMethod() const { + return m_message->method.p; } // getMethod +/** + * @brief Get the length of the method of the request. + * An HTTP request contains a request method which is one of GET, PUT, POST, etc. + * @return The length of the method of the request. + */ +size_t WebServer::HTTPRequest::getMethodLen() const { + return m_message->method.len; +} // getMethodLen + + +/** + * @brief Get the path of the request. + * The path of an HTTP request is the portion of the URL that follows the hostname/port pair + * but does not include any query parameters. Careful, because it's not a standard string + * that is terminated by a null character, use the getPathLen() function to determine the path length + * @return The body of the request. + * @return The path of the request. + */ +const char* WebServer::HTTPRequest::getPath() const { + return m_message->uri.p; +} // getPath + + /** * @brief Get the path of the request. * The path of an HTTP request is the portion of the URL that follows the hostname/port pair * but does not include any query parameters. * @return The path of the request. */ -std::string WebServer::HTTPRequest::getPath() { - return mgStrToString(m_message->uri); +size_t WebServer::HTTPRequest::getPathLen() const { + return m_message->uri.len; } // getPath -#define STATE_NAME 0 -#define STATE_VALUE 1 + /** * @brief Get the query part of the request. * The query is a set of name = value pairs. The return is a map keyed by the name items. * * @return The query part of the request. */ -std::map WebServer::HTTPRequest::getQuery() { +std::map WebServer::HTTPRequest::getQuery() const { // Walk through all the characters in the query string maintaining a simple state machine // that lets us know what we are parsing. std::map queryMap; - std::string queryString = mgStrToString(m_message->query_string); - int i=0; + const char* queryString = m_message->query_string.p; + size_t queryStringLen = m_message->query_string.len; /* * We maintain a simple state machine with states of: @@ -664,7 +814,7 @@ std::map WebServer::HTTPRequest::getQuery() { std::string name = ""; std::string value; // Loop through each character in the query string. - for (i=0; i WebServer::HTTPRequest::getQuery() { value = ""; } } // End state = STATE_NAME - else if (state == STATE_VALUE) { + else { // if (state == STATE_VALUE) if (currentChar != '&') { value += currentChar; } else { - //ESP_LOGD(tag, "name=%s, value=%s", name.c_str(), value.c_str()); + //ESP_LOGD(LOG_TAG, "name=%s, value=%s", name.c_str(), value.c_str()); queryMap[name] = value; state = STATE_NAME; name = ""; @@ -686,7 +836,7 @@ std::map WebServer::HTTPRequest::getQuery() { } // End state = STATE_VALUE } // End for loop if (state == STATE_VALUE) { - //ESP_LOGD(tag, "name=%s, value=%s", name.c_str(), value.c_str()); + //ESP_LOGD(LOG_TAG, "name=%s, value=%s", name.c_str(), value.c_str()); queryMap[name] = value; } return queryMap; @@ -712,16 +862,16 @@ std::map WebServer::HTTPRequest::getQuery() { * * @return A vector of the constituent parts of the path. */ -std::vector WebServer::HTTPRequest::pathSplit() { - std::istringstream stream(getPath()); +std::vector WebServer::HTTPRequest::pathSplit() const { + std::istringstream stream(std::string(getPath(), getPathLen())); // I don't know if there's a better istringstream constructor for this std::vector ret; std::string pathPart; - while(std::getline(stream, pathPart, '/')) { + while (std::getline(stream, pathPart, '/')) { ret.push_back(pathPart); } // Debug - for (int i=0; i WebServer::HTTPRequest::pathSplit() { * @param [in] fileName The name of the file being uploaded (may not be present). * @return N/A. */ -void WebServer::HTTPMultiPart::begin(std::string varName, std::string fileName) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", +void WebServer::HTTPMultiPart::begin(const std::string& varName, const std::string& fileName) { + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::begin(varName=\"%s\", fileName=\"%s\")", varName.c_str(), fileName.c_str()); } // WebServer::HTTPMultiPart::begin @@ -746,7 +896,7 @@ void WebServer::HTTPMultiPart::begin(std::string varName, std::string fileName) * @return N/A. */ void WebServer::HTTPMultiPart::end() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::end()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::end()"); } // WebServer::HTTPMultiPart::end @@ -757,8 +907,8 @@ void WebServer::HTTPMultiPart::end() { * @param [in] data The data received in this callback. * @return N/A. */ -void WebServer::HTTPMultiPart::data(std::string data) { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); +void WebServer::HTTPMultiPart::data(const std::string& data) { + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::data(), length=%d", data.length()); } // WebServer::HTTPMultiPart::data @@ -767,7 +917,7 @@ void WebServer::HTTPMultiPart::data(std::string data) { * @return N/A. */ void WebServer::HTTPMultiPart::multipartEnd() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartEnd()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::multipartEnd()"); } // WebServer::HTTPMultiPart::multipartEnd @@ -776,7 +926,7 @@ void WebServer::HTTPMultiPart::multipartEnd() { * @return N/A. */ void WebServer::HTTPMultiPart::multipartStart() { - ESP_LOGD(tag, "WebServer::HTTPMultiPart::multipartStart()"); + ESP_LOGD(LOG_TAG, "WebServer::HTTPMultiPart::multipartStart()"); } // WebServer::HTTPMultiPart::multipartStart @@ -785,7 +935,6 @@ void WebServer::HTTPMultiPart::multipartStart() { * @return N/A. */ void WebServer::WebSocketHandler::onCreated() { - } // onCreated @@ -794,8 +943,7 @@ void WebServer::WebSocketHandler::onCreated() { * @param [in] message The message received from the client. * @return N/A. */ -void WebServer::WebSocketHandler::onMessage(std::string message){ - +void WebServer::WebSocketHandler::onMessage(const std::string& message){ } // onMessage /** @@ -803,7 +951,6 @@ void WebServer::WebSocketHandler::onMessage(std::string message){ * @return N/A */ void WebServer::WebSocketHandler::onClosed() { - } // onClosed @@ -812,20 +959,21 @@ void WebServer::WebSocketHandler::onClosed() { * @param [in] message The message to send down the socket. * @return N/A. */ -void WebServer::WebSocketHandler::sendData(std::string message) { - ESP_LOGD(tag, "WebSocketHandler::sendData(length=%d)", message.length()); +void WebServer::WebSocketHandler::sendData(const std::string& message) { + ESP_LOGD(LOG_TAG, "WebSocketHandler::sendData(length=%d)", message.length()); mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, message.data(), message.length()); } // sendData + /** * @brief Send data down the WebSocket * @param [in] data The message to send down the socket. * @param [in] size The size of the message * @return N/A. */ -void WebServer::WebSocketHandler::sendData(uint8_t *data, uint32_t size) { +void WebServer::WebSocketHandler::sendData(const uint8_t* data, uint32_t size) { mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_BINARY | WEBSOCKET_OP_CONTINUE, data, size); @@ -842,6 +990,4 @@ void WebServer::WebSocketHandler::close() { mg_send_websocket_frame(m_mgConnection, WEBSOCKET_OP_CLOSE, nullptr, 0); } // close - - #endif // CONFIG_MONGOOSE_PRESENT diff --git a/cpp_utils/WebServer.h b/cpp_utils/WebServer.h index adbba786..961b6662 100644 --- a/cpp_utils/WebServer.h +++ b/cpp_utils/WebServer.h @@ -12,10 +12,11 @@ #include #include #include "sdkconfig.h" + #ifdef CONFIG_MONGOOSE_PRESENT #include - +#define MAX_CHUNK_LENGTH 4090 // 4 kilobytes class WebServer; @@ -32,13 +33,18 @@ class WebServer { class HTTPRequest { public: HTTPRequest(struct http_message* message); - std::string getMethod(); - std::string getPath(); - std::map getQuery(); - std::string getBody(); - std::vector pathSplit(); + const char* getMethod() const; + const char* getPath() const; + const char* getBody() const; + size_t getMethodLen() const; + size_t getPathLen() const; + size_t getBodyLen() const; + std::map getQuery() const; + std::vector pathSplit() const; + private: struct http_message* m_message; + }; // HTTPRequest /** @@ -46,20 +52,30 @@ class WebServer { */ class HTTPResponse { public: - HTTPResponse(struct mg_connection *nc); - void addHeader(std::string name, std::string value); - std::string getRootPath(); + HTTPResponse(struct mg_connection* nc); + void addHeader(const std::string& name, const std::string& value); + void addHeader(std::string&& name, std::string&& value); void setStatus(int status); - void setHeaders(std::map headers); - void sendData(std::string data); - void sendData(uint8_t *pData, size_t length); - void setRootPath(std::string path); + void setHeaders(const std::map& headers); + void setHeaders(std::map&& headers); + void sendData(const std::string& data); + void sendData(const uint8_t* pData, size_t length); + void sendData(const char* pData, size_t length); + const std::string& getRootPath() const; + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); + void sendChunkHead(); + void sendChunk(const char* pData, size_t length); + void closeConnection(); + private: - struct mg_connection *m_nc; + struct mg_connection* m_nc; std::string m_rootPath; int m_status; std::map m_headers; bool m_dataSent; + std::string buildHeaders(); + }; // HTTPResponse /** @@ -85,13 +101,13 @@ class WebServer { */ class HTTPMultiPart { public: - virtual ~HTTPMultiPart() { - }; - virtual void begin(std::string varName, std::string fileName); + virtual ~HTTPMultiPart() = default; + virtual void begin(const std::string& varName, const std::string& fileName); virtual void end(); - virtual void data(std::string data); + virtual void data(const std::string& data); virtual void multipartEnd(); virtual void multipartStart(); + }; // HTTPMultiPart /** @@ -102,30 +118,30 @@ class WebServer { * class MyMultiPart : public WebServer::HTTPMultiPart { * public: * void begin(std::string varName, std::string fileName) { - * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", - * varName.c_str(), fileName.c_str()); + * ESP_LOGD(tag, "MyMultiPart begin(): varName=%s, fileName=%s", + * varName.c_str(), fileName.c_str()); * } * * void end() { - * ESP_LOGD(tag, "MyMultiPart end()"); + * ESP_LOGD(tag, "MyMultiPart end()"); * } * * void data(std::string data) { - * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); + * ESP_LOGD(tag, "MyMultiPart data(): length=%d", data.length()); * } * * void multipartEnd() { - * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); + * ESP_LOGD(tag, "MyMultiPart multipartEnd()"); * } * * void multipartStart() { - * ESP_LOGD(tag, "MyMultiPart multipartStart()"); + * ESP_LOGD(tag, "MyMultiPart multipartStart()"); * } * }; * * class MyMultiPartFactory : public WebServer::HTTPMultiPartFactory { * WebServer::HTTPMultiPart *createNew() { - * return new MyMultiPart(); + * return new MyMultiPart(); * } * }; * @endcode @@ -136,7 +152,8 @@ class WebServer { * @brief Create a new HTTPMultiPart instance. * @return A new HTTPMultiPart instance. */ - virtual HTTPMultiPart *newInstance() = 0; + virtual HTTPMultiPart* newInstance() = 0; + }; /** @@ -145,13 +162,16 @@ class WebServer { */ class PathHandler { public: - PathHandler(std::string method, std::string pathPattern, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse)); - bool match(std::string method, std::string path); - void invoke(HTTPRequest *request, HTTPResponse *response); + PathHandler(const std::string& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + PathHandler(std::string&& method, const std::string& pathPattern, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + bool match(const char* method, size_t method_len, const char* path); + void invoke(HTTPRequest* request, HTTPResponse* response); + private: std::string m_method; std::regex m_pattern; - void (*m_requestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse); + void (*m_requestHandler)(WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse); + }; // PathHandler /** @@ -160,34 +180,43 @@ class WebServer { class WebSocketHandler { public: void onCreated(); - virtual void onMessage(std::string message); + virtual void onMessage(const std::string& message); void onClosed(); - void sendData(std::string message); - void sendData(uint8_t *data, uint32_t size); + void sendData(const std::string& message); + void sendData(const uint8_t* data, uint32_t size); void close(); + private: - struct mg_connection *m_mgConnection; + struct mg_connection* m_mgConnection; + }; class WebSocketHandlerFactory { public: - virtual WebSocketHandler *newInstance() = 0; + virtual WebSocketHandler* newInstance() = 0; + }; WebServer(); virtual ~WebServer(); - void addPathHandler(std::string method, std::string pathExpr, void (*webServerRequestHandler)(WebServer::HTTPRequest *pHttpRequest, WebServer::HTTPResponse *pHttpResponse) ); - std::string getRootPath(); - void setMultiPartFactory(HTTPMultiPartFactory *pMultiPartFactory); - void setRootPath(std::string path); - void setWebSocketHandlerFactory(WebSocketHandlerFactory *pWebSocketHandlerFactory); + void addPathHandler(const std::string& method, const std::string& pathExpr, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + void addPathHandler(std::string&& method, const std::string& pathExpr, void (*webServerRequestHandler) (WebServer::HTTPRequest* pHttpRequest, WebServer::HTTPResponse* pHttpResponse)); + const std::string& getRootPath(); + void setMultiPartFactory(HTTPMultiPartFactory* pMultiPartFactory); + void setRootPath(const std::string& path); + void setRootPath(std::string&& path); + void setWebSocketHandlerFactory(WebSocketHandlerFactory* pWebSocketHandlerFactory); void start(unsigned short port = 80); - void processRequest(struct mg_connection *mgConnection, struct http_message *message); - HTTPMultiPartFactory *m_pMultiPartFactory; - WebSocketHandlerFactory *m_pWebSocketHandlerFactory; + void processRequest(struct mg_connection* mgConnection, struct http_message* message); + void continueConnection(struct mg_connection* mgConnection); + HTTPMultiPartFactory* m_pMultiPartFactory; + WebSocketHandlerFactory* m_pWebSocketHandlerFactory; + private: std::string m_rootPath; std::vector m_pathHandlers; + std::map unfinishedConnection; + }; #endif // CONFIG_MONGOOSE_PRESENT diff --git a/cpp_utils/WebSocket.cpp b/cpp_utils/WebSocket.cpp new file mode 100644 index 00000000..6930f3b6 --- /dev/null +++ b/cpp_utils/WebSocket.cpp @@ -0,0 +1,512 @@ +/* + * WebSocket.cpp + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#include +#include "WebSocket.h" +#include "Task.h" +#include "GeneralUtils.h" +#include + +extern "C" { + extern uint16_t lwip_ntohs(uint16_t); + extern uint32_t lwip_ntohl(uint32_t); + extern uint16_t lwip_htons(uint16_t); + extern uint32_t lwip_htonl(uint32_t); +} + +static const char* LOG_TAG = "WebSocket"; + +// WebSocket op codes as found in a WebSocket frame. +static const uint8_t OPCODE_CONTINUE = 0x00; +static const uint8_t OPCODE_TEXT = 0x01; +static const uint8_t OPCODE_BINARY = 0x02; +static const uint8_t OPCODE_CLOSE = 0x08; +static const uint8_t OPCODE_PING = 0x09; +static const uint8_t OPCODE_PONG = 0x0a; + + +// Structure definition for the WebSocket frame. +struct Frame { + // Byte 0 + uint8_t opCode : 4; // [7:4] + uint8_t rsv3 : 1; // [3] + uint8_t rsv2 : 1; // [2] + uint8_t rsv1 : 1; // [1] + uint8_t fin : 1; // [0] + + // Byte 1 + uint8_t len : 7; // [7:1] + uint8_t mask : 1; // [0] +}; + + +/** + * @brief Dump the content of the WebSocket frame for debugging. + * @param [in] frame The frame to dump. + */ +static void dumpFrame(Frame frame) { + std::ostringstream oss; + oss << "Fin: " << (int) frame.fin << ", OpCode: " << (int) frame.opCode; + switch (frame.opCode) { + case OPCODE_BINARY: { + oss << " BINARY"; + break; + } + case OPCODE_CONTINUE: { + oss << " CONTINUE"; + break; + } + case OPCODE_CLOSE: { + oss << " CLOSE"; + break; + } + case OPCODE_PING: { + oss << " PING"; + break; + } + case OPCODE_PONG: { + oss << " PONG"; + break; + } + case OPCODE_TEXT: { + oss << " TEXT"; + break; + } + default: { + oss << " Unknown"; + break; + } + } + oss << ", Mask: " << (int) frame.mask << ", len: " << (int) frame.len; + ESP_LOGD(LOG_TAG, "WebSocket frame: %s", oss.str().c_str()); +} // dumpFrame + + +/** + * @brief A task that will watch web socket inputs. + * + * When a WebSocket is created it is created by the client requesting an HTTP protocol changed to WebSockets. + * After the original Socket has been flagged as being a WebSocket, we must now start watching that socket for + * incoming asynchronous events. We spawn a task to do this. This is the implementation of that task. + */ +class WebSocketReader: public Task { +public: + WebSocketReader() { + m_end = false; + } + void end() { + m_end = true; + } + +private: + bool m_end; + /** + * @brief Loop over the web socket waiting for new input. + * @param [in] data A pointer to an instance of the WebSocket. + */ + void run(void* data) { + WebSocket* pWebSocket = (WebSocket*) data; + ESP_LOGD("WebSocketReader", "WebSocketReader Task started, socket: %s", pWebSocket->getSocket().toString().c_str()); + + Socket peerSocket = pWebSocket->getSocket(); + + Frame frame; + while (true) { + if (m_end) break; + ESP_LOGD("WebSocketReader", "Waiting on socket data for socket %s", peerSocket.toString().c_str()); + int length = peerSocket.receive((uint8_t*) &frame, sizeof(frame), true); // Read exact + if (length != sizeof(frame)) { + ESP_LOGD("WebSocketReader", "Socket read error"); + pWebSocket->close(); + return; + } + ESP_LOGD("WebSocketReader", "Received data from web socket. Length: %d", length); + dumpFrame(frame); + + // The following section parses the WebSocket frame. + uint32_t payloadLen = 0; + uint8_t mask[4]; + if (frame.len < 126) { + payloadLen = frame.len; + } else if (frame.len == 126) { + uint16_t tempLen; + peerSocket.receive((uint8_t*) &tempLen, sizeof(tempLen), true); + payloadLen = ntohs(tempLen); + } else if (frame.len == 127) { + uint64_t tempLen; + peerSocket.receive((uint8_t*) &tempLen, sizeof(tempLen), true); + payloadLen = ntohl((uint32_t) tempLen); + } + if (frame.mask == 1) { + peerSocket.receive(mask, sizeof(mask), true); + } + + if (payloadLen == 0) { + ESP_LOGD("WebSocketReader", "Web socket payload is not present"); + } else { + ESP_LOGD("WebSocketReader", "Web socket payload, length=%d:", payloadLen); + } + + WebSocketHandler* pWebSocketHandler = pWebSocket->getHandler(); + switch (frame.opCode) { + case OPCODE_TEXT: + case OPCODE_BINARY: { + if (pWebSocketHandler != nullptr) { + WebSocketInputStreambuf streambuf(pWebSocket->getSocket(), payloadLen, (frame.mask == 1) ? mask : nullptr); + pWebSocketHandler->onMessage(&streambuf, pWebSocket); + //streambuf.discard(); + } + break; + } + + // If the WebSocket operation code is close then we are closing the connection. + case OPCODE_CLOSE: { + pWebSocket->m_receivedClose = true; + if (pWebSocketHandler != nullptr) { // If we have a handler, invoke the onClose method upon it. + pWebSocketHandler->onClose(); + } + pWebSocket->close(); // Close the websocket. + break; + } + + case OPCODE_CONTINUE: { + break; + } + + case OPCODE_PING: { + break; + } + + case OPCODE_PONG: { + break; + } + + default: { + ESP_LOGD("WebSocketReader", "Unknown opcode: %d", frame.opCode); + break; + } + } // Switch opCode + + } // while (true) + ESP_LOGD("WebSocketReader", "<< run"); + } // run +}; // WebSocketReader + + +/** + * @brief The default onClose handler. + * If no over-riding handler is provided for the "close" event, this method is called. + */ +void WebSocketHandler::onClose() { + ESP_LOGD("WebSocketHandler", ">> onClose"); + ESP_LOGD("WebSocketHandler", "<< onClose"); +} // onClose + + +/** + * @brief The default onData handler. + * If no over-riding handler is provided for the "message" event, this method is called. + * A particularly useful pattern for using onMessage is: + * ``` + * std::stringstream buffer; + * buffer << pWebSocketInputRecordStreambuf; + * ``` + * This will read the whole message into the string stream. + */ +void WebSocketHandler::onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf, WebSocket* pWebSocket) { + ESP_LOGD("WebSocketHandler", ">> onMessage"); + ESP_LOGD("WebSocketHandler", "<< onMessage"); +} // onData + + +/** + * @brief The default onError handler. + * If no over-riding handler is provided for the "error" event, this method is called. + */ +void WebSocketHandler::onError(std::string error) { + ESP_LOGD("WebSocketHandler", ">> onError: %s", error.c_str()); + ESP_LOGD("WebSocketHandler", "<< onError"); +} // onError + + +/** + * @brief Construct a WebSocket instance. + */ +WebSocket::WebSocket(Socket socket) { + m_receivedClose = false; + m_sentClose = false; + m_socket = socket; + m_pWebSockerReader = new WebSocketReader(); + m_pWebSocketHandler = nullptr; +} // WebSocket + + +/** + * @brief Destructor. + */ +WebSocket::~WebSocket() { + m_pWebSockerReader->stop(); + delete m_pWebSockerReader; +} // ~WebSocket + + +/** + * @brief Close the Web socket + * @param [in] status The code passed in the close request. + * @param [in] message A clarification message on the close request. + */ +void WebSocket::close(uint16_t status, std::string message) { + ESP_LOGD(LOG_TAG, ">> close(): status: %d, message: %s", status, message.c_str()); + + if (m_sentClose) { // If we have previously sent a close request then we can close the underlying socket. + ESP_LOGD(LOG_TAG, "Closing the underlying socket"); + m_socket.close(); // Close the underlying socket. + m_pWebSockerReader->end(); // Stop the web socket reader. + return; + } + m_sentClose = true; // Flag that we have sent a close request. + + Frame frame; // Build the web socket frame indicating a close request. + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = OPCODE_CLOSE; + frame.mask = 0; + frame.len = message.length() + 2; + int rc = m_socket.send((uint8_t*) &frame, sizeof(frame)); + + if (rc > 0) { + rc = m_socket.send(status); + } + + if (rc > 0) { + m_socket.send(message); + } + + if (m_receivedClose || rc == 0 || rc == -1) { + m_socket.close(); // Close the underlying socket. + m_pWebSockerReader->end(); // Stop the web socket reader. + } +} // close + + +/** + * @brief Get the current WebSocketHandler + * A web socket handler is a user registered class instance that is called when an incoming + * event received over the network needs to be handled by user code. + */ +WebSocketHandler* WebSocket::getHandler() { + return m_pWebSocketHandler; +} // getHandler + + +/** + * @brief Get the underlying socket for the websocket. + * @return The socket associated with the Web socket. + */ +Socket WebSocket::getSocket() { + return m_socket; +} // getSocket + + +/** + * @brief Send data down the web socket + * See the WebSocket spec (RFC6455) section "6.1 Sending Data". + * We build a WebSocket frame, send the frame followed by the data. + * @param [in] data The data to send down the WebSocket. + * @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY. + */ +void WebSocket::send(std::string data, uint8_t sendType) { + ESP_LOGD(LOG_TAG, ">> send: Length: %d", data.length()); + Frame frame; + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = (sendType == SEND_TYPE_TEXT) ? OPCODE_TEXT : OPCODE_BINARY; + frame.mask = 0; + if (data.length() < 126) { + frame.len = data.length(); + m_socket.send((uint8_t*) &frame, sizeof(frame)); + } else { + frame.len = 126; + m_socket.send((uint8_t*) &frame, sizeof(frame)); + m_socket.send(htons((uint16_t) data.length())); // Convert to network byte order from host byte order + } + m_socket.send((uint8_t*)data.data(), data.length()); + ESP_LOGD(LOG_TAG, "<< send"); +} // send_cpp + + +/** + * @brief Send data down the web socket + * See the WebSocket spec (RFC6455) section "6.1 Sending Data". + * We build a WebSocket frame, send the frame followed by the data. + * @param [in] data The data to send down the WebSocket. + * @param [in] sendType The type of payload. Either SEND_TYPE_TEXT or SEND_TYPE_BINARY. + */ +void WebSocket::send(uint8_t* data, uint16_t length, uint8_t sendType) { + ESP_LOGD(LOG_TAG, ">> send: Length: %d", length); + Frame frame; + frame.fin = 1; + frame.rsv1 = 0; + frame.rsv2 = 0; + frame.rsv3 = 0; + frame.opCode = (sendType==SEND_TYPE_TEXT) ? OPCODE_TEXT : OPCODE_BINARY; + frame.mask = 0; + if (length < 126) { + frame.len = length; + m_socket.send((uint8_t*) &frame, sizeof(frame)); + } else { + frame.len = 126; + m_socket.send((uint8_t*) &frame, sizeof(frame)); + m_socket.send(htons(length)); // Convert to network byte order from host byte order + } + m_socket.send(data, length); + ESP_LOGD(LOG_TAG, "<< send"); +} + + +/** + * @brief Set the Web socket handler associated with this Websocket. + * + * This will be the user supplied code that will be invoked to process incoming WebSocket + * events. An instance of WebSocketHandler is passed in. + * + */ +void WebSocket::setHandler(WebSocketHandler* pHandler) { + m_pWebSocketHandler = pHandler; +} // setHandler + + +/** + * @brief Start the WebSocket reader reading the socket. + * When we have a new web socket, we want to start watching for new incoming events. This + * function starts that activity. We want to have control over when we start watching. + */ +void WebSocket::startReader() { + ESP_LOGD(LOG_TAG, ">> startReader: Socket: %s", m_socket.toString().c_str()); + m_pWebSockerReader->start(this); +} // startReader + + +/** + * @brief Create a Web Socket input record streambuf + * @param [in] socket The socket we will be reading from. + * @param [in] dataLength The size of a record. + * @param [in] bufferSize The size of the buffer we wish to allocate to hold data. + */ +WebSocketInputStreambuf::WebSocketInputStreambuf( + Socket socket, + size_t dataLength, + uint8_t* pMask, + size_t bufferSize) { + m_socket = socket; // The socket we will be reading from + m_dataLength = dataLength; // The size of the record we wish to read. + m_pMask = pMask; + m_bufferSize = bufferSize; // The size of the buffer used to hold data + m_sizeRead = 0; // The size of data read from the socket + m_buffer = new char[bufferSize]; // Create the buffer used to hold the data read from the socket. + + setg(m_buffer, m_buffer, m_buffer); // Set the initial get buffer pointers to no data. +} // WebSocketInputStreambuf + + +/** + * @brief Destructor + */ +WebSocketInputStreambuf::~WebSocketInputStreambuf() { + delete[] m_buffer; + discard(); +} // ~WebSocketInputRecordStreambuf + + +/** + * @brief Discard data for the record that has not yet been read. + * + * We are working on a logical fixed length record in a socket stream. This means that we know in advance + * how big the record should be. If we have read some data from the stream and no longer wish to consume + * any further, we have to discard the remaining bytes in the stream before we can get to process the + * next record. This function discards the remainder of the data. + * + * For example, if our record size is 1000 bytes and we have read 700 bytes and determine that we no + * longer need to continue, we can't just stop. There are still 300 bytes in the socket stream that + * need to be consumed/discarded before we can move on to the next record. + */ +void WebSocketInputStreambuf::discard() { + uint8_t byte; + ESP_LOGD("WebSocketInputStreambuf", ">> discard: Discarding %d bytes", m_dataLength - m_sizeRead); + while(m_sizeRead < m_dataLength) { + m_socket.receive(&byte, 1); + m_sizeRead++; + } + ESP_LOGD("WebSocketInputStreambuf", "<< discard"); +} // discard + + +/** + * @brief Get the size of the expected record. + * @return The size of the expected record. + */ +size_t WebSocketInputStreambuf::getRecordSize() { + return m_dataLength; +} // getRecordSize + + +/** + * @brief Handle the request to read data from the stream but we need more data from the source. + * + */ +WebSocketInputStreambuf::int_type WebSocketInputStreambuf::underflow() { + ESP_LOGD("WebSocketInputStreambuf", ">> underflow"); + + // If we have already read as many bytes as our record definition says we should read + // then don't attempt to ready any further. + if (m_sizeRead >= getRecordSize()) { + ESP_LOGD("WebSocketInputStreambuf", "<< underflow: Already read maximum"); + return EOF; + } + + // We wish to refill the buffer. We want to read data from the socket. We want to read either + // the size of the buffer to fill it or the maximum number of bytes remaining to be read. + // We will choose which ever is smaller as the number of bytes to read into the buffer. + size_t remainingBytes = getRecordSize() - m_sizeRead; + size_t sizeToRead; + if (remainingBytes < m_bufferSize) { + sizeToRead = remainingBytes; + } else { + sizeToRead = m_bufferSize; + } + + ESP_LOGD("WebSocketInputRecordStreambuf", "- getting next buffer of data; size request: %d", sizeToRead); + size_t bytesRead = m_socket.receive((uint8_t*) m_buffer, sizeToRead, true); + if (bytesRead == 0) { + ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow: Read 0 bytes"); + return EOF; + } + + // If the WebSocket frame shows that we have a mask bit set then we have to unmask the data. + if (m_pMask != nullptr) { + for (int i = 0; i < bytesRead; i++) { + m_buffer[i] = m_buffer[i] ^ m_pMask[(m_sizeRead + i) % 4]; + } + } + + m_sizeRead += bytesRead; // Increase the count of number of bytes actually read from the source. + + setg(m_buffer, m_buffer, m_buffer + bytesRead); // Changethe buffer pointers to reflect the new data read. + ESP_LOGD("WebSocketInputRecordStreambuf", "<< underflow - got %d more bytes", bytesRead); + return traits_type::to_int_type(*gptr()); +} // underflow + + +/** + * @brief Destructor. + */ +WebSocketHandler::~WebSocketHandler() { +} // ~WebSocketHandler() diff --git a/cpp_utils/WebSocket.h b/cpp_utils/WebSocket.h new file mode 100644 index 00000000..6b92aad6 --- /dev/null +++ b/cpp_utils/WebSocket.h @@ -0,0 +1,102 @@ +/* + * WebSocket.h + * + * Created on: Sep 2, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_WEBSOCKET_H_ +#define COMPONENTS_WEBSOCKET_H_ +#include +#include "Socket.h" + +#undef close +#undef send +class WebSocketReader; +class WebSocket; + +// +-------------------------------+ +// | WebSocketInputStreambuf | +// +-------------------------------+ +class WebSocketInputStreambuf : public std::streambuf { +public: + WebSocketInputStreambuf( + Socket socket, + size_t dataLength, + uint8_t* pMask = nullptr, + size_t bufferSize = 2048); + ~WebSocketInputStreambuf(); + int_type underflow(); + void discard(); + size_t getRecordSize(); + +private: + char* m_buffer; + Socket m_socket; + size_t m_dataLength; + size_t m_bufferSize; + size_t m_sizeRead; + uint8_t* m_pMask; + +}; + + +// +------------------+ +// | WebSocketHandler | +// +------------------+ +class WebSocketHandler { +public: + virtual ~WebSocketHandler(); + virtual void onClose(); + virtual void onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf, WebSocket* pWebSocket); + virtual void onError(std::string error); + +}; + + +// +-----------+ +// | WebSocket | +// +-----------+ +class WebSocket { +public: + static const uint16_t CLOSE_NORMAL_CLOSURE = 1000; + static const uint16_t CLOSE_GOING_AWAY = 1001; + static const uint16_t CLOSE_PROTOCOL_ERROR = 1002; + static const uint16_t CLOSE_CANNOT_ACCEPT = 1003; + static const uint16_t CLOSE_NO_STATUS_CODE = 1005; + static const uint16_t CLOSE_CLOSED_ABNORMALLY = 1006; + static const uint16_t CLOSE_NOT_CONSISTENT = 1007; + static const uint16_t CLOSE_VIOLATED_POLICY = 1008; + static const uint16_t CLOSE_TOO_BIG = 1009; + static const uint16_t CLOSE_NO_EXTENSION = 1010; + static const uint16_t CLOSE_UNEXPECTED_CONDITION = 1011; + static const uint16_t CLOSE_SERVICE_RESTART = 1012; + static const uint16_t CLOSE_TRY_AGAIN_LATER = 1013; + static const uint16_t CLOSE_TLS_HANDSHAKE_FAILURE = 1015; + + static const uint8_t SEND_TYPE_BINARY = 0x01; + static const uint8_t SEND_TYPE_TEXT = 0x02; + + WebSocket(Socket socket); + virtual ~WebSocket(); + + void close(uint16_t status = CLOSE_NORMAL_CLOSURE, std::string message = ""); + WebSocketHandler* getHandler(); + Socket getSocket(); + void send(std::string data, uint8_t sendType = SEND_TYPE_BINARY); + void send(uint8_t* data, uint16_t length, uint8_t sendType = SEND_TYPE_BINARY); + void setHandler(WebSocketHandler *handler); + +private: + friend class WebSocketReader; + friend class HttpServerTask; + void startReader(); + bool m_receivedClose; // True when we have received a close request. + bool m_sentClose; // True when we have sent a close request. + Socket m_socket; // Partner socket. + WebSocketHandler* m_pWebSocketHandler; + WebSocketReader* m_pWebSockerReader; + +}; // WebSocket + +#endif /* COMPONENTS_WEBSOCKET_H_ */ diff --git a/cpp_utils/WebSocketFileTransfer.cpp b/cpp_utils/WebSocketFileTransfer.cpp new file mode 100644 index 00000000..aad71454 --- /dev/null +++ b/cpp_utils/WebSocketFileTransfer.cpp @@ -0,0 +1,158 @@ +/* + * WebSocketFileTransfer.cpp + * + * Created on: Sep 9, 2017 + * Author: kolban + */ +#include +#include +#include +#include +#include "GeneralUtils.h" +#include "JSON.h" +static const char* LOG_TAG = "WebSocketFileTransfer"; + +#include "WebSocketFileTransfer.h" + +#undef close + +/** + * @brief Constructor + * @param [in] rootPath The path prefix for new files. + */ +WebSocketFileTransfer::WebSocketFileTransfer(std::string rootPath) { + // If the root path doesn't end with a '/', add one. + if (!GeneralUtils::endsWith(rootPath, '/')) { + rootPath += '/'; + } + m_rootPath = rootPath; + m_pWebSocket = nullptr; + if (rootPath.empty()) { + ESP_LOGE(LOG_TAG, "Root path can not be empty"); + } else if (m_rootPath.substr(m_rootPath.size() - 1) != "/") { + ESP_LOGE(LOG_TAG, "Root path must end with a \"/\""); + } +} // WebSocketFileTransfer + + +// Hide the class in an un-named namespace +namespace { + +/** + * @brief Transfer handler. + */ +class FileTransferWebSocketHandler : public WebSocketHandler { +public: + FileTransferWebSocketHandler(std::string rootPath) { + m_fileName = ""; + m_fileLength = 0; + m_sizeReceived = 0; + m_active = false; + m_rootPath = rootPath; + } // FileTransferWebSocketHandler + + /** + * @brief Handler for the message received over the web socket. + */ + virtual void onMessage(WebSocketInputStreambuf* pWebSocketInputStreambuf) { + ESP_LOGD("FileTransferWebSocketHandler", ">> onMessage"); + // Test to see if we are currently active. If not, this is the start of a transfer. + if (!m_active) { + ESP_LOGD("FileTransferWebSocketHandler", "Not yet active!"); + // Read a chunk of data into memory. + std::stringstream buffer; + buffer << pWebSocketInputStreambuf; + + ESP_LOGD("FileTransferWebSocketHandler", "Data read: %s", buffer.str().c_str()); + + // We expect the first chunk received to be a JSON object that contains + // { + // "name": , // Name of file to create. + // "length": // Length of file. Optional. + // } + JsonObject jo = JSON::parseObject(buffer.str()); + m_fileName = jo.getString("name"); + assert(m_fileName.length() > 0); // Doesn't make any sense to receive a zero length file name. + if (jo.hasItem("length")) { + m_fileLength = jo.getInt("length"); + } + std::string fileName = m_rootPath + m_fileName; + ESP_LOGD("FileTransferWebSocketHandler", "Target file is %s", fileName.c_str()); + + // If the file to create ends in a "/" then we are being asked to create a directory. + if (m_fileName.substr(m_fileName.size() - 1) == "/") { + ESP_LOGD("FileTransferWebSocketHandler", "Is a directory!!"); + fileName = fileName.substr(0, fileName.size() - 1); // Remove the trailing slash + struct stat statbuf; + if (stat(fileName.c_str(), &statbuf) == 0) { + if (S_ISREG(statbuf.st_mode)) { + ESP_LOGE("FileTransferWebSocketHandler", "File already exists and is a file not a directory!"); + } + } // Stat the directory we are trying to create. + else { // The stat on the path failed ... which is ok, as it likely means that the entry didn't exist. + if (mkdir(fileName.c_str(), 0) != 0) { + ESP_LOGE("FileTransferWebSocketHandler", "Failed to make directory \"%s\", error: %s", fileName.c_str(), strerror(errno)); + } + } + } + // We are NOT creating a directory but are instead creating a file. + else { + m_ofStream.open(fileName, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc); + if (!m_ofStream.is_open()) { + ESP_LOGE("FileTransferWebSocketHandler", "Failed to open file %s for writing", m_fileName.c_str()); + return; + } + } + m_active = true; + ESP_LOGD("FileTransferWebSocketHandler", "Filename: %s, length: %d", fileName.c_str(), m_fileLength); + } // !active --- Not active + else { + // We are about to receive a chunk of file + m_ofStream << pWebSocketInputStreambuf; + /* + std::stringstream bufferStream; + bufferStream << pWebSocketInputRecordStreambuf; + m_sizeReceived += bufferStream.str().length(); + ESP_LOGD("FileTransferWebSocketHandler", "Received %d bytes of file data", bufferStream.str().length()); + if (m_fileLength > 0 && m_sizeReceived > m_fileLength) { + ESP_LOGD("FileTransferWebSocketHandler", + "ERROR: Received a total of %d bytes when only %d bytes expected!", m_sizeReceived, m_fileLength); + } + */ + } + } // onMessage + + /** + * @brief Handle a close event on the web socket. + */ + virtual void onClose() { + ESP_LOGD("FileTransferWebSocketHandler", ">> onClose: fileName: %s, sizeReceived: %d", m_fileName.c_str(), m_sizeReceived); + if (m_fileLength > 0 && m_sizeReceived != m_fileLength) { + ESP_LOGD("FileTransferWebSocketHandler", + "ERROR: Transfer finished but we received total of %d bytes and expected %d bytes!", m_sizeReceived, m_fileLength); + } + if (m_ofStream.is_open()) { + m_ofStream.close(); // Close the file now that we have finished writing to it. + } + delete this; // Delete ourselves. + } // onClose + +private: + std::string m_fileName; // The name of the file we are receiving. + uint32_t m_fileLength; // We may optionally receive a file length. + uint32_t m_sizeReceived; // The size of the data actually received so far. + bool m_active; // Are we actively processing a file. + std::ofstream m_ofStream; // The file stream we are writing to when active. + std::string m_rootPath; // The root path for file names. + +}; // FileTransferWebSocketHandler + +} // End un-named namespace + + +void WebSocketFileTransfer::start(WebSocket* pWebSocket) { + ESP_LOGD(LOG_TAG, ">> start"); + pWebSocket->setHandler(new FileTransferWebSocketHandler(m_rootPath)); +} // start + + diff --git a/cpp_utils/WebSocketFileTransfer.h b/cpp_utils/WebSocketFileTransfer.h new file mode 100644 index 00000000..6eb8de43 --- /dev/null +++ b/cpp_utils/WebSocketFileTransfer.h @@ -0,0 +1,23 @@ +/* + * WebSocketFileTransfer.h + * + * Created on: Sep 9, 2017 + * Author: kolban + */ + +#ifndef COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ +#define COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ +#include +#include "WebSocket.h" + +class WebSocketFileTransfer { +private: + WebSocket* m_pWebSocket; // The WebSocket over which the file data will arrive. + std::string m_rootPath; + +public: + WebSocketFileTransfer(std::string rootPath); + void start(WebSocket* pWebSocket); +}; + +#endif /* COMPONENTS_CPP_UTILS_WEBSOCKETFILETRANSFER_H_ */ diff --git a/cpp_utils/WiFi.cpp b/cpp_utils/WiFi.cpp index 2f545d86..3c6d4112 100644 --- a/cpp_utils/WiFi.cpp +++ b/cpp_utils/WiFi.cpp @@ -9,8 +9,8 @@ #include #include #include +#include #include "sdkconfig.h" -#if defined(CONFIG_WIFI_ENABLED) #include "WiFi.h" @@ -19,6 +19,7 @@ #include #include #include +#include "GeneralUtils.h" #include #include #include @@ -26,30 +27,47 @@ #include #include -#include - -static char tag[]= "WiFi"; +static const char* LOG_TAG = "WiFi"; /* static void setDNSServer(char *ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); - inet_pton(AF_INET, ip, &dnsserver); - ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); - dns_setserver(0, &dnsserver); + ip_addr_t dnsserver; + ESP_LOGD(tag, "Setting DNS[%d] to %s", 0, ip); + inet_pton(AF_INET, ip, &dnsserver); + ESP_LOGD(tag, "ip of DNS is %.8x", *(uint32_t *)&dnsserver); + dns_setserver(0, &dnsserver); } */ -WiFi::WiFi() { - ip = ""; - gw = ""; - netmask = ""; - wifiEventHandler = new WiFiEventHandler(); -} +/** + * @brief Creates and uses a default event handler + */ +WiFi::WiFi() + : ip(0) + , gw(0) + , netmask(0) + , m_pWifiEventHandler(nullptr) +{ + m_eventLoopStarted = false; + m_initCalled = false; + //m_pWifiEventHandler = new WiFiEventHandler(); + m_apConnectionStatus = UINT8_MAX; // Are we connected to an access point? +} // WiFi + + +/** + * @brief Deletes the event handler that was used by the class + */ +WiFi::~WiFi() { + if (m_pWifiEventHandler != nullptr) { + delete m_pWifiEventHandler; + m_pWifiEventHandler = nullptr; + } +} // ~WiFi /** @@ -69,51 +87,127 @@ WiFi::WiFi() { * @param [in] ip The IP address of the DNS Server. * @return N/A. */ -void WiFi::addDNSServer(std::string ip) { - ip_addr_t dnsserver; - ESP_LOGD(tag, "Setting DNS[%d] to %s", m_dnsCount, ip.c_str()); - inet_pton(AF_INET, ip.c_str(), &dnsserver); - ::dns_setserver(m_dnsCount, &dnsserver); +void WiFi::addDNSServer(const std::string& ip) { + addDNSServer(ip.c_str()); +} // addDNSServer + + +void WiFi::addDNSServer(const char* ip) { + ip_addr_t dns_server; + if (inet_pton(AF_INET, ip, &dns_server)) { + addDNSServer(ip); + } +} // addDNSServer + + +void WiFi::addDNSServer(ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*) (&ip))[0], ((uint8_t*) (&ip))[1], ((uint8_t*) (&ip))[2], ((uint8_t*) (&ip))[3]); + init(); + ::dns_setserver(m_dnsCount, &ip); m_dnsCount++; + m_dnsCount %= 2; } // addDNSServer + +/** + * @brief Set a reference to a DNS server. + * + * Here we define a server that will act as a DNS server. We use numdns to specify which DNS server to set + * + * For example: + * + * @code{.cpp} + * wifi.setDNSServer(0, "8.8.8.8"); + * wifi.setDNSServer(1, "8.8.4.4"); + * @endcode + * + * @param [in] numdns The DNS number we wish to set + * @param [in] ip The IP address of the DNS Server. + * @return N/A. + */ +void WiFi::setDNSServer(int numdns, const std::string& ip) { + setDNSServer(numdns, ip.c_str()); +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, const char* ip) { + ip_addr_t dns_server; + if (inet_pton(AF_INET, ip, &dns_server)) { + setDNSServer(numdns, dns_server); + } +} // setDNSServer + + +void WiFi::setDNSServer(int numdns, ip_addr_t ip) { + ESP_LOGD(LOG_TAG, "Setting DNS[%d] to %d.%d.%d.%d", m_dnsCount, ((uint8_t*) (&ip))[0], ((uint8_t*) (&ip))[1], ((uint8_t*) (&ip))[2], ((uint8_t*) (&ip))[3]); + init(); + ::dns_setserver(numdns, &ip); +} // setDNSServer + + /** * @brief Connect to an external access point. * * The event handler will be called back with the outcome of the connection. * - * @param[in] ssid The network SSID of the access point to which we wish to connect. - * @param[in] password The password of the access point to which we wish to connect. - * @return N/A. + * @param [in] ssid The network SSID of the access point to which we wish to connect. + * @param [in] password The password of the access point to which we wish to connect. + * @param [in] waitForConnection Block until the connection has an outcome. + * @param [in] mode WIFI_MODE_AP for normal or WIFI_MODE_APSTA if you want to keep an Access Point running while you connect + * @return ESP_OK if we are now connected and wifi_err_reason_t if not. */ -void WiFi::connectAP(std::string ssid, std::string password){ - ::nvs_flash_init(); - ::tcpip_adapter_init(); - if (ip.length() > 0 && gw.length() > 0 && netmask.length() > 0) { +uint8_t WiFi::connectAP(const std::string& ssid, const std::string& password, bool waitForConnection, wifi_mode_t mode){ + ESP_LOGD(LOG_TAG, ">> connectAP"); + + m_apConnectionStatus = UINT8_MAX; + init(); + + if (ip != 0 && gw != 0 && netmask != 0) { ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); // Don't run a DHCP client + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; - inet_pton(AF_INET, ip.data(), &ipInfo.ip); - inet_pton(AF_INET, gw.data(), &ipInfo.gw); - inet_pton(AF_INET, netmask.data(), &ipInfo.netmask); ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); } - - ESP_ERROR_CHECK( esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(::esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); + esp_err_t errRc = ::esp_wifi_set_mode(mode); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } wifi_config_t sta_config; ::memset(&sta_config, 0, sizeof(sta_config)); ::memcpy(sta_config.sta.ssid, ssid.data(), ssid.size()); ::memcpy(sta_config.sta.password, password.data(), password.size()); sta_config.sta.bssid_set = 0; - ESP_ERROR_CHECK(::esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(::esp_wifi_start()); + errRc = ::esp_wifi_set_config(WIFI_IF_STA, &sta_config); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + m_connectFinished.take("connectAP"); // Take the semaphore to wait for a connection. + do { + ESP_LOGD(LOG_TAG, "esp_wifi_connect"); + errRc = ::esp_wifi_connect(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_connect: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + while (!m_connectFinished.take(5000, "connectAP")); // retry if not connected within 5s + m_connectFinished.give(); - ESP_ERROR_CHECK(::esp_wifi_connect()); + ESP_LOGD(LOG_TAG, "<< connectAP"); + return m_apConnectionStatus; // Return ESP_OK if we are now connected and wifi_err_reason_t if not. } // connectAP @@ -121,36 +215,80 @@ void WiFi::connectAP(std::string ssid, std::string password){ * @brief Dump diagnostics to the log. */ void WiFi::dump() { - ESP_LOGD(tag, "WiFi Dump"); - ESP_LOGD(tag, "---------"); + ESP_LOGD(LOG_TAG, "WiFi Dump"); + ESP_LOGD(LOG_TAG, "---------"); char ipAddrStr[30]; ip_addr_t ip = ::dns_getserver(0); inet_ntop(AF_INET, &ip, ipAddrStr, sizeof(ipAddrStr)); - ESP_LOGD(tag, "DNS Server[0]: %s", ipAddrStr); + ESP_LOGD(LOG_TAG, "DNS Server[0]: %s", ipAddrStr); } // dump + +/** + * @brief Returns whether wifi is connected to an access point + */ +bool WiFi::isConnectedToAP() { + return m_apConnectionStatus; +} // isConnected + + +/** + * @brief Primary event handler interface. + */ +/* STATIC */ esp_err_t WiFi::eventHandler(void* ctx, system_event_t* event) { + // This is the common event handler that we have provided for all event processing. It is called for every event + // that is received by the WiFi subsystem. The "ctx" parameter is an instance of the current WiFi object that we are + // processing. We can then retrieve the specific/custom event handler from within it and invoke that. This then makes this + // an indirection vector to the real caller. + + WiFi* pWiFi = (WiFi*) ctx; // retrieve the WiFi object from the passed in context. + + // Invoke the event handler. + esp_err_t rc; + if (pWiFi->m_pWifiEventHandler != nullptr) { + rc = pWiFi->m_pWifiEventHandler->getEventHandler()(pWiFi->m_pWifiEventHandler, event); + } else { + rc = ESP_OK; + } + + // If the event we received indicates that we now have an IP address or that a connection was disconnected then unlock the mutex that + // indicates we are waiting for a connection complete. + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP || event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) { + if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) { // If we connected and have an IP, change the status to ESP_OK. Otherwise, change it to the reason code. + pWiFi->m_apConnectionStatus = ESP_OK; + } else { + pWiFi->m_apConnectionStatus = event->event_info.disconnected.reason; + } + pWiFi->m_connectFinished.give(); + } + + return rc; +} // eventHandler + + /** * @brief Get the AP IP Info. * @return The AP IP Info. */ tcpip_adapter_ip_info_t WiFi::getApIpInfo() { + //init(); tcpip_adapter_ip_info_t ipInfo; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); + ::tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ipInfo); return ipInfo; } // getApIpInfo - /** * @brief Get the MAC address of the AP interface. * @return The MAC address of the AP interface. */ std::string WiFi::getApMac() { uint8_t mac[6]; + //init(); esp_wifi_get_mac(WIFI_IF_AP, mac); - std::stringstream s; - s << std::hex << std::setfill('0') << std::setw(2) << (int) mac[0] << ':' << (int) mac[1] << ':' << (int) mac[2] << ':' << (int) mac[3] << ':' << (int) mac[4] << ':' << (int) mac[5]; - return s.str(); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getApMac @@ -160,11 +298,45 @@ std::string WiFi::getApMac() { */ std::string WiFi::getApSSID() { wifi_config_t conf; + //init(); esp_wifi_get_config(WIFI_IF_AP, &conf); - return std::string((char *)conf.sta.ssid); + return std::string((char*) conf.sta.ssid); } // getApSSID +/** + * @brief Get the current ESP32 IP form AP. + * @return The ESP32 IP. + */ +std::string WiFi::getApIp() { + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + +/** + * @brief Get the current AP netmask. + * @return The Netmask IP. + */ +std::string WiFi::getApNetmask() { + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + +/** + * @brief Get the current AP Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getApGateway() { + tcpip_adapter_ip_info_t ipInfo = getApIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + /** * @brief Lookup an IP address by host name. * @@ -172,16 +344,20 @@ std::string WiFi::getApSSID() { * * @return The IP address of the host or 0.0.0.0 if not found. */ -struct in_addr WiFi::getHostByName(std::string hostName) { +struct in_addr WiFi::getHostByName(const std::string& hostName) { + return getHostByName(hostName.c_str()); +} // getHostByName + + +struct in_addr WiFi::getHostByName(const char* hostName) { struct in_addr retAddr; - struct hostent *he = gethostbyname(hostName.c_str()); + struct hostent* he = gethostbyname(hostName); if (he == nullptr) { retAddr.s_addr = 0; - ESP_LOGD(tag, "Unable to resolve %s - %d", hostName.c_str(), h_errno); + ESP_LOGD(LOG_TAG, "Unable to resolve %s - %d", hostName, h_errno); } else { - retAddr = *(struct in_addr *)(he->h_addr_list[0]); - //ESP_LOGD(tag, "resolved %s to %.8x", hostName, *(uint32_t *)&retAddr); - + retAddr = *(struct in_addr*) (he->h_addr_list[0]); + ESP_LOGD(LOG_TAG, "resolved %s to %.8x", hostName, *(uint32_t*) &retAddr); } return retAddr; } // getHostByName @@ -194,7 +370,7 @@ struct in_addr WiFi::getHostByName(std::string hostName) { std::string WiFi::getMode() { wifi_mode_t mode; esp_wifi_get_mode(&mode); - switch(mode) { + switch (mode) { case WIFI_MODE_NULL: return "WIFI_MODE_NULL"; case WIFI_MODE_STA: @@ -220,6 +396,42 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { } // getStaIpInfo +/** + * @brief Get the current ESP32 IP form STA. + * @return The ESP32 IP. + */ +std::string WiFi::getStaIp() { + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.ip.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaIp + + +/** + * @brief Get the current STA netmask. + * @return The Netmask IP. + */ +std::string WiFi::getStaNetmask() { + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.netmask.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaNetmask + + +/** + * @brief Get the current STA Gateway IP. + * @return The Gateway IP. + */ +std::string WiFi::getStaGateway() { + tcpip_adapter_ip_info_t ipInfo = getStaIpInfo(); + char ipAddrStr[30]; + inet_ntop(AF_INET, &ipInfo.gw.addr, ipAddrStr, sizeof(ipAddrStr)); + return std::string(ipAddrStr); +} // getStaGateway + + /** * @brief Get the MAC address of the STA interface. * @return The MAC address of the STA interface. @@ -227,9 +439,9 @@ tcpip_adapter_ip_info_t WiFi::getStaIpInfo() { std::string WiFi::getStaMac() { uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); - std::stringstream s; - s << std::hex << std::setfill('0') << std::setw(2) << (int) mac[0] << ':' << (int) mac[1] << ':' << (int) mac[2] << ':' << (int) mac[3] << ':' << (int) mac[4] << ':' << (int) mac[5]; - return s.str(); + auto mac_str = (char*) malloc(18); + sprintf(mac_str, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(std::move(mac_str)); } // getStaMac @@ -240,10 +452,49 @@ std::string WiFi::getStaMac() { std::string WiFi::getStaSSID() { wifi_config_t conf; esp_wifi_get_config(WIFI_IF_STA, &conf); - return std::string((char *)conf.ap.ssid); + return std::string((char*) conf.ap.ssid); } // getStaSSID +/** + * @brief Initialize WiFi. + */ +/* PRIVATE */ void WiFi::init() { + // If we have already started the event loop, then change the handler otherwise + // start the event loop. + if (m_eventLoopStarted) { + esp_event_loop_set_cb(WiFi::eventHandler, this); // Returns the old handler. + } else { + esp_err_t errRc = ::esp_event_loop_init(WiFi::eventHandler, this); // Initialze the event handler. + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_event_loop_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + m_eventLoopStarted = true; + } + // Now, one way or another, the event handler is WiFi::eventHandler. + + if (!m_initCalled) { + ::nvs_flash_init(); + ::tcpip_adapter_init(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t errRc = ::esp_wifi_init(&cfg); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_set_storage(WIFI_STORAGE_RAM); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_storage: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + } + m_initCalled = true; +} // init + + /** * @brief Perform a WiFi scan looking for access points. * @@ -255,37 +506,67 @@ std::string WiFi::getStaSSID() { * @return A vector of WiFiAPRecord instances. */ std::vector WiFi::scan() { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK(::esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(::esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK( esp_wifi_start() ); + ESP_LOGD(LOG_TAG, ">> scan"); + std::vector apRecords; + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_STA); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + wifi_scan_config_t conf; memset(&conf, 0, sizeof(conf)); conf.show_hidden = true; + esp_err_t rc = ::esp_wifi_scan_start(&conf, true); if (rc != ESP_OK) { - ESP_LOGE(tag, "esp_wifi_scan_start: %d", rc); + ESP_LOGE(LOG_TAG, "esp_wifi_scan_start: %d", rc); + return apRecords; } - uint16_t apCount; + + uint16_t apCount; // Number of access points available. rc = ::esp_wifi_scan_get_ap_num(&apCount); - ESP_LOGD(tag, "Count of found access points: %d", apCount); - wifi_ap_record_t *list = - (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); - ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list)); - std::vector apRecords; - for (auto i=0; i rhs.m_rssi; }); + return apRecords; } // scan @@ -294,33 +575,94 @@ std::vector WiFi::scan() { * * @param[in] ssid The SSID to use to advertize for stations. * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP * @return N/A. */ -void WiFi::startAP(std::string ssid, std::string password) { - ::nvs_flash_init(); - ::tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(wifiEventHandler->getEventHandler(), wifiEventHandler)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_AP) ); +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth) { + startAP(ssid, password, auth, 0, false, 4); +} // startAP + + +/** + * @brief Start being an access point. + * + * @param[in] ssid The SSID to use to advertize for stations. + * @param[in] password The password to use for station connections. + * @param[in] auth The authorization mode for access to this access point. Options are: + * * WIFI_AUTH_OPEN + * * WIFI_AUTH_WPA_PSK + * * WIFI_AUTH_WPA2_PSK + * * WIFI_AUTH_WPA_WPA2_PSK + * * WIFI_AUTH_WPA2_ENTERPRISE + * * WIFI_AUTH_WEP + * @param[in] channel from the access point. + * @param[in] is the ssid hidden, ore not. + * @param[in] limiting number of clients. + * @return N/A. + */ +void WiFi::startAP(const std::string& ssid, const std::string& password, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection) { + ESP_LOGD(LOG_TAG, ">> startAP: ssid: %s", ssid.c_str()); + + init(); + + esp_err_t errRc = ::esp_wifi_set_mode(WIFI_MODE_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_mode: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + // Build the apConfig structure. wifi_config_t apConfig; ::memset(&apConfig, 0, sizeof(apConfig)); ::memcpy(apConfig.ap.ssid, ssid.data(), ssid.size()); - apConfig.ap.ssid_len = 0; + apConfig.ap.ssid_len = ssid.size(); ::memcpy(apConfig.ap.password, password.data(), password.size()); - apConfig.ap.channel = 0; - apConfig.ap.authmode = WIFI_AUTH_OPEN; - apConfig.ap.ssid_hidden = 0; - apConfig.ap.max_connection = 4; + apConfig.ap.channel = channel; + apConfig.ap.authmode = auth; + apConfig.ap.ssid_hidden = (uint8_t) ssid_hidden; + apConfig.ap.max_connection = max_connection; apConfig.ap.beacon_interval = 100; - ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &apConfig) ); - ESP_ERROR_CHECK( esp_wifi_start() ); + + errRc = ::esp_wifi_set_config(WIFI_IF_AP, &apConfig); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_set_config: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = ::esp_wifi_start(); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "esp_wifi_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } + + errRc = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "tcpip_adapter_dhcps_start: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + ESP_LOGD(LOG_TAG, "<< startAP"); } // startAP +/** + * @brief Set the event handler to use to process detected events. + * @param[in] wifiEventHandler The class that will be used to process events. + */ +void WiFi::setWifiEventHandler(WiFiEventHandler* wifiEventHandler) { + ESP_LOGD(LOG_TAG, ">> setWifiEventHandler: 0x%d", (uint32_t) wifiEventHandler); + this->m_pWifiEventHandler = wifiEventHandler; + ESP_LOGD(LOG_TAG, "<< setWifiEventHandler"); +} // setWifiEventHandler + /** - * @brief Set the IP info used when connecting as a station to an external access point. + * @brief Set the IP info and enable DHCP if ip != 0. If called with ip == 0 then DHCP is enabled. + * If called with bad values it will do nothing. * * Do not call this method if we are being an access point ourselves. * @@ -335,10 +677,50 @@ void WiFi::startAP(std::string ssid, std::string password) { * @param [in] netmask Netmask value. * @return N/A. */ -void WiFi::setIPInfo(std::string ip, std::string gw, std::string netmask) { - this->ip = ip; - this->gw = gw; +void WiFi::setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask) { + setIPInfo(ip.c_str(), gw.c_str(), netmask.c_str()); +} // setIPInfo + + +void WiFi::setIPInfo(const char* ip, const char* gw, const char* netmask) { + uint32_t new_ip; + uint32_t new_gw; + uint32_t new_netmask; + + auto success = (bool) inet_pton(AF_INET, ip, &new_ip); + success = success && inet_pton(AF_INET, gw, &new_gw); + success = success && inet_pton(AF_INET, netmask, &new_netmask); + + if (!success) return; + + setIPInfo(new_ip, new_gw, new_netmask); +} // setIPInfo + + +/** + * @brief Set the IP Info based on the IP address, gateway and netmask. + * @param [in] ip The IP address of our ESP32. + * @param [in] gw The gateway we should use. + * @param [in] netmask Our TCP/IP netmask value. + */ +void WiFi::setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask) { + init(); + + this->ip = ip; + this->gw = gw; this->netmask = netmask; + + if (ip != 0 && gw != 0 && netmask != 0) { + tcpip_adapter_ip_info_t ipInfo; + ipInfo.ip.addr = ip; + ipInfo.gw.addr = gw; + ipInfo.netmask.addr = netmask; + ::tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + ::tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo); + } else { + ip = 0; + ::tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + } } // setIPInfo @@ -349,41 +731,52 @@ void WiFi::setIPInfo(std::string ip, std::string gw, std::string netmask) { */ std::string WiFiAPRecord::toString() { std::string auth; - switch(getAuthMode()) { - case WIFI_AUTH_OPEN: - auth = "WIFI_AUTH_OPEN"; - break; - case WIFI_AUTH_WEP: - auth = "WIFI_AUTH_WEP"; - break; - case WIFI_AUTH_WPA_PSK: - auth = "WIFI_AUTH_WPA_PSK"; - break; - case WIFI_AUTH_WPA2_PSK: - auth = "WIFI_AUTH_WPA2_PSK"; - break; - case WIFI_AUTH_WPA_WPA2_PSK: - auth = "WIFI_AUTH_WPA_WPA2_PSK"; - break; - default: - auth = ""; - break; - } - std::stringstream s; - s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; - return s.str(); + switch (getAuthMode()) { + case WIFI_AUTH_OPEN: + auth = "WIFI_AUTH_OPEN"; + break; + case WIFI_AUTH_WEP: + auth = "WIFI_AUTH_WEP"; + break; + case WIFI_AUTH_WPA_PSK: + auth = "WIFI_AUTH_WPA_PSK"; + break; + case WIFI_AUTH_WPA2_PSK: + auth = "WIFI_AUTH_WPA2_PSK"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + auth = "WIFI_AUTH_WPA_WPA2_PSK"; + break; + default: + auth = ""; + break; + } +// std::stringstream s; +// s<< "ssid: " << m_ssid << ", auth: " << auth << ", rssi: " << m_rssi; + auto info_str = (char*) malloc(6 + 32 + 8 + 22 + 8 + 3 + 1); + sprintf(info_str, "ssid: %s, auth: %s, rssi: %d", m_ssid.c_str(), auth.c_str(), (int) m_rssi); + return std::string(std::move(info_str)); } // toString + +/* MDNS::MDNS() { - ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server)); + esp_err_t errRc = ::mdns_init(TCPIP_ADAPTER_IF_STA, &m_mdns_server); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } } + MDNS::~MDNS() { if (m_mdns_server != nullptr) { mdns_free(m_mdns_server); } m_mdns_server = nullptr; } +*/ + /** * @brief Define the service for mDNS. @@ -393,24 +786,26 @@ MDNS::~MDNS() { * @param [in] port * @return N/A. */ -void MDNS::serviceAdd(std::string service, std::string proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_add(m_mdns_server, service.c_str(), proto.c_str(), port)); +/* +void MDNS::serviceAdd(const std::string& service, const std::string& proto, uint16_t port) { + serviceAdd(service.c_str(), proto.c_str(), port); } // serviceAdd -void MDNS::serviceInstanceSet(std::string service, std::string proto, std::string instance) { - ESP_ERROR_CHECK(mdns_service_instance_set(m_mdns_server, service.c_str(), proto.c_str(), instance.c_str())); +void MDNS::serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance) { + serviceInstanceSet(service.c_str(), proto.c_str(), instance.c_str()); } // serviceInstanceSet -void MDNS::servicePortSet(std::string service, std::string proto, uint16_t port) { - ESP_ERROR_CHECK(mdns_service_port_set(m_mdns_server, service.c_str(), proto.c_str(), port)); +void MDNS::servicePortSet(const std::string& service, const std::string& proto, uint16_t port) { + servicePortSet(service.c_str(), proto.c_str(), port); } // servicePortSet -void MDNS::serviceRemove(std::string service, std::string proto) { - ESP_ERROR_CHECK(mdns_service_remove(m_mdns_server, service.c_str(), proto.c_str())); +void MDNS::serviceRemove(const std::string& service, const std::string& proto) { + serviceRemove(service.c_str(), proto.c_str()); } // serviceRemove +*/ /** @@ -419,9 +814,11 @@ void MDNS::serviceRemove(std::string service, std::string proto) { * @param [in] hostname The host name to set against the mDNS. * @return N/A. */ -void MDNS::setHostname(std::string hostname) { - ESP_ERROR_CHECK(mdns_set_hostname(m_mdns_server,hostname.c_str())); +/* +void MDNS::setHostname(const std::string& hostname) { + setHostname(hostname.c_str()); } // setHostname +*/ /** @@ -430,9 +827,88 @@ void MDNS::setHostname(std::string hostname) { * @param [in] instance The instance name to set against the mDNS. * @return N/A. */ -void MDNS::setInstance(std::string instance) { - ESP_ERROR_CHECK(mdns_set_instance(m_mdns_server, instance.c_str())); +/* +void MDNS::setInstance(const std::string& instance) { + setInstance(instance.c_str()); } // setInstance +*/ -#endif // CONFIG_WIFI_ENABLED +/** + * @brief Define the service for mDNS. + * + * @param [in] service + * @param [in] proto + * @param [in] port + * @return N/A. + */ +/* +void MDNS::serviceAdd(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_add(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_add: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceAdd + + +void MDNS::serviceInstanceSet(const char* service, const char* proto, const char* instance) { + esp_err_t errRc = ::mdns_service_instance_set(m_mdns_server, service, proto, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_instance_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceInstanceSet + + +void MDNS::servicePortSet(const char* service, const char* proto, uint16_t port) { + esp_err_t errRc = ::mdns_service_port_set(m_mdns_server, service, proto, port); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_port_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // servicePortSet + + +void MDNS::serviceRemove(const char* service, const char* proto) { + esp_err_t errRc = ::mdns_service_remove(m_mdns_server, service, proto); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_service_remove: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // serviceRemove +*/ + + +/** + * @brief Set the mDNS hostname. + * + * @param [in] hostname The host name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setHostname(const char* hostname) { + esp_err_t errRc = ::mdns_set_hostname(m_mdns_server,hostname); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_hostname: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setHostname +*/ + + +/** + * @brief Set the mDNS instance. + * + * @param [in] instance The instance name to set against the mDNS. + * @return N/A. + */ +/* +void MDNS::setInstance(const char* instance) { + esp_err_t errRc = ::mdns_set_instance(m_mdns_server, instance); + if (errRc != ESP_OK) { + ESP_LOGE(LOG_TAG, "mdns_set_instance: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + abort(); + } +} // setInstance +*/ diff --git a/cpp_utils/WiFi.h b/cpp_utils/WiFi.h index 9804e492..c0bfa07a 100644 --- a/cpp_utils/WiFi.h +++ b/cpp_utils/WiFi.h @@ -8,28 +8,42 @@ #ifndef MAIN_WIFI_H_ #define MAIN_WIFI_H_ #include "sdkconfig.h" -#if defined(CONFIG_WIFI_ENABLED) + #include #include #include +#include +#include "FreeRTOS.h" #include "WiFiEventHandler.h" /** * @brief Manage mDNS server. */ +/* class MDNS { public: MDNS(); ~MDNS(); - void serviceAdd(std::string service, std::string proto, uint16_t port); - void serviceInstanceSet(std::string service, std::string proto, std::string instance); - void servicePortSet(std::string service, std::string proto, uint16_t port); - void serviceRemove(std::string service, std::string proto); - void setHostname(std::string hostname); - void setInstance(std::string instance); + void serviceAdd(const std::string& service, const std::string& proto, uint16_t port); + void serviceInstanceSet(const std::string& service, const std::string& proto, const std::string& instance); + void servicePortSet(const std::string& service, const std::string& proto, uint16_t port); + void serviceRemove(const std::string& service, const std::string& proto); + void setHostname(const std::string& hostname); + void setInstance(const std::string& instance); + // If we the above functions with a basic char*, a copy would be created into an std::string, + // making the whole thing require twice as much processing power and speed + void serviceAdd(const char* service, const char* proto, uint16_t port); + void serviceInstanceSet(const char* service, const char* proto, const char* instance); + void servicePortSet(const char* service, const char* proto, uint16_t port); + void serviceRemove(const char* service, const char* proto); + void setHostname(const char* hostname); + void setInstance(const char* instance); + private: - mdns_server_t *m_mdns_server = nullptr; + mdns_server_t* m_mdns_server = nullptr; + }; +*/ class WiFiAPRecord { public: @@ -62,10 +76,11 @@ class WiFiAPRecord { std::string toString(); private: - uint8_t m_bssid[6]; - int8_t m_rssi; - std::string m_ssid; + uint8_t m_bssid[6]; + int8_t m_rssi; + std::string m_ssid; wifi_auth_mode_t m_authMode; + }; /** @@ -95,44 +110,53 @@ class WiFiAPRecord { */ class WiFi { private: - std::string ip; - std::string gw; - std::string netmask; - WiFiEventHandler *wifiEventHandler; + static esp_err_t eventHandler(void* ctx, system_event_t* event); + void init(); + uint32_t ip; + uint32_t gw; + uint32_t netmask; + WiFiEventHandler* m_pWifiEventHandler; + uint8_t m_dnsCount = 0; + bool m_eventLoopStarted; + bool m_initCalled; + uint8_t m_apConnectionStatus; // ESP_OK = we are connected to an access point. Otherwise receives wifi_err_reason_t. + FreeRTOS::Semaphore m_connectFinished = FreeRTOS::Semaphore("ConnectFinished"); public: WiFi(); - void addDNSServer(std::string ip); - struct in_addr getHostByName(std::string hostName); - void connectAP(std::string ssid, std::string passwd); - void dump(); - static std::string getApMac(); + ~WiFi(); + void addDNSServer(const std::string& ip); + void addDNSServer(const char* ip); + void addDNSServer(ip_addr_t ip); + void setDNSServer(int numdns, const std::string& ip); + void setDNSServer(int numdns, const char* ip); + void setDNSServer(int numdns, ip_addr_t ip); + struct in_addr getHostByName(const std::string& hostName); + struct in_addr getHostByName(const char* hostName); + uint8_t connectAP(const std::string& ssid, const std::string& password, bool waitForConnection = true, wifi_mode_t mode = WIFI_MODE_STA); + void dump(); + bool isConnectedToAP(); + static std::string getApMac(); static tcpip_adapter_ip_info_t getApIpInfo(); - static std::string getApSSID(); - static std::string getMode(); + static std::string getApSSID(); + static std::string getApIp(); + static std::string getApNetmask(); + static std::string getApGateway(); + static std::string getMode(); static tcpip_adapter_ip_info_t getStaIpInfo(); - static std::string getStaMac(); - static std::string getStaSSID(); + static std::string getStaMac(); + static std::string getStaSSID(); + static std::string getStaIp(); + static std::string getStaNetmask(); + static std::string getStaGateway(); std::vector scan(); - void startAP(std::string ssid, std::string passwd); - void setIPInfo(std::string ip, std::string gw, std::string netmask); - - - - - - /** - * Set the event handler to use to process detected events. - * @param[in] wifiEventHandler The class that will be used to process events. - */ - void setWifiEventHandler(WiFiEventHandler *wifiEventHandler) { - this->wifiEventHandler = wifiEventHandler; - } -private: - int m_dnsCount=0; - //char *m_dnsServer = nullptr; + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth = WIFI_AUTH_OPEN); + void startAP(const std::string& ssid, const std::string& passwd, wifi_auth_mode_t auth, uint8_t channel, bool ssid_hidden, uint8_t max_connection); + void setIPInfo(const std::string& ip, const std::string& gw, const std::string& netmask); + void setIPInfo(const char* ip, const char* gw, const char* netmask); + void setIPInfo(uint32_t ip, uint32_t gw, uint32_t netmask); + void setWifiEventHandler(WiFiEventHandler* wifiEventHandler); }; -#endif // CONFIG_WIFI_ENABLED #endif /* MAIN_WIFI_H_ */ diff --git a/cpp_utils/WiFiEventHandler.cpp b/cpp_utils/WiFiEventHandler.cpp index 1c454d54..204a0661 100644 --- a/cpp_utils/WiFiEventHandler.cpp +++ b/cpp_utils/WiFiEventHandler.cpp @@ -13,7 +13,7 @@ #include #include "sdkconfig.h" -static char tag[] = "WiFiEventHandler"; +static const char* LOG_TAG = "WiFiEventHandler"; /** * @brief The entry point into the event handler. @@ -23,54 +23,96 @@ static char tag[] = "WiFiEventHandler"; * @param [in] event * @return ESP_OK if the event was handled otherwise an error. */ -esp_err_t WiFiEventHandler::eventHandler(void *ctx, system_event_t *event) { - ESP_LOGD(tag, "eventHandler called"); - WiFiEventHandler *pWiFiEventHandler = (WiFiEventHandler *)ctx; +esp_err_t WiFiEventHandler::eventHandler(void* ctx, system_event_t* event) { + ESP_LOGD(LOG_TAG, ">> eventHandler called: ctx=0x%x, event=0x%x", (uint32_t) ctx, (uint32_t) event); + WiFiEventHandler* pWiFiEventHandler = (WiFiEventHandler*) ctx; if (ctx == nullptr) { - ESP_LOGD(tag, "No context"); + ESP_LOGD(LOG_TAG, "No context"); return ESP_OK; } - esp_err_t rc = ESP_OK; - switch(event->event_id) { - case SYSTEM_EVENT_AP_START: + esp_err_t rc = ESP_OK; + switch (event->event_id) { + case SYSTEM_EVENT_AP_START: { rc = pWiFiEventHandler->apStart(); break; - case SYSTEM_EVENT_AP_STOP: - rc = pWiFiEventHandler->apStop(); + } + + case SYSTEM_EVENT_AP_STOP: { + rc = pWiFiEventHandler->apStop(); + break; + } + + case SYSTEM_EVENT_AP_STACONNECTED: { + rc = pWiFiEventHandler->apStaConnected(event->event_info.sta_connected); break; - case SYSTEM_EVENT_STA_CONNECTED: - rc = pWiFiEventHandler->staConnected(); + } + + case SYSTEM_EVENT_AP_STADISCONNECTED: { + rc = pWiFiEventHandler->apStaDisconnected(event->event_info.sta_disconnected); break; - case SYSTEM_EVENT_STA_DISCONNECTED: - rc = pWiFiEventHandler->staDisconnected(); + } + + case SYSTEM_EVENT_SCAN_DONE: { + rc = pWiFiEventHandler->staScanDone(event->event_info.scan_done); break; - case SYSTEM_EVENT_STA_GOT_IP: + } + + case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: { + rc = pWiFiEventHandler->staAuthChange(event->event_info.auth_change); + break; + } + + case SYSTEM_EVENT_STA_CONNECTED: { + rc = pWiFiEventHandler->staConnected(event->event_info.connected); + break; + } + + case SYSTEM_EVENT_STA_DISCONNECTED: { + rc = pWiFiEventHandler->staDisconnected(event->event_info.disconnected); + break; + } + + case SYSTEM_EVENT_STA_GOT_IP: { rc = pWiFiEventHandler->staGotIp(event->event_info.got_ip); break; - case SYSTEM_EVENT_STA_START: - rc = pWiFiEventHandler->staStart(); + } + + case SYSTEM_EVENT_STA_START: { + rc = pWiFiEventHandler->staStart(); break; - case SYSTEM_EVENT_STA_STOP: - rc = pWiFiEventHandler->staStop(); + } + + case SYSTEM_EVENT_STA_STOP: { + rc = pWiFiEventHandler->staStop(); break; - case SYSTEM_EVENT_WIFI_READY: - rc = pWiFiEventHandler->wifiReady(); + } + + case SYSTEM_EVENT_WIFI_READY: { + rc = pWiFiEventHandler->wifiReady(); break; + } + default: break; } - if (pWiFiEventHandler->nextHandler != nullptr) { - printf("Found a next handler\n"); - rc = eventHandler(pWiFiEventHandler->nextHandler, event); + + if (pWiFiEventHandler->m_nextHandler != nullptr) { + ESP_LOGD(LOG_TAG, "Found a next handler"); + rc = eventHandler(pWiFiEventHandler->m_nextHandler, event); } else { - //printf("NOT Found a next handler\n"); + //ESP_LOGD(LOG_TAG, "NOT Found a next handler"); } return rc; -} +} // eventHandler + +/** + * @brief Constructor + */ WiFiEventHandler::WiFiEventHandler() { -} + m_nextHandler = nullptr; +} // WiFiEventHandler /** @@ -79,7 +121,9 @@ WiFiEventHandler::WiFiEventHandler() { * @return The event handler function. */ system_event_cb_t WiFiEventHandler::getEventHandler() { - return eventHandler; + ESP_LOGD(LOG_TAG, ">> getEventHandler()"); + ESP_LOGD(LOG_TAG, "<< getEventHandler: 0x%x", (uint32_t)eventHandler); + return eventHandler; } // getEventHandler @@ -89,68 +133,123 @@ system_event_cb_t WiFiEventHandler::getEventHandler() { * @param [in] event_sta_got_ip The Station Got IP event. * @return An indication of whether or not we processed the event successfully. */ -esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { - ESP_LOGD(tag, "default staGotIp"); +esp_err_t WiFiEventHandler::staGotIp(system_event_sta_got_ip_t info) { + ESP_LOGD(LOG_TAG, "default staGotIp"); return ESP_OK; } // staGotIp + /** * @brief Handle the Access Point started event. * Handle an indication that the ESP32 has started being an access point. * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStart() { - ESP_LOGD(tag, "default apStart"); + ESP_LOGD(LOG_TAG, "default apStart"); return ESP_OK; } // apStart + /** * @brief Handle the Access Point stop event. * Handle an indication that the ESP32 has stopped being an access point. * @return An indication of whether or not we processed the event successfully. */ esp_err_t WiFiEventHandler::apStop() { - ESP_LOGD(tag, "default apStop"); + ESP_LOGD(LOG_TAG, "default apStop"); return ESP_OK; } // apStop + esp_err_t WiFiEventHandler::wifiReady() { - ESP_LOGD(tag, "default wifiReady"); + ESP_LOGD(LOG_TAG, "default wifiReady"); return ESP_OK; } // wifiReady + esp_err_t WiFiEventHandler::staStart() { - ESP_LOGD(tag, "default staStart"); + ESP_LOGD(LOG_TAG, "default staStart"); return ESP_OK; } // staStart + esp_err_t WiFiEventHandler::staStop() { - ESP_LOGD(tag, "default staStop"); + ESP_LOGD(LOG_TAG, "default staStop"); return ESP_OK; } // staStop -esp_err_t WiFiEventHandler::staConnected() { - ESP_LOGD(tag, "default staConnected"); + +/** + * @brief Handle the Station Connected event. + * Handle having connected to remote AP. + * @param [in] event_connected system_event_sta_connected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staConnected(system_event_sta_connected_t info) { + ESP_LOGD(LOG_TAG, "default staConnected"); return ESP_OK; } // staConnected -esp_err_t WiFiEventHandler::staDisconnected() { - ESP_LOGD(tag, "default staDisconnected"); + +/** + * @brief Handle the Station Disconnected event. + * Handle having disconnected from remote AP. + * @param [in] event_disconnected system_event_sta_disconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staDisconnected(system_event_sta_disconnected_t info) { + ESP_LOGD(LOG_TAG, "default staDisconnected"); return ESP_OK; } // staDisconnected -esp_err_t WiFiEventHandler::apStaConnected() { - ESP_LOGD(tag, "default apStaConnected"); + +/** + * @brief Handle a Station Connected to AP event. + * Handle having a station connected to ESP32 soft-AP. + * @param [in] event_sta_connected system_event_ap_staconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::apStaConnected(system_event_ap_staconnected_t info) { + ESP_LOGD(LOG_TAG, "default apStaConnected"); return ESP_OK; } // apStaConnected -esp_err_t WiFiEventHandler::apStaDisconnected() { - ESP_LOGD(tag, "default apStaDisconnected"); + +/** + * @brief Handle a Station Disconnected from AP event. + * Handle having a station disconnected from ESP32 soft-AP. + * @param [in] event_sta_disconnected system_event_ap_stadisconnected_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::apStaDisconnected(system_event_ap_stadisconnected_t info) { + ESP_LOGD(LOG_TAG, "default apStaDisconnected"); return ESP_OK; } // apStaDisconnected + +/** + * @brief Handle a Scan for APs done event. + * Handle having an ESP32 station scan (APs) done. + * @param [in] event_scan_done system_event_sta_scan_done_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staScanDone(system_event_sta_scan_done_t info) { + ESP_LOGD(LOG_TAG, "default staScanDone"); + return ESP_OK; +} // staScanDone + + +/** + * @brief Handle the auth mode of APs change event. + * Handle having the auth mode of AP ESP32 station connected to changed. + * @param [in] event_auth_change system_event_sta_authmode_change_t. + * @return An indication of whether or not we processed the event successfully. + */ +esp_err_t WiFiEventHandler::staAuthChange(system_event_sta_authmode_change_t info) { + ESP_LOGD(LOG_TAG, "default staAuthChange"); + return ESP_OK; +} // staAuthChange + + WiFiEventHandler::~WiFiEventHandler() { - if (nextHandler != nullptr) { - delete nextHandler; - } } // ~WiFiEventHandler diff --git a/cpp_utils/WiFiEventHandler.h b/cpp_utils/WiFiEventHandler.h index 9bfb7e19..9efb837f 100644 --- a/cpp_utils/WiFiEventHandler.h +++ b/cpp_utils/WiFiEventHandler.h @@ -22,13 +22,15 @@ * wifi.startAP("MYSSID", "password"); * * The overridable functions are: - * * esp_err_t apStaConnected() - * * esp_err_t apStaDisconnected() + * * esp_err_t apStaConnected(system_event_ap_staconnected_t info) + * * esp_err_t apStaDisconnected(system_event_ap_stadisconnected_t info) * * esp_err_t apStart() * * esp_err_t apStop() - * * esp_err_t staConnected() - * * esp_err_t staDisconnected() - * * esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) + * * esp_err_t staConnected(system_event_sta_connected_t info) + * * esp_err_t staDisconnected(system_event_sta_disconnected_t info) + * * esp_err_t staGotIp(system_event_sta_got_ip_t info) + * * esp_err_t staScanDone(system_event_sta_scan_done_t info) + * * esp_err_t staAuthChange(system_event_sta_authmode_change_t info) * * esp_err_t staStart() * * esp_err_t staStop() * * esp_err_t wifiReady() @@ -56,11 +58,19 @@ * return ESP_OK; * } * - * esp_err_t MyHandler::staConnected() { + * esp_err_t MyHandler::staConnected(system_event_sta_connected_t info) { * return ESP_OK; * } * - * esp_err_t MyHandler::staDisconnected() { + * esp_err_t MyHandler::staDisconnected(system_event_sta_disconnected_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::apStaConnected(system_event_ap_staconnected_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::apStaDisconnected(system_event_ap_stadisconnected_t info) { * return ESP_OK; * } * @@ -68,7 +78,15 @@ * return ESP_OK; * } * - * esp_err_t MyHandler::staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + * esp_err_t MyHandler::staGotIp(system_event_sta_got_ip_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::staScanDone(system_event_sta_scan_done_t info) { + * return ESP_OK; + * } + * + * esp_err_t MyHandler::staAuthChange(system_event_sta_authmode_change_t info) { * return ESP_OK; * } * @@ -81,14 +99,16 @@ class WiFiEventHandler { public: WiFiEventHandler(); virtual ~WiFiEventHandler(); - system_event_cb_t getEventHandler(); - virtual esp_err_t apStaConnected(); - virtual esp_err_t apStaDisconnected(); + virtual esp_err_t apStaConnected(system_event_ap_staconnected_t info); + virtual esp_err_t apStaDisconnected(system_event_ap_stadisconnected_t info); virtual esp_err_t apStart(); virtual esp_err_t apStop(); - virtual esp_err_t staConnected(); - virtual esp_err_t staDisconnected(); - virtual esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip); + system_event_cb_t getEventHandler(); + virtual esp_err_t staConnected(system_event_sta_connected_t info); + virtual esp_err_t staDisconnected(system_event_sta_disconnected_t info); + virtual esp_err_t staGotIp(system_event_sta_got_ip_t info); + virtual esp_err_t staScanDone(system_event_sta_scan_done_t info); + virtual esp_err_t staAuthChange(system_event_sta_authmode_change_t info); virtual esp_err_t staStart(); virtual esp_err_t staStop(); virtual esp_err_t wifiReady(); @@ -97,8 +117,8 @@ class WiFiEventHandler { * Get the next WiFi event handler in the chain, if there is one. * @return The next WiFi event handler in the chain or nullptr if there is none. */ - WiFiEventHandler *getNextHandler() { - return nextHandler; + WiFiEventHandler* getNextHandler() { + return m_nextHandler; } /** @@ -106,12 +126,14 @@ class WiFiEventHandler { * @param [in] nextHandler The next WiFi event handler in the chain. */ void setNextHandler(WiFiEventHandler* nextHandler) { - this->nextHandler = nextHandler; + this->m_nextHandler = nextHandler; } private: - WiFiEventHandler *nextHandler = nullptr; - static esp_err_t eventHandler(void *ctx, system_event_t *event); + friend class WiFi; + WiFiEventHandler *m_nextHandler; + static esp_err_t eventHandler(void* ctx, system_event_t* event); + }; #endif /* MAIN_WIFIEVENTHANDLER_H_ */ diff --git a/cpp_utils/component.mk b/cpp_utils/component.mk index 32f27517..7dacf4a8 100644 --- a/cpp_utils/component.mk +++ b/cpp_utils/component.mk @@ -7,8 +7,6 @@ # please read the ESP-IDF documents if you need to do this. COMPONENT_ADD_INCLUDEDIRS=. -## Uncomment the following line if we have an implementation of libcurl available to us. -##CXXFLAGS+=-DESP_HAVE_CURL - ## Uncomment the following line to enable exception handling -#CXXFLAGS+=-fexceptions \ No newline at end of file +#CXXFLAGS+=-fexceptions +#CXXFLAGS+= -std=c++11 \ No newline at end of file diff --git a/cpp_utils/library.properties b/cpp_utils/library.properties new file mode 100644 index 00000000..09a02bac --- /dev/null +++ b/cpp_utils/library.properties @@ -0,0 +1,11 @@ +name=ESP32 BLE Arduino +version=0.1.0 +author=Neil Kolban +maintainer=Neil Kolban +sentence=BLE functions for ESP32 +paragraph=BLE functions for ESP32 +category=Communication +url=https://github.com/nkolban/ESP32_BLE_Arduino +architectures=esp32 +includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h +dot_a_linkage=true \ No newline at end of file diff --git a/cpp_utils/mainpage.dox b/cpp_utils/mainpage.dox index 3597471d..5701909e 100644 --- a/cpp_utils/mainpage.dox +++ b/cpp_utils/mainpage.dox @@ -5,4 +5,4 @@ * functions. * * The Github repository is [https://github.com/nkolban/esp32-snippets](https://github.com/nkolban/esp32-snippets). - */ \ No newline at end of file + */ diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino new file mode 100644 index 00000000..7bce73f1 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/BLE_client/BLE_client.ino @@ -0,0 +1,136 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#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 BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +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); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + 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()); + 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()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); + + return 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.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = 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 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // 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(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } 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()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino new file mode 100644 index 00000000..2c2ec6a3 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/BLE_iBeacon/BLE_iBeacon.ino @@ -0,0 +1,104 @@ +/* + 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 "BLEServer.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 is no longer required to crete beacon + // BLEServer *pServer = BLEDevice::createServer(); + + 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/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino index 44506c26..5e915bea 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_notify/BLE_notify.ino @@ -23,8 +23,10 @@ #include #include -BLECharacteristic *pCharacteristic; +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; bool deviceConnected = false; +bool oldDeviceConnected = false; uint8_t value = 0; // See the following for generating UUIDs: @@ -53,7 +55,7 @@ void setup() { BLEDevice::init("MyESP32"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service @@ -81,13 +83,23 @@ void setup() { } void loop() { - - if (deviceConnected) { - Serial.printf("*** NOTIFY: %d ***\n", value); - pCharacteristic->setValue(&value, 1); - pCharacteristic->notify(); - //pCharacteristic->indicate(); - value++; - } - delay(2000); + // notify changed value + if (deviceConnected) { + pCharacteristic->setValue(&value, 1); + pCharacteristic->notify(); + value++; + 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; + } } \ No newline at end of file diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino index 45ebf99f..79793975 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_server/BLE_server.ino @@ -7,8 +7,6 @@ #include #include -BLEDevice ble; - // See the following for generating UUIDs: // https://www.uuidgenerator.net/ @@ -19,8 +17,8 @@ void setup() { Serial.begin(115200); Serial.println("Starting BLE work!"); - ble.init("MyESP32"); - BLEServer *pServer = new BLEServer(); + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); BLECharacteristic *pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, @@ -31,6 +29,7 @@ void setup() { pCharacteristic->setValue("Hello World says Neil"); pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); Serial.println("Characteristic defined! Now you can read it in your phone!"); } @@ -38,4 +37,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: delay(2000); -} \ No newline at end of file +} diff --git a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino index a8ab2d7f..ec014db7 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_uart/BLE_uart.ino @@ -24,8 +24,10 @@ #include #include -BLECharacteristic *pCharacteristic; +BLEServer *pServer = NULL; +BLECharacteristic * pTxCharacteristic; bool deviceConnected = false; +bool oldDeviceConnected = false; uint8_t txValue = 0; // See the following for generating UUIDs: @@ -70,42 +72,55 @@ void setup() { BLEDevice::init("UART Service"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + 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_TX, - BLECharacteristic::PROPERTY_NOTIFY - ); + pTxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_TX, + BLECharacteristic::PROPERTY_NOTIFY + ); - pCharacteristic->addDescriptor(new BLE2902()); + pTxCharacteristic->addDescriptor(new BLE2902()); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( - CHARACTERISTIC_UUID_RX, - BLECharacteristic::PROPERTY_WRITE - ); + BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE + ); - pCharacteristic->setCallbacks(new MyCallbacks()); + pRxCharacteristic->setCallbacks(new MyCallbacks()); // Start the service pService->start(); // Start advertising + pServer->getAdvertising()->addServiceUUID(pService->getUUID()); pServer->getAdvertising()->start(); Serial.println("Waiting a client connection to notify..."); } void loop() { - if (deviceConnected) { - Serial.printf("*** Sent Value: %d ***\n", txValue); - pCharacteristic->setValue(&txValue, 1); - pCharacteristic->notify(); - txValue++; - } - delay(1000); + 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/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino b/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino index ed5ebc64..24a0cd23 100644 --- a/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino +++ b/cpp_utils/tests/BLETests/Arduino/BLE_write/BLE_write.ino @@ -7,8 +7,6 @@ #include #include -BLEDevice ble; - // See the following for generating UUIDs: // https://www.uuidgenerator.net/ @@ -41,9 +39,8 @@ void setup() { Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something"); Serial.println("5- See the magic =)"); - //ble.begin("MyESP32"); - ble.init("MyESP32"); - BLEServer *pServer = new BLEServer(); + BLEDevice::init("MyESP32"); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino new file mode 100644 index 00000000..e77d774f --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_encrypted/BLE_client_encrypted.ino @@ -0,0 +1,138 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +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); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + 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()); + 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()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * 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.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = 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 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // 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(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } 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()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino new file mode 100644 index 00000000..846a664b --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_numeric_confirmation/BLE_client_numeric_confirmation.ino @@ -0,0 +1,169 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +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); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + 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()); + 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()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * 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.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = 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 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // 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(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } 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()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino new file mode 100644 index 00000000..763a87d2 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_client/BLE_client_passkey/BLE_client_passkey.ino @@ -0,0 +1,168 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + +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); +} + +bool connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + 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()); + 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()); + return false; + } + Serial.println(" - Found our characteristic"); + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * 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.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = 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 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // 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(*pServerAddress)) { + Serial.println("We are now connected to the BLE Server."); + connected = true; + } 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()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino new file mode 100644 index 00000000..75a461af --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_authorization/BLE_server_authorization.ino @@ -0,0 +1,44 @@ +/* + 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 +*/ + +#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("ESP32"); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + /* + * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests + */ + + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino new file mode 100644 index 00000000..43ffd2c9 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_encrypted/BLE_server_encrypted.ino @@ -0,0 +1,43 @@ +/* + 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 +*/ + +#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("ESP32"); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + 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(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino new file mode 100644 index 00000000..186019e8 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_numeric_confirmation/BLE_server_numeric_confirmation.ino @@ -0,0 +1,75 @@ +/* + 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 +*/ + +#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 MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("ESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + 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(); + pAdvertising->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino new file mode 100644 index 00000000..93f8c89f --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/BLE_server/BLE_server_passkey/BLE_server_passkey.ino @@ -0,0 +1,78 @@ +/* + 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 +*/ + +#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 MySecurity : public BLESecurityCallbacks { + + bool onConfirmPIN(uint32_t pin){ + return false; + } + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + BLEDevice::init("ESP32"); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + 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(); + pAdvertising->start(); + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino b/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino new file mode 100644 index 00000000..d89c14f9 --- /dev/null +++ b/cpp_utils/tests/BLETests/Arduino/security/StaticPIN/StaticPIN.ino @@ -0,0 +1,47 @@ +/* + 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 +*/ + +#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("ESP32"); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setStaticPIN(123456); + + //set static passkey + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +} diff --git a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp index 6db156fe..bb0bc588 100644 --- a/cpp_utils/tests/BLETests/Sample-MLE-15.cpp +++ b/cpp_utils/tests/BLETests/Sample-MLE-15.cpp @@ -18,6 +18,9 @@ static const char LOG_TAG[] = "Sample-MLE-15"; static BLEUUID serviceUUID((uint16_t)0x1802); static BLEUUID charUUID((uint16_t)0x2a06); +static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) { + +} class MyClient: public Task { void run(void *data) { @@ -34,7 +37,6 @@ class MyClient: public Task { return; } - pRemoteService->getCharacteristics(); BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); if (pRemoteCharacteristic == nullptr) { ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); @@ -43,7 +45,7 @@ class MyClient: public Task { pRemoteCharacteristic->readValue(); pRemoteCharacteristic->writeValue("123"); - pRemoteCharacteristic->registerForNotify(); + pRemoteCharacteristic->registerForNotify(notifyCallback); pClient->disconnect(); ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); diff --git a/cpp_utils/tests/BLETests/Sample1.cpp b/cpp_utils/tests/BLETests/Sample1.cpp index 88f6b3d1..b551a186 100644 --- a/cpp_utils/tests/BLETests/Sample1.cpp +++ b/cpp_utils/tests/BLETests/Sample1.cpp @@ -2,6 +2,7 @@ #include "BLEServer.h" #include #include +#include #include "BLEDevice.h" #include "sdkconfig.h" @@ -9,30 +10,56 @@ // 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" +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define CHARACTERISTIC_UUID2 "54059634-9448-404f-9af4-7d14556f3ad8" +#define CHARACTERISTIC_UUID3 "78f8a814-7b20-40ca-b970-0aba448c53b1" +#define CHARACTERISTIC_UUID4 "03a55273-c1ef-4eab-a6c0-7ff11509122f" +#define CHARACTERISTIC_UUID5 "0d19566d-2144-4443-9779-19d42e283439" static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer* pServer = BLEDevice::createServer(); - BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); + BLEService* pService = pServer->createService(BLEUUID(SERVICE_UUID)); - BLECharacteristic *pCharacteristic = pService->createCharacteristic( + BLECharacteristic* pCharacteristic = pService->createCharacteristic( BLEUUID(CHARACTERISTIC_UUID), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID2), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID3), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID4), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic = pService->createCharacteristic( + BLEUUID(CHARACTERISTIC_UUID5), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); pService->start(); - BLEAdvertising *pAdvertising = pServer->getAdvertising(); + BLEAdvertising* pAdvertising = pServer->getAdvertising(); pAdvertising->start(); } void Sample1(void) { + //esp_log_level_set("*", ESP_LOG_ERROR); run(); } // app_main diff --git a/cpp_utils/tests/BLETests/SampleAsyncScan.cpp b/cpp_utils/tests/BLETests/SampleAsyncScan.cpp new file mode 100644 index 00000000..2973b652 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleAsyncScan.cpp @@ -0,0 +1,58 @@ +/** + * Perform an async scanning for BLE advertised servers. + */ +#include "BLEUtils.h" +#include "BLEScan.h" +#include + +#include "BLEDevice.h" +#include "BLEAdvertisedDevice.h" +#include "sdkconfig.h" + +/** + * Callback for each detected advertised device. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + printf("Advertised Device: %s\n", advertisedDevice.toString().c_str()); + } +}; + + +/** + * Callback invoked when scanning has completed. + */ +static void scanCompleteCB(BLEScanResults scanResults) { + printf("Scan complete!\n"); + printf("We found %d devices\n", scanResults.getCount()); + scanResults.dump(); +} // scanCompleteCB + +/** + * Run the sample. + */ +static void run() { + printf("Async Scanning sample starting\n"); + BLEDevice::init(""); + + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false); + pBLEScan->setActiveScan(true); + printf("About to start scanning for 10 seconds\n"); + pBLEScan->start(10, scanCompleteCB); + printf("Now scanning in the background ... scanCompleteCB() will be called when done.\n"); + + // + // Now going into a loop logging that we are still alive. + // + while(1) { + printf("Tick! - still alive\n"); + FreeRTOS::sleep(1000); + } + printf("Scanning sample ended\n"); +} + +void SampleAsyncScan(void) +{ + run(); +} // app_main diff --git a/cpp_utils/tests/BLETests/SampleClient.cpp b/cpp_utils/tests/BLETests/SampleClient.cpp index 76995633..3b1c7572 100644 --- a/cpp_utils/tests/BLETests/SampleClient.cpp +++ b/cpp_utils/tests/BLETests/SampleClient.cpp @@ -1,3 +1,8 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ #include #include #include @@ -83,7 +88,7 @@ class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); - if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { advertisedDevice.getScan()->stop(); ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); @@ -104,5 +109,5 @@ void SampleClient(void) { BLEScan *pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); - pBLEScan->start(30); + pBLEScan->start(15); } // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleClientAndServer.cpp b/cpp_utils/tests/BLETests/SampleClientAndServer.cpp new file mode 100644 index 00000000..ecb028d9 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClientAndServer.cpp @@ -0,0 +1,195 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include +#include +extern "C" { // See issue 1069: https://github.com/espressif/esp-idf/issues/1069 + #include +} +#include "BLEDevice.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientAndServer"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MyServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting to be a BLE Server"); + + BLEDevice::init("MYDEVICE"); + BLEServer* pServer = BLEDevice::createServer(); + + BLEService* pService = pServer->createService(serviceUUID); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + charUUID, + BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setValue("Hello World!"); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (!advertisedDevice.haveServiceUUID()) { + ESP_LOGD(LOG_TAG, "Found device is not advertising a service ... ignoring"); + return; + } + if (advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + return; + } // Found our server + ESP_LOGD(LOG_TAG, "Found device advertised %s which is not %s ... ignoring", + advertisedDevice.getServiceUUID().toString().c_str(), + serviceUUID.toString().c_str()); + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientAndServer(void) { + ESP_LOGD(LOG_TAG, "SampleClientAndServer starting"); + esp_vfs_dev_uart_register(); + int fd = open("/dev/uart/0", O_RDONLY); + if (fd == -1) { + ESP_LOGE(LOG_TAG, "Failed to open file %s", strerror(errno)); + return; + } + + ESP_LOGD(LOG_TAG, "Enter:"); + ESP_LOGD(LOG_TAG, "C - Client"); + ESP_LOGD(LOG_TAG, "S - Server"); + bool isServer; + while(1) { + uint8_t val; + ssize_t rc; + // Read a character from the UART. Since the UART is non-blocking, we loop until we have + // a character available. + do { + rc = read(fd, &val, 1); + if (rc == -1) { + //ESP_LOGE(LOG_TAG, "Failed to read file %s", strerror(errno)); + FreeRTOS::sleep(100); + //return; + } + } while(rc == -1); + + // See if the character is an indication of being a server or a client. + + if (val == 'c' || val == 'C') { + isServer = false; + break; + } + if (val == 's' || val == 'S') { + isServer = true; + break; + } + } + close(fd); // Close the UART file as we don't need it any more. + ESP_LOGD(LOG_TAG, "Chosen: %s", isServer?"Server":"Client"); + + if (isServer) { + MyServer* pMyServer = new MyServer(); + pMyServer->setStackSize(20000); + pMyServer->start(); + } else { + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); + } + +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp b/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp new file mode 100644 index 00000000..152ff130 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClientWithWiFi.cpp @@ -0,0 +1,122 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" +#include "WiFi.h" +#include "HttpServer.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientWithWiFi"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientWithWiFi(void) { + ESP_LOGD(LOG_TAG, "SampleClientWithWiFi starting"); + WiFi wifi; + wifi.setIPInfo("192.168.1.99", "192.168.1.1", "255.255.255.0"); + wifi.connectAP("sweetie", "l16wint!"); + + HttpServer httpServer; + httpServer.start(80); + + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleClient_Notify.cpp b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp new file mode 100644 index 00000000..aaecbbf7 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleClient_Notify.cpp @@ -0,0 +1,108 @@ +/** + * Create a sample BLE client that connects to a BLE server and then waits for server generated + * notifications and logs them when they arrive. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient_Notify"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + ESP_LOGD(LOG_TAG, "Notify callback for characteristic %s of data length %d", + pBLERemoteCharacteristic->getUUID().toString().c_str(), length); +} + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_Notify(void) { + ESP_LOGD(LOG_TAG, "SampleClient_Notify starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleDisconnect.cpp b/cpp_utils/tests/BLETests/SampleDisconnect.cpp new file mode 100644 index 00000000..88869075 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleDisconnect.cpp @@ -0,0 +1,113 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClientDisconnect"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + for (int i=0; i<5; i++) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClientDisconnect(void) { + ESP_LOGD(LOG_TAG, "Scanning SampleClientDisconnect starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleHIDDevice.cpp b/cpp_utils/tests/BLETests/SampleHIDDevice.cpp new file mode 100644 index 00000000..c3a4df1a --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleHIDDevice.cpp @@ -0,0 +1,220 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include "BLEHIDDevice.h" +#include "SampleKeyboardTypes.h" +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleHIDDevice"; + +static BLEHIDDevice* hid; + +class MyTask : public Task { + void run(void*){ + vTaskDelay(10000); + const char* hello = "Hello world from esp32 hid keyboard!!!"; + while(*hello){ + KEYMAP map = keymap[(uint8_t)*hello]; + uint8_t a[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0}; + hid->inputReport(NULL)->setValue(a,sizeof(a)); + hid->inputReport(NULL)->notify(); + + hello++; + } + uint8_t v[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0}; + hid->inputReport(NULL)->setValue(v, sizeof(v)); + hid->inputReport(NULL)->notify(); + vTaskDelete(NULL); + } +}; + MyTask *task; + + class MyCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* pServer){ + task->start(); + } + + void onDisconnect(BLEServer* pServer){ + + } + }; +uint32_t passKey = 0; + class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGE(LOG_TAG, "The passkey request %d", passKey); + + vTaskDelay(25000); + return passKey; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + passKey = pass_key; + } + bool onSecurityRequest(){ + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } + }; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + task = new MyTask(); + BLEDevice::init("ESP32"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyCallbacks()); + pServer->setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + pServer->setSecurityCallbacks(new MySecurity()); + + /* + * Instantiate hid device + */ + hid = new BLEHIDDevice(pServer); + + /* + * Set manufacturer name + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml + */ + std::string name = "esp-community"; + hid->manufacturer()->setValue(name); + + /* + * Set pnp parameters + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.pnp_id.xml + */ + const uint8_t pnp[] = {0x01,0x02,0xe5,0xab,0xcd,0x01,0x10}; + hid->pnp()->setValue((uint8_t*)pnp, sizeof(pnp)); + + /* + * Set hid informations + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hid_information.xml + */ + const uint8_t val1[] = {0x01,0x11,0x00,0x03}; + hid->hidInfo()->setValue((uint8_t*)val1, 4); + + /* + * Mouse + */ + const uint8_t reportMap2[] = { + USAGE_PAGE(1), 0x01, + USAGE(1), 0x02, + COLLECTION(1), 0x01, + USAGE(1), 0x01, + COLLECTION(1), 0x00, + USAGE_PAGE(1), 0x09, + USAGE_MINIMUM(1), 0x1, + USAGE_MAXIMUM(1), 0x3, + LOGICAL_MINIMUM(1), 0x0, + LOGICAL_MAXIMUM(1), 0x1, + REPORT_COUNT(1), 0x3, + REPORT_SIZE(1), 0x1, + INPUT(1), 0x2, // (Data, Variable, Absolute), ;3 button bits + REPORT_COUNT(1), 0x1, + REPORT_SIZE(1), 0x5, + INPUT(1), 0x1, //(Constant), ;5 bit padding + USAGE_PAGE(1), 0x1, //(Generic Desktop), + USAGE(1), 0x30, + USAGE(1), 0x31, + LOGICAL_MINIMUM(1), 0x81, + LOGICAL_MAXIMUM(1), 0x7f, + REPORT_SIZE(1), 0x8, + REPORT_COUNT(1), 0x2, + INPUT(1), 0x6, //(Data, Variable, Relative), ;2 position bytes (X & Y) + END_COLLECTION(0), + END_COLLECTION(0) + }; + /* + * Keyboard + */ + const uint8_t reportMap[] = { + USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls + USAGE(1), 0x06, // Keyboard + COLLECTION(1), 0x01, // Application + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0xE0, + USAGE_MAXIMUM(1), 0xE7, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, // 1 byte (Modifier) + REPORT_COUNT(1), 0x08, + INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x01, // 1 byte (Reserved) + REPORT_SIZE(1), 0x08, + INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) + REPORT_SIZE(1), 0x01, + USAGE_PAGE(1), 0x08, // LEDs + USAGE_MINIMUM(1), 0x01, // Num Lock + USAGE_MAXIMUM(1), 0x05, // Kana + OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x01, // 3 bits (Padding) + REPORT_SIZE(1), 0x03, + OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x06, // 6 bytes (Keys) + REPORT_SIZE(1), 0x08, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x65, // 101 keys + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0x00, + USAGE_MAXIMUM(1), 0x65, + INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + END_COLLECTION(0) + }; + /* + * Set report map (here is initialized device driver on client side) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.report_map.xml + */ + hid->setReportMap((uint8_t*)reportMap, sizeof(reportMap)); + + /* + * We are prepared to start hid device services. Before this point we can change all values and/or set parameters we need. + * Also before we start, if we want to provide battery info, we need to prepare battery service. + * We can setup characteristics authorization + */ + hid->startServices(); + + /* + * Its good to setup advertising by providing appearance and advertised service. This will let clients find our device by type + */ + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->setAppearance(HID_KEYBOARD); + pAdvertising->addServiceUUID(hid->hidService()->getUUID()); + pAdvertising->start(); + + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setCapability(ESP_IO_CAP_NONE); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleHID(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp new file mode 100644 index 00000000..29bff125 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleHIDKeyboard.cpp @@ -0,0 +1,193 @@ +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include "BLEHIDDevice.h" +#include "HIDKeyboardTypes.h" +#include +#include +#include + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleHIDDevice"; + +static BLEHIDDevice* hid; +BLECharacteristic* input; +BLECharacteristic* output; + +/* + * This callback is connect with output report. In keyboard output report report special keys changes, like CAPSLOCK, NUMLOCK + * We can add digital pins with LED to show status + * bit 1 - NUM LOCK + * bit 2 - CAPS LOCK + * bit 3 - SCROLL LOCK + */ +class MyOutputCallbacks : public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* me){ + uint8_t* value = (uint8_t*)(me->getValue().c_str()); + ESP_LOGI(LOG_TAG, "special keys: %d", *value); + } +}; + +class MyTask : public Task { + void run(void*){ + vTaskDelay(5000/portTICK_PERIOD_MS); // wait 5 seconds before send first message + while(1){ + const char* hello = "Hello world from esp32 hid keyboard!!!\n"; + while(*hello){ + KEYMAP map = keymap[(uint8_t)*hello]; + /* + * simulate keydown, we can send up to 6 keys + */ + uint8_t a[] = {map.modifier, 0x0, map.usage, 0x0,0x0,0x0,0x0,0x0}; + input->setValue(a,sizeof(a)); + input->notify(); + + /* + * simulate keyup + */ + uint8_t v[] = {0x0, 0x0, 0x0, 0x0,0x0,0x0,0x0,0x0}; + input->setValue(v, sizeof(v)); + input->notify(); + hello++; + + vTaskDelay(10/portTICK_PERIOD_MS); + } + vTaskDelay(2000/portTICK_PERIOD_MS); // simulate write message every 2 seconds + } + } +}; + +MyTask *task; +class MyCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* pServer){ + task->start(); + } + + void onDisconnect(BLEServer* pServer){ + task->stop(); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + + task = new MyTask(); + BLEDevice::init("ESP32"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyCallbacks()); + + /* + * Instantiate hid device + */ + hid = new BLEHIDDevice(pServer); + + + input = hid->inputReport(1); // <-- input REPORTID from report map + output = hid->outputReport(1); // <-- output REPORTID from report map + + output->setCallbacks(new MyOutputCallbacks()); + + /* + * Set manufacturer name (OPTIONAL) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml + */ + std::string name = "esp-community"; + hid->manufacturer()->setValue(name); + + /* + * Set pnp parameters (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.pnp_id.xml + */ + + hid->pnp(0x02, 0xe502, 0xa111, 0x0210); + + /* + * Set hid informations (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hid_information.xml + */ + hid->hidInfo(0x00,0x01); + + + /* + * Keyboard + */ + const uint8_t reportMap[] = { + USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls + USAGE(1), 0x06, // Keyboard + COLLECTION(1), 0x01, // Application + REPORT_ID(1), 0x01, // REPORTID + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0xE0, + USAGE_MAXIMUM(1), 0xE7, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x01, + REPORT_SIZE(1), 0x01, // 1 byte (Modifier) + REPORT_COUNT(1), 0x08, + INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x01, // 1 byte (Reserved) + REPORT_SIZE(1), 0x08, + INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) + REPORT_SIZE(1), 0x01, + USAGE_PAGE(1), 0x08, // LEDs + USAGE_MINIMUM(1), 0x01, // Num Lock + USAGE_MAXIMUM(1), 0x05, // Kana + OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x01, // 3 bits (Padding) + REPORT_SIZE(1), 0x03, + OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile + REPORT_COUNT(1), 0x06, // 6 bytes (Keys) + REPORT_SIZE(1), 0x08, + LOGICAL_MINIMUM(1), 0x00, + LOGICAL_MAXIMUM(1), 0x65, // 101 keys + USAGE_PAGE(1), 0x07, // Kbrd/Keypad + USAGE_MINIMUM(1), 0x00, + USAGE_MAXIMUM(1), 0x65, + INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position + END_COLLECTION(0) + }; + /* + * Set report map (here is initialized device driver on client side) (MANDATORY) + * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.report_map.xml + */ + hid->reportMap((uint8_t*)reportMap, sizeof(reportMap)); + + /* + * We are prepared to start hid device services. Before this point we can change all values and/or set parameters we need. + * Also before we start, if we want to provide battery info, we need to prepare battery service. + * We can setup characteristics authorization + */ + hid->startServices(); + + /* + * Its good to setup advertising by providing appearance and advertised service. This will let clients find our device by type + */ + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->setAppearance(HID_KEYBOARD); + pAdvertising->addServiceUUID(hid->hidService()->getUUID()); + pAdvertising->start(); + + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(1000000); + } +}; + + +void SampleHID(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main diff --git a/cpp_utils/tests/BLETests/SampleKeyboardTypes.h b/cpp_utils/tests/BLETests/SampleKeyboardTypes.h new file mode 100644 index 00000000..ef48a526 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleKeyboardTypes.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/cpp_utils/tests/BLETests/SampleNotify.cpp b/cpp_utils/tests/BLETests/SampleNotify.cpp index 738aacb3..3714db34 100644 --- a/cpp_utils/tests/BLETests/SampleNotify.cpp +++ b/cpp_utils/tests/BLETests/SampleNotify.cpp @@ -75,7 +75,7 @@ static void run() { BLEDevice::init("MYDEVICE"); // Create the BLE Server - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service diff --git a/cpp_utils/tests/BLETests/SampleRead.cpp b/cpp_utils/tests/BLETests/SampleRead.cpp index ba498f4b..e0d207c0 100644 --- a/cpp_utils/tests/BLETests/SampleRead.cpp +++ b/cpp_utils/tests/BLETests/SampleRead.cpp @@ -1,3 +1,9 @@ +/** + * Create a BLE Server that when it receive a read request from a BLE client for the value + * of a characteristic will have the BLECharacteristicCallback invoked in its onRead() method. + * This can be then used to set the value of the corresponding characteristic which will then + * be returned back to the client. + */ #include "BLEUtils.h" #include "BLEServer.h" #include @@ -28,7 +34,7 @@ class MyCallbackHandler: public BLECharacteristicCallbacks { static void run() { BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID_BIN, 16, true)); diff --git a/cpp_utils/tests/BLETests/SampleScan.cpp b/cpp_utils/tests/BLETests/SampleScan.cpp index 3dc972ed..299bf5b6 100644 --- a/cpp_utils/tests/BLETests/SampleScan.cpp +++ b/cpp_utils/tests/BLETests/SampleScan.cpp @@ -1,3 +1,6 @@ +/** + * Perform scanning for BLE advertised servers. + */ #include "BLEUtils.h" #include "BLEScan.h" #include diff --git a/cpp_utils/tests/BLETests/SampleSensorTag.cpp b/cpp_utils/tests/BLETests/SampleSensorTag.cpp new file mode 100644 index 00000000..3b081f34 --- /dev/null +++ b/cpp_utils/tests/BLETests/SampleSensorTag.cpp @@ -0,0 +1,171 @@ +/** + * Create a sample BLE client that connects to a BLE server and then retrieves the current + * characteristic value. It will then periodically update the value of the characteristic on the + * remote server with the current time since boot. + */ +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleSensorTag"; + +static BLEUUID luxometerServiceUUID("f000aa70-0451-4000-b000-000000000000"); +static BLEUUID luxometerDataUUID("f000aa71-0451-4000-b000-000000000000"); +static BLEUUID luxometerConfigUUID("f000aa72-0451-4000-b000-000000000000"); +static BLEUUID luxometerPeriodUUID("f000aa73-0451-4000-b000-000000000000"); + +static BLEUUID irTempServiceUUID("f000aa00-0451-4000-b000-000000000000"); +static BLEUUID irTempDataUUID("f000aa01-0451-4000-b000-000000000000"); +static BLEUUID irTempConfigUUID("f000aa02-0451-4000-b000-000000000000"); +static BLEUUID irTempPeriodUUID("f000aa03-0451-4000-b000-000000000000"); + +static BLEUUID keyPressServiceUUID("0000ffe0-0000-1000-8000-00805f9b34fb"); +static BLEUUID keyPressStateUUID("0000ffe1-0000-1000-8000-00805f9b34fb"); + + +void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* data, + size_t length, + bool isNotify) +{ + ESP_LOGD(LOG_TAG, "Notified!"); +} + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + BLERemoteService* pIRTempService = pClient->getService(irTempServiceUUID); + BLERemoteCharacteristic* pIRTempDataCharacteristic = pIRTempService->getCharacteristic(irTempDataUUID); + BLERemoteCharacteristic* pIRTempConfigCharacteristic = pIRTempService->getCharacteristic(irTempConfigUUID); + pIRTempConfigCharacteristic->writeValue(1); + while(1) { + uint32_t rawValue = pIRTempDataCharacteristic->readUInt32(); + float obj = ((rawValue & 0x0000ffff) >> 2) * 0.03125; + float amb = ((rawValue & 0xffff0000) >> 18) * 0.03125; + ESP_LOGD(LOG_TAG, "IT Values: obj: %f, amb: %f", obj, amb); + FreeRTOS::sleep(1000); + } + + /* + BLERemoteService* pKeyPressService = pClient->getService(keyPressServiceUUID); + BLERemoteCharacteristic* pKeyStateCharacteristic = pKeyPressService->getCharacteristic(keyPressStateUUID); + BLERemoteDescriptor *p2902Descriptor = pKeyStateCharacteristic->getDescriptor(BLEUUID("00002902-0000-1000-8000-00805f9b34fb")); + p2902Descriptor->writeValue(1); + pKeyStateCharacteristic->registerForNotify(notifyCallback); + */ + + /* + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(luxometerServiceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", luxometerServiceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pLuxometerConfigCharacteristic = pRemoteService->getCharacteristic(luxometerConfigUUID); + if (pLuxometerConfigCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + BLERemoteCharacteristic* pLuxometerPeriodCharacteristic = pRemoteService->getCharacteristic(luxometerPeriodUUID); + if (pLuxometerPeriodCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + BLERemoteCharacteristic* pLuxometerDataCharacteristic = pRemoteService->getCharacteristic(luxometerDataUUID); + if (pLuxometerDataCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", luxometerDataUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + uint16_t configValue = pLuxometerConfigCharacteristic->readUInt16(); + ESP_LOGD(LOG_TAG, "The pLuxometerConfigCharacteristic value was: %d", configValue); + + pLuxometerConfigCharacteristic->writeValue(1); + + while(1) { + uint16_t lightValue = pLuxometerDataCharacteristic->readUInt16(); + ESP_LOGD(LOG_TAG, "Light value: %d", lightValue); + FreeRTOS::sleep(1000); + } + */ + + /* + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + */ + + while(1) { + FreeRTOS::sleep(5000); + } + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * Scan for BLE servers and find the first one that advertises the SensorTag. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + if (advertisedDevice.getName() == "CC2650 SensorTag") { + ESP_LOGD(LOG_TAG, "Found our SensorTag device! address: %s", advertisedDevice.getAddress().toString().c_str()); + advertisedDevice.getScan()->stop(); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSensorTag(void) { + ESP_LOGD(LOG_TAG, "SampleSensorTag starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(15); +} // SampleClient diff --git a/cpp_utils/tests/BLETests/SampleServer.cpp b/cpp_utils/tests/BLETests/SampleServer.cpp index d497d009..70268f46 100644 --- a/cpp_utils/tests/BLETests/SampleServer.cpp +++ b/cpp_utils/tests/BLETests/SampleServer.cpp @@ -1,10 +1,14 @@ -#include "BLEUtils.h" +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" #include "BLEServer.h" +#include "BLEUtils.h" #include "BLE2902.h" #include #include #include -#include "BLEDevice.h" + #include "sdkconfig.h" @@ -14,19 +18,18 @@ class MainBLEServer: public Task { void run(void *data) { ESP_LOGD(LOG_TAG, "Starting BLE work!"); - BLEDevice::init("MYDEVICE"); - BLEServer* pServer = new BLEServer(); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); - BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x1234)); + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); BLECharacteristic* pCharacteristic = pService->createCharacteristic( - BLEUUID((uint16_t)0x99AA), + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), BLECharacteristic::PROPERTY_BROADCAST | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_INDICATE ); - pCharacteristic->setValue("Hello World!"); BLE2902* p2902Descriptor = new BLE2902(); @@ -36,7 +39,7 @@ class MainBLEServer: public Task { pService->start(); BLEAdvertising* pAdvertising = pServer->getAdvertising(); - pAdvertising->setServiceUUID(pService->getUUID().to128()); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); pAdvertising->start(); ESP_LOGD(LOG_TAG, "Advertising started!"); @@ -47,7 +50,7 @@ class MainBLEServer: public Task { void SampleServer(void) { - esp_log_level_set("*", ESP_LOG_DEBUG); + //esp_log_level_set("*", ESP_LOG_DEBUG); MainBLEServer* pMainBleServer = new MainBLEServer(); pMainBleServer->setStackSize(20000); pMainBleServer->start(); diff --git a/cpp_utils/tests/BLETests/SampleWrite.cpp b/cpp_utils/tests/BLETests/SampleWrite.cpp index 9132b7af..de8f251a 100644 --- a/cpp_utils/tests/BLETests/SampleWrite.cpp +++ b/cpp_utils/tests/BLETests/SampleWrite.cpp @@ -1,3 +1,9 @@ +/** + * Create a BLE Server such that when a client connects and requests a change to the characteristic + * value, the callback associated with the server will be invoked such that the server can perform + * some action based on the new value. The action in this sample is merely to log the new value to + * the console. + */ #include "BLEUtils.h" #include "BLEServer.h" #include @@ -5,6 +11,7 @@ #include #include #include "BLEDevice.h" +#include "GeneralUtils.h" #include "sdkconfig.h" @@ -27,9 +34,11 @@ class MyCallbacks: public BLECharacteristicCallbacks { } }; + static void run() { + GeneralUtils::dumpInfo(); BLEDevice::init("MYDEVICE"); - BLEServer *pServer = new BLEServer(); + BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); @@ -48,6 +57,7 @@ static void run() { pAdvertising->start(); } + void SampleWrite(void) { run(); diff --git a/cpp_utils/tests/BLETests/main.cpp b/cpp_utils/tests/BLETests/main.cpp index bd7060e6..77c3567b 100644 --- a/cpp_utils/tests/BLETests/main.cpp +++ b/cpp_utils/tests/BLETests/main.cpp @@ -1,23 +1,43 @@ +/** + * Main file for running the BLE samples. + */ extern "C" { void app_main(void); } -void SampleServer(void); + +// The list of sample entry points. +void Sample_MLE_15(void); void Sample1(void); +void SampleAsyncScan(void); +void SampleClient(void); +void SampleClient_Notify(void); +void SampleClientAndServer(void); +void SampleClientDisconnect(void); +void SampleClientWithWiFi(void); +void SampleNotify(void); void SampleRead(void); -void SampleWrite(void); void SampleScan(void); -void SampleNotify(void); -void SampleClient(void); -void Sample_MLE_15(void); +void SampleSensorTag(void); +void SampleServer(void); +void SampleWrite(void); +// +// Un-comment ONE of the following +// --- void app_main(void) { - //SampleServer(); + //Sample_MLE_15(); //Sample1(); + //SampleAsyncScan(); + //SampleClient(); + //SampleClient_Notify(); + //SampleClientAndServer(); + //SampleClientDisconnect(); + //SampleClientWithWiFi(); + //SampleNotify(); //SampleRead(); + //SampleSensorTag(); + //SampleScan(); + SampleServer(); //SampleWrite(); - SampleScan(); - //SampleNotify(); - //SampleClient(); - //Sample_MLE_15(); -} +} // app_main diff --git a/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp b/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp new file mode 100644 index 00000000..2ab4c909 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_Encryption.cpp @@ -0,0 +1,122 @@ +/* + * SampleClient_Encryption.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_Encryption(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + + diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp new file mode 100644 index 00000000..2791f3d1 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_numeric_confirmation.cpp @@ -0,0 +1,153 @@ +/* + * SampleClient_authentication_numeric_confirmation.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleClient_authentication_numeric_confirmation(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + diff --git a/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp new file mode 100644 index 00000000..4848385d --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleClient_authentication_passkey.cpp @@ -0,0 +1,152 @@ +/* + * SampleClient_authentication_passkey.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +#include +#include +#include +#include +#include "BLEDevice.h" + +#include "BLEAdvertisedDevice.h" +#include "BLEClient.h" +#include "BLEScan.h" +#include "BLEUtils.h" +#include "Task.h" + +#include "sdkconfig.h" + +static const char* LOG_TAG = "SampleClient"; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return false; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "Security Request"); + return true; + } + void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl){ + if(auth_cmpl.success){ + ESP_LOGI(LOG_TAG, "remote BD_ADDR:"); + esp_log_buffer_hex(LOG_TAG, auth_cmpl.bd_addr, sizeof(auth_cmpl.bd_addr)); + ESP_LOGI(LOG_TAG, "address type = %d", auth_cmpl.addr_type); + } + ESP_LOGI(LOG_TAG, "pair status = %s", auth_cmpl.success ? "success" : "fail"); + } +}; + + +/** + * Become a BLE client to a remote BLE server. We are passed in the address of the BLE server + * as the input parameter when the task is created. + */ +class MyClient: public Task { + void run(void* data) { + + BLEAddress* pAddress = (BLEAddress*)data; + BLEClient* pClient = BLEDevice::createClient(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + // Connect to the remove BLE Server. + pClient->connect(*pAddress); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our service UUID: %s", serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + ESP_LOGD(LOG_TAG, "Failed to find our characteristic UUID: %s", charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + ESP_LOGD(LOG_TAG, "The characteristic value was: %s", value.c_str()); + + while(1) { + // Set a new value of the characteristic + ESP_LOGD(LOG_TAG, "Setting the new value"); + std::ostringstream stringStream; + struct timeval tv; + gettimeofday(&tv, nullptr); + stringStream << "Time since boot: " << tv.tv_sec; + pRemoteCharacteristic->writeValue(stringStream.str()); + + FreeRTOS::sleep(1000); + } + + pClient->disconnect(); + + ESP_LOGD(LOG_TAG, "%s", pClient->toString().c_str()); + ESP_LOGD(LOG_TAG, "-- End of task"); + } // run +}; // MyClient + + +/** + * 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) { + ESP_LOGD(LOG_TAG, "Advertised Device: %s", advertisedDevice.toString().c_str()); + + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { + advertisedDevice.getScan()->stop(); + + ESP_LOGD(LOG_TAG, "Found our device! address: %s", advertisedDevice.getAddress().toString().c_str()); + MyClient* pMyClient = new MyClient(); + pMyClient->setStackSize(18000); + pMyClient->start(new BLEAddress(*advertisedDevice.getAddress().getNative())); + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +/** + * Perform the work of a sample BLE client. + */ +void SampleSecureClient(void) { + ESP_LOGD(LOG_TAG, "Scanning sample starting"); + BLEDevice::init(""); + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(5); +} // SampleClient + + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp b/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp new file mode 100644 index 00000000..10c16218 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_Encryption.cpp @@ -0,0 +1,85 @@ +/* + * SampleServer_Encryption.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MyCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + /* + * Here we have implemented simplest security. This kind security does not provide authentication + */ + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + + pCharacteristic->setCallbacks(new MyCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_Encryption(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp new file mode 100644 index 00000000..3231b37d --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_numeric_confirmation.cpp @@ -0,0 +1,129 @@ +/* + * SampleServer_authentication_numeric_confirmation.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key); + vTaskDelay(5000); + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "SecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class bleCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new bleCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setKeySize(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_IO); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + //pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_authentication_numeric_confirmation(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp new file mode 100644 index 00000000..24d45868 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authentication_passkey.cpp @@ -0,0 +1,124 @@ +/* + * SampleServer_authentication_passkey.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class MyCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; + +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new MyCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleSecureServer(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + + diff --git a/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp new file mode 100644 index 00000000..02e47e4d --- /dev/null +++ b/cpp_utils/tests/BLETests/security/SampleServer_authorization.cpp @@ -0,0 +1,131 @@ +/* + * SampleServer_authorization.cpp + * + * Created on: Dec 23, 2017 + * Author: chegewara + */ + + +/** + * Create a new BLE server. + */ +#include "BLEDevice.h" +#include "BLEServer.h" +#include "BLEUtils.h" +#include "BLE2902.h" +#include +#include +#include + + +#include "sdkconfig.h" + +static char LOG_TAG[] = "SampleServer"; + +class MySecurity : public BLESecurityCallbacks { + + uint32_t onPassKeyRequest(){ + ESP_LOGI(LOG_TAG, "PassKeyRequest"); + return 123456; + } + void onPassKeyNotify(uint32_t pass_key){ + ESP_LOGI(LOG_TAG, "On passkey Notify number:%d", pass_key); + } + bool onConfirmPIN(uint32_t){ + return true; + } + bool onSecurityRequest(){ + ESP_LOGI(LOG_TAG, "On Security Request"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl){ + ESP_LOGI(LOG_TAG, "Starting BLE work!"); + if(cmpl.success){ + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + ESP_LOGD(LOG_TAG, "size: %d", length); + } + } +}; + +class bleCharacteristicCallback: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s", msg.c_str()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } + + void onRead(BLECharacteristic* pCharacteristic) { + std::string msg = pCharacteristic->getValue(); + ESP_LOGI(LOG_TAG, "BLE received: %s, %i", msg.c_str(), msg.length()); + esp_log_buffer_char(LOG_TAG, msg.c_str(), msg.length()); + esp_log_buffer_hex(LOG_TAG, msg.c_str(), msg.length()); + } +}; +class MainBLEServer: public Task { + void run(void *data) { + ESP_LOGD(LOG_TAG, "Starting BLE work!"); + esp_log_buffer_char(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + esp_log_buffer_hex(LOG_TAG, LOG_TAG, sizeof(LOG_TAG)); + BLEDevice::init("ESP32"); + BLEServer* pServer = BLEDevice::createServer(); +// BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT_NO_MITM); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + BLEDevice::setSecurityCallbacks(new MySecurity()); + + BLEService* pService = pServer->createService("91bad492-b950-4226-aa2b-4ede9fa42f59"); + + BLECharacteristic* pCharacteristic = pService->createCharacteristic( + BLEUUID("0d563a58-196a-48ce-ace2-dfec78acc814"), + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_INDICATE + ); + /* + * Authorized permission to read/write characteristic. Require also protecting descriptor 2902 to protect notify/indicate requests + */ + pCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + pCharacteristic->setCallbacks(new bleCharacteristicCallback()); + pCharacteristic->setValue("Hello World!"); + + BLE2902* p2902Descriptor = new BLE2902(); + p2902Descriptor->setNotifications(true); + pCharacteristic->addDescriptor(p2902Descriptor); + /* + * Authorized permission to read/write descriptor to protect notify/indicate requests + */ + p2902Descriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + + pService->start(); + + BLEAdvertising* pAdvertising = pServer->getAdvertising(); + pAdvertising->addServiceUUID(BLEUUID(pService->getUUID())); + + BLESecurity *pSecurity = new BLESecurity(); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); + pSecurity->setCapability(ESP_IO_CAP_NONE); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + + pAdvertising->start(); + + ESP_LOGD(LOG_TAG, "Advertising started!"); + delay(portMAX_DELAY); + } +}; + + +void SampleServer_Authorization(void) +{ + //esp_log_level_set("*", ESP_LOG_DEBUG); + MainBLEServer* pMainBleServer = new MainBLEServer(); + pMainBleServer->setStackSize(20000); + pMainBleServer->start(); + +} // app_main + + diff --git a/cpp_utils/tests/BLETests/security/app_main_security.cpp b/cpp_utils/tests/BLETests/security/app_main_security.cpp new file mode 100644 index 00000000..7b1bbca7 --- /dev/null +++ b/cpp_utils/tests/BLETests/security/app_main_security.cpp @@ -0,0 +1,33 @@ +/** + * Main file for running the BLE samples. + */ +extern "C" { + void app_main(void); +} + + +// The list of sample entry points. + +void SampleClient_Encryption(void); +void SampleServer_Encryption(void); +void SampleClient_authentication_passkey(void); +void SampleServer_authentication_passkey(void); +void SampleClient_authentication_numeric_confirmation(void); +void SampleServer_authentication_numeric_confirmation(void); + +void SampleServer_Authorization(void); + +// +// Un-comment ONE of the following +// --- +void app_main(void) { + + SampleClient_Encryption(); +// SampleServer_Encryption(); +// SampleClient_authentication_passkey(); +// SampleServer_authentication_passkey(); +// SampleClient_authentication_numeric_confirmation(); +// SampleServer_authentication_numeric_confirmation(); +// +// SampleServer_Authorization(); +} // app_main diff --git a/cpp_utils/tests/task_i2c_scanner.cpp b/cpp_utils/tests/task_i2c_scanner.cpp index b1bde2e0..061c3b08 100644 --- a/cpp_utils/tests/task_i2c_scanner.cpp +++ b/cpp_utils/tests/task_i2c_scanner.cpp @@ -6,6 +6,7 @@ #include "sdkconfig.h" +#define DEVICE_ADDRESS 0 #define SDA_PIN 25 #define SCL_PIN 26 @@ -14,7 +15,7 @@ class I2CScanner: public Task { void run(void *data) override { I2C i2c; - i2c.init((gpio_num_t)SDA_PIN, (gpio_num_t)SCL_PIN); + i2c.init((uint8_t)DEVICE_ADDRESS, (gpio_num_t)SDA_PIN, (gpio_num_t)SCL_PIN); i2c.scan(); } // End run }; diff --git a/cpp_utils/tests/test_rest.cpp b/cpp_utils/tests/test_rest.cpp index 4ee38f49..dc168e64 100644 --- a/cpp_utils/tests/test_rest.cpp +++ b/cpp_utils/tests/test_rest.cpp @@ -1,5 +1,14 @@ /* * Test the REST API client. + * This application leverages LibCurl. You must make that package available + * as well as "enable it" from "make menuconfig" and C++ Settings -> libCurl present. + * + * You may also have to include "posix_shims.c" in your compilation to provide resolution + * for Posix calls expected by libcurl that aren't present in ESP-IDF. + * + * See also: + * * https://github.com/nkolban/esp32-snippets/issues/108 + * */ #include #include @@ -32,7 +41,7 @@ class CurlTestTask: public Task { RESTTimings *timings = client.getTimings(); - client.setURL("https://httpbin.org/post"); + client.setURL("http://httpbin.org/post"); client.addHeader("Content-Type", "application/json"); client.post("hello world!"); ESP_LOGD(tag, "Result: %s", client.getResponse().c_str()); diff --git a/curl/README.md b/curl/README.md new file mode 100644 index 00000000..49ea404f --- /dev/null +++ b/curl/README.md @@ -0,0 +1,2 @@ +## Missing Posix functions +LibCurl is a moving target and on occassion, is updated to utilize functions that are not part of the ESP-IDF. For example, at the time of writing, the latest version of LibCurl uses the Posix `access(2)` system call that it previously did not. This call is not present in ESP-IDF. To circumvent the problem, a shim file has been provided in `./posix/posix_shims.c` that provides simple implementations for some missing Posix functions. \ No newline at end of file diff --git a/eclipse/c_includes.xml b/eclipse/c_includes.xml index ee8feb85..60400ad5 100644 --- a/eclipse/c_includes.xml +++ b/eclipse/c_includes.xml @@ -50,7 +50,6 @@ ${IDF_PATH}/components/spi_flash/include ${IDF_PATH}/components/mbedtls/include ${IDF_PATH}/components/mdns/include -${IDF_PATH}/components/json/include ${IDF_PATH}/components/bt/include ${IDF_PATH}/components/bt/bluedroid/bta/include ${IDF_PATH}/components/bt/bluedroid/bta/sys/include @@ -74,6 +73,7 @@ ${IDF_PATH}/components/aws_iot/aws-iot-device-sdk-embedded-C/include ${IDF_PATH}/components/fatfs/src ${IDF_PATH}/components/wear_levelling/include +${IDF_PATH}/components/console @@ -127,18 +127,36 @@ ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + ESP_PLATFORM1 + +PLATFORM_ID11 + + +DEBUG_BUILD1 + diff --git a/error handling/fragments/espToError.c b/error handling/fragments/espToError.c index 66e90eb9..72127583 100644 --- a/error handling/fragments/espToError.c +++ b/error handling/fragments/espToError.c @@ -13,7 +13,7 @@ char *espToString(esp_err_t value) { case ESP_ERR_INVALID_ARG: return "Invalid argument"; case ESP_ERR_INVALID_SIZE: - return "Invalid state"; + return "Invalid size"; case ESP_ERR_INVALID_STATE: return "Invalid state"; case ESP_ERR_NOT_FOUND: diff --git a/filesystems/sendZip/test.js b/filesystems/sendZip/test.js new file mode 100644 index 00000000..3afbb789 --- /dev/null +++ b/filesystems/sendZip/test.js @@ -0,0 +1,24 @@ +// https://www.npmjs.com/package/adm-zip +// https://nodejs.org/api/stream.html +// https://nodejs.org/api/net.html + +var AdmZip = require("adm-zip"); +const net = require("net"); + + +const lenBuf = Buffer.allocUnsafe(4); + + +const client = net.createConnection({host: "192.168.1.99", port: 9876}, ()=>{ + console.log("Connected!"); + var zip = new AdmZip("./foo.zip"); + var zipEntries = zip.getEntries(); + zipEntries.forEach(zipEntry=>{ + console.log("Entry: " + zipEntry.entryName); + lenBuf.writeUInt32LE(zipEntry.entryName.length, 0); + client.write(lenBuf); + client.write(zipEntry.entryName); + }); + + client.end(); +}); \ No newline at end of file diff --git a/filesystems/sendZip/ws_send_zip.js b/filesystems/sendZip/ws_send_zip.js new file mode 100644 index 00000000..67279218 --- /dev/null +++ b/filesystems/sendZip/ws_send_zip.js @@ -0,0 +1,63 @@ +// https://www.npmjs.com/package/adm-zip +// https://nodejs.org/api/stream.html +// https://nodejs.org/api/net.html +//API docs ...https://github.com/websockets/ws/blob/master/doc/ws.md + +const WebSocket = require("ws"); +var AdmZip = require("adm-zip"); + +function *processZip(zipFileName) { + var zip = new AdmZip("./foo.zip"); + var zipEntries = zip.getEntries(); + for (var i=0; i, // The file name to send + * "data": , // The content of the file + * } + * ``` + * @returns N/A + */ +function sendZipEntry(entry) { + const ws = new WebSocket("ws://192.168.1.99:9080/upload"); + ws.on("open", ()=>{ + console.log("Sending file: " + entry.name); + var file = { + "name": entry.name + //"length": 100 + } + ws.send(JSON.stringify(file)); + ws.send(entry.data); + ws.close(); + }); + + ws.on("error", (error)=>{ + console.log("Error: %O", error); + }); + + ws.on("close", (code)=>{ + console.log("WS closed"); + var nextEntry = it.next(); + if (!nextEntry.done) { + sendZipEntry(nextEntry.value); + } + }); +} // sendZipEntry + + +var it = processZip("./foo.zip"); +var nextZip = it.next(); +if (!nextZip.done) { + sendZipEntry(nextZip.value); +} diff --git a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp index 12a7404a..daa4ec22 100644 --- a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp +++ b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.cpp @@ -195,7 +195,7 @@ void Adafruit_SSD1306::begin(uint8_t vccstate, uint8_t i2caddr, bool reset) { dev_config.duty_cycle_pos = 0; dev_config.cs_ena_posttrans = 0; dev_config.cs_ena_pretrans = 0; - dev_config.clock_speed_hz = 100000; // 100KHz + dev_config.clock_speed_hz = 1000000; // 1MHz dev_config.spics_io_num = cs; dev_config.flags = 0; dev_config.queue_size = 1; @@ -393,27 +393,33 @@ void Adafruit_SSD1306::dim(boolean dim) { } void Adafruit_SSD1306::display(void) { - ssd1306_command(SSD1306_COLUMNADDR); - ssd1306_command(0); // Column start address (0 = reset) - ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address (127 = reset) - - ssd1306_command(SSD1306_PAGEADDR); - ssd1306_command(0); // Page start address (0 = reset) - #if SSD1306_LCDHEIGHT == 64 - ssd1306_command(7); // Page end address - #endif - #if SSD1306_LCDHEIGHT == 32 - ssd1306_command(3); // Page end address - #endif - #if SSD1306_LCDHEIGHT == 16 - ssd1306_command(1); // Page end address - #endif + + gpio_set_level((gpio_num_t)dc, 0); + uint8_t cmd_buffer[] = {SSD1306_COLUMNADDR, 0, SSD1306_LCDWIDTH-1, SSD1306_PAGEADDR, 0, (SSD1306_LCDHEIGHT/8-1)}; + spi_transaction_t trans_desc; + trans_desc.addr= 0; + trans_desc.cmd = 0; + trans_desc.flags = 0; + trans_desc.length = 6 * 8; + trans_desc.rxlength = 0; + trans_desc.tx_buffer = cmd_buffer; + trans_desc.rx_buffer = NULL; + + ESP_ERROR_CHECK(spi_device_transmit(spi_handle, &trans_desc)); gpio_set_level((gpio_num_t)dc, 1); - for (uint16_t i=0; i<(SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT/8); i++) { - fastSPIwrite(buffer[i]); - } + //spi_transaction_t trans_desc; + trans_desc.addr= 0; + trans_desc.cmd = 0; + trans_desc.flags = 0; + trans_desc.length = SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH; + trans_desc.rxlength = 0; + trans_desc.tx_buffer = buffer; + trans_desc.rx_buffer = NULL; + + ESP_ERROR_CHECK(spi_device_transmit(spi_handle, &trans_desc)); + } // clear everything @@ -424,8 +430,8 @@ void Adafruit_SSD1306::clearDisplay(void) { inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) { spi_transaction_t trans_desc; - trans_desc.address = 0; - trans_desc.command = 0; + trans_desc.addr= 0; + trans_desc.cmd = 0; trans_desc.flags = 0; trans_desc.length = 8; trans_desc.rxlength = 0; @@ -651,3 +657,15 @@ void Adafruit_SSD1306::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h } } } +#ifndef ARDUINO +void Adafruit_SSD1306::println(char* text){ + print(text); + print((char*)"\n"); +} +void Adafruit_SSD1306::print(char* text){ + while(*text != 0) { + write(*text); + text++; + } +} +#endif diff --git a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h index e25c03f8..98ef0227 100644 --- a/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h +++ b/hardware/displays/Adafruit_SSD1306-Library/Adafruit_SSD1306.h @@ -44,8 +44,8 @@ All text above, and the splash screen must be included in any redistribution SSD1306_96_16 -----------------------------------------------------------------------*/ -// #define SSD1306_128_64 - #define SSD1306_128_32 + #define SSD1306_128_64 +// #define SSD1306_128_32 // #define SSD1306_96_16 /*=========================================================================*/ @@ -124,6 +124,10 @@ class Adafruit_SSD1306 : public Adafruit_GFX { void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS, bool reset=true); void ssd1306_command(uint8_t c); +#ifndef ARDUINO + void print(char*); + void println(char*); +#endif void clearDisplay(void); void invertDisplay(uint8_t i); void display(); diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk b/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk new file mode 100644 index 00000000..61f8990c --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp b/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp new file mode 100644 index 00000000..bc0bd5aa --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/main.cpp @@ -0,0 +1,13 @@ +#include "freertos/FreeRTOS.h" +#include "esp_event.h" + +extern "C"{ + void app_main(void); +} +void test_task(void*); + +void app_main(void) +{ + xTaskCreate(&test_task, "test", 2048, NULL, 5, NULL); +} + diff --git a/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp b/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp new file mode 100644 index 00000000..c93fc522 --- /dev/null +++ b/hardware/displays/Adafruit_SSD1306-Library/examples/main/tests.cpp @@ -0,0 +1,349 @@ +/* + * tests.cpp + * + * Created on: Oct 9, 2017 + * Author: chegewara + */ +#define enablePartialUpdate + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +//#include "math.h" +#include "sdkconfig.h" + +#include "Adafruit_SSD1306.h" +#define SCLK_PIN GPIO_NUM_18 +#define DIN_PIN GPIO_NUM_23 +#define DC_PIN GPIO_NUM_16 +#define CS_PIN GPIO_NUM_5 +#define RST_PIN GPIO_NUM_14 + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define NUMFLAKES 10 +#define XPOS 0 +#define YPOS 1 +#define DELTAY 2 +#define delay(x) vTaskDelay(x/portTICK_PERIOD_MS) + +#define LOGO16_GLCD_HEIGHT 16 +#define LOGO16_GLCD_WIDTH 16 + +static const unsigned char logo16_glcd_bmp[] = +{ 0b00000000, 0b11000000, + 0b00000001, 0b11000000, + 0b00000001, 0b11000000, + 0b00000011, 0b11100000, + 0b11110011, 0b11100000, + 0b11111110, 0b11111000, + 0b01111110, 0b11111111, + 0b00110011, 0b10011111, + 0b00011111, 0b11111100, + 0b00001101, 0b01110000, + 0b00011011, 0b10100000, + 0b00111111, 0b11100000, + 0b00111111, 0b11110000, + 0b01111100, 0b11110000, + 0b01110000, 0b01110000, + 0b00000000, 0b00110000 }; + +Adafruit_SSD1306 display = Adafruit_SSD1306(DIN_PIN, SCLK_PIN, DC_PIN, RST_PIN, CS_PIN); + +void testdrawchar(void) { + display.setTextSize(1); + display.setTextColor(WHITE); + display.setCursor(0,0); + + for (uint8_t i=0; i < 168; i++) { + if (i == '\n') continue; + display.write(i); + //if ((i > 0) && (i % 14 == 0)) + //display.println(); + } + display.display(); +} + +void testdrawcircle(void) { + for (int16_t i=0; i0; i-=5) { + display.fillTriangle(display.width()/2, display.height()/2-i, + display.width()/2-i, display.height()/2+i, + display.width()/2+i, display.height()/2+i, color); + if (color == WHITE) color = BLACK; + else color = WHITE; + display.display(); + } +} + +void testdrawroundrect(void) { + for (int16_t i=0; i=0; i-=4) { + display.drawLine(0, display.height()-1, display.width()-1, i, WHITE); + display.display(); + } + delay(25); + + display.clearDisplay(); + for (int16_t i=display.width()-1; i>=0; i-=4) { + display.drawLine(display.width()-1, display.height()-1, i, 0, WHITE); + display.display(); + } + for (int16_t i=display.height()-1; i>=0; i-=4) { + display.drawLine(display.width()-1, display.height()-1, 0, i, WHITE); + display.display(); + } + delay(25); + + display.clearDisplay(); + for (int16_t i=0; i display.height()) { + icons[f][XPOS] = rand()%(display.width()); + icons[f][YPOS] = 0; + icons[f][DELTAY] = rand()%(5) + 1; + } + } + } +} + +void test_task(void*) { + while(1){ + display.begin(); + //display.setContrast(50); + display.display(); + + delay(2000); + display.clearDisplay(); // clears the screen and buffer + + // draw a single pixel + display.drawPixel(10, 10, WHITE); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw many lines + testdrawline(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw rectangles + testdrawrect(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw multiple rectangles + testfillrect(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw mulitple circles + testdrawcircle(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw a circle, 10 pixel radius + display.fillCircle(display.width()/2, display.height()/2, 10, WHITE); + display.display(); + delay(2000); + display.clearDisplay(); + + testdrawroundrect(); + delay(2000); + display.clearDisplay(); + + testfillroundrect(); + delay(2000); + display.clearDisplay(); + + testdrawtriangle(); + delay(2000); + display.clearDisplay(); + + testfilltriangle(); + delay(2000); + display.clearDisplay(); + + // draw the first ~12 characters in the font + testdrawchar(); + display.display(); + delay(2000); + display.clearDisplay(); + + // draw scrolling text + testscrolltext(); + delay(2000); + display.clearDisplay(); + + // text display tests + display.setTextSize(2); + display.setTextColor(WHITE); + display.setCursor(0,0); + display.println((char*)"Hello, world!"); + display.setTextColor(WHITE, BLACK); // 'inverted' text + display.println((char*)"3.141592"); + display.setTextSize(1); + display.setTextColor(WHITE); + display.print((char*)"0x"); + display.println((char*)"DEADBEEF"); + display.display(); + delay(2000); + + // rotation example + display.clearDisplay(); + display.setRotation(1); // rotate 90 degrees counter clockwise, can also use values of 2 and 3 to go further. + display.setTextSize(1); + display.setTextColor(WHITE); + display.setCursor(0,0); + display.println((char*)"Rotation"); + display.setTextSize(1); + display.println((char*)"Example!"); + display.display(); + delay(2000); + + // revert back to no rotation + display.setRotation(0); + + // miniature bitmap display + display.clearDisplay(); + display.drawBitmap(30, 16, logo16_glcd_bmp, 16, 16, WHITE); + display.display(); + + // invert the display + display.invertDisplay(true); + delay(1000); + display.invertDisplay(false); + delay(1000); + + // draw a bitmap icon and 'animate' movement + testdrawbitmap(logo16_glcd_bmp, LOGO16_GLCD_WIDTH, LOGO16_GLCD_HEIGHT); + } +} + + diff --git a/hardware/displays/U8G2/test_SSD1306.c b/hardware/displays/U8G2/test_SSD1306.c index 62f57cf1..16217017 100644 --- a/hardware/displays/U8G2/test_SSD1306.c +++ b/hardware/displays/U8G2/test_SSD1306.c @@ -40,16 +40,16 @@ void task_test_SSD1306(void *ignore) { u8g2_Setup_ssd1306_128x64_noname_f( &u8g2, U8G2_R0, - u8g2_esp32_msg_comms_cb, - u8g2_esp32_msg_gpio_and_delay_cb); // init u8g2 structure + u8g2_esp32_spi_byte_cb, + u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this, u8g2_SetPowerSave(&u8g2, 0); // wake up display u8g2_ClearBuffer(&u8g2); u8g2_DrawBox(&u8g2, 10,20, 20, 30); - u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); - u8g2_DrawStr(&u8g2, 0,15,"Hello World!"); + u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); + u8g2_DrawStr(&u8g2, 0,15,"Hello World!"); u8g2_SendBuffer(&u8g2); ESP_LOGD(tag, "All done!"); diff --git a/hardware/displays/U8G2/test_SSD1306_i2c.c b/hardware/displays/U8G2/test_SSD1306_i2c.c index a26d91fe..2ea8e6b4 100644 --- a/hardware/displays/U8G2/test_SSD1306_i2c.c +++ b/hardware/displays/U8G2/test_SSD1306_i2c.c @@ -26,12 +26,12 @@ void task_test_SSD1306i2c(void *ignore) { u8g2_t u8g2; // a structure which will contain all the data for one display - u8g2_Setup_ssd1306_128x32_univision_f( + u8g2_Setup_ssd1306_i2c_128x32_univision_f( &u8g2, U8G2_R0, //u8x8_byte_sw_i2c, - u8g2_esp32_msg_i2c_cb, - u8g2_esp32_msg_i2c_and_delay_cb); // init u8g2 structure + u8g2_esp32_i2c_byte_cb, + u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure u8x8_SetI2CAddress(&u8g2.u8x8,0x78); ESP_LOGI(TAG, "u8g2_InitDisplay"); diff --git a/hardware/displays/U8G2/u8g2_esp32_hal.c b/hardware/displays/U8G2/u8g2_esp32_hal.c index c4ea7875..caaa78d9 100644 --- a/hardware/displays/U8G2/u8g2_esp32_hal.c +++ b/hardware/displays/U8G2/u8g2_esp32_hal.c @@ -10,9 +10,11 @@ #include "u8g2_esp32_hal.h" static const char *TAG = "u8g2_hal"; +static const unsigned int I2C_TIMEOUT_MS = 1000; -static spi_device_handle_t handle; // SPI handle. -static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data. +static spi_device_handle_t handle_spi; // SPI handle. +static i2c_cmd_handle_t handle_i2c; // I2C handle. +static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data. #undef ESP_ERROR_CHECK #define ESP_ERROR_CHECK(x) do { esp_err_t rc = (x); if (rc != ESP_OK) { ESP_LOGE("err", "esp_err_t = %d", rc); assert(0 && #x);} } while(0); @@ -26,10 +28,10 @@ void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param) { /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for communications. + * to handle SPI communications. */ -uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - //ESP_LOGD(tag, "msg_comms_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); +uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { case U8X8_MSG_BYTE_SET_DC: if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { @@ -45,12 +47,13 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void } spi_bus_config_t bus_config; + memset(&bus_config, 0, sizeof(spi_bus_config_t)); bus_config.sclk_io_num = u8g2_esp32_hal.clk; // CLK bus_config.mosi_io_num = u8g2_esp32_hal.mosi; // MOSI bus_config.miso_io_num = -1; // MISO bus_config.quadwp_io_num = -1; // Not used bus_config.quadhd_io_num = -1; // Not used - //ESP_LOGI(tag, "... Initializing bus."); + //ESP_LOGI(TAG, "... Initializing bus."); ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &bus_config, 1)); spi_device_interface_config_t dev_config; @@ -67,45 +70,44 @@ uint8_t u8g2_esp32_msg_comms_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void dev_config.queue_size = 200; dev_config.pre_cb = NULL; dev_config.post_cb = NULL; - //ESP_LOGI(tag, "... Adding device bus."); - ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle)); + //ESP_LOGI(TAG, "... Adding device bus."); + ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle_spi)); break; } case U8X8_MSG_BYTE_SEND: { spi_transaction_t trans_desc; - trans_desc.address = 0; - trans_desc.command = 0; + trans_desc.addr = 0; + trans_desc.cmd = 0; trans_desc.flags = 0; trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes. trans_desc.rxlength = 0; trans_desc.tx_buffer = arg_ptr; trans_desc.rx_buffer = NULL; - //ESP_LOGI(tag, "... Transmitting %d bytes.", arg_int); - ESP_ERROR_CHECK(spi_device_transmit(handle, &trans_desc)); + //ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int); + ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc)); break; } } return 0; -} // u8g2_esp32_msg_comms_cb +} // u8g2_esp32_spi_byte_cb /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for communications. + * to handle I2C communications. */ -uint8_t u8g2_esp32_msg_i2c_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - - ESP_LOGD(TAG, "msg_i2c_cb: Received a msg: %d %d", msg, arg_int); - //ESP_LOGD(tag, "msg_i2c_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); +uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { - case U8X8_MSG_BYTE_SET_DC: + case U8X8_MSG_BYTE_SET_DC: { if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { gpio_set_level(u8g2_esp32_hal.dc, arg_int); } break; + } case U8X8_MSG_BYTE_INIT: { if (u8g2_esp32_hal.sda == U8G2_ESP32_HAL_UNDEFINED || @@ -127,108 +129,47 @@ uint8_t u8g2_esp32_msg_i2c_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void * ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf)); ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM); ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0)); -/* - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // dummy write - ESP_ERROR_CHECK(i2c_master_start(cmd)); - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x00 | I2C_MASTER_WRITE, ACK_CHECK_DIS)); - ESP_ERROR_CHECK(i2c_master_stop(cmd)); - ESP_LOGI(TAG, "i2c_master_cmd_begin %d", I2C_MASTER_NUM); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS)); - i2c_cmd_link_delete(cmd); -*/ - break; } case U8X8_MSG_BYTE_SEND: { - uint8_t *data; - uint8_t cmddata; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - ESP_ERROR_CHECK(i2c_master_start(cmd)); -// ESP_LOGI(TAG, "I2CAddress %02X", u8x8_GetI2CAddress(u8x8)>>1); - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, u8x8_GetI2CAddress(u8x8) | I2C_MASTER_WRITE, ACK_CHECK_EN)); - data = (uint8_t *)arg_ptr; - if (arg_int==1) { - cmddata=0; - ESP_ERROR_CHECK(i2c_master_write(cmd, &cmddata, 1, ACK_CHECK_EN)); -// printf("0x%02X ",zerodata); - } else { - cmddata=0x40; - ESP_ERROR_CHECK(i2c_master_write(cmd, &cmddata, 1, ACK_CHECK_EN)); - //bzero(arg_ptr,arg_int); - //*data=0x40; - } - //ESP_ERROR_CHECK(i2c_master_write(cmd, arg_ptr, arg_int, ACK_CHECK_EN)); + uint8_t* data_ptr = (uint8_t*)arg_ptr; + ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE); - while( arg_int > 0 ) { - ESP_ERROR_CHECK(i2c_master_write_byte(cmd, *data, ACK_CHECK_EN)); -// printf("0x%02X ",*data); - data++; + while( arg_int > 0 ) { + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN)); + data_ptr++; arg_int--; - } -// printf("\n"); - - ESP_ERROR_CHECK(i2c_master_stop(cmd)); -// ESP_LOGI(TAG, "i2c_master_cmd_begin %d", I2C_MASTER_NUM); - ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS)); - i2c_cmd_link_delete(cmd); - break; - } - } - return 0; -} // u8g2_esp32_msg_i2c_cb - -/* - * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for GPIO and delay functions. - */ -uint8_t u8g2_esp32_msg_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - //ESP_LOGD(tag, "msg_gpio_and_delay_cb: Received a msg: %d: %s", msg, msgToString(msg, arg_int, arg_ptr)); - switch(msg) { - - // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been - // specified then we define those pins as GPIO outputs. - case U8X8_MSG_GPIO_AND_DELAY_INIT: { - uint64_t bitmask = 0; - if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { - bitmask = bitmask | (1<>1); + ESP_ERROR_CHECK(i2c_master_start(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN)); break; + } - // Delay for the number of milliseconds passed in through arg_int. - case U8X8_MSG_DELAY_MILLI: - vTaskDelay(arg_int/portTICK_PERIOD_MS); + case U8X8_MSG_BYTE_END_TRANSFER: { + ESP_LOGD(TAG, "End I2C transfer."); + ESP_ERROR_CHECK(i2c_master_stop(handle_i2c)); + ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, I2C_TIMEOUT_MS / portTICK_RATE_MS)); + i2c_cmd_link_delete(handle_i2c); break; + } } return 0; -} // u8g2_esp32_msg_gpio_and_delay_cb +} // u8g2_esp32_i2c_byte_cb /* * HAL callback function as prescribed by the U8G2 library. This callback is invoked - * to handle callbacks for I²C and delay functions. + * to handle callbacks for GPIO and delay functions. */ -uint8_t u8g2_esp32_msg_i2c_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { - - ESP_LOGD(TAG, "msg_i2c_and_delay_cb: Received a msg: %d", msg); +uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { + ESP_LOGD(TAG, "gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr); switch(msg) { // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been @@ -236,20 +177,15 @@ uint8_t u8g2_esp32_msg_i2c_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_i case U8X8_MSG_GPIO_AND_DELAY_INIT: { uint64_t bitmask = 0; if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) { - bitmask = bitmask | (1< +#include +#include +#include +#include +#include +#include +#include +#include "errorhandle_func.h" + +#include "sdkconfig.h" + +#define SDA_PIN 23 +#define SCL_PIN 22 +#define RTC_ADDRESS 0x68 // most I2C rtcs have their address on 0x68. any doubt check with i2c scanner snippet + +static char tag[] = "RTC"; + +static uint8_t intToBCD(uint8_t num) { + return ((num / 10) << 4) | (num%10); +} + +static uint8_t bcdToInt(uint8_t bcd) { + // 0x10 + return ((bcd >> 4) * 10) + (bcd & 0x0f);; +} + + +void initI2C() { + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = SDA_PIN; + conf.scl_io_num = SCL_PIN; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = 100000; + ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &conf)); + ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0)); +} + +/* + * PCF8523 slightly changed its 7 bytes encoded in BCD: + * 03h - Seconds - 00-59 + * 04h - Minutes - 00-59 + * 05h - Hours - 00-23 + * 06h - monthday - 01-31 + * 07h - weekday - 00-06 + * 08h - Month - 01-12 + * 09h - Year - 00-99 + * + */ +time_t rtc_readValue() { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_WRITE, true /* expect ack */)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x03, 1)); // start address + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_READ, true /* expect ack */)); + uint8_t data[7]; + ESP_ERROR_CHECK(i2c_master_read(cmd, data, 7, false)); + ESP_ERROR_CHECK(i2c_master_stop(cmd)); + COMMANDCHECKOKERR(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS),"RTC COMMAND"); + i2c_cmd_link_delete(cmd); + + int i; + for (i=0; i<7; i++) { + ESP_LOGD(tag, "%d: 0x%.2x", i, data[i]); + } + + struct tm tm; + tm.tm_sec = bcdToInt(data[0]); + tm.tm_min = bcdToInt(data[1]); + tm.tm_hour = bcdToInt(data[2]); + tm.tm_mday = bcdToInt(data[3]); + tm.tm_mon = bcdToInt(data[5]) - 1; // 0-11 - Note: The month on the PCF8523 is 1-12. + tm.tm_year = bcdToInt(data[6]) + 100; // Years since 1900 + time_t readTime = mktime(&tm); + return readTime; +} + +void rtc_writeValue(time_t newTime) { + ESP_LOGD(tag, ">> writeValue: %ld", newTime); + struct tm tm; + gmtime_r(&newTime, &tm); + char buf[30]; + ESP_LOGD(tag, " - %s", asctime_r(&tm, buf)); + + esp_err_t errRc; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + ESP_ERROR_CHECK(i2c_master_start(cmd)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (RTC_ADDRESS << 1) | I2C_MASTER_WRITE, 1 /* expect ack */)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, 0x03, 1)); + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_sec), 1)); // seconds + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_min), 1 )); // minutes + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_hour), 1 )); // hours + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_mday), 1)); // date of month + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_wday+1), 1 )); // week day + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_mon+1), 1)); // month + ESP_ERROR_CHECK(i2c_master_write_byte(cmd, intToBCD(tm.tm_year-100), 1)); // year + ESP_ERROR_CHECK(i2c_master_stop(cmd)); + errRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS); + if (errRc != 0) { + ESP_LOGE(tag, "i2c_master_cmd_begin: %d", errRc); + } + i2c_cmd_link_delete(cmd); +} + + +/* + implement in your time function + @Kolban created a nice one, here is my contribution. + + esp_err_t sntp_update(){ + + static const char *tag = "TIME_SETUP"; + esp_err_t ret; + char buffer[20]; + EventBits_t bitreturn; + TickType_t waittime = 10000/portTICK_PERIOD_MS; + + + bitreturn = xEventGroupWaitBits(event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, waittime); + + if((bitreturn & BIT0) != 0){ + + printf("going online\n"); + + } + else printf("going offline\n"); + + + // initialize the SNTP service + sntp_setoperatingmode(SNTP_OPMODE_POLL); + // to create SNTP server variable make a #define statement i.e. "pool.ntp.org" + sntp_setservername(0, CONFIG_SNTP_SERVER); + sntp_init(); + + initI2C(); + + time_t t; + struct tm timertc; + t = rtc_readValue(); + localtime_r(&t, &timertc); + + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timertc); + ESP_LOGI(tag,"Current time in rtc (GTM) is: %s\n\n", buffer); + + // wait for the service to set the time + time_t now; + struct tm timeinfo; + time(&now); + localtime_r(&now, &timeinfo); + int counter = 0; + + // try for a minute to get time from network + while((timeinfo.tm_year < (2018 - 1900)) && (counter < 12)) + { + + ESP_LOGW(tag,"Time outdated, waiting...\n"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + time(&now); + localtime_r(&now, &timeinfo); + counter ++; + } + + if((timeinfo.tm_year < (2018 - 1900)) && (counter == 12)){ + ESP_LOGE(tag, "TIMEOUT"); + + + // stick to rtc time + now = rtc_readValue(); + + } + else{ + // update rtc time + rtc_writeValue(now); + } + + + // to create timezone variable make a #define statement i.e. "COT+5" + setenv("TZ",CONFIG_TIMEZONE_TZ, 1); + tzset(); + + // print the actual time in location + localtime_r(&now, &timeinfo); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timeinfo); + ESP_LOGI(tag,"Current time in your Location: %s\n\n", buffer); + + // check time set in rtc in case it was updated from network + t = rtc_readValue(); + localtime_r(&t, &timertc); + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", &timertc); + ESP_LOGI(tag,"Current time in rtc (GTM) is: %s\n\n", buffer); + + ret = ESP_OK; + + return ret; +} + + */ diff --git a/memory/dma/DMA.cpp b/memory/dma/DMA.cpp new file mode 100644 index 00000000..a72a92e5 --- /dev/null +++ b/memory/dma/DMA.cpp @@ -0,0 +1,411 @@ +/* + * DMA.cpp + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#include "DMA.h" +#include +#include +#include +#include + +#include // Inclusions for WRITE_PERI_xxx and family. +#include // Inclusions for I2S registers. +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#define D0 GPIO_NUM_4 +#define D1 GPIO_NUM_5 +#define D2 GPIO_NUM_18 +#define D3 GPIO_NUM_19 +#define D4 GPIO_NUM_36 +#define D5 GPIO_NUM_39 +#define D6 GPIO_NUM_34 +#define D7 GPIO_NUM_35 +#define VSYNC GPIO_NUM_25 +#define HREF GPIO_NUM_23 +#define PCLK GPIO_NUM_22 + +lldesc_t ll1; + +QueueHandle_t queueHandle; +intr_handle_t intHandle; + +class LogInterruptTask : public Task { + void run(void *data) { + printf("Starting interrupt log\n"); + uint32_t queueItem; + while(1) { + xQueueReceive(queueHandle, &queueItem, portMAX_DELAY); + printf("New interrupt value: 0x%x\n", queueItem); + } // End while 1 + } // LogInterruptTask#run +}; // LogInterruptTask + +static void IRAM_ATTR dmaInt(void *arg) { + uint32_t value = 0x99; + xQueueSendToBackFromISR(queueHandle, &value, nullptr); + I2S0.int_clr.val = I2S0.int_raw.val; +} + + +DMA::DMA() { + +} + +DMA::~DMA() { +} + + +void DMA::clearInterupts() { + + // Set 1 bits in the flags for I2S_INT_CLR_REG to clear interrupts. + I2S0.int_clr.val = 0xFFFFFFFF; // 32 bits of 1 to clear all flags. + +} // DMA#clearInterupts + + +void DMA::reset() { + + clearInterupts(); + ll1.eof = 1; + ll1.length = 0; // Number of valid bytes. + ll1.size = 1024; // Size of buffer. + ll1.offset = 0; + ll1.sosf = 0; + ll1.empty = 0; // No next link + ll1.owner = 1; // 1 = DMA, 2 = software + memset((void *)ll1.buf, 0xff, ll1.size); + + + + const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M | I2S_AHBM_FIFO_RST_M; + I2S0.lc_conf.val |= lc_conf_reset_flags; + I2S0.lc_conf.val &= ~lc_conf_reset_flags; + + + // See TRM 12.4.2. + const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M; + I2S0.conf.val |= conf_reset_flags; + I2S0.conf.val &= ~conf_reset_flags; + + while (I2S0.state.rx_fifo_reset_back) { // Bit is 1 while not ready + ; + } + + I2S0.in_link.addr = (uint32_t)&ll1; +} // DMA#reset + + +void DMA::dumpBuffer() { + printf(">> dumpBuffer\n"); + GeneralUtils::hexDump((uint8_t*)ll1.buf, 64); + printf("<< dumpBuffer\n"); +} + +void DMA::setCameraMode() { + printf(">> setCameraMode\n"); + + gpio_pad_select_gpio(D0); + gpio_pad_select_gpio(D1); + gpio_pad_select_gpio(D2); + gpio_pad_select_gpio(D3); + gpio_pad_select_gpio(D4); + gpio_pad_select_gpio(D5); + gpio_pad_select_gpio(D6); + gpio_pad_select_gpio(D7); + gpio_pad_select_gpio(VSYNC); + gpio_pad_select_gpio(HREF); + gpio_pad_select_gpio(PCLK); + + gpio_set_direction(D0, GPIO_MODE_INPUT); + gpio_set_direction(D1, GPIO_MODE_INPUT); + gpio_set_direction(D2, GPIO_MODE_INPUT); + gpio_set_direction(D3, GPIO_MODE_INPUT); + gpio_set_direction(D4, GPIO_MODE_INPUT); + gpio_set_direction(D5, GPIO_MODE_INPUT); + gpio_set_direction(D6, GPIO_MODE_INPUT); + gpio_set_direction(D7, GPIO_MODE_INPUT); + gpio_set_direction(VSYNC, GPIO_MODE_INPUT); + gpio_set_direction(HREF, GPIO_MODE_INPUT); + gpio_set_direction(PCLK, GPIO_MODE_INPUT); + + // Map sources / sinks of data to their logical counter parts. + gpio_matrix_in(D0, I2S0I_DATA_IN0_IDX, false); + gpio_matrix_in(D1, I2S0I_DATA_IN1_IDX, false); + gpio_matrix_in(D2, I2S0I_DATA_IN2_IDX, false); + gpio_matrix_in(D3, I2S0I_DATA_IN3_IDX, false); + gpio_matrix_in(D4, I2S0I_DATA_IN4_IDX, false); + gpio_matrix_in(D5, I2S0I_DATA_IN5_IDX, false); + gpio_matrix_in(D6, I2S0I_DATA_IN6_IDX, false); + gpio_matrix_in(D7, I2S0I_DATA_IN7_IDX, false); + + // Set constants for the bits [15:8] alternating 1 and 0. This results in 0xAA. + gpio_matrix_in(0x30, I2S0I_DATA_IN8_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN9_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN10_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN11_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN12_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN13_IDX, false); + gpio_matrix_in(0x30, I2S0I_DATA_IN14_IDX, false); + gpio_matrix_in(0x38, I2S0I_DATA_IN15_IDX, false); + + gpio_matrix_in(0x38 /*VSYNC*/, I2S0I_V_SYNC_IDX, false); + gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); // Constant high + gpio_matrix_in(0x38 /* HREF */, I2S0I_H_ENABLE_IDX, false); + gpio_matrix_in(PCLK, I2S0I_WS_IN_IDX, false); + + //SET_PERI_REG_BITS(I2SCONF2_REG, I2S_CAMERA_EN, 1, I2S_CAMERA_EN_S); + + + // We must enable I2S0 peripheral + periph_module_enable(PERIPH_I2S0_MODULE); + + reset(); + + + /* + rx_msb_right | result | +---------------+--------------------------------------------------------------------------------+ + 0 | 00 00 v1low v1high 00 00 v2low v2high 00 00 v3low v3high 00 00 v4low v4high | + 1 | 00 00 v1low v1high 00 00 v2low ?? 00 00 v2low ?? 00 00 v3 low ?? | + */ + + // We enable I2S_CONF2_REG:I2S_CAMERA_EN + I2S0.conf2.camera_en = 1; // 1 for camera mode (TRM). + + // We enable I2S_CONF_REG:I2S_LCD_EN + I2S0.conf2.lcd_en = 1; // 1 for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_SLAVE_MOD + I2S0.conf.rx_slave_mod = 1; // 1 for slave receiving mode. Required for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_MSB_RIGHT + I2S0.conf.rx_msb_right = 0; // 0 required for camera mode (TRM). + + // I2S_CONF_REG:I2S_RX_RIGHT_FIRST + I2S0.conf.rx_right_first = 0; // 0 required for camera mode (TRM). + + // I2S_CONF_CHANG_REG:I2S_RX_CHAN_MOD + I2S0.conf_chan.rx_chan_mod = 1; // 1 required for camera mode (TRM). + + // I2S_FIFO_CONF_REG:I2S_RX_FIFO_MOD + I2S0.fifo_conf.rx_fifo_mod = 1; // 1 = 16bit single channel data. Required for camera mode (TRM). + + // Configure clock divider + I2S0.clkm_conf.clkm_div_a = 1; + I2S0.clkm_conf.clkm_div_b = 0; + I2S0.clkm_conf.clkm_div_num = 2; + + // FIFO will sink data to DMA + I2S0.fifo_conf.dscr_en = 1; + + // FIFO configuration + I2S0.fifo_conf.rx_fifo_mod = 2; + I2S0.fifo_conf.rx_fifo_mod_force_en = 1; + I2S0.conf_chan.rx_chan_mod = 1; + + // Clear flags which are used in I2S serial mode + I2S0.sample_rate_conf.rx_bits_mod = 0; + I2S0.conf.rx_right_first = 0; + I2S0.conf.rx_msb_right = 0; + I2S0.conf.rx_msb_shift = 0; + I2S0.conf.rx_mono = 0; + I2S0.conf.rx_short_sync = 0; + I2S0.timing.val = 0; + + + esp_err_t errRc; + errRc = esp_intr_alloc( + ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, + &dmaInt, + NULL, + &intHandle); + if (errRc != ESP_OK) { + printf("esp_intr_alloc: %d\n", errRc); + } + + errRc = esp_intr_enable(intHandle); + if (errRc != ESP_OK) { + printf("esp_intr_enable: %d\n", errRc); + } + + I2S0.rx_eof_num = 8; + + + ll1.eof = 1; + ll1.length = 0; // Number of valid bytes. + ll1.size = 1024; // Size of buffer. + ll1.offset = 0; + ll1.sosf = 0; + ll1.empty = 0; // No next link + ll1.owner = 1; // 1 = DMA, 0 = software + + memset((void *)ll1.buf, 0xff, ll1.size); + + // The address of the first in-link descriptor. + I2S0.in_link.addr = (uint32_t)&ll1; + + // Set to 1 to start in-link descriptor. + //I2S0.in_link.start = 1; + + startRX(); + + clearInterupts(); + + printf("<< setCameraMode\n"); +} // DMA#setCameraMode + + +void DMA::stopRX() { + // Set to 0 to stop receiving data. + I2S0.conf.rx_start = 0; + I2S0.conf.rx_reset = 1; +} // DMA#stopRX + + +void DMA::start() { + ll1.buf = (uint8_t*)malloc(1024); // Allocate storage for the linked list buffer. + queueHandle = xQueueCreate(50, sizeof(uint32_t)); + + LogInterruptTask* pLogInterruptTask = new LogInterruptTask(); + pLogInterruptTask->start(); +} // DMA#start + + +void DMA::startRX() { + // Set to 1 to start receiving data. + + printf(">> DMA::startRX\n"); + + I2S0.int_clr.val = I2S0.int_raw.val; + + I2S0.rx_eof_num = 8; + + I2S0.in_link.addr = (uint32_t)&ll1; + //I2S0.in_link.stop = 1; + I2S0.in_link.start = 1; + //I2S0.in_link.restart = 1; + I2S0.conf.rx_start = 1; + + // Enable interrupt generation + I2S0.int_ena.in_suc_eof = true; + //I2S0.int_ena.in_done = true; + /* + I2S0.int_ena.in_dscr_empty = true; + I2S0.int_ena.in_dscr_err = true; + I2S0.int_ena.in_err_eof = true; + + I2S0.int_ena.rx_hung = true; + I2S0.int_ena.rx_rempty = true; + I2S0.int_ena.rx_take_data = true; + I2S0.int_ena.rx_wfull = true; + */ + //I2S0.int_ena.rx_take_data = true; + + + printf("<< DMA::startRX\n"); +} // DMA#startRX + + +void DMA::dumpStatus() { + printf("conf\n"); + printf("%-20s: %d\n", "I2S_RX_SLAVE_MOD", I2S0.conf.rx_slave_mod); + printf("%-20s: %d\n", "I2S_RX_MSB_RIGHT", I2S0.conf.rx_msb_right); + printf("%-20s: %d\n", "I2S_RX_RIGHT_FIRST", I2S0.conf.rx_right_first); + printf("%-20s: %d\n", "I2S_RX_START", I2S0.conf.rx_start); + printf("%-20s: %d\n", "I2S_RX_RESET", I2S0.conf.rx_reset); + printf("-----\n"); + + printf("conf_chan\n"); + printf("%-20s: %d\n", "I2S_RX_CHAN_MOD", I2S0.conf_chan.rx_chan_mod); + printf("-----\n"); + + printf("fifo_conf (I2S_FIFO_CONF_REG)\n"); + printf("%-26s: %d\n", "I2S_RX_FIFO_MOD_FORCE_EN", I2S0.fifo_conf.rx_fifo_mod_force_en); + printf("%-26s: %d\n", "I2S_RX_FIFO_MOD", I2S0.fifo_conf.rx_fifo_mod); + printf("%-26s: %d\n", "I2S_RX_DSCR_EN", I2S0.fifo_conf.dscr_en); + printf("%-26s: %d\n", "I2S_RX_DATA_NUM", I2S0.fifo_conf.rx_data_num); + printf("-----\n"); + + printf("conf2\n"); + printf("%-20s: %d\n", "I2S_CAMERA_EN", I2S0.conf2.camera_en); + printf("%-20s: %d\n", "I2S_LCD_EN", I2S0.conf2.lcd_en); + printf("-----\n"); + + + printf("state\n"); + printf("%-20s: %d\n", "I2S_RX_FIFO_RESET_BACK", I2S0.state.rx_fifo_reset_back); + printf("-----\n"); + + printf("rx_eof_num (I2S_RXEOF_NUM_REG)\n"); + printf("%-20s: %d\n", "I2S_RXEOF_NUM_REG", I2S0.rx_eof_num); + printf("-----\n"); + + printf("in_eof_des_addr (I2S_IN_EOF_DES_ADDR_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_IN_EOF_DES_ADDR_REG", I2S0.in_eof_des_addr); + printf("-----\n"); + + printf("in_link_dscr (I2S_INLINK_DSCR_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_REG", I2S0.in_link_dscr); + printf("-----\n"); + + printf("in_link_dscr_bf0 (I2S_INLINK_DSCR_BF0_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_BF0_REG", I2S0.in_link_dscr_bf0); + printf("-----\n"); + + printf("in_link_dscr_bf1 (I2S_INLINK_DSCR_BF1_REG)\n"); + printf("%-20s: 0x%x\n", "I2S_INLINK_DSCR_BF1_REG", I2S0.in_link_dscr_bf1); + printf("-----\n"); + + printf("int_raw\n"); + printf("%-25s: %d\n", "I2S_IN_DSCR_EMPTY_INT_RAW", I2S0.int_raw.in_dscr_empty); + printf("%-25s: %d\n", "I2S_IN_DSCR_ERR_INT_RAW", I2S0.int_raw.in_dscr_err); + printf("%-25s: %d\n", "I2S_IN_SUC_EOF_INT_RAW", I2S0.int_raw.in_suc_eof); + printf("%-25s: %d\n", "I2S_IN_ERR_EOF_INT_RAW", I2S0.int_raw.in_err_eof); + printf("%-25s: %d\n", "I2S_IN_DONE_INT_RAW", I2S0.int_raw.in_done); + printf("%-25s: %d\n", "I2S_RX_HUNG_INT_RAW", I2S0.int_raw.rx_hung); + printf("%-25s: %d\n", "I2S_RX_REMPTY_INT_RAW", I2S0.int_raw.rx_rempty); + printf("%-25s: %d\n", "I2S_RX_WFULL_INT_RAW", I2S0.int_raw.rx_wfull); + printf("%-25s: %d\n", "I2S_RX_TAKE_DATA_INT_RAW", I2S0.int_raw.rx_take_data); + printf("-----\n"); + + printf("int_ena\n"); + printf("%-25s: %d\n", "I2S_IN_DSCR_EMPTY_INT_RAW", I2S0.int_ena.in_dscr_empty); + printf("%-25s: %d\n", "I2S_IN_DSCR_ERR_INT_RAW", I2S0.int_ena.in_dscr_err); + printf("%-25s: %d\n", "I2S_IN_SUC_EOF_INT_RAW", I2S0.int_ena.in_suc_eof); + printf("%-25s: %d\n", "I2S_IN_ERR_EOF_INT_RAW", I2S0.int_ena.in_err_eof); + printf("%-25s: %d\n", "I2S_IN_DONE_INT_RAW", I2S0.int_ena.in_done); + printf("%-25s: %d\n", "I2S_RX_HUNG_INT_RAW", I2S0.int_ena.rx_hung); + printf("%-25s: %d\n", "I2S_RX_REMPTY_INT_RAW", I2S0.int_ena.rx_rempty); + printf("%-25s: %d\n", "I2S_RX_WFULL_INT_RAW", I2S0.int_ena.rx_wfull); + printf("%-25s: %d\n", "I2S_RX_TAKE_DATA_INT_RAW", I2S0.int_ena.rx_take_data); + printf("-----\n"); + + printf("in_link\n"); + printf("%-20s: %d\n", "I2S_INLINK_PARK", I2S0.in_link.park); + printf("%-20s: %d\n", "I2S_INLINK_RESTART", I2S0.in_link.restart); + printf("%-20s: %d\n", "I2S_INLINK_START", I2S0.in_link.start); + printf("%-20s: %d\n", "I2S_INLINK_STOP", I2S0.in_link.stop); + printf("%-20s: 0x%x\n", "I2S_INLINK_ADDR", (I2S0.in_link.addr & 0xFFFFF)); + printf("-----\n"); + + printf("lldesc 1: 0x%x\n", (uint32_t)&ll1); + printf("%-20s: %d\n", "length", ll1.length); + printf("%-20s: %d\n", "size", ll1.size); + printf("%-20s: %d\n", "owner", ll1.owner); + printf("%-20s: %d\n", "empty", ll1.empty); + printf("%-20s: 0x%x\n", "&buf", (uint32_t)ll1.buf); +} // DMA#dumpStatus diff --git a/memory/dma/DMA.h b/memory/dma/DMA.h new file mode 100644 index 00000000..2be79458 --- /dev/null +++ b/memory/dma/DMA.h @@ -0,0 +1,26 @@ +/* + * DMA.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_DMA_H_ +#define MAIN_DMA_H_ + +class DMA { +public: + DMA(); + virtual ~DMA(); + void clearInterupts(); + void dumpBuffer(); + void dumpStatus(); + void reset(); + void setCameraMode(); + void start(); + void startRX(); + void stopRX(); + +}; + +#endif /* MAIN_DMA_H_ */ diff --git a/memory/dma/DMA_GPIO.h b/memory/dma/DMA_GPIO.h new file mode 100644 index 00000000..124adc28 --- /dev/null +++ b/memory/dma/DMA_GPIO.h @@ -0,0 +1,13 @@ +/* + * DMA_GPIO.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_DMA_GPIO_H_ +#define MAIN_DMA_GPIO_H_ + + + +#endif /* MAIN_DMA_GPIO_H_ */ diff --git a/memory/dma/GPIODriver.cpp b/memory/dma/GPIODriver.cpp new file mode 100644 index 00000000..78021c0d --- /dev/null +++ b/memory/dma/GPIODriver.cpp @@ -0,0 +1,88 @@ +/* + * GPIODriver.cpp + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#include "GPIODriver.h" +#include "DMA_GPIO.h" +#include +#include +#include + +#define D0 GPIO_NUM_4 +#define D1 GPIO_NUM_5 +#define D2 GPIO_NUM_18 +#define D3 GPIO_NUM_19 +#define D4 GPIO_NUM_13 // 36 +#define D5 GPIO_NUM_14 // 39 +#define D6 GPIO_NUM_15 // 34 +#define D7 GPIO_NUM_16 // 35 +#define VSYNC GPIO_NUM_25 +#define HREF GPIO_NUM_23 +#define PCLK GPIO_NUM_22 + +GPIODriver::GPIODriver() { +} + +GPIODriver::~GPIODriver() { +} + +void GPIODriver::run() { + printf(">> GPIODriver::run\n"); + // Setup the GPIO pins as output. + gpio_pad_select_gpio(D0); + gpio_pad_select_gpio(D1); + gpio_pad_select_gpio(D2); + gpio_pad_select_gpio(D3); + gpio_pad_select_gpio(D4); + gpio_pad_select_gpio(D5); + gpio_pad_select_gpio(D6); + gpio_pad_select_gpio(D7); + gpio_pad_select_gpio(VSYNC); + gpio_pad_select_gpio(HREF); + gpio_pad_select_gpio(PCLK); + + gpio_set_direction(D0, GPIO_MODE_OUTPUT); + gpio_set_direction(D1, GPIO_MODE_OUTPUT); + gpio_set_direction(D2, GPIO_MODE_OUTPUT); + gpio_set_direction(D3, GPIO_MODE_OUTPUT); + gpio_set_direction(D4, GPIO_MODE_OUTPUT); + gpio_set_direction(D5, GPIO_MODE_OUTPUT); + gpio_set_direction(D6, GPIO_MODE_OUTPUT); + gpio_set_direction(D7, GPIO_MODE_OUTPUT); + gpio_set_direction(VSYNC, GPIO_MODE_OUTPUT); + gpio_set_direction(HREF, GPIO_MODE_OUTPUT); + gpio_set_direction(PCLK, GPIO_MODE_OUTPUT); + + gpio_set_level(VSYNC, 1); + gpio_set_level(HREF, 1); + // Initialize the counter. + uint8_t counter = 0; + // Loop + + gpio_set_level(PCLK, 0); + while(1) { + + // Write the counter value to the GPIOs + gpio_set_level(D0, (counter & 0b00000001) != 0); + gpio_set_level(D1, (counter & 0b00000010) != 0); + gpio_set_level(D2, (counter & 0b00000100) != 0); + gpio_set_level(D3, (counter & 0b00001000) != 0); + gpio_set_level(D4, (counter & 0b00010000) != 0); + gpio_set_level(D5, (counter & 0b00100000) != 0); + gpio_set_level(D6, (counter & 0b01000000) != 0); + gpio_set_level(D7, (counter & 0b10000000) != 0); + + + // Pulse the clock + gpio_set_level(PCLK, 1); + vTaskDelay(10/portTICK_PERIOD_MS); + gpio_set_level(PCLK, 0); + + // Delay + vTaskDelay(990/portTICK_PERIOD_MS); + counter++; + } // End while 1 +} // GPIODriver#run diff --git a/memory/dma/GPIODriver.h b/memory/dma/GPIODriver.h new file mode 100644 index 00000000..03f8d7ec --- /dev/null +++ b/memory/dma/GPIODriver.h @@ -0,0 +1,18 @@ +/* + * GPIODriver.h + * + * Created on: May 27, 2018 + * Author: kolban + */ + +#ifndef MAIN_GPIODRIVER_H_ +#define MAIN_GPIODRIVER_H_ + +class GPIODriver { +public: + GPIODriver(); + virtual ~GPIODriver(); + void run(); +}; + +#endif /* MAIN_GPIODRIVER_H_ */ diff --git a/memory/dma/dma_console.cpp b/memory/dma/dma_console.cpp new file mode 100644 index 00000000..bc3bd05b --- /dev/null +++ b/memory/dma/dma_console.cpp @@ -0,0 +1,215 @@ +/** + * DMA Driver + * + * @author Neil Kolban + * @date 2018-05-26 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" +#include "DMA.h" +#include "GPIODriver.h" +#include + +DMA dma; + + +struct arg_end* reset_arg_end = arg_end(10); +void *reset_argtable[] = { + reset_arg_end +}; + +int runReset(int argc, char *argv[]) { + printf("Reset\n"); + dma.reset(); + return 0; +} + + +struct arg_end* start_arg_end = arg_end(10); +void *start_argtable[] = { + start_arg_end +}; + +int runStart(int argc, char *argv[]) { + printf("Started\n"); + return 0; +} + + +struct arg_end* startRX_arg_end = arg_end(10); +void *startRX_argtable[] = { + startRX_arg_end +}; + +int runStartRX(int argc, char *argv[]) { + printf("StartRX\n"); + return 0; +} + + +struct arg_end* stopRX_arg_end = arg_end(10); +void *stopRX_argtable[] = { + stopRX_arg_end +}; + +int runStopRX(int argc, char *argv[]) { + printf("StopRX\n"); + return 0; +} + + +struct arg_end* hexdump_arg_end = arg_end(10); +void *hexdump_argtable[] = { + hexdump_arg_end +}; + +int runHexdump(int argc, char *argv[]) { + printf("Hexdump\n"); + dma.dumpBuffer(); + return 0; +} + +struct arg_end* clientStart_arg_end = arg_end(10); +void *clientStart_arg_end_argtable[] = { + clientStart_arg_end +}; + +int runClientStart(int argc, char *argv[]) { + printf("runClientStart\n"); + GPIODriver gpioDriver; + gpioDriver.run(); + return 0; +} + + +struct arg_end* camera_arg_end = arg_end(10); +void *camera_argtable[] = { + camera_arg_end +}; + +int runCamera(int argc, char *argv[]) { + printf("camera\n"); + dma.setCameraMode(); + return 0; +} + +struct arg_end* stop_arg_end = arg_end(10); +void *stop_argtable[] = { + stop_arg_end +}; + +int runStop(int argc, char *argv[]) { + printf("Stopped\n"); + return 0; +} + + +struct arg_end* status_arg_end = arg_end(10); +void *status_argtable[] = { + stop_arg_end +}; + +int runStatus(int argc, char *argv[]) { + printf("Status\n"); + dma.dumpStatus(); + return 0; +} + + +void test_console(void) +{ + dma.start(); + // Boiler plate setup for using linenoise + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + uart_driver_install((uart_port_t)CONFIG_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + esp_console_config_t consoleConfig; + consoleConfig.max_cmdline_args = 5; + consoleConfig.max_cmdline_length = 100; + + esp_console_init(&consoleConfig); + esp_console_register_help_command(); + + esp_console_cmd_t consoleCmd; + consoleCmd.command = "start"; + consoleCmd.func = runStart; + consoleCmd.help = "Start processing"; + consoleCmd.argtable = start_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "stop"; + consoleCmd.func = runStop; + consoleCmd.help = "Stop processing"; + consoleCmd.argtable = stop_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "status"; + consoleCmd.func = runStatus; + consoleCmd.help = "Status processing"; + consoleCmd.argtable = status_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "camera"; + consoleCmd.func = runCamera; + consoleCmd.help = "camera processing"; + consoleCmd.argtable = camera_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "clientStart"; + consoleCmd.func = runClientStart; + consoleCmd.help = "clientStart processing"; + consoleCmd.argtable = clientStart_arg_end_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "hexdump"; + consoleCmd.func = runHexdump; + consoleCmd.help = "hexdump processing"; + consoleCmd.argtable = hexdump_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "startRX"; + consoleCmd.func = runStartRX; + consoleCmd.help = "startRX processing"; + consoleCmd.argtable = startRX_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "stopRX"; + consoleCmd.func = runStopRX; + consoleCmd.help = "stopRX processing"; + consoleCmd.argtable = stopRX_argtable; + esp_console_cmd_register(&consoleCmd); + + consoleCmd.command = "reset"; + consoleCmd.func = runReset; + consoleCmd.help = "reset processing"; + consoleCmd.argtable = reset_argtable; + esp_console_cmd_register(&consoleCmd); + + + //linenoiseClearScreen(); + linenoiseSetMultiLine(0); + + + while(1) { + char* line = linenoise("Enter command > "); + if (line != NULL) { + int ret; + esp_console_run(line, &ret); + linenoiseHistoryAdd(line); + linenoiseFree(line); + } // Line is not null + printf("--------------\n"); + } // End while loop +} diff --git a/memory/dma/main.cpp b/memory/dma/main.cpp new file mode 100644 index 00000000..f5715f5d --- /dev/null +++ b/memory/dma/main.cpp @@ -0,0 +1,14 @@ +extern "C" { + void app_main(void); +} + +extern void test_linenoise(); +extern void test_argtable(); +extern void test_console(); + +void app_main(void) +{ + //test_linenoise(); + //test_argtable(); + test_console(); +} diff --git a/mongoose/webserver/README.md b/mongoose/webserver/README.md index 4703176a..07e1ef3b 100644 --- a/mongoose/webserver/README.md +++ b/mongoose/webserver/README.md @@ -1,4 +1,4 @@ -#mongoose/webserver +# mongoose/webserver This snippet provides a simple Webserver using the Mongoose library on the ESP32. It provides two URL endpoints: diff --git a/mongoose/webserver/main/component.mk b/mongoose/webserver/main/component.mk index b1671d43..186e7eef 100644 --- a/mongoose/webserver/main/component.mk +++ b/mongoose/webserver/main/component.mk @@ -6,5 +6,5 @@ # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, # please read the ESP-IDF documents if you need to do this. # -CFLAGS += -DCS_PLATFORM=3 -DMG_DISABLE_DIRECTORY_LISTING=1 -DMG_DISABLE_DAV=1 -DMG_DISABLE_CGI=1 +CFLAGS += -DCS_PLATFORM=15 -DMG_DISABLE_DIRECTORY_LISTING=1 -DMG_DISABLE_DAV=1 -DMG_DISABLE_CGI=1 include $(IDF_PATH)/make/component_common.mk diff --git a/mongoose/webserver/sdkconfig b/mongoose/webserver/sdkconfig index 36eff3ff..80a51a3e 100644 --- a/mongoose/webserver/sdkconfig +++ b/mongoose/webserver/sdkconfig @@ -8,150 +8,493 @@ # CONFIG_TOOLPREFIX="xtensa-esp32-elf-" CONFIG_PYTHON="python" +CONFIG_MAKE_WARN_UNDEFINED_VARIABLES=y # # Bootloader config # -# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +CONFIG_LOG_BOOTLOADER_LEVEL_NONE= +CONFIG_LOG_BOOTLOADER_LEVEL_ERROR= CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y -# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set -# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO= +CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG= +CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE= CONFIG_LOG_BOOTLOADER_LEVEL=2 +CONFIG_BOOTLOADER_VDDSDIO_BOOST=y + +# +# Security features +# +CONFIG_SECURE_BOOT_ENABLED= +CONFIG_FLASH_ENCRYPTION_ENABLED= # # Serial flasher config # CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0" -# CONFIG_ESPTOOLPY_BAUD_115200B is not set -# CONFIG_ESPTOOLPY_BAUD_230400B is not set +CONFIG_ESPTOOLPY_BAUD_115200B= +CONFIG_ESPTOOLPY_BAUD_230400B= CONFIG_ESPTOOLPY_BAUD_921600B=y -# CONFIG_ESPTOOLPY_BAUD_2MB is not set -# CONFIG_ESPTOOLPY_BAUD_OTHER is not set +CONFIG_ESPTOOLPY_BAUD_2MB= +CONFIG_ESPTOOLPY_BAUD_OTHER= CONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200 CONFIG_ESPTOOLPY_BAUD=921600 CONFIG_ESPTOOLPY_COMPRESSED=y -# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set -# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set -CONFIG_ESPTOOLPY_FLASHMODE_DIO=y -# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_FLASHMODE_QIO= +CONFIG_FLASHMODE_QOUT= +CONFIG_FLASHMODE_DIO=y +CONFIG_FLASHMODE_DOUT= CONFIG_ESPTOOLPY_FLASHMODE="dio" -# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M= CONFIG_ESPTOOLPY_FLASHFREQ_40M=y -# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set -# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_26M= +CONFIG_ESPTOOLPY_FLASHFREQ_20M= CONFIG_ESPTOOLPY_FLASHFREQ="40m" -# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_1MB= CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB= +CONFIG_ESPTOOLPY_FLASHSIZE_8MB= +CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +CONFIG_ESPTOOLPY_BEFORE_NORESET= +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +CONFIG_ESPTOOLPY_AFTER_NORESET= +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_MONITOR_BAUD_9600B= +CONFIG_MONITOR_BAUD_57600B= +CONFIG_MONITOR_BAUD_115200B=y +CONFIG_MONITOR_BAUD_230400B= +CONFIG_MONITOR_BAUD_921600B= +CONFIG_MONITOR_BAUD_2MB= +CONFIG_MONITOR_BAUD_OTHER= +CONFIG_MONITOR_BAUD_OTHER_VAL=115200 +CONFIG_MONITOR_BAUD=115200 # # Partition Table # CONFIG_PARTITION_TABLE_SINGLE_APP=y -# CONFIG_PARTITION_TABLE_TWO_OTA is not set -# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_TWO_OTA= +CONFIG_PARTITION_TABLE_CUSTOM= CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_APP_OFFSET=0x10000 + +# +# Compiler options +# CONFIG_OPTIMIZATION_LEVEL_DEBUG=y -# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_LEVEL_RELEASE= +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +CONFIG_OPTIMIZATION_ASSERTIONS_SILENT= +CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED= +CONFIG_CXX_EXCEPTIONS= # # Component config # + +# +# Application Level Tracing +# +CONFIG_ESP32_APPTRACE_DEST_TRAX= +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_ENABLE= +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y + +# +# FreeRTOS SystemView Tracing +# +CONFIG_AWS_IOT_SDK= + +# +# Bluetooth +# +CONFIG_BT_ENABLED= +CONFIG_BTDM_CONTROLLER_PINNED_TO_CORE=0 CONFIG_BT_RESERVE_DRAM=0 # -# ESP32-specific config +# ESP32-specific # -# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set -# CONFIG_ESP32_DEFAULT_CPU_FREQ_160 is not set +CONFIG_ESP32_DEFAULT_CPU_FREQ_80= +CONFIG_ESP32_DEFAULT_CPU_FREQ_160= CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 -CONFIG_ESP32_ENABLE_STACK_WIFI=y -# CONFIG_ESP32_ENABLE_STACK_BT is not set -# CONFIG_ESP32_ENABLE_STACK_NONE is not set CONFIG_MEMMAP_SMP=y -# CONFIG_MEMMAP_TRACEMEM is not set +CONFIG_SPIRAM_SUPPORT= +CONFIG_MEMMAP_TRACEMEM= +CONFIG_MEMMAP_TRACEMEM_TWOBANKS= +CONFIG_ESP32_TRAX= CONFIG_TRACEMEM_RESERVE_DRAM=0x0 -CONFIG_WIFI_ENABLED=y +CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH= +CONFIG_ESP32_ENABLE_COREDUMP_TO_UART= +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_ESP32_ENABLE_COREDUMP= +CONFIG_TWO_UNIVERSAL_MAC_ADDRESS= +CONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y +CONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4 CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2048 CONFIG_MAIN_TASK_STACK_SIZE=4096 -CONFIG_NEWLIB_STDOUT_ADDCR=y -# CONFIG_ULP_COPROC_ENABLED is not set +CONFIG_IPC_TASK_STACK_SIZE=1024 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF= +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_LF= +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +CONFIG_NEWLIB_NANO_FORMAT= +CONFIG_CONSOLE_UART_DEFAULT=y +CONFIG_CONSOLE_UART_CUSTOM= +CONFIG_CONSOLE_UART_NONE= +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ULP_COPROC_ENABLED= CONFIG_ULP_COPROC_RESERVE_MEM=0 -# CONFIG_ESP32_PANIC_PRINT_HALT is not set +CONFIG_ESP32_PANIC_PRINT_HALT= CONFIG_ESP32_PANIC_PRINT_REBOOT=y -# CONFIG_ESP32_PANIC_SILENT_REBOOT is not set -# CONFIG_ESP32_PANIC_GDBSTUB is not set +CONFIG_ESP32_PANIC_SILENT_REBOOT= +CONFIG_ESP32_PANIC_GDBSTUB= CONFIG_ESP32_DEBUG_OCDAWARE=y CONFIG_INT_WDT=y CONFIG_INT_WDT_TIMEOUT_MS=300 CONFIG_TASK_WDT=y -# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_PANIC= CONFIG_TASK_WDT_TIMEOUT_S=5 -CONFIG_TASK_WDT_CHECK_IDLE_TASK=y -# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_0=y +CONFIG_BROWNOUT_DET_LVL_SEL_1= +CONFIG_BROWNOUT_DET_LVL_SEL_2= +CONFIG_BROWNOUT_DET_LVL_SEL_3= +CONFIG_BROWNOUT_DET_LVL_SEL_4= +CONFIG_BROWNOUT_DET_LVL_SEL_5= +CONFIG_BROWNOUT_DET_LVL_SEL_6= +CONFIG_BROWNOUT_DET_LVL_SEL_7= +CONFIG_BROWNOUT_DET_LVL=0 +CONFIG_ESP32_TIME_SYSCALL_USE_RTC= CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y -# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set -# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_TIME_SYSCALL_USE_FRC1= +CONFIG_ESP32_TIME_SYSCALL_USE_NONE= CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y +CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL= +CONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32_XTAL_FREQ_40=y +CONFIG_ESP32_XTAL_FREQ_26= +CONFIG_ESP32_XTAL_FREQ_AUTO= +CONFIG_ESP32_XTAL_FREQ=40 +CONFIG_DISABLE_BASIC_ROM_CONSOLE= +CONFIG_NO_BLOBS= + +# +# Wi-Fi +# +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER= +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_AMPDU_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y + +# +# PHY +# +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION= +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 + +# +# Power Management +# +CONFIG_PM_ENABLE= + +# +# Ethernet +# +CONFIG_DMA_RX_BUF_NUM=10 +CONFIG_DMA_TX_BUF_NUM=10 +CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE= +CONFIG_EMAC_TASK_PRIORITY=20 + +# +# FAT Filesystem support +# +CONFIG_FATFS_CODEPAGE_ASCII=y +CONFIG_FATFS_CODEPAGE_437= +CONFIG_FATFS_CODEPAGE_720= +CONFIG_FATFS_CODEPAGE_737= +CONFIG_FATFS_CODEPAGE_771= +CONFIG_FATFS_CODEPAGE_775= +CONFIG_FATFS_CODEPAGE_850= +CONFIG_FATFS_CODEPAGE_852= +CONFIG_FATFS_CODEPAGE_855= +CONFIG_FATFS_CODEPAGE_857= +CONFIG_FATFS_CODEPAGE_860= +CONFIG_FATFS_CODEPAGE_861= +CONFIG_FATFS_CODEPAGE_862= +CONFIG_FATFS_CODEPAGE_863= +CONFIG_FATFS_CODEPAGE_864= +CONFIG_FATFS_CODEPAGE_865= +CONFIG_FATFS_CODEPAGE_866= +CONFIG_FATFS_CODEPAGE_869= +CONFIG_FATFS_CODEPAGE_932= +CONFIG_FATFS_CODEPAGE_936= +CONFIG_FATFS_CODEPAGE_949= +CONFIG_FATFS_CODEPAGE_950= +CONFIG_FATFS_CODEPAGE=1 +CONFIG_FATFS_MAX_LFN=255 +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y # # FreeRTOS # CONFIG_FREERTOS_UNICORE=y CONFIG_FREERTOS_CORETIMER_0=y -# CONFIG_FREERTOS_CORETIMER_1 is not set -# CONFIG_FREERTOS_CORETIMER_2 is not set +CONFIG_FREERTOS_CORETIMER_1= CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE= CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL=y -# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY= +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK= +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 CONFIG_FREERTOS_ASSERT_FAIL_ABORT=y -# CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE is not set -# CONFIG_FREERTOS_ASSERT_DISABLE is not set -CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG=y -# CONFIG_ENABLE_MEMORY_DEBUG is not set +CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE= +CONFIG_FREERTOS_ASSERT_DISABLE= +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1024 CONFIG_FREERTOS_ISR_STACKSIZE=1536 -# CONFIG_FREERTOS_DEBUG_INTERNALS is not set +CONFIG_FREERTOS_LEGACY_HOOKS= +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +CONFIG_SUPPORT_STATIC_ALLOCATION= +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_USE_TRACE_FACILITY= +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS= +CONFIG_FREERTOS_DEBUG_INTERNALS= + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +CONFIG_HEAP_POISONING_LIGHT= +CONFIG_HEAP_POISONING_COMPREHENSIVE= +CONFIG_HEAP_TRACING= + +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y # # Log output # -# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set -# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set -# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set -# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +CONFIG_LOG_DEFAULT_LEVEL_NONE= +CONFIG_LOG_DEFAULT_LEVEL_ERROR= +CONFIG_LOG_DEFAULT_LEVEL_WARN= +CONFIG_LOG_DEFAULT_LEVEL_INFO= +CONFIG_LOG_DEFAULT_LEVEL_DEBUG= CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y CONFIG_LOG_DEFAULT_LEVEL=5 -# CONFIG_LOG_COLORS is not set +CONFIG_LOG_COLORS= # # LWIP # +CONFIG_L2_TO_L3_COPY= CONFIG_LWIP_MAX_SOCKETS=4 -CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX=0 -# CONFIG_LWIP_SO_REUSE is not set +CONFIG_LWIP_SO_REUSE= +CONFIG_LWIP_SO_RCVBUF= CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1 +CONFIG_LWIP_IP_FRAG= +CONFIG_LWIP_IP_REASSEMBLY= +CONFIG_LWIP_STATS= +CONFIG_LWIP_ETHARP_TRUST_IP_MAC=y +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +CONFIG_LWIP_AUTOIP= +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=6 +CONFIG_TCP_MSS=1436 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5744 +CONFIG_TCP_WND_DEFAULT=5744 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +CONFIG_TCP_OVERSIZE_QUARTER_MSS= +CONFIG_TCP_OVERSIZE_DISABLE= + +# +# UDP +# +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=2560 +CONFIG_PPP_SUPPORT= + +# +# ICMP +# +CONFIG_LWIP_MULTICAST_PING= +CONFIG_LWIP_BROADCAST_PING= # # mbedTLS # CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=16384 -# CONFIG_MBEDTLS_DEBUG is not set +CONFIG_MBEDTLS_DEBUG= +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_HARDWARE_MPI= +CONFIG_MBEDTLS_HARDWARE_SHA= +CONFIG_MBEDTLS_HAVE_TIME=y +CONFIG_MBEDTLS_HAVE_TIME_DATE= +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +CONFIG_MBEDTLS_TLS_SERVER_ONLY= +CONFIG_MBEDTLS_TLS_CLIENT_ONLY= +CONFIG_MBEDTLS_TLS_DISABLED= +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +CONFIG_MBEDTLS_PSK_MODES= +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_SSL3= +CONFIG_MBEDTLS_SSL_PROTO_TLS1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS= +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +CONFIG_MBEDTLS_CAMELLIA_C= +CONFIG_MBEDTLS_DES_C= +CONFIG_MBEDTLS_RC4_DISABLED=y +CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT= +CONFIG_MBEDTLS_RC4_ENABLED= +CONFIG_MBEDTLS_BLOWFISH_C= +CONFIG_MBEDTLS_XTEA_C= +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +CONFIG_MBEDTLS_RIPEMD160_C= + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y + +# +# OpenSSL +# +CONFIG_OPENSSL_DEBUG= +CONFIG_OPENSSL_ASSERT_DO_NOTHING=y +CONFIG_OPENSSL_ASSERT_EXIT= + +# +# PThreads +# +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=2048 # # SPI Flash driver # -# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ENABLE_COUNTERS= +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS= +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED= + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +CONFIG_SPIFFS_CACHE_STATS= +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +CONFIG_SPIFFS_GC_STATS= +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y + +# +# Debug Configuration +# +CONFIG_SPIFFS_DBG= +CONFIG_SPIFFS_API_DBG= +CONFIG_SPIFFS_GC_DBG= +CONFIG_SPIFFS_CACHE_DBG= +CONFIG_SPIFFS_CHECK_DBG= +CONFIG_SPIFFS_TEST_VISUALISATION= + +# +# tcpip adapter +# +CONFIG_IP_LOST_TIMER_INTERVAL=120 + +# +# Wear Levelling +# +CONFIG_WL_SECTOR_SIZE_512= +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 diff --git a/networking/FTPServer/FTPCallbacks.cpp b/networking/FTPServer/FTPCallbacks.cpp new file mode 100644 index 00000000..99a6e111 --- /dev/null +++ b/networking/FTPServer/FTPCallbacks.cpp @@ -0,0 +1,154 @@ +#include "FTPServer.h" +#include +#include +#include + + +/** + * Called at the start of a STOR request. The file name is the name of the file the client would like to + * save. + */ +void FTPFileCallbacks::onStoreStart(std::string fileName) { + printf(">> FTPFileCallbacks::onStoreStart: fileName=%s\n", fileName.c_str()); + m_storeFile.open(fileName, std::ios::binary); // Open the file for writing. + if (m_storeFile.fail()) { + throw FTPServer::FileException(); + } + printf("<< FTPFileCallbacks::onStoreStart\n"); +} // FTPFileCallbacks#onStoreStart + + +/** + * Called when the client presents a new chunk of data to be saved. + */ +size_t FTPFileCallbacks::onStoreData(uint8_t* data, size_t size) { + printf(">> FTPFileCallbacks::onStoreData: size=%ld\n", size); + m_storeFile.write((char *)data, size); // Store data received. + printf("<< FTPFileCallbacks::onStoreData: size=%ld\n", size); + return size; +} // FTPFileCallbacks#onStoreData + + +/** + * Called at the end of a STOR request. This indicates that the client has completed its transmission of the + * file. + */ +void FTPFileCallbacks::onStoreEnd() { + printf(">> FTPFileCallbacks::onStoreEnd\n"); + m_storeFile.close(); // Close the open file. + printf("<< FTPFileCallbacks::onStoreEnd\n"); +} // FTPFileCallbacks#onStoreEnd + + +/** + * Called when the client requests retrieval of a file. + */ +void FTPFileCallbacks::onRetrieveStart(std::string fileName) { + printf(">> FTPFileCallbacks::onRetrieveStart: fileName=%s\n", fileName.c_str()); + m_byteCount = 0; + m_retrieveFile.open(fileName, std::ios::binary); + if (m_retrieveFile.fail()) { + printf("<< FTPFileCallbacks::onRetrieveStart: ***FileException***\n"); + throw FTPServer::FileException(); + } + printf("<< FTPFileCallbacks::onRetrieveStart\n"); +} // FTPFileCallbacks#onRetrieveStart + + +/** + * Called when the client is ready to receive the next piece of the file. To indicate that there + * is no more data to send, return a size of 0. + * @param data The data buffer that we can fill to return data back to the client. + * @param size The maximum size of the data buffer that we can populate. + * @return The size of data being returned. Return 0 to indicate that there is no more data to return. + */ +size_t FTPFileCallbacks::onRetrieveData(uint8_t* data, size_t size) { + printf(">> FTPFileCallbacks::onRetrieveData\n"); + m_retrieveFile.read((char *)data, size); + size_t readSize = m_retrieveFile.gcount(); + m_byteCount += readSize; + printf("<< FTPFileCallbacks::onRetrieveData: sizeRead=%ld\n", readSize); + return m_retrieveFile.gcount(); // Return the number of bytes read. +} // FTPFileCallbacks#onRetrieveData + + +/** + * Called when the retrieval has been completed. + */ +void FTPFileCallbacks::onRetrieveEnd() { + printf(">> FTPFileCallbacks::onRetrieveEnd\n"); + m_retrieveFile.close(); + printf("<< FTPFileCallbacks::onRetrieveEnd: bytesTransmitted=%d\n", m_byteCount); +} // FTPFileCallbacks#onRetrieveEnd + + +/** + * Return a list of files in the file system. + * @return a list of files in the file system. + */ +std::string FTPFileCallbacks::onDir() { + + DIR* dir = opendir(FTPServer::getCurrentDirectory().c_str()); + std::stringstream ss; + while(1) { + struct dirent* pDirentry = readdir(dir); + if (pDirentry == nullptr) { + break; + } + ss << pDirentry->d_name << "\r\n"; + } + closedir(dir); + return ss.str(); +} // FTPFileCallbacks#onDir + + +/// ---- END OF FTPFileCallbacks + + +void FTPCallbacks::onStoreStart(std::string fileName) { + printf(">> FTPCallbacks::onStoreStart: fileName=%s\n", fileName.c_str()); + printf("<< FTPCallbacks::onStoreStart\n"); +} // FTPCallbacks#onStoreStart + + +size_t FTPCallbacks::onStoreData(uint8_t* data, size_t size) { + printf(">> FTPCallbacks::onStoreData: size=%ld\n", size); + printf("<< FTPCallbacks::onStoreData\n"); + return 0; +} // FTPCallbacks#onStoreData + + +void FTPCallbacks::onStoreEnd() { + printf(">> FTPCallbacks::onStoreEnd\n"); + printf("<< FTPCallbacks::onStoreEnd\n"); +} // FTPCallbacks#onStoreEnd + + +void FTPCallbacks::onRetrieveStart(std::string fileName) { + printf(">> FTPCallbacks::onRetrieveStart\n"); + printf("<< FTPCallbacks::onRetrieveStart\n"); +} // FTPCallbacks#onRetrieveStart + + +size_t FTPCallbacks::onRetrieveData(uint8_t *data, size_t size) { + printf(">> FTPCallbacks::onRetrieveData\n"); + printf("<< FTPCallbacks::onRetrieveData: 0\n"); + return 0; +} // FTPCallbacks#onRetrieveData + + +void FTPCallbacks::onRetrieveEnd() { +printf(">> FTPCallbacks::onRetrieveEnd\n"); +printf("<< FTPCallbacks::onRetrieveEnd\n"); +} // FTPCallbacks#onRetrieveEnd + + +std::string FTPCallbacks::onDir() { + printf(">> FTPCallbacks::onDir\n"); + printf("<< FTPCallbacks::onDir\n"); + return ""; +} // FTPCallbacks#onDir + +FTPCallbacks::~FTPCallbacks() { + +} // FTPCallbacks#~FTPCallbacks diff --git a/networking/FTPServer/FTPServer.cpp b/networking/FTPServer/FTPServer.cpp new file mode 100644 index 00000000..87899c3b --- /dev/null +++ b/networking/FTPServer/FTPServer.cpp @@ -0,0 +1,819 @@ +/* + * FTPServer.cpp + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#include "FTPServer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// trim from start (in place) +static void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { + return !std::isspace(ch); + })); +} // ltrim + + +// trim from end (in place) +static void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} // rtrim + + +// trim from both ends (in place) +static void trim(std::string &s) { + ltrim(s); + rtrim(s); +} // trim + + +FTPServer::FTPServer() { + printf(">> FTPServer()\n"); + + m_dataSocket = -1; + m_clientSocket = -1; + m_dataPort = -1; + m_dataIp = -1; + m_passiveSocket = -1; + m_serverSocket = -1; + + m_callbacks = nullptr; + m_isPassive = false; + m_isImage = true; + m_chunkSize = 4096; + m_port = 21; // The default Server-PI port + m_loginRequired = false; + m_isAuthenticated = false; + m_userid = ""; + m_password = ""; + + printf("<< FTPServer()\n"); +} // FTPServer#FTPServer + + +FTPServer::~FTPServer() { + // Nothing to do here by default. +} // FTPServer#~FTPServer + + +/** + * Close the connection to the FTP client. + */ +void FTPServer::closeConnection() { + printf(">> closeConnection\n"); + close(m_clientSocket); + printf("<< closeConnection\n"); +} // FTPServer#closeConnection + + +/** + * Close a previously opened data connection. + */ +void FTPServer::closeData() { + printf(">> closeData\n"); + close(m_dataSocket); + m_dataSocket = -1; + printf("<< closeData\n"); +} // FTPServer#closeData + + +/** + * Close the passive listening socket that was opened by listenPassive. + */ +void FTPServer::closePassive() { + printf(">> closePassive\n"); + close(m_passiveSocket); + m_passiveSocket = -1; + printf("<< closePassive\n"); +} // FTPServer#closePassive + + +/** + * Retrieve the current directory. + */ +/* STATIC */ std::string FTPServer::getCurrentDirectory() { + char maxDirectory[256]; + std::string currentDirectory = getcwd(maxDirectory, sizeof(maxDirectory)); + return currentDirectory; +} // FTPServer#getCurrentDirectory + + +/** + * Create a listening socket for the new passive connection. + * @return a String for the passive parameters. + */ +std::string FTPServer::listenPassive() { + printf(">> listenPassive\n"); + + m_passiveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_passiveSocket == -1) { + printf("socket: %s", strerror(errno)); + } + + struct sockaddr_in clientAddrInfo; + unsigned int addrInfoSize = sizeof(clientAddrInfo); + getsockname(m_clientSocket, (struct sockaddr*)&clientAddrInfo, &addrInfoSize); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(0); + int rc = bind(m_passiveSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + printf("bind: %s\n", strerror(errno)); + } + + rc = listen(m_passiveSocket, 5); + if (rc == -1) { + printf("listen: %s", strerror(errno)); + } + + unsigned int addrLen = sizeof(serverAddress); + rc = getsockname(m_passiveSocket, (struct sockaddr*)&serverAddress, &addrLen); + if (rc == -1) { + printf("getsockname: %s\n", strerror(errno)); + } + + + std::stringstream ss; + ss << ((clientAddrInfo.sin_addr.s_addr >> 0) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 8) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 16) & 0xff) << + "," << ((clientAddrInfo.sin_addr.s_addr >> 24) & 0xff) << + "," << ((serverAddress.sin_port >> 0) & 0xff) << + "," << ((serverAddress.sin_port >> 8) & 0xff); + std::string retStr = ss.str(); + + printf("<< listenPassive: %s\n", retStr.c_str()); + return retStr; +} // FTPServer#listenPassive + + +/** + * Handle the AUTH command. + */ +void FTPServer::onAuth(std::istringstream& ss) { + std::string param; + ss >> param; + printf(">> onAuth: %s\n", param.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + printf("<< onAuth\n"); +} // FTPServer#onAuth + + +/** + * Change the current working directory. + * @param ss A string stream where the first parameter is the directory to change to. + */ +void FTPServer::onCwd(std::istringstream& ss) { + std::string path; + ss >> path; + printf(">> onCwd: path=%s\n", path.c_str()); + chdir(path.c_str()); + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); + printf("<< onCwd\n"); +} // FTPServer#onCwd + + +/** + * Process the client transmitted LIST request. + */ +void FTPServer::onList(std::istringstream& ss) { + std::string directory; + ss >> directory; + printf(">> onList: directory=%s\n", directory.c_str()); + + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + if (m_callbacks != nullptr) { + std::string dirString = m_callbacks->onDir(); + sendData((uint8_t *)dirString.data(), dirString.length()); + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + printf("<< onList\n"); +} // FTPServer#onList + + +void FTPServer::onMkd(std::istringstream &ss) { + std::string path; + ss >> path; + printf(">> onMkd: path=%s\n", path.c_str()); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onMkd\n"); +} // FTPServer#onMkd + + +/** + * Process a NOOP operation. + */ +void FTPServer::onNoop(std::istringstream& ss) { + printf(">> onNoop\n"); + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + printf("<< onNoop\n"); +} // FTPServer#onNoop + + +/** + * Process PORT request. The information provided is encoded in the parameter as + * h1,h2,h3,h4,p1,p2 where h1,h2,h3,h4 is the IP address we should connect to + * and p1,p2 is the port number. The data is MSB + * + * Our logic does not form any connection but remembers the ip address and port number + * to be used for a subsequence data connection. + * + * Possible responses: + * 200 + * 500, 501, 421, 530 + */ +void FTPServer::onPort(std::istringstream& ss) { + printf(">> onPort\n"); + char c; + uint16_t h1, h2, h3, h4, p1, p2; + ss >> h1 >> c >> h2 >> c >> h3 >> c >> h4 >> c >> p1 >> c >> p2; + m_dataPort = p1*256 + p2; + printf("%d.%d.%d.%d %d\n", h1, h2, h3, h4, m_dataPort); + m_dataIp = h1<<24 | h2<<16 | h3<<8 | h4; + sendResponse(RESPONSE_200_COMMAND_OK); // Command okay. + m_isPassive = false; + + printf("<< onPort\n"); +} // FTPServer#onPort + + +/** + * Process the PASS command. + * Possible responses: + * 230 + * 202 + * 530 + * 500, 501, 503, 421 + * 332 + */ +void FTPServer::onPass(std::istringstream& ss) { + std::string password; + ss >> password; + printf(">> onPass: password=%s\n", password.c_str()); + + // If the immediate last command wasn't USER then don't try and process PASS. + if (m_lastCommand != "USER") { + sendResponse(RESPONSE_503_BAD_SEQUENCE); + printf("<< onPass\n"); + return; + } + + // Compare the supplied userid and passwords. + if (m_userid == m_suppliedUserid && password == m_password) { + sendResponse(RESPONSE_230_USER_LOGGED_IN); + m_isAuthenticated = true; + } else { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + closeConnection(); + m_isAuthenticated = false; + } + + + printf("<< onPass\n"); +} // FTPServer#onPass + + +/** + * Process the PASV command. + * Possible responses: + * 227 + * 500, 501, 502, 421, 530 + */ +void FTPServer::onPasv(std::istringstream& ss) { + printf(">> onPasv\n"); + std::string ipInfo = listenPassive(); + std::ostringstream responseTextSS; + responseTextSS << "Entering Passive Mode (" << ipInfo << ")."; + std::string responseText; + responseText = responseTextSS.str(); + sendResponse(RESPONSE_227_ENTERING_PASSIVE_MODE, responseText.c_str()); + m_isPassive = true; + + printf("<< onPasv\n"); +} // FTPServer#onPasv + + +/** + * Process the PWD command to determine our current working directory. + * Possible responses: + * 257 + * 500, 501, 502, 421, 550 + */ +void FTPServer::onPWD(std::istringstream& ss) { + printf(">> onPWD\n"); + sendResponse(257, "\"" + getCurrentDirectory() + "\""); + printf("<< onPWD: %s\n", getCurrentDirectory().c_str()); +} // FTPServer#onPWD + + +/** + * Possible responses: + * 221 + * 500 + */ +void FTPServer::onQuit(std::istringstream& ss) { + printf(">> onQuit\n"); + sendResponse(FTPServer::RESPONSE_221_CLOSING_CONTROL_CONNECTION); // Service closing control connection. + closeConnection(); // Close the connection to the client. + printf("<< onQuit\n"); +} // FTPServer#onQuit + + +/** + * Process a RETR command. The client sends this command to retrieve the content of a file. + * The name of the file is the first parameter in the input stream. + * + * Possible responses: + * 125, 150 + * (110) + * 226, 250 + * 425, 426, 451 + * 450, 550 + * 500, 501, 421, 530 + * @param ss The parameter stream. + */ +void FTPServer::onRetr(std::istringstream& ss) { + + // We open a data connection back to the client. We then invoke the callback to indicate that we have + // started a retrieve operation. We call the retrieve callback to request the next chunk of data and + // transmit this down the data connection. We repeat this until there is no more data to send at which + // point we close the data connection and we are done. + printf(">> onRetr\n"); + std::string fileName; + + ss >> fileName; + uint8_t data[m_chunkSize]; + + + if (m_callbacks != nullptr) { + try { + m_callbacks->onRetrieveStart(fileName); + } catch(FTPServer::FileException& e) { + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + printf("<< onRetr: Returned 550 to client.\n"); + return; + } + } + + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + openData(); + if (m_callbacks != nullptr) { + int readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + while(readSize > 0) { + sendData(data, readSize); + readSize = m_callbacks->onRetrieveData(data, m_chunkSize); + } + } + closeData(); + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + printf("<< onRetr\n"); +} // FTPServer#onRetr + + +void FTPServer::onRmd(std::istringstream &ss) { + printf(">> onRmd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onRmd\n"); +} // FTPServer#onRmd + + +/** + * Called to process a STOR request. This means that the client wishes to store a file + * on the server. The name of the file is found in the parameter. + */ +void FTPServer::onStor(std::istringstream& ss) { + printf(">> onStor\n"); + std::string fileName; + ss >> fileName; + + receiveFile(fileName); + printf("<< onStor\n"); +} // FTPServer#onStor + + +void FTPServer::onSyst(std::istringstream& ss) { + printf(">> onSyst\n"); + sendResponse(215, "UNIX Type: L8"); + printf("<< onSyst\n"); +} // FTPServer#onSyst + + +/** + * Process a TYPE request. The parameter that follows is the type of transfer we wish + * to process. Types include: + * I and A. + * + * Possible responses: + * 200 + * 500, 501, 504, 421, 530 + */ +void FTPServer::onType(std::istringstream& ss) { + printf(">> onType\n"); + std::string type; + ss >> type; + if (type.compare("I") == 0) { + m_isImage = true; + } else { + m_isImage = false; + } + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + printf("<< onType: isImage=%d\n", m_isImage); +} // FTPServer#onType + + +/** + * Process a USER request. The parameter that follows is the identity of the user. + * + * Possible responses: + * 230 + * 530 + * 500, 501, 421 + * 331, 332 + * + */ +void FTPServer::onUser(std::istringstream& ss) { + // When we receive a user command, we next want to know if we should ask for a password. If the m_loginRequired + // flag is set then we do indeed want a password and will send the response that we wish one. + + std::string userName; + ss >> userName; + printf(">> onUser: userName=%s\n", userName.c_str()); + if (m_loginRequired) { + sendResponse(FTPServer::RESPONSE_331_PASSWORD_REQUIRED); + } else { + sendResponse(FTPServer::RESPONSE_200_COMMAND_OK); // Command okay. + } + m_suppliedUserid = userName; // Save the username that was supplied. + printf("<< onUser\n"); +} // FTPServer#onUser + + +void FTPServer::onXmkd(std::istringstream &ss) { + printf(">> onXmkd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onXmkd\n"); +} // FTPServer#onXmkd + + +void FTPServer::onXrmd(std::istringstream &ss) { + printf(">> onXrmd\n"); + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); + printf("<< onXrmd\n"); +} // FTPServer#onXrmd + + +/** + * Open a data connection with the client. + * We will use closeData() to close the connection. + * @return True if the data connection succeeded. + */ +bool FTPServer::openData() { + if (m_isPassive) { + // Handle a passive connection ... here we receive a connection from the client from the passive socket. + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_dataSocket = accept(m_passiveSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + if (m_dataSocket == -1) { + printf("FTPServer::openData: accept(): %s\n", strerror(errno)); + closePassive(); + return false; + } + closePassive(); + } else { + // Handle an active connection ... here we connect to the client. + m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(m_dataIp); + serverAddress.sin_port = htons(m_dataPort); + + int rc = connect(m_dataSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)); + if (rc == -1) { + printf("FTPServer::openData: connect(): %s\n", strerror(errno)); + return false; + } + } + return true; +} // FTPServer#openData + + +/** + * Process commands received from the client. + */ +void FTPServer::processCommand() { + sendResponse(FTPServer::RESPONSE_220_SERVICE_READY); // Service ready. + printf(">> FTPServer::processCommand\n"); + m_lastCommand = ""; + while(1) { + std::string line = ""; + char currentChar; + char lastChar = '\0'; + int rc = recv(m_clientSocket, ¤tChar, 1, 0); + while(rc != -1 && rc!=0) { + line += currentChar; + if (lastChar == '\r' && currentChar == '\n') { + break; + } + printf("%c\n", currentChar); + lastChar = currentChar; + rc = recv(m_clientSocket, ¤tChar, 1, 0); + } // End while we are waiting for a line. + + if (rc == 0 || rc == -1) { // If we didn't get a line or an error, then we have finished processing commands. + break; + } + + std::string command; + std::istringstream ss(line); + getline(ss, command, ' '); + trim(command); + + // We now have a command to process. + + printf("Command: \"%s\"\n", command.c_str()); + if (command.compare("USER")==0) { + onUser(ss); + } + else if (command.compare("PASS")==0) { + onPass(ss); + } + else if (m_loginRequired && !m_isAuthenticated) { + sendResponse(RESPONSE_530_NOT_LOGGED_IN); + } + else if (command.compare("PASV")==0) { + onPasv(ss); + } + else if (command.compare("SYST")==0) { + onSyst(ss); + } + else if (command.compare("PORT")==0) { + onPort(ss); + } + else if (command.compare("LIST")==0) { + onList(ss); + } + else if (command.compare("TYPE")==0) { + onType(ss); + } + else if (command.compare("RETR")==0) { + onRetr(ss); + } + else if (command.compare("QUIT")==0) { + onQuit(ss); + } + else if (command.compare("AUTH")==0) { + onAuth(ss); + } + else if (command.compare("STOR")==0) { + onStor(ss); + } + else if (command.compare("PWD")==0) { + onPWD(ss); + } + else if (command.compare("MKD")==0) { + onMkd(ss); + } + else if (command.compare("XMKD")==0) { + onXmkd(ss); + } + else if (command.compare("RMD")==0) { + onRmd(ss); + } + else if (command.compare("XRMD")==0) { + onXrmd(ss); + } + else if (command.compare("CWD")==0) { + onCwd(ss); + } + else { + sendResponse(FTPServer::RESPONSE_500_COMMAND_UNRECOGNIZED); // Syntax error, command unrecognized. + } + m_lastCommand = command; + } // End loop processing commands. + + close(m_clientSocket); // We won't be processing any further commands from this client. + printf("<< FTPServer::processCommand\n"); +} // FTPServer::processCommand + + +/** + * Receive a file from the FTP client (STOR). The name of the file to be created is passed as a + * parameter. + */ +void FTPServer::receiveFile(std::string fileName) { + printf(">> receiveFile: %s\n", fileName.c_str()); + if (m_callbacks != nullptr) { + try { + m_callbacks->onStoreStart(fileName); + } catch(FTPServer::FileException& e) { + printf("Caught a file exception!\n"); + sendResponse(FTPServer::RESPONSE_550_ACTION_NOT_TAKEN); // Requested action not taken. + return; + } + } + openData(); + sendResponse(FTPServer::RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION); // File status okay; about to open data connection. + uint8_t buf[m_chunkSize]; + uint32_t totalSizeRead = 0; + while(1) { + int rc = recv(m_dataSocket, &buf, m_chunkSize, 0); + if (rc <= 0) { + break; + } + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveData(buf, rc); + } + totalSizeRead += rc; + } + sendResponse(FTPServer::RESPONSE_226_CLOSING_DATA_CONNECTION); // Closing data connection. + closeData(); + if (m_callbacks != nullptr) { + m_callbacks->onRetrieveEnd(); + } + printf("<< receiveFile: totalSizeRead=%d\n", totalSizeRead); +} // FTPServer#receiveFile + + +/** + * Send data to the client over the data connection previously opened with a call to openData(). + * @param pData A pointer to the data to send. + * @param size The number of bytes to send. + */ +void FTPServer::sendData(uint8_t* pData, uint32_t size) { + printf(">> FTPServer::sendData: size=%d\n", size); + int rc = send(m_dataSocket, pData, size, 0); + if (rc == -1) { + printf("FTPServer::sendData: send(): %s\n", strerror(errno)); + } + printf("<< FTPServer::sendData\n"); +} // FTPServer#sendData + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. + */ +void FTPServer::sendResponse(int code, std::string text) { + printf(">> sendResponse: (%d) %s\n", code, text.c_str()); + std::ostringstream ss; + ss << code << " " << text << "\r\n"; + int rc = send(m_clientSocket, ss.str().data(), ss.str().length(), 0); + printf("<< sendResponse\n"); +} // FTPServer#sendResponse + + +/** + * Send a response to the client. A response is composed of two parts. The first is a code as architected in the + * FTP specification. The second is a piece of text. In this function, a standard piece of text is used based on + * the code. + */ +void FTPServer::sendResponse(int code) { + std::string text = "unknown"; + + switch(code) { // Map the code to a text string. + case RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION: + text = "File status okay; about to open data connection."; + break; + case RESPONSE_200_COMMAND_OK: + text = "Command okay."; + break; + case RESPONSE_220_SERVICE_READY: + text = "Service ready."; + break; + case RESPONSE_221_CLOSING_CONTROL_CONNECTION: + text = "Service closing control connection."; + break; + case RESPONSE_226_CLOSING_DATA_CONNECTION: + text = "Closing data connection."; + break; + case RESPONSE_230_USER_LOGGED_IN: + text = "User logged in, proceed."; + break; + case RESPONSE_331_PASSWORD_REQUIRED: + text = "Password required."; + break; + case RESPONSE_500_COMMAND_UNRECOGNIZED: + text = "Syntax error, command unrecognized."; + break; + case RESPONSE_502_COMMAND_NOT_IMPLEMENTED: + text = "Command not implemented."; + break; + case RESPONSE_503_BAD_SEQUENCE: + text = "Bad sequence of commands."; + break; + case RESPONSE_530_NOT_LOGGED_IN: + text = "Not logged in."; + break; + case RESPONSE_550_ACTION_NOT_TAKEN: + text = "Requested action not taken."; + break; + default: + break; + } + sendResponse(code, text); // Send the code AND the text to the FTP client. +} // FTPServer#sendResponse + + +/** + * Set the callbacks that are to be invoked to perform work. + * @param pCallbacks An instance of an FTPCallbacks based class. + */ +void FTPServer::setCallbacks(FTPCallbacks* pCallbacks) { + m_callbacks = pCallbacks; +} // FTPServer#setCallbacks + + +void FTPServer::setCredentials(std::string userid, std::string password) { + printf(">> setCredentials: userid=%s\n", userid.c_str()); + m_loginRequired = true; + m_userid = userid; + m_password = password; + printf("<< setCredentials\n"); +} // FTPServer#setCredentials + + +/** + * Set the TCP port we should listen on for FTP client requests. + */ +void FTPServer::setPort(uint16_t port) { + m_port = port; +} // FTPServer#setPort + + +/** + * Start being an FTP Server. + */ +void FTPServer::start() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (m_serverSocket == -1) { + printf("socket: %s", strerror(errno)); + } + + int enable = 1; + setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(m_port); + int rc = bind(m_serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)); + if (rc == -1) { + printf("bind: %s\n", strerror(errno)); + } + rc = listen(m_serverSocket, 5); + if (rc == -1) { + printf("listen: %s", strerror(errno)); + } + while(1) { + waitForFTPClient(); + processCommand(); + } +} // FTPServer#start + + +/** + * Wait for a new client to connect. + */ +int FTPServer::waitForFTPClient() { + printf(">> FTPServer::waitForFTPClient\n"); + + struct sockaddr_in clientAddress; + socklen_t clientAddressLength = sizeof(clientAddress); + m_clientSocket = accept(m_serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength); + + char ipAddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &clientAddress.sin_addr, ipAddr, sizeof(ipAddr)); + printf("Received connection from %s [%d]\n", ipAddr, clientAddress.sin_port); + + struct sockaddr_in socketAddressInfo; + unsigned int socketAddressInfoSize = sizeof(socketAddressInfo); + getsockname(m_clientSocket, (struct sockaddr*)&socketAddressInfo, &socketAddressInfoSize); + + inet_ntop(AF_INET, &socketAddressInfo.sin_addr, ipAddr, sizeof(ipAddr)); + printf("Connected at %s [%d]\n", ipAddr, socketAddressInfo.sin_port); + printf("<< FTPServer::waitForFTPClient: fd=%d\n", m_clientSocket); + + return m_clientSocket; +} // FTPServer::waitForFTPClient + diff --git a/networking/FTPServer/FTPServer.h b/networking/FTPServer/FTPServer.h new file mode 100644 index 00000000..7dc56f13 --- /dev/null +++ b/networking/FTPServer/FTPServer.h @@ -0,0 +1,137 @@ +/* + * FTPServer.h + * + * Created on: May 6, 2018 + * Author: kolban + */ + +#ifndef NETWORKING_FTPSERVER_FTPSERVER_H_ +#define NETWORKING_FTPSERVER_FTPSERVER_H_ +#include +#include +#include +#include +#include + + +class FTPCallbacks { +public: + virtual void onStoreStart(std::string fileName); + virtual size_t onStoreData(uint8_t* data, size_t size); + virtual void onStoreEnd(); + virtual void onRetrieveStart(std::string fileName); + virtual size_t onRetrieveData(uint8_t *data, size_t size); + virtual void onRetrieveEnd(); + virtual std::string onDir(); + virtual ~FTPCallbacks(); +}; + +/** + * An implementation of FTPCallbacks that uses Posix File I/O to perform file access. + */ +class FTPFileCallbacks : public FTPCallbacks { +private: + std::ofstream m_storeFile; // File used to store data from the client. + std::ifstream m_retrieveFile; // File used to retrieve data for the client. + uint32_t m_byteCount; // Count of bytes sent over wire. +public: + void onStoreStart(std::string fileName) override; // Called for a STOR request. + size_t onStoreData(uint8_t* data, size_t size) override; // Called when a chunk of STOR data becomes available. + void onStoreEnd() override; // Called at the end of a STOR request. + void onRetrieveStart(std::string fileName) override; // Called at the start of a RETR request. + size_t onRetrieveData(uint8_t* data, size_t size) override; // Called to retrieve a chunk of RETR data. + void onRetrieveEnd() override; // Called when we have retrieved all the data. + std::string onDir() override; // Called to retrieve all the directory entries. +}; + + +class FTPServer { +private: + int m_serverSocket; // The socket the FTP server is listening on. + int m_clientSocket; // The current client socket. + int m_dataSocket; // The data socket. + int m_passiveSocket; // The socket on which the server is listening for passive FTP connections. + uint16_t m_port; // The port the FTP server will use. + uint16_t m_dataPort; // The port for data connections. + uint32_t m_dataIp; // The ip address for data connections. + bool m_isPassive; // Are we in passive mode? If not, then we are in active mode. + bool m_isImage; // Are we in image mode? + size_t m_chunkSize; // The maximum chunk size. + std::string m_userid; // The required userid. + std::string m_password; // The required password. + std::string m_suppliedUserid; // The userid supplied from the USER command. + bool m_loginRequired; // Do we required a login? + bool m_isAuthenticated; // Have we authenticated? + std::string m_lastCommand; // The last command that was processed. + + FTPCallbacks* m_callbacks; // The callbacks for processing. + + void closeConnection(); + void closeData(); + void closePassive(); + void onAuth(std::istringstream& ss); + void onCwd(std::istringstream& ss); + void onList(std::istringstream& ss); + void onMkd(std::istringstream& ss); + void onNoop(std::istringstream& ss); + void onPass(std::istringstream& ss); + void onPasv(std::istringstream& ss); + void onPort(std::istringstream& ss); + void onPWD(std::istringstream& ss); + void onQuit(std::istringstream& ss); + void onRetr(std::istringstream& ss); + void onRmd(std::istringstream& ss); + void onStor(std::istringstream& ss); + void onSyst(std::istringstream& ss); + void onType(std::istringstream& ss); + void onUser(std::istringstream& ss); + void onXmkd(std::istringstream& ss); + void onXrmd(std::istringstream& ss); + + bool openData(); + + void receiveFile(std::string fileName); + void sendResponse(int code); + void sendResponse(int code, std::string text); + void sendData(uint8_t* pData, uint32_t size); + std::string listenPassive(); + int waitForFTPClient(); + void processCommand(); + +public: + FTPServer(); + virtual ~FTPServer(); + void setCredentials(std::string userid, std::string password); + void start(); + void setPort(uint16_t port); + void setCallbacks(FTPCallbacks* pFTPCallbacks); + static std::string getCurrentDirectory(); + class FileException: public std::exception { + + }; + + // Response codes. + static const int RESPONSE_150_ABOUT_TO_OPEN_DATA_CONNECTION = 150; + static const int RESPONSE_200_COMMAND_OK = 200; + static const int RESPONSE_202_COMMAND_NOT_IMPLEMENTED = 202; + static const int RESPONSE_212_DIRECTORY_STATUS = 212; + static const int RESPONSE_213_FILE_STATUS = 213; + static const int RESPONSE_214_HELP_MESSAGE = 214; + static const int RESPONSE_220_SERVICE_READY = 220; + static const int RESPONSE_221_CLOSING_CONTROL_CONNECTION = 221; + static const int RESPONSE_230_USER_LOGGED_IN = 230; + static const int RESPONSE_226_CLOSING_DATA_CONNECTION = 226; + static const int RESPONSE_227_ENTERING_PASSIVE_MODE = 227; + static const int RESPONSE_331_PASSWORD_REQUIRED = 331; + static const int RESPONSE_332_NEED_ACCOUNT = 332; + static const int RESPONSE_500_COMMAND_UNRECOGNIZED = 500; + static const int RESPONSE_502_COMMAND_NOT_IMPLEMENTED = 502; + static const int RESPONSE_503_BAD_SEQUENCE = 503; + static const int RESPONSE_530_NOT_LOGGED_IN = 530; + static const int RESPONSE_550_ACTION_NOT_TAKEN = 550; + static const int RESPONSE_553_FILE_NAME_NOT_ALLOWED = 553; +}; + + + +#endif /* NETWORKING_FTPSERVER_FTPSERVER_H_ */ diff --git a/networking/FTPServer/README.md b/networking/FTPServer/README.md new file mode 100644 index 00000000..d5d570c3 --- /dev/null +++ b/networking/FTPServer/README.md @@ -0,0 +1,76 @@ +# ESP32 FTP Server +The world is awash in excellent FTP servers both as stand-alone applications as well as libraries that can be linked with to build ones own integrated FTP server. This project is yet another. + +The design intent is that it will be available to ESP32 based applications to server files from a Posix file system. + +## FTP Protocol +We will be concentrating exclsuively on the FTP Server protocol. This will allow an ESP32 to serve up files to FTP clients and allow FTP clients to deposit new files. + +The FTP Protocol is described in [RFC 959](https://tools.ietf.org/html/rfc959). + +Our server engine will implement the Server-PI (Server Protocol Interpreter) and the Server-DTP (Server Data Transfer Process). + +## Commands + +### PASV +The client has requested that the server enter passive mode. The response must be: + +``` +227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). +``` + +### PORT +Client requests that the server form a data connection to the given host and port. This is used in FTP *active* mode. +``` +PORT {h1,h2,h3,h4,p1,p2} +``` +port number is p1*256 + p2 + +### RETR +This command represents a request *from the client* to retrieve a file held by the server. + +### STOR +This command represents a request *from the client* to stor a file on the server. + +### SYST +Determine the System type of the FTP server. +``` +SYST +``` +The recommended response is: +``` +215 UNIX Type: L8 +``` + +### TYPE +Define the type of data to be transmitted. Codes include: + +* `A` - ASCII +* `E` - EBCDIC +* `I` - Image (binary) + +Example: +``` +TYPE I +``` + +### USER +Supply the user name that the client wishes to present to the FTP server. +``` +USER {name} +``` + +A response of `331` will request a password and should then be followed by the `PASS` command. + +## Active vs Passive +The FTP server can communicate data to the client in one of two modes called *active* and *passive*. In active mode, the FTP client is responsible for setting up a listening socket. When the FTP client is ready to receive data, it sends a `PORT` command informing the server of the IP address and port against which the FTP server should form the connection. +In passive mode, the FTP client sends a `PASV` command to the FTP server. The FTP server then responds with a host and port pair which will be listened upon to receive a connection request *from* the client. Once received, the data can then flow over this connection. + +See also: + +* [Active FTP vs. Passive FTP, a Definitive Explanation](http://slacksite.com/other/ftp.html) + +# References + +* [FTP: File Transfer Protocol](https://cr.yp.to/ftp.html) +* [Wikipedia: File Transfer Protocol](https://en.wikipedia.org/wiki/File_Transfer_Protocol) \ No newline at end of file diff --git a/networking/FTPServer/main.cpp b/networking/FTPServer/main.cpp new file mode 100644 index 00000000..45725051 --- /dev/null +++ b/networking/FTPServer/main.cpp @@ -0,0 +1,12 @@ +#include "FTPServer.h" +#include + + +int main(int argc, char* argv[]) { + printf("FTPServer starting!\n"); + FTPServer *ftpServer = new FTPServer(); + ftpServer->setCallbacks(new FTPFileCallbacks()); + //ftpServer->setCredentials("user", "pass"); + ftpServer->setPort(9876); + ftpServer->start(); +} diff --git a/networking/bootwifi/BootWiFi.cpp b/networking/bootwifi/BootWiFi.cpp new file mode 100644 index 00000000..cc07c898 --- /dev/null +++ b/networking/bootwifi/BootWiFi.cpp @@ -0,0 +1,347 @@ +/** + * Bootwifi - Boot the WiFi environment. + * + * Compile with -DBOOTWIFI_OVERRIDE_GPIO= where is a GPIO pin number + * to use a GPIO override. + * See the README.md for full information. + * + */ +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include +#include "BootWiFi.h" +#include "sdkconfig.h" +#include "selectAP.h" + +// If the structure of a record saved for a subsequent reboot changes +// then consider using semver to change the version number or else +// we may try and boot with the wrong data. +#define KEY_VERSION "version" +uint32_t g_version=0x0100; + +#define KEY_CONNECTION_INFO "connectionInfo" // Key used in NVS for connection info +#define BOOTWIFI_NAMESPACE "bootwifi" // Namespace in NVS for bootwifi +#define SSID_SIZE (32) // Maximum SSID size +#define PASSWORD_SIZE (64) // Maximum password size + + +/** + * The information about a WiFi access point connection. + */ +typedef struct { + char ssid[SSID_SIZE]; + char password[PASSWORD_SIZE]; + tcpip_adapter_ip_info_t ipInfo; // Optional static IP information +} connection_info_t; + +//static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. + + +// Forward declarations +static void saveConnectionInfo(connection_info_t *pConnectionInfo); + +static const char LOG_TAG[] = "bootwifi"; + + +static void dumpConnectionInfo(connection_info_t *pConnectionInfo) { + ESP_LOGD(LOG_TAG, "connection_info.ssid = %.*s", SSID_SIZE, pConnectionInfo->ssid); + ESP_LOGD(LOG_TAG, "connection_info.password = %.*s", PASSWORD_SIZE, pConnectionInfo->password); + ESP_LOGD(LOG_TAG, "ip: %s, gw: %s, netmask: %s", + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.ip).c_str(), + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.gw).c_str(), + GeneralUtils::ipToString((uint8_t*)&pConnectionInfo->ipInfo.netmask).c_str()); +} + + +/** + * Retrieve the connection info. A rc==0 means ok. + */ +static int getConnectionInfo(connection_info_t *pConnectionInfo) { + ESP_LOGD(LOG_TAG, ">> getConnectionInfo"); + size_t size; + uint32_t version; + + NVS myNamespace(BOOTWIFI_NAMESPACE); + myNamespace.get(KEY_VERSION, version); + + // Check the versions match + if ((version & 0xff00) != (g_version & 0xff00)) { + ESP_LOGD(LOG_TAG, "Incompatible versions ... current is %x, found is %x", version, g_version); + return -1; + } + + size = sizeof(connection_info_t); + myNamespace.get(KEY_CONNECTION_INFO, (uint8_t*)pConnectionInfo, size); + + // Do a sanity check on the SSID + if (strlen(pConnectionInfo->ssid) == 0) { + ESP_LOGD(LOG_TAG, "NULL ssid detected"); + return -1; + } + dumpConnectionInfo(pConnectionInfo); + ESP_LOGD(LOG_TAG, "<< getConnectionInfo"); + return 0; + +} // getConnectionInfo + + +/** + * Save our connection info for retrieval on a subsequent restart. + */ +static void saveConnectionInfo(connection_info_t *pConnectionInfo) { + dumpConnectionInfo(pConnectionInfo); + NVS myNamespace(BOOTWIFI_NAMESPACE); + myNamespace.set(KEY_CONNECTION_INFO, (uint8_t*)pConnectionInfo, sizeof(connection_info_t)); + myNamespace.set(KEY_VERSION, g_version); + myNamespace.commit(); +} // setConnectionInfo + + +/** + * Retrieve the signal level on the OVERRIDE_GPIO pin. This is used to + * indicate that we should not attempt to connect to any previously saved + * access point we may know about. + */ + +static int checkOverrideGpio() { +#ifdef BOOTWIFI_OVERRIDE_GPIO + gpio_pad_select_gpio(BOOTWIFI_OVERRIDE_GPIO); + gpio_set_direction(BOOTWIFI_OVERRIDE_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode(BOOTWIFI_OVERRIDE_GPIO, GPIO_PULLDOWN_ONLY); + return gpio_get_level(BOOTWIFI_OVERRIDE_GPIO); +#else + return 0; // If no boot override, return false +#endif +} // checkOverrideGpio + +static void sendForm(HttpRequest* pRequest, HttpResponse* pResponse) { + pResponse->setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + pResponse->addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/html"); + pResponse->sendData(std::string((char*)selectAP_html, selectAP_html_len)); + pResponse->close(); +} // sendForm + + +static void copyData(uint8_t* pTarget, size_t targetLength, std::string source) { + memset(pTarget, 0, targetLength); + size_t copySize = (source.length() > targetLength)? targetLength:source.length(); + memcpy(pTarget, source.data(), copySize); + if (copySize < targetLength) { + pTarget[copySize] = '\0'; + } +} // copyData + + +/** + * @brief Process the form response. + */ +static void processForm(HttpRequest* pRequest, HttpResponse* pResponse) { + ESP_LOGD(LOG_TAG, ">> processForm"); + std::map formMap = pRequest->parseForm(); + connection_info_t connectionInfo; + copyData((uint8_t*)connectionInfo.ssid, SSID_SIZE, formMap["ssid"]); + copyData((uint8_t*)connectionInfo.password, PASSWORD_SIZE, formMap["password"]); + + try { + std::string ipStr = formMap.at("ip"); + if (ipStr.empty()) { + ESP_LOGD(LOG_TAG, "No IP address using default 0.0.0.0"); + connectionInfo.ipInfo.ip.addr = 0; + } else { + inet_pton(AF_INET, ipStr.c_str(), &connectionInfo.ipInfo.ip); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No IP address using default 0.0.0.0"); + connectionInfo.ipInfo.ip.addr = 0; + } + + try { + std::string gwStr = formMap.at("gw"); + if (gwStr.empty()) { + ESP_LOGD(LOG_TAG, "No GW address using default 0.0.0.0"); + connectionInfo.ipInfo.gw.addr = 0; + } else { + inet_pton(AF_INET, gwStr.c_str(), &connectionInfo.ipInfo.gw); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No GW address using default 0.0.0.0"); + connectionInfo.ipInfo.gw.addr = 0; + } + + try { + std::string netmaskStr = formMap.at("netmask"); + if (netmaskStr.empty()) { + ESP_LOGD(LOG_TAG, "No Netmask address using default 0.0.0.0"); + connectionInfo.ipInfo.netmask.addr = 0; + } else { + inet_pton(AF_INET, netmaskStr.c_str(), &connectionInfo.ipInfo.netmask); + } + } catch(std::out_of_range& e) { + ESP_LOGD(LOG_TAG, "No Netmask address using default 0.0.0.0"); + connectionInfo.ipInfo.netmask.addr = 0; + } + + ESP_LOGD(LOG_TAG, "ssid: %s, password: %s", connectionInfo.ssid, connectionInfo.password); + + saveConnectionInfo(&connectionInfo); + + pResponse->setStatus(HttpResponse::HTTP_STATUS_OK, "OK"); + pResponse->addHeader(HttpRequest::HTTP_HEADER_CONTENT_TYPE, "text/html"); + //pResponse->sendData(std::string((char*)selectAP_html, selectAP_html_len)); + pResponse->close(); + FreeRTOS::sleep(500); + System::restart(); + ESP_LOGD(LOG_TAG, "<< processForm"); +} // processForm + + +class BootWifiEventHandler: public WiFiEventHandler { +public: + BootWifiEventHandler(BootWiFi *pBootWiFi) { + m_pBootWiFi = pBootWiFi; + } + + /** + * When the access point logic has started and we are able to receive incoming browser + * requests, start the internal HTTP Server. + */ + esp_err_t apStart() { + ESP_LOGD("BootWifiEventHandler", ">> apStart"); + if (m_pBootWiFi->m_httpServerStarted == false) { + m_pBootWiFi->m_httpServerStarted = true; + m_pBootWiFi->m_httpServer.addPathHandler("GET", "/", sendForm); + m_pBootWiFi->m_httpServer.addPathHandler("POST", "/ssidSelected", processForm); + m_pBootWiFi->m_httpServer.start(80); + } + ESP_LOGD("BootWifiEventHandler", "<< apStart"); + return ESP_OK; + } // apStaConnected + + /** + * If we fail to connect as a station, then become an access point and then + * become a web server. + */ + esp_err_t staDisconnected() { + ESP_LOGD("BootWifiEventHandler", ">> staDisconnected"); + m_pBootWiFi->m_wifi.startAP("Duktape", "Duktape"); + ESP_LOGD("BootWifiEventHandler", "<< staDisconnected"); + return ESP_OK; + } // staDisconnected + + + esp_err_t staGotIp(system_event_sta_got_ip_t event_sta_got_ip) { + ESP_LOGD("BootWifiEventHandler", ">> staGotIP"); + m_pBootWiFi->m_apConnectionStatus = ESP_OK; // Set the status to ESP_OK + m_pBootWiFi->m_completeSemaphore.give(); // If we got an IP address, then we can end the boot process. + ESP_LOGD("BootWifiEventHandler", "<< staGotIP"); + return ESP_OK; + } // staGotIp + +private: + BootWiFi *m_pBootWiFi; +}; + + +/** + * Boot WiFi + * + * @brief Get connected to WiFi + * + * @detailed If SSID & Password were previously saved, connect to the AP. Otherwise become an AP and start an HTTP server so that the user can set SSID & Password - then save it. + * + */ +void BootWiFi::bootWiFi2() { + ESP_LOGD(LOG_TAG, ">> bootWiFi2"); + + // Check for a GPIO override which occurs when a physical Pin is high + // during the test. This can force the ability to check for new configuration + // even if the existing configured access point is available. + m_wifi.setWifiEventHandler(new BootWifiEventHandler(this)); + if (checkOverrideGpio()) { + ESP_LOGD(LOG_TAG, "- GPIO override detected"); + m_wifi.startAP(m_ssid, m_password); + } else { + // There was NO GPIO override, proceed as normal. This means we retrieve + // our stored access point information of the access point we should connect + // against. If that information doesn't exist, then again we become an + // access point ourselves in order to allow a client to connect and bring + // up a browser. + connection_info_t connectionInfo; + int rc = getConnectionInfo(&connectionInfo); + if (rc == 0) { + // We have received connection information, let us now become a station + // and attempt to connect to the access point. + ESP_LOGD(LOG_TAG, "- Connecting to access point \"%s\" ...", connectionInfo.ssid); + assert(strlen(connectionInfo.ssid) > 0); + + m_wifi.setIPInfo( + connectionInfo.ipInfo.ip.addr, + connectionInfo.ipInfo.gw.addr, + connectionInfo.ipInfo.netmask.addr + ); + + m_apConnectionStatus = m_wifi.connectAP(connectionInfo.ssid, connectionInfo.password); // Try to connect to the access point. + m_completeSemaphore.give(); // end the boot process so we don't hang... + + } else { + // We do NOT have connection information. Let us now become an access + // point that serves up a web server and allow a browser user to specify + // the details that will be eventually used to allow us to connect + // as a station. + m_wifi.startAP(m_ssid, m_password); + } // We do NOT have connection info + } + ESP_LOGD(LOG_TAG, "<< bootWiFi2"); +} // bootWiFi2 + + +/** + * @brief Set the userid/password pair that will be used for the ESP32 access point. + * @param [in] ssid The network id of the ESP32 when it becomes an access point. + * @param [in] password The password for the ESP32 when it becomes an access point. + */ +void BootWiFi::setAccessPointCredentials(std::string ssid, std::string password) { + m_ssid = ssid; + m_password = password; +} // setAccessPointCredentials + + + +/** + * @brief Main entry point into booting WiFi - see BootWiFi2 for more detail. + * + * The event handler will be called back with the outcome of the connection. + * + * @returns ESP_OK if successfully connected to an access point. Otherwise returns wifi_err_reason_t - to print use GeneralUtils::wifiErrorToString + */ +uint8_t BootWiFi::boot() { + ESP_LOGD(LOG_TAG, ">> boot"); + ESP_LOGD(LOG_TAG, " +----------+"); + ESP_LOGD(LOG_TAG, " | BootWiFi |"); + ESP_LOGD(LOG_TAG, " +----------+"); + ESP_LOGD(LOG_TAG, " Access point credentials: %s/%s", m_ssid.c_str(), m_password.c_str()); + m_completeSemaphore.take("boot"); // Take the semaphore which will be unlocked when we complete booting. + bootWiFi2(); + m_completeSemaphore.wait("boot"); // Wait for the semaphore that indicated we have completed booting. + m_wifi.setWifiEventHandler(nullptr); // Remove the WiFi boot handler when we have completed booting. + ESP_LOGD(LOG_TAG, "<< boot"); + return m_apConnectionStatus; +} // boot + +BootWiFi::BootWiFi() { + m_httpServerStarted = false; + m_apConnectionStatus = UINT8_MAX; + setAccessPointCredentials("esp32", "password"); // Default access point credentials +} diff --git a/networking/bootwifi/BootWiFi.h b/networking/bootwifi/BootWiFi.h new file mode 100644 index 00000000..9c4a17b6 --- /dev/null +++ b/networking/bootwifi/BootWiFi.h @@ -0,0 +1,36 @@ +/* + * BootWiFi.h + * + * Created on: Nov 25, 2016 + * Author: kolban + */ + +#ifndef MAIN_BOOTWIFI_H_ +#define MAIN_BOOTWIFI_H_ + +#include +#include +#include + +typedef void (*bootwifi_callback_t)(int rc); +class BootWifiEventHandler; + +class BootWiFi { +private: + friend BootWifiEventHandler; + void bootWiFi2(); + WiFi m_wifi; + HttpServer m_httpServer; + bool m_httpServerStarted; + std::string m_ssid; + std::string m_password; + uint8_t m_apConnectionStatus; // receives the connection status. ESP_OK = received SYSTEM_EVENT_STA_GOT_IP event. + FreeRTOS::Semaphore m_completeSemaphore = FreeRTOS::Semaphore("completeSemaphore"); + +public: + BootWiFi(); + void setAccessPointCredentials(std::string ssid, std::string password); + uint8_t boot(); +}; + +#endif /* MAIN_BOOTWIFI_H_ */ diff --git a/networking/bootwifi/README.md b/networking/bootwifi/README.md index b6c8bfc2..2f2a7c6c 100644 --- a/networking/bootwifi/README.md +++ b/networking/bootwifi/README.md @@ -1,47 +1,46 @@ # Bootwifi -It is common to want to start an ESP32 and have it connect to a WiFi environment but how -does one bootstrap it? To connect to a WiFi environment, we typically need to know the -SSID and password of the network to which we wish to connect. But without network connection -to the ESP32, how do we set it? This component provides a potential solution. - -The module exposes a function called `bootwifi` which, when called, will own the connection -of the device to the local WiFi environment. To do this, it looks in its flash storage to see -if it has previously been given an SSID/password pair. If it has, it attempts to connect to that -access point and join the network. - -However, let us assume that it has never been given that information. In this case it will become -an access point in its own right. Now you can connect to the ESP32 using your phone or other WiFi -device as it will appear as an access point against which we can connect. Once connected, we can -open a browser to it. In the browser page, we will be prompted for the SSID and password we wish -to subsequently use. This will be saved and used from then on. Now the device will connect to that -network. - -What if we take our ESP32 to a new environment where the previously saved access point is no longer -accessible or we simply just fail to connect? Again, we will fall back into being an access point -and the user will be able to supply new information. - -What if we want to change the access point to which the ESP32 connects even if that access point has -been previously saved and is still connectable? Simple, the ESP32 can check a GPIO pin at startup and, -if that pin is high (default low) then that can be used as a manual indication that we should become -an access point without even attempting to connect to the network. - -This code is supplied in the form of an ESP-IDF module. It depends on a partner module called `mongoose` -that provides a Web Server in order to server up the web pages. A build of `mongoose` is available -however, [Cesanta](https://www.cesanta.com/), the makers of Mongoose are still working on a formal -port to the ESP32 which is anticipated to be available before 2017 so we should really wait for that -to become available. - -##GPIO boot override -To enable the ability to specify a GPIO pin to override known station information, compile -the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the -pin is high at startup, then it will override. The pin is configured as pull-down low so -it need not be artificially held low. The default is no override pin. - -##Future enhancements +It is common to want to start an ESP32 and have it connect to a WiFi environment but how does one bootstrap it? To connect to a WiFi environment, we typically need to know the SSID and password of the network to which we wish to connect. But without network connection to the ESP32, how do we set it? This component provides a potential solution. + +The module exposes a function called `bootwifi` which, when called, will own the connection of the device to the local WiFi environment. To do this, it looks in its flash storage to see if it has previously been given an SSID/password pair. If it has, it attempts to connect to that access point and join the network. + +However, let us assume that it has never been given that information. In this case it will become an access point in its own right. What this means is that the ESP32 will become a WiFi network to which other WiFi devices can connect. Now you can connect to the ESP32 using your phone or other WiFi device as it will appear as an access point against which we can connect. Once connected, we can open a browser to it. In the browser page, we will be prompted for the SSID and password we wish to subsequently use. This will be saved and used from then on. Now the device will connect to that network. + +What if we take our ESP32 to a new environment where the previously saved access point is no longer accessible or we simply just fail to connect? Again, we will fall back into being an access point and the user will be able to supply new information. + +What if we want to change the access point to which the ESP32 connects even if that access point has been previously saved and is still connectable? Simple, the ESP32 can check a GPIO pin at startup and, if that pin is high (default low) then that can be used as a manual indication that we should become an access point without even attempting to connect to the network. + +This code is supplied in the form of an ESP-IDF module. In addition, BootWiFi has a pre-requisite of the `cpp_utils` component also distributed as part of this repoistory. + +The logic uses C++ exception handling and hence C++ exception handling must be enabled in `make menuconfig`. + +## Example +Below is a sample of a minimal main.cpp. +* If you run it on an esp32, it should create a new open wifi network with SSID: "ESP32". +* Connect to that network. +* Then open a browser and go to http://192.168.4.1. +```C++ +#include "BootWiFi.h" +#include "sdkconfig.h" + +extern "C" { + void app_main(void); +} + +BootWiFi *boot; + +void app_main(void) { + boot = new BootWiFi(); + boot->boot(); +} +``` + +## GPIO boot override +To enable the ability to specify a GPIO pin to override known station information, compile the code with `-DBOOTWIFI_OVERRIDE_GPIO=` when `` is a GPIO pin number. If the pin is high at startup, then it will override. The pin is configured as pull-down low so it need not be artificially held low. The default is no override pin. + +## Future enhancements There is always room for enhancements: -* Improve the web page shown to the user - Right now it is pretty basic and ideally could be -dramatically improved. Features to be added include +* Improve the web page shown to the user - Right now it is pretty basic and ideally could be dramatically improved. Features to be added include - listing of available access points for selection * Integrate SSL security. * NeoPixel support for visualization of connection status: @@ -55,15 +54,14 @@ dramatically improved. Features to be added include - Network SSID to use when being an access point. - Network password to use when being an access point (if any). -##Design and implementation notes +## Design and implementation notes The parameters for Bootwifi are stored in Non-Volatile Storage (NVS). The name space in NVS -is "bootwifi". The keys are: +is `bootwifi`. The keys are: -* version - The version of the protocol. -* connectionInfo - The details for connection. +* `version` - The version of the protocol. +* `connectionInfo` - The details for connection. -The form shown to the end user sends back a response as an HTTP POST to "/ssidSelected". -which contains the following form fields: +The form shown to the end user sends back a response as an HTTP POST to `/ssidSelected`. which contains the following form fields: * ssid * password diff --git a/networking/bootwifi/bootwifi.c b/networking/bootwifi/bootwifi.c deleted file mode 100644 index 7de7c2fd..00000000 --- a/networking/bootwifi/bootwifi.c +++ /dev/null @@ -1,511 +0,0 @@ -/** - * Bootwifi - Boot the WiFi environment. - * - * Compile with -DBOOTWIFI_OVERRIDE_GPIO= where is a GPIO pin number - * to use a GPIO override. - * See the README.md for full information. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "bootwifi.h" -#include "sdkconfig.h" -#include "selectAP.h" - -// If the structure of a record saved for a subsequent reboot changes -// then consider using semver to change the version number or else -// we may try and boot with the wrong data. -#define KEY_VERSION "version" -uint32_t g_version=0x0100; - -#define KEY_CONNECTION_INFO "connectionInfo" // Key used in NVS for connection info -#define BOOTWIFI_NAMESPACE "bootwifi" // Namespace in NVS for bootwifi -#define SSID_SIZE (32) // Maximum SSID size -#define PASSWORD_SIZE (64) // Maximum password size - -typedef struct { - char ssid[SSID_SIZE]; - char password[PASSWORD_SIZE]; - tcpip_adapter_ip_info_t ipInfo; // Optional static IP information -} connection_info_t; - -static bootwifi_callback_t g_callback = NULL; // Callback function to be invoked when we have finished. - -static int g_mongooseStarted = 0; // Has the mongoose server started? -static int g_mongooseStopRequest = 0; // Request to stop the mongoose server. - -// Forward declarations -static void saveConnectionInfo(connection_info_t *pConnectionInfo); -static void becomeAccessPoint(); -static void bootWiFi2(); - -static char tag[] = "bootwifi"; - - - - - -/** - * Convert a Mongoose event type to a string. Used for debugging. - */ -static char *mongoose_eventToString(int ev) { - static char temp[100]; - switch (ev) { - case MG_EV_CONNECT: - return "MG_EV_CONNECT"; - case MG_EV_ACCEPT: - return "MG_EV_ACCEPT"; - case MG_EV_CLOSE: - return "MG_EV_CLOSE"; - case MG_EV_SEND: - return "MG_EV_SEND"; - case MG_EV_RECV: - return "MG_EV_RECV"; - case MG_EV_HTTP_REQUEST: - return "MG_EV_HTTP_REQUEST"; - case MG_EV_MQTT_CONNACK: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNACK_ACCEPTED: - return "MG_EV_MQTT_CONNACK"; - case MG_EV_MQTT_CONNECT: - return "MG_EV_MQTT_CONNECT"; - case MG_EV_MQTT_DISCONNECT: - return "MG_EV_MQTT_DISCONNECT"; - case MG_EV_MQTT_PINGREQ: - return "MG_EV_MQTT_PINGREQ"; - case MG_EV_MQTT_PINGRESP: - return "MG_EV_MQTT_PINGRESP"; - case MG_EV_MQTT_PUBACK: - return "MG_EV_MQTT_PUBACK"; - case MG_EV_MQTT_PUBCOMP: - return "MG_EV_MQTT_PUBCOMP"; - case MG_EV_MQTT_PUBLISH: - return "MG_EV_MQTT_PUBLISH"; - case MG_EV_MQTT_PUBREC: - return "MG_EV_MQTT_PUBREC"; - case MG_EV_MQTT_PUBREL: - return "MG_EV_MQTT_PUBREL"; - case MG_EV_MQTT_SUBACK: - return "MG_EV_MQTT_SUBACK"; - case MG_EV_MQTT_SUBSCRIBE: - return "MG_EV_MQTT_SUBSCRIBE"; - case MG_EV_MQTT_UNSUBACK: - return "MG_EV_MQTT_UNSUBACK"; - case MG_EV_MQTT_UNSUBSCRIBE: - return "MG_EV_MQTT_UNSUBSCRIBE"; - case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: - return "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"; - case MG_EV_WEBSOCKET_HANDSHAKE_DONE: - return "MG_EV_WEBSOCKET_HANDSHAKE_DONE"; - case MG_EV_WEBSOCKET_FRAME: - return "MG_EV_WEBSOCKET_FRAME"; - } - sprintf(temp, "Unknown event: %d", ev); - return temp; -} //eventToString - - -// Convert a Mongoose string type to a string. -static char *mgStrToStr(struct mg_str mgStr) { - char *retStr = (char *) malloc(mgStr.len + 1); - memcpy(retStr, mgStr.p, mgStr.len); - retStr[mgStr.len] = 0; - return retStr; -} // mgStrToStr - - -/** - * Handle mongoose events. These are mostly requests to process incoming - * browser requests. The ones we handle are: - * GET / - Send the enter details page. - * GET /set - Set the connection info (REST request). - * POST /ssidSelected - Set the connection info (HTML FORM). - */ -static void mongoose_event_handler(struct mg_connection *nc, int ev, void *evData) { - ESP_LOGD(tag, "- Event: %s", mongoose_eventToString(ev)); - switch (ev) { - case MG_EV_HTTP_REQUEST: { - struct http_message *message = (struct http_message *) evData; - char *uri = mgStrToStr(message->uri); - ESP_LOGD(tag, " - uri: %s", uri); - - if (strcmp(uri, "/set") ==0 ) { - connection_info_t connectionInfo; -//fix - saveConnectionInfo(&connectionInfo); - ESP_LOGD(tag, "- Set the new connection info to ssid: %s, password: %s", - connectionInfo.ssid, connectionInfo.password); - mg_send_head(nc, 200, 0, "Content-Type: text/plain"); - } if (strcmp(uri, "/") == 0) { - mg_send_head(nc, 200, sizeof(selectAP_html), "Content-Type: text/html"); - mg_send(nc, selectAP_html, sizeof(selectAP_html)); - } - // Handle /ssidSelected - // This is an incoming form with properties: - // * ssid - The ssid of the network to connect against. - // * password - the password to use to connect. - // * ip - Static IP address ... may be empty - // * gw - Static GW address ... may be empty - // * netmask - Static netmask ... may be empty - if(strcmp(uri, "/ssidSelected") == 0) { - // We have received a form page containing the details. The form body will - // contain: - // ssid=&password= - ESP_LOGD(tag, "- body: %.*s", message->body.len, message->body.p); - connection_info_t connectionInfo; - mg_get_http_var(&message->body, "ssid", connectionInfo.ssid, SSID_SIZE); - mg_get_http_var(&message->body, "password", connectionInfo.password, PASSWORD_SIZE); - - char ipBuf[20]; - if (mg_get_http_var(&message->body, "ip", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.ip); - } else { - connectionInfo.ipInfo.ip.addr = 0; - } - - if (mg_get_http_var(&message->body, "gw", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.gw); - } - else { - connectionInfo.ipInfo.gw.addr = 0; - } - - if (mg_get_http_var(&message->body, "netmask", ipBuf, sizeof(ipBuf)) > 0) { - inet_pton(AF_INET, ipBuf, &connectionInfo.ipInfo.netmask); - } - else { - connectionInfo.ipInfo.netmask.addr = 0; - } - - ESP_LOGD(tag, "ssid: %s, password: %s", connectionInfo.ssid, connectionInfo.password); - - mg_send_head(nc, 200, 0, "Content-Type: text/plain"); - saveConnectionInfo(&connectionInfo); - bootWiFi2(); - } // url is "/ssidSelected" - // Else ... unknown URL - else { - mg_send_head(nc, 404, 0, "Content-Type: text/plain"); - } - nc->flags |= MG_F_SEND_AND_CLOSE; - free(uri); - break; - } // MG_EV_HTTP_REQUEST - } // End of switch -} // End of mongoose_event_handler - - -// FreeRTOS task to start Mongoose. -static void mongooseTask(void *data) { - struct mg_mgr mgr; - struct mg_connection *connection; - - ESP_LOGD(tag, ">> mongooseTask"); - g_mongooseStopRequest = 0; // Unset the stop request since we are being asked to start. - - mg_mgr_init(&mgr, NULL); - - connection = mg_bind(&mgr, ":80", mongoose_event_handler); - - if (connection == NULL) { - ESP_LOGE(tag, "No connection from the mg_bind()."); - mg_mgr_free(&mgr); - ESP_LOGD(tag, "<< mongooseTask"); - vTaskDelete(NULL); - return; - } - mg_set_protocol_http_websocket(connection); - - // Keep processing until we are flagged that there is a stop request. - while (!g_mongooseStopRequest) { - mg_mgr_poll(&mgr, 1000); - } - - // We have received a stop request, so stop being a web server. - mg_mgr_free(&mgr); - g_mongooseStarted = 0; - - // Since we HAVE ended mongoose, time to invoke the callback. - if (g_callback) { - g_callback(1); - } - - ESP_LOGD(tag, "<< mongooseTask"); - vTaskDelete(NULL); - return; -} // mongooseTask - - -/** - * An ESP32 WiFi event handler. - * The types of events that can be received here are: - * - * SYSTEM_EVENT_AP_PROBEREQRECVED - * SYSTEM_EVENT_AP_STACONNECTED - * SYSTEM_EVENT_AP_STADISCONNECTED - * SYSTEM_EVENT_AP_START - * SYSTEM_EVENT_AP_STOP - * SYSTEM_EVENT_SCAN_DONE - * SYSTEM_EVENT_STA_AUTHMODE_CHANGE - * SYSTEM_EVENT_STA_CONNECTED - * SYSTEM_EVENT_STA_DISCONNECTED - * SYSTEM_EVENT_STA_GOT_IP - * SYSTEM_EVENT_STA_START - * SYSTEM_EVENT_STA_STOP - * SYSTEM_EVENT_WIFI_READY - */ -static esp_err_t esp32_wifi_eventHandler(void *ctx, system_event_t *event) { - // Your event handling code here... - switch(event->event_id) { - // When we have started being an access point, then start being a web server. - case SYSTEM_EVENT_AP_START: { // Handle the AP start event - tcpip_adapter_ip_info_t ip_info; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip_info); - ESP_LOGD(tag, "**********************************************"); - ESP_LOGD(tag, "* We are now an access point and you can point") - ESP_LOGD(tag, "* your browser to http://" IPSTR, IP2STR(&ip_info.ip)); - ESP_LOGD(tag, "**********************************************"); - // Start Mongoose ... - if (!g_mongooseStarted) - { - g_mongooseStarted = 1; - xTaskCreatePinnedToCore(&mongooseTask, "bootwifi_mongoose_task", 8000, NULL, 5, NULL, 0); - } - break; - } // SYSTEM_EVENT_AP_START - - // If we fail to connect to an access point as a station, become an access point. - case SYSTEM_EVENT_STA_DISCONNECTED: { - ESP_LOGD(tag, "Station disconnected started"); - // We think we tried to connect as a station and failed! ... become - // an access point. - becomeAccessPoint(); - break; - } // SYSTEM_EVENT_AP_START - - // If we connected as a station then we are done and we can stop being a - // web server. - case SYSTEM_EVENT_STA_GOT_IP: { - ESP_LOGD(tag, "********************************************"); - ESP_LOGD(tag, "* We are now connected and ready to do work!") - ESP_LOGD(tag, "* - Our IP address is: " IPSTR, IP2STR(&event->event_info.got_ip.ip_info.ip)); - ESP_LOGD(tag, "********************************************"); - g_mongooseStopRequest = 1; // Stop mongoose (if it is running). - // Invoke the callback if Mongoose has NOT been started ... otherwise - // we will invoke the callback when mongoose has ended. - if (!g_mongooseStarted) { - if (g_callback) { - g_callback(1); - } - } // Mongoose was NOT started - break; - } // SYSTEM_EVENT_STA_GOTIP - - default: // Ignore the other event types - break; - } // Switch event - - return ESP_OK; -} // esp32_wifi_eventHandler - - -/** - * Retrieve the connection info. A rc==0 means ok. - */ -static int getConnectionInfo(connection_info_t *pConnectionInfo) { - nvs_handle handle; - size_t size; - esp_err_t err; - uint32_t version; - err = nvs_open(BOOTWIFI_NAMESPACE, NVS_READWRITE, &handle); - if (err != 0) { - ESP_LOGE(tag, "nvs_open: %x", err); - return -1; - } - - // Get the version that the data was saved against. - err = nvs_get_u32(handle, KEY_VERSION, &version); - if (err != ESP_OK) { - ESP_LOGD(tag, "No version record found (%d).", err); - nvs_close(handle); - return -1; - } - - // Check the versions match - if ((version & 0xff00) != (g_version & 0xff00)) { - ESP_LOGD(tag, "Incompatible versions ... current is %x, found is %x", version, g_version); - nvs_close(handle); - return -1; - } - - size = sizeof(connection_info_t); - err = nvs_get_blob(handle, KEY_CONNECTION_INFO, pConnectionInfo, &size); - if (err != ESP_OK) { - ESP_LOGD(tag, "No connection record found (%d).", err); - nvs_close(handle); - return -1; - } - if (err != ESP_OK) { - ESP_LOGE(tag, "nvs_open: %x", err); - nvs_close(handle); - return -1; - } - - // Cleanup - nvs_close(handle); - - // Do a sanity check on the SSID - if (strlen(pConnectionInfo->ssid) == 0) { - ESP_LOGD(tag, "NULL ssid detected"); - return -1; - } - return 0; -} // getConnectionInfo - - -/** - * Save our connection info for retrieval on a subsequent restart. - */ -static void saveConnectionInfo(connection_info_t *pConnectionInfo) { - nvs_handle handle; - ESP_ERROR_CHECK(nvs_open(BOOTWIFI_NAMESPACE, NVS_READWRITE, &handle)); - ESP_ERROR_CHECK(nvs_set_blob(handle, KEY_CONNECTION_INFO, pConnectionInfo, - sizeof(connection_info_t))); - ESP_ERROR_CHECK(nvs_set_u32(handle, KEY_VERSION, g_version)); - ESP_ERROR_CHECK(nvs_commit(handle)); - nvs_close(handle); -} // setConnectionInfo - - -/** - * Become a station connecting to an existing access point. - */ -static void becomeStation(connection_info_t *pConnectionInfo) { - ESP_LOGD(tag, "- Connecting to access point \"%s\" ...", pConnectionInfo->ssid); - assert(strlen(pConnectionInfo->ssid) > 0); - - // If we have a static IP address information, use that. - if (pConnectionInfo->ipInfo.ip.addr != 0) { - ESP_LOGD(tag, " - using a static IP address of " IPSTR, IP2STR(&pConnectionInfo->ipInfo.ip)); - tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); - tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &pConnectionInfo->ipInfo); - } else { - tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); - } - - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA)); - wifi_config_t sta_config; - sta_config.sta.bssid_set = 0; - memcpy(sta_config.sta.ssid, pConnectionInfo->ssid, SSID_SIZE); - memcpy(sta_config.sta.password, pConnectionInfo->password, PASSWORD_SIZE); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config)); - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_connect()); -} // becomeStation - - -/** - * Become an access point. - */ -static void becomeAccessPoint() { - ESP_LOGD(tag, "- Starting being an access point ..."); - // We don't have connection info so be an access point! - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); - wifi_config_t apConfig = { - .ap = { - .ssid="ESP32 Duktape", - .ssid_len=0, - .password="Duktape", - .channel=0, - .authmode=WIFI_AUTH_OPEN, - .ssid_hidden=0, - .max_connection=4, - .beacon_interval=100 - } - }; - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &apConfig)); - ESP_ERROR_CHECK(esp_wifi_start()); -} // becomeAccessPoint - - -/** - * Retrieve the signal level on the OVERRIDE_GPIO pin. This is used to - * indicate that we should not attempt to connect to any previously saved - * access point we may know about. - */ - -static int checkOverrideGpio() { -#ifdef BOOTWIFI_OVERRIDE_GPIO - gpio_pad_select_gpio(BOOTWIFI_OVERRIDE_GPIO); - gpio_set_direction(BOOTWIFI_OVERRIDE_GPIO, GPIO_MODE_INPUT); - gpio_set_pull_mode(BOOTWIFI_OVERRIDE_GPIO, GPIO_PULLDOWN_ONLY); - return gpio_get_level(BOOTWIFI_OVERRIDE_GPIO); -#else - return 0; // If no boot override, return false -#endif -} // checkOverrideGpio - - - -static void bootWiFi2() { - ESP_LOGD(tag, ">> bootWiFi2"); - // Check for a GPIO override which occurs when a physical Pin is high - // during the test. This can force the ability to check for new configuration - // even if the existing configured access point is available. - if (checkOverrideGpio()) { - ESP_LOGD(tag, "- GPIO override detected"); - becomeAccessPoint(); - } else { - // There was NO GPIO override, proceed as normal. This means we retrieve - // our stored access point information of the access point we should connect - // against. If that information doesn't exist, then again we become an - // access point ourselves in order to allow a client to connect and bring - // up a browser. - connection_info_t connectionInfo; - int rc = getConnectionInfo(&connectionInfo); - if (rc == 0) { - // We have received connection information, let us now become a station - // and attempt to connect to the access point. - becomeStation(&connectionInfo); - - } else { - // We do NOT have connection information. Let us now become an access - // point that serves up a web server and allow a browser user to specify - // the details that will be eventually used to allow us to connect - // as a station. - becomeAccessPoint(); - } // We do NOT have connection info - } - ESP_LOGD(tag, "<< bootWiFi2"); -} // bootWiFi2 - - -/** - * Main entry into bootWiFi - */ -void bootWiFi(bootwifi_callback_t callback) { - ESP_LOGD(tag, ">> bootWiFi"); - g_callback = callback; - nvs_flash_init(); - tcpip_adapter_init(); - ESP_ERROR_CHECK(esp_event_loop_init(esp32_wifi_eventHandler, NULL)); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); - ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - - bootWiFi2(); - - ESP_LOGD(tag, "<< bootWiFi"); -} // bootWiFi diff --git a/networking/bootwifi/bootwifi.h b/networking/bootwifi/bootwifi.h deleted file mode 100644 index 885da4a8..00000000 --- a/networking/bootwifi/bootwifi.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * bootwifi.h - * - * Created on: Nov 25, 2016 - * Author: kolban - */ - -#ifndef MAIN_BOOTWIFI_H_ -#define MAIN_BOOTWIFI_H_ - -typedef void (*bootwifi_callback_t)(int rc); -void bootWiFi(); - - -#endif /* MAIN_BOOTWIFI_H_ */ diff --git a/networking/bootwifi/component.mk b/networking/bootwifi/component.mk index e64cac1b..c00ba051 100644 --- a/networking/bootwifi/component.mk +++ b/networking/bootwifi/component.mk @@ -6,10 +6,4 @@ # in the build directory. This behaviour is entirely configurable, # please read the ESP-IDF documents if you need to do this. # -CFLAGS += -DCS_PLATFORM=3 \ - -DMG_DISABLE_DIRECTORY_LISTING=1 \ - -DMG_DISABLE_DAV=1 \ - -DMG_DISABLE_CGI=1 \ - -DMG_DISABLE_FILESYSTEM=1 \ - -DMG_LWIP=1 \ - -DMG_ENABLE_BROADCAST \ No newline at end of file +COMPONENT_ADD_INCLUDEDIRS=. \ No newline at end of file diff --git a/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c b/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c index f46a0fde..38de27c9 100644 --- a/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c +++ b/networking/mqtt/paho_mqtt_embedded_c/MQTTClient-C/src/linux/MQTTLinux.c @@ -215,7 +215,9 @@ static int NetworkConnectSSL(Network *n, char *addr, int port) { mbedtls_entropy_init(&mqtt_ssl_context->entropy); mbedtls_ssl_conf_dbg(&mqtt_ssl_context->conf, my_debug, stdout); +#ifdef CONFIG_MBEDTLS_DEBUG mbedtls_debug_set_threshold(4); // Log at verbose only +#endif // CONFIG_MBEDTLS_DEBUG int ret = mbedtls_ctr_drbg_seed( &mqtt_ssl_context->ctr_drbg, diff --git a/networking/mqtt/paho_mqtt_embedded_c/README.md b/networking/mqtt/paho_mqtt_embedded_c/README.md index c9b4906a..830981ea 100644 --- a/networking/mqtt/paho_mqtt_embedded_c/README.md +++ b/networking/mqtt/paho_mqtt_embedded_c/README.md @@ -35,3 +35,6 @@ Discussion of the Paho clients takes place on the [Eclipse paho-dev mailing list General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt). There is much more information available via the [MQTT community site](http://mqtt.org). + +# ESP32 Specific +The compilation requires that we enable mbedTLS debugging features. Run "make menuconfig" and visit `Component Config -> mbedTLS` and check the `Enable mbedTLS debugging` section. Save and rebuild the environment. diff --git a/posix/README.md b/posix/README.md new file mode 100644 index 00000000..f534e952 --- /dev/null +++ b/posix/README.md @@ -0,0 +1,8 @@ +# POSIX +Posix is the specification for Unix like functions. The ESP-IDF provides many implementations for Posix functions but some are omitted and thus should not be used in ESP32 based applications. However there are times when we received 3rd party code that uses a Posix function that we don't have in our environment. Our choices then become: + +* Contact the 3rd party provider and ask them to alter their code to remove it, replace it or make it optional. +* Contact Espressif and ask them to add support for the missing Posix function. +* Provide a shim that satisfies the Posix function using the capabailities that are available to us. + +In the source file called `posix_shims.c` we provide some of those shims. \ No newline at end of file diff --git a/posix/posix_shims.c b/posix/posix_shims.c new file mode 100644 index 00000000..5515e250 --- /dev/null +++ b/posix/posix_shims.c @@ -0,0 +1,18 @@ +#include +#include +#include + +/** + * @brief Provide a shim for the Posix access(2) function. + * @param [in] pathname The file to check. + * @param [in] mode The mode of access requested. + * @return 0 on success, -1 on error with errno set. + */ +int access(const char *pathname, int mode) { + struct stat statBuf; + if (stat(pathname, &statBuf) == -1) { + errno = ENOENT; + return -1; + } + return 0; // Indicate that all we have access to the file. +} // access diff --git a/tasks/watchdogs/README.md b/tasks/watchdogs/README.md new file mode 100644 index 00000000..052bc868 --- /dev/null +++ b/tasks/watchdogs/README.md @@ -0,0 +1,6 @@ +# Watchdogs +This is a sample application that illustrates the capabilities of watchdogs and watchdog processing. + +A related YouTube video is available here: + +https://www.youtube.com/watch?v=C2xF3O6qkbg \ No newline at end of file diff --git a/tasks/watchdogs/main.cpp b/tasks/watchdogs/main.cpp new file mode 100644 index 00000000..de4484c5 --- /dev/null +++ b/tasks/watchdogs/main.cpp @@ -0,0 +1,155 @@ +#include "freertos/FreeRTOS.h" +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_event_loop.h" +#include "nvs_flash.h" +#include "driver/gpio.h" +#include + +esp_err_t event_handler(void *ctx, system_event_t *event) +{ + return ESP_OK; +} + +extern "C" +{ + void app_main(void); +} + +void highPriorityTask(void *myData) +{ + printf("High priority task started and now looping for 10 seconds. Our priority is %d.\n", uxTaskPriorityGet(nullptr)); + TickType_t startTicks = xTaskGetTickCount(); + while (xTaskGetTickCount() - startTicks < (10 * 1000 / portTICK_PERIOD_MS)) + { + // Do nothing but loop + } + printf("High priority task ended\n"); + vTaskDelete(nullptr); +} + +void hardLoopTask(void *myData) +{ + printf("Hard loop task started ...\n"); + while (1) + { + // do nothing but burn CPU + } +} + +void hardLoopTaskNoInterrupts(void *myData) +{ + printf("Hard loop task disabling interrupts started ...\n"); + taskDISABLE_INTERRUPTS(); + while (1) + { + // do nothing but burn CPU + } +} + +void myTask(void *myData) +{ + printf("# Running in myTask\n"); + printf("# Registering our new task with the task watchdog.\n"); + esp_task_wdt_add(nullptr); + + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + printf("# Removing our watchdog registration so we can do something expensive.\n"); + esp_task_wdt_delete(nullptr); + + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Re-registering our task with the task watchdog.\n"); + esp_task_wdt_add(nullptr); + printf("# Looping 5 times with a delay of 1 second and not feeding the watchdog.\n"); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + } + + printf("# Our current task priority is %d.\n", uxTaskPriorityGet(nullptr)); + printf("# Spwaning a higher priority task\n"); + xTaskCreate(highPriorityTask, // Task code + "Priority task", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 5, // Priority + nullptr // task handle + ); + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + printf("Spawning a hard-loop function!\n"); + xTaskCreate(hardLoopTaskNoInterrupts, // Task code + "Hard Loop", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 5, // Priority + nullptr // task handle + ); + + printf("# Looping 5 times with a delay of 1 second and positively feeding the watchdog.\n"); + esp_task_wdt_reset(); + for (int i = 0; i < 5; i++) + { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Tick\n"); + esp_task_wdt_reset(); + } + + + printf("# Removing our watchdog registration before we end the task.\n"); + esp_task_wdt_delete(nullptr); + + printf("# Ending myTask\n"); + vTaskDelete(nullptr); +} // myTask + +void app_main(void) +{ + xTaskHandle handle; + printf("App starting\n"); + printf("Initializing the task watchdog subsystem with an interval of 2 seconds.\n"); + esp_task_wdt_init(2, false); + + printf("Creatign a new task.\n"); + // Now let us create a new task. + xTaskCreate(myTask, // Task code + "My Task", // Name of task + 16 * 1024, // Stack size + nullptr, // Task data + 0, // Priority + &handle // task handle + ); + + //printf("App Ended!\n"); +} // app_main diff --git a/tools/MMUTool/MMUTool.html b/tools/MMUTool/MMUTool.html new file mode 100644 index 00000000..b50337ab --- /dev/null +++ b/tools/MMUTool/MMUTool.html @@ -0,0 +1,134 @@ + + + + +MMUTool + + + + + +

ESP32 MMU Tool

+

Storage address:

+

MMU entry:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0x3F40 00000x3F40 FFFF0Data
0x3F41 00000x3F41 FFFF1Data
.........Data
0x3F7F 00000x3F7F FFFF63Data
0x4000 00000x4000 FFFF64Instruction
0x4001 00000x4001 FFFF65Instruction
.........Instruction
0x403F 00000x403F FFFF127Instruction
0x4040 00000x4040 FFFF128Instruction
.........Instruction
0x407F 00000x407F FFFF255Instruction
+ + + \ No newline at end of file diff --git a/tools/bootloaderExamine/.gitignore b/tools/bootloaderExamine/.gitignore new file mode 100644 index 00000000..5db6b6fc --- /dev/null +++ b/tools/bootloaderExamine/.gitignore @@ -0,0 +1,2 @@ +/a.out +/app-template.bin diff --git a/tools/bootloaderExamine/main.cpp b/tools/bootloaderExamine/main.cpp new file mode 100644 index 00000000..60472e30 --- /dev/null +++ b/tools/bootloaderExamine/main.cpp @@ -0,0 +1,153 @@ +#include +#include +#include + +/* Main header of binary image */ +typedef struct { + uint8_t magic; + uint8_t segment_count; + /* flash read mode (esp_image_spi_mode_t as uint8_t) */ + uint8_t spi_mode; + /* flash frequency (esp_image_spi_freq_t as uint8_t) */ + uint8_t spi_speed: 4; + /* flash chip size (esp_image_flash_size_t as uint8_t) */ + uint8_t spi_size: 4; + uint32_t entry_addr; + /* WP pin when SPI pins set via efuse (read by ROM bootloader, the IDF bootloader uses software to configure the WP + * pin and sets this field to 0xEE=disabled) */ + uint8_t wp_pin; + /* Drive settings for the SPI flash pins (read by ROM bootloader) */ + uint8_t spi_pin_drv[3]; + /* Reserved bytes in ESP32 additional header space, currently unused */ + uint8_t reserved[11]; + /* If 1, a SHA256 digest "simple hash" (of the entire image) is appended after the checksum. Included in image length. This digest + * is separate to secure boot and only used for detecting corruption. For secure boot signed images, the signature + * is appended after this (and the simple hash is included in the signed data). */ + uint8_t hash_appended; +} __attribute__((packed)) esp_image_header_t; + +typedef struct { + uint32_t load_addr; + uint32_t data_len; +} esp_image_segment_header_t; + + +/** + * @brief Return a memory area name given an address. + * @param addr The address to decode. + * @return A string description of the memory area. + */ +static const char* area(uint32_t addr) { + if (addr >= 0x3FF80000 && addr <= 0x3FF81FFF) { + return "RTS Fast (8K)"; + } + if (addr >= 0x3FF82000 && addr <= 0x3FF8FFFF) { + return "Reserved"; + } + if (addr >= 0x3FF90000 && addr <= 0x3FF9FFFF) { + return "Internal ROM 1 (64K)"; + } + if (addr >= 0x3FFA0000 && addr <= 0x3FFADFFF) { + return "Reserved"; + } + if (addr >= 0x3FFAE000 && addr <= 0x3FFDFFFF) { + return "Internal SRAM 2 (200K)"; + } + if (addr >= 0x3FFE0000 && addr <= 0x3FFFFFFF) { + return "Internal SRAM 1 (128K)"; + } + if (addr >= 0x40000000 && addr <= 0x40007FFF) { + return "Internal ROM 0 0 (32K)"; + } + if (addr >= 0x40008000 && addr <= 0x4005FFFF) { + return "Internal ROM 0 (352K)"; + } + if (addr >= 0x40060000 && addr <= 0x4006FFFF) { + return "Reserved"; + } + if (addr >= 0x40070000 && addr <= 0x4007FFFF) { + return "Internal SRAM 0 (64K)"; + } + if (addr >= 0x40080000 && addr <= 0x4009FFFF) { + return "Internal SRAM 0 (128K)"; + } + if (addr >= 0x400A0000 && addr <= 0x400AFFFF) { + return "Internal SRAM 1 (64K)"; + } + if (addr >= 0x400B0000 && addr <= 0x400B7FFF) { + return "Internal SRAM 1 (32K)"; + } + if (addr >= 0x400B8000 && addr <= 0x400BFFFF) { + return "Internal SRAM 1 (32K)"; + } + if (addr >= 0x400C0000 && addr <= 0x400C1FFF) { + return "RTC FAST Memory (8K)"; + } + if (addr >= 0x50000000 && addr <= 0x50001FFF) { + return "RTC SLOW Memory (8K)"; + } + if (addr >= 0x3F400000 && addr <= 0x3F7FFFFF) { + return "External Memory (Data)"; + } + if (addr >= 0x3F800000 && addr <= 0x3FBFFFFF) { + return "External Memory (Data)"; + } + if (addr >= 0x400C2000 && addr <= 0x40BFFFFF) { + return "External Memory (Instruction)"; + } + return "Un-described"; +} //area + + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s ESP32_BIN_FILE.bin\n", argv[0]); + return 0; + } + + const char* fileName = argv[1]; + esp_image_header_t header; + esp_image_segment_header_t imageSegmentHeader; + + FILE* file = fopen(fileName, "rb"); + if (file == nullptr) { + printf("Failed to open file %s\n", fileName); + return 0; + } + + size_t count = fread(&header, sizeof(esp_image_header_t), 1, file); + if (count != 1) { + printf("Failed to read esp_image_header_t\n"); + return 0; + } + + if (header.magic != 0xE9) { + printf("Failed to find magic number (0xE9) in BIN file header.\n"); + return 0; + } + + printf("Dump of ESP32 binary file: %s\n", fileName); + printf("magic: 0x%x, segment_count: %d, entry_addr: 0x%x - %s, hash_appended: %d\n", + header.magic, header.segment_count, header.entry_addr, area(header.entry_addr), header.hash_appended); + + printf("\n"); + printf("Seg | Start | End | Length | Area\n"); + printf("----+------------+------------+-------------------+-------------------------------\n"); + for (int i=0; i { + resolveMoreData = resolveA; // The promise is resolved when append is called to provide more data. + }); + } // moreData + + + /** + * Get the next SLIP data packet. + * Assume that the serial data received is a sequence of packets. This function returns a promise that + * is resolved when a packet is available for processing. + */ + async function getNextDataPacket() { + var p = new Promise(async (resolve, reject) => { + console.log("getNextDataPacket>>"); + while(true) { // Keep looping until we have a data packet. + if (DataPacket().containsSLIPPacket(serialDataBuffer)) { // Does the buffer now contain a SLIP packet? + var newDataPacket = DataPacket(); // Create a new data packet + serialDataBuffer = newDataPacket.setSLIPPacket(serialDataBuffer); // Load the data packet with the raw serial data. + console.log("Got a new data packet: %s\n%s", + newDataPacket.toString(), hexy.hexy(newDataPacket.getData())); + resolve(newDataPacket); + return; + } + await moreData(); // We didn't find a data packet in our data, so wait for more data. + } // We will loop forever waiting for packets. + }); + return p; // Return the promise that is resolved when we have a data packet. + } // getNextDataPacket + + + empty(); // Initialize / empty the serial data. + return { + "append": append, + "empty": empty, + "getNextDataPacket": getNextDataPacket + } +} // SerialData + + +/** + * A data packet encapsulates a command or response. + */ +function DataPacket() { + var dataPacket = Buffer.allocUnsafe(0); + + /** + * Examine the data buffer looking for a slip trailer (0xc0). If found, we return the index + * at which it was found. If not found, we return -1. + * @param {Buffer} data The data in which we are looking for the slip trailer. + * @returns The original of the slip trailer or -1 if not found. + */ + function getSLIPTrailer(data) { + for (i=1; i 0; + } // containsSLIPPacket + + + /** + * Process the data passed in that is SLIP encoded and build a parsed data packet. + * @param {Buffer} data The data to build a packet from. This will be raw data received from UART. + * @returns The unprocessed data. + */ + function setSLIPPacket(data) { + console.log("setSLIPPacket:\n%s", hexy.hexy(data)); + assert(data); // Assert if we have been presented no data. + assert(data.length > 0); // Assert that the data length is not 0. + assert(data[0] == 0xc0); // Assert if the first bytes isn't a SLIP marker. + + var end = getSLIPTrailer(data); // Get the end marker for the SLIP packet. + if (end == -1) { + throw "No SLIP trailer in data we are to process."; + } + assert(end > 0); + + dataPacket = Buffer.allocUnsafe(data.length); // Our data will be no MORE (but likely less) than data.length bytes in size. + for (var i=1, j=0; i 0xc0 + dataPacket[j] = 0xc0; + i++ + } else if (data[i] == 0xdb && data[i+1] == 0xdd) { // 0xdb 0xdd -> 0xdb + dataPacket[j] = 0xdb; + i++; + } else { + dataPacket[j] = data[i] + } + } // End loop over data. + + // We now have a buffer (dataPacket) that contains our decoded data but will be too large. However, j contains the number of bytes we want. + var temp = Buffer.allocUnsafe(j); + dataPacket.copy(temp, 0, 0, j); // Copy dataPacket[0..j-1] -> temp + dataPacket = temp; // Set datapacket to the shrunk buffer. + + // We have now processed what we wanted from the incoming data and now we must return the unused data. + i++; + var unprocessedLength = data.length - i; + temp = Buffer.allocUnsafe(unprocessedLength); + data.copy(temp, 0, i); // copy data[i..] -> temp + + console.log("setSLIPPacket: New data has length: %d, returning unused buffer of size: %d", dataPacket.length, temp.length); + if (temp.length > 0) { + console.log("Unused:\n%s", hexy.hexy(temp)); + } + return temp; + } // setSLIPPacket + + + /** + * Internally we have a dataPacket. We want to return a SLIP encoded buffer from that packet. + * @returns A SLIP encoded representation of our data Packet. + */ + function getSLIPPacket() { + var temp = Buffer.allocUnsafe(dataPacket.length * 2); + var i = 0; // The variable called "i" will be the current byte being written into the slip encoded output. + temp[i] = 0xc0; + i++; + for (j=0; j temp2 + temp2[i] = 0xc0; // Terminate with a slip trailder. + return temp2; + } // getSLIPPacket + + /** + * Set the data. + * @param {*} data + */ + function setData(data) { + dataPacket = data; + } + + function getData() { + return dataPacket; + } + + function toString() { + var result; + if (dataPacket[0] == 0) { + result = "Request: "; + result += "command: " + commandToString(dataPacket[1]); + result += ", size: " + dataPacket.readUInt16LE(2); + result += ", calculatedSize: " + (dataPacket.length - 8); + } else if (dataPacket[0] == 1) { + result = "Response: "; + result += "command: " + commandToString(dataPacket[1]); + result += ", size: " + dataPacket.readUInt16LE(2); + result += ", calculatedSize: " + (dataPacket.length - 8); + result += ", value: " + dataPacket.readUInt32LE(4).toString(16); + // For the ESP32 + // Size - 4 = status + // Size - 3 = error + result += ", status: " + dataPacket[dataPacket.length-2]; + result += ", errorCode: " + dataPacket[dataPacket.length-1]; + } else { + result = "Unknown command: " + dataPacket[0]; + } + return result; + } // toString + + return { + "containsSLIPPacket": containsSLIPPacket, // Does the data passed in define contain a SLIP packet? + "getSLIPPacket": getSLIPPacket, + "setSLIPPacket": setSLIPPacket, + "getData": getData, + "setData": setData, + "toString": toString + } // End of return +} // DataPacket + + +/** + * Convert a command code to a string representation. + * @param {String} command The command to be converted. + * @returns A string representation of the command. + */ +function commandToString(command) { + switch(command) { + case COMMAND_FLASH_BEGIN: + return "COMMAND_FLASH_BEGIN"; + case COMMAND_FLASH_DATA: + return "COMMAND_FLASH_DATA"; + case COMMAND_FLASH_END: + return "COMMAND_FLASH_END"; + case COMMAND_MEM_BEGIN: + return "COMMAND_MEM_BEGIN"; + case COMMAND_MEM_END: + return "COMMAND_MEM_END"; + case COMMAND_MEM_DATA: + return "COMMAND_MEM_DATA"; + case COMMAND_SYNC: + return "COMMAND_SYNC"; + case COMMAND_WRITE_REG: + return "COMMAND_WRITE_REG"; + case COMMAND_READ_REG: + return "COMMAND_READ_REG"; + case COMMAND_SPI_SET_PARAMS: + return "COMMAND_SPI_SET_PARAMS"; + case COMMAND_SPI_FLASH_MD5: + return "COMMAND_SPI_FLASH_MD5"; + } + return "Unknown command: " + command; +} // commandToString + + +function setFlags(options) { + return new Promise((resolve, reject) => { + port.set(options, () => { + resolve(); + }); + }); +} // setFlags + + +function delay(msecs) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, msecs); + }); +} // delay + + +/** + * Write the command header + * @param command // The ESP32 flash loader command + * @param data // The data to send. + * @param checksum // [Optional] The checksum value + * @returns + */ +async function buildAndSendRequest(command, data, checksum = 0) { + console.log("buildAndSendRequest: %s, checksum: 0x%s, dataLength: %d", commandToString(command), checksum.toString(16), data.length); + var buf = Buffer.allocUnsafe(8 + data.length); // Create a buffer used to hold the command. + buf.writeUInt8(0, 0); // Flag as a request [0] + buf.writeUInt8(command, 1); // Write the command [1] + buf.writeUInt16LE(data.length, 2); // Write the size [2-3] + buf.writeUInt32LE(checksum, 4); // Write the checksum [4-7] + data.copy(buf, 8); // Copy data[0..] to buf[8..] + var dataPacket = DataPacket(); // Create a new data packet object + dataPacket.setData(buf); // Populate the data packet + await portWrite(dataPacket.getSLIPPacket()); // Send the data packet to the ESP32. +} // buildAndSendRequest + + +/** + * Promise to flush all pending transmitting data before resolution. + * @returns A promise that is resolved when the transmission data has all been sent. + */ +async function drain() { + return new Promise((resolve, reject) => { + port.drain(() => {resolve()}); + }); +} // drain + + +/** + * Promise to discard all un-transmitted output data and discard all unread input data. + * @returns A promise that is resolved when input/output data is discarded. + */ +function flush() { + return; + console.log("Flushing..."); + return new Promise((resolve, reject) => { + port.flush(()=>{resolve()}); + }); +} // flush + +// We send a UART command request. The thinking is that we will now start to receive a UART command response. +// The response won't arrive as a unit but will instead be dribbled in over time asynchronously. As such, we need to maintain +// state to process it. A response is composed of a response header and response payload. The response header (as seen by +// UART) will contain: +// 0xC0 - SLIP header +// 0x01 - Direction. Should be 0x01 for a response. +// 0x?? - Command +// 0x?? 0x?? - Size of payload data. +// 0x?? 0x?? 0x?? 0x?? - Value. Used by some commands (NOT ALL)!! +// 0x?? ... - Data as indicated by the size command. +// 0x?? 0x?? 0x?? 0x?? - Outcome +// 0xC0 - SLIP trailer +function ProcessResponse() { + var command; + var data; + var dataSize; + var value; + var status; + var errorCode; + var resolve; + var reject = null; + var promise = null; + + var timeoutId; + + + async function getResponse() { + console.log(">> ProcessResponse.getResponse()") + + promise = new Promise(async (resolveA, rejectA) => { + resolve = resolveA; + reject = rejectA; + timeoutId = setTimeout(()=>{ + console.log("getResponse timeout!"); + reject(); + }, 1000); + var dp = await serialData.getNextDataPacket(); + console.log(" .. getResponse ... we think we have a new datapacket!"); + processPacket(dp.getData()); + resolve(); + }); + return promise; + } + + function processPacket(inputData) { + assert(inputData); + assert(inputData.length > 0); + console.log("Received a packet, length %d:\n%s", inputData.length, hexy.hexy(inputData)); + // 0 - direction + // 1 - command + // [2-3] - size + // [4-7] - value + // [8-(length-3)] - data + // [length-2] - status + // [length-1] - error code + if (inputData.readUInt8(0) != 0x01) { + throw "Expected direction to be 0x01 for a response."; + } + command = inputData.readUInt8(1); + dataSize = inputData.readUInt16LE(2); + value = inputData.readUInt32LE(4); + data = Buffer.allocUnsafe(inputData.length - 8 - 2); + inputData.copy(data, 0, 8, inputData.length - 2); + status = inputData.readUInt8(inputData.length - 2); + erorCode = inputData.readUInt8(inputData.length - 1); + clearTimeout(timeoutId); + } // processPacket + + return { + getResponse: getResponse, + getData: function() { + return data; + } + }; +} // ProcessResponse + + +/** + * Send a SYNC command and wait for the response. + */ +async function doSync() { + console.log("Sending sync"); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(36); // The sync message is 0x07 0x07 0x12 0x20 0x55 (repeated 32 times) + buf.writeUInt8(0x07, 0); + buf.writeUInt8(0x07, 1); + buf.writeUInt8(0x12, 2); + buf.writeUInt8(0x20, 3); + for (var i=4; i<36; i++) { + buf.writeUInt8(0x55, i); + } + await buildAndSendRequest(COMMAND_SYNC, buf); + console.log("response: " + response); + try { + await response.getResponse(); + resolve(); + } + catch(e) { + console.log("Failed to get SYNC command response: " + e); + reject(); + } + }); + return p; +} // doSync + + +/** + * Calculate the checksum of the data. + * The current algorithm is the seed 0xEF XORed with each of the bytes of data. + * @param {Buffer} data The data against which the checksum is to be calculated. + * @returns The checksum value. + */ +function calculateChecksum(data) { + var checksum = 0xef; + for (var i=0; i> memEnd: executeFlag: %d, entrypointAddress: 0x%s", executeFlag, entrypointAddress.toString(16)); + var p = new Promise(async function (resolve, reject) { + await flush(); // Discard any pending input or output. + var buf = Buffer.allocUnsafe(2*4); // Allocate the payload which is 2 * 32bit words + buf.writeUInt32LE(executeFlag, 0); // Execute flag. + buf.writeUInt32LE(entrypointAddress, 4); // Entry point address. + await buildAndSendRequest(COMMAND_MEM_END, buf); + await response.getResponse(); // Wait for a response. + resolve(); // Resolve the promise. + }); + return p; + } // memEnd + + /** + * Determine if we have sent all the data for the given MEM_BEGIN transaction. + * @returns True if we have sent all the data we need to send. + */ + function allDataSent() { + // We maintain state in a number of variables. The variable called sizeWritten is the number of bytes that we have written to + // the ESP32. The variable called totalSize is the number of bytes that we expect to write. The notion of all data sent + // will occur when the sizeWritten is equal to totalSize. + return sizeWritten >= totalSize; + } // allDataSent + + + /** + * Write a MEM_DATA command. + * This command should follow a MEM_BEGIN command. If transmits a unit of data that is written directly into + * the ESP32 RAM. The address in RAM is supplied by the MEM_BEGIN command and previous MEM_DATA calls. + * @param {Buffer} data + */ + async function memData(data) { + console.log(">> memData: sequenceId: %d", sequenceNumber); + var sizeRemaining = totalSize - sizeWritten; // Calculate the amount of data still expected. + var offsetIntoData = sequenceNumber * MEM_PACKET_SIZE; // Calculate the offset into the data from which to send. + var sizeToWrite = sizeRemaining > MEM_PACKET_SIZE?MEM_PACKET_SIZE:sizeRemaining; // Calculate how large this unit should be. + var buf2 = Buffer.allocUnsafe(4*4 + data.length); // Allocate storage for the command preamble. + buf2.writeUInt32LE(sizeToWrite, 0); // Size of data to be sent. + buf2.writeUInt32LE(sequenceNumber, 4); // Current sequence number. + buf2.writeUInt32LE(0, 8); // Zero. + buf2.writeUInt32LE(0, 12); // Zero. + data.copy(buf2, 16); + //var buf = Buffer.concat([buf2, data]); + //console.log("Here is the data we are sending:\n%s", hexy.hexy(buf)); + + var p = new Promise(async function(resolve, reject) { + console.log("+----------+"); + console.log("| MEM_DATA |"); + console.log("+----------+"); + await flush(); // Discard any untransmitted and unreceived data. // End the SLIP communication. + await buildAndSendRequest(COMMAND_MEM_DATA, buf2, calculateChecksum(data)); + sequenceNumber ++; // Increment the sequence number. + sizeWritten += sizeToWrite; // Updae the size of the data that we have now written. // Ensure that all the transmitted data is now outbound. + await response.getResponse(); // Await a response. + resolve(); // Indicate that the command has been completed. + }); + return p; + }; // memData + + + /** + * Transmit a buffer of data to a given address within ESP32 RAM. + * @param {Buffer} data The data to be written into ESP32 RAM. + * @param {integer} address The address that data will be written to. + */ + async function send(data, address) { + // If the data is not 32bit aligned, expand to 32 bits. + if (data.length % 4 != 0) { + let buf = Buffer.allocUnsafe((data.length + 4) & 0xFFFFFFFC).fill(0xff); + data.copy(buf); + data = buf; + } + console.log(">> send data of size: %d, to RAM address 0x%s", data.length, address.toString(16)); + await memBegin(data.length, address); + while(!allDataSent()) { + await memData(data); + } + }; // send + + + return { + "send": send, + "end": memEnd + }; +} // MemCommands + + +function portWrite(data) { + console.log("Writing data down port, length: %d", data.length) + return new Promise((resolve, reject) => { + port.write(data, async ()=> { + await drain; + resolve() + }); + }); +} // portWrite + + +/** + * Process the handling of flash commands. + */ +function FlashCommands() { + var sequenceNumber; // The current sequence number. + var totalSize; // The total number of bytes we expect to send. + var sizeWritten; // The number of bytes we have written so far. + + const FLASH_PACKET_SIZE = 0x4000; // 16K = 16384 bytes + + + async function flashBegin(size, address) { + console.log("Flash Begin: size: %d, address: 0x%s", size, address.toString(16)); + var p = new Promise(async function (resolve, reject) { + console.log("+-------------+"); + console.log("| FLASH_BEGIN |"); + console.log("+-------------+"); + await flush(); + var buf = Buffer.allocUnsafe(4*4); + buf.writeUInt32LE(size, 0); // Size of data to be sent/erased. + buf.writeUInt32LE(Math.ceil(size/FLASH_PACKET_SIZE), 4); // Number of data packets to be sent. + buf.writeUInt32LE(FLASH_PACKET_SIZE, 8); // Size of each data packet. + buf.writeUInt32LE(address, 12); // Offset in memory to start writing. + await buildAndSendRequest(COMMAND_FLASH_BEGIN, buf); + await response.getResponse(); + resolve(); + }); + return p; + } // flashBegin + + + async function flashData(data, sequenceNumber) { + console.log(">> flashData: sequenceId: %d, size: %d", sequenceNumber, data.length); + if (data.length < FLASH_PACKET_SIZE) { + var temp = Buffer.alloc(FLASH_PACKET_SIZE, 0xff); + data.copy(temp); + data = temp; + } + var buf2 = Buffer.alloc(4*4 + data.length); // Allocate storage for the command preamble. + buf2.writeUInt32LE(data.length, 0); // Size of data to be sent. + buf2.writeUInt32LE(sequenceNumber, 4); // Current sequence number. + buf2.writeUInt32LE(0, 8); + buf2.writeUInt32LE(0, 12); + data.copy(buf2, 16); + //var buf = Buffer.concat([buf2, data]); + //console.log("Here is the data we are sending:\n%s", hexy.hexy(buf)); + + var p = new Promise(async function(resolve, reject) { + console.log("+------------+"); + console.log("| FLASH_DATA |"); + console.log("+------------+"); + await flush(); // Discard any untransmitted and unreceived data. + await buildAndSendRequest(COMMAND_FLASH_DATA, buf2, calculateChecksum(data)); + await response.getResponse(); // Await a response. + resolve(); // Indicate that the command has been completed. + }); + return p; + }; // flashData + + + /** + * Send the FLASH_END command. + * @param {Integer} command + * @returns A promise that is resolved when the FLASH_END command completes. + */ + function flashEnd(command) { + console.log("+-----------+"); + console.log("| FLASH_END |"); + console.log("+-----------+"); + console.log(">> memEnd: command: %d", command); + var p = new Promise(async function (resolve, reject) { + await flush(); // Discard any pending input or output. + var buf = Buffer.allocUnsafe(4); // Allocate the payload which is one 32bit word. + buf.writeUInt32LE(command, 0); // Execute flag. + await buildAndSendRequest(COMMAND_FLASH_END, buf); + await response.getResponse(); // Wait for a response. + resolve(); // Resolve the promise. + }); + return p; + } // flashEnd + + /** + * Transmit a buffer of data to a given address within ESP32 FLASH. + * @param {Buffer} data The data to be written into ESP32 FLASH. + * @param {integer} address The address that data will be written to. + */ + async function send(data, address) { + return new Promise(async (resolve, reject) => { + // If the data is not 32bit aligned, expand to 32 bits. + if (data.length % 4 != 0) { + let buf = Buffer.allocUnsafe((data.length + 4) & 0xFFFFFFFC).fill(0xff); + data.copy(buf); + data = buf; + } + console.log(">> send flash data of size: %d, to flash address 0x%s", data.length, address.toString(16)); + await flashBegin(data.length, address); + + sequenceNumber = 0; // The sequence number of the next FLASH_DATA transmission. + totalSize = data.length; // The size of the data we are going to be sending. + sizeWritten = 0; // The size of the data we have written so far. + + while(sizeWritten < totalSize) { + var sizeRemaining = totalSize - sizeWritten; // Calculate the amount of data still expected. + var offsetIntoData = sequenceNumber * FLASH_PACKET_SIZE; // Calculate the offset into the data from which to send. + var sizeToWrite = sizeRemaining > FLASH_PACKET_SIZE?FLASH_PACKET_SIZE:sizeRemaining; // Calculate how large this unit should be. + var tempBuf = Buffer.allocUnsafe(sizeToWrite); + data.copy(tempBuf, 0, offsetIntoData, offsetIntoData+sizeToWrite); + await flashData(tempBuf, sequenceNumber); + sequenceNumber++; + sizeWritten += sizeToWrite; + } + await flashEnd(1); + resolve(); + }); + }; // send + + return { + "send": send, + "end": flashEnd + }; + +} // FlashCommands + + +/** + * Upload the flasher application. + * @returns A Promise that is fulfilled when the flasher has been uploaded. + */ +async function uploadFlasher() { + // We read a JSON file that has been prepared to contain the flasher application. This JSON contains: + // { + // textAddress: - A string representation (hex) of the memory address into which the text data should be loaded. + // dataAddress: - A string representation (hex) of the memory address into which the data data should be loaded. + // textData: - A base64 encoded representation of the text data. + // dataData: - A base64 encoded representation of the data data + // entryPoint: - A string representation of the entry point (hex) for execution. + // } + // + return new Promise(async (resolve, reject)=>{ + console.log(">> uploadFlasher"); + // Load the flasher from the local file. + var flasherDataRaw = fs.readFileSync("esptool/flasher_stub/build/flasher_stub.json", "utf8"); + var flasherData = JSON.parse(flasherDataRaw); + + flasherData.textData = Buffer.from(flasherData.textData, "base64"); + flasherData.dataData = Buffer.from(flasherData.dataData, "base64"); + console.log("Text data length: %d, Data data length: %d", flasherData.textData.length, flasherData.dataData.length); + + await MemCommands().send(flasherData.textData, parseInt(flasherData.textAddress, 16)); + await MemCommands().send(flasherData.dataData, parseInt(flasherData.dataAddress, 16)); + await MemCommands().end(0, parseInt(flasherData.entryPoint, 16)); + console.log("<< uploadFlasher complete"); + resolve(); + }); // End of promise +} // uploadFlasher + + +/** + * Write a value to an ESP32 register defined. + * @param {Integer} address The address of the register. + * @param {Integer} value The value to be written to the register. + * @returns A promise that is fulfilled when the register has been written. + */ +function writeReg(address, value) { + console.log("+-----------+"); + console.log("| WRITE_REG |"); + console.log("+-----------+"); + console.log(">> Writing register: Command: 0x%s, Address: 0x%s, value: 0x%s", + COMMAND_WRITE_REG.toString(16), address.toString(16), value.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4*4); + buf.writeUInt32LE(address, 0); + buf.writeUInt32LE(value, 4); + buf.writeUInt32LE(0xffffffff, 8); // Mask + buf.writeUInt32LE(0, 12); // Delay + await buildAndSendRequest(COMMAND_WRITE_REG, buf); + await response.getResponse(); + resolve(); + }); + return p; +} // writeReg + + +/** + * Set the SPI configuration parameters. + * @param {Integer} size The size of SPI flash. + * @returns A promise that is fulfilled when the SPI parameters have been set. + */ +function spiSetParams(size) { + console.log("+----------------+"); + console.log("| SPI_SET_PARAMS |"); + console.log("+----------------+"); + console.log(">> SPI Set Params: Command: 0x%s, size:%d", + COMMAND_SPI_SET_PARAMS.toString(16), size); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(6*4); + buf.writeUInt32LE(0, 0); // id + buf.writeUInt32LE(size, 4); // Total size + buf.writeUInt32LE(64*1024, 8); // block size + buf.writeUInt32LE(4*1024, 12); // sector size + buf.writeUInt32LE(256, 16); // page size + buf.writeUInt32LE(0xffff, 20); // status mask + await buildAndSendRequest(COMMAND_SPI_SET_PARAMS, buf); + await response.getResponse(); + resolve(); + }); + return p; +} // spiSetParams + + +/** + * Erase the flash memory. + * @returns A promise that is fulfilled when the flash memory has been erased. + */ +function eraseFlash() { + console.log("+-------------+"); + console.log("| ERASE_FLASH |"); + console.log("+-------------+"); + console.log(">> Erase Flash: Command: 0x%s", COMMAND_ERASE_FLASH.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(0); + await buildAndSendRequest(COMMAND_ERASE_FLASH, buf); + await response.getResponse(); + resolve(); + }); + return p; +} // spiSetParams + + +/** + * Read an ESP32 register. + * @param {Integer} address The address of the register to be read. + * @returns The result of reading the register. + */ +function readReg(address) { + console.log(">> Reading register: Command: 0x%s, Address: 0x%s", COMMAND_READ_REG.toString(16), address.toString(16)); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4); + buf.writeUInt32LE(address, 0); + await buildAndSendRequest(COMMAND_READ_REG, buf); + var result = await response.getResponse(); + console.log("readReg: %O", result); + resolve(result); + }); + return p; +} // readReg + + + +/** + * Get the MD5 hash of an area of flash memory. + * @param {Integer} address The address of the flash memory. + * @param {Integer} size The size of the flasg memory. + * @returns The MD5 hash value. + */ +function spiFlashMD5(address, size) { + console.log("+---------------+"); + console.log("| SPI_FLASH_MD5 |"); + console.log("+---------------+"); + console.log(">> spiFlashMD5: Command: 0x%s, Address: 0x%s, Size: %d", COMMAND_SPI_FLASH_MD5.toString(16), address.toString(16), size); + var p = new Promise(async function(resolve, reject) { + await flush(); + var buf = Buffer.allocUnsafe(4 * 4); + buf.writeUInt32LE(address, 0); + buf.writeUInt32LE(size, 4); + buf.writeUInt32LE(0, 8); + buf.writeUInt32LE(0, 12); + await buildAndSendRequest(COMMAND_SPI_FLASH_MD5, buf); + await response.getResponse(); + console.log("spiFlashMD5 result:\n%s", hexy.hexy(response.getData())); + resolve(response.getData()); + }); + return p; +} // spiFlashMD5 + + +/** + * Read the value of an eFuse. + * @param {Integer} fuseNumber + * @returns The value of the eFuse. + */ +function readEfuse(fuseNumber) { + console.log(">> readEFuse: %d", fuseNumber); + return readReg(EFUSE_REG_BASE + (4 * fuseNumber)); +} // readEfuse + + +/** + * The ESP32 needs to be in a "download" mode in order to process commands. For many boards, a sequence of + * DTR/RTS UART twiddles can be sends to achieve that goal. This function performs that task. + * + * @returns A Promise that is fulfilled when the ESP32 is in download mode. + */ +function enterDownloadMode() { + console.log(">> enterDownloadMode: Enter download mode, esp32r0Delay = " + esp32r0Delay); + return new Promise((resolve, reject) => { + setFlags({ dtr: false, rts: true }).then(()=>{ + return delay(100); + }).then(() => { + if (esp32r0Delay) { + return delay(1200); + } + return Promise.resolve(); + }).then(()=> { + return setFlags({ dtr: true, rts: false }); + }).then(() => { + if (esp32r0Delay) { + return delay(400); + } + return Promise.resolve(); + }).then(()=> { + return delay(50); + }).then(()=> { + return setFlags({ dtr: false, rts: false }); + }).then(() => { + resolve(); + }); + }); +} // enterDownloadMode + + +/** + * Flash the content of the file supplied by filename into ESP32 flash specified by the address. + * @param {Integer} address The address in flash to write the file. + * @param {String} fileName The name of the file to read from. + */ +async function flashFile(address, fileName) { + console.log("Flashing file %s to address 0x%s", fileName, address.toString(16)); + var fileData = fs.readFileSync(fileName); + console.log("Read file ... size is %d, md5: %s", fileData.length, md5(fileData)); + await FlashCommands().send(fileData, address); + console.log("Flash File complete"); + return fileData.length; +} // flashFile + + +async function sleep(interval) { + return new Promise((resolve) => { + setTimeout(() => {resolve()}, interval); + }); +} + + +console.log("Start!"); +if (argv._.length%2 != 0) { + console.log("Wrong number of args"); + return; +} +response = ProcessResponse(); +console.log("Response created: " + response); +port.open(async (err)=>{ + if (err) { + console.log("Error opening: " + err); + return; + } + console.log("Open!"); + await enterDownloadMode(); + //var p = Promise.resolve(); + console.log("We should now be in ESP32 download mode!"); + serialData.empty(); + while(1) { + try { + await doSync(); + break; + } + catch(e) { + console.log(e); + } + } + await sleep(1000); + serialData.empty(); + console.log("+----------------+") + console.log("| Sync Complete! |"); + console.log("+----------------+") + + await readEfuse(3); + console.log("Read fuse completed!"); + await uploadFlasher(); + serialData.empty(); + console.log("Flasher upload completed and running!"); + /* + await writeReg(0x6000202c, 0x00000017); + await writeReg(0x6000201c, 0x90000000); + await writeReg(0x60002024, 0x9f000070); + await writeReg(0x60002080, 0x00000000); + await writeReg(0x60002000, 0x00040000); + await writeReg(0x6000201c, 0x80000040); + await writeReg(0x60002024, 0x00000000); + */ + await spiSetParams(0x400000); + + // Walk through each of the files presented and upload them. The input on the command line should be of the form: + // address file [address file]* + + for (var i=0; i { + serialData.append(data); // Append the new data into the serial buffer. +}); + +console.log("Init done"); diff --git a/tools/sdkconfig_compare/README.md b/tools/sdkconfig_compare/README.md new file mode 100644 index 00000000..78879264 --- /dev/null +++ b/tools/sdkconfig_compare/README.md @@ -0,0 +1 @@ +This is a node.js application which takes as input two files. Each file should be an ESP-IDF `sdkconfig` file. The tool compares the two files and logs the logical distinctions between them. For example, if one file has an entry while the other doesn't, that will be logged. If one file has an entry with a value that is different from the other, that too will be logged. \ No newline at end of file diff --git a/tools/sdkconfig_compare/sdkconfig_compare.js b/tools/sdkconfig_compare/sdkconfig_compare.js new file mode 100644 index 00000000..7ad29ed2 --- /dev/null +++ b/tools/sdkconfig_compare/sdkconfig_compare.js @@ -0,0 +1,95 @@ +// Node.js application for comparing two ESP-IDF configuration files (sdkconfig) +const fs = require("fs"); // Require the file system processing library +const readline = require("readline"); // Require the readline processing library + +// buildMap +// Read the sdkconfig file specified by fileName and produce a map of the name/value pairs contained +// within. A Promise is returned that is fulfilled when the file has been read. +function buildMap(fileName) { + const promise = new Promise(function(resolve, reject) { + var readStream = fs.createReadStream(fileName); + readStream.on("error", (err) => { + reject(err); + }); + const map = {}; + + const lineReader = readline.createInterface({ + input: readStream, + crlfDelay: Infinity + }); + + // Called when a new line has been read from the file. + lineReader.on("line", (line) => { + line = line.trim(); // Trim whitespace from the line. + + if (line.length == 0) { // Ignore empty lines + return; + } + if (line.startsWith("#")) { // Ignore comment lines + return; + } + + const parts = line.split("="); // Split the line into parts separated by the '=' character. + if (map.hasOwnProperty(parts[0])) { + console.log(`Odd ... we found ${parts[0]} twice.`); + } + map[parts[0]] = parts[1]; // Populate the map element. + }); // on(line) + + // Called when all the lines from the file have been consumed. + lineReader.on("close", () => { + resolve(map); + }); // on(close) + + }); + return promise; +} // buildMap + + +const args = process.argv; +if (args.length != 4) { + console.log("Usage: node sdkconfig_compare file1 file2"); + process.exit(); +} +const file1 = args[2]; +const file2 = args[3]; +buildMap(file1).then((result)=> { + buildMap(file2).then((result2) => { + + // Three passes + // In A and not B + // in B and not A + // value different in A and B + for (const prop in result) { + if (result.hasOwnProperty(prop)) { + if (!result2.hasOwnProperty(prop)) { + console.log(`${prop} in ${file1} but not in ${file2}`); + } + } + } + + for (const prop in result2) { + if (result2.hasOwnProperty(prop)) { + if (!result.hasOwnProperty(prop)) { + console.log(`${prop} in ${file2} but not in ${file1}`); + } + } + } + + for (const prop in result) { + if (result.hasOwnProperty(prop)) { + if (result2.hasOwnProperty(prop)) { + if (result[prop] != result2[prop]) { + console.log(`${prop} values different "${result[prop]}" vs "${result2[prop]}"`); + } + } + } + } + }).catch((err) => { + console.log(err); + process.exit(); + }); +}).catch((err) => { + console.log(err); + process.exit(); +}); \ No newline at end of file