This repository is organised following hexagonal achitecture conventions. This means that the core code of the library (domain) is isolated (inside folder src/ezsp) from the code that allows this library to make use of external resources or library (spi) and from translation code that makes information available from this library to other software (api).
src/ezsp
contains most of the library code (EZSP/ASH encoding/decoding and protocol sequence implementation)src/spi
contains the adapters for platform-specific implementations of timers and serial port accesssrc/spi/serial
contains the concrete implementations of an adapter to read/write on the serial port for Linux and Windows (using libserialcpp)src/spi/cppthreads
contains the concrete implementation of timers using C++11 threadssrc/spi/mock-uart
contains a framework to emulate a serial port for unit testingsrc/spi/raritan
contains the concrete implementations of adapters for the Raritan framework (using selectors and an event-driven main loop)src/spi/console
contains the concrete implementations of a console loggersrc/spi/aes
contains the concrete implementations of a basic AES encryption/decryptionexample
contains the code for a sample demo program to read and report sensor values from a zigbee networktests
contains code for automatic unit testing
SPI connectors implement a concrete implementation that complies to a specified interface declared in interface headers (.h header files which filename starts with a capital I). This allows dependency inversion paradigm, where the connector depends on the library, rather than the library depends on the underlying connectors. Connectors then become interchangeable, and we use this method a lot to abstract the library from the services it uses (a version of these services are using the Raritan framework, another version is independent from the Raritan framework and can run outside of it).
C++ code should be formatted using astyle --style=java --indent=tab --break-closing-brackets
to ensure homogeneous code style and indentation.
This means identation should use tabs, and opening curly braces should be placed at the end of the previous line like most java-formatted code.
-
include/spi/ITimer.h and include/spi/ITimerBuilder.h
ITimer.h: Abstract interface to which must conforms implementations of classes that handle timed callbacks ITimerBuilder.h: Abstract interface to which must conforms implementations of builder classes that generate ITimer-derived objects
Concrete Builder API for each framework:
- include/spi/TimerBuilder.h
- src/spi/TimerBuilder.cpp A TimerBuilder will generate the appropriate ITimer-derived object (for example a RaritanTimer or a CppThreadsTimer) for the platform where it runs, thus avoiding platform-specific code.
Framework implementation: Concrete implementation in the raritan framework:
- src/spi/raritan/RaritanTimer.{h,cpp}
Concrete implementation using C++ threads:
- src/spi/cppthreads/CppThreadsTimer.{h,cpp}
-
include/spi/Logger.h
Generic singleton logger
-
include/spi/ILogger.h
Abstract interface to which must conforms implementations of classes that log debug/warning/error messages
Framework implementation: Concrete implementation in the raritan framework:
- src/spi/raritan/RaritanLogger.{h,cpp}
Concrete implementation that outputs messages on the system console:
- src/spi/console/ConsoleLogger.{h,cpp}
-
include/spi/IAsyncDataInputObserver.h
Abstract interface to which must conforms implementations of classes that get asynchronous notifications on incoming data This is an exception, it is not a DIP implementation, the concrete implementation lies inside the applicative code (inside the library)
-
include/spi/IUartDriver.h and include/spi/IUartDriverBuilder.h
IUartDriver.h: Abstract interface to which must conforms concrete implementations of classes that manipulate UARTs IUartDriverBuilder.h: Abstract interface to which must conforms implementations of builder classes that generate IUartDriver-derived objects
Concrete Builder API for each framework:
- include/spi/UartDriverBuilder.h
- src/spi/UartDriverBuilder.cpp A UartDriverBuilder will generate the appropriate IUartDriver-derived object (for example a RaritanUartDriver or a SerialUartDriver) for the platform where it runs, thus avoiding platform-specific code.
Framework implementation: Concrete implementation in the raritan framework:
- src/spi/raritan/RaritanUartDriver.{h,cpp}
Concrete implementation using libserial:
- src/spi/serial/SerialUartDriver.{h,cpp}
Concrete implementation of a robotized emulated serial port for unit testing:
- src/spi/mock-uart/MockUartDriver.{h,cpp}
-
include/spi/IAes.h
Abstract interface to which must conforms implementations of classes that encrypt/decrypt using AES
Concrete Builder API for each framework:
- include/spi/IAesBuilder.h
- src/spi/IAesBuilder.cpp A IasBuilder will generate the appropriate IAes-derived object (for example a CAes) for the platform where it runs, thus avoiding platform-specific code.
Framework implementation: Concrete implementation using a custom AES code:
- src/spi/aes/custom-aes/custom-aes.{h,cpp}
All dependency on an external service should be implemented as an SPI (Service Provider Interface), as dictated by Hexagonal Architeture.
-
C++11 is supported and C++11 features are to be preferred over home-made equivalent using earlier versions of the standard C++. For example, you should use
std::thread
, constexpr, std::move that is available in C++11. C++14-specific and above (C++17, C++20 etc.) language and STL features should be avoided (see the related Wikipedia page). -
All headers that will be required for compilation by a client code of the library should be placed inside the folder include/. In that folder, we will distinguish between headers for the libezsp itself (domain), located in include/ezsp/ and the includes related to SPIs located in include/spi/.
-
All
#include
directives used inside the headers files located under include/ should be done using named header include (using angle brackets<
and>
). -
All classes that should be accessible from the outside should prefix the class name with the macro LIBEXPORT (defined in include/ezsp/export.h. Most probably, all classes and functions in header files located in under include/ should thus declare themselves using LIBEXPORT.
-
All header files located under include/ should use #ifdef, #define, #endif preprocessor conditions in order not to double-declare content when included more than once (or they could also use
#pragma once
, but this is less portable).
Indentation of the code should match what is enforced by the following command:
astyle --style=java --indent=tab --break-closing-brackets
In order to generate the source code documentation out of the doxygen tags contained inside the code, run the following command:
cd /path/to/libezsp
doxygen Doxyfile.dev
The resulting doc will be available via a browser by opening `~/libezsp/doc/html/index.html
Unit tests are located under tests
, and are grouped by families in common .cpp files.
Unit tests can be run inside the cpputest framework (requires installing cpputest) or as a standalone executable (using the lightweight unit test utilities located in src/tests/TestHarness.h
).
When run as standalone, an executable named test_runner
will be compiled and will contain all the code to run for automated unit testing.
In order to both compile and run unit tests, using -DUSE_CPPTHREADS=ON -DUSE_GCOV=y -DUSE_SERIALCPP=OFF -DUSE_MOCKSERIAL=ON options when running cmake:
cmake -DUSE_CPPTHREADS=ON -DUSE_GCOV=y -DUSE_SERIALCPP=OFF -DUSE_MOCKSERIAL=ON .
make
./tests/gptest
If all tests pass, the above command will succeed with exit code 0.
Note that this is what travis runs when performing coverage check (see .travis.yml).