diff --git a/ampel-firmware/ampel-firmware.ino b/ampel-firmware/ampel-firmware.ino
index facb1e3874f36a28d389f9f01c403278691a8d7c..e8eef2f414953c155f0f2e781d0da75f35623c97 100644
--- a/ampel-firmware/ampel-firmware.ino
+++ b/ampel-firmware/ampel-firmware.ino
@@ -83,12 +83,12 @@ void setup() {
   sensor::initialize();
 
   Serial.print(F("Sensor ID: "));
-  Serial.println(SENSOR_ID);
+  Serial.println(ampel.sensorId);
   Serial.print(F("Board    : "));
-  Serial.println(BOARD);
+  Serial.println(ampel.board);
 
 #ifdef AMPEL_WIFI
-  WiFiConnect(SENSOR_ID);
+  WiFiConnect(ampel.sensorId);
 
   Serial.print(F("WiFi - Status: "));
   Serial.println(WiFi.status());
@@ -100,7 +100,7 @@ void setup() {
 
     ntp::initialize();
 
-    if (MDNS.begin(SENSOR_ID.c_str())) { // Start the mDNS responder for SENSOR_ID.local
+    if (MDNS.begin(ampel.sensorId.c_str())) { // Start the mDNS responder for SENSOR_ID.local
       MDNS.addService("http", "tcp", 80);
       Serial.println(F("mDNS responder started"));
     } else {
@@ -108,7 +108,7 @@ void setup() {
     }
 
 #  ifdef AMPEL_MQTT
-    mqtt::initialize("CO2sensors/" + SENSOR_ID);
+    mqtt::initialize("CO2sensors/" + ampel.sensorId);
 #  endif
   }
 #endif
@@ -162,10 +162,10 @@ void loop() {
   }
 
   uint32_t duration = millis() - t0;
-  if (duration > max_loop_duration) {
-    max_loop_duration = duration;
+  if (duration > ampel.max_loop_duration) {
+    ampel.max_loop_duration = duration;
     Serial.print(F("Debug - Max loop duration : "));
-    Serial.print(max_loop_duration);
+    Serial.print(ampel.max_loop_duration);
     Serial.println(F(" ms."));
   }
 }
diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp
index 430b945575fe37c115351a741cf90caffa549e64..39c90baab92ec423fc28e0c68963924791557bda 100644
--- a/ampel-firmware/co2_sensor.cpp
+++ b/ampel-firmware/co2_sensor.cpp
@@ -14,7 +14,7 @@ namespace config {
 #else
   const float temperature_offset = -3.0;  // [K] Temperature measured by sensor is usually at least 3K too high.
 #endif
-  const bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false]
+  bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false]
 }
 
 namespace sensor {
@@ -22,7 +22,7 @@ namespace sensor {
   uint16_t co2 = 0;
   float temperature = 0;
   float humidity = 0;
-  String timestamp = "";
+  char timestamp[23];
   int16_t stable_measurements = 0;
   uint32_t waiting_color = color::blue;
   bool should_calibrate = false;
@@ -71,17 +71,15 @@ namespace sensor {
     Serial.print(F("Auto-calibration is "));
     Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF.");
 
-    sensor_console::defineIntCommand("co2", setCO2forDebugging, " 1500 (Sets co2 level, for debugging purposes)");
-    sensor_console::defineIntCommand("timer", setTimer, " 30 (Sets measurement interval, in s)");
-    sensor_console::defineCommand("calibrate", startCalibrationProcess, " (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,
-        " 600 (Starts calibration process, to given ppm)");
+        F(" 600 (Starts calibration process, to given ppm)"));
     sensor_console::defineIntCommand("calibrate!", calibrateSensorRightNow,
-        " 600 (Calibrates right now, to given ppm)");
-    sensor_console::defineCommand("reset", []() {
-      ESP.restart();
-    }, " (Restarts the sensor)");
-    sensor_console::defineCommand("night_mode", led_effects::toggleNightMode, " (Toggles night mode on/off)");
+        F(" 600 (Calibrates right now, to given ppm)"));
+    sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration,
+        F(" 0/1 (Disables/enables autocalibration)"));
   }
 
   //NOTE: should timer deviation be used to adjust measurement_timestep?
