-
Notifications
You must be signed in to change notification settings - Fork 0
Communication
In this section the usage of all communication related features will be explained.
PacketValue is a class that encapsulates the conversion procedure of a value so it is ready to be sent. It works the following way:
Where:
-
The cast_type is the type that needs to be transmitted or the value received from a sender.
-
The source is a double pointer that stores the reference of the value that will store the data sent or received.
-
The factor multiplies the received value contained in the source before casting it to the cast_type. This is used to maintain precision in floating point numbers while minimizing the bit rate. Similarly when a new value is stored it is also divided by the factor.
It contains 3 methods:
-
convert(): returns the converted value ready to be sent
-
load(): receives the new value received as a parameter, performs the inverse conversion and stores the converted value at source
-
size(): returns the size in bytes that the cast_type needs to be stored
A real use case might be the following:
double speed = 5.43;
double factor = 100;
using cast_type = uint16_t
uint16_t variable_to_be_sent;
PacketValue speed_packet_value = PacketValue<cast_type>(&speed, factor);
variable_to_be_sent = speed_packet_value.convert(); //Now variable_to_be_sent contains 543
uint16_t speed_received = 704;
speed_packet_value.load(speed_received); //Now speed contains 7.04
size_t size_of_variable = speed_packet_value.size() //returns sizeof(cast_type) which is 2 in this case
The Packet class automates the creation of a union-like buffer. A union buffer is a buffer with zero aligment between data of different types, where each of the elements can be accesed individually while still being able to manipualate the buffer as a whole.
It can be seen as a special type of LinkedList of PacketValues.
Each of the Packets contains an id value. By now it is a two bytes length unsigned integer, which is used to identify the Packet from both ends of the communication protocol.
A packet is contructed the following way:
Packets have two main functionalities:
-
Being built: A packet can be built by calling the build() method over it. When a packet is built it performs convert() on all of its PacketValues. Then copies all of its converted values on a memory allocated eight bit unsigned integer buffer which is returned by the build() method.
This buffer is stored by the packet and can be obtained without building the packet again by accesing the instance variable buffer.
The memory in the buffer is in the same order as introduced in the PacketValues List
The average time complexity of building a Packet is Ɵ(n) where n is the number of PacketValues while the worst case scenario is O(2n) which only takes place the first time a Packet is built as it has to calculate how much space is needed to malloc for the buffer.
-
Being loaded: New data can be loaded to a Packet by handling as an argument an eight bit unsigned integer pointer to the save_data() method, this method will perform load() to each of the PacketValues with their corresponding type and postion in the buffer.
Additionally when a new Packet is constructed its save_data function is added to a static map called save_by_id contained in the Packet<> base class such that when a receiver receives a new payload for a packet it can load this new packet payload just by obtaining its id.
The id can be obtained from a payload by handling the payload buffer to the static get_id() method of the Packet<> base class
Each of the Packets has its own type beacause they operate with templates as it is the only way of storing multiple different type PacketValues, as PacketValues might also have different types between them. This means that storing multiple Packets inside most dataclasses won't work, tuples will work as each of the elements inside a tuple can be a different type.
An example of use might be the following:
double a = 3.1;
double b = 3.5;
Packet packet = {
90,
PacketValue<uint16_t>(&a,10),
PacketValue<uint16_t>(&b,10)
};
packet.build();
uint8_t* buffer = packet.buffer; //This is the same as uint8_t* buffer = packet.build()
//Buffer will include the following information 9A 00 36 01 5E 01 in a Little Endian system as 0x009A is 90, 0x0136 is 310 and 0x015E is 350
uint8_t* new_data;
new_data[0] = 90;
new_data[1] = 0;
new_data[2] = 55;
new_data[3] = 0;
new_data[4] = 55;
new_data[5] = 0;
packet.save_data(new_data); // This is the same as Packet<>::save_by_id[90](new_data)
//Now a contains 5.5 and b also contains 5.5
Order is a peculiar type of Packet, in fact it inherits from it, the only difference between a Packet and an Order is that an Order has a non anonymous function linked to it, this function should be called by the communication protocols when it receives an order.
The callback function can be omitted from the constructor and be later added with the set_callback() method.
Additionally when an Order is constructed its id is added to a static map called on_received in the Packet<> base class which lets the receiver identify the Order and execute the callback just with the payload.
The sender of the Order doesn't have to implement the callback function to the Order if it is not going to respond to it and it's limited to just sending it.