Skip to content

Week 11 — Networking and Communications


Assignment

Individual: Design, build, and connect wired or wireless node(s) with network or bus addresses and local input and/or output device(s).

Group: Send a message between two projects.


Here is my plan for the week:

Group Assignment

For this weeks group assignment you can click here: Group Assignment

What I Built

This week I built a wireless weather station using two XIAO ESP32-C3 boards — one reads temperature and humidity from a DHT11 sensor and sends the data wirelessly, the other receives it and displays it live on a 0.96" OLED screen. I used two protocols: ESP-NOW for the wireless communication between the boards, and I2C for the OLED display on the receiver side.

All components laid out — two XIAO ESP32-C3 boards, DHT11 sensor, and 0.96" OLED display


Understanding the Protocols

Before jumping in, I want to quickly explain the protocols I used because the assignment specifically asks for network or bus addresses — and I want to be clear about what those are in my project.

UART is the simplest serial protocol — just two wires, TX and RX, no clock. Both devices agree on a baud rate beforehand and that's how they stay in sync. I've been using this every time I open the Serial Monitor in Arduino — that's UART between my ESP32 and my laptop.

I2C uses two wires — SDA (data) and SCL (clock). It's synchronous, meaning one device drives a clock signal and everyone follows it. What makes I2C special is addressing — every device on the bus has a unique 7-bit address. My OLED's I2C address is 0x3C, which is how the ESP32 knows which device to talk to even if multiple things share the same two wires.

SPI is faster than I2C, uses four wires (MOSI, MISO, SCK, CS), and selects devices using a chip select pin. I used this with my TFT display in Week 10.

ESP-NOW is a wireless protocol by Espressif that lets ESP32 boards talk directly to each other without needing a WiFi router. Each ESP32 has a unique MAC address — a 6-byte hardware ID burned into the chip at the factory. The sender stores the receiver's MAC address and sends data packets directly to it. No internet, no router, just board to board. The MAC address is the "network address" this week's assignment is asking for.


The Plan

Both protocol requirements are covered — ESP-NOW handles the wireless network layer between boards, and I2C handles the wired connection between the receiver and the OLED.


Wiring

Sender board — DHT11 to XIAO ESP32-C3

DHT11 Pin XIAO ESP32-C3
VCC 3.3V
GND GND
DATA D2 (GPIO4)

Sender board with DHT11 wired up

Receiver board — OLED to XIAO ESP32-C3

OLED Pin XIAO ESP32-C3
VCC 3.3V
GND GND
SDA D4 (GPIO6)
SCL D5 (GPIO7)

Receiver board with OLED connected via I2C


Getting the Receiver's MAC Address

The first thing I had to do was get the MAC address of the receiver board. ESP-NOW needs it hardcoded in the sender so it knows exactly where to send the data. I uploaded a simple sketch to the receiver and read the address from Serial Monitor.

I generated this code using Claude AI with the following prompt:

"I am doing Fab Academy Week 11 networking and communications. I have two XIAO ESP32-C3 boards. I need a simple sketch to get the MAC address of my receiver board so I can use it for ESP-NOW. Factcheck before you answer."

MAC address finder code in Arduino IDE

Serial Monitor showing the receiver's MAC address: D4:F9:8D:04:04:40

The receiver's MAC address came out as D4:F9:8D:04:04:40. I plugged this into the sender code as:

uint8_t receiverMAC[] = {0xD4, 0xF9, 0x8D, 0x04, 0x04, 0x40};

Testing the Sensor First

Before adding any wireless stuff I tested the DHT11 alone on the sender to make sure it was reading correctly. Good habit — test each part in isolation before combining everything.

DHT11 sensor test on Serial Monitor showing temp and humidity

Sensor was working fine. Room was around 19-20°C which matched what I expected.


Sender Code

The sender reads DHT11 every 3 seconds and packs the temperature and humidity into a struct, then fires it off via ESP-NOW to the receiver's MAC address. I use 3 seconds instead of 2 because the DHT11 has a hardware limitation — it can only take a fresh reading every 2 seconds minimum, and right on that edge it sometimes fails. 3 seconds gives it enough breathing room.

I also ran into a compile error with the ESP-NOW send callback. In ESP32 Arduino core 3.x, the callback signature changed — it now takes wifi_tx_info_t* instead of uint8_t*. So the onSent function had to be updated to match.

I generated this code using Claude AI with the following prompt:

"Write an Arduino sender sketch for XIAO ESP32-C3 using ESP-NOW. It should read temperature and humidity from a DHT11 connected to D2 (GPIO4) and send the data wirelessly to a receiver board with MAC address D4:F9:8D:04:04:40. Factcheck the code before you answer me."

When I tried to compile I got a callback signature error. I debugged it with Claude using the prompt:

