ampel-firmware.ino 12.8 KB
Newer Older
Eric Duminil's avatar
Eric Duminil committed
1
/**
Eric Duminil's avatar
Eric Duminil committed
2
 * Example: IotWebConf18ShellCommands.ino
Eric Duminil's avatar
Eric Duminil committed
3
4
 * Description:
 *   This example is a modified IotWebConf01Minimal.ino, with added shell commands.
Eric Duminil's avatar
Eric Duminil committed
5
 *
Eric Duminil's avatar
Eric Duminil committed
6
 *   Simply type "help" via Serial in order to see the available commands:
Eric Duminil's avatar
Eric Duminil committed
7

Eric Duminil's avatar
Doc    
Eric Duminil committed
8
9
10
11
12
13
14
15
16
17
18
      > help
      'help' not supported. Available commands :
        ap 0/1 (Enables/disables access point).
        ap_pwd abc (Sets AP password to abc).
        name abc (Sets ThingName to abc).
        reset (Restarts the ESP).
        reset_config (Resets the complete IotWeb config).
        save_config (Saves the config to EEPROM).
        ssid name (Sets SSID to name).
        wifi 0/1 (Enables/disables WiFi).
        wifi_pwd abc (Sets WiFi password to abc).
Eric Duminil's avatar
Eric Duminil committed
19

Eric Duminil's avatar
Eric Duminil committed
20
21
22
23
24
  * The commands allow you to setup the thing directly via Serial,
  * without the need to type long passwords on a smartphone.
  *
  * You can copy-paste multiple commands at once, separated by a newline.
  * They will be executed one after the other.
Eric Duminil's avatar
Eric Duminil committed
25

Eric Duminil's avatar
Doc    
Eric Duminil committed
26
27
28
      > name my_thing
      Calling : name('my_thing')
      Setting Thing name to my_thing
Eric Duminil's avatar
Eric Duminil committed
29

Eric Duminil's avatar
Doc    
Eric Duminil committed
30
31
32
      > wifi_pwd my_wifi_password
      Calling : wifi_pwd('my_wifi_password')
      Setting WiFi password to my_wifi_password
Eric Duminil's avatar
Eric Duminil committed
33

Eric Duminil's avatar
Eric Duminil committed
34
35
36
37
      > ssid my_wifi
      Calling : ssid('my_wifi')
      Setting WiFi ssid to my_wifi

Eric Duminil's avatar
Doc    
Eric Duminil committed
38
39
40
      > ap_pwd p4ssw0rd
      Calling : ap_pwd('p4ssw0rd')
      Setting AP password to p4ssw0rd
Eric Duminil's avatar
Eric Duminil committed
41

Eric Duminil's avatar
Eric Duminil committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
      > save_config
      Calling : save_config()

      Config version: init
      Config size: 165
      Saving configuration
      [iwcAll]
      |-- [iwcSys]
      |   |-- 'iwcThingName' with value: 'my_thing'
      |   |-- 'iwcApPassword' with value: <hidden>
      |   |-- [iwcWifi0]
      |   |   |-- 'iwcWifiSsid' with value: 'my_wifi'
      |   |   \-- 'iwcWifiPassword' with value: <hidden>
      |   \-- 'iwcApTimeout' with value: '30'
      |-- [iwcCustom]
      \-- [hidden]

      State changing from: 2 to 3
Eric Duminil's avatar
Doc    
Eric Duminil committed
60
61
62
63
      Connecting to [my_wifi] (password is hidden)
      WiFi timeout (ms): 30000
      ...
      ...
Eric Duminil's avatar
Eric Duminil committed
64
 *
Eric Duminil's avatar
Eric Duminil committed
65
66
 */

Eric Duminil's avatar
Eric Duminil committed
67
#include <IotWebConf.h>
Eric Duminil's avatar
Eric Duminil committed
68
69
70
71
72
73
74
75
76
77
78
79
80
81

// -- Initial name of the Thing. Used e.g. as SSID of the own Access Point.
const char thingName[] = "testThing";

// -- Initial password to connect to the Thing, when it creates an own Access Point.
const char wifiInitialApPassword[] = "smrtTHNG8266";

// -- Method declarations.
void handleRoot();

DNSServer dnsServer;
WebServer server(80);

IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword);
82

