Swadge 2024 2.0.0
APIs to develop games for the Magfest Swadge
Loading...
Searching...
No Matches
p2pConnection.h File Reference

Detailed Description

Design Philosophy

p2pConnection is a connection protocol with a little bit of message reliability that sits on top of ESP-NOW. Think of ESP-NOW like UDP, where you can quickly and unreliably broadcast and receive packets, and p2pConnection as TCP, which trades some speed for reliability. p2pConnection messages have sequence numbers for deduplication, are acknowledged, and are retried if not acknowledged.

Connections are made when two Swadges broadcast connection messages to each other, then send start messages to each other, and acknowledge each other's start message. The play order is determined by who acknowledges the start message first, which is suitably random. A connection sequence looks like this:

Connection Sequence

After connection, Swadges are free to send messages to each other. An example of unreliable communication with retries and duplication is as follows.

Unreliable Communication

Usage

p2pSendCb() and p2pRecvCb() must be called from the ESP-NOW callbacks to pass data to and from p2p.

p2pInitialize() should be called to initialize p2p. p2pDeinit() should be called when the Swadge mode is done to clean up.

The connection won't actually start until p2pStartConnection() is called. Connection statues will be delivered to the Swadge mode through the

p2pGetPlayOrder() can be called after connection to figure out of this Swadge is player one or two.

p2pSendMsg() can be called to send a message from one Swadge to another.

p2pSetDataInAck() can be called to set up data to be delivered in the next acknowledge message. This is useful for high-bandwidth transactional messages. For example, one Swadge may send it's button state to the other, and receive the game state in the acknowledge message. This turns four messages (button state, ack, game state, ack) into two messages (button state, ack[game state]). Note that the acknowledge message with data is not acknowledged itself, but the Swadge sending the inital message will retry until it receives the acknowledge. p2pClearDataInAck() can be called to clear the data to be sent in the acknowledge.

Tips

p2pConnection can be finicky to use, so here are a few tips to ensure consistent connections and data transfer.

  1. It's preferred to use ESP_NOW rather than ESP_NOW_IMMEDIATE when setting up your Swadge mode. This is because ESP_NOW uses a queue to pass ESP-NOW packets to the Swadge Mode while ESP_NOW_IMMEDIATE passes them directly from the system callback function. As the IDF documentation states:

    ‍The receiving callback function also runs from the Wi-Fi task. So, do not do lengthy operations in the callback function. Instead, post the necessary data to a queue and handle it from a lower priority task.

    If ESP_NOW_IMMEDIATE is used, the receive callback (p2pMsgRxCbFn) should return as quickly as possible.
  2. Don't halt the Swadge Mode or take too long in any functions when p2p is active. Remember that p2p is running its own timers, so if the Swadge Mode blocks p2p operation, timers may expire without getting the chance to retry messages. As a good rule of thumb, if you notice visual stutters then something is taking too long.
  3. You must wait for both sides of the p2p connection to be established before transmitting any data packets. Roles can be checked by calling p2pGetPlayOrder(). The Swadge with the GOING_SECOND role finishes its handshake first, and if a data packet is sent immediately after the CON_ESTABLISHED event occurs, then that packet will mess up the connection handshake for the other Swadge. The Swadge with the GOING_FIRST role finishes its handshake second, and once that CON_ESTABLISHED event occurs, that Swadge may send a data packet to the other. That's why the role is called GOING_FIRST!
  4. Try to not to have multiple Swadges transmit at the same time. Instead, have one send a message and have the other respond. ESP32-S2s only have one antenna, so they cannot transmit and receive at the same time. If they do it's likely that at least one message will fail. Remember that p2pGetPlayOrder() can be used to determine which Swadge should send the first message (GOING_FIRST) and which should send responses (GOING_SECOND). It's useful to think through the messages that will be sent between two Swadges and visualize them in a sequence diagram to avoid collisions. PlantUML is a good visualization tool, and is what is used for the diagrams above.
  5. p2pSendMsg() does not queue messages, so if you try to send multiple messages without first receiving the transmit callback (p2pMsgTxCbFn), then only the last sent message will be sent successfully. Instead, you should either combine data into a single packet (which is preferred, fewer larger packets tend to be faster) or wait for a transmission to completely finish before starting the next.

