diff --git a/Documentation/how-to-build.rst b/Documentation/how-to-build.rst
index 33a6dc9791f1123b6cfbaec4b3e51e82f77ef1b6..252c1cf2dd424ad7da9648c0272742b32d453531 100644
--- a/Documentation/how-to-build.rst
+++ b/Documentation/how-to-build.rst
@@ -1,8 +1,8 @@
 How To Build
 ============
 If you just want to write MicroPython code for card10, you probably **won't**
-need to build the firmware yourself.  This page is for people who want to work
-on the underlying firmware itself.
+need to build the firmware yourself.  This page is for people **who want to work
+on the underlying firmware itself**.
 
 Dependencies
 ------------
@@ -26,29 +26,55 @@ Dependencies
     .. code-block:: shell-session
 
         dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib
+        
+  - macOS (Note: The card10 firmware team used Linux so far. macOS recommendations here are experimental.) 
+    
+    You can use `Homebrew`_ to install the required tools.
+    The version of the Arm crosscompiler tool chain is quite important; with the wrong version, e.g. strip and/or ld might throw strange errors.
+    
+    .. code-block:: shell-session
+            
+        brew tap px4/px4
+        brew install px4/px4/gcc-arm-none-eabi-63
+        brew install coreutils
 
   - Alternative: Download `ARM's GNU toolchain`_.  **TODO**
+.. _Homebrew: https://brew.sh/
+
 * **python3**:  For meson and various scripts needed for building.
 * **meson** (>0.43.0) & **ninja**:  Unfortunately most distros only have very old versions
   of meson in their repositories.  Instead, you'll probably save yourself a lot
   of headaches by installing meson from *pip*.
 
-   - Ubuntu / Debian:
+  - Ubuntu / Debian:
 
     .. code-block:: shell-session
 
        apt install ninja-build
        pip3 install --user meson
 
-   - Arch (has latest *meson* in the repos):
+  - Arch (has latest *meson* in the repos):
 
     .. code-block:: shell-session
 
        pacman -S meson
+       
+  - macOS 
+  
+    .. code-block:: shell-session
+            
+        brew install ninja
+        pip3 install --user meson  # see https://mesonbuild.com/Getting-meson.html - you will have to add ~/.local/bin to your PATH.
 
 * **python3-crc16**: Install with ``pip3 install --user crc16``.
 * **python3-pillow**: Python Image Library ``pip3 install --user pillow``.
 
+  - Arch
+
+    .. code-block:: shell-session
+
+       pacman -S python-crc16 python-pillow
+
 .. _ARM's GNU toolchain: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
 
 Cloning
diff --git a/Documentation/pycardium/os.rst b/Documentation/pycardium/os.rst
index 325586631487d80b8658953ad5f7578bdf2ada58..e7e174ca3dc7b27dad5746f6bce86782b76f6bc8 100644
--- a/Documentation/pycardium/os.rst
+++ b/Documentation/pycardium/os.rst
@@ -2,7 +2,8 @@
 
 ``os`` - OS Functions
 =====================
-The ``os`` module allows access to a few core functionalities of Epicardium and functions found in CPythons ``os`` module.
+The ``os`` module allows access to a few core functionalities of Epicardium and
+functions found in CPythons ``os`` module.
 
 CPython-Like
 ------------
@@ -29,8 +30,8 @@ CPython-Like
    :returns: ``bytes()`` object with ``n`` random bytes.
 
 
-Epicardium-Specific
--------------------
+Card10-Specific
+---------------
 
 .. py:function:: exit(ret = None)
 
@@ -52,6 +53,15 @@ Epicardium-Specific
    :param str name: Path to new app/script/l0dable.
    :return: This function never returns.  It can, however raise an exception.
 
+.. py:function:: read_battery()
+
+   Read the current battery voltage in V.  Please keep in mind that battery
+   voltage behaves exponentially when interpreting this value.
+
+   .. warning::
+
+      Card10 will hard-shutdown once the voltage drops below 3.4 V
+
 .. py:function:: reset()
 
    Reboot card10.
diff --git a/Documentation/pycardium/personal_state.rst b/Documentation/pycardium/personal_state.rst
index 62355e1a05ccab5eda7e871ffc9bcf654e21ec22..2601a056ecd882bd09b5dadce03811b01631a71d 100644
--- a/Documentation/pycardium/personal_state.rst
+++ b/Documentation/pycardium/personal_state.rst
@@ -2,7 +2,12 @@
 
 ``personal_state`` - Personal State
 ===================================
