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 loopKey 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:
- Deep sleep between telemetry intervals
- Batch telemetry — collect multiple readings and publish in one wake cycle
- Reduce keep-alive to minimum acceptable value
- Use QoS 0 for telemetry if occasional loss is acceptable
Next Steps
- Device Protocol — full message format specification
- Security — mTLS and HMAC details
- Partner Integration — JWT auth for partner devices