Example

static void demoEspNowRecvCb(const uint8_t* mac_addr, const uint8_t* data, uint8_t len, int8_t rssi);
static void demoEspNowSendCb(const uint8_t* mac_addr, esp_now_send_status_t status);
static void demoConCb(p2pInfo* p2p, connectionEvt_t evt);
static void demoMsgRxCb(p2pInfo* p2p, const uint8_t* payload, uint8_t len);
static void demoMsgTxCbFn(p2pInfo* p2p, messageStatus_t status, const uint8_t* data, uint8_t len);
// Make sure the Swadge mode callbacks are set
swadgeMode_t demoMode = {
.fnEspNowRecvCb = demoEspNowRecvCb,
.fnEspNowSendCb = demoEspNowSendCb,
...
};
// Variable which contains all the state information
p2pInfo_t p2p;
...
// Initialize and start connection
p2pInitialize(&p2p, 'd', demoConCb, demoMsgRxCb, -70);
...
// Send a message
const uint8_t testMsg[] = {0x01, 0x02, 0x03, 0x04};
p2pSendMsg(&p2p, testMsg, ARRAY_SIZE(testMsg), demoMsgTxCbFn);
...
static void demoEspNowRecvCb(const uint8_t* mac_addr, const uint8_t* data, uint8_t len, int8_t rssi)
{
p2pRecvCb(&p2p, mac_addr, data, len, rssi);
}
static void demoEspNowSendCb(const uint8_t* mac_addr, esp_now_send_status_t status)
{
p2pSendCb(&p2p, mac_addr, status);
}
static void demoConCb(p2pInfo* p2p, connectionEvt_t evt)
{
// Do something when a connection event happens
}
static void demoMsgRxCb(p2pInfo* p2p, const uint8_t* payload, uint8_t len)
{
// Do something when a message is received
}
static void demoMsgTxCbFn(p2pInfo* p2p, messageStatus_t status, const uint8_t* data, uint8_t len)
{
// Do something when a message is acknowledged
}
@ ESP_NOW
ESP-NOW packets are delivered to Swadge modes from the main loop.
Definition hdw-esp-now.h:101
#define ARRAY_SIZE(arr)
Definition macros.h:66
void p2pSendCb(p2pInfo *p2p, const uint8_t *mac_addr, esp_now_send_status_t status)
This must be called by whatever function is registered to the Swadge mode's fnEspNowSendCb.
Definition p2pConnection.c:821
void p2pSendMsg(p2pInfo *p2p, const uint8_t *payload, uint16_t len, p2pMsgTxCbFn msgTxCbFn)
Send a message from one Swadge to another. This must not be called before the CON_ESTABLISHED event o...
Definition p2pConnection.c:295
void p2pRecvCb(p2pInfo *p2p, const uint8_t *mac_addr, const uint8_t *data, uint8_t len, int8_t rssi)
This function must be called whenever an ESP NOW packet is received.
Definition p2pConnection.c:432
void p2pStartConnection(p2pInfo *p2p)
Start the connection process by sending broadcasts and notify the mode.
Definition p2pConnection.c:175
connectionEvt_t
These are the states a Swadge will go through when connecting to another.
Definition p2pConnection.h:210
messageStatus_t
Message statuses after transmission.
Definition p2pConnection.h:220
All the state variables required for a P2P session with another Swadge.
Definition p2pConnection.h:322
A struct of all the function pointers necessary for a swadge mode. If a mode does not need a particul...
Definition swadge2024.h:233
wifiMode_t wifiMode
This is a setting, not a function pointer. Set it to NO_WIFI to save power by not using WiFi at all....
Definition swadge2024.h:244

Go to the source code of this file.

Data Structures

struct  p2pConMsg_t
 The byte format for a connection message for a P2P session. More...
 
struct  p2pCommonHeader_t
 The byte format for a common header for all P2P packets. More...
 
struct  p2pDataMsg_t
 The byte format for a P2P data packet. More...
 
