Hands-on lab implementing packet forwarding on ESP32 with TTL decrement and route lookup, simulating routing table decisions with multiple route types in embedded C, and visualizing network convergence using Python and matplotlib.
13.1 Learning Objectives
By the end of this chapter, you will be able to:
Implement packet forwarding logic on ESP32 with TTL decrement and route lookup
Simulate routing table decisions using embedded C code with multiple route types
Visualize network convergence using Python and matplotlib to understand route propagation
Analyze packet drop scenarios including TTL expiration and missing routes
Debug routing issues by understanding forwarding statistics and error conditions
For Beginners: Advanced Routing Labs
These labs let you experiment with more complex routing scenarios, like networks with multiple paths, failures, and changing conditions. Think of it as practicing emergency detour planning – you will see how networks automatically find new routes when the usual paths are blocked.
“This is the most exciting lab,” said Sammy the Sensor. “We actually implement packet forwarding on an ESP32 – the same microcontroller used in real IoT devices! You will write code that looks up routes, decrements TTL, and forwards packets.”
“The TTL decrement is critical,” explained Max the Microcontroller. “Every time we forward a packet, we subtract one from the TTL field. If it reaches zero, we drop the packet and send an error message back. This is how real routers work, and we implement it in embedded C.”
“The convergence visualization uses Python and matplotlib,” added Lila the LED. “You can watch how routing tables evolve over time as routers exchange updates. Seeing the algorithm converge step by step really helps you understand why some protocols are faster than others.”
“Pay close attention to the error handling,” advised Bella the Battery. “What happens when there is no matching route? What about when the TTL expires? These edge cases are where most routing bugs hide in production deployments.”
13.2 Prerequisites
Before working through these advanced labs, ensure you have completed:
Time: ~30 min | Difficulty: Advanced | Code: P07.C20.U01
Objective: Simulate packet forwarding with TTL decrement on ESP32.
Hardware Required:
ESP32 development board
USB cable
Computer with Arduino IDE
Software Setup:
Install ESP32 board support in Arduino IDE
No additional libraries needed
13.3.1 Understanding the Simulation
This lab simulates how a router processes packets:
Figure 13.1: Packet Forwarding Process: A router first checks TTL - if zero, the packet is dropped. Otherwise, TTL is decremented and the routing table is consulted. Connected routes result in direct delivery while static/default routes forward to the next hop.
Try It: ESP32 Packet Forwarding Simulator
Objective: Run the packet forwarding simulator on ESP32 and observe how a router processes packets: TTL decrement, route lookup, and forwarding decisions.
Paste the code below into the simulator. Watch the Serial Monitor to see the routing table, packet forwarding decisions, and statistics for forwarded vs dropped packets. Try modifying the TTL values or adding new routing table entries to observe different behaviors.
What to Observe:
Packets with TTL=0 are dropped immediately with a “Time Exceeded” message
Packets matching a connected route are delivered directly (metric 0)
Packets with no matching route use the default route (0.0.0.0/0)
The statistics at the end show forwarding efficiency
13.3.2 Complete ESP32 Code
#include <WiFi.h>// Simulated packet structurestruct IPPacket {char source_ip[16];char dest_ip[16];uint8_t ttl;uint8_t protocol;char payload[64];uint32_t packet_id;};// Simulated routing table entrystruct RouteEntry {char destination[16];char next_hop[16];char interface[10];int metric;};// Simulated routing tableRouteEntry routing_table[]={{"192.168.1.0/24","0.0.0.0","eth0",0},// Connected{"10.0.0.0/8","0.0.0.0","eth1",0},// Connected{"172.16.0.0/16","10.0.0.2","eth1",10},// Static{"0.0.0.0/0","192.168.1.1","eth0",1}// Default};constint ROUTING_TABLE_SIZE =4;uint32_t packets_forwarded =0;uint32_t packets_dropped =0;uint32_t ttl_expired =0;void setup(){ Serial.begin(115200); delay(2000); Serial.println("\n========================================"); Serial.println(" ESP32 Packet Forwarding Simulator"); Serial.println(" Routing & TTL Management Demo"); Serial.println("========================================\n"); displayRoutingTable(); Serial.println("\n======================================="); Serial.println("Starting Packet Forwarding Simulation"); Serial.println("=======================================\n");// Simulate various packet scenarios simulatePacketScenarios(); displayStatistics();}void loop(){// Simulation runs once in setup()}void displayRoutingTable(){ Serial.println("Routing Table:"); Serial.println("-----------------------------------------------------"); Serial.println("Destination Next Hop Interface Metric"); Serial.println("-----------------------------------------------------");for(int i =0; i < ROUTING_TABLE_SIZE; i++){char line[100]; sprintf(line,"%-17s%-15s%-10s%d", routing_table[i].destination, routing_table[i].next_hop, routing_table[i].interface, routing_table[i].metric); Serial.println(line);} Serial.println("-----------------------------------------------------");}RouteEntry* lookupRoute(constchar* dest_ip){// Simplified lookup - in reality would do longest prefix matching// Check for local network matches (simplified)if(strncmp(dest_ip,"192.168.1.",10)==0){return&routing_table[0];// 192.168.1.0/24}elseif(strncmp(dest_ip,"10.",3)==0){return&routing_table[1];// 10.0.0.0/8}elseif(strncmp(dest_ip,"172.16.",7)==0){return&routing_table[2];// 172.16.0.0/16}else{return&routing_table[3];// Default route}}enum ForwardResult { FORWARDED, TTL_EXPIRED, NO_ROUTE};ForwardResult forwardPacket(IPPacket* packet){ Serial.println("\n-----------------------------------------"); Serial.printf("| Packet ID: %lu\n", packet->packet_id); Serial.println("-----------------------------------------"); Serial.printf(" Source: %s -> Destination: %s\n", packet->source_ip, packet->dest_ip); Serial.printf(" TTL: %d Protocol: %d\n", packet->ttl, packet->protocol); Serial.printf(" Payload: %s\n", packet->payload);// Step 1: Check TTLif(packet->ttl ==0){ Serial.println(" X Action: DROP (TTL expired)"); Serial.println(" -> Would send ICMP Time Exceeded to source"); ttl_expired++; packets_dropped++;return TTL_EXPIRED;}// Step 2: Decrement TTL packet->ttl--; Serial.printf(" -> TTL decremented to %d\n", packet->ttl);// Step 3: Route lookup RouteEntry* route = lookupRoute(packet->dest_ip);if(route == NULL){ Serial.println(" X Action: DROP (No route to destination)"); Serial.println(" -> Would send ICMP Destination Unreachable"); packets_dropped++;return NO_ROUTE;}// Step 4: Forward packet Serial.printf(" OK Route found: %s\n", route->destination);if(strcmp(route->next_hop,"0.0.0.0")==0){ Serial.printf(" -> Direct delivery on %s\n", route->interface);}else{ Serial.printf(" -> Forward to next hop: %s via %s\n", route->next_hop, route->interface);} Serial.printf(" OK Packet forwarded (metric: %d)\n", route->metric); packets_forwarded++;return FORWARDED;}void simulatePacketScenarios(){ IPPacket packet; packet.protocol =6;// TCP// Scenario 1: Local network packet Serial.println("\n=== Scenario 1: Local Network ==="); strcpy(packet.source_ip,"192.168.1.100"); strcpy(packet.dest_ip,"192.168.1.50"); packet.ttl =64; packet.packet_id =1001; strcpy(packet.payload,"HTTP Request"); forwardPacket(&packet);// Scenario 2: Remote network via static route Serial.println("\n\n=== Scenario 2: Static Route ==="); strcpy(packet.source_ip,"192.168.1.100"); strcpy(packet.dest_ip,"172.16.50.100"); packet.ttl =64; packet.packet_id =1002; strcpy(packet.payload,"IoT Sensor Data"); forwardPacket(&packet);// Scenario 3: Internet via default route Serial.println("\n\n=== Scenario 3: Default Route (Internet) ==="); strcpy(packet.source_ip,"192.168.1.100"); strcpy(packet.dest_ip,"8.8.8.8"); packet.ttl =64; packet.packet_id =1003; strcpy(packet.payload,"DNS Query"); forwardPacket(&packet);// Scenario 4: TTL expiration Serial.println("\n\n=== Scenario 4: TTL Expiration ==="); strcpy(packet.source_ip,"192.168.1.100"); strcpy(packet.dest_ip,"10.0.0.50"); packet.ttl =1;// Will expire after decrement packet.packet_id =1004; strcpy(packet.payload,"Data packet"); forwardPacket(&packet); forwardPacket(&packet);// Second hop - TTL will be 0// Scenario 5: Multiple hops Serial.println("\n\n=== Scenario 5: Multiple Hops (TTL tracking) ==="); strcpy(packet.source_ip,"192.168.1.100"); strcpy(packet.dest_ip,"10.0.0.50"); packet.ttl =5; packet.packet_id =1005; strcpy(packet.payload,"Multi-hop packet"); Serial.println("Simulating packet traverse across multiple routers:");for(int hop =1; hop <=5; hop++){ Serial.printf("\n--- Hop %d ---\n", hop); ForwardResult result = forwardPacket(&packet);if(result != FORWARDED){break;}}}void displayStatistics(){ Serial.println("\n\n========================================"); Serial.println(" Forwarding Statistics"); Serial.println("========================================"); Serial.printf(" Packets Forwarded: %lu\n", packets_forwarded); Serial.printf(" Packets Dropped: %lu\n", packets_dropped); Serial.printf(" TTL Expired: %lu\n", ttl_expired); Serial.printf(" Total Processed: %lu\n", packets_forwarded + packets_dropped);if(packets_forwarded + packets_dropped >0){float success_rate =(float)packets_forwarded /(packets_forwarded + packets_dropped)*100.0; Serial.printf(" Success Rate: %.1f%%\n", success_rate);} Serial.println("\n======================================="); Serial.println("Simulation Complete"); Serial.println("=======================================\n");}
13.3.3 Expected Serial Monitor Output
========================================
ESP32 Packet Forwarding Simulator
Routing & TTL Management Demo
========================================
Routing Table:
-----------------------------------------------------
Destination Next Hop Interface Metric
-----------------------------------------------------
192.168.1.0/24 0.0.0.0 eth0 0
10.0.0.0/8 0.0.0.0 eth1 0
172.16.0.0/16 10.0.0.2 eth1 10
0.0.0.0/0 192.168.1.1 eth0 1
-----------------------------------------------------
=======================================
Starting Packet Forwarding Simulation
=======================================
=== Scenario 1: Local Network ===
-----------------------------------------
| Packet ID: 1001
-----------------------------------------
Source: 192.168.1.100 -> Destination: 192.168.1.50
TTL: 64 Protocol: 6
Payload: HTTP Request
-> TTL decremented to 63
OK Route found: 192.168.1.0/24
-> Direct delivery on eth0
OK Packet forwarded (metric: 0)
=== Scenario 2: Static Route ===
-----------------------------------------
| Packet ID: 1002
-----------------------------------------
Source: 192.168.1.100 -> Destination: 172.16.50.100
TTL: 64 Protocol: 6
Payload: IoT Sensor Data
-> TTL decremented to 63
OK Route found: 172.16.0.0/16
-> Forward to next hop: 10.0.0.2 via eth1
OK Packet forwarded (metric: 10)
[... more scenarios ...]
13.3.4 Learning Outcomes
See TTL decrement in action
Understand routing table lookup process
Observe different forwarding decisions (connected, static, default)
Visualize packet drop scenarios
Try It: TTL and Forwarding Statistics Calculator
Adjust the initial TTL and number of hops to predict packet fate and forwarding efficiency.
Show code
viewof initTTL = Inputs.range([1,128], {value:64,step:1,label:"Initial TTL"})viewof numHops = Inputs.range([1,20], {value:5,step:1,label:"Number of hops to traverse"})viewof totalPackets = Inputs.range([1,100], {value:10,step:1,label:"Total packets sent"})viewof dropRate = Inputs.range([0,50], {value:10,step:1,label:"No-route drop rate (%)"})
Time: ~25 min | Difficulty: Advanced | Code: P07.C20.U02
Objective: Visualize routing protocol convergence and link failure recovery.
13.4.1 Setup
pip install matplotlib networkx
13.4.2 Understanding Convergence
Figure 13.2: Convergence Process: Distance vector protocols converge by exchanging routing tables. Each round, routers learn about destinations one hop further away. Convergence is complete when no routing table changes occur between rounds.
13.4.3 Python Simulation Code
"""Network Convergence SimulatorDemonstrates distance vector routing protocol convergence"""import matplotlib.pyplot as pltimport networkx as nxfrom collections import defaultdictclass DistanceVectorRouter:"""Simulates a router running distance vector protocol"""def__init__(self, name):self.name = nameself.neighbors = {} # neighbor_name: costself.routing_table = {} # destination: (cost, next_hop)def add_neighbor(self, neighbor_name, cost):"""Add a directly connected neighbor"""self.neighbors[neighbor_name] = costself.routing_table[neighbor_name] = (cost, neighbor_name)def receive_update(self, from_neighbor, neighbor_table):""" Process routing update from a neighbor Returns True if routing table changed """ changed =False cost_to_neighbor =self.neighbors.get(from_neighbor, float('inf'))for destination, (dest_cost, _) in neighbor_table.items():if destination ==self.name:continue new_cost = cost_to_neighbor + dest_cost current =self.routing_table.get(destination)if current isNoneor new_cost < current[0]:self.routing_table[destination] = (new_cost, from_neighbor) changed =Truereturn changeddef get_routing_table(self):"""Return copy of routing table for sharing"""returndict(self.routing_table)class NetworkSimulator:"""Simulates a network of distance vector routers"""def__init__(self):self.routers = {}self.history = [] # Track convergence historydef add_router(self, name):"""Add a router to the network"""self.routers[name] = DistanceVectorRouter(name)self.routers[name].routing_table[name] = (0, name)def add_link(self, router1, router2, cost):"""Add a bidirectional link between routers"""self.routers[router1].add_neighbor(router2, cost)self.routers[router2].add_neighbor(router1, cost)def run_convergence(self, max_rounds=10):""" Run distance vector convergence Returns number of rounds to converge """print("\nRunning distance vector convergence...")print("="*60)for round_num inrange(max_rounds):print(f"\n{'='*60}")print(f"Round {round_num}")print(f"{'='*60}")# Snapshot current stateself.history.append(self._get_network_state())# Each router sends updates to neighbors any_change =False updates = {}# Collect all updates firstfor name, router inself.routers.items(): updates[name] = router.get_routing_table()# Apply updatesfor name, router inself.routers.items():for neighbor in router.neighbors:if router.receive_update(neighbor, updates[neighbor]): any_change =True# Print current routing tablesself._print_routing_tables()ifnot any_change and round_num >0:print(f"\nConverged in {round_num} rounds")return round_numprint(f"\nDid not converge in {max_rounds} rounds")return max_roundsdef _get_network_state(self):"""Capture current network state for visualization""" state = {}for name, router inself.routers.items(): state[name] =dict(router.routing_table)return statedef _print_routing_tables(self):"""Print all routing tables"""for name, router inself.routers.items():print(f"\nRouting Table for {name}:")print("Destination Cost Next Hop")print("-"*40)for dest, (cost, next_hop) insorted(router.routing_table.items()):print(f"{dest:<15}{cost:<6}{next_hop}")def simulate_link_failure(self, router1, router2):"""Simulate a link failure between two routers"""print(f"\n{'='*60}")print(f"Link Failure: {router1}-{router2} link goes down")print(f"{'='*60}")# Remove neighbor relationshipif router2 inself.routers[router1].neighbors:delself.routers[router1].neighbors[router2]if router1 inself.routers[router2].neighbors:delself.routers[router2].neighbors[router1]# Mark routes via failed link as invalidfor name, router inself.routers.items(): invalid_dests = []for dest, (cost, next_hop) in router.routing_table.items():if next_hop == router2 and name == router1: invalid_dests.append(dest)elif next_hop == router1 and name == router2: invalid_dests.append(dest)for dest in invalid_dests:del router.routing_table[dest]def visualize_topology(self):"""Create network topology visualization""" G = nx.Graph()# Add nodes and edgesfor name, router inself.routers.items(): G.add_node(name)for neighbor, cost in router.neighbors.items(): G.add_edge(name, neighbor, weight=cost)# Draw network plt.figure(figsize=(10, 8)) pos = nx.spring_layout(G, seed=42) nx.draw_networkx_nodes(G, pos, node_size=700, node_color='#2C3E50') nx.draw_networkx_labels(G, pos, font_size=12, font_color='white') nx.draw_networkx_edges(G, pos, width=2, edge_color='#16A085')# Add edge labels (costs) edge_labels = nx.get_edge_attributes(G, 'weight') nx.draw_networkx_edge_labels(G, pos, edge_labels, font_size=10) plt.title("Network Topology", fontsize=14) plt.axis('off') plt.tight_layout() plt.savefig('network_topology.png', dpi=150) plt.show()print("Saved: network_topology.png")def main():"""Run the network convergence simulation"""print("Network Convergence Simulator")print("="*60)# Create network sim = NetworkSimulator()# Add routersfor name in ['R1', 'R2', 'R3', 'R4', 'R5']: sim.add_router(name)# Add links (creating a mesh topology) sim.add_link('R1', 'R2', 1) sim.add_link('R1', 'R3', 5) sim.add_link('R2', 'R4', 2) sim.add_link('R3', 'R4', 1) sim.add_link('R4', 'R5', 3)print("\nInitial Network Topology:")print(""" R1 ---- 1 ---- R2 | | 5 2 | | R3 ---- 1 ---- R4 ---- 3 ---- R5 """)# Run convergence rounds = sim.run_convergence()# Simulate link failure sim.simulate_link_failure('R1', 'R2')print("\nReconverging after link failure...") sim.run_convergence()# Visualize (optional - requires display)try: sim.visualize_topology()exceptExceptionas e:print(f"Visualization skipped: {e}")if__name__=="__main__": main()
13.4.4 Expected Output
Network Convergence Simulator
======================================================================
Initial Network Topology:
R1 ---- 1 ---- R2
| |
5 2
| |
R3 ---- 1 ---- R4 ---- 3 ---- R5
Running distance vector convergence...
============================================================
============================================================
Round 0
============================================================
Routing Table for R1:
Destination Cost Next Hop
----------------------------------------
R1 0 R1
R2 1 R2
R3 5 R3
Routing Table for R2:
Destination Cost Next Hop
----------------------------------------
R1 1 R1
R2 0 R2
R4 2 R4
[... continues ...]
============================================================
Round 3
============================================================
Routing Table for R1:
Destination Cost Next Hop
----------------------------------------
R1 0 R1
R2 1 R2
R3 4 R2
R4 3 R2
R5 6 R2
Converged in 3 rounds
============================================================
Link Failure: R1-R2 link goes down
============================================================
Reconverging after link failure...
[... reconvergence output ...]
13.4.5 Key Concepts Demonstrated
Distance Vector Propagation: Each round, knowledge spreads one hop further
Bellman-Ford Algorithm: Cost via neighbor = cost_to_neighbor + neighbor’s_cost
Convergence Detection: No changes between rounds indicates convergence
Link Failure Recovery: Routes via failed link are removed, new paths discovered
13.5 Routing Metrics in IoT Networks
Different routing protocols use different metrics to determine the best path. Understanding these metrics helps you choose the right protocol for your IoT deployment.
Figure 13.3: Routing Metrics Comparison: Three common routing metrics used in IoT networks. Hop Count (top) counts the number of routers traversed. ETX (middle) measures expected transmission attempts accounting for packet loss. Latency (bottom) measures end-to-end delay.
Choosing the Right Metric
Use Hop Count when:
Network links are reliable (wired connections)
Minimizing routing overhead is critical
Simple routing decisions are sufficient
Use ETX when:
Wireless links with variable quality
Retransmissions impact performance
Link reliability varies significantly (typical in IoT)
For IoT sensor networks, ETX is often the best choice because wireless links experience packet loss, and minimizing retransmissions saves battery power.
Interactive: ZRP Zone Routing Protocol Animation
Putting Numbers to It
How many convergence rounds does a distance-vector protocol need to propagate routing information across a network? Consider a 10-router linear topology: R1 ↔︎ R2 ↔︎ R3 ↔︎ … ↔︎ R10.
Round-by-round propagation (each router exchanges with neighbors every 30 seconds): - Round 1: R1 knows itself (0 hops). R2 learns R1 (1 hop). Distance information propagates 1 hop. - Round 2: R3 learns R1 (2 hops via R2). Distance propagates 2 hops total. - Round n: Information propagates \(n\) hops from source.
Full convergence (R10 learns about R1): - Requires 9 rounds (R1 is 9 hops away) - Time: \(9 \times 30\text{ s} = 270\text{ seconds} = 4.5\text{ minutes}\)
Link failure impact (R5 ↔︎ R6 link breaks): - All routers on either side of the break must relearn paths to destinations on the other side - R10’s route to R1 becomes invalid; without an alternative path, R10 marks R1 as unreachable - Count-to-infinity: Without split horizon, routers may falsely believe they can still reach the disconnected side via each other, incrementing cost until it hits the maximum (16 for RIP). This can take many rounds. With split horizon + poison reverse, the failure propagates immediately and converges in about 5 rounds = 150 seconds.
Why OSPF is faster: Link-state protocols flood topology changes instantly (< 1 second), then all routers recompute locally. Distance-vector waits for multiple rounds of neighbor exchanges.
Match: Lab Concepts
Order: ESP32 Packet Forwarding Simulation Steps
Common Pitfalls
1. Not Validating Lab Topology Against Real Network Behavior
Advanced routing labs use software simulators that may not accurately model link quality variations and timing. Always follow up simulator exercises with physical hardware testing when designing production networks.
2. Skipping Error Condition Testing in Lab Exercises
Labs that only test successful routing scenarios miss important behaviors. Deliberately inject link failures, packet loss, and node failures in lab exercises to develop troubleshooting skills.
3. Not Documenting Lab Configurations
Advanced lab configurations are complex enough that reproducing them from memory is unreliable. Document all router configurations, route tables, and experiment parameters before changing anything.
🏷️ Label the Diagram
Code Challenge
13.6 Summary
ESP32 packet forwarding simulation demonstrates TTL decrement, route lookup, and forwarding decisions in embedded systems
Routing table structures include destination network, next hop, interface, and metric for making forwarding decisions
Python convergence simulation visualizes how distance vector protocols propagate routing information across a network
Link failure recovery shows how routers automatically discover new paths when existing routes become unavailable
Routing metrics (hop count, ETX, latency) serve different purposes depending on network characteristics and application requirements
Forwarding statistics help diagnose routing issues by tracking successful forwards, drops, and TTL expirations
13.7 Knowledge Check
Quiz: Routing Labs Advanced
Decision Framework: Choosing Routing Metrics for IoT Networks
When configuring routing for IoT deployments, selecting the right metric dramatically impacts network performance. Use this framework to choose the optimal metric based on your network characteristics and application requirements.
Metric Type
Best For
Avoid When
Example Use Case
Hop Count
Stable wired networks, simple topologies
Wireless links with variable quality
Office Ethernet backbone with reliable switches
ETX (Expected Transmission Count)
Wireless mesh with lossy links, battery-powered devices
Low-latency requirements (computation overhead)
ZigBee home automation with 15-30% packet loss
Latency
Real-time control systems, VoIP, video
Best-effort sensor data, infrequent updates
Industrial control with <50ms response requirements
High-throughput video surveillance, bulk data transfer
Low-rate sensor networks (overkill)
Security camera mesh network
Step-by-Step Selection Process:
Step 1: Identify Your Primary Constraint
Power-constrained (battery)? → Consider Energy or ETX
Latency-sensitive? → Use Latency metric
Lossy wireless links? → Use ETX
Simple wired topology? → Use Hop Count
Step 2: Evaluate Secondary Factors
Computational resources: ETX requires link quality probing (CPU cost)
Protocol support: Does your protocol (RPL, AODV, etc.) support the metric?
Stability: More complex metrics may cause routing instability if links fluctuate rapidly
Step 3: Test and Validate
Deploy test network with chosen metric
Measure actual vs expected performance (delivery rate, latency, battery life)
Adjust metric or thresholds if results don’t match requirements
Worked Example: Smart Agriculture Deployment
Network: 200 soil moisture sensors across 50-acre farm - Wireless: LoRaWAN (long-range, low-power) - Power: Solar + battery backup - Data rate: 1 reading every 30 minutes - Environment: Open field with occasional rain/fog affecting RF propagation
Metric Comparison:
Metric
Configuration
Observed Performance
Issues
Hop Count
Default (all links cost 1)
72% delivery rate
Chose 3-hop path through wet area (30% loss/hop) instead of 4-hop reliable path
ETX
Probe interval 60s
94% delivery rate, +12% battery drain
Better paths selected, but probing cost noticeable
ETX (optimized)
Probe interval 300s
93% delivery rate, +3% battery drain
Acceptable trade-off: slightly slower adaptation, much lower power
Decision: Use ETX with 300-second probe interval - Why: Balances link quality awareness (ETX) with energy efficiency (infrequent probing) - Trade-off: 5-minute delay to detect link quality changes, but farm environment changes slowly - Result: Network operates 18 months on solar + 5000mAh battery vs 8 months with hop count
Key Insight: Don’t just accept default hop count! IoT networks with wireless links benefit significantly from link-quality-aware metrics like ETX, with proper tuning to balance responsiveness and energy consumption.