ampel-firmware.ino 11.5 KB
Newer Older
Eric Duminil's avatar
Eric Duminil committed
1
2
3
4
5
6
/**
 * Example: Shell commands
 * Description:
 *   This example is a modified IotWebConf01Minimal.ino, with added shell commands.
 *   
 *   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
Doc    
Eric Duminil committed
20
  * The commands allow you to setup the thing directly via Serial:
Eric Duminil's avatar
Eric Duminil committed
21

Eric Duminil's avatar
Doc    
Eric Duminil committed
22
23
24
      > name my_thing
      Calling : name('my_thing')
      Setting Thing name to my_thing
Eric Duminil's avatar
Eric Duminil committed
25

Eric Duminil's avatar
Doc    
Eric Duminil committed
26
27
28
      > ssid my_wifi
      Calling : ssid('my_wifi')
      Setting WiFi ssid to my_wifi
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
Doc    
Eric Duminil committed
34
35
36
      > ap_pwd p4ssw0rd
      Calling : ap_pwd('p4ssw0rd')
      Setting AP password to p4ssw0rd
Eric Duminil's avatar
Eric Duminil committed
37

Eric Duminil's avatar
Doc    
Eric Duminil committed
38
39
40
41
42
43
      State changing from: 1 to 3
      Connecting to [my_wifi] (password is hidden)
      WiFi timeout (ms): 30000
      State changed from: 1 to 3
      ...
      ...
Eric Duminil's avatar
Eric Duminil committed
44
45
46
 *   
 */

Eric Duminil's avatar
Eric Duminil committed
47
#include <IotWebConf.h>
Eric Duminil's avatar
Eric Duminil committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61

// -- 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);
62

Eric Duminil's avatar
Eric Duminil committed
63
/***
Eric Duminil's avatar
Eric Duminil committed
64
 * Shell interface. Could be moved to shell.h
65
 */
Eric Duminil's avatar
Eric Duminil committed
66
namespace shell {
Eric Duminil's avatar
Eric Duminil committed
67
68
69
70
71
72
73
74
  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
75

Eric Duminil's avatar
Eric Duminil committed
76
77
78
/*****************************************************************************
 * Define functions which can be called in the shell.
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
79

Eric Duminil's avatar
Eric Duminil committed
80
81
82
83
84
void resetConfig(){
  Serial.println(F("Resetting config..."));
  iotWebConf.getRootParameterGroup()->applyDefaultValue();
  Serial.println(F("Done!"));
}
Eric Duminil's avatar
Eric Duminil committed
85

Eric Duminil's avatar
Eric Duminil committed
86
87
88
89
90
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
91

Eric Duminil's avatar
Eric Duminil committed
92
93
94
95
96
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
97

Eric Duminil's avatar
Eric Duminil committed
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
131
 * Define shell commands with name, function and documentation.
Eric Duminil's avatar
Eric Duminil committed
132
133
 * The commands accept either 0 argument, one string or one integer.
 ****************************************************************************/
Eric Duminil's avatar
Eric Duminil committed
134

Eric Duminil's avatar
Eric Duminil committed
135
136
137
138
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
139

Eric Duminil's avatar
Eric Duminil committed
140
141
  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
142
143
  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
144

Eric Duminil's avatar
Eric Duminil committed
145
146
  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
147
}
Eric Duminil's avatar
Eric Duminil committed
148

Eric Duminil's avatar
Eric Duminil committed
149
150
151
152
153
154
void setup() 
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("Starting up...");

Eric Duminil's avatar
Eric Duminil committed
155
156
//  iotWebConf.getApTimeoutParameter()->defaultValue = "600";

Eric Duminil's avatar
Eric Duminil committed
157
158
159
160
161
162
163
  // -- 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
164

Eric Duminil's avatar
Eric Duminil committed
165
  defineShellCommands();
Eric Duminil's avatar
Eric Duminil committed
166

Eric Duminil's avatar
Eric Duminil committed
167
  Serial.println("Ready.");
168
169
}

Eric Duminil's avatar
Eric Duminil committed
170
171
172
void loop() 
{
  iotWebConf.doLoop();
Eric Duminil's avatar
Eric Duminil committed
173
  shell::checkSerialInput();
174
175
}

Eric Duminil's avatar
Eric Duminil committed
176
177
178
179
180
181
182
183
184
185
/**
 * 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
186
  }
Eric Duminil's avatar
Eric Duminil committed
187
188
189
190
  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
191
192

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

Eric Duminil's avatar
Eric Duminil committed
195
/*** 
Eric Duminil's avatar
Eric Duminil committed
196
 * Shell logic. Could be moved to shell.cpp
Eric Duminil's avatar
Eric Duminil committed
197
198
 */

Eric Duminil's avatar
Eric Duminil committed
199
namespace shell {
Eric Duminil's avatar
Eric Duminil committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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
  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
350
      shell::processSerialInput(Serial.read());
Eric Duminil's avatar
Eric Duminil committed
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
379
380
381
382
383
384
385
386
387
388
    }
  }

  /*
   * 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
389
}