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 {
enum packetSubType_t {
class basePacket {
    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);
      virtual void print(void); //Prints debug messages on the serial monitor.

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); and basePacket(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 in baseGame::offeringGame to send an OFFERING_GAME_PACKET and COIN_FLIP_PACKET. Also used in baseGame::acceptingGame to send an ACCEPTING_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 have print() methods for debugging may want to call this function first. Note it is print() and not println().

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.

WARNING: When implementing your derived Move and Results classes it is recommended you DO NOT make use of pointers to data unless you create a mechanism to send the pointed to data in a separate packet and then reassemble it on the other side.

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.

This guide was first published on Jul 07, 2020. It was last updated on Jul 07, 2020.

This page (The basePacket object class) was last updated on Jul 07, 2020.

Text editor powered by tinymce.