"I am getting this error: invalid conversion from void ()(const uint8_t, esp_now_send_status_t) to esp_now_send_cb_t. I am using ESP32 Arduino core 3.x on a XIAO ESP32-C3. Fix it and explain why."

#include <esp_now.h>
#include <WiFi.h>
#include "DHT.h"

#define DHTPIN 4        // D2 on XIAO ESP32-C3
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

// Receiver MAC address
uint8_t receiverMAC[] = {0xD4, 0xF9, 0x8D, 0x04, 0x04, 0x40};

// Data structure to send
typedef struct {
  float temperature;
  float humidity;
} SensorData;

SensorData data;

// Callback - fires after send attempt
// Note: core 3.x changed the signature to wifi_tx_info_t*
void onSent(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) {
  if (status == ESP_NOW_SEND_SUCCESS) {
    Serial.println("Send status: SUCCESS");
  } else {
    Serial.println("Send status: FAILED — retrying...");
    esp_now_send(receiverMAC, (uint8_t *)&data, sizeof(data));
  }
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  delay(2000); // Let DHT11 stabilize on boot

  WiFi.mode(WIFI_STA);

  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed");
    return;
  }

  esp_now_register_send_cb(onSent);

  esp_now_peer_info_t peerInfo = {};
  memcpy(peerInfo.peer_addr, receiverMAC, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }

  Serial.println("Sender ready");
}

void loop() {
  float temp = dht.readTemperature();
  float hum = dht.readHumidity();

  // Validate reading before sending
  if (isnan(temp) || isnan(hum)) {
    Serial.println("DHT11 read failed — skipping");
    delay(3000);
    return;
  }

  data.temperature = temp;
  data.humidity = hum;

  Serial.print("Sending — Temp: ");
  Serial.print(data.temperature);
  Serial.print("°C  Humidity: ");
  Serial.print(data.humidity);
  Serial.println("%");

  esp_now_send(receiverMAC, (uint8_t *)&data, sizeof(data));

  delay(3000); // 3s gives DHT11 enough time for a fresh reading
}

Sender code uploaded to XIAO ESP32-C3 in Arduino IDE

Serial Monitor on sender showing successful transmissions


Receiver Code

The receiver sits and waits. When ESP-NOW data arrives, a callback function fires, unpacks the struct, and immediately updates the OLED via I2C. The display shows "Waiting for data.." on boot and switches to live readings as soon as the first packet arrives.

I generated this code using Claude AI with the following prompt:

"Write an Arduino receiver sketch for XIAO ESP32-C3 using ESP-NOW. When data arrives it should display the temperature and humidity on a 0.96 inch OLED connected via I2C. The OLED I2C address is 0x3C. Factcheck the code before you answer me."

#include <esp_now.h>
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C  // I2C address of the OLED

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Must match sender struct exactly
typedef struct {
  float temperature;
  float humidity;
} SensorData;

SensorData incomingData;

// Callback - fires when data arrives via ESP-NOW
void onReceive(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) {
  memcpy(&incomingData, data, sizeof(incomingData));

  Serial.print("Temp: ");
  Serial.print(incomingData.temperature);
  Serial.print("°C  Humidity: ");
  Serial.print(incomingData.humidity);
  Serial.println("%");

  // Update OLED display via I2C
  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("-- Weather Station --");

  display.setTextSize(2);
  display.setCursor(0, 20);
  display.print("T: ");
  display.print(incomingData.temperature, 1);
  display.println(" C");

  display.setCursor(0, 44);
  display.print("H: ");
  display.print(incomingData.humidity, 1);
  display.println(" %");

  display.display();
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  // Init OLED over I2C
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println("OLED init failed");
    while (true);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 28);
  display.println("  Waiting for data..");
  display.display();

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed");
    return;
  }

  esp_now_register_recv_cb(onReceive);
  Serial.println("Receiver ready");
}

void loop() {}

Receiver code uploaded to XIAO ESP32-C3 in Arduino IDE

Serial Monitor on receiver showing incoming data


Results

Both boards running, data flying wirelessly, OLED updating live every 3 seconds. The OLED talks to the ESP32 over I2C at address 0x3C, and the two boards communicate via ESP-NOW using the receiver's MAC address D4:F9:8D:04:04:40 as the network address.

OLED displaying live temperature and humidity received wirelessly

Hero shot — both boards running, OLED showing Weather Station data


What I Learned

I finally actually understand what a bus address is — it's not just some abstract thing in code, it's literally how devices identify themselves on a shared line. The OLED's 0x3C and the receiver's MAC address D4:F9:8D:04:04:40 are both real examples of that. The ESP32 core 3.x callback issue was annoying but I'm glad I ran into it as I learned some new lessons.