struct  _p2pInfo
 All the state variables required for a P2P session with another Swadge. More...
 
struct  p2pPacket_t
 All the information for a packet to store between the receive callback and the task it's actually processed in. More...
 
struct  _p2pInfo.ack
 Variables used for acknowledging and retrying messages. More...
 
struct  _p2pInfo.cnc
 Connection state variables. More...
 
struct  _p2pInfo.tmr
 The timers used for connection and ACKing. More...
 

Macros

#define P2P_MAX_DATA_LEN   245
 The maximum payload of a p2p packet is 245 bytes.
 
#define P2P_START_BYTE   'p'
 A start byte for all p2p packets.
 

Typedefs

typedef struct _p2pInfo p2pInfo
 All the state variables required for a P2P session with another Swadge.
 
typedef void(* p2pConCbFn) (p2pInfo *p2p, connectionEvt_t evt)
 This typedef is for the function callback which delivers connection statuses to the Swadge mode.
 
typedef void(* p2pMsgRxCbFn) (p2pInfo *p2p, const uint8_t *payload, uint8_t len)
 This typedef is for the function callback which delivers received p2p packets to the Swadge mode.
 
typedef void(* p2pMsgTxCbFn) (p2pInfo *p2p, messageStatus_t status, const uint8_t *data, uint8_t len)
 This typedef is for the function callback which delivers acknowledge status for transmitted messages to the Swadge mode.
 
typedef void(* p2pAckSuccessFn) (p2pInfo *p2p, const uint8_t *data, uint8_t len)
 This typedef is for a function callback called when a message is acknowledged. It make also contain a data packet which was appended to the ACK.
 
typedef void(* p2pAckFailureFn) (p2pInfo *p2p)
 This typedef is for a function callback called when a message is not acknowledged.
 

Enumerations

enum  playOrder_t { NOT_SET , GOING_SECOND , GOING_FIRST }
 After connecting, one Swadge will be GOING_FIRST and one will be GOING_SECOND. More...
 
enum  connectionEvt_t {
  CON_STARTED , RX_GAME_START_ACK , RX_GAME_START_MSG , CON_ESTABLISHED ,
  CON_LOST
}
 These are the states a Swadge will go through when connecting to another. More...
 
enum  messageStatus_t { MSG_ACKED , MSG_FAILED }
 Message statuses after transmission. More...
 
enum  p2pMsgType_t {
  P2P_MSG_CONNECT , P2P_MSG_START , P2P_MSG_ACK , P2P_MSG_DATA_ACK ,
  P2P_MSG_DATA
}
 The five different types of p2p messages. More...
 

Functions

void p2pInitialize (p2pInfo *p2p, uint8_t modeId, p2pConCbFn conCbFn, p2pMsgRxCbFn msgRxCbFn, int8_t connectionRssi)
 Initialize the p2p connection protocol.
 
void p2pSetAsymmetric (p2pInfo *p2p, uint8_t incomingModeId)
 Set the p2p connection protocol to listen for a different mode ID from its own.
 
void p2pDeinit (p2pInfo *p2p)
 Stop up all timers.
 
void p2pStartConnection (p2pInfo *p2p)
 Start the connection process by sending broadcasts and notify the mode.
 
void p2pSendMsg (p2pInfo *p2p, const uint8_t *payload, uint16_t len, p2pMsgTxCbFn msgTxCbFn)
 Send a message from one Swadge to another. This must not be called before the CON_ESTABLISHED event occurs. Message addressing, ACKing, and retries all happen automatically.
 
void p2pSendCb (p2pInfo *p2p, const uint8_t *mac_addr, esp_now_send_status_t status)
 This must be called by whatever function is registered to the Swadge mode's fnEspNowSendCb.
 
void p2pRecvCb (p2pInfo *p2p, const uint8_t *mac_addr, const uint8_t *data, uint8_t len, int8_t rssi)
 This function must be called whenever an ESP NOW packet is received.
 
void p2pSetDataInAck (p2pInfo *p2p, const uint8_t *ackData, uint8_t ackDataLen)
 Set data to be automatically used as the payload all future ACKs, until the data is cleared.
 
