Project 6.9: "Wireless Sensor Network"
What you’ll learn
- ✅ Multi‑node networking: Connect several R32 boards and exchange small, labeled packets reliably.
- ✅ Distributed sensing: Collect sensor readings from many nodes and aggregate them.
- ✅ Fault tolerance: Detect missing nodes and keep the system running with graceful fallbacks.
- ✅ Energy optimization: Reduce power usage with smart duty cycles and sleep intervals.
- ✅ Central visualization: Show live, combined data from all nodes on a simple hub page or PC log.
Blocks glossary (used in this project)
- WiFi STA: Join the same network with SSID/PASSWORD.
- UDP send/recv: Lightweight datagrams for node messages and sensor reports.
- Node IDs: Short labels (N1, N2, N3) for addressing and grouping.
- Heartbeats: Periodic “I’m alive” signals with timestamps to detect failures.
- Duty cycle: Schedule active sensing and network sleeps to save energy.
- Serial println: Print “HB:…”, “SENS:…”, “AGG:…”, “NODE:…”, “PWR:…”, “FAULT:…”.
What you need
| Part | How many? | Pin connection / Notes |
|---|---|---|
| D1 R32 (ESP32) | 3–6 | Each with USB; same WiFi network |
| Sensors (demo: ADC) | 3–6 | One per node (e.g., Pin 34 analog) |
| Optional PC | 1 | Same network; receives UDP or opens hub page |
Notes
- Assign each node a unique ID (N1, N2, …) and static or known IP if possible.
- Keep prints short and consistent across nodes to simplify classroom debugging.
Before you start
- All nodes flashed and labeled with unique IDs
- All nodes on the same WiFi network
- Serial monitors open; quick test:
print("Ready!") # Confirm serial is working so you can see messages
Microprojects 1–5
Microproject 6.9.1 – Communication between multiple R32
Goal: Each node sends a heartbeat; the hub receives and prints IDs and timestamps.
Blocks used:
- UDP sockets: sendto and recvfrom.
- Serial println: HB:SENT and HB:RECV.
MicroPython code (Node side):
import network # Import network to manage WiFi
import socket # Import socket to send UDP datagrams
import time # Import time to build timestamps
SSID = "YourSSID" # Set WiFi SSID
PASSWORD = "YourPassword" # Set WiFi password
NODE_ID = "N1" # Set this node's ID (change per board: N1/N2/N3...)
HUB_IP = "192.168.1.100" # Set hub IP (PC or hub R32)
HUB_PORT = 6300 # Set hub UDP port
wlan = network.WLAN(network.STA_IF) # Create WiFi station interface
wlan.active(True) # Activate WiFi hardware
wlan.connect(SSID, PASSWORD) # Connect to the WiFi network
print("NET:CONNECTING", SSID) # Print connecting SSID
while not wlan.isconnected(): # Wait until connected
time.sleep(0.2) # Short wait
print("NET:CONNECTED", wlan.ifconfig()[0]) # Print local IP
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
print("UDP:READY", HUB_IP, HUB_PORT) # Print hub target info
def hhmmss(): # Build simple time text from ticks
t = time.ticks_ms() // 1000 # Seconds since boot
h = (t // 3600) % 24 # Hours modulo 24
m = (t % 3600) // 60 # Minutes
s = t % 60 # Seconds
return "{:02d}:{:02d}:{:02d}".format(h, m, s) # Format hh:mm:ss
while True: # Heartbeat loop
msg = ("HB:" + NODE_ID + " TIME=" + hhmmss()).encode() # Build heartbeat bytes
u.sendto(msg, (HUB_IP, HUB_PORT)) # Send heartbeat to hub
print("HB:SENT", NODE_ID) # Print heartbeat sent
time.sleep(1.0) # Wait 1 second before next heartbeat
MicroPython code (Hub side):
import socket # Import socket to receive UDP
import time # Import time for timeouts
PORT = 6300 # Hub listen port
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
u.bind(("0.0.0.0", PORT)) # Bind to all interfaces
u.settimeout(0.5) # Short timeout for responsiveness
print("HUB:LISTEN", PORT) # Print listening port
while True: # Receive loop
try: # Try receive data
data, addr = u.recvfrom(256) # Receive up to 256 bytes
text = data.decode() # Decode to text
print("HB:RECV", addr[0], text) # Print sender IP and heartbeat text
except OSError: # Timeout occurred
pass # Continue loop
time.sleep(0.05) # Small delay
Reflection: A steady heartbeat builds trust—everyone knows who’s online.
Challenge:
- Easy: Add the node’s local IP in each heartbeat.
- Harder: Track last heartbeat per node and print “NODE:STALE” after 5 seconds of silence.
Microproject 6.9.2 – Collection of distributed data
Goal: Nodes send sensor readings; hub aggregates and prints per‑node values.
Blocks used:
- ADC read: Simple analog value per node.
- UDP reports: SENS:Nx V=… to hub.
MicroPython code (Node side):
import machine # Import machine to read ADC
import socket # Import socket to send UDP
import time # Import time for pacing
NODE_ID = "N1" # Set unique node ID
HUB_IP = "192.168.1.100" # Set hub IP
HUB_PORT = 6301 # Set hub port for sensor data
adc = machine.ADC(machine.Pin(34)) # Create ADC on Pin 34 for sensor reading
adc.atten(machine.ADC.ATTN_11DB) # Set attenuation for full-scale range
adc.width(machine.ADC.WIDTH_12BIT) # Set 12-bit resolution (0–4095)
print("SENS:ADC READY 34") # Confirm sensor ADC ready
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
print("UDP:READY", HUB_IP, HUB_PORT) # Print hub target for data
while True: # Reporting loop
v = adc.read() # Read sensor value
msg = ("SENS:" + NODE_ID + " V=" + str(v)).encode() # Build sensor report bytes
u.sendto(msg, (HUB_IP, HUB_PORT)) # Send report to hub
print("SENS:SENT", v) # Print sent value
time.sleep(1.5) # Wait 1.5 seconds between reports
MicroPython code (Hub side):
import socket # Import socket for UDP receive
import time # Import time for timeouts
PORT = 6301 # Sensor hub port
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
u.bind(("0.0.0.0", PORT)) # Bind to port
u.settimeout(0.5) # Short timeout
print("AGG:LISTEN", PORT) # Print listen port
values = {} # Create dict to hold latest per-node values
while True: # Aggregation loop
try: # Try receive
data, addr = u.recvfrom(256) # Receive bytes
text = data.decode() # Decode to text
print("AGG:RECV", text) # Print received text
parts = text.split() # Split by spaces
node = parts[0].split(":")[1] if ":" in parts[0] else "?" # Extract node ID
val = int(parts[1].split("=")[1]) if "=" in parts[1] else 0 # Extract value
values[node] = val # Store latest value for node
print("AGG:STATE", values) # Print current aggregation state
except OSError: # Timeout
pass # Continue loop
time.sleep(0.05) # Small delay
Reflection: A simple aggregator turns many readings into one coherent picture.
Challenge:
- Easy: Add a moving average per node before printing.
- Harder: Add a “UNIT:” field so nodes can report different sensors consistently.
Microproject 6.9.3 – Fault tolerance of a node
Goal: Hub detects missing nodes and marks them as FAULT; nodes retry sending on failure.
Blocks used:
- Last‑seen map: node → timestamp.
- Retry logic: Node resends if send fails.
MicroPython code (Hub side):
import socket # Import socket for UDP receive
import time # Import time to track timestamps
PORT = 6302 # Fault-detection hub port
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
u.bind(("0.0.0.0", PORT)) # Bind port for hearing nodes
u.settimeout(0.5) # Short timeout
print("FAULT:LISTEN", PORT) # Print listening port
last_seen = {} # Dict: node ID -> last timestamp (ms)
timeout_ms = 5000 # Consider node missing if >5 s since last heartbeat
while True: # Detection loop
now = time.ticks_ms() # Current time in ms
try: # Try receive
data, addr = u.recvfrom(256) # Receive bytes
text = data.decode() # Decode to text
print("FAULT:RECV", text) # Print message
node = text.split(":")[1].split()[0] if ":" in text else "?" # Extract node ID
last_seen[node] = now # Update last seen timestamp
print("NODE:SEEN", node) # Print node seen
except OSError: # Timeout
pass # Continue loop
for node, ts in list(last_seen.items()): # Iterate nodes
if time.ticks_diff(now, ts) > timeout_ms: # If timeout exceeded
print("FAULT:MISSING", node) # Print missing node
time.sleep(0.2) # Small delay
MicroPython code (Node side):
import socket # Import socket for UDP send
import time # Import time for retries
NODE_ID = "N1" # Set node ID
HUB_IP = "192.168.1.100" # Set hub IP
HUB_PORT = 6302 # Set hub fault-detection port
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
print("FAULT:SOCK_READY", HUB_IP, HUB_PORT) # Print hub info
def send_hb(): # Send heartbeat with simple retry
msg = ("HB:" + NODE_ID).encode() # Build heartbeat bytes
try: # Try to send
u.sendto(msg, (HUB_IP, HUB_PORT)) # Send message
print("HB:SENT", NODE_ID) # Print success
except OSError: # On send error
print("HB:RETRY", NODE_ID) # Print retry notice
time.sleep(0.2) # Short wait
try: # Try again
u.sendto(msg, (HUB_IP, HUB_PORT)) # Send again
print("HB:SENT2", NODE_ID) # Print success
except OSError: # If still failing
print("HB:FAIL", NODE_ID) # Print failure
while True: # Heartbeat loop
send_hb() # Send heartbeat
time.sleep(1.0) # Wait 1 second
Reflection: Knowing who’s missing lets the system adapt instead of stall.
Challenge:
- Easy: Add “FAULT:RECOVERED” when a missing node returns.
- Harder: Promote a backup node to “temp hub” if the main hub fails.
Microproject 6.9.4 – Energy optimization
Goal: Nodes duty‑cycle sensing and networking to save energy while staying responsive.
Blocks used:
- Intervals: ACTIVE window and SLEEP window.
- Reduced frequency: Longer gaps when stable.
MicroPython code (Node side):
import machine # Import machine to read ADC
import socket # Import socket to send UDP
import time # Import time to schedule duty cycles
NODE_ID = "N1" # Set node ID
HUB_IP = "192.168.1.100" # Set hub IP
HUB_PORT = 6303 # Set hub port for energy-aware data
adc = machine.ADC(machine.Pin(34)) # Create ADC on Pin 34
adc.atten(machine.ADC.ATTN_11DB) # Set attenuation
adc.width(machine.ADC.WIDTH_12BIT) # Set resolution
print("PWR:ADC READY 34") # Confirm ADC
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket
print("PWR:UDP READY", HUB_IP, HUB_PORT) # Confirm UDP
ACTIVE_MS = 3000 # Active window duration in ms
SLEEP_MS = 2000 # Sleep window duration in ms
stable_count = 0 # Count consecutive stable readings
last_val = -1 # Store last reading value
def send_val(v): # Send sensor value with power tag
msg = ("SENS:" + NODE_ID + " V=" + str(v) + " PWR=DC").encode() # Build message bytes
u.sendto(msg, (HUB_IP, HUB_PORT)) # Send to hub
print("PWR:SENT", v) # Print sent value
while True: # Duty-cycle loop
start = time.ticks_ms() # Record start time
while time.ticks_diff(time.ticks_ms(), start) < ACTIVE_MS: # During active window
v = adc.read() # Read sensor
send_val(v) # Send sensor value
if last_val != -1 and abs(v - last_val) < 20: # If stable within small delta
stable_count += 1 # Increase stability count
else: # If not stable
stable_count = 0 # Reset stability
last_val = v # Update last value
gap = 1000 if stable_count > 3 else 400 # Choose longer gap if stable
print("PWR:GAP", gap) # Print chosen gap
time.sleep(gap / 1000.0) # Sleep for gap
print("PWR:SLEEP", SLEEP_MS) # Print sleep window
time.sleep(SLEEP_MS / 1000.0) # Sleep window to save energy
Reflection: Smarter timing cuts energy use without losing important changes.
Challenge:
- Easy: Add “PWR:LEVEL=LOW/MED/HIGH” based on gap length.
- Harder: Pause WiFi during long sleeps and reconnect on ACTIVE start.
Microproject 6.9.5 – Centralized data visualization
Goal: Hub serves a simple web page showing latest values per node.
Blocks used:
- TCP server: Port 80 with minimal HTML.
- Shared dict: Node → latest value.
MicroPython code (Hub side):
import socket # Import socket for TCP server
import time # Import time for pacing
values = {"N1": 0, "N2": 0, "N3": 0} # Initialize latest values per node
def page_html(): # Build HTML showing values
body = "<html><body><h1>Network Sensors</h1>" # Start HTML body
for k, v in values.items(): # Iterate node values
body += "<p>" + k + ": " + str(v) + "</p>" # Add node line
body += "</body></html>" # Close HTML body
return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n" + body # Build response
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] # Resolve address for port 80
srv = socket.socket() # Create TCP socket
srv.bind(addr) # Bind to port 80
srv.listen(1) # Listen for one client at a time
srv.settimeout(0.2) # Short timeout for responsiveness
print("WEB:LISTENING", addr) # Print listening address
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket for data updates
udp.bind(("0.0.0.0", 6303)) # Bind UDP port for energy-aware data
udp.settimeout(0.2) # Short timeout
print("AGG:LISTEN 6303") # Print UDP listen port
while True: # Main loop
try: # Try accept a web client
cl, remote = srv.accept() # Accept client
req = cl.recv(256) # Read request bytes
line = req.decode().split('\r\n')[0] # Get request line
print("WEB:REQ", line) # Print request line
cl.send(page_html().encode()) # Send HTML page
cl.close() # Close client
except OSError: # Accept timeout
pass # Continue loop
try: # Try receive UDP sensor update
data, addr = udp.recvfrom(256) # Receive bytes
text = data.decode() # Decode to text
print("AGG:RECV", text) # Print received text
parts = text.split() # Split by spaces
node = parts[0].split(":")[1] if ":" in parts[0] else "?" # Extract node ID
val = int(parts[1].split("=")[1]) if "=" in parts[1] else 0 # Extract value
values[node] = val # Update dict
except OSError: # Timeout
pass # Continue loop
time.sleep(0.05) # Small delay
Reflection: A simple page makes the invisible visible—every student can see the network breathe.
Challenge:
- Easy: Add a timestamp line per node.
- Harder: Color values green/yellow/red based on thresholds with inline CSS.
Main project – Wireless sensor network
Blocks steps (with glossary)
- Multi‑node heartbeats: Each node announces presence; hub confirms receipt.
- Distributed sensing: Nodes send ADC readings; hub aggregates latest values.
- Fault detection: Hub marks missing nodes; nodes retry sending if errors occur.
- Energy optimization: Nodes duty‑cycle sensing and reporting based on stability.
- Visualization: Hub serves a tiny HTML page showing the latest network state.
MicroPython code (mirroring blocks)
# Project 6.9 – Wireless Sensor Network (Nodes + Hub)
import network # Import network for WiFi STA mode
import socket # Import socket for UDP and TCP
import machine # Import machine for ADC
import time # Import time for pacing and timestamps
# Hub configuration (run this on the HUB R32 or PC)
HUB_RX_HB = 6300 # Heartbeat port
HUB_RX_SENS = 6301 # Sensor aggregation port
HUB_RX_PWR = 6303 # Energy-aware data port
WEB_PORT = 80 # Web visualization port
# Node configuration (set per node when used in node mode)
SSID = "YourSSID" # WiFi SSID
PASSWORD = "YourPassword" # WiFi password
NODE_ID = "N1" # Unique node ID (N1/N2/N3...)
HUB_IP = "192.168.1.100" # Hub IP address
# Shared functions
def hhmmss(): # Build hh:mm:ss string from ticks
t = time.ticks_ms() // 1000 # Seconds since boot
h = (t // 3600) % 24 # Hours modulo 24
m = (t % 3600) // 60 # Minutes
s = t % 60 # Seconds
return "{:02d}:{:02d}:{:02d}".format(h, m, s) # Format text
# =========================
# HUB PROGRAM (aggregation + web)
# =========================
def run_hub(): # Run hub services
u_hb = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket for heartbeats
u_hb.bind(("0.0.0.0", HUB_RX_HB)) # Bind heartbeat port
u_hb.settimeout(0.2) # Short timeout
print("HUB:HB_LISTEN", HUB_RX_HB) # Print heartbeat listen
u_sens = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket for sensors
u_sens.bind(("0.0.0.0", HUB_RX_SENS)) # Bind sensor port
u_sens.settimeout(0.2) # Short timeout
print("HUB:SENS_LISTEN", HUB_RX_SENS) # Print sensor listen
u_pwr = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Create UDP socket for energy-aware data
u_pwr.bind(("0.0.0.0", HUB_RX_PWR)) # Bind power data port
u_pwr.settimeout(0.2) # Short timeout
print("HUB:PWR_LISTEN", HUB_RX_PWR) # Print power listen
addr = socket.getaddrinfo('0.0.0.0', WEB_PORT)[0][-1] # Resolve web address
srv = socket.socket() # Create TCP server socket
srv.bind(addr) # Bind web port
srv.listen(1) # Listen for one client at a time
srv.settimeout(0.2) # Short timeout
print("WEB:LISTEN", addr) # Print web listen
last_seen = {} # Dict: node -> last seen ms
values = {} # Dict: node -> latest value
pwr_values = {} # Dict: node -> latest energy-aware value
def page_html(): # Build network status page
body = "<html><body><h1>WSN Status</h1>" # Start HTML
now = hhmmss() # Current time label
body += "<p>Time: " + now + "</p>" # Add time
body += "<h2>Nodes</h2>" # Nodes section
for node, ts in last_seen.items(): # Iterate nodes
age_ms = time.ticks_diff(time.ticks_ms(), ts) # Compute age
status = "OK" if age_ms < 5000 else "STALE" # Determine status
val = values.get(node, "-") # Get sensor value
pval = pwr_values.get(node, "-") # Get power value
body += "<p>" + node + " | status=" + status + " | val=" + str(val) + " | pwr=" + str(pval) + "</p>" # Add node line
body += "</body></html>" # Close HTML
return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n" + body # Build response
while True: # Hub main loop
# Receive heartbeats
try: # Try receive heartbeat
data, addr_hb = u_hb.recvfrom(256) # Receive bytes
text = data.decode() # Decode text
print("HB:RECV", text) # Print heartbeat
node = text.split(":")[1].split()[0] if ":" in text else "?" # Extract node
last_seen[node] = time.ticks_ms() # Update last seen
except OSError: # Timeout
pass # Continue
# Receive sensor values
try: # Try receive sensor data
data, addr_s = u_sens.recvfrom(256) # Receive bytes
text = data.decode() # Decode text
print("SENS:RECV", text) # Print sensor line
parts = text.split() # Split tokens
node = parts[0].split(":")[1] if ":" in parts[0] else "?" # Extract node
val = int(parts[1].split("=")[1]) if "=" in parts[1] else 0 # Extract value
values[node] = val # Store latest value
except OSError: # Timeout
pass # Continue
# Receive power-aware values
try: # Try receive power data
data, addr_p = u_pwr.recvfrom(256) # Receive bytes
text = data.decode() # Decode text
print("PWR:RECV", text) # Print power line
parts = text.split() # Split tokens
node = parts[0].split(":")[1] if ":" in parts[0] else "?" # Extract node
val = int(parts[1].split("=")[1]) if "=" in parts[1] else 0 # Extract value
pwr_values[node] = val # Store latest power value
except OSError: # Timeout
pass # Continue
# Serve web page
try: # Try accept web client
cl, remote = srv.accept() # Accept client
req = cl.recv(256) # Read request bytes
line = req.decode().split('\r\n')[0] # Get first line
print("WEB:REQ", line) # Print request line
cl.send(page_html().encode()) # Send page
cl.close() # Close client
except OSError: # Timeout
pass # Continue
time.sleep(0.05) # Small delay for responsiveness
# =========================
# NODE PROGRAM (heartbeat + sensing + energy)
# =========================
def run_node(): # Run node sender
wlan = network.WLAN(network.STA_IF) # Create WiFi station
wlan.active(True) # Activate WiFi
wlan.connect(SSID, PASSWORD) # Connect credentials
print("NET:CONNECTING", SSID) # Print SSID
while not wlan.isconnected(): # Wait until connected
time.sleep(0.2) # Short delay
print("NET:CONNECTED", wlan.ifconfig()[0]) # Print local IP
adc = machine.ADC(machine.Pin(34)) # Create ADC
adc.atten(machine.ADC.ATTN_11DB) # Set attenuation
adc.width(machine.ADC.WIDTH_12BIT) # Set resolution
print("ADC:READY 34") # Confirm ADC
u_hb = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP heartbeat socket
u_sens = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP sensor socket
u_pwr = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP power data socket
print("UDP:TARGET", HUB_IP, HUB_RX_HB, HUB_RX_SENS, HUB_RX_PWR) # Print targets
ACTIVE_MS = 3000 # Active window ms
SLEEP_MS = 2000 # Sleep window ms
stable_count = 0 # Stability counter
last_val = -1 # Last sensor value
while True: # Node main loop
# Send heartbeat
hb = ("HB:" + NODE_ID + " TIME=" + hhmmss()).encode() # Build heartbeat
u_hb.sendto(hb, (HUB_IP, HUB_RX_HB)) # Send heartbeat
print("HB:SENT", NODE_ID) # Print sent
# Active window
start = time.ticks_ms() # Record start
while time.ticks_diff(time.ticks_ms(), start) < ACTIVE_MS: # Active loop
v = adc.read() # Read sensor value
msg_s = ("SENS:" + NODE_ID + " V=" + str(v)).encode() # Build sensor report
u_sens.sendto(msg_s, (HUB_IP, HUB_RX_SENS)) # Send sensor report
print("SENS:SENT", v) # Print sent
# Energy-aware report
msg_p = ("SENS:" + NODE_ID + " V=" + str(v) + " PWR=DC").encode() # Build power-aware report
u_pwr.sendto(msg_p, (HUB_IP, HUB_RX_PWR)) # Send power report
print("PWR:SENT", v) # Print power sent
# Stability logic
if last_val != -1 and abs(v - last_val) < 20: # If stable
stable_count += 1 # Increase stability
else: # If not stable
stable_count = 0 # Reset
last_val = v # Update last value
gap = 1000 if stable_count > 3 else 400 # Choose gap
print("PWR:GAP", gap) # Print gap
time.sleep(gap / 1000.0) # Sleep for gap
# Sleep window
print("PWR:SLEEP", SLEEP_MS) # Print sleep notice
time.sleep(SLEEP_MS / 1000.0) # Sleep to save energy
# Choose one function to run per device:
# run_hub() # Uncomment on the hub R32
# run_node() # Uncomment on each node R32 (change NODE_ID and HUB_IP)
External explanation
- What it teaches: You built a complete small network: nodes announce themselves, send sensor values with energy‑aware timing, the hub detects faults and serves a live page with the latest state.
- Why it works: UDP makes tiny messages fast; heartbeats track presence; duty cycles save power; simple parsing keeps the hub readable; a minimal web server turns the data into a shared view.
- Key concept: “Announce → report → detect → optimize → visualize.”
Story time
In the classroom, each robot whispers its status: “I’m here. Value: 213.” The hub listens, notices who’s quiet, and shows the network breathing on a little page. It feels like a constellation—lights flicker, but the pattern holds.
Debugging (2)
Debugging 6.9.1 – Nodes do not communicate
Problem: Hub prints nothing; nodes say HB:SENT.
Clues: Wrong hub IP or port mismatch.
Broken code:
u_hb.sendto(hb, ("192.168.1.200", 6300)) # Wrong hub IP
Fixed code:
HUB_IP = "192.168.1.100" # Correct hub IP
HUB_RX_HB = 6300 # Match hub heartbeat port
print("DEBUG:TARGET", HUB_IP, HUB_RX_HB) # Verify targets
Why it works: Matching IP and ports ensures packets reach the hub.
Avoid next time: Write targets on a card and test with a single node first.
Debugging 6.9.2 – Data loss on the network
Problem: Hub receives some values but many are missing.
Clues: Messages arrive in bursts; no pacing.
Broken code:
u_sens.sendto(msg_s, (HUB_IP, HUB_RX_SENS)) # Spammed with 0 ms delay
Fixed code:
time.sleep(gap / 1000.0) # Add a short delay (400–1000 ms) between sends
print("DEBUG:PACED_SEND") # Confirm pacing
Why it works: Small gaps reduce congestion and allow the hub to process packets.
Avoid next time: Pace sends and keep payloads short with clear tags.
Final checklist
- All nodes connect to WiFi and send HB with IDs and time
- Hub aggregates sensor values and prints per‑node state
- Missing nodes are detected as FAULT after a timeout
- Nodes duty‑cycle reports and show longer gaps when stable
- Hub serves a web page that shows latest values and node status
Extras
- 🧠 Student tip: Keep a simple schema: “HB:Nx TIME=hh:mm:ss” and “SENS:Nx V=####”. Consistency prevents parsing headaches.
- 🧑🏫 Instructor tip: Assign NODE_IDs at the start and run a roll call: the hub should list everyone.
- 📖 Glossary:
- Heartbeat: A periodic “I’m alive” message.
- Duty cycle: Alternating active and sleep windows to save energy.
- Aggregation: Combining many readings into a single, coherent state.
- 💡 Mini tips:
- Prefer short tags and integers; avoid big JSON in classroom demos.
- Use timeouts and small delays to keep loops responsive.
- Start with 2–3 nodes before scaling to 6+ to tune pacing.