Eric Duminil's avatar
Eric Duminil committed
83
84
85
86
87
/*****************************************************************************
 * Shell interface.
 *
 * (Could be moved to shell.h)
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
88
namespace shell {
Eric Duminil's avatar
Eric Duminil committed
89
90
91
92
93
94
95
96
  void defineCommand(const char *name, void (*function)(), const __FlashStringHelper *doc_fstring);
  void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring);
  void defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring);

  void checkSerialInput();

  void execute(const char *command_line);
}
Eric Duminil's avatar
Eric Duminil committed
97

Eric Duminil's avatar
Eric Duminil committed
98
99
100
/*****************************************************************************
 * Define functions which can be called in the shell.
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
101

Eric Duminil's avatar
Eric Duminil committed
102
103
104
105
106
void resetConfig(){
  Serial.println(F("Resetting config..."));
  iotWebConf.getRootParameterGroup()->applyDefaultValue();
  Serial.println(F("Done!"));
}
Eric Duminil's avatar
Eric Duminil committed
107

Eric Duminil's avatar
Eric Duminil committed
108
109
110
111
112
void setSSID(char *ssid){
  Serial.print(F("Setting WiFi ssid to "));
  Serial.println(ssid);
  strlcpy(iotWebConf.getWifiSsidParameter()->valueBuffer, ssid, iotWebConf.getWifiSsidParameter()->getLength());
}
Eric Duminil's avatar
Eric Duminil committed
113

Eric Duminil's avatar
Eric Duminil committed
114
115
116
117
118
void setWifiPassword(char *pwd){
  Serial.print(F("Setting WiFi password to "));
  Serial.println(pwd);
  strlcpy(iotWebConf.getWifiPasswordParameter()->valueBuffer, pwd, iotWebConf.getWifiPasswordParameter()->getLength());
}
Eric Duminil's avatar
Eric Duminil committed
119

Eric Duminil's avatar
Eric Duminil committed
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
void setApPassword(char *pwd){
  Serial.print(F("Setting AP password to "));
  Serial.println(pwd);
  strlcpy(iotWebConf.getApPasswordParameter()->valueBuffer, pwd, iotWebConf.getApPasswordParameter()->getLength());
}

void setThingName(char *name){
  Serial.print(F("Setting Thing name to "));
  Serial.println(name);
  strlcpy(iotWebConf.getThingNameParameter()->valueBuffer, name, iotWebConf.getThingNameParameter()->getLength());
}

void apOnOff(int onOff){
  if (onOff) {
    Serial.print(F("Enable "));
  } else {
    Serial.print(F("Disable "));
  }
  Serial.println(F("AP mode!"));
  iotWebConf.forceApMode(onOff);
}

void wifiOnOff(int onOff){
  if (onOff) {
    Serial.print(F("Enable Wifi!"));
    iotWebConf.goOnLine();
  } else {
    Serial.print(F("Disable Wifi!"));
    iotWebConf.goOffLine();
  }
}

/*****************************************************************************
Eric Duminil's avatar
Eric Duminil committed
153
 * Define shell commands with name, function and documentation.
Eric Duminil's avatar
Eric Duminil committed
154
 * The commands accept either 0 argument, one string or one integer.
Eric Duminil's avatar
Eric Duminil committed
155
156
157
 *
 * Feel free to add other commands, e.g. to set custom parameters.
 * The second argument can be either a function name or a lambda.
Eric Duminil's avatar
Eric Duminil committed
158
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
159

Eric Duminil's avatar
Eric Duminil committed
160
161
162
163
void defineShellCommands() {
  shell::defineCommand("reset", []() { ESP.restart(); }, F("(Restarts the ESP)"));
  shell::defineCommand("save_config", []() { iotWebConf.saveConfig(); }, F("(Saves the config to EEPROM)"));
  shell::defineCommand("reset_config", resetConfig, F("(Resets the complete IotWeb config)"));
Eric Duminil's avatar
Eric Duminil committed
164

Eric Duminil's avatar
Eric Duminil committed
165
166
  shell::defineStringCommand("ssid", setSSID, F("name (Sets SSID to name)"));
  shell::defineStringCommand("wifi_pwd", setWifiPassword, F("abc (Sets WiFi password to abc)"));
Eric Duminil's avatar
Eric Duminil committed
167
168
  shell::defineStringCommand("ap_pwd", setApPassword, F("abc (Sets AP password to abc)"));
  shell::defineStringCommand("name", setThingName, F("abc (Sets ThingName to abc)"));
Eric Duminil's avatar
Eric Duminil committed
169

Eric Duminil's avatar
Eric Duminil committed
170
171
  shell::defineIntCommand("ap", apOnOff, F("0/1 (Enables/disables access point)"));
  shell::defineIntCommand("wifi", wifiOnOff, F("0/1 (Enables/disables WiFi)"));
Eric Duminil's avatar
Eric Duminil committed
172
}
Eric Duminil's avatar
Eric Duminil committed
173

Eric Duminil's avatar
Eric Duminil committed
174
175
176
177
178
/*****************************************************************************
 * Arduino sketch
 ****************************************************************************/

void setup()
Eric Duminil's avatar
Eric Duminil committed
179
180
181
{
  Serial.begin(115200);
  Serial.println();
Eric Duminil's avatar
Eric Duminil committed
182
  Serial.println(F("Starting up IotWebConf18ShellCommands..."));
Eric Duminil's avatar
Eric Duminil committed
183
184
185
186
187
188
189
190

  // -- Initializing the configuration.
  iotWebConf.init();

  // -- Set up required URL handlers on the web server.
  server.on("/", handleRoot);
  server.on("/config", []{ iotWebConf.handleConfig(); });
  server.onNotFound([](){ iotWebConf.handleNotFound(); });
Eric Duminil's avatar
Eric Duminil committed
191

Eric Duminil's avatar
Eric Duminil committed
192
  defineShellCommands();
Eric Duminil's avatar
Eric Duminil committed
193

Eric Duminil's avatar
Eric Duminil committed
194
  Serial.println(F("Ready."));
195
196
}

