21  Libraries & Version Control

21.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Use Sensor Libraries: Integrate Adafruit Unified Sensor and similar libraries for standardized sensor access
  • Implement Communication Protocols: Add Wi-Fi, MQTT, and HTTP connectivity using established libraries
  • Manage Dependencies: Track library versions and handle dependency conflicts
  • Apply Version Control: Use Git effectively for IoT firmware projects

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.

“Libraries are pre-written code that saves you weeks of work!” said Max the Microcontroller enthusiastically. “Instead of writing 200 lines of code to talk to a temperature sensor, you install the Adafruit DHT library and it is just three lines. Someone already solved that problem for you.”

Sammy the Sensor appreciated libraries most of all. “The Adafruit Unified Sensor library gives every sensor the same interface. Whether I am a temperature sensor, a humidity sensor, or an accelerometer, the code to read me looks the same. It is like having a universal remote for sensors!”

Lila the LED added, “And communication libraries handle the hard networking stuff. The PubSubClient library manages MQTT connections, WiFiManager handles Wi-Fi setup with a nice configuration portal, and ArduinoJson parses JSON data. You do not need to understand every protocol detail – the library handles it.” Bella the Battery brought up version control. “Always use Git to track your code changes! If a library update breaks something, you can roll back to the working version. And lock your library versions in platformio.ini so your project builds the same way every time. There is nothing worse than a build that worked yesterday but fails today because a library silently updated.”

Key Concepts

  • IoT Architecture: Layered model comprising perception, network, and application tiers defining how sensors, gateways, and cloud services interact.
  • Sensor: Device converting a physical phenomenon (temperature, pressure, light) into a measurable electrical signal.
  • Connectivity: Wireless or wired communication link transmitting sensor data from device to gateway or cloud platform.
  • Edge Computing: Processing data close to the sensor to reduce latency, bandwidth, and cloud dependency.
  • Security: Set of measures protecting IoT devices, communications, and data from unauthorised access and tampering.
  • Scalability: System property ensuring performance remains acceptable as device count grows from prototype to mass deployment.
  • Interoperability: Ability of devices from different vendors to exchange and use information without special configuration.

21.2 Prerequisites

Before diving into this chapter, you should be familiar with:

In 60 Seconds

This chapter covers libraries & version control, explaining the core concepts, practical design decisions, and common pitfalls that IoT practitioners need to build effective, reliable connected systems.


21.3 Sensor Libraries

Adafruit Unified Sensor Library: Standardized interface for various sensors.

Example:

#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

DHT_Unified dht(DHTPIN, DHT22);

void setup() {
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
}

void loop() {
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  Serial.print("Temp: ");
  Serial.println(event.temperature);
}

Benefits:

  • Consistent API across sensors
  • Easy to swap sensors
  • Well-documented and tested

Popular Sensor Libraries:

Sensor Type Library Sensors Supported
Environmental Adafruit BME280 Temperature, humidity, pressure
Motion Adafruit MPU6050 Accelerometer, gyroscope
Light Adafruit TSL2591 Lux measurement
Distance NewPing Ultrasonic sensors
GPS TinyGPS++ NMEA parsing
Try It: Unified Sensor Library Explorer

Explore how the Adafruit Unified Sensor Library provides a consistent API across different sensor types. Select a sensor to see how each one implements the same standardized interface.

21.4 Communication Libraries

21.4.1 Wi-Fi

#include <WiFi.h>

void setup() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  Serial.println(WiFi.localIP());
}

21.4.2 MQTT

#include <PubSubClient.h>

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length) {
  // Handle incoming messages
}

void setup() {
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // Publish data
  client.publish("sensors/temperature", "25.5");
}

Library Selection Impact on Flash Budget: ESP32 with 4MB flash partitioned as 1.5MB app, 1.5MB OTA, 1MB file system:

Baseline firmware (no libraries): 180KB Available for app logic: \(1,500KB - 180KB = 1,320KB\)

Adding MQTT library options:

  • PubSubClient (minimal): +12KB (1,308KB remaining, 99% free)
  • Mosquitto-ESP32 (full-featured): +187KB (1,133KB remaining, 86% free)
  • AWS IoT SDK: +420KB (900KB remaining, 68% free)

Adding TLS/SSL:

  • mbedTLS (required for secure MQTT): +280KB

Total with secure AWS IoT: \(180 + 420 + 280 = 880KB\) (59% flash used)

Heavy libraries leave less room for app features and future OTA updates (which need space for both old and new firmware). Choose minimal libraries for memory-constrained devices.

