Assignment items

Group assignment

Individual assignment

Fab Academy Rubric — Have you?
The criteria evaluators look for this week (Networking and Communications).





The process

Group assignment


As a remote student joining from Kuwait, I was online with the VujaDé Lab session in Saudi Arabia when the team ran this week's group experiment. The session is documented in detail by my colleague Sarah AlDosary. Because I was remote I couldn't pair with another in-person student, so my own networking experimentation in the individual assignment below uses two of my own ESP32-C3 XIAO Connection Nodes communicating over ESP-NOW — the same Node-to-Node pattern that will run between the left and right clips in my final project.


Individual assignment:


01: Connection protocols available on the XIAO ESP32-C3


Understanding the four communication protocols available on the XIAO ESP32-C3 Microcontroller was the starting point for choosing how the two Connection Nodes in my final project would talk to each other and to the phone app. Each protocol has a different sweet spot for speed, range, power consumption, and whether it needs an external router. I used to gather the summaries below.

Prompt to Gemini: I want to start documenting Week 11 — FabLab Networking and Communication. First, let's have a short brief about the following communications: wired USB-C, wireless Wi-Fi, wireless Bluetooth Low Energy, ESP-NOW — I'll be using the XIAO ESP32-C3. (Verbatim prompt — kept as sent.)

01 | USB-C (Serial) — a wired connection. Very high speed, medium power draw on the bus, range is the length of the cable (short). It uses physical TX / RX lines internal to the USB chip to transmit and receive. The ESP32-C3 has a built-in USB Serial / JTAG controller, so plugging in a Type-C cable establishes a direct, high-speed, reliable wired serial link. Best for: debugging via the Serial Monitor, flashing firmware, and high-reliability, short-distance data transfer between the Microcontroller and a computer.

02 | Wi-Fi — a wireless protocol that requires a router. Range 50–100 m indoors, high speed, high power consumption. Operates primarily on 2.4 GHz for microcontrollers, using standard IP routing (TCP/IP, UDP, HTTP, MQTT). On the XIAO ESP32-C3, the 2.4 GHz Wi-Fi subsystem is fully integrated — the board can act as a Station (STA) to connect to a home / lab router, or as an Access Point (AP) so a phone can connect directly to it. Best for: IoT projects, uploading data to cloud dashboards, hosting a local web server, and long-range indoor communication through walls via a router.

03 | Bluetooth Low Energy (BLE) — wireless personal-area-network technology. Low speed, very low power consumption, range 10–30 m, no router required. Unlike classic Bluetooth (which streams heavy audio), BLE stays in sleep mode until a connection is initiated. It organises data into Services and Characteristics using a client-server structure. The chip supports Bluetooth 5.0 and BLE mesh, and is power-efficient enough for the XIAO to broadcast advertisements that smartphones or BLE gateways can pick up. Best for: connecting the XIAO to a smartphone app, battery-powered sensors, and close-range proximity tracking.

04 | ESP-NOW — wireless, medium speed, range up to 200 m in open air, low power consumption, no router required. ESP-NOW is a proprietary protocol developed by Espressif (the makers of the ESP32). It enables direct peer-to-peer wireless communication between ESP chips without needing a Wi-Fi router. It uses the same 2.4 GHz radio hardware as Wi-Fi but skips the lengthy Wi-Fi handshake and connection process — letting one Microcontroller flash a packet to another ESP32 / ESP8266 in milliseconds. Best for: ultra-fast, low-latency communication between multiple Fab Lab Connection Nodes, remote controls, and battery-optimised mesh networks where no router is available.

What an achievement!
  • Feedback: These are the four communication channels I'll combine in the project: USB-C to flash firmware onto the Microcontrollers, BLE to connect the gateway Node to the phone app, and ESP-NOW to keep the two Connection Nodes (left + right clip) talking to each other without involving the app.
  • Challenge: I'll need to test each protocol individually before stacking them. The hardest challenge I'm anticipating: keeping the app cleanly paired with the gateway Node so the two Microcontrollers' data streams don't get misinterpreted or cross-wired. This is exactly what I returned to and pivoted in Week 16, where I made the left clip the BLE gateway and used ESP-NOW just between the clips.

