diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino
index 65cf8fedcc6bddc98dd858f35bd5f2c871690d8d..a69c42a46670f7484c07cc5c9a1eae2824a588dc 100644
--- a/ampel-firmware/ampel-firmware.ino
+++ b/ampel-firmware/ampel-firmware.ino
@@ -1,32 +1,20 @@
-/**
- * IotWebConf03TypedParameters.ino -- IotWebConf is an ESP8266/ESP32
- *   non blocking WiFi/AP web configuration library for Arduino.
- *   https://github.com/prampec/IotWebConf 
- *
- * Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
- *
- * This software may be modified and distributed under the terms
- * of the MIT license.  See the LICENSE file for details.
- */
+#include <IotWebConf.h>
+#include <IotWebConfTParameter.h>
 
-/**
- * Example: Custom parameters
- * Description:
- *   Typed Parameters are a new approach to store/handle parameter data.
- *   This part of the software is very experimental, and certainly
- *   not recommended for beginners.
- *   The goal of this particular example is to compare the original
- *   approach of IotWebConf03CustomParameters to this new typed
- *   parameters, as both examples should work the same.
- *
- * Hardware setup for this example:
- *   - An LED is attached to LED_BUILTIN pin with setup On=LOW.
- *   - [Optional] A push button is attached to pin D2, the other leg of the
- *     button should be attached to GND.
+/** Other scripts can use this namespace, in order to define commands, via callbacks.
+ * Those callbacks can then be used to send commands to the sensor (reset, calibrate, led on/off, ...)
+ * The callbacks can either have no parameter, one int32_t parameter or one char pointer.
  */
 
-#include <IotWebConf.h>
-#include <IotWebConfTParameter.h>
+namespace sensor_console {
+  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);
+}
 
 // -- Initial name of the Thing. Used e.g. as SSID of the own Access Point.
 const char thingName[] = "testThing";
@@ -38,16 +26,7 @@ const char wifiInitialApPassword[] = "smrtTHNG8266";
 #define NUMBER_LEN 32
 
 // -- Configuration specific key. The value should be modified if config structure was changed.
-#define CONFIG_VERSION "dem4"
-
-// -- When CONFIG_PIN is pulled to ground on startup, the Thing will use the initial
-//      password to buld an AP. (E.g. in case of lost password)
-#define CONFIG_PIN 2 // D2
-
-// -- Status indicator pin.
-//      First it will light up (kept LOW), on Wifi connection it will blink,
-//      when connected to the Wifi it will turn off (kept HIGH).
-#define STATUS_PIN LED_BUILTIN
+#define CONFIG_VERSION "dem99"
 
 // -- Method declarations.
 void handleRoot();
@@ -129,8 +108,6 @@ void setup()
   group2.addItem(&dateParam);
   group2.addItem(&timeParam);
 
-  iotWebConf.setStatusPin(STATUS_PIN);
-  iotWebConf.setConfigPin(CONFIG_PIN);
   iotWebConf.addSystemParameter(&stringParam);
   iotWebConf.addParameterGroup(&group1);
   iotWebConf.addParameterGroup(&group2);
@@ -144,6 +121,34 @@ void setup()
   server.on("/", handleRoot);
   server.on("/config", []{ iotWebConf.handleConfig(); });
   server.onNotFound([](){ iotWebConf.handleNotFound(); });
+  
+  sensor_console::defineCommand("reset", [](){ ESP.restart(); }, F("(Restarts the ESP)"));
+  
+  sensor_console::defineCommand("save_config", [](){ iotWebConf.saveConfig(); }, F("(Saves the config to EEPROM)"));
+
+  sensor_console::defineCommand("reset_config", []() {
+    Serial.println(F("Resetting config..."));
+    iotWebConf.getRootParameterGroup()->applyDefaultValue();
+    Serial.println(F("Done!"));
+  }, F("(Resets the complete IotWeb config)"));
+
+  sensor_console::defineStringCommand("ssid", [](char *ssid) {
+    Serial.print(F("Setting WiFi ssid to "));
+    Serial.println(ssid);
+    strlcpy(iotWebConf.getWifiSsidParameter()->valueBuffer, ssid, iotWebConf.getWifiSsidParameter()->getLength());
+  },   F("name (Sets SSID to name)"));
+
+  sensor_console::defineStringCommand("wifi_pwd", [](char *pwd) {
+    Serial.print(F("Setting WiFi password to "));
+    Serial.println(pwd);
+    strlcpy(iotWebConf.getWifiPasswordParameter()->valueBuffer, pwd, iotWebConf.getWifiPasswordParameter()->getLength());
+  }, F("abc (Sets WiFi password to abc)"));
+  
+  sensor_console::defineStringCommand("ap_pwd", [](char *pwd) {
+    Serial.print(F("Setting AP password to "));
+    Serial.println(pwd);
+    strlcpy(iotWebConf.getWifiPasswordParameter()->valueBuffer, pwd, iotWebConf.getWifiPasswordParameter()->getLength());
+  }, F("abc (Sets AP password to abc)"));
 
   Serial.println("Ready.");
 }
@@ -152,6 +157,7 @@ void loop()
 {
   // -- doLoop should be called as frequently as possible.
   iotWebConf.doLoop();
+  sensor_console::checkSerialInput();
 }
 
 /**
@@ -198,3 +204,199 @@ void configSaved()
 {
   Serial.println("Configuration was updated.");
 }
+
+/*** 
+ * Sensor console
+ */
+
+namespace sensor_console {
+  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) {
+      sensor_console::processSerialInput(Serial.read());
+    }
+  }
+
+  /*
+   * 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();
+  }
+}