-The :py:mod:`personal_state` module allows you to set and get the card10 users personal state from your script. The personal state is displayed on the top-left LED on the bottom of the harmonics board. While the personal state is set the LED can't be controlled by the :py:mod:`leds` module.
+The :py:mod:`personal_state` module allows you to set and get the card10 users
+`personal state`_ from your script. The personal state is displayed on the
+top-left LED on the bottom of the harmonics board. While the personal state is
+set the LED can't be controlled by the :py:mod:`leds` module.
+
+.. _personal state: https://card10.badge.events.ccc.de/ps/
 
 **Example**:
 
@@ -26,8 +31,13 @@ The :py:mod:`personal_state` module allows you to set and get the card10 users p
 
    Set the users personal state.
 
-   :param int state: ID of the personal state to set. Must be one of :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`, :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`.
-   :param int persistent: Controls whether the personal state is persistent. A persistent state is not reset when the pycardium application is changed or restarted. In persistent mode the personal state LED is not controllable by the pycardium application.
+   :param int state: ID of the personal state to set. Must be one of
+      :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`,
+      :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`.
+   :param int persistent: Controls whether the personal state is persistent. A
+      persistent state is not reset when the pycardium application is changed
+      or restarted. In persistent mode the personal state LED is not
+      controllable by the pycardium application.
 
 .. py:function:: clear()
 
@@ -40,7 +50,8 @@ The :py:mod:`personal_state` module allows you to set and get the card10 users p
 
    Get the users personal state.
 
-   :returns: A tuple containing the currently set state and a boolean indicating if it's persistent or not.
+   :returns: A tuple containing the currently set state and a boolean
+      indicating if it's persistent or not.
 
 .. py:data:: NO_STATE
 
diff --git a/epicardium/ble/filetransfer.c b/epicardium/ble/filetransfer.c
index 03181390c573edb90e5be63ede43ed3b0b33b161..5efc3a77a68db51bb6ec6ed194012377faa5e6a4 100644
--- a/epicardium/ble/filetransfer.c
+++ b/epicardium/ble/filetransfer.c
@@ -33,6 +33,7 @@
 #include "hci_vs.h"
 
 #include <epicardium.h>
+#include "modules/log.h"
 
 #include "util/bstream.h"
 #include "att_api.h"
@@ -40,6 +41,7 @@
 #include "FreeRTOS.h"
 #include "crc32.h"
 
+#include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
@@ -217,9 +219,11 @@ static void sendCrcResponse(
 				msg,
 				sizeof(answer) - len);
 			len += strlen(msg);
-			printf("BLE file transfer: %s\n", msg);
+			LOG_ERR("filetrans", "%s\n", msg);
 		} else {
-			printf("error message \"%s\" too long\n", msg);
+			LOG_ERR("filetrans",
+				"error message \"%s\" too long\n",
+				msg);
 		}
 	}
 
@@ -227,6 +231,41 @@ static void sendCrcResponse(
 	AttsHandleValueNtf(connId, FILE_TRANS_CENTRAL_RX_VAL_HDL, len, answer);
 }
 
