CPI Documentation
Firmware Guide

Firmware Guide

ESP32 firmware example for connecting to CPI via MQTT with HMAC-SHA256 signing.

Overview

The apps/fw-example/ directory contains an ESP32 firmware example that demonstrates how to connect to the CPI platform using the mTLS authentication method. This guide covers the key integration patterns.

For partner devices using TOKEN (JWT) authentication, see the Partner Integration Guide instead.

Connection Flow

ESP32 Boot
  → Load credentials from NVS (Non-Volatile Storage)
  → Connect WiFi
  → Establish mTLS connection to Mosquitto (port 8883)
  → Publish ONLINE status
  → Subscribe to commands topic
  → Begin telemetry publishing loop

Key Integration Points

1. Credential Storage

Store provisioning credentials in the ESP32's NVS partition:

  • Device ID (UUID)
  • HMAC secret (64-char hex string)
  • Client certificate (PEM, for mTLS)
  • Client private key (PEM, for mTLS)
  • CA certificate (PEM, for server verification)

2. MQTT Topics

// Topic format: cpi/{deviceId}/{channel}
#define TOPIC_TELEMETRY "cpi/%s/telemetry"
#define TOPIC_STATUS    "cpi/%s/status"
#define TOPIC_COMMANDS  "cpi/%s/commands"
#define TOPIC_ACK       "cpi/%s/ack"

3. HMAC Signing

Use mbedTLS (included in ESP-IDF) for HMAC-SHA256:

#include "mbedtls/md.h"

int sign_message(const char *secret, const char *message,
                 char *hex_out, size_t hex_out_len) {
    unsigned char digest[32];
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
    mbedtls_md_hmac_starts(&ctx, (unsigned char *)secret, strlen(secret));
    mbedtls_md_hmac_update(&ctx, (unsigned char *)message, strlen(message));
    mbedtls_md_hmac_finish(&ctx, digest);
    mbedtls_md_free(&ctx);

    for (int i = 0; i < 32; i++)
        snprintf(hex_out + i * 2, 3, "%02x", digest[i]);
    hex_out[64] = '\0';
    return 0;
}

4. Telemetry Publishing

void publish_telemetry(esp_mqtt_client_handle_t client,
                       const char *device_id, const char *secret) {
    int64_t ts = esp_timer_get_time() / 1000; // ms since boot
    char nonce[37];
    generate_uuid(nonce); // Implement UUID v4 generation

    // Read sensors
    float temperature = read_temperature();
    float humidity = read_humidity();

    // Build compact JSON data
    char data_json[128];
    snprintf(data_json, sizeof(data_json),
             "{\"temperature\":%.2f,\"humidity\":%.2f}",
             temperature, humidity);

    // Sign
    char signing_str[512], sig[65];
    snprintf(signing_str, sizeof(signing_str),
             "%s|%lld|%s|%s", device_id, ts, nonce, data_json);
    sign_message(secret, signing_str, sig, sizeof(sig));

    // Publish
    char payload[512];
    snprintf(payload, sizeof(payload),
             "{\"ts\":%lld,\"nonce\":\"%s\",\"data\":%s,\"sig\":\"%s\"}",
             ts, nonce, data_json, sig);

    char topic[128];
    snprintf(topic, sizeof(topic), TOPIC_TELEMETRY, device_id);
    esp_mqtt_client_publish(client, topic, payload, 0, 1, 0);
}

5. Command Handling

void handle_command(const char *payload, int payload_len,
                    const char *device_id, const char *secret) {
    cJSON *cmd = cJSON_Parse(payload);
    // Verify signature before executing
    // Send RECEIVED ACK
    // Execute command
    // Send COMPLETED or FAILED ACK
    cJSON_Delete(cmd);
}

Clock Synchronization

ESP32 devices should synchronize via SNTP before connecting:

void init_sntp(void) {
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "pool.ntp.org");
    sntp_init();
}

The platform requires timestamps within ±5 minutes of server time.

Power Management

For battery-powered devices:

  1. Deep sleep between telemetry intervals
  2. Batch telemetry — collect multiple readings and publish in one wake cycle
  3. Reduce keep-alive to minimum acceptable value
  4. Use QoS 0 for telemetry if occasional loss is acceptable

Next Steps

On this page