Arduino命令解析库(Commander)的使用和实现

发布时间 2024-01-06 15:00:50作者: Dapenson

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实例:commandcommand2,分别用于处理一级命令和二级命令。通过定义回调函数 doLdoLA,我们可以在收到相应命令时执行自定义的操作。

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就可以通过串口接收命令并执行相应操作,实现了简单而灵活的命令解析功能。