# ProtoPlexer protocol
## Description
**ProtoPlexer** protocol is lightweight hardware protocol designed to be used in small satellite onboard network.
It was designed to with [CAN](https://en.wikipedia.org/wiki/CAN_bus)
satellite network in mind, yet it should be suitable for other onboard hardware networks and for other applications.
### Features
* Network address handling
* Message priorities
* Secure merging of the short chunks to the messages up to 64kbytes long;
* Message labeling with 16bit ID describing message type and content. Message types are unique within mission.
* FIFO for incoming messages
* Event generation for received full messages
* Customizable RAM footprint
### Underlaying protocol requirement.
To be implemented underlaying protocol should provide several features such as:
* Broadcasting messages
* Ability to separate *chunk* messages from the stream (in CAN networks it’s one CAN frame) at least 6bytes long.
Small *chunks* should have explicit way to determine first byte
* Ability for every node to broadcast
### Deployment overview
To deploy and use **ProtoPlexer** user should:
* Create hardware interface derived from either **plexer_queued** or
**plexer_blocking** depending on whether message processing should be
synchronous or deferred. Both classes are defined in
[plexer.h](/doc/trunk/plexer.h)
* **plexer_blocking** will immediately call **rx_event()** when
**add_chunk()** has received a full message. Outoing messages are
immediately transferred to **send_chunk()**. You should choose this if your
application is running on an RTOS where one task is dedicated to processing
incomming messages and the driver implementation has an internal tx queue
to prevent blocking.
* **plexer_queued** will queue incomming messages and call the message
handler (**rx_event()**) when the application calls **poll_new()**. In a
similar fashion, outgoing chunks are buffered and only transferred to the
**send_chunk()** driver method when **poll_tx()** is called. You should
choose this if your application is a super loop, you want precise control
over when new messages are processed or if your driver does not have an
integrated tx queue.
* Implement **send_chunk()** to send chunks originating from the application
* Either call **add_chunk()** every time a chunk is received fom the network or
if you are using **plexer_queued**, implement **receive_chunk()** and call
**poll_rx()** regularly.
* Implement **rx_event()** to handle new received messages
* If you are using **plexer_queued**:
* Find some place in your software to regularly call **poll_tx()** and
**poll_new()**
* Optionally implement **rx_notification()** which will be called in
**add_chunk()** whenever a message is received. This can be used by the
application to determine when to call **poll_new()**
### Protocol key parts and description
First lets describe some key definitions of the protocol parts.
#### Nodes
Every logical instance within network have 12bit address and called **node**. **Nodes** could be hardware devices, software applications,
or logical parts of software applications. Every time you need to separate network flow to or from one part of your system - you could create a
**node** for it and assign address.
#### Messages
Protocol transfer data between **nodes** using messages, messages have following structure:
* Hardware header
* Address from
* Address to
* Inverted priority
* Message header
* Message ID
* Length
* CRC
* Data
**Hardware header** contains data used to transmit data over underlaying protocol.
**Addresses** from an to - are addresses of transmitting and receiving **nodes** accordingly.
**Inverted priority** - is priority used to
transmit message over underlaying protocol. Because **ProtoPlexer** designed with CAN network in mind and main application is satellite
protocol mostly for CAN network, which uses inverted priority for messages, we also use inverted one. So the lowest parameter value
means biggest priority. It's advised to use lowest priority (biggest value) unless you have to do otherwise.
**Message header** contains data used to transmit data within **ProtoPlexer** protocol
**Message ID** is unique ID for specific message type. Protocol does not yet specify **Message ID**'s or their usage, but range 0xFFF0-0xFFFF
should be considered reserved for protocol purposes. Later it may contain messages like "ping" "version" and other, which should
be implemented on every **node**. It's possible to use the same **Message ID** for different purposes within differed devices, yet it's
strongly not recommended, unless you have to.
**Length** and **CRC** used within protocol and shouldn't be used by user. They, as you could expect are the whole length of the message and it's
CRC16 value. 16bit length means messages longer then 64kbytes are not possible.
**Data** is any user data associated with **Message ID** it's advised to use strong correlation between the **Message ID** and data interpritation.
Also you should consider using messages without data. Sometimes **Message ID** is enough to transmit command for the node.
#### Chunks
Chunks - are minimal frames used to transmit data over underlaying protocol. **ProtoPlexer** separate every message to the chunks according to
hardware settings, and transmit data over underlaying protocol in chunks. In CAN network they are equal to CAN frames. During receive **ProtoPlexer**
receive separate chunks from network, and keep them till the whole **message** is ready.
Every chunk have the following fields
* Hardware header
* Address from
* Address to
* Inverted priority
* Starting bit
* Data
Different approaches could be used to transmit chunks on different hardware networks, example for some of them would be provided in further
articles. Underlaying protocol and it's hardware driver should receive data in chunks, transmit them and provide received chunks to the protocol.
#### Plexer
Plexer is actual ProtoPlexer implementation class. It has following interface
plexer_result poll_rx();
plexer_result poll_tx();
plexer_result poll_new();
plexer_result send_msg (plexer_msg * msg);
plexer_result add_chunk(const plexer_chunk * chunk);
virtual plexer_event_result rx_event (plexer_msg *, plexer_eventtype);
virtual shared_ptr<plexer_chunk> receive_chunk();
virtual plexer_hw_result send_chunk(plexer_chunk * chunk);
uint16_t own_address() const;
uint_fast16_t max_send_attempts() const;
void setMax_send_attempts(const uint_fast16_t &max_send_attempts);
uint_fast16_t max_chunk_length() const;
static std::string result2string(plexer_result value);
**poll_rx** function should be called from user software. This function check (once) **plexer_hw** for new messages, and in case last **chunk** of valid
message received it also calls **receive event** with **polling** type of event. This function will not keep asking **plexer_hw** till it's empty.
In case you want such behavior you should poll plexer itself till it respond that **hardware** is empty.
**poll_tx** function is used to transmit data to network. When message **sent** protocol will not block thread till it actually transmitted over network.
**ProtoPlexer** will cut the **message** to **chunks** according to settings and put them into outgoing buffer. To send message you should call **poll_tx**
till this buffer is empty. **ProtoPlexer** will try to send every message up to 5 times as reaction to **poll_tx** and will return "ok" if **hardware**
is busy. Only afterwards it'll respond with error code.
Note: this is only required when using **plexer_queued**. **plexer_blocking** will send outgoing chunks immediately.
**poll_new** function used to check received messages buffer. We need one because messages not always cleared after event. In case some messages exist
in **ProtoPlexer** internal buffer it'll also call **receive event** with polling event type.
Note: this is only required when using **plexer_queud**. **plexer_blocking** will react to new messages immediately.
**add_chunk** function used internally by **poll_rx** but you could also use it in your **hardware** driver. Your driver may call this function right after
receiving **chunk**. This way **plexer** will not need to wait till application **poll** it for the new messages, and will generate event (if needed) right away
It's possible to use both ways to add chunks to the protocol. But you should guarantee sequence order. Easiest way to do so is limiting receive to
one of two ways only calling **add_chunk** from hardware driver, or only responding to **poll_rx**
**rx_event** is virtual method used to provide reaction to received messages. Your event should react to the message according to your needs.
**receive_chunk** is a virtual methods providing access to underlaying protocol driver. It's used by **plexer_queued** to poll the hardware driver for new **chunks** in **poll_rx**
**send_chunk** is one of virtual methods providing access to underlaying protocol driver. It's used by **plexer** internally to send **chunks**
over underlaying protocol during message transmission.
**own_address** is **address** of this **node** in the ProtoPlexer network
**max\_send_attempts / setMax\_send_attempts** is getter and setter for send attempts limit in **plexer_queued**. Default value is 5. Every time user poll **poll_tx**
he will receive result of such polling. In case **plexer** will be unable to send **chunk** within **max\_send_attempts** count user will still receive
**ok** error code. If **plexer** will fail to transmit one chunks more than **max\_send_attempts** count user will receive **hw\_send\_too\_many_attempts**
result. **Plexer** will reset counter to zero, but will not remove such chunk, and will continue with it later.
Note: **plexer_queued** assumes that the driver will take care of handling retries.
**max\_chunk_length** will be used by protocol to cut long messages to chunks during send implementation. Yet your driver could return longer chunks
from receive_chunk() it's set once by **plexer** constructor
**result2string** will just convert error codes to string for debug/logging purposes
## Deployment
First you need to create class derived from **plexer_queued** for example
class some_example_class : public plexer_queued
{
public:
// where some_hardware_const is the maximum number transmission unit of the network
// (i.e. the number of bytes that can be transmitted in one network packet)
// E.g. 8 for CAN, 64 for CAN-FD, 1500 for Ethernet and so on
some_example_class(uint16_t own_address) :plexer(own_address, some_hardware_const) { }
shared_ptr<plexer_chunk> receive_chunk() override;
plexer_hw_result send_chunk(plexer_chunk *chunk) override;
plexer_event_result rx_event(plexer_msg * msg) override;
};
Also you need some underlaying protocol driver connected to [plexer](/doc/trunk/plexer.h)s methods.
It could be inside or outside new class, example not provided.
You also need some event to process received messages. Example of such event could look like this:
void some_example_class::rx_event(plexer_msg * msg)
{
switch (static_cast<some_mission_specific_enum>(msg->message_ID()))
{
case some_mission_specific_enum::ID_reboot:
{
reboot();
} break;
case some_mission_specific_enum::ID_hello:
{
cout << "Hello";
} break;
default:
{
cout << "Unknown message";
} break;
}
}
All you need now is an instance of your class to communicate with **ProtoPlexer** network.
auto plexer_inst = make_shared<plexer>(some_own_address_constant);
## Hardware specific implementation
Since **ProtoPlexer** use some features of underlaying protocol for addressing and header transmission those rules should be fixed for every givend underlaying protocol.
If some is not covered in this document you could design your own approach, and use it within your system. You could also send it to be integrated in this document.
### CAN implementation
In CAN implementation maximum chunk length is fixed to be 8bytes exactly and chunks are actually CAN frames.
CAN bus should use 29bit CAN\_ID **ProtoPlexer** hardware header is transmitted inside CAN\_ID as following:
| **CAN_ID bits** | 25-28 | 24 | 12-23 | 0-11 |
|-----------------|-------------------|--------------|--------------|------------|
| fields | priority inverted | starting bit | address from | address to |
### UART implementation
In UART implementation maximum chunk length is mission specific, yet no more than 250bytes
and chunks contain 4 bytes header with all chunk specific information. Chunks are separated using
[COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) protocol
| **UART bits** | 25-32 | 24 | 12-23 | 0-11 |
|-----------------|-------------------|--------------|--------------|------------|
| fields | priority inverted | starting bit | address from | address to |
\
\
\
\
\
\
\