Eric Duminil's avatar
Eric Duminil committed
197
void loop()
Eric Duminil's avatar
Eric Duminil committed
198
199
{
  iotWebConf.doLoop();
Eric Duminil's avatar
Eric Duminil committed
200
  shell::checkSerialInput();
201
202
}

Eric Duminil's avatar
Eric Duminil committed
203
204
205
206
207
208
209
210
211
212
/**
 * Handle web requests to "/" path.
 */
void handleRoot()
{
  // -- Let IotWebConf test and handle captive portal requests.
  if (iotWebConf.handleCaptivePortal())
  {
    // -- Captive portal request were already served.
    return;
Eric Duminil's avatar
Eric Duminil committed
213
  }
Eric Duminil's avatar
Eric Duminil committed
214
215
216
217
  const char* s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"
    "<title>IotWebConf 18 Shell</title></head><body>"
    "Go to <a href='config'>configure page</a> to change settings."
    "</body></html>\n";
Eric Duminil's avatar
Eric Duminil committed
218
219

  server.send(200, "text/html", s);
Eric Duminil's avatar
Eric Duminil committed
220
221
}

Eric Duminil's avatar
Eric Duminil committed
222
223
224
225
226
/*****************************************************************************
 * Shell logic.
 *
 * (Could be moved to shell.cpp)
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
227

Eric Duminil's avatar
Eric Duminil committed
228
namespace shell {
Eric Duminil's avatar
Eric Duminil committed
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  const uint8_t MAX_COMMANDS = 10;
  const uint8_t MAX_COMMAND_SIZE = 40;

  uint8_t commands_count = 0;

  enum input_type {
    NONE,
    INT32,
    STRING
  };

  struct Command {
    const char *name;
    union {
      void (*voidFunction)();
      void (*intFunction)(int32_t);
      void (*strFunction)(char*);
    };
    const char *doc;
    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];
  };

  Command commands[MAX_COMMANDS];

  bool addCommand(const char *name, const __FlashStringHelper *doc_fstring) {
    if (commands_count < MAX_COMMANDS) {
      commands[commands_count].name = name;
      commands[commands_count].doc = (const char*) doc_fstring;
      return true;
    } else {
      Serial.println(F("Too many commands have been defined."));
      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;
    }
  }

  void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) {
    if (addCommand(name, doc_fstring)) {
      commands[commands_count].intFunction = function;
      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;
    }
  }

  /*
   * Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
   * a CommandLine struct (function_name, argument_type and argument)
   */
  void parseCommand(const char *command, CommandLine &command_line) {
    if (strlen(command) == 0) {
      Serial.println(F("Received empty command"));
      command_line.argument_type = NONE;
      return;
    }

    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;
    } else {
      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);
      Serial.print(F(" "));
      Serial.print(commands[i].doc);
      Serial.println(F("."));
    }
  }

  /*
   * Saves bytes from Serial.read() until enter is pressed, and tries to run the corresponding command.
   *   http://www.gammon.com.au/serial
   */
  void processSerialInput(const byte input_byte) {
    static char input_line[MAX_COMMAND_SIZE];
    static unsigned int input_pos = 0;
    switch (input_byte) {
    case '\n': // end of text
      Serial.println();
      input_line[input_pos] = 0;
      execute(input_line);
      input_pos = 0;
      break;
    case '\r': // discard carriage return
      break;
    case '\b': // backspace
      if (input_pos > 0) {
        input_pos--;
        Serial.print(F("\b \b"));
      }
      break;
    default:
      if (input_pos == 0) {
        Serial.print(F("> "));
      }
      // keep adding if not full ... allow for terminating null byte
      if (input_pos < (MAX_COMMAND_SIZE - 1)) {
        input_line[input_pos++] = input_byte;
        Serial.print((char) input_byte);
      }
      break;
    }
  }

  void checkSerialInput() {
    while (Serial.available() > 0) {
Eric Duminil's avatar
Eric Duminil committed
379
      shell::processSerialInput(Serial.read());
Eric Duminil's avatar
Eric Duminil committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
    }
  }

  /*
   * Tries to find the corresponding callback for a given command. Name and parameter type should fit.
   */
  void execute(const char *command_str) {
    CommandLine input;
    parseCommand(command_str, input);
    for (uint8_t i = 0; i < commands_count; i++) {
      if (!strcmp(input.function_name, commands[i].name) && input.argument_type == commands[i].parameter_type) {
        Serial.print(F("Calling : "));
        Serial.print(input.function_name);
        switch (input.argument_type) {
        case NONE:
          Serial.println(F("()"));
          commands[i].voidFunction();
          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;
        }
      }
    }
    Serial.print(F("'"));
    Serial.print(command_str);
    Serial.println(F("' not supported. Available commands :"));
    listAvailableCommands();
  }
Eric Duminil's avatar
Eric Duminil committed
418
}