diff --git a/Documentation/card10-cfg.rst b/Documentation/card10-cfg.rst
index 3b3fdf7ab2f25229a314f96734b4cfe613c3abbf..8ad094c0e8c2a8de425990bb72c5eb8e05472698 100644
--- a/Documentation/card10-cfg.rst
+++ b/Documentation/card10-cfg.rst
@@ -40,4 +40,6 @@ Option name     Type       Description
 ``execute_elf`` Boolean    Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
 --------------- ---------- -----------
 ``timezone``    String     Timezone for card10; must be of format ``[+-]HHMM``.  Examples: ``+0800``, ``-0200``
+--------------- ---------- -----------
+``default_app`` String     Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used.
 =============== ========== ===========
diff --git a/Documentation/conf.py b/Documentation/conf.py
index d5c08c5f4b769dea51941bc31451da0b19eee0df..b80d8dadd0485fdaee5a14577412eb6972d6b213 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -62,6 +62,8 @@ class ColorExample(rst.Directive):
         color = self.arguments[0]
         html_text = '<div style="width: 30px;height: 30px;background: {};border: black 1px solid;border-radius: 15px;"></div>'
         return [nodes.raw("", html_text.format(color), format="html")]
+
+
 # }}}
 
 # -- Options for HTML output ------------------------------------------------- {{{
@@ -112,6 +114,7 @@ autodoc_mock_imports = [
     "sys_display",
     "sys_leds",
     "sys_max30001",
+    "sys_config",
     "ucollections",
     "urandom",
     "utime",
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 6693f3c2637eb5f53ed908623723231bfd8a6559..f6ce6fcd564b08a7f01229a1a235334bb8d1c7c2 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -27,6 +27,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
    pycardium/max30001
    pycardium/buttons
    pycardium/color
+   pycardium/config
    pycardium/display
    pycardium/gpio
    pycardium/leds
diff --git a/Documentation/pycardium/config.rst b/Documentation/pycardium/config.rst
new file mode 100644
index 0000000000000000000000000000000000000000..926098eb1daa66b553732648eb344634626cb621
--- /dev/null
+++ b/Documentation/pycardium/config.rst
@@ -0,0 +1,9 @@
+``config`` - Configuration
+==========================
+The ``config`` module provides functions to interact with card10's
+configuration file (``card10.cfg``).
+
+.. automodule:: config
+   :members:
+
+
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 84071d042c85643dfc181549f038e46c9c4b354a..f2398732994160a13b949f79825060221d53587f 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -145,6 +145,7 @@ typedef _Bool bool;
 #define API_CONFIG_GET_STRING      0x130
 #define API_CONFIG_GET_INTEGER     0x131
 #define API_CONFIG_GET_BOOLEAN     0x132
+#define API_CONFIG_SET_STRING      0x133
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -2060,5 +2061,21 @@ API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *v
 API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len));
 
 
+/**
+ * Write a string to the configuration file.
+ *
+ * :param char* key: Name of the option to write
+ * :param char* value: The value to write
+ * :return: `0` on success or a negative value if an error occured. Possivle
+ *    errors:
+ *
+ *    - ``-EINVAL``: Parameters out of range
+ *    - ``-ENOENT``: Key already exists but can not be read
+ *    - ``-EIO``   : Unspecified I/O error
+ *    - Any fopen/fread/fwrite/fclose related error code
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value));
 #endif /* _EPICARDIUM_H */
 
diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c
index 7371f02a94d984b67b60b3652651015484eb582a..d4c2b74e0b2fed4915ae2670a8da39b078f6042c 100644
--- a/epicardium/modules/config.c
+++ b/epicardium/modules/config.c
@@ -7,9 +7,11 @@
 #include <stdbool.h>
 #include <ctype.h>
 #include <string.h>
+#include <strings.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <stddef.h>
+#include <stdio.h>
 
 #define MAX_LINE_LENGTH 80
 #define KEYS_PER_BLOCK 16
@@ -125,25 +127,39 @@ static void add_config_pair(
 	slot->value_offset = value_offset;
 }
 
+static void trim(char *str)
+{
+	char *start = str;
+	while (*start && !isgraph(*start))
+		start++;
+
+	if (strlen(start) > 0) {
+		char *end = start + strlen(start) - 1;
+		while (*end && !isgraph(*end))
+			end--;
+		end[1] = 0;
+	}
+
+	memmove(str, start, strlen(start) + 1);
+}
+
 // parses one line of the config file
