diff --git a/examples/nucleo_g071rb/amnb/main.cpp b/examples/nucleo_g071rb/amnb/main.cpp new file mode 100644 index 0000000000..41784b7105 --- /dev/null +++ b/examples/nucleo_g071rb/amnb/main.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +using namespace Board; +using namespace std::chrono_literals; +using namespace modm::amnb; +// ---------------------------------------------------------------------------- + +const Listener listeners[] = +{ + {1, +[](uint8_t sender, const uint32_t& data) + { + MODM_LOG_INFO << "Node2 and Node3 received Broadcast 1 from '" << sender; + MODM_LOG_INFO << "': " << data << modm::endl; + } + }, + {2, [](uint8_t sender) + { + MODM_LOG_INFO << "Node2 and Node3 received Broadcast 2 from '" << sender << "'" << modm::endl; + } + }, +}; +const Action actions[] = +{ + {1, +[]() -> Response + { + static uint8_t counter{0}; + MODM_LOG_INFO << "Node1 and Node3 received Action 1" << modm::endl; + return counter++; + } + }, + {2, +[](const uint32_t& data) -> Response + { + static uint8_t counter{0}; + MODM_LOG_INFO << "Node1 and Node3 received Action 2 with argument: " << data << modm::endl; + return ErrorResponse(counter++); + } + }, +}; + +// Two nodes on the same device on different UARTs of course! +DeviceWrapper device1; +DeviceWrapper device2; +DeviceWrapper device3; +Node node1(device1, 1, actions); +Node node2(device2, 2, listeners); +Node node3(device3, 3, actions, listeners); + +// You need to connect D1 with D15 and with A0 +using PinNode1 = GpioC4; // D1 +using PinNode2 = GpioB8; // D15 +using PinNode3 = GpioA0; // A0 + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + LedD13::setOutput(); + + Usart1::connect(); + Usart1::initialize(); + // Use Single-Wire Half-Duplex Mode + PinNode1::configure(Gpio::OutputType::OpenDrain); + PinNode1::configure(Gpio::InputType::PullUp); + USART1->CR1 &= ~USART_CR1_UE; + USART1->CR3 = USART_CR3_HDSEL; + USART1->CR1 |= USART_CR1_UE; + + Usart3::connect(); + Usart3::initialize(); + // Use Single-Wire Half-Duplex Mode + PinNode2::configure(Gpio::OutputType::OpenDrain); + PinNode2::configure(Gpio::InputType::PullUp); + USART3->CR1 &= ~USART_CR1_UE; + USART3->CR3 = USART_CR3_HDSEL; + USART3->CR1 |= USART_CR1_UE; + + Usart4::connect(); + Usart4::initialize(); + // Use Single-Wire Half-Duplex Mode + PinNode3::configure(Gpio::OutputType::OpenDrain); + PinNode3::configure(Gpio::InputType::PullUp); + USART4->CR1 &= ~USART_CR1_UE; + USART4->CR3 = USART_CR3_HDSEL; + USART4->CR1 |= USART_CR1_UE; + + + modm::ShortPeriodicTimer tmr{1s}; + uint32_t counter{0}; + + while (true) + { + node1.update(); + node2.update(); + node3.update(); + + if (tmr.execute()) + { + LedD13::toggle(); + node1.broadcast(1, counter++); + node3.broadcast(2); + + { + modm::ResumableResult< Result > res{0}; + while((res = node2.request(1, 1)).getState() == modm::rf::Running) + { node1.update(); node2.update(); node3.update(); } + MODM_LOG_INFO << "Node1 responded with: " << res.getResult().error(); + MODM_LOG_INFO << " " << *res.getResult().result() << modm::endl; + } + { + modm::ResumableResult< Result > res{0}; + while((res = node1.request(3, 2, counter)).getState() == modm::rf::Running) + { node1.update(); node2.update(); node3.update(); } + MODM_LOG_INFO << "Node3 responded with: " << res.getResult().error(); + MODM_LOG_INFO << " " << *res.getResult().resultError() << modm::endl; + } + } + } + + return 0; +} diff --git a/examples/nucleo_g071rb/amnb/project.xml b/examples/nucleo_g071rb/amnb/project.xml new file mode 100644 index 0000000000..740d681a8f --- /dev/null +++ b/examples/nucleo_g071rb/amnb/project.xml @@ -0,0 +1,19 @@ + + modm:nucleo-g071rb + + + + + + + + + + modm:platform:gpio + modm:communication:amnb + modm:platform:uart:1 + modm:platform:uart:3 + modm:platform:uart:4 + modm:build:scons + + diff --git a/src/modm/communication/amnb.hpp b/src/modm/communication/amnb.hpp index 185e4e9043..ec8f1630aa 100644 --- a/src/modm/communication/amnb.hpp +++ b/src/modm/communication/amnb.hpp @@ -1,6 +1,5 @@ /* - * Copyright (c) 2010-2011, Fabian Greif - * Copyright (c) 2011-2015, Niklas Hauser + * Copyright (c) 2020, Niklas Hauser * * This file is part of the modm project. * @@ -10,90 +9,4 @@ */ // ---------------------------------------------------------------------------- -/** - * @ingroup modm_communication - * @defgroup amnb Asynchronous Multi-Node Bus (AMNB) - * - * @section amnb_intro Introduction - * - * The AMNB (**A**synchronous **M**ulti-**N**ode **B**us) is a - * multi-master bus system, using p-persitent CSMA to send messages. - * - * One bus can be populated with up to 64 nodes. The nodes can be queried for - * data and they will respond like an SAB Slave, and can query data from other - * nodes like an SAB Master, or they can just broadcast a message. - * Each node can listen to all the responses and broadcasts and store that - * information for its purpose. - * - * Action callbacks to query requests can be defined as well as universal - * callbacks to any transmitted messaged (Listener callbacks). - * As an optional advanced feature, error handling callbacks can also be defined, - * which fire if messages have not been able to be sent, or requests timed out - * or misbehaved in other manners, or other nodes query unavailable information. - * - * @section amnb_protocol Protocol - * - * Features: - * - Maximum payload length is 32 byte. - * - CRC8 (1-Wire) - * - * @subsection structure Structure - * -@verbatim -+------+--------+--------+---------+--------------+-----+ -| SYNC | LENGTH | HEADER | COMMAND | ... DATA ... | CRC | -+------+--------+--------+---------+--------------+-----+ -@endverbatim - * - * - `SYNC` - Synchronization byte (always 0x54) - * - `LENGTH` - Length of the payload (without header, command and CRC byte) - * - `HEADER` - Address of the slave and two flag bits - * - `COMMAND` - Command code - * - `DATA` - Up to 32 byte of payload - * - `CRC` - CRC-8 checksum (iButton) - * - * @subsubsection header Header - * -@verbatim - 7 6 5 4 3 2 1 0 -+---+---+---+---+---+---+---+---+ -| Flags | ADDRESS | -+---+---+---+---+---+---+---+---+ - - Flags | Meaning ---------+--------- - 0 0 | data broadcast by a node - 0 1 | data request by a node - 1 0 | negative response from the node (NACK) - 1 1 | positive response from the node (ACK) -@endverbatim - * - * When transmitting, the *second bit* determines, whether or not to expect an - * answer from the addressed node. - * To just send information without any need for acknowledgment, use a broadcast. - * When a node is responding, the *second bit* has to following meaning: - * - * - `true` - Message is an positive response and may contain a payload - * - `false` - Message signals an error condition and carries only one byte of - * payload. This byte is an error code. - * - * @section amnb_electric Electrical characteristics - * - * Between different boards CAN transceivers are used. Compared to RS485 the - * CAN transceivers have the advantage to work without a separate direction input. - * You can just connected the transceiver directly to the UART of your - * microcontroller. - * These are identical to the SAB CAN electrical characteristics. - * You have to use the CAN transceivers, otherwise it cannot be determined, if - * the bus is busy or available for transmission. - * - * @author Fabian Greif - * @author Niklas Hauser - */ - -#ifndef MODM_AMNB_HPP -#define MODM_AMNB_HPP - #include "amnb/node.hpp" - -#endif // MODM_AMNB_HPP diff --git a/src/modm/communication/amnb/constants.hpp b/src/modm/communication/amnb/constants.hpp deleted file mode 100644 index 96e81f0912..0000000000 --- a/src/modm/communication/amnb/constants.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2009-2011, Fabian Greif - * Copyright (c) 2010, Martin Rosekeit - * Copyright (c) 2011-2013, Niklas Hauser - * Copyright (c) 2013, Sascha Schade - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#ifndef MODM_AMNB_CONSTANTS_HPP -#define MODM_AMNB_CONSTANTS_HPP - -#include - -namespace modm -{ - namespace amnb - { - /** - * \brief Error code - * - * Error codes below 0x20 are reserved for the system. Every other - * code may be used by user. - * - * \ingroup amnb - */ - enum Error - { - /** - * \brief Universal error code - * - * Use this code if you're not sure what error to signal. If - * the user should react to the error code, create a specific - * one for the given problem. - */ - ERROR_GENERAL_ERROR = 0x00, - ERROR_ACTION_NO_ACTION = 0x01, ///< No corresponding action found for this command - - /** - * \brief Unexpected payload length - * - * The payload length of the received message differs from the - * expected length for the given command. - */ - ERROR_ACTION_WRONG_PAYLOAD_LENGTH = 0x02, - - /** - * \brief No response given by the user - * - * This error code is generated when no response method is called - * by the user during an action callback. - */ - ERROR_ACTION_NO_RESPONSE = 0x03, - - ERROR_QUERY_ERROR_CODE = 0x04, ///< Query answer contains an more detailed error code - - ERROR_QUERY_TIMEOUT = 0x05, ///< Query timed out - - ERROR_QUERY_WRONG_PAYLOAD_LENGTH = 0x06, ///< Query answer has wrong payload length - - ERROR_QUERY_IN_PROGRESS = 0x07, ///< Query is already in progress - - ERROR_TRANSMITTER_BUSY = 0x08, ///< Interface is currently transmitting - - ERROR_MESSAGE_OVERWRITTEN = 0x09, ///< Another message will be transmitted before this one - }; - - /** - * \brief Flags - * \ingroup amnb - */ - enum Flags - { - BROADCAST = 0x00, ///< send a message without getting a response - REQUEST = 0x40, ///< request data from a node - NACK = 0x80, ///< data request Negative ACKnowledge - ACK = 0xc0 ///< data request positive ACKnowledge - }; - - /** - * \brief Maximum length for the payload - * \ingroup amnb - */ - const uint8_t maxPayloadLength = 32; - - /** - * \internal - * \brief Universal base class for the AMNB interface - * \ingroup amnb - */ - const uint8_t syncByte = 0x54; - - /** - * \internal - * \brief Initial value for the CRC8 calculation - * \ingroup amnb - */ - const uint8_t crcInitialValue = 0x00; - } -} - -#endif // MODM_AMNB_CONSTANTS_HPP diff --git a/src/modm/communication/amnb/handler.hpp.in b/src/modm/communication/amnb/handler.hpp.in new file mode 100644 index 0000000000..bad628b7a4 --- /dev/null +++ b/src/modm/communication/amnb/handler.hpp.in @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "message.hpp" +#include + +namespace modm::amnb +{ + +/// @ingroup modm_communication_amnb +enum class Error : uint8_t +{ + Ok = 0, + + RequestTimeout = 1, + WrongArgumentSize = 2, + NoAction = 3, + ResponseAllocationFailed = 4, + RequestAllocationFailed = 5, + UserError = 6, + Unknown = 7, +}; + +/// @ingroup modm_communication_amnb +struct Listener +{ + inline + Listener(int command, void(*listener)(uint8_t)) + : command(command), callback((void*)listener), + redirect([](const Message &msg, void *cb) + { + reinterpret_cast(cb)(msg.address()); + }) + {} + + template + Listener(int command, void(*listener)(uint8_t, const T&)) + : command(command), callback((void*)listener), + redirect([](const Message &msg, void* cb) + { + if (const T* arg = msg.get(); arg and msg.length() == sizeof(T)) + reinterpret_cast(cb)(msg.address(), *arg); + }) + {} + +protected: + const uint8_t command; + void *const callback; + void (*const redirect)(const Message &msg, void *cb); + + inline + void call(const Message &msg) const + { + redirect(msg, callback); + } + + template< size_t, size_t > friend class Node; +}; + +/// @ingroup modm_communication_amnb +struct Response +{ + inline + Response() + : msg(0, 0, Type::Response) {} + + template + Response(T retval) + : msg(0, 0, sizeof(T), Type::Response) + { + if (T* arg = msg.get(); arg) *arg = retval; + else { + msg = Message(0, 0, 1, Type::Error); + *msg.get() = Error::ResponseAllocationFailed; + } + } + + Message msg; +}; + +/// @ingroup modm_communication_amnb +template< typename T > +inline Response +ErrorResponse(T error) +{ + Response res(error); + res.msg.setType(Type::UserError); + return res; +} + +/// @ingroup modm_communication_amnb +struct Action +{ + inline + Action(int command, Response(*action)()) + : command(command), callback((void*)action), + redirect([](const Message &msg, void *cb) -> Message + { + if (msg.length() == 0) + return reinterpret_cast(cb)().msg; + return Response(Error::WrongArgumentSize).msg; + }) + {} + + template + Action(int command, Response(*action)(const T&)) + : command(command), callback((void*)action), + redirect([](const Message &msg, void* cb) -> Message + { + const T* arg = msg.get(); + if (arg == nullptr) + return Response(Error::ResponseAllocationFailed).msg; + if (msg.length() < sizeof(T)) + return Response(Error::WrongArgumentSize).msg; + return reinterpret_cast(cb)(*arg).msg; + }) + {} + + inline + Action(int command, void(*action)()) + : command(command), callback((void*)action), + redirect([](const Message &msg, void *cb) -> Message + { + if (msg.length() == 0) { + reinterpret_cast(cb)(); + return Response().msg; + } + return Response(Error::WrongArgumentSize).msg; + }) + {} + + template + Action(int command, void(*action)(const T&)) + : command(command), callback((void*)action), + redirect([](const Message &msg, void* cb) -> Message + { + + const T* arg = msg.get(); + if (arg == nullptr) + return Response(Error::ResponseAllocationFailed).msg; + if (msg.length() < sizeof(T)) + return Response(Error::WrongArgumentSize).msg; + reinterpret_cast(cb)(*arg); + return Response().msg; + }) + {} + +protected: + const uint8_t command; + void *const callback; + Message (*const redirect)(const Message &msg, void *cb); + + inline + Message call(const Message &msg) const + { + return redirect(msg, callback); + } + + template< size_t, size_t > friend class Node; +}; + +/// @ingroup modm_communication_amnb +template< class ReturnType = void, class ErrorType = void > +struct Result +{ + Result() = default; + + bool + hasError() const { return syserr != Error::Ok; } + + Error + error() const { return syserr; } + + const ErrorType* + resultError() const { return errval; } + + const ReturnType* + result() const { return retval; } + +protected: + Result(Message &msg) + { + switch(msg.type()) + { + case Type::Response: + if (retval = msg.get(); not retval) + syserr = Error::ResponseAllocationFailed; + break; + case Type::UserError: + if (errval = msg.get(); errval) + syserr = Error::UserError; + else + syserr = Error::ResponseAllocationFailed; + break; + case Type::Error: + syserr = *msg.get(); + break; + default: + syserr = Error::Unknown; + } + } + union { + const ReturnType *retval{nullptr}; + const ErrorType *errval; + }; + Error syserr{Error::Ok}; + + template< size_t, size_t > friend class Node; +}; + +/// @cond +template< class ReturnType> +struct Result +{ + Result() = default; + + bool + hasError() const { return syserr != Error::Ok; } + + Error + error() const { return syserr; } + + const Error* + resultError() const { return &syserr; } + + const ReturnType* + result() const { return retval; } + +protected: + Result(Message &msg) + { + switch(msg.type()) + { + case Type::Response: + retval = msg.get(); + if (not retval) syserr = Error::ResponseAllocationFailed; + break; + case Type::Error: + syserr = *msg.get(); + break; + case Type::UserError: + syserr = Error::UserError; + msg.deallocate(); + break; + default: + syserr = Error::Unknown; + } + } + const ReturnType *retval{nullptr}; + Error syserr{Error::Ok}; + + template< size_t, size_t > friend class Node; +}; + +template<> +struct Result +{ + Result() = default; + + bool + hasError() const { return syserr != Error::Ok; } + + Error + error() const { return syserr; } + + const Error* + resultError() const { return &syserr; } + + const Error* + result() const { return &syserr; } + +protected: + Result(Message &msg) + { + switch(msg.type()) + { + case Type::Response: + break; + case Type::Error: + syserr = *msg.get(); + break; + case Type::UserError: + syserr = Error::UserError; + break; + default: + syserr = Error::Unknown; + } + msg.deallocate(); + } + Error syserr{Error::Ok}; + + template< size_t, size_t > friend class Node; +}; +/// @endcond + +} + +%% if with_io +#include + +inline modm::IOStream& +operator << (modm::IOStream& s, const modm::amnb::Error error) +{ + using namespace modm::amnb; + switch(error) + { + case Error::Ok: s << "Ok"; break; + case Error::NoAction: s << "NoAction"; break; + case Error::RequestTimeout: s << "RequestTimeout"; break; + case Error::WrongArgumentSize: s << "WrongArgumentSize"; break; + case Error::RequestAllocationFailed: s << "RequestAllocationFailed"; break; + case Error::ResponseAllocationFailed: s << "ResponseAllocationFailed"; break; + case Error::UserError: s << "UserError"; break; + case Error::Unknown: s << "Unknown"; break; + } + return s; +} +%% endif + diff --git a/src/modm/communication/amnb/interface.hpp b/src/modm/communication/amnb/interface.hpp deleted file mode 100644 index 56b69c9c03..0000000000 --- a/src/modm/communication/amnb/interface.hpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2009, Georgi Grinshpun - * Copyright (c) 2009-2011, Fabian Greif - * Copyright (c) 2011-2013, 2015-2016, Niklas Hauser - * Copyright (c) 2013, Sascha Schade - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#ifndef MODM_AMNB_INTERFACE_HPP -#define MODM_AMNB_INTERFACE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "constants.hpp" - -#define AMNB_TIMING_DEBUG 0 - -namespace modm -{ - namespace amnb - { - /** - * \internal - * \brief Universal base class for the AMNB interface - * - * \see - * Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products - * \ingroup amnb - */ - uint8_t - crcUpdate(uint8_t crc, uint8_t data); - - /** - * \brief AMNB interface - * - * \author Niklas Hauser - * - * \ingroup amnb - */ - template - class Interface - { - public: - /** - * \brief Initialize the interface - * - * \param seed for the random number generator - */ - static void - initialize(int seed); - - /** - * \brief Send a message - * - * \param address receiver address - * \param flags see modm::amnb::Flags - * \param command command byte - * \param *payload data field - * \param payloadLength size of the data field - */ - static bool - sendMessage(uint8_t address, Flags flags, uint8_t command, - const void *payload, uint8_t payloadLength); - - /** - * \brief Send a message - */ - template - static bool modm_always_inline - sendMessage(uint8_t address, Flags flags, uint8_t command, - const T& payload); - - /** - * \brief Send a empty message - */ - static bool modm_always_inline - sendMessage(uint8_t address, Flags flags, uint8_t command); - - /** - * \brief Check if a message was received - * - * Reset the status with a call of dropMessage(). - */ - static modm_always_inline bool - isMessageAvailable(); - - static modm_always_inline uint8_t - getTransmittedAddress(); - - static modm_always_inline uint8_t - getTransmittedCommand(); - - static modm_always_inline Flags - getTransmittedFlags(); - - static modm_always_inline uint8_t - getAddress(); - - static modm_always_inline uint8_t - getCommand(); - - static modm_always_inline bool - isResponse(); - - /** - * \brief Check if the message is an ACK or NACK - * \return \c true if the message is an ACK, \c false on NACK. - */ - static modm_always_inline bool - isAcknowledge(); - - /** - * \return \c true you are allowed to send right now - */ - static modm_always_inline bool - isBusAvailable(); - - /** - * \brief Check if there has been an error during transmission - * \return \c true if the message has been transmitted without - * collision. - */ - static modm_always_inline bool - messageTransmitted(); - - /** - * \brief Access the data of a received message - * - * Data access is only valid after isMessageAvailable() returns - * \c true and before any call of dropMessage() or update() - */ - static modm_always_inline const uint8_t * - getPayload(); - - /** - * \return Size of the received message. Zero if no message - * is available at the moment. - */ - static modm_always_inline uint8_t - getPayloadLength(); - - /** - * \brief End procession of the current message - */ - static void - dropMessage(); - - /** - * \brief Update internal status - * - * Has to be called periodically. Decodes received messages. - */ - static void - update(); - -#if AMNB_TIMING_DEBUG - static modm::ShortDuration latency; - static uint8_t collisions; -#endif - - private: - static bool - writeMessage(); - - enum State - { - SYNC, - LENGTH, - DATA - }; - - static uint8_t rx_buffer[maxPayloadLength + 3]; - static uint8_t tx_buffer[maxPayloadLength + 4]; - static uint8_t crc; - static uint8_t position; - static uint8_t length; - static uint8_t lengthOfReceivedMessage; - static uint8_t lengthOfTransmitMessage; - static modm::ShortTimeout resetTimer; - static const uint8_t resetTimeout = 4; - - static bool rescheduleTransmit; - static bool hasMessageToSend; - static bool messageSent; - static bool transmitting; - static modm::PreciseTimeout rescheduleTimer; - static uint8_t rescheduleTimeout; - - static State state; - }; - } -} - -#include "interface_impl.hpp" - -#endif // MODM_AMNB_INTERFACE_HPP diff --git a/src/modm/communication/amnb/interface.hpp.in b/src/modm/communication/amnb/interface.hpp.in new file mode 100644 index 0000000000..e9dc8944d5 --- /dev/null +++ b/src/modm/communication/amnb/interface.hpp.in @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "message.hpp" +#include +#include +#include + +namespace modm::amnb +{ + +enum class InterfaceStatus : uint8_t +{ + Ok = 0, + + HeaderInvalid, + DataInvalid, + MediumBusy, + MediumEmpty, + + SyncReadFailed, + HeaderReadFailed, + DataReadFailed, + AllocationFailed, + + SyncWriteFailed, + HeaderWriteFailed, + DataWriteFailed, +}; + +/// @ingroup modm_communication_amnb +class Device +{ +public: + virtual bool + hasReceived() = 0; + + virtual modm::ResumableResult + write(uint8_t data) = 0; + + virtual modm::ResumableResult + read(uint8_t *data) = 0; +}; + +/// @ingroup modm_communication_amnb +template< class Uart, uint16_t TimeoutUsTx = 1000, uint16_t TimeoutUsRx = 10'000 > +class DeviceWrapper : public Device, modm::Resumable<2> +{ +public: + bool + hasReceived() final + { + return Uart::receiveBufferSize() > 0; + } + + modm::ResumableResult + write(uint8_t data) final + { + RF_BEGIN(0); + RF_WAIT_UNTIL(Uart::write(data)); + + timeout.restart(std::chrono::microseconds(TimeoutUsTx)); + RF_WAIT_UNTIL(Uart::read(rx_data) or Uart::hasError() or timeout.isExpired()); + if (timeout.isExpired() or Uart::hasError() or rx_data != data) + { + Uart::discardTransmitBuffer(); + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_END_RETURN(true); + } + + modm::ResumableResult + read(uint8_t *data) final + { + RF_BEGIN(1); + timeout.restart(std::chrono::microseconds(TimeoutUsRx)); + RF_WAIT_UNTIL(Uart::read(*data) or Uart::hasError() or timeout.isExpired()); + if (timeout.isExpired() or Uart::hasError()) + { + Uart::discardReceiveBuffer(); + Uart::clearError(); + RF_RETURN(false); + } + RF_END_RETURN(true); + } + +protected: + modm::ShortPreciseTimeout timeout; + uint8_t rx_data; +}; + +/// @ingroup modm_communication_amnb +template< size_t MaxHeapAllocation = 0 > +class Interface : modm::Resumable<6> +{ +public: + Interface(Device &device) + : device(device) {} + + bool + isMediumBusy() const + { return isReceiving or device.hasReceived(); } + + modm::ResumableResult + transmit(const Message *message) + { + RF_BEGIN(0); + + if (isMediumBusy()) + RF_RETURN(InterfaceStatus::MediumBusy); + isTransmitting = true; + + tx_data = STX; + if (not RF_CALL(write())) RF_RETURN(InterfaceStatus::SyncWriteFailed); + if (not RF_CALL(write())) RF_RETURN(InterfaceStatus::SyncWriteFailed); + + for (tx_index = 0; tx_index < message->headerLength(); tx_index++) + if (not RF_CALL(write_escaped(message->self()[tx_index]))) + RF_RETURN(InterfaceStatus::HeaderWriteFailed); + + for (tx_index = 0; tx_index < message->dataLength(); tx_index++) + if (not RF_CALL(write_escaped(message->get()[tx_index]))) + RF_RETURN(InterfaceStatus::DataWriteFailed); + + isTransmitting = false; + RF_END_RETURN(InterfaceStatus::Ok); + } + + modm::ResumableResult + receiveHeader(Message *message) + { + RF_BEGIN(1); + + if (isTransmitting) + RF_RETURN(InterfaceStatus::MediumBusy); + + if (not device.hasReceived()) + RF_RETURN(InterfaceStatus::MediumEmpty); + + if (not RF_CALL(read())) RF_RETURN(InterfaceStatus::SyncReadFailed); + if (rx_data != STX) RF_RETURN(InterfaceStatus::SyncReadFailed); + isReceiving = true; + if (not RF_CALL(read())) { + isReceiving = false; + RF_RETURN(InterfaceStatus::SyncReadFailed); + } + if (rx_data != STX) { + isReceiving = false; + RF_RETURN(InterfaceStatus::SyncReadFailed); + } + + for(rx_index = 0; rx_index < message->SMALL_HEADER_SIZE; rx_index++) + { + if (not RF_CALL(read_escaped())) RF_RETURN(InterfaceStatus::HeaderReadFailed); + message->self()[rx_index] = rx_data; + } + for(; rx_index < message->headerLength(); rx_index++) + { + if (not RF_CALL(read_escaped())) RF_RETURN(InterfaceStatus::HeaderReadFailed); + message->self()[rx_index] = rx_data; + } + + if (not message->isHeaderValid()) { + isReceiving = false; + RF_RETURN(InterfaceStatus::HeaderInvalid); + } + if (not message->isLarge()) isReceiving = false; + RF_END_RETURN(InterfaceStatus::Ok); + } + + modm::ResumableResult + receiveData(Message *message, bool allocate=true) + { + RF_BEGIN(1); // same index as receiveHeader!!! + + if (not message->isLarge()) RF_RETURN(InterfaceStatus::Ok); + + if ( (rx_allocated = allocate and (message->dataLength() <= MaxHeapAllocation)) ) + rx_allocated = message->allocate(); + + for(rx_index = 0; rx_index < message->dataLength(); rx_index++) + { + if (not RF_CALL(read_escaped())) RF_RETURN(InterfaceStatus::DataReadFailed); + if (rx_allocated) message->get()[rx_index] = rx_data; + } + isReceiving = false; + if (allocate and not rx_allocated) RF_RETURN(InterfaceStatus::AllocationFailed); + + RF_END_RETURN(message->isDataValid() ? InterfaceStatus::Ok : InterfaceStatus::DataInvalid); + } + +protected: + modm::ResumableResult + write_escaped(uint8_t data) + { + RF_BEGIN(2); + if (data == STX or data == DLE) { + tx_data = DLE; + if (not RF_CALL(write())) RF_RETURN(false); + tx_data = data ^ 0x20; + } + else { + tx_data = data; + } + RF_END_RETURN_CALL(write()); + } + + modm::ResumableResult + read_escaped() + { + RF_BEGIN(3); + if (not RF_CALL(read())) RF_RETURN(false); + if (rx_data == DLE) { + if (not RF_CALL(read())) RF_RETURN(false); + rx_data ^= 0x20; + } + RF_END_RETURN(true); + } + + modm::ResumableResult + write() + { + RF_BEGIN(4); + if (RF_CALL(device.write(tx_data))) + RF_RETURN(true); + isTransmitting = false; + RF_END_RETURN(false); + } + + modm::ResumableResult + read() + { + RF_BEGIN(5); + if (RF_CALL(device.read(&rx_data))) + RF_RETURN(true); + isReceiving = false; + RF_END_RETURN(false); + } + +protected: + Device &device; + uint16_t tx_index; + uint16_t rx_index; + uint8_t tx_data; + uint8_t rx_data; + bool rx_allocated; + bool isReceiving{false}; + bool isTransmitting{false}; + static constexpr uint8_t STX{0x7E}; + static constexpr uint8_t DLE{0x7D}; +}; + +} // namespace modm::amnb + +%% if with_io +#include + +inline modm::IOStream& +operator << (modm::IOStream& s, const modm::amnb::InterfaceStatus status) +{ + using namespace modm::amnb; + switch(status) + { + case InterfaceStatus::Ok: s << "Ok"; break; + case InterfaceStatus::HeaderInvalid: s << "HeaderInvalid"; break; + case InterfaceStatus::DataInvalid: s << "DataInvalid"; break; + case InterfaceStatus::MediumBusy: s << "MediumBusy"; break; + case InterfaceStatus::MediumEmpty: s << "MediumEmpty"; break; + case InterfaceStatus::SyncReadFailed: s << "SyncReadFailed"; break; + case InterfaceStatus::HeaderReadFailed: s << "HeaderReadFailed"; break; + case InterfaceStatus::DataReadFailed: s << "DataReadFailed"; break; + case InterfaceStatus::AllocationFailed: s << "AllocationFailed"; break; + case InterfaceStatus::SyncWriteFailed: s << "SyncWriteFailed"; break; + case InterfaceStatus::HeaderWriteFailed: s << "HeaderWriteFailed"; break; + case InterfaceStatus::DataWriteFailed: s << "DataWriteFailed"; break; + } + return s; +} +%% endif diff --git a/src/modm/communication/amnb/interface_impl.hpp b/src/modm/communication/amnb/interface_impl.hpp deleted file mode 100644 index 320344d7db..0000000000 --- a/src/modm/communication/amnb/interface_impl.hpp +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2009-2010, Martin Rosekeit - * Copyright (c) 2009-2012, Fabian Greif - * Copyright (c) 2011-2013, 2015, Niklas Hauser - * Copyright (c) 2012-2013, Sascha Schade - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#ifndef MODM_AMNB_INTERFACE_HPP -# error "Don't include this file directly, use 'interface.hpp' instead!" -#endif - -#include - -// ---------------------------------------------------------------------------- -#ifdef __AVR__ -# include -#endif - -uint8_t -modm::amnb::crcUpdate(uint8_t crc, uint8_t data) -{ -#ifdef __AVR__ - return _crc_ibutton_update(crc, data); -#else - crc = crc ^ data; - for (uint_fast8_t i = 0; i < 8; ++i) - { - if (crc & 0x01) { - crc = (crc >> 1) ^ 0x8C; - } - else { - crc >>= 1; - } - } - return crc; -#endif -} - -// ---------------------------------------------------------------------------- -template typename modm::amnb::Interface::State \ - modm::amnb::Interface::state = SYNC; - -template -uint8_t modm::amnb::Interface::rx_buffer[maxPayloadLength + 3]; - -template -uint8_t modm::amnb::Interface::tx_buffer[maxPayloadLength + 4]; - -template -uint8_t modm::amnb::Interface::crc; - -template -uint8_t modm::amnb::Interface::position; - -template -uint8_t modm::amnb::Interface::length; - -template -uint8_t modm::amnb::Interface::lengthOfReceivedMessage = 0; - -template -uint8_t modm::amnb::Interface::lengthOfTransmitMessage = 0; - -template -bool modm::amnb::Interface::hasMessageToSend = false; - -template -bool modm::amnb::Interface::rescheduleTransmit = false; - -template -bool modm::amnb::Interface::transmitting = false; - -template -modm::ShortTimeout modm::amnb::Interface::resetTimer; - -template -modm::Timeout modm::amnb::Interface::rescheduleTimer; - -template -uint8_t modm::amnb::Interface::rescheduleTimeout; - -template -bool modm::amnb::Interface::messageSent = false; - -#if AMNB_TIMING_DEBUG -template -modm::ShortDuration modm::amnb::Interface::latency; - -template -uint8_t modm::amnb::Interface::collisions; -#endif - -// ---------------------------------------------------------------------------- - -template -void -modm::amnb::Interface::initialize(int seed) -{ - srand(seed); - rescheduleTimeout = static_cast(rand()) % TIMEOUT; - state = SYNC; -} - -// ---------------------------------------------------------------------------- - -template -bool -modm::amnb::Interface::writeMessage() -{ - uint8_t check; - transmitting = true; - Device::resetErrorFlags(); - - for (uint_fast8_t i=0; i < lengthOfTransmitMessage; ++i) { - Device::write(tx_buffer[i]); - - // try and read the transmitted byte back but do not wait infinity - uint16_t count(0); - while (!Device::read(check) && (++count <= 1000)) ; - - // if the read timed out or framing error occurred or content mismatch - if ((count > 1000) || Device::readErrorFlags() || (check != tx_buffer[i])) { - // stop transmitting, signal the collision - transmitting = false; - rescheduleTransmit = true; - Device::resetErrorFlags(); - // and wait for a random amount of time before sending again - rescheduleTimer.restart(std::chrono::microseconds(rescheduleTimeout)); -#if AMNB_TIMING_DEBUG - ++collisions; -#endif - return false; - } - } - -#if AMNB_TIMING_DEBUG - latency = modm::Clock::now() - latency; -#endif - - messageSent = true; - hasMessageToSend = false; - transmitting = false; - return true; -} - -template -bool -modm::amnb::Interface::sendMessage(uint8_t address, Flags flags, - uint8_t command, - const void *payload, uint8_t payloadLength) -{ - // dont overwrite the buffer when transmitting - if (transmitting) return false; - - hasMessageToSend = false; - messageSent = false; - uint8_t crc; - -#if AMNB_TIMING_DEBUG - latency = modm::Clock::now(); -#endif - - tx_buffer[0] = syncByte; - tx_buffer[1] = payloadLength; - tx_buffer[2] = address | flags; - tx_buffer[3] = command; - - crc = crcUpdate(crcInitialValue, payloadLength); - crc = crcUpdate(crc, address | flags); - crc = crcUpdate(crc, command); - - const uint8_t *ptr = static_cast(payload); - uint_fast8_t i=0; - for (; i < payloadLength; ++i) - { - crc = crcUpdate(crc, *ptr); - tx_buffer[i+4] = *ptr; - ptr++; - } - - tx_buffer[i+4] = crc; - - lengthOfTransmitMessage = payloadLength + 5; - hasMessageToSend = true; - return true; -} - -template template -bool -modm::amnb::Interface::sendMessage(uint8_t address, Flags flags, - uint8_t command, - const T& payload) -{ - return sendMessage(address, flags, - command, - reinterpret_cast(&payload), sizeof(T)); -} - -template -bool -modm::amnb::Interface::sendMessage(uint8_t address, Flags flags, uint8_t command) -{ - return sendMessage(address, flags, - command, - 0, 0); -} - -// ---------------------------------------------------------------------------- - -template -bool -modm::amnb::Interface::isMessageAvailable() -{ - return (lengthOfReceivedMessage != 0); -} - -template -uint8_t -modm::amnb::Interface::getTransmittedAddress() -{ - return (tx_buffer[0] & 0x3f); -} - -template -uint8_t -modm::amnb::Interface::getTransmittedCommand() -{ - return tx_buffer[1]; -} - -template -modm::amnb::Flags -modm::amnb::Interface::getTransmittedFlags() -{ - return static_cast(tx_buffer[0] & 0xc0); -} - -template -uint8_t -modm::amnb::Interface::getAddress() -{ - return (rx_buffer[0] & 0x3f); -} - -template -uint8_t -modm::amnb::Interface::getCommand() -{ - return rx_buffer[1]; -} - -template -bool -modm::amnb::Interface::isResponse() -{ - return (rx_buffer[0] & 0x80); -} - -template -bool -modm::amnb::Interface::isAcknowledge() -{ - return (rx_buffer[0] & 0x40); -} - -template -bool -modm::amnb::Interface::isBusAvailable() -{ - return (state == SYNC) && !transmitting && !rescheduleTransmit; -} - -template -bool -modm::amnb::Interface::messageTransmitted() -{ - return messageSent; -} - -template -const uint8_t* -modm::amnb::Interface::getPayload() -{ - return rx_buffer+2; -} - -template -uint8_t -modm::amnb::Interface::getPayloadLength() -{ - return (lengthOfReceivedMessage - 3); -} - -template -void -modm::amnb::Interface::dropMessage() -{ - lengthOfReceivedMessage = 0; -} - -// ---------------------------------------------------------------------------- - -template -void -modm::amnb::Interface::update() -{ - uint8_t byte; - while (Device::read(byte)) - { - if (Device::readErrorFlags()) - { - // collision has been detected - rescheduleTransmit = true; - Device::resetErrorFlags(); - - // erase the message in the buffer - Device::flushReceiveBuffer(); - - // and wait for a random amount of time before sending again - rescheduleTimeout = static_cast(rand()) % TIMEOUT; - rescheduleTimer.restart(std::chrono::microseconds(rescheduleTimeout)); - state = SYNC; -#if AMNB_TIMING_DEBUG - ++collisions; -#endif - return; - } - - switch (state) - { - case SYNC: - if (byte == syncByte) state = LENGTH; - break; - - case LENGTH: - if (byte > maxPayloadLength) { - state = SYNC; - } - else { - length = byte + 3; // +3 for header, command and crc byte - position = 0; - crc = crcUpdate(crcInitialValue, byte); - state = DATA; - } - break; - - case DATA: - rx_buffer[position++] = byte; - crc = crcUpdate(crc, byte); - - if (position >= length) - { - if (crc == 0) lengthOfReceivedMessage = length; - state = SYNC; - } - break; - - default: - state = SYNC; - break; - } - - resetTimer.restart(std::chrono::microseconds(resetTimeout)); - } - if ((state != SYNC) && resetTimer.isExpired()) state = SYNC; - - // check if we have waited for a random amount of time - if (rescheduleTransmit && rescheduleTimer.isExpired()) rescheduleTransmit = false; - - if (hasMessageToSend && !rescheduleTransmit && !transmitting && (state == SYNC)) { - // if channel is free, send with probability P - if (rescheduleTimeout < static_cast(2.56f * PROBABILITY)) { - writeMessage(); - } - // otherwise reschedule - else { - rescheduleTransmit = true; - rescheduleTimer.restart(std::chrono::microseconds(rescheduleTimeout % TIMEOUT)); - } - rescheduleTimeout = static_cast(rand()); - } -} diff --git a/src/modm/communication/amnb/message.hpp.in b/src/modm/communication/amnb/message.hpp.in new file mode 100644 index 0000000000..b75551372a --- /dev/null +++ b/src/modm/communication/amnb/message.hpp.in @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +namespace modm::amnb +{ + +/// @ingroup modm_communication_amnb +enum class Type: uint8_t +{ + Broadcast = 0b000 << 5, ///< Message Broadcast + Request = 0b010 << 5, ///< Action request + Response = 0b011 << 5, ///< Positive Response with a custom user payload + Error = 0b100 << 5, ///< Negative Response with a system error code + UserError = 0b101 << 5, ///< Negative Response with a custom user payload +}; + +/// @ingroup modm_communication_amnb +class modm_packed modm_aligned(4) Message +{ +public: + inline Message() = default; + inline Message(uint8_t address, uint8_t command, uint16_t length, Type type=Type::Broadcast) + : header{0, address, command, uint8_t(uint8_t(type) | + ((length <= SMALL_LENGTH) ? length : LENGTH_MASK))}, + storage{(length <= SMALL_LENGTH) ? uint16_t(0) : length} {} + + inline Message(uint16_t length) : Message(0, 0, length) {} + inline Message(uint8_t address, uint8_t command, Type type=Type::Broadcast) + : Message(address, command, 0, type) {} + + inline Message(const Message& m) { *this = m; } + inline Message(Message&& m) { *this = std::move(m); } + + inline ~Message() { deallocate(); } + + inline Message& operator=(const Message& m) + { + deallocate(); + header = m.header; + if (isLarge()) { + storage.large = m.storage.large; + if (storage.large.data) (*storage.large.data)++; + } + else storage.small = m.storage.small; + return *this; + } + inline Message& operator=(Message&& m) + { + deallocate(); + header = m.header; + if (isLarge()) { + storage.large = m.storage.large; + m.storage.large.data = nullptr; + } + else storage.small = m.storage.small; + m.header.type_length &= ~LENGTH_MASK; + return *this; + } + +public: + inline uint8_t address() const { return header.address; } + inline void setAddress(uint8_t address) { header.address = address; } + + inline uint8_t command() const { return header.command; } + inline void setCommand(uint8_t command) { header.command = command; } + + inline uint16_t length() const + { + if (isLarge()) return storage.large.length; + return std::min(smallLength(), SMALL_LENGTH); + } + inline void setLength(uint16_t length) + { + deallocate(); + if (length <= SMALL_LENGTH) { + header.type_length = (header.type_length & ~LENGTH_MASK) | length; + } + else { + header.type_length |= LENGTH_MASK; + storage.large.length = length; + storage.large.data = nullptr; + } + } + + inline Type type() const + { return Type(header.type_length & TYPE_MASK); } + inline void setType(Type type) + { header.type_length = (header.type_length & ~TYPE_MASK) | uint8_t(type); } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + template + T* get() + { + if (isLarge()) { + if (not allocate() or sizeof(T) > storage.large.length) return nullptr; + return reinterpret_cast(storage.large.data + LARGE_DATA_OFFSET); + } + if (not smallLength() or sizeof(T) > smallLength()) return nullptr; + return reinterpret_cast(storage.small.data); + } + + template + const T* get() const + { + if (isLarge()) { + if (sizeof(T) > storage.large.length or not storage.large.data) return nullptr; + return reinterpret_cast(storage.large.data + LARGE_DATA_OFFSET); + } + if (not smallLength() or sizeof(T) > smallLength()) return nullptr; + return reinterpret_cast(storage.small.data); + } +#pragma GCC diagnostic pop + +protected: + inline bool isHeaderValid() const + { return crcHeader() == header.crc; } + + inline bool isDataValid() const + { + if (not isHeaderValid()) return false; + if (dataLength()) return (crcData() == storage.large.crc); + return true; + } + + inline void setValid() + { + if (isLarge() and allocate()) { + storage.large.crc = crcData(); + } + header.crc = crcHeader(); + } + +protected: + inline uint8_t + crcHeader() const + { + return modm::math::crc8_ccitt(&header.address, headerLength()-1); + } + + inline uint16_t + crcData() const + { + return modm::math::crc16_ccitt(storage.large.data + LARGE_DATA_OFFSET, dataLength()); + } + + inline uint8_t smallLength() const + { + return header.type_length & LENGTH_MASK; + } + + inline bool isLarge() const + { + return smallLength() > SMALL_LENGTH; + } + + inline uint8_t headerLength() const + { + if (isLarge()) return LARGE_HEADER_SIZE; + return SMALL_HEADER_SIZE + std::min(smallLength(), SMALL_LENGTH); + } + + inline uint8_t dataLength() const + { + return isLarge() ? storage.large.length : 0; + } + + inline bool allocate() + { + if (isLarge() and not storage.large.data) { +%% if with_heap + storage.large.data = new (std::nothrow) uint8_t[storage.large.length + LARGE_DATA_OFFSET]; + if (storage.large.data) { + *storage.large.data = 1; + return true; + } +%% endif + return false; + } + return true; + } + inline void deallocate() + { +%% if with_heap + if (isLarge() and storage.large.data) { + if (*storage.large.data <= 1) delete storage.large.data; + else (*storage.large.data)--; + storage.large.data = nullptr; + } +%% endif + } + inline uint8_t* self() + { return &header.crc; } + inline const uint8_t* self() const + { return &header.crc; } + +protected: + static constexpr uint8_t TYPE_MASK{0xE0}; + static constexpr uint8_t LENGTH_MASK{0x1F}; + static constexpr uint8_t SMALL_LENGTH{28}; +%% if target.platform == "avr" + static constexpr uint8_t LARGE_DATA_OFFSET{1}; +%% else + static constexpr uint8_t LARGE_DATA_OFFSET{4}; +%% endif + + static constexpr uint8_t SMALL_HEADER_SIZE{4}; + static constexpr uint8_t LARGE_HEADER_SIZE{8}; + +public: + static constexpr uint8_t SizeSmall{SMALL_LENGTH}; + static constexpr uint16_t SizeMax{8*1024}; + +protected: + // NOTE: This arrangement allows this data structure to be 4-byte aligned + // so that any Cortex-M0 device has no issues with unaligned access + // of neither the header values nor the data cast to another struct! + struct modm_packed Header { + uint8_t crc{0}; // 0 + uint8_t address{0}; // 1 + uint8_t command{0}; // 2 + uint8_t type_length{0}; // 3 + } header; + union modm_packed Storage { + inline Storage(uint16_t length=0) + : large{length,0,nullptr} {} + + struct modm_packed { + uint16_t length; // 4 5 + uint16_t crc; // 6 7 + uint8_t *data; // 8... 4-byte aligned + } large; + struct modm_packed { + uint8_t data[SMALL_LENGTH]; // 4... 4-byte aligned + } small; + } storage; + +private: + template< size_t > friend class Interface; + template< size_t, size_t > friend class Node; + template< class, class > friend class Result; +}; +static_assert(sizeof(Message) == 32, "modm::amnb::Message must be memory-packed!"); + +} // namespace modm::amnb + +%% if with_io +#include + +inline modm::IOStream& +operator << (modm::IOStream& s, const modm::amnb::Type type) +{ + using namespace modm::amnb; + switch(type) + { + case Type::Broadcast: s << "Broadcast"; break; + case Type::Request: s << "Request"; break; + case Type::UserError: s << "UserError"; break; + case Type::Error: s << "Error"; break; + case Type::Response: s << "Response"; break; + } + return s; +} +%% endif diff --git a/src/modm/communication/amnb/module.lb b/src/modm/communication/amnb/module.lb new file mode 100644 index 0000000000..2f7fbdbed3 --- /dev/null +++ b/src/modm/communication/amnb/module.lb @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Niklas Hauser +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":communication:amnb" + module.description = FileReader("module.md") + +def prepare(module, options): + module.depends( + ":math:utils", + ":processing:resumable", + ":processing:timer", + ":container") + + module.add_option( + BooleanOption(name="with_heap", + description="Allow large messages to allocate on the heap.", + default=True)) + + return True + +def build(env): + env.substitutions = { + "with_io": env.has_module(":io"), + "target": env[":target"].identifier, + "with_heap": env["with_heap"] + } + env.outbasepath = "modm/src/modm/communication/amnb" + env.template("message.hpp.in") + env.template("interface.hpp.in") + env.template("handler.hpp.in") + env.copy("node.hpp") + env.copy("../amnb.hpp") diff --git a/src/modm/communication/amnb/module.md b/src/modm/communication/amnb/module.md new file mode 100644 index 0000000000..263307638d --- /dev/null +++ b/src/modm/communication/amnb/module.md @@ -0,0 +1,88 @@ +# Asynchronous Multi-Node Bus (AMNB) + +The AMNB (**A**synchronous **M**ulti-**N**ode **B**us) is a multi-master bus +system, using p-persistent CSMA/CD to send messages. + +One bus can be populated with up to 256 (logical) nodes. The nodes can be +queried for data and they will respond like an SAB Slave, and can query data +from other nodes like an SAB Master, or they can just broadcast a message. Each +node can listen to all the responses and broadcasts and store that information +for its purpose. + +Action callbacks to query requests can be defined as well as universal callbacks +to any transmitted messaged (Listener callbacks). As an optional advanced +feature, error handling callbacks can also be defined, which fire if messages +have not been able to be sent, or requests timed out or misbehaved in other +manners, or other nodes query unavailable information. + + +## Protocol Structure + +Message without data: + +- `SYNC`: Synchronization sequence. +- `CRC8`: checksum for the header +- `ADDR`: Node address. +- `TYPE`: Message type . +- `COMMAND`: Command code. + +``` + 1 2 3 4 5 6 ++------+------+------+---------+------+ +| SYNC | CRC8 | ADDR | COMMAND | TYPE | ++------+------+------+---------+------+ +``` + +Message with <=28B data: + +- `CRC8`: checksum for the header and data. +- `TYPE/LENGTH`: Message type and length. + +``` + 1 2 3 4 5 6 7 ... ++------+------+------+---------+-------------+--------------------+ +| SYNC | CRC8 | ADDR | COMMAND | TYPE/LENGTH | ... <=28B DATA ... | ++------+------+------+---------+-------------+--------------------+ +``` + +Message with <8kB data: + +- `CRC16`: checksum for the data. +- `DATA`: Up to 8kB payload (nodes may support *much* less though!) + +``` + 1 2 3 4 5 6 7 8 9 10 11 ... ++------+------+------+---------+------+--------+-------+-------------------+ +| SYNC | CRC8 | ADDR | COMMAND | TYPE | LENGTH | CRC16 | ... <8kB DATA ... | ++------+------+------+---------+------+--------+-------+-------------------+ +``` + + +## Message Types + +| Type | Meaning | +|:----:|:---------------------------------------| +| 000 | Data broadcast by a node | +| 010 | Data request by a node | +| 100 | Negative response from the node (NACK) | +| 101 | Positive response from the node (ACK) | + +When transmitting, the *second bit* determines, whether or not to expect an +answer from the addressed node. To just send information without any need for +acknowledgment, use a broadcast. + +When a node is responding, the *second bit* has the following meaning: + +- `true`: Message is an positive response and may contain a payload +- `false`: Message signals an error condition and carries only one byte of + payload. This byte is an error code. + + +## Electrical characteristics + +Between different boards CAN transceivers are used. Compared to RS485 the CAN +transceivers have the advantage to work without a separate direction input. You +can just connected the transceiver directly to the UART of your microcontroller. +These are identical to the SAB CAN electrical characteristics. You have to use +the CAN transceivers, otherwise it cannot be determined, if the bus is busy or +available for transmission. diff --git a/src/modm/communication/amnb/node.hpp b/src/modm/communication/amnb/node.hpp index 7f9e790c63..4ee5c21a28 100644 --- a/src/modm/communication/amnb/node.hpp +++ b/src/modm/communication/amnb/node.hpp @@ -1,7 +1,5 @@ /* - * Copyright (c) 2011, Fabian Greif - * Copyright (c) 2011-2013, 2015-2016, Niklas Hauser - * Copyright (c) 2012-2013, Sascha Schade + * Copyright (c) 2020, Niklas Hauser * * This file is part of the modm project. * @@ -11,559 +9,314 @@ */ // ---------------------------------------------------------------------------- -#ifndef MODM_AMNB_NODE_HPP -#define MODM_AMNB_NODE_HPP - -#include -#include -#include +#pragma once #include "interface.hpp" +#include "handler.hpp" +#include +#include -namespace modm +namespace modm::amnb { - namespace amnb + +/** + * @author Niklas Hauser + * @ingroup modm_communication_amnb + */ +template < size_t TxBufferSize = 2, size_t MaxHeapAllocation = 0 > +class Node : public modm::Resumable<6> +{ + static_assert(2 <= TxBufferSize, "TxBuffer must have at least two messages!"); +public: + Node(Device &device, uint8_t address): interface(device), address(address) + { setSeed(); } + + template< size_t Actions > + Node(Device &device, uint8_t address, const Action (&actions)[Actions]) + : interface(device), actionList(actions), actionCount(Actions), address(address) { - /** - * \internal - * \brief Interface used to transmit data through a slave object - * - * \ingroup amnb - */ - class Transmitter - { - public: - virtual void - send(bool acknowledge, const void *payload, uint8_t payloadLength) = 0; - }; - - /** - * \brief Response object for an action call - * - * \ingroup amnb - */ - class Response - { - template - friend class Node; - - public: - /** - * \brief Signal an error condition - * - * \param errorCode Error code. Values below 0x20 are reserved - * for the system, feel free to use any other - * value for specific error conditions. - * - * \see modm::amnb::Error - */ - void - error(uint8_t errorCode = ERROR_GENERAL_ERROR); - - /** - * \brief Send a response without any data - */ - void - send(); - - /** - * \brief Send a response with an attached payload - * - * \param payload Pointer to the payload - * \param length Number of bytes - */ - void - send(const void *payload, std::size_t length); - - /** - * \brief Send a response with an attached payload - */ - template - modm_always_inline void - send(const T& payload); - - protected: - Response(Transmitter *parent); - - Response(const Response&); - - Response& - operator = (const Response&); - - Transmitter *transmitter; - bool triggered; ///< is used by amnp::Node to check that a response was send - }; - - /** - * \brief Base-class for every object which should be used inside - * a callback. - * - * Example: - * \code - * class Sensor : public modm::amnb::Callable - * { - * public: - * void - * sendValue(modm::amnb::Response& response) - * { - * response.send(this->value); - * } - * - * // ... - * - * private: - * int8_t value; - * }; - * \endcode - * - * A complete example is available in the \c example/amnb folder. - * - * \see modm::amnb::Node - * \ingroup amnb - */ - struct Callable - { - }; - - /** - * \brief Possible Action - * - * \see AMNB_ACTION() - * \ingroup amnb - */ - struct Action + static_assert(Actions <= 0xff, "Actions list must be smaller than 255!"); + setSeed(); + } + + template< size_t Listeners > + Node(Device &device, uint8_t address, const Listener (&listeners)[Listeners]) + : interface(device), listenerList(listeners), listenerCount(Listeners), address(address) + { + static_assert(Listeners <= 0xff, "Listeners list must be smaller than 255!"); + setSeed(); + } + + template< size_t Actions, size_t Listeners > + Node(Device &device, uint8_t address, const Action (&actions)[Actions], const Listener (&listeners)[Listeners]) + : interface(device), actionList(actions), listenerList(listeners), + actionCount(Actions), listenerCount(Listeners), address(address) + { + static_assert(Actions <= 0xff, "Actions list must be smaller than 255!"); + static_assert(Listeners <= 0xff, "Listeners list must be smaller than 255!"); + setSeed(); + } + + void + setAddress(uint8_t address) + { + this->address = address; + setSeed(); + } + +public: + bool + broadcast(uint8_t command) + { + if (tx_queue.isFull()) return false; + return tx_queue.push(std::move(Message(address, command, Type::Broadcast))); + } + template< typename T > + bool + broadcast(uint8_t command, const T &argument) + { + if (tx_queue.isFull()) return false; + Message msg(address, command, sizeof(T), Type::Broadcast); + T* t = msg.get(); + if (t == nullptr) return false; + *t = argument; + return tx_queue.push(std::move(msg)); + } + + template< class ReturnType = void, class ErrorType = void > + modm::ResumableResult< Result > + request(uint8_t from, uint8_t command) + { + RF_BEGIN(0); + request_msg = Message(from, command, Type::Request); + RF_CALL(request()); + RF_END_RETURN(request_msg); + } + + template< class ReturnType = void, class ErrorType = void, class T > + modm::ResumableResult< Result > + request(uint8_t from, uint8_t command, const T &argument) + { + RF_BEGIN(1); { - typedef void (Callable::*Callback)(Response& response, const void *payload); - - inline void - call(Response& response, const void *payload); - - uint8_t command; - uint8_t payloadLength; //!< Payload length in bytes - Callable *object; - Callback function; //!< Method callActionback - }; - - /** - * \brief Possible Listener - * - * \see AMNB_LISTEN() - * \ingroup amnb - */ - struct Listener + request_msg = Message(from, command, sizeof(T), Type::Request); + T* arg = request_msg.get(); + if constexpr (sizeof(T) > Message::SMALL_LENGTH) { + if (arg == nullptr) { + request_msg = Response(Error::RequestAllocationFailed).msg; + RF_RETURN(request_msg); + } + } + *arg = argument; + } + RF_CALL(request()); + RF_END_RETURN(request_msg); + } + +public: + void + update() + { + transmit(); + receive(); + } + +protected: + modm::ResumableResult + transmit() + { + RF_BEGIN(2); + while(1) { - typedef void (Callable::*Callback)(const void *payload, const uint8_t length, const uint8_t sender); - - inline void - call(const void *payload, const uint8_t length, const uint8_t sender); - - uint8_t address; //!< Address of transmitting node - uint8_t command; - Callable *object; - Callback function; //!< Method callActionback - }; - - /** - * \brief Possible Error - * - * \see AMNB_ERROR() - * \ingroup amnb - */ - struct ErrorHandler + if (not tx_queue.isEmpty()) + { + RF_WAIT_WHILE(isResumableRunning(3)); + RF_CALL(send(tx_queue.get())); + tx_queue.pop(); + } + RF_YIELD(); + } + RF_END(); + } + + modm::ResumableResult + send(Message &msg) + { + RF_BEGIN(3); + + msg.setValid(); + tx_counter = std::min(10, msg.command() >> (8 - PRIORITY_BITS)); + while(1) { - typedef void (Callable::*Callback)(Flags type, const uint8_t errorCode); - - inline void - call(Flags type, const uint8_t errorCode); - - uint8_t address; //!< Node address of message - uint8_t command; //!< Command of message - Callable *object; - Callback function; //!< Method callActionback - }; - - /** - * \brief AMNB Node - * - * \code - * typedef modm::amnb::Node< modm::amnb::Interface< modm::BufferedUart0 > > Node; - * - * FLASH_STORAGE(modm::amnb::Action actionList[]) = - * { - * AMNB_ACTION(0x57, object, Object::method1, 0), - * AMNB_ACTION(0x03, object, Object::method2, 2), - * }; - * // optional - * FLASH_STORAGE(modm::amnb::Listener listenList[]) = - * { - * AMNB_LISTEN(0x29, 0x46, object, Object::method) - * }; - * // also optional - * FLASH_STORAGE(modm::amnb::ErrorHandler errorHandlerList[]) = - * { - * AMNB_LISTEN(0x29, 0x46, object, Object::method) - * }; - * - * int - * main() - * { - * // initialize the interface - * Node node(0x02, - * modm::accessor::asFlash(actionList), - * sizeof(actionList) / sizeof(modm::amnb::Action), - * // optional - * modm::accessor::asFlash(listenList), - * sizeof(listenList) / sizeof(modm::amnb::Listener), - * modm::accessor::asFlash(errorHandlerList), - * sizeof(errorHandlerList) / sizeof(modm::amnb::ErrorHandler)); - * - * while (true) - * { - * node.update(); - * } - * } - * \endcode - * - * A complete example is available in the \c example/amnb folder. - * - * \author Fabian Greif, Niklas Hauser - * \ingroup amnb - */ - template - class Node : protected Transmitter + while (interface.isMediumBusy()) + { + RF_WAIT_WHILE(interface.isMediumBusy()); + reschedule(RESCHEDULE_MASK_SHORT); + RF_WAIT_UNTIL(tx_timer.isExpired()); + } + + if (RF_CALL(interface.transmit(&msg)) == InterfaceStatus::Ok) + break; + + if (--tx_counter == 0) break; + + // a collision or other write issue occurred + RF_WAIT_WHILE(interface.isMediumBusy()); + reschedule(RESCHEDULE_MASK_LONG); + RF_WAIT_UNTIL(tx_timer.isExpired()); + } + RF_END(); + } + + modm::ResumableResult + request() + { + RF_BEGIN(4); + + RF_WAIT_WHILE(isResumableRunning(3)); + response_status = ResponseStatus::Waiting; + RF_CALL(send(request_msg)); + + response_timer.restart(1s); + RF_WAIT_UNTIL((response_status == ResponseStatus::Received) or response_timer.isExpired()); + response_status = ResponseStatus::NotWaiting; + + if (response_timer.isExpired()) { + request_msg = Message(address, request_msg.command(), 1, Type::Error); + *request_msg.get() = Error::RequestTimeout; + } + + RF_END(); + } + + modm::ResumableResult + receive() + { + RF_BEGIN(5); + while(1) { - public: - /** - * \brief Initialize the node - * - * \param address Own address - * \param actionList List of all action callbacks, need to be - * stored in flash-memory - * \param actionCount Number of entries in \a actionList - * \param listenList List of all listener callbacks, need to be - * stored in flash-memory - * \param listenCount Number of entries in \a listenList - * \param errorHandlerList List of all error handler callbacks, - * need to be stored in flash-memory - * \param errorHandlerCount Number of entries in \a errorHandlerList - * - * \see AMNB_ACTION() - * \see AMNB_LISTEN() - * \see AMNB_ERROR() - */ - Node(uint8_t address, modm::accessor::Flash actionList, uint8_t actionCount, - modm::accessor::Flash listenList, uint8_t listenCount, - modm::accessor::Flash errorHandlerList, uint8_t errorHandlerCount); - - /** - * \brief Initialize the node without error handlers - */ - Node(uint8_t address, modm::accessor::Flash actionList, uint8_t actionCount, - modm::accessor::Flash listenList, uint8_t listenCount); - - /** - * \brief Initialize the node without listeners or error handlers - */ - Node(uint8_t address, modm::accessor::Flash actionList, uint8_t actionCount); - - /** - * \brief Start a new query with a payload - * - * \param slaveAddress - * \param command - * \param payload - * \param responseLength Expected payload length of the response - * \return \c true if no error occurred - */ - template - bool - query(uint8_t slaveAddress, uint8_t command, - const T& payload, uint8_t responseLength); - - template - bool - query(uint8_t slaveAddress, uint8_t command, - const void *payload, uint8_t payloadLength, uint8_t responseLength); - - /** - * \brief Start a new query without any payload - * - * \return \c true if no error occurred - */ - bool - query(uint8_t slaveAddress, uint8_t command, uint8_t responseLength); - - /** - * \brief Start a new broadcast with a payload - * - * \param command - * \param payload - * \return \c true if no error occurred - */ - template - bool - broadcast(uint8_t command, const T& payload); - - /** - * \brief Start a new broadcast with a payload - * - * \param command - * \param payload - * \param payloadLength - * \return \c true if no error occurred - */ - bool - broadcast(uint8_t command, const void *payload, uint8_t payloadLength); - - /** - * \brief Start a new broadcast without any payload - * - * \return \c true if no error occurred - */ - bool - broadcast(uint8_t command); - - bool - isQueryCompleted(); - - /** - * \brief Check if the last query could be performed successful - * - * Only valid if isQueryCompleted() returns \c true. - * - * \return \c true if the query was successful. Use getResponse() to - * access the result. - */ - bool - isSuccess(); - - /** - * \brief Check error code - * - * Only valid if isQueryCompleted() returns \c true while - * isSuccess() returns \c false. - * - * \return Error code - * \see modm::amnb::Error - */ - uint8_t - getErrorCode(); - - - template - inline const T * - getResponse(); - - inline const void * - getResponse(); - - /** - * \brief Receive and process messages - * - * This method will decode the incoming messages and call the - * corresponding callback methods from the action list. It must - * be called periodically, best in every main loop cycle. - */ - void - update(); - - protected: - void - send(bool acknowledge, const void *payload, uint8_t payloadLength); - - bool - checkErrorHandlers(uint8_t address, uint8_t command, Flags type, uint8_t errorCode); - - - uint8_t ownAddress; - modm::accessor::Flash actionList; - uint8_t actionCount; - modm::accessor::Flash listenList; - uint8_t listenCount; - modm::accessor::Flash errorHandlerList; - uint8_t errorHandlerCount; - - uint8_t currentCommand; - Response response; - - enum QueryStatus + rx_msg.deallocate(); // deallocates previous message + if (RF_CALL(interface.receiveHeader(&rx_msg)) == InterfaceStatus::Ok) { - IN_PROGRESS, ///< Query in progress - SUCCESS, ///< Response successfully received - ERROR_RESPONSE = 0x40, ///< Error in the received message - ERROR_TIMEOUT = 0x41, ///< No message received within the timeout window - ERROR_PAYLOAD = 0x42, ///< Wrong payload size - }; - - QueryStatus queryStatus; - uint8_t expectedAddress; - uint8_t expectedResponseLength; - modm::ShortTimeout timer; - /// timeout value in milliseconds - static constexpr std::chrono::milliseconds timeout{10}; - }; + // Check lists if we are interested in this message + is_rx_msg_for_us = handleRxMessage(false); + // Receive the message data, only allocate if it's for us + if (RF_CALL(interface.receiveData(&rx_msg, is_rx_msg_for_us)) == InterfaceStatus::Ok) + { + // Only handle message *with* data if it's for us + if (is_rx_msg_for_us) handleRxMessage(true); + } + } + RF_YIELD(); + } + RF_END(); } -} -#ifdef __DOXYGEN__ - /** - * \brief Define a amnb::Action - * - * Example: - * \code - * class Sensor : public modm::amnb::Callable - * { - * public: - * void - * sendValue(modm::amnb::Response& response) - * { - * response.send(this->value); - * } - * - * void - * doSomething(modm::amnb::Response& response, const uint32_t* parameter) - * { - * // ... do something useful ... - * - * response.send(); - * } - * - * // ... - * - * private: - * int8_t value; - * }; - * - * Sensor sensor; - * - * FLASH_STORAGE(modm::amnb::Action actionList[]) = - * { - * AMNB_ACTION(0x57, sensor, Sensor::sendValue, 0), - * AMNB_ACTION(0x03, sensor, Sensor::doSomething, sizeof(uint32_t)), - * }; - * \endcode - * - * A complete example is available in the \c example/amnb folder. - * - * \param command Command byte - * \param object - * \param function Member function of object - * \param length Parameter size in bytes - * - * \see modm::amnb::Action - * \ingroup amnb - */ - #define AMNB_ACTION(command, object, function, length) -#else - #define AMNB_ACTION(command, object, function, length) \ - { command, \ - length, \ - static_cast(&object), \ - reinterpret_cast(&function) } -#endif // __DOXYGEN__ - - - -#ifdef __DOXYGEN__ - /** - * \brief Define a amnb::Listener - * - * Example: - * \code - * class ListenToNodes : public modm::amnb::Callable - * { - * public: - * void - * listenToCommand(uint8_t *payload, const uint8_t length, uint8_t sender) - * { - * // ... do something useful ... - * - * } - * - * void - * listenToCommandWithOutCaringForSender(uint8_t *payload, const uint8_t length) - * { - * // ... do something useful ... - * - * } - * - * // ... - * }; - * - * ListenToNodes listen; - * - * FLASH_STORAGE(modm::amnb::Listener listenList[]) = - * { - * AMNB_LISTEN(0x46, 0x03, listen, ListenToNodes::listenToCommand), - * }; - * \endcode - * - * A complete example is available in the \c example/amnb folder. - * - * \param address Address of the transmitting node - * \param command Command byte - * \param object - * \param function Member function of object - * - * \see modm::amnb::Listener - * \ingroup amnb - */ - #define AMNB_LISTEN(address, command, object, function) -#else - #define AMNB_LISTEN(address, command, object, function) \ - { address, \ - command, \ - static_cast(&object), \ - reinterpret_cast(&function) } -#endif // __DOXYGEN__ - - -#ifdef __DOXYGEN__ - /** - * \brief Define a amnb::ErrorHandler - * - * Example: - * \code - * class handleErrors : public modm::amnb::Callable - * { - * public: - * void - * errorForCommand(modm::amnb::Flags type, const uint8_t errorCode) - * { - * // ... do something useful with that information ... - * - * } - * - * // ... - * }; - * - * handleErrors errorhandler; - * - * FLASH_STORAGE(modm::amnb::Listener listenList[]) = - * { - * AMNB_LISTEN(0x37, 0x57, errorhandler, handleErrors::errorForCommand), - * }; - * \endcode - * - * A complete example is available in the \c example/amnb folder. - * - * \param address Node address of message - * \param command Command of message - * \param object - * \param function Member function of object - * - * \see modm::amnb::ErrorHandler - * \ingroup amnb - */ - #define AMNB_ERROR(address, command, object, function) -#else - #define AMNB_ERROR(address, command, object, function) \ - { address, \ - command, \ - static_cast(&object), \ - reinterpret_cast(&function) } -#endif // __DOXYGEN__ - -#include "node_impl.hpp" - -#endif // MODM_AMNB_NODE_HPP +protected: + bool + handleRxMessage(bool complete) + { + switch(rx_msg.type()) + { + case Type::Broadcast: + for (size_t ii=0; ii() = Error::NoAction; + tx_queue.push(std::move(msg)); + } + break; + + default: + if (response_status == ResponseStatus::Waiting) + { + if (complete and + request_msg.address() == rx_msg.address() and + request_msg.command() == rx_msg.command()) + { + request_msg = std::move(rx_msg); + response_status = ResponseStatus::Received; + } + return true; + } + } + return false; + } + + void + setSeed() + { lfsr = address << 8 | (address + 1); } + + void + reschedule(uint8_t mask) + { + lfsr ^= lfsr >> 7; + lfsr ^= lfsr << 9; + lfsr ^= lfsr >> 13; + + const uint16_t priority = ((1u << PRIORITY_BITS) - 1 - tx_counter) >> 3; + const uint16_t delay = (lfsr & ((1ul << mask) - 1)) | (priority << mask); + tx_timer.restart(std::chrono::microseconds(delay)); + } + +protected: + Interface interface; + + const Action *const actionList{nullptr}; + const Listener *const listenerList{nullptr}; + + modm::ShortPreciseTimeout tx_timer; + modm::ShortTimeout response_timer; + + modm::BoundedQueue tx_queue; + Message request_msg; + Message rx_msg; + + uint16_t lfsr; + const uint8_t actionCount{0}; + const uint8_t listenerCount{0}; + uint8_t address; + + uint8_t tx_counter; + bool is_rx_msg_for_us; + + enum class ResponseStatus : uint8_t + { + NotWaiting = 0, + Waiting, + Received, + } + response_status{ResponseStatus::NotWaiting}; + + static constexpr uint8_t PRIORITY_BITS{5}; + static constexpr uint8_t RESCHEDULE_MASK_SHORT{7}; + static constexpr uint8_t RESCHEDULE_MASK_LONG{11}; +}; + +} diff --git a/src/modm/communication/amnb/node_impl.hpp b/src/modm/communication/amnb/node_impl.hpp deleted file mode 100644 index e81ce164f6..0000000000 --- a/src/modm/communication/amnb/node_impl.hpp +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright (c) 2011, Fabian Greif - * Copyright (c) 2011-2013, Niklas Hauser - * Copyright (c) 2013, Sascha Schade - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#ifndef MODM_AMNB_NODE_HPP -#error "Don't include this file directly, use 'node.hpp' instead!" -#endif - -// ---------------------------------------------------------------------------- -template -void -modm::amnb::Response::send(const T& payload) -{ - triggered = true; - transmitter->send(true, reinterpret_cast(&payload), sizeof(T)); -} - -modm::amnb::Response::Response(Transmitter *parent) : -transmitter(parent), triggered(false) -{ -} - -void -modm::amnb::Response::error(uint8_t errorCode) -{ - triggered = true; - - uint8_t error = errorCode; - transmitter->send(false, &error, 1); -} - -void -modm::amnb::Response::send() -{ - triggered = true; - transmitter->send(true, 0, 0); -} - -void -modm::amnb::Response::send(const void *payload, std::size_t length) -{ - triggered = true; - transmitter->send(true, payload, length); -} - -// ---------------------------------------------------------------------------- -inline void -modm::amnb::Action::call(Response& response, const void *payload) -{ - // redirect call to the actual object - (object->*function)(response, payload); -} - -// ---------------------------------------------------------------------------- -inline void -modm::amnb::Listener::call(const void *payload, const uint8_t length, const uint8_t sender) -{ - // redirect call to the actual object - (object->*function)(payload, length, sender); -} - -// ---------------------------------------------------------------------------- -inline void -modm::amnb::ErrorHandler::call(Flags type, const uint8_t errorCode) -{ - // redirect call to the actual object - (object->*function)(type, errorCode); -} - -// Disable warnings for Visual Studio about using 'this' in a base member -// initializer list. -// In this case though it is totally safe so it is ok to disable this warning. -#ifdef MODM_COMPILER_MSVC -# pragma warning(disable:4355) -#endif - -// ---------------------------------------------------------------------------- -template -modm::amnb::Node::Node(uint8_t address, - modm::accessor::Flash actionList, - uint8_t actionCount, - modm::accessor::Flash listenList, - uint8_t listenCount, - modm::accessor::Flash errorHandlerList, - uint8_t errorHandlerCount) : -ownAddress(address), -actionList(actionList), actionCount(actionCount), -listenList(listenList), listenCount(listenCount), -errorHandlerList(errorHandlerList), errorHandlerCount(errorHandlerCount), -response(this) -{ - Interface::initialize(address); - queryStatus = ERROR_TIMEOUT; -} - -template -modm::amnb::Node::Node(uint8_t address, - modm::accessor::Flash actionList, - uint8_t actionCount, - modm::accessor::Flash listenList, - uint8_t listenCount) : -ownAddress(address), -actionList(actionList), actionCount(actionCount), -listenList(listenList), listenCount(listenCount), -errorHandlerCount(0), -response(this) -{ - Interface::initialize(address); - queryStatus = ERROR_TIMEOUT; -} - -template -modm::amnb::Node::Node(uint8_t address, - modm::accessor::Flash actionList, - uint8_t actionCount) : -ownAddress(address), -actionList(actionList), actionCount(actionCount), -listenCount(0), -errorHandlerCount(0), -response(this) -{ - Interface::initialize(address); - queryStatus = ERROR_TIMEOUT; -} - -// ---------------------------------------------------------------------------- -template template -bool -modm::amnb::Node::query(uint8_t slaveAddress, uint8_t command, - const T& payload, uint8_t responseLength) -{ - if (queryStatus == IN_PROGRESS) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_QUERY_IN_PROGRESS); - return false; - } - - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - } - if (!Interface::sendMessage(slaveAddress, REQUEST, command, payload)) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - - queryStatus = IN_PROGRESS; - expectedResponseLength = responseLength; - expectedAddress = slaveAddress; - - timer.restart(timeout); - return noError; -} - -template template -bool -modm::amnb::Node::query(uint8_t slaveAddress, uint8_t command, - const void *payload, uint8_t payloadLength, uint8_t responseLength) -{ - if (queryStatus == IN_PROGRESS) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_QUERY_IN_PROGRESS); - return false; - } - - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - } - if (!Interface::sendMessage(slaveAddress, REQUEST, command, payload, payloadLength)) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - - queryStatus = IN_PROGRESS; - expectedResponseLength = responseLength; - expectedAddress = slaveAddress; - - timer.restart(timeout); - return noError; -} - -template -bool -modm::amnb::Node::query(uint8_t slaveAddress, uint8_t command, - uint8_t responseLength) -{ - if (queryStatus == IN_PROGRESS) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_QUERY_IN_PROGRESS); - return false; - } - - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - } - if (!Interface::sendMessage(slaveAddress, REQUEST, command, 0, 0)) { - checkErrorHandlers(slaveAddress, command, REQUEST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - - queryStatus = IN_PROGRESS; - expectedResponseLength = responseLength; - expectedAddress = slaveAddress; - - timer.restart(timeout); - return noError; -} - -// ---------------------------------------------------------------------------- -template template -bool -modm::amnb::Node::broadcast(uint8_t command, const T& payload) -{ - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - - } - if (!Interface::sendMessage(this->ownAddress, BROADCAST, command, payload)) { - checkErrorHandlers(this->ownAddress, command, BROADCAST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - return noError; -} - -template -bool -modm::amnb::Node::broadcast(uint8_t command, const void *payload, uint8_t payloadLength) -{ - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - } - if (!Interface::sendMessage(this->ownAddress, BROADCAST, command, payload, payloadLength)) { - checkErrorHandlers(this->ownAddress, command, BROADCAST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - return noError; -} - -template -bool -modm::amnb::Node::broadcast(uint8_t command) -{ - bool noError(true); - if (!Interface::messageTransmitted()) { - checkErrorHandlers(Interface::getTransmittedAddress(), - Interface::getTransmittedCommand(), - Interface::getTransmittedFlags(), - ERROR_MESSAGE_OVERWRITTEN); - noError = false; - } - if (!Interface::sendMessage(this->ownAddress, BROADCAST, command, 0,0)) { - checkErrorHandlers(this->ownAddress, command, BROADCAST, ERROR_TRANSMITTER_BUSY); - noError = false; - } - return noError; -} - -// ---------------------------------------------------------------------------- -template -bool -modm::amnb::Node::isQueryCompleted() -{ - return (queryStatus != IN_PROGRESS); -} - -// ---------------------------------------------------------------------------- -template -bool -modm::amnb::Node::isSuccess() -{ - return (queryStatus == SUCCESS); -} - -// ---------------------------------------------------------------------------- -template -uint8_t -modm::amnb::Node::getErrorCode() -{ - if (queryStatus == ERROR_RESPONSE) { - // Error code is in the first payload byte - return Interface::getPayload()[0]; - } - else { - return queryStatus; - } -} - -// ---------------------------------------------------------------------------- -template template -const T * -modm::amnb::Node::getResponse() -{ - return reinterpret_cast(Interface::getPayload()); -} - -template -const void * -modm::amnb::Node::getResponse() -{ - return reinterpret_cast(Interface::getPayload()); -} - -// ---------------------------------------------------------------------------- -template -void -modm::amnb::Node::update() -{ - Interface::update(); - - if (Interface::isMessageAvailable()) - { - uint8_t messageAddress = Interface::getAddress(); - uint8_t messageCommand = Interface::getCommand(); - bool checkListeners = false; - // -------------------------------------------------------------------- - if (Interface::isResponse()) - { - // my request answer - if ((queryStatus == IN_PROGRESS) && (messageAddress == expectedAddress)) - { - if (Interface::isAcknowledge()) - { - if (Interface::getPayloadLength() == expectedResponseLength) { - queryStatus = SUCCESS; - - checkListeners = true; - } - else { - queryStatus = ERROR_PAYLOAD; - checkErrorHandlers(messageAddress, messageCommand, ACK, ERROR_QUERY_WRONG_PAYLOAD_LENGTH); - } - } - else { - queryStatus = ERROR_RESPONSE; - checkErrorHandlers(messageAddress, messageCommand, NACK, ERROR_QUERY_ERROR_CODE); - } - } - // other request answer - else { - checkListeners = true; - } - } - // -------------------------------------------------------------------- - else { - // Requests - if (Interface::isAcknowledge()) - { - if (messageAddress == this->ownAddress) - { - this->response.triggered = false; - this->currentCommand = messageCommand; - - modm::accessor::Flash list = actionList; - for (uint8_t i = 0; i < actionCount; ++i, ++list) - { - Action action(*list); - if (this->currentCommand == action.command) - { - if (Interface::getPayloadLength() == action.payloadLength) - { - // execute callback function - action.call(this->response, Interface::getPayload()); - - if (!this->response.triggered) { - this->response.error(ERROR_ACTION_NO_RESPONSE); - checkErrorHandlers(messageAddress, messageCommand, NACK, ERROR_ACTION_NO_RESPONSE); - } - } - else { - this->response.error(ERROR_ACTION_WRONG_PAYLOAD_LENGTH); - checkErrorHandlers(messageAddress, messageCommand, NACK, ERROR_ACTION_WRONG_PAYLOAD_LENGTH); - } - break; - } - } - } - } - // Broadcasts - else { - checkListeners = true; - } - - if (!this->response.triggered) { - this->response.error(ERROR_ACTION_NO_ACTION); - checkErrorHandlers(messageAddress, messageCommand, NACK, ERROR_ACTION_NO_ACTION); - } - } - // -------------------------------------------------------------------- - - if (checkListeners && (listenCount > 0)) - { // check if we want to listen to it - modm::accessor::Flash list = listenList; - for (uint8_t i = 0; i < listenCount; ++i, ++list) - { - Listener listen(*list); - if ((messageAddress == listen.address) && (messageCommand == listen.command)) - { - // execute callback function - listen.call(Interface::getPayload(), Interface::getPayloadLength(), messageAddress); - break; - } - } - } - // finished with the message, drop it. - Interface::dropMessage(); - } - // my request timeout - else if (timer.isExpired() && (queryStatus == IN_PROGRESS)) - { - queryStatus = ERROR_TIMEOUT; - checkErrorHandlers(Interface::getTransmittedAddress(), Interface::getTransmittedCommand(), REQUEST, ERROR_QUERY_TIMEOUT); - } -} - - -template -bool -modm::amnb::Node::checkErrorHandlers(uint8_t address, uint8_t command, Flags type, uint8_t errorCode) -{ - if (errorHandlerCount == 0) return false; - - modm::accessor::Flash list = errorHandlerList; - for (uint8_t i = 0; i < errorHandlerCount; ++i, ++list) - { - ErrorHandler errorHandler(*list); - if ((address == errorHandler.address) && (command == errorHandler.command)) - { - // execute callback function - errorHandler.call(type, errorCode); - return true; - } - } - return false; -} - -// ---------------------------------------------------------------------------- -template -void -modm::amnb::Node::send(bool acknowledge, - const void *payload, uint8_t payloadLength) -{ - Flags flags; - if (acknowledge) { - flags = modm::amnb::ACK; - } - else { - flags = modm::amnb::NACK; - } - - Interface::sendMessage(this->ownAddress, flags, this->currentCommand, - payload, payloadLength); -} diff --git a/test/modm/communication/amnb/interface_test.cpp b/test/modm/communication/amnb/interface_test.cpp new file mode 100644 index 0000000000..3f291f1e96 --- /dev/null +++ b/test/modm/communication/amnb/interface_test.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "interface_test.hpp" +#include +#include +#include + +using namespace modm::amnb; +using namespace modm_test; + +class AmnbTestMessage : public Message +{ +public: + using Message::Message; + + using Message::isHeaderValid; + using Message::isDataValid; + using Message::setValid; + using Message::allocate; + using Message::self; +}; + +void +AmnbInterfaceTest::setUp() +{ + SharedMedium::reset(); +} + +// ---------------------------------------------------------------------------- +void +AmnbInterfaceTest::testSerialize() +{ + DeviceWrapper dev; + Interface<100> interface(dev); // max 100B heap allocation! + { + // Test inplace message tx + AmnbTestMessage msg(7, 0x7D); + msg.setValid(); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::Ok); + // with byte stuffing! + const uint8_t raw[] = {0x7E, 0x7E, 108, 7, 0x7D, 0x5D, 0}; + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + SharedMedium::reset(); + { + // Test inplace message tx + AmnbTestMessage msg(200, 0x7E, 8, Type::Request); + msg.get()[0] = 0x03020100ul; + msg.get()[1] = 0x07067E7Dul; + msg.setValid(); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::Ok); + // with byte stuffing! + const uint8_t raw[] = {0x7E, 0x7E, 18, 200, 0x7D, 0x5E, 0x48, 0, 1, 2, 3, 0x7D, 0x5D, 0x7D, 0x5E, 6, 7}; + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + SharedMedium::reset(); + { + // Test heap message tx + AmnbTestMessage msg(10, 14, 32, Type::Error); + for (size_t ii=0; ii < 32/sizeof(uint32_t); ii++) + msg.get()[ii] = 0x0302017Dul+ii; + msg.setValid(); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::Ok); + // with byte stuffing! + const uint8_t raw[] = {0x7E, 0x7E, 8, 10, 14, 0x9F, 32, 0, 205, 202, + 0x7D, 0x5D, 1, 2, 3, 0x7D, 0x5E, 1, 2, 3, 0x7F, 1, 2, 3, 0x80, 1, 2, 3, + 0x81, 1, 2, 3, 0x82, 1, 2, 3, 0x83, 1, 2, 3, 0x84, 1, 2, 3}; + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + + SharedMedium::reset(); + { + // Test inplace message rx + SharedMedium::add_rx({0x7E, 0x7E, 247, 0x7D, 0x5D, 100, 0}); + AmnbTestMessage msg; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + + TEST_ASSERT_EQUALS(msg.address(), 0x7D); + TEST_ASSERT_EQUALS(msg.command(), 100); + TEST_ASSERT_EQUALS(msg.length(), 0); + TEST_ASSERT_EQUALS(msg.type(), Type::Broadcast); + TEST_ASSERT_TRUE(msg.get() == nullptr); + } + SharedMedium::reset(); + { + // Test inplace message rx + SharedMedium::add_rx({0x7E, 0x7E, 56, 5, 15, 0x44, 0x7D, 0x5E, 1, 2, 3}); + AmnbTestMessage msg; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + + TEST_ASSERT_EQUALS(msg.address(), 5); + TEST_ASSERT_EQUALS(msg.command(), 15); + TEST_ASSERT_EQUALS(msg.length(), 4); + TEST_ASSERT_EQUALS(msg.type(), Type::Request); + TEST_ASSERT_EQUALS(*msg.get(), 0x0302017Eul); + } + SharedMedium::reset(); + { + SharedMedium::add_rx({0x7E, 0x7E, 82, 20, 54, 0x5F, 36, 0, 143, 101, + 1, 2, 3, 4, 2, 2, 3, 4, 3, 2, 3, 4, 4, 2, 3, 4, + 5, 2, 3, 4, 6, 2, 3, 4, 7, 2, 3, 4, 8, 2, 3, 4, 9, 2, 3, 4}); + + AmnbTestMessage msg; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + + TEST_ASSERT_EQUALS(msg.address(), 20); + TEST_ASSERT_EQUALS(msg.command(), 54); + TEST_ASSERT_EQUALS(msg.length(), 36); + TEST_ASSERT_EQUALS(msg.type(), Type::Request); + + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveData(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_TRUE(msg.isDataValid()); + + const uint32_t raw[] = {0x04030201ul, 0x04030202ul, 0x04030203ul, 0x04030204ul, + 0x04030205ul, 0x04030206ul, 0x04030207ul, 0x04030208ul, 0x04030209ul}; + TEST_ASSERT_EQUALS_ARRAY(msg.get(), raw, 9); + } +} + +void +AmnbInterfaceTest::testFailures() +{ + DeviceWrapper dev; + Interface<35> interface(dev); + { + AmnbTestMessage msg; + msg.setValid(); + + { + SharedMedium::fail_tx_index = 0; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::SyncWriteFailed); + const uint8_t raw[] = {0x7E}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::raw_transmitted, raw, sizeof(raw)); + }{ + SharedMedium::reset(); + SharedMedium::fail_tx_index = 1; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::SyncWriteFailed); + const uint8_t raw[] = {0x7E, 0x7E}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::raw_transmitted, raw, sizeof(raw)); + }{ + SharedMedium::reset(); + SharedMedium::fail_tx_index = 2; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::HeaderWriteFailed); + const uint8_t raw[] = {0x7E, 0x7E, 110}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::raw_transmitted, raw, sizeof(raw)); + }{ + SharedMedium::reset(); + SharedMedium::fail_tx_index = 4; + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::HeaderWriteFailed); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&msg)), InterfaceStatus::Ok); + const uint8_t raw[] = {0x7E, 0x7E, 110,0,0,0x7E, 0x7E, 110,0,0,0}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::raw_transmitted, raw, sizeof(raw)); + } + } + + { + AmnbTestMessage msg; + // wrong synchronization + SharedMedium::reset(); + SharedMedium::add_rx({0x00}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::SyncReadFailed); + // second sync byte wrong + SharedMedium::add_rx({0x7E, 0x00}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::SyncReadFailed); + // sync byte read error + SharedMedium::fail_rx_index = 1; // sync read error + SharedMedium::add_rx({0x7E, 0x7E}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::SyncReadFailed); + + SharedMedium::reset(); + SharedMedium::fail_rx_index = 2; // header read error + SharedMedium::add_rx({0x7E, 0x7E, 0x00}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::HeaderReadFailed); + + // CRC is wrong for no data + SharedMedium::add_rx({0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::HeaderInvalid); + SharedMedium::reset(); + + SharedMedium::fail_rx_index = 6; // short data read error + SharedMedium::add_rx({0x7E, 0x7E, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::HeaderReadFailed); + // wrong CRC for short data + SharedMedium::add_rx({0x7E, 0x7E, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::HeaderInvalid); + SharedMedium::reset(); + + SharedMedium::fail_rx_index = 11; // long data with read error + SharedMedium::add_rx({0x7E, 0x7E, 8, 10, 14, 0x9F, 32, 0, 205, 202, 1, 2}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveData(&msg)), InterfaceStatus::DataReadFailed); + + // allocation failed, received message too large + SharedMedium::add_rx({0x7E, 0x7E, 82, 20, 54, 0x5F, 36, 0, 143, 101, + 1, 2, 3, 4, 2, 2, 3, 4, 3, 2, 3, 4, 4, 2, 3, 4, + 5, 2, 3, 4, 6, 2, 3, 4, 7, 2, 3, 4, 8, 2, 3, 4, 9, 2, 3, 4}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveData(&msg)), InterfaceStatus::AllocationFailed); + + // Data CRC is wrong + SharedMedium::add_rx({0x7E, 0x7E, 8, 10, 14, 0x9F, 32, 0, 205, 202, + 1, 2, 3, 4, 2, 2, 3, 4, 3, 2, 3, 4, 4, 2, 3, 4, + 5, 2, 3, 4, 6, 2, 3, 4, 7, 2, 3, 4, 8, 2, 3, 4}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&msg)), InterfaceStatus::Ok); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveData(&msg)), InterfaceStatus::DataInvalid); + } +} + +void +AmnbInterfaceTest::testInterlock() +{ + DeviceWrapper dev; + Interface interface(dev); + AmnbTestMessage tx_msg; + AmnbTestMessage rx_msg; + + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::MediumEmpty); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); + SharedMedium::reset(); + + SharedMedium::add_rx({0x7E}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({0x00}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::SyncReadFailed); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); + + // same but with read error + SharedMedium::reset(); + SharedMedium::fail_rx_index = 1; + SharedMedium::add_rx({0x7E}); + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({0x7E}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::SyncReadFailed); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); + + // same but with read error later + SharedMedium::add_rx({0x7E, 0x7E}); + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({207, 7, 4}); + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({0}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::Ok); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); + + // same but with read error later + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E}); + SharedMedium::fail_rx_index = 3; + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({207, 7, 4}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::HeaderReadFailed); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); + + // header invalid + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E}); + interface.receiveHeader(&rx_msg); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::MediumBusy); + + SharedMedium::add_rx({207, 7, 5, 0}); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.receiveHeader(&rx_msg)), InterfaceStatus::HeaderInvalid); + TEST_ASSERT_EQUALS(RF_CALL_BLOCKING(interface.transmit(&tx_msg)), InterfaceStatus::Ok); +} diff --git a/test/modm/communication/amnb/interface_test.hpp b/test/modm/communication/amnb/interface_test.hpp new file mode 100644 index 0000000000..2a41310cd7 --- /dev/null +++ b/test/modm/communication/amnb/interface_test.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_communication +class AmnbInterfaceTest : public unittest::TestSuite +{ +public: + void setUp() override; + + void testSerialize(); + void testFailures(); + void testInterlock(); +}; diff --git a/test/modm/communication/amnb/message_test.cpp b/test/modm/communication/amnb/message_test.cpp new file mode 100644 index 0000000000..124da43bc0 --- /dev/null +++ b/test/modm/communication/amnb/message_test.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include "message_test.hpp" +#include + +namespace modm +{ + +template +struct has_to_chars : std::false_type {}; +template +struct has_to_chars()))>> : std::true_type {}; + +template::value, int>::type = 0> +IOStream& +operator << (IOStream& stream, const T& v) +{ stream << to_chars(v); return stream; } + +} + +using namespace modm::amnb; + +class AmnbTestMessage : public Message +{ +public: + using Message::Message; + + using Message::isHeaderValid; + using Message::isDataValid; + using Message::setValid; + using Message::allocate; + using Message::self; + + Header& getHeader() { return header; } + Storage& getStorage() { return storage; } +}; + +// ---------------------------------------------------------------------------- +void +AmnbMessageTest::testConstructor() +{ + TEST_ASSERT_EQUALS(sizeof(Message), 32u); + + { + AmnbTestMessage msg; + TEST_ASSERT_EQUALS(msg.address(), 0u); + TEST_ASSERT_EQUALS(msg.command(), 0u); + TEST_ASSERT_EQUALS(msg.length(), 0u); + TEST_ASSERT_EQUALS(msg.type(), Type::Broadcast); + TEST_ASSERT_TRUE(msg.get() == nullptr); + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + msg.setValid(); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + } + { + AmnbTestMessage msg(4, 16); + TEST_ASSERT_EQUALS(msg.address(), 4u); + TEST_ASSERT_EQUALS(msg.command(), 16u); + TEST_ASSERT_EQUALS(msg.length(), 0u); + TEST_ASSERT_EQUALS(msg.type(), Type::Broadcast); + TEST_ASSERT_TRUE(msg.get() == nullptr); + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + msg.setValid(); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + } + { + struct Small + { + uint32_t val1{1}; + uint8_t val2{2}; + uint16_t val3{3}; + }; + AmnbTestMessage msg(6, 12, sizeof(Small)); + TEST_ASSERT_EQUALS(msg.address(), 6); + TEST_ASSERT_EQUALS(msg.command(), 12); + TEST_ASSERT_EQUALS(msg.length(), sizeof(Small)); + TEST_ASSERT_EQUALS(msg.type(), Type::Broadcast); + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + msg.setValid(); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + + const auto data = msg.get(); + TEST_ASSERT_TRUE(data != nullptr); + new (data) Small; + TEST_ASSERT_EQUALS(data->val1, 1u); + TEST_ASSERT_EQUALS(data->val2, 2u); + TEST_ASSERT_EQUALS(data->val3, 3u); + + // type too large for payload + TEST_ASSERT_TRUE(msg.get() == nullptr); + + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + msg.setValid(); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + } +} + +void +AmnbMessageTest::testAllocator() +{ + { + AmnbTestMessage msg(200); + TEST_ASSERT_TRUE(msg.allocate()); + } + { + struct Large + { + uint8_t buffer[42]; + }; + AmnbTestMessage msg(sizeof(Large)); + TEST_ASSERT_EQUALS(msg.address(), 0u); + TEST_ASSERT_EQUALS(msg.command(), 0u); + TEST_ASSERT_EQUALS(msg.length(), sizeof(Large)); + TEST_ASSERT_EQUALS(msg.type(), Type::Broadcast); + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + msg.setValid(); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + + const auto data = msg.get(); + TEST_ASSERT_TRUE(data != nullptr); + TEST_ASSERT_TRUE(data->buffer != nullptr); + + // type too large + TEST_ASSERT_TRUE(msg.get() == nullptr); + } +} + +void +AmnbMessageTest::testLifetime() +{ + { + AmnbTestMessage msg(10); + *msg.get() = 0x10; + TEST_ASSERT_EQUALS(*msg.get(), 0x10); + { + AmnbTestMessage short_msg(20); + *short_msg.get() = 0x20; + TEST_ASSERT_EQUALS(*short_msg.get(), 0x20); + msg = short_msg; + } + TEST_ASSERT_EQUALS(msg.length(), 20); + TEST_ASSERT_EQUALS(*msg.get(), 0x20); + } + { + AmnbTestMessage msg(40); + *msg.get() = 0x40; + TEST_ASSERT_EQUALS(*msg.get(), 0x40); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + { + AmnbTestMessage short_msg(50); + *short_msg.get() = 0x50; + TEST_ASSERT_EQUALS(*short_msg.get(), 0x50); + TEST_ASSERT_EQUALS(*(short_msg.getStorage().large.data), 1); + + short_msg = msg; + TEST_ASSERT_EQUALS(*short_msg.get(), 0x40); + TEST_ASSERT_EQUALS(msg.length(), 40); + TEST_ASSERT_EQUALS(msg.getStorage().large.data, short_msg.getStorage().large.data); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 2); + } + TEST_ASSERT_TRUE(msg.get()); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + } + { + AmnbTestMessage msg(40); + *msg.get() = 0x40; + TEST_ASSERT_EQUALS(*msg.get(), 0x40); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + { + AmnbTestMessage short_msg(50); + *short_msg.get() = 0x50; + TEST_ASSERT_EQUALS(*short_msg.get(), 0x50); + + msg = short_msg; + TEST_ASSERT_EQUALS(*msg.get(), 0x50); + TEST_ASSERT_EQUALS(msg.length(), 50); + TEST_ASSERT_EQUALS(msg.getStorage().large.data, short_msg.getStorage().large.data); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 2); + } + TEST_ASSERT_EQUALS(msg.length(), 50); + TEST_ASSERT_TRUE(msg.get()); + TEST_ASSERT_EQUALS(*msg.get(), 0x50); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + } + { + AmnbTestMessage msg(40); + *msg.get() = 0x40; + TEST_ASSERT_EQUALS(*msg.get(), 0x40); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + { + AmnbTestMessage short_msg(50); + *short_msg.get() = 0x50; + TEST_ASSERT_EQUALS(*short_msg.get(), 0x50); + + msg = std::move(short_msg); + TEST_ASSERT_TRUE(short_msg.get() == nullptr); + TEST_ASSERT_EQUALS(short_msg.length(), 0); + TEST_ASSERT_EQUALS(*msg.get(), 0x50); + TEST_ASSERT_EQUALS(msg.length(), 50); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + } + TEST_ASSERT_EQUALS(msg.length(), 50); + TEST_ASSERT_TRUE(msg.get()); + TEST_ASSERT_EQUALS(*msg.get(), 0x50); + TEST_ASSERT_EQUALS(*(msg.getStorage().large.data), 1); + } +} + + +#include + +void +AmnbMessageTest::testSerialize() +{ + { + AmnbTestMessage msg(6, 12, 4, Type::Request); + *msg.get() = 0x08070605ul; + msg.setValid(); + + const uint8_t raw[] = {197, 6, 12, 0x44, 5, 6, 7, 8}; + TEST_ASSERT_EQUALS_ARRAY(msg.self(), raw, sizeof(raw)); + } + { + AmnbTestMessage msg; + const uint8_t raw[] = {197, 4, 15, 0x64, 0, 1, 2, 3}; + std::memcpy(msg.self(), raw, sizeof(raw)); + + TEST_ASSERT_EQUALS(msg.address(), 4); + TEST_ASSERT_EQUALS(msg.command(), 15); + TEST_ASSERT_EQUALS(msg.length(), 4); + TEST_ASSERT_EQUALS(msg.type(), Type::Response); + TEST_ASSERT_TRUE(msg.isHeaderValid()); + TEST_ASSERT_TRUE(msg.isDataValid()); + const auto data = msg.get(); + TEST_ASSERT_TRUE(data != nullptr); + TEST_ASSERT_EQUALS(*data, 0x03020100ul); + } + // failures + { + AmnbTestMessage msg; + // wrong length, wrong crc + const uint8_t raw[] = {18, 4, 15, 0xde}; + std::memcpy(msg.self(), raw, sizeof(raw)); + TEST_ASSERT_EQUALS(msg.length(), 0u); // assumes large frame + TEST_ASSERT_FALSE(msg.isHeaderValid()); + TEST_ASSERT_FALSE(msg.isDataValid()); + } + + /* + { + AmnbTestMessage msg(0x10, 0x80, 1, Type::UserError); + // *msg.get() = 0x07060504ul; + // *msg.get() = Error::AllocationFailed; + *msg.get() = 0x07; + msg.setValid(); + + const uint8_t raw[32] = {0}; + TEST_ASSERT_EQUALS_ARRAY(msg.self(), raw, sizeof(raw)); + } + //*/ +} diff --git a/test/modm/communication/amnb/message_test.hpp b/test/modm/communication/amnb/message_test.hpp new file mode 100644 index 0000000000..abab77d365 --- /dev/null +++ b/test/modm/communication/amnb/message_test.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_communication +class AmnbMessageTest : public unittest::TestSuite +{ +public: + void + testConstructor(); + + void + testAllocator(); + + void + testLifetime(); + + void + testSerialize(); +}; diff --git a/test/modm/communication/amnb/node_test.cpp b/test/modm/communication/amnb/node_test.cpp new file mode 100644 index 0000000000..d05943ca9e --- /dev/null +++ b/test/modm/communication/amnb/node_test.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "node_test.hpp" +#include +#include +#include +#include + +using namespace modm::amnb; +using namespace modm_test; +using namespace std::chrono_literals; +using milli_clock = modm_test::chrono::milli_clock; +using micro_clock = modm_test::chrono::micro_clock; + +void +AmnbNodeTest::setUp() +{ + SharedMedium::reset(); +} + +// ---------------------------------------------------------------------------- +void +AmnbNodeTest::testBroadcast() +{ + DeviceWrapper dev; + Node node(dev, 0x08); + { + node.broadcast(0x10); + node.broadcast(0x20, uint32_t(0x03020100)); + node.update(); node.update(); + const uint8_t raw[] = {0x7E, 0x7E, 84, 0x08, 0x10, 0, 0x7E, 0x7E, 58, 0x08, 0x20, 4, 0, 1, 2, 3}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // test automatic retransmit + SharedMedium::reset(); + SharedMedium::fail_tx_index = 2; + { + node.broadcast(0x70); + for(uint32_t ii=0; ii < 10000; ii += 10) { node.update(); micro_clock::setTime(ii); } + // first transmission attempt fails + const uint8_t raw[] = {0x7E, 0x7E, 110, 0x7E, 0x7E, 110, 0x08, 0x70, 0}; + TEST_ASSERT_EQUALS(SharedMedium::raw_transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::raw_transmitted, raw, sizeof(raw)); + } +} + +void +AmnbNodeTest::testRequest() +{ + DeviceWrapper dev; + Node node(dev, 0x08); + // should discard unrelated response + SharedMedium::add_queued_rx({0x7E, 0x7E, 251, 0x08, 0x70, 0xc0}, 6); + { + micro_clock::setTime(0); milli_clock::setTime(0); + modm::ResumableResult< Result<> > res{0}; + for(uint32_t ii=0; (res = node.request(0x10, 0x80)).getState() == modm::rf::Running; ii += 10) + { micro_clock::setTime(ii); milli_clock::setTime(ii/1000); node.update(); } + + TEST_ASSERT_EQUALS(res.getResult().error(), Error::RequestTimeout); + TEST_ASSERT_EQUALS(SharedMedium::tx_count, 6u); + TEST_ASSERT_EQUALS(SharedMedium::rx_count, 12u); + } + SharedMedium::reset(); + // System error allocation failed + SharedMedium::add_queued_rx({0x7E, 0x7E, 217, 16, 128, 129, 4}, 6); + { + micro_clock::setTime(0); milli_clock::setTime(0); + modm::ResumableResult< Result<> > res{0}; + for(uint32_t ii=0; (res = node.request(0x10, 0x80)).getState() == modm::rf::Running; ii += 10) + { micro_clock::setTime(ii); milli_clock::setTime(ii/1000); node.update(); } + + TEST_ASSERT_EQUALS(res.getResult().error(), Error::ResponseAllocationFailed); + + TEST_ASSERT_EQUALS(SharedMedium::tx_count, 6u); + TEST_ASSERT_EQUALS(SharedMedium::rx_count, 13u); + } + SharedMedium::reset(); + // System error allocation failed + SharedMedium::add_queued_rx({0x7E, 0x7E, 32, 16, 128, 161, 7}, 6); + { + micro_clock::setTime(0); milli_clock::setTime(0); + modm::ResumableResult< Result > res{0}; + for(uint32_t ii=0; (res = node.request(0x10, 0x80, uint8_t(0x10))).getState() == modm::rf::Running; ii += 10) + { micro_clock::setTime(ii); milli_clock::setTime(ii/1000); node.update(); } + + TEST_ASSERT_EQUALS(res.getResult().error(), Error::UserError); + TEST_ASSERT_EQUALS(*res.getResult().resultError(), 0x07u); + + TEST_ASSERT_EQUALS(SharedMedium::tx_count, 7u); + TEST_ASSERT_EQUALS(SharedMedium::rx_count, 14u); + } + SharedMedium::reset(); + // should discard unrelated response + SharedMedium::add_queued_rx({0x7E, 0x7E, 225, 0x08, 0x70, 0x60}); + // should discard invalid response + SharedMedium::add_queued_rx({0x7E, 0x7E, 0, 0x10, 0x80, 0x60}); + // should use real valid response + SharedMedium::add_queued_rx({0x7E, 0x7E, 243, 16, 128, 100, 4, 5, 6, 7}, 10); + { + micro_clock::setTime(0); milli_clock::setTime(0); + modm::ResumableResult< Result > res{0}; + for(uint32_t ii=0; + (res = node.request(0x10, 0x80, uint32_t(0x03020100))).getState() == modm::rf::Running; + ii += 10) + { micro_clock::setTime(ii); milli_clock::setTime(ii/1000); node.update(); } + + TEST_ASSERT_EQUALS(res.getResult().error(), Error::Ok); + TEST_ASSERT_EQUALS(*res.getResult().result(), 0x07060504ul); + + TEST_ASSERT_EQUALS(SharedMedium::tx_count, 10u); + TEST_ASSERT_EQUALS(SharedMedium::rx_count, 32u); + } +} + +void +AmnbNodeTest::testAction() +{ + static uint8_t trig{0}; + const Action actions[] = + { + {1, []() + { + trig |= 1; + } + }, + {2, +[](const uint32_t& data) + { + TEST_ASSERT_EQUALS(data, uint32_t(0x03020100)); + trig |= 2; + } + }, + {3, +[](const uint32_t& data) -> Response + { + TEST_ASSERT_EQUALS(data, uint32_t(0x03020100)); + trig |= 4; + return uint32_t(0x07060504); + } + }, + {4, []() -> Response + { + trig |= 8; + return ErrorResponse(uint8_t(3)); + } + }, + {5, +[](const uint32_t& data) -> Response + { + TEST_ASSERT_EQUALS(data, uint32_t(0x03020100)); + trig |= 16; + return ErrorResponse(uint32_t(0xbaadc0de)); + } + }, + }; + DeviceWrapper dev; + Node node(dev, 0x08, actions); + + // System Error: Wrong payload size! + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 104, 8, 1, 68, 0, 1, 2, 3}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 0); + const uint8_t raw[] = {0x7E, 0x7E, 237, 8, 1, 97, 2}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // System Error: No action! + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 163, 8, 10, 64}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 0); + const uint8_t raw[] = {0x7E, 0x7E, 76, 8, 10, 129, 3}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + + // Positive Response without argument + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 227, 8, 1, 64}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 1); + const uint8_t raw[] = {0x7E, 0x7E, 42, 8, 1, 96}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // Positive Response with argument + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 141, 8, 2, 68, 0, 1, 2, 3}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 2); + const uint8_t raw[] = {0x7E, 0x7E, 209, 8, 2, 96}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // Positive Reponse with argument and return value + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 46, 8, 3, 68, 0, 1, 2, 3}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 4); + const uint8_t raw[] = {0x7E, 0x7E, 84, 8, 3, 100, 4, 5, 6, 7}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // Negative Response without argument + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 233, 8, 4, 64}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 8); + const uint8_t raw[] = {0x7E, 0x7E, 94, 8, 4, 161, 3}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } + // Negative Response with argument + trig = 0; + SharedMedium::reset(); + SharedMedium::add_rx({0x7E, 0x7E, 227, 8, 5, 68, 0, 1, 2, 3}); + { + node.update(); node.update(); + TEST_ASSERT_EQUALS(trig, 16); + const uint8_t raw[] = {0x7E, 0x7E, 133, 8, 5, 164, 0xde, 0xc0, 0xad, 0xba}; + TEST_ASSERT_EQUALS(SharedMedium::transmitted.size(), sizeof(raw)); + TEST_ASSERT_EQUALS_ARRAY(SharedMedium::transmitted, raw, sizeof(raw)); + } +} + +void +AmnbNodeTest::testListener() +{ + static uint8_t trig{0}; + const Listener listeners[] = + { + {1, [](uint8_t sender) + { + TEST_ASSERT_EQUALS(sender, 0x20); + trig |= 1; + } + }, + {2, +[](uint8_t sender, const uint32_t& data) + { + TEST_ASSERT_EQUALS(sender, 0x10); + TEST_ASSERT_EQUALS(data, uint32_t(0x03020100)); + trig |= 2; + } + }, + }; + DeviceWrapper dev; + Node node(dev, 8, listeners); + + trig = 0; + SharedMedium::add_rx({0x7E, 0x7E, 4, 32, 1, 0}); + { + node.update(); + TEST_ASSERT_EQUALS(trig, 1); + node.update(); + TEST_ASSERT_EQUALS(trig, 1); + } + trig = 0; + SharedMedium::add_rx({0x7E, 0x7E, 102, 16, 2, 4, 0, 1, 2, 3}); + { + node.update(); + TEST_ASSERT_EQUALS(trig, 2); + node.update(); + TEST_ASSERT_EQUALS(trig, 2); + } + trig = 0; + SharedMedium::add_rx({0x7E, 0x7E, 4, 32, 1, 0}); + SharedMedium::add_rx({0x7E, 0x7E, 102, 16, 2, 4, 0, 1, 2, 3}); + { + node.update(); + TEST_ASSERT_EQUALS(trig, 1); + node.update(); + TEST_ASSERT_EQUALS(trig, 3); + } +} diff --git a/test/modm/communication/amnb/node_test.hpp b/test/modm/communication/amnb/node_test.hpp new file mode 100644 index 0000000000..9f577560db --- /dev/null +++ b/test/modm/communication/amnb/node_test.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_communication +class AmnbNodeTest : public unittest::TestSuite +{ +public: + void setUp() override; + + void testBroadcast(); + void testRequest(); + void testAction(); + void testListener(); +}; diff --git a/test/modm/communication/module.lb b/test/modm/communication/module.lb index 93175ceb51..7d94e632fd 100644 --- a/test/modm/communication/module.lb +++ b/test/modm/communication/module.lb @@ -10,6 +10,19 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +class Amnb(Module): + def init(self, module): + module.name = "amnb" + + def prepare(self, module, options): + module.depends("modm:communication:amnb", ":mock:clock", ":mock:shared_medium") + return True + + def build(self, env): + env.outbasepath = "modm-test/src/modm-test/communication" + env.copy("amnb") + + class Sab(Module): def init(self, module): module.name = "sab" @@ -40,6 +53,7 @@ def init(module): module.name = ":test:communication" def prepare(module, options): + module.add_submodule(Amnb()) module.add_submodule(Sab()) module.add_submodule(Xpcc()) return True diff --git a/test/modm/mock/module.lb b/test/modm/mock/module.lb index 1c6af50d9d..03ec138194 100644 --- a/test/modm/mock/module.lb +++ b/test/modm/mock/module.lb @@ -62,6 +62,18 @@ class IoDevice(Module): env.copy("io_device.hpp") env.copy("io_device.cpp") +class SharedMedium(Module): + def init(self, module): + module.name = "shared_medium" + + def prepare(self, module, options): + module.depends(":stdc++") + return True + + def build(self, env): + env.outbasepath = "modm-test/src/modm-test/mock" + env.copy("shared_medium.hpp") + def init(module): module.name = ":mock" @@ -71,6 +83,7 @@ def prepare(module, options): module.add_submodule(SpiDevice()) module.add_submodule(SpiMaster()) module.add_submodule(IoDevice()) + module.add_submodule(SharedMedium()) return True def build(env): diff --git a/test/modm/mock/shared_medium.hpp b/test/modm/mock/shared_medium.hpp new file mode 100644 index 0000000000..fcbc9fb826 --- /dev/null +++ b/test/modm/mock/shared_medium.hpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once +#include + +namespace modm_test +{ + +class SharedMedium +{ +public: + inline static bool + write(uint8_t data) + { + raw_transmitted.push_back(data); + transmitted.push_back(data); + // corrupt the data on the shared medium + if (fail_tx_index < ++tx_count) + data ^= 0x10; + // loop back on shared medium + received.push_back(data); + raw_received.push_back(data); + return true; + } + + inline static bool + read(uint8_t& byte) + { + if (received.empty()) { + if (rx_queue.empty()) return false; + received = std::move(rx_queue); + rx_queue_index = size_t(-1); + } + byte = received.front(); + received.erase(received.begin()); + rx_count++; + return true; + } + + inline static bool + hasError() + { + return (fail_rx_index < rx_count); + } + + inline static void + clearError() + { + } + + inline static void + discardTransmitBuffer() + { + transmitted.clear(); + received.clear(); + tx_count = 0; + fail_tx_index = size_t(-1); + } + + inline static void + discardReceiveBuffer() + { + received.clear(); + rx_count = 0; + fail_rx_index = size_t(-1); + } + + inline static size_t + receiveBufferSize() + { + if (rx_queue_index <= tx_count) return 1; + return received.size(); + } + +public: + inline static void + reset() + { + discardTransmitBuffer(); + discardReceiveBuffer(); + clearError(); + rx_queue.clear(); + raw_transmitted.clear(); + raw_received.clear(); + rx_queue_index = size_t(-1); + } + + inline static void + add_rx(std::initializer_list data) + { + received.insert(received.end(), data); + } + inline static void + add_queued_rx(std::initializer_list data, size_t index=0) + { + rx_queue_index = index; + rx_queue.insert(rx_queue.end(), data); + } + +public: + static inline std::vector raw_received; + static inline std::vector raw_transmitted; + + static inline std::vector received; + static inline std::vector transmitted; + static inline size_t tx_count{0}; + static inline size_t rx_count{0}; + + static inline size_t fail_tx_index{size_t(-1)}; + static inline size_t fail_rx_index{size_t(-1)}; + + static inline std::vector rx_queue; + static inline size_t rx_queue_index{size_t(-1)}; +}; + +}