Device Commands
Device Commands API
Section titled “Device Commands API”Remote command execution endpoint for controlling device operations, resets, and maintenance tasks.
Endpoint
Section titled “Endpoint”POST /api/v1/device/command/{device_id}Purpose
Section titled “Purpose”- Execute remote device operations
- Perform system resets and recovery
- Trigger maintenance procedures
- Control device lifecycle states
Request
Section titled “Request”Headers
Section titled “Headers”Content-Type: application/jsonAuthorization: Bearer {device_token}Request Body
Section titled “Request Body”{ "device_id": "BCGMCU_001", "command": { "type": "bcgmcu_reset", "parameters": { "reset_type": "soft", "preserve_calibration": true, "restart_mode": "auto" }, "execution_delay": 0, "command_id": "cmd_reset_001" }, "timestamp": "2025-01-27T10:30:00.000Z"}Field Descriptions
Section titled “Field Descriptions”| Field | Type | Required | Description |
|---|---|---|---|
| device_id | string | Yes | Target device identifier |
| command | object | Yes | Command specification |
| timestamp | string | Yes | Command issue timestamp |
Command Object
Section titled “Command Object”| Field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | Command type identifier |
| parameters | object | No | Command-specific parameters |
| execution_delay | integer | No | Delay in seconds before execution |
| command_id | string | Yes | Unique command identifier |
Available Commands
Section titled “Available Commands”1. BCGMCU Reset
Section titled “1. BCGMCU Reset”Restart or reset the BCGMCU module.
{ "type": "bcgmcu_reset", "parameters": { "reset_type": "soft", "preserve_calibration": true, "restart_mode": "auto" }}Parameters:
reset_type: “soft”, “hard”, or “factory”preserve_calibration: Keep existing calibration datarestart_mode: “auto” or “manual”
2. System Restart
Section titled “2. System Restart”Restart the entire ESP32 device.
{ "type": "system_restart", "parameters": { "restart_delay": 5, "preserve_config": true, "clear_queues": false }}Parameters:
restart_delay: Seconds before restartpreserve_config: Maintain current configurationclear_queues: Clear pending data queues
3. Firmware Update
Section titled “3. Firmware Update”Initiate over-the-air firmware update.
{ "type": "firmware_update", "parameters": { "firmware_url": "https://updates.example.com/firmware.bin", "version": "1.1.0", "checksum": "sha256:abc123...", "auto_restart": true }}Parameters:
firmware_url: Download URL for firmwareversion: Target firmware versionchecksum: SHA-256 checksum for validationauto_restart: Restart after update
4. Data Export
Section titled “4. Data Export”Export stored data for analysis or backup.
{ "type": "data_export", "parameters": { "start_time": "2025-01-27T00:00:00.000Z", "end_time": "2025-01-27T23:59:59.000Z", "export_format": "json", "include_raw": true }}Parameters:
start_time: Export range startend_time: Export range endexport_format: “json” or “csv”include_raw: Include raw sensor data
5. Network Diagnostics
Section titled “5. Network Diagnostics”Perform network connectivity tests.
{ "type": "network_diagnostics", "parameters": { "test_endpoints": [ "https://google.com", "https://api.endpoint.com" ], "include_traceroute": false, "dns_test": true }}Parameters:
test_endpoints: URLs to test connectivityinclude_traceroute: Perform traceroute analysisdns_test: Test DNS resolution
6. Storage Cleanup
Section titled “6. Storage Cleanup”Clean up device storage and temporary files.
{ "type": "storage_cleanup", "parameters": { "clear_logs": true, "clear_temp_files": true, "clear_old_batches": true, "days_to_keep": 7 }}Parameters:
clear_logs: Remove old log filesclear_temp_files: Clear temporary filesclear_old_batches: Remove old batch filesdays_to_keep: Keep files newer than N days
Response
Section titled “Response”Success Response (200 OK)
Section titled “Success Response (200 OK)”{ "command_status": "queued", "command_id": "cmd_reset_001", "estimated_execution": "2025-01-27T10:30:02.000Z", "expected_downtime": 30, "recovery_expected": "2025-01-27T10:30:32.000Z"}Response Fields
Section titled “Response Fields”| Field | Type | Description |
|---|---|---|
| command_status | string | ”queued”, “executing”, “completed”, or “failed” |
| command_id | string | Echo of command identifier |
| estimated_execution | string | When command will execute |
| expected_downtime | integer | Expected downtime in seconds |
| recovery_expected | string | When device should be back online |
Error Responses
Section titled “Error Responses”400 Bad Request - Invalid Command
Section titled “400 Bad Request - Invalid Command”{ "error": "Invalid command type", "command_type": "invalid_command", "supported_commands": [ "bcgmcu_reset", "system_restart", "firmware_update", "data_export", "network_diagnostics", "storage_cleanup" ]}409 Conflict - Device Busy
Section titled “409 Conflict - Device Busy”{ "error": "Device busy", "current_command": "firmware_update", "estimated_completion": "2025-01-27T10:45:00.000Z"}Implementation Example
Section titled “Implementation Example”ESP32 Command Handler
Section titled “ESP32 Command Handler”#include <HTTPClient.h>#include <ArduinoJson.h>#include <Update.h>
class CommandHandler {private: struct QueuedCommand { String commandId; String commandType; JsonObject parameters; unsigned long executeTime; bool executed; };
std::vector<QueuedCommand> commandQueue; bool commandInProgress = false; String currentCommandId;
public: void checkForCommands() { // Check if server indicated pending commands if (commandsPending) { fetchCommands(); }
// Process queued commands processCommandQueue(); }
void processCommandQueue() { if (commandInProgress) return;
unsigned long now = millis();
for (auto& cmd : commandQueue) { if (!cmd.executed && now >= cmd.executeTime) { executeCommand(cmd); cmd.executed = true; break; // Execute one command at a time } }
// Clean up executed commands commandQueue.erase( std::remove_if(commandQueue.begin(), commandQueue.end(), [](const QueuedCommand& c) { return c.executed; }), commandQueue.end() ); }
private: void executeCommand(QueuedCommand& cmd) { commandInProgress = true; currentCommandId = cmd.commandId;
Serial.printf("Executing command: %s (ID: %s)\n", cmd.commandType.c_str(), cmd.commandId.c_str());
if (cmd.commandType == "bcgmcu_reset") { executeBCGMCUReset(cmd.parameters); } else if (cmd.commandType == "system_restart") { executeSystemRestart(cmd.parameters); } else if (cmd.commandType == "firmware_update") { executeFirmwareUpdate(cmd.parameters); } else if (cmd.commandType == "data_export") { executeDataExport(cmd.parameters); } else if (cmd.commandType == "network_diagnostics") { executeNetworkDiagnostics(cmd.parameters); } else if (cmd.commandType == "storage_cleanup") { executeStorageCleanup(cmd.parameters); } else { Serial.println("Unknown command type: " + cmd.commandType); }
commandInProgress = false; currentCommandId = ""; }
void executeBCGMCUReset(JsonObject& params) { String resetType = params["reset_type"]; bool preserveCalibration = params["preserve_calibration"];
// Send command acknowledgment sendCommandStatus("executing");
if (resetType == "soft") { bcgmcu.softReset(preserveCalibration); } else if (resetType == "hard") { bcgmcu.hardReset(preserveCalibration); } else if (resetType == "factory") { bcgmcu.factoryReset(); }
// Wait for BCGMCU to restart delay(5000);
// Verify BCGMCU is responsive if (bcgmcu.isOnline()) { sendCommandStatus("completed"); } else { sendCommandStatus("failed", "BCGMCU did not respond after reset"); } }
void executeSystemRestart(JsonObject& params) { int restartDelay = params["restart_delay"] | 5; bool preserveConfig = params["preserve_config"] | true; bool clearQueues = params["clear_queues"] | false;
sendCommandStatus("executing");
// Prepare for restart if (clearQueues) { clearDataQueues(); }
if (preserveConfig) { saveCurrentConfig(); }
// Notify server of imminent restart sendCommandStatus("completed", "Restarting in " + String(restartDelay) + " seconds");
// Delay then restart delay(restartDelay * 1000); ESP.restart(); }
void executeFirmwareUpdate(JsonObject& params) { String firmwareUrl = params["firmware_url"]; String version = params["version"]; String checksum = params["checksum"]; bool autoRestart = params["auto_restart"] | true;
sendCommandStatus("executing", "Downloading firmware...");
HTTPClient https; https.begin(firmwareUrl);
int httpCode = https.GET();
if (httpCode == 200) { int contentLength = https.getSize();
if (Update.begin(contentLength)) { WiFiClient* client = https.getStreamPtr(); size_t written = Update.writeStream(*client);
if (Update.end()) { if (Update.isFinished()) { sendCommandStatus("completed", "Firmware updated to " + version);
if (autoRestart) { delay(2000); ESP.restart(); } } else { sendCommandStatus("failed", "Update incomplete"); } } else { sendCommandStatus("failed", "Update write failed"); } } else { sendCommandStatus("failed", "Not enough space for update"); } } else { sendCommandStatus("failed", "Failed to download firmware"); }
https.end(); }
void executeDataExport(JsonObject& params) { String startTime = params["start_time"]; String endTime = params["end_time"]; String format = params["export_format"] | "json"; bool includeRaw = params["include_raw"] | false;
sendCommandStatus("executing", "Exporting data...");
// Create export file String filename = "/exports/export_" + currentCommandId + "." + format; File exportFile = SPIFFS.open(filename, "w");
if (exportFile) { int recordsExported = exportDataRange(exportFile, startTime, endTime, includeRaw); exportFile.close();
// Upload export file if (uploadExportFile(filename)) { sendCommandStatus("completed", "Exported " + String(recordsExported) + " records"); SPIFFS.remove(filename); // Clean up after upload } else { sendCommandStatus("failed", "Export upload failed"); } } else { sendCommandStatus("failed", "Could not create export file"); } }
void executeNetworkDiagnostics(JsonObject& params) { sendCommandStatus("executing", "Running network diagnostics...");
JsonArray endpoints = params["test_endpoints"]; bool dnsTest = params["dns_test"] | true;
StaticJsonDocument<1024> results; JsonObject diagnostics = results.createNestedObject("diagnostics");
// Test WiFi connection diagnostics["wifi_connected"] = (WiFi.status() == WL_CONNECTED); diagnostics["wifi_rssi"] = WiFi.RSSI(); diagnostics["local_ip"] = WiFi.localIP().toString();
// DNS test if (dnsTest) { IPAddress ip; bool dnsWorking = WiFi.hostByName("google.com", ip); diagnostics["dns_working"] = dnsWorking; diagnostics["dns_resolved_ip"] = ip.toString(); }
// Test endpoints JsonArray endpointResults = diagnostics.createNestedArray("endpoint_tests");
for (JsonVariant endpoint : endpoints) { JsonObject test = endpointResults.createNestedObject(); String url = endpoint.as<String>(); test["url"] = url;
HTTPClient https; https.begin(url); https.setTimeout(5000);
int httpCode = https.GET(); test["http_code"] = httpCode; test["response_time"] = https.connected() ? "success" : "failed";
https.end(); }
// Send results String diagnosticsJson; serializeJson(results, diagnosticsJson); sendCommandStatus("completed", diagnosticsJson); }
void executeStorageCleanup(JsonObject& params) { sendCommandStatus("executing", "Cleaning up storage...");
bool clearLogs = params["clear_logs"] | true; bool clearTempFiles = params["clear_temp_files"] | true; bool clearOldBatches = params["clear_old_batches"] | true; int daysToKeep = params["days_to_keep"] | 7;
int filesRemoved = 0; int bytesFreed = 0;
time_t cutoffTime = time(nullptr) - (daysToKeep * 24 * 3600);
// Clear logs if (clearLogs) { filesRemoved += cleanupDirectory("/logs", cutoffTime, &bytesFreed); }
// Clear temp files if (clearTempFiles) { filesRemoved += cleanupDirectory("/temp", 0, &bytesFreed); }
// Clear old batches if (clearOldBatches) { filesRemoved += cleanupDirectory("/batches", cutoffTime, &bytesFreed); }
String result = "Cleaned " + String(filesRemoved) + " files, freed " + String(bytesFreed) + " bytes"; sendCommandStatus("completed", result); }
void sendCommandStatus(String status, String details = "") { StaticJsonDocument<256> doc; doc["command_id"] = currentCommandId; doc["status"] = status; doc["timestamp"] = getCurrentTimestamp();
if (details.length() > 0) { doc["details"] = details; }
String payload; serializeJson(doc, payload);
// Send status update to server HTTPClient https; https.begin(API_BASE_URL + "/device/command/status"); https.addHeader("Content-Type", "application/json"); https.addHeader("Authorization", "Bearer " + deviceToken);
https.POST(payload); https.end(); }};Command Security
Section titled “Command Security”class CommandSecurity {public: bool validateCommand(JsonObject& command) { String commandType = command["type"];
// Check if command type is allowed if (!isCommandAllowed(commandType)) { return false; }
// Validate command signature if required if (command.containsKey("signature")) { return validateSignature(command); }
// Rate limiting if (isCommandRateLimited(commandType)) { return false; }
return true; }
private: bool isCommandAllowed(String commandType) { std::vector<String> allowedCommands = { "bcgmcu_reset", "system_restart", "firmware_update", "data_export", "network_diagnostics", "storage_cleanup" };
return std::find(allowedCommands.begin(), allowedCommands.end(), commandType) != allowedCommands.end(); }
bool isCommandRateLimited(String commandType) { // Implement rate limiting logic static std::map<String, unsigned long> lastExecution;
if (commandType == "firmware_update") { // Only allow firmware update once per hour if (millis() - lastExecution[commandType] < 3600000) { return true; } }
lastExecution[commandType] = millis(); return false; }};Best Practices
Section titled “Best Practices”- Command Validation: Validate all commands before execution
- Status Updates: Provide real-time command execution status
- Error Handling: Gracefully handle command failures
- Rate Limiting: Prevent command abuse with rate limits
- Logging: Log all command executions for audit
- Recovery: Implement recovery mechanisms for failed commands
- Security: Validate command authenticity and authorization