-static void
-parse_line(char *line, char *eol, int line_number, size_t line_offset)
+static void parse_line(char *line, int line_number, size_t line_offset)
 {
-	char *line_start = line;
+	char *line_start  = line;
+	char *value_start = strchr(line, '=') + 1;
 
-	//skip leading whitespace
-	while (*line && isspace((int)*line))
-		++line;
+	trim(line);
 
-	char *key = line;
-	if (*key == '#') {
+	//printf(line);
+	if (*line == '#') {
 		//skip comments
 		return;
 	}
 
 	char *eq = strchr(line, '=');
 	if (!eq) {
-		if (*key) {
+		if (*line) {
 			LOG_WARN(
 				"card10.cfg",
 				"line %d: syntax error",
@@ -152,26 +168,19 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset)
 		}
 		return;
 	}
+	*eq = 0;
+
+	char *key = line;
+	trim(key);
 
-	char *e_key = eq - 1;
-	//skip trailing whitespace in key
-	while (e_key > key && isspace((int)*e_key))
-		--e_key;
-	e_key[1] = '\0';
 	if (*key == '\0') {
 		LOG_WARN("card10.cfg", "line %d: empty key", line_number);
 		return;
 	}
 
 	char *value = eq + 1;
-	//skip leading whitespace
-	while (*value && isspace((int)*value))
-		++value;
-
-	char *e_val = eol - 1;
-	//skip trailing whitespace
-	while (e_val > value && isspace((int)*e_val))
-		--e_val;
+	trim(value);
+
 	if (*value == '\0') {
 		LOG_WARN(
 			"card10.cfg",
@@ -182,21 +191,44 @@ parse_line(char *line, char *eol, int line_number, size_t line_offset)
 		return;
 	}
 
-	size_t value_offset = value - line_start + line_offset;
+	size_t value_offset = value_start - line_start + line_offset;
 
 	add_config_pair(key, value, line_number, value_offset);
 }
 
-// convert windows line endings to unix line endings.
-// we don't care about the extra empty lines
-static void convert_crlf_to_lflf(char *buf, int n)
+typedef struct {
+	int line_number;
+	int file_offset;
+	int line_start;
+	char line[MAX_LINE_LENGTH + 1];
+	int line_length;
+} parser_state;
+
+int parse_character(char c, parser_state *s)
 {
-	while (n--) {
-		if (*buf == '\r') {
-			*buf = '\n';
+	if (c != '\r' && c != '\n') {
+		if (s->line_length == MAX_LINE_LENGTH) {
+			LOG_WARN(
+				"card10.cfg",
+				"line:%d: too long - aborting",
+				s->line_number
+			);
+			return -1;
+		}
+		s->line[s->line_length++] = c;
+	} else {
+		s->line[s->line_length] = 0;
+		//printf("New line: %s (%d %d)\n", s->line, s->line_number, s->line_start);
+		parse_line(s->line, s->line_number, s->line_start);
+		s->line_length = 0;
+		s->line_start  = s->file_offset + 1;
+		if (c == '\n') {
+			s->line_number++;
 		}
-		buf++;
 	}
+
+	s->file_offset++;
+	return 0;
 }
 
 // parses the entire config file
@@ -213,77 +245,21 @@ void load_config(void)
 		);
 		return;
 	}
-	char buf[MAX_LINE_LENGTH + 1];
-	int line_number    = 0;
-	size_t file_offset = 0;
+
+	char buf[128];
 	int nread;
+	parser_state s;
+	memset(&s, 0, sizeof(s));
+	s.line_number = 1;
 	do {
-		nread = epic_file_read(fd, buf, MAX_LINE_LENGTH);
-		convert_crlf_to_lflf(buf, nread);
-		if (nread < MAX_LINE_LENGTH) {
-			//add fake EOL to ensure termination
-			buf[nread++] = '\n';
+		nread = epic_file_read(fd, buf, sizeof(buf));
+		int i;
+		for (i = 0; i < nread; i++) {
+			parse_character(buf[i], &s);
 		}
-		//zero-terminate buffer
-		buf[nread]   = '\0';
-		char *line   = buf;
-		char *eol    = NULL;
-		int last_eol = 0;
-		while (line) {
-			//line points one character past the last (if any) '\n' hence '- 1'
-			last_eol = line - buf - 1;
-			eol      = strchr(line, '\n');
-			++line_number;
-			if (eol) {
-				*eol = '\0';
-				parse_line(line, eol, line_number, file_offset);
-				file_offset += eol - line + 1;
-				line = eol + 1;
-				continue;
-			}
-			if (line == buf) {
-				//line did not fit into buf
-				LOG_WARN(
-					"card10.cfg",
-					"line:%d: too long - aborting",
-					line_number
-				);
-				return;
-			}
-			int seek_back = last_eol - nread;
+	} while (nread == sizeof(buf));
+	parse_character('\n', &s);
 
-			LOG_DEBUG(
-				"card10.cfg",
-				"nread, last_eol, seek_back: %d,%d,%d",
-				nread,
-				last_eol,
-				seek_back
-			);
-			assert(seek_back <= 0);
-			if (!seek_back) {
-				break;
-			}
-
-			int rc = epic_file_seek(fd, seek_back, SEEK_CUR);
-			if (rc < 0) {
-				LOG_ERR("card10.cfg", "seek failed, aborting");
-				return;
-			}
-			char newline;
-			rc = epic_file_read(fd, &newline, 1);
-			if (rc < 0 || (newline != '\n' && newline != '\r')) {
-				LOG_ERR("card10.cfg", "read failed, aborting");
-				LOG_DEBUG(
-					"card10.cfg",
-					"read failed at read-back of newline: rc: %d read: %d",
-					rc,
-					(int)newline
-				);
-				return;
-			}
-			break;
-		}
-	} while (nread == MAX_LINE_LENGTH);
 	epic_file_close(fd);
 }
 
@@ -355,10 +331,11 @@ int epic_config_get_string(const char *key, char *buf, size_t buf_len)
 		return -ENOENT;
 	}
 
-	char *eol = strchr(buf, '\n');
-	if (eol) {
-		*eol = '\0';
-	}
+	char *end = buf;
+	while (*end && !iscntrl(*end))
+		end++;
+	*end = 0;
+	trim(buf);
 
 	return 0;
 }
