初始化提交
This commit is contained in:
113
arduino-cli/libraries/painlessMesh-master/.clang-format
Normal file
113
arduino-cli/libraries/painlessMesh-master/.clang-format
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
14
arduino-cli/libraries/painlessMesh-master/.gitignore
vendored
Normal file
14
arduino-cli/libraries/painlessMesh-master/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
*.swp
|
||||
compile_*.sh
|
||||
.vscode/arduino.json
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/settings.json
|
||||
doxygen/*/
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
Makefile
|
||||
bin/
|
||||
cmake_install.cmake
|
||||
compile_commands.json
|
||||
64
arduino-cli/libraries/painlessMesh-master/.gitlab-ci.yml
Normal file
64
arduino-cli/libraries/painlessMesh-master/.gitlab-ci.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
|
||||
platformio:
|
||||
before_script:
|
||||
- apt-get update -qq && apt-get install -y -qq python-pip
|
||||
- pip install platformio
|
||||
- platformio lib --global install ArduinoJson TaskScheduler PubSubClient ESPAsyncTCP AsyncTCP
|
||||
script:
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/mqttBridge/mqttBridge.ino
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/basic/basic.ino -O "build_flags = -Werror"
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/startHere/startHere.ino -O "build_flags = -Wall -Wextra -Wno-unused-parameter -Werror"
|
||||
- platformio ci --lib="." --board=esp32dev examples/startHere/startHere.ino -O "build_flags = -std=c++14"
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/bridge/bridge.ino -O "build_flags = -Werror"
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/logServer/logServer.ino -O "build_flags = -Werror"
|
||||
- platformio ci --lib="." --board=nodemcuv2 examples/logClient/logClient.ino -O "build_flags = -Werror"
|
||||
- platformio ci --lib="." --lib="./examples/namedMesh/" --board=esp32dev examples/namedMesh/namedMesh.ino -O ""
|
||||
|
||||
catch_testing:
|
||||
before_script:
|
||||
- apt-get update -qq && apt-get install -y -qq cmake
|
||||
- apt-get install libboost-system-dev -y -qq
|
||||
- cmake . -DCMAKE_CXX_FLAGS="-Wall -Werror"
|
||||
- make
|
||||
script:
|
||||
- run-parts --regex catch_ bin/
|
||||
|
||||
arduino:
|
||||
before_script:
|
||||
- wget https://downloads.arduino.cc/arduino-1.8.9-linux64.tar.xz
|
||||
- tar xvfJ arduino-1.8.9-linux64.tar.xz
|
||||
- cd arduino-1.8.9
|
||||
- ./arduino --pref "boardsmanager.additional.urls=https://adafruit.github.io/arduino-board-index/package_adafruit_index.json,http://arduino.esp8266.com/stable/package_esp8266com_index.json" --save-prefs
|
||||
- ./arduino --install-boards esp8266:esp8266
|
||||
- ./arduino --install-library TaskScheduler
|
||||
- ./arduino --install-library ArduinoJson
|
||||
- git clone https://github.com/me-no-dev/ESPAsyncTCP; cp -r ESPAsyncTCP/src ~/Arduino/libraries/ESPAsyncTCP
|
||||
- git clone https://github.com/me-no-dev/AsyncTCP; cp -r AsyncTCP/src ~/Arduino/libraries/AsyncTCP
|
||||
- cp -r ../src ~/Arduino/libraries/painlessMesh
|
||||
script:
|
||||
- ./arduino -v --board esp8266:esp8266:d1_mini:xtal=80,eesz=4M1M --verify ../examples/startHere/startHere.ino
|
||||
- ./arduino -v --board esp8266:esp8266:d1_mini:xtal=80,eesz=4M1M --verify ../examples/startHere/basic.ino
|
||||
|
||||
pages:
|
||||
script:
|
||||
- apt-get update && apt-get install -y doxygen
|
||||
- doxygen doxygen/Doxyfile
|
||||
- mv doxygen/documentation/html/ public/
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- master
|
||||
|
||||
pages_develop:
|
||||
script:
|
||||
- apt-get update && apt-get install -y doxygen
|
||||
- doxygen doxygen/Doxyfile
|
||||
- mv doxygen/documentation/html/ html/
|
||||
artifacts:
|
||||
paths:
|
||||
- html
|
||||
only:
|
||||
- develop
|
||||
6
arduino-cli/libraries/painlessMesh-master/.gitmodules
vendored
Normal file
6
arduino-cli/libraries/painlessMesh-master/.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "test/ArduinoJson"]
|
||||
path = test/ArduinoJson
|
||||
url = https://github.com/bblanchon/ArduinoJson.git
|
||||
[submodule "test/TaskScheduler"]
|
||||
path = test/TaskScheduler
|
||||
url = https://github.com/arkhipenko/TaskScheduler
|
||||
26
arduino-cli/libraries/painlessMesh-master/CMakeLists.txt
Normal file
26
arduino-cli/libraries/painlessMesh-master/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
cmake_minimum_required (VERSION 2.6)
|
||||
project (painlessMesh)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
FIND_PACKAGE(Boost 1.42.0 REQUIRED COMPONENTS system)
|
||||
IF(Boost_FOUND)
|
||||
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${Boost_LIBRARY_DIRS})
|
||||
ENDIF(Boost_FOUND)
|
||||
|
||||
FILE(GLOB TESTFILES test/**/catch_*.cpp)
|
||||
foreach(TESTFILE ${TESTFILES})
|
||||
get_filename_component(NAME ${TESTFILE} NAME_WE)
|
||||
add_executable(${NAME} ${TESTFILE} test/catch/fake_serial.cpp src/scheduler.cpp)
|
||||
target_include_directories(${NAME} PUBLIC test/include/ test/catch/ test/ArduinoJson/src/ src/ test/TaskScheduler/src)
|
||||
endforeach()
|
||||
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
||||
|
||||
add_executable(catch_tcp_integration test/boost/tcp_integration.cpp test/catch/fake_serial.cpp src/painlessMeshConnection.cpp src/scheduler.cpp)
|
||||
target_include_directories(catch_tcp_integration PUBLIC test/include/ test/boost/ test/ArduinoJson/src/ test/TaskScheduler/src/ src/)
|
||||
TARGET_LINK_LIBRARIES(catch_tcp_integration ${Boost_LIBRARIES})
|
||||
35
arduino-cli/libraries/painlessMesh-master/CONTRIBUTING.md
Normal file
35
arduino-cli/libraries/painlessMesh-master/CONTRIBUTING.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Contributing
|
||||
|
||||
We try to follow the [git flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) development model. Which means that we have a `develop` branch and `master` branch. All development is done under feature branches, which are (when finished) merged into the development branch. When a new version is released we merge the `develop` branch into the `master` branch.
|
||||
|
||||
## Git flow
|
||||
|
||||
If you would like to use [git flow tools](http://danielkummer.github.io/git-flow-cheatsheet/) you are more than welcome to. We use it and it's pretty nifty. If you see a `feature\` prefix on a comment then that is git flow automating branch creation. It does need more typing than just plain git so I suggest creating shell aliases for the commands.
|
||||
|
||||
## Submit a pull request:
|
||||
|
||||
* If your push triggered a 'you just pushed...' message from GitLab then click on the button provided by that pop up to create a pull request.
|
||||
* If not, then create a pull request and point it to your branch.
|
||||
* Make sure that you're attempting to merge into `develop` and not `master`.
|
||||
* Get your code reviewed by another contributor. If there are no contributors who possess the same set of skills then get them to review it anyway but explain what the code does beforehand and why. Use it as an opportunity for discussion around the feature set, to transfer knowledge, and to possibly [rubber duck](https://en.wikipedia.org/wiki/Rubber_duck_debugging) your code.
|
||||
* Once the code is reviewed then have your reviewer merge your code.
|
||||
|
||||
NOTE: Tests *must* pass in order for the code to be merged.
|
||||
|
||||
NOTE: Always do a `git pull` on `develop` before you start working to capture the latest changes.
|
||||
|
||||
## Versioning
|
||||
|
||||
This project will try its best to adhere to [semver](http://semver.org/) i.e, a codified guide to versioning software. When a new feature is developed or a bug is fixed the version will need to be bumped to signify the change.
|
||||
|
||||
The semver string is built like this:
|
||||
|
||||
Major.Minor.Patch
|
||||
|
||||
A major version bump means that a massive change took place and that application will probably have to be redeployed because a *backwards incompatible* version was released. Example: A library => model relationship change which requires previous configuration options to become invalid.
|
||||
|
||||
A minor version is a *backwards compatible* addition or change to the core software. Most development activity will be this type of version bump. Example: A new feature or model.
|
||||
|
||||
A patch version is a *backwards compatible* bug fix or application configuration change.
|
||||
|
||||
Documentation doesn't require a version bump.
|
||||
674
arduino-cli/libraries/painlessMesh-master/LICENSE
Normal file
674
arduino-cli/libraries/painlessMesh-master/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
painlessMesh
|
||||
Copyright (C) 2016 BlackEdder
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
painlessMesh Copyright (C) 2016 BlackEdder
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
202
arduino-cli/libraries/painlessMesh-master/README.md
Normal file
202
arduino-cli/libraries/painlessMesh-master/README.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Intro to painlessMesh
|
||||
|
||||
painlessMesh is a library that takes care of the particulars of creating a simple mesh network using esp8266 and esp32 hardware. The goal is to allow the programmer to work with a mesh network without having to worry about how the network is structured or managed.
|
||||
|
||||
### True ad-hoc networking
|
||||
|
||||
painlessMesh is a true ad-hoc network, meaning that no-planning, central controller, or router is required. Any system of 1 or more nodes will self-organize into fully functional mesh. The maximum size of the mesh is limited (we think) by the amount of memory in the heap that can be allocated to the sub-connections buffer and so should be really quite high.
|
||||
|
||||
### JSON based
|
||||
|
||||
painlessMesh uses JSON objects for all its messaging. There are a couple of reasons for this. First, it makes the code and the messages human readable and painless to understand and second, it makes it painless to integrate painlessMesh with javascript front-ends, web applications, and other apps. Some performance is lost, but I haven’t been running into performance issues yet. Converting to binary messaging would be fairly straight forward if someone wants to contribute.
|
||||
|
||||
### Wifi & Networking
|
||||
|
||||
painlessMesh is designed to be used with Arduino, but it does not use the Arduino WiFi libraries, as we were running into performance issues (primarily latency) with them. Rather the networking is all done using the native esp32 and esp8266 SDK libraries, which are available through the Arduino IDE. Hopefully though, which networking libraries are used won’t matter to most users much as you can just include painlessMesh.h, run the init() and then work the library through the API.
|
||||
|
||||
### painlessMesh is not IP networking
|
||||
|
||||
painlessMesh does not create a TCP/IP network of nodes. Rather each of the nodes is uniquely identified by its 32bit chipId which is retrieved from the esp8266/esp32 using the `system_get_chip_id()` call in the SDK. Every node will have a unique number. Messages can either be broadcast to all of the nodes on the mesh, or sent specifically to an individual node which is identified by its `nodeId.
|
||||
|
||||
### Limitations and caveats
|
||||
|
||||
- Try to avoid using `delay()` in your code. To maintain the mesh we need to perform some tasks in the background. Using `delay()` will stop these tasks from happening and can cause the mesh to lose stability/fall apart. Instead we recommend using [TaskScheduler](http://playground.arduino.cc/Code/TaskScheduler) which is used in `painlessMesh` itself. Documentation can be found [here](https://github.com/arkhipenko/TaskScheduler/wiki/Full-Document). For other examples on how to use the scheduler see the example folder.
|
||||
- `painlessMesh` subscribes to WiFi events. Please be aware that as a result `painlessMesh` can be incompatible with user programs/other libraries that try to bind to the same events.
|
||||
- Try to be conservative in the number of messages (and especially broadcast messages) you sent per minute. This is to prevent the hardware from overloading. Both esp8266 and esp32 are limited in processing power/memory, making it easy to overload the mesh and destabilise it. And while `painlessMesh` tries to prevent this from happening, it is not always possible to do so.
|
||||
- Messages can go missing or be dropped due to high traffic and you can not rely on all messages to be delivered. One suggestion to work around is to resend messages every so often. Even if some go missing, most should go through. Another option is to have your nodes send replies when they receive a message. The sending nodes can the resend the message if they haven’t gotten a reply in a certain amount of time.
|
||||
|
||||
# Installation
|
||||
|
||||
`painlessMesh` is included in both the Arduino Library Manager and the platformio library registry and can easily be installed via either of those methods.
|
||||
|
||||
## Dependencies
|
||||
|
||||
painlessMesh makes use of the following libraries, which can be installed through the Arduino Library Manager
|
||||
|
||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
||||
- [TaskScheduler](https://github.com/arkhipenko/TaskScheduler)
|
||||
- [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) (ESP8266)
|
||||
- [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) (ESP32)
|
||||
|
||||
If platformio is used to install the library, then the dependency will be installed automatically.
|
||||
|
||||
## Examples
|
||||
|
||||
StartHere is a basic how to use example. It blinks built-in LED (in ESP-12) as many times as nodes are connected to the mesh. Further examples are under the examples directory and shown on the platformio [page](http://platformio.org/lib/show/1269/painlessMesh).
|
||||
|
||||
# Getting help
|
||||
|
||||
There is help available from a variety of sources:
|
||||
|
||||
- The [included examples](https://gitlab.com/painlessMesh/painlessMesh/tree/master/examples)
|
||||
- The [API documentation](http://painlessmesh.gitlab.io/painlessMesh/index.html)
|
||||
- The [wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/home)
|
||||
- On our new [forum/mailinglist](https://groups.google.com/forum/#!forum/painlessmesh-user)
|
||||
- On the [gitter channel](https://gitter.im/painlessMesh/Lobby)
|
||||
|
||||
# Contributing
|
||||
|
||||
We try to follow the [git flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) development model. Which means that we have a `develop` branch and `master` branch. All development is done under feature branches, which are (when finished) merged into the development branch. When a new version is released we merge the `develop` branch into the `master` branch. For more details see the [CONTRIBUTING](https://gitlab.com/painlessMesh/painlessMesh/blob/master/CONTRIBUTING.md) file.
|
||||
|
||||
|
||||
# painlessMesh API
|
||||
|
||||
Using painlessMesh is painless!
|
||||
|
||||
First include the library and create an painlessMesh object like this.
|
||||
|
||||
```
|
||||
#include <painlessMesh.h>
|
||||
painlessMesh mesh;
|
||||
```
|
||||
|
||||
The main member functions are included below. **Full documentation can be found [here](https://painlessmesh.gitlab.io/painlessMesh/index.html)**
|
||||
|
||||
## Member Functions
|
||||
|
||||
### void painlessMesh::init(String ssid, String password, uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA, _auth_mode authmode = AUTH_WPA2_PSK, uint8_t channel = 1, phy_mode_t phymode = PHY_MODE_11G, uint8_t maxtpw = 82, uint8_t hidden = 0, uint8_t maxconn = 4)
|
||||
|
||||
Add this to your setup() function.
|
||||
Initialize the mesh network. This routine does the following things.
|
||||
|
||||
- Starts a wifi network
|
||||
- Begins searching for other wifi networks that are part of the mesh
|
||||
- Logs on to the best mesh network node it finds… if it doesn’t find anything, it starts a new search in 5 seconds.
|
||||
|
||||
`ssid` = the name of your mesh. All nodes share same AP ssid. They are distinguished by BSSID.
|
||||
`password` = wifi password to your mesh.
|
||||
`port` = the TCP port that you want the mesh server to run on. Defaults to 5555 if not specified.
|
||||
[`connectMode`](https://gitlab.com/painlessMesh/painlessMesh/wikis/connect-mode:-WIFI_AP,-WIFI_STA,-WIFI_AP_STA-mode) = switch between WIFI_AP, WIFI_STA and WIFI_AP_STA (default) mode
|
||||
|
||||
### void painlessMesh::stop()
|
||||
|
||||
Stop the node. This will cause the node to disconnect from all other nodes and stop/sending messages.
|
||||
|
||||
### void painlessMesh::update( void )
|
||||
|
||||
Add this to your loop() function
|
||||
This routine runs various maintainance tasks... Not super interesting, but things don't work without it.
|
||||
|
||||
### void painlessMesh::onReceive( &receivedCallback )
|
||||
|
||||
Set a callback routine for any messages that are addressed to this node. Callback routine has the following structure.
|
||||
|
||||
`void receivedCallback( uint32_t from, String &msg )`
|
||||
|
||||
Every time this node receives a message, this callback routine will the called. “from” is the id of the original sender of the message, and “msg” is a string that contains the message. The message can be anything. A JSON, some other text string, or binary data.
|
||||
|
||||
### void painlessMesh::onNewConnection( &newConnectionCallback )
|
||||
|
||||
This fires every time the local node makes a new connection. The callback has the following structure.
|
||||
|
||||
`void newConnectionCallback( uint32_t nodeId )`
|
||||
|
||||
`nodeId` is new connected node ID in the mesh.
|
||||
|
||||
### void painlessMesh::onChangedConnections( &changedConnectionsCallback )
|
||||
|
||||
This fires every time there is a change in mesh topology. Callback has the following structure.
|
||||
|
||||
`void onChangedConnections()`
|
||||
|
||||
There are no parameters passed. This is a signal only.
|
||||
|
||||
### bool painlessMesh::isConnected( nodeId )
|
||||
|
||||
Returns if a given node is currently connected to the mesh.
|
||||
|
||||
`nodeId` is node ID that the request refers to.
|
||||
|
||||
### void painlessMesh::onNodeTimeAdjusted( &nodeTimeAdjustedCallback )
|
||||
|
||||
This fires every time local time is adjusted to synchronize it with mesh time. Callback has the following structure.
|
||||
|
||||
`void onNodeTimeAdjusted(int32_t offset)`
|
||||
|
||||
`offset` is the adjustment delta that has benn calculated and applied to local clock.
|
||||
|
||||
### void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived)
|
||||
|
||||
This fires when a time delay masurement response is received, after a request was sent. Callback has the following structure.
|
||||
|
||||
`void onNodeDelayReceived(uint32_t nodeId, int32_t delay)`
|
||||
|
||||
`nodeId` The node that originated response.
|
||||
|
||||
`delay` One way network trip delay in microseconds.
|
||||
|
||||
### bool painlessMesh::sendBroadcast( String &msg, bool includeSelf = false)
|
||||
|
||||
Sends msg to every node on the entire mesh network. By default the current node is excluded from receiving the message (`includeSelf = false`). `includeSelf = true` overrides this behaviour, causing the `receivedCallback` to be called when sending a broadcast message.
|
||||
|
||||
returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure.
|
||||
|
||||
### bool painlessMesh::sendSingle(uint32_t dest, String &msg)
|
||||
|
||||
Sends msg to the node with Id == dest.
|
||||
|
||||
returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure.
|
||||
|
||||
### String painlessMesh::subConnectionJson()
|
||||
|
||||
Returns mesh topology in JSON format.
|
||||
|
||||
### std::list<uint32_t> painlessMesh::getNodeList()
|
||||
|
||||
Get a list of all known nodes. This includes nodes that are both directly and indirectly connected to the current node.
|
||||
|
||||
### uint32_t painlessMesh::getNodeId( void )
|
||||
|
||||
Return the chipId of the node that we are running on.
|
||||
|
||||
### uint32_t painlessMesh::getNodeTime( void )
|
||||
|
||||
Returns the mesh timebase microsecond counter. Rolls over 71 minutes from startup of the first node.
|
||||
|
||||
Nodes try to keep a common time base synchronizing to each other using [an SNTP based protocol](https://gitlab.com/painlessMesh/painlessMesh/wikis/mesh-protocol#time-sync)
|
||||
|
||||
### bool painlessMesh::startDelayMeas(uint32_t nodeId)
|
||||
|
||||
Sends a node a packet to measure network trip delay to that node. Returns true if nodeId is connected to the mesh, false otherwise. After calling this function, user program have to wait to the response in the form of a callback specified by `void painlessMesh::onNodeDelayReceived(nodeDelayCallback_t onDelayReceived)`.
|
||||
|
||||
nodeDelayCallback_t is a funtion in the form of `void (uint32_t nodeId, int32_t delay)`.
|
||||
|
||||
### void painlessMesh::stationManual( String ssid, String password, uint16_t port, uint8_t *remote_ip )
|
||||
|
||||
Connects the node to an AP outside the mesh. When specifying a `remote_ip` and `port`, the node opens a TCP connection after establishing the WiFi connection.
|
||||
|
||||
Note: The mesh must be on the same WiFi channel as the AP.
|
||||
|
||||
# Funding and donations
|
||||
|
||||
You can donate using one of our cryptocoin addresses:
|
||||
|
||||
- ethereum: 0x45B4638faAB5CF1bbcC1ee177681E74343EC9c86
|
||||
- bitcoin: 1HqEkiU3BxGwJ3EL9QJFThWF4zGN4tisim
|
||||
- bitcoin cash: qqu7vx77q8tfllep24zvvlfcqg7w0qwrtu8paaaxdu
|
||||
|
||||
Most development of painlessMesh has been done as a hobby, but some specific features have been funded by the companies listed below:
|
||||
|
||||

|
||||
|
||||
[Sowillo](http://sowillo.com/en/)
|
||||
5
arduino-cli/libraries/painlessMesh-master/autotest.sh
Normal file
5
arduino-cli/libraries/painlessMesh-master/autotest.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
inotify-hookable -w src -w test/startHere/startHere.ino -f CMakeLists.txt -c "platformio ci --lib=\".\" --board=esp32dev test/startHere/startHere.ino -O \"build_flags = -std=c++14\"" &
|
||||
|
||||
inotify-hookable -w src -w test -f CMakeLists.txt -c "rm bin/catch_*; cmake . -DCMAKE_CXX_FLAGS=\"-Wall -Werror\"; make -j4; run-parts --regex catch_ bin/"
|
||||
2427
arduino-cli/libraries/painlessMesh-master/doxygen/Doxyfile
Normal file
2427
arduino-cli/libraries/painlessMesh-master/doxygen/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library
|
||||
//
|
||||
// 1. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
|
||||
// 2. prints anything it receives to Serial.print
|
||||
//
|
||||
//
|
||||
//************************************************************
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
painlessMesh mesh;
|
||||
|
||||
// User stub
|
||||
void sendMessage() ; // Prototype so PlatformIO doesn't complain
|
||||
|
||||
Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
|
||||
|
||||
void sendMessage() {
|
||||
String msg = "Hello from node ";
|
||||
msg += mesh.getNodeId();
|
||||
mesh.sendBroadcast( msg );
|
||||
taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
|
||||
}
|
||||
|
||||
// Needed for painless library
|
||||
void receivedCallback( uint32_t from, String &msg ) {
|
||||
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
|
||||
void newConnectionCallback(uint32_t nodeId) {
|
||||
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
|
||||
}
|
||||
|
||||
void changedConnectionCallback() {
|
||||
Serial.printf("Changed connections\n");
|
||||
}
|
||||
|
||||
void nodeTimeAdjustedCallback(int32_t offset) {
|
||||
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
|
||||
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
mesh.onNewConnection(&newConnectionCallback);
|
||||
mesh.onChangedConnections(&changedConnectionCallback);
|
||||
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
|
||||
|
||||
userScheduler.addTask( taskSendMessage );
|
||||
taskSendMessage.enable();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// it will run the user scheduler as well
|
||||
mesh.update();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library to
|
||||
// connect to a node on another network. Please see the WIKI on gitlab
|
||||
// for more details
|
||||
// https://gitlab.com/painlessMesh/painlessMesh/wikis/bridge-between-mesh-and-another-network
|
||||
//************************************************************
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
#define STATION_SSID "mySSID"
|
||||
#define STATION_PASSWORD "myPASSWORD"
|
||||
#define STATION_PORT 5555
|
||||
uint8_t station_ip[4] = {192,168,1,128}; // IP of the server
|
||||
|
||||
// prototypes
|
||||
void receivedCallback( uint32_t from, String &msg );
|
||||
|
||||
painlessMesh mesh;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
|
||||
|
||||
|
||||
// Channel set to 6. Make sure to use the same channel for your mesh and for you other
|
||||
// network (STATION_SSID)
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6 );
|
||||
// Setup over the air update support
|
||||
mesh.initOTA("bridge");
|
||||
|
||||
mesh.stationManual(STATION_SSID, STATION_PASSWORD, STATION_PORT, station_ip);
|
||||
// Bridge node, should (in most cases) be a root node. See [the wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation) for some background
|
||||
mesh.setRoot(true);
|
||||
// This node and all other nodes should ideally know the mesh contains a root, so call this on all nodes
|
||||
mesh.setContainsRoot(true);
|
||||
|
||||
|
||||
mesh.onReceive(&receivedCallback);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
}
|
||||
|
||||
void receivedCallback( uint32_t from, String &msg ) {
|
||||
Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ArduinoJson
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
@@ -0,0 +1,33 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library and echos any
|
||||
// messages it receives
|
||||
//
|
||||
//************************************************************
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
// Prototypes
|
||||
void receivedCallback( uint32_t from, String &msg );
|
||||
|
||||
painlessMesh mesh;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
|
||||
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
}
|
||||
|
||||
void receivedCallback( uint32_t from, String &msg ) {
|
||||
Serial.printf("echoNode: Received from %u msg=%s\n", from, msg.c_str());
|
||||
mesh.sendSingle(from, msg);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ArduinoJson
|
||||
# painlessMesh
|
||||
@@ -0,0 +1,94 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library to
|
||||
// setup a node that logs to a central logging node
|
||||
// The logServer example shows how to configure the central logging nodes
|
||||
//************************************************************
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
painlessMesh mesh;
|
||||
|
||||
// Prototype
|
||||
void receivedCallback( uint32_t from, String &msg );
|
||||
|
||||
size_t logServerId = 0;
|
||||
|
||||
// Send message to the logServer every 10 seconds
|
||||
Task myLoggingTask(10000, TASK_FOREVER, []() {
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
DynamicJsonDocument jsonBuffer(1024);
|
||||
JsonObject msg = jsonBuffer.to<JsonObject>();
|
||||
#else
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& msg = jsonBuffer.createObject();
|
||||
#endif
|
||||
msg["topic"] = "sensor";
|
||||
msg["value"] = random(0, 180);
|
||||
|
||||
String str;
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
serializeJson(msg, str);
|
||||
#else
|
||||
msg.printTo(str);
|
||||
#endif
|
||||
if (logServerId == 0) // If we don't know the logServer yet
|
||||
mesh.sendBroadcast(str);
|
||||
else
|
||||
mesh.sendSingle(logServerId, str);
|
||||
|
||||
// log to serial
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
serializeJson(msg, Serial);
|
||||
#else
|
||||
msg.printTo(Serial);
|
||||
#endif
|
||||
Serial.printf("\n");
|
||||
});
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
|
||||
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6 );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
|
||||
// Add the task to the your scheduler
|
||||
userScheduler.addTask(myLoggingTask);
|
||||
myLoggingTask.enable();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// it will run the user scheduler as well
|
||||
mesh.update();
|
||||
}
|
||||
|
||||
void receivedCallback( uint32_t from, String &msg ) {
|
||||
Serial.printf("logClient: Received from %u msg=%s\n", from, msg.c_str());
|
||||
|
||||
// Saving logServer
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
DynamicJsonDocument jsonBuffer(1024 + msg.length());
|
||||
DeserializationError error = deserializeJson(jsonBuffer, msg);
|
||||
if (error) {
|
||||
Serial.printf("DeserializationError\n");
|
||||
return;
|
||||
}
|
||||
JsonObject root = jsonBuffer.as<JsonObject>();
|
||||
#else
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& root = jsonBuffer.parseObject(msg);
|
||||
#endif
|
||||
if (root.containsKey("topic")) {
|
||||
if (String("logServer").equals(root["topic"].as<String>())) {
|
||||
// check for on: true or false
|
||||
logServerId = root["nodeId"];
|
||||
Serial.printf("logServer detected!!!\n");
|
||||
}
|
||||
Serial.printf("Handled from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ArduinoJson
|
||||
# painlessMesh
|
||||
@@ -0,0 +1,78 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library to
|
||||
// setup a single node (this node) as a logging node
|
||||
// The logClient example shows how to configure the other nodes
|
||||
// to log to this server
|
||||
//************************************************************
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
painlessMesh mesh;
|
||||
// Prototype
|
||||
void receivedCallback( uint32_t from, String &msg );
|
||||
|
||||
|
||||
// Send my ID every 10 seconds to inform others
|
||||
Task logServerTask(10000, TASK_FOREVER, []() {
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
DynamicJsonDocument jsonBuffer(1024);
|
||||
JsonObject msg = jsonBuffer.to<JsonObject>();
|
||||
#else
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& msg = jsonBuffer.createObject();
|
||||
#endif
|
||||
msg["topic"] = "logServer";
|
||||
msg["nodeId"] = mesh.getNodeId();
|
||||
|
||||
String str;
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
serializeJson(msg, str);
|
||||
#else
|
||||
msg.printTo(str);
|
||||
#endif
|
||||
mesh.sendBroadcast(str);
|
||||
|
||||
// log to serial
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
serializeJson(msg, Serial);
|
||||
#else
|
||||
msg.printTo(Serial);
|
||||
#endif
|
||||
Serial.printf("\n");
|
||||
});
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE | DEBUG ); // all types on
|
||||
//mesh.setDebugMsgTypes( ERROR | CONNECTION | SYNC | S_TIME ); // set before init() so that you can see startup messages
|
||||
mesh.setDebugMsgTypes( ERROR | CONNECTION | S_TIME ); // set before init() so that you can see startup messages
|
||||
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6 );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
|
||||
mesh.onNewConnection([](size_t nodeId) {
|
||||
Serial.printf("New Connection %u\n", nodeId);
|
||||
});
|
||||
|
||||
mesh.onDroppedConnection([](size_t nodeId) {
|
||||
Serial.printf("Dropped Connection %u\n", nodeId);
|
||||
});
|
||||
|
||||
// Add the task to the your scheduler
|
||||
userScheduler.addTask(logServerTask);
|
||||
logServerTask.enable();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// it will run the user scheduler as well
|
||||
mesh.update();
|
||||
}
|
||||
|
||||
void receivedCallback( uint32_t from, String &msg ) {
|
||||
Serial.printf("logServer: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ArduinoJson
|
||||
# painlessMesh
|
||||
@@ -0,0 +1,118 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library to
|
||||
// connect to a another network and relay messages from a MQTT broker to the nodes of the mesh network.
|
||||
// To send a message to a mesh node, you can publish it to "painlessMesh/to/12345678" where 12345678 equals the nodeId.
|
||||
// To broadcast a message to all nodes in the mesh you can publish it to "painlessMesh/to/broadcast".
|
||||
// When you publish "getNodes" to "painlessMesh/to/gateway" you receive the mesh topology as JSON
|
||||
// Every message from the mesh which is send to the gateway node will be published to "painlessMesh/from/12345678" where 12345678
|
||||
// is the nodeId from which the packet was send.
|
||||
//************************************************************
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <painlessMesh.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
#define STATION_SSID "YourAP_SSID"
|
||||
#define STATION_PASSWORD "YourAP_PWD"
|
||||
|
||||
#define HOSTNAME "MQTT_Bridge"
|
||||
|
||||
// Prototypes
|
||||
void receivedCallback( const uint32_t &from, const String &msg );
|
||||
void mqttCallback(char* topic, byte* payload, unsigned int length);
|
||||
|
||||
IPAddress getlocalIP();
|
||||
|
||||
IPAddress myIP(0,0,0,0);
|
||||
IPAddress mqttBroker(192, 168, 1, 1);
|
||||
|
||||
painlessMesh mesh;
|
||||
WiFiClient wifiClient;
|
||||
PubSubClient mqttClient(mqttBroker, 1883, mqttCallback, wifiClient);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
|
||||
|
||||
// Channel set to 6. Make sure to use the same channel for your mesh and for you other
|
||||
// network (STATION_SSID)
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6 );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
|
||||
mesh.stationManual(STATION_SSID, STATION_PASSWORD);
|
||||
mesh.setHostname(HOSTNAME);
|
||||
|
||||
// Bridge node, should (in most cases) be a root node. See [the wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation) for some background
|
||||
mesh.setRoot(true);
|
||||
// This node and all other nodes should ideally know the mesh contains a root, so call this on all nodes
|
||||
mesh.setContainsRoot(true);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
mqttClient.loop();
|
||||
|
||||
if(myIP != getlocalIP()){
|
||||
myIP = getlocalIP();
|
||||
Serial.println("My IP is " + myIP.toString());
|
||||
|
||||
if (mqttClient.connect("painlessMeshClient")) {
|
||||
mqttClient.publish("painlessMesh/from/gateway","Ready!");
|
||||
mqttClient.subscribe("painlessMesh/to/#");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void receivedCallback( const uint32_t &from, const String &msg ) {
|
||||
Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
|
||||
String topic = "painlessMesh/from/" + String(from);
|
||||
mqttClient.publish(topic.c_str(), msg.c_str());
|
||||
}
|
||||
|
||||
void mqttCallback(char* topic, uint8_t* payload, unsigned int length) {
|
||||
char* cleanPayload = (char*)malloc(length+1);
|
||||
payload[length] = '\0';
|
||||
memcpy(cleanPayload, payload, length+1);
|
||||
String msg = String(cleanPayload);
|
||||
free(cleanPayload);
|
||||
|
||||
String targetStr = String(topic).substring(16);
|
||||
|
||||
if(targetStr == "gateway")
|
||||
{
|
||||
if(msg == "getNodes")
|
||||
{
|
||||
auto nodes = mesh.getNodeList(true);
|
||||
String str;
|
||||
for (auto &&id : nodes)
|
||||
str += String(id) + String(" ");
|
||||
mqttClient.publish("painlessMesh/from/gateway", str.c_str());
|
||||
}
|
||||
}
|
||||
else if(targetStr == "broadcast")
|
||||
{
|
||||
mesh.sendBroadcast(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t target = strtoul(targetStr.c_str(), NULL, 10);
|
||||
if(mesh.isConnected(target))
|
||||
{
|
||||
mesh.sendSingle(target, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
mqttClient.publish("painlessMesh/from/gateway", "Client not connected!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress getlocalIP() {
|
||||
return IPAddress(mesh.getStationIP());
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
PubSubClient
|
||||
# painlessMesh
|
||||
@@ -0,0 +1,119 @@
|
||||
#include<map>
|
||||
|
||||
#include "painlessMesh.h"
|
||||
using namespace painlessmesh;
|
||||
|
||||
typedef std::function<void(String &from, String &msg)> namedReceivedCallback_t;
|
||||
|
||||
class namedMesh : public painlessMesh {
|
||||
public:
|
||||
namedMesh() {
|
||||
auto cb = [this](uint32_t from, String &msg) {
|
||||
// Try to parse it.. Need to test it with non json function
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
DynamicJsonDocument jsonBuffer(1024 + msg.length());
|
||||
deserializeJson(jsonBuffer, msg);
|
||||
JsonObject root = jsonBuffer.as<JsonObject>();
|
||||
#else
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject &root = jsonBuffer.parseObject(msg);
|
||||
#endif
|
||||
if (root.containsKey("topic") &&
|
||||
String("nameBroadCast").equals(root["topic"].as<String>())) {
|
||||
nameMap[from] = root["name"].as<String>();
|
||||
} else {
|
||||
if (userReceivedCallback)
|
||||
// If needed send it on to userReceivedCallback
|
||||
userReceivedCallback(from, msg);
|
||||
if (userNamedReceivedCallback) {
|
||||
String name;
|
||||
// If needed look up name and send it on to
|
||||
// userNamedReceivedCallback
|
||||
if (nameMap.count(from) > 0) {
|
||||
name = nameMap[from];
|
||||
} else {
|
||||
name = String(from);
|
||||
}
|
||||
userNamedReceivedCallback(name, msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
painlessMesh::onReceive(cb);
|
||||
changedConnectionCallbacks.push_back([this](uint32_t id) {
|
||||
if (nameBroadCastTask.isEnabled())
|
||||
nameBroadCastTask.forceNextIteration();
|
||||
});
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
void setName(String &name) {
|
||||
nodeName = name;
|
||||
// Start broadcast task if not done yet
|
||||
if (!nameBroadCastInit) {
|
||||
// Initialize
|
||||
nameBroadCastTask.set(5*TASK_MINUTE, TASK_FOREVER,
|
||||
[this]() {
|
||||
String msg;
|
||||
// Create arduinoJson msg
|
||||
#if ARDUINOJSON_VERSION_MAJOR==6
|
||||
DynamicJsonDocument jsonBuffer(1024);
|
||||
JsonObject root = jsonBuffer.to<JsonObject>();
|
||||
root["topic"] = "nameBroadCast";
|
||||
root["name"] = this->getName();
|
||||
serializeJson(root, msg);
|
||||
#else
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& root = jsonBuffer.createObject();
|
||||
root["topic"] = "nameBroadCast";
|
||||
root["name"] = this->getName();
|
||||
root.printTo(msg);
|
||||
#endif
|
||||
this->sendBroadcast(msg);
|
||||
}
|
||||
);
|
||||
// Add it
|
||||
mScheduler->addTask(nameBroadCastTask);
|
||||
nameBroadCastTask.enableDelayed();
|
||||
|
||||
nameBroadCastInit = true;
|
||||
}
|
||||
nameBroadCastTask.forceNextIteration();
|
||||
}
|
||||
|
||||
using painlessMesh::sendSingle;
|
||||
bool sendSingle(String &name, String &msg) {
|
||||
// Look up name
|
||||
for (auto && pr : nameMap) {
|
||||
if (name.equals(pr.second)) {
|
||||
uint32_t to = pr.first;
|
||||
return painlessMesh::sendSingle(to, msg);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void stop() {
|
||||
nameBroadCastTask.disable();
|
||||
mScheduler->deleteTask(nameBroadCastTask);
|
||||
painlessMesh::stop();
|
||||
}
|
||||
|
||||
virtual void onReceive(receivedCallback_t onReceive) {
|
||||
userReceivedCallback = onReceive;
|
||||
}
|
||||
void onReceive(namedReceivedCallback_t onReceive) {
|
||||
userNamedReceivedCallback = onReceive;
|
||||
}
|
||||
protected:
|
||||
String nodeName;
|
||||
std::map<uint32_t, String> nameMap;
|
||||
|
||||
receivedCallback_t userReceivedCallback;
|
||||
namedReceivedCallback_t userNamedReceivedCallback;
|
||||
|
||||
bool nameBroadCastInit = false;
|
||||
Task nameBroadCastTask;
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library
|
||||
//
|
||||
// This example shows how to build a mesh with named nodes
|
||||
//
|
||||
//************************************************************
|
||||
#include "namedMesh.h"
|
||||
|
||||
#define MESH_SSID "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
namedMesh mesh;
|
||||
|
||||
String nodeName = "logNode"; // Name needs to be unique
|
||||
|
||||
Task taskSendMessage( TASK_SECOND*30, TASK_FOREVER, []() {
|
||||
String msg = String("This is a message from: ") + nodeName + String(" for logNode");
|
||||
String to = "logNode";
|
||||
mesh.sendSingle(to, msg);
|
||||
}); // start with a one second interval
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes(ERROR | DEBUG | CONNECTION); // set before init() so that you can see startup messages
|
||||
|
||||
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
||||
|
||||
mesh.setName(nodeName); // This needs to be an unique name!
|
||||
|
||||
mesh.onReceive([](uint32_t from, String &msg) {
|
||||
Serial.printf("Received message by id from: %u, %s\n", from, msg.c_str());
|
||||
});
|
||||
|
||||
mesh.onReceive([](String &from, String &msg) {
|
||||
Serial.printf("Received message by name from: %s, %s\n", from.c_str(), msg.c_str());
|
||||
});
|
||||
|
||||
mesh.onChangedConnections([]() {
|
||||
Serial.printf("Changed connection\n");
|
||||
});
|
||||
|
||||
userScheduler.addTask(taskSendMessage);
|
||||
taskSendMessage.enable();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// it will run the user scheduler as well
|
||||
mesh.update();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
arduinoUnity
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
@@ -0,0 +1,20 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
arduinoUnity
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
@@ -0,0 +1,153 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the easyMesh library
|
||||
//
|
||||
// 1. blinks led once for every node on the mesh
|
||||
// 2. blink cycle repeats every BLINK_PERIOD
|
||||
// 3. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
|
||||
// 4. prints anything it receives to Serial.print
|
||||
//
|
||||
//
|
||||
//************************************************************
|
||||
#include <painlessMesh.h>
|
||||
|
||||
// some gpio pin that is connected to an LED...
|
||||
// on my rig, this is 5, change to the right number of your LED.
|
||||
#define LED 2 // GPIO number of connected LED, ON ESP-12 IS GPIO2
|
||||
|
||||
#define BLINK_PERIOD 3000 // milliseconds until cycle repeat
|
||||
#define BLINK_DURATION 100 // milliseconds LED is on for
|
||||
|
||||
#define MESH_SSID "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
// Prototypes
|
||||
void sendMessage();
|
||||
void receivedCallback(uint32_t from, String & msg);
|
||||
void newConnectionCallback(uint32_t nodeId);
|
||||
void changedConnectionCallback();
|
||||
void nodeTimeAdjustedCallback(int32_t offset);
|
||||
void delayReceivedCallback(uint32_t from, int32_t delay);
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
painlessMesh mesh;
|
||||
|
||||
bool calc_delay = false;
|
||||
SimpleList<uint32_t> nodes;
|
||||
|
||||
void sendMessage() ; // Prototype
|
||||
Task taskSendMessage( TASK_SECOND * 1, TASK_FOREVER, &sendMessage ); // start with a one second interval
|
||||
|
||||
// Task to blink the number of nodes
|
||||
Task blinkNoNodes;
|
||||
bool onFlag = false;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
mesh.setDebugMsgTypes(ERROR | DEBUG); // set before init() so that you can see error messages
|
||||
|
||||
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
||||
mesh.onReceive(&receivedCallback);
|
||||
mesh.onNewConnection(&newConnectionCallback);
|
||||
mesh.onChangedConnections(&changedConnectionCallback);
|
||||
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
|
||||
mesh.onNodeDelayReceived(&delayReceivedCallback);
|
||||
|
||||
userScheduler.addTask( taskSendMessage );
|
||||
taskSendMessage.enable();
|
||||
|
||||
blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
|
||||
// If on, switch off, else switch on
|
||||
if (onFlag)
|
||||
onFlag = false;
|
||||
else
|
||||
onFlag = true;
|
||||
blinkNoNodes.delay(BLINK_DURATION);
|
||||
|
||||
if (blinkNoNodes.isLastIteration()) {
|
||||
// Finished blinking. Reset task for next run
|
||||
// blink number of nodes (including this node) times
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
// Calculate delay based on current mesh time and BLINK_PERIOD
|
||||
// This results in blinks between nodes being synced
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD -
|
||||
(mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
}
|
||||
});
|
||||
userScheduler.addTask(blinkNoNodes);
|
||||
blinkNoNodes.enable();
|
||||
|
||||
randomSeed(analogRead(A0));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
digitalWrite(LED, !onFlag);
|
||||
}
|
||||
|
||||
void sendMessage() {
|
||||
String msg = "Hello from node ";
|
||||
msg += mesh.getNodeId();
|
||||
msg += " myFreeMemory: " + String(ESP.getFreeHeap());
|
||||
mesh.sendBroadcast(msg);
|
||||
|
||||
if (calc_delay) {
|
||||
SimpleList<uint32_t>::iterator node = nodes.begin();
|
||||
while (node != nodes.end()) {
|
||||
mesh.startDelayMeas(*node);
|
||||
node++;
|
||||
}
|
||||
calc_delay = false;
|
||||
}
|
||||
|
||||
Serial.printf("Sending message: %s\n", msg.c_str());
|
||||
|
||||
taskSendMessage.setInterval( random(TASK_SECOND * 1, TASK_SECOND * 5)); // between 1 and 5 seconds
|
||||
}
|
||||
|
||||
|
||||
void receivedCallback(uint32_t from, String & msg) {
|
||||
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
|
||||
void newConnectionCallback(uint32_t nodeId) {
|
||||
// Reset blink task
|
||||
onFlag = false;
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
|
||||
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
|
||||
Serial.printf("--> startHere: New Connection, %s\n", mesh.subConnectionJson(true).c_str());
|
||||
}
|
||||
|
||||
void changedConnectionCallback() {
|
||||
Serial.printf("Changed connections\n");
|
||||
// Reset blink task
|
||||
onFlag = false;
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
|
||||
nodes = mesh.getNodeList();
|
||||
|
||||
Serial.printf("Num nodes: %d\n", nodes.size());
|
||||
Serial.printf("Connection list:");
|
||||
|
||||
SimpleList<uint32_t>::iterator node = nodes.begin();
|
||||
while (node != nodes.end()) {
|
||||
Serial.printf(" %u", *node);
|
||||
node++;
|
||||
}
|
||||
Serial.println();
|
||||
calc_delay = true;
|
||||
}
|
||||
|
||||
void nodeTimeAdjustedCallback(int32_t offset) {
|
||||
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
|
||||
}
|
||||
|
||||
void delayReceivedCallback(uint32_t from, int32_t delay) {
|
||||
Serial.printf("Delay to node %u is %d us\n", from, delay);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ESPAsyncTCP
|
||||
ESPAsyncWebServer
|
||||
ArduinoJson
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32doit-devkit-v1
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
AsyncTCP
|
||||
AsyncWebServer
|
||||
ArduinoJson
|
||||
@@ -0,0 +1,89 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the painlessMesh library to
|
||||
// connect to a another network and broadcast message from a webpage to the edges of the mesh network.
|
||||
// This sketch can be extended further using all the abilities of the AsyncWebserver library (WS, events, ...)
|
||||
// for more details
|
||||
// https://gitlab.com/painlessMesh/painlessMesh/wikis/bridge-between-mesh-and-another-network
|
||||
// for more details about my version
|
||||
// https://gitlab.com/Assassynv__V/painlessMesh
|
||||
// and for more details about the AsyncWebserver library
|
||||
// https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
//************************************************************
|
||||
|
||||
#include "IPAddress.h"
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include "Hash.h"
|
||||
#include <ESPAsyncTCP.h>
|
||||
#else
|
||||
#include <AsyncTCP.h>
|
||||
#endif
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#define MESH_PREFIX "whateverYouLike"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
#define STATION_SSID "mySSID"
|
||||
#define STATION_PASSWORD "myPASSWORD"
|
||||
|
||||
#define HOSTNAME "HTTP_BRIDGE"
|
||||
|
||||
// Prototype
|
||||
void receivedCallback( const uint32_t &from, const String &msg );
|
||||
IPAddress getlocalIP();
|
||||
|
||||
painlessMesh mesh;
|
||||
AsyncWebServer server(80);
|
||||
IPAddress myIP(0,0,0,0);
|
||||
IPAddress myAPIP(0,0,0,0);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION ); // set before init() so that you can see startup messages
|
||||
|
||||
// Channel set to 6. Make sure to use the same channel for your mesh and for you other
|
||||
// network (STATION_SSID)
|
||||
mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6 );
|
||||
mesh.onReceive(&receivedCallback);
|
||||
|
||||
mesh.stationManual(STATION_SSID, STATION_PASSWORD);
|
||||
mesh.setHostname(HOSTNAME);
|
||||
|
||||
// Bridge node, should (in most cases) be a root node. See [the wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation) for some background
|
||||
mesh.setRoot(true);
|
||||
// This node and all other nodes should ideally know the mesh contains a root, so call this on all nodes
|
||||
mesh.setContainsRoot(true);
|
||||
|
||||
myAPIP = IPAddress(mesh.getAPIP());
|
||||
Serial.println("My AP IP is " + myAPIP.toString());
|
||||
|
||||
//Async webserver
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send(200, "text/html", "<form>Text to Broadcast<br><input type='text' name='BROADCAST'><br><br><input type='submit' value='Submit'></form>");
|
||||
if (request->hasArg("BROADCAST")){
|
||||
String msg = request->arg("BROADCAST");
|
||||
mesh.sendBroadcast(msg);
|
||||
}
|
||||
});
|
||||
server.begin();
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
if(myIP != getlocalIP()){
|
||||
myIP = getlocalIP();
|
||||
Serial.println("My IP is " + myIP.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void receivedCallback( const uint32_t &from, const String &msg ) {
|
||||
Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
|
||||
IPAddress getlocalIP() {
|
||||
return IPAddress(mesh.getStationIP());
|
||||
}
|
||||
29
arduino-cli/libraries/painlessMesh-master/library.json
Normal file
29
arduino-cli/libraries/painlessMesh-master/library.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "painlessMesh",
|
||||
"keywords": "ethernet, m2m, iot",
|
||||
"description": "A painless way to setup a mesh with ESP8266 and ESP32 devices",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitlab.com/painlessMesh/painlessMesh"
|
||||
},
|
||||
"version": "1.4.2",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "espressif8266, espressif32",
|
||||
"dependencies": [
|
||||
{ "name": "ArduinoJson" },
|
||||
{ "name": "TaskScheduler" },
|
||||
{ "name": "AsyncTCP", "platforms" : "espressif32" },
|
||||
{ "name": "ESPAsyncTCP", "platforms" : "espressif8266" }
|
||||
],
|
||||
"authors": [
|
||||
{ "name": "Scotty Franzyshen" },
|
||||
{
|
||||
"name": "Coopdis",
|
||||
"url": "https://github.com/Coopdis"
|
||||
},
|
||||
{ "name": "Edwin van Leeuwen" },
|
||||
{ "name": "Germán Martín" },
|
||||
{ "name": "Maximilian Schwarz" },
|
||||
{ "name": "Doanh Doanh" }
|
||||
]
|
||||
}
|
||||
10
arduino-cli/libraries/painlessMesh-master/library.properties
Normal file
10
arduino-cli/libraries/painlessMesh-master/library.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
name=Painless Mesh
|
||||
version=1.4.2
|
||||
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh
|
||||
maintainer=Edwin van Leeuwen
|
||||
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices
|
||||
paragraph=A painless way to setup a mesh with ESP8266 and ESP32 devices
|
||||
category=Communication
|
||||
url=https://gitlab.com/painlessMesh/painlessMesh
|
||||
architectures=esp8266,esp32
|
||||
includes=painlessMesh.h,painlessMeshSync.h
|
||||
327
arduino-cli/libraries/painlessMesh-master/src/arduino/wifi.hpp
Normal file
327
arduino-cli/libraries/painlessMesh-master/src/arduino/wifi.hpp
Normal file
@@ -0,0 +1,327 @@
|
||||
#ifndef _PAINLESS_MESH_ARDUINO_WIFI_HPP_
|
||||
#define _PAINLESS_MESH_ARDUINO_WIFI_HPP_
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
#include "painlessMeshConnection.h"
|
||||
#include "painlessMeshSTA.h"
|
||||
|
||||
#include "painlessmesh/callback.hpp"
|
||||
#include "painlessmesh/mesh.hpp"
|
||||
#include "painlessmesh/router.hpp"
|
||||
#include "painlessmesh/tcp.hpp"
|
||||
|
||||
extern painlessmesh::logger::LogClass Log;
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace wifi {
|
||||
class Mesh : public painlessmesh::Mesh<MeshConnection> {
|
||||
public:
|
||||
/** Initialize the mesh network
|
||||
*
|
||||
* Add this to your setup() function. This routine does the following things:
|
||||
*
|
||||
* - Starts a wifi network
|
||||
* - Begins searching for other wifi networks that are part of the mesh
|
||||
* - Logs on to the best mesh network node it finds… if it doesn’t find
|
||||
* anything, it starts a new search in 5 seconds.
|
||||
*
|
||||
* @param ssid The name of your mesh. All nodes share same AP ssid. They are
|
||||
* distinguished by BSSID.
|
||||
* @param password Wifi password to your mesh.
|
||||
* @param port the TCP port that you want the mesh server to run on. Defaults
|
||||
* to 5555 if not specified.
|
||||
* @param connectMode Switch between WIFI_AP, WIFI_STA and WIFI_AP_STA
|
||||
* (default) mode
|
||||
*/
|
||||
void init(TSTRING ssid, TSTRING password, uint16_t port = 5555,
|
||||
WiFiMode_t connectMode = WIFI_AP_STA, uint8_t channel = 1,
|
||||
uint8_t hidden = 0, uint8_t maxconn = MAX_CONN) {
|
||||
using namespace logger;
|
||||
// Init random generator seed to generate delay variance
|
||||
randomSeed(millis());
|
||||
|
||||
// Shut Wifi down and start with a blank slage
|
||||
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
|
||||
|
||||
Log(STARTUP, "init(): %d\n",
|
||||
WiFi.setAutoConnect(false)); // Disable autoconnect
|
||||
WiFi.persistent(false);
|
||||
|
||||
// start configuration
|
||||
if (!WiFi.mode(connectMode)) {
|
||||
Log(GENERAL, "WiFi.mode() false");
|
||||
}
|
||||
|
||||
_meshSSID = ssid;
|
||||
_meshPassword = password;
|
||||
_meshChannel = channel;
|
||||
_meshHidden = hidden;
|
||||
_meshMaxConn = maxconn;
|
||||
_meshPort = port;
|
||||
|
||||
uint8_t MAC[] = {0, 0, 0, 0, 0, 0};
|
||||
if (WiFi.softAPmacAddress(MAC) == 0) {
|
||||
Log(ERROR, "init(): WiFi.softAPmacAddress(MAC) failed.\n");
|
||||
}
|
||||
uint32_t nodeId = tcp::encodeNodeId(MAC);
|
||||
if (nodeId == 0) Log(ERROR, "NodeId set to 0\n");
|
||||
|
||||
this->init(nodeId);
|
||||
|
||||
tcpServerInit();
|
||||
eventHandleInit();
|
||||
|
||||
_apIp = IPAddress(0, 0, 0, 0);
|
||||
|
||||
if (connectMode & WIFI_AP) {
|
||||
apInit(nodeId); // setup AP
|
||||
}
|
||||
if (connectMode & WIFI_STA) {
|
||||
this->initStation();
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize the mesh network
|
||||
*
|
||||
* Add this to your setup() function. This routine does the following things:
|
||||
*
|
||||
* - Starts a wifi network
|
||||
* - Begins searching for other wifi networks that are part of the mesh
|
||||
* - Logs on to the best mesh network node it finds… if it doesn’t find
|
||||
* anything, it starts a new search in 5 seconds.
|
||||
*
|
||||
* @param ssid The name of your mesh. All nodes share same AP ssid. They are
|
||||
* distinguished by BSSID.
|
||||
* @param password Wifi password to your mesh.
|
||||
* @param port the TCP port that you want the mesh server to run on. Defaults
|
||||
* to 5555 if not specified.
|
||||
* @param connectMode Switch between WIFI_AP, WIFI_STA and WIFI_AP_STA
|
||||
* (default) mode
|
||||
*/
|
||||
void init(TSTRING ssid, TSTRING password, Scheduler *baseScheduler,
|
||||
uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA,
|
||||
uint8_t channel = 1, uint8_t hidden = 0,
|
||||
uint8_t maxconn = MAX_CONN) {
|
||||
this->setScheduler(baseScheduler);
|
||||
init(ssid, password, port, connectMode, channel, hidden, maxconn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect (as a station) to a specified network and ip
|
||||
*
|
||||
* You can pass {0,0,0,0} as IP to have it connect to the gateway
|
||||
*
|
||||
* This stops the node from scanning for other (non specified) nodes
|
||||
* and you should probably also use this node as an anchor: `setAnchor(true)`
|
||||
*/
|
||||
void stationManual(TSTRING ssid, TSTRING password, uint16_t port = 0,
|
||||
IPAddress remote_ip = IPAddress(0, 0, 0, 0)) {
|
||||
// Set station config
|
||||
stationScan.manualIP = remote_ip;
|
||||
|
||||
// Start scan
|
||||
stationScan.init(this, ssid, password, port);
|
||||
stationScan.manual = true;
|
||||
}
|
||||
|
||||
void initStation() {
|
||||
stationScan.init(this, _meshSSID, _meshPassword, _meshPort);
|
||||
mScheduler->addTask(stationScan.task);
|
||||
stationScan.task.enable();
|
||||
|
||||
this->droppedConnectionCallbacks.push_back(
|
||||
[this](uint32_t nodeId, bool station) {
|
||||
if (station) {
|
||||
if (WiFi.status() == WL_CONNECTED) WiFi.disconnect();
|
||||
this->stationScan.connectToAP();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void tcpServerInit() {
|
||||
using namespace logger;
|
||||
Log(GENERAL, "tcpServerInit():\n");
|
||||
_tcpListener = new AsyncServer(_meshPort);
|
||||
painlessmesh::tcp::initServer<MeshConnection,
|
||||
painlessmesh::Mesh<MeshConnection>>(
|
||||
(*_tcpListener), (*this));
|
||||
Log(STARTUP, "AP tcp server established on port %d\n", _meshPort);
|
||||
return;
|
||||
}
|
||||
|
||||
void tcpConnect() {
|
||||
using namespace logger;
|
||||
// TODO: move to Connection or StationConnection?
|
||||
Log(GENERAL, "tcpConnect():\n");
|
||||
if (stationScan.manual && stationScan.port == 0)
|
||||
return; // We have been configured not to connect to the mesh
|
||||
|
||||
// TODO: We could pass this to tcpConnect instead of loading it here
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED && WiFi.localIP()) {
|
||||
AsyncClient *pConn = new AsyncClient();
|
||||
|
||||
IPAddress ip = WiFi.gatewayIP();
|
||||
if (stationScan.manualIP) {
|
||||
ip = stationScan.manualIP;
|
||||
}
|
||||
|
||||
painlessmesh::tcp::connect<MeshConnection,
|
||||
painlessmesh::Mesh<MeshConnection>>(
|
||||
(*pConn), ip, stationScan.port, (*this));
|
||||
} else {
|
||||
Log(ERROR, "tcpConnect(): err Something un expected in tcpConnect()\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool setHostname(const char *hostname) {
|
||||
#ifdef ESP8266
|
||||
return WiFi.hostname(hostname);
|
||||
#elif defined(ESP32)
|
||||
if (strlen(hostname) > 32) {
|
||||
return false;
|
||||
}
|
||||
return WiFi.setHostname(hostname);
|
||||
#endif // ESP8266
|
||||
}
|
||||
|
||||
IPAddress getStationIP() { return WiFi.localIP(); }
|
||||
IPAddress getAPIP() { return _apIp; }
|
||||
|
||||
void stop() {
|
||||
// remove all WiFi events
|
||||
#ifdef ESP32
|
||||
WiFi.removeEvent(eventScanDoneHandler);
|
||||
WiFi.removeEvent(eventSTAStartHandler);
|
||||
WiFi.removeEvent(eventSTADisconnectedHandler);
|
||||
WiFi.removeEvent(eventSTAGotIPHandler);
|
||||
#elif defined(ESP8266)
|
||||
eventSTAConnectedHandler = WiFiEventHandler();
|
||||
eventSTADisconnectedHandler = WiFiEventHandler();
|
||||
eventSTAGotIPHandler = WiFiEventHandler();
|
||||
#endif // ESP32
|
||||
// Stop scanning task
|
||||
stationScan.task.setCallback(NULL);
|
||||
mScheduler->deleteTask(stationScan.task);
|
||||
painlessmesh::Mesh<MeshConnection>::stop();
|
||||
|
||||
// Shutdown wifi hardware
|
||||
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class ::StationScan;
|
||||
TSTRING _meshSSID;
|
||||
TSTRING _meshPassword;
|
||||
uint8_t _meshChannel;
|
||||
uint8_t _meshHidden;
|
||||
uint8_t _meshMaxConn;
|
||||
uint16_t _meshPort;
|
||||
|
||||
IPAddress _apIp;
|
||||
StationScan stationScan;
|
||||
|
||||
void init(Scheduler *scheduler, uint32_t id) {
|
||||
painlessmesh::Mesh<MeshConnection>::init(scheduler, id);
|
||||
}
|
||||
|
||||
void init(uint32_t id) { painlessmesh::Mesh<MeshConnection>::init(id); }
|
||||
|
||||
void apInit(uint32_t nodeId) {
|
||||
_apIp = IPAddress(10, (nodeId & 0xFF00) >> 8, (nodeId & 0xFF), 1);
|
||||
IPAddress netmask(255, 255, 255, 0);
|
||||
|
||||
WiFi.softAPConfig(_apIp, _apIp, netmask);
|
||||
WiFi.softAP(_meshSSID.c_str(), _meshPassword.c_str(), _meshChannel,
|
||||
_meshHidden, _meshMaxConn);
|
||||
}
|
||||
void eventHandleInit() {
|
||||
using namespace logger;
|
||||
#ifdef ESP32
|
||||
eventScanDoneHandler = WiFi.onEvent(
|
||||
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (this->semaphoreTake()) {
|
||||
Log(CONNECTION, "eventScanDoneHandler: SYSTEM_EVENT_SCAN_DONE\n");
|
||||
this->stationScan.scanComplete();
|
||||
this->semaphoreGive();
|
||||
}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_SCAN_DONE);
|
||||
|
||||
eventSTAStartHandler = WiFi.onEvent(
|
||||
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (this->semaphoreTake()) {
|
||||
Log(CONNECTION, "eventSTAStartHandler: SYSTEM_EVENT_STA_START\n");
|
||||
this->semaphoreGive();
|
||||
}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_START);
|
||||
|
||||
eventSTADisconnectedHandler = WiFi.onEvent(
|
||||
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (this->semaphoreTake()) {
|
||||
Log(CONNECTION,
|
||||
"eventSTADisconnectedHandler: SYSTEM_EVENT_STA_DISCONNECTED\n");
|
||||
this->droppedConnectionCallbacks.execute(0, true);
|
||||
this->semaphoreGive();
|
||||
}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
|
||||
eventSTAGotIPHandler = WiFi.onEvent(
|
||||
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
if (this->semaphoreTake()) {
|
||||
Log(CONNECTION, "eventSTAGotIPHandler: SYSTEM_EVENT_STA_GOT_IP\n");
|
||||
this->tcpConnect(); // Connect to TCP port
|
||||
this->semaphoreGive();
|
||||
}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
|
||||
#elif defined(ESP8266)
|
||||
eventSTAConnectedHandler = WiFi.onStationModeConnected(
|
||||
[&](const WiFiEventStationModeConnected &event) {
|
||||
// Log(CONNECTION, "Event: Station Mode Connected to \"%s\"\n",
|
||||
// event.ssid.c_str());
|
||||
Log(CONNECTION, "Event: Station Mode Connected\n");
|
||||
});
|
||||
|
||||
eventSTADisconnectedHandler = WiFi.onStationModeDisconnected(
|
||||
[&](const WiFiEventStationModeDisconnected &event) {
|
||||
Log(CONNECTION, "Event: Station Mode Disconnected\n");
|
||||
this->droppedConnectionCallbacks.execute(0, true);
|
||||
});
|
||||
|
||||
eventSTAGotIPHandler =
|
||||
WiFi.onStationModeGotIP([&](const WiFiEventStationModeGotIP &event) {
|
||||
Log(CONNECTION,
|
||||
"Event: Station Mode Got IP (IP: %s Mask: %s Gateway: %s)\n",
|
||||
event.ip.toString().c_str(), event.mask.toString().c_str(),
|
||||
event.gw.toString().c_str());
|
||||
this->tcpConnect(); // Connect to TCP port
|
||||
});
|
||||
#endif // ESP32
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
WiFiEventId_t eventScanDoneHandler;
|
||||
WiFiEventId_t eventSTAStartHandler;
|
||||
WiFiEventId_t eventSTADisconnectedHandler;
|
||||
WiFiEventId_t eventSTAGotIPHandler;
|
||||
#elif defined(ESP8266)
|
||||
WiFiEventHandler eventSTAConnectedHandler;
|
||||
WiFiEventHandler eventSTADisconnectedHandler;
|
||||
WiFiEventHandler eventSTAGotIPHandler;
|
||||
#endif // ESP8266
|
||||
AsyncServer *_tcpListener;
|
||||
};
|
||||
} // namespace wifi
|
||||
}; // namespace painlessmesh
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
271
arduino-cli/libraries/painlessMesh-master/src/boost/asynctcp.hpp
Normal file
271
arduino-cli/libraries/painlessMesh-master/src/boost/asynctcp.hpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#ifndef _BOOST_ASYNCTCP_HPP_
|
||||
#define _BOOST_ASYNCTCP_HPP_
|
||||
|
||||
#include <boost/array.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef TCP_MSS
|
||||
#define TCP_MSS 1024
|
||||
#endif
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
#define ASYNC_WRITE_FLAG_COPY 0x01
|
||||
|
||||
class AsyncClient;
|
||||
|
||||
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)>
|
||||
AcAckHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, void* data, size_t len)>
|
||||
AcDataHandler;
|
||||
// typedef std::function<void(void*, AsyncClient*, struct pbuf* pb)>
|
||||
// AcPacketHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, uint32_t time)>
|
||||
AcTimeoutHandler;
|
||||
|
||||
typedef boost::asio::ip::address IPAddress;
|
||||
|
||||
class AsyncClient {
|
||||
public:
|
||||
AsyncClient(boost::asio::io_service& io_service)
|
||||
: _io_service(io_service), mSocket(_io_service) {}
|
||||
|
||||
bool connect(IPAddress ipaddress, uint16_t port) {
|
||||
namespace ip = boost::asio::ip;
|
||||
auto endpoint = ip::tcp::endpoint(ipaddress, port);
|
||||
|
||||
mSocket.async_connect(endpoint, [&](auto& ec) { this->handleConnect(ec); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void initRead() {
|
||||
mSocket.async_read_some(
|
||||
boost::asio::buffer(mInputBuffer, TCP_MSS),
|
||||
[&](auto& ec, auto len) { this->handleData(ec, len); });
|
||||
}
|
||||
|
||||
size_t write(const void* data, size_t len,
|
||||
size_t copy = ASYNC_WRITE_FLAG_COPY) {
|
||||
if (writing) return 0;
|
||||
writing = true;
|
||||
if (copy == ASYNC_WRITE_FLAG_COPY) {
|
||||
memcpy(mWriteBuffer, data, len);
|
||||
mSocket.async_send(
|
||||
boost::asio::buffer(mWriteBuffer, len),
|
||||
[&](auto& ec, auto len) { this->handleWrite(ec, len); });
|
||||
} else {
|
||||
mSocket.async_send(
|
||||
boost::asio::buffer(data, len),
|
||||
[&](auto& ec, auto len) { this->handleWrite(ec, len); });
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// Dummy functions for compatibility with ESPAsycnTCP
|
||||
void send() {}
|
||||
void setNoDelay(bool value = true) {}
|
||||
void setRxTimeout(uint32_t timeout) {}
|
||||
const char* errorToString(int8_t error) { return ""; }
|
||||
|
||||
// TODO: check if there is a better one
|
||||
void abort() { this->close(true); }
|
||||
|
||||
void onConnect(AcConnectHandler cb, void* arg = 0) {
|
||||
_connect_cb = cb;
|
||||
_connect_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onDisconnect(AcConnectHandler cb, void* arg = 0) {
|
||||
_discard_cb = cb;
|
||||
_discard_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onAck(AcAckHandler cb, void* arg = 0) {
|
||||
_sent_cb = cb;
|
||||
_sent_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onError(AcErrorHandler cb, void* arg = 0) {
|
||||
_error_cb = cb;
|
||||
_error_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onData(AcDataHandler cb, void* arg = 0) {
|
||||
_recv_cb = cb;
|
||||
_recv_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onTimeout(AcTimeoutHandler cb, void* arg = 0) {
|
||||
_timeout_cb = cb;
|
||||
_timeout_cb_arg = arg;
|
||||
}
|
||||
|
||||
void onPoll(AcConnectHandler cb, void* arg = 0) {
|
||||
_poll_cb = cb;
|
||||
_poll_cb_arg = arg;
|
||||
}
|
||||
|
||||
bool connected() { return mSocket.is_open(); }
|
||||
|
||||
bool freeable() { return !this->connected(); }
|
||||
|
||||
void close(bool now = true) {
|
||||
if (this->connected()) {
|
||||
mSocket.close();
|
||||
}
|
||||
if (!disconnectCalled) {
|
||||
disconnectCalled = true;
|
||||
if (_discard_cb) {
|
||||
_discard_cb(_discard_cb_arg, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~AsyncClient() { close(true); }
|
||||
|
||||
size_t space() {
|
||||
// This could be more intelligent, but simple and safe for now
|
||||
if (writing) return 0;
|
||||
return TCP_MSS;
|
||||
}
|
||||
|
||||
bool canSend() { return this->space() > 0; }
|
||||
|
||||
size_t ack(size_t len) {
|
||||
// Currently assumes that len is actually the size of the data that was sent
|
||||
// Otherwise it will break
|
||||
initRead();
|
||||
return len;
|
||||
}
|
||||
|
||||
tcp::socket& socket() { return mSocket; }
|
||||
|
||||
protected:
|
||||
boost::asio::io_service& _io_service;
|
||||
tcp::socket mSocket;
|
||||
|
||||
char mInputBuffer[TCP_MSS];
|
||||
char mWriteBuffer[TCP_MSS];
|
||||
bool writing = false;
|
||||
|
||||
bool disconnectCalled = false;
|
||||
|
||||
AcConnectHandler _connect_cb = 0;
|
||||
void* _connect_cb_arg = 0;
|
||||
AcConnectHandler _discard_cb = 0;
|
||||
void* _discard_cb_arg = 0;
|
||||
AcAckHandler _sent_cb = 0;
|
||||
void* _sent_cb_arg = 0;
|
||||
AcErrorHandler _error_cb = 0;
|
||||
void* _error_cb_arg = 0;
|
||||
AcDataHandler _recv_cb = 0;
|
||||
void* _recv_cb_arg = 0;
|
||||
AcTimeoutHandler _timeout_cb = 0;
|
||||
void* _timeout_cb_arg = 0;
|
||||
AcConnectHandler _poll_cb = 0;
|
||||
void* _poll_cb_arg = 0;
|
||||
|
||||
void handleConnect(const boost::system::error_code& ec) {
|
||||
if (!ec) {
|
||||
if (_connect_cb) _connect_cb(_connect_cb_arg, this);
|
||||
|
||||
initRead();
|
||||
} else {
|
||||
handleError(ec);
|
||||
if (this->connected()) close(true);
|
||||
}
|
||||
}
|
||||
|
||||
void handleData(const boost::system::error_code& ec, size_t len) {
|
||||
if (disconnectCalled) return;
|
||||
|
||||
if (!ec) {
|
||||
if (_recv_cb) {
|
||||
_recv_cb(_recv_cb_arg, this, (void*)mInputBuffer, len);
|
||||
}
|
||||
} else {
|
||||
handleError(ec);
|
||||
close(true);
|
||||
}
|
||||
}
|
||||
|
||||
void handleWrite(const boost::system::error_code& ec, size_t len) {
|
||||
if (disconnectCalled) return;
|
||||
|
||||
if (!ec) {
|
||||
if (_sent_cb) {
|
||||
// TODO send actual time
|
||||
_sent_cb(_sent_cb_arg, this, len, 0);
|
||||
}
|
||||
writing = false;
|
||||
} else {
|
||||
handleError(ec);
|
||||
close(true);
|
||||
}
|
||||
}
|
||||
|
||||
void handleError(const boost::system::error_code& ec) {
|
||||
if (ec != boost::asio::error::eof && _error_cb) {
|
||||
_error_cb(_error_cb_arg, this, ec.value());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncServer {
|
||||
public:
|
||||
AsyncServer(boost::asio::io_service& io_service, uint16_t port)
|
||||
: _io_service(io_service),
|
||||
// mSocket(io_service),
|
||||
_port(port),
|
||||
mAcceptor(io_service) {}
|
||||
|
||||
~AsyncServer() { this->end(); }
|
||||
|
||||
void onClient(AcConnectHandler cb, void* arg = 0) {
|
||||
_connect_cb = cb;
|
||||
_connect_cb_arg = arg;
|
||||
}
|
||||
|
||||
// begin() should do start_accept (see server2):w
|
||||
void begin() {
|
||||
mAcceptor.open(tcp::v4());
|
||||
int one = 1;
|
||||
setsockopt(mAcceptor.native_handle(), SOL_SOCKET,
|
||||
SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one));
|
||||
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), _port);
|
||||
mAcceptor.bind(endpoint);
|
||||
mAcceptor.listen();
|
||||
initAccept();
|
||||
}
|
||||
|
||||
// Acceptor stop?
|
||||
void end() { mAcceptor.close(); }
|
||||
|
||||
// Dummy function for compatibility with ESPAsycnTCP
|
||||
void setNoDelay(bool value = true) {}
|
||||
|
||||
protected:
|
||||
boost::asio::io_service& _io_service;
|
||||
uint16_t _port;
|
||||
tcp::acceptor mAcceptor;
|
||||
AcConnectHandler _connect_cb = 0;
|
||||
void* _connect_cb_arg = 0;
|
||||
|
||||
void initAccept() {
|
||||
AsyncClient* client = new AsyncClient(mAcceptor.get_io_service());
|
||||
mAcceptor.async_accept(
|
||||
client->socket(), [this, client](const boost::system::error_code& e) {
|
||||
if (!e && this->_connect_cb) {
|
||||
this->_connect_cb(this->_connect_cb_arg, client);
|
||||
this->initAccept();
|
||||
client->initRead();
|
||||
} else
|
||||
std::cout << "Error: " << e.message() << std::endl;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
63
arduino-cli/libraries/painlessMesh-master/src/painlessMesh.h
Normal file
63
arduino-cli/libraries/painlessMesh-master/src/painlessMesh.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef _EASY_MESH_H_
|
||||
#define _EASY_MESH_H_
|
||||
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
#define _TASK_STD_FUNCTION
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif // ESP32
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
#include "painlessMeshConnection.h"
|
||||
#include "painlessMeshSTA.h"
|
||||
|
||||
#include "arduino/wifi.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_OTA
|
||||
#include "painlessmesh/ota.hpp"
|
||||
#endif
|
||||
|
||||
#include "painlessmesh/buffer.hpp"
|
||||
#include "painlessmesh/layout.hpp"
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#include "painlessmesh/mesh.hpp"
|
||||
#include "painlessmesh/ntp.hpp"
|
||||
#include "painlessmesh/plugin.hpp"
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
#include "painlessmesh/router.hpp"
|
||||
#include "painlessmesh/tcp.hpp"
|
||||
using namespace painlessmesh::logger;
|
||||
|
||||
#define MIN_FREE_MEMORY \
|
||||
4000 // Minimum free memory, besides here all packets in queue are discarded.
|
||||
#define MAX_MESSAGE_QUEUE \
|
||||
50 // MAX number of unsent messages in queue. Newer messages are discarded
|
||||
#define MAX_CONSECUTIVE_SEND 5 // Max message burst
|
||||
|
||||
/*! \mainpage painlessMesh: A painless way to setup a mesh.
|
||||
*
|
||||
* painlessMesh is designed in a modular way, with many parent classes. The best
|
||||
* place to get started with the documentation is to have a look at
|
||||
* painlessmesh::wifi::Mesh (the main painlessMesh class is an alias (typedef)
|
||||
* of the painlessmesh::wifi::Mesh class). Make sure to also explore the public
|
||||
* member functions inherited from other classes, to get full information on the
|
||||
* functions available to you.
|
||||
*/
|
||||
|
||||
#ifndef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
class MeshConnection;
|
||||
using painlessMesh = painlessmesh::Mesh<MeshConnection>;
|
||||
#endif
|
||||
|
||||
#endif // _EASY_MESH_H_
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// painlessMeshConnection.cpp
|
||||
//
|
||||
//
|
||||
// Created by Bill Gray on 7/26/16.
|
||||
//
|
||||
//
|
||||
|
||||
#include "painlessMeshConnection.h"
|
||||
#include "painlessMesh.h"
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
#include "painlessmesh/logger.hpp"
|
||||
using namespace painlessmesh;
|
||||
|
||||
//#include "lwip/priv/tcpip_priv.h"
|
||||
|
||||
extern LogClass Log;
|
||||
|
||||
static painlessmesh::buffer::temp_buffer_t shared_buffer;
|
||||
|
||||
ICACHE_FLASH_ATTR MeshConnection::MeshConnection(
|
||||
AsyncClient *client_ptr, painlessmesh::Mesh<MeshConnection> *pMesh,
|
||||
bool is_station) {
|
||||
using namespace painlessmesh;
|
||||
station = is_station;
|
||||
mesh = pMesh;
|
||||
client = client_ptr;
|
||||
|
||||
client->setNoDelay(true);
|
||||
if (station) { // we are the station, start nodeSync
|
||||
Log(CONNECTION, "meshConnectedCb(): we are STA\n");
|
||||
} else {
|
||||
Log(CONNECTION, "meshConnectedCb(): we are AP\n");
|
||||
}
|
||||
}
|
||||
|
||||
ICACHE_FLASH_ATTR MeshConnection::~MeshConnection() {
|
||||
Log(CONNECTION, "~MeshConnection():\n");
|
||||
this->close();
|
||||
if (!client->freeable()) {
|
||||
Log(CONNECTION, "~MeshConnection(): Closing pcb\n");
|
||||
client->close(true);
|
||||
}
|
||||
client->abort();
|
||||
delete client;
|
||||
}
|
||||
|
||||
void MeshConnection::initTCPCallbacks() {
|
||||
using namespace logger;
|
||||
client->onDisconnect(
|
||||
[self = this->shared_from_this()](void *arg, AsyncClient *client) {
|
||||
// Making a copy of self->mesh pointer, because self->close() can
|
||||
// invalidate the original pointer, causing a segmentation fault
|
||||
// when trying to access self->mesh afterwards
|
||||
auto m = self->mesh;
|
||||
if (m->semaphoreTake()) {
|
||||
Log(CONNECTION, "onDisconnect(): dropping %u now= %u\n", self->nodeId,
|
||||
m->getNodeTime());
|
||||
self->close();
|
||||
m->semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
|
||||
client->onData(
|
||||
[self = this->shared_from_this()](void *arg, AsyncClient *client,
|
||||
void *data, size_t len) {
|
||||
using namespace logger;
|
||||
if (self->mesh->semaphoreTake()) {
|
||||
Log(COMMUNICATION, "onData(): fromId=%u\n", self ? self->nodeId : 0);
|
||||
|
||||
self->receiveBuffer.push(static_cast<const char *>(data), len,
|
||||
shared_buffer);
|
||||
|
||||
// Signal that we are done
|
||||
self->client->ack(len);
|
||||
self->readBufferTask.forceNextIteration();
|
||||
|
||||
self->mesh->semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
|
||||
client->onAck(
|
||||
[self = this->shared_from_this()](void *arg, AsyncClient *client,
|
||||
size_t len, uint32_t time) {
|
||||
using namespace logger;
|
||||
if (self->mesh->semaphoreTake()) {
|
||||
self->sentBufferTask.forceNextIteration();
|
||||
self->mesh->semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
|
||||
client->onError(
|
||||
[self = this->shared_from_this()](void *arg, AsyncClient *client,
|
||||
int8_t err) {
|
||||
if (self->mesh->semaphoreTake()) {
|
||||
// When AsyncTCP gets an error it will call both
|
||||
// onError and onDisconnect
|
||||
// so we handle this in the onDisconnect callback
|
||||
Log(CONNECTION, "tcp_err(): MeshConnection %s\n",
|
||||
client->errorToString(err));
|
||||
self->mesh->semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
}
|
||||
|
||||
void MeshConnection::initTasks() {
|
||||
using namespace logger;
|
||||
|
||||
timeOutTask.set(NODE_TIMEOUT, TASK_ONCE, [self = this->shared_from_this()]() {
|
||||
Log(CONNECTION, "Time out reached\n");
|
||||
self->close();
|
||||
});
|
||||
mesh->mScheduler->addTask(timeOutTask);
|
||||
|
||||
this->nodeSyncTask.set(
|
||||
TASK_MINUTE, TASK_FOREVER, [self = this->shared_from_this()]() {
|
||||
Log(SYNC, "nodeSyncTask(): request with %u\n", self->nodeId);
|
||||
router::send<protocol::NodeSyncRequest, MeshConnection>(
|
||||
self->request(self->mesh->asNodeTree()), self);
|
||||
self->timeOutTask.disable();
|
||||
self->timeOutTask.restartDelayed();
|
||||
});
|
||||
mesh->mScheduler->addTask(this->nodeSyncTask);
|
||||
if (station)
|
||||
this->nodeSyncTask.enable();
|
||||
else
|
||||
this->nodeSyncTask.enableDelayed(10 * TASK_SECOND);
|
||||
|
||||
receiveBuffer = painlessmesh::buffer::ReceiveBuffer<TSTRING>();
|
||||
readBufferTask.set(
|
||||
TASK_SECOND, TASK_FOREVER, [self = this->shared_from_this()]() {
|
||||
Log(GENERAL, "readBufferTask()\n");
|
||||
if (!self->receiveBuffer.empty()) {
|
||||
TSTRING frnt = self->receiveBuffer.front();
|
||||
self->receiveBuffer.pop_front();
|
||||
if (!self->receiveBuffer.empty())
|
||||
self->readBufferTask.forceNextIteration();
|
||||
router::routePackage<MeshConnection>(
|
||||
(*self->mesh), self->shared_from_this(), frnt,
|
||||
self->mesh->callbackList, self->mesh->getNodeTime());
|
||||
}
|
||||
});
|
||||
mesh->mScheduler->addTask(readBufferTask);
|
||||
readBufferTask.enableDelayed();
|
||||
|
||||
sentBufferTask.set(
|
||||
TASK_SECOND, TASK_FOREVER, [self = this->shared_from_this()]() {
|
||||
Log(GENERAL, "sentBufferTask()\n");
|
||||
if (!self->sentBuffer.empty() && self->client->canSend()) {
|
||||
auto ret = self->writeNext();
|
||||
if (ret)
|
||||
self->sentBufferTask.forceNextIteration();
|
||||
else
|
||||
self->sentBufferTask.delay(100 * TASK_MILLISECOND);
|
||||
}
|
||||
});
|
||||
mesh->mScheduler->addTask(sentBufferTask);
|
||||
sentBufferTask.enableDelayed();
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR MeshConnection::close() {
|
||||
if (!connected) return;
|
||||
|
||||
Log(CONNECTION, "MeshConnection::close() %u.\n", this->nodeId);
|
||||
this->connected = false;
|
||||
|
||||
this->timeSyncTask.setCallback(NULL);
|
||||
this->nodeSyncTask.setCallback(NULL);
|
||||
this->readBufferTask.setCallback(NULL);
|
||||
this->sentBufferTask.setCallback(NULL);
|
||||
this->timeOutTask.setCallback(NULL);
|
||||
this->timeSyncTask.disable();
|
||||
this->nodeSyncTask.disable();
|
||||
this->readBufferTask.disable();
|
||||
this->sentBufferTask.disable();
|
||||
this->timeOutTask.disable();
|
||||
|
||||
this->client->onDisconnect(NULL, NULL);
|
||||
this->client->onError(NULL, NULL);
|
||||
this->client->onData(NULL, NULL);
|
||||
this->client->onAck(NULL, NULL);
|
||||
|
||||
mesh->addTask(
|
||||
[mesh = this->mesh, nodeId = this->nodeId, station = this->station]() {
|
||||
Log(CONNECTION, "closingTask(): dropping %u now= %u\n", nodeId,
|
||||
mesh->getNodeTime());
|
||||
mesh->changedConnectionCallbacks.execute(nodeId);
|
||||
mesh->droppedConnectionCallbacks.execute(nodeId, station);
|
||||
});
|
||||
|
||||
if (client->connected()) {
|
||||
Log(CONNECTION, "close(): Closing pcb\n");
|
||||
client->close();
|
||||
}
|
||||
|
||||
receiveBuffer.clear();
|
||||
sentBuffer.clear();
|
||||
NodeTree::clear();
|
||||
Log(CONNECTION, "MeshConnection::close() done. Was station: %d.\n",
|
||||
this->station);
|
||||
}
|
||||
|
||||
bool ICACHE_FLASH_ATTR MeshConnection::addMessage(TSTRING &message,
|
||||
bool priority) {
|
||||
if (ESP.getFreeHeap() - message.length() >=
|
||||
MIN_FREE_MEMORY) { // If memory heap is enough, queue the message
|
||||
if (priority) {
|
||||
sentBuffer.push(message, priority);
|
||||
Log(COMMUNICATION,
|
||||
"addMessage(): Package sent to queue beginning -> %d , "
|
||||
"FreeMem: %d\n",
|
||||
sentBuffer.size(), ESP.getFreeHeap());
|
||||
} else {
|
||||
if (sentBuffer.size() < MAX_MESSAGE_QUEUE) {
|
||||
sentBuffer.push(message, priority);
|
||||
Log(COMMUNICATION,
|
||||
"addMessage(): Package sent to queue end -> %d , FreeMem: "
|
||||
"%d\n",
|
||||
sentBuffer.size(), ESP.getFreeHeap());
|
||||
} else {
|
||||
Log(ERROR, "addMessage(): Message queue full -> %d , FreeMem: %d\n",
|
||||
sentBuffer.size(), ESP.getFreeHeap());
|
||||
sentBufferTask.forceNextIteration();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sentBufferTask.forceNextIteration();
|
||||
return true;
|
||||
} else {
|
||||
// connection->sendQueue.clear(); // Discard all messages if free memory is
|
||||
// low
|
||||
Log(DEBUG, "addMessage(): Memory low, message was discarded\n");
|
||||
sentBufferTask.forceNextIteration();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICACHE_FLASH_ATTR MeshConnection::writeNext() {
|
||||
if (sentBuffer.empty()) {
|
||||
Log(COMMUNICATION, "writeNext(): sendQueue is empty\n");
|
||||
return false;
|
||||
}
|
||||
auto len = sentBuffer.requestLength(shared_buffer.length);
|
||||
auto snd_len = client->space();
|
||||
if (len > snd_len) len = snd_len;
|
||||
if (len > 0) {
|
||||
// sentBuffer.read(len, shared_buffer);
|
||||
// auto written = client->write(shared_buffer.buffer, len, 1);
|
||||
auto data_ptr = sentBuffer.readPtr(len);
|
||||
auto written = client->write(data_ptr, len, 1);
|
||||
if (written == len) {
|
||||
Log(COMMUNICATION, "writeNext(): Package sent\n");
|
||||
client->send(); // TODO only do this for priority messages
|
||||
sentBuffer.freeRead();
|
||||
sentBufferTask.forceNextIteration();
|
||||
return true;
|
||||
} else if (written == 0) {
|
||||
Log(COMMUNICATION,
|
||||
"writeNext(): tcp_write Failed node=%u. Resending later\n", nodeId);
|
||||
return false;
|
||||
} else {
|
||||
Log(ERROR,
|
||||
"writeNext(): Less written than requested. Please report bug on "
|
||||
"the issue tracker\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log(COMMUNICATION, "writeNext(): tcp_sndbuf not enough space\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef _PAINLESS_MESH_CONNECTION_H_
|
||||
#define _PAINLESS_MESH_CONNECTION_H_
|
||||
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
#define _TASK_STD_FUNCTION
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#endif // ESP32
|
||||
|
||||
#include "painlessmesh/buffer.hpp"
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
#include "painlessmesh/layout.hpp"
|
||||
#include "painlessmesh/mesh.hpp"
|
||||
|
||||
#ifndef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
class MeshConnection;
|
||||
using painlessMesh = painlessmesh::Mesh<MeshConnection>;
|
||||
#endif
|
||||
|
||||
class MeshConnection : public painlessmesh::layout::Neighbour,
|
||||
public std::enable_shared_from_this<MeshConnection> {
|
||||
public:
|
||||
AsyncClient *client;
|
||||
painlessmesh::Mesh<MeshConnection> *mesh;
|
||||
|
||||
bool newConnection = true;
|
||||
bool connected = true;
|
||||
bool station = true;
|
||||
|
||||
// Timestamp to be compared in manageConnections() to check response
|
||||
// for timeout
|
||||
uint32_t timeDelayLastRequested = 0;
|
||||
|
||||
bool addMessage(TSTRING &message, bool priority = false);
|
||||
bool writeNext();
|
||||
painlessmesh::buffer::ReceiveBuffer<TSTRING> receiveBuffer;
|
||||
painlessmesh::buffer::SentBuffer<TSTRING> sentBuffer;
|
||||
|
||||
Task nodeSyncTask;
|
||||
Task timeSyncTask;
|
||||
Task readBufferTask;
|
||||
Task sentBufferTask;
|
||||
Task timeOutTask;
|
||||
|
||||
MeshConnection(AsyncClient *client, painlessmesh::Mesh<MeshConnection> *pMesh,
|
||||
bool station);
|
||||
~MeshConnection();
|
||||
|
||||
void initTCPCallbacks();
|
||||
void initTasks();
|
||||
|
||||
void handleMessage(TSTRING msg, uint32_t receivedAt);
|
||||
|
||||
void close();
|
||||
|
||||
friend painlessmesh::Mesh<MeshConnection>;
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// painlessMeshSTA.cpp
|
||||
//
|
||||
//
|
||||
// Created by Bill Gray on 7/26/16.
|
||||
//
|
||||
//
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "arduino/wifi.hpp"
|
||||
|
||||
#include "painlessmesh/layout.hpp"
|
||||
#include "painlessmesh/tcp.hpp"
|
||||
|
||||
extern painlessmesh::logger::LogClass Log;
|
||||
|
||||
//***********************************************************************
|
||||
// Calculate NodeID from a hardware MAC address
|
||||
void ICACHE_FLASH_ATTR StationScan::init(painlessmesh::wifi::Mesh *pMesh,
|
||||
TSTRING pssid, TSTRING ppassword,
|
||||
uint16_t pport) {
|
||||
ssid = pssid;
|
||||
password = ppassword;
|
||||
mesh = pMesh;
|
||||
port = pport;
|
||||
|
||||
task.set(SCAN_INTERVAL, TASK_FOREVER, [this]() { stationScan(); });
|
||||
}
|
||||
|
||||
// Starts scan for APs whose name is Mesh SSID
|
||||
void ICACHE_FLASH_ATTR StationScan::stationScan() {
|
||||
using namespace painlessmesh::logger;
|
||||
Log(CONNECTION, "stationScan(): %s\n", ssid.c_str());
|
||||
|
||||
#ifdef ESP32
|
||||
WiFi.scanNetworks(true, true);
|
||||
#elif defined(ESP8266)
|
||||
WiFi.scanNetworksAsync([&](int networks) { this->scanComplete(); }, true);
|
||||
#endif
|
||||
|
||||
task.delay(10 * SCAN_INTERVAL); // Scan should be completed by then and next
|
||||
// step called. If not then we restart here.
|
||||
return;
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR StationScan::scanComplete() {
|
||||
using namespace painlessmesh::logger;
|
||||
Log(CONNECTION, "scanComplete(): Scan finished\n");
|
||||
|
||||
aps.clear();
|
||||
Log(CONNECTION, "scanComplete():-- > Cleared old APs.\n");
|
||||
|
||||
auto num = WiFi.scanComplete();
|
||||
if (num == WIFI_SCAN_RUNNING || num == WIFI_SCAN_FAILED) return;
|
||||
|
||||
Log(CONNECTION, "scanComplete(): num = %d\n", num);
|
||||
|
||||
for (auto i = 0; i < num; ++i) {
|
||||
WiFi_AP_Record_t record;
|
||||
record.ssid = WiFi.SSID(i);
|
||||
if (record.ssid != ssid) {
|
||||
if (record.ssid.equals("") && mesh->_meshHidden) {
|
||||
// Hidden mesh
|
||||
record.ssid = ssid;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
record.rssi = WiFi.RSSI(i);
|
||||
if (record.rssi == 0) continue;
|
||||
|
||||
memcpy((void *)&record.bssid, (void *)WiFi.BSSID(i), sizeof(record.bssid));
|
||||
aps.push_back(record);
|
||||
Log(CONNECTION, "\tfound : %s, %ddBm\n", record.ssid.c_str(),
|
||||
(int16_t)record.rssi);
|
||||
}
|
||||
|
||||
Log(CONNECTION, "\tFound %d nodes\n", aps.size());
|
||||
|
||||
task.yield([this]() {
|
||||
// Task filter all unknown
|
||||
filterAPs();
|
||||
|
||||
// Next task is to sort by strength
|
||||
task.yield([this] {
|
||||
aps.sort([](WiFi_AP_Record_t a, WiFi_AP_Record_t b) {
|
||||
return a.rssi > b.rssi;
|
||||
});
|
||||
// Next task is to connect to the top ap
|
||||
task.yield([this]() { connectToAP(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR StationScan::filterAPs() {
|
||||
auto ap = aps.begin();
|
||||
while (ap != aps.end()) {
|
||||
auto apNodeId = painlessmesh::tcp::encodeNodeId(ap->bssid);
|
||||
if (painlessmesh::router::findRoute<MeshConnection>((*mesh), apNodeId) !=
|
||||
NULL) {
|
||||
ap = aps.erase(ap);
|
||||
} else {
|
||||
ap++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR StationScan::requestIP(WiFi_AP_Record_t &ap) {
|
||||
using namespace painlessmesh::logger;
|
||||
Log(CONNECTION, "connectToAP(): Best AP is %u<---\n",
|
||||
painlessmesh::tcp::encodeNodeId(ap.bssid));
|
||||
WiFi.begin(ap.ssid.c_str(), password.c_str(), mesh->_meshChannel, ap.bssid);
|
||||
return;
|
||||
}
|
||||
|
||||
void ICACHE_FLASH_ATTR StationScan::connectToAP() {
|
||||
using namespace painlessmesh;
|
||||
using namespace painlessmesh::logger;
|
||||
// Next task will be to rescan
|
||||
task.setCallback([this]() { stationScan(); });
|
||||
|
||||
if (manual) {
|
||||
if ((WiFi.SSID() == ssid) && WiFi.status() == WL_CONNECTED) {
|
||||
Log(CONNECTION,
|
||||
"connectToAP(): Already connected using manual connection. "
|
||||
"Disabling scanning.\n");
|
||||
task.disable();
|
||||
return;
|
||||
} else {
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
mesh->closeConnectionSTA();
|
||||
task.enableDelayed(10 * SCAN_INTERVAL);
|
||||
return;
|
||||
} else if (aps.empty() || !ssid.equals(aps.begin()->ssid)) {
|
||||
task.enableDelayed(SCAN_INTERVAL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aps.empty()) {
|
||||
// No unknown nodes found
|
||||
if (WiFi.status() == WL_CONNECTED &&
|
||||
!(mesh->shouldContainRoot && !layout::isRooted(mesh->asNodeTree()))) {
|
||||
// if already connected -> scan slow
|
||||
Log(CONNECTION,
|
||||
"connectToAP(): Already connected, and no unknown nodes found: "
|
||||
"scan rate set to slow\n");
|
||||
task.delay(random(2, 4) * SCAN_INTERVAL);
|
||||
} else {
|
||||
// else scan fast (SCAN_INTERVAL)
|
||||
Log(CONNECTION,
|
||||
"connectToAP(): No unknown nodes found scan rate set to "
|
||||
"normal\n");
|
||||
task.setInterval(0.5 * SCAN_INTERVAL);
|
||||
}
|
||||
mesh->stability += min(1000 - mesh->stability, (size_t)25);
|
||||
} else {
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Log(CONNECTION,
|
||||
"connectToAP(): Unknown nodes found. Current stability: %s\n",
|
||||
String(mesh->stability).c_str());
|
||||
|
||||
int prob = mesh->stability;
|
||||
if (!mesh->shouldContainRoot)
|
||||
// Slower when part of bigger network
|
||||
prob /= 2 * (1 + layout::size(mesh->asNodeTree()));
|
||||
if (!layout::isRooted(mesh->asNodeTree()) && random(0, 1000) < prob) {
|
||||
Log(CONNECTION, "connectToAP(): Reconfigure network: %s\n",
|
||||
String(prob).c_str());
|
||||
// close STA connection, this will trigger station disconnect which
|
||||
// will trigger connectToAP()
|
||||
mesh->closeConnectionSTA();
|
||||
mesh->stability = 0; // Discourage switching again
|
||||
// wifiEventCB should be triggered before this delay runs out
|
||||
// and reset the connecting
|
||||
task.delay(3 * SCAN_INTERVAL);
|
||||
} else {
|
||||
if (mesh->shouldContainRoot)
|
||||
// Increase scanning rate, because we want to find root
|
||||
task.delay(0.5 * SCAN_INTERVAL);
|
||||
else
|
||||
task.delay(random(2, 4) * SCAN_INTERVAL);
|
||||
}
|
||||
} else {
|
||||
// Else try to connect to first
|
||||
auto ap = aps.front();
|
||||
aps.pop_front(); // drop bestAP from mesh list, so if doesn't work out,
|
||||
// we can try the next one
|
||||
requestIP(ap);
|
||||
// Trying to connect, if that fails we will reconnect later
|
||||
Log(CONNECTION,
|
||||
"connectToAP(): Trying to connect, scan rate set to "
|
||||
"4*normal\n");
|
||||
task.delay(2 * SCAN_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef _PAINLESS_MESH_STA_H_
|
||||
#define _PAINLESS_MESH_STA_H_
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include <list>
|
||||
|
||||
typedef struct {
|
||||
uint8_t bssid[6];
|
||||
TSTRING ssid;
|
||||
int8_t rssi;
|
||||
} WiFi_AP_Record_t;
|
||||
|
||||
class StationScan {
|
||||
public:
|
||||
Task task; // Station scanning for connections
|
||||
|
||||
StationScan() {}
|
||||
void init(painlessmesh::wifi::Mesh *pMesh, TSTRING ssid, TSTRING password,
|
||||
uint16_t port);
|
||||
void stationScan();
|
||||
void scanComplete();
|
||||
void filterAPs();
|
||||
void connectToAP();
|
||||
|
||||
protected:
|
||||
TSTRING ssid;
|
||||
TSTRING password;
|
||||
painlessMesh *mesh;
|
||||
uint16_t port;
|
||||
std::list<WiFi_AP_Record_t> aps;
|
||||
|
||||
void requestIP(WiFi_AP_Record_t &ap);
|
||||
|
||||
// Manually configure network and ip
|
||||
bool manual = false;
|
||||
IPAddress manualIP = IPAddress(0, 0, 0, 0);
|
||||
|
||||
friend painlessMesh;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,123 @@
|
||||
#ifndef _PAINLESS_MESH_BASE64_HPP_
|
||||
#define _PAINLESS_MESH_BASE64_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace base64 {
|
||||
|
||||
static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
static const TSTRING chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
inline TSTRING encode(unsigned char const* bytes_to_encode,
|
||||
unsigned int in_len) {
|
||||
TSTRING ret;
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
while (in_len--) {
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
if (i == 3) {
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
|
||||
((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
|
||||
((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (i = 0; (i < 4); i++) ret += chars[char_array_4[i]];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (j = i; j < 3; j++) char_array_3[j] = '\0';
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] =
|
||||
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] =
|
||||
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
for (j = 0; (j < i + 1); j++) ret += chars[char_array_4[j]];
|
||||
while ((i++ < 3)) ret += '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline TSTRING encode(const TSTRING& str64)
|
||||
{
|
||||
return encode((unsigned char*)str64.c_str(), str64.length());
|
||||
}
|
||||
|
||||
static const int B64index[256] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63,
|
||||
0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
|
||||
|
||||
inline const TSTRING decode(const void* data, const size_t &len)
|
||||
{
|
||||
if (len == 0) return "";
|
||||
|
||||
unsigned char *p = (unsigned char*) data;
|
||||
size_t j = 0,
|
||||
pad1 = len % 4 || p[len - 1] == '=',
|
||||
pad2 = pad1 && (len % 4 > 2 || p[len - 2] != '=');
|
||||
const size_t last = (len - pad1) / 4 << 2;
|
||||
#ifdef PAINLESSMESH_ENABLE_STD_STRING
|
||||
TSTRING result(last / 4 * 3 + pad1 + pad2, '\0');
|
||||
#else
|
||||
TSTRING result;
|
||||
result.reserve(last / 4 * 3 + pad1 + pad2);
|
||||
for (size_t i = 0; i < last / 4 * 3 + pad1 + pad2; ++i)
|
||||
result.concat('\0');
|
||||
#endif
|
||||
unsigned char *str = (unsigned char*) &result[0];
|
||||
|
||||
for (size_t i = 0; i < last; i += 4)
|
||||
{
|
||||
int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
|
||||
str[j++] = n >> 16;
|
||||
str[j++] = n >> 8 & 0xFF;
|
||||
str[j++] = n & 0xFF;
|
||||
}
|
||||
if (pad1)
|
||||
{
|
||||
int n = B64index[p[last]] << 18 | B64index[p[last + 1]] << 12;
|
||||
str[j++] = n >> 16;
|
||||
if (pad2)
|
||||
{
|
||||
n |= B64index[p[last + 2]] << 6;
|
||||
str[j++] = n >> 8 & 0xFF;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline TSTRING decode(const TSTRING& str64)
|
||||
{
|
||||
return decode(str64.c_str(), str64.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,227 @@
|
||||
#ifndef _PAINLESS_MESH_BUFFER_HPP_
|
||||
#define _PAINLESS_MESH_BUFFER_HPP_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#ifndef TCP_MSS
|
||||
#define TCP_MSS 1024
|
||||
#endif
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace buffer {
|
||||
|
||||
// Temporary buffer used by ReceiveBuffer and SentBuffer
|
||||
struct temp_buffer_t {
|
||||
size_t length = TCP_MSS;
|
||||
char buffer[TCP_MSS];
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief ReceivedBuffer handles cstrings and stores them as strings
|
||||
*/
|
||||
template <class T>
|
||||
class ReceiveBuffer {
|
||||
public:
|
||||
ReceiveBuffer() { buffer = T(); }
|
||||
|
||||
/**
|
||||
* Push a message into the buffer
|
||||
*/
|
||||
void push(const char *cstr, size_t length, temp_buffer_t &buf) {
|
||||
auto data_ptr = cstr;
|
||||
do {
|
||||
auto len = strnlen(data_ptr, length);
|
||||
do {
|
||||
auto read_len = std::min(len, buf.length);
|
||||
memcpy(buf.buffer, data_ptr, read_len);
|
||||
buf.buffer[read_len] = '\0';
|
||||
auto newBuffer = T(buf.buffer);
|
||||
stringAppend(buffer, newBuffer);
|
||||
len -= newBuffer.length();
|
||||
length -= newBuffer.length();
|
||||
data_ptr += newBuffer.length() * sizeof(char);
|
||||
} while (len > 0);
|
||||
if (length > 0) {
|
||||
// Skip/remove the '\0' between the messages
|
||||
length -= 1;
|
||||
data_ptr += 1 * sizeof(char);
|
||||
if (buffer.length() > 0) { // skip empty buffers
|
||||
jsonStrings.push_back(buffer);
|
||||
buffer = T();
|
||||
}
|
||||
}
|
||||
} while (length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the oldest message from the buffer
|
||||
*/
|
||||
T front() {
|
||||
if (!empty()) return (*jsonStrings.begin());
|
||||
return T();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the oldest message from the buffer
|
||||
*/
|
||||
void pop_front() { jsonStrings.pop_front(); }
|
||||
|
||||
/**
|
||||
* Is the buffer empty
|
||||
*/
|
||||
bool empty() { return jsonStrings.empty(); }
|
||||
|
||||
/**
|
||||
* Clear the buffer
|
||||
*/
|
||||
void clear() {
|
||||
jsonStrings.clear();
|
||||
buffer = T();
|
||||
}
|
||||
|
||||
private:
|
||||
T buffer;
|
||||
std::list<T> jsonStrings;
|
||||
|
||||
/**
|
||||
* Helper function to deal with difference Arduino String
|
||||
* and std::string
|
||||
*/
|
||||
inline void stringAppend(T &buffer, T &newBuffer) { buffer.concat(newBuffer); };
|
||||
};
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_STD_STRING
|
||||
template <>
|
||||
inline void ReceiveBuffer<std::string>::stringAppend(std::string &buffer,
|
||||
std::string &newBuffer) {
|
||||
buffer.append(newBuffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief SentBuffer stores messages (strings) and allows them to be read in any
|
||||
* length
|
||||
*/
|
||||
template <class T>
|
||||
class SentBuffer {
|
||||
public:
|
||||
SentBuffer(){};
|
||||
|
||||
/**
|
||||
* push a message into the buffer.
|
||||
*
|
||||
* \param priority Whether this is a high priority message.
|
||||
*
|
||||
* High priority messages will be sent to the front of the buffer
|
||||
*/
|
||||
void push(T message, bool priority = false) {
|
||||
if (priority) {
|
||||
if (clean)
|
||||
jsonStrings.push_front(message);
|
||||
else
|
||||
jsonStrings.insert((++jsonStrings.begin()), message);
|
||||
} else
|
||||
jsonStrings.push_back(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request whether the passed length is readable
|
||||
*
|
||||
* Returns the actual length available (<= the requested length
|
||||
*/
|
||||
size_t requestLength(size_t buffer_length) {
|
||||
if (jsonStrings.empty())
|
||||
return 0;
|
||||
else
|
||||
// String.toCharArray automatically turns the last character into
|
||||
// a \0, we need the extra space to deal with that annoyance
|
||||
return std::min(buffer_length - 1, jsonStrings.begin()->length() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given length into the passed buffer
|
||||
*
|
||||
* Note the user should first make sure the requested length is available
|
||||
* using `SentBuffer.requestLength()`, otherwise this function might fail.
|
||||
* Note that if multiple messages are read then they are separated using '\0'.
|
||||
*/
|
||||
void read(size_t length, temp_buffer_t &buf) {
|
||||
// TODO: I don't think we actually need to copy here, and/or
|
||||
// we should add a non-copy mode, that returns a pointer directly
|
||||
// to the data (using c_str()).
|
||||
//
|
||||
// Note that toCharrArray always null terminates
|
||||
// independent of whether the whole string was read so we use one extra
|
||||
// space
|
||||
jsonStrings.front().toCharArray(buf.buffer, length + 1);
|
||||
last_read_size = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer directly to the oldest message
|
||||
*
|
||||
* Note the user should first make sure the requested length is available
|
||||
* using `SentBuffer.requestLength()`, otherwise this function might fail.
|
||||
* Note that if multiple messages are read then they are separated using '\0'.
|
||||
*/
|
||||
const char* readPtr(size_t length) {
|
||||
last_read_size = length;
|
||||
return jsonStrings.front().c_str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the previously read messages from the buffer.
|
||||
*
|
||||
* Should be called after a call of read() to clear the buffer.
|
||||
*/
|
||||
void freeRead() {
|
||||
if (last_read_size == jsonStrings.begin()->length() + 1) {
|
||||
jsonStrings.pop_front();
|
||||
clean = true;
|
||||
} else {
|
||||
// jsonStrings.begin()->remove(0, last_read_size);
|
||||
stringEraseFront((*jsonStrings.begin()), last_read_size);
|
||||
clean = false;
|
||||
}
|
||||
last_read_size = 0;
|
||||
}
|
||||
|
||||
bool empty() { return jsonStrings.empty(); }
|
||||
|
||||
void clear() { jsonStrings.clear(); }
|
||||
|
||||
size_t size() { return jsonStrings.size(); }
|
||||
|
||||
private:
|
||||
size_t last_read_size = 0;
|
||||
bool clean = true;
|
||||
std::list<T> jsonStrings;
|
||||
|
||||
inline void stringEraseFront(T &string, size_t length) { string.remove(0, length); };
|
||||
};
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_STD_STRING
|
||||
template <>
|
||||
inline void SentBuffer<std::string>::read(size_t length, temp_buffer_t &buf) {
|
||||
jsonStrings.front().copy(buf.buffer, length);
|
||||
// Mimic String.toCharArray behaviour, which will insert
|
||||
// null termination at the end of original string and the last
|
||||
// character
|
||||
if (length == jsonStrings.front().length() + 1) buf.buffer[length - 1] = '\0';
|
||||
buf.buffer[length] = '\0';
|
||||
last_read_size = length;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void SentBuffer<std::string>::stringEraseFront(std::string &string,
|
||||
size_t length) {
|
||||
string.erase(0, length);
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace buffer
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef _PAINLESS_MESH_CALLBACK_HPP_
|
||||
#define _PAINLESS_MESH_CALLBACK_HPP_
|
||||
|
||||
#include<map>
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
|
||||
extern painlessmesh::logger::LogClass Log;
|
||||
|
||||
namespace painlessmesh {
|
||||
|
||||
/**
|
||||
* Helper functions to work with multiple callbacks
|
||||
*/
|
||||
namespace callback {
|
||||
template <typename... Args>
|
||||
class List {
|
||||
public:
|
||||
int execute(Args... args) {
|
||||
for (auto&& f : callbacks) {
|
||||
f(args...);
|
||||
}
|
||||
return callbacks.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* Needs to be wrapped into semaphore
|
||||
*
|
||||
int executeWithScheduler(Scheduler& scheduler, Args... args) {
|
||||
scheduler.execute();
|
||||
for (auto&& f : callbacks) {
|
||||
f(args...);
|
||||
scheduler.execute();
|
||||
}
|
||||
return callbacks.size();
|
||||
}*/
|
||||
|
||||
/** Add callbacks to the end of the list.
|
||||
*/
|
||||
void push_back(std::function<void(Args...)> func) {
|
||||
callbacks.push_back(func);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::list<std::function<void(Args...)>> callbacks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manage callbacks for receiving packages
|
||||
*/
|
||||
template <typename... Args>
|
||||
class PackageCallbackList {
|
||||
public:
|
||||
/**
|
||||
* Add a callback for specific package id
|
||||
*/
|
||||
void onPackage(int id, std::function<void(Args...)> func) {
|
||||
callbackMap[id].push_back(func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the callbacks associated with a certain package
|
||||
*/
|
||||
int execute(int id, Args... args) { return callbackMap[id].execute(args...); }
|
||||
|
||||
protected:
|
||||
std::map<int, List<Args...>> callbackMap;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using MeshPackageCallbackList =
|
||||
PackageCallbackList<protocol::Variant, std::shared_ptr<T>, uint32_t>;
|
||||
} // namespace callback
|
||||
} // namespace painlessmesh
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "Arduino.h"
|
||||
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
#define _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
|
||||
#include<list>
|
||||
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
#define _TASK_STD_FUNCTION
|
||||
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
#undef ARDUINOJSON_ENABLE_STD_STRING
|
||||
#include <ArduinoJson.h>
|
||||
#undef ARDUINOJSON_ENABLE_STD_STRING
|
||||
|
||||
// Enable (arduino) wifi support
|
||||
#define PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
|
||||
// Enable OTA support
|
||||
#define PAINLESSMESH_ENABLE_OTA
|
||||
|
||||
#define NODE_TIMEOUT 5 * TASK_SECOND
|
||||
#define SCAN_INTERVAL 30 * TASK_SECOND // AP scan period in ms
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#endif // ESP32
|
||||
|
||||
typedef String TSTRING;
|
||||
|
||||
// backward compatibility
|
||||
template <typename T>
|
||||
using SimpleList = std::list<T>;
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace wifi {
|
||||
class Mesh;
|
||||
};
|
||||
}; // namespace painlessmesh
|
||||
|
||||
/** A convenience typedef to access the mesh class*/
|
||||
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
|
||||
using painlessMesh = painlessmesh::wifi::Mesh;
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
#define MAX_CONN 10
|
||||
#else
|
||||
#define MAX_CONN 4
|
||||
#endif // DEBUG
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,183 @@
|
||||
#ifndef _PAINLESS_MESH_LAYOUT_HPP_
|
||||
#define _PAINLESS_MESH_LAYOUT_HPP_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace layout {
|
||||
|
||||
#include <memory>
|
||||
/**
|
||||
* Whether the tree contains the given nodeId
|
||||
*/
|
||||
inline bool contains(protocol::NodeTree nodeTree, uint32_t nodeId) {
|
||||
if (nodeTree.nodeId == nodeId) {
|
||||
return true;
|
||||
}
|
||||
for (auto&& s : nodeTree.subs) {
|
||||
if (contains(s, nodeId)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline protocol::NodeTree excludeRoute(protocol::NodeTree&& tree,
|
||||
uint32_t exclude) {
|
||||
// Make sure to exclude any subs with nodeId == 0,
|
||||
// even if exlude is not set to zero
|
||||
tree.subs.remove_if([exclude](protocol::NodeTree s) {
|
||||
return s.nodeId == 0 || s.nodeId == exclude;
|
||||
});
|
||||
return tree;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class Layout {
|
||||
public:
|
||||
size_t stability = 0;
|
||||
std::list<std::shared_ptr<T> > subs;
|
||||
|
||||
/** Return the nodeId of the node that we are running on.
|
||||
*
|
||||
* On the ESP hardware nodeId is uniquely calculated from the MAC address of
|
||||
* the node.
|
||||
*/
|
||||
uint32_t getNodeId() { return nodeId; }
|
||||
|
||||
protocol::NodeTree asNodeTree() {
|
||||
auto nt = protocol::NodeTree(nodeId, root);
|
||||
for (auto&& s : subs) {
|
||||
if (s->nodeId == 0) continue;
|
||||
nt.subs.push_back(protocol::NodeTree(*s));
|
||||
}
|
||||
return nt;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t nodeId = 0;
|
||||
bool root = false;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void syncLayout(Layout<T>& layout, uint32_t changedId) {
|
||||
// TODO: this should be called from changed connections and dropped
|
||||
// connections events
|
||||
for (auto&& sub : layout.subs) {
|
||||
if (sub->connected && !sub->newConnection && sub->nodeId != 0 &&
|
||||
sub->nodeId != changedId) { // Exclude current
|
||||
sub->nodeSyncTask.forceNextIteration();
|
||||
}
|
||||
}
|
||||
layout.stability /= 2;
|
||||
}
|
||||
|
||||
class Neighbour : public protocol::NodeTree {
|
||||
public:
|
||||
// Inherit constructors
|
||||
using protocol::NodeTree::NodeTree;
|
||||
|
||||
/**
|
||||
* Is the passed nodesync valid
|
||||
*
|
||||
* If not then the caller of this function should probably disconnect
|
||||
* this neighbour.
|
||||
*/
|
||||
bool validSubs(protocol::NodeTree tree) {
|
||||
if (nodeId == 0) // Cant really know, so valid as far as we know
|
||||
return true;
|
||||
if (nodeId != tree.nodeId) return false;
|
||||
for (auto&& s : tree.subs) {
|
||||
if (layout::contains(s, nodeId)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subs with the new subs
|
||||
*
|
||||
* \param tree The possible new tree with this node as base
|
||||
*
|
||||
* Generally one probably wants to call validSubs before calling this
|
||||
* function.
|
||||
*
|
||||
* \return Whether we adopted the new tree
|
||||
*/
|
||||
bool updateSubs(protocol::NodeTree tree) {
|
||||
if (nodeId == 0 || tree != (*this)) {
|
||||
nodeId = tree.nodeId;
|
||||
subs = tree.subs;
|
||||
root = tree.root;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a request
|
||||
*/
|
||||
protocol::NodeSyncRequest request(NodeTree&& layout) {
|
||||
auto subTree = excludeRoute(std::move(layout), nodeId);
|
||||
return protocol::NodeSyncRequest(subTree.nodeId, nodeId, subTree.subs,
|
||||
subTree.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reply
|
||||
*/
|
||||
protocol::NodeSyncReply reply(NodeTree&& layout) {
|
||||
auto subTree = excludeRoute(std::move(layout), nodeId);
|
||||
return protocol::NodeSyncReply(subTree.nodeId, nodeId, subTree.subs,
|
||||
subTree.root);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The size of the mesh (the number of nodes)
|
||||
*/
|
||||
inline uint32_t size(protocol::NodeTree nodeTree) {
|
||||
auto no = 1;
|
||||
for (auto&& s : nodeTree.subs) {
|
||||
no += size(s);
|
||||
}
|
||||
return no;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the top node in the tree is also the root of the mesh
|
||||
*/
|
||||
inline bool isRoot(protocol::NodeTree nodeTree) {
|
||||
if (nodeTree.root) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any node in the tree is also root of the mesh
|
||||
*/
|
||||
inline bool isRooted(protocol::NodeTree nodeTree) {
|
||||
if (isRoot(nodeTree)) return true;
|
||||
for (auto&& s : nodeTree.subs) {
|
||||
if (isRooted(s)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all nodes in a list container
|
||||
*/
|
||||
inline std::list<uint32_t> asList(protocol::NodeTree nodeTree,
|
||||
bool includeSelf = true) {
|
||||
std::list<uint32_t> lst;
|
||||
if (includeSelf) lst.push_back(nodeTree.nodeId);
|
||||
for (auto&& s : nodeTree.subs) {
|
||||
lst.splice(lst.end(), asList(s));
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
} // namespace layout
|
||||
} // namespace painlessmesh
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef _PAINLESS_MESH_LOGGER_HPP_
|
||||
#define _PAINLESS_MESH_LOGGER_HPP_
|
||||
namespace painlessmesh {
|
||||
namespace logger {
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
typedef enum {
|
||||
ERROR = 1 << 0,
|
||||
STARTUP = 1 << 1,
|
||||
MESH_STATUS = 1 << 2,
|
||||
CONNECTION = 1 << 3,
|
||||
SYNC = 1 << 4,
|
||||
S_TIME = 1 << 5,
|
||||
COMMUNICATION = 1 << 6,
|
||||
GENERAL = 1 << 7,
|
||||
MSG_TYPES = 1 << 8,
|
||||
REMOTE = 1 << 9, // not yet implemented
|
||||
APPLICATION = 1 << 10,
|
||||
DEBUG = 1 << 11
|
||||
} LogLevel;
|
||||
|
||||
class LogClass {
|
||||
public:
|
||||
void setLogLevel(uint16_t newTypes) {
|
||||
// set the different kinds of debug messages you want to generate.
|
||||
types = newTypes;
|
||||
Serial.print(F("\nsetLogLevel:"));
|
||||
if (types & ERROR) {
|
||||
Serial.print(F(" ERROR |"));
|
||||
}
|
||||
if (types & STARTUP) {
|
||||
Serial.print(F(" STARTUP |"));
|
||||
}
|
||||
if (types & MESH_STATUS) {
|
||||
Serial.print(F(" MESH_STATUS |"));
|
||||
}
|
||||
if (types & CONNECTION) {
|
||||
Serial.print(F(" CONNECTION |"));
|
||||
}
|
||||
if (types & SYNC) {
|
||||
Serial.print(F(" SYNC |"));
|
||||
}
|
||||
if (types & S_TIME) {
|
||||
Serial.print(F(" S_TIME |"));
|
||||
}
|
||||
if (types & COMMUNICATION) {
|
||||
Serial.print(F(" COMMUNICATION |"));
|
||||
}
|
||||
if (types & GENERAL) {
|
||||
Serial.print(F(" GENERAL |"));
|
||||
}
|
||||
if (types & MSG_TYPES) {
|
||||
Serial.print(F(" MSG_TYPES |"));
|
||||
}
|
||||
if (types & REMOTE) {
|
||||
Serial.print(F(" REMOTE |"));
|
||||
}
|
||||
if (types & APPLICATION) {
|
||||
Serial.print(F(" APPLICATION |"));
|
||||
}
|
||||
if (types & DEBUG) {
|
||||
Serial.print(F(" DEBUG |"));
|
||||
}
|
||||
Serial.println();
|
||||
return;
|
||||
}
|
||||
void operator()(LogLevel type, const char* format...) {
|
||||
if (type & types) { // Print only the message types set for output
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
vsnprintf(str, 200, format, args);
|
||||
|
||||
if (types) {
|
||||
switch (type) {
|
||||
case ERROR:
|
||||
Serial.print(F("ERROR: "));
|
||||
break;
|
||||
case STARTUP:
|
||||
Serial.print(F("STARTUP: "));
|
||||
break;
|
||||
case MESH_STATUS:
|
||||
Serial.print(F("MESH_STATUS: "));
|
||||
break;
|
||||
case CONNECTION:
|
||||
Serial.print(F("CONNECTION: "));
|
||||
break;
|
||||
case SYNC:
|
||||
Serial.print(F("SYNC: "));
|
||||
break;
|
||||
case S_TIME:
|
||||
Serial.print(F("S_TIME: "));
|
||||
break;
|
||||
case COMMUNICATION:
|
||||
Serial.print(F("COMMUNICATION: "));
|
||||
break;
|
||||
case GENERAL:
|
||||
Serial.print(F("GENERAL: "));
|
||||
break;
|
||||
case MSG_TYPES:
|
||||
Serial.print(F("MSG_TYPES: "));
|
||||
break;
|
||||
case REMOTE:
|
||||
Serial.print(F("REMOTE: "));
|
||||
break;
|
||||
case APPLICATION:
|
||||
Serial.print(F("APPLICATION: "));
|
||||
break;
|
||||
case DEBUG:
|
||||
Serial.print(F("DEBUG: "));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.print(str);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t types = 0;
|
||||
char str[200];
|
||||
};
|
||||
|
||||
} // namespace logger
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
#ifndef _PAINLESS_MESH_MESH_HPP_
|
||||
#define _PAINLESS_MESH_MESH_HPP_
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/ntp.hpp"
|
||||
#include "painlessmesh/plugin.hpp"
|
||||
#include "painlessmesh/tcp.hpp"
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_OTA
|
||||
#include "painlessmesh/ota.hpp"
|
||||
#endif
|
||||
|
||||
namespace painlessmesh {
|
||||
typedef std::function<void(uint32_t nodeId)> newConnectionCallback_t;
|
||||
typedef std::function<void(uint32_t nodeId)> droppedConnectionCallback_t;
|
||||
typedef std::function<void(uint32_t from, TSTRING &msg)> receivedCallback_t;
|
||||
typedef std::function<void()> changedConnectionsCallback_t;
|
||||
typedef std::function<void(int32_t offset)> nodeTimeAdjustedCallback_t;
|
||||
typedef std::function<void(uint32_t nodeId, int32_t delay)> nodeDelayCallback_t;
|
||||
|
||||
/**
|
||||
* Main api class for the mesh
|
||||
*
|
||||
* Brings all the functions together except for the WiFi functions
|
||||
*/
|
||||
template <class T>
|
||||
class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
||||
public:
|
||||
void init(uint32_t id) {
|
||||
using namespace logger;
|
||||
if (!isExternalScheduler) {
|
||||
mScheduler = new Scheduler();
|
||||
}
|
||||
this->nodeId = id;
|
||||
|
||||
#ifdef ESP32
|
||||
xSemaphore = xSemaphoreCreateMutex();
|
||||
#endif
|
||||
|
||||
mScheduler->enableAll();
|
||||
|
||||
// Add package handlers
|
||||
this->callbackList = painlessmesh::ntp::addPackageCallback(
|
||||
std::move(this->callbackList), (*this));
|
||||
this->callbackList = painlessmesh::router::addPackageCallback(
|
||||
std::move(this->callbackList), (*this));
|
||||
|
||||
this->changedConnectionCallbacks.push_back([this](uint32_t nodeId) {
|
||||
Log(MESH_STATUS, "Changed connections in neighbour %u\n", nodeId);
|
||||
if (nodeId != 0) layout::syncLayout<T>((*this), nodeId);
|
||||
});
|
||||
this->droppedConnectionCallbacks.push_back([this](uint32_t nodeId,
|
||||
bool station) {
|
||||
Log(MESH_STATUS, "Dropped connection %u, station %d\n", nodeId, station);
|
||||
this->eraseClosedConnections();
|
||||
});
|
||||
this->newConnectionCallbacks.push_back([this](uint32_t nodeId) {
|
||||
Log(MESH_STATUS, "New connection %u\n", nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
void init(Scheduler *scheduler, uint32_t id) {
|
||||
this->setScheduler(scheduler);
|
||||
this->init(id);
|
||||
}
|
||||
|
||||
#ifdef PAINLESSMESH_ENABLE_OTA
|
||||
void initOTA(TSTRING role = "") {
|
||||
painlessmesh::plugin::ota::addPackageCallback(*this->mScheduler, (*this),
|
||||
role);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Set the node as an root/master node for the mesh
|
||||
*
|
||||
* This is an optional setting that can speed up mesh formation.
|
||||
* At most one node in the mesh should be a root, or you could
|
||||
* end up with multiple subMeshes.
|
||||
*
|
||||
* We recommend any AP_ONLY nodes (e.g. a bridgeNode) to be set
|
||||
* as a root node.
|
||||
*
|
||||
* If one node is root, then it is also recommended to call
|
||||
* painlessMesh::setContainsRoot() on all the nodes in the mesh.
|
||||
*/
|
||||
void setRoot(bool on = true) { this->root = on; };
|
||||
|
||||
/**
|
||||
* The mesh should contains a root node
|
||||
*
|
||||
* This will cause the mesh to restructure more quickly around the root node.
|
||||
* Note that this could have adverse effects if set, while there is no root
|
||||
* node present. Also see painlessMesh::setRoot().
|
||||
*/
|
||||
void setContainsRoot(bool on = true) { shouldContainRoot = on; };
|
||||
|
||||
/**
|
||||
* Check whether this node is a root node.
|
||||
*/
|
||||
bool isRoot() { return this->root; };
|
||||
|
||||
void setDebugMsgTypes(uint16_t types) { Log.setLogLevel(types); }
|
||||
|
||||
/**
|
||||
* Disconnect and stop this node
|
||||
*/
|
||||
void stop() {
|
||||
using namespace logger;
|
||||
// Close all connections
|
||||
while (this->subs.size() > 0) {
|
||||
auto conn = this->subs.begin();
|
||||
(*conn)->close();
|
||||
this->eraseClosedConnections();
|
||||
}
|
||||
plugin::PackageHandler<T>::stop();
|
||||
}
|
||||
|
||||
/** Perform crucial maintenance task
|
||||
*
|
||||
* Add this to your loop() function. This routine runs various maintenance
|
||||
* tasks.
|
||||
*/
|
||||
void update(void) {
|
||||
if (semaphoreTake()) {
|
||||
mScheduler->execute();
|
||||
semaphoreGive();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** Send message to a specific node
|
||||
*
|
||||
* @param destId The nodeId of the node to send it to.
|
||||
* @param msg The message to send
|
||||
*
|
||||
* @return true if everything works, false if not.
|
||||
*/
|
||||
bool sendSingle(uint32_t destId, TSTRING msg) {
|
||||
Log(logger::COMMUNICATION, "sendSingle(): dest=%u msg=%s\n", destId,
|
||||
msg.c_str());
|
||||
auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
|
||||
return painlessmesh::router::send<T>(single, (*this));
|
||||
}
|
||||
|
||||
/** Broadcast a message to every node on the mesh network.
|
||||
*
|
||||
* @param includeSelf Send message to myself as well. Default is false.
|
||||
*
|
||||
* @return true if everything works, false if not
|
||||
*/
|
||||
bool sendBroadcast(TSTRING msg, bool includeSelf = false) {
|
||||
using namespace logger;
|
||||
Log(COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str());
|
||||
auto pkg = painlessmesh::protocol::Broadcast(this->nodeId, 0, msg);
|
||||
auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
|
||||
if (success && includeSelf) {
|
||||
auto variant = protocol::Variant(pkg);
|
||||
this->callbackList.execute(pkg.type, pkg, NULL, 0);
|
||||
}
|
||||
if (success > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Sends a node a packet to measure network trip delay to that node.
|
||||
*
|
||||
* After calling this function, user program have to wait to the response in
|
||||
* the form of a callback specified by onNodeDelayReceived().
|
||||
*
|
||||
* @return true if nodeId is connected to the mesh, false otherwise
|
||||
*/
|
||||
bool startDelayMeas(uint32_t id) {
|
||||
using namespace logger;
|
||||
Log(S_TIME, "startDelayMeas(): NodeId %u\n", id);
|
||||
auto conn = painlessmesh::router::findRoute<T>((*this), id);
|
||||
if (!conn) return false;
|
||||
return router::send<protocol::TimeDelay, T>(
|
||||
protocol::TimeDelay(this->nodeId, id, this->getNodeTime()), conn);
|
||||
}
|
||||
|
||||
/** Set a callback routine for any messages that are addressed to this node.
|
||||
*
|
||||
* Every time this node receives a message, this callback routine will the
|
||||
* called. “from” is the id of the original sender of the message, and “msg”
|
||||
* is a string that contains the message. The message can be anything. A
|
||||
* JSON, some other text string, or binary data.
|
||||
*
|
||||
* \code
|
||||
* mesh.onReceive([](auto nodeId, auto msg) {
|
||||
* // Do something with the message
|
||||
* Serial.println(msg);
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onReceive(receivedCallback_t onReceive) {
|
||||
using namespace painlessmesh;
|
||||
this->callbackList.onPackage(
|
||||
protocol::SINGLE,
|
||||
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
|
||||
auto pkg = variant.to<protocol::Single>();
|
||||
onReceive(pkg.from, pkg.msg);
|
||||
return false;
|
||||
});
|
||||
this->callbackList.onPackage(
|
||||
protocol::BROADCAST,
|
||||
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
|
||||
auto pkg = variant.to<protocol::Broadcast>();
|
||||
onReceive(pkg.from, pkg.msg);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/** Callback that gets called every time the local node makes a new
|
||||
* connection.
|
||||
*
|
||||
* \code
|
||||
* mesh.onNewConnection([](auto nodeId) {
|
||||
* // Do something with the event
|
||||
* Serial.println(String(nodeId));
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onNewConnection(newConnectionCallback_t onNewConnection) {
|
||||
Log(logger::GENERAL, "onNewConnection():\n");
|
||||
newConnectionCallbacks.push_back([onNewConnection](uint32_t nodeId) {
|
||||
if (nodeId != 0) onNewConnection(nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
/** Callback that gets called every time the local node drops a connection.
|
||||
*
|
||||
* \code
|
||||
* mesh.onDroppedConnection([](auto nodeId) {
|
||||
* // Do something with the event
|
||||
* Serial.println(String(nodeId));
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onDroppedConnection(droppedConnectionCallback_t onDroppedConnection) {
|
||||
droppedConnectionCallbacks.push_back(
|
||||
[onDroppedConnection](uint32_t nodeId, bool station) {
|
||||
if (nodeId != 0) onDroppedConnection(nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
/** Callback that gets called every time the layout of the mesh changes
|
||||
*
|
||||
* \code
|
||||
* mesh.onChangedConnections([]() {
|
||||
* // Do something with the event
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onChangedConnections(changedConnectionsCallback_t onChangedConnections) {
|
||||
Log(logger::GENERAL, "onChangedConnections():\n");
|
||||
changedConnectionCallbacks.push_back(
|
||||
[onChangedConnections](uint32_t nodeId) {
|
||||
if (nodeId != 0) onChangedConnections();
|
||||
});
|
||||
}
|
||||
|
||||
/** Callback that gets called every time node time gets adjusted
|
||||
*
|
||||
* Node time is automatically kept in sync in the mesh. This gets called
|
||||
* whenever the time is to far out of sync with the rest of the mesh and gets
|
||||
* adjusted.
|
||||
*
|
||||
* \code
|
||||
* mesh.onNodeTimeAdjusted([](auto offset) {
|
||||
* // Do something with the event
|
||||
* Serial.println(String(offset));
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t onTimeAdjusted) {
|
||||
Log(logger::GENERAL, "onNodeTimeAdjusted():\n");
|
||||
nodeTimeAdjustedCallback = onTimeAdjusted;
|
||||
}
|
||||
|
||||
/** Callback that gets called when a delay measurement is received.
|
||||
*
|
||||
* This fires when a time delay masurement response is received, after a
|
||||
* request was sent.
|
||||
*
|
||||
* \code
|
||||
* mesh.onNodeDelayReceived([](auto nodeId, auto delay) {
|
||||
* // Do something with the event
|
||||
* Serial.println(String(delay));
|
||||
* });
|
||||
* \endcode
|
||||
*/
|
||||
void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived) {
|
||||
Log(logger::GENERAL, "onNodeDelayReceived():\n");
|
||||
nodeDelayReceivedCallback = onDelayReceived;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we connected/know a route to the given node?
|
||||
*
|
||||
* @param nodeId The nodeId we are looking for
|
||||
*/
|
||||
bool isConnected(uint32_t nodeId) {
|
||||
return painlessmesh::router::findRoute<T>((*this), nodeId) != NULL;
|
||||
}
|
||||
|
||||
/** Get a list of all known nodes.
|
||||
*
|
||||
* This includes nodes that are both directly and indirectly connected to the
|
||||
* current node.
|
||||
*/
|
||||
std::list<uint32_t> getNodeList(bool includeSelf = false) {
|
||||
return painlessmesh::layout::asList(this->asNodeTree(), includeSelf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a json representation of the current mesh layout
|
||||
*/
|
||||
inline TSTRING subConnectionJson(bool pretty = false) {
|
||||
return this->asNodeTree().toString(pretty);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Task> addTask(unsigned long aInterval,
|
||||
long aIterations,
|
||||
std::function<void()> aCallback) {
|
||||
return plugin::PackageHandler<T>::addTask((*this->mScheduler), aInterval,
|
||||
aIterations, aCallback);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Task> addTask(std::function<void()> aCallback) {
|
||||
return plugin::PackageHandler<T>::addTask((*this->mScheduler), aCallback);
|
||||
}
|
||||
|
||||
~Mesh() {
|
||||
this->stop();
|
||||
if (!isExternalScheduler) delete mScheduler;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setScheduler(Scheduler *baseScheduler) {
|
||||
this->mScheduler = baseScheduler;
|
||||
isExternalScheduler = true;
|
||||
}
|
||||
|
||||
void startTimeSync(std::shared_ptr<T> conn) {
|
||||
using namespace logger;
|
||||
Log(S_TIME, "startTimeSync(): from %u with %u\n", this->nodeId,
|
||||
conn->nodeId);
|
||||
painlessmesh::protocol::TimeSync timeSync;
|
||||
if (ntp::adopt(this->asNodeTree(), (*conn))) {
|
||||
timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId,
|
||||
this->getNodeTime());
|
||||
Log(S_TIME, "startTimeSync(): Requesting time from %u\n", conn->nodeId);
|
||||
} else {
|
||||
timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId);
|
||||
Log(S_TIME, "startTimeSync(): Requesting %u to adopt our time\n",
|
||||
conn->nodeId);
|
||||
}
|
||||
router::send<protocol::TimeSync, T>(timeSync, conn, true);
|
||||
}
|
||||
|
||||
bool closeConnectionSTA() {
|
||||
auto connection = this->subs.begin();
|
||||
while (connection != this->subs.end()) {
|
||||
if ((*connection)->station) {
|
||||
// We found the STA connection, close it
|
||||
(*connection)->close();
|
||||
return true;
|
||||
}
|
||||
++connection;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void eraseClosedConnections() {
|
||||
using namespace logger;
|
||||
Log(CONNECTION, "eraseClosedConnections():\n");
|
||||
this->subs.remove_if(
|
||||
[](const std::shared_ptr<T> &conn) { return !conn->connected; });
|
||||
}
|
||||
|
||||
// Callback functions
|
||||
callback::List<uint32_t> newConnectionCallbacks;
|
||||
callback::List<uint32_t, bool> droppedConnectionCallbacks;
|
||||
callback::List<uint32_t> changedConnectionCallbacks;
|
||||
nodeTimeAdjustedCallback_t nodeTimeAdjustedCallback;
|
||||
nodeDelayCallback_t nodeDelayReceivedCallback;
|
||||
#ifdef ESP32
|
||||
SemaphoreHandle_t xSemaphore = NULL;
|
||||
#endif
|
||||
|
||||
bool isExternalScheduler = false;
|
||||
|
||||
/// Is the node a root node
|
||||
bool shouldContainRoot;
|
||||
|
||||
Scheduler *mScheduler;
|
||||
|
||||
/**
|
||||
* Wrapper function for ESP32 semaphore function
|
||||
*
|
||||
* Waits for the semaphore to be available and then returns true
|
||||
*
|
||||
* Always return true on ESP8266
|
||||
*/
|
||||
bool semaphoreTake() {
|
||||
#ifdef ESP32
|
||||
return xSemaphoreTake(xSemaphore, (TickType_t)10) == pdTRUE;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function for ESP32 semaphore give function
|
||||
*
|
||||
* Does nothing on ESP8266 hardware
|
||||
*/
|
||||
void semaphoreGive() {
|
||||
#ifdef ESP32
|
||||
xSemaphoreGive(xSemaphore);
|
||||
#endif
|
||||
}
|
||||
|
||||
friend T;
|
||||
friend void onDataCb(void *, AsyncClient *, void *, size_t);
|
||||
friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t);
|
||||
friend void meshRecvCb(void *, AsyncClient *, void *, size_t);
|
||||
friend void painlessmesh::ntp::handleTimeSync<Mesh, T>(
|
||||
Mesh &, painlessmesh::protocol::TimeSync, std::shared_ptr<T>, uint32_t);
|
||||
friend void painlessmesh::ntp::handleTimeDelay<Mesh, T>(
|
||||
Mesh &, painlessmesh::protocol::TimeDelay, std::shared_ptr<T>, uint32_t);
|
||||
friend void painlessmesh::router::handleNodeSync<Mesh, T>(
|
||||
Mesh &, protocol::NodeTree, std::shared_ptr<T> conn);
|
||||
friend void painlessmesh::tcp::initServer<T, Mesh>(AsyncServer &, Mesh &);
|
||||
friend void painlessmesh::tcp::connect<T, Mesh>(AsyncClient &, IPAddress,
|
||||
uint16_t, Mesh &);
|
||||
}; // namespace painlessmesh
|
||||
}; // namespace painlessmesh
|
||||
#endif
|
||||
@@ -0,0 +1,245 @@
|
||||
#ifndef _PAINLESS_MESH_NTP_HPP_
|
||||
#define _PAINLESS_MESH_NTP_HPP_
|
||||
|
||||
#ifndef TIME_SYNC_INTERVAL
|
||||
#define TIME_SYNC_INTERVAL 1 * TASK_MINUTE // Time resync period
|
||||
#endif
|
||||
|
||||
#ifndef TIME_SYNC_ACCURACY
|
||||
#define TIME_SYNC_ACCURACY 5000 // Minimum time sync accuracy (5ms
|
||||
#endif
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "painlessmesh/callback.hpp"
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#include "painlessmesh/router.hpp"
|
||||
|
||||
extern painlessmesh::logger::LogClass Log;
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace ntp {
|
||||
|
||||
class MeshTime {
|
||||
public:
|
||||
/** Returns the mesh time in microsecond precision.
|
||||
*
|
||||
* Time rolls over every 71 minutes.
|
||||
*
|
||||
* Nodes try to keep a common time base synchronizing to each other using [an
|
||||
* SNTP based
|
||||
* protocol](https://gitlab.com/painlessMesh/painlessMesh/wikis/mesh-protocol#time-sync)
|
||||
*/
|
||||
uint32_t getNodeTime() { return micros() + timeOffset; }
|
||||
|
||||
protected:
|
||||
uint32_t timeOffset = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the offset of the local clock using the ntp algorithm
|
||||
*
|
||||
* See ntp overview for more information
|
||||
*/
|
||||
inline int32_t clockOffset(uint32_t time0, uint32_t time1, uint32_t time2,
|
||||
uint32_t time3) {
|
||||
uint32_t offset =
|
||||
((int32_t)(time1 - time0) / 2) + ((int32_t)(time2 - time3) / 2);
|
||||
|
||||
// Take small steps to avoid over correction
|
||||
if (offset < 0.5 * TASK_SECOND && offset > 4) offset = offset / 4;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the time it took to get reply from other node
|
||||
*
|
||||
* See ntp algorithm for more information
|
||||
*/
|
||||
inline int32_t tripDelay(uint32_t time0, uint32_t time1, uint32_t time2,
|
||||
uint32_t time3) {
|
||||
return ((time3 - time0) - (time2 - time1)) / 2;
|
||||
}
|
||||
|
||||
inline bool adopt(protocol::NodeTree mesh, protocol::NodeTree connection) {
|
||||
auto mySubCount =
|
||||
layout::size(layout::excludeRoute(std::move(mesh), connection.nodeId));
|
||||
auto remoteSubCount = layout::size(connection);
|
||||
if (mySubCount > remoteSubCount) return false;
|
||||
if (mySubCount == remoteSubCount) {
|
||||
if (connection.nodeId == 0)
|
||||
Log(logger::ERROR, "Adopt called on uninitialized connection\n");
|
||||
return mesh.nodeId < connection.nodeId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void initTimeSync(protocol::NodeTree mesh, std::shared_ptr<T> connection,
|
||||
uint32_t nodeTime) {
|
||||
using namespace painlessmesh::logger;
|
||||
painlessmesh::protocol::TimeSync timeSync;
|
||||
if (adopt(mesh, (*connection))) {
|
||||
timeSync = painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId,
|
||||
nodeTime);
|
||||
Log(S_TIME, "initTimeSync(): Requesting time from %u\n",
|
||||
connection->nodeId);
|
||||
} else {
|
||||
timeSync =
|
||||
painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId);
|
||||
Log(S_TIME, "initTimeSync(): Requesting %u to adopt our time\n",
|
||||
connection->nodeId);
|
||||
}
|
||||
|
||||
router::send<protocol::TimeSync, T>(timeSync, connection, true);
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void handleTimeSync(T& mesh, painlessmesh::protocol::TimeSync timeSync,
|
||||
std::shared_ptr<U> conn, uint32_t receivedAt) {
|
||||
switch (timeSync.msg.type) {
|
||||
case (painlessmesh::protocol::TIME_SYNC_ERROR):
|
||||
Log(logger::ERROR,
|
||||
"handleTimeSync(): Received time sync error. Restarting time "
|
||||
"sync.\n");
|
||||
conn->timeSyncTask.forceNextIteration();
|
||||
break;
|
||||
case (painlessmesh::protocol::TIME_SYNC_REQUEST): // Other party request me
|
||||
// to ask it for time
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): Received requesto to start TimeSync with "
|
||||
"node: %u\n",
|
||||
conn->nodeId);
|
||||
timeSync.reply(mesh.getNodeTime());
|
||||
router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
|
||||
break;
|
||||
|
||||
case (painlessmesh::protocol::TIME_REQUEST):
|
||||
timeSync.reply(receivedAt, mesh.getNodeTime());
|
||||
router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
|
||||
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): timeSyncStatus with %u completed\n", conn->nodeId);
|
||||
|
||||
// After response is sent I assume sync is completed
|
||||
conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
|
||||
break;
|
||||
|
||||
case (painlessmesh::protocol::TIME_REPLY): {
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): %u adopting TIME_RESPONSE from %u\n", mesh.nodeId,
|
||||
conn->nodeId);
|
||||
int32_t offset = painlessmesh::ntp::clockOffset(
|
||||
timeSync.msg.t0, timeSync.msg.t1, timeSync.msg.t2, receivedAt);
|
||||
mesh.timeOffset += offset; // Accumulate offset
|
||||
|
||||
// flag all connections for re-timeSync
|
||||
if (mesh.nodeTimeAdjustedCallback) {
|
||||
mesh.nodeTimeAdjustedCallback(offset);
|
||||
}
|
||||
|
||||
if (offset < TIME_SYNC_ACCURACY && offset > -TIME_SYNC_ACCURACY) {
|
||||
// mark complete only if offset was less than 10 ms
|
||||
conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): timeSyncStatus with %u completed\n",
|
||||
conn->nodeId);
|
||||
|
||||
// Time has changed, update other nodes
|
||||
for (auto&& connection : mesh.subs) {
|
||||
if (connection->nodeId != conn->nodeId) { // exclude this connection
|
||||
connection->timeSyncTask.forceNextIteration();
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): timeSyncStatus with %u brought forward\n",
|
||||
connection->nodeId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Iterate sync procedure if accuracy was not enough
|
||||
conn->timeSyncTask.delay(200 * TASK_MILLISECOND); // Small delay
|
||||
Log(logger::S_TIME,
|
||||
"handleTimeSync(): timeSyncStatus with %u needs further tries\n",
|
||||
conn->nodeId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log(logger::ERROR, "handleTimeSync(): unkown type %u, %u\n",
|
||||
timeSync.msg.type, painlessmesh::protocol::TIME_SYNC_REQUEST);
|
||||
break;
|
||||
}
|
||||
Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void handleTimeDelay(T& mesh, painlessmesh::protocol::TimeDelay timeDelay,
|
||||
std::shared_ptr<U> conn, uint32_t receivedAt) {
|
||||
Log(logger::S_TIME, "handleTimeDelay(): from %u in timestamp\n",
|
||||
timeDelay.from);
|
||||
|
||||
switch (timeDelay.msg.type) {
|
||||
case (painlessmesh::protocol::TIME_SYNC_ERROR):
|
||||
Log(logger::ERROR,
|
||||
"handleTimeDelay(): Error in requesting time delay. Please try "
|
||||
"again.\n");
|
||||
break;
|
||||
|
||||
case (painlessmesh::protocol::TIME_REQUEST):
|
||||
// conn->timeSyncStatus == IN_PROGRESS;
|
||||
Log(logger::S_TIME, "handleTimeDelay(): TIME REQUEST received.\n");
|
||||
|
||||
// Build time response
|
||||
timeDelay.reply(receivedAt, mesh.getNodeTime());
|
||||
router::send<protocol::TimeDelay, U>(timeDelay, conn);
|
||||
break;
|
||||
|
||||
case (painlessmesh::protocol::TIME_REPLY): {
|
||||
Log(logger::S_TIME, "handleTimeDelay(): TIME RESPONSE received.\n");
|
||||
int32_t delay = painlessmesh::ntp::tripDelay(
|
||||
timeDelay.msg.t0, timeDelay.msg.t1, timeDelay.msg.t2, receivedAt);
|
||||
Log(logger::S_TIME, "handleTimeDelay(): Delay is %d\n", delay);
|
||||
|
||||
// conn->timeSyncStatus == COMPLETE;
|
||||
|
||||
if (mesh.nodeDelayReceivedCallback)
|
||||
mesh.nodeDelayReceivedCallback(timeDelay.from, delay);
|
||||
} break;
|
||||
|
||||
default:
|
||||
Log(logger::ERROR,
|
||||
"handleTimeDelay(): Unknown timeSyncMessageType received. Ignoring "
|
||||
"for now.\n");
|
||||
}
|
||||
|
||||
Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
|
||||
}
|
||||
|
||||
template <class T, typename U>
|
||||
callback::MeshPackageCallbackList<U> addPackageCallback(
|
||||
callback::MeshPackageCallbackList<U>&& callbackList, T& mesh) {
|
||||
// TimeSync
|
||||
callbackList.onPackage(
|
||||
protocol::TIME_SYNC,
|
||||
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
|
||||
uint32_t receivedAt) {
|
||||
auto timeSync = variant.to<protocol::TimeSync>();
|
||||
handleTimeSync<T, U>(mesh, timeSync, connection, receivedAt);
|
||||
return false;
|
||||
});
|
||||
|
||||
// TimeDelay
|
||||
callbackList.onPackage(
|
||||
protocol::TIME_DELAY,
|
||||
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
|
||||
uint32_t receivedAt) {
|
||||
auto timeDelay = variant.to<protocol::TimeDelay>();
|
||||
handleTimeDelay<T, U>(mesh, timeDelay, connection, receivedAt);
|
||||
return false;
|
||||
});
|
||||
|
||||
return callbackList;
|
||||
}
|
||||
|
||||
} // namespace ntp
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
@@ -0,0 +1,409 @@
|
||||
#ifndef _PAINLESS_MESH_PLUGIN_OTA_HPP_
|
||||
#define _PAINLESS_MESH_PLUGIN_OTA_HPP_
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/base64.hpp"
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#include "painlessmesh/plugin.hpp"
|
||||
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
#ifdef ESP32
|
||||
#include <SPIFFS.h>
|
||||
#include <Update.h>
|
||||
#else
|
||||
#include <FS.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace plugin {
|
||||
|
||||
/** OTA over the mesh
|
||||
*
|
||||
* OTA is implemented as a painlessmesh::plugin.
|
||||
*
|
||||
* The protocol consists of three message types: ota::Announce, ota::DataRequest
|
||||
* and ota::Data. The first message is generally send by the node that
|
||||
* distributes the firmware and announces the current version of firmware
|
||||
* available for each hardware and role. Firmware version is determined by
|
||||
* its MD5 signature. See
|
||||
* [painlessMeshBoost](http://gitlab.com/painlessMesh/painlessMeshBoost) for a
|
||||
* possible implementation of a distribution node.
|
||||
*
|
||||
* Once a node receives a announce message it will check it against its own role
|
||||
* and hardware to discover if it is suitable this node. If that checks out and
|
||||
* the MD5 is different than its own MD5 it will send a data request back to the
|
||||
* firmware distribution node. This request also includes a partNo, to determine
|
||||
* which part of the data it needs (starting from zero).
|
||||
*
|
||||
* When the distribution node receives a data request, it sends the data back to
|
||||
* the node (with a data message). The node will then write this data and
|
||||
* request the next part of the data. This exchange continuous until the node
|
||||
* has all the data, written it and reboots into the new firmware.
|
||||
*/
|
||||
namespace ota {
|
||||
|
||||
/** Package used by the firmware distribution node to announce new version
|
||||
* available
|
||||
*
|
||||
* This is based on the general BroadcastPackage to ensure it is being
|
||||
* broadcasted. It is possible to define a Announce::role, which defines the
|
||||
* node role the firmware is meant for.
|
||||
*
|
||||
* The package type/identifier is set to 10.
|
||||
*/
|
||||
class Announce : public BroadcastPackage {
|
||||
public:
|
||||
TSTRING md5;
|
||||
TSTRING hardware;
|
||||
|
||||
/**
|
||||
* \brief The type of node the firmware is meant for
|
||||
*
|
||||
* Nodes can fulfill different roles, which require specific firmware. E.g a
|
||||
* node can be a sensor and therefore needs the firmware meant for sensor
|
||||
* nodes. This allows one to set the type of node (role) this firmware is
|
||||
* aimed at.
|
||||
*
|
||||
* Note that the role should not contain underscores or dots.
|
||||
*/
|
||||
TSTRING role;
|
||||
|
||||
/** Force an update even if the node already has this firmware version
|
||||
*
|
||||
* Mainly usefull when testing updates etc.
|
||||
*/
|
||||
bool forced = false;
|
||||
size_t noPart;
|
||||
|
||||
Announce() : BroadcastPackage(10) {}
|
||||
|
||||
Announce(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
|
||||
md5 = jsonObj["md5"].as<TSTRING>();
|
||||
hardware = jsonObj["hardware"].as<TSTRING>();
|
||||
role = jsonObj["role"].as<TSTRING>();
|
||||
if (jsonObj.containsKey("forced")) forced = jsonObj["forced"];
|
||||
noPart = jsonObj["noPart"];
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
|
||||
jsonObj["md5"] = md5;
|
||||
jsonObj["hardware"] = hardware;
|
||||
jsonObj["role"] = role;
|
||||
if (forced) jsonObj["forced"] = forced;
|
||||
jsonObj["noPart"] = noPart;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(noJsonFields + 5) +
|
||||
round(1.1 * (md5.length() + hardware.length() + role.length()));
|
||||
}
|
||||
|
||||
protected:
|
||||
Announce(int type, router::Type routing) : BroadcastPackage(type) {
|
||||
this->routing = routing;
|
||||
}
|
||||
};
|
||||
|
||||
class Data;
|
||||
|
||||
/** Request (part of) the firmware update
|
||||
*
|
||||
* This is send by the node needing the new firmware, to the firmware
|
||||
* distribution node to request a part (DataRequest::partNo) of the data.
|
||||
*
|
||||
* The package type/identifier is set to 11.
|
||||
*/
|
||||
class DataRequest : public Announce {
|
||||
public:
|
||||
size_t partNo = 0;
|
||||
uint32_t dest = 0;
|
||||
|
||||
DataRequest() : Announce(11, router::SINGLE) {}
|
||||
|
||||
DataRequest(JsonObject jsonObj) : Announce(jsonObj) {
|
||||
dest = jsonObj["dest"];
|
||||
partNo = jsonObj["partNo"];
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = Announce::addTo(std::move(jsonObj));
|
||||
jsonObj["dest"] = dest;
|
||||
jsonObj["partNo"] = partNo;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
static DataRequest replyTo(const Announce& ann, uint32_t from,
|
||||
size_t partNo) {
|
||||
DataRequest req;
|
||||
req.dest = ann.from;
|
||||
req.md5 = ann.md5;
|
||||
req.hardware = ann.hardware;
|
||||
req.role = ann.role;
|
||||
req.forced = ann.forced;
|
||||
req.noPart = ann.noPart;
|
||||
req.partNo = partNo;
|
||||
req.from = from;
|
||||
return req;
|
||||
}
|
||||
|
||||
static DataRequest replyTo(const Data& d, size_t partNo);
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(noJsonFields + 5 + 2) +
|
||||
round(1.1 * (md5.length() + hardware.length() + role.length()));
|
||||
}
|
||||
|
||||
protected:
|
||||
DataRequest(int type) : Announce(type, router::SINGLE) {}
|
||||
};
|
||||
|
||||
/** Package containing part of the firmware
|
||||
*
|
||||
* The package type/identifier is set to 12.
|
||||
*/
|
||||
class Data : public DataRequest {
|
||||
public:
|
||||
TSTRING data;
|
||||
|
||||
Data() : DataRequest(12) {}
|
||||
|
||||
Data(JsonObject jsonObj) : DataRequest(jsonObj) {
|
||||
data = jsonObj["data"].as<TSTRING>();
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = DataRequest::addTo(std::move(jsonObj));
|
||||
jsonObj["data"] = data;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
static Data replyTo(const DataRequest& req, TSTRING data, size_t partNo) {
|
||||
Data d;
|
||||
d.from = req.dest;
|
||||
d.dest = req.from;
|
||||
d.md5 = req.md5;
|
||||
d.hardware = req.hardware;
|
||||
d.role = req.role;
|
||||
d.forced = req.forced;
|
||||
d.noPart = req.noPart;
|
||||
d.partNo = partNo;
|
||||
d.data = data;
|
||||
return d;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(noJsonFields + 5 + 2 + 1) +
|
||||
round(1.1 * (md5.length() + hardware.length() + role.length() +
|
||||
data.length()));
|
||||
}
|
||||
};
|
||||
|
||||
inline DataRequest DataRequest::replyTo(const Data& d, size_t partNo) {
|
||||
DataRequest req;
|
||||
req.from = d.dest;
|
||||
req.dest = d.from;
|
||||
req.md5 = d.md5;
|
||||
req.hardware = d.hardware;
|
||||
req.role = d.role;
|
||||
req.forced = d.forced;
|
||||
req.noPart = d.noPart;
|
||||
req.partNo = partNo;
|
||||
return req;
|
||||
}
|
||||
|
||||
/** Data related to the current state of the node update
|
||||
*
|
||||
* This class is used by the OTA algorithm to keep track of both the current
|
||||
* version of the software and the ongoing update.
|
||||
*
|
||||
* The firmware md5 uniquely identifies each firmware version
|
||||
*/
|
||||
class State : public protocol::PackageInterface {
|
||||
public:
|
||||
TSTRING md5;
|
||||
#ifdef ESP32
|
||||
TSTRING hardware = "ESP32";
|
||||
#else
|
||||
TSTRING hardware = "ESP8266";
|
||||
#endif
|
||||
TSTRING role;
|
||||
size_t noPart = 0;
|
||||
size_t partNo = 0;
|
||||
TSTRING ota_fn = "/ota_fw.json";
|
||||
|
||||
State() {}
|
||||
|
||||
State(JsonObject jsonObj) {
|
||||
md5 = jsonObj["md5"].as<TSTRING>();
|
||||
hardware = jsonObj["hardware"].as<TSTRING>();
|
||||
role = jsonObj["role"].as<TSTRING>();
|
||||
}
|
||||
|
||||
State(const Announce& ann) {
|
||||
md5 = ann.md5;
|
||||
hardware = ann.hardware;
|
||||
role = ann.role;
|
||||
noPart = ann.noPart;
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["role"] = role;
|
||||
jsonObj["md5"] = md5;
|
||||
jsonObj["hardware"] = hardware;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(3) +
|
||||
round(1.1 * (md5.length() + hardware.length() + role.length()));
|
||||
}
|
||||
|
||||
std::shared_ptr<Task> task;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void addPackageCallback(Scheduler& scheduler, plugin::PackageHandler<T>& mesh,
|
||||
TSTRING role = "") {
|
||||
using namespace logger;
|
||||
#if defined(ESP32) || defined(ESP8266)
|
||||
auto currentFW = std::make_shared<State>();
|
||||
currentFW->role = role;
|
||||
auto updateFW = std::make_shared<State>();
|
||||
updateFW->role = role;
|
||||
#ifdef ESP32
|
||||
SPIFFS.begin(true); // Start the SPI Flash Files System
|
||||
#else
|
||||
SPIFFS.begin(); // Start the SPI Flash Files System
|
||||
#endif
|
||||
if (SPIFFS.exists(currentFW->ota_fn)) {
|
||||
auto file = SPIFFS.open(currentFW->ota_fn, "r");
|
||||
TSTRING msg = "";
|
||||
while (file.available()) {
|
||||
msg += (char)file.read();
|
||||
}
|
||||
auto var = protocol::Variant(msg);
|
||||
auto fw = var.to<State>();
|
||||
if (fw.role == role && fw.hardware == currentFW->hardware) {
|
||||
Log(DEBUG, "MD5 found %s\n", fw.md5.c_str());
|
||||
currentFW->md5 = fw.md5;
|
||||
}
|
||||
}
|
||||
|
||||
mesh.onPackage(10, [currentFW, updateFW, &mesh,
|
||||
&scheduler](protocol::Variant variant) {
|
||||
// convert variant to Announce
|
||||
auto pkg = variant.to<Announce>();
|
||||
// Check if we want the update
|
||||
if (currentFW->role == pkg.role && currentFW->hardware == pkg.hardware) {
|
||||
if ((currentFW->md5 == pkg.md5 && !pkg.forced) ||
|
||||
updateFW->md5 == pkg.md5)
|
||||
// Either already have it, or already updating to it
|
||||
return false;
|
||||
else {
|
||||
auto request = DataRequest::replyTo(pkg, mesh.getNodeId(), updateFW->partNo);
|
||||
updateFW->md5 = pkg.md5;
|
||||
// enable the request task
|
||||
updateFW->task =
|
||||
mesh.addTask(scheduler, 30 * TASK_SECOND, 10,
|
||||
[request, &mesh]() { mesh.sendPackage(&request); });
|
||||
updateFW->task->setOnDisable([updateFW]() {
|
||||
Log(ERROR, "OTA: Did not receive the requested data.\n");
|
||||
updateFW->md5 = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
mesh.onPackage(11, [currentFW](protocol::Variant variant) {
|
||||
Log(ERROR, "Data request should not be send to this node\n");
|
||||
return false;
|
||||
});
|
||||
|
||||
mesh.onPackage(12, [currentFW, updateFW, &mesh,
|
||||
&scheduler](protocol::Variant variant) {
|
||||
auto pkg = variant.to<Data>();
|
||||
// Check whether it is a new part, of correct md5 role etc etc
|
||||
if (updateFW->partNo == pkg.partNo && updateFW->md5 == pkg.md5 &&
|
||||
updateFW->role == pkg.role && updateFW->hardware == pkg.hardware) {
|
||||
// If so write
|
||||
if (pkg.partNo == 0) {
|
||||
#ifdef ESP32
|
||||
uint32_t maxSketchSpace = UPDATE_SIZE_UNKNOWN;
|
||||
#else
|
||||
uint32_t maxSketchSpace =
|
||||
(ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||
#endif
|
||||
Log(DEBUG, "Sketch size %d\n", maxSketchSpace);
|
||||
if (Update.isRunning()) {
|
||||
Update.end(false);
|
||||
}
|
||||
if (!Update.begin(maxSketchSpace)) { // start with max available size
|
||||
Log(DEBUG, "handleOTA(): OTA start failed!");
|
||||
Update.printError(Serial);
|
||||
Update.end();
|
||||
} else {
|
||||
Update.setMD5(pkg.md5.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// write data
|
||||
auto b64Data = base64::decode(pkg.data);
|
||||
if (Update.write((uint8_t*)b64Data.c_str(), b64Data.length()) !=
|
||||
b64Data.length()) {
|
||||
Log(ERROR, "handleOTA(): OTA write failed!");
|
||||
Update.printError(Serial);
|
||||
Update.end();
|
||||
updateFW->md5 = "";
|
||||
updateFW->partNo = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If last part then write ota_fn and reboot
|
||||
if (pkg.partNo == pkg.noPart - 1) {
|
||||
// check md5, reboot
|
||||
if (Update.end(true)) { // true to set the size to the
|
||||
// current progress
|
||||
auto file = SPIFFS.open(updateFW->ota_fn, "w");
|
||||
String msg;
|
||||
auto var = protocol::Variant(updateFW.get());
|
||||
var.printTo(msg);
|
||||
file.print(msg);
|
||||
file.close();
|
||||
|
||||
Log(DEBUG, "handleOTA(): OTA Success! %s, %s\n", msg.c_str(),
|
||||
updateFW->role.c_str());
|
||||
ESP.restart();
|
||||
} else {
|
||||
Log(DEBUG, "handleOTA(): OTA failed!\n");
|
||||
Update.printError(Serial);
|
||||
updateFW->md5 = "";
|
||||
updateFW->partNo = 0;
|
||||
}
|
||||
updateFW->task->setOnDisable(NULL);
|
||||
updateFW->task->disable();
|
||||
} else {
|
||||
// else request more
|
||||
++updateFW->partNo;
|
||||
auto request = DataRequest::replyTo(pkg, updateFW->partNo);
|
||||
updateFW->task->setCallback(
|
||||
[request, &mesh]() { mesh.sendPackage(&request); });
|
||||
updateFW->task->disable();
|
||||
updateFW->task->restart();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ota
|
||||
} // namespace plugin
|
||||
} // namespace painlessmesh
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#ifndef _PAINLESS_MESH_PLUGIN_HPP_
|
||||
#define _PAINLESS_MESH_PLUGIN_HPP_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/router.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
|
||||
/** Plugin interface for painlessMesh packages/messages
|
||||
*
|
||||
* This interface allows one to design their own messages types/packages, and
|
||||
* add handlers that are called when the new package type arrives at a node.
|
||||
* Here you can think of things like sensor packages, which hold the
|
||||
* measurements done by the sensors. The packages related to OTA updates are
|
||||
* also implemented as a plugin system (see plugin::ota). Each package type is
|
||||
* uniquely identified using the protocol::PackageInterface::type. Currently
|
||||
* default package types use numbers up to 12, so to be on the safe side we
|
||||
* recommend your own packages to use higher type values, e.g. start counting at
|
||||
* 20 at the lowest.
|
||||
*
|
||||
* An important piece of information is how a package should be routed.
|
||||
* Currently we have three main routing algorithms (router::Type).
|
||||
*
|
||||
* \code
|
||||
* using namespace painlessmesh;
|
||||
*
|
||||
* // Inherit from SinglePackage, the most basic package with
|
||||
* router::Type::SINGLE class SensorPackage : public plugin::SinglePackage {
|
||||
*
|
||||
* };
|
||||
*
|
||||
* \endcode
|
||||
*/
|
||||
namespace plugin {
|
||||
|
||||
class SinglePackage : public protocol::PackageInterface {
|
||||
public:
|
||||
uint32_t from;
|
||||
uint32_t dest;
|
||||
router::Type routing;
|
||||
int type;
|
||||
int noJsonFields = 4;
|
||||
|
||||
SinglePackage(int type) : routing(router::SINGLE), type(type) {}
|
||||
|
||||
SinglePackage(JsonObject jsonObj) {
|
||||
from = jsonObj["from"];
|
||||
dest = jsonObj["dest"];
|
||||
type = jsonObj["type"];
|
||||
routing = static_cast<router::Type>(jsonObj["routing"].as<int>());
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["from"] = from;
|
||||
jsonObj["dest"] = dest;
|
||||
jsonObj["routing"] = static_cast<int>(routing);
|
||||
jsonObj["type"] = type;
|
||||
return jsonObj;
|
||||
}
|
||||
};
|
||||
|
||||
class BroadcastPackage : public protocol::PackageInterface {
|
||||
public:
|
||||
uint32_t from;
|
||||
router::Type routing;
|
||||
int type;
|
||||
int noJsonFields = 3;
|
||||
|
||||
BroadcastPackage(int type) : routing(router::BROADCAST), type(type) {}
|
||||
|
||||
BroadcastPackage(JsonObject jsonObj) {
|
||||
from = jsonObj["from"];
|
||||
type = jsonObj["type"];
|
||||
routing = static_cast<router::Type>(jsonObj["routing"].as<int>());
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["from"] = from;
|
||||
jsonObj["routing"] = static_cast<int>(routing);
|
||||
jsonObj["type"] = type;
|
||||
return jsonObj;
|
||||
}
|
||||
};
|
||||
|
||||
class NeighbourPackage : public plugin::SinglePackage {
|
||||
public:
|
||||
NeighbourPackage(int type) : SinglePackage(type) {
|
||||
routing = router::NEIGHBOUR;
|
||||
}
|
||||
|
||||
NeighbourPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle different plugins
|
||||
*
|
||||
* Responsible for
|
||||
* - having a list of plugin types
|
||||
* - the functions defined to handle the different plugin types
|
||||
* - tasks?
|
||||
*/
|
||||
template <typename T>
|
||||
class PackageHandler : public layout::Layout<T> {
|
||||
public:
|
||||
void stop() {
|
||||
for (auto&& task : taskList) {
|
||||
task->disable();
|
||||
task->setCallback(NULL);
|
||||
}
|
||||
taskList.clear();
|
||||
}
|
||||
|
||||
~PackageHandler() {
|
||||
if (taskList.size() > 0)
|
||||
Log(logger::ERROR,
|
||||
"~PackageHandler(): Always call PackageHandler::stop(scheduler) "
|
||||
"before calling this destructor");
|
||||
}
|
||||
|
||||
bool sendPackage(const protocol::PackageInterface* pkg) {
|
||||
auto variant = protocol::Variant(pkg);
|
||||
// if single or neighbour with direction
|
||||
if (variant.routing() == router::SINGLE ||
|
||||
(variant.routing() == router::NEIGHBOUR && variant.dest() != 0)) {
|
||||
return router::send(variant, (*this));
|
||||
}
|
||||
|
||||
// if broadcast or neighbour without direction
|
||||
if (variant.routing() == router::BROADCAST ||
|
||||
(variant.routing() == router::NEIGHBOUR && variant.dest() == 0)) {
|
||||
auto i = router::broadcast(variant, (*this), 0);
|
||||
if (i > 0) return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onPackage(int type, std::function<bool(protocol::Variant)> function) {
|
||||
auto func = [function](protocol::Variant var, std::shared_ptr<T>,
|
||||
uint32_t) { return function(var); };
|
||||
this->callbackList.onPackage(type, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task to the scheduler
|
||||
*
|
||||
* The task will be stored in a list and a shared_ptr to the task will be
|
||||
* returned. If the task is anonymous (i.e. no shared_ptr to it is held
|
||||
* anywhere else) and disabled then it will be reused when a new task is
|
||||
* added.
|
||||
*/
|
||||
std::shared_ptr<Task> addTask(Scheduler& scheduler, unsigned long aInterval,
|
||||
long aIterations,
|
||||
std::function<void()> aCallback) {
|
||||
using namespace painlessmesh::logger;
|
||||
for (auto&& task : taskList) {
|
||||
if (task.use_count() == 1 && !task->isEnabled()) {
|
||||
task->set(aInterval, aIterations, aCallback, NULL, NULL);
|
||||
task->enable();
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Task> task =
|
||||
std::make_shared<Task>(aInterval, aIterations, aCallback);
|
||||
scheduler.addTask((*task));
|
||||
task->enable();
|
||||
taskList.push_front(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
std::shared_ptr<Task> addTask(Scheduler& scheduler,
|
||||
std::function<void()> aCallback) {
|
||||
return this->addTask(scheduler, 0, TASK_ONCE, aCallback);
|
||||
}
|
||||
|
||||
protected:
|
||||
callback::MeshPackageCallbackList<T> callbackList;
|
||||
std::list<std::shared_ptr<Task> > taskList = {};
|
||||
};
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
#ifndef _PAINLESS_MESH_PROTOCOL_HPP_
|
||||
#define _PAINLESS_MESH_PROTOCOL_HPP_
|
||||
|
||||
#include <cmath>
|
||||
#include <list>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
|
||||
namespace router {
|
||||
|
||||
/** Different ways to route packages
|
||||
*
|
||||
* NEIGHBOUR packages are send to the neighbour and will be immediately handled
|
||||
* there. The TIME_SYNC and NODE_SYNC packages are NEIGHBOUR. SINGLE messages
|
||||
* are meant for a specific node. When another node receives this message, it
|
||||
* will look in its routing information and send it on to the correct node,
|
||||
* withouth processing the message in any other way. Only the targetted node
|
||||
* will actually parse/handle this message (without sending it on). Finally,
|
||||
* BROADCAST message are send to every node and processed/handled by every node.
|
||||
* */
|
||||
enum Type { ROUTING_ERROR = -1, NEIGHBOUR, SINGLE, BROADCAST };
|
||||
} // namespace router
|
||||
|
||||
namespace protocol {
|
||||
|
||||
enum Type {
|
||||
TIME_DELAY = 3,
|
||||
TIME_SYNC = 4,
|
||||
NODE_SYNC_REQUEST = 5,
|
||||
NODE_SYNC_REPLY = 6,
|
||||
CONTROL = 7, // deprecated
|
||||
BROADCAST = 8, // application data for everyone
|
||||
SINGLE = 9 // application data for a single node
|
||||
};
|
||||
|
||||
enum TimeType {
|
||||
TIME_SYNC_ERROR = -1,
|
||||
TIME_SYNC_REQUEST,
|
||||
TIME_REQUEST,
|
||||
TIME_REPLY
|
||||
};
|
||||
|
||||
class PackageInterface {
|
||||
public:
|
||||
virtual JsonObject addTo(JsonObject&& jsonObj) const = 0;
|
||||
virtual size_t jsonObjectSize() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single package
|
||||
*
|
||||
* Message send to a specific node
|
||||
*/
|
||||
class Single : public PackageInterface {
|
||||
public:
|
||||
int type = SINGLE;
|
||||
uint32_t from;
|
||||
uint32_t dest;
|
||||
TSTRING msg = "";
|
||||
|
||||
Single() {}
|
||||
Single(uint32_t fromID, uint32_t destID, TSTRING& message) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
msg = message;
|
||||
}
|
||||
|
||||
Single(JsonObject jsonObj) {
|
||||
dest = jsonObj["dest"].as<uint32_t>();
|
||||
from = jsonObj["from"].as<uint32_t>();
|
||||
msg = jsonObj["msg"].as<TSTRING>();
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["type"] = type;
|
||||
jsonObj["dest"] = dest;
|
||||
jsonObj["from"] = from;
|
||||
jsonObj["msg"] = msg;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(4) + round(1.1 * msg.length());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast package
|
||||
*/
|
||||
class Broadcast : public Single {
|
||||
public:
|
||||
int type = BROADCAST;
|
||||
|
||||
using Single::Single;
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = Single::addTo(std::move(jsonObj));
|
||||
jsonObj["type"] = type;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(4) + round(1.1 * msg.length());
|
||||
}
|
||||
};
|
||||
|
||||
class NodeTree : public PackageInterface {
|
||||
public:
|
||||
uint32_t nodeId = 0;
|
||||
bool root = false;
|
||||
std::list<NodeTree> subs;
|
||||
|
||||
NodeTree() {}
|
||||
|
||||
NodeTree(uint32_t nodeID, bool iAmRoot) {
|
||||
nodeId = nodeID;
|
||||
root = iAmRoot;
|
||||
}
|
||||
|
||||
NodeTree(JsonObject jsonObj) {
|
||||
if (jsonObj.containsKey("root")) root = jsonObj["root"].as<bool>();
|
||||
if (jsonObj.containsKey("nodeId"))
|
||||
nodeId = jsonObj["nodeId"].as<uint32_t>();
|
||||
else
|
||||
nodeId = jsonObj["from"].as<uint32_t>();
|
||||
|
||||
if (jsonObj.containsKey("subs")) {
|
||||
auto jsonArr = jsonObj["subs"].as<JsonArray>();
|
||||
for (size_t i = 0; i < jsonArr.size(); ++i) {
|
||||
subs.push_back(NodeTree(jsonArr[i].as<JsonObject>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["nodeId"] = nodeId;
|
||||
if (root) jsonObj["root"] = root;
|
||||
if (subs.size() > 0) {
|
||||
JsonArray subsArr = jsonObj.createNestedArray("subs");
|
||||
for (auto&& s : subs) {
|
||||
JsonObject subObj = subsArr.createNestedObject();
|
||||
subObj = s.addTo(std::move(subObj));
|
||||
}
|
||||
}
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
bool operator==(const NodeTree& b) const {
|
||||
if (!(this->nodeId == b.nodeId && this->root == b.root &&
|
||||
this->subs.size() == b.subs.size()))
|
||||
return false;
|
||||
auto itA = this->subs.begin();
|
||||
auto itB = b.subs.begin();
|
||||
for (size_t i = 0; i < this->subs.size(); ++i) {
|
||||
if ((*itA) != (*itB)) {
|
||||
return false;
|
||||
}
|
||||
++itA;
|
||||
++itB;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const NodeTree& b) const { return !this->operator==(b); }
|
||||
|
||||
TSTRING toString(bool pretty = false);
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
size_t base = 1;
|
||||
if (root) ++base;
|
||||
if (subs.size() > 0) ++base;
|
||||
size_t size = JSON_OBJECT_SIZE(base);
|
||||
if (subs.size() > 0) size += JSON_ARRAY_SIZE(subs.size());
|
||||
for (auto&& s : subs) size += s.jsonObjectSize();
|
||||
return size;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
nodeId = 0;
|
||||
subs.clear();
|
||||
root = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NodeSyncRequest package
|
||||
*/
|
||||
class NodeSyncRequest : public NodeTree {
|
||||
public:
|
||||
int type = NODE_SYNC_REQUEST;
|
||||
uint32_t from;
|
||||
uint32_t dest;
|
||||
|
||||
NodeSyncRequest() {}
|
||||
NodeSyncRequest(uint32_t fromID, uint32_t destID, std::list<NodeTree> subTree,
|
||||
bool iAmRoot = false) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
subs = subTree;
|
||||
nodeId = fromID;
|
||||
root = iAmRoot;
|
||||
}
|
||||
|
||||
NodeSyncRequest(JsonObject jsonObj) : NodeTree(jsonObj) {
|
||||
dest = jsonObj["dest"].as<uint32_t>();
|
||||
from = jsonObj["from"].as<uint32_t>();
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = NodeTree::addTo(std::move(jsonObj));
|
||||
jsonObj["type"] = type;
|
||||
jsonObj["dest"] = dest;
|
||||
jsonObj["from"] = from;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
bool operator==(const NodeSyncRequest& b) const {
|
||||
if (!(this->from == b.from && this->dest == b.dest)) return false;
|
||||
return NodeTree::operator==(b);
|
||||
}
|
||||
|
||||
bool operator!=(const NodeSyncRequest& b) const {
|
||||
return !this->operator==(b);
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
size_t base = 4;
|
||||
if (root) ++base;
|
||||
if (subs.size() > 0) ++base;
|
||||
size_t size = JSON_OBJECT_SIZE(base);
|
||||
if (subs.size() > 0) size += JSON_ARRAY_SIZE(subs.size());
|
||||
for (auto&& s : subs) size += s.jsonObjectSize();
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NodeSyncReply package
|
||||
*/
|
||||
class NodeSyncReply : public NodeSyncRequest {
|
||||
public:
|
||||
int type = NODE_SYNC_REPLY;
|
||||
|
||||
using NodeSyncRequest::NodeSyncRequest;
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = NodeSyncRequest::addTo(std::move(jsonObj));
|
||||
jsonObj["type"] = type;
|
||||
return jsonObj;
|
||||
}
|
||||
};
|
||||
|
||||
struct time_sync_msg_t {
|
||||
int type = TIME_SYNC_ERROR;
|
||||
uint32_t t0 = 0;
|
||||
uint32_t t1 = 0;
|
||||
uint32_t t2 = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* TimeSync package
|
||||
*/
|
||||
class TimeSync : public PackageInterface {
|
||||
public:
|
||||
int type = TIME_SYNC;
|
||||
uint32_t dest;
|
||||
uint32_t from;
|
||||
time_sync_msg_t msg;
|
||||
|
||||
TimeSync() {}
|
||||
|
||||
TimeSync(uint32_t fromID, uint32_t destID) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
msg.type = TIME_SYNC_REQUEST;
|
||||
}
|
||||
|
||||
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
msg.type = TIME_REQUEST;
|
||||
msg.t0 = t0;
|
||||
}
|
||||
|
||||
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0, uint32_t t1) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
msg.type = TIME_REPLY;
|
||||
msg.t0 = t0;
|
||||
msg.t1 = t1;
|
||||
}
|
||||
|
||||
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0, uint32_t t1,
|
||||
uint32_t t2) {
|
||||
from = fromID;
|
||||
dest = destID;
|
||||
msg.type = TIME_REPLY;
|
||||
msg.t0 = t0;
|
||||
msg.t1 = t1;
|
||||
msg.t2 = t2;
|
||||
}
|
||||
|
||||
TimeSync(JsonObject jsonObj) {
|
||||
dest = jsonObj["dest"].as<uint32_t>();
|
||||
from = jsonObj["from"].as<uint32_t>();
|
||||
msg.type = jsonObj["msg"]["type"].as<int>();
|
||||
if (jsonObj["msg"].containsKey("t0"))
|
||||
msg.t0 = jsonObj["msg"]["t0"].as<uint32_t>();
|
||||
if (jsonObj["msg"].containsKey("t1"))
|
||||
msg.t1 = jsonObj["msg"]["t1"].as<uint32_t>();
|
||||
if (jsonObj["msg"].containsKey("t2"))
|
||||
msg.t2 = jsonObj["msg"]["t2"].as<uint32_t>();
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["type"] = type;
|
||||
jsonObj["dest"] = dest;
|
||||
jsonObj["from"] = from;
|
||||
auto msgObj = jsonObj.createNestedObject("msg");
|
||||
msgObj["type"] = msg.type;
|
||||
if (msg.type >= 1) msgObj["t0"] = msg.t0;
|
||||
if (msg.type >= 2) {
|
||||
msgObj["t1"] = msg.t1;
|
||||
msgObj["t2"] = msg.t2;
|
||||
}
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reply to the current message with the new time set
|
||||
*/
|
||||
void reply(uint32_t newT0) {
|
||||
msg.t0 = newT0;
|
||||
++msg.type;
|
||||
std::swap(from, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reply to the current message with the new time set
|
||||
*/
|
||||
void reply(uint32_t newT1, uint32_t newT2) {
|
||||
msg.t1 = newT1;
|
||||
msg.t2 = newT2;
|
||||
++msg.type;
|
||||
std::swap(from, dest);
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TimeDelay package
|
||||
*/
|
||||
class TimeDelay : public TimeSync {
|
||||
public:
|
||||
int type = TIME_DELAY;
|
||||
using TimeSync::TimeSync;
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = TimeSync::addTo(std::move(jsonObj));
|
||||
jsonObj["type"] = type;
|
||||
return jsonObj;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Can store any package variant
|
||||
*
|
||||
* Internally stores packages as a JsonObject. Main use case is to convert
|
||||
* different packages from and to Json (using ArduinoJson).
|
||||
*/
|
||||
class Variant {
|
||||
public:
|
||||
#ifdef ARDUINOJSON_ENABLE_STD_STRING
|
||||
/**
|
||||
* Create Variant object from a json string
|
||||
*
|
||||
* @param json The json string containing a package
|
||||
*/
|
||||
Variant(std::string json)
|
||||
: jsonBuffer(JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4) +
|
||||
2 * json.length()) {
|
||||
error = deserializeJson(jsonBuffer, json,
|
||||
DeserializationOption::NestingLimit(255));
|
||||
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a json string
|
||||
*
|
||||
* @param json The json string containing a package
|
||||
* @param capacity The capacity to reserve for parsing the string
|
||||
*/
|
||||
Variant(std::string json, size_t capacity) : jsonBuffer(capacity) {
|
||||
error = deserializeJson(jsonBuffer, json,
|
||||
DeserializationOption::NestingLimit(255));
|
||||
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
/**
|
||||
* Create Variant object from a json string
|
||||
*
|
||||
* @param json The json string containing a package
|
||||
*/
|
||||
Variant(String json)
|
||||
: jsonBuffer(JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4) +
|
||||
2 * json.length()) {
|
||||
error = deserializeJson(jsonBuffer, json,
|
||||
DeserializationOption::NestingLimit(255));
|
||||
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a json string
|
||||
*
|
||||
* @param json The json string containing a package
|
||||
* @param capacity The capacity to reserve for parsing the string
|
||||
*/
|
||||
Variant(String json, size_t capacity) : jsonBuffer(capacity) {
|
||||
error = deserializeJson(jsonBuffer, json,
|
||||
DeserializationOption::NestingLimit(255));
|
||||
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* Create Variant object from any package implementing PackageInterface
|
||||
*/
|
||||
Variant(const PackageInterface* pkg) : jsonBuffer(pkg->jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = pkg->addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a Single package
|
||||
*
|
||||
* @param single The single package
|
||||
*/
|
||||
Variant(Single single) : jsonBuffer(single.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = single.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a Broadcast package
|
||||
*
|
||||
* @param broadcast The broadcast package
|
||||
*/
|
||||
Variant(Broadcast broadcast) : jsonBuffer(broadcast.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = broadcast.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a NodeTree
|
||||
*
|
||||
* @param nodeTree The NodeTree
|
||||
*/
|
||||
Variant(NodeTree nodeTree) : jsonBuffer(nodeTree.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = nodeTree.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a NodeSyncReply package
|
||||
*
|
||||
* @param nodeSyncReply The nodeSyncReply package
|
||||
*/
|
||||
Variant(NodeSyncReply nodeSyncReply)
|
||||
: jsonBuffer(nodeSyncReply.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = nodeSyncReply.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a NodeSyncRequest package
|
||||
*
|
||||
* @param nodeSyncRequest The nodeSyncRequest package
|
||||
*/
|
||||
Variant(NodeSyncRequest nodeSyncRequest)
|
||||
: jsonBuffer(nodeSyncRequest.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = nodeSyncRequest.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a TimeSync package
|
||||
*
|
||||
* @param timeSync The timeSync package
|
||||
*/
|
||||
Variant(TimeSync timeSync) : jsonBuffer(timeSync.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = timeSync.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Variant object from a TimeDelay package
|
||||
*
|
||||
* @param timeDelay The timeDelay package
|
||||
*/
|
||||
Variant(TimeDelay timeDelay) : jsonBuffer(timeDelay.jsonObjectSize()) {
|
||||
jsonObj = jsonBuffer.to<JsonObject>();
|
||||
jsonObj = timeDelay.addTo(std::move(jsonObj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this package is of the given type
|
||||
*/
|
||||
template <typename T>
|
||||
inline bool is() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Variant to the given type
|
||||
*/
|
||||
template <typename T>
|
||||
inline T to() {
|
||||
return T(jsonObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return package type
|
||||
*/
|
||||
int type() { return jsonObj["type"].as<int>(); }
|
||||
|
||||
/**
|
||||
* Package routing method
|
||||
*/
|
||||
router::Type routing() {
|
||||
if (jsonObj.containsKey("routing"))
|
||||
return (router::Type)jsonObj["routing"].as<int>();
|
||||
|
||||
auto type = this->type();
|
||||
if (type == SINGLE || type == TIME_DELAY) return router::SINGLE;
|
||||
if (type == BROADCAST) return router::BROADCAST;
|
||||
if (type == NODE_SYNC_REQUEST || type == NODE_SYNC_REPLY ||
|
||||
type == TIME_SYNC)
|
||||
return router::NEIGHBOUR;
|
||||
return router::ROUTING_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination node of the package
|
||||
*/
|
||||
uint32_t dest() {
|
||||
if (jsonObj.containsKey("dest")) return jsonObj["dest"].as<uint32_t>();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef ARDUINOJSON_ENABLE_STD_STRING
|
||||
/**
|
||||
* Print a variant to a string
|
||||
*
|
||||
* @return A json representation of the string
|
||||
*/
|
||||
void printTo(std::string& str, bool pretty = false) {
|
||||
if (pretty)
|
||||
serializeJsonPretty(jsonObj, str);
|
||||
else
|
||||
serializeJson(jsonObj, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
/**
|
||||
* Print a variant to a string
|
||||
*
|
||||
* @return A json representation of the string
|
||||
*/
|
||||
void printTo(String& str, bool pretty = false) {
|
||||
if (pretty)
|
||||
serializeJsonPretty(jsonObj, str);
|
||||
else
|
||||
serializeJson(jsonObj, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
DeserializationError error = DeserializationError::Ok;
|
||||
|
||||
private:
|
||||
DynamicJsonDocument jsonBuffer;
|
||||
JsonObject jsonObj;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<Single>() {
|
||||
return jsonObj["type"].as<int>() == SINGLE;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<Broadcast>() {
|
||||
return jsonObj["type"].as<int>() == BROADCAST;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<NodeSyncReply>() {
|
||||
return jsonObj["type"].as<int>() == NODE_SYNC_REPLY;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<NodeSyncRequest>() {
|
||||
return jsonObj["type"].as<int>() == NODE_SYNC_REQUEST;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<TimeSync>() {
|
||||
return jsonObj["type"].as<int>() == TIME_SYNC;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool Variant::is<TimeDelay>() {
|
||||
return jsonObj["type"].as<int>() == TIME_DELAY;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline JsonObject Variant::to<JsonObject>() {
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
inline TSTRING NodeTree::toString(bool pretty) {
|
||||
TSTRING str;
|
||||
auto variant = Variant(*this);
|
||||
variant.printTo(str, pretty);
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
#ifndef _PAINLESS_MESH_ROUTER_HPP_
|
||||
#define _PAINLESS_MESH_ROUTER_HPP_
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "painlessmesh/callback.hpp"
|
||||
#include "painlessmesh/layout.hpp"
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
|
||||
extern painlessmesh::logger::LogClass Log;
|
||||
|
||||
namespace painlessmesh {
|
||||
|
||||
/**
|
||||
* Helper functions to route messages
|
||||
*/
|
||||
namespace router {
|
||||
template <class T>
|
||||
std::shared_ptr<T> findRoute(layout::Layout<T> tree,
|
||||
std::function<bool(std::shared_ptr<T>)> func) {
|
||||
auto route = std::find_if(tree.subs.begin(), tree.subs.end(), func);
|
||||
if (route == tree.subs.end()) return NULL;
|
||||
return (*route);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<T> findRoute(layout::Layout<T> tree, uint32_t nodeId) {
|
||||
return findRoute<T>(tree, [nodeId](std::shared_ptr<T> s) {
|
||||
return layout::contains((*s), nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
bool send(T package, std::shared_ptr<U> conn, bool priority = false) {
|
||||
auto variant = painlessmesh::protocol::Variant(package);
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
return conn->addMessage(msg, priority);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
bool send(protocol::Variant variant, std::shared_ptr<U> conn,
|
||||
bool priority = false) {
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
return conn->addMessage(msg, priority);
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
bool send(T package, layout::Layout<U> layout) {
|
||||
auto variant = painlessmesh::protocol::Variant(package);
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
auto conn = findRoute<U>(layout, variant.dest);
|
||||
if (conn) return conn->addMessage(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class U>
|
||||
bool send(protocol::Variant variant, layout::Layout<U> layout) {
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
auto conn = findRoute<U>(layout, variant.dest());
|
||||
if (conn) return conn->addMessage(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
size_t broadcast(T package, layout::Layout<U> layout, uint32_t exclude) {
|
||||
auto variant = painlessmesh::protocol::Variant(package);
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
size_t i = 0;
|
||||
for (auto&& conn : layout.subs) {
|
||||
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
||||
auto sent = conn->addMessage(msg);
|
||||
if (sent) ++i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
size_t broadcast(protocol::Variant variant, layout::Layout<T> layout,
|
||||
uint32_t exclude) {
|
||||
TSTRING msg;
|
||||
variant.printTo(msg);
|
||||
size_t i = 0;
|
||||
for (auto&& conn : layout.subs) {
|
||||
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
||||
auto sent = conn->addMessage(msg);
|
||||
if (sent) ++i;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void routePackage(layout::Layout<T> layout, std::shared_ptr<T> connection,
|
||||
TSTRING pkg, callback::MeshPackageCallbackList<T> cbl, uint32_t receivedAt) {
|
||||
using namespace logger;
|
||||
static size_t baseCapacity = 512;
|
||||
Log(COMMUNICATION, "routePackage(): Recvd from %u: %s\n", connection->nodeId,
|
||||
pkg.c_str());
|
||||
// Using a ptr so we can overwrite it if we need to grow capacity.
|
||||
// Bug in copy constructor with grown capacity can cause segmentation fault
|
||||
auto variant =
|
||||
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
||||
while (variant->error == 3 && baseCapacity <= 20480) {
|
||||
// Not enough memory, adapt scaling (variant::capacityScaling) and log the
|
||||
// new value
|
||||
Log(DEBUG,
|
||||
"routePackage(): parsing failed. err=%u, increasing capacity: %u\n",
|
||||
variant->error, baseCapacity);
|
||||
baseCapacity += 256;
|
||||
variant =
|
||||
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
||||
}
|
||||
if (variant->error) {
|
||||
Log(ERROR,
|
||||
"routePackage(): parsing failed. err=%u, total_length=%d, data=%s<--\n",
|
||||
variant->error, pkg.length(), pkg.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (variant->routing() == SINGLE && variant->dest() != layout.getNodeId()) {
|
||||
// Send on without further processing
|
||||
send<T>((*variant), layout);
|
||||
return;
|
||||
} else if (variant->routing() == BROADCAST) {
|
||||
broadcast<T>((*variant), layout, connection->nodeId);
|
||||
}
|
||||
auto calls = cbl.execute(variant->type(), (*variant), connection, receivedAt);
|
||||
if (calls == 0)
|
||||
Log(DEBUG, "routePackage(): No callbacks executed; %u, %s\n", variant->type(), pkg.c_str());
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void handleNodeSync(T& mesh, protocol::NodeTree newTree,
|
||||
std::shared_ptr<U> conn) {
|
||||
Log(logger::SYNC, "handleNodeSync(): with %u\n", conn->nodeId);
|
||||
|
||||
if (!conn->validSubs(newTree)) {
|
||||
Log(logger::SYNC, "handleNodeSync(): invalid new connection\n");
|
||||
conn->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (conn->newConnection) {
|
||||
auto oldConnection = router::findRoute<U>(mesh, newTree.nodeId);
|
||||
if (oldConnection) {
|
||||
Log(logger::SYNC,
|
||||
"handleNodeSync(): already connected to %u. Closing the new "
|
||||
"connection \n",
|
||||
conn->nodeId);
|
||||
conn->close();
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.addTask([&mesh, remoteNodeId = newTree.nodeId]() {
|
||||
Log(logger::CONNECTION, "newConnectionTask():\n");
|
||||
Log(logger::CONNECTION, "newConnectionTask(): adding %u now= %u\n",
|
||||
remoteNodeId, mesh.getNodeTime());
|
||||
mesh.newConnectionCallbacks.execute(remoteNodeId);
|
||||
});
|
||||
|
||||
// Initially interval is every 10 seconds,
|
||||
// this will slow down to TIME_SYNC_INTERVAL
|
||||
// after first succesfull sync
|
||||
// TODO move it to a new connection callback and use initTimeSync from
|
||||
// ntp.hpp
|
||||
conn->timeSyncTask.set(10 * TASK_SECOND, TASK_FOREVER, [conn, &mesh]() {
|
||||
Log(logger::S_TIME, "timeSyncTask(): %u\n", conn->nodeId);
|
||||
mesh.startTimeSync(conn);
|
||||
});
|
||||
mesh.mScheduler->addTask(conn->timeSyncTask);
|
||||
if (conn->station)
|
||||
// We are STA, request time immediately
|
||||
conn->timeSyncTask.enable();
|
||||
else
|
||||
// We are the AP, give STA the change to initiate time sync
|
||||
conn->timeSyncTask.enableDelayed();
|
||||
conn->newConnection = false;
|
||||
}
|
||||
|
||||
if (conn->updateSubs(newTree)) {
|
||||
mesh.addTask([&mesh, nodeId = newTree.nodeId]() {
|
||||
mesh.changedConnectionCallbacks.execute(nodeId);
|
||||
});
|
||||
} else {
|
||||
conn->nodeSyncTask.delay();
|
||||
mesh.stability += std::min(1000 - mesh.stability, (size_t)25);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, typename U>
|
||||
callback::MeshPackageCallbackList<U> addPackageCallback(
|
||||
callback::MeshPackageCallbackList<U>&& callbackList, T& mesh) {
|
||||
// REQUEST type,
|
||||
callbackList.onPackage(
|
||||
protocol::NODE_SYNC_REQUEST,
|
||||
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
|
||||
uint32_t receivedAt) {
|
||||
auto newTree = variant.to<protocol::NodeSyncRequest>();
|
||||
handleNodeSync<T, U>(mesh, newTree, connection);
|
||||
send<protocol::NodeSyncReply>(
|
||||
connection->reply(std::move(mesh.asNodeTree())), connection, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
// Reply type just handle it
|
||||
callbackList.onPackage(
|
||||
protocol::NODE_SYNC_REPLY,
|
||||
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
|
||||
uint32_t receivedAt) {
|
||||
auto newTree = variant.to<protocol::NodeSyncReply>();
|
||||
handleNodeSync<T, U>(mesh, newTree, connection);
|
||||
connection->timeOutTask.disable();
|
||||
return false;
|
||||
});
|
||||
|
||||
return callbackList;
|
||||
}
|
||||
|
||||
} // namespace router
|
||||
} // namespace painlessmesh
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef _PAINLESS_MESH_TCP_HPP_
|
||||
#define _PAINLESS_MESH_TCP_HPP_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace tcp {
|
||||
inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
|
||||
using namespace painlessmesh::logger;
|
||||
Log(GENERAL, "encodeNodeId():\n");
|
||||
uint32_t value = 0;
|
||||
|
||||
value |= hwaddr[2] << 24; // Big endian (aka "network order"):
|
||||
value |= hwaddr[3] << 16;
|
||||
value |= hwaddr[4] << 8;
|
||||
value |= hwaddr[5];
|
||||
return value;
|
||||
}
|
||||
|
||||
template <class T, class M>
|
||||
void initServer(AsyncServer &server, M &mesh) {
|
||||
using namespace logger;
|
||||
server.setNoDelay(true);
|
||||
|
||||
server.onClient(
|
||||
[&mesh](void *arg, AsyncClient *client) {
|
||||
if (mesh.semaphoreTake()) {
|
||||
Log(CONNECTION, "New AP connection incoming\n");
|
||||
auto conn = std::make_shared<T>(client, &mesh, false);
|
||||
conn->initTasks();
|
||||
conn->initTCPCallbacks();
|
||||
mesh.subs.push_back(conn);
|
||||
mesh.semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
server.begin();
|
||||
}
|
||||
|
||||
template <class T, class M>
|
||||
void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh) {
|
||||
using namespace logger;
|
||||
client.onError([&mesh](void *, AsyncClient *client, int8_t err) {
|
||||
if (mesh.semaphoreTake()) {
|
||||
Log(CONNECTION, "tcp_err(): error trying to connect %d\n", err);
|
||||
mesh.droppedConnectionCallbacks.execute(0, true);
|
||||
mesh.semaphoreGive();
|
||||
}
|
||||
});
|
||||
|
||||
client.onConnect(
|
||||
[&mesh](void *, AsyncClient *client) {
|
||||
if (mesh.semaphoreTake()) {
|
||||
Log(CONNECTION, "New STA connection incoming\n");
|
||||
auto conn = std::make_shared<T>(client, &mesh, true);
|
||||
conn->initTasks();
|
||||
conn->initTCPCallbacks();
|
||||
mesh.subs.push_back(conn);
|
||||
mesh.semaphoreGive();
|
||||
}
|
||||
},
|
||||
NULL);
|
||||
|
||||
client.connect(ip, port);
|
||||
}
|
||||
} // namespace tcp
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
@@ -0,0 +1,202 @@
|
||||
#ifndef _PAINLESS_MESH_PLUGIN_PERFORMANCE_HPP_
|
||||
#define _PAINLESS_MESH_PLUGIN_PERFORMANCE_HPP_
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
#include "painlessmesh/plugin.hpp"
|
||||
|
||||
namespace painlessmesh {
|
||||
namespace plugin {
|
||||
/** Add performance tracking to the mesh
|
||||
*
|
||||
* Nodes will send out special packages to each other, allowing each node to
|
||||
* keep track of different performance measures
|
||||
*/
|
||||
namespace performance {
|
||||
|
||||
/** Track mean and variance of the given value
|
||||
*
|
||||
* Older values are discounted, so that you keep a rolling average
|
||||
*/
|
||||
class Stats {
|
||||
public:
|
||||
void update(double v, double alpha = 0.1) {
|
||||
if (!init) {
|
||||
mu = v;
|
||||
init = true;
|
||||
return;
|
||||
}
|
||||
auto d = v - mu;
|
||||
mu = mu + alpha * d;
|
||||
var = (1 - alpha) * (var + alpha * pow(d, 2));
|
||||
}
|
||||
|
||||
// Returns the mean and 95% interval based on 1.96*sd? or 2.96*sd
|
||||
TSTRING toString() const {
|
||||
#ifdef PAINLESSMESH_ENABLE_STD_STRING
|
||||
std::stringstream ss;
|
||||
ss << mu << "[" << mu - 1.96 * sqrt(var) << "," << mu + 1.96 * sqrt(var)
|
||||
<< "]";
|
||||
return ss.str();
|
||||
#else
|
||||
return TSTRING(mu) + TSTRING("[") + TSTRING(mu - 1.96 * sqrt(var)) +
|
||||
TSTRING(",") + TSTRING(mu + 1.96 * sqrt(var)) + TSTRING("]");
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
double mu = 0;
|
||||
double var = 0;
|
||||
bool init = false;
|
||||
};
|
||||
|
||||
class PerformancePackage : public plugin::BroadcastPackage {
|
||||
public:
|
||||
int id = 0; // Can see if we missed values
|
||||
int time; // Get an idea of the delay, by comparing it with nodetime
|
||||
int stability; // stability of the sending node
|
||||
int freeMemory; // memory of the sending node
|
||||
#ifdef ESP32
|
||||
TSTRING hardware = "ESP32";
|
||||
#else
|
||||
TSTRING hardware = "ESP8266";
|
||||
#endif
|
||||
PerformancePackage() : plugin::BroadcastPackage(13) {}
|
||||
|
||||
PerformancePackage(JsonObject jsonObj) : plugin::BroadcastPackage(jsonObj) {
|
||||
id = jsonObj["id"];
|
||||
time = jsonObj["time"];
|
||||
stability = jsonObj["stability"];
|
||||
freeMemory = jsonObj["freeMemory"];
|
||||
hardware = jsonObj["hardware"].as<TSTRING>();
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
|
||||
jsonObj["type"] = type;
|
||||
jsonObj["id"] = id;
|
||||
jsonObj["time"] = time;
|
||||
jsonObj["stability"] = stability;
|
||||
jsonObj["freeMemory"] = freeMemory;
|
||||
jsonObj["hardware"] = hardware;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(4 + 5) + round(2 * (hardware.length()));
|
||||
}
|
||||
};
|
||||
|
||||
/// Numbers to track for each node we receive PerformancePackages from
|
||||
class Track {
|
||||
public:
|
||||
uint32_t nodeId;
|
||||
TSTRING hardware;
|
||||
uint32_t hits = 0;
|
||||
uint32_t misses = 0;
|
||||
int lastId = 0;
|
||||
Stats delay;
|
||||
Stats stability;
|
||||
Stats freeMemory;
|
||||
uint32_t present = 0; // Every so often check if each node is absent or
|
||||
// present in the layout
|
||||
uint32_t absent = 0;
|
||||
Track() {}
|
||||
|
||||
void addTo(JsonObject& jsonObj) const {
|
||||
jsonObj["nodeId"] = nodeId;
|
||||
jsonObj["hardware"] = hardware;
|
||||
jsonObj["hits"] = hits;
|
||||
jsonObj["misses"] = misses;
|
||||
jsonObj["delay"] = delay.toString();
|
||||
jsonObj["stability"] = stability.toString();
|
||||
jsonObj["freeMemory"] = freeMemory.toString();
|
||||
jsonObj["present"] = present;
|
||||
jsonObj["absent"] = absent;
|
||||
}
|
||||
};
|
||||
|
||||
/// Holds resulst from all the nodes
|
||||
class TrackMap : public protocol::PackageInterface,
|
||||
public std::map<uint32_t, Track> {
|
||||
public:
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj["event"] = "performance";
|
||||
// Start array
|
||||
auto jsonArr = jsonObj.createNestedArray("nodes");
|
||||
// for each in map do
|
||||
for (auto&& pair : (*this)) {
|
||||
auto obj = jsonArr.createNestedObject();
|
||||
pair.second.addTo(obj);
|
||||
}
|
||||
return jsonObj;
|
||||
} // namespace performance
|
||||
|
||||
size_t jsonObjectSize() const {
|
||||
return JSON_OBJECT_SIZE(2 + 15) + JSON_ARRAY_SIZE(this->size()) +
|
||||
this->size()*(JSON_OBJECT_SIZE(9) + 4 * 100);
|
||||
}
|
||||
}; // namespace plugin
|
||||
|
||||
template <class T>
|
||||
void begin(T& mesh, double frequency = 2) {
|
||||
auto tracker = std::make_shared<TrackMap>();
|
||||
auto sendPkg = std::make_shared<PerformancePackage>();
|
||||
|
||||
mesh.onPackage(sendPkg->type, [&mesh, tracker](protocol::Variant var) {
|
||||
auto pkg = var.to<PerformancePackage>();
|
||||
// if not in tracker, add it
|
||||
if (!tracker->count(pkg.from)) {
|
||||
tracker->operator[](pkg.from) = Track();
|
||||
if (pkg.id > 0) tracker->operator[](pkg.from).lastId = pkg.id - 1;
|
||||
}
|
||||
// update all the values in the trackmap
|
||||
tracker->operator[](pkg.from).nodeId = pkg.from;
|
||||
tracker->operator[](pkg.from).hardware = pkg.hardware;
|
||||
++tracker->operator[](pkg.from).hits;
|
||||
if (pkg.id < tracker->operator[](pkg.from).lastId) // Node was reset?
|
||||
tracker->operator[](pkg.from).lastId = pkg.id;
|
||||
else {
|
||||
tracker->operator[](pkg.from).misses +=
|
||||
(pkg.id - tracker->operator[](pkg.from).lastId) - 1;
|
||||
}
|
||||
tracker->operator[](pkg.from).lastId = pkg.id;
|
||||
tracker->operator[](pkg.from).delay.update(
|
||||
((int)mesh.getNodeTime() - pkg.time) / 1000);
|
||||
tracker->operator[](pkg.from).stability.update(pkg.stability);
|
||||
tracker->operator[](pkg.from).freeMemory.update(pkg.freeMemory);
|
||||
return false;
|
||||
});
|
||||
|
||||
sendPkg->from = mesh.getNodeId();
|
||||
mesh.addTask(frequency*TASK_SECOND, TASK_FOREVER, [sendPkg, &mesh]() {
|
||||
++sendPkg->id;
|
||||
sendPkg->time = mesh.getNodeTime();
|
||||
sendPkg->stability = mesh.stability;
|
||||
sendPkg->freeMemory = ESP.getFreeHeap();
|
||||
mesh.sendPackage(sendPkg.get());
|
||||
});
|
||||
|
||||
mesh.addTask(TASK_MINUTE, TASK_FOREVER, [tracker, &mesh]() {
|
||||
for (auto&& pair : (*tracker)) {
|
||||
if (mesh.isConnected(pair.first))
|
||||
++pair.second.present;
|
||||
else
|
||||
++pair.second.absent;
|
||||
}
|
||||
protocol::Variant var(tracker.get());
|
||||
TSTRING str;
|
||||
var.printTo(str);
|
||||
#ifdef PAINLESSMESH_ENABLE_STD_STRING
|
||||
std::cout << str << std::endl;
|
||||
#else
|
||||
Serial.println(str);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace performance
|
||||
} // namespace plugin
|
||||
} // namespace painlessmesh
|
||||
#endif
|
||||
15
arduino-cli/libraries/painlessMesh-master/src/scheduler.cpp
Normal file
15
arduino-cli/libraries/painlessMesh-master/src/scheduler.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* https://github.com/arkhipenko/TaskScheduler/tree/master/examples/Scheduler_example16_Multitab
|
||||
*/
|
||||
|
||||
// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
|
||||
// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
|
||||
// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
|
||||
// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
|
||||
// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
// #define _TASK_MICRO_RES // Support for microsecond resolution
|
||||
#define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY)
|
||||
// #define _TASK_DEBUG // Make all methods and variables public for debug purposes
|
||||
|
||||
#include <TaskScheduler.h>
|
||||
2
arduino-cli/libraries/painlessMesh-master/src/wifi.cpp
Normal file
2
arduino-cli/libraries/painlessMesh-master/src/wifi.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "arduino/wifi.hpp"
|
||||
painlessmesh::logger::LogClass Log;
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Wrapper file, which is used to test on PC hardware
|
||||
*/
|
||||
#ifndef ARDUINO_WRAP_H
|
||||
#define ARDUINO_WRAP_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define F(string_literal) string_literal
|
||||
#define ARDUINO_ARCH_ESP8266
|
||||
#define PAINLESSMESH_BOOST
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL 0
|
||||
#endif
|
||||
|
||||
inline unsigned long millis() {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
long long milliseconds =
|
||||
te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
|
||||
// printf("milliseconds: %lld\n", milliseconds);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
inline unsigned long micros() {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
long long milliseconds = te.tv_sec * 1000000LL + te.tv_usec;
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
inline void delay(int i) { usleep(i); }
|
||||
|
||||
inline void yield() {}
|
||||
|
||||
/**
|
||||
* Override the configution file.
|
||||
**/
|
||||
|
||||
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
#define _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
#define _TASK_STD_FUNCTION
|
||||
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
#include <ArduinoJson.h>
|
||||
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
|
||||
#define ICACHE_FLASH_ATTR
|
||||
|
||||
#define PAINLESSMESH_ENABLE_STD_STRING
|
||||
#define PAINLESSMESH_ENABLE_OTA
|
||||
#define NODE_TIMEOUT 5 * TASK_SECOND
|
||||
|
||||
typedef std::string TSTRING;
|
||||
|
||||
#ifdef ESP32
|
||||
#define MAX_CONN 10
|
||||
#else
|
||||
#define MAX_CONN 4
|
||||
#endif // DEBUG
|
||||
|
||||
#include "boost/asynctcp.hpp"
|
||||
#include "fake_serial.hpp"
|
||||
|
||||
typedef enum {
|
||||
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
|
||||
WL_IDLE_STATUS = 0,
|
||||
WL_NO_SSID_AVAIL = 1,
|
||||
WL_SCAN_COMPLETED = 2,
|
||||
WL_CONNECTED = 3,
|
||||
WL_CONNECT_FAILED = 4,
|
||||
WL_CONNECTION_LOST = 5,
|
||||
WL_DISCONNECTED = 6
|
||||
} wl_status_t;
|
||||
|
||||
class WiFiClass {
|
||||
public:
|
||||
void disconnect() {}
|
||||
auto status() { return WL_CONNECTED; }
|
||||
};
|
||||
|
||||
class ESPClass {
|
||||
public:
|
||||
size_t getFreeHeap() { return 1e6; }
|
||||
};
|
||||
|
||||
extern WiFiClass WiFi;
|
||||
extern ESPClass ESP;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,399 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "boost/asynctcp.hpp"
|
||||
|
||||
WiFiClass WiFi;
|
||||
ESPClass ESP;
|
||||
|
||||
#include "painlessMeshConnection.h"
|
||||
|
||||
#include "painlessmesh/mesh.hpp"
|
||||
|
||||
using PMesh = painlessmesh::Mesh<MeshConnection>;
|
||||
|
||||
using namespace painlessmesh;
|
||||
painlessmesh::logger::LogClass Log;
|
||||
|
||||
class MeshTest : public PMesh {
|
||||
public:
|
||||
MeshTest(Scheduler *scheduler, size_t id, boost::asio::io_service &io)
|
||||
: io_service(io) {
|
||||
this->nodeId = id;
|
||||
this->init(scheduler, this->nodeId);
|
||||
timeOffset = runif(0, 1e09);
|
||||
pServer = std::make_shared<AsyncServer>(io_service, this->nodeId);
|
||||
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, (*this));
|
||||
}
|
||||
|
||||
void connect(MeshTest &mesh) {
|
||||
auto pClient = new AsyncClient(io_service);
|
||||
painlessmesh::tcp::connect<MeshConnection, PMesh>(
|
||||
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"),
|
||||
mesh.nodeId, (*this));
|
||||
}
|
||||
|
||||
std::shared_ptr<AsyncServer> pServer;
|
||||
boost::asio::io_service &io_service;
|
||||
};
|
||||
|
||||
class Nodes {
|
||||
public:
|
||||
Nodes(Scheduler *scheduler, size_t n, boost::asio::io_service &io)
|
||||
: io_service(io) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto m = std::make_shared<MeshTest>(scheduler, i + baseID, io_service);
|
||||
if (i > 0) m->connect((*nodes[runif(0, i - 1)]));
|
||||
nodes.push_back(m);
|
||||
}
|
||||
}
|
||||
void update() {
|
||||
for (auto &&m : nodes) {
|
||||
m->update();
|
||||
io_service.poll();
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
for (auto &&m : nodes) m->stop();
|
||||
}
|
||||
|
||||
auto size() { return nodes.size(); }
|
||||
|
||||
std::shared_ptr<MeshTest> get(size_t nodeId) {
|
||||
return nodes[nodeId - baseID];
|
||||
}
|
||||
|
||||
size_t baseID = 6481;
|
||||
std::vector<std::shared_ptr<MeshTest>> nodes;
|
||||
boost::asio::io_service &io_service;
|
||||
};
|
||||
|
||||
SCENARIO("We can setup and connect two meshes over localport") {
|
||||
using namespace logger;
|
||||
Scheduler scheduler;
|
||||
Log.setLogLevel(ERROR);
|
||||
boost::asio::io_service io_service;
|
||||
|
||||
PMesh mesh1;
|
||||
mesh1.init(&scheduler, 6841);
|
||||
std::shared_ptr<AsyncServer> pServer;
|
||||
pServer = std::make_shared<AsyncServer>(io_service, 6841);
|
||||
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, mesh1);
|
||||
|
||||
PMesh mesh2;
|
||||
mesh2.init(&scheduler, 6842);
|
||||
auto pClient = new AsyncClient(io_service);
|
||||
painlessmesh::tcp::connect<MeshConnection, PMesh>(
|
||||
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"), 6841,
|
||||
mesh2);
|
||||
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
mesh1.update();
|
||||
mesh2.update();
|
||||
io_service.poll();
|
||||
}
|
||||
|
||||
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
|
||||
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
|
||||
}
|
||||
|
||||
SCENARIO("The MeshTest class works correctly") {
|
||||
using namespace logger;
|
||||
Scheduler scheduler;
|
||||
Log.setLogLevel(ERROR);
|
||||
boost::asio::io_service io_service;
|
||||
|
||||
MeshTest mesh1(&scheduler, 6841, io_service);
|
||||
MeshTest mesh2(&scheduler, 6842, io_service);
|
||||
mesh2.connect(mesh1);
|
||||
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
mesh1.update();
|
||||
mesh2.update();
|
||||
io_service.poll();
|
||||
}
|
||||
|
||||
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
|
||||
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
|
||||
}
|
||||
|
||||
SCENARIO("We can send a message using our Nodes class") {
|
||||
delay(1000);
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
Nodes n(&scheduler, 12, io_service);
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
REQUIRE(layout::size(n.nodes[0]->asNodeTree()) == 12);
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
std::string z;
|
||||
n.nodes[0]->onReceive([&x, &y, &z](auto id, auto msg) {
|
||||
++x;
|
||||
y = id;
|
||||
z = msg;
|
||||
});
|
||||
n.nodes[10]->sendSingle(n.nodes[2]->getNodeId(), "Blaat");
|
||||
for (auto i = 0; i < 1000; ++i) n.update();
|
||||
REQUIRE(x == 0);
|
||||
REQUIRE(y == 0);
|
||||
REQUIRE(z == "");
|
||||
|
||||
n.nodes[10]->sendSingle(n.nodes[0]->getNodeId(), "Blaat");
|
||||
for (auto i = 0; i < 1000; ++i) n.update();
|
||||
REQUIRE(z == "Blaat");
|
||||
REQUIRE(x == 1);
|
||||
REQUIRE(y == n.nodes[10]->getNodeId());
|
||||
|
||||
n.nodes[5]->onReceive([&x, &y, &z](auto id, auto msg) {
|
||||
++x;
|
||||
y = id;
|
||||
z = msg;
|
||||
});
|
||||
n.nodes[10]->sendBroadcast("Blargh");
|
||||
for (auto i = 0; i < 10000; ++i) n.update();
|
||||
REQUIRE(z == "Blargh");
|
||||
REQUIRE(x == 3);
|
||||
REQUIRE(y == n.nodes[10]->getNodeId());
|
||||
n.stop();
|
||||
}
|
||||
|
||||
SCENARIO("Time sync works") {
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
auto dim = runif(8, 15);
|
||||
Nodes n(&scheduler, dim, io_service);
|
||||
|
||||
int diff = 0;
|
||||
for (size_t i = 0; i < n.size() - 1; ++i) {
|
||||
diff += std::abs((int)n.nodes[0]->getNodeTime() -
|
||||
(int)n.nodes[i + 1]->getNodeTime());
|
||||
}
|
||||
REQUIRE(diff / n.size() > 10000);
|
||||
|
||||
for (auto i = 0; i < 10000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
diff = 0;
|
||||
for (size_t i = 0; i < n.size() - 1; ++i) {
|
||||
diff += std::abs((int)n.nodes[0]->getNodeTime() -
|
||||
(int)n.nodes[i + 1]->getNodeTime());
|
||||
}
|
||||
REQUIRE(diff / n.size() < 10000);
|
||||
n.stop();
|
||||
}
|
||||
|
||||
SCENARIO("Rooting works") {
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
auto dim = runif(8, 15);
|
||||
Nodes n(&scheduler, dim, io_service);
|
||||
|
||||
n.nodes[5]->setRoot(true);
|
||||
REQUIRE(n.nodes[5]->isRoot());
|
||||
REQUIRE(layout::isRooted(n.nodes[5]->asNodeTree()));
|
||||
|
||||
for (auto i = 0; i < 10000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
for (auto &&node : n.nodes) {
|
||||
REQUIRE(layout::isRooted(node->asNodeTree()));
|
||||
if (n.nodes[5]->getNodeId() == node->getNodeId()) {
|
||||
REQUIRE(node->isRoot());
|
||||
} else {
|
||||
REQUIRE(!node->isRoot());
|
||||
}
|
||||
}
|
||||
|
||||
n.stop();
|
||||
}
|
||||
|
||||
SCENARIO("Network loops are detected") {
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
MeshTest mesh1(&scheduler, 6841, io_service);
|
||||
MeshTest mesh2(&scheduler, 6842, io_service);
|
||||
MeshTest mesh3(&scheduler, 6843, io_service);
|
||||
MeshTest mesh4(&scheduler, 6844, io_service);
|
||||
MeshTest mesh5(&scheduler, 6845, io_service);
|
||||
|
||||
mesh1.connect(mesh5);
|
||||
mesh2.connect(mesh1);
|
||||
mesh3.connect(mesh2);
|
||||
mesh4.connect(mesh3);
|
||||
mesh5.connect(mesh4);
|
||||
|
||||
for (auto i = 0; i < 10000; ++i) {
|
||||
mesh1.update();
|
||||
io_service.poll();
|
||||
mesh2.update();
|
||||
io_service.poll();
|
||||
mesh3.update();
|
||||
io_service.poll();
|
||||
mesh4.update();
|
||||
io_service.poll();
|
||||
mesh5.update();
|
||||
io_service.poll();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// Looped network, should break up so it can reform
|
||||
REQUIRE(layout::size(mesh1.asNodeTree()) < 5);
|
||||
REQUIRE(layout::size(mesh2.asNodeTree()) < 5);
|
||||
REQUIRE(layout::size(mesh3.asNodeTree()) < 5);
|
||||
REQUIRE(layout::size(mesh4.asNodeTree()) < 5);
|
||||
REQUIRE(layout::size(mesh5.asNodeTree()) < 5);
|
||||
|
||||
mesh1.stop();
|
||||
mesh2.stop();
|
||||
mesh3.stop();
|
||||
mesh4.stop();
|
||||
mesh5.stop();
|
||||
}
|
||||
|
||||
SCENARIO("Disconnects are detected and forwarded") {
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
auto dim = runif(10, 15);
|
||||
Nodes n(&scheduler, dim, io_service);
|
||||
|
||||
// Dummy task. This can catch mistaken use of the scheduler
|
||||
Task dummyT;
|
||||
int y = 0;
|
||||
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
|
||||
scheduler.addTask(dummyT);
|
||||
dummyT.enable();
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
for (auto &&node : n.nodes) {
|
||||
REQUIRE(layout::size(node->asNodeTree()) == dim);
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
n.nodes[5]->onChangedConnections([&x]() { ++x; });
|
||||
|
||||
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
|
||||
|
||||
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
|
||||
|
||||
auto no = n.nodes[5]->subs.size();
|
||||
REQUIRE(no > 0);
|
||||
|
||||
auto ptr = (*n.nodes[5]->subs.begin());
|
||||
|
||||
(*n.nodes[5]->subs.begin())->close();
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
REQUIRE(n.nodes[5]->subs.size() == no - 1);
|
||||
REQUIRE(ptr.use_count() == 1);
|
||||
ptr = NULL;
|
||||
|
||||
REQUIRE(x == 3);
|
||||
|
||||
for (auto &&node : n.nodes) {
|
||||
REQUIRE(layout::size(node->asNodeTree()) < dim);
|
||||
}
|
||||
|
||||
n.stop();
|
||||
REQUIRE(y > 0);
|
||||
}
|
||||
|
||||
SCENARIO("Disconnects don't lead to crashes") {
|
||||
using namespace logger;
|
||||
Log.setLogLevel(ERROR);
|
||||
|
||||
Scheduler scheduler;
|
||||
boost::asio::io_service io_service;
|
||||
auto dim = runif(10, 15);
|
||||
Nodes n(&scheduler, dim, io_service);
|
||||
|
||||
// Dummy task. This can catch mistaken use of the scheduler
|
||||
Task dummyT;
|
||||
int y = 0;
|
||||
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
|
||||
scheduler.addTask(dummyT);
|
||||
dummyT.enable();
|
||||
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
for (auto &&node : n.nodes) {
|
||||
REQUIRE(layout::size(node->asNodeTree()) == dim);
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
n.nodes[5]->onChangedConnections([&x]() { ++x; });
|
||||
|
||||
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
|
||||
|
||||
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
|
||||
|
||||
auto no = n.nodes[5]->subs.size();
|
||||
REQUIRE(no > 0);
|
||||
|
||||
auto ptr = (*n.nodes[5]->subs.begin());
|
||||
|
||||
(*n.nodes[5]->subs.begin())->close();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
io_service.poll();
|
||||
}
|
||||
n.update();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
io_service.poll();
|
||||
}
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
n.update();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
REQUIRE(n.nodes[5]->subs.size() == no - 1);
|
||||
REQUIRE(ptr.use_count() == 1);
|
||||
ptr = NULL;
|
||||
|
||||
REQUIRE(x == 3);
|
||||
|
||||
for (auto &&node : n.nodes) {
|
||||
REQUIRE(layout::size(node->asNodeTree()) < dim);
|
||||
}
|
||||
|
||||
n.stop();
|
||||
REQUIRE(y > 0);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Wrapper file, which is used to test on PC hardware
|
||||
*/
|
||||
#ifndef ARDUINO_WRAP_H
|
||||
#define ARDUINO_WRAP_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define F(string_literal) string_literal
|
||||
#define ARDUINO_ARCH_ESP8266
|
||||
#define PAINLESSMESH_BOOST
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL 0
|
||||
#endif
|
||||
|
||||
inline unsigned long millis() {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
long long milliseconds =
|
||||
te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
|
||||
// printf("milliseconds: %lld\n", milliseconds);
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
inline unsigned long micros() {
|
||||
struct timeval te;
|
||||
gettimeofday(&te, NULL); // get current time
|
||||
long long milliseconds = te.tv_sec * 1000000LL + te.tv_usec;
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
inline void delay(int i) { usleep(i); }
|
||||
|
||||
inline void yield() {}
|
||||
|
||||
struct IPAddress {
|
||||
IPAddress() {}
|
||||
IPAddress(int, int, int, int) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the configution file.
|
||||
**/
|
||||
|
||||
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
#define _PAINLESS_MESH_CONFIGURATION_HPP_
|
||||
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
#define _TASK_STD_FUNCTION
|
||||
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
|
||||
#define ICACHE_FLASH_ATTR
|
||||
|
||||
#define PAINLESSMESH_ENABLE_STD_STRING
|
||||
|
||||
// Enable OTA support
|
||||
#define PAINLESSMESH_ENABLE_OTA
|
||||
|
||||
#define NODE_TIMEOUT 5 * TASK_SECOND
|
||||
|
||||
typedef std::string TSTRING;
|
||||
|
||||
#ifdef ESP32
|
||||
#define MAX_CONN 10
|
||||
#else
|
||||
#define MAX_CONN 4
|
||||
#endif // DEBUG
|
||||
|
||||
#include "fake_asynctcp.hpp"
|
||||
#include "fake_serial.hpp"
|
||||
|
||||
extern WiFiClass WiFi;
|
||||
extern ESPClass ESP;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessmesh/base64.hpp"
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
SCENARIO("Base64 encoding can succesfully be decoded") {
|
||||
using namespace painlessmesh;
|
||||
auto bindata = randomString(100);
|
||||
auto enc = base64::encode(bindata);
|
||||
auto dec = base64::decode(enc);
|
||||
REQUIRE(dec.length() > 0);
|
||||
REQUIRE(dec == bindata);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
#include "ArduinoJson.h"
|
||||
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
#undef PAINLESSMESH_ENABLE_ARDUINO_STRING
|
||||
#define PAINLESSMESH_ENABLE_STD_STRING
|
||||
typedef std::string TSTRING;
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/buffer.hpp"
|
||||
|
||||
using namespace painlessmesh::buffer;
|
||||
|
||||
SCENARIO("ReceiveBuffer receives strings and needs to process them") {
|
||||
temp_buffer_t tmp_buffer;
|
||||
char cstring[3 * tmp_buffer.length];
|
||||
ReceiveBuffer<std::string> rBuffer = ReceiveBuffer<std::string>();
|
||||
|
||||
GIVEN("A random string of short length pushed to the received buffer") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
auto length = runif(10, tmp_buffer.length - 10);
|
||||
randomCString(cstring, length);
|
||||
// Note we need to send a \0 to know this is the end
|
||||
rBuffer.push(cstring, length + 1, tmp_buffer);
|
||||
THEN("It gets copied to the front of the buffer") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
REQUIRE(rBuffer.front() == std::string(cstring));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A random string of long length pushed to the received buffer") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
randomCString(cstring, length);
|
||||
// Note we need to send a \0 to know this is the end
|
||||
rBuffer.push(cstring, length + 1, tmp_buffer);
|
||||
THEN("It gets copied to the front of the buffer") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
REQUIRE(rBuffer.front() == std::string(cstring));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A random string we can push it in multiple parts") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
size_t part_len = length / 2;
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, part_len, tmp_buffer);
|
||||
THEN("The first part doesn't get copied to the front of the buffer") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
}
|
||||
|
||||
auto data_ptr = cstring + sizeof(char) * part_len;
|
||||
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
|
||||
THEN(
|
||||
"When getting the second part the whole thing gets copied to the front "
|
||||
"of the buffer") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
REQUIRE(rBuffer.front() == std::string(cstring));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("ReceiveBuffer can receive multiple messages and hold them all") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
size_t part_len = length / 2;
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, part_len, tmp_buffer);
|
||||
auto data_ptr = cstring + sizeof(char) * part_len;
|
||||
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
|
||||
}
|
||||
THEN(
|
||||
"When getting the second part the whole thing gets copied to the front "
|
||||
"of the buffer") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
rBuffer.pop_front();
|
||||
}
|
||||
REQUIRE(rBuffer.empty());
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN(
|
||||
"ReceiveBuffer can receive multiple messages in one char string "
|
||||
"(separated by \0") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
auto length = runif(10, tmp_buffer.length - 10);
|
||||
randomCString(cstring, length);
|
||||
auto data_ptr = cstring + sizeof(char) * (length + 1);
|
||||
auto length2 = runif(10, tmp_buffer.length - 10);
|
||||
randomCString(data_ptr, length2);
|
||||
|
||||
// Note we need to send a \0 to know this is the end
|
||||
rBuffer.push(cstring, length + length2 + 2, tmp_buffer);
|
||||
THEN("We have both strings in the buffer") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
REQUIRE(rBuffer.front() == std::string(cstring));
|
||||
rBuffer.pop_front();
|
||||
REQUIRE(!rBuffer.empty());
|
||||
REQUIRE(rBuffer.front() == std::string(data_ptr));
|
||||
rBuffer.pop_front();
|
||||
REQUIRE(rBuffer.empty());
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN(
|
||||
"ReceiveBuffer has copied the message we can overwrite the previous "
|
||||
"cstring and buffer without affecting the outcome") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
cstring[0] = 'B';
|
||||
cstring[1] = 'l';
|
||||
cstring[2] = 'a';
|
||||
cstring[3] = 'a';
|
||||
cstring[4] = 't';
|
||||
cstring[5] = '\0';
|
||||
rBuffer.push(cstring, 3, tmp_buffer);
|
||||
cstring[0] = 'r';
|
||||
cstring[1] = 'n';
|
||||
cstring[2] = 'd';
|
||||
randomCString(tmp_buffer.buffer, tmp_buffer.length);
|
||||
auto data_ptr = cstring + sizeof(char) * 3;
|
||||
rBuffer.push(data_ptr, 3, tmp_buffer);
|
||||
THEN("We still have the correct result") {
|
||||
REQUIRE(rBuffer.front() == std::string("Blaat"));
|
||||
REQUIRE(std::string(cstring) != std::string("Blaat"));
|
||||
REQUIRE(std::string(tmp_buffer.buffer, 6) != std::string("Blaat"));
|
||||
REQUIRE(std::string(tmp_buffer.buffer, 5) != std::string("Blaat"));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A buffer with multiple messages") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
size_t part_len = length / 2;
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, part_len, tmp_buffer);
|
||||
auto data_ptr = cstring + sizeof(char) * part_len;
|
||||
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
|
||||
}
|
||||
THEN("We can clear it") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
rBuffer.clear();
|
||||
REQUIRE(rBuffer.empty());
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A buffer with a half written message") {
|
||||
REQUIRE(rBuffer.empty());
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
size_t part_len = length / 2;
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, part_len, tmp_buffer);
|
||||
auto data_ptr = cstring + sizeof(char) * part_len;
|
||||
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
|
||||
}
|
||||
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
|
||||
size_t part_len = length / 2;
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, part_len, tmp_buffer);
|
||||
|
||||
THEN("We can clear removes it") {
|
||||
REQUIRE(!rBuffer.empty());
|
||||
rBuffer.clear();
|
||||
REQUIRE(rBuffer.empty());
|
||||
}
|
||||
rBuffer.clear();
|
||||
randomCString(cstring, length);
|
||||
rBuffer.push(cstring, length + 1, tmp_buffer);
|
||||
THEN("Reusing it works correctly") {
|
||||
REQUIRE(rBuffer.front() == std::string(cstring));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("SentBuffer receives strings and can be read in parts") {
|
||||
temp_buffer_t tmp_buffer;
|
||||
SentBuffer<std::string> sBuffer = SentBuffer<std::string>();
|
||||
GIVEN("A SentBuffer and a string") {
|
||||
auto length = runif(0, tmp_buffer.length - 10);
|
||||
auto msg = randomString(length);
|
||||
THEN("We can pass strings to it") {
|
||||
REQUIRE(sBuffer.empty());
|
||||
sBuffer.push(msg);
|
||||
REQUIRE(!sBuffer.empty());
|
||||
}
|
||||
THEN("We can pass it and read it back") {
|
||||
REQUIRE(sBuffer.empty());
|
||||
sBuffer.push(msg);
|
||||
REQUIRE(!sBuffer.empty());
|
||||
auto rlength = sBuffer.requestLength(2 * length);
|
||||
REQUIRE(rlength <= 2 * length);
|
||||
sBuffer.read(rlength, tmp_buffer);
|
||||
if (rlength == msg.length() + 1)
|
||||
REQUIRE(std::string(tmp_buffer.buffer, msg.length()) == msg);
|
||||
|
||||
// Test free read as well
|
||||
sBuffer.freeRead();
|
||||
if (rlength == msg.length() + 1) REQUIRE(sBuffer.empty());
|
||||
}
|
||||
}
|
||||
|
||||
// We can read in multiple parts
|
||||
GIVEN("A long string passed to the SentBuffer") {
|
||||
size_t length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length - 10);
|
||||
auto msg = randomString(length);
|
||||
sBuffer.push(msg);
|
||||
char cstring[length + 1];
|
||||
auto data_ptr = cstring;
|
||||
THEN("We can read it in multiple parts") {
|
||||
while (!sBuffer.empty()) {
|
||||
auto rlength = sBuffer.requestLength(tmp_buffer.length);
|
||||
sBuffer.read(rlength, tmp_buffer);
|
||||
memcpy(data_ptr, tmp_buffer.buffer, rlength);
|
||||
data_ptr += rlength * sizeof(char);
|
||||
sBuffer.freeRead();
|
||||
}
|
||||
REQUIRE(std::string(cstring) == msg);
|
||||
}
|
||||
|
||||
THEN("We can use direct access to read it in multiple parts") {
|
||||
while (!sBuffer.empty()) {
|
||||
auto rlength = sBuffer.requestLength(tmp_buffer.length);
|
||||
auto ptr = sBuffer.readPtr(rlength);
|
||||
memcpy(data_ptr, ptr, rlength);
|
||||
data_ptr += rlength * sizeof(char);
|
||||
sBuffer.freeRead();
|
||||
}
|
||||
REQUIRE(std::string(cstring) == msg);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN(
|
||||
"We have read a message halfway, priority messages can safely be added") {
|
||||
size_t length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length - 10);
|
||||
auto msg1 = randomString(length);
|
||||
auto msg2 = randomString(length);
|
||||
auto msgH = randomString(length);
|
||||
sBuffer.push(msg1);
|
||||
char cstring[3 * length + 3];
|
||||
auto data_ptr = cstring;
|
||||
THEN("We can read it in multiple parts") {
|
||||
auto rlength = sBuffer.requestLength(tmp_buffer.length);
|
||||
|
||||
// Read first message halfway
|
||||
sBuffer.read(rlength, tmp_buffer);
|
||||
memcpy(data_ptr, tmp_buffer.buffer, rlength);
|
||||
data_ptr += rlength * sizeof(char);
|
||||
sBuffer.freeRead();
|
||||
sBuffer.push(msg2);
|
||||
sBuffer.push(msgH, true);
|
||||
|
||||
// Read the rest of the messages
|
||||
while (!sBuffer.empty()) {
|
||||
rlength = sBuffer.requestLength(tmp_buffer.length);
|
||||
sBuffer.read(rlength, tmp_buffer);
|
||||
memcpy(data_ptr, tmp_buffer.buffer, rlength);
|
||||
data_ptr += rlength * sizeof(char);
|
||||
sBuffer.freeRead();
|
||||
}
|
||||
REQUIRE(std::string(cstring) == msg1);
|
||||
REQUIRE(std::string(cstring + length + 1) == msgH);
|
||||
REQUIRE(std::string(cstring + 2 * (length + 1)) == msg2);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A SentBuffer with a message in it") {
|
||||
auto length = runif(0, tmp_buffer.length - 10);
|
||||
auto msg = randomString(length);
|
||||
sBuffer.push(msg);
|
||||
|
||||
REQUIRE(!sBuffer.empty());
|
||||
THEN("Clear will empty it") {
|
||||
sBuffer.clear();
|
||||
REQUIRE(sBuffer.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/callback.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
logger::LogClass Log;
|
||||
|
||||
SCENARIO("CallbackMap should hold multiple callbacks by ID") {
|
||||
GIVEN("A callback map with added callbacks") {
|
||||
auto cbl = callback::PackageCallbackList<int>();
|
||||
|
||||
auto i = 0;
|
||||
auto j = 0;
|
||||
|
||||
cbl.onPackage(1, [&i](int z) { ++i; });
|
||||
cbl.onPackage(1, [&j](int z) { ++j; });
|
||||
|
||||
WHEN("We call execute") {
|
||||
auto cnt = cbl.execute(1, 0);
|
||||
REQUIRE(cnt == 2);
|
||||
THEN("The callbacks are called") {
|
||||
REQUIRE(i == 1);
|
||||
REQUIRE(j == 1);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("We call execute on another event") {
|
||||
auto cnt = cbl.execute(2, 0);
|
||||
REQUIRE(cnt == 0);
|
||||
THEN("The callbacks are not called") {
|
||||
REQUIRE(i == 0);
|
||||
REQUIRE(j == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
#include "ArduinoJson.h"
|
||||
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
#undef PAINLESSMESH_ENABLE_ARDUINO_STRING
|
||||
#define PAINLESSMESH_ENABLE_STD_STRING
|
||||
typedef std::string TSTRING;
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/layout.hpp"
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
SCENARIO("isRoot returns true if the top level Node is the root of the mesh") {
|
||||
GIVEN("A nodeTree with root as a top node") {
|
||||
std::string rootJson =
|
||||
"{\"type\":6,\"root\":true,\"dest\":2428398258,\"from\":3907768579,"
|
||||
"\"subs\":[{"
|
||||
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
|
||||
"1895675348}]}]}";
|
||||
auto variant = protocol::Variant(rootJson);
|
||||
auto tree1 = variant.to<protocol::NodeTree>();
|
||||
THEN("isRoot returns true") { REQUIRE(layout::isRoot(tree1)); }
|
||||
}
|
||||
GIVEN("A nodeTree without a root as a top node") {
|
||||
std::string jsonTree1 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant1 = protocol::Variant(jsonTree1);
|
||||
auto tree1 = variant1.to<protocol::NodeTree>();
|
||||
std::string jsonTree2 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant2 = protocol::Variant(jsonTree2);
|
||||
auto tree2 = variant2.to<protocol::NodeTree>();
|
||||
THEN("isRoot returns false") {
|
||||
REQUIRE(!layout::isRoot(tree1));
|
||||
REQUIRE(!layout::isRoot(tree2));
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A random tree with a root at top level") {
|
||||
auto tree1 = createNodeTree(runif(1, 255), 0);
|
||||
THEN("isRoot returns true") { REQUIRE(layout::isRoot(tree1)); }
|
||||
}
|
||||
|
||||
GIVEN("A random tree with no root at top level") {
|
||||
auto noNodes = runif(2, 255);
|
||||
auto tree1 = createNodeTree(noNodes, runif(1, noNodes - 1));
|
||||
auto tree2 = createNodeTree(runif(1, 255), -1);
|
||||
THEN("isRoot returns false") {
|
||||
REQUIRE(!layout::isRoot(tree1));
|
||||
REQUIRE(!layout::isRoot(tree2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("isRooted returns true if any node in the mesh is the root node") {
|
||||
GIVEN("A nodeTree with root as a top node") {
|
||||
std::string rootJson =
|
||||
"{\"type\":6,\"root\":true,\"dest\":2428398258,\"from\":3907768579,"
|
||||
"\"subs\":[{"
|
||||
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
|
||||
"1895675348}]}]}";
|
||||
auto variant = protocol::Variant(rootJson);
|
||||
auto tree1 = variant.to<protocol::NodeTree>();
|
||||
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
|
||||
}
|
||||
GIVEN("A nodeTree with a root some where else") {
|
||||
std::string jsonTree1 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant1 = protocol::Variant(jsonTree1);
|
||||
auto tree1 = variant1.to<protocol::NodeTree>();
|
||||
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
|
||||
}
|
||||
|
||||
GIVEN("A nodeTree without a root any where else") {
|
||||
std::string jsonTree2 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant2 = protocol::Variant(jsonTree2);
|
||||
auto tree2 = variant2.to<protocol::NodeTree>();
|
||||
THEN("isRooted returns false") { REQUIRE(!layout::isRoot(tree2)); }
|
||||
}
|
||||
|
||||
GIVEN("A random tree with a root") {
|
||||
auto noNodes = runif(1, 255);
|
||||
auto tree1 = createNodeTree(noNodes, runif(0, noNodes - 1));
|
||||
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
|
||||
}
|
||||
|
||||
GIVEN("A random tree without a root") {
|
||||
auto tree1 = createNodeTree(runif(1, 255), -1);
|
||||
THEN("isRooted returns false") { REQUIRE(!layout::isRooted(tree1)); }
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("We can get the size of the mesh") {
|
||||
GIVEN("A tree with a set size") {
|
||||
std::string jsonTree =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant = protocol::Variant(jsonTree);
|
||||
auto tree = variant.to<protocol::NodeTree>();
|
||||
THEN("Size returns the correct size") { REQUIRE(layout::size(tree) == 4); }
|
||||
}
|
||||
GIVEN("A random tree with a set size") {
|
||||
auto noNodes = runif(1, 255);
|
||||
auto tree = createNodeTree(noNodes, runif(-1, noNodes - 1));
|
||||
THEN("Size returns the correct size") {
|
||||
REQUIRE(layout::size(tree) == noNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("We can confirm whether a mesh contains specific nodes") {
|
||||
GIVEN("A tree with known nodes") {
|
||||
std::string jsonTree =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant = protocol::Variant(jsonTree);
|
||||
auto tree = variant.to<protocol::NodeTree>();
|
||||
THEN(
|
||||
"contains should return the true when it contains a node, false "
|
||||
"otherwise") {
|
||||
REQUIRE(layout::contains(tree, 1895675348));
|
||||
REQUIRE(layout::contains(tree, 3907768579));
|
||||
REQUIRE(layout::contains(tree, 3959373838));
|
||||
REQUIRE(!layout::contains(tree, 0));
|
||||
REQUIRE(!layout::contains(tree, 2428398258));
|
||||
REQUIRE(!layout::contains(tree, 3107768579));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("A layout neighbour knows when to update its sub") {
|
||||
GIVEN("A Neighbour") {
|
||||
std::string jsonTree =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
auto variant = protocol::Variant(jsonTree);
|
||||
auto tree = variant.to<layout::Neighbour>();
|
||||
// auto neighbour = std::interpret_cast<layout::Neighbour*>(pTree);
|
||||
auto neighbour = tree;
|
||||
THEN("When passed the same tree updateSubs() will return false") {
|
||||
REQUIRE(!neighbour.updateSubs(tree));
|
||||
}
|
||||
|
||||
auto tree1 = createNodeTree(runif(2, 5), -1);
|
||||
tree1.nodeId = neighbour.nodeId;
|
||||
THEN("When passing a different tree it will get updated") {
|
||||
REQUIRE(neighbour.updateSubs(tree1));
|
||||
REQUIRE(tree1 == neighbour);
|
||||
}
|
||||
|
||||
THEN("When current nodeId is zero then updateSubs() will return true") {
|
||||
neighbour.nodeId = 0;
|
||||
REQUIRE(neighbour.updateSubs(tree));
|
||||
REQUIRE(neighbour == tree);
|
||||
REQUIRE(neighbour.nodeId == tree.nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
using namespace painlessmesh::logger;
|
||||
|
||||
LogClass Log;
|
||||
|
||||
SCENARIO("We can log things") {
|
||||
Log.setLogLevel(ERROR | DEBUG | COMMUNICATION);
|
||||
Log(ERROR, "We should see the next %u lines\n", 3);
|
||||
Log(DEBUG, "We should see the next %u lines\n", 2);
|
||||
Log(COMMUNICATION, "We should see the next %u lines\n", 1);
|
||||
Log(ERROR, "But not the next one\n");
|
||||
Log(S_TIME, "This should not be showing\n");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/ntp.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
logger::LogClass Log;
|
||||
@@ -0,0 +1,174 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/plugin.hpp"
|
||||
#include "plugin/performance.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
logger::LogClass Log;
|
||||
|
||||
class CustomPackage : public plugin::SinglePackage {
|
||||
public:
|
||||
double sensor = 1.0;
|
||||
|
||||
CustomPackage() : SinglePackage(20) {}
|
||||
|
||||
CustomPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
|
||||
sensor = jsonObj["sensor"];
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = SinglePackage::addTo(std::move(jsonObj));
|
||||
jsonObj["sensor"] = sensor;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 1); }
|
||||
};
|
||||
|
||||
class BCustomPackage : public plugin::BroadcastPackage {
|
||||
public:
|
||||
double sensor = 1.0;
|
||||
|
||||
BCustomPackage() : BroadcastPackage(21) {}
|
||||
|
||||
BCustomPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
|
||||
sensor = jsonObj["sensor"];
|
||||
}
|
||||
|
||||
JsonObject addTo(JsonObject&& jsonObj) const {
|
||||
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
|
||||
jsonObj["sensor"] = sensor;
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 1); }
|
||||
};
|
||||
|
||||
class MockConnection : public layout::Neighbour {
|
||||
public:
|
||||
bool addMessage(TSTRING msg) { return true; }
|
||||
};
|
||||
|
||||
SCENARIO("We can send a custom package") {
|
||||
GIVEN("A package") {
|
||||
auto pkg = CustomPackage();
|
||||
pkg.from = 1;
|
||||
pkg.dest = 2;
|
||||
pkg.sensor = 0.5;
|
||||
REQUIRE(pkg.routing == router::SINGLE);
|
||||
REQUIRE(pkg.type == 20);
|
||||
WHEN("Converting it to and from Variant") {
|
||||
auto var = protocol::Variant(&pkg);
|
||||
auto pkg2 = var.to<CustomPackage>();
|
||||
THEN("Should result in the same values") {
|
||||
REQUIRE(pkg2.sensor == pkg.sensor);
|
||||
REQUIRE(pkg2.from == pkg.from);
|
||||
REQUIRE(pkg2.dest == pkg.dest);
|
||||
REQUIRE(pkg2.routing == pkg.routing);
|
||||
REQUIRE(pkg2.type == pkg.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A broadcast package") {
|
||||
auto pkg = BCustomPackage();
|
||||
pkg.from = 1;
|
||||
pkg.sensor = 0.5;
|
||||
REQUIRE(pkg.routing == router::BROADCAST);
|
||||
REQUIRE(pkg.type == 21);
|
||||
WHEN("Converting it to and from Variant") {
|
||||
auto var = protocol::Variant(&pkg);
|
||||
auto pkg2 = var.to<CustomPackage>();
|
||||
THEN("Should result in the same values") {
|
||||
REQUIRE(pkg2.sensor == pkg.sensor);
|
||||
REQUIRE(pkg2.from == pkg.from);
|
||||
REQUIRE(pkg2.routing == pkg.routing);
|
||||
REQUIRE(pkg2.type == pkg.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A package handler function") {
|
||||
auto handler = plugin::PackageHandler<MockConnection>();
|
||||
auto func = [](protocol::Variant variant) {
|
||||
auto pkg = variant.to<CustomPackage>();
|
||||
REQUIRE(pkg.routing == router::BROADCAST);
|
||||
return false;
|
||||
};
|
||||
THEN("We can pass it to handler") { handler.onPackage(20, func); }
|
||||
}
|
||||
|
||||
GIVEN("A package") {
|
||||
auto handler = plugin::PackageHandler<MockConnection>();
|
||||
auto pkg = CustomPackage();
|
||||
THEN("We can call sendPackage") {
|
||||
auto res = handler.sendPackage(&pkg);
|
||||
REQUIRE(!res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("We can add tasks to the taskscheduler") {
|
||||
GIVEN("A couple of tasks added") {
|
||||
Scheduler mScheduler;
|
||||
auto handler = plugin::PackageHandler<MockConnection>();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
auto task1 = handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
|
||||
auto task2 = handler.addTask(mScheduler, 0, 3, [&j]() { ++j; });
|
||||
auto task3 = handler.addTask(mScheduler, 0, 3, [&k]() { ++k; });
|
||||
auto task4 = handler.addTask(mScheduler, 0, 3, []() {});
|
||||
|
||||
WHEN("Executing the tasks") {
|
||||
THEN("They should be called and automatically removed") {
|
||||
REQUIRE(i == 0);
|
||||
REQUIRE(j == 0);
|
||||
mScheduler.execute();
|
||||
REQUIRE(i == 1);
|
||||
mScheduler.execute();
|
||||
REQUIRE(i == 1);
|
||||
REQUIRE(j == 2);
|
||||
// Still kept in handler, because hasn't been executed 3 times yet
|
||||
task3->disable();
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("We can add anonymous tasks to the taskscheduler") {
|
||||
GIVEN("A couple of tasks added") {
|
||||
Scheduler mScheduler;
|
||||
auto handler = plugin::PackageHandler<MockConnection>();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
|
||||
handler.addTask(mScheduler, 0, 3, [&j]() { ++j; });
|
||||
handler.addTask(mScheduler, 0, 3, [&k]() { ++k; });
|
||||
|
||||
WHEN("Executing the tasks") {
|
||||
THEN("They should be called and automatically removed") {
|
||||
REQUIRE(i == 0);
|
||||
REQUIRE(j == 0);
|
||||
mScheduler.execute();
|
||||
REQUIRE(i == 1);
|
||||
mScheduler.execute();
|
||||
REQUIRE(i == 1);
|
||||
REQUIRE(j == 2);
|
||||
handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
|
||||
mScheduler.execute();
|
||||
REQUIRE(i == 2);
|
||||
handler.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#define ARDUINOJSON_USE_LONG_LONG 1
|
||||
#include "ArduinoJson.h"
|
||||
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
|
||||
typedef std::string TSTRING;
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
|
||||
using namespace painlessmesh::protocol;
|
||||
|
||||
SCENARIO("A variant knows its type", "[Variant][protocol]") {
|
||||
GIVEN("A json string with the type 9 ") {
|
||||
std::string str = "{\"type\": 9}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a Single type") {
|
||||
REQUIRE(variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with the type 8 ") {
|
||||
std::string str = "{\"type\": 8}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a Broadcast type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(variant.is<Broadcast>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with the type 6 ") {
|
||||
std::string str = "{\"type\": 6}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a NodeSyncReply type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(variant.is<NodeSyncReply>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with the type 5 ") {
|
||||
std::string str = "{\"type\": 5}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a NodeSyncRequest type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(variant.is<NodeSyncRequest>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with the type 4 ") {
|
||||
std::string str = "{\"type\": 4}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a TimeSync type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(variant.is<TimeSync>());
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("A json string with the type 3 ") {
|
||||
std::string str = "{\"type\": 3}";
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(str);
|
||||
|
||||
THEN("The variant is a TimeDelay type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(variant.is<TimeDelay>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SCENARIO("A variant can take a packageinterface", "[Variant][protocol]") {
|
||||
GIVEN("A Single package") {
|
||||
auto pkg = createSingle();
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(&pkg);
|
||||
THEN("The variant is a Single type") {
|
||||
REQUIRE(variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
|
||||
THEN("The variant can be converted to a Single") {
|
||||
auto newPkg = variant.to<Single>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.msg == pkg.msg);
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("A variant can take any package", "[Variant][protocol]") {
|
||||
GIVEN("A Single package") {
|
||||
auto pkg = createSingle();
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a Single type") {
|
||||
REQUIRE(variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
|
||||
THEN("The variant can be converted to a Single") {
|
||||
auto newPkg = variant.to<Single>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.msg == pkg.msg);
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A Broadcast package") {
|
||||
auto pkg = createBroadcast(5);
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a Broadcast type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
|
||||
THEN("The variant can be converted to a Broadcast") {
|
||||
auto newPkg = variant.to<Broadcast>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.msg == pkg.msg);
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A NodeSyncReply package") {
|
||||
auto pkg = createNodeSyncReply(15);
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a NodeSyncReply type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
THEN("The variant can be converted to a NodeSyncReply") {
|
||||
auto newPkg = variant.to<NodeSyncReply>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.nodeId == pkg.nodeId);
|
||||
REQUIRE(newPkg.root == pkg.root);
|
||||
REQUIRE(newPkg.subs.size() == pkg.subs.size());
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
REQUIRE(newPkg == pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A NodeSyncReply package of random size") {
|
||||
auto pkg = createNodeSyncReply();
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
|
||||
THEN("The variant throws no error") { REQUIRE(!variant.error); }
|
||||
THEN("The variant is a NodeSyncReply type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
THEN("The variant can be converted to a NodeSyncReply") {
|
||||
auto newPkg = variant.to<NodeSyncReply>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.nodeId == pkg.nodeId);
|
||||
REQUIRE(newPkg.root == pkg.root);
|
||||
REQUIRE(newPkg.subs.size() == pkg.subs.size());
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
REQUIRE(newPkg == pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A NodeSyncRequest package") {
|
||||
auto pkg = createNodeSyncRequest(5);
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a NodeSyncRequest type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
THEN("The variant can be converted to a NodeSyncRequest") {
|
||||
auto newPkg = variant.to<NodeSyncRequest>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.nodeId == pkg.nodeId);
|
||||
REQUIRE(newPkg.root == pkg.root);
|
||||
REQUIRE(newPkg.subs.size() == pkg.subs.size());
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
REQUIRE(newPkg == pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A TimeSync package") {
|
||||
auto pkg = createTimeSync();
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a TimeSync type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(variant.is<TimeSync>());
|
||||
REQUIRE(!variant.is<TimeDelay>());
|
||||
}
|
||||
|
||||
THEN("The variant can be converted to a TimeSync") {
|
||||
auto newPkg = variant.to<TimeSync>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
REQUIRE(newPkg.msg.type == pkg.msg.type);
|
||||
REQUIRE(newPkg.msg.t0 == pkg.msg.t0);
|
||||
REQUIRE(newPkg.msg.t1 == pkg.msg.t1);
|
||||
REQUIRE(newPkg.msg.t2 == pkg.msg.t2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A TimeDelay package") {
|
||||
auto pkg = createTimeDelay();
|
||||
WHEN("Passed to a Variant") {
|
||||
auto variant = Variant(pkg);
|
||||
THEN("The variant is a TimeDelay type") {
|
||||
REQUIRE(!variant.is<Single>());
|
||||
REQUIRE(!variant.is<Broadcast>());
|
||||
REQUIRE(!variant.is<NodeSyncReply>());
|
||||
REQUIRE(!variant.is<NodeSyncRequest>());
|
||||
REQUIRE(!variant.is<TimeSync>());
|
||||
REQUIRE(variant.is<TimeDelay>());
|
||||
}
|
||||
THEN("The variant can be converted to a TimeDelay") {
|
||||
auto newPkg = variant.to<TimeDelay>();
|
||||
REQUIRE(newPkg.dest == pkg.dest);
|
||||
REQUIRE(newPkg.from == pkg.from);
|
||||
REQUIRE(newPkg.type == pkg.type);
|
||||
REQUIRE(newPkg.msg.type == pkg.msg.type);
|
||||
REQUIRE(newPkg.msg.t0 == pkg.msg.t0);
|
||||
REQUIRE(newPkg.msg.t1 == pkg.msg.t1);
|
||||
REQUIRE(newPkg.msg.t2 == pkg.msg.t2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("NodeSyncReply is backwards compatible", "[Variant][protocol]") {
|
||||
GIVEN("A json string without a base nodeId") {
|
||||
std::string old =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"subs\":[{"
|
||||
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
|
||||
"1895675348,\"root\":true}]}]}";
|
||||
std::string withId =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
WHEN("Converted to a NodeSyncReply") {
|
||||
auto variant = Variant(old);
|
||||
auto nsr = variant.to<NodeSyncReply>();
|
||||
auto variantId = Variant(withId);
|
||||
auto nsrId = variantId.to<NodeSyncReply>();
|
||||
THEN("NodeId is set to from") {
|
||||
REQUIRE(nsr.from == nsr.nodeId);
|
||||
REQUIRE(nsr == nsrId);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Converted to a NodeTree") {
|
||||
auto variant = Variant(old);
|
||||
auto ns = variant.to<NodeTree>();
|
||||
auto variantId = Variant(withId);
|
||||
auto nsId = variantId.to<NodeTree>();
|
||||
auto variantReply = Variant(withId);
|
||||
auto nsrId = variantId.to<NodeSyncReply>();
|
||||
THEN("NodeId is set to from value") {
|
||||
REQUIRE(nsrId.from == ns.nodeId);
|
||||
REQUIRE(nsrId.nodeId == nsId.nodeId);
|
||||
REQUIRE(nsrId.subs == ns.subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with root explicitly set to false") {
|
||||
std::string old =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
std::string withId =
|
||||
"{\"type\":6,\"dest\":2428398258,\"root\":false,\"from\":3907768579,"
|
||||
"\"nodeId\":3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{"
|
||||
"\"nodeId\":416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
WHEN("Converted to a NodeSyncReply") {
|
||||
auto variant = Variant(old);
|
||||
auto nsr = variant.to<NodeSyncReply>();
|
||||
auto variantId = Variant(withId);
|
||||
auto nsrId = variantId.to<NodeSyncReply>();
|
||||
THEN("NodeId is set to from") { REQUIRE(nsr == nsrId); }
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A json string with subs explicitly set to empty") {
|
||||
std::string old =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true,\"subs\":[]}]}]}";
|
||||
std::string withId =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
|
||||
WHEN("Converted to a NodeSyncReply") {
|
||||
auto variant = Variant(old);
|
||||
auto nsr = variant.to<NodeSyncReply>();
|
||||
auto variantId = Variant(withId);
|
||||
auto nsrId = variantId.to<NodeSyncReply>();
|
||||
THEN("NodeId is set to from") { REQUIRE(nsr == nsrId); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("NodeSyncReply supports the == operator", "[Variant][protocol]") {
|
||||
GIVEN("Different NodeSyncReplies") {
|
||||
auto pkg1 = createNodeSyncReply(5);
|
||||
auto pkg2 = createNodeSyncReply(5);
|
||||
|
||||
// Same subs different base
|
||||
auto pkg3 = createNodeSyncReply(5);
|
||||
auto pkg4 = createNodeSyncReply(5);
|
||||
pkg4.subs = pkg3.subs;
|
||||
|
||||
// Same base different subs
|
||||
auto pkg5 = pkg4;
|
||||
pkg5.subs = pkg1.subs;
|
||||
THEN("They are not equal") {
|
||||
REQUIRE(pkg1 != pkg2);
|
||||
REQUIRE(pkg2 == pkg2);
|
||||
REQUIRE(!(pkg2 != pkg2));
|
||||
REQUIRE(!(pkg1 != pkg1));
|
||||
|
||||
REQUIRE(pkg3 != pkg4);
|
||||
REQUIRE(pkg3.subs == pkg4.subs);
|
||||
|
||||
REQUIRE(pkg5 != pkg4);
|
||||
REQUIRE(pkg5.subs != pkg4.subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("A variant can printTo a package", "[Variant][protocol]") {
|
||||
GIVEN("A NodeSyncReply package printed to a string using Variant") {
|
||||
auto pkg = createNodeSyncReply(5);
|
||||
std::string str;
|
||||
auto variant = Variant(pkg);
|
||||
variant.printTo(str);
|
||||
THEN("It can be converted back into an identical pkg") {
|
||||
auto variant = Variant(str);
|
||||
auto pkg2 = variant.to<NodeSyncReply>();
|
||||
REQUIRE(pkg2 == pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("The Variant type properly carries over errors",
|
||||
"[Variant][protocol][error]") {
|
||||
GIVEN("A large and small NodeSyncReply pkg") {
|
||||
auto large_pkg = createNodeSyncReply(100);
|
||||
auto large_variant = Variant(large_pkg);
|
||||
std::string large_json;
|
||||
large_variant.printTo(large_json);
|
||||
|
||||
auto small_pkg = createNodeSyncReply(5);
|
||||
auto small_variant = Variant(small_pkg);
|
||||
std::string small_json;
|
||||
small_variant.printTo(small_json);
|
||||
|
||||
THEN("It carries over the ArduinoJson error") {
|
||||
auto large_var = Variant(large_json, 1024);
|
||||
REQUIRE(large_var.error);
|
||||
auto small_var = Variant(small_json, 1024);
|
||||
REQUIRE(!small_var.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO(
|
||||
"The construction of a Time package automatically sets the correct time "
|
||||
"sync type",
|
||||
"[protocol]") {
|
||||
GIVEN("Calling the constructor with no time") {
|
||||
auto pkg1 = TimeSync(10, 11);
|
||||
THEN("The time type is TIME_SYNC_REQUEST") {
|
||||
REQUIRE(pkg1.msg.type == TIME_SYNC_REQUEST);
|
||||
}
|
||||
}
|
||||
GIVEN("Calling the constructor with one time") {
|
||||
auto pkg1 = TimeSync(10, 11, 12);
|
||||
THEN("The time type is TIME_REQUEST") {
|
||||
REQUIRE(pkg1.msg.type == TIME_REQUEST);
|
||||
REQUIRE(pkg1.msg.t0 == 12);
|
||||
}
|
||||
}
|
||||
GIVEN("Calling the constructor with two or three times") {
|
||||
auto pkg2 = TimeSync(10, 11, 12, 13);
|
||||
auto pkg3 = TimeSync(10, 11, 12, 13, 14);
|
||||
THEN("The time type is TIME_REPLY") {
|
||||
REQUIRE(pkg2.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg2.msg.t0 == 12);
|
||||
REQUIRE(pkg2.msg.t1 == 13);
|
||||
REQUIRE(pkg3.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg3.msg.t0 == 12);
|
||||
REQUIRE(pkg3.msg.t1 == 13);
|
||||
REQUIRE(pkg3.msg.t2 == 14);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("Calling the constructor with no time") {
|
||||
auto pkg1 = TimeDelay(10, 11);
|
||||
THEN("The time type is TIME_SYNC_REQUEST") {
|
||||
REQUIRE(pkg1.msg.type == TIME_SYNC_REQUEST);
|
||||
}
|
||||
}
|
||||
GIVEN("Calling the constructor with one time") {
|
||||
auto pkg1 = TimeDelay(10, 11, 12);
|
||||
THEN("The time type is TIME_REQUEST") {
|
||||
REQUIRE(pkg1.msg.type == TIME_REQUEST);
|
||||
REQUIRE(pkg1.msg.t0 == 12);
|
||||
}
|
||||
}
|
||||
GIVEN("Calling the constructor with two or three times") {
|
||||
auto pkg2 = TimeDelay(10, 11, 12, 13);
|
||||
auto pkg3 = TimeDelay(10, 11, 12, 13, 14);
|
||||
THEN("The time type is TIME_REPLY") {
|
||||
REQUIRE(pkg2.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg2.msg.t0 == 12);
|
||||
REQUIRE(pkg2.msg.t1 == 13);
|
||||
REQUIRE(pkg3.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg3.msg.t0 == 12);
|
||||
REQUIRE(pkg3.msg.t1 == 13);
|
||||
REQUIRE(pkg3.msg.t2 == 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("We can construct a reply to Time packages", "[protocol]") {
|
||||
GIVEN("A reply to a TIME_SYNC_REQUEST") {
|
||||
auto origPkg1 = TimeSync(10, 11);
|
||||
auto pkg1 = TimeSync(10, 11);
|
||||
pkg1.reply(12);
|
||||
auto origPkg2 = TimeDelay(10, 11);
|
||||
auto pkg2 = TimeDelay(10, 11);
|
||||
pkg2.reply(12);
|
||||
THEN("It will set t0, update type and swap from and dest.") {
|
||||
REQUIRE(pkg1.msg.type == TIME_REQUEST);
|
||||
REQUIRE(pkg2.msg.type == TIME_REQUEST);
|
||||
REQUIRE(pkg1.msg.t0 == 12);
|
||||
REQUIRE(pkg2.msg.t0 == 12);
|
||||
REQUIRE(pkg1.from == origPkg1.dest);
|
||||
REQUIRE(pkg2.from == origPkg2.dest);
|
||||
REQUIRE(pkg1.dest == origPkg1.from);
|
||||
REQUIRE(pkg2.dest == origPkg2.from);
|
||||
REQUIRE(pkg1.type == origPkg1.type);
|
||||
REQUIRE(pkg2.type == origPkg2.type);
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("A reply to a TIME_REQUEST") {
|
||||
auto origPkg1 = TimeSync(10, 11, 12);
|
||||
auto pkg1 = TimeSync(10, 11, 12);
|
||||
pkg1.reply(13, 14);
|
||||
auto origPkg2 = TimeDelay(10, 11, 12);
|
||||
auto pkg2 = TimeDelay(10, 11, 12);
|
||||
pkg2.reply(13, 14);
|
||||
THEN("It will set t1 and t2, update type and swap from and dest.") {
|
||||
REQUIRE(pkg1.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg2.msg.type == TIME_REPLY);
|
||||
REQUIRE(pkg1.msg.t0 == 12);
|
||||
REQUIRE(pkg2.msg.t0 == 12);
|
||||
REQUIRE(pkg1.msg.t1 == 13);
|
||||
REQUIRE(pkg2.msg.t1 == 13);
|
||||
REQUIRE(pkg1.msg.t2 == 14);
|
||||
REQUIRE(pkg2.msg.t2 == 14);
|
||||
REQUIRE(pkg1.from == origPkg1.dest);
|
||||
REQUIRE(pkg2.from == origPkg2.dest);
|
||||
REQUIRE(pkg1.dest == origPkg1.from);
|
||||
REQUIRE(pkg2.dest == origPkg2.from);
|
||||
REQUIRE(pkg1.type == origPkg1.type);
|
||||
REQUIRE(pkg2.type == origPkg2.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Package constructors work as expected", "[protocol]") {
|
||||
GIVEN("A Single package constructed with the constructor") {
|
||||
std::string str = "Blaat";
|
||||
auto pkg = Single(10, 0, str);
|
||||
THEN("Message will be set correctly") { REQUIRE(pkg.msg == "Blaat"); }
|
||||
}
|
||||
GIVEN("A Broadcast package constructed with the constructor") {
|
||||
std::string str = "Blaat";
|
||||
auto pkg = Broadcast(10, 0, str);
|
||||
THEN("Message will be set correctly") {
|
||||
REQUIRE(pkg.msg == "Blaat");
|
||||
REQUIRE(pkg.type == BROADCAST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
#include "painlessmesh/router.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
logger::LogClass Log;
|
||||
|
||||
/*
|
||||
class MockConnection : public protocol::NodeTree {
|
||||
public:
|
||||
void addMessage(TSTRING msg, bool priority = false) { ++cnt; }
|
||||
|
||||
int cnt = 0;
|
||||
};
|
||||
|
||||
SCENARIO("findRoute works as expected with different types of connections") {
|
||||
GIVEN("A layout with Neighbour shared ptrs") {
|
||||
auto layout = layout::Layout<layout::Neighbour>();
|
||||
std::string jsonTree1 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
|
||||
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
|
||||
"416992913},{\"nodeId\":1895675348}]}]}";
|
||||
auto variant1 = protocol::Variant(jsonTree1);
|
||||
auto tree1 =
|
||||
std::make_shared<layout::Neighbour>(variant1.to<layout::Neighbour>());
|
||||
|
||||
std::string jsonTree2 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
|
||||
"3907768580,\"subs\":[{\"nodeId\":3959373839,\"subs\":[{\"nodeId\":"
|
||||
"416992914},{\"nodeId\":1895675349}]}]}";
|
||||
auto variant2 = protocol::Variant(jsonTree2);
|
||||
auto tree2 =
|
||||
std::make_shared<layout::Neighbour>(variant2.to<layout::Neighbour>());
|
||||
|
||||
std::string jsonTree3 =
|
||||
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
|
||||
"3907768581,\"subs\":[{\"nodeId\":3959373840,\"subs\":[{\"nodeId\":"
|
||||
"416992915},{\"nodeId\":1895675350}]}]}";
|
||||
auto variant3 = protocol::Variant(jsonTree3);
|
||||
auto tree3 =
|
||||
std::make_shared<layout::Neighbour>(variant3.to<layout::Neighbour>());
|
||||
|
||||
layout.subs.push_back(tree1);
|
||||
layout.subs.push_back(tree2);
|
||||
layout.subs.push_back(tree3);
|
||||
|
||||
layout.nodeId = runif(1, 1000);
|
||||
|
||||
THEN("findRoute works") {
|
||||
auto rt = router::findRoute<layout::Neighbour>(layout, 1895675350);
|
||||
REQUIRE(rt->nodeId == 3907768581);
|
||||
rt = router::findRoute<layout::Neighbour>(layout, 1895675351);
|
||||
REQUIRE(!rt);
|
||||
}
|
||||
|
||||
THEN("It can be converted to a NodeTree") {
|
||||
auto lay = layout.asNodeTree();
|
||||
auto nt = protocol::NodeTree();
|
||||
nt.nodeId = lay.nodeId;
|
||||
for (auto &&s : lay.subs) {
|
||||
nt.subs.push_back(s);
|
||||
}
|
||||
REQUIRE(nt == lay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("routePackage should route the package correctly") {
|
||||
GIVEN("A CallbackList and layout") {
|
||||
auto cbl = router::CallbackList();
|
||||
auto lay = layout::Layout<MockConnection>();
|
||||
lay.nodeId = 1;
|
||||
lay.subs.push_back(std::make_shared<MockConnection>());
|
||||
lay.subs.back()->nodeId = 2;
|
||||
lay.subs.push_back(std::make_shared<MockConnection>());
|
||||
lay.subs.back()->nodeId = 3;
|
||||
lay.subs.push_back(std::make_shared<MockConnection>());
|
||||
lay.subs.back()->nodeId = 4;
|
||||
|
||||
// void routePackage(Layout<T>, T conn, TSTRING pkg, CallbackMap)
|
||||
WHEN("Passed a package with routing NEIGHBOUR") {
|
||||
auto pkg = createTimeSync();
|
||||
TSTRING str;
|
||||
auto var = protocol::Variant(pkg);
|
||||
REQUIRE(var.routing() == router::NEIGHBOUR);
|
||||
var.printTo(str);
|
||||
// message type NEIGHBOUR should result in a callback
|
||||
}
|
||||
|
||||
// message type BROADCAST should result in a callback and being send on
|
||||
|
||||
// message type SINGLE if destination is other then send otherwise callback
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "catch_utils.hpp"
|
||||
|
||||
WiFiClass WiFi;
|
||||
ESPClass ESP;
|
||||
|
||||
#include "painlessmesh/logger.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
logger::LogClass Log;
|
||||
|
||||
SCENARIO("Fake Async classes behave similar to real ones") {
|
||||
int i = 0;
|
||||
std::string j = "";
|
||||
auto server = AsyncServer();
|
||||
AsyncClient *conn;
|
||||
server.onClient([&conn, &i, &j](void *, AsyncClient *client) {
|
||||
conn = client;
|
||||
conn->onData([&j](void *, AsyncClient *client, void *data,
|
||||
size_t len) { j = std::string((char *)data, len); },
|
||||
NULL);
|
||||
++i;
|
||||
});
|
||||
|
||||
auto client = AsyncClient(&server);
|
||||
|
||||
std::string j2 = "";
|
||||
client.onData([&j2](void *arg, AsyncClient *client, void *data,
|
||||
size_t len) { j2 = std::string((char *)data, len); },
|
||||
NULL);
|
||||
|
||||
client.connect(IPAddress(), 0);
|
||||
|
||||
THEN("server.onConnect is called") { REQUIRE(i == 1); }
|
||||
THEN("I can send data") {
|
||||
client.write("Blaat", 5);
|
||||
REQUIRE(j == "Blaat");
|
||||
|
||||
conn->write("Blaat terug", 11);
|
||||
REQUIRE(j2 == "Blaat terug");
|
||||
}
|
||||
delete conn;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
DSM2_tx implements the serial communication protocol used for operating
|
||||
the RF modules that can be found in many DSM2-compatible transmitters.
|
||||
Copyrigt (C) 2012 Erik Elmore <erik@ironsavior.net>
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "fake_serial.hpp"
|
||||
|
||||
void FakeSerial::begin(unsigned long speed) { return; }
|
||||
|
||||
void FakeSerial::end() { return; }
|
||||
|
||||
size_t FakeSerial::write(const unsigned char buf[], size_t size) {
|
||||
using namespace std;
|
||||
ios_base::fmtflags oldFlags = cout.flags();
|
||||
streamsize oldPrec = cout.precision();
|
||||
char oldFill = cout.fill();
|
||||
|
||||
cout << "Serial::write: ";
|
||||
cout << internal << setfill('0');
|
||||
|
||||
for (unsigned int i = 0; i < size; i++) {
|
||||
cout << setw(2) << hex << (unsigned int)buf[i] << " ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout.flags(oldFlags);
|
||||
cout.precision(oldPrec);
|
||||
cout.fill(oldFill);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void FakeSerial::print(const char* buf) { std::cout << buf; }
|
||||
|
||||
void FakeSerial::println() { std::cout << std::endl; }
|
||||
|
||||
FakeSerial Serial;
|
||||
13287
arduino-cli/libraries/painlessMesh-master/test/include/catch2/catch.hpp
Normal file
13287
arduino-cli/libraries/painlessMesh-master/test/include/catch2/catch.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
||||
#ifndef CATCH_UTILS_H_
|
||||
#define CATCH_UTILS_H_
|
||||
|
||||
/*
|
||||
* Some helper functions to be used in catch based tests
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
|
||||
static std::random_device
|
||||
rd; // Will be used to obtain a seed for the random number engine
|
||||
static std::mt19937 gen(rd());
|
||||
|
||||
uint32_t runif(uint32_t from, uint32_t to) {
|
||||
std::uniform_int_distribution<uint32_t> distribution(from, to);
|
||||
return distribution(gen);
|
||||
}
|
||||
|
||||
uint32_t rbinom(size_t n, double p) {
|
||||
std::binomial_distribution<uint32_t> distribution(n, p);
|
||||
return distribution(gen);
|
||||
}
|
||||
|
||||
std::string randomString(uint32_t length) {
|
||||
std::string str;
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
char rnd = (char)runif(65, 90);
|
||||
str += rnd;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void randomCString(char* str, uint32_t length) {
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
char rnd = (char)runif(65, 90);
|
||||
str[i] = rnd;
|
||||
}
|
||||
str[length] = '\0';
|
||||
}
|
||||
|
||||
painlessmesh::protocol::Single createSingle(int length = -1) {
|
||||
auto pkg = painlessmesh::protocol::Single();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
|
||||
if (length < 0) length = runif(0, 4096);
|
||||
pkg.msg = randomString(length);
|
||||
return pkg;
|
||||
}
|
||||
|
||||
painlessmesh::protocol::Broadcast createBroadcast(int length = -1) {
|
||||
auto pkg = painlessmesh::protocol::Broadcast();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (length < 0) length = runif(0, 4096);
|
||||
pkg.msg = randomString(length);
|
||||
return pkg;
|
||||
}
|
||||
|
||||
/*
|
||||
```
|
||||
{
|
||||
"dest": ...,
|
||||
"from": ...,
|
||||
"type": ...,
|
||||
"subs": [
|
||||
{
|
||||
"nodeId": ...,
|
||||
"root" : true,
|
||||
"subs": [
|
||||
{
|
||||
"nodeId": ...,
|
||||
"subs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
*/
|
||||
painlessmesh::protocol::NodeTree createNodeTree(int nodes, int contains_root) {
|
||||
auto pkg = painlessmesh::protocol::NodeTree();
|
||||
pkg.nodeId = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (contains_root == 0) {
|
||||
pkg.root = true;
|
||||
}
|
||||
--nodes; // The current node
|
||||
--contains_root;
|
||||
auto noSubs = runif(1, 5);
|
||||
for (uint32_t i = 0; i < noSubs; ++i) {
|
||||
if (nodes > 0) {
|
||||
if (i == noSubs - 1) {
|
||||
pkg.subs.push_back(createNodeTree(nodes, contains_root));
|
||||
} else {
|
||||
auto newNodes = 1 + rbinom(nodes - 1, 1.0 / noSubs);
|
||||
nodes -= newNodes;
|
||||
if (newNodes > 0)
|
||||
pkg.subs.push_back(createNodeTree(newNodes, contains_root));
|
||||
contains_root -= newNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
painlessmesh::protocol::NodeSyncReply createNodeSyncReply(
|
||||
int nodes = -1, bool contains_root = true) {
|
||||
auto pkg = painlessmesh::protocol::NodeSyncReply();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (nodes < 0) nodes = runif(1, 254);
|
||||
auto rt = -1;
|
||||
if (contains_root) rt = runif(0, nodes - 1);
|
||||
auto ns = createNodeTree(nodes, rt);
|
||||
pkg.subs = ns.subs;
|
||||
pkg.nodeId = ns.nodeId;
|
||||
pkg.root = ns.root;
|
||||
return pkg;
|
||||
}
|
||||
|
||||
painlessmesh::protocol::NodeSyncRequest createNodeSyncRequest(
|
||||
int nodes = -1, bool contains_root = true) {
|
||||
auto pkg = painlessmesh::protocol::NodeSyncRequest();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (nodes < 0) nodes = runif(1, 254);
|
||||
auto rt = -1;
|
||||
if (contains_root) rt = runif(0, nodes - 1);
|
||||
auto ns = createNodeTree(nodes, rt);
|
||||
pkg.subs = ns.subs;
|
||||
pkg.nodeId = ns.nodeId;
|
||||
pkg.root = ns.root;
|
||||
return pkg;
|
||||
}
|
||||
|
||||
/*
|
||||
```
|
||||
{
|
||||
"dest": 887034362,
|
||||
"from": 37418,
|
||||
"type":4,
|
||||
"msg":{
|
||||
"type":0
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"dest": 887034362,
|
||||
"from": 37418,
|
||||
"type":4,
|
||||
"msg":{
|
||||
"type":1,
|
||||
"t0":32990
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"dest": 37418,
|
||||
"from": 887034362,
|
||||
"type":4,
|
||||
"msg":{
|
||||
"type":2,
|
||||
"t0":32990,
|
||||
"t1":448585896,
|
||||
"t2":448596056,
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
painlessmesh::protocol::TimeSync createTimeSync(int type = -1) {
|
||||
auto pkg = painlessmesh::protocol::TimeSync();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
|
||||
if (type < 0) type = runif(0, 2);
|
||||
pkg.msg.type = type;
|
||||
auto t = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (type >= 1) pkg.msg.t0 = t;
|
||||
if (type >= 2) {
|
||||
t += runif(0, 10000);
|
||||
pkg.msg.t1 = t;
|
||||
t += runif(0, 10000);
|
||||
pkg.msg.t2 = t;
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
painlessmesh::protocol::TimeDelay createTimeDelay(int type = -1) {
|
||||
auto pkg = painlessmesh::protocol::TimeDelay();
|
||||
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
|
||||
if (type < 0) type = runif(0, 2);
|
||||
pkg.msg.type = type;
|
||||
auto t = runif(0, std::numeric_limits<uint32_t>::max());
|
||||
if (type == 1) pkg.msg.t0 = t;
|
||||
if (type == 2) {
|
||||
t += runif(0, 10000);
|
||||
pkg.msg.t1 = t;
|
||||
t += runif(0, 10000);
|
||||
pkg.msg.t2 = t;
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <cstring>
|
||||
#include "Arduino.h"
|
||||
|
||||
#define ASYNC_WRITE_FLAG_COPY \
|
||||
0x01 // will allocate new buffer to hold the data while sending (else will
|
||||
// hold reference to the data given)
|
||||
#define ASYNC_WRITE_FLAG_MORE \
|
||||
0x02 // will not send PSH flag, meaning that there should be more data to be
|
||||
// sent before the application should react.
|
||||
|
||||
class AsyncClient;
|
||||
|
||||
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)>
|
||||
AcAckHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, void* data, size_t len)>
|
||||
AcDataHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, struct pbuf* pb)>
|
||||
AcPacketHandler;
|
||||
typedef std::function<void(void*, AsyncClient*, uint32_t time)>
|
||||
AcTimeoutHandler;
|
||||
|
||||
class AsyncServer;
|
||||
|
||||
class AsyncClient {
|
||||
public:
|
||||
AsyncClient() {}
|
||||
|
||||
AsyncClient(AsyncServer* server) : mServer(server) {}
|
||||
|
||||
void setNoDelay(bool nodelay) {}
|
||||
void setRxTimeout(uint32_t timeout) {}
|
||||
void onData(AcDataHandler cb, void* arg = 0) {_recv_cb = cb;}
|
||||
void onAck(AcAckHandler cb, void* arg = 0) { _sent_cb = cb; }
|
||||
void onError(AcErrorHandler cb, void* arg = 0) {}
|
||||
void onDisconnect(AcConnectHandler cb, void* arg = 0) {_discard_cb = cb;}
|
||||
void onConnect(AcConnectHandler cb, void* arg = 0) { _connect_cb = cb; }
|
||||
|
||||
const char* errorToString(int err) { return ""; }
|
||||
|
||||
bool connected() { return mOther; }
|
||||
|
||||
bool canSend() { return true; }
|
||||
void ack(int len) {
|
||||
if (_sent_cb)
|
||||
_sent_cb(NULL, this, len, 0);
|
||||
}
|
||||
void close(bool now = false) {
|
||||
/*if (mOther && mOther->_discard_cb) {
|
||||
mOther->_discard_cb(NULL, this);
|
||||
mOther = NULL;
|
||||
}
|
||||
mServer = NULL;*/
|
||||
}
|
||||
bool connect(IPAddress ip, uint16_t port);
|
||||
size_t space() { return 1000; }
|
||||
bool send() { return true; }
|
||||
size_t write(const char* data, size_t size,
|
||||
uint8_t apiflags = ASYNC_WRITE_FLAG_COPY) {
|
||||
char* cpy[size];
|
||||
memcpy(&cpy, data, size);
|
||||
void * arg = NULL;
|
||||
if (mOther && mOther->_recv_cb) {
|
||||
mOther->_recv_cb(arg, mOther, cpy, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
bool freeable() { return true; }
|
||||
int8_t abort() { return 0; }
|
||||
bool free() { return true; }
|
||||
|
||||
bool operator==(const AsyncClient &other) {
|
||||
return mOther == other.mOther;
|
||||
}
|
||||
|
||||
protected:
|
||||
AsyncServer* mServer = NULL;
|
||||
AsyncClient* mOther = NULL;
|
||||
AcConnectHandler _connect_cb = NULL;
|
||||
AcConnectHandler _discard_cb = NULL;
|
||||
AcDataHandler _recv_cb = NULL;
|
||||
AcAckHandler _sent_cb = NULL;
|
||||
};
|
||||
|
||||
class AsyncServer : public AsyncClient {
|
||||
public:
|
||||
AsyncServer() {}
|
||||
AsyncServer(uint16_t port) {}
|
||||
void onClient(AcConnectHandler cb, void* arg = 0) { _connect_cb = cb; }
|
||||
void begin() {}
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
|
||||
WL_IDLE_STATUS = 0,
|
||||
WL_NO_SSID_AVAIL = 1,
|
||||
WL_SCAN_COMPLETED = 2,
|
||||
WL_CONNECTED = 3,
|
||||
WL_CONNECT_FAILED = 4,
|
||||
WL_CONNECTION_LOST = 5,
|
||||
WL_DISCONNECTED = 6
|
||||
} wl_status_t;
|
||||
|
||||
class WiFiClass {
|
||||
public:
|
||||
void disconnect() {}
|
||||
auto status() {
|
||||
return WL_CONNECTED;
|
||||
}
|
||||
};
|
||||
|
||||
class ESPClass {
|
||||
public:
|
||||
size_t getFreeHeap() { return 1e6; }
|
||||
};
|
||||
|
||||
inline bool AsyncClient::connect(IPAddress ip, uint16_t port) {
|
||||
this->mOther = new AsyncClient();
|
||||
this->mOther->mOther = this;
|
||||
void * arg = NULL;
|
||||
if (mServer->_connect_cb)
|
||||
mServer->_connect_cb(arg, this->mOther);
|
||||
if (this->_connect_cb)
|
||||
this->_connect_cb(arg, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
DSM2_tx implements the serial communication protocol used for operating
|
||||
the RF modules that can be found in many DSM2-compatible transmitters.
|
||||
Copyrigt (C) 2012 Erik Elmore <erik@ironsavior.net>
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
class FakeSerial {
|
||||
public:
|
||||
void begin(unsigned long);
|
||||
void end();
|
||||
size_t write(const unsigned char*, size_t);
|
||||
void print(const char*);
|
||||
void println();
|
||||
};
|
||||
|
||||
extern FakeSerial Serial;
|
||||
@@ -0,0 +1,39 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the easyMesh library
|
||||
//
|
||||
// 1. blinks led once for every node on the mesh
|
||||
// 2. blink cycle repeats every BLINK_PERIOD
|
||||
// 3. sends a silly message to every node on the mesh at a random time between 1
|
||||
// and 5 seconds
|
||||
// 4. prints anything it receives to Serial.print
|
||||
//
|
||||
//
|
||||
//************************************************************
|
||||
#include <painlessMesh.h>
|
||||
|
||||
#include "painlessmesh/ota.hpp"
|
||||
#include "painlessmesh/protocol.hpp"
|
||||
#include "plugin/performance.hpp"
|
||||
|
||||
#define MESH_SSID "otatest"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
using namespace painlessmesh;
|
||||
|
||||
painlessMesh mesh;
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mesh.setDebugMsgTypes(
|
||||
ERROR | CONNECTION |
|
||||
DEBUG); // set before init() so that you can see error messages
|
||||
|
||||
mesh.init(MESH_SSID, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6);
|
||||
mesh.initOTA("performance");
|
||||
plugin::performance::begin(mesh, 2);
|
||||
}
|
||||
|
||||
void loop() { mesh.update(); }
|
||||
@@ -0,0 +1,20 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
arduinoUnity
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
@@ -0,0 +1,20 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
arduinoUnity
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
@@ -0,0 +1,163 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the easyMesh library
|
||||
//
|
||||
// 1. blinks led once for every node on the mesh
|
||||
// 2. blink cycle repeats every BLINK_PERIOD
|
||||
// 3. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
|
||||
// 4. prints anything it receives to Serial.print
|
||||
//
|
||||
//
|
||||
//************************************************************
|
||||
#include <painlessMesh.h>
|
||||
|
||||
#include "painlessmesh/ota.hpp"
|
||||
#include "plugin/performance.hpp"
|
||||
|
||||
// some gpio pin that is connected to an LED...
|
||||
// on my rig, this is 5, change to the right number of your LED.
|
||||
#define LED 2 // GPIO number of connected LED, ON ESP-12 IS GPIO2
|
||||
#define SEND_FREQ 2
|
||||
|
||||
#define BLINK_PERIOD 3000 // milliseconds until cycle repeat
|
||||
#define BLINK_DURATION 100 // milliseconds LED is on for
|
||||
|
||||
#define MESH_SSID "otatest"
|
||||
#define MESH_PASSWORD "somethingSneaky"
|
||||
#define MESH_PORT 5555
|
||||
|
||||
// Prototypes
|
||||
void sendMessage();
|
||||
void receivedCallback(uint32_t from, String & msg);
|
||||
void newConnectionCallback(uint32_t nodeId);
|
||||
void changedConnectionCallback();
|
||||
void nodeTimeAdjustedCallback(int32_t offset);
|
||||
void delayReceivedCallback(uint32_t from, int32_t delay);
|
||||
|
||||
Scheduler userScheduler; // to control your personal task
|
||||
painlessMesh mesh;
|
||||
|
||||
bool calc_delay = false;
|
||||
std::list<uint32_t> nodes;
|
||||
|
||||
void sendMessage() ; // Prototype
|
||||
Task taskSendMessage( TASK_SECOND/SEND_FREQ, TASK_FOREVER, &sendMessage ); // start with a one second interval
|
||||
|
||||
// Task to blink the number of nodes
|
||||
Task blinkNoNodes;
|
||||
bool onFlag = false;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
mesh.setDebugMsgTypes(ERROR | CONNECTION | DEBUG); // set before init() so that you can see error messages
|
||||
|
||||
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6);
|
||||
mesh.initOTA("otatest");
|
||||
|
||||
mesh.setContainsRoot(true);
|
||||
|
||||
mesh.onReceive(&receivedCallback);
|
||||
mesh.onNewConnection(&newConnectionCallback);
|
||||
mesh.onChangedConnections(&changedConnectionCallback);
|
||||
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
|
||||
mesh.onNodeDelayReceived(&delayReceivedCallback);
|
||||
|
||||
painlessmesh::plugin::performance::begin(mesh);
|
||||
|
||||
userScheduler.addTask( taskSendMessage );
|
||||
taskSendMessage.enable();
|
||||
|
||||
blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
|
||||
// If on, switch off, else switch on
|
||||
if (onFlag)
|
||||
onFlag = false;
|
||||
else
|
||||
onFlag = true;
|
||||
blinkNoNodes.delay(BLINK_DURATION);
|
||||
|
||||
if (blinkNoNodes.isLastIteration()) {
|
||||
// Finished blinking. Reset task for next run
|
||||
// blink number of nodes (including this node) times
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
// Calculate delay based on current mesh time and BLINK_PERIOD
|
||||
// This results in blinks between nodes being synced
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD -
|
||||
(mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
}
|
||||
});
|
||||
userScheduler.addTask(blinkNoNodes);
|
||||
blinkNoNodes.enable();
|
||||
|
||||
randomSeed(analogRead(A0));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh.update();
|
||||
digitalWrite(LED, !onFlag);
|
||||
}
|
||||
|
||||
uint32_t lastTime = 0;
|
||||
void sendMessage() {
|
||||
String msg = "Hello from node ";
|
||||
msg += mesh.getNodeId();
|
||||
msg += " myFreeMemory: " + String(ESP.getFreeHeap());
|
||||
mesh.sendBroadcast(msg);
|
||||
|
||||
if (calc_delay) {
|
||||
SimpleList<uint32_t>::iterator node = nodes.begin();
|
||||
while (node != nodes.end()) {
|
||||
mesh.startDelayMeas(*node);
|
||||
node++;
|
||||
}
|
||||
calc_delay = false;
|
||||
}
|
||||
|
||||
Serial.printf("Mesh stability: %u\n", mesh.stability);
|
||||
Serial.printf("Sending message: %s with delay %u\n", msg.c_str(), mesh.getNodeTime() - lastTime);
|
||||
lastTime = mesh.getNodeTime();
|
||||
}
|
||||
|
||||
void receivedCallback(uint32_t from, String & msg) {
|
||||
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
|
||||
}
|
||||
|
||||
void newConnectionCallback(uint32_t nodeId) {
|
||||
// Reset blink task
|
||||
onFlag = false;
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
|
||||
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
|
||||
Serial.printf("--> startHere: New Connection, %s\n", mesh.subConnectionJson(true).c_str());
|
||||
}
|
||||
|
||||
void changedConnectionCallback() {
|
||||
Serial.printf("Changed connections\n");
|
||||
// Reset blink task
|
||||
onFlag = false;
|
||||
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
|
||||
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
|
||||
|
||||
nodes = mesh.getNodeList();
|
||||
|
||||
Serial.printf("Num nodes: %d\n", nodes.size());
|
||||
Serial.printf("Connection list:");
|
||||
|
||||
SimpleList<uint32_t>::iterator node = nodes.begin();
|
||||
while (node != nodes.end()) {
|
||||
Serial.printf(" %u", *node);
|
||||
node++;
|
||||
}
|
||||
Serial.println();
|
||||
calc_delay = true;
|
||||
}
|
||||
|
||||
void nodeTimeAdjustedCallback(int32_t offset) {
|
||||
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
|
||||
}
|
||||
|
||||
void delayReceivedCallback(uint32_t from, int32_t delay) {
|
||||
Serial.printf("Delay to node %u is %d us\n", from, delay);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[platformio]
|
||||
src_dir = .
|
||||
lib_extra_dirs = .piolibdeps/, ../../
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
TaskScheduler
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ArduinoJson
|
||||
arduinoUnity
|
||||
TaskScheduler
|
||||
AsyncTCP
|
||||
88
arduino-cli/libraries/painlessMesh-master/test/wifi/wifi.ino
Normal file
88
arduino-cli/libraries/painlessMesh-master/test/wifi/wifi.ino
Normal file
@@ -0,0 +1,88 @@
|
||||
//************************************************************
|
||||
// this is a simple example that uses the easyMesh library
|
||||
//
|
||||
// 1. blinks led once for every node on the mesh
|
||||
// 2. blink cycle repeats every BLINK_PERIOD
|
||||
// 3. sends a silly message to every node on the mesh at a random time between 1
|
||||
// and 5 seconds
|
||||
// 4. prints anything it receives to Serial.print
|
||||
//
|
||||
//
|
||||
//************************************************************
|
||||
#include "painlessmesh/configuration.hpp"
|
||||
|
||||
#include "painlessMeshConnection.h"
|
||||
|
||||
#include "painlessmesh/mesh.hpp"
|
||||
#include "painlessmesh/tcp.hpp"
|
||||
#include "plugin/performance.hpp"
|
||||
|
||||
using namespace painlessmesh;
|
||||
using namespace logger;
|
||||
|
||||
painlessmesh::Mesh<MeshConnection> mesh;
|
||||
|
||||
std::shared_ptr<AsyncServer> pServer;
|
||||
|
||||
WiFiEventId_t eventSTADisconnectedHandler;
|
||||
WiFiEventId_t eventSTAGotIPHandler;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Log.setLogLevel(ERROR | CONNECTION | DEBUG);
|
||||
uint8_t mac[] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
/*auto _apIp = IPAddress(10, (nodeId & 0xFF00) >> 8, (nodeId & 0xFF), 1);
|
||||
IPAddress netmask(255, 255, 255, 0);
|
||||
|
||||
WiFi.softAPConfig(_apIp, _apIp, netmask);*/
|
||||
|
||||
WiFi.softAP("otatest", "somethingSneaky", 6);
|
||||
if (WiFi.softAPmacAddress(mac) == 0) {
|
||||
Log(ERROR, "init(): WiFi.softAPmacAddress(MAC) failed.\n");
|
||||
}
|
||||
auto nodeId = painlessmesh::tcp::encodeNodeId(mac);
|
||||
if (nodeId == 0) Log(ERROR, "NodeId set to 0\n");
|
||||
Log(ERROR, "Bla %u\n", nodeId);
|
||||
mesh.init(nodeId);
|
||||
mesh.setRoot(true);
|
||||
plugin::performance::begin(mesh);
|
||||
|
||||
pServer = std::make_shared<AsyncServer>(5555);
|
||||
painlessmesh::tcp::initServer<MeshConnection>((*pServer), mesh);
|
||||
eventSTAGotIPHandler = WiFi.onEvent(
|
||||
[&](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
// if (this->semaphoreTake()) {
|
||||
Log(CONNECTION, "eventSTAGotIPHandler: SYSTEM_EVENT_STA_GOT_IP\n");
|
||||
AsyncClient *pConn = new AsyncClient();
|
||||
painlessmesh::tcp::connect<MeshConnection>(
|
||||
(*pConn), IPAddress(192, 168, 1, 69), 5555, mesh);
|
||||
// this->tcpConnect(); // Connect to TCP port
|
||||
// this->semaphoreGive();
|
||||
//}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
|
||||
|
||||
eventSTADisconnectedHandler = WiFi.onEvent(
|
||||
[](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
//if (this->semaphoreTake()) {
|
||||
Log(CONNECTION,
|
||||
"eventSTADisconnectedHandler: SYSTEM_EVENT_STA_DISCONNECTED\n");
|
||||
// WiFi.disconnect();
|
||||
// Search for APs and connect to the best one
|
||||
//this->stationScan.connectToAP();
|
||||
//this->semaphoreGive();
|
||||
//}
|
||||
},
|
||||
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
|
||||
|
||||
|
||||
WiFi.setAutoConnect(true);
|
||||
WiFi.begin("BigBird", "eendolleman");
|
||||
Log(CONNECTION, "Beginning\n");
|
||||
mesh.addTask(TASK_SECOND, TASK_FOREVER, []() {
|
||||
Log(CONNECTION, "Connected? %d\n", WiFi.status() == WL_CONNECTED);
|
||||
});
|
||||
}
|
||||
|
||||
void loop() { mesh.update(); }
|
||||
Reference in New Issue
Block a user