diff --git a/doc/YAP/README.md b/doc/YAP/README.md deleted file mode 100644 index 17ef4cda..00000000 --- a/doc/YAP/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Yet Another Protocol (YAP) - -The YAP is being developed for the communication between the Zumo Robot and a client application. - -## Table of Contents - -- [Network Architecture](#network-architecture) -- [Frame](#frame) -- [Control Channel](#control-channel-channel-0) - - [SYNC](#sync-d0--0x00) - - [SYNC_RSP](#sync_rsp-d0--0x01) - - [SCRB](#scrb-d0--0x02) - - [SCRB_RSP](#scrb_rsp-d0--0x03) -- [Internal Architecture](#internal-architecture) - ---- - -## Network Architecture - -- Server-Client Architecture -- One-to-one. One Server to one client. - ---- - -## Frame - -The Protocol sends and received Frames of the following form: - -```cpp -/** Data container of the Frame Fields */ -typedef union _Frame -{ - struct _Fields - { - /** Header */ - union _Header - { - struct _HeaderFields - { - /** Channel ID */ - uint8_t m_channel; - - /** Channel ID */ - uint8_t m_dlc; - - /** Frame Checksum */ - uint8_t m_checksum; - - } __attribute__((packed)) headerFields; - - /** Raw Header Data*/ - uint8_t rawHeader[HEADER_LEN]; - - } __attribute__((packed)) header; - - /** Payload */ - struct _Payload - { - /** Data of the Frame */ - uint8_t m_data[MAX_DATA_LEN]; - - } __attribute__((packed)) payload; - - } __attribute__((packed)) fields; - - /** Raw Frame Data */ - uint8_t raw[MAX_FRAME_LEN] = {0U}; - -} __attribute__((packed)) Frame; -``` - -### Header - -#### Channel Field - -- Length: 1 Byte. -- Channel on which the data is being sent. -- [Channel 0](#control-channel-channel-0) is reserved for the server. -- Channels 1 to 255 are "Data Channels". -- The Application can publish or subscribe to any of these channels using the channel's name. -- Client suscribes to a channel using [Channel 0](#control-channel-channel-0). - -#### DLC Field - -- Contains the size of the payload contained by the frame. - -#### Checksum Field - -- Simple Checksum. -- Applied to previous fields. -- checksum = sum(Channel + Data Bytes) % UINT8_MAX - -### Payload Field - -- Data Length: Set by the DLC Field. -- Contains Application Data Bytes. - ---- - -## Control Channel (Channel 0) - -- Control Channel: Exchange of Commands. Can not be used to send data. -- Channel 0 has no **external** Callback. The state of the server can be polled by the application through getter functions ( or similar depending on the implementation). -- D0 (Data Byte 0) is used as a Command Byte. Defines the command that is being sent. -- Even-number *Commands* in D0 are used as Commands, while uneven-number *Commands* are used as the response to the immediately previous (n-1) command. - -### SYNC (D0 = 0x00) - -- Server sends SYNC Command with current timestamp. -- Client responds with [SYNC_RSP](#sync_rsp-d0--0x01). -- Server can calculate Round-Trip-Time. -- SYNC Package must be sent periodically depending on current [State](#state-machine). The period is also used as a timeout for the previous SYNC. -- Used as a "Heartbeat" or "keep-alive" by the client. - -### SYNC_RSP (D0 = 0x01) - -- Client Response to [SYNC](#sync-d0--0x00). -- Data Payload is the same timestamp as in SYNC Command. - -### SCRB (D0 = 0x02) - -- Client sends the name of the channel it wants to suscribe to. -- Server responds with the number and the name of the requested channel, if it is found and valid. If the channel is not found, the response has channel number = 0. - -### SCRB_RSP (D0 = 0x03) - -- Server Response to [SCRB](#scrb-d0--0x02). -- Channel Number on Data Byte 1 (D1). -- Channel Name on the following bytes - ---- - -## Internal Architecture - -### Data - -- Information is sent directly from application to the Serial Driver. No queueing or buffering. -- The Protocol can send a maximum of 255 Bytes. - -### Channels - -```cpp -/** - * Channel Definition. - */ -struct Channel -{ - char m_name[CHANNEL_NAME_MAX_LEN]; /**< Name of the channel. */ - uint8_t m_dlc; /**< Payload length of channel */ - ChannelCallback m_callback; /**< Callback to provide received data to the application. */ - - /** - * Channel Constructor. - */ - Channel() : m_name{0U}, m_dlc(0U), m_callback(nullptr) - { - } -}; -``` - -- Channel has 3 members: Name, DLC, and callback function. - -### Channel Creation and Subscription - -![CreateSubscribeSequence](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/BlueAndi/RadonUlzer/main/doc/YAP/SubscribeSequence.puml) - -#### Channel Creation - -- Application initializes a channel with a name and a DLC, protocol looks for a free channel number and returns its channel number to the application. -- If no channel is free, it returns 0 as it is an invalid Data Channel. - -#### Channel Subscription - -- Application can subscribe to a remote data channel by its name and a callback to the function that must be called when data is received in said channel. -- Function has no return value, as the response from the server is asynchron. - -### Callback - -```cpp -/** - * Channel Notification Prototype Callback. - * Provides the received data in the respective channel to the application. - */ -typedef void (*ChannelCallback)(const uint8_t* payload, const uint8_t payloadSize); -``` - -- Callback passes only a pointer to the received Buffer. Data must be copied by application. -- Memory is freed by the protocol after the callback is done. -- DLC is passed as payloadSize to the application. - -### State Machine - -#### Out-of-Sync - -- Client is disconnected/does not respond to SYNC Command. -- No external data is sent in this state. -- SYNC Period set to 1 second. - -#### Synced - -- Client is connected and responds to SYNC Commands. -- SYNC Period set to 5 seconds. diff --git a/doc/YAP/SubscribeSequence.puml b/doc/YAP/SubscribeSequence.puml deleted file mode 100644 index 595731c6..00000000 --- a/doc/YAP/SubscribeSequence.puml +++ /dev/null @@ -1,41 +0,0 @@ -@startuml Subscribe to Channels - -actor Application_1 as app1 -participant Server_1 as s1 -participant Server_2 as s2 -actor Application_2 as app2 - - -== Setup == - -app2 -> s2 ++: createChannel(channelName, DLC) -s2 -> s2 : Copy Channel Name and\nDLC to txChannels Array -s2 -> s2 : Increase txChannels Counter -return - -app1 -> s1 ++: subscribeToChannel(channelName, Callback) -s1 -> s1 : Copy Channel Name and\nCB to pendingChannels Array -s1 -> s1 : Increase pendingChannels Counter -return - - -== Loop == -...Servers succesfully synchronized... - -s1 ->> s2: SCRB Command:\nSends Channel Name - -alt Valid Channel - ||| - s2 ->> s1: SCRB_RSP Command:\nSends Channel Number and\nChannel Name - s1 ->> s1: Copy CB from pendingChannels Array\nto rxCallbacks Array - s1 -> s1 : Increase rxChannels Counter - ||| -else Invalid/Not Found Channel - ||| - s2 ->> s1: SCRB_RSP Command:\nSends Channel Number = 0\nand Channel Name - ||| -end - -s1 -> s1 : Decrease pendingChannels Counter - -@enduml \ No newline at end of file diff --git a/doc/YAP/SyncSequence.puml b/doc/YAP/SyncSequence.puml deleted file mode 100644 index 611168aa..00000000 --- a/doc/YAP/SyncSequence.puml +++ /dev/null @@ -1,40 +0,0 @@ -@startuml Synchronization Sequence YAPServer -Title Synchronization Sequence - -participant Server_1 -participant Server_2 - -note across: Both Servers are out of Sync -== SYNC of Server_1 == - -Server_1 ->> Server_2 : SYNC -note left: Server_1 wants to send Data Frames. Sends SYNC - -Server_2 -->> Server_1 : SYNC_RSP -note left: Only Server_1 is Synced\n and can send Data Frames -note right: Server_2 remains as listen-only.\n It does not know that its RSP got through - -== SYNC of Server_2 == - -Server_2 ->> Server_1 : SYNC -note right: Server_2 wants to send Data Frames. Sends SYNC - -Server_1 -->> Server_2 : SYNC_RSP -note left: Server_1 is already Synced.\nCan still reply with SYNC_RSP -note right: Server_2 is Synced\nand can send Data Frames - -note across: Both Servers are Synced - -== Server_1 falls Out-Of-Sync == -Server_1 ->> Server_2 : SYNC -note left: Periodic Heartbeat - -Server_2 --x Server_1 : SYNC_RSP -note right: Server_2 may or may not respond. -note left: After timeout, Server_1 cannot\nsend Data Frames - -Server_2 ->> Server_1 : SYNC -Server_1 -->> Server_2 : SYNC_RSP -note right: Server_2 remains synced. - -@enduml \ No newline at end of file diff --git a/doc/architecture/REMOTECONTROL.md b/doc/architecture/REMOTECONTROL.md index 78938aa7..49297f13 100644 --- a/doc/architecture/REMOTECONTROL.md +++ b/doc/architecture/REMOTECONTROL.md @@ -1,7 +1,7 @@ # Radon Ulzer - Remote Control * [General](#general) - * [YAP Channels](#yap-channels) + * [SerialMuxProt Channels](#serialmuxprot-channels) * [Rx channel "REMOTE\_CMD"](#rx-channel-remote_cmd) * [Tx channel "REMOTE\_RSP"](#tx-channel-remote_rsp) * [Rx channel "MOT\_SPEEDS"](#rx-channel-mot_speeds) @@ -22,13 +22,13 @@ # General -The robot can be controlled remotely by using the YAP protocol. +The robot can be controlled remotely by using the SerialMuxProt protocol. On target the physical communication uses the serial. On simulation the physical communication uses a socket connection. -## YAP Channels +## SerialMuxProt Channels ### Rx channel "REMOTE_CMD" This channel is used to receive commands, which will be executed by the application in RemoteCtrl state. A command related response will be sent via the "REMOTE_RSP" channel. diff --git a/doc/architecture/uml/LogicalView/Service.plantuml b/doc/architecture/uml/LogicalView/Service.plantuml index 205bdbcc..f50d85b6 100644 --- a/doc/architecture/uml/LogicalView/Service.plantuml +++ b/doc/architecture/uml/LogicalView/Service.plantuml @@ -64,9 +64,9 @@ package "Service" as serviceLayer { speed with PID controllers. end note - class YAP <> + class SerialMuxProt <> - note top of YAP + note top of SerialMuxProt Communication Protocol for transmitting and receiving information to/from a remote client using data channels diff --git a/doc/configuration/uml/RemoteControlSim.puml b/doc/configuration/uml/RemoteControlSim.puml index dc2fd2a4..8607dbde 100644 --- a/doc/configuration/uml/RemoteControlSim.puml +++ b/doc/configuration/uml/RemoteControlSim.puml @@ -12,15 +12,15 @@ node "PC" { component "DiffDrive" <> as diffdrive component "SocketServer" <> as socketServer - component "YAP" <> as yap + component "SerialMuxProt" <> as smp component "HAL" <> as hal - app ..> yap: <> + app ..> smp: <> app <.. odometry: <> app <.. speedometer: <> app ..> diffdrive: <> - yap <..> socketServer: <> + smp <..> socketServer: <> odometry ..> hal: <> speedometer ..> hal: <> diff --git a/lib/ConvoyLeader/App.cpp b/lib/ConvoyLeader/App.cpp index fd394a3a..9dee3894 100644 --- a/lib/ConvoyLeader/App.cpp +++ b/lib/ConvoyLeader/App.cpp @@ -73,12 +73,12 @@ void App::setup() Board::getInstance().init(); m_systemStateMachine.setState(&StartupState::getInstance()); m_controlInterval.start(DIFFERENTIAL_DRIVE_CONTROL_PERIOD); - m_yapServer.createChannel(POSITION_CHANNEL, POSITION_CHANNEL_DLC); + m_smpServer.createChannel(POSITION_CHANNEL, POSITION_CHANNEL_DLC); } void App::loop() { - m_yapServer.process(millis()); + m_smpServer.process(millis()); Speedometer::getInstance().process(); if (true == m_controlInterval.isTimeout()) @@ -95,7 +95,7 @@ void App::loop() */ Odometry::getInstance().process(); - /* Send Position to YAP Client */ + /* Send Position to SerialMuxProt Client */ reportPosition(); m_controlInterval.restart(); @@ -123,7 +123,7 @@ void App::reportPosition() Util::int32ToByteArray(&outBuf[0U], (sizeof(outBuf) - sizeof(int32_t)), xPos); Util::int32ToByteArray(&outBuf[4U], (sizeof(outBuf) - sizeof(int32_t)), yPos); - m_yapServer.sendData(POSITION_CHANNEL, outBuf, sizeof(outBuf)); + m_smpServer.sendData(POSITION_CHANNEL, outBuf, sizeof(outBuf)); } void App::positionCallback(const uint8_t* payload, const uint8_t payloadSize) diff --git a/lib/ConvoyLeader/App.h b/lib/ConvoyLeader/App.h index 2b33dc36..8f8cd5af 100644 --- a/lib/ConvoyLeader/App.h +++ b/lib/ConvoyLeader/App.h @@ -45,7 +45,7 @@ *****************************************************************************/ #include #include -#include +#include #include /****************************************************************************** @@ -67,7 +67,7 @@ class App App() : m_systemStateMachine(), m_controlInterval(), - m_yapServer(Serial) + m_smpServer(Serial) { } @@ -109,16 +109,16 @@ class App SimpleTimer m_controlInterval; /** - * YAP Server Instance + * SerialMuxProt Server Instance * * @tparam tMaxChannels set to 10, as App does not require * more channels for external communication. */ - YAPServer<10U> m_yapServer; + SerialMuxProtServer<10U> m_smpServer; /** * Report the current position of the robot using the Odometry data. - * Sends data through the YAPServer. + * Sends data through the SerialMuxProtServer. */ void reportPosition(); diff --git a/lib/ConvoyLeader/library.json b/lib/ConvoyLeader/library.json index 94916605..d71b015f 100644 --- a/lib/ConvoyLeader/library.json +++ b/lib/ConvoyLeader/library.json @@ -13,6 +13,9 @@ "name": "Service" }, { "name": "HALInterfaces" + }, { + "name": "SerialMuxProt", + "version" : "https://github.com/gabryelreyes/SerialMuxProt.git" }], "frameworks": "*", "platforms": "*" diff --git a/lib/RemoteControl/App.cpp b/lib/RemoteControl/App.cpp index 038cad7e..11bbc951 100644 --- a/lib/RemoteControl/App.cpp +++ b/lib/RemoteControl/App.cpp @@ -40,6 +40,7 @@ #include #include #include +#include /****************************************************************************** * Compiler Switches @@ -95,19 +96,19 @@ void App::setup() m_sendLineSensorsDataInterval.start(SEND_LINE_SENSORS_DATA_PERIOD); /* Remote control commands/responses */ - m_yapServer.subscribeToChannel(CH_NAME_CMD, App_cmdChannelCallback); - m_yapChannelIdRemoteCtrlRsp = m_yapServer.createChannel(CH_NAME_RSP, sizeof(RemoteCtrlState::RspId)); + m_smpServer.subscribeToChannel(CH_NAME_CMD, App_cmdChannelCallback); + m_smpChannelIdRemoteCtrlRsp = m_smpServer.createChannel(CH_NAME_RSP, sizeof(RemoteCtrlState::RspId)); /* Receiving linear motor speed left/right */ - m_yapServer.subscribeToChannel(CH_NAME_MOTOR_SPEEDS, App_motorSpeedsChannelCallback); + m_smpServer.subscribeToChannel(CH_NAME_MOTOR_SPEEDS, App_motorSpeedsChannelCallback); /* Providing line sensor data */ - m_yapChannelIdLineSensors = m_yapServer.createChannel(CH_NAME_LINE_SENSORS, lineSensorChannelDlc); + m_smpChannelIdLineSensors = m_smpServer.createChannel(CH_NAME_LINE_SENSORS, lineSensorChannelDlc); } void App::loop() { - m_yapServer.process(millis()); + m_smpServer.process(millis()); Speedometer::getInstance().process(); if (true == m_controlInterval.isTimeout()) @@ -164,7 +165,7 @@ void App::sendRemoteControlResponses() { const uint8_t* payload = reinterpret_cast(&remoteControlRspId); - (void)m_yapServer.sendData(m_yapChannelIdRemoteCtrlRsp, payload, sizeof(remoteControlRspId)); + (void)m_smpServer.sendData(m_smpChannelIdRemoteCtrlRsp, payload, sizeof(remoteControlRspId)); m_lastRemoteControlRspId = remoteControlRspId; } @@ -186,7 +187,7 @@ void App::sendLineSensorsData() const ++lineSensorIdx; } - (void)m_yapServer.sendData(m_yapChannelIdLineSensors, payload, sizeof(payload)); + (void)m_smpServer.sendData(m_smpChannelIdLineSensors, payload, sizeof(payload)); } /****************************************************************************** @@ -198,7 +199,7 @@ void App::sendLineSensorsData() const *****************************************************************************/ /** - * Receives remote control commands over YAP channel. + * Receives remote control commands over SerialMuxProt channel. * * @param[in] payload Command id * @param[in] payloadSize Size of command id @@ -214,7 +215,7 @@ static void App_cmdChannelCallback(const uint8_t* payload, const uint8_t payload } /** - * Receives motor speeds over YAP channel. + * Receives motor speeds over SerialMuxProt channel. * * @param[in] payload Motor speed left/right * @param[in] payloadSize Size of twice motor speeds diff --git a/lib/RemoteControl/App.h b/lib/RemoteControl/App.h index 21e17539..7e3c7813 100644 --- a/lib/RemoteControl/App.h +++ b/lib/RemoteControl/App.h @@ -45,7 +45,7 @@ *****************************************************************************/ #include #include -#include +#include #include "RemoteCtrlState.h" @@ -68,9 +68,9 @@ class App m_systemStateMachine(), m_controlInterval(), m_sendLineSensorsDataInterval(), - m_yapServer(Serial), - m_yapChannelIdRemoteCtrlRsp(0U), - m_yapChannelIdLineSensors(0U), + m_smpServer(Serial), + m_smpChannelIdRemoteCtrlRsp(0U), + m_smpChannelIdLineSensors(0U), m_lastRemoteControlRspId(RemoteCtrlState::RSP_ID_OK) { } @@ -99,16 +99,16 @@ class App /** Sending Data period in ms. */ static const uint32_t SEND_LINE_SENSORS_DATA_PERIOD = 20; - /** YAP channel name for receiving commands. */ + /** SerialMuxProt channel name for receiving commands. */ static const char* CH_NAME_CMD; - /** YAP channel name for sending command responses. */ + /** SerialMuxProt channel name for sending command responses. */ static const char* CH_NAME_RSP; - /** YAP channel name for receiving motor sppeds. */ + /** SerialMuxProt channel name for receiving motor sppeds. */ static const char* CH_NAME_MOTOR_SPEEDS; - /** YAP channel name for sending line sensors data. */ + /** SerialMuxProt channel name for sending line sensors data. */ static const char* CH_NAME_LINE_SENSORS; /** The system state machine. */ @@ -121,18 +121,18 @@ class App SimpleTimer m_sendLineSensorsDataInterval; /** - * YAP Server Instance + * SerialMuxProt Server Instance * * @tparam tMaxChannels set to 10, as App does not require * more channels for external communication. */ - YAPServer<10U> m_yapServer; + SerialMuxProtServer<10U> m_smpServer; /** Channel id sending remote control command responses. */ - uint8_t m_yapChannelIdRemoteCtrlRsp; + uint8_t m_smpChannelIdRemoteCtrlRsp; /** Channel id sending line sensors data. */ - uint8_t m_yapChannelIdLineSensors; + uint8_t m_smpChannelIdLineSensors; /** Last remote control response id */ RemoteCtrlState::RspId m_lastRemoteControlRspId; @@ -146,7 +146,7 @@ class App void sendRemoteControlResponses(); /** - * Send line sensors data via YAP. + * Send line sensors data via SerialMuxProt. */ void sendLineSensorsData() const; }; diff --git a/lib/RemoteControl/RemoteCtrlState.h b/lib/RemoteControl/RemoteCtrlState.h index 3a74aeb7..af60b934 100644 --- a/lib/RemoteControl/RemoteCtrlState.h +++ b/lib/RemoteControl/RemoteCtrlState.h @@ -45,7 +45,6 @@ *****************************************************************************/ #include #include -#include /****************************************************************************** * Macros diff --git a/lib/RemoteControl/library.json b/lib/RemoteControl/library.json index b8d48d50..423fd7f4 100644 --- a/lib/RemoteControl/library.json +++ b/lib/RemoteControl/library.json @@ -13,6 +13,9 @@ "name": "Service" }, { "name": "HALInterfaces" + }, { + "name": "SerialMuxProt", + "version" : "https://github.com/gabryelreyes/SerialMuxProt.git" }], "frameworks": "*", "platforms": "*" diff --git a/lib/Service/YAPCommon.hpp b/lib/Service/YAPCommon.hpp deleted file mode 100644 index b666aac7..00000000 --- a/lib/Service/YAPCommon.hpp +++ /dev/null @@ -1,178 +0,0 @@ -/* MIT License - -Copyright (c) 2023 Gabryel Reyes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -/******************************************************************************* - DESCRIPTION -*******************************************************************************/ -/** - * @brief Common Constants and Structures of the Yet Another Protocol YAP - * @author Gabryel Reyes - * - * @addtogroup Service - * - * @{ - */ - -#ifndef YAP_COMMON_H_ -#define YAP_COMMON_H_ - -/****************************************************************************** - * Includes - *****************************************************************************/ - -#include - -/****************************************************************************** - * Macros - *****************************************************************************/ - -/** Channel Field Length in Bytes */ -#define CHANNEL_LEN (1U) - -/** DLC Field Length in Bytes */ -#define DLC_LEN (1U) - -/** Checksum Field Length in Bytes */ -#define CHECKSUM_LEN (1U) - -/** Length of Complete Header Field */ -#define HEADER_LEN (CHANNEL_LEN + DLC_LEN + CHECKSUM_LEN) - -/** Data Field Length in Bytes */ -#define MAX_DATA_LEN (32U) - -/** Total Frame Length in Bytes */ -#define MAX_FRAME_LEN (HEADER_LEN + MAX_DATA_LEN) - -/** Max length of channel name */ -#define CHANNEL_NAME_MAX_LEN (10U) - -/** Available Bytes in Control Channel Payload for data. */ -#define CONTROL_CHANNEL_PAYLOAD_DATA_LENGTH (4U) - -/* Length of Command in Bytes */ -#define CONTROL_CHANNEL_CMD_BYTE_LENGTH (1U) - -/** Number of Control Channel. */ -#define CONTROL_CHANNEL_NUMBER (0U) - -/** DLC of Heartbeat Command. */ -#define CONTROL_CHANNEL_PAYLOAD_LENGTH (CHANNEL_NAME_MAX_LEN + CONTROL_CHANNEL_PAYLOAD_DATA_LENGTH + CONTROL_CHANNEL_CMD_BYTE_LENGTH) - -/** Index of the Command Byte of the Control Channel*/ -#define CONTROL_CHANNEL_COMMAND_INDEX (0U) - -/** Index of the start of the payload of the Control Channel*/ -#define CONTROL_CHANNEL_PAYLOAD_INDEX (1U) - -/** Period of Heartbeat when Synced. */ -#define HEATBEAT_PERIOD_SYNCED (5000U) - -/** Period of Heartbeat when Unsynced */ -#define HEATBEAT_PERIOD_UNSYNCED (1000U) - -/** Max number of attempts at receiving a Frame before resetting RX Buffer */ -#define MAX_RX_ATTEMPTS (MAX_FRAME_LEN) - -/****************************************************************************** - * Types and Classes - *****************************************************************************/ - -/** - * Channel Notification Prototype Callback. - * Provides the received data in the respective channel to the application. - */ -typedef void (*ChannelCallback)(const uint8_t* payload, const uint8_t payloadSize); - -/** - * Channel Definition. - */ -struct Channel -{ - char m_name[CHANNEL_NAME_MAX_LEN]; /**< Name of the channel. */ - uint8_t m_dlc; /**< Payload length of channel */ - ChannelCallback m_callback; /**< Callback to provide received data to the application. */ - - /** - * Channel Constructor. - */ - Channel() : m_name{0U}, m_dlc(0U), m_callback(nullptr) - { - } -}; - -/** Data container of the Frame Fields */ -typedef union _Frame -{ - struct _Fields - { - /** Header */ - union _Header - { - struct _HeaderFields - { - /** Channel ID */ - uint8_t m_channel; - - /** Channel ID */ - uint8_t m_dlc; - - /** Frame Checksum */ - uint8_t m_checksum; - - } __attribute__((packed)) headerFields; - - /** Raw Header Data*/ - uint8_t rawHeader[HEADER_LEN]; - - } __attribute__((packed)) header; - - /** Payload */ - struct _Payload - { - /** Data of the Frame */ - uint8_t m_data[MAX_DATA_LEN]; - - } __attribute__((packed)) payload; - - } __attribute__((packed)) fields; - - /** Raw Frame Data */ - uint8_t raw[MAX_FRAME_LEN] = {0U}; - -} __attribute__((packed)) Frame; - -/** - * Enumeration of Commands of Control Channel. - */ -enum COMMANDS : uint8_t -{ - SYNC = 0x00, /**< SYNC Command */ - SYNC_RSP, /**< SYNC Response */ - SCRB, /**< Subscribe Command */ - SCRB_RSP, /**< Subscribe Response */ -}; - -#endif /* YAP_COMMON_H_ */ -/** @} */ diff --git a/lib/Service/YAPServer.hpp b/lib/Service/YAPServer.hpp deleted file mode 100644 index 8729a806..00000000 --- a/lib/Service/YAPServer.hpp +++ /dev/null @@ -1,711 +0,0 @@ -/* MIT License - * - * Copyright (c) 2023 Andreas Merkle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/******************************************************************************* - DESCRIPTION -*******************************************************************************/ -/** - * @brief Yet Another Protocol (YAP) Server - * @author Gabryel Reyes - * - * @addtogroup Service - * - * @{ - */ - -#ifndef YAP_SERVER_H -#define YAP_SERVER_H - -/****************************************************************************** - * Compile Switches - *****************************************************************************/ - -/****************************************************************************** - * Includes - *****************************************************************************/ - -#include -#include -#include -#include - -/****************************************************************************** - * Macros - *****************************************************************************/ - -/****************************************************************************** - * Types and Classes - *****************************************************************************/ - -/** - * Class for the YAP Server. - * @tparam tMaxChannels Maximum number of channels - */ -template -class YAPServer -{ -public: - /** - * Construct the YAP Server. - */ - YAPServer(Stream& stream) : - m_rxCallbacks{nullptr}, - m_isSynced(false), - m_lastSyncCommand(0U), - m_lastSyncResponse(0U), - m_stream(stream), - m_receiveFrame(), - m_receivedBytes(0U), - m_rxAttempts(0U), - m_numberOfTxChannels(0U), - m_numberOfRxChannels(0U), - m_numberOfPendingChannels(0U) - { - } - - /** - * Destroy the YAP Server. - */ - ~YAPServer() - { - } - - /** - * Manage the Server functions. - * Call this function cyclic. - * @param[in] currentTimestamp Time in milliseconds. - */ - void process(const uint32_t currentTimestamp) - { - /* Periodic Heartbeat */ - heartbeat(currentTimestamp); - - /* Process RX data */ - processRxData(); - } - - /** - * Send a frame with the selected bytes. - * @param[in] channelNumber Channel to send frame to. - * @param[in] payload Byte buffer to be sent. - * @param[in] payloadSize Amount of bytes to send. - * @returns If payload succesfully sent, returns true. Otherwise, false. - */ - bool sendData(uint8_t channelNumber, const uint8_t* payload, uint8_t payloadSize) const - { - bool isSent = false; - - if ((CONTROL_CHANNEL_NUMBER != channelNumber) && (nullptr != payload) && (true == m_isSynced)) - { - isSent = send(channelNumber, payload, payloadSize); - } - - return isSent; - } - - /** - * Send a frame with the selected bytes. - * @param[in] channelName Channel to send frame to. - * @param[in] payload Byte buffer to be sent. - * @param[in] payloadSize Amount of bytes to send. - * @returns If payload succesfully sent, returns true. Otherwise, false. - */ - bool sendData(const char* channelName, const uint8_t* payload, uint8_t payloadSize) const - { - bool isSent = false; - - if (nullptr != channelName) - { - isSent = sendData(getTxChannelNumber(channelName), payload, payloadSize); - } - - return isSent; - } - - /** - * Get Number of a TX channel by its name. - * @param[in] channelName Name of Channel - * @returns Number of the Channel, or 0 if not channel with the name is present. - */ - uint8_t getTxChannelNumber(const char* channelName) const - { - uint8_t idx = tMaxChannels; - - if (nullptr != channelName) - { - for (idx = 0U; idx < tMaxChannels; idx++) - { - if (0U == strncmp(channelName, m_txChannels[idx].m_name, CHANNEL_NAME_MAX_LEN)) - { - break; - } - } - } - - return (idx == tMaxChannels) ? 0U : (idx + 1U); - } - - /** - * Creates a new TX Channel on the server. - * @param[in] channelName Name of the channel. - * It will not be checked if the name already exists. - * @param[in] dlc Length of the payload of this channel. - * @returns The channel number if succesfully created, or 0 if not able to create new channel. - */ - uint8_t createChannel(const char* channelName, uint8_t dlc) - { - /* Using strnlen in case the name is not null-terminated. */ - uint8_t nameLength = strnlen(channelName, CHANNEL_NAME_MAX_LEN); - uint8_t idx = 0U; - - if ((nullptr != channelName) && (0U != nameLength) && (MAX_DATA_LEN >= dlc) && (0U != dlc) && - (tMaxChannels > m_numberOfTxChannels)) - { - /* - * Number of TX Channels corresponds to idx in TX Channel Array - * as these are ordered and Channels cannot be deleted. - */ - memcpy(m_txChannels[m_numberOfTxChannels].m_name, channelName, nameLength); - m_txChannels[m_numberOfTxChannels].m_dlc = dlc; - - /* Increase Channel Counter. */ - m_numberOfTxChannels++; - - /* Provide Channel Number. Could be summarized with operation above. */ - idx = m_numberOfTxChannels; - } - - return idx; - } - - /** - * Suscribe to a Channel to receive the incoming data. - * @param[in] channelName Name of the Channel to suscribe to. - * @param[in] callback Callback to return the incoming data. - */ - void subscribeToChannel(const char* channelName, ChannelCallback callback) - { - if ((nullptr != channelName) && (nullptr != callback) && (tMaxChannels > m_numberOfPendingChannels)) - { - /* Save Name and Callback for channel creation after response */ - /* Using strnlen in case the name is not null-terminated. */ - uint8_t nameLength = strnlen(channelName, CHANNEL_NAME_MAX_LEN); - - /* - * Number of Pending Channels corresponds to idx in Pending Channel Array - * as these are ordered and Channels cannot be deleted. - */ - memcpy(m_pendingSuscribeChannels[m_numberOfPendingChannels].m_name, channelName, nameLength); - m_pendingSuscribeChannels[m_numberOfPendingChannels].m_callback = callback; - - /* Increase Channel Counter. */ - m_numberOfPendingChannels++; - } - } - - /** - * Returns current Sync state of th YAP Server. - */ - bool isSynced() - { - return m_isSynced; - } - - /** - * Get the number of configured TX channels. - * @returns Number of configured Data Channels. The Command Channel is ignored here. - */ - uint8_t getNumberOfTxChannels() - { - return m_numberOfTxChannels; - } - - /** - * Get the number of configured RX channels. - * @returns Number of configured Data Channels. The Command Channel is ignored here. - */ - uint8_t getNumberOfRxChannels() - { - return m_numberOfRxChannels; - } - -private: - /** - * Control Channel Command: SYNC - * @param[in] payload Incomming Command data - */ - void cmdSYNC(const uint8_t* payload) - { - uint8_t buf[CONTROL_CHANNEL_PAYLOAD_LENGTH] = {COMMANDS::SYNC_RSP, payload[0U], payload[1U], payload[2U], - payload[3U]}; - /* Ignore return as SYNC_RSP can fail */ - (void)send(CONTROL_CHANNEL_NUMBER, buf, sizeof(buf)); - } - - /** - * Control Channel Command: SYNC_RSP - * @param[in] payload Incomming Command data - */ - void cmdSYNC_RSP(const uint8_t* payload) - { - uint32_t rcvTimestamp = 0; - - /* Using (payloadSize - 1U) as CMD Byte is not passed. */ - if (true == Util::byteArrayToUint32(payload, sizeof(uint32_t), rcvTimestamp)) - { - /* Check Timestamp with m_lastSyncCommand */ - if (rcvTimestamp == m_lastSyncCommand) - { - m_lastSyncResponse = m_lastSyncCommand; - m_isSynced = true; - - /* Manage Pending Subscriptions. */ - managePendingSubscriptions(); - } - else - { - m_isSynced = false; - } - } - else - { - m_isSynced = false; - } - } - - /** - * Control Channel Command: SCRB - * @param[in] payload Incomming Command data - */ - void cmdSCRB(const uint8_t* payload) - { - uint8_t buf[CONTROL_CHANNEL_PAYLOAD_LENGTH] = {COMMANDS::SCRB_RSP}; - buf[1U] = getTxChannelNumber(reinterpret_cast(payload)); - - /* Name is always sent back. */ - memcpy(&buf[2U], payload, CHANNEL_NAME_MAX_LEN); - - if (false == send(CONTROL_CHANNEL_NUMBER, buf, sizeof(buf))) - { - /* Fall out of sync if failed to send. */ - m_isSynced = false; - } - } - - /** - * Control Channel Command: SCRB_RSP - * @param[in] payload Incomming Command data - */ - void cmdSCRB_RSP(const uint8_t* payload) - { - uint8_t channelNumber = payload[0U]; - const uint8_t* channelName = &payload[1U]; - - if ((tMaxChannels >= channelNumber) && (nullptr != channelName) && (0U < m_numberOfPendingChannels)) - { - for (uint8_t idx = 0; idx < tMaxChannels; idx++) - { - /* Check if a SCRB is pending. */ - if (nullptr != m_pendingSuscribeChannels[idx].m_callback) - { - /* Check if its the correct channel. */ - if (0U == strncmp(reinterpret_cast(channelName), m_pendingSuscribeChannels[idx].m_name, - CHANNEL_NAME_MAX_LEN)) - { - /* Channel is found in the Server. */ - if (0U != channelNumber) - { - uint8_t channelArrayIndex = (channelNumber - 1U); - - /* Channel is empty. Increase Counter*/ - if (nullptr == m_rxCallbacks[channelArrayIndex]) - { - /* Increase RX Channel Counter. */ - m_numberOfRxChannels++; - } - - m_rxCallbacks[channelArrayIndex] = m_pendingSuscribeChannels[idx].m_callback; - } - - /* Channel is no longer pending. */ - m_pendingSuscribeChannels[idx].m_callback = nullptr; - - /* Decrease Pending Channel Counter. */ - m_numberOfPendingChannels--; - break; - } - } - } - } - } - - /** - * Callback for the Control Channel - * @param[in] payload Payload of received frame. - * @param[in] payloadSize Length of Payload - */ - void callbackControlChannel(const uint8_t* payload, const uint8_t payloadSize) - { - if ((nullptr == payload) || (CONTROL_CHANNEL_PAYLOAD_LENGTH != payloadSize)) - { - return; - } - - uint8_t cmdByte = payload[CONTROL_CHANNEL_COMMAND_INDEX]; - const uint8_t* cmdData = &payload[CONTROL_CHANNEL_PAYLOAD_INDEX]; - - switch (cmdByte) - { - - case COMMANDS::SYNC: - cmdSYNC(cmdData); - break; - - case COMMANDS::SYNC_RSP: - cmdSYNC_RSP(cmdData); - break; - - case COMMANDS::SCRB: - cmdSCRB(cmdData); - break; - - case COMMANDS::SCRB_RSP: - cmdSCRB_RSP(cmdData); - break; - - default: - break; - } - } - - /** - * Receive and process RX Data. - */ - void processRxData() - { - uint8_t expectedBytes = 0; - uint8_t dlc = 0; - - /* Determine how many bytes to read. */ - if (HEADER_LEN > m_receivedBytes) - { - /* Header must be read. */ - expectedBytes = (HEADER_LEN - m_receivedBytes); - } - else - { - /* Header has been read. Get DLC of Rx Channel using Header. */ - dlc = m_receiveFrame.fields.header.headerFields.m_dlc; - - /* DLC = 0 means that the channel does not exist. */ - if ((0U != dlc) && (MAX_RX_ATTEMPTS >= m_rxAttempts)) - { - expectedBytes = (dlc - (m_receivedBytes - HEADER_LEN)); - m_rxAttempts++; - } - } - - /* Are we expecting to read anything? */ - if (0U != expectedBytes) - { - /* Read the required amount of bytes, if available. */ - if (expectedBytes <= m_stream.available()) - { - m_receivedBytes += m_stream.readBytes(&m_receiveFrame.raw[m_receivedBytes], expectedBytes); - } - - /* Frame has been received. */ - if ((0U != dlc) && ((HEADER_LEN + dlc) == m_receivedBytes)) - { - if (true == isFrameValid(m_receiveFrame)) - { - uint8_t channelArrayIndex = (m_receiveFrame.fields.header.headerFields.m_channel - 1U); - - /* Differenciate between Control and Data Channels. */ - if (CONTROL_CHANNEL_NUMBER == m_receiveFrame.fields.header.headerFields.m_channel) - { - callbackControlChannel(m_receiveFrame.fields.payload.m_data, CONTROL_CHANNEL_PAYLOAD_LENGTH); - } - else if (nullptr != m_rxCallbacks[channelArrayIndex]) - { - /* Callback */ - m_rxCallbacks[channelArrayIndex](m_receiveFrame.fields.payload.m_data, dlc); - } - } - - /* Frame received. Cleaning! */ - clearLocalRxBuffers(); - } - } - else - { - /* Invalid header. Delete Frame. */ - clearLocalRxBuffers(); - } - } - - /** - * Clear RX Buffer and counters. - */ - void clearLocalRxBuffers() - { - m_receivedBytes = 0U; - m_rxAttempts = 0U; - } - - /** - * Periodic heartbeat. - * Sends SYNC Command depending on the current Sync state. - * @param[in] currentTimestamp Time in milliseconds. - */ - void heartbeat(const uint32_t currentTimestamp) - { - uint32_t heartbeatPeriod = HEATBEAT_PERIOD_UNSYNCED; - - if (true == m_isSynced) - { - heartbeatPeriod = HEATBEAT_PERIOD_SYNCED; - } - - if ((currentTimestamp - m_lastSyncCommand) >= heartbeatPeriod) - { - /* Timeout. */ - if (m_lastSyncCommand != m_lastSyncResponse) - { - m_isSynced = false; - } - - /* Send SYNC Command. */ - uint8_t buf[CONTROL_CHANNEL_PAYLOAD_LENGTH] = {COMMANDS::SYNC}; - - /* Using (sizeof(buf) - 1U) as CMD Byte is not passed. */ - Util::uint32ToByteArray(&buf[CONTROL_CHANNEL_PAYLOAD_INDEX], (sizeof(buf) - 1), currentTimestamp); - - if (true == send(CONTROL_CHANNEL_NUMBER, buf, sizeof(buf))) - { - m_lastSyncCommand = currentTimestamp; - } - } - } - - /** - * Subscribe to any pending Channels if synced to server. - */ - void managePendingSubscriptions() - { - if ((true == m_isSynced) && (0U < m_numberOfPendingChannels)) - { - for (uint8_t idx = 0; idx < tMaxChannels; idx++) - { - if (nullptr != m_pendingSuscribeChannels[idx].m_callback) - { - /* Suscribe to channel. */ - uint8_t buf[CONTROL_CHANNEL_PAYLOAD_LENGTH] = {COMMANDS::SCRB}; - memcpy(&buf[CONTROL_CHANNEL_PAYLOAD_INDEX], m_pendingSuscribeChannels[idx].m_name, - CHANNEL_NAME_MAX_LEN); - - if (false == send(CONTROL_CHANNEL_NUMBER, buf, sizeof(buf))) - { - /* Out-of-Sync on failed send. */ - m_isSynced = false; - break; - } - } - } - } - } - - /** - * Send a frame with the selected bytes. - * @param[in] channelNumber Channel to send frame to. - * @param[in] payload Byte buffer to be sent. - * @param[in] payloadSize Amount of bytes to send. - * @returns If payload succesfully sent, returns true. Otherwise, false. - */ - bool send(uint8_t channelNumber, const uint8_t* payload, uint8_t payloadSize) const - { - bool frameSent = false; - uint8_t channelDLC = getTxChannelDLC(channelNumber); - - if ((nullptr != payload) && (channelDLC == payloadSize) && - (true == m_isSynced || (CONTROL_CHANNEL_NUMBER == channelNumber))) - { - const uint8_t frameLength = HEADER_LEN + channelDLC; - uint8_t writtenBytes = 0; - Frame newFrame; - - newFrame.fields.header.headerFields.m_channel = channelNumber; - newFrame.fields.header.headerFields.m_dlc = channelDLC; - memcpy(newFrame.fields.payload.m_data, payload, channelDLC); - newFrame.fields.header.headerFields.m_checksum = checksum(newFrame); - - writtenBytes = m_stream.write(newFrame.raw, frameLength); - - if (frameLength == writtenBytes) - { - frameSent = true; - } - } - - return frameSent; - } - - /** - * Check if a Frame is valid using its checksum. - * @param[in] frame Frame to be checked. - * @returns true if the Frame's checksum is correct. - */ - bool isFrameValid(const Frame& frame) - { - /* Frame is valid when both checksums are the same. */ - return (checksum(frame) == frame.fields.header.headerFields.m_checksum); - } - - /** - * Get the Payload Length of a TX channel - * @param[in] channel Channel number to check - * @returns DLC of the channel, or 0 if channel is not found. - */ - uint8_t getTxChannelDLC(uint8_t channel) const - { - uint8_t channelDLC = 0U; - - if (CONTROL_CHANNEL_NUMBER == channel) - { - channelDLC = CONTROL_CHANNEL_PAYLOAD_LENGTH; - } - else if (tMaxChannels >= channel) - { - uint8_t channelIdx = channel - 1U; - channelDLC = m_txChannels[channelIdx].m_dlc; - } - else - { - /* Invalid channel, nothing to do. */ - ; - } - - return channelDLC; - } - - /** - * Performs the checksum of a Frame. - * @param[in] frame Frame to calculate checksum - * @returns checksum value - */ - uint8_t checksum(const Frame& frame) const - { - uint32_t sum = frame.fields.header.headerFields.m_channel; - sum += frame.fields.header.headerFields.m_dlc; - - for (size_t idx = 0U; idx < frame.fields.header.headerFields.m_dlc; idx++) - { - sum += frame.fields.payload.m_data[idx]; - } - - return (sum % UINT8_MAX); - } - -private: - /** - * Array of tx Data Channels. - * Server publishes to these channels. - */ - Channel m_txChannels[tMaxChannels]; - - /** - * Array of rx channel Callbacks. - * Server is subscribed to these channels. - */ - ChannelCallback m_rxCallbacks[tMaxChannels]; - - /** - * Array of pending rx Data Channels. - * Server subscribes to these channels but no response has been received. - */ - Channel m_pendingSuscribeChannels[tMaxChannels]; - - /** - * Current Sync state. - */ - bool m_isSynced; - - /** - * Last Heartbeat timestamp. - */ - uint32_t m_lastSyncCommand; - - /** - * Last sync response timestamp. - */ - uint32_t m_lastSyncResponse; - - /** - * Stream for input and output of data. - */ - Stream& m_stream; - - /** - * Frame buffer for received Bytes. - * Allows direct access to Frame Fields once bytes are in the raw buffer. - */ - Frame m_receiveFrame; - - /** - * Number of bytes that have been correctly received. - */ - uint8_t m_receivedBytes; - - /** - * Number of attempts performed at receiving a Frame. - */ - uint8_t m_rxAttempts; - - /** - * Number of TX Channels configured. - */ - uint8_t m_numberOfTxChannels; - - /** - * Number of RX Channels configured. - */ - uint8_t m_numberOfRxChannels; - - /** - * Number of Pending Channels to subscribe. - */ - uint8_t m_numberOfPendingChannels; - -private: - /* Not allowed. */ - YAPServer(); - YAPServer(const YAPServer& avg); - YAPServer& operator=(const YAPServer& avg); -}; - -/****************************************************************************** - * Functions - *****************************************************************************/ - -#endif /* YAP_SERVER_H */ -/** @} */ diff --git a/scripts/python-yap/.gitignore b/scripts/python-yap/.gitignore deleted file mode 100644 index ed8ebf58..00000000 --- a/scripts/python-yap/.gitignore +++ /dev/null @@ -1 +0,0 @@ -__pycache__ \ No newline at end of file diff --git a/scripts/python-yap/__main__.py b/scripts/python-yap/__main__.py deleted file mode 100644 index 714ee7c5..00000000 --- a/scripts/python-yap/__main__.py +++ /dev/null @@ -1,152 +0,0 @@ -""" Main programm entry point""" - -# MIT License -# -# Copyright (c) 2023 Gabryel Reyes -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# - - -################################################################################ -# Imports -################################################################################ - -import sys -import time -from struct import Struct -import keyboard # pylint: disable=import-error -import numpy as np -from yap import YAP -from socket_client import SocketClient - -################################################################################ -# Variables -################################################################################ - -g_socket = SocketClient("localhost", 65432) -yap_server = YAP(10, g_socket) -START_TIME = round(time.time()*1000) - -################################################################################ -# Classes -################################################################################ - -################################################################################ -# Functions -################################################################################ - - -def get_milliseconds() -> int: - """ Get current system milliseconds """ - return round(time.time()*1000) - - -def millis() -> int: - """ Get current program milliseconds """ - current_time = get_milliseconds() - return current_time - START_TIME - - -def callback_line_sensors(payload: bytearray) -> None: - """ Callback of LINE_SENS Channel """ - unpacker = Struct(">HHHHH") - data = unpacker.unpack_from(payload) - print(np.array(data, dtype=np.int16)) - -def callback_remote_response(payload:bytearray) -> None: - """ Callback of REMOTE_CMD Channel """ - if payload == b'\x00': - print("Command OK") - elif payload == b'\x01': - print("Command Pending") - elif payload == b'\x02': - print("Command Error") - -def send_motor_setpoints(set_point_left: int, set_point_right: int): - """ - Send Motor Setpoints - """ - - packer = Struct(">H") - payload = bytearray() - payload.extend(packer.pack(set_point_left)) - payload.extend(packer.pack(set_point_right)) - - if len(payload) == 4: - yap_server.send_data("MOT_SPEEDS", payload) - -def send_command(command: str) -> None: - """Send command to RadonUlzer""" - - payload = bytearray() - - if command == "line_calib": - payload.append(0x01) - elif command == "motor_calib": - payload.append(0x02) - elif command == "enable_drive": - payload.append(0x03) - - yap_server.send_data("REMOTE_CMD", payload) - - -def main(): - """The program entry point function.adadadadada - Returns: - int: System exit status - """ - print("Starting System.") - last_time = 0 - - try: - g_socket.connect_to_server() - except Exception as err: # pylint: disable=broad-exception-caught - print(err) - return - - yap_server.create_channel("MOT_SPEEDS", 4) - yap_server.create_channel("REMOTE_CMD", 1) - yap_server.subscribe_to_channel("REMOTE_RSP", callback_remote_response) - yap_server.subscribe_to_channel("LINE_SENS", callback_line_sensors) - - keyboard.on_press_key("w", lambda e: send_motor_setpoints(0x8000, 0x8000)) - keyboard.on_press_key("s", lambda e: send_motor_setpoints(0x7FFF, 0x7FFF)) - keyboard.on_press_key("a", lambda e: send_motor_setpoints(0x7FFF, 0x8000)) - keyboard.on_press_key("d", lambda e: send_motor_setpoints(0x8000, 0x7FFF)) - keyboard.on_release_key("w", lambda e: send_motor_setpoints(0x0000, 0x0000)) - keyboard.on_release_key("a", lambda e: send_motor_setpoints(0x0000, 0x0000)) - keyboard.on_release_key("s", lambda e: send_motor_setpoints(0x0000, 0x0000)) - keyboard.on_release_key("d", lambda e: send_motor_setpoints(0x0000, 0x0000)) - keyboard.on_press_key("l", lambda e: send_command("line_calib")) - keyboard.on_press_key("m", lambda e: send_command("motor_calib")) - keyboard.on_press_key("e", lambda e: send_command("enable_drive")) - - while True: - if (millis() - last_time) >= 5: - last_time = millis() - yap_server.process(millis()) - -################################################################################ -# Main -################################################################################ - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/python-yap/requirements.txt b/scripts/python-yap/requirements.txt deleted file mode 100644 index 1cd99053..00000000 --- a/scripts/python-yap/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -keyboard==0.13.5 \ No newline at end of file diff --git a/scripts/python-yap/serial_client.py b/scripts/python-yap/serial_client.py deleted file mode 100644 index b2f75a0a..00000000 --- a/scripts/python-yap/serial_client.py +++ /dev/null @@ -1,150 +0,0 @@ -""" Implementation of a Serial Client for Serial Communication """ - -# MIT License -# -# Copyright (c) 2023 Andreas Merkle -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# - - -################################################################################ -# Imports -################################################################################ - -import serial - -################################################################################ -# Variables -################################################################################ - -################################################################################ -# Classes -################################################################################ - - -class SerialClient: - """ - Class for Serial Communication - """ - - def __init__(self, port_name: str, port_speed: int) -> None: - """ SerialClient Constructor. - - Parameters - ---------- - port_name : str - Name of Serial Port. - port_speed : int - Speed of Serial Port. - """ - - self.__port_name = port_name - self.__port_speed = port_speed - self.__serial = serial.Serial(port_name, port_speed, timeout=1) - - def connect_to_server(self) -> None: - """ Connect to the specified Host and Port. """ - - try: - # Clean Serial before starting - available_bytes = self.available() - if available_bytes > 0: - self.read_bytes(available_bytes) - except: - print( - f"Failed to connect to \"{self.__port_name}\" on port {self.__port_speed}") - raise - - def disconnect_from_server(self) -> None: - """ Close the connection to Server. """ - - self.__serial.close() - - def write(self, payload : bytearray) -> int: - """ Sends Data to the Server. - - Parameters - ---------- - payload : bytearray - Payload to send. - - Returns - ---------- - Number of bytes sent - """ - - bytes_sent = 0 - - try: - bytes_sent = self.__serial.write(payload) - except BlockingIOError as err: - raise err - - return bytes_sent - - def available(self) -> int: - """ Check if there is anything available for reading - - Returns - ---------- - Number of bytes available for reading. - """ - - rcvd_data = b'' - try: - rcvd_data = self.__serial.in_waiting - except BlockingIOError: - # Exception thrown on non-blocking Serial when there is nothing to read. - pass - - return rcvd_data - - def read_bytes(self, length: int) -> tuple[int, bytearray]: - """ Read a given number of Bytes from Serial. - - Parameters - ---------- - lenght : int - Number of bytes to read. - - Returns - ---------- - Tuple: - - int: Number of bytes received. - - bytearray: Received data. - """ - - rcvd_data = b'' - try: - rcvd_data = self.__serial.read(length) - except BlockingIOError: - # Exception thrown on non-blocking Serial when there is nothing to read. - pass - - return len(rcvd_data), rcvd_data - - -################################################################################ -# Functions -################################################################################ - -################################################################################ -# Main -################################################################################ diff --git a/scripts/python-yap/socket_client.py b/scripts/python-yap/socket_client.py deleted file mode 100644 index 64622cb5..00000000 --- a/scripts/python-yap/socket_client.py +++ /dev/null @@ -1,153 +0,0 @@ -""" Implementation of a Socket Client for Inter-Process Communication """ - -# MIT License -# -# Copyright (c) 2023 Andreas Merkle -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# - - -################################################################################ -# Imports -################################################################################ - -import socket - -################################################################################ -# Variables -################################################################################ - -################################################################################ -# Classes -################################################################################ - - -class SocketClient: - """ - Class for Socket Communication - """ - - def __init__(self, server_address: str, port_number: int) -> None: - """ SocketClient Constructor. - - Parameters - ---------- - server_address : str - Address of the Server to connect to. - port_number : int - Port of of Server. - """ - - self.__server_address = server_address - self.__port_number = port_number - self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - def connect_to_server(self) -> None: - """ Connect to the specified Host and Port. """ - - try: - self.__socket.connect((self.__server_address, self.__port_number)) - self.__socket.setblocking(False) - - # Clean socket before starting - available_bytes = self.available() - if available_bytes > 0: - self.read_bytes(available_bytes) - except: - print( - f"Failed to connect to \"{self.__server_address}\" on port {self.__port_number}") - raise - - def disconnect_from_server(self) -> None: - """ Close the connection to Server. """ - - self.__socket.close() - - def write(self, payload : bytearray) -> int: - """ Sends Data to the Server. - - Parameters - ---------- - payload : bytearray - Payload to send. - - Returns - ---------- - Number of bytes sent - """ - - bytes_sent = 0 - - try: - bytes_sent = self.__socket.send(payload) - except BlockingIOError as err: - raise err - - return bytes_sent - - def available(self) -> int: - """ Check if there is anything available for reading - - Returns - ---------- - Number of bytes available for reading. - """ - - rcvd_data = b'' - try: - rcvd_data = self.__socket.recv(2048, socket.MSG_PEEK) - except BlockingIOError: - # Exception thrown on non-blocking socket when there is nothing to read. - pass - - return len(rcvd_data) - - def read_bytes(self, length: int) -> tuple[int, bytearray]: - """ Read a given number of Bytes from Socket. - - Parameters - ---------- - lenght : int - Number of bytes to read. - - Returns - ---------- - Tuple: - - int: Number of bytes received. - - bytearray: Received data. - """ - - rcvd_data = b'' - try: - rcvd_data = self.__socket.recv(length) - except BlockingIOError: - # Exception thrown on non-blocking socket when there is nothing to read. - pass - - return len(rcvd_data), rcvd_data - - -################################################################################ -# Functions -################################################################################ - -################################################################################ -# Main -################################################################################ diff --git a/scripts/python-yap/yap.py b/scripts/python-yap/yap.py deleted file mode 100644 index d79af32f..00000000 --- a/scripts/python-yap/yap.py +++ /dev/null @@ -1,650 +0,0 @@ -""" Yet Another Protocol (YAP) for lightweight communication. """ - -# MIT License -# -# Copyright (c) 2023 Andreas Merkle -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# - - -################################################################################ -# Imports -################################################################################ - -from dataclasses import dataclass -from struct import Struct -from socket_client import SocketClient - -################################################################################ -# Variables -################################################################################ - -################################################################################ -# Classes -################################################################################ - - -@dataclass(frozen=True) -class YAPConstants: - """ Container Data class for YAP Constants """ - - CHANNEL_LEN = 1 # Channel Field Length in Bytes - DLC_LEN = 1 # DLC Field Length in Bytes - CHECKSUM_LEN = 1 # Checksum Field Length in Bytes - HEADER_LEN = CHANNEL_LEN + DLC_LEN + \ - CHECKSUM_LEN # Length of Complete Header Field - MAX_DATA_LEN = 32 # Data Field Length in Bytes - MAX_FRAME_LEN = HEADER_LEN + MAX_DATA_LEN # Total Frame Length in Bytes - CHANNEL_NAME_MAX_LEN = 10 # Max length of channel name - # Available Bytes in Control Channel Payload for data. - CONTROL_CHANNEL_PAYLOAD_DATA_LENGTH = 4 - CONTROL_CHANNEL_CMD_BYTE_LENGTH = 1 # Lenght of Command in Bytes - CONTROL_CHANNEL_NUMBER = 0 # Number of Control Channel. - CONTROL_CHANNEL_PAYLOAD_LENGTH = CHANNEL_NAME_MAX_LEN + \ - CONTROL_CHANNEL_PAYLOAD_DATA_LENGTH + \ - CONTROL_CHANNEL_CMD_BYTE_LENGTH # DLC of Heartbeat Command. - # Index of the Command Byte of the Control Channel - CONTROL_CHANNEL_COMMAND_INDEX = 0 - # Index of the start of the payload of the Control Channel - CONTROL_CHANNEL_PAYLOAD_INDEX = 1 - HEATBEAT_PERIOD_SYNCED = 5000 # Period of Heartbeat when Synced. - HEATBEAT_PERIOD_UNSYNCED = 1000 # Period of Heartbeat when Unsynced - # Max number of attempts at receiving a Frame before resetting RX Buffer - MAX_RX_ATTEMPTS = MAX_FRAME_LEN - - @dataclass - class Commands(): - """ Enumeration of Commands of Control Channel. """ - SYNC = 0 - SYNC_RSP = 1 - SCRB = 2 - SCRB_RSP = 3 - -@dataclass -class Channel(): - """ Channel Definition """ - - def __init__(self, channel_name: str = "", channel_dlc: int = 0, channel_callback=None) -> None: - self.name = channel_name - self.dlc = channel_dlc - self.callback = channel_callback - - -class Frame(): - """ Frame Defintion """ - - def __init__(self) -> None: - self.raw = bytearray(YAPConstants.MAX_FRAME_LEN) - self.channel = 0 - self.dlc = 0 - self.checksum = 0 - self.payload = bytearray(YAPConstants.MAX_DATA_LEN) - - def unpack_header(self): - """ Unpack/parse channel number and checksum from raw frame """ - header_packer = Struct(">3B") - self.channel, self.dlc, self.checksum = header_packer.unpack_from( - self.raw) - - def unpack_payload(self): - """ Unpack/parse payload from raw frame """ - self.payload = self.raw[3:] - - def pack_frame(self): - """ Pack header and payload into raw array""" - self.raw[0] = self.channel - self.raw[1] = self.dlc - self.raw[2] = self.checksum - self.raw[3:] = self.payload - -@dataclass -class ChannelArrays: - """ Container Class for Channel Arrays and their counters """ - - def __init__(self, max_configured_channels: int) -> None: - self.number_of_rx_channels = 0 - self.number_of_tx_channels = 0 - self.number_of_pending_channels = 0 - self.rx_channels = [Channel() for x in range(max_configured_channels)] - self.tx_channels = [Channel() for x in range(max_configured_channels)] - self.pending_channels = [Channel() for x in range(max_configured_channels)] - - -@dataclass -class SyncData: - """ Container Dataclass for Synchronization Data. """ - - def __init__(self) -> None: - self.is_synced = False - self.last_sync_response = 0 - self.last_sync_command = 0 - -@dataclass -class RxData: - """ Container Dataclass for Receive Data and counters. """ - - def __init__(self) -> None: - self.received_bytes = 0 - self.rx_attempts = 0 - self.receive_frame = Frame() - -class YAP: - """ Yet Another Protocol YAP """ - - def __init__(self, max_configured_channels: int, stream: SocketClient) -> None: - self.__max_configured_channels = max_configured_channels - self.__stream = stream - self.__rx_data = RxData() - self.__sync_data = SyncData() - self.__channels = ChannelArrays(max_configured_channels) - - def process(self, current_timestamp: int) -> None: - """Manage the Server functions. - Call this function cyclic. - - Parameters - ---------- - current_timestamp : int - Time in milliseconds. - - """ - - # Periodic Heartbeat. - self.__heartbeat(current_timestamp) - - # Process RX data. - self.__process_rx() - - def send_data(self, channel_name: str, payload: bytearray) -> bool: - """Send a frame with the selected bytes. - - Parameters: - ---------- - channel_name : str - Channel to send frame to. - payload : bytearray - Byte buffer to be sent. - - Returns: - -------- - If payload succesfully sent, returns true. Otherwise, false. - """ - is_sent = False - channel_number = self.get_tx_channel_number(channel_name) - - if (YAPConstants.CONTROL_CHANNEL_NUMBER != channel_number) and\ - (self.__sync_data.is_synced is True): - - is_sent = self.__send(channel_number, payload) - - return is_sent - - def is_synced(self) -> bool: - """ Returns current Sync state of th YAP Server. """ - return self.__sync_data.is_synced - - def get_number_rx_channels(self) -> int: - """Get the number of configured RX channels.""" - return self.__channels.number_of_rx_channels - - def get_number_tx_channels(self) -> int: - """Get the number of configured TX channels.""" - return self.__channels.number_of_tx_channels - - def create_channel(self, name: str, dlc: int) -> int: - """Creates a new TX Channel on the server. - - Parameters: - ---------- - name : str - Name of the channel. It will not be checked if the name already exists. - dlc : int - Length of the payload of this channel. - - Returns: - -------- - The channel number if succesfully created, or 0 if not able to create new channel. - """ - name_length = len(name) - idx = 0 - - if (0 != name_length) and\ - (YAPConstants.CHANNEL_NAME_MAX_LEN >= name_length) and\ - (0 != dlc) and\ - (YAPConstants.MAX_DATA_LEN >= dlc) and\ - (self.__max_configured_channels > self.__channels.number_of_tx_channels): - - # Create Channel - self.__channels.tx_channels[self.__channels.number_of_tx_channels] = Channel( - name, dlc) - - # Increase Channel Counter - self.__channels.number_of_tx_channels += 1 - - # Provide Channel Number. Could be summarized with operation above. - idx = self.__channels.number_of_tx_channels - - return idx - - def subscribe_to_channel(self, name: str, callback) -> None: - """ Suscribe to a Channel to receive the incoming data. - - Parameters: - ---------- - name : str - Name of the Channel to suscribe to. - callback : function - Callback to return the incoming data. - """ - - if (YAPConstants.CHANNEL_NAME_MAX_LEN >= len(name)) and\ - (callback is not None) and\ - (self.__max_configured_channels > self.__channels.number_of_pending_channels): - - # Save Name and Callback for channel creation after response - self.__channels.pending_channels[self.__channels.number_of_pending_channels] = Channel( - channel_name=name, channel_dlc=0, channel_callback=callback) - - # Increase Channel Counter - self.__channels.number_of_pending_channels += 1 - - def get_tx_channel_number(self, channel_name: str) -> int: - """Get Number of a TX channel by its name. - - Parameters: - ----------- - channel_name : str - Name of channel - - Returns: - -------- - Number of the Channel, or 0 if not channel with the name is present. - """ - - channel_number = 0 - for idx in range(self.__max_configured_channels): - if self.__channels.tx_channels[idx].name == channel_name: - channel_number = idx + 1 - break - - return channel_number - - def __heartbeat(self, current_timestamp: int) -> None: - """ Periodic heartbeat. - Sends SYNC Command depending on the current Sync state. - - Parameters - ---------- - current_timestamp : int - Time in milliseconds. - - """ - - heartbeat_period = YAPConstants.HEATBEAT_PERIOD_UNSYNCED - - if self.__sync_data.is_synced is True: - heartbeat_period = YAPConstants.HEATBEAT_PERIOD_SYNCED - - if (current_timestamp - self.__sync_data.last_sync_command) >= heartbeat_period: - - # Timeout - if self.__sync_data.last_sync_command != self.__sync_data.last_sync_response: - self.__sync_data.is_synced = False - - # Pack big-endian uint32 - packer = Struct(">L") - timestamp_array = packer.pack(current_timestamp) - - # Send SYNC Command - command = bytearray() - command.append(YAPConstants.Commands.SYNC) - command.extend(timestamp_array) - - # Pad array if necessary - command = command.ljust( - YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH, b'\x00') - - if self.__send(YAPConstants.CONTROL_CHANNEL_NUMBER, command) is True: - self.__sync_data.last_sync_command = current_timestamp - - def __process_rx(self) -> None: - """ Receive and process RX Data. """ - - expected_bytes = 0 - dlc = 0 - - # Determine how many bytes to read. - if YAPConstants.HEADER_LEN > self.__rx_data.received_bytes: - # Header must be read. - expected_bytes = YAPConstants.HEADER_LEN - self.__rx_data.received_bytes - else: - # Header has been read. Get DLC of Rx Channel using header. - self.__rx_data.receive_frame.unpack_header() - # dlc = self.__get_channel_dlc(self.__receive_frame.channel, False) - dlc = self.__rx_data.receive_frame.dlc - - # DLC = 0 means that the channel does not exist. - if (0 != dlc) and (YAPConstants.MAX_RX_ATTEMPTS >= self.__rx_data.rx_attempts): - remaining_payload_bytes = self.__rx_data.received_bytes - YAPConstants.HEADER_LEN - expected_bytes = dlc - remaining_payload_bytes - self.__rx_data.rx_attempts += 1 - - # Are we expecting to read anything? - if 0 != expected_bytes: - - # Read the required amount of bytes, if available. - if self.__stream.available() >= expected_bytes: - rcvd, data = self.__stream.read_bytes(expected_bytes) - self.__rx_data.receive_frame.raw[self.__rx_data.received_bytes:] = data - self.__rx_data.received_bytes += rcvd - - # Frame has been received. - if (0 != dlc) and ((YAPConstants.HEADER_LEN + dlc) == self.__rx_data.received_bytes): - - # Check validity - if self.__is_frame_valid(self.__rx_data.receive_frame) is True: - channel_array_index = self.__rx_data.receive_frame.channel - 1 - self.__rx_data.receive_frame.unpack_payload() - - # Differenciate between Control and Data Channels. - if YAPConstants.CONTROL_CHANNEL_NUMBER == self.__rx_data.receive_frame.channel: - self.__callback_control_channel( - self.__rx_data.receive_frame.payload) - elif self.__channels.rx_channels[channel_array_index].callback is not None: - self.__channels.rx_channels[channel_array_index].callback( - self.__rx_data.receive_frame.payload) - - # Frame received. Cleaning! - self.__clear_local_buffers() - else: - # Invalid Header. Delete Frame! - self.__clear_local_buffers() - - def __process_subscriptions(self) -> None: - """ Subscribe to any pending Channels if synced to server. """ - - # If synced and a channel is pending - if (self.__sync_data.is_synced is True) and\ - (0 < self.__channels.number_of_pending_channels): - # Channel Iterator - for pending_channel in self.__channels.pending_channels: - - # Channel is pending - if pending_channel.callback is not None: - - # Subscribe to Channel - request = bytearray( - YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH) - request[0] = YAPConstants.Commands.SCRB - request[1:] = bytearray(pending_channel.name, 'ascii') - - # Pad array if necessary - request = request.ljust( - YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH, b'\x00') - - if self.__send(YAPConstants.CONTROL_CHANNEL_NUMBER, request) is False: - # Fall out of sync if failed to send. - self.__sync_data.is_synced = False - break - - def __clear_local_buffers(self) -> None: - """ Clear Local RX Buffers """ - self.__rx_data.receive_frame = Frame() - self.__rx_data.received_bytes = 0 - self.__rx_data.rx_attempts = 0 - - def __get_tx_channel_dlc(self, channel_number: int) -> int: - """ Get the Payload Length of a channel. - - Parameters - ---------- - channel_number : int - Channel number to check. - - is_tx_channel : bool - Is the Channel a TX Channel? If false, will return value for an RX Channel instead. - - Returns - ------- - DLC of the channel, or 0 if channel is not found. - """ - dlc = 0 - - if YAPConstants.CONTROL_CHANNEL_NUMBER == channel_number: - dlc = YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH - else: - if self.__max_configured_channels >= channel_number: - channel_idx = channel_number - 1 - dlc = self.__channels.tx_channels[channel_idx].dlc - - return dlc - - def __is_frame_valid(self, frame: Frame) -> bool: - """ Check if a Frame is valid using its checksum. - - Parameters - ---------- - frame : Frame - Frame to be checked - - Returns: - True if the Frame's checksum is correct. Otherwise, False. - """ - - return self.__checksum(frame.raw) == frame.checksum - - def __checksum(self, raw_frame: bytearray) -> int: - """ - Performs simple checksum of the Frame to confirm its validity. - - Parameters: - ---------- - raw_frame : Frame - Frame to calculate checksum for - - Returns: - ------- - Checksum value - """ - newsum = raw_frame[0] + raw_frame[1] - - for idx in range(3, len(raw_frame)): - newsum += raw_frame[idx] - - newsum = newsum % 255 - - return newsum - - def __send(self, channel_number: int, payload: bytearray) -> bool: - """ Send a frame with the selected bytes. - - Parameters: - ---------- - channel_number : int - Channel to send frame to. - payload : bytearray - Payload to send - - Returns: - -------- - If payload succesfully sent, returns True. Otherwise, False. - """ - - frame_sent = False - channel_dlc = self.__get_tx_channel_dlc(channel_number) - - if (len(payload) == channel_dlc) and \ - ((self.__sync_data.is_synced is True) or - (YAPConstants.CONTROL_CHANNEL_NUMBER == channel_number)): - written_bytes = 0 - new_frame = Frame() - new_frame.channel = channel_number - new_frame.dlc = channel_dlc - new_frame.payload = payload - new_frame.pack_frame() # Pack Header and payload in Frame - new_frame.checksum = self.__checksum(new_frame.raw) - new_frame.pack_frame() # Pack Checksum in Frame - - try: - written_bytes = self.__stream.write(new_frame.raw) - except Exception as excpt: # pylint: disable=broad-exception-caught - print(excpt) - - if written_bytes == (YAPConstants.HEADER_LEN + channel_dlc): - frame_sent = True - - return frame_sent - - def __cmd_sync(self, payload: bytearray) -> None: - """ Control Channel Command: SYNC - - Parameters: - ----------- - payload : bytearray - Command Data of received frame - """ - - response = bytearray(YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH) - response[YAPConstants.CONTROL_CHANNEL_COMMAND_INDEX] = YAPConstants.Commands.SYNC_RSP - response[YAPConstants.CONTROL_CHANNEL_PAYLOAD_INDEX:] = payload - - self.__send(YAPConstants.CONTROL_CHANNEL_NUMBER, response) - - def __cmd_sync_rsp(self, payload: bytearray) -> None: - """ Control Channel Command: SYNC_RSP - - Parameters: - ----------- - payload : bytearray - Command Data of received frame - """ - - # Parse big-endian uint32 - unpacker = Struct(">L") - rcvd_timestamp = unpacker.unpack_from(payload)[0] - - if self.__sync_data.last_sync_command == rcvd_timestamp: - self.__sync_data.last_sync_response = self.__sync_data.last_sync_command - self.__sync_data.is_synced = True - - # Process pending Subscriptions - self.__process_subscriptions() - else: - self.__sync_data.is_synced = False - - def __cmd_scrb(self, payload: bytearray) -> None: - """ Control Channel Command: SCRB - - Parameters: - ----------- - payload : bytearray - Command Data of received frame - """ - - response = bytearray(YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH) - response[YAPConstants.CONTROL_CHANNEL_COMMAND_INDEX] = YAPConstants.Commands.SCRB_RSP - - # Parse name - channel_name = str(payload, "ascii").strip('\x00') - response[1] = self.get_tx_channel_number(channel_name) - - # Name is always sent back. - response[2:] = bytearray(channel_name, 'ascii') - - # Pad array if necessary - response = response.ljust( - YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH, b'\x00') - - if self.__send(YAPConstants.CONTROL_CHANNEL_NUMBER, response) is False: - # Fall out of sync if failed to send. - self.__sync_data.is_synced = False - - def __cmd_scrb_rsp(self, payload: bytearray) -> None: - """ Control Channel Command: SCRB - - Parameters: - ----------- - payload : bytearray - Command Data of received frame - """ - - # Parse payload - channel_number = payload[0] - channel_name = str(payload[1:], "ascii").strip('\x00') - - if (self.__max_configured_channels >= channel_number) and \ - (0 < self.__channels.number_of_pending_channels): - - # Channel Iterator - for potential_channel in self.__channels.pending_channels: - # Check if a SCRB is pending and is the correct channel - if (potential_channel.callback is not None) and\ - (potential_channel.name == channel_name): - # Channel is found in the server - if 0 != channel_number: - channel_array_index = channel_number - 1 - - if self.__channels.rx_channels[channel_array_index].callback is None: - self.__channels.number_of_rx_channels += 1 - - self.__channels.rx_channels[channel_array_index] = Channel( - channel_name, 0, potential_channel.callback) - - # Channel is no longer pending - potential_channel = Channel() - - # Decrease Channel Counter - self.__channels.number_of_pending_channels -= 1 - - # Break out of iterator - break - - def __callback_control_channel(self, payload: bytearray) -> None: - """ Callback for the Control Channel - - Parameters: - ----------- - payload : bytearray - Payload of received frame - """ - if len(payload) != YAPConstants.CONTROL_CHANNEL_PAYLOAD_LENGTH: - return - - cmd_byte = payload[YAPConstants.CONTROL_CHANNEL_COMMAND_INDEX] - cmd_data = payload[YAPConstants.CONTROL_CHANNEL_PAYLOAD_INDEX:] - - # Switch not available until Python 3.10 - # Find command handler - if YAPConstants.Commands.SYNC == cmd_byte: - self.__cmd_sync(cmd_data) - elif YAPConstants.Commands.SYNC_RSP == cmd_byte: - self.__cmd_sync_rsp(cmd_data) - elif YAPConstants.Commands.SCRB == cmd_byte: - self.__cmd_scrb(cmd_data) - elif YAPConstants.Commands.SCRB_RSP == cmd_byte: - self.__cmd_scrb_rsp(cmd_data) - -################################################################################ -# Functions -################################################################################ - -################################################################################ -# Main -################################################################################ diff --git a/test/common/TestStream.h b/test/common/TestStream.h deleted file mode 100644 index 0b0061b8..00000000 --- a/test/common/TestStream.h +++ /dev/null @@ -1,244 +0,0 @@ -/* MIT License - * - * Copyright (c) 2023 Andreas Merkle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/******************************************************************************* - DESCRIPTION -*******************************************************************************/ -/** - * @brief Stream Module for uint testing - * @author Gabryel Reyes - */ - -#ifndef TEST_STREAM_H_ -#define TEST_STREAM_H_ - -/****************************************************************************** - * Includes - *****************************************************************************/ - -#include -#include -#include -#include - -/****************************************************************************** - * Macros - *****************************************************************************/ - -/****************************************************************************** - * Types and Classes - *****************************************************************************/ - -/** - * Test Stream Class for Unit Testing - */ -class TestStream : public Stream -{ -public: - /** - * Stream Constructor. - */ - TestStream() : Stream(), m_outputBuffer{0xA5}, m_rcvQueue() - { - } - - /** - * Stream Destructor. - */ - ~TestStream() - { - } - - void print(const char str[]) - { - /* Not implemented*/ - (void)str; - } - - void print(uint8_t value) - { - /* Not implemented*/ - (void)value; - } - - void print(uint16_t value) - { - /* Not implemented*/ - (void)value; - } - - void print(uint32_t value) - { - /* Not implemented*/ - (void)value; - } - - void print(int8_t value) - { - /* Not implemented*/ - (void)value; - } - - void print(int16_t value) - { - /* Not implemented*/ - (void)value; - } - - void print(int32_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(const char str[]) - { - /* Not implemented*/ - (void)str; - } - - void println(uint8_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(uint16_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(uint32_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(int8_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(int16_t value) - { - /* Not implemented*/ - (void)value; - } - - void println(int32_t value) - { - /* Not implemented*/ - (void)value; - } - - /** - * Write bytes to stream. - * @param[in] buffer Byte Array to send. - * @param[in] length Length of Buffer. - */ - size_t write(const uint8_t* buffer, size_t length) final - { - size_t idx = 0; - - for (idx = 0; idx < length; idx++) - { - m_outputBuffer[idx] = buffer[idx]; - } - - return idx; - } - - /** - * Check if there are available bytes in the Stream. - * @returns Number of available bytes. - */ - int available() const final - { - return m_rcvQueue.size(); - } - - /** - * Read bytes into a buffer. - * @param[in] buffer Array to write bytes to. - * @param[in] length number of bytes to be read. - * @returns Number of bytes read from Stream. - */ - size_t readBytes(uint8_t* buffer, size_t length) final - { - size_t count = 0; - - while ((!m_rcvQueue.empty()) && (count <= length)) - { - buffer[count] = m_rcvQueue.front(); - m_rcvQueue.pop(); - count++; - } - - return count; - } - - /** - * Push a data Array to the receive Queue of the Stream. - * Used to make data available for the "read" functions. - */ - void pushToQueue(const uint8_t* payload, uint8_t payloadSize) - { - for (uint8_t idx = 0; idx < payloadSize; idx++) - { - m_rcvQueue.push(payload[idx]); - } - } - - /** - * Flush output buffer, setting all values to 0x00 - */ - void flushOutputBuffer() - { - memset(m_outputBuffer, 0xA5, MAX_FRAME_LEN); - } - - /** - * Flush input queue (buffer) - */ - void flushInputBuffer() - { - while (!m_rcvQueue.empty()) - { - m_rcvQueue.pop(); - } - } - - /** - * Buffer to write any output of the Stream - */ - uint8_t m_outputBuffer[MAX_FRAME_LEN]; - - /** - * Byte Queue working as an RX Buffer. - */ - std::queue m_rcvQueue; -}; - -#endif /* TEST_STREAM_H_ */ \ No newline at end of file diff --git a/test/test_YAP/test_YAP.cpp b/test/test_YAP/test_YAP.cpp deleted file mode 100644 index b22835e1..00000000 --- a/test/test_YAP/test_YAP.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* MIT License - * - * Copyright (c) 2023 Andreas Merkle - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * @author Gabryel Reyes - * @brief This module contains the YAP Server tests. - */ - -/****************************************************************************** - * Compile Switches - *****************************************************************************/ - -/****************************************************************************** - * Includes - *****************************************************************************/ - -#include -#include -#include - -/****************************************************************************** - * Compiler Switches - *****************************************************************************/ - -/****************************************************************************** - * Macros - *****************************************************************************/ - -/****************************************************************************** - * Types and classes - *****************************************************************************/ - -/****************************************************************************** - * Prototypes - *****************************************************************************/ - -static void testChannelCallback(const uint8_t* payload, uint8_t payloadSize); -static void testCmdSync(); -static void testCmdSyncRsp(); -static void testCmdScrb(); -static void testCmdScrbRsp(); -static void testChannelCreation(); -static void testDataSend(); - -/****************************************************************************** - * Local Variables - *****************************************************************************/ - -static uint8_t emptyOutputBuffer[MAX_FRAME_LEN]; -static TestStream gTestStream; -static const uint8_t controlChannelFrameLength = (HEADER_LEN + CONTROL_CHANNEL_PAYLOAD_LENGTH); -static const uint8_t testPayload[4U] = {0x12, 0x34, 0x56, 0x78}; -static bool callbackCalled = false; - -/****************************************************************************** - * Public Methods - *****************************************************************************/ - -/****************************************************************************** - * Protected Methods - *****************************************************************************/ - -/****************************************************************************** - * Private Methods - *****************************************************************************/ - -/****************************************************************************** - * External Functions - *****************************************************************************/ - -/** - * Program setup routine, which is called once at startup. - */ -void setup() -{ - memset(emptyOutputBuffer, 0xA5, sizeof(emptyOutputBuffer)); -#ifndef TARGET_NATIVE - /* https://docs.platformio.org/en/latest/plus/unit-testing.html#demo */ - delay(2000); -#endif /* Not defined TARGET_NATIVE */ -} - -/** - * Main entry point. - */ -void loop() -{ - UNITY_BEGIN(); - - RUN_TEST(testCmdSync); - RUN_TEST(testCmdSyncRsp); - RUN_TEST(testCmdScrb); - RUN_TEST(testCmdScrbRsp); - RUN_TEST(testChannelCreation); - RUN_TEST(testDataSend); - - UNITY_END(); - -#ifndef TARGET_NATIVE - /* Don't exit on the robot to avoid a endless test loop. - * If the test runs on the pc, it must exit. - */ - for (;;) - { - } -#endif /* Not defined TARGET_NATIVE */ -} - -/** - * Initialize the test setup. - */ -extern void setUp(void) -{ - /* Not used. */ -} - -/** - * Clean up test setup. - */ -extern void tearDown(void) -{ - /* Not used. */ -} - -/****************************************************************************** - * Local Functions - *****************************************************************************/ - -/** - * Callback for incoming data from test channel. - * @param[in] payload Byte buffer containing incomming data. - * @param[in] payloadSize Number of bytes received. - */ -static void testChannelCallback(const uint8_t* payload, uint8_t payloadSize) -{ - callbackCalled = true; - TEST_ASSERT_EQUAL_UINT8_ARRAY(testPayload, payload, payloadSize); -} - -/** - * Test SYNC Command of YAP Server. - */ -static void testCmdSync() -{ - YAPServer<2U> testYapServer(gTestStream); - uint8_t expectedOutputBufferVector[6U][MAX_FRAME_LEN] = { - {0x00, 0x0F, 0xFA, 0x00, 0x00, 0x00, 0x03, 0xE8}, /* SYNC 1000ms*/ - {0x00, 0x0F, 0xE6, 0x00, 0x00, 0x00, 0x07, 0xD0}, /* SYNC 2000ms*/ - {0x00, 0x0F, 0x82, 0x00, 0x00, 0x00, 0x1B, 0x58}, /* SYNC 7000ms*/ - {0x00, 0x0F, 0x1E, 0x00, 0x00, 0x00, 0x2E, 0xE0}, /* SYNC 12000ms*/ - {0x00, 0x0F, 0xB9, 0x00, 0x00, 0x00, 0x42, 0x68} /* SYNC 17000ms*/ - }; - uint8_t inputQueueVector[2U][MAX_FRAME_LEN] = {{0x00, 0x0F, 0xE7, 0x01, 0x00, 0x00, 0x07, 0xD0}, - {0x00, 0x0F, 0x83, 0x01, 0x00, 0x00, 0x1B, 0x58}}; - - /* - * Case: Unsynced Heartbeat. - */ - - gTestStream.flushOutputBuffer(); - - /* Unsynced Heartbeat at 0 milliseconds */ - testYapServer.process(0U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - - /* Unsynced Heartbeat after 1000 milliseconds */ - testYapServer.process(1000U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0U], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - - /* No Heartbeat expected */ - testYapServer.process(1500U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - - /* Unsynced Heartbeat after 2000 milliseconds */ - testYapServer.process(2000U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[1U], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - - /* - * Case: Sync - */ - - /* Put Data in RX Queue */ - gTestStream.pushToQueue(inputQueueVector[0], controlChannelFrameLength); - - /* Two process calls required */ - testYapServer.process(2500U); /* Read Frame Header */ - testYapServer.process(2700U); /* Read Frame Payload */ - TEST_ASSERT_TRUE(testYapServer.isSynced()); - - /* No output expected */ - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushInputBuffer(); - - /* - * Case: Synced Heartbeat. - * Last Sync = 2000 ms - * isSynced = true - * Next Sync = 7000 ms - */ - - /* No output expected. Would be Heartbeat if Unsynced. */ - testYapServer.process(3000U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, controlChannelFrameLength); - - /* Synced Heartbeat. */ - testYapServer.process(7000U); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[2U], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - - /** - * Case: Maintain Sync - */ - - /* Put SYNC_RSP in RX Queue. Otherwise will fall out of Sync. */ - gTestStream.pushToQueue(inputQueueVector[1], controlChannelFrameLength); - - /* Two process calls required */ - testYapServer.process(9000U); /* Read Frame Header */ - testYapServer.process(11000U); /* Read Frame Payload */ - TEST_ASSERT_TRUE(testYapServer.isSynced()); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, controlChannelFrameLength); - - /* Synced Heartbeat */ - testYapServer.process(12000U); - TEST_ASSERT_TRUE(testYapServer.isSynced()); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[3U], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); - - /** - * Case: Fall out of Sync. - * No data passed to RX Queue. - */ - - /* Synced Heartbeat. Fall out fo sync, as last Heartbeat was not Acknowledged. */ - testYapServer.process(17000U); - TEST_ASSERT_FALSE(testYapServer.isSynced()); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[4U], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushOutputBuffer(); -} - -/** - * Test SYNC_RSP Command of YAP Server. - */ -static void testCmdSyncRsp() -{ - YAPServer<2U> testYapServer(gTestStream); - uint8_t testTime = 0U; - uint8_t numberOfCases = 3U; - uint8_t expectedOutputBufferVector[numberOfCases][MAX_FRAME_LEN] = { - {0x00, 0x0F, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x0F, 0x25, 0x01, 0x12, 0x34, 0x56, 0x78}, - {0x00, 0x0F, 0x10, 0x01, 0xFF, 0xFF, 0xFF, 0xFF}}; - uint8_t inputQueueVector[numberOfCases][MAX_FRAME_LEN] = {{0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x0F, 0x24, 0x00, 0x12, 0x34, 0x56, 0x78}, - {0x00, 0x0F, 0x0F, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}}; - - /* Ignore SYNC */ - testYapServer.process(testTime++); - gTestStream.flushOutputBuffer(); - - /* Test for SYNC_RSP */ - for (uint8_t testCase = 0; testCase < numberOfCases; testCase++) - { - gTestStream.pushToQueue(inputQueueVector[testCase], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[testCase], gTestStream.m_outputBuffer, - controlChannelFrameLength); - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); - } -} - -/** - * Test SCRB Command of YAP Server. - */ -static void testCmdScrb() -{ - YAPServer<2U> testYapServer(gTestStream); - uint8_t testTime = 0U; - uint8_t numberOfCases = 2U; - uint8_t expectedOutputBufferVector[numberOfCases][MAX_FRAME_LEN] = { - {0x00, 0x0F, 0x53, 0x03, 0x00, 'T', 'E', 'S', 'T'}, {0x00, 0x0F, 0x54, 0x03, 0x01, 'T', 'E', 'S', 'T'}}; - uint8_t inputQueueVector[numberOfCases][MAX_FRAME_LEN] = { - {0x00, 0x0F, 0x52, 0x02, 'T', 'E', 'S', 'T', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; - - /* Ignore SYNC */ - testYapServer.process(testTime++); - gTestStream.flushOutputBuffer(); - - /* - * Case: Suscribe to Unknown Channel - */ - gTestStream.pushToQueue(inputQueueVector[0], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0], gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - gTestStream.flushInputBuffer(); - - /* - * Case: Subscribe to Known Channel - */ - TEST_ASSERT_EQUAL_UINT8(1U, testYapServer.createChannel("TEST", 8U)); - - gTestStream.pushToQueue(inputQueueVector[0], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[1], gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - gTestStream.flushInputBuffer(); - - /* - * Case: Subscribe to a Duplicate Channel - */ - TEST_ASSERT_EQUAL_UINT8(2U, testYapServer.createChannel("TEST", 8U)); - - gTestStream.pushToQueue(inputQueueVector[0], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[1], gTestStream.m_outputBuffer, controlChannelFrameLength); - gTestStream.flushOutputBuffer(); - gTestStream.flushInputBuffer(); -} - -/** - * Test SCRB_RSP Command of YAP Server. - */ -static void testCmdScrbRsp() -{ - YAPServer<2U> testYapServer(gTestStream); - uint8_t testTime = 1U; - uint8_t numberOfCases = 3U; - uint8_t expectedOutputBufferVector[numberOfCases][MAX_FRAME_LEN] = { - {0x00, 0x0F, 0x52, 0x02, 'T', 'E', 'S', 'T', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; - uint8_t inputQueueVector[numberOfCases][MAX_FRAME_LEN] = {{0x00, 0x0F, 0x10, 0x01, 0x00, 0x00, 0x00}, - {0x00, 0x0F, 0x53, 0x03, 0x00, 'T', 'E', 'S', 'T'}, - {0x00, 0x0F, 0x54, 0x03, 0x01, 'T', 'E', 'S', 'T'}}; - - /* - * Case: Suscribe to Unknown Channel - */ - testYapServer.subscribeToChannel("TEST", testChannelCallback); - - /* Sync */ - gTestStream.pushToQueue(inputQueueVector[0U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_TRUE(testYapServer.isSynced()); - - /* Subscription sent. */ - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0], gTestStream.m_outputBuffer, controlChannelFrameLength); - - /* Clear Subscription. */ - gTestStream.pushToQueue(inputQueueVector[1U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.getNumberOfRxChannels()); - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); - - /* De-Sync */ - testTime += 5000U; - testYapServer.process(testTime++); - - /* - * Case: Suscribe to Known Channel - */ - testYapServer.subscribeToChannel("TEST", testChannelCallback); - - /* Sync */ - gTestStream.pushToQueue(inputQueueVector[0U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_TRUE(testYapServer.isSynced()); - - /* Subscription sent. */ - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0], gTestStream.m_outputBuffer, controlChannelFrameLength); - - /* Clear Subscription. */ - gTestStream.pushToQueue(inputQueueVector[2U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8(1U, testYapServer.getNumberOfRxChannels()); - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); - - /* De-Sync */ - testTime += 5000U; - testYapServer.process(testTime++); - - /* - * Case: Suscribe again to Known Channel - */ - testYapServer.subscribeToChannel("TEST", testChannelCallback); - - /* Sync */ - gTestStream.pushToQueue(inputQueueVector[0U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_TRUE(testYapServer.isSynced()); - - /* Subscription sent. */ - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0], gTestStream.m_outputBuffer, controlChannelFrameLength); - - /* Clear Subscription. */ - gTestStream.pushToQueue(inputQueueVector[2U], controlChannelFrameLength); - testYapServer.process(testTime++); - testYapServer.process(testTime++); - TEST_ASSERT_EQUAL_UINT8(1U, testYapServer.getNumberOfRxChannels()); - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); -} - -/** - * Test Channel Creation on a YAP Server. - */ -static void testChannelCreation() -{ - const uint8_t maxChannels = 5U; - YAPServer testYapServer(gTestStream); - - /* No Channels Configured on Start */ - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.getNumberOfTxChannels()); - - /* - * Case: Try to configure invalid channels. - */ - - /* Channel Name is empty */ - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.createChannel("", 1U)); - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.getNumberOfTxChannels()); - - /* DLC = 0U */ - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.createChannel("TEST", 0U)); - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.getNumberOfTxChannels()); - - /* - * Case: Configure maximum valid channels. - */ - for (uint8_t channelNumber = 0; channelNumber < maxChannels; channelNumber++) - { - TEST_ASSERT_EQUAL_UINT8(channelNumber, testYapServer.getNumberOfTxChannels()); - TEST_ASSERT_EQUAL_UINT8((channelNumber + 1U), testYapServer.createChannel("TEST", 1U)); - TEST_ASSERT_EQUAL_UINT8((channelNumber + 1U), testYapServer.getNumberOfTxChannels()); - } - - /* - * Case: Try to configure more than the maximum number of channels. - */ - - TEST_ASSERT_EQUAL_UINT8(0U, testYapServer.createChannel("TEST", 1U)); - TEST_ASSERT_EQUAL_UINT8(maxChannels, testYapServer.getNumberOfTxChannels()); -} - -/** - * Test data send on YAP Server. - */ -static void testDataSend() -{ - YAPServer<1U> testYapServer(gTestStream); - uint8_t expectedOutputBufferVector[1U][MAX_FRAME_LEN] = {{0x01, 0x04, 0x1A, 0x12, 0x34, 0x56, 0x78}}; - uint8_t inputQueueVector[1U][MAX_FRAME_LEN] = {{0x00, 0x0F, 0x10, 0x01, 0x00, 0x00, 0x00}}; - - /* Flush Stream */ - gTestStream.flushInputBuffer(); - gTestStream.flushOutputBuffer(); - - /* - * Case: Send data on Control Channel. - */ - testYapServer.sendData((uint8_t)CONTROL_CHANNEL_NUMBER, testPayload, sizeof(testPayload)); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, sizeof(testPayload)); - - /* - * Case: Send on non-existent channel while unsynced. - */ - testYapServer.sendData("TEST", testPayload, sizeof(testPayload)); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, sizeof(testPayload)); - - /* Create a Channel */ - TEST_ASSERT_EQUAL_UINT8(1U, testYapServer.createChannel("TEST", sizeof(testPayload))); - - /* - * Case: Send on existent channel while unsynced. - */ - testYapServer.sendData("TEST", testPayload, sizeof(testPayload)); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, sizeof(testPayload)); - - /* Sync */ - gTestStream.pushToQueue(inputQueueVector[0U], controlChannelFrameLength); - testYapServer.process(1U); - testYapServer.process(2U); - TEST_ASSERT_TRUE(testYapServer.isSynced()); - - /* - * Case: Send on non-existent channel. - */ - testYapServer.sendData("HELLO", testPayload, sizeof(testPayload)); - TEST_ASSERT_EQUAL_UINT8_ARRAY(emptyOutputBuffer, gTestStream.m_outputBuffer, sizeof(testPayload)); - - /* - * Case: Send on existent channel. - */ - testYapServer.sendData("TEST", testPayload, sizeof(testPayload)); - TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedOutputBufferVector[0U], gTestStream.m_outputBuffer, sizeof(testPayload)); -}