@@ -389,10 +366,10 @@ int epic_config_get_boolean(const char *key, bool *value)
 	char buf[MAX_LINE_LENGTH];
 	epic_config_get_string(key, buf, MAX_LINE_LENGTH);
 
-	if (!strcmp(buf, "true")) {
+	if (!strcasecmp(buf, "true")) {
 		*value = true;
 		return 0;
-	} else if (!strcmp(buf, "false")) {
+	} else if (!strcasecmp(buf, "false")) {
 		*value = false;
 		return 0;
 	}
@@ -411,3 +388,256 @@ bool config_get_boolean_with_default(const char *key, bool default_value)
 		return value;
 	}
 }
+
+int epic_config_set_string(const char *key, const char *value_in)
+{
+	char value[MAX_LINE_LENGTH + 1];
+
+	if (strlen(key) > MAX_LINE_LENGTH) {
+		return -EINVAL;
+	}
+
+	/* TODO: Change interface of trim to take the buffer and size directly */
+	if (strlen(value_in) > MAX_LINE_LENGTH) {
+		return -EINVAL;
+	}
+
+	strcpy(value, value_in);
+	trim(value);
+
+	if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) {
+		return -EINVAL;
+	}
+
+	/* Check if key is sane. No control characters, spaces, equal signs or pounds allowed */
+	for (size_t i = 0; i < strlen(key); i++) {
+		char c = key[i];
+		if (!isgraph(c) || c == '=' || c == '#') {
+			return -EINVAL;
+		}
+	}
+	/* Check if value is sane. No control characters allowed */
+	for (size_t i = 0; i < strlen(value); i++) {
+		char c = value[i];
+		if (!isprint(c)) {
+			return -EINVAL;
+		}
+	}
+
+	config_slot *slot = find_config_slot(key);
+	bool present      = slot && slot->value_offset;
+	int ret           = 0;
+
+	if (!present) {
+		/* Easy case: We simply add the new option at the
+		 * end of the file. */
+
+		char buf[MAX_LINE_LENGTH];
+		/* Leading new line because I'm lazy */
+		ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value);
+
+		if (ret < 0 || ret >= (int)sizeof(buf)) {
+			return -EINVAL;
+		}
+
+		int fd = epic_file_open("card10.cfg", "a");
+		if (fd < 0) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"open for appending failed: %s (%d)",
+				strerror(-fd),
+				fd
+			);
+			return fd;
+		}
+
+		int write_ret = epic_file_write(fd, buf, strlen(buf));
+		if (write_ret < 0) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"writing failed: %s (%d)",
+				strerror(-write_ret),
+				write_ret
+			);
+		}
+
+		if (write_ret < (int)strlen(buf)) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"writing failed to write all bytes (%d of %d)",
+				write_ret,
+				strlen(buf)
+			);
+		}
+
+		ret = epic_file_close(fd);
+
+		if (ret < 0) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"close failed: %s (%d)",
+				strerror(-ret),
+				ret
+			);
+		}
+		if (write_ret < 0) {
+			ret = write_ret;
+		}
+		if (ret < 0) {
+			goto out;
+		}
+		if (write_ret < (int)strlen(buf)) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"writing failed to write all bytes (%d of %d)",
+				write_ret,
+				strlen(buf)
+			);
+			ret = -EIO;
+			goto out;
+		}
+	} else {
+		/* Complex case: The value is already somewhere in the file.
+		 * We do not want to lose existing formatting or comments.
+		 * Solution: Copy parts of the file, insert new value, copy
+		 * rest, rename.
+		 */
+		char buf[MAX_LINE_LENGTH + 1];
+		int fd1 = -1;
+		int fd2 = -1;
+		ret     = epic_config_get_string(key, buf, sizeof(buf));
+
+		size_t nread = read_config_offset(
+			slot->value_offset, buf, sizeof(buf)
+		);
+		if (nread == 0) {
+			LOG_DEBUG("card10.cfg", "could not read old value", );
+			goto complex_out;
+		}
+
+		char *end = buf;
+		while (*end && (!iscntrl(*end) || isblank(*end)))
+			end++;
+		*end = 0;
+
+		int old_len = strlen(buf);
+
+		fd1 = epic_file_open("card10.cfg", "r");
+		if (fd1 < 0) {
+			LOG_DEBUG(
+				"card10.cfg",
+				"open for read failed: %s (%d)",
+				strerror(-fd1),
+				fd1
+			);
+			ret = fd1;
+			goto complex_out;
+		}
+
+		fd2 = epic_file_open("card10.nfg", "w");
+		if (fd2 < 0) {
+			LOG_DEBUG(
+				"card10.nfg",
+				"open for writing failed: %s (%d)",
+				strerror(-fd2),
+				fd2
+			);
+			ret = fd2;
+			goto complex_out;
+		}
+
+		/* Copy over slot->value_offset bytes */
+		int i = slot->value_offset;
+		while (i > 0) {
+			int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i;
+			ret   = epic_file_read(fd1, buf, n);
+			if (ret < 0) {
+				LOG_DEBUG(
+					"card10.cfg",
+					"read failed: rc: %d",
+					ret
+				);
+				goto complex_out;
+			}
+
+			int ret2 = epic_file_write(fd2, buf, ret);
+
+			if (ret2 < 0) {
+				ret = ret2;
+				LOG_DEBUG(
+					"card10.nfg",
+					"write failed: rc: %d",
+					ret
+				);
+				goto complex_out;
+			}
+			i -= ret;
+		}
+
+		/* Insert new value into the new file */
+		ret = epic_file_write(fd2, value, strlen(value));
+		if (ret < 0) {
+			LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret);
+			goto complex_out;
+		}
+
+		/* Skip the old value inside the old file */
+		epic_file_seek(fd1, old_len, SEEK_CUR);
+
+		/* Copy the rest of the old file to the new file */
+		while (true) {
+			int ret = epic_file_read(fd1, buf, sizeof(buf));
+
+			if (ret == 0) {
+				break;
+			}
+
+			if (ret < 0) {
+				LOG_DEBUG(
+					"card10.cfg",
+					"read failed: rc: %d",
+					ret
+				);
+				goto complex_out;
+			}
+
+			int ret2 = epic_file_write(fd2, buf, ret);
+
+			if (ret2 < 0) {
+				ret = ret2;
+				LOG_DEBUG(
+					"card10.nfg",
+					"write failed: rc: %d",
+					ret
+				);
+				goto complex_out;
+			}
+
+			if (ret < (int)sizeof(buf)) {
+				break;
+			}
+		}
+
+	complex_out:
+		if (fd1 >= 0) {
+			epic_file_close(fd1);
+		}
+		if (fd2 >= 0) {
+			int ret2 = epic_file_close(fd2);
+			if (ret >= 0) {
+				ret = ret2;
+			}
+		}
+
+		if (ret >= 0) {
+			epic_file_unlink("card10.cfg");
+			epic_file_rename("card10.nfg", "card10.cfg");
+		}
+	}
+
+out:
+	/* Reload config so the new key or the changed value is available */
+	load_config();
+
+	return ret < 0 ? ret : 0;
+}
diff --git a/epicardium/modules/usb.c b/epicardium/modules/usb.c
index 572b2b5fcffa8fd2684c28b60c4d9f2937fdd3f9..5129042559d6af9079a0464dc419b6021a3678e3 100644
--- a/epicardium/modules/usb.c
+++ b/epicardium/modules/usb.c
@@ -12,6 +12,7 @@
 #include "epicardium.h"
 
 #include "modules/filesystem.h"