void p2pClearDataInAck (p2pInfo *p2p)
 Clear any data which was set to be used as the payload in the next ACK.
 
playOrder_t p2pGetPlayOrder (p2pInfo *p2p)
 After the swadge is connected to another, return whether this Swadge is player 1 or player 2. This can be used to determine client/server roles.
 
void p2pSetPlayOrder (p2pInfo *p2p, playOrder_t order)
 Override whether the Swadge is player 1 or player 2. You probably shouldn't do this, but you might want to for single player modes.
 

Data Structure Documentation

◆ p2pConMsg_t

struct p2pConMsg_t
Data Fields
uint8_t startByte Start byte, must be P2P_START_BYTE.
uint8_t modeId Mode byte, must be unique per-mode.
p2pMsgType_t messageType Message type byte.

◆ p2pCommonHeader_t

struct p2pCommonHeader_t
Data Fields
uint8_t startByte Start byte, must be P2P_START_BYTE.
uint8_t modeId Mode byte, must be unique per-mode.
p2pMsgType_t messageType Message type byte.
uint8_t seqNum A sequence number for this packet.
uint8_t macAddr[6] The MAC address destination for this packet.

◆ p2pDataMsg_t

struct p2pDataMsg_t
Data Fields
p2pCommonHeader_t hdr The common header bytes for a P2P packet.
uint8_t data[P2P_MAX_DATA_LEN] The data bytes sent or received.

◆ _p2pInfo

struct _p2pInfo
Data Fields
uint8_t modeId The mode ID set by the mode using P2P.
uint8_t incomingModeId A mode ID to listen for which is different than initialized mode ID. See p2pSetAsymmetric()
p2pConMsg_t conMsg The connection message to transmit.
p2pCommonHeader_t startMsg The start message to transmit.
p2pDataMsg_t ackMsg The acknowledge message to transmit.
uint8_t dataInAckLen The length of any extra data which was appended to the ACK, see p2pSetDataInAck()
p2pConCbFn conCbFn A callback function called during the connection process.
p2pMsgRxCbFn msgRxCbFn A callback function called when receiving a message.
p2pMsgTxCbFn msgTxCbFn A callback function called when transmitting a message.
int8_t connectionRssi The minimum RSSI required to begin a connection.
struct _p2pInfo.ack ack Variables used for acknowledging and retrying messages.
struct _p2pInfo.cnc cnc Connection state variables.
struct _p2pInfo.tmr tmr The timers used for connection and ACKing.

◆ p2pPacket_t

struct p2pPacket_t
Data Fields
int8_t rssi The received signal strength indicator for the packet.
uint8_t mac[6] The MAC address of the sender.
uint8_t len The length of the received bytes.
p2pDataMsg_t data The received bytes.

◆ _p2pInfo.ack

struct _p2pInfo.ack
Data Fields
bool isWaitingForAck true if waiting for an ACK after transmitting a message
p2pDataMsg_t msgToAck A transmitted message which is waiting for an ACK.
uint16_t msgToAckLen The length of the message which is waiting for an ACK.
uint32_t timeSentUs The time the message is waiting for an ACK was transmitted.
p2pAckSuccessFn SuccessFn A callback function to be called if the message is ACKed.
p2pAckFailureFn FailureFn A callback function to be called if the message is not ACKed.

◆ _p2pInfo.cnc

struct _p2pInfo.cnc
Data Fields
playOrder_t playOrder Either GOING_FIRST or GOING_SECOND depending on how the handshake went.
uint8_t myMac[6] This Swadge's MAC address.
uint8_t otherMac[6] The other Swadge's MAC address.
bool isActive true if the connection process has started
bool isConnected true if connected to another Swadge
bool broadcastReceived true if a broadcast was received to start the connection handshake
bool rxGameStartMsg true if the other Swadge's game start message was received
bool rxGameStartAck True if this Swadge's game start message was acknowledged.
bool otherMacReceived true if the other Swadge's MAC address has been received
uint8_t mySeqNum The current sequence number used for transmissions.
uint8_t lastSeqNum The last sequence number used for transmissions.

