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:
After connection, Swadges are free to send messages to each other. An example of unreliable communication with retries and duplication is as follows.
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.
- 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.
- 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.
- 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!
- 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.
- 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 demoMsgRxCb(
p2pInfo* p2p,
const uint8_t* payload, uint8_t len);
.fnEspNowRecvCb = demoEspNowRecvCb,
.fnEspNowSendCb = demoEspNowSendCb,
...
};
p2pInfo_t p2p;
...
p2pInitialize(&p2p, 'd', demoConCb, demoMsgRxCb, -70);
...
const uint8_t testMsg[] = {0x01, 0x02, 0x03, 0x04};
...
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 demoMsgRxCb(
p2pInfo* p2p,
const uint8_t* payload, uint8_t len)
{
}
{
}
@ 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
|
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.
|
|