+#include "modules/config.h"
 
 #include "usb/cdcacm.h"
 #include "usb/mass_storage.h"
@@ -139,6 +140,7 @@ int epic_usb_shutdown(void)
 	esb_deinit();
 	if (s_fsDetached) {
 		fatfs_attach();
+		load_config();
 	}
 	return 0;
 }
@@ -155,6 +157,7 @@ int epic_usb_cdcacm(void)
 	esb_deinit();
 	if (s_fsDetached) {
 		fatfs_attach();
+		load_config();
 	}
 	return esb_init(&s_cfg_cdcacm);
 }
diff --git a/preload/main.py b/preload/main.py
index 7c8417e9ae1375779ef5a7c2fd4fdcca35cd36b7..eca0ccb401ba2474e61608a992c6fc377d54ed30 100644
--- a/preload/main.py
+++ b/preload/main.py
@@ -3,7 +3,13 @@ import os
 
 def main():
     # Try loading analog clock
-    default_app = "apps/analog_clock/__init__.py"
+    try:
+        import config
+
+        default_app = config.get_string("default_app")
+    except OSError:
+        default_app = "apps/analog_clock/__init__.py"
+
     try:
         with open(default_app, "r"):
             pass
diff --git a/preload/menu.py b/preload/menu.py
index 7a9ad11b9105a9f79564895d478b60c5fa067903..1ace36aa43f5d5ccfe0106eba245320517263ca6 100644
--- a/preload/menu.py
+++ b/preload/menu.py
@@ -90,6 +90,24 @@ def usb_mode(disp):
     os.exit(0)
 
 
