Week 11
Networking and communications
Week assignment
- Group assignment:
- send a message between two projects ✔
- Individual assignment:
- design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s) ✔
Starting this week, I need to focus more on the development of my final project. My project consists of two distinct parts that must communicate with each other. The PCB located on the bench must sequentially receive readings from each group of load cells positioned under the seats. The averaged value over a period of time (yet to be defined) will be used to determine the bending moment values acting on the simply supported beam. My idea is that the number of segments matches the number of columns in the LED matrix that will be placed in front of the bench.
These values will be sent to the second PCB, whose MCU will be responsible for controlling the operation of the LEDs in the matrix. The neutral axis, located in the central row of the matrix, will be constantly illuminated in one color, probably blue. The received bending moment values will be remapped to represent the negative and positive moment diagrams. In architecture, it is common to represent negative values above the neutral axis and positive ones below. One reason for this is to help students understand where reinforcements should be placed in a hypothetical structure. In engineering and other countries, it is common to represent them the other way around.
In-depth recommendation for Spanish architecture students.
Bending moment diagrams should always be drawn on the tensioned face of the beam.
Group assignment
Throughout the development of this group assignment, we set out to implement a system that, based on the weight detected by a load cell connected to an ESP32-C3, would control the rotation of a servo motor built by Pablo, through a web server configured by Francisco. During the process, we tackled several technical challenges and progressively refined the implementation until the system worked as intended. Below are the main steps and key decisions we took.
I was responsible for everything related to the load cell, the initial configuration of the OLED display, and the design and generation of the client-side code. In my individual assignment, the client read the data provided by the server and performed different tasks based on that information. In the case of the group assignment, the client now requests the server to perform specific actions—in this case, rotating the servo to a given angle based on information collected on the client side.
It has been an interesting process of relearning concepts I had long forgotten.
Link to week 11 group assignment
Individual assignment
On the night of Wednesday, April 2nd, I was reviewing the documentation of Adrian, Vera, and other Fab Academy graduates. Neil’s lesson opened my eyes to a world of communications that, like any regular user, I only knew superficially. Now, I need to go deeper to understand how I can establish communication between different platforms.
WIFI
The first thing I did was upload this code to find out the MAC address of the ESP32C3 that I made in Week 10. The MAC address of my MCU is B0:81:84:04:82:E0
.
#include "WiFi.h"
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_MODE_STA);
Serial.println(WiFi.macAddress());
}
void loop(){
Serial.println(WiFi.macAddress());
}
On Thursday, Luis explained different methods of communication, and we reviewed Neil’s class to gather more resources and better understand the requirements for this week’s assignments.
We also began planning the machine we intend to build next week: a traveller system designed to take photographs of architectural models that students at the School of Architecture can later study. I also performed the first TPU printing tests after generating the STL file for the belt using OpenSCAD.
My individual assignment consists of creating communication between two PCBs. The first PCB will act as a server, sharing the weight detected by a load cell over a WiFi network named PepeWifi
. The second PCB will function as a client, receiving the weight data and displaying it on an OLED screen connected via I2C. This system is intended for use in my final project. Additionally, I will connect an LED strip to the client PCB, which will illuminate based on a remapped version of the received weight value. In my final project, this setup will be used to visualize the bending moment values across different columns of the LED matrix I am currently developing.
Creating a WiFi Access Point Server
ETSAC (A Coruña School of Architecture Welcome)
This week I need to create a server that acts as a WiFi access point (PepeWifi) and delivers readings from a load cell connected to the HX711 module via an HTTP request. I will use the PCB I fabricated in week 10. I will also reuse the code I developed in previous weeks for automatic taring and scaling of the load cell.
I started preparing this on the evening of Thursday, April 3rd, and completed the integration by mid-morning the following day. The steps I followed were:
- I used the HX711 library to obtain weight readings.
- I configured the ESP32 as an access point using WiFi.softAP().
- I initialized the HTTP server on port 80 with WiFiServer server(80).
- I read the weight using get_units(10) and multiplied it by a scale factor stored in Preferences.
On Thursday, with Luis’s help, I had set up the server response in full HTML format (Load cell weight: x.xx kg)
. However, on Friday, this format caused issues when trying to extract just the numeric value on the client side. To fix this, I simplified the server response to output only the number representing the load cell reading using:
client.println(String(reading, 2));
Even so, an error occurred because I had commented out the blank line between the HTTP headers and the response body. This prevented the client from interpreting the received data correctly. I resolved it by uncommenting client.println();
just before sending the reading, which adds the required separation line after the header, and everything worked correctly.
To verify that the server was functioning properly, I modified the code to allow a device connected to the AP to access the reading through a standard web browser at http://192.168.4.1.
I connected using my phone and confirmed that the value was displayed and updated correctly. With this verification, I could confirm the server was working as expected. Luis suggested it might be useful for the LED placed on the PCB to blink every time a reading is sent to the client. I’ve left this as a pending task for now.
Designing the ESP32-C3 Client
Once the server side was operational by mid-morning, I began developing the client code. The idea was to connect the ESP32-C3 client to the server’s access point and display the received weight both in the serial monitor and on an OLED SSD1306 screen. Since I had previously worked with OLED displays during Output Devices week, I reused that code and integrated it with the client WiFi connection logic. I used the PCB from week 10 as it includes I2C connectivity and an external power input to supply the 50-LED module with 5V. The steps I followed were:
- I used the WiFiClient class to connect to the server.
- I implemented logic to send a basic HTTP GET request to the server.
- I parsed the HTTP response by detecting the blank line separating headers and body. This took some effort to get right.
- I validated the received string to ensure it represented a numeric value (isNumeric()).
These were the main steps, although during testing I had to solve several issues. The most significant were:
- The client was printing incorrect characters (e.g., H, empty strings).
Trying to connect to the server……
Connected to the server
Incoming reading: H kg
Attempting to connect to the server…
This was due to incorrectly handling the HTTP protocol; the H being part of the HTTP header. I resolved it by adjusting the code to skip the header and isolate the payload string.
- The Xiao ESP32-C3 was continuously restarting if it couldn’t connect to the access point. The message displayed corresponds to the ESP32-C3 bootloader startup, indicating an unexpected reset, most likely due to a runtime crash.
load:0x403ce710,len:0x2f58 entry 0x403cc710
The reset was my fault, since the code contained the following instruction:
if (attempts >= maxReconnectAttempts) {
Serial.println("Failed to connect to the access point (AP). Restarting...");
ESP.restart();
}
I resolved this by replacing ESP.restart()
with while(true);
to halt execution and display an error message instead of rebooting the MCU.
- Some readings were empty or non-numeric. I added a validation function to ensure the received string was numeric before processing it.
String response = "";
bool headersEnded = false;
unsigned long startTime = millis();
while (client.connected() && millis() - startTime < 2000) {
while (client.available()) {
String line = client.readStringUntil('\n');
line.trim();
if (!headersEnded) {
if (line.length() == 0) {
headersEnded = true;
}
} else {
response = line;
break;
}
}
}
And this aditional code prevents access to the string if it is empty:
if (response.length() > 0 && (response.toFloat() != 0.0 || response == "0.00")) {
Serial.println("Reading received: " + response + " kg");
} else {
Serial.println("Invalid reading received: '" + response + "'");
}
At this stage, the Arduino IDE’s serial monitor correctly displayed the weight value transferred from the server to the client.
Integrating an OLED Display
As mentioned above, I wanted to display the weight value on an OLED SSD1306 screen. I had previously experimented with different text sizes, and this time I aimed to display three static header lines:
- Fabacademy 2025
- Pepe Vázquez
- Weight: [ x.xx kg] by adding in this space a dynamic capture of the last reading of the weighing carried out.
To achieve this:
- I initialized the OLED screen using Adafruit_SSD1306 with I2C address 0x3C.
- I displayed the static header lines in small text size.
- I reserved the lower section of the display to dynamically update the weight value in a larger font. I used fillRect() to clear only that portion on each update to avoid flickering.
After several iterations, I achieved a stable and reliable system. Currently, the ESP32-C3 server transmits real-time load cell readings via HTTP. On the other end, the ESP32-C3 client connects to the server and correctly interprets the response, validating the readings.
The weight is clearly displayed on both the serial monitor and the SSD1306 OLED display, and, as shown in the video, responds accurately to the weight of an object placed on the load cells. It’s worth noting that I used platform-type load cells, as these are the ones I’ll place under each seat in my final project.
The system appears to handle connection errors, discards invalid values, and no longer restarts automatically. The next step will be to get the 50-LED module working. It is now 17:20 on April 4th, I’m getting started on that next.
Integrating 50LEDs module
In previous weeks, I had been working with Adafruit libraries to control the WS2811 50-LED module. Now, as I focus on my final project, I want to interpret the weight reading by illuminating a specific number of LEDs. I have defined the following functionality: considering the module has 50 LEDs, the maximum weight value will be remapped between 0 and 10 kg. The LEDs will light up in gray from the first LED up to the last three LEDs that correspond to the mapped value. These final three LEDs will be lit in green. The remaining LEDs, from the end of the mapped range to the last LED in the strip, will remain off.
Of course, I also want to maintain the visualization of the weight reading in the Serial Monitor and on the OLED display. For this, I proceeded as follows:
- Verified that the Adafruit NeoPixel library was installed.
- Defined D10 as the data pin controlling the LED module.
- Remapped the weight from a range of 0 to 10 kg to a value between 0 and 50 LEDs.
- The first
n-3
LEDs are lit in medium gray. - The last three active LEDs are lit in green; the rest remain off.
I implemented the following code in the client:
#include <Adafruit_NeoPixel.h>
#define LED_PIN 10 // D10
#define LED_COUNT 50
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Later, in the setup()
function, I initialized the LEDs:
strip.begin();
strip.show(); // Turns off all LEDs
Inside the loop()
function, after the if block where the received weight is printed to the Serial Monitor, I added the following code to remap the weight reading and drive the 50 LEDs:
float weight = response.toFloat();
int activeLEDs = map(weight * 10, 0, 100, 0, LED_COUNT); // Weight from 0 to 10 kg → 0 to 50 LEDs
if (activeLEDs > LED_COUNT) activeLEDs = LED_COUNT;
if (activeLEDs < 0) activeLEDs = 0;
for (int i = 0; i < LED_COUNT; i++) {
if (i < activeLEDs - 3) {
strip.setPixelColor(i, strip.Color(100, 100, 100)); // medium gray
} else if (i < activeLEDs) {
strip.setPixelColor(i, strip.Color(150, 0, 0)); // medium red
} else {
strip.setPixelColor(i, 0); // off
}
}
strip.show();
The LED module implementation worked on the first try. I conducted a small test at home by sequentially loading the load cell up to the maximum value of 10 kg I had set for the remapping. I recorded a video of the process, but I believe it is better explained by showing three representative stages. This also avoids adding unnecessary storage to my repository.
No load: all LEDs off.
Intermediate load. three green LEDs representing the remapped value. The previous ones in gray, and the following ones off.
Maximum load: all LEDs on. The last three are green.
Weekend Advances
Server Side
On Saturday afternoon I was at the Fab Lab working on the parametric model for fabricating the LED matrix. My plan is to use the laser cutting machine, and I will document the work done in the final project development section. I designed a Grasshopper strategy that allows me to automatically generate the cutting paths. On Sunday morning, I carried out the laser cutting at the Fab using a 5.4 mm thick MDF board. After determining the kerf for the material, I adopted a value of 4.9 mm in Grasshopper to calculate the joints between the parts.
On Sunday afternoon, I focused on developing the Arduino IDE code, starting with the server side and then moving to the client side. I used ChatGPT to refine the code, providing detailed explanations of the process I wanted to simulate and iterating through several versions.
Initially, I worked on extending the functionality of the WiFi server, which originally read the values from a single load cell and sent them to the client. The goal was to adapt the system to read two independent load cells and transmit both values over the network. This is a preliminary step towards scaling the system to a distributed setup with nine load cells, which is the target for my final project.
The original code was designed for a single load cell connected via an HX711 module, with DOUT on pin D4 and CLK on D3. I modified it to add a second cell, also with an HX711 module, using DOUT on D0 while sharing the same CLK line on D3.
In previous weeks, I had already worked on the challenges of using a shared CLK line across multiple load cells. To extend the code, I connected an additional load cell and adjusted the program so that both cells initialize independently, perform their own taring, and are read within the main loop. To keep the data synchronized, I structured the response as a JSON object sent over WiFi, including both readings under the keys "c1"
and "c2"
. If a load cell does not respond or is not ready, its value defaults to zero, ensuring a stable and predictable output for the client to handle. I also added the ability to independently calibrate each cell via the serial monitor by entering S1
, S2
, …, Sn
.
The next step will be to generalize the code structure to support a dynamic number of load cells, which I’ve deferred for now.
Client Side
After verifying the server was working correctly, I focused on making several adjustments to the OLED display on the WiFi client. After a few unsatisfactory attempts using a scrolling interface, I decided to return to a previously used format from other Fab Academy weeks, which I find clearer and more functional: the word "Weight"
is displayed in large font at the top of the screen, and just below it, the measured value is shown in brackets followed by the unit "kg"
. This design makes the value easily readable even from a distance.
The main improvement was implementing an automatic alternation system between the two load cells. I programmed the microcontroller to periodically switch the displayed value from cell 1 to cell 2, repeating the cycle. This removes the need for buttons or external interfaces to view both readings. Additionally, I added a display of the total weight, which is the sum of the two load cells.
If either load cell is not sending valid data (e.g., during system startup or due to a temporary disconnection), I configured the code to display a default value of 0.00 kg
. This preserves visual consistency and avoids display errors. Furthermore, any value below 0.05 kg is shown as 0.00 kg
.
The system is now ready for future expansion with more load cells, requiring only minor adjustments to the alternation and reading logic.
In parallel with the display, I refined the LED matrix visualization system. Since the frame is a 3x3 matrix, I assigned each column a specific purpose:
- First column: readings from load cell 1
- Second column: readings from load cell 2
- Third column: total weight (sum of both)
I remapped the weight ranges to the three matrix positions according to the following logic:
- Between 0.001 and 0.50 kg → cell 1
- Between 0.51 and 1.00 kg → cell 2
- Between 1.01 and 1.50 kg → cell 3
This scheme is applied across all columns, and the activation logic is as follows:
- If load cell 1 reaches a new maximum level, that level is lit in green.
- If load cell 2 reaches a new maximum level, it is shown in red.
- If the sum of both cells reaches a new maximum, the corresponding level lights up in blue.
All levels below the current maximum for each category are displayed in a very soft gray, serving as a visual reference. This allows for quick identification of surpassed levels without drawing attention away from the active maximum. I plan to use a similar system to represent bending moment diagrams in my final project.
Steps | Load1 kg | Load2 kg | Total kg | Left scale1 | Center scale2 | Right scale 1+2 |
---|---|---|---|---|---|---|
1 | 0,48 | 0,00 | 0.48 | green | – | blue |
2 | 0,48 | 0.41 | 0.89 | green | red | blue |
3 | 0,48 | 0.95 | 1.43 | green | gray + red | 2xgray + blue |
Data transmission between the server and client sides is now working completely reliably.
Final Thoughts
At the beginning of the week, I was quite concerned about my lack of knowledge regarding communication between microprocessors. Although many years ago I had set up the web server for my department on an old Windows NT server at the University of A Coruña, this week has helped me to revisit key concepts and strategies for connecting the server side and the client side.
I particularly enjoyed comparing the tasks developed in my individual assignment with those in the group assignment, where we switched the functionalities between the server and the client. I believe this week also marked significant progress in the development of my final project, as I was able to build a small frame prototype to position the LEDs, and I now have the Grasshopper strategy ready to generate the cutting paths for all of its components.