sensor_console.cpp 5.41 KB
Newer Older
1
#include "sensor_console.h"
2

Eric Duminil's avatar
Eric Duminil committed
3
namespace sensor_console {
Eric Duminil's avatar
Eric Duminil committed
4
  const uint8_t MAX_COMMANDS = 20;
Eric Duminil's avatar
Eric Duminil committed
5
  const uint8_t MAX_COMMAND_SIZE = 30;
Eric Duminil's avatar
Eric Duminil committed
6

Eric Duminil's avatar
Eric Duminil committed
7
  uint8_t commands_count = 0;
Eric Duminil's avatar
Eric Duminil committed
8

9
10
11
12
13
14
  enum input_type {
    NONE,
    INT32,
    STRING
  };

Eric Duminil's avatar
Eric Duminil committed
15
  struct Command {
Eric Duminil's avatar
Eric Duminil committed
16
    const char *name;
Eric Duminil's avatar
Eric Duminil committed
17
    union {
18
      void (*voidFunction)();
Eric Duminil's avatar
Eric Duminil committed
19
      void (*intFunction)(int32_t);
20
      void (*strFunction)(char*);
Eric Duminil's avatar
Eric Duminil committed
21
    };
22
    const char *doc;
23
24
25
26
27
28
29
30
    input_type parameter_type;
  };

  struct CommandLine {
    char function_name[MAX_COMMAND_SIZE];
    input_type argument_type;
    int32_t int_argument;
    char str_argument[MAX_COMMAND_SIZE];
31
32
  };

Eric Duminil's avatar
Eric Duminil committed
33
  Command commands[MAX_COMMANDS];
34

35
  bool addCommand(const char *name, const __FlashStringHelper *doc_fstring) {
Eric Duminil's avatar
Eric Duminil committed
36
37
    if (commands_count < MAX_COMMANDS) {
      commands[commands_count].name = name;
38
39
      commands[commands_count].doc = (const char*) doc_fstring;
      return true;
Eric Duminil's avatar
Eric Duminil committed
40
    } else {
Eric Duminil's avatar
Eric Duminil committed
41
      Serial.println(F("Too many commands have been defined."));
42
43
44
45
46
47
48
49
      return false;
    }
  }

  void defineCommand(const char *name, void (*function)(), const __FlashStringHelper *doc_fstring) {
    if (addCommand(name, doc_fstring)) {
      commands[commands_count].voidFunction = function;
      commands[commands_count++].parameter_type = NONE;
Eric Duminil's avatar
Eric Duminil committed
50
    }
51
52
  }

53
  void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) {
54
    if (addCommand(name, doc_fstring)) {
Eric Duminil's avatar
Eric Duminil committed
55
      commands[commands_count].intFunction = function;
56
57
58
59
60
61
62
63
      commands[commands_count++].parameter_type = INT32;
    }
  }

  void defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring) {
    if (addCommand(name, doc_fstring)) {
      commands[commands_count].strFunction = function;
      commands[commands_count++].parameter_type = STRING;
64
    }
65
66
  }

Eric Duminil's avatar
Eric Duminil committed
67
  /*
68
69
   * Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
   * a CommandLine struct (function_name, argument_type and argument)
Eric Duminil's avatar
Eric Duminil committed
70
   */
71
72
  void parseCommand(const char *command, CommandLine &command_line) {
    if (strlen(command) == 0) {
Eric Duminil's avatar
Eric Duminil committed
73
      Serial.println(F("Received empty command"));
74
75
      command_line.argument_type = NONE;
      return;
Eric Duminil's avatar
Eric Duminil committed
76
    }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

    char *first_space;
    first_space = strchr(command, ' ');

    if (first_space == NULL) {
      command_line.argument_type = NONE;
      strlcpy(command_line.function_name, command, MAX_COMMAND_SIZE);
      return;
    }

    strlcpy(command_line.function_name, command, first_space - command + 1);
    strlcpy(command_line.str_argument, first_space + 1, MAX_COMMAND_SIZE - (first_space - command) - 1);

    char *end;
    command_line.int_argument = strtol(command_line.str_argument, &end, 0); // Accepts 123 or 0xFF00FF

    if (*end) {
      command_line.argument_type = STRING;
Eric Duminil's avatar
Eric Duminil committed
95
    } else {
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
      command_line.argument_type = INT32;
    }
  }

  int compareCommandNames(const void *s1, const void *s2) {
    struct Command *c1 = (struct Command*) s1;
    struct Command *c2 = (struct Command*) s2;
    return strcmp(c1->name, c2->name);
  }

  void listAvailableCommands() {
    qsort(commands, commands_count, sizeof(commands[0]), compareCommandNames);
    for (uint8_t i = 0; i < commands_count; i++) {
      Serial.print(F("  "));
      Serial.print(commands[i].name);
111
      Serial.print(F(" "));
112
113
      Serial.print(commands[i].doc);
      Serial.println(F("."));
Eric Duminil's avatar
Eric Duminil committed
114
    }
Eric Duminil's avatar
Eric Duminil committed
115
116
  }

