Skip to content

Device Commands

Remote command execution endpoint for controlling device operations, resets, and maintenance tasks.

POST /api/v1/device/command/{device_id}
  • Execute remote device operations
  • Perform system resets and recovery
  • Trigger maintenance procedures
  • Control device lifecycle states
Content-Type: application/json
Authorization: Bearer {device_token}
{
"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"
}
FieldTypeRequiredDescription
device_idstringYesTarget device identifier
commandobjectYesCommand specification
timestampstringYesCommand issue timestamp
FieldTypeRequiredDescription
typestringYesCommand type identifier
parametersobjectNoCommand-specific parameters
execution_delayintegerNoDelay in seconds before execution
command_idstringYesUnique command identifier

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 data
  • restart_mode: “auto” or “manual”

Restart the entire ESP32 device.

{
"type": "system_restart",
"parameters": {
"restart_delay": 5,
"preserve_config": true,
"clear_queues": false
}
}

Parameters:

  • restart_delay: Seconds before restart
  • preserve_config: Maintain current configuration
  • clear_queues: Clear pending data queues

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 firmware
  • version: Target firmware version
  • checksum: SHA-256 checksum for validation
  • auto_restart: Restart after update

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 start
  • end_time: Export range end
  • export_format: “json” or “csv”
  • include_raw: Include raw sensor data

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 connectivity
  • include_traceroute: Perform traceroute analysis
  • dns_test: Test DNS resolution

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 files
  • clear_temp_files: Clear temporary files
  • clear_old_batches: Remove old batch files
  • days_to_keep: Keep files newer than N days
{
"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"
}
FieldTypeDescription
command_statusstring”queued”, “executing”, “completed”, or “failed”
command_idstringEcho of command identifier
estimated_executionstringWhen command will execute
expected_downtimeintegerExpected downtime in seconds
recovery_expectedstringWhen device should be back online
{
"error": "Invalid command type",
"command_type": "invalid_command",
"supported_commands": [
"bcgmcu_reset", "system_restart", "firmware_update",
"data_export", "network_diagnostics", "storage_cleanup"
]
}
{
"error": "Device busy",
"current_command": "firmware_update",
"estimated_completion": "2025-01-27T10:45:00.000Z"
}
#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();
}
};
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;
}
};
  1. Command Validation: Validate all commands before execution
  2. Status Updates: Provide real-time command execution status
  3. Error Handling: Gracefully handle command failures
  4. Rate Limiting: Prevent command abuse with rate limits
  5. Logging: Log all command executions for audit
  6. Recovery: Implement recovery mechanisms for failed commands
  7. Security: Validate command authenticity and authorization