From c0d46ad17922e2f844c5adb4ddfebea8af0cdf7e Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 19 Apr 2022 16:06:05 +0100 Subject: [PATCH] Part of #113. Added Mesh Modem client and RabbitMQ Link Significant changes:- - Added herald/mesh/modem_client.cpp/h and MeshModem class (shell only) - Added herald/mesh/modem_rabbitmq.cpp/h to link MeshModem to RabbitMQ - Added mesh_modem_rmq_adapter sample app to provide containerisable and locally runnable command link MeshModem to RabbitMQ link - MeshModemRabbitMQAdapter compiles successfully - Currently untested Signed off by: Adam Fowler --- CMakeLists.txt | 3 +- README.md | 114 +++--- herald-mesh-modem/.clang-format | 30 ++ .../.vscode/c_cpp_properties.json | 41 ++ herald-mesh-modem/.vscode/launch.json | 226 +++++++++++ herald-mesh-modem/.vscode/settings.json | 86 +++++ herald-mesh-modem/CMakeLists.txt | 117 ++++++ herald-mesh-modem/app.overlay | 20 + herald-mesh-modem/prj.conf | 113 ++++++ herald-mesh-modem/src/main.cpp | 167 ++++++++ herald-mesh-proxy/CMakeLists.txt | 21 - herald-mesh-proxy/src/main.cpp | 15 - herald-mesh-relay/prj.conf | 1 + herald-mesh-rmq-adapter/CMakeLists.txt | 57 +++ herald-mesh-rmq-adapter/README.md | 37 ++ herald-mesh-rmq-adapter/cmake/Findlibuv.cmake | 30 ++ herald-mesh-rmq-adapter/src/main.cpp | 106 +++++ herald/herald.cmake | 4 + herald/include/herald/mesh/modem.h | 60 +++ herald/include/herald/mesh/modem_client.h | 54 +++ herald/include/herald/mesh/modem_codes.h | 152 ++++++++ herald/include/herald/mesh/modem_rabbitmq.h | 241 ++++++++++++ herald/src/mesh/modem.c | 80 ++++ herald/src/mesh/modem_client.cpp | 40 ++ herald/src/mesh/modem_rabbitmq.cpp | 365 ++++++++++++++++++ 25 files changed, 2097 insertions(+), 83 deletions(-) create mode 100644 herald-mesh-modem/.clang-format create mode 100644 herald-mesh-modem/.vscode/c_cpp_properties.json create mode 100644 herald-mesh-modem/.vscode/launch.json create mode 100644 herald-mesh-modem/.vscode/settings.json create mode 100644 herald-mesh-modem/CMakeLists.txt create mode 100644 herald-mesh-modem/app.overlay create mode 100644 herald-mesh-modem/prj.conf create mode 100644 herald-mesh-modem/src/main.cpp delete mode 100644 herald-mesh-proxy/CMakeLists.txt delete mode 100644 herald-mesh-proxy/src/main.cpp create mode 100644 herald-mesh-rmq-adapter/CMakeLists.txt create mode 100644 herald-mesh-rmq-adapter/README.md create mode 100644 herald-mesh-rmq-adapter/cmake/Findlibuv.cmake create mode 100644 herald-mesh-rmq-adapter/src/main.cpp create mode 100644 herald/include/herald/mesh/modem.h create mode 100644 herald/include/herald/mesh/modem_client.h create mode 100644 herald/include/herald/mesh/modem_codes.h create mode 100644 herald/include/herald/mesh/modem_rabbitmq.h create mode 100644 herald/src/mesh/modem.c create mode 100644 herald/src/mesh/modem_client.cpp create mode 100644 herald/src/mesh/modem_rabbitmq.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f79905..4683400 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,5 +22,6 @@ add_subdirectory(heraldns-cli) add_subdirectory(herald) add_subdirectory(herald-tests) add_subdirectory(herald-programmer) -add_subdirectory(herald-mesh-proxy) +add_subdirectory(herald-mesh-relay) +add_subdirectory(herald-mesh-rmq-adapter) add_subdirectory(doxygen) \ No newline at end of file diff --git a/README.md b/README.md index 6d49265..5bfd855 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ This repository contains a Herald Bluetooth Protocol and Payload set of implementations for native platforms. This includes Windows 10 desktop and Nordic Semiconductor or other Zephyr RTOS -capable boards. The board used for development is the -Seeed nRF52840MK-USB-Dongle. +capable boards. The boards used for development are Nordic Semiconductor +nRF52 and 53 development kits and derived dongles. This particular code base is the core Herald library. It is used by all examples, including Wearables (just like the Herald for @@ -15,30 +15,41 @@ This implementation was introduced in Herald v1.2. ## License and Copyright -Copyright 2020-2021 Herald Project Contributors +Copyright 2020-2022 Herald Project Contributors [![License: Apache-2.0](https://img.shields.io/badge/License-Apache2.0-yellow.svg)](https://opensource.org/licenses/Apache-2.0) See LICENSE.txt and NOTICE.txt for details. -## Demonstration apps / libraries +## Ready to use applications -The following apps are available:- +The following applications are downloadable and ready to use on +[Herald Hardware](https://github.com/theheraldproject/herald-hardware) +and other compatible devices. -- herald-tests - Herald core C++ API tests -- herald-venue-beacon - Herald Zephyr RTOS based app for Venue beacons as a replacement/supplement for QR code scanning when visiting business, bars, and restaurants. [See the separate README](./herald-venue-beacon/README.md) -- herald-wearable - Herald Zephyr RTOS based app for wearable devices. The equivalent of the herald-for-ios and herald-for-android demo apps for phones -- heraldns and heraldns-cli and heraldns-tests - Not strictly using the Herald API, but used to test epidemiological/scientific theories that may be merged in to herald's core API in future. Command line utility to simulate social mixing analyses and virus spread. +Embedded Smart Beacon applications (Zephyr RTOS based):- +- [Herald Bluetooth MESH Gateway](herald-mesh-relay) - Create a Smart Building, connect devices, and build open source Smart Hospitals and Internet of Things applications +- [Herald Bluetooth MESH Modem](herald-mesh-modem) - Connect a Herald Bluetooth MESH network to your servers, connecting your organisations applications to the Herald Bluetooth MESH network + +Server applications (Linux based):- +- [Herald Bluetooth MESH RabbitMQ Adapter](herald-mesh-rmq-adapter) - Control a Herald Bluetooth MESH Modem remotely by connecting it to a reliable message broker + +## The Herald API for Embedded devices and C++ apps The following are libraries available for your own projects:- -- herald - Core herald API (include as an external library or call herald.cmake directly to statically compile in to your app) +- [herald](/herald) - Core herald API (include as an external library or call herald.cmake directly to statically compile in to your app). Works on all platforms, but currently targets Windows, Linux and Zephyr for the main API, and Zephyr for the NFC & Bluetooth Low Energy and Bluetooth MESH transport layers. + - Adding a new platform just requires creating 4 classes for the new platform: Context, ConcreteBluetoothReceiver, ConcreteBluetoothTransmitter, SHA256 +- [herald mesh](/herald/include/herald/mesh) provides higher level Zephyr MESH API for creating Smart Hospitals style Internet of Things environments + - A Linux MESH Modem tty interaction adapter layer class is also provided called `herald::mesh::MeshModem`. -The following are 'coming soon':- +## Demonstration apps / libraries + +The following non-production sample and demo apps are also available:- -- herald-mesh-proxy - Rather than a beacon that acts on its own, this mesh proxy provides the same functions of a beacon but also value add functionality from having a Bluetooth 5.0 mesh data network -- herald-programmer - Utility to reprogram Herald based beacons / mesh proxies in the field in a more -user friendly way than development tools (E.g. carehomes, field personnel updates, etc.) +- herald-tests - Herald core C++ API tests +- herald-venue-beacon - Herald Zephyr RTOS based app for Venue beacons as a replacement/supplement for QR code scanning when visiting business, bars, and restaurants. [See the separate README](./herald-venue-beacon/README.md) +- herald-wearable - Herald Zephyr RTOS based app for wearable devices. The equivalent of the herald-for-ios and herald-for-android demo apps for phones to enable Digital Contact Tracing ## Supported / tested platforms @@ -47,18 +58,20 @@ subset of use cases. If you'd like to contribute platform support feel free to p - Windows 64-bit with CLang 10.0+ for VS community edition 2017 - We don't currently support the vsc++ compiler +- Linux 64-bit (We use Manjaro) - Zephyr RTOS / arm with arm-none-eabi-gcc 8.3+ for nRF52840, nRF52832 - We specifically test using the [Nordic nRF Connect SDK Zephyr variant](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/zephyr.html) [External] -Other platforms which may work but which we do not provide direct advice for:- +## Platforms supported in other Herald projects -- Other [Zephyr supported boards](https://docs.zephyrproject.org/latest/boards/index.html) [External] -- Linux Desktops/laptops (yet) +We have similar API and apps for other platforms. Below are some useful references:- + +- Other [Zephyr supported boards](https://docs.zephyrproject.org/latest/boards/index.html) [External] where these applications may work with some configuration changes +- We also have a higher level [Mobile app flutter API](https://github.com/theheraldproject/herald-for-flutter/) which includes UI components and supports multiple mobile devices. We also have native lower-level mobile device support:- - Android devices see the separate [herald-for-android](https://github.com/theheraldproject/herald-for-android/) project -- Apple OS X Desktops/laptops (yet) - iOS devices see the separate [herald-for-ios](https://github.com/theheraldproject/herald-for-ios) project -## Implementation differences +## Herald API implementation differences Some thin wrappers were not required in the C++ version compared to the Java and Swift versions as the base primitives were already @@ -81,40 +94,40 @@ C++ files on some platforms where known utility libraries are always present. Cu - herald::datatype::uuid - high level UUID interface with just the functionality required by Herald and no more. - herald::datatype::base64string - encodes and decodes a Data instance as a Base64 string +- herald::datatype::sha256 - SHA256 functionality, currently supported on Windows (OpenSSL-3), Zephyr (TineCrypt or MbedTLS) and Linux (OpenSSL-3) ## Implementation details Any trivial wrapper classes have been implemented as structs. -Any Interfaces from Java and Swift have been implemented as -pure virtual base classes. The code base is being heavily -refactored to use references only and avoid using any pointers, +Any Interfaces from Java and Swift have not been implemented, with their +derived classes being instead defined as templates. The code base has been heavily +refactored in V2.0+ to use references only and avoid using any pointers, including smart pointers. This allows us to be able to predict and restrict memory use at compile time, and provide for maximum memory safety. This code base implements Bluetooth Low Energy (BLe) -implementations on Zephyr/nRF Connect as standard. +implementations on Zephyr/nRF Connect as standard. We also support USB (MESH modem), +Bluetooth MESH (Smart Hospitals), and NFC (secure out of band provisioning of MESH devices). ## What isn't implemented -Any higher level implementation details, such as Mesh gateway payloads -and interconnect logic is within a downstream library project. That -is to keep this set of classes simple and consistent with the iOS -and Android equivalent Herald libraries. +User interface code - only embedded applications and the Herald libraries are in +this project repository. We anticipate the 'user interface' to be provided by +mobile applications paired with wearables or beacons via the +[Herald Flutter API](https://github.com/theheraldproject/herald-for-flutter) +or via cloud native web applications using the `herald-mesh-rmq-adapter` to +interact with a Herald Bluetooth MESH network. ## What is implemented -We do provide a demo Windows application in this repository, and a demo -zephyr serial application. These act as 'Herald consumer / demo devices' -and implement the same basic features as the Android and iOS demo apps. -They are provided to allow us to carry out regression testing on each -version. - -These demo apps are not intended as production ready applications. They -can be used as a reference implementation for any Herald based apps -and devices you wish to create. +This repository is the primary development reference API for all current and future +Herald project software work. New API additions are implemented here first. This +is because embedded devices are often much more constrained, and so ensuring +functionality first works here helps ensure a simple interface and higher +performance on other platforms. ## Specific platform limitations @@ -126,25 +139,34 @@ than respecify our own (E.g. base64, random number generation, uuid). ### Zephyr OS limitations -We cannot use dynamic_pointer_cast to cast a std::shared_ptr -to a std::shared_ptr because this uses RTTI which is not -supported by Zephyr. We use static_pointer_cast instead, but only when we -know the class implementation can only have one definition (i.e. the -one for the current platform). We are moving to remove all use of -smart pointers generally in favour of references. - +Note: In the v2.0 release we have removed all internal use of std::unique_ptr and std::shared_ptr in favour of templates and static sizes for things like SensorDelegates throughout the code base. This is to avoid Zephyr memory management bugs and improve (i.e. to drastically lower) memory use (SRAM) on Nordic Semiconductor and other embedded devices. + We also use noexcept rather than throw exceptions for the same reason. +This is particularly useful for embedded development, but also provides +a robust C++ API for other platforms. -See the [Zephyr C++ limitations](https://docs.zephyrproject.org/latest/reference/kernel/other/cxx_support.html) [External] page for details. +Note that Zephyr doesn't support RTTI in C++. See the [Zephyr C++ limitations](https://docs.zephyrproject.org/latest/reference/kernel/other/cxx_support.html) [External] page for full C++ compatibility details. Note that this page is out of date somewhat. The 'new' keyword, for example, is supported in Zephyr although it is very buggy. -Note: In the v2.0 release we have removed all internal use of std::unique_ptr and std::shared_ptr in favour of templates and static sizes for things like SensorDelegates throughout the code base. This is to avoid Zephyr memory management bugs and improve (lower) memory use (SRAM) on Nordic Semiconductor devices. +### Mac OS limitations + +Mac OS is not a primary development platform today and so some features do not work there. These are:- + +- No builds of the herald-tests project will succeed due to the gcov code coverage library not being present on MAC OS +- The herald-mesh-rmq-adapter app only supports Linux (its only TCP&TLS implementation) + +### Windows limitations + +For some reason the same Zephyr app build ends up with lower SRAM usage when built on Linux rather than Windows. Other than that everything else works fine. Windows 10 and Linux Manjaro are the primary development platforms. + +These features do not work on Windows:- +- The herald-mesh-rmq-adapter app only supports Linux (its only TCP&TLS implementation) ## Building with Code Coverage 1. Open Visual Studio Code 1. Perform a CMake build using CLang on Windows in Debug mode -1. Execute this in the build folder on the command line: ```cmake .. -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON``` to add code coverage support +1. Execute the following in the `build` folder on the command line: ```cmake .. -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON``` to add code coverage support 1. Open the CMake tools tab in Visual Studio Code 1. Expand 'herald-tests' 1. Run the 'ccov-report' utility \ No newline at end of file diff --git a/herald-mesh-modem/.clang-format b/herald-mesh-modem/.clang-format new file mode 100644 index 0000000..ee1294c --- /dev/null +++ b/herald-mesh-modem/.clang-format @@ -0,0 +1,30 @@ +# Use the Google style in this project. +BasedOnStyle: Google + +# Some folks prefer to write "int& foo" while others prefer "int &foo". The +# Google Style Guide only asks for consistency within a project, we chose +# "int& foo" for this project: +DerivePointerAlignment: false +PointerAlignment: Left + +IncludeBlocks: Regroup +IncludeCategories: +- Regex: '^\"../../herald' # herald includes + Priority: 1000 +- Regex: '^\"' # all other quoted (project) includes + Priority: 1500 +- Regex: '^' # C++ top level includes + Priority: 4500 + +IndentCaseLabels: 'true' +Standard: Cpp11 \ No newline at end of file diff --git a/herald-mesh-modem/.vscode/c_cpp_properties.json b/herald-mesh-modem/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..4390b49 --- /dev/null +++ b/herald-mesh-modem/.vscode/c_cpp_properties.json @@ -0,0 +1,41 @@ +{ + "configurations": [ + { + "name": "Win64", + "includePath": [ + "${default}", + "${workspaceFolder}/../herald/include" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.19041.0", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/Hostx64/x64/cl.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "msvc-x64", + "configurationProvider": "ms-vscode.cmake-tools" + }, + { + "name": "zephyr", + "includePath": [ + "${default}", + "${workspaceFolder}/../herald/include", + "/opt/nordic/ncs/v1.9.1/zephyr/include" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "compilerPath": "/opt/nordic/ncs/v1.9.1/toolchain/bin/arm-none-eabi-gcc", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-arm", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/herald-mesh-modem/.vscode/launch.json b/herald-mesh-modem/.vscode/launch.json new file mode 100644 index 0000000..7b13bb1 --- /dev/null +++ b/herald-mesh-modem/.vscode/launch.json @@ -0,0 +1,226 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "cwd": "${workspaceRoot}", + "executable": "./build/zephyr/zephyr.elf", + "name": "Debug on NRF5340_XXAA_APP using Cortex-Debug", + "request": "launch", + "type": "cortex-debug", + "showDevDebugOutput": false, + "servertype": "jlink", + "interface": "swd", + "device": "NRF5340_XXAA_APP", + "serverpath": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + // "armToolchainPath": "${config:armToolchainPath}/bin", // Only works if you have installed GNU Arm embeeded toolchain external to NCS + "armToolchainPath": "${env:ZEPHYR_BASE}/../toolchain/opt/bin", // Works if ZEPHYR_BASE set (which needs to be for our CMake to run) + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "type": "console" + } + ] + } + }, + { + "cwd": "${workspaceRoot}", + "executable": "./build/zephyr/zephyr.elf", + "name": "Debug on NRF52840_XXAA using Cortex-Debug", + "request": "launch", + "type": "cortex-debug", + "showDevDebugOutput": true, + "servertype": "jlink", + "interface": "swd", + "device": "NRF52840_XXAA", + "svdFile": "${env:ZEPHYR_BASE}/../modules/hal/nordic/nrfx/mdk/nrf52840.svd", + "serverpath": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + // "armToolchainPath": "${config:armToolchainPath}/bin", // Only works if you have installed GNU Arm embeeded toolchain external to NCS + "armToolchainPath": "${env:ZEPHYR_BASE}/../toolchain/opt/bin", // Works if ZEPHYR_BASE set (which needs to be for our CMake to run) + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "type": "console" + } + ] + } + }, + { + "cwd": "${workspaceRoot}", + "executable": "./build/zephyr/zephyr.elf", + "name": "Debug on NRF52833_XXAA using Cortex-Debug", + "request": "launch", + "type": "cortex-debug", + "showDevDebugOutput": false, + "servertype": "jlink", + "interface": "swd", + "device": "NRF52833_XXAA", + "serverpath": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + // "armToolchainPath": "${config:armToolchainPath}/bin", // Only works if you have installed GNU Arm embeeded toolchain external to NCS + "armToolchainPath": "${env:ZEPHYR_BASE}/../toolchain/opt/bin", // Works if ZEPHYR_BASE set (which needs to be for our CMake to run) + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "type": "console" + } + ] + } + }, + { + "cwd": "${workspaceRoot}", + "executable": "./build/zephyr/zephyr.elf", + "name": "Debug on NRF52832_XXAA using Cortex-Debug", + "request": "launch", + "type": "cortex-debug", + "showDevDebugOutput": false, + "servertype": "jlink", + "interface": "swd", + "device": "NRF52832_XXAA", + "serverpath": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + // "armToolchainPath": "${config:armToolchainPath}/bin", // Only works if you have installed GNU Arm embeeded toolchain external to NCS + "armToolchainPath": "${env:ZEPHYR_BASE}/../toolchain/opt/bin", // Works if ZEPHYR_BASE set (which needs to be for our CMake to run) + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "type": "console" + } + ] + } + }, + + + + + + + + { + "type": "gnu-debugger", + "request": "launch", + "name": "Debug on NRF5340_XXAA_APP using GNU Debugger", + "program": "${workspaceFolder}/build/zephyr/zephyr.elf", + "toolchain": "${config:armToolchainPath}/bin", + "client": "arm-none-eabi-gdb.exe", + "server": "JLinkGDBServer", + "windows": { + "server": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + }, + "serverArgs": [ + "-device", "NRF5340_XXAA_APP", + "-if", "SWD", + "-speed", "4000" + ], + "serverHost": "localhost", + "serverPort": 2331, + "customVariables": [ + "port0", + "port1", + "port2", + ], + "autoRun": false, + "debugOutput": false //, + //"preLaunchTask": "build firmware" + }, + + + { + "type": "gnu-debugger", + "request": "launch", + "name": "Debug on NRF52840_XXAA using GNU Debugger", + "program": "${workspaceFolder}/build/zephyr/zephyr.elf", + "toolchain": "${config:armToolchainPath}/bin", + "client": "arm-none-eabi-gdb.exe", + "server": "JLinkGDBServer", + "windows": { + "server": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + }, + "serverArgs": [ + "-device", "NRF52840_XXAA", + "-if", "SWD", + "-speed", "4000" + ], + "serverHost": "localhost", + "serverPort": 2331, + "customVariables": [ + "port0", + "port1", + "port2", + ], + "autoRun": false, + "debugOutput": false //, + //"preLaunchTask": "build firmware" + }, + + + { + "type": "gnu-debugger", + "request": "launch", + "name": "Debug on NRF52833_XXAA using GNU Debugger", + "program": "${workspaceFolder}/build/zephyr/zephyr.elf", + "toolchain": "${config:armToolchainPath}/bin", + "client": "arm-none-eabi-gdb.exe", + "server": "JLinkGDBServer", + "windows": { + "server": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + }, + "serverArgs": [ + "-device", "NRF52833_XXAA", + "-if", "SWD", + "-speed", "4000" + ], + "serverHost": "localhost", + "serverPort": 2331, + "customVariables": [ + "port0", + "port1", + "port2", + ], + "autoRun": false, + "debugOutput": false //, + //"preLaunchTask": "build firmware" + }, + + + { + "type": "gnu-debugger", + "request": "launch", + "name": "Debug on NRF52832_XXAA using GNU Debugger", + "program": "${workspaceFolder}/build/zephyr/zephyr.elf", + "toolchain": "${config:armToolchainPath}/bin", + "client": "arm-none-eabi-gdb.exe", + "server": "JLinkGDBServer", + "windows": { + "server": "C:/Program Files (x86)/SEGGER/JLink/JLinkGDBServerCL.exe", + }, + "serverArgs": [ + "-device", "NRF52832_XXAA", + "-if", "SWD", + "-speed", "4000" + ], + "serverHost": "localhost", + "serverPort": 2331, + "customVariables": [ + "port0", + "port1", + "port2", + ], + "autoRun": false, + "debugOutput": false //, + //"preLaunchTask": "build firmware" + } + ] +} \ No newline at end of file diff --git a/herald-mesh-modem/.vscode/settings.json b/herald-mesh-modem/.vscode/settings.json new file mode 100644 index 0000000..0bd4fea --- /dev/null +++ b/herald-mesh-modem/.vscode/settings.json @@ -0,0 +1,86 @@ +{ + "files.associations": { + "memory": "cpp", + "atomic": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "variant": "cpp", + "xmemory": "cpp", + "xstddef": "cpp", + "xtr1common": "cpp", + "xutility": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "ctime": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "fstream": "cpp", + "future": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "istream": "cpp", + "mutex": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "bit": "cpp", + "bitset": "cpp", + "netfwd": "cpp" + }, + "cmake.configureEnvironment": { + // "BOARD": "nrf52dk_nrf52832" + // "BOARD": "nrf52833dk_nrf52833" + "BOARD": "nrf52840dk_nrf52840" + // "BOARD": "nrf5340dk_nrf5340_cpuapp" + // ,"HERALD_ANALYSIS_ENABLED":"y" + }, + "kconfig.zephyr.base": "${ZEPHYR_BASE}", + "nrf-connect.applications": [ + "${workspaceFolder}" + ], + "kconfig.zephyr.board": { + "board": "nrf52840dk_nrf52840", + "arch": "arm", + "dir": "${ZEPHYR_BASE}/boards/arm/nrf52840dk_nrf52840" + }, + "nrf-connect.topdir": "${nrf-connect.sdk:1.9.1}", + "nrf-connect.toolchain.path": "${nrf-connect.toolchain:1.9.1}" +} \ No newline at end of file diff --git a/herald-mesh-modem/CMakeLists.txt b/herald-mesh-modem/CMakeLists.txt new file mode 100644 index 0000000..ca68874 --- /dev/null +++ b/herald-mesh-modem/CMakeLists.txt @@ -0,0 +1,117 @@ +# Copyright 2020-2022 Herald Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(NO_BUILD_TYPE_WARNING ON) + + +# Zephyr build environment variables set here +#set(ENV{BOARD} nrf52840dongle_nrf52840) # nRF52840 USB dongle +#set(ENV{BOARD} nrf52dk_nrf52832) # nRF52832 USB dongle +#set(ENV{BOARD} nrf52833dk_nrf52833) # nRF52833 DK +#set(ENV{BOARD} nrf5340dk_nrf5340_cpuapp) # nRF5340 DK using secure app core +# Detect if already set. If not, default to latest nRF DK board (5340) +if(DEFINED ENV{BOARD}) + message("Using board specified in BOARD variable: $ENV{BOARD}") +else() + message("No board specified in BOARD variable. Defaulting to nrf5340dk app core") + set(ENV{BOARD} nrf5340dk_nrf5340_cpuapp) # nRF5340 DK using secure app core + set(ENV{ARCH} arm) + message("BOARD variable now set to: $ENV{BOARD}") +endif() + +# Specify additional PROJ settings based on build settings +# set(BASE_CONFIG ../config/zephyr) +# set(OVERLAY_CONFIG +# ${BASE_CONFIG}/base-mesh.conf +# ${BASE_CONFIG}/usb.conf +# # ${BASE_CONFIG}/receiver.conf +# # ${BASE_CONFIG}/transmitter.conf +# ) + +# if($ENV{BOARD} MATCHES .*nrf5340.*) +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/nrf5340.conf +# ) +# add_definitions(-DHERALD_MEMORYARENA_MAX=4096) +# elseif($ENV{BOARD} MATCHES .*nrf52840.*) +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/nrf52840.conf +# ) +# add_definitions(-DHERALD_MEMORYARENA_MAX=4096) +# elseif($ENV{BOARD} MATCHES .*nrf52832.*) +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/nrf52832.conf +# ) +# add_definitions(-DHERALD_MEMORYARENA_MAX=512) +# elseif($ENV{BOARD} MATCHES .*nrf52833.*) +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/nrf52833.conf +# ) +# add_definitions(-DHERALD_MEMORYARENA_MAX=4096) +# endif() +# if(CMAKE_BUILD_TYPE MATCHES Debug) +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/debug.conf +# ) +# else() +# set(OVERLAY_CONFIG +# ${OVERLAY_CONFIG} +# ${BASE_CONFIG}/release.conf +# ) +# endif() + + +# Generic Zephyr build command below +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(herald-mesh-modem VERSION 2.1.0 LANGUAGES CXX) + +# NOTE: Don't include C library as it bloats the binary beyond DFU max load size (i.e. beyond half of the available flash) + +set(HERALD_TARGET zephyr) + + +# WARNING DO NOT USE THE ZEPHYR DEFAULT EXTERNAL PROJECT INCLUDE METHOD +# IT DOES NOT PROVIDE ZEPHYR BUILD SETTINGS CORRECTLY WHEN CROSS COMPILING ON WINDOWS + + +# Include Herald core library +set(HERALD_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../herald) +include(../herald/herald.cmake) # include sources and headers + +message("HERALD HEADERS: ${HERALD_HEADERS}") + +include_directories(../herald/include) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-DHERALD_LOG_LEVEL=4 -DCONFIG_APP_LOG_LEVEL=4) +else() + add_definitions(-DHERALD_LOG_LEVEL=0 -DCONFIG_APP_LOG_LEVEL=0) +endif() +#HERALD_LOG_LEVEL 4 = debug, 3 = info, 2 = warn / errors (Zephyr LOG_WARN), 1 = info contacts log only (Zephyr LOG_ERR) + +# gcc arm does not warn about missing defs until runtime +# add_compile_options(--no-undefined -ffunction-sections -fdata-sections -s -fno-unaligned-access -fno-exceptions -fno-rtti -Wall -Wextra -Wl,-z,defs,norelro,--strict,--strict_symbols,--gc-sections,--hash-style=gnu) + +message("HERALD SOURCES MESH: ${HERALD_SOURCES_MESH}") +target_sources(app PRIVATE + # ${HERALD_SOURCES} + # ${HERALD_SOURCES_ZEPHYR} + # ${HERALD_SOURCES_TINYCRYPT} + # #${HERALD_SOURCES_MBEDTLS} + # ${HERALD_SOURCES_MESH} + ../herald/src/mesh/modem.c + src/main.cpp +) +target_include_directories(app PRIVATE include) diff --git a/herald-mesh-modem/app.overlay b/herald-mesh-modem/app.overlay new file mode 100644 index 0000000..d9807a9 --- /dev/null +++ b/herald-mesh-modem/app.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// &zephyr_udc0 { +// cdc_acm_uart0 { +// compatible = "zephyr,cdc-acm-uart"; +// label = "CDC_ACM_0"; +// }; +// }; + +// Newer Zephyr versions:- +&usbd { + cdc_acm_uart0: cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + label = "CDC_ACM_0"; + }; +}; \ No newline at end of file diff --git a/herald-mesh-modem/prj.conf b/herald-mesh-modem/prj.conf new file mode 100644 index 0000000..62f7f82 --- /dev/null +++ b/herald-mesh-modem/prj.conf @@ -0,0 +1,113 @@ +CONFIG_USB_CDC_ACM=y +#CONFIG_RING_BUFFER=y +# ^^^ Implied by the first line +CONFIG_USB_DEVICE_VID=0x8086 +CONFIG_USB_DEVICE_PID=0x0001 +CONFIG_STDOUT_CONSOLE=y +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Herald MESH Modem" +CONFIG_LOG=y +CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_UART_LINE_CTRL=y + + + + + +# CONFIG_BT_DEVICE_NAME="Herald MESH Modem" + +# CONFIG_GPIO=y + + + +# # FROM MESH NEEDS + +# # General configuration +# CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +# CONFIG_FLASH=y +# CONFIG_FLASH_MAP=y +# CONFIG_NVS=y +# # Below required for BT_SETTINGS +# CONFIG_SETTINGS=y +# CONFIG_HWINFO=y +# CONFIG_DK_LIBRARY=y + +# # Bluetooth configuration +# CONFIG_BT=y +# # Use Linux Foundation code +# CONFIG_BT_COMPANY_ID=0x0059 +# #CONFIG_BT_DEVICE_NAME="Mesh and Peripheral Coex" +# CONFIG_BT_L2CAP_TX_MTU=69 +# CONFIG_BT_L2CAP_TX_BUF_COUNT=8 +# CONFIG_BT_OBSERVER=y +# CONFIG_BT_PERIPHERAL=y +# CONFIG_BT_SETTINGS=y + +# CONFIG_BT_EXT_ADV=y +# # This defaults to 1! +# CONFIG_BT_EXT_ADV_MAX_ADV_SET=5 +# # CONFIG_BT_EXT_ADV_MAX_ADV_SET=2 +# CONFIG_BT_MAX_CONN=3 + +# # Disable unused Bluetooth features +# CONFIG_BT_CTLR_DUP_FILTER_LEN=0 +# CONFIG_BT_CTLR_LE_ENC=n +# CONFIG_BT_DATA_LEN_UPDATE=n +# CONFIG_BT_PHY_UPDATE=n +# CONFIG_BT_CTLR_CHAN_SEL_2=n +# CONFIG_BT_CTLR_MIN_USED_CHAN=n +# CONFIG_BT_CTLR_PRIVACY=n + +# # Use the BLE 5.0 2M phy for more ADV_EXT throughput +# # TODO verify it's actually being used +# CONFIG_BT_CTLR_PHY_2M=y + +# # Bluetooth mesh configuration +# CONFIG_BT_MESH=y +# CONFIG_BT_MESH_RELAY=y +# CONFIG_BT_MESH_FRIEND=y +# CONFIG_BT_MESH_ADV_BUF_COUNT=13 +# CONFIG_BT_MESH_TX_SEG_MAX=10 +# CONFIG_BT_MESH_PB_GATT=y +# # The following is needed to set mesh model and publishing config from a phone +# CONFIG_BT_MESH_GATT_PROXY=y + +# # TODO replace the below with a Herald NFC provisioner instead +# CONFIG_BT_MESH_DK_PROV=y + +# # The following is separate from BT_EXT_ADV +# CONFIG_BT_MESH_ADV_EXT=y + +# # Bluetooth mesh models +# CONFIG_BT_MESH_ONOFF_SRV=y + +# # Enable the LBS service +# CONFIG_BT_LBS=n +# CONFIG_BT_LBS_POLL_BUTTON=n + +# # CONFIG_USE_SEGGER_RTT=y +# CONFIG_RTT_CONSOLE=y +# CONFIG_UART_CONSOLE=n + +# # DEBUG ONLY - Allows resetting of provisioning state via RTT (mesh reset) +# # CONFIG_BT_MESH_SHELL=y +# # CONFIG_SHELL_BACKEND_RTT=y + +# CONFIG_BT_DEBUG_LOG=y +# CONFIG_BT_MESH_DEBUG=y +# CONFIG_BT_MESH_DEBUG_MODEL=y +# CONFIG_BT_MESH_DEBUG_ACCESS=y +# CONFIG_BT_MESH_DEBUG_MODEL=y +# CONFIG_BT_MESH_DEBUG_TRANS=n +# CONFIG_BT_MESH_DEBUG_NET=n +# CONFIG_BT_MESH_DEBUG_CRYPTO=n +# CONFIG_BT_MESH_DEBUG_ADV=y + + +# # For issues with advertising failing:- +# # CONFIG_BT_DEBUG_HCI_DRIVER=y +# # CONFIG_BT_DEBUG_HCI_CORE=y +# CONFIG_BT_DEBUG_CONN=y diff --git a/herald-mesh-modem/src/main.cpp b/herald-mesh-modem/src/main.cpp new file mode 100644 index 0000000..8019231 --- /dev/null +++ b/herald-mesh-modem/src/main.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Sample echo app for CDC ACM class + * + * Sample app for USB CDC ACM class driver. The received data is echoed back + * to the serial port. + */ + +#include "herald/mesh/modem.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(cdc_acm_echo, LOG_LEVEL_INF); + +#define RING_BUF_SIZE 1024 +uint8_t ring_buffer_command[RING_BUF_SIZE]; +uint8_t ring_buffer_response[RING_BUF_SIZE]; + +struct ring_buf command_buffer; +struct ring_buf response_buffer; + +uint8_t hi[6] = { + 0x00,0x00, // hello + 0x00,0x01, // command id = 0x0001 + 0xff,0xff // boundary +}; + +static void interrupt_handler(const struct device* dev, void* user_data) { + ARG_UNUSED(user_data); + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + int recv_len, rb_len; + uint8_t buffer[64]; + size_t len = MIN(ring_buf_space_get(&command_buffer), sizeof(buffer)); + + recv_len = uart_fifo_read(dev, buffer, len); + if (recv_len < 0) { + LOG_ERR("Failed to read UART FIFO"); + recv_len = 0; + }; + + rb_len = ring_buf_put(&command_buffer, buffer, recv_len); + if (rb_len < recv_len) { + LOG_ERR("Drop %u bytes", recv_len - rb_len); + } + + LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len); + if (rb_len) { + uart_irq_tx_enable(dev); + } + } + + if (uart_irq_tx_ready(dev)) { + uint8_t buffer[64]; + int rb_len, send_len; + + rb_len = ring_buf_get(&response_buffer, buffer, sizeof(buffer)); + if (!rb_len) { + LOG_DBG("Ring buffer empty, disable TX IRQ"); + uart_irq_tx_disable(dev); + continue; + } + + send_len = uart_fifo_fill(dev, buffer, rb_len); + if (send_len < rb_len) { + LOG_ERR("Drop %d bytes", rb_len - send_len); + } + + LOG_DBG("ringbuf -> tty fifo %d bytes", send_len); + } + } +} + +void main(void) { + const struct device* dev; + uint32_t baudrate, dtr = 0U; + int ret; + + dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); + if (!device_is_ready(dev)) { + LOG_ERR("CDC ACM device not ready"); + return; + } + + ret = usb_enable(NULL); + if (ret != 0) { + LOG_ERR("Failed to enable USB"); + return; + } + + ring_buf_init(&command_buffer, sizeof(ring_buffer_command), + ring_buffer_command); + ring_buf_init(&response_buffer, sizeof(ring_buffer_response), + ring_buffer_response); + + LOG_INF("Wait for DTR"); + + while (true) { + uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr); + if (dtr) { + break; + } else { + /* Give CPU resources to low priority threads. */ + k_sleep(K_MSEC(100)); + } + } + + LOG_INF("DTR set"); + + /* They are optional, we use them to test the interrupt endpoint */ + ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DCD, 1); + if (ret) { + LOG_WRN("Failed to set DCD, ret code %d", ret); + } + + ret = uart_line_ctrl_set(dev, UART_LINE_CTRL_DSR, 1); + if (ret) { + LOG_WRN("Failed to set DSR, ret code %d", ret); + } + + /* Wait 1 sec for the host to do all settings */ + k_busy_wait(1000000); + + ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate); + if (ret) { + LOG_WRN("Failed to get baudrate, ret code %d", ret); + } else { + LOG_INF("Baudrate detected: %d", baudrate); + } + + uart_irq_callback_set(dev, interrupt_handler); + + /* Enable rx interrupts */ + uart_irq_rx_enable(dev); + + // write dummy hello command to command bugger + ring_buf_put(&command_buffer, hi, 6); + ring_buf_put_finish(&command_buffer, 6); + + int cres; + while (true) { + LOG_DBG("Main thread still running"); + cres = bt_mesh_modem_process_commands(&command_buffer,&response_buffer); + if (0 != cres) { + LOG_ERR("Error in command processing %d",cres); + } + + /* Give CPU resources to low priority threads. */ + k_sleep(K_MSEC(1000)); + } +} diff --git a/herald-mesh-proxy/CMakeLists.txt b/herald-mesh-proxy/CMakeLists.txt deleted file mode 100644 index b73c264..0000000 --- a/herald-mesh-proxy/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.12) - -add_executable(herald-mesh-proxy - src/main.cpp -) - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) - -target_link_libraries(herald-mesh-proxy PRIVATE herald Threads::Threads) - -target_compile_features(herald-mesh-proxy PRIVATE cxx_std_17) - -include_directories( - ${herald_SOURCE_DIR} - include -) - -install(TARGETS herald-mesh-proxy - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) diff --git a/herald-mesh-proxy/src/main.cpp b/herald-mesh-proxy/src/main.cpp deleted file mode 100644 index 2119d4d..0000000 --- a/herald-mesh-proxy/src/main.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2020-2021 Herald Project Contributors -// SPDX-License-Identifier: Apache-2.0 -// - -/* - * The main executable of the herald-mesh-proxy process - */ -#include -#include "herald/herald.h" - -using namespace herald; - -int main(int argc, char* argv[]) { - return 1; // TODO actual main function -} diff --git a/herald-mesh-relay/prj.conf b/herald-mesh-relay/prj.conf index a3117b0..af38429 100644 --- a/herald-mesh-relay/prj.conf +++ b/herald-mesh-relay/prj.conf @@ -167,6 +167,7 @@ CONFIG_BT_MESH_DEBUG_MODEL=y CONFIG_BT_MESH_DEBUG_TRANS=n CONFIG_BT_MESH_DEBUG_NET=n CONFIG_BT_MESH_DEBUG_CRYPTO=n +CONFIG_BT_MESH_DEBUG_ADV=y # For issues with advertising failing:- diff --git a/herald-mesh-rmq-adapter/CMakeLists.txt b/herald-mesh-rmq-adapter/CMakeLists.txt new file mode 100644 index 0000000..8183496 --- /dev/null +++ b/herald-mesh-rmq-adapter/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright 2020-2022 Herald Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.12) +project(herald-mesh-adapter VERSION 2.1.0 LANGUAGES CXX) + +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +set(NO_BUILD_TYPE_WARNING ON) + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +find_package(OpenSSL 3.0.2 REQUIRED) +find_package(amqpcpp REQUIRED) +find_package(libuv REQUIRED) + +set(HERALD_BASE ${CMAKE_CURRENT_SOURCE_DIR}/../herald) +include(../herald/herald.cmake) # find sources and headers + +include_directories(../herald/include) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-DHERALD_LOG_LEVEL=4) +else() + add_definitions(-DHERALD_LOG_LEVEL=0) +endif() + +add_executable(herald-mesh-adapter + ${HERALD_SOURCES_MESH_CLIENT} + + # main application file + src/main.cpp +) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +target_include_directories(herald-mesh-adapter SYSTEM PRIVATE + ${OPENSSL_INCLUDE_DIR} + ${AMQPCPP_INCLUDE_DIR} + ${LIBUV_INCLUDE_DIRS} +) + +target_link_libraries(herald-mesh-adapter PRIVATE + amqpcpp + ${LIBUV_LIBRARIES} + Threads::Threads + dl + OpenSSL::SSL +) + +target_compile_features(herald-mesh-adapter PRIVATE cxx_std_17) diff --git a/herald-mesh-rmq-adapter/README.md b/herald-mesh-rmq-adapter/README.md new file mode 100644 index 0000000..9019a02 --- /dev/null +++ b/herald-mesh-rmq-adapter/README.md @@ -0,0 +1,37 @@ +# Herald Bluetooth MESH Modem RabbitMQ Adapter Application + +This application is a Linux only application that links a +[herald-mesh-modem](../herald-mesh-modem) +attached to the computer/server via USB to a RabbitMQ reliable +message broker control plane. + +This allows data to flow between an event driven microservices application +environment and a Smart Hospitals network built using beacons enrolled in a +[herald-mesh-relay](../herald-mesh-relay) +Bluetooth MESH network. + +MESH administrative and mesh model publish/subscribe functionality is supported. + +## A note on Bluetooth MESH standards compliance + +Where possible we re-use existing Bluetooth SIG standard MESH models. These are +mainly concerned with MESH administration and smart lighting and other building +control use cases. + +For our healthcare use cases in Herald we've created new MESH models but have tried +to do so in a way that they are generally applicable beyond a hospital. The +Location MESH model, for example, supports internal building navigation in +any Bluetooth MESH network, supporting clients that are Bluetooth LE based such +as mobile phones. + +We intend to try out new model ideas within this project and then, subject to adoption +as a vendor model by the whole Linux Foundation (Not just Linux Foundation Public +Health, of which the Herald Project is a part), or adoption as a standard model +by the Bluetooth SIG, we'll look to upstream the models in to Zephyr RTOS. + +## Features + +Added in v2.1 (First release):- +- Test commands (hello, status) +- Default fixed subscriptions to all Presence messages (Herald LE and other LE devices) +- Programming of Mesh relay nodes' Venue Beacon and Location metadata for site navigation \ No newline at end of file diff --git a/herald-mesh-rmq-adapter/cmake/Findlibuv.cmake b/herald-mesh-rmq-adapter/cmake/Findlibuv.cmake new file mode 100644 index 0000000..b78cdc9 --- /dev/null +++ b/herald-mesh-rmq-adapter/cmake/Findlibuv.cmake @@ -0,0 +1,30 @@ +# This file is MIT licensed and copied from:- +# https://raw.githubusercontent.com/xenoscopic/libuv-cmake/master/Findlibuv.cmake + +# Standard FIND_PACKAGE module for libuv, sets the following variables: +# - LIBUV_FOUND +# - LIBUV_INCLUDE_DIRS (only if LIBUV_FOUND) +# - LIBUV_LIBRARIES (only if LIBUV_FOUND) + +# Try to find the header +FIND_PATH(LIBUV_INCLUDE_DIR NAMES uv.h) + +# Try to find the library +FIND_LIBRARY(LIBUV_LIBRARY NAMES uv libuv) + +# Handle the QUIETLY/REQUIRED arguments, set LIBUV_FOUND if all variables are +# found +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBUV + REQUIRED_VARS + LIBUV_LIBRARY + LIBUV_INCLUDE_DIR) + +# Hide internal variables +MARK_AS_ADVANCED(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) + +# Set standard variables +IF(LIBUV_FOUND) + SET(LIBUV_INCLUDE_DIRS "${LIBUV_INCLUDE_DIR}") + SET(LIBUV_LIBRARIES "${LIBUV_LIBRARY}") +ENDIF() diff --git a/herald-mesh-rmq-adapter/src/main.cpp b/herald-mesh-rmq-adapter/src/main.cpp new file mode 100644 index 0000000..74861c8 --- /dev/null +++ b/herald-mesh-rmq-adapter/src/main.cpp @@ -0,0 +1,106 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +// TODO fail to build currently if not done on Linux +// (RMQ Library platform support for TCP is limited.) + +/** + * @file The application binary for a Herald Bluetooth MESH + * and RabbitMQ Adapter connection. + */ +#include "herald/mesh/modem_rabbitmq.h" + +#include +#include + +#include +#include +#include +#include + +// Declare our singleton adapter (one per app instance) +herald::mesh::MeshModemRabbitMQAdapter adapter; + +void signalHandler(int signum) { + std::cout << "Interrupt signal (" << signum << ") received." << std::endl; + + // cleanup and close up stuff here + if (adapter.isConnected()) { + std::cout << "Adapter disconnecting..." << std::endl; + bool disconnected = adapter.disconnect(); + if (disconnected) { + std::cout << "Adapter disconnected successfully. Quitting." << std::endl; + } else { + std::cerr << "Adapter did not disconnect successfully. Quitting anyway." + << std::endl; + } + } else { + std::cout << "Adapter already disconnected. Quitting." << std::endl; + } + + // TODO any uvloop cleanup + + // terminate program + exit(signum); +} + +int main(int argc, char *argv[]) { + // The app name, plus:- + // - 5 arguments for RabbitMQ + // - 1 optional argument for --no-openssl + if (6 > argc) { + std::cerr << "Usage: herald-mesh-adapter " << std::endl; + return EXIT_FAILURE; + } + // if (7 == argc) { + // // If we've not been told to not initialise openssl dynamically, then do it! + // if (0 != strcmp("--no-openssl",argv[6])) { + // // dynamically open the openssl library + // // TODO allow this to be dynamically specified via --openssldir and --prefix (for '/lib/') + // void* handle = dlopen("/usr/local/ssl/lib/openssl-3.so", RTLD_LAZY); + + // // tell AMQP-CPP library where the handle to openssl can be found + // AMQP::openssl(handle); + + // OPENSSL_init_ssl(0, NULL); + // } + // } + + auto* loop = uv_default_loop(); + + // Configure the RabbitMQ Adapter + bool configured = adapter.configure(loop, argv[1], argv[2], argv[3], + argv[4], argv[5]); + if (!configured) { + // fail with message + std::cerr << "Failed to configure RabbitMQ adapter with configuration:-" + << std::endl; + std::cerr << " amqpurl: " << argv[1] << std::endl; + std::cerr << " exchangeName: " << argv[2] << std::endl; + std::cerr << " commandQueueName: " << argv[3] << std::endl; + std::cerr << " routingKey: " << argv[4] << std::endl; + std::cerr << " modemTtyFilePath: " << argv[5] << std::endl; + return EXIT_FAILURE; + } + + // TODO Dynamic RMQ library and OpenSSL library detection. + // Quit if either is not detected. + + // Now connect to RMQ so we can send all status information + bool connected = adapter.connect(); + if (!connected) { + std::cerr << "Could not connect to RabbitMQ so failing" << std::endl; + return EXIT_FAILURE; + } + + // TODO try to connect to tty specified too + + // TODO pass initial MESH network connection info + + + // Enter an infinite loop until we get a SIGTERM + uv_run(loop, UV_RUN_DEFAULT); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/herald/herald.cmake b/herald/herald.cmake index 48cc414..d3beb70 100644 --- a/herald/herald.cmake +++ b/herald/herald.cmake @@ -181,6 +181,10 @@ set(HERALD_SOURCES_MESH ${HERALD_BASE}/src/mesh/presence_client.c ${HERALD_BASE}/src/mesh/presence_server.c ) +set(HERALD_SOURCES_MESH_CLIENT + ${HERALD_BASE}/src/mesh/modem_rabbitmq.cpp + ${HERALD_BASE}/src/mesh/modem_client.cpp +) set(HERALD_SOURCES_OPENSSL ${HERALD_BASE}/src/datatype/openssl/sha256.cpp ) diff --git a/herald/include/herald/mesh/modem.h b/herald/include/herald/mesh/modem.h new file mode 100644 index 0000000..593f31d --- /dev/null +++ b/herald/include/herald/mesh/modem.h @@ -0,0 +1,60 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef HERALD_MESH_MODEM_H +#define HERALD_MESH_MODEM_H + +#ifdef __ZEPHYR__ +#ifdef CONFIG_USB_CDC_ACM +//& CONFIG_BT_MESH +// TODO warning message on the above if included without these configured + +#include "modem_codes.h" + +#include +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file This file contains C headers for the Herald MESH Modem protocol. + * \note This relies upon the Zephyr CDC_ACM driver which is widely supported + * in all operating systems. + */ + +/** + * @brief Holds references to BT MESH and USB (Modem) pointers + */ +struct bt_mesh_modem_context { + +}; + +/** + * @brief Contains callbacks for dynamic app functionality + */ +struct bt_mesh_modem_cb { + // int (*set)(struct bt_mesh_herald_presence_server* srv, + // const struct bt_mesh_herald_presence_set* set, + // struct bt_mesh_herald_presence_status* rsp); +}; + +/** + * @brief This assumes the device has been read and we're now + * synchronously processing commands in a buffer. + * + * @param commands The incoming command buffer + * @param response The outgoing response buffer + * @return int 0 if successful + */ +int bt_mesh_modem_process_commands( + struct ring_buf* commands, struct ring_buf* response); + +#ifdef __cplusplus +} +#endif + +#endif // CDC ACM and MESH support +#endif // Zephyr + +#endif // END HERALD_MESH_MODEM_H diff --git a/herald/include/herald/mesh/modem_client.h b/herald/include/herald/mesh/modem_client.h new file mode 100644 index 0000000..8c6d172 --- /dev/null +++ b/herald/include/herald/mesh/modem_client.h @@ -0,0 +1,54 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef HERALD_MESH_MODEM_CLIENT_H +#define HERALD_MESH_MODEM_CLIENT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file Defines a client API for interacting over a tty device + * to a Herald MESH modem. + */ + +/** + * @brief A set of callbacks for the mesh modem client + */ +struct bt_mesh_modem_client_cb { + int (*read)(uint8_t* buffer,size_t sz); +}; + +#ifdef __cplusplus +} // ends extern C linking + +// TODO include a C++ class wrapper here too + +namespace herald { +namespace mesh { + +class MeshModem { +public: + MeshModem(const int serial_port, const struct bt_mesh_modem_client_cb callbacks); + ~MeshModem(); + + void pause(); + void resume(); + + int write(uint8_t* buffer, size_t sz); +private: + int port; + const struct bt_mesh_modem_client_cb cb; +}; + +} +} + +#endif // c++ + +#endif // include \ No newline at end of file diff --git a/herald/include/herald/mesh/modem_codes.h b/herald/include/herald/mesh/modem_codes.h new file mode 100644 index 0000000..11adc71 --- /dev/null +++ b/herald/include/herald/mesh/modem_codes.h @@ -0,0 +1,152 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +#ifndef HERALD_MESH_MODEM_CODES_H +#define HERALD_MESH_MODEM_CODES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// enum bt_mesh_modem_command { +const uint16_t + // General modem system commands (no mesh effects) + bt_mesh_modem_command_hello = 0x0000, + // bt_mesh_modem_command_status = 0x0001, + + // // Low-level generic MESH Model commands (maps directly onto BL Mesh Ops) + // bt_mesh_modem_command_get = 0x0010, + // bt_mesh_modem_command_set = 0x0011, + // bt_mesh_modem_command_subscribe = 0x0012, + // bt_mesh_modem_command_unsubscribe = + // 0x0013, // unsubscribes group from mesh model + // bt_mesh_modem_command_publish = 0x0014, + // bt_mesh_modem_command_send = 0x0015, + + // // Higher-level Herald MESH Model commands (quicker) + // bt_mesh_modem_command_switch_modem = + // 0x0f00, // set modem node for all current Herald subscriptions + // // The above is useful for a quick HA/DR failover scenario to a + // // different RabbitMQ connected server (with its own modem) + // bt_mesh_modem_command_make_full_gateway = + // 0x0f01, // presence (all LE), Location beacon, etc. + // // Also configures all appropriate subscriptions + + // // Node management high level commands + // // store_update = 0x0f51, // store modem app update in flash + // // validate_update = 0x0f52, // update modem from update file (no + // reboot) + // // update_and_reboot = 0x0f53, // order mesh node to reboot with update + // // store_binary = 0x0f54, // store binary file for distribution (for + // onward + // // tx) + // // delete_binary = 0x0f55, // delete (actually just removes record of) + // binary + // // distribute_binary = 0x0f56, // distribute stored binary between mesh + // nodes + // // store_update_from_binary = + // // 0x0f57, // instruct node to update app from nearby + // binary + // // receive_frame = 0x0f58, // send data from of binary to usb modem + // // receive_final_frame = 0x0f59, // send final frame, including size + + // // // MESH networking debugging functionality + // // mesh_nearby_nodes = 0x0f70, // ask a mesh node to send info on other + // nodes + // // mesh_ping = + // // 0x0f71, // send packet between 2 nodes, asking both to send + // result + + // // // LE networking status functionality + // // le_connection_info = 0x0f80, // ask for max/used/avail LE connection + // info + + // // Final catch all command values + // bt_mesh_modem_command_length = 0xfffe, // size of dynamic value being + // sent + bt_mesh_modem_command_boundary = + 0xffff // command boundary (for overflow detection) + // } +; + +/** + * MARK: Example from the above to update a distant mesh node:- + * Command: + * cmd=store_binary,cid=456,id=1,framesize=128bytes,size=89543bytes,sha256=134...,boundary + * Response: resp=ok,cid=456,boundary + * Command: cmd=receive_frame,cid=457,id=1,seq=1,xor=0x0547,boundary + * Response: resp=ok,cid=457,boundary + * Various of the above, multiple commands and cids, same binary id + * End with receive final frame, which includes the length of last frame + * + * Command: + * cmd=distribute_binary,cid=458,id=1,fromid=0000(modem),toid=0102,boundary This + * is a long term operation, so generates multiple responses over time Response: + * resp=ok,cid=458,boundary Generate pub/priv key pairs Send pub for modem and + * priv for node to 0102, with binary id=1,sha256 Open LE connection from modem + * to 0102, negotiating l2cap packet size Response: + * resp=op_started,cid=458,boundary Multiple frames of Herald LE unconnected + * transfer, using secured model Response: + * resp=op_progress,cid=458,progress=05(%),boundary Multiple frames of Herald LE + * unconnected transfer, using secured model Response: + * resp=op_progress,cid=458,progress=63(%),boundary Multiple frames of Herald LE + * unconnected transfer, using secured model Response: + * resp=op_progress,cid=458,progress=97(%),boundary Multiple frames of Herald LE + * unconnected transfer, using secured model LE connection closed Fetch sha-256 + * confirmation from node01 for binary and confirm Response: + * resp=op_succeeded,cid=458,boundary + * + * Command: + * cmd=store_update_from_binary,cid=459,id=1,fromid=0102,toid=0105,boundary + * Response: resp=ok,cid=459,boundary + * Response: resp=op_started,cid=459,boundary + * Response: resp=op_progress,cid=459,progress=06(%),boundary + * Response: resp=op_progress,cid=459,progress=56(%),boundary + * Response: resp=op_progress,cid=459,progress=94(%),boundary + * Asks over mesh for update validation + * Response: resp=op_succeeded,cid=459,boundary + * + * Command: cmd=validate_update,cid=460,onnode=0105,boundary + * Response: resp=ok,cid=460,boundary + * Command: cmd=update_and_reboot,cid=461,onnode=0105,boundary + * Response: resp=ok,cid=461,boundary + * OK when command confirmed received + * Response: resp=op_started,cid=461,boundary + * Sent when node sends update status of rebooting + * Response: resp=op_succeeded,cid=461,boundary + * Sent when node sends update status of success on reconnecting to mesh + */ + +// enum bt_mesh_modem_response { +const uint16_t + // General response commands (direct response) + bt_mesh_modem_response_ok = 0x0000, + // bt_mesh_modem_response_unknown = 0x0001, + // bt_mesh_modem_response_invalid = 0x0002, + + // // Dynamic information data + // bt_mesh_modem_response_model_status = + // 0x0010, // For all published messages arriving at the modem + + // // Long term operation monitoring + // bt_mesh_modem_response_op_started = 0x0ff0, + // bt_mesh_modem_response_op_progress = 0x0ff1, + // bt_mesh_modem_response_op_failed = 0x0ff2, + // bt_mesh_modem_response_op_succeeded = 0x0ff3, + + // // Final catch all response values + // bt_mesh_modem_response_length = 0xfffe, // size of dynamic value being + // sent + bt_mesh_modem_response_boundary = + 0xffff // command boundary (for overflow detection) + // } +; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/herald/include/herald/mesh/modem_rabbitmq.h b/herald/include/herald/mesh/modem_rabbitmq.h new file mode 100644 index 0000000..6019b4a --- /dev/null +++ b/herald/include/herald/mesh/modem_rabbitmq.h @@ -0,0 +1,241 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +/** + * @file Wraps a MESH modem client with a link to RabbitMQ. + */ + +#ifndef HERALD_MESH_MODEM_RABBITMQ_H +#define HERALD_MESH_MODEM_RABBITMQ_H + +#include "modem_client.h" + +// AMQP-CPP is Apache-2.0 licensed +// Note: Its TCP layer only supports Linux (So using libuv instead) +#include +#include +#include + +namespace herald { +namespace mesh { + +namespace internal { + +class RmqHandler : public AMQP::LibUvHandler { +private: + /** + * Method that is called when a connection error occurs + * @param connection + * @param message + */ + virtual void onError(AMQP::TcpConnection* connection, + const char* message) override; + + /** + * Method that is called when the TCP connection ends up in a connected state + * @param connection The TCP connection + */ + virtual void onConnected(AMQP::TcpConnection* connection) override; + +public: + /** + * Constructor + * @param uv_loop + */ + RmqHandler(uv_loop_t* loop); + + /** + * Destructor + */ + virtual ~RmqHandler(); +}; + +// class MeshAdapterTcpHandler : public AMQP::TcpHandler { +// MeshAdapterTcpHandler(); +// ~MeshAdapterTcpHandler(); + +// /** +// * Method that is called by the AMQP library when a new connection +// * is associated with the handler. This is the first call to your handler +// * @param connection The connection that is attached to the handler +// */ +// virtual void onAttached(AMQP::TcpConnection* connection) override; + +// /** +// * Method that is called by the AMQP library when the TCP connection +// * has been established. After this method has been called, the library +// * still has take care of setting up the optional TLS layer and of +// * setting up the AMQP connection on top of the TCP layer., This method +// * is always paired with a later call to onLost(). +// * @param connection The connection that can now be used +// */ +// virtual void onConnected(AMQP::TcpConnection* connection) override; + +// /** +// * Method that is called when the secure TLS connection has been established. +// * This is only called for amqps:// connections. It allows you to inspect +// * whether the connection is secure enough for your liking (you can +// * for example check the server certificate). The AMQP protocol still has +// * to be started. +// * @param connection The connection that has been secured +// * @param ssl SSL structure from openssl library +// * @return bool True if connection can be used +// */ +// virtual bool onSecured(AMQP::TcpConnection* connection, +// const SSL* ssl) override; + +// /** +// * Method that is called when the server tries to negotiate a heartbeat +// * interval, and that is overridden to get rid of the default implementation +// * (which vetoes the suggested heartbeat interval), and accept the interval +// * instead. +// * @param connection The connection on which the error occurred +// * @param interval The suggested interval in seconds +// */ +// virtual uint16_t onNegotiate(AMQP::TcpConnection *connection, uint16_t interval) override; + +// /** +// * Method that is called by the AMQP library when the login attempt +// * succeeded. After this the connection is ready to use. +// * @param connection The connection that can now be used +// */ +// virtual void onReady(AMQP::TcpConnection* connection) override; + +// /** +// * Method that is called by the AMQP library when a fatal error occurs +// * on the connection, for example because data received from RabbitMQ +// * could not be recognized, or the underlying connection is lost. This +// * call is normally followed by a call to onLost() (if the error occurred +// * after the TCP connection was established) and onDetached(). +// * @param connection The connection on which the error occurred +// * @param message A human readable error message +// */ +// virtual void onError(AMQP::TcpConnection* connection, +// const char* message) override; + +// /** +// * Method that is called when the AMQP protocol is ended. This is the +// * counter-part of a call to connection.close() to graceful shutdown +// * the connection. Note that the TCP connection is at this time still +// * active, and you will also receive calls to onLost() and onDetached() +// * @param connection The connection over which the AMQP protocol ended +// */ +// virtual void onClosed(AMQP::TcpConnection* connection) override; + +// /** +// * Method that is called when the TCP connection was closed or lost. +// * This method is always called if there was also a call to onConnected() +// * @param connection The connection that was closed and that is now +// * unusable +// */ +// virtual void onLost(AMQP::TcpConnection* connection) override; + +// /** +// * Final method that is called. This signals that no further calls to your +// * handler will be made about the connection. +// * @param connection The connection that can be destructed +// */ +// virtual void onDetached(AMQP::TcpConnection* connection) override; + +// /** +// * Method that is called by the AMQP-CPP library when it wants to interact +// * with the main event loop. The AMQP-CPP library is completely non-blocking, +// * and only make "write()" or "read()" system calls when it knows in advance +// * that these calls will not block. To register a filedescriptor in the +// * event loop, it calls this "monitor()" method with a filedescriptor and +// * flags telling whether the filedescriptor should be checked for readability +// * or writability. +// * +// * @param connection The connection that wants to interact with the +// * event loop +// * @param fd The filedescriptor that should be checked +// * @param flags Bitwise or of AMQP::readable and/or AMQP::writable +// */ +// virtual void monitor(AMQP::TcpConnection* connection, int fd, +// int flags) override; +// }; + +} // end internal namespace + +class MeshModemRabbitMQAdapter { +public: + MeshModemRabbitMQAdapter(); + ~MeshModemRabbitMQAdapter(); + + /** + * @brief Validate and save this configuration + * + * @param amqpUrl + * @param exchangeName + * @param commandQueueName + * @param routingKey + * @param modemTtyFile + * @return true If configuration valid and accepted + * @return false If not (configuration will not be reset) + */ + bool configure(uv_loop_t* uvloop, + std::string amqpUrl, std::string exchangeName, + std::string commandQueueName, std::string routingKey, + std::string modemTtyFile); + + /** + * @brief Attempt to connect to RabbitMQ, and try to stay connected. + * + * @return true If connection, exchange declare, and queue bind calls accepted + * @return false If calls failed + */ + bool connect(); + + /** + * @brief Disconnect from RabbitMQ. + * @note The destructor calls this method for you + * @note This method checks isConnected() for you + * + * @param immediate Whether to force an immediate disconnection (defaults to false) + * @return true If disconnected (whether already disconnected, or just disconnected) + * @return false If disconnection call failed + */ + bool disconnect(bool immediate = false); + + /** + * @brief Returns whether we are currently connected to RabbitMQ + * @note This may return false after connect() called if the connection + * has temporarily dropped out but not yet been reestablished. + * + * @return true If actually connected to RabbitMQ + * @return false If not currently connected (may be retrying behind the scenes) + */ + bool isConnected(); + + // TODO figure out how to make this private! + int read(uint8_t* buffer, size_t sz); + + private: + // LibUV info + uv_loop_t* loop; + + // RabbitMQ details + std::string rmqUrl; + std::string rmqExchangeName; + std::string rmqCommandQueueName; + std::string rmqRoutingKey; + std::optional rmqHandler; + std::optional rmqConnection; + std::optional rmqChannel; + + // TTY (CDC ACM over USBD) Herald Bluetooth MESH Modem link + std::string mdmFilePath; + struct termios tty; + int mdmPortNumber; + std::optional modem; + + // overall state + bool ready; +}; + + +} // end namespace +} // end namespace + +#endif // end HERALD_MESH_MODEM_RABBITMQ_H \ No newline at end of file diff --git a/herald/src/mesh/modem.c b/herald/src/mesh/modem.c new file mode 100644 index 0000000..196309a --- /dev/null +++ b/herald/src/mesh/modem.c @@ -0,0 +1,80 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +#include "herald/mesh/modem_codes.h" +#include "herald/mesh/modem.h" + +// #include "herald/mesh/mesh.h" +// #include "herald/mesh/presence.h" +// #include "herald/mesh/presence_client.h" + +#include + +// #include +// LOG_MODULE_DECLARE(bt_mesh_modem, LOG_LEVEL_INF); + +static uint64_t bt_mesh_modem_version = 0x00010000; // Sem Ver 0.1.0-release + +int bt_mesh_modem_process_commands(struct ring_buf* commands, + struct ring_buf* response) { + + size_t sz = ring_buf_size_get(commands); + + // reusable variables + uint16_t cmd; // command code + uint16_t cid; // command ID + uint32_t success; // read/write success code + + // Assume we're starting at the beginning of a buffer that hasn't overflown + while (5 < sz) { // must have at least command, command id, boundary + // 1. Read first two bytes as the command + ring_buf_get(commands, (uint8_t*)&cmd, 2); + + ring_buf_get(commands, (uint8_t*)&cid, 2); + + + // 2. Now process the command + + // TODO add error handling for each put + // TODO ensure the output buffer has space before we write to it + switch (cmd) { + case bt_mesh_modem_command_hello: + ring_buf_put(response, (uint8_t*)&bt_mesh_modem_response_ok, 2); + // put the command ID back to the modem + ring_buf_put(response, (uint8_t*)&cid, 2); + break; + // case bt_mesh_modem_command_status: + // break; + + // // model commands + // case bt_mesh_modem_command_get: + // break; + // case bt_mesh_modem_command_set: + // break; + // case bt_mesh_modem_command_subscribe: + // break; + // case bt_mesh_modem_command_unsubscribe: + // break; + // case bt_mesh_modem_command_publish: + // break; + // case bt_mesh_modem_command_send: + // break; + + // higher level herald convenience commands + default: + // unknown command + // LOG_ERR("Unknown command code %d (command ID %d)",cmd,cid); + // success = ring_buf_put(response, + // (uint8_t*)&bt_mesh_modem_response_unknown, 2); + // put the command ID back to the modem + ring_buf_put(response, (uint8_t*)&cid, 2); + } + // send boundary + ring_buf_put(response, (uint8_t*)&bt_mesh_modem_response_boundary, 2); + // TODO auto advance to next boundary here (to 0xffff) + sz = ring_buf_size_get(commands); + } // end while has more commands + + return 0; +} \ No newline at end of file diff --git a/herald/src/mesh/modem_client.cpp b/herald/src/mesh/modem_client.cpp new file mode 100644 index 0000000..0bec9b9 --- /dev/null +++ b/herald/src/mesh/modem_client.cpp @@ -0,0 +1,40 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +#include "herald/mesh/modem_client.h" + +namespace herald { +namespace mesh { + +MeshModem::MeshModem(const int serial_port, + const struct bt_mesh_modem_client_cb callbacks) + : port(serial_port), + cb(callbacks) +{ + // Do nothing else +} + +MeshModem::~MeshModem() = default; + +void +MeshModem::pause() +{ + // TODO do something +} + +void +MeshModem::resume() +{ + // TODO do something +} + +int +MeshModem::write(uint8_t* buffer, size_t sz) +{ + // TODO do something + return 0; +} + +} // namespace mesh +} // namespace herald \ No newline at end of file diff --git a/herald/src/mesh/modem_rabbitmq.cpp b/herald/src/mesh/modem_rabbitmq.cpp new file mode 100644 index 0000000..7777fa6 --- /dev/null +++ b/herald/src/mesh/modem_rabbitmq.cpp @@ -0,0 +1,365 @@ +// Copyright 2022 Herald Project Contributors +// SPDX-License-Identifier: Apache-2.0 +// + +/** + * @file Implements the wrapping of a MESH modem client with a link to RabbitMQ. + */ + +#include "herald/mesh/modem_rabbitmq.h" +#include "herald/mesh/modem_client.h" + +#include + +#include + +// C library headers +#include +#include + +// Linux headers +#include // Error integer and strerror() function +#include // Contains file controls like O_RDWR +#include // Contains POSIX terminal control definitions +#include // write(), read(), close() + +namespace herald { +namespace mesh { + +namespace internal { + +// TODO make this less nasty, and allow for multiple RabbitMqAdapter instances + +MeshModemRabbitMQAdapter* globalModemAdapter = NULL; + +int global_read_fn(uint8_t* buffer, size_t sz) { + if (NULL != globalModemAdapter) { + return globalModemAdapter->read(buffer, sz); + } + return 0; +} + +RmqHandler::RmqHandler(uv_loop_t* loop) : AMQP::LibUvHandler(loop) {} + +RmqHandler::~RmqHandler() = default; + +void +RmqHandler::onError(AMQP::TcpConnection* connection, const char* message) +{ + std::cout << "error: " << message << std::endl; +} + +void +RmqHandler::onConnected(AMQP::TcpConnection* connection) +{ + std::cout << "connected" << std::endl; +} + +// void +// MeshAdapterTcpHandler::onAttached(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation, for example initialize things +// // to handle the connection. +// } + +// void +// MeshAdapterTcpHandler::onConnected(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation (probably not needed) +// } + +// bool +// MeshAdapterTcpHandler::onSecured(AMQP::TcpConnection* connection, +// const SSL* ssl) { +// // @todo +// // add your own implementation, for example by reading out the +// // certificate and check if it is indeed yours + +// // TODO WE MUST IMPLEMENT THIS!!! AMQP CPP DOESNT DO IT FOR US! +// // See SSL_get_peer_certificate() or SSL_get_verify_result() + +// return true; +// } + +// uint16_t +// MeshAdapterTcpHandler::onNegotiate(AMQP::TcpConnection *connection, uint16_t interval) +// { +// // we accept the suggestion from the server, but if the interval is smaller +// // that one minute, we will use a one minute interval instead +// if (interval < 60) interval = 60; + +// // @todo +// // set a timer in your event loop, and make sure that you call +// // connection->heartbeat() every _interval_ seconds if no other +// // instruction was sent in that period. + +// // return the interval that we want to use +// return interval; +// } + +// void +// MeshAdapterTcpHandler::onReady(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation, for example by creating a channel +// // instance, and start publishing or consuming +// } + +// void +// MeshAdapterTcpHandler::onError(AMQP::TcpConnection* connection, +// const char* message) { +// // @todo +// // add your own implementation, for example by reporting the error +// // to the user of your program and logging the error +// } + +// void +// MeshAdapterTcpHandler::onClosed(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation (probably not necessary, but it could +// // be useful if you want to do some something immediately after the +// // amqp connection is over, but do not want to wait for the tcp +// // connection to shut down +// } + +// void +// MeshAdapterTcpHandler::onLost(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation (probably not necessary) +// } + +// void +// MeshAdapterTcpHandler::onDetached(AMQP::TcpConnection* connection) { +// // @todo +// // add your own implementation, like cleanup resources or exit the +// // application +// } + +// void +// MeshAdapterTcpHandler::monitor(AMQP::TcpConnection* connection, int fd, +// int flags) { +// // @todo +// // add your own implementation, for example by adding the file +// // descriptor to the main application event loop (like the select() or +// // poll() loop). When the event loop reports that the descriptor becomes +// // readable and/or writable, it is up to you to inform the AMQP-CPP +// // library that the filedescriptor is active by calling the +// // connection->process(fd, flags) method. +// } + +} // end internal namespace + +MeshModemRabbitMQAdapter::MeshModemRabbitMQAdapter() + : rmqUrl(), + rmqExchangeName(), + rmqCommandQueueName(), + rmqRoutingKey(), + rmqHandler(), + rmqConnection(), + rmqChannel(), + mdmFilePath(), + tty(), + mdmPortNumber(0), + modem(), + ready(false) +{ + // Do nothing - all done by configure() and connect() +} + +MeshModemRabbitMQAdapter::~MeshModemRabbitMQAdapter() { + disconnect(true); + // do nothing else (everything uses smart pointers) +} + +bool MeshModemRabbitMQAdapter::configure(uv_loop_t* uvloop, + std::string amqpUrl, + std::string exchangeName, + std::string commandQueueName, + std::string routingKey, + std::string modemTtyFile) { + // TODO input validation + + // LibUV settings + loop = uvloop; + + // RabbitMQ settings + rmqUrl = amqpUrl; + rmqExchangeName = exchangeName; + rmqCommandQueueName = commandQueueName; + rmqRoutingKey = routingKey; + + // MESH Modem settings + mdmFilePath = modemTtyFile; + + return true; // TEMPORARY UNTIL VALIDATION IMPLEMENTED +} + +bool +MeshModemRabbitMQAdapter::connect() +{ + ready = false; + + // TODO check if already assigned + // TODO ensure configure has occurred + + // 1. Connect to RabbitMQ and declare objects + + // declare handler + rmqHandler.emplace(loop); + + // create a AMQP connection object + + AMQP::Address address(rmqUrl); + rmqConnection.emplace( + &*rmqHandler, + address + ); + + // and create a channel + rmqChannel.emplace(&*rmqConnection); + + // use the channel object to call the AMQP method you like + rmqChannel->declareExchange(rmqExchangeName, + AMQP::fanout); // TODO verify exchange type + rmqChannel + ->declareQueue(rmqCommandQueueName, + AMQP::durable + AMQP::autodelete + AMQP::exclusive) + .onSuccess([](const std::string& name, uint32_t messagecount, + uint32_t consumercount) { + // report the name of the temporary queue + std::cout << "declared queue " << name << std::endl; + }); + rmqChannel->bindQueue(rmqExchangeName, rmqCommandQueueName, rmqRoutingKey); + + // TODO set up heartbeat for RabbitMQ + + // 2. Attempt to connect to the local modem device + + // Open the serial port + mdmPortNumber = open(mdmFilePath.c_str(), O_RDWR); // read and write + + // Read in existing settings, and handle any error + if (0 != tcgetattr(mdmPortNumber, &tty)) { + printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); + return false; + } + + tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) + tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in + // communication (most common) + tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size + tty.c_cflag |= CS8; // 8 bits per byte (most common) + tty.c_cflag &= + ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common) + tty.c_cflag |= + CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1) + + tty.c_lflag &= ~ICANON; + tty.c_lflag &= ~ECHO; // Disable echo + tty.c_lflag &= ~ECHOE; // Disable erasure + tty.c_lflag &= ~ECHONL; // Disable new-line echo + tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL); // Disable any special handling of received bytes + + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes + // (e.g. newline chars) + tty.c_oflag &= + ~ONLCR; // Prevent conversion of newline to carriage return/line feed + // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT + // PRESENT ON LINUX) tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars + // (0x004) in output (NOT PRESENT ON LINUX) + + tty.c_cc[VTIME] = 10; // Wait for up to 1s (10 deciseconds), returning as + // soon as any data is received. + tty.c_cc[VMIN] = 0; + + // Set in/out baud rate to be 115200 + cfsetispeed(&tty, B115200); + cfsetospeed(&tty, B115200); + + // Save tty settings, also checking for error + if (tcsetattr(mdmPortNumber, TCSANOW, &tty) != 0) { + printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); + return false; + } + + // Now finally pass this open file to the MeshModem class + modem.emplace(mdmPortNumber, + bt_mesh_modem_client_cb { + .read = &herald::mesh::internal::global_read_fn + } + ); + + ready = true; + + // Now both sides are ready, start processing + modem->resume(); + + // callback function that is called when the consume operation starts + auto startCb = [](const std::string& consumertag) { + std::cout << "RabbitMQ consume operation started" << std::endl; + }; + + // callback function that is called when the consume operation failed + auto errorCb = [](const char* message) { + std::cout << "RabbitMQ consume operation failed" << std::endl; + }; + + // callback operation when a message was received + auto messageCb = [this](const AMQP::Message& message, + uint64_t deliveryTag, + bool redelivered) { + std::cout << "RabbitMQ message received" << std::endl; + + // TODO pass this along to the MeshModem as a write() + + // acknowledge the message + rmqChannel->ack(deliveryTag); + }; + + // start consuming from the queue, and install the callbacks + rmqChannel->consume(rmqCommandQueueName) + .onReceived(messageCb) + .onSuccess(startCb) + .onError(errorCb); + + return true; +} + +int +MeshModemRabbitMQAdapter::read(uint8_t* buffer, size_t sz) +{ + std::cout << "Reading " << sz << " bytes of data" << std::endl; + // TODO read something from serial port + // TODO determine protobuf message type + // TODO send to appropriate queue + return sz; // TODO replace this with actual read size +} + +bool +MeshModemRabbitMQAdapter::disconnect(bool immediate) +{ + ready = false; // stop processing immediately + + // Disconnect from the local file path + modem->pause(); + modem.reset(); + close(mdmPortNumber); + mdmPortNumber = 0; + + if (isConnected()) { + // TODO disconnect from RabbitMQ + } + + return true; +} + +bool +MeshModemRabbitMQAdapter::isConnected() +{ + return ready; +} + +} // end namespace +} // end namespace \ No newline at end of file