02: Connecting two Microcontroller Nodes over ESP-NOW


Testing the protocols using Thonny IDE — the MicroPython toolchain setup is documented in Week 04 — Embedded Programming. I used Gemini for coding assistance to establish the Node-to-Node connection in MicroPython. The plan: Connection Node 1 is the sender — an ESP32-C3 XIAO with an MPU6050 (input from W09). Connection Node 2 is the receiver — an ESP32-C3 XIAO with an LED (output from W10). When Node 1 detects motion past a calibrated threshold, it transmits an ESP-NOW packet to Node 2, which blinks the LED.

HTTP link

01 | I'm using two XIAO ESP32-C3 Microcontrollers that are already mounted on the PCBs from previous weeks: Connection Node 2 — the receiver (PCB with an LED) and Connection Node 1 — the sender (PCB with an MPU6050 and an LED).

HTTP link

02 | Finding the MAC address of Connection Node 2 (the receiver) so Node 1 knows where to send packets. The output is in the machine format: b'\x24\xdc\xc3\x12\x34\x56' — raw bytes the radio transmitter can use directly. Microcontrollers have limited processing power, so the chip doesn't want to waste cycles parsing colon-separated strings on every transmit; it wants the raw bytes already. The human format24:DC:C3:1A:2B:3C — is the same address as a string literal for use in config menus, command lines, and documentation.

import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
print("Connection Node 2 MAC Address:", wlan.config('mac'))
HTTP link

03 | On Connection Node 1 (the sender), I uploaded this lightweight MPU6050 driver and saved it on the Microcontroller's filesystem as mpu6050.py. To confirm the upload, the file appears in the file-explorer side panel of Thonny.

import machine

class MPU6050:
    def __init__(self, i2c, addr=0x68):
        self.i2c = i2c
        self.addr = addr
        # Wake up the MPU-6050 (it starts in sleep mode)
        self.i2c.writeto_mem(self.addr, 0x6B, b'\x00')

    def get_values(self):
        # Read 6 bytes of data starting from ACCEL_XOUT_H (0x3B)
        raw = self.i2c.readfrom_mem(self.addr, 0x3B, 6)

        # Combine high and low bytes and convert to signed 16-bit integers
        x = (raw[0] << 8 | raw[1])
        y = (raw[2] << 8 | raw[3])
        z = (raw[4] << 8 | raw[5])

        if x > 32767: x -= 65536
        if y > 32767: y -= 65536
        if z > 32767: z -= 65536

        return {"X": x, "Y": y, "Z": z}
HTTP link

04 | To confirm the MPU6050 is connected to Connection Node 1 on the correct GPIO pins, I ran this I²C-scan helper. It tries the common SDA / SCL pairs for the XIAO ESP32-C3 and reports which pair finds the sensor at address 0x68.

import machine

# Common pairs for XIAO ESP32-C3: (SDA, SCL)
pin_pairs = [
    (6, 7),  # True ESP32-C3 hardware mapping for D4/D5
    (4, 5),  # MicroPython default mapping alias
    (2, 3),  # Alternative GPIO mapping
]

found = False

for sda_pin, scl_pin in pin_pairs:
    print(f"Testing pair -> SDA: GPIO {sda_pin}, SCL: GPIO {scl_pin}...")
    try:
        i2c = machine.I2C(0, sda=machine.Pin(sda_pin), scl=machine.Pin(scl_pin))
        devices = i2c.scan()
        if devices:
            print(f"🎉 SUCCESS! Found device {devices} at SDA:{sda_pin}, SCL:{scl_pin}")
            found = True
            break
    except Exception as e:
        print(f"  Error on pins {sda_pin}/{scl_pin}: {e}")

if not found:
    print("\n❌ No devices found on any common pin pairs.")
    print("If the green light is on, double-check that your SDA and SCL wires aren't swapped accidentally!")
HTTP link

05 | With the I²C pins confirmed (SDA = 6, SCL = 7), I started reading live values from the MPU6050. Moving the sensor in different directions showed the X / Y / Z values changing on screen in Thonny's REPL — confirmation that the input chain is healthy before we add networking on top.

