11 - Network and Communications

Group assignment

The group assignment is documented here https://academany.fabcloud.io/fabacademy/2025/labs/barcelona/students/group-assignments/week11.html

We demonstrated:

It was really nice connecting two devices through wifi, something I will be using a lot in the future

Individual Assignment and Learnings

We have our barduino development board

https://fablabbcn-projects.gitlab.io/electronics/barduino-docs/GettingStarted/pinout/#outputs

I’ve designed a board with a Xiao esp32s3, grove connectors, DuPont connectors and screw terminal connections for the higher voltage elements.

Figure 1: KiCAD schematic layout for Koji Fermentation Chambre
Figure 2: PCB design

I ran into some practical errors, spacing for the grove connectors, GND islands that aren’t connected

Figure 3: board issues
Figure 4: no quick fixes

Assignment

I2C connection

The Grove connectors are isolated on my board with a direct connection to the Xiao. There are no pullup resistors used as the Xiao and the Seeed Studio sensors are already designed correctly. I’ll fix the board and code the Xiao and Temperature sensor so it works with the corrected board.

I connected a Xiao to both the temperature sensor and the Barduino 4.0.2 board on the I2C line

I coded the Xiao to be the main, his job is to read the temperature, send the value to the secondary Barduino board. If the temperature is above 25oC The Barduino then responds with a message saying it is very hot.

Main Xiao code:

#include <Wire.h>
#include "Seeed_SHT35.h"

// Define the I2C Slave Address of the other ESP32
const byte slaveAddress = 0x08; // Choose a unique 7-bit I2C address (1-127)

// Define SDA and SCL pins for XIAO ESP32S3
#define SDAPIN D4  // GPIO5
#define SCLPIN D5  // GPIO6

SHT35 sensor(SCLPIN); // .

void setup() {
  Serial.begin(115200); //
  delay(10);
  Serial.println("XIAO ESP32S3 I2C Master/SHT35"); //
  Wire.begin(SDAPIN, SCLPIN); // Initialize Wire with your defined pins

  if (sensor.init()) {
    Serial.println("sensor init failed!!"); // 
  } else {
    Serial.println("SHT35 sensor initialized successfully."); // 
  }
  delay(1000);
}

void loop() {
  delay(2000);

  float temp, hum;
  if (NO_ERROR != sensor.read_meas_data_single_shot(HIGH_REP_WITH_STRCH, &temp, &hum)) {
    Serial.println("Failed to read temperature from SHT35!"); 
  } else {
    Serial.print("SHT35 Temperature: ");
    Serial.println(temp); 

    Serial.print("Sending temperature to slave (0x"); 
    Serial.print(slaveAddress, HEX); 
    Serial.println(")..."); 

    // Add a small delay before sending data
    delay(100); // Adjust this delay if necessary

    Wire.beginTransmission(slaveAddress);
    Wire.write((byte*)&temp, sizeof(temp)); 
    byte error = Wire.endTransmission();

    if (error == 0) {
      Serial.println("Temperature sent successfully."); 

      // Receive the reply from the slave
      Wire.requestFrom(slaveAddress, 64); // Request up to 64 bytes for the reply
      if (Wire.available()) {
        String repy = "";
        while (Wire.available()) {
          char c = Wire.read();
          reply += c;
        }
        Serial.print("Slave replied: "); 
        Serial.println(reply); 

        // Send the final reply back
        String finalReply = "I'm fine thanks, disfruta!";
        Wire.beginTransmission(slaveAddress);
        Wire.write((const uint8_t*)finalReply.c_str(), finalReply.length()); // Cast to const uint8_t*
        Wire.endTransmission();
        Serial.println("Final reply sent.");
      } else {
        Serial.println("No reply received from slave.");
      }
    } else {
      Serial.print("Error sending temperature, error code: ");
      Serial.println(error);
    }
  }
}

Secondary Barduino Code

#include <Wire.h>

// I2C Slave Address - Must match the master's slaveAddress
const byte slaveAddress = 0x08;