21.4.3 HTTP/REST

#include <HTTPClient.h>

void sendData() {
  HTTPClient http;
  http.begin("http://api.example.com/data");
  http.addHeader("Content-Type", "application/json");

  int httpCode = http.POST("{\"sensor\":\"temp\",\"value\":25.5}");

  if (httpCode > 0) {
    String response = http.getString();
    Serial.println(response);
  }

  http.end();
}

21.4.4 WebSocket

#include <WebSocketsClient.h>

WebSocketsClient webSocket;

void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
  switch(type) {
    case WStype_CONNECTED:
      webSocket.sendTXT("Hello Server");
      break;
    case WStype_TEXT:
      Serial.printf("Received: %s\n", payload);
      break;
  }
}

void setup() {
  webSocket.begin("server.com", 80, "/ws");
  webSocket.onEvent(webSocketEvent);
}

void loop() {
  webSocket.loop();
}
Try It: Communication Protocol Comparison

Compare IoT communication protocols across key dimensions. Adjust the importance weights to see which protocol best fits your project requirements.

21.5 Display Libraries

OLED Displays:

#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("Hello IoT!");
  display.display();
}

TFT Displays:

#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();

void setup() {
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE);
  tft.drawString("Sensor Data", 10, 10);
}

21.6 Cloud Integration Libraries

AWS IoT:

#include <AWS_IOT.h>

AWS_IOT aws;

void setup() {
  aws.connect(HOST, CLIENT_ID);
}

void loop() {
  if (aws.publish(TOPIC, "{\"temp\":25.5}")) {
    Serial.println("Published");
  }
}

Azure IoT Hub:

#include <AzureIoTHub.h>
#include <AzureIoTProtocol_MQTT.h>

void sendMessage() {
  IOTHUB_MESSAGE_HANDLE messageHandle =
    IoTHubMessage_CreateFromString(message);
  IoTHubClient_SendEventAsync(iotHubClientHandle,
    messageHandle, sendCallback, NULL);
}

Firebase:

#include <Firebase_ESP_Client.h>

FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

void setup() {
  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;
  Firebase.begin(&config, &auth);
}

void loop() {
  Firebase.RTDB.setFloat(&fbdo, "/sensors/temp", 25.5);
}

21.7 Git for IoT Projects

Repository Structure:

iot-project/
├── firmware/
│   ├── src/
│   ├── lib/
│   ├── test/
│   └── platformio.ini
├── hardware/
│   ├── schematics/
│   └── pcb/
├── docs/
├── .gitignore
└── README.md

.gitignore for IoT:

# Build artifacts
.pio/
build/
*.hex
*.bin
*.elf

# IDE files
.vscode/
.idea/
*.swp

# Environment-specific
secrets.h
credentials.json

# OS files
.DS_Store
Thumbs.db

