diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 11db5c3dad0fb2097a4574a3d300af6115722666..0fa5e8e3bc86a6fcba64f74d0f81fb5c139c531c 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -103,15 +103,14 @@ namespace sensor { Serial.println(F(" s during acclimatization.")); scd30.setMeasurementInterval(config::measurement_timestep_bootup); // [s] - sensor_console::defineIntCommand("co2", setCO2forDebugging, F(" 1500 (Sets co2 level, for debugging purposes)")); - sensor_console::defineIntCommand("timer", setTimer, F(" 30 (Sets measurement interval, in s)")); - sensor_console::defineCommand("calibrate", startCalibrationProcess, F(" (Starts calibration process)")); + sensor_console::defineIntCommand("co2", setCO2forDebugging, F("1500 (Sets co2 level, for debugging purposes)")); + sensor_console::defineIntCommand("timer", setTimer, F("30 (Sets measurement interval, in s)")); + sensor_console::defineCommand("calibrate", startCalibrationProcess, F("(Starts calibration process)")); sensor_console::defineIntCommand("calibrate", calibrateSensorToSpecificPPM, - F(" 600 (Starts calibration process, to given ppm)")); + F("600 (Starts calibration process, to given ppm)")); sensor_console::defineIntCommand("calibrate!", calibrateSensorRightNow, - F(" 600 (Calibrates right now, to given ppm)")); - sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration, - F(" 0/1 (Disables/enables autocalibration)")); + F("600 (Calibrates right now, to given ppm)")); + sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration, F("0/1 (Disables/enables autocalibration)")); } bool hasSensorSettled() { diff --git a/ampel-firmware/csv_writer.cpp b/ampel-firmware/csv_writer.cpp index a83bed666455545d2d5cb7c68a3d583d88f7b40a..d0828a40999dc7326d1ce19f9a4808be10dd6852 100644 --- a/ampel-firmware/csv_writer.cpp +++ b/ampel-firmware/csv_writer.cpp @@ -118,9 +118,9 @@ namespace csv_writer { showFilesystemContent(); Serial.println(); - sensor_console::defineIntCommand("csv", setCSVinterval, F(" 60 (Sets CSV writing interval, in s)")); - sensor_console::defineCommand("format_filesystem", formatFilesystem, F(" (Deletes the whole filesystem)")); - sensor_console::defineCommand("show_csv", showCSVContent, F(" (Displays the complete CSV file on Serial)")); + sensor_console::defineIntCommand("csv", setCSVinterval, F("60 (Sets CSV writing interval, in s)")); + sensor_console::defineCommand("format_filesystem", formatFilesystem, F("(Deletes the whole filesystem)")); + sensor_console::defineCommand("show_csv", showCSVContent, F("(Displays the complete CSV file on Serial)")); } File openOrCreate() { diff --git a/ampel-firmware/led_effects.cpp b/ampel-firmware/led_effects.cpp index ee0fccde5222ecf935dc444d86ed8663d5291a77..061b6f869b2c57494dfdbdbe3495b8a90736ef05 100644 --- a/ampel-firmware/led_effects.cpp +++ b/ampel-firmware/led_effects.cpp @@ -70,11 +70,19 @@ namespace led_effects { onBoardLEDOff(); } + void showColor(int32_t color) { + config::night_mode = true; // In order to avoid overwriting the desired color next time CO2 is displayed + pixels.setBrightness(255); + pixels.fill(color); + pixels.show(); + } + void setupRing() { pixels.begin(); pixels.setBrightness(config::max_brightness); LEDsOff(); - sensor_console::defineCommand("night_mode", toggleNightMode, F(" (Toggles night mode on/off)")); + sensor_console::defineCommand("night_mode", toggleNightMode, F("(Toggles night mode on/off)")); + sensor_console::defineIntCommand("color", showColor, F("0xFF0015 (Shows color, specified as RGB, for debugging)")); } void toggleNightMode() { diff --git a/ampel-firmware/lorawan.cpp b/ampel-firmware/lorawan.cpp index 784b46fb61b25bc8d3868a3a541baa14f241150a..746893b3698137ff14417158e5a2728fd8ad4a25 100644 --- a/ampel-firmware/lorawan.cpp +++ b/ampel-firmware/lorawan.cpp @@ -47,7 +47,7 @@ namespace lorawan { LMIC_reset(); // Join, but don't send anything yet. LMIC_startJoining(); - sensor_console::defineIntCommand("lora", setLoRaInterval, F(" 300 (Sets LoRaWAN sending interval, in s)")); + sensor_console::defineIntCommand("lora", setLoRaInterval, F("300 (Sets LoRaWAN sending interval, in s)")); } // Checks if OTAA is connected, or if payload should be sent. @@ -96,7 +96,7 @@ namespace lorawan { printHex2(artKey[i]); } Serial.println(); - Serial.print(" NwkSKey: "); + Serial.print(F(" NwkSKey: ")); for (size_t i = 0; i < sizeof(nwkKey); ++i) { if (i != 0) Serial.print("-"); diff --git a/ampel-firmware/mqtt.cpp b/ampel-firmware/mqtt.cpp index 5c0e67cb43fc0ad46bd80fd74278f54219ad3d40..cb0c5019c00d14094d6389684bb120eb061197f6 100644 --- a/ampel-firmware/mqtt.cpp +++ b/ampel-firmware/mqtt.cpp @@ -36,9 +36,9 @@ namespace mqtt { // mqttClient.setSocketTimeout(config::mqtt_timeout); //NOTE: somehow doesn't seem to have any effect on connect() mqttClient.setServer(config::mqtt_server, config::mqtt_port); - sensor_console::defineIntCommand("mqtt", setMQTTinterval, F(" 60 (Sets MQTT sending interval, in s)")); + sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)")); sensor_console::defineCommand("send_local_ip", sendInfoAboutLocalNetwork, - F(" (Sends local IP and SSID via MQTT. Can be useful to find sensor)")); + F("(Sends local IP and SSID via MQTT. Can be useful to find sensor)")); } void publish(const char *timestamp, int16_t co2, float temperature, float humidity) { @@ -79,7 +79,7 @@ namespace mqtt { command[i] = message[i]; } command[length] = 0; - sensor_console::runCommand(command); + sensor_console::execute(command); led_effects::onBoardLEDOff(); } diff --git a/ampel-firmware/sensor_console.cpp b/ampel-firmware/sensor_console.cpp index 5f1876ddda8f2405b4f1a2eea253408123408824..5fc3377ced8a4d77b9337fad19c71483159ac74c 100644 --- a/ampel-firmware/sensor_console.cpp +++ b/ampel-firmware/sensor_console.cpp @@ -6,75 +6,112 @@ namespace sensor_console { uint8_t commands_count = 0; + enum input_type { + NONE, + INT32, + STRING + }; + struct Command { const char *name; union { + void (*voidFunction)(); void (*intFunction)(int32_t); - void (*voidFunction)(void); + void (*strFunction)(char*); }; const char *doc; - bool has_parameter; + 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]; - //NOTE: Probably possible to DRY (with templates?) - void defineCommand(const char *name, void (*function)(void), const __FlashStringHelper *doc_fstring) { - const char *doc = (const char*) doc_fstring; + bool addCommand(const char *name, const __FlashStringHelper *doc_fstring) { if (commands_count < MAX_COMMANDS) { commands[commands_count].name = name; - commands[commands_count].voidFunction = function; - commands[commands_count].doc = doc; - commands[commands_count].has_parameter = false; - commands_count++; + 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) { - const char *doc = (const char*) doc_fstring; - if (commands_count < MAX_COMMANDS) { - commands[commands_count].name = name; + if (addCommand(name, doc_fstring)) { commands[commands_count].intFunction = function; - commands[commands_count].doc = doc; - commands[commands_count].has_parameter = true; - commands_count++; - } else { - Serial.println(F("Too many commands have been defined.")); + 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 function_name and an argument. - * Returns 0 if both are found, 1 if there is a problem and 2 if no argument is found. + * Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into + * a CommandLine struct (function_name, argument_type and argument) */ - uint8_t parseCommand(const char *command, char *function_name, int32_t &argument) { - char split_command[MAX_COMMAND_SIZE]; - strlcpy(split_command, command, MAX_COMMAND_SIZE); - char *arg; - char *part1; - part1 = strtok(split_command, " "); - if (!part1) { + void parseCommand(const char *command, CommandLine &command_line) { + if (strlen(command) == 0) { Serial.println(F("Received empty command")); - // Empty string - return 1; + command_line.argument_type = NONE; + return; } - strlcpy(function_name, part1, MAX_COMMAND_SIZE); - arg = strtok(NULL, " "); - uint8_t code = 0; - if (arg) { - char *end; - argument = strtol(arg, &end, 10); - if (*end) { - // Second argument isn't a number - code = 2; - } + + 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 { - // No argument - code = 2; + 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(".")); } - return code; } /* @@ -88,7 +125,7 @@ namespace sensor_console { case '\n': // end of text Serial.println(); input_line[input_pos] = 0; - runCommand(input_line); + execute(input_line); input_pos = 0; break; case '\r': // discard carriage return @@ -112,50 +149,40 @@ namespace sensor_console { } } - int compareName(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]), compareName); - for (uint8_t i = 0; i < commands_count; i++) { - Serial.print(" "); - Serial.print(commands[i].name); - Serial.print(commands[i].doc); - Serial.println("."); - } - } - /* - * Tries to find the corresponding callback for a given command. Name and number of argument should fit. + * Tries to find the corresponding callback for a given command. Name and parameter type should fit. */ - void runCommand(const char *command) { - char function_name[MAX_COMMAND_SIZE]; - int32_t argument = 0; - bool has_argument; - has_argument = (parseCommand(command, function_name, argument) == 0); - + void execute(const char *command_str) { + CommandLine input; + parseCommand(command_str, input); for (uint8_t i = 0; i < commands_count; i++) { - if (!strcmp(function_name, commands[i].name) && has_argument == commands[i].has_parameter) { + if (!strcmp(input.function_name, commands[i].name) && input.argument_type == commands[i].parameter_type) { Serial.print(F("Calling : ")); - Serial.print(function_name); - if (has_argument) { - Serial.print(F("(")); - Serial.print(argument); - Serial.println(F(")")); - commands[i].intFunction(argument); - } else { + 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; } - return; } } Serial.print(F("'")); - Serial.print(command); + Serial.print(command_str); Serial.println(F("' not supported. Available commands :")); listAvailableCommands(); } + } diff --git a/ampel-firmware/sensor_console.h b/ampel-firmware/sensor_console.h index b46e212e06c308e15ba7497509c33ffd104932ad..5cf4450d19535d19e871f5d4d48b11d09521070e 100644 --- a/ampel-firmware/sensor_console.h +++ b/ampel-firmware/sensor_console.h @@ -8,11 +8,13 @@ */ namespace sensor_console { - void defineCommand(const char *command, void (*function)(void), const __FlashStringHelper *ifsh); - void defineIntCommand(const char *command, void (*function)(int32_t), const __FlashStringHelper *ifsh); + 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 processSerialInput(const byte in_byte); - void runCommand(const char *command); + + void execute(const char *command_line); } #endif diff --git a/ampel-firmware/util.cpp b/ampel-firmware/util.cpp index e3b2d6e8913172cce6e4dd868aace8c03e8be02f..841b08ff3a6b04d2dde6e39094d38c601f7d2e72 100644 --- a/ampel-firmware/util.cpp +++ b/ampel-firmware/util.cpp @@ -74,11 +74,11 @@ char* getSensorId() { Ampel::Ampel() : board(current_board), sensorId(getSensorId()), max_loop_duration(0) { - sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F(" 1618829570 (Sets time to the given UNIX time)")); - sensor_console::defineCommand("free", Ampel::showFreeSpace, F(" (Displays available heap space)")); + sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)")); + sensor_console::defineCommand("free", Ampel::showFreeSpace, F("(Displays available heap space)")); sensor_console::defineCommand("reset", []() { ESP.restart(); - }, F(" (Restarts the sensor)")); + }, F("(Restarts the sensor)")); } Ampel ampel; diff --git a/ampel-firmware/web_server.cpp b/ampel-firmware/web_server.cpp index c885ae9749ef8099544f941151d2e3edcf45ae2c..98d18fb6f98e6573f9df8d1689037833e341fbb3 100644 --- a/ampel-firmware/web_server.cpp +++ b/ampel-firmware/web_server.cpp @@ -292,7 +292,7 @@ namespace web_server { } http.sendHeader("Location", "/"); http.send(303); - sensor_console::runCommand(http.arg("send").c_str()); + sensor_console::execute(http.arg("send").c_str()); } void handlePageNotFound() { diff --git a/ampel-firmware/wifi_util.cpp b/ampel-firmware/wifi_util.cpp index 335f94b45d8ca228496fa2957853e0ad4ba25511..4cdc7a2a597d3d66523c6e37b837346e5231311e 100644 --- a/ampel-firmware/wifi_util.cpp +++ b/ampel-firmware/wifi_util.cpp @@ -43,8 +43,8 @@ namespace wifi { // Initialize Wi-Fi void connect(const char *hostname) { - sensor_console::defineCommand("wifi_scan", scanNetworks, F(" (Scans available WiFi networks)")); - sensor_console::defineCommand("local_ip", showLocalIp, F(" (Displays local IP and current SSID)")); + sensor_console::defineCommand("wifi_scan", scanNetworks, F("(Scans available WiFi networks)")); + sensor_console::defineCommand("local_ip", showLocalIp, F("(Displays local IP and current SSID)")); //NOTE: WiFi Multi could allow multiple SSID and passwords. WiFi.persistent(false); // Don't write user & password to Flash.