- Date Created: 2025-12-09
- Last Modified: 2025-12-09
CommandQueue実装仕様書¶
概要¶
v1.13.0の Command クラスと統合する新しいテキストコマンド処理システム。ENABLE_DEVICE_RESPONSE=1時のみコンパイルされる完全に独立した実装。
ファイル構成¶
新規作成ファイル¶
include/command_queue.h (200行)
src/command_queue.cpp (300行, コア: キュー + 受信 + パース)
src/command_manager.cpp (100行, ディスパッチテーブル + ユーティリティのみ)
src/command/ (ハンドラサブディレクトリ - 新規)
├─ version.cpp (20行, GET_VERSION)
├─ status.cpp (25行, GET_STATUS)
├─ mac_address.cpp (15行, GET_MAC_ADDRESS)
├─ poll_count.cpp (25行, SET_POLL_COUNT)
├─ threshold.cpp (35行, SET_THRESHOLD + GET_THRESHOLD)
├─ deadtime.cpp (20行, SET_DEADTIME)
├─ stream.cpp (30行, SET_STREAM + GET_STREAM)
├─ test_led.cpp (20行, TEST_LED)
├─ uptime.cpp (15行, GET_UPTIME)
├─ help.cpp (40行, GET_HELP)
├─ bme280.cpp (30行, GET_BME280)
├─ reset.cpp (15行, RESET)
├─ rtc.cpp (35行, #if ENABLE_RTC: SET_RTC_TIME + GET_RTC_TIME)
├─ gnss.cpp (60行, #if ENABLE_GNSS: SYNC_TIME + 4個のGETTER)
└─ wifi.cpp (45行, #if ENABLE_WIFI: 3個のWiFiハンドラ)
計画の特徴: - コアファイル: command_queue.cpp, command_manager.cpp は src/ ルートに置く - ハンドラ分割: 各コマンド (またはコマンドグループ) が独立したファイル - ファイル名規則: コマンド名をスネークケースで使用 (例: poll_count.cpp, mac_address.cpp) - 条件付きコンパイル: ENABLE_RTC, ENABLE_GNSS, ENABLE_WIFI に応じて該当ファイルを除外
修正ファイル¶
src/main.cpp (条件付きコンパイル追加)
platformio.ini (src_filter設定)
既存ファイル(変更なし)¶
include/command.h(v1.13.0 - Command class)src/command.cpp(v1.13.0 - Command implementation)- その他の既存コード
include/command_queue.h - 仕様¶
ヘッダーガード¶
#ifndef COMMAND_QUEUE_H
#define COMMAND_QUEUE_H
#if ENABLE_DEVICE_RESPONSE // ← この条件が重要(全ファイル内容を囲む)
// ... 以下全て ENABLE_DEVICE_RESPONSE=1 時のみコンパイル ...
#endif // ENABLE_DEVICE_RESPONSE
#endif // COMMAND_QUEUE_H
定義と型¶
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
// Configuration
#define COMMAND_QUEUE_SIZE 10 // FreeRTOS Queue size
#define COMMAND_RECEPTION_TIMEOUT_MS 500 // Serial read timeout
#define MAX_COMMAND_LENGTH 64 // Max command line length
#define MAX_COMMAND_ARGS 2 // Max arguments per command
#define MAX_ARG_LENGTH 16 // Max arg string length
// Data structure: Simplified command representation
typedef struct {
char name[32]; // Command name (null-terminated)
uint8_t arg_count; // Number of arguments (0-2)
char args[MAX_COMMAND_ARGS][MAX_ARG_LENGTH]; // Argument strings
} command_t;
// Response structure
typedef struct {
bool is_ok; // Success/failure flag
char message[512]; // JSON response (null-terminated)
} command_response_t;
Public API¶
class CommandQueue {
public:
/**
* @brief Initialize CommandQueue (call once in setup())
*
* Initializes FreeRTOS static queue for command buffering.
*/
static void init(void);
/**
* @brief Receive and queue incoming text commands
*
* Main entry point for serial command reception.
* Must be called once per loop() iteration.
*
* Flow:
* 1. Check Serial.available()
* 2. Read complete line (newline-terminated)
* 3. Parse into command_t
* 4. Queue for deferred processing
*
* @return true if serial input was processed, false if no data available
*/
static bool receive(void);
/**
* @brief Check if commands are pending in queue
*
* @return true if queue has items, false if empty
*/
static bool has_pending(void);
/**
* @brief Dequeue and execute next command
*
* Main entry point for command execution.
* Must be called once per loop() iteration.
*
* Flow:
* 1. Check queue has items
* 2. Dequeue oldest command_t
* 3. Lookup handler in command_table
* 4. Call handler (executes Command class methods)
* 5. Send response to serial
*
* @return true if command was processed, false if queue is empty
*/
static bool execute(void);
private:
// Internal helper methods (static)
static bool receive_line(char* buffer, size_t buffer_size);
static command_t parse(const char* line);
static void discard_input(void);
static command_response_t dispatch(const command_t& cmd);
};
src/command_queue.cpp - 仕様¶
ファイル構成¶
#include "command_queue.h"
#include "command.h"
#include "device_response_builder.h"
#include <Arduino.h>
#include <cstring>
#include <ctype.h>
#include <stdlib.h>
#if ENABLE_DEVICE_RESPONSE // ← 全ファイルを囲む
// ============================================================================
// SECTION 1: QUEUE STORAGE (FreeRTOS Static Allocation)
// ============================================================================
// ============================================================================
// SECTION 2: HELPER FUNCTIONS
// ============================================================================
// ============================================================================
// SECTION 3: PUBLIC API IMPLEMENTATION
// ============================================================================
#endif // ENABLE_DEVICE_RESPONSE
主要実装部分¶
1. Queue初期化¶
static QueueHandle_t g_queue = NULL;
static StaticQueue_t g_queue_storage;
static uint8_t g_queue_buffer[COMMAND_QUEUE_SIZE * sizeof(command_t)];
void CommandQueue::init(void) {
g_queue = xQueueCreateStatic(
COMMAND_QUEUE_SIZE,
sizeof(command_t),
g_queue_buffer,
&g_queue_storage
);
configASSERT(g_queue != NULL);
}
2. シリアル受信¶
static bool receive_line(char* buffer, size_t buffer_size) {
// 入力バッファがない場合は即座に終了
if (!Serial.available()) {
return false;
}
// タイムアウト設定(500ms)
Serial.setTimeout(COMMAND_RECEPTION_TIMEOUT_MS);
// '\n'までのバイト列を読み込む
size_t bytes_read = Serial.readBytesUntil('\n', buffer, buffer_size - 1);
// オーバーフロー検出(バッファ満杯のまま'\n'なし)
if (bytes_read == (buffer_size - 1)) {
discard_input();
return false;
}
// 空コマンド検出
if (bytes_read == 0) {
return false;
}
// 末尾の空白を削除
while (bytes_read > 0 && isspace(buffer[bytes_read - 1])) {
bytes_read--;
}
// Null終端
buffer[bytes_read] = '\0';
return bytes_read > 0;
}
3. パース¶
static command_t parse(const char* line) {
command_t cmd = {0};
// 行のコピー(strtok は入力を破壊するため)
char work[MAX_COMMAND_LENGTH];
strncpy(work, line, sizeof(work) - 1);
work[sizeof(work) - 1] = '\0';
// コマンド名抽出(最初のトークン)
char* token = strtok(work, " ");
if (!token) {
return cmd; // 空オブジェクト返す
}
strncpy(cmd.name, token, sizeof(cmd.name) - 1);
cmd.name[sizeof(cmd.name) - 1] = '\0';
// 大文字化
for (int i = 0; cmd.name[i]; i++) {
cmd.name[i] = toupper((unsigned char)cmd.name[i]);
}
// エイリアス解決(「T」→「SET_THRESHOLD」など)
resolve_alias(cmd.name);
// 引数抽出
while ((token = strtok(NULL, " ")) && cmd.arg_count < MAX_COMMAND_ARGS) {
strncpy(cmd.args[cmd.arg_count], token, sizeof(cmd.args[0]) - 1);
cmd.args[cmd.arg_count][sizeof(cmd.args[0]) - 1] = '\0';
cmd.arg_count++;
}
return cmd;
}
4. キューイング¶
bool CommandQueue::receive(void) {
if (!Serial.available()) {
return false;
}
char buffer[MAX_COMMAND_LENGTH];
if (!receive_line(buffer, sizeof(buffer))) {
discard_input();
return true; // 入力は処理した(破棄)
}
command_t cmd = parse(buffer);
// キューに追加
BaseType_t result = xQueueSend(g_queue, &cmd, 0);
if (result != pdTRUE) {
// キュー満杯時はエラーを即座に返す
// (実装パターンはhandlers.cppで定義)
}
return true;
}
5. 実行¶
bool CommandQueue::execute(void) {
if (!has_pending()) {
return false;
}
command_t cmd = {0};
if (xQueueReceive(g_queue, &cmd, 0) != pdTRUE) {
return false;
}
// ディスパッチ&実行
command_response_t response = dispatch(cmd);
// レスポンス送信
if (strlen(response.message) > 0) {
Serial.print(response.message);
}
return true;
}
6. ディスパッチ¶
static command_response_t dispatch(const command_t& cmd) {
// command_manager.cpp で定義されたテーブル(ハンドラーは src/command/*.cpp で実装)
extern const command_entry_t command_table[];
for (int i = 0; command_table[i].name != NULL; i++) {
// コマンド名で一致確認
if (strcmp(cmd.name, command_table[i].name) == 0) {
return command_table[i].handler(cmd); // src/command/*.cpp で実装されたハンドラーを呼び出し
}
// エイリアスで一致確認
for (int j = 0; command_table[i].aliases[j] != NULL; j++) {
if (strcmp(cmd.name, command_table[i].aliases[j]) == 0) {
return command_table[i].handler(cmd); // src/command/*.cpp で実装されたハンドラーを呼び出し
}
}
}
// コマンド見つからず
command_response_t response;
response.is_ok = false;
snprintf(response.message, sizeof(response.message),
"{\"type\":\"response\",\"status\":\"error\",\"message\":\"Unknown command\"}");
return response;
}
src/command_manager.cpp - 仕様¶
役割と設計¶
command_manager.cpp はハンドラの実装ではなく、ディスパッチテーブルとユーティリティ関数を提供する中核ファイルです。
構成:
- Command dispatch table: 全48個のコマンドマッピング(名前、エイリアス、ハンドラー関数ポインタ)
- Utility functions: error_response(), success_response() など、全ハンドラーが共通で使用する関数
- Handler forward declarations: src/command/*.cpp で実装されるハンドラーの前方宣言
ファイルサイズ:
- 約100行(ディスパッチテーブル + ユーティリティのみ)
- 実装詳細は src/command/*.cpp に分散
ファイル構成¶
#include "command_queue.h"
#include "command.h"
#include "device_response_builder.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <cstring>
#include <stdlib.h>
#if ENABLE_DEVICE_RESPONSE // ← 全ファイルを囲む
// ============================================================================
// SECTION 1: HANDLER FORWARD DECLARATIONS (from src/command/*.cpp)
// ============================================================================
// Declared in src/command/version.cpp:
static command_response_t handle_get_version(const command_t& cmd);
// Declared in src/command/status.cpp:
static command_response_t handle_get_status(const command_t& cmd);
// Declared in src/command/mac_address.cpp:
static command_response_t handle_get_mac_address(const command_t& cmd);
// ... etc for all 48 handlers defined across src/command/*.cpp files ...
// ============================================================================
// SECTION 2: COMMAND DISPATCH TABLE
// ============================================================================
// const command_entry_t command_table[] = { ... };
// ============================================================================
// SECTION 3: UTILITY FUNCTIONS
// ============================================================================
// error_response(), success_response()
// ============================================================================
// SECTION 4: HANDLER IMPLEMENTATIONS
// ============================================================================
// handle_get_version(), handle_get_status(), ... 48個のハンドラ実装
#endif // ENABLE_DEVICE_RESPONSE
ハンドラ数と分類¶
System Info (3):
- GET_VERSION
- GET_STATUS
- GET_MAC_ADDRESS
Detection (4):
- SET_POLL_COUNT
- GET_THRESHOLD
- SET_THRESHOLD
- SET_DEADTIME
Testing (1):
- TEST_LED
RTC (2) - conditional ENABLE_RTC:
- SET_RTC_TIME
- GET_RTC_TIME
GNSS (4) - conditional ENABLE_GNSS:
- SYNC_TIME
- GET_GNSS_TIME
- GET_GNSS_STATUS
- GET_GNSS_POSITION
WiFi (3) - conditional ENABLE_WIFI:
- SET_WIFI_SSID
- GET_WIFI_STATUS
- SET_WIFI_ENABLE
Diagnostics/Stream (5):
- GET_UPTIME
- GET_HELP
- SET_STREAM
- GET_STREAM
- GET_QUEUE_STATS (conditional ENABLE_QUEUE)
Configuration (1):
- RESET
BME280 (1):
- GET_BME280
Total: 48 handlers
Dispatch Table構造¶
typedef struct {
const char* name; // Primary command name
const char* aliases[4]; // Backward-compat aliases (last=NULL)
command_response_t (*handler)(const command_t&); // Function pointer
const char* category; // Help category
const char* description; // Help description
} command_entry_t;
extern const command_entry_t command_table[];
const command_entry_t command_table[] = {
// System Info
{"GET_VERSION", {"V", NULL}, handle_get_version, "System", "Firmware version"},
{"GET_STATUS", {"S", "STATUS", NULL}, handle_get_status, "System", "System status"},
{"GET_MAC_ADDRESS", {NULL}, handle_get_mac_address, "System", "MAC address"},
// Detection
{"SET_POLL_COUNT", {"C", NULL}, handle_set_poll_count, "Detection", "Set poll count"},
{"SET_THRESHOLD", {"T", NULL}, handle_set_threshold, "Detection", "Set threshold"},
{"GET_THRESHOLD", {"G", NULL}, handle_get_threshold, "Detection", "Get threshold"},
{"SET_DEADTIME", {"D", NULL}, handle_set_deadtime, "Detection", "Set deadtime"},
// ... その他41個 ...
{NULL, {NULL}, NULL, NULL, NULL} // Terminator
};
src/command/*.cpp - ハンドラーファイル構成¶
各ハンドラーファイルは独立した実装を持ちます。ファイル名はコマンド名に基づいています。
例1: src/command/version.cpp (GET_VERSION)¶
#include "command_queue.h"
#include "command.h"
#include "device_response_builder.h"
#include <Arduino.h>
#if ENABLE_DEVICE_RESPONSE
/**
* @brief GET_VERSION command handler
* Returns firmware version
*/
static command_response_t handle_get_version(const command_t& cmd) {
if (cmd.arg_count != 0) {
return error_response("GET_VERSION", "No arguments expected", 1);
}
const char* version = Command::getInstance().get_version();
command_response_t response;
response.is_ok = true;
snprintf(response.message, sizeof(response.message),
"{\"type\":\"response\",\"status\":\"ok\",\"version\":\"%s\"}", version);
return response;
}
#endif // ENABLE_DEVICE_RESPONSE
例2: src/command/threshold.cpp (SET_THRESHOLD + GET_THRESHOLD)¶
#include "command_queue.h"
#include "command.h"
#include "device_response_builder.h"
#include <Arduino.h>
#include <stdlib.h>
#if ENABLE_DEVICE_RESPONSE
static command_response_t handle_set_threshold(const command_t& cmd) {
if (cmd.arg_count != 2) {
return error_response("SET_THRESHOLD", "Missing arguments: <ch> <val>", 1);
}
uint8_t ch = atoi(cmd.args[0]);
uint16_t val = atoi(cmd.args[1]);
if (!Command::getInstance().set_threshold(ch, val)) {
return error_response("SET_THRESHOLD", "Invalid channel [1,3] or value [0,1023]", 2);
}
command_response_t response;
response.is_ok = true;
snprintf(response.message, sizeof(response.message),
"{\"type\":\"response\",\"status\":\"ok\",\"threshold\":{\"ch\":%d,\"value\":%d}}",
ch, val);
return response;
}
static command_response_t handle_get_threshold(const command_t& cmd) {
if (cmd.arg_count != 1) {
return error_response("GET_THRESHOLD", "Missing argument: <ch>", 1);
}
uint8_t ch = atoi(cmd.args[0]);
uint16_t val = Command::getInstance().get_threshold(ch);
command_response_t response;
response.is_ok = true;
snprintf(response.message, sizeof(response.message),
"{\"type\":\"response\",\"status\":\"ok\",\"threshold\":{\"ch\":%d,\"value\":%d}}",
ch, val);
return response;
}
#endif // ENABLE_DEVICE_RESPONSE
ハンドラーファイルの設計原則:
- 1ファイル = 1〜2個のコマンド: 関連するコマンド(SET_THRESHOLD + GET_THRESHOLD)は同じファイルに
- ファイルサイズ: 15〜60行(実装の複雑さに応じて)
- 前方宣言なし: ハンドラー関数を
staticで定義 - 条件付きコンパイル:
#if ENABLE_DEVICE_RESPONSEで囲む - ユーティリティ関数使用:
error_response(),success_response()をcommand_manager.cppから使用
src/main.cpp - 変更部分¶
条件付きコンパイル¶
#if ENABLE_DEVICE_RESPONSE
// CommandQueue新規実装(v1.14.0)
#include "command_queue.h"
void setup() {
Serial.begin(115200);
CommandQueue::init();
// その他の初期化
}
void loop() {
// Detection processing
// ...
CommandQueue::receive(); // シリアル受信
CommandQueue::execute(); // コマンド実行
}
#else
// Legacy text_command(v1.12.0以前)
#include "text_command.h"
#include "runtime_config.h"
void setup() {
Serial.begin(115200);
text_command_init();
config_init();
// その他の初期化
}
void loop() {
// Detection processing
// ...
text_receive(); // シリアル受信
text_execute(); // コマンド実行
}
#endif
platformio.ini - 変更部分¶
src_filter設定¶
[env:esp32dev-dev]
build_flags = -DENABLE_DEVICE_RESPONSE=1
src_filter =
+<*>
-<text_command.cpp>
-<text_command_manager.cpp>
-<runtime_config.cpp>
[env:esp32dev-release]
build_flags = -DENABLE_DEVICE_RESPONSE=0
src_filter =
+<*>
-<command_queue.cpp>
-<command_manager.cpp>
-<command/>>
-<command.cpp>
説明:
- esp32dev-dev(開発向け ENABLE_DEVICE_RESPONSE=1):
- +<*> すべてのファイルを含める
- -<text_command.cpp> レガシテキストコマンド除外
- -<text_command_manager.cpp> レガシマネージャー除外
- -<runtime_config.cpp> レガシ設定マネージャー除外
- command_queue.cpp, command_manager.cpp, command/* は自動的に含まれる
esp32dev-release(リリース向け ENABLE_DEVICE_RESPONSE=0):+<*>すべてのファイルを含める-<command_queue.cpp>新しいコマンドキュー除外-<command_manager.cpp>新しいマネージャー除外-<command/>>command/ ディレクトリ全体除外-<command.cpp>新しいCommand クラス除外- text_command.cpp, text_command_manager.cpp は自動的に含まれる
テスト方法¶
ビルド確認¶
# CommandQueue パス(ENABLE_DEVICE_RESPONSE=1)
task dev:build
# Legacy パス(ENABLE_DEVICE_RESPONSE=0)
task prod:build
シリアルモニタテスト¶
# 開発環境起動
task dev:upload
task monitor
# コマンド入力テスト
GET_VERSION
S
C 200
T 1 512
完成時の確認項目¶
- ✅ コンパイル成功(両方のEnable_device_response設定)
- ✅ テキストコマンドプロトコル変化なし
- ✅ 全ハンドラが Command クラス使用
- ✅ DAC同期が自動実行
- ✅ レスポンス形式が JSON
- ✅ シリアル通信正常