Tasmota/lib/lib_div/QuickESPNow/src/QuickEspNow_esp32.cpp
2025-02-14 12:55:58 +01:00

532 lines
19 KiB
C++

#include "QuickEspNow.h"
#ifdef ESP32
QuickEspNow quickEspNow;
constexpr auto PEERLIST_TAG = "PEERLIST";
bool QuickEspNow::begin (uint8_t channel, uint32_t wifi_interface, bool synchronousSend) {
wifi_second_chan_t ch2 = WIFI_SECOND_CHAN_NONE;
this->synchronousSend = synchronousSend;
DEBUG_DBG (QESPNOW_TAG, "Channel: %d, Interface: %d", channel, wifi_interface);
// Set the wifi interface
switch (wifi_interface) {
case WIFI_IF_STA:
wifi_if = WIFI_IF_STA;
break;
case WIFI_IF_AP:
wifi_if = WIFI_IF_AP;
break;
default:
DEBUG_ERROR (QESPNOW_TAG, "Unknown wifi interface");
return false;
break;
}
// check channel
if (channel != CURRENT_WIFI_CHANNEL && (channel < MIN_WIFI_CHANNEL || channel > MAX_WIFI_CHANNEL)) {
DEBUG_ERROR (QESPNOW_TAG, "Invalid wifi channel %d", channel);
return false;
}
// use current channel
if (channel == CURRENT_WIFI_CHANNEL) {
uint8_t ch;
esp_wifi_get_channel (&ch, &ch2);
channel = ch;
DEBUG_DBG (QESPNOW_TAG, "Current channel: %d : %d", channel, ch2);
followWiFiChannel = true;
}
setChannel (channel, ch2);
DEBUG_INFO (QESPNOW_TAG, ARDUHAL_LOG_COLOR (ARDUHAL_LOG_COLOR_RED) "Starting ESP-NOW in in channel %u interface %s", channel, wifi_if == WIFI_IF_STA ? "STA" : "AP");
this->channel = channel;
return initComms ();
}
void QuickEspNow::stop () {
DEBUG_INFO (QESPNOW_TAG, "-------------> ESP-NOW STOP");
if (espnowTxTask) {
vTaskDelete (espnowTxTask);
espnowTxTask = nullptr;
}
if (espnowRxTask) {
vTaskDelete (espnowRxTask);
espnowRxTask = nullptr;
}
esp_now_unregister_recv_cb ();
esp_now_unregister_send_cb ();
esp_now_deinit ();
followWiFiChannel = false;
}
bool QuickEspNow::readyToSendData () {
return uxQueueMessagesWaiting (tx_queue) < queueSize;
}
bool QuickEspNow::setChannel (uint8_t channel, wifi_second_chan_t ch2) {
if (followWiFiChannel) {
DEBUG_WARN(QESPNOW_TAG, "Cannot set channel while following WiFi channel");
return false;
}
esp_err_t err_ok;
if ((err_ok = esp_wifi_set_promiscuous (true))) {
DEBUG_ERROR (QESPNOW_TAG, "Error setting promiscuous mode: %s", esp_err_to_name (err_ok));
return false;
}
if ((err_ok = esp_wifi_set_channel (channel, ch2))) { // This is needed even in STA mode. If not done and using IDF > 4.0, the ESP-NOW will not work.
DEBUG_DBG (QESPNOW_TAG, "Error setting wifi channel: %d - %s", err_ok, esp_err_to_name (err_ok));
return false;
}
if ((err_ok = esp_wifi_set_promiscuous (false))) {
DEBUG_ERROR (QESPNOW_TAG, "Error setting promiscuous mode off: %s", esp_err_to_name (err_ok));
return false;
}
this->channel = channel;
return true;
}
comms_send_error_t QuickEspNow::send (const uint8_t* dstAddress, const uint8_t* payload, size_t payload_len) {
comms_tx_queue_item_t message;
if (!dstAddress || !payload || !payload_len) {
DEBUG_WARN (QESPNOW_TAG, "Parameters error");
return COMMS_SEND_PAYLOAD_LENGTH_ERROR;
}
if (payload_len > ESP_NOW_MAX_DATA_LEN) {
DEBUG_WARN (QESPNOW_TAG, "Length error. %d", payload_len);
return COMMS_SEND_PAYLOAD_LENGTH_ERROR;
}
if (uxQueueMessagesWaiting (tx_queue) >= queueSize) {
// comms_tx_queue_item_t tempBuffer;
// xQueueReceive (tx_queue, &tempBuffer, 0);
#ifdef MEAS_TPUT
//txDataDropped += tempBuffer.payload_len;
#endif // MEAS_TPUT
//DEBUG_DBG (QESPNOW_TAG, "Message dropped");
return COMMS_SEND_QUEUE_FULL_ERROR;
}
memcpy (message.dstAddress, dstAddress, ESP_NOW_ETH_ALEN);
message.payload_len = payload_len;
memcpy (message.payload, payload, payload_len);
if (xQueueSend (tx_queue, &message, pdMS_TO_TICKS (10))) {
#ifdef MEAS_TPUT
txDataSent += message.payload_len;
#endif // MEAS_TPUT
DEBUG_DBG (QESPNOW_TAG, "--------- %d Comms messages queued. Len: %d", uxQueueMessagesWaiting (tx_queue), payload_len);
DEBUG_VERBOSE (QESPNOW_TAG, "--------- Ready to send is %s", readyToSend ? "true" : "false");
DEBUG_VERBOSE (QESPNOW_TAG, "--------- SyncronousSend is %s", synchronousSend ? "true" : "false");
if (synchronousSend) {
waitingForConfirmation = true;
DEBUG_INFO (QESPNOW_TAG, "--------- Waiting for send confirmation");
while (waitingForConfirmation) {
taskYIELD ();
}
DEBUG_INFO (QESPNOW_TAG, "--------- Confirmation is %s", sentStatus == ESP_NOW_SEND_SUCCESS ? "true" : "false");
return (sentStatus == ESP_NOW_SEND_SUCCESS) ? COMMS_SEND_OK : COMMS_SEND_CONFIRM_ERROR;
}
return COMMS_SEND_OK;
} else {
DEBUG_WARN (QESPNOW_TAG, "Error queuing Comms message to " MACSTR, MAC2STR (dstAddress));
return COMMS_SEND_MSG_ENQUEUE_ERROR;
}
}
void QuickEspNow::onDataRcvd (comms_hal_rcvd_data dataRcvd) {
this->dataRcvd = dataRcvd;
}
#ifdef MEAS_TPUT
void QuickEspNow::calculateDataTP () {
time_t measTime = (millis () - lastDataTPMeas);
lastDataTPMeas = millis ();
if (txDataSent > 0) {
txDataTP = txDataSent * 1000 / measTime;
//DEBUG_WARN("Meas time: %d, Data sent: %d, Data TP: %f", measTime, txDataSent, txDataTP);
txDroppedDataRatio = (float)txDataDropped / (float)txDataSent;
//DEBUG_WARN("Data dropped: %d, Drop ratio: %f", txDataDropped, txDroppedDataRatio);
txDataSent = 0;
} else {
txDataTP = 0;
txDroppedDataRatio = 0;
}
if (rxDataReceived > 0) {
rxDataTP = rxDataReceived * 1000 / measTime;
//DEBUG_WARN("Meas time: %d, Data received: %d, Data TP: %f", measTime, rxDataReceived, rxDataTP);
rxDataReceived = 0;
} else {
rxDataTP = 0;
}
txDataDropped = 0;
}
void QuickEspNow::tp_timer_cb (void* param) {
quickEspNow.calculateDataTP ();
DEBUG_WARN (QESPNOW_TAG, "TxData TP: %.3f kbps, Drop Ratio: %.2f %%, RxDataTP: %.3f kbps",
quickEspNow.txDataTP * 8 / 1000,
quickEspNow.txDroppedDataRatio * 100,
quickEspNow.rxDataTP * 8 / 1000);
}
#endif // MEAS_TPUT
void QuickEspNow::onDataSent (comms_hal_sent_data sentResult) {
this->sentResult = sentResult;
}
int32_t QuickEspNow::sendEspNowMessage (comms_tx_queue_item_t* message) {
int32_t error;
if (!message) {
DEBUG_WARN (QESPNOW_TAG, "Message is null");
return -1;
}
if (!(message->payload_len) || (message->payload_len > ESP_NOW_MAX_DATA_LEN)) {
DEBUG_WARN (QESPNOW_TAG, "Message length error");
return -1;
}
DEBUG_VERBOSE (QESPNOW_TAG, "ESP-NOW message to " MACSTR, MAC2STR (message->dstAddress));
addPeer (message->dstAddress);
DEBUG_DBG (QESPNOW_TAG, "Peer added " MACSTR, MAC2STR (message->dstAddress));
readyToSend = false;
DEBUG_VERBOSE (QESPNOW_TAG, "-------------- Ready to send: false");
error = esp_now_send (message->dstAddress, message->payload, message->payload_len);
DEBUG_DBG (QESPNOW_TAG, "esp now send result = %s", esp_err_to_name (error));
if (error != ESP_OK) {
DEBUG_WARN (QESPNOW_TAG, "Error sending message: %s", esp_err_to_name (error));
}
// if (error == ESP_OK) {
// txDataSent += message->payload_len;
// }
if (error == ESP_ERR_ESPNOW_NO_MEM) {
delay (2);
}
return error;
}
void QuickEspNow::espnowTxHandle () {
if (readyToSend) {
//DEBUG_WARN ("Process queue: Elements: %d", tx_queue.size ());
comms_tx_queue_item_t message;
while (xQueueReceive (tx_queue, &message, pdMS_TO_TICKS (1000))) {
DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", uxQueueMessagesWaiting (tx_queue));
while (!readyToSend && !synchronousSend) {
delay (0);
}
if (!sendEspNowMessage (&message)) {
DEBUG_DBG (QESPNOW_TAG, "Message to " MACSTR " sent. Len: %u", MAC2STR (message.dstAddress), message.payload_len);
} else {
DEBUG_WARN (QESPNOW_TAG, "Error sending message to " MACSTR ". Len: %u", MAC2STR (message.dstAddress), message.payload_len);
}
//message.payload_len = 0;
DEBUG_DBG (QESPNOW_TAG, "Comms message pop. Queue size %d", uxQueueMessagesWaiting (tx_queue));
}
} else {
DEBUG_DBG (QESPNOW_TAG, "Not ready to send");
}
}
void QuickEspNow::enableTransmit (bool enable) {
DEBUG_DBG (QESPNOW_TAG, "Send esp-now task %s", enable ? "enabled" : "disabled");
if (enable) {
if (espnowTxTask_cb) {
vTaskResume (espnowTxTask);
vTaskResume (espnowRxTask);
}
} else {
if (espnowTxTask_cb) {
vTaskSuspend (espnowTxTask);
vTaskSuspend (espnowRxTask);
}
}
}
bool QuickEspNow::addPeer (const uint8_t* peer_addr) {
esp_now_peer_info_t peer;
esp_err_t error = ESP_OK;
if (peer_list.get_peer_number () >= ESP_NOW_MAX_TOTAL_PEER_NUM) {
DEBUG_VERBOSE (QESPNOW_TAG, "Peer list full. Deleting older");
if (uint8_t* deleted_mac = peer_list.delete_peer ()) {
esp_now_del_peer (deleted_mac);
} else {
DEBUG_ERROR (QESPNOW_TAG, "Error deleting peer");
return false;
}
}
if (peer_list.peer_exists (peer_addr)) {
DEBUG_VERBOSE (QESPNOW_TAG, "Peer already exists");
ESP_ERROR_CHECK (esp_now_get_peer (peer_addr, &peer));
uint8_t currentChannel = peer.channel;
DEBUG_DBG (QESPNOW_TAG, "Peer " MACSTR " is using channel %d", MAC2STR (peer_addr), currentChannel);
if (currentChannel != this->channel) {
DEBUG_DBG (QESPNOW_TAG, "Peer channel has to change from %d to %d", currentChannel, this->channel);
ESP_ERROR_CHECK_WITHOUT_ABORT (esp_now_get_peer (peer_addr, &peer));
peer.channel = this->channel;
ESP_ERROR_CHECK_WITHOUT_ABORT (esp_now_mod_peer (&peer));
DEBUG_ERROR (QESPNOW_TAG, "Peer channel changed to %d", this->channel);
}
return true;
}
memcpy (peer.peer_addr, peer_addr, ESP_NOW_ETH_ALEN);
uint8_t ch;
wifi_second_chan_t secondCh;
esp_wifi_get_channel (&ch, &secondCh);
peer.channel = ch;
peer.ifidx = wifi_if;
peer.encrypt = false;
error = esp_now_add_peer (&peer);
if (!error) {
DEBUG_DBG (QESPNOW_TAG, "Peer added");
peer_list.add_peer (peer_addr);
} else {
DEBUG_ERROR (QESPNOW_TAG, "Error adding peer: %s", esp_err_to_name (error));
return false;
}
DEBUG_DBG (QESPNOW_TAG, "Peer " MACSTR " added on channel %u. Result 0x%X %s", MAC2STR (peer_addr), ch, error, esp_err_to_name (error));
return error == ESP_OK;
}
bool QuickEspNow::initComms () {
if (esp_now_init ()) {
DEBUG_ERROR (QESPNOW_TAG, "Failed to init ESP-NOW");
// ESP.restart ();
// delay (1);
return false;
}
esp_now_register_recv_cb (rx_cb);
esp_now_register_send_cb (reinterpret_cast<esp_now_send_cb_t>(tx_cb));
int txQueueSize = queueSize;
if (synchronousSend) {
txQueueSize = 1;
}
tx_queue = xQueueCreate (txQueueSize, sizeof (comms_tx_queue_item_t));
xTaskCreateUniversal (espnowTxTask_cb, "espnow_loop", 8 * 1024, NULL, 1, &espnowTxTask, CONFIG_ARDUINO_RUNNING_CORE);
rx_queue = xQueueCreate (queueSize, sizeof (comms_rx_queue_item_t));
xTaskCreateUniversal (espnowRxTask_cb, "receive_handle", 4 * 1024, NULL, 1, &espnowRxTask, CONFIG_ARDUINO_RUNNING_CORE);
#ifdef MEAS_TPUT
dataTPTimer = xTimerCreate ("espnow_tp_timer", pdMS_TO_TICKS (MEAS_TP_EVERY_MS), pdTRUE, NULL, tp_timer_cb);
xTimerStart (dataTPTimer, 0);
#endif // MEAS_TPUT
return true;
}
void QuickEspNow::espnowTxTask_cb (void* param) {
for (;;) {
quickEspNow.espnowTxHandle ();
}
}
void QuickEspNow::espnowRxHandle () {
comms_rx_queue_item_t rxMessage;
if (xQueueReceive (rx_queue, &rxMessage, portMAX_DELAY)) {
DEBUG_DBG (QESPNOW_TAG, "Comms message got from queue. %d left", uxQueueMessagesWaiting (rx_queue));
DEBUG_VERBOSE (QESPNOW_TAG, "Received message from " MACSTR " Len: %u", MAC2STR (rxMessage.srcAddress), rxMessage.payload_len);
DEBUG_VERBOSE (QESPNOW_TAG, "Message: %.*s", rxMessage.payload_len, rxMessage.payload);
if (quickEspNow.dataRcvd) {
bool broadcast = !memcmp (rxMessage.dstAddress, ESPNOW_BROADCAST_ADDRESS, ESP_NOW_ETH_ALEN);
quickEspNow.dataRcvd (rxMessage.srcAddress, rxMessage.payload, rxMessage.payload_len, rxMessage.rssi, broadcast); // rssi should be in dBm but it has added almost 100 dB. Do not know why
}
} else {
DEBUG_DBG (QESPNOW_TAG, "No message in queue");
}
}
void QuickEspNow::espnowRxTask_cb (void* param) {
for (;;) {
quickEspNow.espnowRxHandle ();
}
}
void QuickEspNow::rx_cb(const esp_now_recv_info_t* esp_now_info, const uint8_t* data, int len) {
espnow_frame_format_t* espnow_data = (espnow_frame_format_t*)(data - sizeof(espnow_frame_format_t));
wifi_promiscuous_pkt_t* promiscuous_pkt = (wifi_promiscuous_pkt_t*)(data - sizeof(wifi_pkt_rx_ctrl_t) - sizeof(espnow_frame_format_t));
wifi_pkt_rx_ctrl_t* rx_ctrl = &promiscuous_pkt->rx_ctrl;
const uint8_t* mac_addr = esp_now_info->src_addr;
comms_rx_queue_item_t message;
DEBUG_DBG(QESPNOW_TAG, "Received message with RSSI %d from " MACSTR " Len: %u", rx_ctrl->rssi, MAC2STR(esp_now_info->src_addr), len);
memcpy(message.srcAddress, mac_addr, ESP_NOW_ETH_ALEN);
memcpy(message.payload, data, len);
message.payload_len = len;
message.rssi = rx_ctrl->rssi;
memcpy(message.dstAddress, espnow_data->destination_address, ESP_NOW_ETH_ALEN);
if (uxQueueMessagesWaiting(quickEspNow.rx_queue) >= quickEspNow.queueSize) {
comms_rx_queue_item_t tempBuffer;
xQueueReceive(quickEspNow.rx_queue, &tempBuffer, 0);
DEBUG_DBG(QESPNOW_TAG, "Rx Message dropped");
}
#ifdef MEAS_TPUT
quickEspNow.rxDataReceived += len;
#endif // MEAS_TPUT
if (!xQueueSend(quickEspNow.rx_queue, &message, pdMS_TO_TICKS(100))) {
DEBUG_WARN(QESPNOW_TAG, "Error sending message to queue");
}
}
void QuickEspNow::tx_cb (uint8_t* mac_addr, uint8_t status) {
quickEspNow.readyToSend = true;
quickEspNow.sentStatus = status;
quickEspNow.waitingForConfirmation = false;
DEBUG_DBG (QESPNOW_TAG, "-------------- Ready to send: true. Status: %d", status);
if (quickEspNow.sentResult) {
quickEspNow.sentResult (mac_addr, status);
}
}
uint8_t PeerListClass::get_peer_number () {
return peer_list.peer_number;
}
bool PeerListClass::peer_exists (const uint8_t* mac) {
for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) {
if (memcmp (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN) == 0) {
if (peer_list.peer[i].active) {
peer_list.peer[i].last_msg = millis ();
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " found. Updated last_msg", MAC2STR (mac));
return true;
}
}
}
return false;
}
peer_t* PeerListClass::get_peer (const uint8_t* mac) {
for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) {
if (memcmp (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN) == 0) {
if (peer_list.peer[i].active) {
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " found", MAC2STR (mac));
return &(peer_list.peer[i]);
}
}
}
return NULL;
}
bool PeerListClass::update_peer_use (const uint8_t* mac) {
peer_t* peer = get_peer (mac);
if (peer) {
peer->last_msg = millis ();
return true;
}
return false;
}
bool PeerListClass::add_peer (const uint8_t* mac) {
if (int i = peer_exists (mac)) {
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " already exists", MAC2STR (mac));
return false;
}
if (peer_list.peer_number >= ESP_NOW_MAX_TOTAL_PEER_NUM) {
//DEBUG_VERBOSE (PEERLIST_TAG, "Peer list full. Deleting older");
#ifndef UNIT_TEST
DEBUG_ERROR (PEERLIST_TAG, "Should never happen");
#endif
return false;
// delete_peer (); // Delete should happen in higher level
}
for (uint8_t i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) {
if (!peer_list.peer[i].active) {
memcpy (peer_list.peer[i].mac, mac, ESP_NOW_ETH_ALEN);
peer_list.peer[i].active = true;
peer_list.peer[i].last_msg = millis ();
peer_list.peer_number++;
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " added. Total peers = %d", MAC2STR (mac), peer_list.peer_number);
return true;
}
}
return false;
}
bool PeerListClass::delete_peer (const uint8_t* mac) {
peer_t* peer = get_peer (mac);
if (peer) {
peer->active = false;
peer_list.peer_number--;
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " deleted. Total peers = %d", MAC2STR (mac), peer_list.peer_number);
return true;
}
return false;
}
// Delete peer with older message
uint8_t* PeerListClass::delete_peer () {
uint32_t oldest_msg = 0;
int oldest_index = -1;
uint8_t* mac = NULL;
for (int i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) {
if (peer_list.peer[i].active) {
if (peer_list.peer[i].last_msg < oldest_msg || oldest_msg == 0) {
oldest_msg = peer_list.peer[i].last_msg;
oldest_index = i;
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " is %d ms old. Deleting", MAC2STR (peer_list.peer[i].mac), oldest_msg);
}
}
}
if (oldest_index != -1) {
peer_list.peer[oldest_index].active = false;
peer_list.peer_number--;
mac = peer_list.peer[oldest_index].mac;
DEBUG_VERBOSE (PEERLIST_TAG, "Peer " MACSTR " deleted. Last message %d ms ago. Total peers = %d", MAC2STR (mac), millis () - peer_list.peer[oldest_index].last_msg, peer_list.peer_number);
}
return mac;
}
bool QuickEspNow::setWiFiBandwidth (wifi_interface_t iface, wifi_bandwidth_t bw) {
esp_err_t err_ok;
if ((err_ok = esp_wifi_set_bandwidth (iface, bw))) {
DEBUG_ERROR (QESPNOW_TAG, "Error setting wifi bandwidth: %s", esp_err_to_name (err_ok));
}
return !err_ok;
}
#ifdef UNIT_TEST
void PeerListClass::dump_peer_list () {
Serial.printf ("Number of peers %d\n", peer_list.peer_number);
for (int i = 0; i < ESP_NOW_MAX_TOTAL_PEER_NUM; i++) {
if (peer_list.peer[i].active) {
Serial.printf ("Peer " MACSTR " is %d ms old\n", MAC2STR (peer_list.peer[i].mac), millis () - peer_list.peer[i].last_msg);
}
}
}
#endif // UNIT_TEST
#endif // ESP32