Construct OOP sensor libraries using inheritance, polymorphism, and encapsulation for extensible multi-sensor systems
Build functional data pipelines that process sensor data through composable pure-function transformations
Configure professional workflows with Git branching strategies, PlatformIO, and CI/CD pipelines
Integrate multiple paradigms in complete, working IoT applications that combine OOP, functional, and event-driven code
For Beginners: IoT Programming Examples
Prototyping is building rough, working versions of your IoT device to test ideas quickly and cheaply. Think of it like building a model airplane before constructing the real thing – a prototype reveals problems when they are still easy and inexpensive to fix. Modern prototyping tools make it possible to go from idea to working device in days rather than months.
Sensor Squad: Code in Action!
“Let me show you how object-oriented programming works for sensors,” said Max the Microcontroller. “You create a Sensor class with properties like name, pin, and readInterval. Then you create instances – temperatureSensor, humiditySensor – each with their own settings. When you want a new sensor, just create another instance. No copying code!”
Sammy the Sensor demonstrated functional programming: “Imagine a data pipeline: raw reading goes in, gets filtered to remove noise, converted to the right units, checked against thresholds, and packaged for transmission. Each step is a pure function that takes input and produces output. Clean, testable, and easy to debug!”
Lila the LED showed the professional workflow: “Use Git branches for features, PlatformIO for building and uploading, and a CI pipeline that automatically tests your code when you push changes. It sounds like a lot of setup, but once it is running, it catches bugs before they reach the hardware!” Bella the Battery concluded, “Real code examples teach you more than theory. Follow along, modify the examples, and build something of your own!”
6.2 Prerequisites
Before diving into this chapter, you should be familiar with:
Programming Paradigms: Understanding of OOP, functional, and event-driven approaches
Best Practices: Guidelines for paradigm and tool selection
Interactive: ESP32 Wi-Fi Connection State Machine
6.3 Introduction
This chapter demonstrates how different programming paradigms are applied in real Arduino/ESP32 projects. Each example is complete and runnable, helping you choose the right approach for your IoT applications.
6.4 Example 1: Object-Oriented Sensor Library Architecture
Scenario
Building a reusable, extensible sensor library using OOP principles for a multi-sensor IoT device.
Extensibility: Add new sensor types by inheriting from Sensor
Polymorphism: Treat all sensors uniformly through base class interface
Maintainability: Each sensor encapsulates its own logic
Reusability: Sensor classes can be used in multiple projects
Testability: Easy to mock sensors for testing
How to extend:
Create new sensor class inheriting from Sensor
Implement begin(), read(), and getUnit()
Add sensor to manager: sensorMgr.addSensor(new YourSensor(...))
Try It: OOP Class Hierarchy Explorer
Explore how the Sensor class hierarchy works by configuring different sensor types. See how polymorphism lets the SensorManager treat all sensors uniformly while each subclass provides its own implementation.
Predictability: Pure functions always return same output for same input
Testability: Easy to unit test without mocking hardware
Reliability: No hidden side effects or state mutations
Composability: Build complex pipelines from simple functions
Debugging: Easier to trace data transformations
Real-world applications:
Edge analytics and data preprocessing
Sensor fusion algorithms
Digital signal processing
Machine learning feature engineering
Data quality validation
Try It: Sensor Data Filter Explorer
Experiment with different filter algorithms applied to noisy sensor data. Adjust the noise level, window size, and EMA smoothing factor to see how each filter cleans the signal differently.
name: PlatformIO CIon:push:branches:[ main, develop ]pull_request:branches:[ main ]jobs:build:runs-on: ubuntu-lateststrategy:matrix:environment:[esp32_dev, esp32_prod]steps:-uses: actions/checkout@v3-name: Cache PlatformIOuses: actions/cache@v3with: path: | ~/.platformio .piokey: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }}-name: Set up Pythonuses: actions/setup-python@v4with:python-version:'3.9'-name: Install PlatformIO Corerun: pip install --upgrade platformio-name: Build firmwarerun: pio run -e ${{ matrix.environment }}-name: Run testsrun: pio test -e native-name: Upload firmware artifactsuses: actions/upload-artifact@v3with:name: firmware-${{ matrix.environment }}path: .pio/build/${{ matrix.environment }}/firmware.binretention-days:30release:needs: buildruns-on: ubuntu-latestif: startsWith(github.ref, 'refs/tags/v')steps:-uses: actions/checkout@v3-name: Create Releaseuses: softprops/action-gh-release@v1with: files: | .pio/build/*/firmware.bindraft:falseprerelease:false
Git Branching Strategy:
# Initialize repositorygit initgit add .git commit -m"Initial commit: Weather station firmware"# Create develop branch for active developmentgit checkout -b develop# Feature development workflowgit checkout -b feature/mqtt-reconnect# ... make changes ...git add src/mqtt_client.cppgit commit -m"Add MQTT reconnection logic with exponential backoff"git push origin feature/mqtt-reconnect# Create pull request for code review# After approval, merge to developgit checkout developgit merge feature/mqtt-reconnectgit push origin develop# Release workflowgit checkout maingit merge developgit tag -a v1.0.0 -m"Release version 1.0.0"git push origin main --tags# Hotfix workflowgit checkout -b hotfix/sensor-timeout main# ... fix critical bug ...git commit -m"Fix sensor timeout causing device crash"git checkout maingit merge hotfix/sensor-timeoutgit tag v1.0.1git checkout developgit merge hotfix/sensor-timeout
Professional Git commit messages:
# Good commit messages follow this format:# <type>(<scope>): <subject>## Types: feat, fix, docs, style, refactor, test, choregit commit -m"feat(mqtt): add QoS 1 support with message acknowledgment"git commit -m"fix(sensor): correct BME280 I2C address detection"git commit -m"refactor(power): extract sleep logic into PowerManager class"git commit -m"docs(readme): add wiring diagram and setup instructions"git commit -m"test(wifi): add unit tests for connection retry logic"
Using PlatformIO CLI:
# Build for developmentpio run -e esp32_dev# Upload to devicepio run -e esp32_dev -t upload# Monitor serial outputpio device monitor# Run unit testspio test# Clean buildpio run -t clean# Update librariespio pkg update# Build and upload in one commandpio run -e esp32_dev -t upload &&pio device monitor# OTA update to remote devicepio run -e esp32_ota -t upload
A team of 5 developers can work on same codebase without conflicts
Automated builds run on every commit, catching integration issues early
Firmware versions are traceable to specific git commits
Production secrets never committed to repository
OTA updates deployed with confidence after CI tests pass
Try It: CI/CD Pipeline Timing Simulator
Configure your project’s CI/CD pipeline and see how build times, test coverage, and team size affect total deployment time. Compare manual vs automated workflows.
Show code
viewof ciTeamSize = Inputs.range([1,20], {value:5,step:1,label:"Team size (developers)"})viewof ciBuildTime = Inputs.range([10,120], {value:45,step:5,label:"Build time (seconds)"})viewof ciTestCount = Inputs.range([0,200], {value:50,step:10,label:"Number of unit tests"})viewof ciDailyCommits = Inputs.range([1,30], {value:8,step:1,label:"Daily commits (team total)"})viewof ciUseOTA = Inputs.checkbox(["Enable OTA deployment"], {value: ["Enable OTA deployment"]})
Event-driven programming enables responsive, low-power IoT systems by reacting to events rather than continuously polling, ideal for battery-powered devices.
Threaded Execution Model
Threaded Example
Multi-threaded programming using an RTOS enables complex IoT applications with concurrent tasks, proper resource sharing, and predictable timing behavior.
Interrupt Handling Architecture
Interrupt Handling
Proper interrupt handling is fundamental to responsive embedded systems, enabling immediate response to hardware events while maintaining code reliability.
Worked Example: Migrating from Arduino IDE to PlatformIO with CI/CD
Scenario: A hobbyist project (ESP32 weather station) is becoming a product. Need professional workflow: version control, automated testing, multiple deployment environments.
Starting Point: Arduino IDE Project
weather_station/
├── weather_station.ino (all code in one file, 800 lines)
└── config.h (hardcoded WiFi credentials)
Common Mistake: Global State in Multi-File Projects
The Mistake: A developer splits a large .ino file into multiple .cpp files, using global variables for shared state. Random bugs appear when compiling, and the project becomes unmaintainable.
Bad Code:
main.cpp:
#include "sensor.h"#include "network.h"float temperature =0.0;// Global variablevoid setup(){ initSensor(); initNetwork();}void loop(){ updateSensor(); publishData();}
sensor.cpp:
externfloat temperature;// Declare extern to access globalvoid updateSensor(){ temperature = readDHT22();// Modifies global}
class NetworkPublisher {public:void publish(float value){ mqtt.publish("temp", String(value));}};
main.cpp:
#include "sensor.h"#include "network.h"TemperatureSensor tempSensor;// Owned by mainNetworkPublisher publisher;void setup(){// No globals to initialize}void loop(){ tempSensor.update();float temp = tempSensor.getTemperature();// Explicit data flow publisher.publish(temp);}
Benefits:
✅ Clear Ownership:main.cpp owns tempSensor ✅ Testable: Can create isolated TemperatureSensor instance in tests ✅ Thread-Safe: Each object instance is independent ✅ No Name Collisions: State is scoped to class ✅ Explicit Dependencies: Function parameters show data flow
Alternative: Dependency Injection
class NetworkPublisher {public:void publish(const TemperatureSensor& sensor){// DI via parameterfloat temp = sensor.getTemperature(); mqtt.publish("temp", String(temp));}};void loop(){ tempSensor.update(); publisher.publish(tempSensor);// Inject dependency}
Key Takeaway: Minimize globals. Use classes for encapsulation, parameters for data flow.
Match the Code Pattern to Its Paradigm
Order the OOP Sensor Library Design Steps
Common Pitfalls
1. Copying Code Without Understanding Timing Assumptions
Example code often uses fixed delays calibrated for one microcontroller clock speed that produce wrong timings on a different clock. Copying without checking can cause sensors to receive malformed I2C timing. Parameterise timing values from the clock speed constant and verify with a logic analyser on the actual target hardware.
2. Using Blocking Read Functions in Event-Driven Architectures
Calling blocking sensor read functions within an MQTT callback can stall the network stack long enough for a watchdog reset or missed keep-alive. Trigger sensor reads from a timer, cache the latest value, and return cached data from any function called within a network callback.
3. Not Handling Partial Reads on Serial Interfaces
Assuming a serial read() always returns a complete packet causes firmware to process partial payloads as valid data when bytes arrive in multiple TCP segments. Implement a length-prefixed or delimiter-terminated framing protocol and accumulate bytes into a ring buffer until a complete frame is received.
Label the Diagram
6.8 Summary
OOP Sensor Libraries provide extensible, maintainable sensor management through inheritance, polymorphism, and encapsulation—ideal for multi-sensor systems
Functional Data Pipelines enable predictable, testable data processing through pure functions, immutable data, and composition—perfect for edge analytics
Professional Git Workflows with feature branches, pull requests, and semantic versioning enable team collaboration and traceable firmware releases
PlatformIO Configuration supports multi-environment builds (dev/prod/test/OTA) with locked dependencies and CI/CD integration
Combining Paradigms is the norm—use OOP for structure, functional for data processing, event-driven for responsiveness
CI/CD Automation catches bugs early, ensures consistent builds, and enables confident OTA deployments
This chapter covers iot programming examples, explaining the core concepts, practical design decisions, and common pitfalls that IoT practitioners need to build effective, reliable connected systems.