◆ _p2pInfo.tmr

struct _p2pInfo.tmr
Data Fields
esp_timer_handle_t TxRetry A timer used to retry a transmission multiple times if not acknowledged.
esp_timer_handle_t TxAllRetries A timer used to cancel a transmission if all attempts failed.
esp_timer_handle_t Connection A timer used to cancel a connection if the handshake fails.
esp_timer_handle_t Reinit A timer used to restart P2P after any complete failures.

Macro Definition Documentation

◆ P2P_MAX_DATA_LEN

#define P2P_MAX_DATA_LEN   245

The maximum payload of a p2p packet is 245 bytes.

◆ P2P_START_BYTE

#define P2P_START_BYTE   'p'

A start byte for all p2p packets.

Typedef Documentation

◆ p2pInfo

typedef struct _p2pInfo p2pInfo

All the state variables required for a P2P session with another Swadge.

◆ p2pConCbFn

typedef void(* p2pConCbFn) (p2pInfo *p2p, connectionEvt_t evt)

This typedef is for the function callback which delivers connection statuses to the Swadge mode.

Parameters
p2pThe p2pInfo
evtThe connection event

◆ p2pMsgRxCbFn

typedef void(* p2pMsgRxCbFn) (p2pInfo *p2p, const uint8_t *payload, uint8_t len)

This typedef is for the function callback which delivers received p2p packets to the Swadge mode.

Parameters
p2pThe p2pInfo
payloadThe data that was received
lenThe length of the data that was received

◆ p2pMsgTxCbFn

typedef void(* p2pMsgTxCbFn) (p2pInfo *p2p, messageStatus_t status, const uint8_t *data, uint8_t len)

This typedef is for the function callback which delivers acknowledge status for transmitted messages to the Swadge mode.

Parameters
p2pThe p2pInfo
statusThe status of the transmission
dataThe data that was transmitted
lenThe length of the data that was transmitted

◆ p2pAckSuccessFn

typedef void(* p2pAckSuccessFn) (p2pInfo *p2p, const uint8_t *data, uint8_t len)

This typedef is for a function callback called when a message is acknowledged. It make also contain a data packet which was appended to the ACK.

Parameters
p2pThe p2pInfo
dataA data payload returned along with the ACK
lenThe length of the data payload appended to the ACK

◆ p2pAckFailureFn

typedef void(* p2pAckFailureFn) (p2pInfo *p2p)

This typedef is for a function callback called when a message is not acknowledged.

Parameters
p2pThe p2pInfo

Enumeration Type Documentation

◆ playOrder_t

After connecting, one Swadge will be GOING_FIRST and one will be GOING_SECOND.

Enumerator
NOT_SET 

Swadges haven't connected yet.

GOING_SECOND 

This Swadge goes second (player two)

GOING_FIRST 

This swadge goes first (player one)

◆ connectionEvt_t

These are the states a Swadge will go through when connecting to another.

Enumerator
CON_STARTED 

Connection has started.

RX_GAME_START_ACK 

This Swadge's start message has been ACKed.

RX_GAME_START_MSG 

Another Swadge's start message has been received.

CON_ESTABLISHED 

Connection has been established.

CON_LOST 

Connection was lost.

◆ messageStatus_t

Message statuses after transmission.

Enumerator
MSG_ACKED 

The message was acknowledged after transmission.

MSG_FAILED 

The message transmission failed.

◆ p2pMsgType_t

The five different types of p2p messages.

Enumerator
P2P_MSG_CONNECT 

The connection broadcast.

P2P_MSG_START 

The start message, used during connection.

P2P_MSG_ACK 

An acknowledge message.

P2P_MSG_DATA_ACK 

An acknowledge message with extra data.

P2P_MSG_DATA 

A data message.

Function Documentation

◆ p2pInitialize()

void p2pInitialize ( p2pInfo * p2p,
uint8_t modeId,
p2pConCbFn conCbFn,
p2pMsgRxCbFn msgRxCbFn,
int8_t connectionRssi )

