I have the following program which transmits temperature and humidity every 5 seconds.
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <DHT.h>
// --- Configuration ---
#define DHTPIN 2
#define DHTTYPE DHT22 // DHT11 for the 3 Pin modules
#define WIFI_CHANNEL 1 // Locks channel to prevent connection drops on C3
// PASTE YOUR RECEIVER'S MAC ADDRESS HERE
uint8_t receiverMac[] = {0xe8, 0x3d, 0xc1, 0x8b, 0xad, 0x10};
// --- Variables ---
DHT dht(DHTPIN, DHTTYPE);
unsigned long lastSend = 0;
const unsigned long interval = 5000; // Send data every 5 seconds
// Structured package layout
typedef struct struct_message {
float temp;
float hum;
}
struct_message;
struct_message sensorPayload;
esp_now_peer_info_t peerInfo;
// Delivery confirmation callback
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("Packet Delivery Status: ");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Failed");
}
void setup() {
Serial.begin(115200);
dht.begin();
WiFi.mode(WIFI_STA);
WiFi.setTxPower(WIFI_POWER_8_5dBm); // added to see if it would help
// Lock ESP32-C3 to channel 1 to guarantee alignment with receiver
esp_wifi_set_promiscuous(true);
esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);
esp_wifi_set_promiscuous(false);
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// esp_now_register_send_cb(onDataSent);
// Register receiver node
memcpy(peerInfo.peer_addr, receiverMac, 6);
peerInfo.channel = WIFI_CHANNEL;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to pair with receiver");
return;
}
}
void loop() {
if (millis() - lastSend >= interval) {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("DHT22 Error: Failed read. Check wires.");
lastSend = millis();
return;
}
sensorPayload.temp = t;
sensorPayload.hum = h;
esp_err_t result = esp_now_send(receiverMac, (uint8_t *) &sensorPayload, sizeof(sensorPayload));
if (result == ESP_OK) {
Serial.printf("Transmitted: T=%.1f C, H=%.1f%%\n", sensorPayload.temp, sensorPayload.hum);
} else {
Serial.println("Error launching ESP-NOW broadcast frame");
}
lastSend = millis();
}
}
I got the sketch from a tutorial on YouTube, but can't find it again! I am trying to understand how it works so that I can write further programs. The main bit I am stuck on at the minute is the "struct" bit:
From what I think I understand, I am creating a structure called "struct_message" that contains two floating numbers labelled "temp" and "hum". The "struct_message" on the line after the brackets close, I thought initialised it but the next line down "struct_message sensorPayLoad" is confusing me. I know the sensorPayLoad is mentioned further on but what is that line actually doing. Any help on understanding this program would be much appreciated.
I was confused also at first and then noticed there was no semicolon after the closing brace.
Two things happen, tied to the two occurrences of struct_message in the declaration. The first occurrence, the one right after the struct keyword, is the tag, it defines the structure type itself and gives it the name struct_message in the tag namespace. The second occurrence, the one right before the final semicolon, is the typedef name, it declares struct_message as an alias for that same struct type in the ordinary namespace.
In C, that second occurrence is necessary, it's what lets you write struct_message sensorPayload later without needing to write struct struct_message sensorPayload. In C++, that second occurrence is redundant, because C++ already makes the first occurrence, the tag, usable directly as a type name on its own, so the alias created by the second occurrence just ends up naming the same thing the tag already names.
So do I need the semi-colon after the closing brace? The program seems to work OK without it, but I would rather learn the correct way of doing things.
struct_message is somewhat redundant. Why not simply message?
out coding guidelines at Qualcomm were to also Capitalize user defined variable names, e.g. Message. Those same guidelines also required appended the type of vairable, Message_s indicating it is a struct
such guidelines make it clearer what the symbol means without having to dig possibly thru multiple layers of code to find it's definition to know what you're dealing with
I was not logged in to YouTube so no history, my browser history gets cleared out every few months so no joy there. Not really necessary now as I have a better understanding of how the program works, thanks to those that contributed.
If it's just the one (or few) "I want to put some fields together in a single object" and you never reference the type of that, it doesn't need a name. "Message" is a pretty generic, low-info name anyway; as is putting the word "struct" in the name.
But even if you don't use the name yourself, the compiler might, like with error messages. For example, if you try Serial.print(sensorPayload)
with a name, the error is "no matching function for call to 'HardwareSerial::print(Msg&)'"
without a name, it's "call to 'HardwareSerial::print(<unnamed struct>&)'"
That error should be immediately followed with the actual line, so you can still see what it's talking about. But the "Copy error message" thing that pops up only shows that first part, so....
(Note there is a different C/C++ concept called an anonymous struct that should not be confused with unnamed.)