Trying to understand program!

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:

// Structured package layout 
typedef struct struct_message {
  float temp;
  float hum;
} 
struct_message;
struct_message sensorPayload;

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.

presumably you're referring to the following lines

typedef struct struct_message {
    float temp;
    float hum;
}

struct_message;
struct_message sensorPayload;

this line does nothing

struct_message;

and these lines could have been wrriten this way

struct struct_message {
    float temp;
    float hum;
}  sensorPayload;

or

struct Msg {
    float temp;
    float hum;
}  sensorPayload;

@gcjr

Thank you for the speedy reply! I thought that the name of the structure was struct_message. If so what does sensorPayLoad signify?

it's actually part of the typedef.


no, it's the name of the type.

when you do

struct_message sensorPayload;

you define a variable named sensorPayload of type struct_message

it's like when you do int foo; you define a foo variable of type int.

here you are just using the custom type name you created.

The concepts to grasp here are ‘prototyping’, ‘declaration’, ‘instantiation’, and ‘initialisation’…

They can appear in a number of ways , and are interoperating, and can be combined - depending on the statement structure. (!)

Each needs the others in different ways,

i see now

this is an example of misinterpreting thing when they are written in an unconventional way.

it's also an example of uneccesary coding

@J-M-L

Thank you, that is much clearer now.

yes to both !

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.

@J-M-L

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.

no you don't want to add one (unless your remove the struct_message that was coming next)

your code reads as
typedef struct XXX {...} XXX;
The two XXX used to serve a purpose and are from people used to code in C and old habits die hard...

As @gcjr said, in C++ you would just define the structure name without repeating or using typedef, just
struct XXX {...};

if you want to keep it close to what was written, use

struct struct_message {
    float temp;
    float hum;
};
struct_message sensorPayload;

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

Your YouTube profile has a list of all the videos you have watched in chronological order.

unless you deactivated the history for privacy/targetting reasons

@J-M-L

Thanks for that, I used the following as indicated by @gcjr and it has worked fine, easier to follow as well:

struct Msg {
    float temp;
    float hum;
}  sensorPayload;

@xfpd @J-M-L

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.

You can even have an unnamed struct type

struct {
  float temp;
  float hum;
} sensorPayload, actuallyTwoOfThem;

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.)

I don't know if this helps, but I like watching the DroneBot Workshop videos, and this one came to mind:

Code:

Why change the original code?
Was there a problem with it?

@kenb4

I saw this on a C++ website and thought I'd give it a try and it compiled OK. Thanks for the error info as well.

@InquisitiveMind

I have watched a few DroneBot Workshop videos and found them very helpful. I will have a look at that one, thanks for pointing it out.