Arduino命令解析库(Commander)的使用和实现
Arduino命令解析库(Commander)的使用和实现
在嵌入式系统中,特别是在Arduino平台上,命令解析是一种常见的通信方式。
本文将介绍一个简单的Arduino命令解析库(Commander),并提供使用示例、头文件和源文件。
内容摘自simpleFOC的Commander部分。
使用示例
以下是一个简单的使用示例,演示如何在Arduino中使用Commander库:
#include <Commander.h>
Commander command = Commander('\n', false);
Commander command2 = Commander('\n', false);
// 一级命令
void doL(char *cmd)
{
// L-0.02
// 取出cmd中的带符号浮点数
float value_1 = atof(cmd);
Serial.printf("L: %f\n", value_1);
// 将后续目录进行解析
command2.run(cmd);
};
// 二级命令
void doLA(char *cmd)
{
// LA-0.02
// 取出cmd中的带符号浮点数
float value_1 = atof(cmd);
Serial.printf("LA: %f\n", value_1);
};
void setup()
{
Serial.begin(115200);
// 添加新命令 (命令关键字,回调函数,标签文本)
command.add('L', doL, "getL");
command2.add('A', doLA, "getLA");
delay(100);
}
void loop()
{
delay(2);
command.run(Serial);
}
在这个示例中,我们创建了两个Commander实例:command
和command2
,分别用于处理一级命令和二级命令。通过定义回调函数 doL
和 doLA
,我们可以在收到相应命令时执行自定义的操作。
Commander.h
#ifndef COMMANDS_H
#define COMMANDS_H
#include "Arduino.h"
// commander configuration
#define CMD_SCAN '?' //!< command scaning the network - only for commander
#define CMD_VERBOSE '@' //!< command setting output mode - only for commander
#define CMD_DECIMAL '#' //!< command setting decimal places - only for commander
#define MAX_COMMAND_LENGTH 20
// Commander用于显示用户类型的详细信息
enum VerboseMode : uint8_t
{
nothing = 0x00, // 无显示 - 适合监控
on_request = 0x01, // 仅在用户请求时显示
user_friendly = 0x02 // 向用户显示文本消息
};
// 回调函数指针定义
typedef void (*CommandCallback)(char *); //!< 命令回调函数指针
/**
* 这是一个Commander类,它实现了基于IDvalue(例如"AB5.321" - 命令ID A,子命令ID B,值 5.321)的字符串通信协议。
* - 该类可以与HardwareSerial实例结合使用,它将读写,或者可以用来解析从用户外部获得的字符串。
* - Commander还提供了一个非常简单的命令>回调接口,使用户可以将回调函数附加到某个命令ID,详见函数add()。
*/
class Commander
{
public:
/**
* 默认构造函数接收一个串行接口,它用于输出值。
* 如果使用run()函数,它将使用此串行实例读取串行用户命令。
*
* @param serial - 串行通信端口实例
* @param eol - 换行符号字符
* @param echo - 回显最后一个输入字符(用于命令行反馈)
*/
Commander(Stream &serial, char eol = '\n', bool echo = false);
Commander(char eol = '\n', bool echo = false);
/**
* run()函数,它读取串行端口并触发已添加到Commander的回调函数,当用户请求它们时,即当他发送命令时。
*
* 它有默认命令(字母可以在commands.h文件中更改)
* '@' - 详细模式
* '#' - 小数位数
* '?' - 扫描命令 - 显示所有附加节点的标签。
*/
void run();
/**
* run()函数,它读取用户输入的字符串并触发已添加到Commander的回调函数,
* 当用户请求它们时,即当他发送命令时。
*
* 它有默认命令(字母可以在commands.h文件中更改)
* '@' - 详细模式
* '#' - 小数位数
* '?' - 扫描命令 - 显示所有附加节点的标签。
* @param reader - 读取用户输入的临时流
* @param eol - 临时的换行符。
*/
void run(Stream &reader, char eol = '\n');
/**
* run()函数,它读取用户输入的字符串并触发已添加到Commander的回调函数,
* 当用户请求它们时,即当他发送命令时。
*
* 它有默认命令(字母可以在commands.h文件中更改)
* '@' - 详细模式
* '#' - 小数位数
* '?' - 扫描命令 - 显示所有附加节点的标签。
* @param user_input - 用户输入的字符串。
*/
void run(char *user_input);
/**
* add()函数,用于在Commander上添加具有命令ID的回调函数。
*
* @param id - char命令字母
* @param onCommand - void函数指针(char *)
* @param label - 发送扫描命令时显示的字符串标签(可选)
*/
void add(char id, CommandCallback onCommand, char *label = nullptr);
// 输出变量
VerboseMode verbose = VerboseMode::user_friendly; //!< 标志该命令应输出用户易于理解的文本的标志
uint8_t decimal_places = 3; //!< 用于显示数字时使用的小数位数
// 监视函数
Stream *com_port = nullptr; //!< 如果提供了,则是串行端口终端变量
char eol = '\n'; //!< 结束符字符
bool echo = false; //!< 回显最后一个输入的字符(用于命令行反馈)
private:
// 订阅的命令回调变量
CommandCallback call_list[20]; //!< 存储命令回调函数指针的数组,20是任意数字
char call_ids[20]; //!< 已添加的回调命令
char *call_label[20]; //!< 已添加的回调标签
int call_count = 0; //!< 订阅的回调函数数量
// 帮助串行通信读取的变量
char received_chars[MAX_COMMAND_LENGTH] = {0}; //!< 目前接收到的用户信息,等待换行
int rec_cnt = 0; //!< 接收到的字符数量
// 串行打印功能
/**
* 如果verbose模式打开,则仅打印字符串消息
* @param message - 要打印的消息
*/
void printVerbose(const char *message);
/**
* 仅在verbose模式打开时打印字符串消息
* - 处理由F宏定义的字符串的函数
* @param message - 要打印的消息
*/
void printVerbose(const __FlashStringHelper *message);
/**
* 使用所需的小数点数将数字打印到串行
* @param message - 要打印的数字
*/
void print(const float number);
void print(const int number);
void print(const char *message);
void print(const __FlashStringHelper *message);
void print(const char message);
void println(const float number);
void println(const int number);
void println(const char *message);
void println(const __FlashStringHelper *message);
void println(const char message);
void printError();
bool isSentinel(char ch);
};
#endif
Commander.cpp
#include "Commander.h"
Commander::Commander(Stream &serial, char eol, bool echo)
{
com_port = &serial;
this->eol = eol;
this->echo = echo;
}
Commander::Commander(char eol, bool echo)
{
this->eol = eol;
this->echo = echo;
}
void Commander::add(char id, CommandCallback onCommand, char *label)
{
call_list[call_count] = onCommand;
call_ids[call_count] = id;
call_label[call_count] = label;
call_count++;
}
void Commander::run()
{
if (!com_port)
return;
run(*com_port, eol);
}
void Commander::run(Stream &serial, char eol)
{
Stream *tmp = com_port; // save the serial instance
char eol_tmp = this->eol;
this->eol = eol;
com_port = &serial;
// a string to hold incoming data
while (serial.available())
{
// get the new byte:
int ch = serial.read();
received_chars[rec_cnt++] = (char)ch;
// end of user input
if (echo)
print((char)ch);
if (isSentinel(ch))
{
// execute the user command
run(received_chars);
// reset the command buffer
received_chars[0] = 0;
rec_cnt = 0;
}
if (rec_cnt >= MAX_COMMAND_LENGTH)
{ // prevent buffer overrun if message is too long
received_chars[0] = 0;
rec_cnt = 0;
}
}
com_port = tmp; // reset the instance to the internal value
this->eol = eol_tmp;
}
void Commander::run(char *user_input)
{
// execute the user command
char id = user_input[0];
switch (id)
{
case CMD_SCAN:
for (int i = 0; i < call_count; i++)
{
print(call_ids[i]);
print(":");
if (call_label[i])
println(call_label[i]);
else
println("");
}
break;
case CMD_VERBOSE:
if (!isSentinel(user_input[1]))
verbose = (VerboseMode)atoi(&user_input[1]);
printVerbose(F("Verb:"));
switch (verbose)
{
case VerboseMode::nothing:
println(F("off!"));
break;
case VerboseMode::on_request:
case VerboseMode::user_friendly:
println(F("on!"));
break;
}
break;
case CMD_DECIMAL:
if (!isSentinel(user_input[1]))
decimal_places = atoi(&user_input[1]);
printVerbose(F("Decimal:"));
println(decimal_places);
break;
default:
for (int i = 0; i < call_count; i++)
{
if (id == call_ids[i])
{
call_list[i](&user_input[1]);
break;
}
}
break;
}
}
bool Commander::isSentinel(char ch)
{
if (ch == eol)
return true;
else if (ch == '\r')
{
printVerbose(F("Warn: \\r detected! \n"));
}
return false;
}
void Commander::print(const int number)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->print(number);
}
void Commander::print(const float number)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->print((float)number, (int)decimal_places);
}
void Commander::print(const char *message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->print(message);
}
void Commander::print(const __FlashStringHelper *message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->print(message);
}
void Commander::print(const char message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->print(message);
}
void Commander::println(const int number)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->println(number);
}
void Commander::println(const float number)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->println((float)number, (int)decimal_places);
}
void Commander::println(const char *message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->println(message);
}
void Commander::println(const __FlashStringHelper *message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->println(message);
}
void Commander::println(const char message)
{
if (!com_port || verbose == VerboseMode::nothing)
return;
com_port->println(message);
}
void Commander::printVerbose(const char *message)
{
if (verbose == VerboseMode::user_friendly)
print(message);
}
void Commander::printVerbose(const __FlashStringHelper *message)
{
if (verbose == VerboseMode::user_friendly)
print(message);
}
void Commander::printError()
{
println(F("err"));
}
在源文件中,我们实现了Commander类的构造函数和一些成员函数,其中包括添加命令、运行解析器等功能。这个简单的库通过串口与用户进行交互,解析用户输入的命令,并根据命令执行相应的回调函数。
在使用Commander库时,用户只需要定义自己的回调函数,并在setup函数中添加命令即可。这样,Arduino就可以通过串口接收命令并执行相应操作,实现了简单而灵活的命令解析功能。