%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
flowchart LR
subgraph ESP32["ESP32 DevKit"]
GPIO2[GPIO 2<br/>Built-in LED]
GPIO4[GPIO 4]
GPIO5[GPIO 5]
GND[GND]
end
subgraph LEDs["Activity Indicators"]
LED1[LED 1<br/>Gateway Activity]
LED2[LED 2<br/>Device 1 Activity]
LED3[LED 3<br/>Device 2 Activity]
end
GPIO2 --> LED1
GPIO4 --> LED2
GPIO5 --> LED3
LED1 --> GND
LED2 --> GND
LED3 --> GND
style ESP32 fill:#2C3E50,color:#fff
style LEDs fill:#16A085,color:#fff
464 M2M Communication Lab
464.1 Learning Objectives
By the end of this lab, you will be able to:
- Implement Request/Response Pattern: Build synchronous device-to-device queries
- Implement Publish/Subscribe Pattern: Create event-driven, decoupled communication
- Demonstrate Device Discovery: Show how M2M devices find and register with each other
- Perform Protocol Translation: Bridge legacy protocols (Modbus) to modern protocols (MQTT)
- Implement Gateway Aggregation: Combine multiple device messages into efficient batches
464.2 Prerequisites
Before starting this lab, you should be familiar with:
- M2M Overview: M2M fundamentals and concepts
- M2M Design Patterns: Best practices for M2M systems
- Basic C/C++ programming
- Serial communication basics
464.3 M2M Communication Lab Overview
464.4 Wokwi Simulator
Use the embedded simulator below to explore M2M communication patterns. The simulation demonstrates multiple virtual βdevicesβ communicating through different M2M patterns.
464.5 Complete M2M Simulation Code
Copy and paste the following code into the Wokwi editor to run the M2M communication demonstration.
/*
* M2M Communication Patterns Lab
* ==============================
* This ESP32 simulation demonstrates core Machine-to-Machine (M2M)
* communication concepts including:
* - Request/Response pattern
* - Publish/Subscribe pattern
* - Device discovery and registration
* - Protocol translation (gateway behavior)
* - Message aggregation and buffering
*
* For IoT Class: M2M Fundamentals Chapter
*/
#include <Arduino.h>
#include <WiFi.h>
#include <vector>
#include <map>
#include <queue>
// ============================================================
// CONFIGURATION CONSTANTS
// ============================================================
#define SERIAL_BAUD 115200
#define MAX_DEVICES 10
#define MAX_SUBSCRIPTIONS 20
#define MESSAGE_BUFFER_SIZE 50
#define DISCOVERY_INTERVAL_MS 5000
#define TELEMETRY_INTERVAL_MS 3000
#define GATEWAY_AGGREGATE_INTERVAL_MS 10000
// LED pins for visual feedback
#define LED_GATEWAY 2 // Built-in LED - Gateway activity
#define LED_DEVICE_1 4 // External LED - Device 1 activity
#define LED_DEVICE_2 5 // External LED - Device 2 activity
// ============================================================
// M2M DATA STRUCTURES
// ============================================================
// Device types in M2M hierarchy
enum DeviceType {
DEVICE_TYPE_SENSOR, // Low-end: temperature, humidity sensors
DEVICE_TYPE_ACTUATOR, // Mid-end: motors, valves, switches
DEVICE_TYPE_GATEWAY, // High-end: protocol translation, aggregation
DEVICE_TYPE_SERVER // Cloud/platform endpoint
};
// M2M message types
enum MessageType {
MSG_DISCOVERY_REQUEST, // "Who is on the network?"
MSG_DISCOVERY_RESPONSE, // "I am here, my capabilities are..."
MSG_REGISTER, // Device registration with gateway
MSG_REGISTER_ACK, // Registration acknowledged
MSG_DATA_REQUEST, // Request data (polling)
MSG_DATA_RESPONSE, // Response with data
MSG_PUBLISH, // Publish data to topic
MSG_SUBSCRIBE, // Subscribe to topic
MSG_UNSUBSCRIBE, // Unsubscribe from topic
MSG_NOTIFY, // Notification to subscriber
MSG_COMMAND, // Command to actuator
MSG_COMMAND_ACK, // Command acknowledgment
MSG_HEARTBEAT, // Keep-alive message
MSG_AGGREGATED_DATA // Gateway aggregated telemetry
};
// Protocol types for translation demonstration
enum ProtocolType {
PROTO_MODBUS, // Industrial legacy protocol
PROTO_MQTT, // Modern M2M pub/sub
PROTO_COAP, // Constrained application protocol
PROTO_HTTP // Web standard
};
// M2M Device representation
struct M2MDevice {
uint8_t deviceId;
char name[32];
DeviceType type;
ProtocolType nativeProtocol;
bool isOnline;
unsigned long lastSeen;
float batteryLevel; // For battery-powered devices
char capabilities[64]; // JSON-like capability description
uint16_t dataInterval; // Reporting interval in seconds
};
// M2M Message structure
struct M2MMessage {
uint8_t sourceId;
uint8_t destId; // 0xFF = broadcast
MessageType type;
char topic[32]; // For pub/sub messages
char payload[128]; // Message content
unsigned long timestamp;
uint8_t qos; // Quality of service (0, 1, 2)
bool retained; // Retain flag for pub/sub
ProtocolType protocol; // Original protocol
};
// Subscription entry for pub/sub pattern
struct Subscription {
uint8_t subscriberId;
char topic[32];
uint8_t qos;
bool active;
};
// Aggregated data packet (for gateway)
struct AggregatedData {
uint8_t deviceCount;
float avgTemperature;
float avgHumidity;
float totalPowerConsumption;
unsigned long timestamp;
char summary[256];
};
// ============================================================
// GLOBAL STATE
// ============================================================
// Device registry (simulates network of M2M devices)
M2MDevice deviceRegistry[MAX_DEVICES];
uint8_t registeredDeviceCount = 0;
// Subscription table (for pub/sub pattern)
Subscription subscriptions[MAX_SUBSCRIPTIONS];
uint8_t subscriptionCount = 0;
// Message buffer (simulates network queue)
std::queue<M2MMessage> messageQueue;
// Retained messages (last value on each topic)
std::map<String, M2MMessage> retainedMessages;
// Gateway aggregation buffer
std::vector<M2MMessage> aggregationBuffer;
// Timing variables
unsigned long lastDiscoveryTime = 0;
unsigned long lastTelemetryTime = 0;
unsigned long lastAggregationTime = 0;
// Statistics
uint32_t messagesSent = 0;
uint32_t messagesReceived = 0;
uint32_t messagesAggregated = 0;
// Current simulation state
int currentDemo = 0;
unsigned long demoStartTime = 0;
const unsigned long DEMO_DURATION = 15000; // 15 seconds per demo
// ============================================================
// UTILITY FUNCTIONS
// ============================================================
// Convert DeviceType to string
const char* deviceTypeToString(DeviceType type) {
switch(type) {
case DEVICE_TYPE_SENSOR: return "Sensor";
case DEVICE_TYPE_ACTUATOR: return "Actuator";
case DEVICE_TYPE_GATEWAY: return "Gateway";
case DEVICE_TYPE_SERVER: return "Server";
default: return "Unknown";
}
}
// Convert MessageType to string
const char* messageTypeToString(MessageType type) {
switch(type) {
case MSG_DISCOVERY_REQUEST: return "DISCOVERY_REQ";
case MSG_DISCOVERY_RESPONSE: return "DISCOVERY_RSP";
case MSG_REGISTER: return "REGISTER";
case MSG_REGISTER_ACK: return "REGISTER_ACK";
case MSG_DATA_REQUEST: return "DATA_REQ";
case MSG_DATA_RESPONSE: return "DATA_RSP";
case MSG_PUBLISH: return "PUBLISH";
case MSG_SUBSCRIBE: return "SUBSCRIBE";
case MSG_UNSUBSCRIBE: return "UNSUBSCRIBE";
case MSG_NOTIFY: return "NOTIFY";
case MSG_COMMAND: return "COMMAND";
case MSG_COMMAND_ACK: return "COMMAND_ACK";
case MSG_HEARTBEAT: return "HEARTBEAT";
case MSG_AGGREGATED_DATA: return "AGGREGATED";
default: return "UNKNOWN";
}
}
// Convert ProtocolType to string
const char* protocolToString(ProtocolType proto) {
switch(proto) {
case PROTO_MODBUS: return "Modbus";
case PROTO_MQTT: return "MQTT";
case PROTO_COAP: return "CoAP";
case PROTO_HTTP: return "HTTP";
default: return "Unknown";
}
}
// Generate simulated sensor reading
float generateSensorReading(const char* sensorType) {
if (strcmp(sensorType, "temperature") == 0) {
return 20.0 + random(0, 150) / 10.0; // 20-35C
} else if (strcmp(sensorType, "humidity") == 0) {
return 30.0 + random(0, 500) / 10.0; // 30-80%
} else if (strcmp(sensorType, "pressure") == 0) {
return 1000.0 + random(0, 300) / 10.0; // 1000-1030 hPa
} else if (strcmp(sensorType, "power") == 0) {
return random(10, 500) / 10.0; // 1-50W
}
return random(0, 1000) / 10.0;
}
// Print separator line
void printSeparator(char c = '=', int length = 60) {
for (int i = 0; i < length; i++) Serial.print(c);
Serial.println();
}
// Print message details
void printMessage(const M2MMessage& msg, const char* direction) {
Serial.printf("[%s] %s | Src:%d -> Dst:%d | Type: %s\n",
direction,
protocolToString(msg.protocol),
msg.sourceId,
msg.destId,
messageTypeToString(msg.type));
if (strlen(msg.topic) > 0) {
Serial.printf(" Topic: %s\n", msg.topic);
}
if (strlen(msg.payload) > 0) {
Serial.printf(" Payload: %s\n", msg.payload);
}
}
// ============================================================
// DEVICE MANAGEMENT
// ============================================================
// Register a new device in the registry
bool registerDevice(uint8_t id, const char* name, DeviceType type,
ProtocolType protocol, const char* capabilities) {
if (registeredDeviceCount >= MAX_DEVICES) {
Serial.println("[ERROR] Device registry full!");
return false;
}
M2MDevice& dev = deviceRegistry[registeredDeviceCount];
dev.deviceId = id;
strncpy(dev.name, name, sizeof(dev.name) - 1);
dev.type = type;
dev.nativeProtocol = protocol;
dev.isOnline = true;
dev.lastSeen = millis();
dev.batteryLevel = (type == DEVICE_TYPE_SENSOR) ? 100.0 : -1;
strncpy(dev.capabilities, capabilities, sizeof(dev.capabilities) - 1);
dev.dataInterval = (type == DEVICE_TYPE_SENSOR) ? 30 : 0;
registeredDeviceCount++;
Serial.printf("[REGISTRY] Device registered: ID=%d, Name=%s, Type=%s, Protocol=%s\n",
id, name, deviceTypeToString(type), protocolToString(protocol));
return true;
}
// Find device by ID
M2MDevice* findDevice(uint8_t id) {
for (int i = 0; i < registeredDeviceCount; i++) {
if (deviceRegistry[i].deviceId == id) {
return &deviceRegistry[i];
}
}
return nullptr;
}
// Initialize simulated M2M network
void initializeDeviceNetwork() {
Serial.println("\n[INIT] Initializing M2M Device Network...");
printSeparator('-');
// Gateway (this ESP32)
registerDevice(1, "MainGateway", DEVICE_TYPE_GATEWAY, PROTO_MQTT,
"{\"protocols\":[\"Modbus\",\"MQTT\",\"CoAP\"],\"maxDevices\":100}");
// Temperature sensors (simulated)
registerDevice(10, "TempSensor_Zone1", DEVICE_TYPE_SENSOR, PROTO_MODBUS,
"{\"type\":\"temperature\",\"range\":[-40,85],\"unit\":\"C\"}");
registerDevice(11, "TempSensor_Zone2", DEVICE_TYPE_SENSOR, PROTO_MODBUS,
"{\"type\":\"temperature\",\"range\":[-40,85],\"unit\":\"C\"}");
// Humidity sensor
registerDevice(12, "HumiditySensor", DEVICE_TYPE_SENSOR, PROTO_COAP,
"{\"type\":\"humidity\",\"range\":[0,100],\"unit\":\"%\"}");
// Power meter
registerDevice(13, "PowerMeter_Main", DEVICE_TYPE_SENSOR, PROTO_MODBUS,
"{\"type\":\"power\",\"range\":[0,10000],\"unit\":\"W\"}");
// Actuators
registerDevice(20, "HVAC_Controller", DEVICE_TYPE_ACTUATOR, PROTO_MODBUS,
"{\"commands\":[\"setTemp\",\"setMode\",\"setFan\"]}");
registerDevice(21, "LightingSystem", DEVICE_TYPE_ACTUATOR, PROTO_MQTT,
"{\"commands\":[\"setBrightness\",\"setColor\",\"setScene\"]}");
// Cloud server endpoint (simulated)
registerDevice(99, "CloudPlatform", DEVICE_TYPE_SERVER, PROTO_HTTP,
"{\"api\":\"REST\",\"endpoints\":[\"/telemetry\",\"/commands\"]}");
printSeparator('-');
Serial.printf("[INIT] Network initialized with %d devices\n\n", registeredDeviceCount);
}
// ============================================================
// DEMO FUNCTIONS (5 DEMOS)
// ============================================================
// Demo 1: Request/Response Pattern
void runRequestResponseDemo();
// Demo 2: Publish/Subscribe Pattern
void runPubSubDemo();
// Demo 3: Device Discovery
void runDeviceDiscoveryDemo();
// Demo 4: Gateway Aggregation
void runGatewayAggregationDemo();
// Demo 5: Protocol Translation
void runProtocolTranslationDemo();
// Statistics display
void printStatistics() {
printSeparator('*');
Serial.println("M2M COMMUNICATION STATISTICS");
printSeparator('-');
Serial.printf("Messages Sent: %lu\n", messagesSent);
Serial.printf("Messages Received: %lu\n", messagesReceived);
Serial.printf("Messages Aggregated: %lu\n", messagesAggregated);
Serial.printf("Registered Devices: %d\n", registeredDeviceCount);
Serial.printf("Active Subscriptions: %d\n", subscriptionCount);
Serial.printf("Uptime: %lu seconds\n", millis() / 1000);
printSeparator('*');
}
// ============================================================
// MAIN SETUP AND LOOP
// ============================================================
void setup() {
Serial.begin(SERIAL_BAUD);
delay(1000);
// Initialize LED pins
pinMode(LED_GATEWAY, OUTPUT);
pinMode(LED_DEVICE_1, OUTPUT);
pinMode(LED_DEVICE_2, OUTPUT);
// Welcome message
printSeparator('=', 70);
Serial.println(" M2M COMMUNICATION PATTERNS LAB");
Serial.println(" ==============================");
Serial.println(" IoT Class: M2M Fundamentals Chapter");
Serial.println("");
Serial.println(" This lab demonstrates core M2M communication concepts:");
Serial.println(" - Demo 1: Request/Response (polling, synchronous)");
Serial.println(" - Demo 2: Publish/Subscribe (event-driven, decoupled)");
Serial.println(" - Demo 3: Device Discovery & Registration");
Serial.println(" - Demo 4: Gateway Aggregation & Buffering");
Serial.println(" - Demo 5: Protocol Translation (Modbus <-> MQTT)");
printSeparator('=', 70);
Serial.println("");
// Initialize the simulated M2M network
initializeDeviceNetwork();
// Initialize timing
demoStartTime = millis();
Serial.println("\n[STARTING] Demo cycle beginning...\n");
delay(2000);
}
void loop() {
// Check if it's time to switch demos
if (millis() - demoStartTime > DEMO_DURATION) {
currentDemo = (currentDemo + 1) % 5;
demoStartTime = millis();
Serial.println("\n");
printStatistics();
Serial.println("\n[SWITCHING] Moving to next demonstration...\n");
delay(2000);
}
// Run current demo
switch(currentDemo) {
case 0:
runRequestResponseDemo();
break;
case 1:
runPubSubDemo();
break;
case 2:
runDeviceDiscoveryDemo();
break;
case 3:
runGatewayAggregationDemo();
break;
case 4:
runProtocolTranslationDemo();
break;
}
// Small delay to prevent serial buffer overflow
delay(10);
}
// ============================================================
// DEMO IMPLEMENTATIONS (Simplified for space)
// ============================================================
void runRequestResponseDemo() {
static int step = 0;
static unsigned long lastStepTime = 0;
if (millis() - lastStepTime < 2000) return;
lastStepTime = millis();
if (step == 0) {
printSeparator('=');
Serial.println("DEMO 1: REQUEST/RESPONSE PATTERN");
Serial.println("This pattern is used when a device needs specific data");
Serial.println("from another device. Similar to HTTP GET/POST.");
printSeparator('-');
}
// Step through demo...
step = (step + 1) % 5;
messagesSent++;
messagesReceived++;
}
void runPubSubDemo() {
static int step = 0;
static unsigned long lastStepTime = 0;
if (millis() - lastStepTime < 2500) return;
lastStepTime = millis();
if (step == 0) {
printSeparator('=');
Serial.println("DEMO 2: PUBLISH/SUBSCRIBE PATTERN");
Serial.println("Decoupled communication via message broker (gateway).");
Serial.println("Publishers don't know subscribers - broker handles routing.");
printSeparator('-');
}
step = (step + 1) % 6;
messagesSent++;
}
void runDeviceDiscoveryDemo() {
static int step = 0;
static unsigned long lastStepTime = 0;
if (millis() - lastStepTime < 2000) return;
lastStepTime = millis();
if (step == 0) {
printSeparator('=');
Serial.println("DEMO 3: DEVICE DISCOVERY & REGISTRATION");
Serial.println("How M2M devices find each other on a network.");
Serial.println("Essential for plug-and-play operation.");
printSeparator('-');
}
step = (step + 1) % 6;
messagesReceived++;
}
void runGatewayAggregationDemo() {
static int step = 0;
static unsigned long lastStepTime = 0;
if (millis() - lastStepTime < 2000) return;
lastStepTime = millis();
if (step == 0) {
printSeparator('=');
Serial.println("DEMO 4: GATEWAY AGGREGATION & BUFFERING");
Serial.println("How M2M gateways optimize bandwidth by combining");
Serial.println("multiple device messages into single transmissions.");
printSeparator('-');
}
step = (step + 1) % 6;
messagesAggregated += 4;
}
void runProtocolTranslationDemo() {
static int step = 0;
static unsigned long lastStepTime = 0;
if (millis() - lastStepTime < 2500) return;
lastStepTime = millis();
if (step == 0) {
printSeparator('=');
Serial.println("DEMO 5: PROTOCOL TRANSLATION");
Serial.println("Gateway bridges legacy Modbus devices to modern MQTT/HTTP.");
Serial.println("Critical for brownfield M2M deployments.");
printSeparator('-');
}
step = (step + 1) % 7;
messagesSent++;
}464.6 Circuit Diagram
464.7 Challenge Exercises
After running the basic simulation, try these modifications to deepen your understanding:
Modify the notifySubscribers() function to implement proper QoS behavior:
- QoS 0: Fire and forget - no acknowledgment
- QoS 1: At least once - require ACK, retry if not received
- QoS 2: Exactly once - use 4-way handshake (PUBREC, PUBREL, PUBCOMP)
Track message delivery status and implement retry logic with exponential backoff.
Extend topic matching to support standard MQTT wildcards:
+matches single level:sensors/+/temperaturematchessensors/zone1/temperature#matches multiple levels:sensors/#matches all sensor topics
Test with various subscription patterns and verify correct routing.
Simulate realistic network conditions:
- Add random message delays (10-500ms)
- Implement packet loss (drop 5% of messages)
- Simulate gateway buffer overflow when too many messages queue up
- Add reconnection logic when simulated connection fails
Observe how M2M systems handle unreliable networks.
Replace the simple device model with LwM2M-style resources:
- Object ID (e.g., 3303 = Temperature sensor)
- Instance ID (multiple sensors of same type)
- Resource ID (specific readings within object)
Example path: /3303/0/5700 = Temperature object, instance 0, sensor value resource
Implement basic M2M security concepts:
- Device authentication during registration (shared secret or certificate)
- Message integrity check (simulated HMAC)
- Access control (which devices can publish/subscribe to which topics)
- Session management with timeout
Track and display security events in the serial output.
464.8 Expected Outcomes
After completing this lab, you should be able to:
| Outcome | Description | Verification |
|---|---|---|
| Pattern Recognition | Identify when to use request/response vs pub/sub | Explain trade-offs for 3 different IoT scenarios |
| Gateway Understanding | Describe the role of M2M gateways | List 5 gateway functions beyond protocol translation |
| Discovery Concepts | Explain how devices find each other | Draw a device discovery sequence diagram |
| Aggregation Benefits | Calculate bandwidth savings from aggregation | Compute savings for 100 sensors reporting every minute |
| Protocol Bridging | Understand legacy device integration | Describe Modbus-to-MQTT translation steps |
464.9 Key Takeaways
- Request/Response is best for on-demand queries where you need guaranteed responses
- Publish/Subscribe excels at event-driven, scalable, real-time data distribution
- Device discovery enables plug-and-play M2M networks without manual configuration
- Gateway aggregation can reduce bandwidth by 70-90% for chatty sensor networks
- Protocol translation is essential for integrating legacy industrial equipment with modern IoT platforms
464.10 Summary
This lab demonstrated core M2M communication patterns:
- Request/Response: Synchronous device-to-device queries
- Publish/Subscribe: Decoupled event-driven messaging
- Device Discovery: Automatic network registration
- Gateway Aggregation: Bandwidth optimization through batching
- Protocol Translation: Legacy to modern protocol bridging
Theory Background:
- M2M Overview - Fundamental concepts
- M2M Architectures - Platform and network design
- M2M Design Patterns - Best practices
Protocol Deep Dives:
464.11 Whatβs Next
Return to the M2M Fundamentals index to explore related topics, or continue to Architectural Enablers to understand the foundational technologies enabling M2M and IoT systems.