+/*
+ * This function splits the path into the folders and the file name and 
+ * creates all the missing folders.
+ */
+static int bleFileCreateOrOpen(char *filepath)
+{
+	char *pathEnd;
+	int pos = 0;
+	int ret;
+
+	while (true) {
+		pathEnd = strchr(filepath + pos, '/');
+		if (!pathEnd)
+			return epic_file_open(filepath, "w");
+
+		pathEnd[0] = '\00';
+		pos        = pathEnd - filepath + 1;
+
+		if (strlen(filepath)) {
+			ret = epic_file_stat(filepath, NULL);
+			if (ret == -ENOENT) {
+				ret = epic_file_mkdir(filepath);
+				if (ret) {
+					LOG_ERR("filetrans",
+						"mkdir failed: %s, ret: %i\n",
+						filepath,
+						ret);
+					return ret;
+				}
+			}
+		}
+		pathEnd[0] = '/';
+	}
+}
+
 static uint8_t bleFileOpen(dmConnId_t connId, uint8_t *pValue, uint16_t len)
 {
 	char filepath[100];
@@ -238,11 +277,12 @@ static uint8_t bleFileOpen(dmConnId_t connId, uint8_t *pValue, uint16_t len)
 
 	/* Copy only file path and not type, make sure this is NULL terminated */
 	strncpy(filepath, (char *)pValue + 1, len - 1);
+	filepath[len - 1] = 0;
 
 	if (file_fd != -1)
 		epic_file_close(file_fd);
 
-	file_fd = epic_file_open(filepath, "w");
+	file_fd = bleFileCreateOrOpen(filepath);
 	if (file_fd < 0) {
 		sendCrcResponse(connId, 'e', 0, NULL, "open failed");
 		return ATT_ERR_RESOURCES;
@@ -305,8 +345,9 @@ static uint8_t handleCentralTX(
 	} else if (
 		operation != ATT_PDU_EXEC_WRITE_REQ &&
 		operation != ATT_PDU_WRITE_CMD) {
-		printf("operation 0x%x not supported, try normal write\n",
-		       operation);
+		LOG_ERR("filetrans",
+			"operation 0x%x not supported, try normal write\n",
+			operation);
 		return ATT_ERR_INVALID_PDU;
 	}
 
@@ -334,7 +375,7 @@ static uint8_t handleCentralTX(
 		return ATT_SUCCESS;
 
 	case 'E':
-		printf("Error was acked");
+		LOG_ERR("filetrans", "Error was acked");
 		return ATT_SUCCESS;
 
 	default:
@@ -365,7 +406,9 @@ static uint8_t writeCallback(
 			connId, handle, operation, offset, len, pValue, pAttr
 		);
 	default:
-		printf("unsupported characteristic: %c\n", handle);
+		LOG_ERR("filetrans",
+			"unsupported characteristic: %c\n",
+			handle);
 		return ATT_ERR_HANDLE;
 	}
 }
@@ -377,7 +420,7 @@ static uint8_t readCallback(
 	uint16_t offset,
 	attsAttr_t *pAttr
 ) {
-	printf("read callback\n");
+	LOG_ERR("filetrans", "read callback\n");
 	return ATT_SUCCESS;
 }
 
diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c
index 72a5d67f0191b78563725255521227396b799c88..55f1debf584f7df765e71015cf2f792628286ab8 100644
--- a/lib/gfx/gfx.c
+++ b/lib/gfx/gfx.c
@@ -86,17 +86,25 @@ void gfx_puts(
 	Color fg,
 	Color bg
 ) {
+	// iterate over the string
 	while (*str) {
-		gfx_putchar(font, r, x, y, *str, fg, bg);
-		str++;
-
-		x += font->Width;
-		if (x >= r->width) {
+		// if the current position plus the width of the next character
+		// would bring us outside of the display ...
+		if ((x + font->Width) > r->width) {
+			// ... we move down a line before printing the character
 			x = 0;
 			y += font->Height;
 		}
+		// if the line is outside the display we return
 		if (y >= r->height)
 			return;
+
+		// now print the character
+		gfx_putchar(font, r, x, y, *str, fg, bg);
+		str++;
+
+		// move along on the x axis to get the position of the next character
+		x += font->Width;
 	}
 }
 
diff --git a/preload/apps/card10_nickname/__init__.py b/preload/apps/card10_nickname/__init__.py
index 301b0906d8d92770f949dbbabb989b6156e7b2b0..d38523fbf5d5ffd31b040e77797ede286cf30c4f 100644
--- a/preload/apps/card10_nickname/__init__.py
+++ b/preload/apps/card10_nickname/__init__.py
@@ -7,7 +7,6 @@ Improvement ideas
     - fade effekt
 - led nick writing
 """
-
 import utime
 import display
 import leds
@@ -19,7 +18,7 @@ import os
 
 FILENAME = 'nickname.txt'
 FILENAME_ADV = 'nickname.json'
-ANIM_TYPES = ['none', 'led', 'fade']
+ANIM_TYPES = ['none', 'led', 'fade', 'gay']
 
 
 def render_error(err1, err2):
@@ -31,24 +30,70 @@ def render_error(err1, err2):
         disp.close()
 
 
+def get_time():
+    timestamp = ''
+    if utime.localtime()[3] < 10:
+        timestamp = timestamp + '0'
+    timestamp = timestamp + str(utime.localtime()[3]) + ':'
+    if utime.localtime()[4] < 10:
+        timestamp = timestamp + '0'
+    timestamp = timestamp + str(utime.localtime()[4]) + ':'
+    if utime.localtime()[5] < 10:
+        timestamp = timestamp + '0'
+    timestamp = timestamp + str(utime.localtime()[5])
+    return timestamp
+
+
+def toggle_rockets(state):
+    brightness = 15
+    if not state:
+        brightness = 0
+    leds.set_rocket(0, brightness)
+    leds.set_rocket(1, brightness)
+    leds.set_rocket(2, brightness)
+
+
 def render_nickname(title, sub, fg, bg, fg_sub, bg_sub, main_bg):
-    anim = 'led'
+    anim = 0
     posy = 30
     if sub != '':
         posy = 18
     r = 255
     g = 0
     b = 0
+    r_sub = sub
+    last_btn_poll = utime.time() - 2
     while True:
+        sleep = 0.5
+        if sub == '#time':
+            r_sub = get_time()
         dark = 0
-        if light_sensor.get_reading() < 30:
+        if light_sensor.get_reading() < 40:
             dark = 1
         r_fg_color = fg[dark]
         r_bg_color = bg[dark]
         r_fg_sub_color = fg_sub[dark]
         r_bg_sub_color = bg_sub[dark]
         r_bg = main_bg[dark]
-        if anim == 'fade':
+        # Button handling
+        pressed = buttons.read(
+            buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT
+        )
+        if utime.time() - last_btn_poll >= 1:
+            last_btn_poll = utime.time()
+            if pressed & buttons.BOTTOM_RIGHT != 0:
+                anim = anim + 1
+                if anim >= len(ANIM_TYPES):
+                    anim = 0
+            if pressed & buttons.BOTTOM_LEFT != 0:
+                anim = anim - 1
+                if anim < 0:
+                    anim = len(ANIM_TYPES) - 1
+        # Animations
+        if ANIM_TYPES[anim] == 'fade':
+            sleep = 0.1
+            leds.clear()
+            toggle_rockets(False)
             if r > 0 and b == 0:
                 r = r - 1
                 g = g + 1
@@ -59,34 +104,32 @@ def render_nickname(title, sub, fg, bg, fg_sub, bg_sub, main_bg):
                 r = r + 1
                 b = b - 1
             r_bg = [r, g, b]
-        if anim == 'led':
-            for i in range(0, 11):
-                leds.prep(i, r_bg)
-            leds.update()
-            leds.dim_top(3)
-            leds.set_rocket(0, 15)
-            leds.set_rocket(1, 15)
-            leds.set_rocket(2, 15)
-        if anim == 'none':
+            r_bg_color = r_bg
+            r_bg_sub_color = r_bg
+        if ANIM_TYPES[anim] == 'led':
+            if dark == 1:
+                for i in range(0, 11):
+                    leds.prep(i, r_bg)
+                leds.update()
+                leds.dim_top(4)
+                toggle_rockets(True)
+            else:
+                leds.clear()
+                toggle_rockets(False)
+        if ANIM_TYPES[anim] == 'gay':
+            toggle_rockets(False)
+            leds.gay(0.4)
+        if ANIM_TYPES[anim] == 'none':
             leds.clear()
-            leds.set_rocket(0, 0)
-            leds.set_rocket(1, 0)
-            leds.set_rocket(2, 0)
+            toggle_rockets(False)
         with display.open() as disp:
             disp.rect(0, 0, 160, 80, col=r_bg, filled=True)
             disp.print(title, fg=r_fg_color, bg=r_bg_color, posx=80 - round(len(title) / 2 * 14), posy=posy)
-            if sub != '':
-                disp.print(sub, fg=r_fg_sub_color, bg=r_bg_sub_color, posx=80 - round(len(sub) / 2 * 14), posy=42)
+            if r_sub != '':
+                disp.print(r_sub, fg=r_fg_sub_color, bg=r_bg_sub_color, posx=80 - round(len(r_sub) / 2 * 14), posy=42)
             disp.update()
             disp.close()
-        pressed = buttons.read(
-            buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT
-        )
-        if pressed & buttons.BOTTOM_LEFT != 0:
-            anim = ANIM_TYPES[1]
-        if pressed & buttons.BOTTOM_RIGHT != 0:
-            anim = ANIM_TYPES[0]
-        utime.sleep(0.3)
+        utime.sleep(sleep)
 
 
 def get_key(json, key, default):
@@ -95,12 +138,10 @@ def get_key(json, key, default):
     except KeyError:
         return default
 
-
 leds.clear()
 with display.open() as disp:
     disp.clear().update()
     disp.close()
-
 if FILENAME_ADV in os.listdir("."):
     f = open(FILENAME_ADV, 'r')
     try:
diff --git a/preload/apps/card10_nickname/metadata.json b/preload/apps/card10_nickname/metadata.json
index db70873d61d8a58c53d656f8e9c593c818fe811e..2323731badeabe7aaa4f58d1a88e0c1eb1a6c979 100644
--- a/preload/apps/card10_nickname/metadata.json
+++ b/preload/apps/card10_nickname/metadata.json
@@ -1 +1 @@
-{"name":"Card10 Nickname","description":"Nickname app for the card10 badge\r\n\r\nEverything you need can be found here: https:\/\/github.com\/vabene1111\/card10-nickname","category":"graphics","author":"vabene1111","revision":3}
\ No newline at end of file
+{"name":"Card10 Nickname","description":"Nickname app for the card10 badge\r\n\r\nEverything you need can be found here: https:\/\/github.com\/vabene1111\/card10-nickname","category":"graphics","author":"vabene1111","revision":4}
\ No newline at end of file
diff --git a/preload/apps/lsd_nickname/__init__.py b/preload/apps/lsd_nickname/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4d20c38b323e591fa65e7bb8240d187985680ef
--- /dev/null
+++ b/preload/apps/lsd_nickname/__init__.py
@@ -0,0 +1,37 @@
+import display
+import leds
+import utime
+
+_rand = 123456789
+def rand():
+    global _rand
+    _rand = (1103515245 * _rand + 12345) & 0xffffff
+    return _rand
+
+gs = 160
+colors = [ ((i>>2)*gs, (i>>1&1)*gs, (i&1)*gs) for i in range(1, 8) ]
+
+nick = 'sample text'
+try:
+    with open('/nickname.txt') as f:
+        nick = f.read()
+except:
+    pass
+
+while True:
+    with display.open() as d:
+        for k in range(4):
+            (x1, y1) = (rand()%159, rand()%79)
+            (x2, y2) = (min(x1+rand()%40, 159), min(y1+rand()%40, 79))
+            try:
+                d.rect(x1, y1, x2, y2, col=colors[rand() % len(colors)], filled=True)
+            except:
+                pass
+        fg = colors[rand()%len(colors)]
+        nx = 80-round(len(nick)/2 * 14)
+        d.print(nick, fg=fg, bg=[0xff-c for c in fg], posx=(nx-8)+rand()%16, posy=22+rand()%16)
+        d.update()
+        d.close()
+    leds.set(rand() % 11, colors[rand() % len(colors)])
+    leds.set_rocket(rand() % 3, rand() % 32)
+    utime.sleep_us(1) # Feed watch doge
\ No newline at end of file
diff --git a/preload/apps/lsd_nickname/metadata.json b/preload/apps/lsd_nickname/metadata.json
new file mode 100644
index 0000000000000000000000000000000000000000..5aa83b008c5aa8292e9bf5f3a5e379f3fceb61fc
--- /dev/null
+++ b/preload/apps/lsd_nickname/metadata.json
@@ -0,0 +1 @@
+{"name":"LSD-Nickname","description":"Renderer for nickname.txt, but oh god what the fuck","category":"graphics","author":"polyfloyd","revision":1}
\ No newline at end of file
diff --git a/pycardium/modules/py/ledfx.py b/pycardium/modules/py/ledfx.py
index 1d9e3acace3faa61ef0fdde9171b9b32ab6ef196..ab8076e652a9ec9b87373574b624e7fae47cf544 100644
--- a/pycardium/modules/py/ledfx.py
+++ b/pycardium/modules/py/ledfx.py
@@ -1,4 +1,6 @@
-import leds, utime, math
+import leds
+import math
+import utime
 
 
 def col_cor(colors, brightness=1, gamma=1):
@@ -12,13 +14,16 @@ def col_cor(colors, brightness=1, gamma=1):
 
 
 def halo(colors):
-    """ 
-    sets the four bottom/side LEDs to colors corresponding to the color spectrum on the outermost of the top 11 LEDs
+    """
+    Set the four bottom/side LEDs to colors corresponding to the color spectrum
+    on the outermost of the top 11 LEDs.
     """
     used_leds = len(colors)
-    #add additional RGB-Color-lists to the colors-list to fill up the top LEDs with emptiness
+
+    # add additional RGB-Color-lists to the colors-list to fill up the top LEDs with emptiness
     colors += [[0, 0, 0]] * (11 - used_leds)
-    #add four additional colors. the last one, the first one twice, the last one. 
+
+    # add four additional colors. the last one, the first one twice, the last one.
     colors += [colors[used_leds - 1]] + [colors[0]] * 2 + [colors[used_leds - 1]]
     return colors
 
@@ -30,51 +35,61 @@ def kitt(
     minimum=0.3,
     rgb=[255, 0, 0],
     spectrum=[],
-    
     halo=False,
 ):
     """
-    LED Animation. Knight rider-Style. 
-    :param cycles: amount of cycles for the animation
-    :param delay: time in microseconds until the animation moves on. (we could also call it framerate)
-    :param power: the shape of your brightness curve. bigger values make a steeper curve, smaller values make the curve wider.
-    :param minimum: the minimal brightness
-    :param rgb: if you don't enter a spectrum this is the color we'll use
-    :param specttrum: a color spectrum consisting of up to 11 RGB-Value-Lists (e.g. [[255,255,255], [0,0,0], [255,255,255] and so on] - ). if you use less, the animation will be less wide.
+    LED Animation. Knight rider-Style.
+
+    :param int cycles: Amount of cycles for the animation
+    :param int delay: Time in microseconds until the animation moves on (Inverse of Framerate).
+    :param int power: Shape of your brightness curve.  Bigger values make a
+       steeper curve, smaller values make the curve wider.
+    :param float minimum: Minimal brightness.
+    :param [r,g,b] rgb: If you don't enter a spectrum this is the color used.
+    :param list spectrum: A color spectrum consisting of up to 11 RGB-Value-Lists
+       (e.g. ``[[255,255,255], [0,0,0], [255,255,255], ...]`` ). If you use
+       less, the animation will be less wide.
+    :param func halo: Halo function.  See :py:func:`ledfx.halo`.
     """
-    
-    # create a basic table of values for a smooth increment of the LED brightness (if you don't understand this, don't worry, i don't either. just paste it into the python shell and see the output). Basically creates a negative cosinus curve. 
+
+    # create a basic table of values for a smooth increment of the LED
+    # brightness (if you don't understand this, don't worry, i don't either.
+    # just paste it into the python shell and see the output). Basically
+    # creates a negative cosinus curve.
     kitt_table = [((-math.cos(math.pi * (x / 10.0))) + 1) / 2.0 for x in range(21)]
-    #adjust the values to start with a minimum brightness and the width of the curve to the given power.
+
+    # adjust the values to start with a minimum brightness and the width of the
+    # curve to the given power.
     kitt_table = [math.pow(x, power) * (1 - minimum) + minimum for x in kitt_table]
 
-    #for the amount of specified cycles 
+    # for the amount of specified cycles
     for i in range(cycles):
-        #repeat every 20 steps
+        # repeat every 20 steps
         j = i % 20
-        #and go backwards after 10 steps
+        # and go backwards after 10 steps
         if j > 10:
             j = 20 - j
-        #if a color spectrum wasn't given
+
         if spectrum == []:
-            #set the amount of LEDs used to 11, because we're using the full width
             used_leds = 11
-            #set the color values to the LEDs by multiplying the given color value with the corresponding brightness value in the kitt table
+
+            # set the color values to the LEDs by multiplying the given color
+            # value with the corresponding brightness value in the kitt table
             output = [[int(x * y) for y in rgb] for x in kitt_table[j : (j + used_leds)]]
         else:
-            #use the amount of leds specified in the spectrum
             used_leds = len(spectrum)
-            #multiply the color values in the corresponding spectrum tuple with the brightness value in the kitt table
+
+            # multiply the color values in the corresponding spectrum tuple
+            # with the brightness value in the kitt table
             output = [
                 [int(y * kitt_table[j + x]) for y in spectrum[x]]
                 for x in range(used_leds)
             ]
-        #if a halo is True, also use the four bottom LEDs
+
         if halo:
             halo(output)
-        #set the LEDs to the output defined above
+
         leds.set_all(output)
-        #sleep for the amount of milliseconds specified in delay
         utime.sleep_ms(delay)
-    #Switch off all LEDs.
+
     leds.clear()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..73516b14481fc30a0516fad07a866ec007db3a8c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+meson~=0.51.1
+crc16~=0.1.1
+pillow~=6.1.0