The baseMove
and baseResults
classes are each derived from another class basePacket
. While we do not need to create an extension of basePacket
, Both of the baseMove
and baseResults
classes inherit its data and methods so we will examine it first. Data is transmitted in packets of binary data. The maximum size of a packet depends upon the type of radio you are using but for the RF69HCW it is 60 bytes per packet.
While we could have implemented data transfer in some more human readable format such as JSON, we risked bumping up against that 60 byte limit if a particular game needed to transfer a lot of data. By sending it in binary form we make it as compact as possible and we don't have to bother with encoding or decoding JSON even though there are libraries to assist us in doing so.
The code for the basePacket
, baseMove
, and baseResults
objects are found in the files TwoPlayerGame_base_packet.h and TwoPlayerGame_base_packet.cpp.
Here is the definition of the basePacket
class:
enum packetType_t { NO_PACKET_TYPE, OFFERING_GAME_PACKET, ACCEPTING_GAME_PACKET, MOVE_PACKET, RESULTS_PACKET, COIN_FLIP_PACKET }; enum packetSubType_t { NO_SUBTYPE, NORMAL_MOVE, PASS_MOVE, QUIT_MOVE, NORMAL_RESULTS, HIT_RESULTS, MISS_RESULTS, WIN_RESULTS, LOSE_RESULTS, TIE_RESULTS, FLIP_TRUE, FLIP_FALSE }; class basePacket { public: baseRadio* Radio; packetType_t type; packetSubType_t subType; basePacket(void) {subType=NO_SUBTYPE;} basePacket(baseRadio* radio_ptr) {subType=NO_SUBTYPE; Radio=radio_ptr;}; virtual size_t my_size() { return sizeof( *this ); } virtual bool send(void); virtual bool send(packetType_t t) {type=t; return send();}; bool requireTypeTimeout(packetType_t t,uint16_t timeout); void requireType(packetType_t t); #if(TPG_DEBUG) virtual void print(void); //Prints debug messages on the serial monitor. #endif };
Each packet has a pointer to the Radio
object so that it knows how to send itself or be received into itself. It also has 2 other data items type
and subType
with the legal values defined in the enums shown above. These are the only data items contained in the basePacket
and they are inherited by any classes based upon it. The methods are as follows:
-
basePacket(void);
andbasePacket(baseRadio* radio_ptr);
-- Constructors. -
virtual size_t my_size() { return sizeof( *this ); }
-- Returns the size of the actual object as instantiated. We will explain later why this is necessary. -
virtual bool send(void);
-- Sends the packet. -
virtual bool send(packetType_t t);
-- Sends a simple non-data packet. Used inbaseGame::offeringGame
to send anOFFERING_GAME_PACKET
andCOIN_FLIP_PACKET
. Also used inbaseGame::acceptingGame
to send anACCEPTING_GAME_PACKET
. Returns true if packet was acknowledged. -
bool requireTypeTimeout(packetType_t t,uint16_t timeout);
-- Waits for the specified time in attempt to receive a particular type of packet from the other device. Returns true if the proper packet was received before timeout. Returns false if either time ran out or a received packet was the wrong type. -
void requireType(packetType_t t);
-- Waits indefinitely for a packet of a particular type. Ignores any of other packets. -
virtual void print(void);
-- Prints debug messages on the serial monitor. Derived classes that haveprint()
methods for debugging may want to call this function first. Note it isprint()
and notprintln()
.
How Packets Are Transmitted
We want to transmit data from a packet object on this device to an identical packet object on the other device. When sending data, the Radio object expects a pointer to the address where your data begins and a number that is the length of the data. Similarly when receiving data it expects and address to where the data should be placed and the maximum length of that location. Rather than copying the data into some buffer to be transmitted or to receive it into a buffer, we decided to just point to a location within the object itself and let the Radio object transmit directly out of the packet object on one device into a packet object on the other device. We had to be careful however that we didn't overwrite something that we didn't want to mess with.
If we start at address this
(which is a pointer to the object itself) it would include the pointer to the function table. Although we are compiling identical code on identical devices we can't be 100% sure those addresses would be the same. In fact we have tested this code using PyGamer as one device and PyBadge as the other device. Similarly the first data item in a packet is a pointer to the radio object which we don't want to overwrite either. So we have to offset everything by 2*sizeof(void*)
. We know that sizeof(void*)
is 4 but who knows... we might someday port this to a 64-bit platform Therefore using sizeof(void*)
ensures we get it right and it self documents the code to explain where that 4 value came from.
In addition to needing to know where in the object we want to start transmitting data, we also need to know the size of the derived packet object. The virtual method my_size()
correctly tells a base method the size of the actual object. Therefore the data we will transmit in each packet starts at this+(2*sizeof(void))
and the length of the data is my_size()-(2*sizeof(void))
.
NOTE: The my_size()
gives the size of the object but object sizes are typically rounded off to multiples of 4. So if you have an odd amount of data, there will be some garbage data at the end to pad it out to a multiple of 4. Make sure you compile the code for both devices using the same compiler and the same settings to ensure that such padding occurs the same on both devices.
Page last edited March 08, 2024
Text editor powered by tinymce.