Eric Duminil's avatar
Doc    
Eric Duminil committed
117
118
119
120
  /*
   * Saves bytes from Serial.read() until enter is pressed, and tries to run the corresponding command.
   *   http://www.gammon.com.au/serial
   */
Eric Duminil's avatar
Eric Duminil committed
121
  void processSerialInput(const byte input_byte) {
122
123
124
125
    static char input_line[MAX_COMMAND_SIZE];
    static unsigned int input_pos = 0;
    switch (input_byte) {
    case '\n': // end of text
126
      Serial.println();
127
      input_line[input_pos] = 0;
128
      execute(input_line);
129
130
131
132
      input_pos = 0;
      break;
    case '\r': // discard carriage return
      break;
Eric Duminil's avatar
Eric Duminil committed
133
134
135
    case '\b': // backspace
      if (input_pos > 0) {
        input_pos--;
136
        Serial.print(F("\b \b"));
Eric Duminil's avatar
Eric Duminil committed
137
138
      }
      break;
139
    default:
140
141
142
      if (input_pos == 0) {
        Serial.print(F("> "));
      }
143
      // keep adding if not full ... allow for terminating null byte
Eric Duminil's avatar
Eric Duminil committed
144
      if (input_pos < (MAX_COMMAND_SIZE - 1)) {
145
        input_line[input_pos++] = input_byte;
146
        Serial.print((char) input_byte);
Eric Duminil's avatar
Eric Duminil committed
147
      }
148
149
150
151
      break;
    }
  }

Eric Duminil's avatar
Eric Duminil committed
152
  /*
153
   * Tries to find the corresponding callback for a given command. Name and parameter type should fit.
Eric Duminil's avatar
Eric Duminil committed
154
   */
155
156
157
  void execute(const char *command_str) {
    CommandLine input;
    parseCommand(command_str, input);
Eric Duminil's avatar
Eric Duminil committed
158
    for (uint8_t i = 0; i < commands_count; i++) {
159
      if (!strcmp(input.function_name, commands[i].name) && input.argument_type == commands[i].parameter_type) {
Eric Duminil's avatar
Eric Duminil committed
160
        Serial.print(F("Calling : "));
161
162
163
        Serial.print(input.function_name);
        switch (input.argument_type) {
        case NONE:
Eric Duminil's avatar
Eric Duminil committed
164
          Serial.println(F("()"));
Eric Duminil's avatar
Eric Duminil committed
165
          commands[i].voidFunction();
166
167
168
169
170
171
172
173
174
175
176
177
178
          return;
        case INT32:
          Serial.print(F("("));
          Serial.print(input.int_argument);
          Serial.println(F(")"));
          commands[i].intFunction(input.int_argument);
          return;
        case STRING:
          Serial.print(F("('"));
          Serial.print(input.str_argument);
          Serial.println(F("')"));
          commands[i].strFunction(input.str_argument);
          return;
Eric Duminil's avatar
Eric Duminil committed
179
        }
Eric Duminil's avatar
Eric Duminil committed
180
      }
181
    }
Eric Duminil's avatar
Eric Duminil committed
182
    Serial.print(F("'"));
183
    Serial.print(command_str);
Eric Duminil's avatar
Eric Duminil committed
184
    Serial.println(F("' not supported. Available commands :"));
Eric Duminil's avatar
Eric Duminil committed
185
    listAvailableCommands();
186
  }
187

188
}