As previously mentioned, we have already created a radio object class for the RFM69HCW radios. You can use it in this project and not worry about creating your own. However if you would like to implement some other means of transmitting data between your devices you would start with the baseRadio class and make an extension of it with the specifics for your particular radio.

Our implementation of the RF69Radio class is based upon the RadioHead Library, therefore our baseRadio class closely resembles some of the methods used in RadioHead. If you create another transport system that is not based on RadioHead you will have to conform to our API which might be a little more difficult.

NOTE: The RadioHead library refers to our device as an "RF69". Throughout our code we refer to it by that name or perhaps "RF69HCW". However the actual name of the Radio FeatherWing is "RFM69HCW" with an "M" in it. We didn't notice this until the code was written. We chose not to rewrite everything with the extra "M". A search and replace would have messed up the RadioHead references which needed to remain "RF69".
class baseRadio {
  public:
    uint8_t myPlayerNum;    //my radios address
    uint8_t otherPlayerNum; //opponent's radio address
    virtual bool setup(uint8_t myPlayerNum, uint8_t otherPlayerNum)=0;
    virtual bool send(uint8_t* packet_ptr,uint8_t len)=0;
    virtual bool recvTimeout(uint8_t* packet_ptr,uint8_t* len_ptr,uint16_t timeout)=0;
    virtual bool recv(uint8_t* packet_ptr,uint8_t* len_ptr)=0;
    virtual bool available(void)=0;
};

Note that this is an abstract class that cannot be instantiated itself. All of the methods are pure virtual so you will have to implement all of them.

Each device has its own unique address. Player 1 will be device #1 and Player 2 will be device #2. This device number is the ONLY difference between the software on one device versus the other.

  • bool setup(uint8_t myPlayerNum, uint8_t otherPlayerNum); -- Gets called ONCE during your Game.setup() call inside your main program setup(). Returns true if the setup was successful.
  • bool send(uint8_t* packet_ptr,uint8_t len); -- Sends a packet of data starting at memory address packet_ptr with the number of bytes specified by len. Returns true if sent data was acknowledged as received.
  • bool recvTimeout(uint8_t* packet_ptr,uint8_t* len_ptr,uint16_t timeout); -- Attempts to receive a packet and waits until the specified timeout. The packet_ptr is the start address of the data. The len_ptr points to a uint8_t specifying the size of our buffer. Upon return it passes back the actual number of bytes received. Returns true if data was received before the timeout.
  • bool recv(uint8_t* packet_ptr,uint8_t* len_ptr); -- If data is available it receives it. Parameters are the same as the first two of recvTimeout explained above. Returns true if successful.
  • bool available(void); -- Returns true if data is available to be received.

See the discussion on basePacket about how we determine the start address in the length of the data to send.

Our RF69Radio object class

The RadioHead library offers an advanced packet transmission system called RHReliableDatagram that includes encrypting the data, an automatic acknowledgment of a packet received, and multiple retries if an initial transmission doesn't get through. All of this is completely transparent to us. We just send or receive using the methods we defined in our baseRadio class which in turn call similar methods in the RHReliableDatagram library.

Every packet which is sent is acknowledged by the receiving device using a system that is invisible to us. So for example if send a Move packet, when it is received by the other device it sends a hidden acknowledgment back to us to let us know the move was received. This is not the Results packet that we ourselves send back. This is a behind-the-scenes acknowledgment. When the other device sends a Results packet in response to our move, it also is acknowledged. Only after multiple failures to receive an acknowledgment do we get a packet error. This makes for extremely reliable data transmission. If you are implementing a different type of radio and it is supported by RadioHead and RHReliableDatagram then you should definitely take advantage of that system.

The only errors we are likely to see is if one device is too busy perhaps playing a sound file or doing something else that would prevent it from sending an acknowledgment signal. This would cause a fatal error in our code. At some point we might try to implement a way to recover such mistakes but keep in mind that we only get this failure after the RHReliableDatagram system has already tried multiple times so it is unlikely we can do anything to recover from such errors. In implementing the sound effects in the Battleship game we created a special myDelay(amount) function to use instead of the traditional Arduino delay(amount). It includes a yield() statement and it seemed to solve many of our conflicts between packet transmission and sound effects.

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

This page (The baseRadio object class) was last updated on Oct 15, 2021.

Text editor powered by tinymce.