@@ -168,7 +166,7 @@ namespace sensor {
 
     if (freshData) {
       // checkTimerDeviation();
-      timestamp = ntp::getLocalTime();
+      ntp::getLocalTime(timestamp);
       co2 = scd30.getCO2();
       temperature = scd30.getTemperature();
       humidity = scd30.getHumidity();
@@ -212,6 +210,13 @@ namespace sensor {
     Serial.println(co2);
   }
 
+  void setAutoCalibration(int32_t autoCalibration) {
+    config::auto_calibrate_sensor = autoCalibration;
+    scd30.setAutoSelfCalibration(autoCalibration);
+    Serial.print(F("Setting auto-calibration to : "));
+    Serial.println(autoCalibration ? F("On.") : F("Off."));
+  }
+
   void setTimer(int32_t timestep) {
     if (timestep >= 2 && timestep <= 1800) {
       Serial.print(F("Setting Measurement Interval to : "));
diff --git a/ampel-firmware/co2_sensor.h b/ampel-firmware/co2_sensor.h
index dd18bcf312048448ea665ad61b12ccb14b6f27c4..275c6e755d84681230101df08bd5bd4060528bd1 100644
--- a/ampel-firmware/co2_sensor.h
+++ b/ampel-firmware/co2_sensor.h
@@ -12,7 +12,7 @@
 
 namespace config {
   extern uint16_t measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor)
-  extern const bool auto_calibrate_sensor; // [true / false]
+  extern bool auto_calibrate_sensor; // [true / false]
   extern uint16_t co2_calibration_level; // [ppm]
   extern const float temperature_offset; // [K] Sign isn't relevant.
 }
@@ -22,7 +22,7 @@ namespace sensor {
   extern uint16_t co2;
   extern float temperature;
   extern float humidity;
-  extern String timestamp;
+  extern char timestamp[23];
 
   void initialize();
   bool processData();
@@ -32,5 +32,6 @@ namespace sensor {
   void setTimer(int32_t timestep);
   void calibrateSensorToSpecificPPM(int32_t calibrationLevel);
   void calibrateSensorRightNow(int32_t calibrationLevel);
+  void setAutoCalibration(int32_t autoCalibration);
 }
 #endif
diff --git a/ampel-firmware/config.public.h b/ampel-firmware/config.public.h
index feb7f4e612f0fecb0b268f58b7b8fbdee04cb6ff..1ca67666cb3f76cb20ca1a9d03dc1b38d6bc3558 100644
--- a/ampel-firmware/config.public.h
+++ b/ampel-firmware/config.public.h
@@ -144,7 +144,7 @@
  */
 
 #  define NTP_SERVER "pool.ntp.org"
-#  define UTC_OFFSET_IN_SECONDS 3600 // [s] 3600 for UTC+1
+#  define UTC_OFFSET_IN_SECONDS 7200 // [s] 3600 for UTC+1, 7200 for UTC+1 and daylight saving time
 
 /**
  * Others
diff --git a/ampel-firmware/csv_writer.cpp b/ampel-firmware/csv_writer.cpp
index 2a7f462a4a750ed7565a8ff8318d6a608cbf12a0..199d9f39f38e99cd6e641a42e5690e07439522f0 100644
--- a/ampel-firmware/csv_writer.cpp
+++ b/ampel-firmware/csv_writer.cpp
@@ -6,7 +6,7 @@ namespace config {
 }
 namespace csv_writer {
   unsigned long last_written_at = 0;
-  String last_successful_write = "";
+  char last_successful_write[23];
 
 #if defined(ESP8266)
   /**
@@ -78,7 +78,7 @@ namespace csv_writer {
   }
 #endif
 
-  const String filename = "/" + SENSOR_ID + ".csv";
+  const String filename = "/" + ampel.sensorId + ".csv";
 
   int getAvailableSpace() {
     return getTotalSpace() - getUsedSpace();
@@ -115,9 +115,9 @@ namespace csv_writer {
     showFilesystemContent();
     Serial.println();
 
-    sensor_console::defineIntCommand("csv", setCSVinterval, " 60 (Sets CSV writing interval, in s)");
-    sensor_console::defineCommand("format_filesystem", formatFilesystem, " (Deletes the whole filesystem)");
-    sensor_console::defineCommand("show_csv", showCSVContent, " (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() {
@@ -132,11 +132,11 @@ namespace csv_writer {
     return csv_file;
   }
 
-  void log(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity) {
+  void log(const char *timestamp, const int16_t &co2, const float &temperature, const float &humidity) {
     led_effects::onBoardLEDOn();
     File csv_file = openOrCreate();
     char csv_line[42];
-    snprintf(csv_line, sizeof(csv_line), "%s;%d;%.1f;%.1f\r\n", timeStamp.c_str(), co2, temperature, humidity);
+    snprintf(csv_line, sizeof(csv_line), "%s;%d;%.1f;%.1f\r\n", timestamp, co2, temperature, humidity);
     if (csv_file) {
       size_t written_bytes = csv_file.print(csv_line);
       csv_file.close();
@@ -145,7 +145,7 @@ namespace csv_writer {
       } else {
         Serial.print(F("CSV - Wrote : "));
         Serial.print(csv_line);
-        last_successful_write = ntp::getLocalTime();
+        ntp::getLocalTime(last_successful_write);
       }
       updateFsInfo();
       delay(50);
@@ -156,7 +156,7 @@ namespace csv_writer {
     led_effects::onBoardLEDOff();
   }
 
-  void logIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity) {
+  void logIfTimeHasCome(const char *timeStamp, const int16_t &co2, const float &temperature, const float &humidity) {
     unsigned long now = seconds();
     if (now - last_written_at > config::csv_interval) {
       last_written_at = now;
diff --git a/ampel-firmware/csv_writer.h b/ampel-firmware/csv_writer.h
index 080d5cdcc2e4b2e5aa17617118120f5c5213dac6..ab4a87b2972e8b3ae597e637a6f309da0d829658 100644
--- a/ampel-firmware/csv_writer.h
+++ b/ampel-firmware/csv_writer.h
@@ -20,9 +20,9 @@ namespace config {
   extern uint16_t csv_interval; // [s]
 }
 namespace csv_writer {
-  extern String last_successful_write;
+  extern char last_successful_write[23];
   void initialize();
-  void logIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temperature, const float &humidity);
+  void logIfTimeHasCome(const char* timestamp, const int16_t &co2, const float &temperature, const float &humidity);
   int getAvailableSpace();
   extern const String filename;
 
diff --git a/ampel-firmware/led_effects.cpp b/ampel-firmware/led_effects.cpp
index 51ba98b96c3e19df4737c0ef77900fa450e6e231..ee0fccde5222ecf935dc444d86ed8663d5291a77 100644
--- a/ampel-firmware/led_effects.cpp
+++ b/ampel-firmware/led_effects.cpp
@@ -74,6 +74,7 @@ namespace led_effects {
     pixels.begin();
     pixels.setBrightness(config::max_brightness);
     LEDsOff();
+    sensor_console::defineCommand("night_mode", toggleNightMode, F(" (Toggles night mode on/off)"));
   }
 
   void toggleNightMode() {
diff --git a/ampel-firmware/led_effects.h b/ampel-firmware/led_effects.h
index 3deb809940e61c9d042ddc52614f7b0ec16a08ca..96367ef5f2909d38377e2441f91d4e2e1abe2198 100644
--- a/ampel-firmware/led_effects.h
+++ b/ampel-firmware/led_effects.h
@@ -2,6 +2,7 @@
 #define LED_EFFECTS_H_INCLUDED
 #include <Arduino.h>
 #include "config.h"
+#include "sensor_console.h"
 
 // Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip)
 // https://github.com/adafruit/Adafruit_NeoPixel
diff --git a/ampel-firmware/lorawan.cpp b/ampel-firmware/lorawan.cpp
index 70a02e1c8325a21a42f63402956d497264baf3d8..784b46fb61b25bc8d3868a3a541baa14f241150a 100644
--- a/ampel-firmware/lorawan.cpp
+++ b/ampel-firmware/lorawan.cpp
@@ -33,7 +33,7 @@ void os_getDevKey(u1_t *buf) {
 namespace lorawan {
   bool waiting_for_confirmation = false;
   bool connected = false;
-  String last_transmission = "";
+  char last_transmission[23] = "";
 
   void initialize() {
     Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz."));
@@ -47,7 +47,7 @@ namespace lorawan {
     LMIC_reset();
     // Join, but don't send anything yet.
     LMIC_startJoining();
-    sensor_console::defineIntCommand("lora", setLoRaInterval, " 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.
@@ -65,8 +65,10 @@ namespace lorawan {
   }
 
   void onEvent(ev_t ev) {
+    char current_time[23];
+    ntp::getLocalTime(current_time);
     Serial.print("LoRa - ");
-    Serial.print(ntp::getLocalTime());
+    Serial.print(current_time);
     Serial.print(" - ");
     switch (ev) {
     case EV_JOINING:
@@ -113,7 +115,7 @@ namespace lorawan {
       Serial.println(F("EV_REJOIN_FAILED"));
       break;
     case EV_TXCOMPLETE:
-      last_transmission = ntp::getLocalTime();
+      ntp::getLocalTime(last_transmission);
       Serial.println(F("EV_TXCOMPLETE"));
       break;
     case EV_TXSTART:
diff --git a/ampel-firmware/lorawan.h b/ampel-firmware/lorawan.h
index 6cf1226f72f7385e075ff387c160d0dbdefa2ffd..45f462b2ef761066797ccd2ff114762088b5c628 100644
--- a/ampel-firmware/lorawan.h
+++ b/ampel-firmware/lorawan.h
@@ -39,7 +39,7 @@ namespace config {
 namespace lorawan {
   extern bool waiting_for_confirmation;
   extern bool connected;
-  extern String last_transmission;
+  extern char last_transmission[];
   void initialize();
   void process();
   void preparePayloadIfTimeHasCome(const int16_t &co2, const float &temp, const float &hum);
diff --git a/ampel-firmware/mqtt.cpp b/ampel-firmware/mqtt.cpp
index 516090744a5ac7c8cfed9cfdee02391b5bb78d4e..fda75272a905792d62cb38f1e43b53c6bfd464b7 100644
--- a/ampel-firmware/mqtt.cpp
+++ b/ampel-firmware/mqtt.cpp
@@ -25,7 +25,7 @@ namespace mqtt {
 
   String publish_topic;
   const char *json_sensor_format;
-  String last_successful_publish = "";
+  char last_successful_publish[23] = "";
 
   void initialize(String &topic) {
     json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}");
@@ -36,22 +36,22 @@ 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, " 60 (Sets MQTT sending interval, in s)");
+    sensor_console::defineIntCommand("mqtt", setMQTTinterval, F(" 60 (Sets MQTT sending interval, in s)"));
     sensor_console::defineCommand("local_ip", sendInfoAboutLocalNetwork,
-        " (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 String &timestamp, int16_t co2, float temperature, float humidity) {
+  void publish(const char *timestamp, int16_t co2, float temperature, float humidity) {
     if (WiFi.status() == WL_CONNECTED && mqttClient.connected()) {
       led_effects::onBoardLEDOn();
       Serial.print(F("MQTT - Publishing message ... "));
 
       char payload[75]; // Should be enough for json...
-      snprintf(payload, sizeof(payload), json_sensor_format, timestamp.c_str(), co2, temperature, humidity);
+      snprintf(payload, sizeof(payload), json_sensor_format, timestamp, co2, temperature, humidity);
       // Topic is the same as clientID. e.g. 'CO2sensors/ESP3d03da'
       if (mqttClient.publish(publish_topic.c_str(), payload)) {
         Serial.println(F("OK"));
-        last_successful_publish = ntp::getLocalTime();
+        ntp::getLocalTime(last_successful_publish);
       } else {
         Serial.println(F("Failed."));
       }
@@ -120,12 +120,12 @@ namespace mqtt {
     }
   }
 
-  void publishIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temp, const float &hum) {
+  void publishIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temp, const float &hum) {
     // Send message via MQTT according to sending interval
     unsigned long now = seconds();
     if (now - last_sent_at > config::mqtt_sending_interval) {
       last_sent_at = now;
-      publish(timeStamp, co2, temp, hum);
+      publish(timestamp, co2, temp, hum);
     }
   }
 
diff --git a/ampel-firmware/mqtt.h b/ampel-firmware/mqtt.h
index aaaa95621d10cdf0a8608700654a89b360563989..9167c47dda720528f138ff048630d399e6656984 100644
--- a/ampel-firmware/mqtt.h
+++ b/ampel-firmware/mqtt.h
@@ -11,11 +11,11 @@ namespace config {
   extern uint16_t mqtt_sending_interval; // [s]
 }
 namespace mqtt {
-  extern String last_successful_publish;
+  extern char last_successful_publish[];
   extern bool connected;
   void initialize(String &topic);
   void keepConnection();
-  void publishIfTimeHasCome(const String &timeStamp, const int16_t &co2, const float &temp, const float &hum);
+  void publishIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temp, const float &hum);
 
   void setMQTTinterval(int32_t sending_interval);
   void sendInfoAboutLocalNetwork();
diff --git a/ampel-firmware/sensor_console.cpp b/ampel-firmware/sensor_console.cpp
index ec55534d0ed69c96a820047939ff0b1285db4faa..fd29d4acc03cfe3bf55cb90ce26d8b245bbccc69 100644
--- a/ampel-firmware/sensor_console.cpp
+++ b/ampel-firmware/sensor_console.cpp
@@ -19,7 +19,8 @@ namespace sensor_console {
   Command commands[MAX_COMMANDS];
 
   //NOTE: Probably possible to DRY (with templates?)
-  void defineCommand(const char *name, void (*function)(void), const char *doc) {
+  void defineCommand(const char *name, void (*function)(void), const __FlashStringHelper *doc_fstring) {
+    const char *doc = (const char PROGMEM*) doc_fstring;
     if (commands_count < MAX_COMMANDS) {
       commands[commands_count].name = name;
       commands[commands_count].voidFunction = function;
@@ -31,7 +32,8 @@ namespace sensor_console {
     }
   }
 
-  void defineIntCommand(const char *name, void (*function)(int32_t), const char *doc) {
+  void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) {
+    const char *doc = (const char PROGMEM*) doc_fstring;
     if (commands_count < MAX_COMMANDS) {
       commands[commands_count].name = name;
       commands[commands_count].intFunction = function;
@@ -50,13 +52,11 @@ namespace sensor_console {
   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);
-    Serial.print(F("Received : '"));
-    Serial.print(command);
-    Serial.println("'");
     char *arg;
     char *part1;
     part1 = strtok(split_command, " ");
     if (!part1) {
+      Serial.println(F("Received empty command"));
       // Empty string
       return 1;
     }
@@ -126,7 +126,6 @@ namespace sensor_console {
       Serial.print(commands[i].doc);
       Serial.println(".");
     }
-    led_effects::showKITTWheel(color::red, 1);
   }
 
   /*
@@ -143,18 +142,20 @@ namespace sensor_console {
         Serial.print(F("Calling : "));
         Serial.print(function_name);
         if (has_argument) {
-          Serial.print("(");
+          Serial.print(F("("));
           Serial.print(argument);
-          Serial.println(")");
+          Serial.println(F(")"));
           commands[i].intFunction(argument);
         } else {
-          Serial.println("()");
+          Serial.println(F("()"));
           commands[i].voidFunction();
         }
         return;
       }
     }
-    Serial.println(F("Message not supported. Available commands :"));
+    Serial.print(F("'"));
+    Serial.print(command);
+    Serial.println(F("' not supported. Available commands :"));
     listAvailableCommands();
   }
 }
diff --git a/ampel-firmware/sensor_console.h b/ampel-firmware/sensor_console.h
index 8afa40c34838b2c321b55546a75a34d74360ab6b..e15241416c4ca45d6ad83992cf9609af51f0dc43 100644
--- a/ampel-firmware/sensor_console.h
+++ b/ampel-firmware/sensor_console.h
@@ -1,5 +1,6 @@
+#ifndef SENSOR_CONSOLE_H_INCLUDED
+#define SENSOR_CONSOLE_H_INCLUDED
 #include <Arduino.h>
-#include "led_effects.h"
 
 /** 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, night mode, ...)
@@ -9,6 +10,8 @@
 namespace sensor_console {
   void processSerialInput(const byte in_byte);
   void runCommand(const char *command);
-  void defineIntCommand(const char *command, void (*function)(int32_t), const char *doc);
-  void defineCommand(const char *command, void (*function)(void), const char *doc);
+  void defineIntCommand(const char *command, void (*function)(int32_t), const __FlashStringHelper *ifsh);
+  void defineCommand(const char *command, void (*function)(void), const __FlashStringHelper *ifsh);
 }
+
+#endif
diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp b/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp
index 048a82a4bcec1d3885070fbb83db764f60369080..1b52de5af6eefae690044fa9138222fb3aa78d1b 100644
--- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp
+++ b/ampel-firmware/src/lib/NTPClient-master/NTPClient.cpp
@@ -152,19 +152,17 @@ int NTPClient::getSeconds() {
   return (this->getEpochTime() % 60);
 }
 
-String NTPClient::getFormattedTime(unsigned long secs) {
+void NTPClient::getFormattedTime(char *formatted_time, unsigned long secs) {
   unsigned long rawTime = secs ? secs : this->getEpochTime();
   unsigned int hours = (rawTime % 86400L) / 3600;
   unsigned int minutes = (rawTime % 3600) / 60;
   unsigned int seconds = rawTime % 60;
 
-  char formatted_time[9];
-  snprintf(formatted_time, sizeof(formatted_time), "%02d:%02d:%02d", hours, minutes, seconds);
-  return String(formatted_time);
+  snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds);
 }
 
 // Based on https://github.com/PaulStoffregen/Time/blob/master/Time.cpp
-String NTPClient::getFormattedDate(unsigned long secs) {
+void NTPClient::getFormattedDate(char *formatted_date, unsigned long secs) {
   unsigned long rawTime = (secs ? secs : this->getEpochTime()) / 86400L;  // in days
   unsigned long days = 0, year = 1970;
   uint8_t month;
@@ -187,11 +185,9 @@ String NTPClient::getFormattedDate(unsigned long secs) {
   month++; // jan is month 1
   rawTime++; // first day is day 1
 
-  char formatted_date[23];
-  snprintf(formatted_date, sizeof(formatted_date), "%4lu-%02d-%02lu %s%+03d",
-      year, month, rawTime, this->getFormattedTime(secs).c_str(), this->_timeOffset / 3600);
-
-  return String(formatted_date);
+  char formatted_time[9];
+  this->getFormattedTime(formatted_time, secs);
+  snprintf(formatted_date, 23, "%4lu-%02d-%02lu %s%+03d", year, month, rawTime, formatted_time, this->_timeOffset / 3600);
 }
 
 void NTPClient::end() {
diff --git a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h b/ampel-firmware/src/lib/NTPClient-master/NTPClient.h
index ad450706490679ee8a459c5471efec167e419d21..3349dbb7df998fc931874c3f1ec86cda07fc3f80 100644
--- a/ampel-firmware/src/lib/NTPClient-master/NTPClient.h
+++ b/ampel-firmware/src/lib/NTPClient-master/NTPClient.h
@@ -80,7 +80,7 @@ class NTPClient {
     /**
     * @return secs argument (or 0 for current time) formatted like `hh:mm:ss`
     */
-    String getFormattedTime(unsigned long secs = 0);
+    void getFormattedTime(char *formatted_time, unsigned long secs = 0);
 
     /**
      * @return time in seconds since Jan. 1, 1970
@@ -91,7 +91,7 @@ class NTPClient {
     * @return secs argument (or 0 for current date) formatted to ISO 8601
     * like `2004-02-12T15:19:21+00:00`
     */
-    String getFormattedDate(unsigned long secs = 0);
+    void getFormattedDate(char *formatted_date, unsigned long secs = 0);
 
     /**
      * Stops the underlying UDP client
diff --git a/ampel-firmware/util.cpp b/ampel-firmware/util.cpp
index 31df19dd856e5c3f17b5eb929979660eb27db715..8a593666ae0b4fa10fd9568ad29447a8680c0812 100644
--- a/ampel-firmware/util.cpp
+++ b/ampel-firmware/util.cpp
@@ -5,6 +5,14 @@ namespace config {
   const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1
 }
 
+#if defined(ESP8266)
+const char *current_board = "ESP8266";
+#elif defined(ESP32)
+const char *current_board = "ESP32";
+#else
+const char *current_board = "UNKNOWN";
+#endif
+
 // Get last 3 bytes of ESP MAC (worldwide unique)
 String macToID() {
   uint8_t mac[6];
@@ -24,22 +32,51 @@ String macToID() {
 namespace ntp {
   WiFiUDP ntpUDP;
   NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL);
+  bool connected_at_least_once = false;
 
   void initialize() {
     timeClient.begin();
   }
 
   void update() {
-    timeClient.update();
+    connected_at_least_once |= timeClient.update();
+  }
+
+  void getLocalTime(char *timestamp) {
+    timeClient.getFormattedDate(timestamp);
   }
 
-  String getLocalTime() {
-    return timeClient.getFormattedDate();
+  void setLocalTime(int32_t unix_seconds) {
+    char time[23];
+    timeClient.getFormattedDate(time);
+    Serial.print(F("Current time : "));
+    Serial.println(time);
+    if (connected_at_least_once) {
+      Serial.println(F("NTP update already happened. Not changing anything."));
+      return;
+    }
+    Serial.print(F("Setting UNIX time to : "));
+    Serial.println(unix_seconds);
+    timeClient.setEpochTime(unix_seconds - seconds());
+    timeClient.getFormattedDate(time);
+    Serial.print(F("Current time : "));
+    Serial.println(time);
   }
 }
 
-uint32_t max_loop_duration = 0;
+void Ampel::showFreeSpace() {
+  Serial.print(F("Free heap space : "));
+  Serial.print(get_free_heap_size());
+  Serial.println(F(" bytes."));
+}
+
+Ampel::Ampel() :
+    board(current_board), sensorId("ESP" + macToID()), 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::defineCommand("reset", []() {
+    ESP.restart();
+  }, F(" (Restarts the sensor)"));
+}
 
-//FIXME: Remove every instance of Strings, to avoid heap fragmentation problems. (Start:  "Free heap space : 17104 bytes")
-// See more https://cpp4arduino.com/2020/02/07/how-to-format-strings-without-the-string-class.html
-const String SENSOR_ID = "ESP" + macToID();
+Ampel ampel;
diff --git a/ampel-firmware/util.h b/ampel-firmware/util.h
index 2997933480b06db67b18a09bafa2d156d8b7e7b5..b99a5d52d6c137c92372223da317ae7e2bf2ad64 100644
--- a/ampel-firmware/util.h
+++ b/ampel-firmware/util.h
@@ -2,26 +2,23 @@
 #define AMPEL_UTIL_H_INCLUDED
 #include <Arduino.h>
 #include "config.h"
+#include "sensor_console.h"
 
 #include <WiFiUdp.h> // required for NTP
 #include "src/lib/NTPClient-master/NTPClient.h" // NTP
 
 #if defined(ESP8266)
-#  define BOARD "ESP8266"
 #  include <ESP8266WiFi.h> // required to get MAC address
 #  define get_free_heap_size() system_get_free_heap_size()
 #elif defined(ESP32)
-#  define BOARD "ESP32"
 #  include <WiFi.h> // required to get MAC address
 #  define get_free_heap_size() esp_get_free_heap_size()
-#else
-#  define BOARD "Unknown"
 #endif
 
 namespace ntp {
   void initialize();
   void update();
-  String getLocalTime();
+  void getLocalTime(char *timestamp);
 }
 
 namespace util {
@@ -35,10 +32,18 @@ namespace util {
     return b > a ? b : a;
   }
 }
+class Ampel {
+private:
+  static void showFreeSpace();
+public:
+  const char *board;
+  const String sensorId;
+  uint32_t max_loop_duration;
+  Ampel();
+};
 
 //NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over.
 #define seconds() (millis() / 1000UL)
-extern uint32_t max_loop_duration;
-const extern String SENSOR_ID;
+extern Ampel ampel;
 
 #endif
diff --git a/ampel-firmware/web_server.cpp b/ampel-firmware/web_server.cpp
index c9dfeab5e2546703dc2968f5eefa45799d86282a..ee20bf43cf57c5171a0b28a5915354c9cdf8dc48 100644
--- a/ampel-firmware/web_server.cpp
+++ b/ampel-firmware/web_server.cpp
@@ -184,7 +184,7 @@ namespace web_server {
     http.begin();
 
     Serial.print(F("You can access this sensor via http://"));
-    Serial.print(SENSOR_ID);
+    Serial.print(ampel.sensorId);
     Serial.print(F(".local (might be unstable) or http://"));
     Serial.println(WiFi.localIP());
   }
@@ -212,7 +212,7 @@ namespace web_server {
     char content[2000]; // Update if needed
     // INFO - Header size : 1767 - Body size : 1812 - Script size : 1909
 
-    snprintf_P(content, sizeof(content), header_template, sensor::co2, SENSOR_ID.c_str(),
+    snprintf_P(content, sizeof(content), header_template, sensor::co2, ampel.sensorId.c_str(),
         WiFi.localIP().toString().c_str()
 #ifdef AMPEL_CSV
         , csv_writer::filename.c_str()
@@ -225,21 +225,21 @@ namespace web_server {
     http.send_P(200, PSTR("text/html"), content);
 
     // Body
-    snprintf_P(content, sizeof(content), body_template, SENSOR_ID.c_str(), sensor::co2, sensor::temperature,
-        sensor::humidity, sensor::timestamp.c_str(), config::measurement_timestep,
+    snprintf_P(content, sizeof(content), body_template, ampel.sensorId.c_str(), sensor::co2, sensor::temperature,
+        sensor::humidity, sensor::timestamp, config::measurement_timestep,
 #ifdef AMPEL_CSV
-        csv_writer::last_successful_write.c_str(), config::csv_interval, csv_writer::getAvailableSpace() / 1024,
+        csv_writer::last_successful_write, config::csv_interval, csv_writer::getAvailableSpace() / 1024,
 #endif
 #ifdef AMPEL_MQTT
-        mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish.c_str(), config::mqtt_sending_interval,
+        mqtt::connected ? "Yes" : "No", mqtt::last_successful_publish, config::mqtt_sending_interval,
 #endif
 #if defined(AMPEL_LORAWAN) && defined(ESP32)
-        lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission.c_str(),
+        lorawan::connected ? "Yes" : "No", LMIC_FREQUENCY_PLAN, lorawan::last_transmission,
         config::lorawan_sending_interval,
 #endif
-        config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", SENSOR_ID.c_str(), SENSOR_ID.c_str(),
-        WiFi.localIP().toString().c_str(), WiFi.localIP().toString().c_str(), get_free_heap_size(), max_loop_duration,
-        BOARD, dd, hh, mm, ss);
+        config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId.c_str(),
+        ampel.sensorId.c_str(), WiFi.localIP().toString().c_str(), WiFi.localIP().toString().c_str(),
+        get_free_heap_size(), ampel.max_loop_duration, ampel.board, dd, hh, mm, ss);
 
     Serial.print(F(" - Body size : "));
     http.sendContent(content);
@@ -248,7 +248,7 @@ namespace web_server {
     // Script
     snprintf_P(content, sizeof(content), script_template
 #ifdef AMPEL_CSV
-        , csv_writer::filename.c_str(), SENSOR_ID.c_str()
+        , csv_writer::filename.c_str(), ampel.sensorId.c_str()
 #endif
         );