wip
This commit is contained in:
parent
7fd631541b
commit
2b1f79ee4a
9
.clang-format
Normal file
9
.clang-format
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
ColumnLimit: 100
|
||||||
|
IndentWidth: 4
|
||||||
|
AlignConsecutiveAssignments: true
|
||||||
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
|
AllowShortLoopsOnASingleLine: true
|
||||||
|
...
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
.cache
|
||||||
|
@ -2,16 +2,15 @@ cmake_minimum_required (VERSION 3.15)
|
|||||||
project (simplesync)
|
project (simplesync)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic")
|
set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic")
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
option(BUILD_TESTS "Build all tests." OFF)
|
option(BUILD_TESTS "Build all tests." OFF)
|
||||||
|
option(BUILD_PYLIB "Build python lib." OFF)
|
||||||
|
|
||||||
add_definitions(-D__LINUX_BUILD)
|
add_definitions(-D__LINUX_BUILD)
|
||||||
|
|
||||||
include_directories(src)
|
include_directories(include)
|
||||||
|
|
||||||
set(SRCS src/simplesync.hpp)
|
set(SRCS )#src/simplesync.hpp)
|
||||||
|
|
||||||
# Build the shared library
|
|
||||||
|
|
||||||
# Build the static library
|
# Build the static library
|
||||||
add_library(simplesync INTERFACE ${SRCS})
|
add_library(simplesync INTERFACE ${SRCS})
|
||||||
@ -58,6 +57,20 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/simplesync.hpp
|
|||||||
export( EXPORT simplesyncTargets
|
export( EXPORT simplesyncTargets
|
||||||
FILE ${CMAKE_CURRENT_BINARY_DIR}/simplesync-targets.cmake)
|
FILE ${CMAKE_CURRENT_BINARY_DIR}/simplesync-targets.cmake)
|
||||||
|
|
||||||
|
if (BUILD_PYLIB)
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
pybind11
|
||||||
|
GIT_REPOSITORY https://github.com/pybind/pybind11
|
||||||
|
GIT_TAG v2.11.1
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(pybind11)
|
||||||
|
find_package(pybind11 CONFIG)
|
||||||
|
|
||||||
|
pybind11_add_module(pysimplesync src/python_module.cpp)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
|
287
LICENSE.txt
Normal file
287
LICENSE.txt
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
EUROPEAN UNION PUBLIC LICENCE v. 1.2
|
||||||
|
EUPL © the European Union 2007, 2016
|
||||||
|
|
||||||
|
This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
|
||||||
|
below) which is provided under the terms of this Licence. Any use of the Work,
|
||||||
|
other than as authorised under this Licence is prohibited (to the extent such
|
||||||
|
use is covered by a right of the copyright holder of the Work).
|
||||||
|
|
||||||
|
The Work is provided under the terms of this Licence when the Licensor (as
|
||||||
|
defined below) has placed the following notice immediately following the
|
||||||
|
copyright notice for the Work:
|
||||||
|
|
||||||
|
Licensed under the EUPL
|
||||||
|
|
||||||
|
or has expressed by any other means his willingness to license under the EUPL.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
In this Licence, the following terms have the following meaning:
|
||||||
|
|
||||||
|
- ‘The Licence’: this Licence.
|
||||||
|
|
||||||
|
- ‘The Original Work’: the work or software distributed or communicated by the
|
||||||
|
Licensor under this Licence, available as Source Code and also as Executable
|
||||||
|
Code as the case may be.
|
||||||
|
|
||||||
|
- ‘Derivative Works’: the works or software that could be created by the
|
||||||
|
Licensee, based upon the Original Work or modifications thereof. This Licence
|
||||||
|
does not define the extent of modification or dependence on the Original Work
|
||||||
|
required in order to classify a work as a Derivative Work; this extent is
|
||||||
|
determined by copyright law applicable in the country mentioned in Article 15.
|
||||||
|
|
||||||
|
- ‘The Work’: the Original Work or its Derivative Works.
|
||||||
|
|
||||||
|
- ‘The Source Code’: the human-readable form of the Work which is the most
|
||||||
|
convenient for people to study and modify.
|
||||||
|
|
||||||
|
- ‘The Executable Code’: any code which has generally been compiled and which is
|
||||||
|
meant to be interpreted by a computer as a program.
|
||||||
|
|
||||||
|
- ‘The Licensor’: the natural or legal person that distributes or communicates
|
||||||
|
the Work under the Licence.
|
||||||
|
|
||||||
|
- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
|
||||||
|
Licence, or otherwise contributes to the creation of a Derivative Work.
|
||||||
|
|
||||||
|
- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
|
||||||
|
the Work under the terms of the Licence.
|
||||||
|
|
||||||
|
- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
|
||||||
|
renting, distributing, communicating, transmitting, or otherwise making
|
||||||
|
available, online or offline, copies of the Work or providing access to its
|
||||||
|
essential functionalities at the disposal of any other natural or legal
|
||||||
|
person.
|
||||||
|
|
||||||
|
2. Scope of the rights granted by the Licence
|
||||||
|
|
||||||
|
The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
|
sublicensable licence to do the following, for the duration of copyright vested
|
||||||
|
in the Original Work:
|
||||||
|
|
||||||
|
- use the Work in any circumstance and for all usage,
|
||||||
|
- reproduce the Work,
|
||||||
|
- modify the Work, and make Derivative Works based upon the Work,
|
||||||
|
- communicate to the public, including the right to make available or display
|
||||||
|
the Work or copies thereof to the public and perform publicly, as the case may
|
||||||
|
be, the Work,
|
||||||
|
- distribute the Work or copies thereof,
|
||||||
|
- lend and rent the Work or copies thereof,
|
||||||
|
- sublicense rights in the Work or copies thereof.
|
||||||
|
|
||||||
|
Those rights can be exercised on any media, supports and formats, whether now
|
||||||
|
known or later invented, as far as the applicable law permits so.
|
||||||
|
|
||||||
|
In the countries where moral rights apply, the Licensor waives his right to
|
||||||
|
exercise his moral right to the extent allowed by law in order to make effective
|
||||||
|
the licence of the economic rights here above listed.
|
||||||
|
|
||||||
|
The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
|
||||||
|
any patents held by the Licensor, to the extent necessary to make use of the
|
||||||
|
rights granted on the Work under this Licence.
|
||||||
|
|
||||||
|
3. Communication of the Source Code
|
||||||
|
|
||||||
|
The Licensor may provide the Work either in its Source Code form, or as
|
||||||
|
Executable Code. If the Work is provided as Executable Code, the Licensor
|
||||||
|
provides in addition a machine-readable copy of the Source Code of the Work
|
||||||
|
along with each copy of the Work that the Licensor distributes or indicates, in
|
||||||
|
a notice following the copyright notice attached to the Work, a repository where
|
||||||
|
the Source Code is easily and freely accessible for as long as the Licensor
|
||||||
|
continues to distribute or communicate the Work.
|
||||||
|
|
||||||
|
4. Limitations on copyright
|
||||||
|
|
||||||
|
Nothing in this Licence is intended to deprive the Licensee of the benefits from
|
||||||
|
any exception or limitation to the exclusive rights of the rights owners in the
|
||||||
|
Work, of the exhaustion of those rights or of other applicable limitations
|
||||||
|
thereto.
|
||||||
|
|
||||||
|
5. Obligations of the Licensee
|
||||||
|
|
||||||
|
The grant of the rights mentioned above is subject to some restrictions and
|
||||||
|
obligations imposed on the Licensee. Those obligations are the following:
|
||||||
|
|
||||||
|
Attribution right: The Licensee shall keep intact all copyright, patent or
|
||||||
|
trademarks notices and all notices that refer to the Licence and to the
|
||||||
|
disclaimer of warranties. The Licensee must include a copy of such notices and a
|
||||||
|
copy of the Licence with every copy of the Work he/she distributes or
|
||||||
|
communicates. The Licensee must cause any Derivative Work to carry prominent
|
||||||
|
notices stating that the Work has been modified and the date of modification.
|
||||||
|
|
||||||
|
Copyleft clause: If the Licensee distributes or communicates copies of the
|
||||||
|
Original Works or Derivative Works, this Distribution or Communication will be
|
||||||
|
done under the terms of this Licence or of a later version of this Licence
|
||||||
|
unless the Original Work is expressly distributed only under this version of the
|
||||||
|
Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
|
||||||
|
(becoming Licensor) cannot offer or impose any additional terms or conditions on
|
||||||
|
the Work or Derivative Work that alter or restrict the terms of the Licence.
|
||||||
|
|
||||||
|
Compatibility clause: If the Licensee Distributes or Communicates Derivative
|
||||||
|
Works or copies thereof based upon both the Work and another work licensed under
|
||||||
|
a Compatible Licence, this Distribution or Communication can be done under the
|
||||||
|
terms of this Compatible Licence. For the sake of this clause, ‘Compatible
|
||||||
|
Licence’ refers to the licences listed in the appendix attached to this Licence.
|
||||||
|
Should the Licensee's obligations under the Compatible Licence conflict with
|
||||||
|
his/her obligations under this Licence, the obligations of the Compatible
|
||||||
|
Licence shall prevail.
|
||||||
|
|
||||||
|
Provision of Source Code: When distributing or communicating copies of the Work,
|
||||||
|
the Licensee will provide a machine-readable copy of the Source Code or indicate
|
||||||
|
a repository where this Source will be easily and freely available for as long
|
||||||
|
as the Licensee continues to distribute or communicate the Work.
|
||||||
|
|
||||||
|
Legal Protection: This Licence does not grant permission to use the trade names,
|
||||||
|
trademarks, service marks, or names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the copyright notice.
|
||||||
|
|
||||||
|
6. Chain of Authorship
|
||||||
|
|
||||||
|
The original Licensor warrants that the copyright in the Original Work granted
|
||||||
|
hereunder is owned by him/her or licensed to him/her and that he/she has the
|
||||||
|
power and authority to grant the Licence.
|
||||||
|
|
||||||
|
Each Contributor warrants that the copyright in the modifications he/she brings
|
||||||
|
to the Work are owned by him/her or licensed to him/her and that he/she has the
|
||||||
|
power and authority to grant the Licence.
|
||||||
|
|
||||||
|
Each time You accept the Licence, the original Licensor and subsequent
|
||||||
|
Contributors grant You a licence to their contributions to the Work, under the
|
||||||
|
terms of this Licence.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty
|
||||||
|
|
||||||
|
The Work is a work in progress, which is continuously improved by numerous
|
||||||
|
Contributors. It is not a finished work and may therefore contain defects or
|
||||||
|
‘bugs’ inherent to this type of development.
|
||||||
|
|
||||||
|
For the above reason, the Work is provided under the Licence on an ‘as is’ basis
|
||||||
|
and without warranties of any kind concerning the Work, including without
|
||||||
|
limitation merchantability, fitness for a particular purpose, absence of defects
|
||||||
|
or errors, accuracy, non-infringement of intellectual property rights other than
|
||||||
|
copyright as stated in Article 6 of this Licence.
|
||||||
|
|
||||||
|
This disclaimer of warranty is an essential part of the Licence and a condition
|
||||||
|
for the grant of any rights to the Work.
|
||||||
|
|
||||||
|
8. Disclaimer of Liability
|
||||||
|
|
||||||
|
Except in the cases of wilful misconduct or damages directly caused to natural
|
||||||
|
persons, the Licensor will in no event be liable for any direct or indirect,
|
||||||
|
material or moral, damages of any kind, arising out of the Licence or of the use
|
||||||
|
of the Work, including without limitation, damages for loss of goodwill, work
|
||||||
|
stoppage, computer failure or malfunction, loss of data or any commercial
|
||||||
|
damage, even if the Licensor has been advised of the possibility of such damage.
|
||||||
|
However, the Licensor will be liable under statutory product liability laws as
|
||||||
|
far such laws apply to the Work.
|
||||||
|
|
||||||
|
9. Additional agreements
|
||||||
|
|
||||||
|
While distributing the Work, You may choose to conclude an additional agreement,
|
||||||
|
defining obligations or services consistent with this Licence. However, if
|
||||||
|
accepting obligations, You may act only on your own behalf and on your sole
|
||||||
|
responsibility, not on behalf of the original Licensor or any other Contributor,
|
||||||
|
and only if You agree to indemnify, defend, and hold each Contributor harmless
|
||||||
|
for any liability incurred by, or claims asserted against such Contributor by
|
||||||
|
the fact You have accepted any warranty or additional liability.
|
||||||
|
|
||||||
|
10. Acceptance of the Licence
|
||||||
|
|
||||||
|
The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
|
||||||
|
placed under the bottom of a window displaying the text of this Licence or by
|
||||||
|
affirming consent in any other similar way, in accordance with the rules of
|
||||||
|
applicable law. Clicking on that icon indicates your clear and irrevocable
|
||||||
|
acceptance of this Licence and all of its terms and conditions.
|
||||||
|
|
||||||
|
Similarly, you irrevocably accept this Licence and all of its terms and
|
||||||
|
conditions by exercising any rights granted to You by Article 2 of this Licence,
|
||||||
|
such as the use of the Work, the creation by You of a Derivative Work or the
|
||||||
|
Distribution or Communication by You of the Work or copies thereof.
|
||||||
|
|
||||||
|
11. Information to the public
|
||||||
|
|
||||||
|
In case of any Distribution or Communication of the Work by means of electronic
|
||||||
|
communication by You (for example, by offering to download the Work from a
|
||||||
|
remote location) the distribution channel or media (for example, a website) must
|
||||||
|
at least provide to the public the information requested by the applicable law
|
||||||
|
regarding the Licensor, the Licence and the way it may be accessible, concluded,
|
||||||
|
stored and reproduced by the Licensee.
|
||||||
|
|
||||||
|
12. Termination of the Licence
|
||||||
|
|
||||||
|
The Licence and the rights granted hereunder will terminate automatically upon
|
||||||
|
any breach by the Licensee of the terms of the Licence.
|
||||||
|
|
||||||
|
Such a termination will not terminate the licences of any person who has
|
||||||
|
received the Work from the Licensee under the Licence, provided such persons
|
||||||
|
remain in full compliance with the Licence.
|
||||||
|
|
||||||
|
13. Miscellaneous
|
||||||
|
|
||||||
|
Without prejudice of Article 9 above, the Licence represents the complete
|
||||||
|
agreement between the Parties as to the Work.
|
||||||
|
|
||||||
|
If any provision of the Licence is invalid or unenforceable under applicable
|
||||||
|
law, this will not affect the validity or enforceability of the Licence as a
|
||||||
|
whole. Such provision will be construed or reformed so as necessary to make it
|
||||||
|
valid and enforceable.
|
||||||
|
|
||||||
|
The European Commission may publish other linguistic versions or new versions of
|
||||||
|
this Licence or updated versions of the Appendix, so far this is required and
|
||||||
|
reasonable, without reducing the scope of the rights granted by the Licence. New
|
||||||
|
versions of the Licence will be published with a unique version number.
|
||||||
|
|
||||||
|
All linguistic versions of this Licence, approved by the European Commission,
|
||||||
|
have identical value. Parties can take advantage of the linguistic version of
|
||||||
|
their choice.
|
||||||
|
|
||||||
|
14. Jurisdiction
|
||||||
|
|
||||||
|
Without prejudice to specific agreement between parties,
|
||||||
|
|
||||||
|
- any litigation resulting from the interpretation of this License, arising
|
||||||
|
between the European Union institutions, bodies, offices or agencies, as a
|
||||||
|
Licensor, and any Licensee, will be subject to the jurisdiction of the Court
|
||||||
|
of Justice of the European Union, as laid down in article 272 of the Treaty on
|
||||||
|
the Functioning of the European Union,
|
||||||
|
|
||||||
|
- any litigation arising between other parties and resulting from the
|
||||||
|
interpretation of this License, will be subject to the exclusive jurisdiction
|
||||||
|
of the competent court where the Licensor resides or conducts its primary
|
||||||
|
business.
|
||||||
|
|
||||||
|
15. Applicable Law
|
||||||
|
|
||||||
|
Without prejudice to specific agreement between parties,
|
||||||
|
|
||||||
|
- this Licence shall be governed by the law of the European Union Member State
|
||||||
|
where the Licensor has his seat, resides or has his registered office,
|
||||||
|
|
||||||
|
- this licence shall be governed by Belgian law if the Licensor has no seat,
|
||||||
|
residence or registered office inside a European Union Member State.
|
||||||
|
|
||||||
|
Appendix
|
||||||
|
|
||||||
|
‘Compatible Licences’ according to Article 5 EUPL are:
|
||||||
|
|
||||||
|
- GNU General Public License (GPL) v. 2, v. 3
|
||||||
|
- GNU Affero General Public License (AGPL) v. 3
|
||||||
|
- Open Software License (OSL) v. 2.1, v. 3.0
|
||||||
|
- Eclipse Public License (EPL) v. 1.0
|
||||||
|
- CeCILL v. 2.0, v. 2.1
|
||||||
|
- Mozilla Public Licence (MPL) v. 2
|
||||||
|
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
|
||||||
|
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
|
||||||
|
works other than software
|
||||||
|
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
|
||||||
|
- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
|
||||||
|
Reciprocity (LiLiQ-R+).
|
||||||
|
|
||||||
|
The European Commission may update this Appendix to later versions of the above
|
||||||
|
licences without producing a new version of the EUPL, as long as they provide
|
||||||
|
the rights granted in Article 2 of this Licence and protect the covered Source
|
||||||
|
Code from exclusive appropriation.
|
||||||
|
|
||||||
|
All other changes or additions to this Appendix require the production of a new
|
||||||
|
EUPL version.
|
@ -1,4 +1,4 @@
|
|||||||
## Build
|
## Build
|
||||||
```
|
```
|
||||||
mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . ) && build/simplesync_test
|
mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. && cmake --build . ) && build/simplesync_test
|
||||||
```
|
```
|
||||||
|
250
include/nonstd_functional.hpp
Normal file
250
include/nonstd_functional.hpp
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
* Copyright (c) 2019 winterscar
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifdef _NONSTD_FUNCTIONAL_HHP
|
||||||
|
#define _NONSTD_FUNCTIONAL_HHP
|
||||||
|
#include <Arduino.h>
|
||||||
|
// extern void * operator new(size_t size, void * ptr);
|
||||||
|
void* operator new(size_t size, void* ptr) {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
namespace nonstd {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct tag {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
template <class Tag>
|
||||||
|
using type_t = typename Tag::type;
|
||||||
|
|
||||||
|
using size_t = decltype(sizeof(int));
|
||||||
|
|
||||||
|
// move
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T&& move(T& t) {
|
||||||
|
return static_cast<T&&>(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct remove_reference : tag<T> {};
|
||||||
|
template <class T>
|
||||||
|
struct remove_reference<T&> : tag<T> {};
|
||||||
|
template <class T>
|
||||||
|
using remove_reference_t = type_t<remove_reference<T>>;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T&& forward(remove_reference_t<T>& t) {
|
||||||
|
return static_cast<T&&>(t);
|
||||||
|
}
|
||||||
|
template <class T>
|
||||||
|
T&& forward(remove_reference_t<T>&& t) {
|
||||||
|
return static_cast<T&&>(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decay
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct remove_const : tag<T> {};
|
||||||
|
template <class T>
|
||||||
|
struct remove_const<T const> : tag<T> {};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct remove_volatile : tag<T> {};
|
||||||
|
template <class T>
|
||||||
|
struct remove_volatile<T volatile> : tag<T> {};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct remove_cv : remove_const<type_t<remove_volatile<T>>> {};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct decay3 : remove_cv<T> {};
|
||||||
|
template <class R, class... Args>
|
||||||
|
struct decay3<R(Args...)> : tag<R (*)(Args...)> {};
|
||||||
|
template <class T>
|
||||||
|
struct decay2 : decay3<T> {};
|
||||||
|
template <class T, size_t N>
|
||||||
|
struct decay2<T[N]> : tag<T*> {};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct decay : decay2<remove_reference_t<T>> {};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using decay_t = type_t<decay<T>>;
|
||||||
|
|
||||||
|
// is_convertible
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
T declval(); // no implementation
|
||||||
|
|
||||||
|
template <class T, T t>
|
||||||
|
struct integral_constant {
|
||||||
|
static constexpr T value = t;
|
||||||
|
constexpr integral_constant(){};
|
||||||
|
constexpr operator T() const { return value; }
|
||||||
|
constexpr T operator()() const { return value; }
|
||||||
|
};
|
||||||
|
template <bool b>
|
||||||
|
using bool_t = integral_constant<bool, b>;
|
||||||
|
using true_type = bool_t<true>;
|
||||||
|
using false_type = bool_t<false>;
|
||||||
|
|
||||||
|
template <class...>
|
||||||
|
struct voider : tag<void> {};
|
||||||
|
template <class... Ts>
|
||||||
|
using void_t = type_t<voider<Ts...>>;
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
template <template <class...> class Z, class, class... Ts>
|
||||||
|
struct can_apply : false_type {};
|
||||||
|
template <template <class...> class Z, class... Ts>
|
||||||
|
struct can_apply<Z, void_t<Z<Ts...>>, Ts...> : true_type {};
|
||||||
|
} // namespace details
|
||||||
|
template <template <class...> class Z, class... Ts>
|
||||||
|
using can_apply = details::can_apply<Z, void, Ts...>;
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
template <class From, class To>
|
||||||
|
using try_convert = decltype(To{declval<From>()});
|
||||||
|
}
|
||||||
|
template <class From, class To>
|
||||||
|
struct is_convertible : can_apply<details::try_convert, From, To> {};
|
||||||
|
template <>
|
||||||
|
struct is_convertible<void, void> : true_type {};
|
||||||
|
|
||||||
|
// enable_if
|
||||||
|
|
||||||
|
template <bool, class = void>
|
||||||
|
struct enable_if {};
|
||||||
|
template <class T>
|
||||||
|
struct enable_if<true, T> : tag<T> {};
|
||||||
|
template <bool b, class T = void>
|
||||||
|
using enable_if_t = type_t<enable_if<b, T>>;
|
||||||
|
|
||||||
|
// res_of
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
template <class G, class... Args>
|
||||||
|
using invoke_t = decltype(declval<G>()(declval<Args>()...));
|
||||||
|
|
||||||
|
template <class Sig, class = void>
|
||||||
|
struct res_of {};
|
||||||
|
template <class G, class... Args>
|
||||||
|
struct res_of<G(Args...), void_t<invoke_t<G, Args...>>> : tag<invoke_t<G, Args...>> {};
|
||||||
|
} // namespace details
|
||||||
|
template <class Sig>
|
||||||
|
using res_of = details::res_of<Sig>;
|
||||||
|
template <class Sig>
|
||||||
|
using res_of_t = type_t<res_of<Sig>>;
|
||||||
|
|
||||||
|
// aligned_storage
|
||||||
|
|
||||||
|
template <size_t size, size_t align>
|
||||||
|
struct alignas(align) aligned_storage_t {
|
||||||
|
char buff[size];
|
||||||
|
};
|
||||||
|
|
||||||
|
// is_same
|
||||||
|
|
||||||
|
template <class A, class B>
|
||||||
|
struct is_same : false_type {};
|
||||||
|
template <class A>
|
||||||
|
struct is_same<A, A> : true_type {};
|
||||||
|
|
||||||
|
template <class Sig, size_t sz, size_t algn>
|
||||||
|
struct small_task;
|
||||||
|
|
||||||
|
template <class R, class... Args, size_t sz, size_t algn>
|
||||||
|
struct small_task<R(Args...), sz, algn> {
|
||||||
|
struct vtable_t {
|
||||||
|
void (*mover)(void* src, void* dest);
|
||||||
|
void (*destroyer)(void*);
|
||||||
|
R (*invoke)(void const* t, Args&&... args);
|
||||||
|
template <class T>
|
||||||
|
static vtable_t const* get() {
|
||||||
|
static const vtable_t table = {
|
||||||
|
[](void* src, void* dest) { new (dest) T(move(*static_cast<T*>(src))); },
|
||||||
|
[](void* t) { static_cast<T*>(t)->~T(); },
|
||||||
|
[](void const* t, Args&&... args) -> R { return (*static_cast<T const*>(t))(forward<Args>(args)...); }};
|
||||||
|
return &table;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vtable_t const* table = nullptr;
|
||||||
|
aligned_storage_t<sz, algn> data;
|
||||||
|
template <class F, class dF = decay_t<F>, enable_if_t<!is_same<dF, small_task>{}>* = nullptr,
|
||||||
|
enable_if_t<is_convertible<res_of_t<dF&(Args...)>, R>{}>* = nullptr>
|
||||||
|
small_task(F&& f) : table(vtable_t::template get<dF>()) {
|
||||||
|
static_assert(sizeof(dF) <= sz, "object too large");
|
||||||
|
static_assert(alignof(dF) <= algn, "object too aligned");
|
||||||
|
new (&data) dF(forward<F>(f));
|
||||||
|
}
|
||||||
|
~small_task() {
|
||||||
|
if (table) table->destroyer(&data);
|
||||||
|
}
|
||||||
|
small_task(const small_task& o) : table(o.table) { data = o.data; }
|
||||||
|
small_task(small_task&& o) : table(o.table) {
|
||||||
|
if (table) table->mover(&o.data, &data);
|
||||||
|
}
|
||||||
|
small_task() {}
|
||||||
|
small_task& operator=(const small_task& o) {
|
||||||
|
this->~small_task();
|
||||||
|
new (this) small_task(move(o));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
small_task& operator=(small_task&& o) {
|
||||||
|
this->~small_task();
|
||||||
|
new (this) small_task(move(o));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
explicit operator bool() const { return table; }
|
||||||
|
R operator()(Args... args) const { return table->invoke(&data, forward<Args>(args)...); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class R, class... Args, size_t sz, size_t algn>
|
||||||
|
inline bool operator==(const small_task<R(Args...), sz, algn>& __f, nullptr_t) {
|
||||||
|
return !static_cast<bool>(__f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @overload
|
||||||
|
template <class R, class... Args, size_t sz, size_t algn>
|
||||||
|
inline bool operator==(nullptr_t, const small_task<R(Args...), sz, algn>& __f) {
|
||||||
|
return !static_cast<bool>(__f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class R, class... Args, size_t sz, size_t algn>
|
||||||
|
inline bool operator!=(const small_task<R(Args...), sz, algn>& __f, nullptr_t) {
|
||||||
|
return static_cast<bool>(__f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @overload
|
||||||
|
template <class R, class... Args, size_t sz, size_t algn>
|
||||||
|
inline bool operator!=(nullptr_t, const small_task<R(Args...), sz, algn>& __f) {
|
||||||
|
return static_cast<bool>(__f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Sig>
|
||||||
|
using function = small_task<Sig, sizeof(void*) * 4, alignof(void*)>;
|
||||||
|
} // namespace nonstd
|
||||||
|
|
||||||
|
#endif /* _NONSTD_FUNCTIONAL_HHP */
|
470
include/simplesync.hpp
Normal file
470
include/simplesync.hpp
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
#ifndef SIMPLESYNC_HPP
|
||||||
|
#define SIMPLESYNC_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#ifdef __AVR__
|
||||||
|
#include "include/nonstd_functional.hpp"
|
||||||
|
using nonstd::function;
|
||||||
|
#else /* ndef __AVR__ */
|
||||||
|
using std::function;
|
||||||
|
#endif
|
||||||
|
#else /* ndef ARDUINO */
|
||||||
|
using std::function;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace simplesync {
|
||||||
|
|
||||||
|
enum error_t {
|
||||||
|
BUFFER_OVERFLOW = (-1),
|
||||||
|
INVALID_COMMAND = (-2),
|
||||||
|
ID_INVALID = (-3),
|
||||||
|
OUT_OF_MEMORY = (-4),
|
||||||
|
CRC_MISSMATCH = (-5),
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int cobs_encode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out,
|
||||||
|
unsigned int buf_out_size) {
|
||||||
|
unsigned int i_out = 1;
|
||||||
|
unsigned int last0 = 0;
|
||||||
|
for (unsigned int i_in = 0; i_in < buf_in_size; i_in += 1, i_out += 1) {
|
||||||
|
if (i_out >= buf_out_size) return OUT_OF_MEMORY;
|
||||||
|
if ((buf_in[i_in] != 0x00) && ((i_out - last0) < 0xFF)) {
|
||||||
|
buf_out[i_out] = buf_in[i_in];
|
||||||
|
} else {
|
||||||
|
buf_out[last0] = i_out - last0;
|
||||||
|
last0 = i_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf_out[last0] = i_out - last0;
|
||||||
|
buf_out[i_out] = 0x00;
|
||||||
|
return i_out + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int cobs_decode(uint8_t *buf_in, unsigned int &buf_in_size_used, uint8_t *buf_out,
|
||||||
|
unsigned int &buf_out_size_used) {
|
||||||
|
unsigned int i_out = 0;
|
||||||
|
unsigned int next0 = 0;
|
||||||
|
bool skip_next0 = true;
|
||||||
|
unsigned int i_in;
|
||||||
|
for (i_in = 0; i_in < buf_in_size_used || buf_in[i_in] == 0x00; i_in += 1) {
|
||||||
|
if (i_out >= buf_out_size_used) {
|
||||||
|
buf_out_size_used = i_out;
|
||||||
|
buf_in_size_used = i_in;
|
||||||
|
return OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
if (next0 == i_in) {
|
||||||
|
if (buf_in[i_in] == 0x00) break;
|
||||||
|
if (!skip_next0) buf_out[i_out++] = 0x00;
|
||||||
|
next0 = i_in + buf_in[i_in];
|
||||||
|
skip_next0 = (next0 - i_in) == 0xFF;
|
||||||
|
} else {
|
||||||
|
buf_out[i_out++] = buf_in[i_in];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf_out_size_used = i_out;
|
||||||
|
buf_in_size_used = i_in;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int encode_zigzag(int input) {
|
||||||
|
return (input << 1) ^ (input >> (sizeof(int) * 8 - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_zigzag(int input) { return (input >> 1) ^ (-(input & 1)); }
|
||||||
|
|
||||||
|
inline unsigned int encode_unsigned_varint(unsigned int input, uint8_t *out) {
|
||||||
|
int offset = 0;
|
||||||
|
while (input > 0x7F) {
|
||||||
|
out[offset++] = input & 0x7F;
|
||||||
|
input >>= 7;
|
||||||
|
}
|
||||||
|
out[offset++] = input | 0x80;
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int encode_varint(int input, uint8_t *out) {
|
||||||
|
return encode_unsigned_varint(encode_zigzag(input), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_unsigned_varint(uint8_t *input, unsigned int &out) {
|
||||||
|
int offset = 0;
|
||||||
|
out = 0;
|
||||||
|
for (;; offset += 1) {
|
||||||
|
out |= ((unsigned int)(input[offset] & 0x7F)) << (7 * offset);
|
||||||
|
if (input[offset] & 0x80) break;
|
||||||
|
}
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_varint(uint8_t *input, int &out) {
|
||||||
|
unsigned int out_uint;
|
||||||
|
int offset = decode_unsigned_varint(input, out_uint);
|
||||||
|
out = decode_zigzag(out_uint);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t calc_fletcher_checksum(uint8_t *buf, unsigned int buf_size) {
|
||||||
|
uint32_t c0, c1;
|
||||||
|
for (c0 = c1 = 0; buf_size > 0;) {
|
||||||
|
size_t blocklen = buf_size;
|
||||||
|
if (blocklen > 5802) blocklen = 5802;
|
||||||
|
buf_size -= blocklen;
|
||||||
|
do {
|
||||||
|
c0 = c0 + *buf++;
|
||||||
|
c1 = c1 + c0;
|
||||||
|
} while (--blocklen);
|
||||||
|
c0 = c0 % 255;
|
||||||
|
c1 = c1 % 255;
|
||||||
|
}
|
||||||
|
return (c1 << 8 | c0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename time_t, typename timedelta_t, unsigned int BUF_SIZE = 256,
|
||||||
|
unsigned int str_len_max = 32>
|
||||||
|
class SimpleSync {
|
||||||
|
public:
|
||||||
|
static constexpr unsigned int STR_LEN_MAX = str_len_max;
|
||||||
|
class NumberInterface {
|
||||||
|
public:
|
||||||
|
time_t last_send;
|
||||||
|
char *key;
|
||||||
|
timedelta_t update_interval;
|
||||||
|
bool send_requested = true;
|
||||||
|
int value;
|
||||||
|
NumberInterface(SimpleSync &s, char *key, timedelta_t update_interval)
|
||||||
|
: key(key), update_interval(update_interval) {
|
||||||
|
s.number_interfaces.push_back(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringInterface {
|
||||||
|
public:
|
||||||
|
time_t last_send;
|
||||||
|
char *key;
|
||||||
|
timedelta_t update_interval;
|
||||||
|
bool send_requested = true;
|
||||||
|
char value[str_len_max + 1];
|
||||||
|
StringInterface(SimpleSync &s, char *key, timedelta_t update_interval)
|
||||||
|
: key(key), update_interval(update_interval) {
|
||||||
|
value[str_len_max] = 0x00;
|
||||||
|
s.string_interfaces.push_back(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<NumberInterface *> number_interfaces;
|
||||||
|
std::vector<StringInterface *> string_interfaces;
|
||||||
|
|
||||||
|
function<int(uint8_t *, unsigned int)> write_pkg;
|
||||||
|
function<void(NumberInterface *&, char *, int)> number_update;
|
||||||
|
function<void(StringInterface *&, char *, char *)> string_update;
|
||||||
|
SimpleSync(function<int(uint8_t *, unsigned int)> write_pkg,
|
||||||
|
function<void(NumberInterface *&, char *, int)> number_update,
|
||||||
|
function<void(StringInterface *&, char *, char *)> string_update)
|
||||||
|
: write_pkg(write_pkg), number_update(number_update), string_update(string_update) {}
|
||||||
|
|
||||||
|
int parse_pkg(uint8_t *buf, unsigned int buf_size) {
|
||||||
|
if (buf_size < 4) return CRC_MISSMATCH;
|
||||||
|
uint16_t crc = calc_fletcher_checksum(buf, buf_size - 2);
|
||||||
|
if (buf[--buf_size] != (crc & 0xFF) || (buf[--buf_size] != (crc >> 8)))
|
||||||
|
return CRC_MISSMATCH;
|
||||||
|
for (unsigned int parsed = 0; parsed < buf_size;) {
|
||||||
|
switch (buf[parsed]) {
|
||||||
|
case 0x00: {
|
||||||
|
for (auto ni : number_interfaces) ni->send_requested = true;
|
||||||
|
for (auto ni : string_interfaces) ni->send_requested = true;
|
||||||
|
} break;
|
||||||
|
case 0x01: {
|
||||||
|
char *key = (char *)&buf[parsed + 1];
|
||||||
|
parsed += 1 + strlen(key) + 1;
|
||||||
|
int value;
|
||||||
|
parsed += decode_varint(&buf[parsed], value);
|
||||||
|
NumberInterface *ni_ptr = nullptr;
|
||||||
|
for (auto ni : number_interfaces) {
|
||||||
|
if (strcmp(ni->key, key) == 0) {
|
||||||
|
ni_ptr = ni;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (number_update) {
|
||||||
|
number_update(ni_ptr, key, value);
|
||||||
|
}
|
||||||
|
if (ni_ptr) {
|
||||||
|
ni_ptr->value = value;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case 0x02: {
|
||||||
|
char *key = (char *)&buf[parsed + 1];
|
||||||
|
parsed += 1 + strlen(key) + 1;
|
||||||
|
char *value = (char *)&buf[parsed];
|
||||||
|
unsigned int strl = strlen(value) + 1;
|
||||||
|
parsed += strl;
|
||||||
|
StringInterface *stri_ptr = nullptr;
|
||||||
|
for (StringInterface *si : string_interfaces) {
|
||||||
|
if (strcmp(si->key, key) == 0) {
|
||||||
|
stri_ptr = si;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (string_update) {
|
||||||
|
string_update(stri_ptr, key, value);
|
||||||
|
}
|
||||||
|
if (stri_ptr) {
|
||||||
|
memcpy(stri_ptr->value, value, str_len_max > strl ? strl : str_len_max);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
// Error: Unknown datatype, ignore rest of the packet
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_all_interfaces() const {
|
||||||
|
uint8_t buf[] = {0x00, 0x00};
|
||||||
|
unsigned int buf_len_used = sizeof(buf);
|
||||||
|
uint8_t cobs_buf[10];
|
||||||
|
unsigned int cobs_buf_needed = cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE);
|
||||||
|
write_pkg(cobs_buf, cobs_buf_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(time_t time) {
|
||||||
|
uint8_t buf[BUF_SIZE];
|
||||||
|
unsigned int buf_len_used = 0;
|
||||||
|
for (auto ni : number_interfaces) {
|
||||||
|
if (ni->send_requested ||
|
||||||
|
(ni->last_send > time || ni->last_send <= time - ni->update_interval)) {
|
||||||
|
unsigned int strl = strlen(ni->key) + 1;
|
||||||
|
unsigned int buf_len_needed = 1 + strl + 8 * sizeof(int) / 7;
|
||||||
|
buf_len_needed += buf_len_needed / 0xFF + 3 + 2; // COBS overhead
|
||||||
|
if (BUF_SIZE < buf_len_needed) continue; // Error: update to large, skip interface
|
||||||
|
if (BUF_SIZE < buf_len_used + buf_len_needed) {
|
||||||
|
uint8_t cobs_buf[BUF_SIZE];
|
||||||
|
uint16_t crc = calc_fletcher_checksum(buf, buf_len_used);
|
||||||
|
buf[buf_len_used++] = crc >> 8;
|
||||||
|
buf[buf_len_used++] = crc & 0xFF;
|
||||||
|
unsigned int cobs_buf_needed =
|
||||||
|
cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE);
|
||||||
|
write_pkg(cobs_buf, cobs_buf_needed);
|
||||||
|
buf_len_used = 0;
|
||||||
|
}
|
||||||
|
buf[buf_len_used++] = 0x01;
|
||||||
|
memcpy((char *)&buf[buf_len_used], ni->key, strl);
|
||||||
|
buf_len_used += strl;
|
||||||
|
buf_len_used += encode_varint(ni->value, &buf[buf_len_used]);
|
||||||
|
ni->last_send = time;
|
||||||
|
ni->send_requested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto ni : string_interfaces) {
|
||||||
|
if (ni->send_requested ||
|
||||||
|
(ni->last_send > time || ni->last_send <= time - ni->update_interval)) {
|
||||||
|
unsigned int strl = strlen(ni->key) + 1;
|
||||||
|
unsigned int strl_value = strlen(ni->value) + 1;
|
||||||
|
unsigned int buf_len_needed = 1 + strl + strl_value;
|
||||||
|
buf_len_needed += buf_len_needed / 0xFF + 3 + 2; // COBS overhead
|
||||||
|
if (BUF_SIZE < buf_len_needed) continue; // Error: update to large, skip interface
|
||||||
|
if (BUF_SIZE < buf_len_used + buf_len_needed + 2) {
|
||||||
|
uint8_t cobs_buf[BUF_SIZE];
|
||||||
|
uint16_t crc = calc_fletcher_checksum(buf, buf_len_used);
|
||||||
|
buf[buf_len_used++] = crc >> 8;
|
||||||
|
buf[buf_len_used++] = crc & 0xFF;
|
||||||
|
unsigned int cobs_buf_needed =
|
||||||
|
cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE);
|
||||||
|
write_pkg(cobs_buf, cobs_buf_needed);
|
||||||
|
buf_len_used = 0;
|
||||||
|
}
|
||||||
|
buf[buf_len_used++] = 0x02;
|
||||||
|
memcpy((char *)&buf[buf_len_used], ni->key, strl);
|
||||||
|
buf_len_used += strl;
|
||||||
|
memcpy((char *)&buf[buf_len_used], ni->value, strl_value);
|
||||||
|
buf_len_used += strl_value;
|
||||||
|
ni->last_send = time;
|
||||||
|
ni->send_requested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buf_len_used == 0) return;
|
||||||
|
uint8_t cobs_buf[BUF_SIZE];
|
||||||
|
uint16_t crc = calc_fletcher_checksum(buf, buf_len_used);
|
||||||
|
buf[buf_len_used++] = crc >> 8;
|
||||||
|
buf[buf_len_used++] = crc & 0xFF;
|
||||||
|
unsigned int cobs_buf_needed = cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE);
|
||||||
|
write_pkg(cobs_buf, cobs_buf_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_stream_buf(uint8_t *buf, unsigned int buf_size) {
|
||||||
|
int parsed = 0;
|
||||||
|
for (unsigned int i = 0; i < buf_size; i += 1) {
|
||||||
|
if (buf[i] == 0x00) {
|
||||||
|
unsigned int buf_used = buf_size - parsed;
|
||||||
|
uint8_t packet_buf[BUF_SIZE];
|
||||||
|
unsigned int packed_buf_used = sizeof(packet_buf);
|
||||||
|
if (cobs_decode(buf + parsed, buf_used, packet_buf, packed_buf_used) == 0) {
|
||||||
|
parse_pkg(packet_buf, packed_buf_used);
|
||||||
|
}
|
||||||
|
parsed = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t stream_buf[BUF_SIZE];
|
||||||
|
unsigned int stream_buf_used = 0;
|
||||||
|
void handle_stream_buf(unsigned int buf_new_size) {
|
||||||
|
for (int i = stream_buf_used - buf_new_size; i < (int)stream_buf_used; i += 1) {
|
||||||
|
if (stream_buf[i] == 0x00) {
|
||||||
|
unsigned int stream_buf_pkg_delimiter = i;
|
||||||
|
uint8_t packet_buf[BUF_SIZE];
|
||||||
|
unsigned int packed_buf_used = sizeof(packet_buf);
|
||||||
|
if (cobs_decode(stream_buf, stream_buf_pkg_delimiter, packet_buf,
|
||||||
|
packed_buf_used) == 0) {
|
||||||
|
parse_pkg(packet_buf, packed_buf_used);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
memmove(stream_buf, stream_buf + i, stream_buf_used - i);
|
||||||
|
stream_buf_used -= i;
|
||||||
|
i = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
int handle_stream(const uint8_t *buf_new, unsigned int buf_new_size) {
|
||||||
|
if (buf_new_size + stream_buf_used >= BUF_SIZE) {
|
||||||
|
/* ERROR: buffer overflow -> discared data */
|
||||||
|
stream_buf_used = 0;
|
||||||
|
return BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
memcpy(stream_buf + stream_buf_used, buf_new, buf_new_size);
|
||||||
|
stream_buf_used += buf_new_size;
|
||||||
|
handle_stream_buf(buf_new_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberInterface *get_number_if(const char *const key) const {
|
||||||
|
for (const auto ni : number_interfaces) {
|
||||||
|
if (strcmp(ni->key, key) == 0) return ni;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringInterface *get_string_if(const char *const key) const {
|
||||||
|
for (const auto ni : string_interfaces) {
|
||||||
|
if (strcmp(ni->key, key) == 0) return ni;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
SimpleSync(Print *ostream, function<void(NumberInterface *&, char *, int)> number_update,
|
||||||
|
function<void(StringInterface *&, char *, char *)> string_update)
|
||||||
|
: write_pkg([ostream](uint8_t *buf, unsigned int buf_size) {
|
||||||
|
if (ostream) {
|
||||||
|
ostream->write(buf, buf_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}),
|
||||||
|
number_update(number_update), string_update(string_update) {}
|
||||||
|
int handle_stream(Stream &in_stream) {
|
||||||
|
unsigned int buf_new_size = in_stream.available();
|
||||||
|
if (buf_new_size + stream_buf_used > BUF_SIZE) {
|
||||||
|
/* ERROR: buffer overflow -> discared data */
|
||||||
|
stream_buf_used = 0;
|
||||||
|
return BUFFER_OVERFLOW;
|
||||||
|
}
|
||||||
|
in_stream.readBytes(stream_buf + stream_buf_used, buf_new_size);
|
||||||
|
stream_buf_used += buf_new_size;
|
||||||
|
handle_stream_buf(buf_new_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename time_t, typename timedelta_t, unsigned int buf_len = 256,
|
||||||
|
unsigned int str_len_max = 32>
|
||||||
|
class SimpleSyncStatic : public SimpleSync<time_t, timedelta_t, buf_len, str_len_max> {
|
||||||
|
public:
|
||||||
|
SimpleSyncStatic(function<int(uint8_t *, unsigned int)> write_pkg)
|
||||||
|
: SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(write_pkg, nullptr, nullptr){};
|
||||||
|
#ifdef ARDUINO
|
||||||
|
SimpleSyncStatic(Print *ostream)
|
||||||
|
: SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(ostream, nullptr, nullptr) {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
template <typename time_t, typename timedelta_t, unsigned int buf_len = 256,
|
||||||
|
unsigned int str_len_max = 32>
|
||||||
|
class SimpleSyncDynamic : public SimpleSync<time_t, timedelta_t, buf_len, str_len_max> {
|
||||||
|
std::vector<typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface *>
|
||||||
|
dyn_number_interfaces;
|
||||||
|
std::vector<typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface *>
|
||||||
|
dyn_string_interfaces;
|
||||||
|
std::vector<char *> dyn_keys;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimpleSyncDynamic(
|
||||||
|
function<int(uint8_t *, unsigned int)> write_pkg,
|
||||||
|
function<
|
||||||
|
void(typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface *&,
|
||||||
|
char *, int)>
|
||||||
|
dyn_number_update,
|
||||||
|
function<
|
||||||
|
void(typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface *&,
|
||||||
|
char *, char *)>
|
||||||
|
dyn_string_update,
|
||||||
|
timedelta_t dyn_update_feq)
|
||||||
|
: SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(
|
||||||
|
write_pkg,
|
||||||
|
[this, dyn_number_update, dyn_update_feq](
|
||||||
|
typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface *
|
||||||
|
&ni,
|
||||||
|
char *key, int value) {
|
||||||
|
if (ni == nullptr) {
|
||||||
|
auto allocated_key = (char *)malloc(strlen(key) + 1);
|
||||||
|
strcpy(allocated_key, key);
|
||||||
|
dyn_keys.push_back(allocated_key);
|
||||||
|
ni = new SimpleSync<time_t, timedelta_t, buf_len,
|
||||||
|
str_len_max>::NumberInterface(*this, allocated_key,
|
||||||
|
dyn_update_feq);
|
||||||
|
ni->send_requested = false;
|
||||||
|
dyn_number_interfaces.push_back(ni);
|
||||||
|
}
|
||||||
|
dyn_number_update(ni, key, value);
|
||||||
|
},
|
||||||
|
[this, dyn_string_update, dyn_update_feq](
|
||||||
|
typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface *
|
||||||
|
&ni,
|
||||||
|
char *key, char *value) {
|
||||||
|
if (ni == nullptr) {
|
||||||
|
auto allocated_key = (char *)malloc(strlen(key) + 1);
|
||||||
|
strcpy(allocated_key, key);
|
||||||
|
dyn_keys.push_back(allocated_key);
|
||||||
|
ni = new SimpleSync<time_t, timedelta_t, buf_len,
|
||||||
|
str_len_max>::StringInterface(*this, allocated_key,
|
||||||
|
dyn_update_feq);
|
||||||
|
ni->send_requested = false;
|
||||||
|
dyn_string_interfaces.push_back(ni);
|
||||||
|
}
|
||||||
|
dyn_string_update(ni, key, value);
|
||||||
|
}){};
|
||||||
|
~SimpleSyncDynamic() {
|
||||||
|
for (auto i : dyn_number_interfaces) delete i;
|
||||||
|
for (auto i : dyn_string_interfaces) delete i;
|
||||||
|
for (auto i : dyn_keys) free(i);
|
||||||
|
};
|
||||||
|
#ifdef ARDUINO
|
||||||
|
SimpleSyncDynamic(Print *ostream)
|
||||||
|
: SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(ostream, nullptr, nullptr) {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
} // namespace simplesync
|
||||||
|
#endif // SIMPLESYNC_HPP
|
29
library.json
Normal file
29
library.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "SimpleSync",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "simple messaging protocol",
|
||||||
|
"repository":
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.nilsschulte.de/nils/simplesync.git"
|
||||||
|
},
|
||||||
|
"authors":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Nils Schulte",
|
||||||
|
"email": "git@nilsschulte.de",
|
||||||
|
"url": "https://nilsschulte.de/",
|
||||||
|
"maintainer": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "EUPL-1.2",
|
||||||
|
"homepage": "https://git.nilsschulte.de/nils/simplesync/",
|
||||||
|
"build": {
|
||||||
|
"srcFilter": [
|
||||||
|
"-<python_module.cpp>",
|
||||||
|
"-<test>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"frameworks": "*",
|
||||||
|
"platforms": "*"
|
||||||
|
}
|
83
src/python_module.cpp
Normal file
83
src/python_module.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <pybind11/cast.h>
|
||||||
|
#include <pybind11/chrono.h>
|
||||||
|
#include <pybind11/functional.h>
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
|
#include "simplesync.hpp"
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
typedef simplesync::SimpleSyncDynamic<std::chrono::time_point<std::chrono::steady_clock>,
|
||||||
|
std::chrono::nanoseconds, 1024>
|
||||||
|
SimpleS;
|
||||||
|
|
||||||
|
PYBIND11_MODULE(pysimplesync, m) {
|
||||||
|
py::class_<SimpleS>(m, "SimpleSync")
|
||||||
|
.def(py::init([]() {
|
||||||
|
return std::unique_ptr<SimpleS>(new SimpleS(
|
||||||
|
[](uint8_t *buf, unsigned int buf_size) {
|
||||||
|
py::print(py::bytes((char *)buf, buf_size));
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
[](SimpleS::NumberInterface *&, char *key, int value) {
|
||||||
|
py::print(std::string(key), ": ", value);
|
||||||
|
},
|
||||||
|
nullptr, std::chrono::nanoseconds::max()));
|
||||||
|
}))
|
||||||
|
.def(
|
||||||
|
"__getitem__",
|
||||||
|
[](SimpleS &s, const std::string &key) -> py::object {
|
||||||
|
auto ni = s.get_number_if(key.c_str());
|
||||||
|
if (ni) return py::int_(ni->value);
|
||||||
|
auto si = s.get_string_if(key.c_str());
|
||||||
|
if (si) return py::str(si->value);
|
||||||
|
throw pybind11::key_error();
|
||||||
|
},
|
||||||
|
py::is_operator())
|
||||||
|
.def(
|
||||||
|
"__getitem__",
|
||||||
|
[](SimpleS &s, unsigned int i) -> py::tuple {
|
||||||
|
if (i < s.number_interfaces.size()) {
|
||||||
|
return py::make_tuple(py::str(s.number_interfaces[i]->key),
|
||||||
|
py::int_(s.number_interfaces[i]->value));
|
||||||
|
}
|
||||||
|
i -= s.number_interfaces.size();
|
||||||
|
if (i < s.string_interfaces.size()) {
|
||||||
|
return py::make_tuple(py::str(s.string_interfaces[i]->key),
|
||||||
|
py::str(s.string_interfaces[i]->value));
|
||||||
|
}
|
||||||
|
throw pybind11::index_error();
|
||||||
|
},
|
||||||
|
py::is_operator())
|
||||||
|
.def(
|
||||||
|
"__setitem__",
|
||||||
|
[](SimpleS &s, const std::string &key, py::object value) {
|
||||||
|
if (py::isinstance<py::int_>(value)) {
|
||||||
|
auto ni = s.get_number_if(key.c_str());
|
||||||
|
if (ni) {
|
||||||
|
ni->value = value.cast<int>();
|
||||||
|
ni->send_requested = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (py::isinstance<py::str>(value)) {
|
||||||
|
auto ni = s.get_string_if(key.c_str());
|
||||||
|
if (ni) {
|
||||||
|
if(value.cast<std::string>().length() > SimpleS::STR_LEN_MAX)
|
||||||
|
throw pybind11::buffer_error();
|
||||||
|
strcpy(ni->value, value.cast<std::string>().c_str());
|
||||||
|
ni->send_requested = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw pybind11::type_error();
|
||||||
|
throw pybind11::key_error();
|
||||||
|
},
|
||||||
|
py::is_operator())
|
||||||
|
.def("update", [](SimpleS &s) { s.update(std::chrono::steady_clock::now()); })
|
||||||
|
.def("handle_stream", [](SimpleS &s, const std::string &buf_new) {
|
||||||
|
/* handle_stream(b'\x06\x01\x74\x65\x73\x74\x04\x6d\x21\x97\x00') */
|
||||||
|
s.handle_stream((const uint8_t *)buf_new.data(), buf_new.length());
|
||||||
|
});
|
||||||
|
}
|
@ -1,326 +0,0 @@
|
|||||||
#ifndef SIMPLESYNC_HPP
|
|
||||||
#define SIMPLESYNC_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace simplesync {
|
|
||||||
typedef uint32_t id_t;
|
|
||||||
enum error_t {
|
|
||||||
BUFFER_OVERFLOW = (-1),
|
|
||||||
INVALID_COMMAND = (-2),
|
|
||||||
ID_INVALID = (-3),
|
|
||||||
OUT_OF_MEMORY = (-4),
|
|
||||||
};
|
|
||||||
enum command_t {
|
|
||||||
DESCRIBE_IF = 0,
|
|
||||||
UPDATE_DESCTIPTION = 1,
|
|
||||||
REQUEST_DATA = 2,
|
|
||||||
UPDATE_DATA = 3,
|
|
||||||
};
|
|
||||||
enum data_type_t {
|
|
||||||
BOOL = 0,
|
|
||||||
STATE = 1,
|
|
||||||
NUMBER = 2,
|
|
||||||
UNIT = 3,
|
|
||||||
STRING = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
inline unsigned int encode_zigzag(int input) {
|
|
||||||
return (input << 1) ^ (input >> (sizeof(int) * 8 - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned int decode_zigzag(int input) {
|
|
||||||
return (input >> 1) ^ (-(input & 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned int encode_unsigned_varint(unsigned int input, uint8_t *out) {
|
|
||||||
int offset = 0;
|
|
||||||
while (input > 0x7F) {
|
|
||||||
out[offset++] = input & 0x7F;
|
|
||||||
input >>= 7;
|
|
||||||
}
|
|
||||||
out[offset++] = input | 0x80;
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned int encode_varint(int input, uint8_t *out) {
|
|
||||||
return encode_unsigned_varint(encode_zigzag(input), out);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned int decode_unsigned_varint(uint8_t *input, unsigned int &out) {
|
|
||||||
int offset = 0;
|
|
||||||
out = 0;
|
|
||||||
for (;; offset += 1) {
|
|
||||||
out |= ((unsigned int)(input[offset] & 0x7F)) << (7 * offset);
|
|
||||||
if (input[offset] & 0x80)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return offset + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned int decode_varint(uint8_t *input, int &out) {
|
|
||||||
unsigned int out_uint;
|
|
||||||
int offset = decode_unsigned_varint(input, out_uint);
|
|
||||||
out = decode_zigzag(out_uint);
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SimpleSync;
|
|
||||||
class Interface {
|
|
||||||
public:
|
|
||||||
bool sync_requested = false;
|
|
||||||
bool description_requested = false;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend class SimpleSync;
|
|
||||||
Interface *next = nullptr;
|
|
||||||
char *name;
|
|
||||||
bool allocated_name = false;
|
|
||||||
id_t idx;
|
|
||||||
data_type_t data_type;
|
|
||||||
Interface(const char *name, data_type_t data_type, SimpleSync &sync);
|
|
||||||
int describe(uint8_t *buf, unsigned int buf_size) {
|
|
||||||
int offset = 0;
|
|
||||||
offset += encode_unsigned_varint(idx, buf + offset);
|
|
||||||
offset += encode_unsigned_varint(data_type, buf + offset);
|
|
||||||
unsigned int name_len = strlen(name);
|
|
||||||
offset += encode_unsigned_varint(name_len, buf + offset);
|
|
||||||
if (offset + name_len >= buf_size)
|
|
||||||
return BUFFER_OVERFLOW;
|
|
||||||
memcpy(buf + offset, name, name_len);
|
|
||||||
offset += name_len;
|
|
||||||
return offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
int parse_description(uint8_t *buf) {
|
|
||||||
int offset = 0;
|
|
||||||
offset += decode_unsigned_varint(buf + offset, idx);
|
|
||||||
|
|
||||||
unsigned int data_type_int;
|
|
||||||
offset += decode_unsigned_varint(buf + offset, data_type_int);
|
|
||||||
data_type = data_type_t(data_type_int);
|
|
||||||
|
|
||||||
unsigned int name_len;
|
|
||||||
offset += decode_unsigned_varint(buf + offset, name_len);
|
|
||||||
name = (char *)realloc((void *)name, name_len);
|
|
||||||
if (name == nullptr)
|
|
||||||
return OUT_OF_MEMORY;
|
|
||||||
allocated_name = true;
|
|
||||||
memcpy(name, buf + offset, name_len);
|
|
||||||
offset += name_len;
|
|
||||||
return offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual ~Interface() { free(name); };
|
|
||||||
|
|
||||||
public:
|
|
||||||
// static int parse_command_type(uint8_t* buf, command_t& command, unsigned
|
|
||||||
// int& data_length) {
|
|
||||||
// command = command_t(buf[0]);
|
|
||||||
// int command_size = 1;
|
|
||||||
// switch (command_t(buf[0])) {
|
|
||||||
// case (STATE):
|
|
||||||
// case (STRING):
|
|
||||||
// case (INTERFACE):
|
|
||||||
// command_size += sizeof(uint32_t);
|
|
||||||
// memcpy(&data_length, &buf[1], sizeof(uint32_t)); // FIXME():
|
|
||||||
// handle endianess break;
|
|
||||||
// case (REQUEST_IF):
|
|
||||||
// data_length = 0;
|
|
||||||
// break;
|
|
||||||
// case (BOOLEAN):
|
|
||||||
// data_length = 1;
|
|
||||||
// break;
|
|
||||||
// case (NUMBER32):
|
|
||||||
// data_length = 4;
|
|
||||||
// break;
|
|
||||||
// case (NUMBER64):
|
|
||||||
// data_length = 8;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// return command_size;
|
|
||||||
// switch (command) {
|
|
||||||
// case(DESCRIBE_IF):
|
|
||||||
// break;
|
|
||||||
// case(UPDATE_DATA):
|
|
||||||
// break;
|
|
||||||
// case(REQUEST_DATA):
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
virtual int update(uint8_t *buf) = 0;
|
|
||||||
virtual int write_update(uint8_t *buf, unsigned int buf_size) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NumberPointerInterface : public Interface {
|
|
||||||
int *pointer = nullptr;
|
|
||||||
|
|
||||||
public:
|
|
||||||
NumberPointerInterface(const char *name, int *pointer, SimpleSync &sync)
|
|
||||||
: Interface(name, data_type_t::NUMBER, sync), pointer(pointer){};
|
|
||||||
virtual int update(uint8_t *buf) { return decode_varint(buf, *pointer); };
|
|
||||||
virtual int write_update(uint8_t *buf, unsigned int buf_size) {
|
|
||||||
if (buf_size < sizeof(int) * 8 / 7)
|
|
||||||
return BUFFER_OVERFLOW;
|
|
||||||
return encode_varint(*pointer, buf);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
inline int parse_command(uint8_t *buf, command_t &command) {
|
|
||||||
command = command_t(buf[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int write_command(uint8_t *buf, command_t command) {
|
|
||||||
buf[0] = (uint8_t)command;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SimpleSync {
|
|
||||||
friend class Interface;
|
|
||||||
Interface *first = nullptr;
|
|
||||||
|
|
||||||
public:
|
|
||||||
unsigned int num_interfaces() {
|
|
||||||
unsigned int ret = 0;
|
|
||||||
for (Interface *c = first; c != nullptr; c = c->next)
|
|
||||||
ret += 1;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
int request_interface_descriptions(uint8_t *buf) {
|
|
||||||
buf[0] = command_t::DESCRIBE_IF;
|
|
||||||
buf[1] = 0;
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int update(uint8_t *buf_in, unsigned int buf_size) {
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
command_t command;
|
|
||||||
offset += parse_command(&buf_in[offset], command);
|
|
||||||
id_t if_id;
|
|
||||||
offset += decode_unsigned_varint(&buf_in[offset], if_id);
|
|
||||||
if ((unsigned int)offset >= buf_size)
|
|
||||||
return BUFFER_OVERFLOW;
|
|
||||||
if (if_id == 0) {
|
|
||||||
if (command == command_t::DESCRIBE_IF) {
|
|
||||||
for (Interface *current = first; current->next != nullptr;
|
|
||||||
current = current->next) {
|
|
||||||
current->description_requested = true;
|
|
||||||
}
|
|
||||||
} else if (command == command_t::REQUEST_DATA) {
|
|
||||||
for (Interface *current = first; current->next != nullptr;
|
|
||||||
current = current->next) {
|
|
||||||
current->sync_requested = true;
|
|
||||||
}
|
|
||||||
} else if (command == command_t::UPDATE_DATA) {
|
|
||||||
return ID_INVALID;
|
|
||||||
} else {
|
|
||||||
return INVALID_COMMAND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command == command_t::DESCRIBE_IF) {
|
|
||||||
for (Interface *current = first; current->next != nullptr;
|
|
||||||
current = current->next) {
|
|
||||||
if (if_id == current->idx) {
|
|
||||||
current->description_requested = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (command == command_t::REQUEST_DATA) {
|
|
||||||
for (Interface *current = first; current->next != nullptr;
|
|
||||||
current = current->next) {
|
|
||||||
if (if_id == current->idx) {
|
|
||||||
current->sync_requested = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (command == command_t::UPDATE_DATA) {
|
|
||||||
for (Interface *current = first;; current = current->next) {
|
|
||||||
if (current == nullptr)
|
|
||||||
return ID_INVALID;
|
|
||||||
|
|
||||||
if (if_id == current->idx) {
|
|
||||||
int data_offset = current->update(&buf_in[offset]);
|
|
||||||
if (data_offset < 0) {
|
|
||||||
return BUFFER_OVERFLOW;
|
|
||||||
}
|
|
||||||
offset += data_offset;
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (command == command_t::UPDATE_DESCTIPTION) {
|
|
||||||
for (Interface *current = first;; current = current->next) {
|
|
||||||
if (current == nullptr) {
|
|
||||||
// FIXME new description
|
|
||||||
return ID_INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (if_id == current->idx) {
|
|
||||||
int data_offset = current->parse_description(&buf_in[offset]);
|
|
||||||
if (data_offset < 0) {
|
|
||||||
return BUFFER_OVERFLOW;
|
|
||||||
}
|
|
||||||
offset += data_offset;
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
int generate_message(uint8_t *buf, unsigned int buf_size) {
|
|
||||||
if (first == nullptr)
|
|
||||||
return 0;
|
|
||||||
int offset = 0;
|
|
||||||
int new_offset = 0;
|
|
||||||
for (Interface *current = first; current != nullptr;
|
|
||||||
current = current->next) {
|
|
||||||
id_t if_id = current->idx;
|
|
||||||
|
|
||||||
if (current->description_requested) {
|
|
||||||
offset += write_command(buf + offset, UPDATE_DESCTIPTION);
|
|
||||||
offset += encode_unsigned_varint(if_id, buf + offset);
|
|
||||||
new_offset = current->describe(buf + offset, buf_size - offset);
|
|
||||||
} else if (current->sync_requested) {
|
|
||||||
offset += write_command(buf + offset, UPDATE_DATA);
|
|
||||||
offset += encode_unsigned_varint(if_id, buf + offset);
|
|
||||||
new_offset = current->write_update(buf + offset, buf_size - offset);
|
|
||||||
}
|
|
||||||
if (new_offset > 0) {
|
|
||||||
offset += new_offset;
|
|
||||||
if (current->description_requested) {
|
|
||||||
current->description_requested = false;
|
|
||||||
} else if (current->sync_requested) {
|
|
||||||
current->sync_requested = false;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
} else {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline Interface::Interface(const char *name, const data_type_t data_type,
|
|
||||||
SimpleSync &sync)
|
|
||||||
: name(nullptr), data_type(data_type) {
|
|
||||||
unsigned int name_len = strlen(name);
|
|
||||||
this->name = (char *)realloc((void *)this->name, name_len);
|
|
||||||
memcpy(this->name, name, name_len);
|
|
||||||
idx = sync.num_interfaces() + 16;
|
|
||||||
if (sync.first == nullptr) {
|
|
||||||
sync.first = this;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Interface *last = sync.first;
|
|
||||||
for (; last->next != nullptr; last = last->next)
|
|
||||||
;
|
|
||||||
last->next = this;
|
|
||||||
}
|
|
||||||
} // namespace simplesync
|
|
||||||
#endif // SIMPLESYNC_HPP
|
|
72
src/utils.h
Normal file
72
src/utils.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
|
||||||
|
inline int cobs_encode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out,
|
||||||
|
unsigned int buf_out_size) {
|
||||||
|
unsigned int i_out = 1;
|
||||||
|
unsigned int last0 = 0;
|
||||||
|
for (unsigned int i_in = 0; i_in < buf_in_size; i_in += 1, i_out += 1) {
|
||||||
|
if (i_out >= buf_out_size) return OUT_OF_MEMORY;
|
||||||
|
if ((buf_in[i_in] != 0x00) && ((i_out - last0) < 0xFF)) {
|
||||||
|
buf_out[i_out] = buf_in[i_in];
|
||||||
|
} else {
|
||||||
|
buf_out[last0] = i_out - last0;
|
||||||
|
last0 = i_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf_out[last0] = i_out - last0;
|
||||||
|
buf_out[i_out] = 0x00;
|
||||||
|
return i_out + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int cobs_decode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out,
|
||||||
|
unsigned int buf_out_size) {
|
||||||
|
unsigned int i_out = 0;
|
||||||
|
unsigned int next0 = 0;
|
||||||
|
bool skip_next0 = true;
|
||||||
|
for (unsigned int i_in = 0; i_in < buf_in_size || buf_in[i_in] == 0x00; i_in += 1) {
|
||||||
|
if (i_out >= buf_out_size) return OUT_OF_MEMORY;
|
||||||
|
if (next0 == i_in) {
|
||||||
|
if (!skip_next0) buf_out[i_out++] = 0x00;
|
||||||
|
next0 = buf_in[i_in];
|
||||||
|
skip_next0 = (next0 - i_in) == 0xFF;
|
||||||
|
} else
|
||||||
|
buf_out[i_out++] = buf_in[i_in];
|
||||||
|
}
|
||||||
|
return i_out-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int encode_zigzag(int input) {
|
||||||
|
return (input << 1) ^ (input >> (sizeof(int) * 8 - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_zigzag(int input) { return (input >> 1) ^ (-(input & 1)); }
|
||||||
|
|
||||||
|
inline unsigned int encode_unsigned_varint(unsigned int input, uint8_t *out) {
|
||||||
|
int offset = 0;
|
||||||
|
while (input > 0x7F) {
|
||||||
|
out[offset++] = input & 0x7F;
|
||||||
|
input >>= 7;
|
||||||
|
}
|
||||||
|
out[offset++] = input | 0x80;
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int encode_varint(int input, uint8_t *out) {
|
||||||
|
return encode_unsigned_varint(encode_zigzag(input), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_unsigned_varint(uint8_t *input, unsigned int &out) {
|
||||||
|
int offset = 0;
|
||||||
|
out = 0;
|
||||||
|
for (;; offset += 1) {
|
||||||
|
out |= ((unsigned int)(input[offset] & 0x7F)) << (7 * offset);
|
||||||
|
if (input[offset] & 0x80) break;
|
||||||
|
}
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int decode_varint(uint8_t *input, int &out) {
|
||||||
|
unsigned int out_uint;
|
||||||
|
int offset = decode_unsigned_varint(input, out_uint);
|
||||||
|
out = decode_zigzag(out_uint);
|
||||||
|
return offset;
|
||||||
|
}
|
@ -1,74 +1,81 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "simplesync.hpp"
|
#include "simplesync.hpp"
|
||||||
|
|
||||||
|
|
||||||
TEST(simplesync_test, varint) {
|
TEST(simplesync_test, varint) {
|
||||||
|
|
||||||
uint8_t buf[1024];
|
uint8_t buf[1024];
|
||||||
for (int i = -999999; i < 999999; i += 29) {
|
for (int i = -999999; i < 999999; i += 29) {
|
||||||
int offset = simplesync::encode_varint(i, buf);
|
int offset = simplesync::encode_varint(i, buf);
|
||||||
ASSERT_LE(1, offset);
|
ASSERT_LE(1, offset);
|
||||||
for (int o = 0; o < offset - 1; o += 1)
|
for (int o = 0; o < offset - 1; o += 1)
|
||||||
EXPECT_FALSE(buf[o] & 0x80)
|
EXPECT_FALSE(buf[o] & 0x80) << "offset:" << offset << " " << (uint32_t)(buf[o] & 0x7F);
|
||||||
<< "offset:" << offset << " " << (uint32_t)(buf[o] & 0x7F);
|
EXPECT_TRUE(buf[offset - 1] & 0x80)
|
||||||
EXPECT_TRUE(buf[offset - 1] & 0x80)
|
<< "offset:" << offset << " " << (uint32_t)(buf[offset - 1] & 0x7F);
|
||||||
<< "offset:" << offset << " " << (uint32_t)(buf[offset - 1] & 0x7F);
|
int out;
|
||||||
int out;
|
offset = simplesync::decode_varint(buf, out);
|
||||||
offset = simplesync::decode_varint(buf, out);
|
ASSERT_LE(1, offset);
|
||||||
ASSERT_LE(1, offset);
|
ASSERT_EQ(out, i);
|
||||||
ASSERT_EQ(out, i);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(simplesync_test, encode_decode) {
|
TEST(simplesync_test, cobs_encode) {
|
||||||
|
uint8_t buf_raw[] = {0x11, 0x22, 0x00, 0x33};
|
||||||
|
uint8_t buf_coded[] = {0x03, 0x11, 0x22, 0x02, 0x33, 0x00};
|
||||||
|
|
||||||
uint8_t buf[1024];
|
uint8_t buf[sizeof(buf_coded) + 1];
|
||||||
unsigned int buf_size = sizeof(buf);
|
unsigned int buf_used = simplesync::cobs_encode(buf_raw, sizeof(buf_raw), buf, sizeof(buf));
|
||||||
|
ASSERT_EQ(buf_used, sizeof(buf_coded));
|
||||||
|
ASSERT_EQ(0, memcmp(buf_coded, buf, sizeof(buf_coded)));
|
||||||
|
|
||||||
simplesync::SimpleSync s;
|
unsigned int buf_out_used = sizeof(buf);
|
||||||
|
unsigned int buf_in_used = sizeof(buf_coded);
|
||||||
int number = 42;
|
int error = simplesync::cobs_decode(buf_coded, buf_in_used, buf, buf_out_used);
|
||||||
simplesync::NumberPointerInterface number_if("n1", &number, s);
|
ASSERT_EQ(error, 0);
|
||||||
ASSERT_EQ(s.num_interfaces(), 1);
|
ASSERT_EQ(buf_used, sizeof(buf_coded));
|
||||||
|
ASSERT_EQ(buf_out_used, sizeof(buf_raw));
|
||||||
number_if.sync_requested = true;
|
ASSERT_EQ(0, memcmp(buf_raw, buf, sizeof(buf_raw)));
|
||||||
int buf_used = s.generate_message(buf, buf_size);
|
|
||||||
ASSERT_LE(1, buf_used);
|
|
||||||
|
|
||||||
number = 0;
|
|
||||||
|
|
||||||
int buf_parsed = s.update(buf, buf_used);
|
|
||||||
ASSERT_LE(0, buf_parsed);
|
|
||||||
ASSERT_EQ(buf_parsed, buf_used);
|
|
||||||
ASSERT_EQ(number, 42);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#include <BSONPP.h>
|
uint8_t stream_buf[1024];
|
||||||
|
unsigned int buf_size = sizeof(stream_buf);
|
||||||
|
unsigned int buf_used = 0;
|
||||||
|
|
||||||
TEST(simplesync_test, encode_decode) {
|
TEST(simplesync_test, encode_decode) {
|
||||||
|
buf_used = 0;
|
||||||
|
typedef simplesync::SimpleSyncStatic<int, int, 999> S;
|
||||||
|
S s([](uint8_t *buf, unsigned int buf_size) {
|
||||||
|
memcpy(stream_buf + buf_used, buf, buf_size);
|
||||||
|
buf_used += buf_size;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
const char* test_str_key = "stringKey";
|
S::NumberInterface n1(s, (char *)"n1", 0);
|
||||||
const char* test_str_value = "Just a medium length string";
|
S::StringInterface s1(s, (char *)"s1", 0);
|
||||||
// This is the buffer to be used by the library.
|
S::NumberInterface n2(s, (char *)"n2", 2);
|
||||||
uint8_t buffer[256];
|
|
||||||
BSONPP doc(buffer, sizeof(buffer));
|
|
||||||
doc.append(test_str_key, "Just a medium length string");
|
|
||||||
doc.append("num", 10234234);
|
|
||||||
|
|
||||||
// To read BSON from a buffer pass in the buffer, length and false. By default
|
n1.value = 4;
|
||||||
// when passed a buffer it clears the buffer and initialises a new BSON
|
strcpy(s1.value, "hallo");
|
||||||
// object. Set false to avoid that.
|
n2.value = 2;
|
||||||
BSONPP parsed(buffer, sizeof(buffer), false);
|
|
||||||
|
|
||||||
char *val;
|
for (int time = 0; time < 5; time += 1) {
|
||||||
ASSERT_EQ(BSONPP_SUCCESS, parsed.get(test_str_key, &val));
|
buf_used = 0;
|
||||||
EXPECT_STREQ(val, test_str_value);
|
s.update(time);
|
||||||
|
s.update(time);
|
||||||
// ASSERT_TRUE(strcmp(decoded.str, "Hello") == 0);
|
unsigned int parsed = 0;
|
||||||
}*/
|
for (unsigned int i = 1; i <= buf_used; i += 1)
|
||||||
|
parsed += s.parse_stream_buf(stream_buf + parsed, i - parsed);
|
||||||
|
ASSERT_EQ(parsed, buf_used);
|
||||||
|
}
|
||||||
|
ASSERT_EQ(n1.value, 4);
|
||||||
|
ASSERT_EQ(n2.value, 2);
|
||||||
|
ASSERT_STREQ(s1.value,"hallo");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user