// Define I2C pins for the Slave ESP32-S3
const int sdaPin = 8; // GPIO8
const int sclPin = 9; // GPIO9

// Buffer to store received temperature
float receivedTemperature;
byte receivedData[sizeof(float)];

// Buffer for the reply message
char replyMsg[64];

// Flag to indicate if the master is requesting a reply
bool masterIsRequesting = false;

void receiveEvent(int howMany) {
  Serial.println("receiveEvent triggered");
  if (howMany == sizeof(float)) {
    // Read temperature data
    for (int i = 0; i < howMany; i++) {
      receivedData[i] = Wire.read();
    }
    memcpy(&receivedTemperature, receivedData, sizeof(receivedTemperature));
    Serial.print("Temperature received: ");
    Serial.println(receivedTemperature);

    // Prepare the reply based on temperature
    if (receivedTemperature > 25.0) {
      strcpy(replyMsg, "My word it is hot! I could do with an ice plunge");
    } else {
      strcpy(replyMsg, "It's quite pleasant here, actually.");
    }
    masterIsRequesting = true; // Set the flag that we have a reply ready
  } else if (howMany > 0) {
    // Handle the final reply from the master
    String receivedFinalReply = "";
    while (Wire.available()) {
      receivedFinalReply += (char)Wire.read();
    }
    Serial.print("Master's final reply: ");
    Serial.println(receivedFinalReply);
  } else {
    Serial.println("Received unexpected number of bytes.");
  }
}

void requestEvent() {
  Serial.println("requestEvent triggered");
  if (masterIsRequesting) {
    Wire.write((uint8_t*)replyMsg, strlen(replyMsg)); // Send the prepared reply as bytes
    masterIsRequesting = false; // Reset the flag
  } else {
    // Optionally send a default or empty response if no reply is ready
    const char emptyResponse[] = "";
    Wire.write((uint8_t*)emptyResponse, strlen(emptyResponse));
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("I2C Slave/Replier");
  Wire.begin(slaveAddress, sdaPin, sclPin); // Initialize Wire as slave with specified pins
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
}

void loop() {
  delay(100);
}

Unfortunately i’m stuck debugging, the Xiao reads the temperature but there is a problem with the transmission to the Barduino, here is the serial monitor error message.

SHT35 Temperature: 23.81
Sending temperature to slave (0x8)...
E (1156466) i2c.master: I2C transaction unexpected nack detected
E (1156466) i2c.master: s_i2c_synchronous_transaction(924): I2C transaction failed
E (1156468) i2c.master: i2c_master_multi_buffer_transmit(1186): I2C transaction failed
Error sending temperature, error code: 4

I used DSO Nano to check that there was a message being transmitted on the pins. The oscillations suggest that it is, the GND is correctly connected and the 5V from the Xiao is powering the Barduino. I spend 2 days trying to debug this with no avail so I decided to move on to ESP-NOW. The error is typical of the wrong pins being used.

So I decided to use another communication method (ESP-NOW) and a Xiao ESP32c3 instead of the Barduino.

ESP-NOW connection

The first step is to find the MAC address for both the Main and the Secondary using the example ESP_NOW_Broadcast_Master and ESP_NOW_Broadcast_Slave examples in Arduino_IDE.

These addresses are then used to communicate between the Xiao ESP32s3 and the Xiao ESP32c3.

Establish Communication and Send Information

The S3 Main will communicate and send the temperature reading from the thermistor to C3 Secondary’s MAC address.

Reception and Response

The C3 Secondary will receive the temperature and reply “Cool”, “Nice” or “Scorchio” back to the S3 Master according to the temperature:

The Arduino Sketch for the Main is given here .

The code specific to ESP-NOW communication is highlighted below

// ESP32-S3 - MASTER (Thermistor, LCD, ESP-NOW Send/Recv) - Revised Serial Output

#include <esp_now.h>
#include <WiFi.h>
#include <Wire.h>
#include "DFRobot_RGBLCD1602.h"
#include <cmath>
#include <esp_wifi.h> // For esp_now_recv_info_t

// --- ESP-NOW Configuration ---
uint8_t slaveMacAddress[] = {0x34, 0x85, 0x18, 0x03, 0x38, 0xD4}; // YOUR C3 SLAVE MAC

typedef struct struct_temp_message {
  float temperature;
  int id;
} struct_temp_message;

typedef struct struct_reply_message {
  char status_text[20];
  int original_id;
} struct_reply_message;

struct_temp_message tempToSend;
struct_reply_message replyReceived;
esp_now_peer_info_t peerInfo;

volatile bool newReplyArrived = false;
int messageCounter = 0;
float currentTemperatureCelsius = -999.0;
float temperatureAtLastSend = -999.0;

// --- ESP-NOW Callbacks ---
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  // This confirms the ESP-NOW packet was queued or sent at a low level
  // Serial.print("S3_MASTER: Low-level send status to C3: ");
  // Serial.println(status == ESP_NOW_SEND_SUCCESS ? "OK" : "Fail");
}

void OnDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *incomingData, int len) {
  if (len == sizeof(replyReceived)) {
    memcpy(&replyReceived, incomingData, sizeof(replyReceived));
    newReplyArrived = true;
  }
}

void setup() {
  delay(500); 
  Serial.begin(115200);
  unsigned long startWait = millis();
  while (!Serial && (millis() - startWait < 2000)) { delay(10); }

  Serial.println("\n--- S3 MASTER Initializing ---");

  Wire.begin(LCD_I2C_SDA_PIN, LCD_I2C_SCL_PIN);
  lcd.init();
  lcd.setRGB(50, 50, 150); 
  lcd.clear();
  lcd.setCursor(0,0); lcd.print("S3 Master Boot");
  replyReceived.status_text[0] = '\0'; 

  WiFi.mode(WIFI_STA);
  Serial.print("S3_MASTER MAC: "); Serial.println(WiFi.macAddress()); 

  if (esp_now_init() != ESP_OK) {
    Serial.println("S3_MASTER: ESP-NOW Init FAILED");
    lcd.setCursor(0,1); lcd.print("ESP-NOW ERR");
    while(1) delay(100); 
  }
  esp_now_register_send_cb(OnDataSent);
  esp_now_register_recv_cb(OnDataRecv);

  memcpy(peerInfo.peer_addr, slaveMacAddress, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("S3_MASTER: Peer Add FAILED");
    lcd.setCursor(0,1); lcd.print("Peer Add ERR");
    while(1) delay(100); 
  }
  lcd.setCursor(0,1); lcd.print("Ready");
  Serial.println("--- S3 MASTER Ready ---");
  delay(1000);
}

unsigned long lastEspNowSendTime = 0;
const long espNowSendInterval = 10000; // Send ESP-NOW message every 10 seconds

unsigned long lastSensorReadTime = 0;
const long sensorReadInterval = 1000; // Read sensor & update LCD every 1 second

void loop() {
  unsigned long currentTime = millis();

  if (currentTime - lastSensorReadTime >= sensorReadInterval) {
    lastSensorReadTime = currentTime;
    readThermistor();
    updateDisplay();
  }

  if (currentTime - lastEspNowSendTime >= espNowSendInterval) {
    lastEspNowSendTime = currentTime;
    if (currentTemperatureCelsius > -270.0) {
        tempToSend.temperature = currentTemperatureCelsius;
        temperatureAtLastSend = currentTemperatureCelsius; 
        tempToSend.id = messageCounter++;
        
        // Acknowledge sending temperature
        Serial.printf("S3_MASTER: Sending Temp to C3: %.1f%cC (ID: %d)\n",
                      tempToSend.temperature, (char)223, tempToSend.id);
                      
        esp_now_send(slaveMacAddress, (uint8_t *) &tempToSend, sizeof(tempToSend));
    } else {
        Serial.println("S3_MASTER: Invalid temp, not sending.");
    }
  }

  if (newReplyArrived) {
    newReplyArrived = false; 
    
    // Acknowledge reply received and print formatted message
    Serial.printf("S3_MASTER: Reply received from C3 for ID %d.\n", replyReceived.original_id);
    Serial.printf(">>> Ohh %.1f%cC, that's %s!\n\n",
                  temperatureAtLastSend, 
                  (char)223, 
                  replyReceived.status_text);
    updateDisplay(); 
  }
  delay(10); 
}