+class SubMenu(simple_menu.Menu):
+    timeout = 30.0
+
+    def on_select(self, name, index):
+        print(name)
+        if name == "Make Default":
+            self.disp.clear(color.CHAOSBLUE)
+            self.disp.print("Making", posx=0, posy=20)
+            self.disp.print(self.app.name, posx=0, posy=40)
+            self.disp.print("default", posx=0, posy=60)
+            self.disp.update()
+            import config
+
+            config.set_string("default_app", self.app.path)
+            utime.sleep(1)
+        self.exit()
+
+
 class MainMenu(simple_menu.Menu):
     timeout = 30.0
 
@@ -112,6 +130,14 @@ class MainMenu(simple_menu.Menu):
             utime.sleep(1.0)
             os.exit(1)
 
+    def on_long_select(self, app, index):
+        print("Long press\n")
+        if app.path not in ["main.py", "USB_STORAGE_FLAG"]:
+            # sm = SubMenu(("Make Default", "Fav", "Unfav", "Delete", "Exit"))
+            sm = SubMenu(("Make Default", "Exit", "", ""))
+            sm.app = app
+            sm.run()
+
     def on_timeout(self):
         try:
             f = open("main.py")
diff --git a/pycardium/meson.build b/pycardium/meson.build
index 931662c251b4c52bc3cb6305351bb9f90fb67aad..d12e62f5ed6e58cd8699890a404ffc23a4b33f15 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -3,6 +3,7 @@ name = 'pycardium'
 modsrc = files(
   'modules/bhi160-sys.c',
   'modules/buttons.c',
+  'modules/sys_config.c',
   'modules/fat_file.c',
   'modules/fat_reader_import.c',
   'modules/gpio.c',
diff --git a/pycardium/modules/py/config.py b/pycardium/modules/py/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d70936ff2ba9c161712ad07a244f6ebe583a646
--- /dev/null
+++ b/pycardium/modules/py/config.py
@@ -0,0 +1,48 @@
+import sys_config
+
+
+def set_string(key, value):
+    """
+    Write a string to the configuration file ``card10.cfg``.
+
+    Both ``key`` and ``value`` must be strings or must be
+    convertible to a string using the ``str()`` function.
+
+    ``key`` must not contain spaces, control characters (including tabs),
+    number signs ans equal signs.
+    ``value` must not contain control characters (including tabs).
+    Neither is allowed to contain the sub-string ``execute_elf``.
+
+    The key/value pair is immediately written to the configuration
+    file (``card10.cfg``). After the file is written, configuration
+    is read again and the new value is available via ``config.get_string``.
+
+    :param str key:     Name of the configuration option.
+    :param str value:   Value to write.
+    :raises OSError: If writing to the configuration file failed.
+    :raises OSError: If key or value contain illegal characters.
+    :raises ValueError: If key or value contain the sub-string ``execute_elf``.
+
+    .. versionadded:: 1.16
+    """
+
+    sys_config.set_string(str(key), str(value))
+
+
+def get_string(key):
+    """
+    Read a string from the configuration file ``card10.cfg``.
+
+    ``key`` must be a string or must be convertible to a string using
+    the ``str()`` function.
+
+
+    :param str key: Name of the configuration option.
+    :rtype: str
+    :returns: Value of the configuration option.
+    :raises OSError: if the key is not present in the configuration.
+
+    .. versionadded:: 1.16
+    """
+
+    return sys_config.get_string(str(key))
diff --git a/pycardium/modules/py/meson.build b/pycardium/modules/py/meson.build
index fe26c64e2c87640a580e70f535d46d5a5b1b961f..b8d3310f053c8a46febe1935b15686cbcff86c6a 100644
--- a/pycardium/modules/py/meson.build
+++ b/pycardium/modules/py/meson.build
@@ -1,4 +1,5 @@
 python_modules = files(
+  'config.py',
   'bhi160.py',
   'bme680.py',
   'color.py',
diff --git a/pycardium/modules/py/simple_menu.py b/pycardium/modules/py/simple_menu.py
index b4b27ef5741ce442c4e7187fec9efea75f1a682b..8ff0f62da0b67bd95a6acf8e3a0319bf1b339475 100644
--- a/pycardium/modules/py/simple_menu.py
+++ b/pycardium/modules/py/simple_menu.py
@@ -141,6 +141,18 @@ class Menu:
         """
         pass
 
+    def on_long_select(self, item, index):
+        """
+        Hook when an item as selected using a long press.
+
+        The given ``index`` was selected with a long SELECT button press.  Overwrite
+        this function in your menu to perform an action on select.
+
+        :param item: The item which was selected.
+        :param int index: Index into the ``entries`` list of the ``item``.
+        """
+        pass
+
     def on_select(self, item, index):
         """
         Hook when an item as selected.
@@ -238,9 +250,7 @@ class Menu:
             offset + 20,
             col=self.color_1 if index % 2 == 0 else self.color_2,
         )
-        self.disp.print(
-            string, posx=14, posy=offset, fg=self.color_text, bg=None,
-        )
+        self.disp.print(string, posx=14, posy=offset, fg=self.color_text, bg=None)
 
     def draw_menu(self, offset=0):
         """
@@ -318,8 +328,18 @@ class Menu:
                         print("Exception during menu.on_scroll():")
                         sys.print_exception(e)
                 elif ev == self.button_select:
+                    t0 = utime.time()
+                    long_press = False
+                    while buttons.read(buttons.TOP_RIGHT) > 0:
+                        if utime.time() - t0 > 1:
+                            long_press = True
+                            break
+
                     try:
-                        self.on_select(self.entries[self.idx], self.idx)
+                        if long_press:
+                            self.on_long_select(self.entries[self.idx], self.idx)
+                        else:
+                            self.on_select(self.entries[self.idx], self.idx)
                         self.select_time = utime.time_ms()
                     except _ExitMenuException:
                         raise
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index c2c1fdace490c618807adca14bca281276714741..3a6e9dfcf47dfd16160b9b0cf76809436176bc55 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -188,3 +188,7 @@ Q(MAX86150)
 /* ws2812 */
 Q(ws2812)
 Q(set_all)
+
+Q(config)
+Q(set_string)
+Q(get_string)
diff --git a/pycardium/modules/sys_config.c b/pycardium/modules/sys_config.c
new file mode 100644
index 0000000000000000000000000000000000000000..a811a19a82ec75fba161cb40280475039348bf83
--- /dev/null
+++ b/pycardium/modules/sys_config.c
@@ -0,0 +1,85 @@
+#include "epicardium.h"
+
+#include "py/obj.h"
+#include "py/runtime.h"
+
+#include <string.h>
+#include <strings.h>
+
+#include <stdbool.h>
+
+static void extract_string(
+	mp_obj_t obj, char *buf, size_t buf_len, const char *error_message
+) {
+	size_t len;
+	const char *str_ptr = mp_obj_str_get_data(obj, &len);
+	/*
+	 * The string retrieved from MicroPython is not NULL-terminated so we
+	 * first need to copy it and add a NULL-byte.
+	 */
+	if (len > (buf_len - 1)) {
+		mp_raise_ValueError(error_message);
+	}
+	memcpy(buf, str_ptr, len);
+	buf[len] = '\0';
+}
+
+static mp_obj_t mp_config_set_string(mp_obj_t key_in, mp_obj_t value_in)
+{
+	const char *const forbidden_key = "execute_elf";
+
+	char key_str[128], value_str[128];
+
+	extract_string(key_in, key_str, sizeof(key_str), "key too long");
+	extract_string(
+		value_in, value_str, sizeof(value_str), "value too long"
+	);
+
+	if (strstr(key_str, forbidden_key) != NULL ||
+	    strstr(value_str, forbidden_key) != NULL) {
+		/* A Permission Error might be nice but is not available in MP */
+		mp_raise_ValueError("Not allowed");
+	}
+
+	int status = epic_config_set_string(key_str, value_str);
+	if (status < 0) {
+		mp_raise_OSError(-status);
+	}
+
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(set_string_obj, mp_config_set_string);
+
+static mp_obj_t mp_config_get_string(mp_obj_t key_in)
+{
+	char key_str[128], value_str[128];
+	extract_string(key_in, key_str, sizeof(key_str), "key too long");
+
+	int status =
+		epic_config_get_string(key_str, value_str, sizeof(value_str));
+	if (status < 0) {
+		mp_raise_OSError(-status);
+	}
+
+	mp_obj_t ret = mp_obj_new_str(value_str, strlen(value_str));
+	return ret;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(get_string_obj, mp_config_get_string);
+
+static const mp_rom_map_elem_t config_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_config) },
+	{ MP_ROM_QSTR(MP_QSTR_set_string), MP_ROM_PTR(&set_string_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_get_string), MP_ROM_PTR(&get_string_obj) },
+};
+
+static MP_DEFINE_CONST_DICT(config_module_globals, config_module_globals_table);
+
+// Define module object.
+const mp_obj_module_t config_module = {
+	.base    = { &mp_type_module },
+	.globals = (mp_obj_dict_t *)&config_module_globals,
+};
+
+/* This is a special macro that will make MicroPython aware of this module */
+/* clang-format off */
+MP_REGISTER_MODULE(MP_QSTR_sys_config, config_module, MODULE_CONFIG_ENABLED);
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 3d6ae86af0bb255b111f1c04cdb0c6a2c837414a..2b7059c895ae9d98c66b26c1c0319b09e6a0c71f 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -64,6 +64,7 @@ int mp_hal_trng_read_int(void);
 #define MODULE_UTIME_ENABLED                (1)
 #define MODULE_VIBRA_ENABLED                (1)
 #define MODULE_WS2812_ENABLED               (1)
+#define MODULE_CONFIG_ENABLED               (1)
 
 /*
  * This port is intended to be 32-bit, but unfortunately, int32_t for