import machine
import time
from mpu6050 import MPU6050

# Initialize I2C using your verified pins (SDA: 6, SCL: 7)
i2c = machine.I2C(0, scl=machine.Pin(7), sda=machine.Pin(6))
sensor = MPU6050(i2c)

print("Reading live data from MPU-6050... Press Ctrl+C to stop.\n")
time.sleep(1)

while True:
    try:
        data = sensor.get_values()
        # Print the data in a clear layout
        print(f"X: {data['X']:6d} | Y: {data['Y']:6d} | Z: {data['Z']:6d}")
    except Exception as e:
        print("Error reading sensor:", e)

    time.sleep(0.2) # Read 5 times a second

06 | Then I connected the two Microcontroller Nodes by hard-coding the MAC address of Connection Node 2 (the receiver) into the code running on Connection Node 1 (the MPU6050 sender). The sender calibrates a baseline at startup, then continuously compares live readings against it — when motion exceeds the THRESHOLD, it sends a BLINK_LED packet to Node 2 over ESP-NOW.

Connection Node 1 — the MPU6050 sender:

import network
import espnow
import machine
import time

# --- 1. THE INLINED MPU-6050 DRIVER ---
class MPU6050:
    def __init__(self, i2c, addr=0x68):
        self.i2c = i2c
        self.addr = addr
        self.i2c.writeto_mem(self.addr, 0x6B, b'\x00') # Wake up

    def get_values(self):
        raw = self.i2c.readfrom_mem(self.addr, 0x3B, 6)
        x = (raw[0] << 8 | raw[1])
        y = (raw[2] << 8 | raw[3])
        z = (raw[4] << 8 | raw[5])
        if x > 32767: x -= 65536
        if y > 32767: y -= 65536
        if z > 32767: z -= 65536
        return {"X": x, "Y": y, "Z": z}

# --- 2. INITIALIZE ESP-NOW WIRELESS ---
wlan = network.WLAN(network.STA_IF)
wlan.active(True)

e = espnow.ESPNow()
e.active(True)

# !!! REPLACE WITH THE EXACT RECEIVER MAC ADDRESS !!!
peer_mac = b'\x24\xdc\xc3\x12\x34\x56'
e.add_peer(peer_mac)

# --- 3. INITIALIZE HARDWARE I2C (SDA:6, SCL:7) ---
i2c = machine.I2C(0, scl=machine.Pin(7), sda=machine.Pin(6))
sensor = MPU6050(i2c)

# --- 4. CALIBRATION RUN ---
THRESHOLD = 3000
print("Calibrating baseline... Keep sensor still.")
time.sleep(1)
baseline = sensor.get_values()
print("Sender Armed! Move the sensor to transmit triggers.\n")

while True:
    try:
        current = sensor.get_values()

        # Calculate absolute motion differences
        diff_x = abs(current["X"] - baseline["X"])
        diff_y = abs(current["Y"] - baseline["Y"])

        # Stream live data to Thonny Shell
        print(f"LIVE DATA -> X: {current['X']:6d} | Y: {current['Y']:6d} ", end="")

        # Check if movement breaks threshold limits
        if diff_x > THRESHOLD or diff_y > THRESHOLD:
            print(f"  📡 WIRELESS PACKET TRANSMITTED! (Delta X:{diff_x} Y:{diff_y})")

            # Send the trigger payload to Connection Node 2
            e.send(peer_mac, b'BLINK_LED')

            time.sleep(0.8) # Prevent packet flooding while shaking
            baseline = sensor.get_values() # Reset baseline point
        else:
            print("\r", end="") # Clean lines refresh trick

    except Exception as err:
        print("\nSensor Loop Error:", err)

    time.sleep(0.1)

Connection Node 2 — the receiver / blinker:

import network
import espnow
import machine
import time

# 1. Initialize Wi-Fi in Station Mode
wlan = network.WLAN(network.STA_IF)
wlan.active(True)

e = espnow.ESPNow()
e.active(True)