The code for the secondary is given here.

// ESP32-C3 - SLAVE (Temp Receiver, Reply Sender) - Revised Serial Output

#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h> // For esp_now_recv_info_t

// MAC Address of the MASTER (ESP32-S3)
uint8_t masterMacAddress[] = {0xF0, 0x9E, 0x9E, 0x3B, 0xDF, 0xBC}; // YOUR S3 MASTER MAC

// Data structures (must match master)
typedef struct struct_temp_message {
  float temperature;
  int id;
} struct_temp_message;

typedef struct struct_reply_message {
  char status_text[20];
  int original_id;
} struct_reply_message;

struct_temp_message tempReceived;
struct_reply_message replyToSend;
esp_now_peer_info_t peerInfo;

volatile bool newTempPacketArrived = false;

// Callback when reply is sent
void OnReplySent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  // This confirms the ESP-NOW packet was queued or sent at a low level
  // Serial.print("C3_SLAVE: Low-level reply send status to S3: ");
  // Serial.println(status == ESP_NOW_SEND_SUCCESS ? "OK" : "Fail");
}

// Callback when temperature data is received
void OnTempDataRecv(const esp_now_recv_info_t *recv_info, const uint8_t *incomingData, int len) {
  if (len == sizeof(tempReceived)) {
    memcpy(&tempReceived, incomingData, sizeof(tempReceived));
    // Acknowledge temperature received
    Serial.printf("C3_SLAVE: Received Temp from S3: %.1f%cC (ID: %d)\n",
                  tempReceived.temperature, (char)223, tempReceived.id);
    newTempPacketArrived = true;
  } else {
    Serial.println("C3_SLAVE: Temp packet length mismatch.");
  }
}

void setup() {
  delay(500);
  Serial.begin(115200);
  unsigned long startWait = millis();
  while (!Serial && (millis() - startWait < 2000)) { delay(10); }

  Serial.println("\n--- C3 SLAVE Initializing ---");

  WiFi.mode(WIFI_STA);
  Serial.print("C3_SLAVE MAC: "); Serial.println(WiFi.macAddress());

  if (esp_now_init() != ESP_OK) {
    Serial.println("C3_SLAVE: ESP-NOW Init FAILED");
    while(1) delay(100);
  }
  esp_now_register_send_cb(OnReplySent);
  esp_now_register_recv_cb(OnTempDataRecv);

  memcpy(peerInfo.peer_addr, masterMacAddress, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("C3_SLAVE: Master Peer Add FAILED");
  }
  Serial.println("--- C3 SLAVE Ready ---");
}

void loop() {
  if (newTempPacketArrived) {
    newTempPacketArrived = false; 

    if (tempReceived.temperature <= 21.0) strcpy(replyToSend.status_text, "cool");
    else if (tempReceived.temperature <= 27.0) strcpy(replyToSend.status_text, "nice");
    else strcpy(replyToSend.status_text, "scorchio");
    
    replyToSend.original_id = tempReceived.id;

    // Acknowledge sending reply
    Serial.printf("C3_SLAVE: Sending Reply to S3: '%s' (for ID: %d)\n\n",
                  replyToSend.status_text, replyToSend.original_id);
                  
    esp_now_send(masterMacAddress, (uint8_t *) &replyToSend, sizeof(replyToSend));
  }
  delay(10);
}

All of the conversation I had with Gemini AI can be found here https://aistudio.google.com/app/prompts?state=%7B%22ids%22:%5B%221INJIsE-Knz7bPVaML35G52bRcvmm7aMd%22%5D,%22action%22:%22open%22,%22userId%22:%22100010994613142035023%22,%22resourceKeys%22:%7B%7D%7D&usp=sharing

A video showing the setup working together is given below