Branching Strategy:

  • main: Production-ready code
  • develop: Integration branch
  • feature/*: New features
  • bugfix/*: Bug fixes
  • release/*: Release preparation

Commit Best Practices:

# Good commit messages
git commit -m "Add BME280 sensor support with I2C interface"
git commit -m "Fix deep sleep wake-up issue on ESP32"
git commit -m "Optimize Wi-Fi reconnection logic to reduce power"

# Poor commit messages
git commit -m "Update"
git commit -m "Fix bug"
git commit -m "Changes"
Try It: Git Commit Message Analyzer

Practice writing good IoT firmware commit messages. Enter a message to get instant feedback on quality and adherence to best practices.

21.8 Collaborative Development

Pull Request Workflow:

  1. Create feature branch
  2. Implement and test changes
  3. Push to remote repository
  4. Create pull request
  5. Code review
  6. Merge to develop

Code Review Checklist:

21.9 Dependency Management

PlatformIO lib_deps:

[env:esp32dev]
lib_deps =
    # Exact version
    knolleary/PubSubClient@2.8

    # Version range (semver)
    adafruit/Adafruit BME280 Library@^2.2.2

    # Git repository
    https://github.com/me/mylib.git

    # Local library
    lib/custom_sensor

Version Pinning Best Practices:

Approach Syntax Use Case
Exact version @2.8.0 Production builds
Compatible updates @^2.8.0 Accept patches
Any version (no version) Only for prototyping
Try It: Dependency Version Strategy Simulator

Simulate what happens when you use different version pinning strategies over time. See how exact pinning, semver ranges, and unpinned dependencies behave as libraries release updates.

Worked Example: Dependency Audit for a Production Air Quality Monitor

Scenario: A startup ships 500 outdoor air quality monitors to cities across Europe. Each device runs an ESP32 with PlatformIO firmware using 8 libraries. After 6 months in production, a critical vulnerability is found in the MQTT library (PubSubClient), and a sensor library update breaks compatibility with their hardware revision. The team must audit, patch, and deploy updates to all 500 devices.

Initial dependency state (from platformio.ini):

Library Pinned Version Current Latest Issue
PubSubClient @^2.8 (range) 2.9.0 v2.9 fixes buffer overflow CVE-2023-XXXX but changes callback signature
Adafruit BME680 @2.0.1 (exact) 2.0.4 v2.0.4 adds support for new sensor revision (BME688)
ArduinoJson (no version) 7.0.0 v7 is a breaking rewrite; v6 API completely different
WiFiManager @^2.0.15 2.0.17 Minor patches, safe to update
NTPClient @3.2.1 3.2.1 No change
SPIFFS Built-in - No change
TinyGPS++ @1.0.3 1.0.3 No change
AsyncTCP Git URL Latest commit Unknown; no version pinning

Audit findings:

  1. Critical vulnerability: PubSubClient 2.8 has a heap buffer overflow when receiving MQTT messages > 256 bytes. Attackers on the same network can crash the device or execute arbitrary code. Must patch immediately.
  2. Silent breakage risk: ArduinoJson was unpinned. A pio lib update would pull v7.0, which renames StaticJsonDocument to JsonDocument and changes 14 API calls in the firmware. The build would fail with 14 compile errors.
  3. Supply chain risk: AsyncTCP is pinned to a Git repository with no version tag. If the repository owner force-pushes or deletes the repo, all future builds fail.

Resolution steps:

Step Action Rationale
1 Pin ALL libraries to exact versions: @2.8.0, @6.21.3, etc. Prevent silent breaking changes
2 Create feature/mqtt-security-patch branch Isolate changes from main
3 Update PubSubClient to @2.9.0, fix callback signature change (1 line: add unsigned intsize_t) Patch CVE while minimizing code changes
4 Pin ArduinoJson to @6.21.3 (latest v6) Avoid v7 breaking changes until planned migration
5 Fork AsyncTCP to company GitHub, pin to fork with tag v1.1.4-pinned Eliminate supply chain dependency on external repo
6 Run full test suite on hardware Verify no regressions
7 Deploy via OTA to 10 devices (canary group) Catch field issues before fleet-wide rollout
8 Monitor for 48 hours, then deploy to remaining 490 devices Staged rollout reduces blast radius

Cost of NOT auditing dependencies:

  • If ArduinoJson auto-updated to v7: 3-5 days of developer time to port 14 API calls + retest
  • If PubSubClient stayed unpatched: potential remote code execution on 500 public-facing devices
  • If AsyncTCP repo disappeared: build pipeline blocked until alternative found

Key takeaway: Pin exact versions (@2.8.0) in production, use semantic ranges (@^2.8) only during active development. Audit dependencies quarterly. Fork critical libraries that lack stable release practices.

Common Pitfall: Library Version Conflicts

Symptom: Code compiles fine, then fails after pio lib update

Cause: Library update introduced breaking change

Prevention:

  1. Pin exact versions in production: lib@2.8.0
  2. Test updates in separate branch
  3. Read library changelogs before updating
  4. Use CI/CD to catch breakages early

Recovery:

# Revert to known working versions
pio lib uninstall PubSubClient
pio lib install "PubSubClient@2.8.0"

21.10 Knowledge Check

Common Pitfalls

Adding too many features before validating core user needs wastes weeks of effort on a direction that user testing reveals is wrong. IoT projects frequently discover that users want simpler interactions than engineers assumed. Define and test a minimum viable version first, then add complexity only in response to validated user requirements.

Treating security as a phase-2 concern results in architectures (hardcoded credentials, unencrypted channels, no firmware signing) that are expensive to remediate after deployment. Include security requirements in the initial design review, even for prototypes, because prototype patterns become production patterns.

Designing only for the happy path leaves a system that cannot recover gracefully from sensor failures, connectivity outages, or cloud unavailability. Explicitly design and test the behaviour for each failure mode and ensure devices fall back to a safe, locally functional state during outages.

21.11 What’s Next

If you want to… Read this
Explore application domains for this technology Application Domains Overview
Learn about UX design for connected devices UX Design for IoT
Start prototyping with the concepts covered Prototyping Essentials