# 2. Configure Pin 10 as Output for the built-in blue LED
led = machine.Pin(10, machine.Pin.OUT)
led.value(1) # Ensure LED is off initially

print("Connection Node 2 listening for MPU status triggers...")

while True:
    host, msg = e.recv()
    if msg:
        print(f"Received message: {msg} from address: {host}")

        if msg == b'BLINK_LED':
            print("Executing blink sequence!")
            for _ in range(4):
                led.value(0) # LED ON
                time.sleep(0.1)
                led.value(1) # LED OFF
                time.sleep(0.1)
On the way to the final project
  • Feedback: Today I'm a real step closer to the final project. I was able to establish a working ESP-NOW connection between two Microcontroller Nodes — exactly the pattern that becomes the left and right shoulder clips in the final project, sharing their data with the app interface I'll build later.
  • Challenge: I'll need to decide whether to use ESP-NOW between the clips and BLE between the gateway clip and the app — that's the architecture I ended up choosing and documented in detail in Week 16. Another challenge in mind: how to read the MPU6050 values and convert the raw integers into something more user-friendly on the app side. That became the main focus of the IoT integration in W16.

Reflection

What worked
  • Connecting two ESP32-C3 XIAO Connection Nodes over ESP-NOW end-to-end: Node 1 calibrates an MPU6050 baseline, watches for motion that exceeds the threshold, and transmits a BLINK_LED packet; Node 2 receives the packet and blinks an LED. All in MicroPython on Thonny.
  • Reusing the MPU6050 driver from W09 — Input Devices and the LED output from W10 — Output Devices — by W11 the parts were already proven; this week added just the communication layer.
  • Comparing the four available protocols on the XIAO (USB-C / Wi-Fi / BLE / ESP-NOW) before writing a single line of code gave me a clear mental model of which to pick for which job. That shortlist directly informed the final-project architecture: USB-C for flashing, BLE for the gateway Node to phone, ESP-NOW between the two Connection Nodes.
  • The threshold-based packet transmission (only send when motion exceeds the baseline + sleep 0.8 s afterwards) is a useful pattern for any wearable where battery life matters — it avoids flooding the receiver with packets at every micro-tremor.
What didn't
  • The MAC address being hard-coded in the sender's source means swapping a Microcontroller forces a code edit. For the final project I'd pull this into a config file or a runtime-paired peer table.
  • The THRESHOLD = 3000 value is a guess from one calibration session — it triggered some false positives during normal hand movement and missed a few intentional shakes. Needs proper characterisation per Microcontroller.
  • As a remote student I couldn't pair with another in-person student for the group experiment, so my Node-to-Node test uses two of my own Microcontrollers — same protocol, less variety.
  • I didn't get to characterise actual range or packet loss for ESP-NOW in this week — I just confirmed that point-to-point works at desk distance.
What I'd do differently
  • Print the MAC address in both formats (raw bytes and colon-separated string) from the start, so I don't have to convert by hand.
  • Move the peer MAC out of the source and into a config file so Microcontroller swaps don't need code edits.
  • Add a heartbeat ping in addition to event-based packets, so link health is monitored continuously — not just on motion.
  • Run a real range and packet-loss characterisation between the two Connection Nodes (distance, packet count, retransmits) before relying on ESP-NOW as the only link in the final project.
Key learnings
  • Microcontroller vs Connection Node is a useful distinction: the chip is the hardware; the Node is its role in the network. Same Microcontroller can play different Node roles depending on the code it's running (sender, receiver, gateway).
  • ESP-NOW is the right protocol when you don't want a router in the loop — peer-to-peer, low latency, low power, MAC-addressed. This is the call I made for clip-to-clip in the final project (see W16).
  • BLE is the right protocol when a phone is one of the peers — close range, low power, native to every modern phone, no router required.
  • Picking the right protocol per edge of the network is a design decision in itself — there's no single "wireless protocol" that covers everything, and stacking them (USB-C + BLE + ESP-NOW in my project) is a normal pattern.
  • Networking week is where the input (W09) + output (W10) weeks combine into a system. The first ESP-NOW packet that successfully triggered a blink across the desk was the moment the final project's architecture became real.