Initialize the p2p connection protocol.

Parameters
p2pThe p2pInfo struct with all the state information
modeId8 bit mode ID. Must be unique per-swadge mode.
conCbFnA function pointer which will be called when connection events occur
msgRxCbFnA function pointer which will be called when a packet is received for the swadge mode
connectionRssiThe strength needed to start a connection with another swadge. A positive value means swadges are quite close. I've seen RSSI go as low as -70. 0 is practically touching

◆ p2pSetAsymmetric()

void p2pSetAsymmetric ( p2pInfo * p2p,
uint8_t incomingModeId )

Set the p2p connection protocol to listen for a different mode ID from its own.

Parameters
p2pThe p2pInfo struct with all the state information
incomingModeIdThe unique mode ID used by the other end of this connection

◆ p2pDeinit()

void p2pDeinit ( p2pInfo * p2p)

Stop up all timers.

Parameters
p2pThe p2pInfo struct with all the state information

◆ p2pStartConnection()

void p2pStartConnection ( p2pInfo * p2p)

Start the connection process by sending broadcasts and notify the mode.

Parameters
p2pThe p2pInfo struct with all the state information

◆ p2pSendMsg()

void p2pSendMsg ( p2pInfo * p2p,
const uint8_t * payload,
uint16_t len,
p2pMsgTxCbFn msgTxCbFn )

Send a message from one Swadge to another. This must not be called before the CON_ESTABLISHED event occurs. Message addressing, ACKing, and retries all happen automatically.

Parameters
p2pThe p2pInfo struct with all the state information
payloadA byte array to be copied to the payload for this message
lenThe length of the byte array
msgTxCbFnA callback function when this message is ACKed or dropped

◆ p2pSendCb()

void p2pSendCb ( p2pInfo * p2p,
const uint8_t * mac_addr,
esp_now_send_status_t status )

This must be called by whatever function is registered to the Swadge mode's fnEspNowSendCb.

This is called after an attempted transmission. If it was successful, and the message should be acked, start a retry timer. If it wasn't successful, just try again

Parameters
p2pThe p2pInfo struct with all the state information
mac_addrunused
statusWhether the transmission succeeded or failed

◆ p2pRecvCb()

void p2pRecvCb ( p2pInfo * p2p,
const uint8_t * mac_addr,
const uint8_t * data,
uint8_t len,
int8_t rssi )

This function must be called whenever an ESP NOW packet is received.

Parameters
p2pThe p2pInfo struct with all the state information
mac_addrThe MAC of the swadge that sent the data
dataThe data
lenThe length of the data
rssiThe rssi of the received data

◆ p2pSetDataInAck()

void p2pSetDataInAck ( p2pInfo * p2p,
const uint8_t * ackData,
uint8_t ackDataLen )

Set data to be automatically used as the payload all future ACKs, until the data is cleared.

This data will not be transmitted until the next ACK is sent, whenever that is. Normally an ACK does not contain any payload data, but an application can have a more efficient continuous request and response flow by embedding the response in the ACK.

Parameters
p2pThe p2pInfo struct with all the state information
ackDataThe data to be included in the next ACK, will be copied here
ackDataLenThe length of the data to be included in the next ACK

◆ p2pClearDataInAck()

void p2pClearDataInAck ( p2pInfo * p2p)

Clear any data which was set to be used as the payload in the next ACK.

Parameters
p2pThe p2pInfo struct with all the state information

◆ p2pGetPlayOrder()

playOrder_t p2pGetPlayOrder ( p2pInfo * p2p)

After the swadge is connected to another, return whether this Swadge is player 1 or player 2. This can be used to determine client/server roles.

Parameters
p2pThe p2pInfo struct with all the state information
Returns
GOING_SECOND, GOING_FIRST, or NOT_SET

◆ p2pSetPlayOrder()

void p2pSetPlayOrder ( p2pInfo * p2p,
playOrder_t order )

Override whether the Swadge is player 1 or player 2. You probably shouldn't do this, but you might want to for single player modes.

Parameters
p2pThe p2pInfo struct with all the state information
orderThe order to set