diff --git a/python/bitmapfont.py b/python/bitmapfont.py
index 8b7004e13b761072882a3393f7a36f71071faca4..b8e2fe881447cbe824ec4f80b333cd20f27fcd5c 100644
--- a/python/bitmapfont.py
+++ b/python/bitmapfont.py
@@ -1,147 +1,147 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # 5x7 font from http://www.hwsw.no/snippets/5x7_LCD_font.php
 # every byte contains pixel data for one column, LSB is on top, MSB is ignored.
+import clearscreen
+import client
 FONT = {
-    " ":  [0x00,0x00,0x00,0x00,0x00],
-    "!":  [0x00,0x00,0x4F,0x00,0x00],
-    "\"": [0x00,0x03,0x00,0x03,0x00],
-    "#":  [0x14,0x7F,0x14,0x7F,0x14],
-    "$":  [0x24,0x2A,0x7F,0x2A,0x12],
-    "%":  [0x23,0x13,0x08,0x64,0x62],
-    "&":  [0x36,0x49,0x55,0x22,0x50],
-    "'":  [0x00,0x00,0x03,0x00,0x00],
-    "(":  [0x00,0x1C,0x22,0x41,0x00],
-    ")":  [0x00,0x41,0x22,0x1C,0x00],
-    "*":  [0x14,0x08,0x3E,0x08,0x14],
-    "+":  [0x08,0x08,0x3E,0x08,0x08],
-    ",":  [0x00,0x00,0x40,0x20,0x00],
-    "-":  [0x00,0x08,0x08,0x08,0x00],
-    ".":  [0x00,0x00,0x40,0x00,0x00],
-    "/":  [0x20,0x10,0x08,0x04,0x02],
+    " ": [0x00, 0x00, 0x00, 0x00, 0x00],
+    "!": [0x00, 0x00, 0x4F, 0x00, 0x00],
+    '"': [0x00, 0x03, 0x00, 0x03, 0x00],
+    "#": [0x14, 0x7F, 0x14, 0x7F, 0x14],
+    "$": [0x24, 0x2A, 0x7F, 0x2A, 0x12],
+    "%": [0x23, 0x13, 0x08, 0x64, 0x62],
+    "&": [0x36, 0x49, 0x55, 0x22, 0x50],
+    "'": [0x00, 0x00, 0x03, 0x00, 0x00],
+    "(": [0x00, 0x1C, 0x22, 0x41, 0x00],
+    ")": [0x00, 0x41, 0x22, 0x1C, 0x00],
+    "*": [0x14, 0x08, 0x3E, 0x08, 0x14],
+    "+": [0x08, 0x08, 0x3E, 0x08, 0x08],
+    ",": [0x00, 0x00, 0x40, 0x20, 0x00],
+    "-": [0x00, 0x08, 0x08, 0x08, 0x00],
+    ".": [0x00, 0x00, 0x40, 0x00, 0x00],
+    "/": [0x20, 0x10, 0x08, 0x04, 0x02],
+
+    "0": [0x3E, 0x51, 0x49, 0x45, 0x3E],
+    "1": [0x00, 0x42, 0x7F, 0x40, 0x00],
+    "2": [0x42, 0x61, 0x51, 0x49, 0x46],
+    "3": [0x21, 0x41, 0x45, 0x4B, 0x31],
+    "4": [0x18, 0x14, 0x12, 0x7F, 0x10],
+    "5": [0x27, 0x45, 0x45, 0x45, 0x39],
+    "6": [0x3C, 0x4A, 0x49, 0x49, 0x30],
+    "7": [0x01, 0x71, 0x09, 0x05, 0x03],
+    "8": [0x36, 0x49, 0x49, 0x49, 0x36],
+    "9": [0x06, 0x49, 0x49, 0x29, 0x1E],
+    ":": [0x00, 0x00, 0x24, 0x00, 0x00],
+    ";": [0x00, 0x00, 0x64, 0x00, 0x00],
+    "<": [0x08, 0x14, 0x22, 0x41, 0x00],
+    "=": [0x14, 0x14, 0x14, 0x14, 0x14],
+    ">": [0x00, 0x41, 0x22, 0x14, 0x08],
+    "?": [0x02, 0x01, 0x51, 0x09, 0x06],
+
+    "@": [0x30, 0x49, 0x79, 0x41, 0x3E],
+    "A": [0x7E, 0x11, 0x11, 0x11, 0x7E],
+    "B": [0x7F, 0x49, 0x49, 0x49, 0x36],
+    "C": [0x3E, 0x41, 0x41, 0x41, 0x22],
+    "D": [0x7F, 0x41, 0x41, 0x22, 0x1C],
+    "E": [0x7F, 0x49, 0x49, 0x49, 0x41],
+    "F": [0x7F, 0x09, 0x09, 0x09, 0x01],
+    "G": [0x3E, 0x41, 0x49, 0x49, 0x7A],
+    "H": [0x7F, 0x08, 0x08, 0x08, 0x7F],
+    "I": [0x00, 0x41, 0x7F, 0x41, 0x00],
+    "J": [0x20, 0x40, 0x41, 0x3F, 0x01],
+    "K": [0x7F, 0x08, 0x14, 0x22, 0x41],
+    "L": [0x7F, 0x40, 0x40, 0x40, 0x40],
+    "M": [0x7F, 0x02, 0x0C, 0x02, 0x7F],
+    "N": [0x7F, 0x04, 0x08, 0x10, 0x7F],
+    "O": [0x3E, 0x41, 0x41, 0x41, 0x3E],
 
-    "0":  [0x3E,0x51,0x49,0x45,0x3E],
-    "1":  [0x00,0x42,0x7F,0x40,0x00],
-    "2":  [0x42,0x61,0x51,0x49,0x46],
-    "3":  [0x21,0x41,0x45,0x4B,0x31],
-    "4":  [0x18,0x14,0x12,0x7F,0x10],
-    "5":  [0x27,0x45,0x45,0x45,0x39],
-    "6":  [0x3C,0x4A,0x49,0x49,0x30],
-    "7":  [0x01,0x71,0x09,0x05,0x03],
-    "8":  [0x36,0x49,0x49,0x49,0x36],
-    "9":  [0x06,0x49,0x49,0x29,0x1E],
-    ":":  [0x00,0x00,0x24,0x00,0x00],
-    ";":  [0x00,0x00,0x64,0x00,0x00],
-    "<":  [0x08,0x14,0x22,0x41,0x00],
-    "=":  [0x14,0x14,0x14,0x14,0x14],
-    ">":  [0x00,0x41,0x22,0x14,0x08],
-    "?":  [0x02,0x01,0x51,0x09,0x06],
-    
-    "@":  [0x30,0x49,0x79,0x41,0x3E],
-    "A":  [0x7E,0x11,0x11,0x11,0x7E],
-    "B":  [0x7F,0x49,0x49,0x49,0x36],
-    "C":  [0x3E,0x41,0x41,0x41,0x22],
-    "D":  [0x7F,0x41,0x41,0x22,0x1C],
-    "E":  [0x7F,0x49,0x49,0x49,0x41],
-    "F":  [0x7F,0x09,0x09,0x09,0x01],
-    "G":  [0x3E,0x41,0x49,0x49,0x7A],
-    "H":  [0x7F,0x08,0x08,0x08,0x7F],
-    "I":  [0x00,0x41,0x7F,0x41,0x00],
-    "J":  [0x20,0x40,0x41,0x3F,0x01],
-    "K":  [0x7F,0x08,0x14,0x22,0x41],
-    "L":  [0x7F,0x40,0x40,0x40,0x40],
-    "M":  [0x7F,0x02,0x0C,0x02,0x7F],
-    "N":  [0x7F,0x04,0x08,0x10,0x7F],
-    "O":  [0x3E,0x41,0x41,0x41,0x3E],
-    
-    "P":  [0x7F,0x09,0x09,0x09,0x06],
-    "Q":  [0x3E,0x41,0x51,0x21,0x5E],
-    "R":  [0x7F,0x09,0x19,0x29,0x46],
-    "S":  [0x46,0x49,0x49,0x49,0x31],
-    "T":  [0x01,0x01,0x7F,0x01,0x01],
-    "U":  [0x3F,0x40,0x40,0x40,0x3F],
-    "V":  [0x1F,0x20,0x40,0x20,0x1F],
-    "W":  [0x3F,0x40,0x30,0x40,0x3F],
-    "X":  [0x63,0x14,0x08,0x14,0x63],
-    "Y":  [0x07,0x08,0x70,0x08,0x07],
-    "Z":  [0x61,0x51,0x49,0x45,0x43],
-    "[":  [0x00,0x7F,0x41,0x41,0x00],
-    "\\": [0x02,0x04,0x08,0x10,0x20],
-    "]":  [0x00,0x41,0x41,0x7F,0x00],
-    "^":  [0x04,0x02,0x01,0x02,0x04],
-    "_":  [0x00,0x40,0x40,0x40,0x40],
-    
-    "`":  [0x00,0x01,0x02,0x04,0x00],
-    "a":  [0x20,0x54,0x54,0x54,0x78],
-    "b":  [0x7F,0x50,0x48,0x48,0x30],
-    "c":  [0x38,0x44,0x44,0x44,0x20],
-    "d":  [0x38,0x44,0x44,0x48,0x7F],
-    "e":  [0x38,0x54,0x54,0x54,0x18],
-    "f":  [0x08,0x7E,0x09,0x01,0x02],
-    "g":  [0x0C,0x54,0x54,0x54,0x3C],
-    "h":  [0x7F,0x08,0x04,0x04,0x78],
-    "i":  [0x00,0x44,0x7D,0x40,0x00],
-    "j":  [0x20,0x40,0x44,0x3D,0x00],
-    "k":  [0x7F,0x10,0x28,0x44,0x00],
-    "l":  [0x00,0x41,0x7F,0x40,0x00],
-    "m":  [0x78,0x04,0x18,0x04,0x78],
-    "n":  [0x7C,0x08,0x04,0x04,0x78],
-    "o":  [0x38,0x44,0x44,0x44,0x38],
-    
-    "p":  [0x7C,0x14,0x14,0x14,0x08],
-    "q":  [0x08,0x14,0x14,0x18,0x7C],
-    "r":  [0x7C,0x08,0x04,0x04,0x08],
-    "s":  [0x48,0x54,0x54,0x54,0x20],
-    "t":  [0x04,0x3F,0x44,0x40,0x20],
-    "u":  [0x3C,0x40,0x40,0x20,0x7C],
-    "v":  [0x1C,0x20,0x40,0x20,0x1C],
-    "w":  [0x3C,0x40,0x30,0x40,0x3C],
-    "x":  [0x44,0x28,0x10,0x28,0x44],
-    "y":  [0x0C,0x50,0x50,0x50,0x3C],
-    "z":  [0x44,0x64,0x54,0x4C,0x44],
-    "{":  [0x00,0x08,0x36,0x41,0x00],
-    "|":  [0x00,0x00,0x7F,0x00,0x00],
-    "}":  [0x00,0x41,0x36,0x08,0x00],
-    "~":  [0x0C,0x02,0x0C,0x10,0x0C],
-    "\x7f":  [0x55,0xAA,0x55,0xAA,0x55],
+    "P": [0x7F, 0x09, 0x09, 0x09, 0x06],
+    "Q": [0x3E, 0x41, 0x51, 0x21, 0x5E],
+    "R": [0x7F, 0x09, 0x19, 0x29, 0x46],
+    "S": [0x46, 0x49, 0x49, 0x49, 0x31],
+    "T": [0x01, 0x01, 0x7F, 0x01, 0x01],
+    "U": [0x3F, 0x40, 0x40, 0x40, 0x3F],
+    "V": [0x1F, 0x20, 0x40, 0x20, 0x1F],
+    "W": [0x3F, 0x40, 0x30, 0x40, 0x3F],
+    "X": [0x63, 0x14, 0x08, 0x14, 0x63],
+    "Y": [0x07, 0x08, 0x70, 0x08, 0x07],
+    "Z": [0x61, 0x51, 0x49, 0x45, 0x43],
+    "[": [0x00, 0x7F, 0x41, 0x41, 0x00],
+    "\\": [0x02, 0x04, 0x08, 0x10, 0x20],
+    "]": [0x00, 0x41, 0x41, 0x7F, 0x00],
+    "^": [0x04, 0x02, 0x01, 0x02, 0x04],
+    "_": [0x00, 0x40, 0x40, 0x40, 0x40],
 
-    u"ä": [0x20,0x55,0x54,0x55,0x78],
-    u"ü": [0x3C,0x41,0x40,0x21,0x7C],
-    u"ö": [0x38,0x45,0x44,0x45,0x38],
-    u"Ä": [0x7C,0x13,0x12,0x13,0x7C],
-    u"Ü": [0x3E,0x41,0x40,0x41,0x3E],
-    u"Ö": [0x3C,0x43,0x42,0x43,0x3C],
-    u"ß": [0x7F,0x01,0x49,0x4e,0x30],
+    "`": [0x00, 0x01, 0x02, 0x04, 0x00],
+    "a": [0x20, 0x54, 0x54, 0x54, 0x78],
+    "b": [0x7F, 0x50, 0x48, 0x48, 0x30],
+    "c": [0x38, 0x44, 0x44, 0x44, 0x20],
+    "d": [0x38, 0x44, 0x44, 0x48, 0x7F],
+    "e": [0x38, 0x54, 0x54, 0x54, 0x18],
+    "f": [0x08, 0x7E, 0x09, 0x01, 0x02],
+    "g": [0x0C, 0x54, 0x54, 0x54, 0x3C],
+    "h": [0x7F, 0x08, 0x04, 0x04, 0x78],
+    "i": [0x00, 0x44, 0x7D, 0x40, 0x00],
+    "j": [0x20, 0x40, 0x44, 0x3D, 0x00],
+    "k": [0x7F, 0x10, 0x28, 0x44, 0x00],
+    "l": [0x00, 0x41, 0x7F, 0x40, 0x00],
+    "m": [0x78, 0x04, 0x18, 0x04, 0x78],
+    "n": [0x7C, 0x08, 0x04, 0x04, 0x78],
+    "o": [0x38, 0x44, 0x44, 0x44, 0x38],
 
-    u"‘": [0x00,0x00,0x03,0x00,0x00],
-    u"’": [0x00,0x00,0x03,0x00,0x00],
-    u"‚": [0x00,0x00,0x60,0x00,0x00],
+    "p": [0x7C, 0x14, 0x14, 0x14, 0x08],
+    "q": [0x08, 0x14, 0x14, 0x18, 0x7C],
+    "r": [0x7C, 0x08, 0x04, 0x04, 0x08],
+    "s": [0x48, 0x54, 0x54, 0x54, 0x20],
+    "t": [0x04, 0x3F, 0x44, 0x40, 0x20],
+    "u": [0x3C, 0x40, 0x40, 0x20, 0x7C],
+    "v": [0x1C, 0x20, 0x40, 0x20, 0x1C],
+    "w": [0x3C, 0x40, 0x30, 0x40, 0x3C],
+    "x": [0x44, 0x28, 0x10, 0x28, 0x44],
+    "y": [0x0C, 0x50, 0x50, 0x50, 0x3C],
+    "z": [0x44, 0x64, 0x54, 0x4C, 0x44],
+    "{": [0x00, 0x08, 0x36, 0x41, 0x00],
+    "|": [0x00, 0x00, 0x7F, 0x00, 0x00],
+    "}": [0x00, 0x41, 0x36, 0x08, 0x00],
+    "~": [0x0C, 0x02, 0x0C, 0x10, 0x0C],
+    "\x7f": [0x55, 0xAA, 0x55, 0xAA, 0x55],
 
-    u"“": [0x00,0x03,0x00,0x03,0x00],
-    u"”": [0x00,0x03,0x00,0x03,0x00],
-    u"„": [0x00,0x60,0x00,0x60,0x00],
+    u"ä": [0x20, 0x55, 0x54, 0x55, 0x78],
+    u"ü": [0x3C, 0x41, 0x40, 0x21, 0x7C],
+    u"ö": [0x38, 0x45, 0x44, 0x45, 0x38],
+    u"Ä": [0x7C, 0x13, 0x12, 0x13, 0x7C],
+    u"Ü": [0x3E, 0x41, 0x40, 0x41, 0x3E],
+    u"Ö": [0x3C, 0x43, 0x42, 0x43, 0x3C],
+    u"ß": [0x7F, 0x01, 0x49, 0x4e, 0x30],
 
-    u"›": [0x00,0x00,0x28,0x10,0x00],
-    u"‹": [0x00,0x10,0x28,0x00,0x00],
-    u"»": [0x28,0x10,0x28,0x10,0x00],
-    u"«": [0x00,0x10,0x28,0x10,0x28],
+    u"‘": [0x00, 0x00, 0x03, 0x00, 0x00],
+    u"’": [0x00, 0x00, 0x03, 0x00, 0x00],
+    u"‚": [0x00, 0x00, 0x60, 0x00, 0x00],
 
-    u"☐": [0xff,0x41,0x41,0x41,0xff],
+    u"“": [0x00, 0x03, 0x00, 0x03, 0x00],
+    u"”": [0x00, 0x03, 0x00, 0x03, 0x00],
+    u"„": [0x00, 0x60, 0x00, 0x60, 0x00],
+
+    u"›": [0x00, 0x00, 0x28, 0x10, 0x00],
+    u"‹": [0x00, 0x10, 0x28, 0x00, 0x00],
+    u"»": [0x28, 0x10, 0x28, 0x10, 0x00],
+    u"«": [0x00, 0x10, 0x28, 0x10, 0x28],
+
+    u"☐": [0xff, 0x41, 0x41, 0x41, 0xff],
 }
 
-import client
-import clearscreen
 
-def test():
+def test() -> None:
     clearscreen.clear()
-    client.write(0,0,"Hello World!")
+    client.write(0, 0, "Hello World!")
     x = 0
     y = 1
     for c in sorted(FONT.keys()):
-        client.blit(x*client.PWIDTH, y*client.PHEIGHT,
-            client.PWIDTH, client.PHEIGHT, client.char_to_pixel_segment(c))
+        client.blit(x * client.PWIDTH, y * client.PHEIGHT,
+                    client.PWIDTH, client.PHEIGHT, client.char_to_pixel_segment(c))
         x += 1
-        if(x > 15):
+        if (x > 15):
             x = 0
             y += 1
diff --git a/python/brightness_test.py b/python/brightness_test.py
index cc67d0c06fdcd6e49eae05b5184eba1e6cd4ad12..efd3525d94dd883e263a7024435d75028411fa1a 100755
--- a/python/brightness_test.py
+++ b/python/brightness_test.py
@@ -1,11 +1,13 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import client
 
-def bright(b, x, y, w, h):
-    pixels = [b] * w * h
-    client.blit(x,y,w,h,pixels)
 
-if __name__=="__main__":
+def bright(b: int, x: int, y: int, w: int, h: int) -> None:
+    pixels = bytes([b] * w * h)
+    client.blit(x, y, w, h, pixels)
+
+
+if __name__ == "__main__":
     bright(0, 0, 0, 480, 70)
-    for i in range(0,16):
-      bright(i*16, i*20, 0, 19, 70)
+    for i in range(0, 16):
+        bright(i * 16, i * 20, 0, 19, 70)
diff --git a/python/cat.py b/python/cat.py
old mode 100644
new mode 100755
index 3081ceb7e366935803072458075264527d66e99a..0a0312ee83e8603aca9db85ee28f6d52f1a43e5c
--- a/python/cat.py
+++ b/python/cat.py
@@ -1,11 +1,10 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import sys
 import client
 
 # read lines from stdin and write them to the display.
-line = sys.stdin.readline().decode("utf8")
-while(line != ""):
+line = sys.stdin.readline()
+while line != "":
     client.writeline(line)
-    line = sys.stdin.readline().decode("utf8")
-
+    line = sys.stdin.readline()
diff --git a/python/clearscreen.py b/python/clearscreen.py
index 1e451973d69fcd7422f1689f040250276912fd20..c62cc1311030043cc4b3d7839892fef0df873adf 100755
--- a/python/clearscreen.py
+++ b/python/clearscreen.py
@@ -1,10 +1,11 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import client
-import random
 
-def clear():
-    pixels = [0] * client.HEIGHT * client.WIDTH
-    client.blit(0,0,client.WIDTH,client.HEIGHT,pixels)
 
-if __name__=="__main__":
+def clear(value: int = 0) -> None:
+    pixels = bytes([value,] * (client.HEIGHT * client.WIDTH))
+    client.blit(0, 0, client.WIDTH, client.HEIGHT, pixels)
+
+
+if __name__ == "__main__":
     clear()
diff --git a/python/client.py b/python/client.py
index 063050fbbcbbfa1650b7ea3f887cdb6f72e69464..9897fb296a6f7e72a926bdc34beb2d9fc601fa70 100644
--- a/python/client.py
+++ b/python/client.py
@@ -1,6 +1,10 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
+from typing import Sequence
+import zmq
+import struct
+import sys
 import os
 import bitmapfont
 
@@ -9,28 +13,25 @@ SPLIT_I = 0
 
 SPLIT = os.environ.get("SPLIT")
 if SPLIT:
-  SPLIT = list(map(int, SPLIT.split('/')))
-  SPLIT_N = SPLIT[1]
-  SPLIT_I = SPLIT[0]-1
+    SPLITx = [int(s) for s in SPLIT.split('/', 1)]
+    SPLIT_N = SPLITx[1]
+    SPLIT_I = SPLITx[0] - 1
 
-NUM_SEG_X = 96
-NUM_SEG_Y = 10
+NUM_SEG_X: int = 96
+NUM_SEG_Y: int = 10
 PWIDTH = 5
-WIDTH = int(PWIDTH*NUM_SEG_X/SPLIT_N)
-WOFFSET = (WIDTH*SPLIT_I)
+WIDTH = PWIDTH * NUM_SEG_X // SPLIT_N
+WOFFSET = WIDTH * SPLIT_I
 PHEIGHT = 7
 PPAD = 5
-HEIGHT = int(PHEIGHT*NUM_SEG_Y)
+HEIGHT = PHEIGHT * NUM_SEG_Y
 
-import sys
 
 SERVER = "tcp://mensadisplay:5556"
 
 if len(sys.argv) >= 2:
-  SERVER = sys.argv[1]
+    SERVER = sys.argv[1]
 
-import struct
-import zmq
 
 context = zmq.Context()
 socket = context.socket(zmq.REQ)
@@ -40,84 +41,103 @@ socket.connect(SERVER)
 # RAW PIXEL HANDLING
 ################################################################################
 
-def set_pixel(x, y, v):
-  x += WOFFSET
-  tx = struct.pack('<BiiB', 0, x, y, v)
-  socket.send_multipart([tx, b''])
-  rx = socket.recv()
-  #print("Received reply %s [%r]" % ((x, y, v), rx))
 
-def set_pixels(pixels):
-  msg = []
-  for x, y, v in pixels:
+def set_pixel(x: int, y: int, v: int) -> None:
+    x += WOFFSET
+    tx = struct.pack('<BiiB', 0, x, y, v)
+    socket.send_multipart([tx, b''])
+    socket.recv()
+
+
+def set_pixels(pixels: Sequence[tuple[int, int, int]]) -> None:
+    msg = []
+    for x, y, v in pixels:
+        x += WOFFSET
+        msg.append(struct.pack('<BiiB', 0, x, y, v))
+    socket.send_multipart(msg + [b''])
+    socket.recv()
+
+
+def blit(x: int, y: int, w: int, h: int, pixels: bytes, rec: bool = True) -> None:
     x += WOFFSET
-    msg.append(struct.pack('<BiiB', 0, x, y, v))
-  socket.send_multipart(msg + [b''])
-  rx = socket.recv()
+    assert w * h == len(pixels)
+    msg = struct.pack('<Biiii', 1, x, y, w, h) + pixels
+    socket.send_multipart([msg, b''])
+    if rec:
+        socket.recv()
 
-def blit(x, y, w, h, pixels, rec=True):
-  x += WOFFSET
-  assert w*h == len(pixels)
-  msg = struct.pack('<Biiii', 1, x, y, w, h)+bytes(pixels)
-  socket.send_multipart([msg, b''])
-  if rec:
-    rx = socket.recv()
 
-def rec():
-  rx = socket.recv()
+def rec() -> bytes:
+    return socket.recv()
 
 ################################################################################
 # TEXT RENDERING WITH BITMAP FONT
 ################################################################################
 
+
 # screen buffer used for text handling
-SCREENBUFFER = [0] * WIDTH * HEIGHT
+SCREENBUFFER = bytearray(WIDTH * HEIGHT)
+
 
-# blit to screen buffer, also updates screen
-def screenbuf_blit(x, y, w, h, pixels):
+def screenbuf_blit(x: int, y: int, w: int, h: int, pixels: bytes) -> None:
+    """
+    blit to screen buffer, also updates screen
+    """
     blit(x, y, w, h, pixels)
-    assert w*h == len(pixels)
+    assert w * h == len(pixels)
     for dy in range(0, h):
         for dx in range(0, w):
-            if(y+dy >= HEIGHT or x+dx >= WIDTH):
+            if y + dy >= HEIGHT or x + dx >= WIDTH:
                 return
-            pix = pixels[dy * w + dx];
-            SCREENBUFFER[(y+dy) * WIDTH + (x+dx)] = pix
+            pix = pixels[dy * w + dx]
+            SCREENBUFFER[(y + dy) * WIDTH + (x + dx)] = pix
+
 
-# scroll the screen buffer up y pixels, also updates screen
-def screenbuf_scroll(y):
+def screenbuf_scroll(y: int) -> None:
+    """
+    scroll the screen buffer up y pixels, also updates screen
+    """
     buf = SCREENBUFFER
     for dy in range(y, HEIGHT):
         for x in range(0, WIDTH):
-            SCREENBUFFER[(dy-y) * WIDTH + x] = buf[dy * WIDTH + x]
+            SCREENBUFFER[(dy - y) * WIDTH + x] = buf[dy * WIDTH + x]
     screenbuf_render()
 
-# draw screen buffer to screen
-def screenbuf_render():
+
+def screenbuf_render() -> None:
+    """
+    draw screen buffer to screen
+    """
     blit(0, 0, WIDTH, HEIGHT, SCREENBUFFER)
 
-# return array of size PWIDTH * PHEIGHT (indexed by row, then column)
-def char_to_pixel_segment(c):
-    pixels = [0] * PWIDTH * PHEIGHT
 
-    if(c not in list(bitmapfont.FONT.keys())):
-        c = "☐";
+def char_to_pixel_segment(c: str) -> bytes:
+    """
+    return array of size PWIDTH * PHEIGHT (indexed by row, then column)
+    """
+    pixels = bytearray(PWIDTH * PHEIGHT)
+
+    if (c not in list(bitmapfont.FONT.keys())):
+        c = "☐"
 
     for x in range(0, PWIDTH):
         for y in range(0, PHEIGHT):
-            pix = (bitmapfont.FONT[c][x] & (1<<y)) >> y
+            pix = (bitmapfont.FONT[c][x] & (1 << y)) >> y
             pixels[y * PWIDTH + x] = pix * 255
     return pixels
 
-# write string, starting at segment x,y. Tabs are expanded to 8 spaces, new
-# lines always begin at the given x position. No boundary checks are done, text
-# may be clipped at the border, in this case the function returns.
-# This function returns a tuple (x,y,success) where (x,y) gives the position of
-# the last character written, and success is set to False if the function
-# returned because of clipped text.
-def write(x, y, string):
+
+def write(x: int, y: int, string: str) -> tuple[int, int, bool]:
+    """
+    write string, starting at segment x,y. Tabs are expanded to 8 spaces, new
+    lines always begin at the given x position. No boundary checks are done, text
+    may be clipped at the border, in this case the function returns.
+    This function returns a tuple (x, y, success) where (x, y) gives the position of
+    the last character written, and success is set to False if the function
+    returned because of clipped text.
+    """
     orig_x = x
-    string = string.replace("\t", " "*8)
+    string = string.replace("\t", " " * 8)
     for c in string:
         if c == "\n":
             y += 1
@@ -126,34 +146,39 @@ def write(x, y, string):
             pass
         else:
             pixels = char_to_pixel_segment(c)
-            screenbuf_blit(x*PWIDTH, y*PHEIGHT, PWIDTH, PHEIGHT, pixels)
+            screenbuf_blit(x * PWIDTH, y * PHEIGHT, PWIDTH, PHEIGHT, pixels)
             x += 1
 
-        if(x > NUM_SEG_X):
-            return (x,y,False)
+        if (x > NUM_SEG_X):
+            return (x, y, False)
+
+    return (x, y, True)
 
-    return (x,y,True)
 
 # write line to screen as if on a terminal, scroll up if neccessary
 cur_line = 0
-def writeline(string):
+
+
+def writeline(string: str) -> None:
     global cur_line
-    if(cur_line >= NUM_SEG_Y):
+    if cur_line >= NUM_SEG_Y:
         scrollline()
         cur_line -= 1
-    (new_x, new_y, success) = write(0, cur_line, string.strip("\r\n"))
+    new_x, new_y, _ = write(0, cur_line, string.strip("\r\n"))
     cur_line = new_y
 
     # clear remaining row
-    clear_chars = (NUM_SEG_X-new_x)
+    clear_chars = (NUM_SEG_X - new_x)
     screenbuf_blit(new_x * PWIDTH, cur_line * PHEIGHT,
-        clear_chars * PWIDTH, PHEIGHT,
-        [0] * clear_chars * PWIDTH * PHEIGHT)
+                   clear_chars * PWIDTH, PHEIGHT,
+                   bytes(clear_chars * PWIDTH * PHEIGHT))
 
     cur_line += 1
 
-# scroll the content y lines up and clear last line
-def scrollline(y = 1):
-    screenbuf_scroll(y*PHEIGHT)
-    screenbuf_blit(0, (NUM_SEG_Y-1) * PHEIGHT, WIDTH, PHEIGHT,
-        [0] * WIDTH * PHEIGHT)
+
+def scrollline(y: int = 1) -> None:
+    """
+    scroll the content y lines up and clear last line
+    """
+    screenbuf_scroll(y * PHEIGHT)
+    screenbuf_blit(0, (NUM_SEG_Y - 1) * PHEIGHT, WIDTH, PHEIGHT, bytes(WIDTH * PHEIGHT))
diff --git a/python/clock.py b/python/clock.py
index ffd1c008ada5568c632aa856e0ae84f2d5329c0a..038c3a9ed5413553a60b815222a8920b5eb10347 100755
--- a/python/clock.py
+++ b/python/clock.py
@@ -1,12 +1,9 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 import font_client
-
 from datetime import datetime
-
 import dateutil.tz
 
 while True:
-  #font_client.draw(datetime.now().isoformat(), points=30)
-  font_client.draw(datetime.now(dateutil.tz.tzlocal()).isoformat(), points=10)
-
+    # font_client.draw(datetime.now().isoformat(), points=30)
+    font_client.draw(datetime.now(dateutil.tz.tzlocal()).isoformat(), points=20)
diff --git a/python/cornerclock.py b/python/cornerclock.py
index dda96a3a6c0771024453442a8305e91392c2a72e..389ac9fcf27c4e17ff0f14643054f984fd33de68 100755
--- a/python/cornerclock.py
+++ b/python/cornerclock.py
@@ -1,4 +1,4 @@
-#/usr/bin/env python
+#!/usr/bin/env python3
 
 import datetime
 import time
@@ -6,6 +6,5 @@ import client
 
 while True:
     t = datetime.datetime.now().strftime(" %H:%M:%S")
-    client.write(client.NUM_SEG_X - len(t), 0, t);
+    client.write(client.NUM_SEG_X - len(t), 0, t)
     time.sleep(1)
-
diff --git a/python/dodecahedra.py b/python/dodecahedra.py
old mode 100644
new mode 100755
index 705b0bb44ba9e0356bb88b15833adc31210ce42c..1fdd441d5b23146372625f3211ac7acd9af3d874
--- a/python/dodecahedra.py
+++ b/python/dodecahedra.py
@@ -1,5 +1,6 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # Raoul, Hackover 2014, GPL v2
+from typing import Any, List
 import client
 import time
 from numpy import sin, cos, pi, array, clip, linspace, dot
@@ -17,6 +18,7 @@ xmax = client.WIDTH - sigma * B
 ymax = client.HEIGHT - tau * B - 2
 NT = 12
 
+
 def cube(a=10):
     vert = [(-a, -a, -a),
             (-a, a, -a),
@@ -46,26 +48,26 @@ edge_map_cube = [(0, 1),
 def dode(a=15):
     p = 1.618033
     q = 1.0
-    vert = [(-q, -q, -q), # 0
+    vert = [(-q, -q, -q),  # 0
             (-q, q, -q),
             (q, q, -q),
             (q, -q, -q),
-            (-q, -q, q), # 4
+            (-q, -q, q),  # 4
             (-q, q, q),
             (q, q, q),
             (q, -q, q),
-            (0, 1/p, p), # 8
-            (0, -1/p, p),
-            (0, 1/p, -p),
-            (0, -1/p, -p),
-            (p, 0, 1/p), # 12
-            (-p, 0, 1/p),
-            (p, 0, -1/p),
-            (-p, 0, -1/p),
-            (1/p, p, 0), # 16
-            (-1/p, p, 0),
-            (1/p, -p, 0),
-            (-1/p, -p, 0)]
+            (0, 1 / p, p),  # 8
+            (0, -1 / p, p),
+            (0, 1 / p, -p),
+            (0, -1 / p, -p),
+            (p, 0, 1 / p),  # 12
+            (-p, 0, 1 / p),
+            (p, 0, -1 / p),
+            (-p, 0, -1 / p),
+            (1 / p, p, 0),  # 16
+            (-1 / p, p, 0),
+            (1 / p, -p, 0),
+            (-1 / p, -p, 0)]
     #
     #          5---------6
     #         /    8    /
@@ -79,7 +81,7 @@ def dode(a=15):
     #       |Y   11    /
     #       0-X-------3
     #
-    return a*array(vert)
+    return a * array(vert)
 
 
 edge_map_dode = [(3, 14),
@@ -92,11 +94,11 @@ edge_map_dode = [(3, 14),
                  (4, 13),
                  (5, 13),
                  (13, 15),
-                 (1,10),
-                 (2,10),
+                 (1, 10),
+                 (2, 10),
                  (0, 11),
                  (3, 11),
-                 (10,11),
+                 (10, 11),
                  (4, 9),
                  (7, 9),
                  (5, 8),
@@ -114,28 +116,28 @@ edge_map_dode = [(3, 14),
                  (17, 16)]
 
 
-def rotatex(vertl, phi = pi/3):
-    Rx = array([[1,         0,        0],
-                [0,  cos(phi), sin(phi)],
+def rotatex(vertl, phi=pi / 3):
+    Rx = array([[1, 0, 0],
+                [0, cos(phi), sin(phi)],
                 [0, -sin(phi), cos(phi)]])
     return dot(Rx, vertl.T).T
 
 
-def rotatey(vertl, phi = pi/3):
-    Ry = array([[ cos(phi), 0, sin(phi)],
-                [        0, 1,        0],
+def rotatey(vertl, phi=pi / 3):
+    Ry = array([[cos(phi), 0, sin(phi)],
+                [0, 1, 0],
                 [-sin(phi), 0, cos(phi)]])
     return dot(Ry, vertl.T).T
 
 
-def rotatez(vertl, phi = pi/3):
-    Rz = array([[ cos(phi), sin(phi), 0],
+def rotatez(vertl, phi=pi / 3):
+    Rz = array([[cos(phi), sin(phi), 0],
                 [-sin(phi), cos(phi), 0],
-                [        0,        0, 1]])
+                [0, 0, 1]])
     return dot(Rz, vertl.T).T
 
 
-def propagate(x, v):
+def propagate(x, v) -> None:
     ti = x[:] + v[:]
     x[:] = ti
     if abs(ti[0]) < xmin or abs(ti[0]) > xmax:
@@ -159,8 +161,8 @@ def project(vertlist):
 
 
 def reflect(v1, v2, n):
-    v1n = v1 - 2*dot(v1, n) * n
-    v2n = v2 - 2*dot(v2, n) * n
+    v1n = v1 - 2 * dot(v1, n) * n
+    v2n = v2 - 2 * dot(v2, n) * n
     return v1n, v2n
 
 
@@ -168,32 +170,31 @@ def line(u, v):
     xd = v[0] - u[0]
     yd = v[1] - u[1]
     t = linspace(0, 1, NT)
-    lx = u[0] + xd*t
-    ly = u[1] + yd*t
+    lx = u[0] + xd * t
+    ly = u[1] + yd * t
     return lx.round(), ly.round()
 
 
 def show(vl, el):
-    pixels = []
-    pixelsold = []
+    pixels: list[tuple[int, int, int]] = []
+    pixelsold: list[tuple[int, int, int]] = []
     for e in el:
         lx, ly = line(vl[e[0]], vl[e[1]])
-        for lxi, lyi in zip(lx, ly):
-            pixels.append( (lxi, lyi, 255) )
-            pixelsold.append( (lxi, lyi, 0) )
-
+        for lxi, lyi in zip(map(int, lx), map(int, ly)):
+            pixels.append((lxi, lyi, 255))
+            pixelsold.append((lxi, lyi, 0))
     return pixels, pixelsold
 
 
-def clear():
-    pixels = [0] * client.HEIGHT * client.WIDTH
+def clear() -> None:
+    pixels = bytes(client.HEIGHT * client.WIDTH)
     client.blit(0, 0, client.WIDTH, client.HEIGHT, pixels)
 
 
-if __name__=="__main__":
-    x1 = array([CENX-100, CENY, 0])
+if __name__ == "__main__":
+    x1 = array([CENX - 100, CENY, 0])
     v1 = array([1.5, 0.75, 0])
-    x2 = array([CENX+100, CENY, 0])
+    x2 = array([CENX + 100, CENY, 0])
     v2 = array([-2.5, -0.25, 0])
 
     c1 = dode(16)
@@ -201,22 +202,22 @@ if __name__=="__main__":
     m = edge_map_dode
 
     offset = len(c1)
-    m += [(mi[0]+offset, mi[1]+offset) for mi in m]
+    m += [(mi[0] + offset, mi[1] + offset) for mi in m]
 
-    c1 = rotatez(c1, pi/4.0)
-    c1 = rotatex(c1, pi/3.0)
-    c2 = rotatez(c2, pi/4.0)
-    c2 = rotatex(c2, pi/3.0)
+    c1 = rotatez(c1, pi / 4.0)
+    c1 = rotatex(c1, pi / 3.0)
+    c2 = rotatez(c2, pi / 4.0)
+    c2 = rotatex(c2, pi / 3.0)
 
-    r1y = pi/50
-    r1z = pi/30
-    r2y = -pi/30
-    r2z = -pi/50
+    r1y = pi / 50
+    r1z = pi / 30
+    r2y = -pi / 30
+    r2z = -pi / 50
 
     ti = 0
-    pixold = []
+    pixold: list[tuple[int, int, int]] = []
     clear()
-    while(True):
+    while (True):
         c1 = rotatey(c1, r1y)
         c1 = rotatez(c1, r1z)
         c2 = rotatey(c2, r2y)
@@ -227,28 +228,28 @@ if __name__=="__main__":
 
         n = x1 - x2
         if norm(n) < 54:
-            v1, v2 = reflect(v1, v2, n/norm(n))
-            ry = clip(2*random.uniform(), -1.5, 1.5)
-            rz = clip(2*random.uniform(), -1.5, 1.5)
-            r1y = clip(r1y*ry, -0.28, 0.28)
-            r1z = clip(r1z*rz, -0.28, 0.28)
-            r2y = clip(r2y*ry, -0.28, 0.28)
-            r2z = clip(r2z*rz, -0.28, 0.28)
+            v1, v2 = reflect(v1, v2, n / norm(n))
+            ry = clip(2 * random.uniform(), -1.5, 1.5)
+            rz = clip(2 * random.uniform(), -1.5, 1.5)
+            r1y = clip(r1y * ry, -0.28, 0.28)
+            r1z = clip(r1z * rz, -0.28, 0.28)
+            r2y = clip(r2y * ry, -0.28, 0.28)
+            r2z = clip(r2z * rz, -0.28, 0.28)
 
         cc1 = project(move(c1, x1))
         cc2 = project(move(c2, x2))
 
-        pix, pixoldn = show(cc1+cc2, m)
-        client.set_pixels(pixold+pix)
+        pix, pixoldn = show(cc1 + cc2, m)
+        client.set_pixels(pixold + pix)
         pixold = pixoldn
 
         ti += 1
         if ti > 2000:
             ti = 0
             pixold = []
-            x1 = array([CENX-100, CENY, 0])
+            x1 = array([CENX - 100, CENY, 0])
             v1 = array([1.5, 0.75, 0])
-            x2 = array([CENX+100, CENY, 0])
+            x2 = array([CENX + 100, CENY, 0])
             v2 = array([-2.5, -0.25, 0])
             clear()
 
diff --git a/python/fire.py b/python/fire.py
index 00106adc79b01c868df38627eb12744481928cff..74f7b388a737de93f901c0ee3fac345d531fb5e0 100755
--- a/python/fire.py
+++ b/python/fire.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import client
 import random
 import math
@@ -6,54 +6,63 @@ import math
 """ Fire effect """
 
 cool_offs = 0
-cool_map = [0] * client.HEIGHT * client.WIDTH
-for x in range(1, client.WIDTH-2):
-    for y in range(1, client.HEIGHT-2):
-	    cool_map[y*client.WIDTH + x] = random.random() * 3 + math.sin((x + random.random()*10)/480.0*math.pi*7) * math.sin((y + random.random() * 10)/70.0*math.pi*5) * 3
+cool_map = [0,] * (client.HEIGHT * client.WIDTH)
+for x in range(1, client.WIDTH - 2):
+    for y in range(1, client.HEIGHT - 2):
+        cool_map[y * client.WIDTH + x] = int(random.random() * 3 + math.sin((x + random.random() * 10) / 480.0 * math.pi * 7) * math.sin((y + random.random() * 10) / 70.0 * math.pi * 5) * 3)
 
 
-def init_fire():
-    current = [0] * client.HEIGHT * client.WIDTH
+def init_fire() -> bytes:
+    current = bytearray(client.HEIGHT * client.WIDTH)
     return current
 
-# Smooth image and cool (darken) pixels according to cool_map
-def avg_cooled(x, y, buf):
-	res = 0
-	res += buf[(y*client.WIDTH) + x-1]
-	res += buf[(y*client.WIDTH) + x+1]
-	res += buf[(y*client.WIDTH-1) + x]
-	res += buf[(y*client.WIDTH+1) + x]
-	res += buf[(y*client.WIDTH) + x]
-	res = int(res / 5.0 - cool_map[(y+ cool_offs)%client.HEIGHT*client.WIDTH + x])
-	if res < 0:
-		res = 0
-	return res
-
-# Move everything up one pixel and generate new fire at the bottom
-def move_and_smooth(current, new):
+
+def avg_cooled(x: int, y: int, buf: bytes) -> int:
+    """
+    Smooth image and cool (darken) pixels according to cool_map
+    """
+    res = 0
+    res += buf[(y * client.WIDTH) + x - 1]
+    res += buf[(y * client.WIDTH) + x + 1]
+    res += buf[(y * client.WIDTH - 1) + x]
+    res += buf[(y * client.WIDTH + 1) + x]
+    res += buf[(y * client.WIDTH) + x]
+    res = int(res / 5.0 - cool_map[(y + cool_offs) % client.HEIGHT * client.WIDTH + x])
+    if res < 0:
+        res = 0
+    return res
+
+#
+
+
+def move_and_smooth(current: bytes, new: bytearray) -> None:
+    """
+    Move everything up one pixel and generate new fire at the bottom
+    """
     global cool_offs
-    for x in range(1, client.WIDTH-2):
-        for y in range(1, client.HEIGHT-2):
-		new[y*client.WIDTH + x] = avg_cooled(x, y+1, current)
-    for i in range(5, client.WIDTH-5, 3):
+    for x in range(1, client.WIDTH - 2):
+        for y in range(1, client.HEIGHT - 2):
+            new[y * client.WIDTH + x] = avg_cooled(x, y + 1, current)
+    for i in range(5, client.WIDTH - 5, 3):
         bright = int(random.random() * 180 + 70)
-	new[(client.HEIGHT-1) * client.WIDTH + i] = bright
-	new[(client.HEIGHT-1) * client.WIDTH + i + 1] = bright
-	new[(client.HEIGHT-1) * client.WIDTH + i + 2] = bright
-        new[(client.HEIGHT-2) * client.WIDTH + i] = bright
-        new[(client.HEIGHT-2) * client.WIDTH + i + 1] = bright
-        new[(client.HEIGHT-2) * client.WIDTH + i + 2] = bright
+        new[(client.HEIGHT - 1) * client.WIDTH + i] = bright
+        new[(client.HEIGHT - 1) * client.WIDTH + i + 1] = bright
+        new[(client.HEIGHT - 1) * client.WIDTH + i + 2] = bright
+        new[(client.HEIGHT - 2) * client.WIDTH + i] = bright
+        new[(client.HEIGHT - 2) * client.WIDTH + i + 1] = bright
+        new[(client.HEIGHT - 2) * client.WIDTH + i + 2] = bright
     # The cool map moves as well
     cool_offs = (cool_offs - 1) % client.HEIGHT
 
-if __name__=="__main__":
+
+if __name__ == "__main__":
     current = init_fire()
-    new = [0] * client.HEIGHT * client.WIDTH
-    sending = [(x*x)/256 for x in current]
+    new = bytearray(client.HEIGHT * client.WIDTH)
+    sending = bytes([(x * x) // 256 for x in current])
     while (True):
-        client.blit(0, 0, client.WIDTH, client.HEIGHT, sending, rec = False)
-	move_and_smooth(current, new)
-	current = new
-	# x^2 will work better with the brightness levels we have
-        sending = [(x*x)/256 for x in current]
-	client.rec()
+        client.blit(0, 0, client.WIDTH, client.HEIGHT, sending, rec=False)
+        move_and_smooth(current, new)
+        current = new
+        # x^2 will work better with the brightness levels we have
+        sending = bytes([(x * x) // 256 for x in current])
+        client.rec()
diff --git a/python/font_client.py b/python/font_client.py
index 68f71fe0ae9d22a5828725b761e07389ac6be552..3e9ea7ec37d36ca059a22887f6870ad668874986 100755
--- a/python/font_client.py
+++ b/python/font_client.py
@@ -1,6 +1,8 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
-import sys, client
+import sys
+from typing import Optional
+import client
 
 import pygame
 pygame.init()
@@ -9,7 +11,8 @@ screen = pygame.Surface((client.WIDTH, client.HEIGHT), depth=8)
 
 prev_screen = None
 
-def send(screen):
+
+def send(screen: pygame.Surface) -> None:
     global prev_screen
     w = screen.get_width()
     h = screen.get_height()
@@ -24,66 +27,68 @@ def send(screen):
                     xs.add(x)
                     ys.add(y)
     else:
-        xs = set((0, client.WIDTH-1))
-        ys = set((0, client.HEIGHT-1))
+        xs = set((0, client.WIDTH - 1))
+        ys = set((0, client.HEIGHT - 1))
 
     if not xs:
         return
     prev_screen = screen.copy()
 
     pixels = []
-    for y in range(min(ys), max(ys)+1):
-        for x in range(min(xs), max(xs)+1):
+    for y in range(min(ys), max(ys) + 1):
+        for x in range(min(xs), max(xs) + 1):
             pixels.append(pxarray[x][y])
     del pxarray
-    client.blit(min(xs), min(ys), max(xs)-min(xs)+1, max(ys)-min(ys)+1, pixels)
+    client.blit(min(xs), min(ys), max(xs) - min(xs) + 1, max(ys) - min(ys) + 1, bytes(pixels))
+
 
 prev_mx = None
 font = None
 
-def draw(text, mark=None, points=None):
+
+def draw(text: str, mark: Optional[int] = None, points: Optional[int] = None) -> None:
     global prev_mx, font
     points = points if points else 35 if mark else 60
     font = pygame.font.SysFont("DejaVu Sans Mono", points, bold=True)
     count = len(text)
-    text = font.render(text, True, (255, 255, 255), (0, 0, 0))
-    screen.set_palette(text.get_palette())
+    rendered_text = font.render(text, True, (255, 255, 255), (0, 0, 0))
+    screen.set_palette(rendered_text.get_palette())
     screen.fill((0, 0, 0))
-    w = text.get_width()
-    h = text.get_height()
-    x = (client.WIDTH-w)/2
-    y = (client.HEIGHT-h)/2
+    w = rendered_text.get_width()
+    h = rendered_text.get_height()
+    x = (client.WIDTH - w) / 2
+    y = (client.HEIGHT - h) / 2
     if mark:
-        cw = w/count
-        mx = x+cw*(mark+0.5)
+        cw = w / count
+        mx = x + cw * (mark + 0.5)
         if not mx == prev_mx:
             for size in range(240, 5, -15):
                 pygame.draw.line(screen, 255,
-                        (mx, 0), 
-                        (mx, client.HEIGHT-1),
-                        size)
+                                 (mx, 0),
+                                 (mx, client.HEIGHT - 1),
+                                 size)
                 send(screen)
                 pygame.draw.line(screen, 0,
-                        (mx, 0), 
-                        (mx, client.HEIGHT-1),
-                        size)
+                                 (mx, 0),
+                                 (mx, client.HEIGHT - 1),
+                                 size)
             prev_mx = mx
         pygame.draw.line(screen, 255,
-                (mx, 0), 
-                (mx, client.HEIGHT-1),
-                5)
-    screen.blit(text, (x, y))
+                         (mx, 0),
+                         (mx, client.HEIGHT - 1),
+                         5)
+    screen.blit(rendered_text, (x, y))
 
     send(screen)
 
-if __name__=="__main__":
+
+if __name__ == "__main__":
     if len(sys.argv) >= 3:
-        text = sys.argv[2].decode('utf-8')
+        text = sys.argv[2]
     else:
         text = "Hello World!"
+    mark: Optional[int] = None
     if len(sys.argv) >= 4:
         mark = int(sys.argv[3])
-    else:
-        mark = None
 
     draw(text, mark)
diff --git a/python/fonts/5x7.pcf.gz b/python/fonts/5x7.pcf.gz
new file mode 100644
index 0000000000000000000000000000000000000000..e58bdef21e6a55a8204cb1f682687608710a7fee
Binary files /dev/null and b/python/fonts/5x7.pcf.gz differ
diff --git a/python/gol-client.py b/python/gol-client.py
index 7acdb9442c0ec497b7c1a753d7d567a00e913734..c39bb7f5b75ffa57b9700d338610ab631b61a918 100755
--- a/python/gol-client.py
+++ b/python/gol-client.py
@@ -1,59 +1,28 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
+import random
+import time
+from typing import Sequence
 import client
 
 SIZE = 1
 
-import time
 
-def rect(x,y,w,h,r,g,b):
+def rect(x: int, y: int, w: int, h: int, r: int, g: int, b: int) -> None:
     pixels = []
-    for i in xrange(x,x+w):
-        for j in xrange(y,y+h):
-            #pixel(i,j,r,g,b)
+    for i in range(x, x + w):
+        for j in range(y, y + h):
+            # pixel(i,j,r,g,b)
             pixels.append((i, j, r))
     client.set_pixels(pixels)
 
-def draw(x, y, v):
-    rect(x*SIZE, y*SIZE, SIZE, SIZE, 255*v, 255*v, 255*v)
-
-import random
-
-class Game(object):
-    def __init__(self, state, infinite_board = True):
-        self.state = state
-        self.width = state.width
-        self.height = state.height
-        self.infinite_board = infinite_board
-
-    def step(self, count = 1):
-        for generation in range(count):
-            new_board = [[False] * self.width for row in range(self.height)]
-
-            for y, row in enumerate(self.state.board):
-                for x, cell in enumerate(row):
-                    neighbours = self.neighbours(x, y)
-                    previous_state = self.state.board[y][x]
-                    should_live = neighbours == 3 or (neighbours == 2 and previous_state == True)
-                    new_board[y][x] = should_live
-
-            self.state.update(new_board)
-
-    def neighbours(self, x, y):
-        count = 0
-
-        for hor in [-1, 0, 1]:
-            for ver in [-1, 0, 1]:
-                if not hor == ver == 0 and (self.infinite_board == True or (0 <= x + hor < self.width and 0 <= y + ver < self.height)):
-                    count += self.state.board[(y + ver) % self.height][(x + hor) % self.width]
 
-        return count
+def draw(x: int, y: int, v: int) -> None:
+    rect(x * SIZE, y * SIZE, SIZE, SIZE, 255 * v, 255 * v, 255 * v)
 
-    def display(self):
-        return self.state.display()
 
 class State(object):
-    def __init__(self, positions, x, y, width, height):
+    def __init__(self, positions: str, x: int, y: int, width: int, height: int):
         self.width = width
         self.height = height
         self.board = [[False] * self.width for row in range(self.height)]
@@ -63,23 +32,23 @@ class State(object):
         for l, row in enumerate(positions.splitlines()):
             for r, cell in enumerate(row.strip()):
                 if cell == 'o':
-                    active_cells.append((r,l))
+                    active_cells.append((r, l))
 
         board = [[False] * width for row in range(height)]
 
-        for cell in active_cells:
-            board[cell[1] + y][cell[0] + x] = True
+        for active_cell in active_cells:
+            board[active_cell[1] + y][active_cell[0] + x] = True
 
         self.update(board)
 
-    def update(self, new):
+    def update(self, new: Sequence[Sequence[bool]]) -> None:
         for y, row in enumerate(new):
             for x, cell in enumerate(row):
                 if self.board[y][x] != cell:
                     self.board[y][x] = cell
                     draw(x, y, cell)
 
-    def display(self):
+    def display(self) -> str:
         output = ''
 
         for y, row in enumerate(self.board):
@@ -92,6 +61,41 @@ class State(object):
 
         return output
 
+
+class Game(object):
+    def __init__(self, state: State, infinite_board: bool = True):
+        self.state = state
+        self.width = state.width
+        self.height = state.height
+        self.infinite_board = infinite_board
+
+    def step(self, count: int = 1) -> None:
+        for generation in range(count):
+            new_board = [[False] * self.width for row in range(self.height)]
+
+            for y, row in enumerate(self.state.board):
+                for x, cell in enumerate(row):
+                    neighbours = self.neighbours(x, y)
+                    previous_state = self.state.board[y][x]
+                    should_live = neighbours == 3 or (neighbours == 2 and previous_state == True)
+                    new_board[y][x] = should_live
+
+            self.state.update(new_board)
+
+    def neighbours(self, x: int, y: int) -> int:
+        count = 0
+
+        for hor in [-1, 0, 1]:
+            for ver in [-1, 0, 1]:
+                if not hor == ver == 0 and (self.infinite_board == True or (0 <= x + hor < self.width and 0 <= y + ver < self.height)):
+                    count += self.state.board[(y + ver) % self.height][(x + hor) % self.width]
+
+        return count
+
+    def display(self) -> str:
+        return self.state.display()
+
+
 glider = """
 oo.
 o.o
@@ -111,10 +115,10 @@ oo........o...o.oo....o.o...........
 """
 
 my_game = Game(State(gun,
-    x = 20, y = 10,
-    width = client.WIDTH/SIZE, height = client.HEIGHT/SIZE)
-)
+                     x=20, y=10,
+                     width=client.WIDTH // SIZE, height=client.HEIGHT // SIZE)
+               )
 
 while True:
-    #print my_game.display()
+    # print my_game.display()
     my_game.step(1)
diff --git a/python/irc.py b/python/irc.py
old mode 100644
new mode 100755
index 386f8c000d2b6b1897664657eb3f21ee81a55fc5..306b18e897a9a41ee12f85fdb6f5ec067308dbdd
--- a/python/irc.py
+++ b/python/irc.py
@@ -1,52 +1,53 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
-import sys
 import socket
 import string
 import client
 
-def printline(nick, msg, me=False):
-  l = len(nick) + 3
-  if me:
-    nick = "* %s " % nick
-  else:
-    nick = "<%s> " % nick
-  while msg:
-    client.writeline("%s%s" % (nick, msg[:96-len(nick)]))
-    nick = " " * len(nick)
-    msg = msg[96-len(nick):].strip()
 
-HOST="irc.freenode.net"
-PORT=6667
-NICK="mensadisplay"
-IDENT="mensadisplay"
-REALNAME="MensaDisplay"
-readbuffer=""
+def printline(nick: str, msg: str, me: bool = False) -> None:
+    l = len(nick) + 3
+    if me:
+        nick = "* %s " % nick
+    else:
+        nick = "<%s> " % nick
+    while msg:
+        client.writeline("%s%s" % (nick, msg[:96 - len(nick)]))
+        nick = " " * len(nick)
+        msg = msg[96 - len(nick):].strip()
 
-s=socket.socket( )
+
+HOST = "irc.libera.chat"
+PORT = 6667
+NICK = "mensadisplay"
+IDENT = "mensadisplay"
+REALNAME = "MensaDisplay"
+readbuffer = b""
+
+s = socket.socket()
 s.connect((HOST, PORT))
-s.send("NICK %s\r\n" % NICK)
-s.send("USER %s %s bla :%s\r\n" % (IDENT, HOST, REALNAME))
-s.send("JOIN #stratum0\r\n")
-while 1:
-  readbuffer=readbuffer+s.recv(1024)
-  temp=string.split(readbuffer, "\n")
-  readbuffer=temp.pop( )
+s.send(f"NICK {NICK}\r\n".encode("utf-8"))
+s.send(f"USER {IDENT} {HOST} bla :{REALNAME}\r\n".encode("utf-8"))
+s.send(b"JOIN #stratum0\r\n")
+while True:
+    readbuffer = readbuffer + s.recv(1024)
+    temp = readbuffer.split(b"\n")
+    readbuffer = temp.pop()
 
-  for line in temp:
-    line = string.rstrip(line)
-    line = string.split(line)
-    print line
-    if line[1] == "PRIVMSG":
-      nick = line[0].split("!")[0][1:]
-      msg = " ".join(line[3:])[1:]
-      try:
-        if msg.startswith("\x01ACTION"):
-          #me
-          printline(nick, msg[8:-1].decode("utf8"), True)
-        else:
-          printline(nick, msg.decode("utf8"))
-      except UnicodeEncodeError:
-        pass
-    if(line[0]=="PING"):
-      s.send("PONG %s\r\n" % line[1])
\ No newline at end of file
+    for line in temp:
+        line = line.rstrip()  # string.rstrip(line)
+        words = line.split()
+        print(words)
+        if words[1] == b"PRIVMSG":
+            nick = words[0].split(b"!")[0][1:].decode("utf8")
+            msg = b" ".join(words[3:])[1:]
+            try:
+                if msg.startswith(b"\x01ACTION"):
+                    # me
+                    printline(nick, msg[8:-1].decode("utf8"), True)
+                else:
+                    printline(nick, msg.decode("utf8"))
+            except UnicodeEncodeError:
+                pass
+        if (words[0] == b"PING"):
+            s.send(b"PONG " + words[1] + b"\r\n")
diff --git a/python/munching.py b/python/munching.py
index 3a783dbd9d02b4f0c31f46824fc04f1ab1fa846b..fb16412c927086b3026f7108be837a5944097c65 100755
--- a/python/munching.py
+++ b/python/munching.py
@@ -1,23 +1,22 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 import client
 import random
 import time
 import clearscreen
 
-if __name__=="__main__":
+if __name__ == "__main__":
     t = 0
     s = 64
-    pixels = [0] * client.HEIGHT * client.WIDTH
+    pixels = bytearray(client.HEIGHT * client.WIDTH)
     clearscreen.clear()
     while True:
         if t == 63:
             time.sleep(1)
-        t = (t+1) % 64
+        t = (t + 1) % 64
         for x in range(client.WIDTH):
             for y in range(client.HEIGHT):
-                #pixels[s * x + y] = 0 if (y == (x^t)) else 1
-                pixels[s * x + y] = 0 if ((x&y&t)) else 255 
+                # pixels[s * x + y] = 0 if (y == (x^t)) else 1
+                pixels[s * x + y] = 0 if ((x & y & t)) else 255
         client.blit(0, 0, client.WIDTH, client.HEIGHT, pixels)
-        print t
-        #time.sleep(0.09)
-
+        print(t)
+        # time.sleep(0.09)
diff --git a/python/random_pixel.py b/python/random_pixel.py
index 6c7fee927c9040b4cc51575631491b24cfd2abe9..244005dd063120d63850f6525005b25ff70f2e97 100755
--- a/python/random_pixel.py
+++ b/python/random_pixel.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import client
 import random
 import time
@@ -6,18 +6,17 @@ import time
 blockx = 5
 blocky = 7
 
-if __name__=="__main__":
-    while(True):
-        x = random.random() * (client.WIDTH/blockx)
-        y = random.random() * (client.HEIGHT/blocky)
-	x = int(x) * blockx
-	y = int(y) * blocky
-	if random.random() > 0.5:
+if __name__ == "__main__":
+    while True:
+        x = random.random() * (client.WIDTH / blockx)
+        y = random.random() * (client.HEIGHT / blocky)
+        x = int(x) * blockx
+        y = int(y) * blocky
+        if random.random() > 0.5:
             bright = random.random()
-	    bright = (bright * bright) * 255
-	    pix = [int(bright)] * blockx * blocky
-	    client.blit(x, y, blockx, blocky, pix)
-	else:
-	    pix = [0] * blockx * blocky
-	    client.blit(x, y, blockx, blocky, pix)
-
+            bright = (bright * bright) * 255
+            pix = bytes([int(bright)] * blockx * blocky)
+            client.blit(x, y, blockx, blocky, pix)
+        else:
+            pix = bytes([0] * blockx * blocky)
+            client.blit(x, y, blockx, blocky, pix)
diff --git a/python/reddit.py b/python/reddit.py
index 0d0eaba3139623bd94b0ae332a7dc8292f13b6d2..fe1df9c9c3678657c0d525323f5fdae221e1bab4 100755
--- a/python/reddit.py
+++ b/python/reddit.py
@@ -1,34 +1,27 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # 10x48x5x7 Pixel
 
-import client, praw
+import client
+import praw
 from time import sleep
+import os
 
 SERVER = "tcp://localhost:5570"
 XOFF = 0
 YOFF = 0
 TEXT = ""
 
-r = praw.Reddit(user_agent='Stratum0 Braunschweig Mensadisplay Parser')
-reddit = ["opensource"
-         ,"linux"
-         ,"netsec"
-         ,"sysadmin"
-         ,"worldnews"
-         ,"shittyaskscience"
-         ,"showerthoughts"
-         ,"explainlikeimcalvin"
-         ,"pettyrevenge"
-         ,"all"]
+r = praw.Reddit(
+    client_id=os.environ.get("REDDIT_CLIENT_ID"),
+    client_secret=os.environ.get("REDDIT_CLIENT_SECRET"),
+    user_agent='Stratum0 Braunschweig Mensadisplay Parser')
+reddit = ["opensource", "linux", "netsec", "sysadmin", "worldnews", "shittyaskscience", "showerthoughts", "explainlikeimcalvin", "pettyrevenge", "all"]
 
 while True:
-  for i in reddit:
-    client.writeline("reddit.com/r/" + i)
-    submissions = r.get_subreddit(i).get_hot(limit=9)
-    subs = [unicode(x) for x in submissions]
-    for i in range(0,9):
-      votes,title = subs[i].split(' :: ',1)
-      TEXT = '%s :: %s' % (votes.rjust(5),title)
-      print(repr(TEXT))
-      client.writeline(TEXT)
-      sleep(10)
+    for i in reddit:
+        client.writeline("reddit.com/r/" + i)
+        for submission in r.subreddit(i).hot(limit=9):
+            TEXT = f'{submission.upvote_ratio:5.0f} :: {submission.title}'
+            print(TEXT)
+            client.writeline(TEXT)
+            sleep(10)
diff --git a/python/requirements.txt b/python/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d49aed1e1ac53d040ac761f321e1c4170a55f1f9
--- /dev/null
+++ b/python/requirements.txt
@@ -0,0 +1,6 @@
+numpy
+praw
+pygame
+python-dateutil
+pyzmq
+requests
diff --git a/python/speedtest.py b/python/speedtest.py
index 974775e77369ad627f9882134701e98f05fd1248..60177f1b3be806db7037c5bf39b609f2ccf27606 100755
--- a/python/speedtest.py
+++ b/python/speedtest.py
@@ -1,8 +1,8 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import client
 import random
 
-if __name__=="__main__":
+if __name__ == "__main__":
     for y in range(0, client.HEIGHT):
         for x in range(0, client.WIDTH):
-            client.set_pixel(x,y,255)
+            client.set_pixel(x, y, 255)
diff --git a/python/test-client.py b/python/test-client.py
index ee3aede1693ce24aff82115512507fb3380fd32d..8a01c3b48397328d0da798c008218a9470306878 100755
--- a/python/test-client.py
+++ b/python/test-client.py
@@ -1,29 +1,29 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
-import sys, client
+import pygame
+import sys
+import client
 
 SERVER = "tcp://localhost:5570"
 XOFF = 0
 YOFF = 0
 TEXT = "Hello World!"
 
-if __name__=="__main__":
-  if len(sys.argv) >= 4:
-    XOFF = int(sys.argv[2])
-    YOFF = int(sys.argv[3])
-  if len(sys.argv) >= 5:
-    TEXT = sys.argv[4]
-
-import pygame
-pygame.init()
+if __name__ == "__main__":
+    if len(sys.argv) >= 4:
+        XOFF = int(sys.argv[2])
+        YOFF = int(sys.argv[3])
+    if len(sys.argv) >= 5:
+        TEXT = sys.argv[4]
 
-font = pygame.font.Font("/usr/share/fonts/X11/misc/5x7.pcf.gz", 7)
-text = font.render(TEXT, True, (255, 255, 255), (0, 0, 0))
-pxarray = pygame.PixelArray(text)
-pixels = []
-for x in range(text.get_width()):
-    for y in range(text.get_height()):
-        pixels.append((XOFF+x, YOFF+y, pxarray[x][y]))
-del pxarray
-client.set_pixels(pixels)
+    pygame.init()
 
+    font = pygame.font.Font("./fonts/5x7.pcf.gz", 7)
+    text = font.render(TEXT, True, (255, 255, 255), (0, 0, 0))
+    pxarray = pygame.PixelArray(text)
+    pixels = []
+    for x in range(text.get_width()):
+        for y in range(text.get_height()):
+            pixels.append((XOFF + x, YOFF + y, pxarray[x][y]))
+    del pxarray
+    client.set_pixels(pixels)
diff --git a/python/test-proxy.py b/python/test-proxy.py
index 93f6d498ef37a9ea5975f2ace79c3c3d18429319..cb5ad4c43e5aa5467e7e621d505454bd5785813a 100755
--- a/python/test-proxy.py
+++ b/python/test-proxy.py
@@ -1,6 +1,7 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
-import zmq, sys
+import zmq
+import sys
 
 context = zmq.Context()
 
@@ -18,4 +19,3 @@ zmq.device(zmq.QUEUE, frontend, backend)
 frontend.close()
 backend.close()
 context.term()
-
diff --git a/python/test-server.py b/python/test-server.py
index af124c0ad5a2431507b241e62e5894915be81462..bf42e23ee7b20afb327aa541b353c3414c66bad4 100755
--- a/python/test-server.py
+++ b/python/test-server.py
@@ -1,38 +1,42 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
+import sys
+import pygame
+import zmq
+import struct
 PWIDTH = 5
-WIDTH = PWIDTH*96
+WIDTH = PWIDTH * 96
 PHEIGHT = 7
 PPAD = 5
-HEIGHT = (PHEIGHT+PPAD)*9+PHEIGHT
+HEIGHT = (PHEIGHT + PPAD) * 9 + PHEIGHT
 ZOOM = 2
 
-import pygame, sys
 pygame.init()
 
-size = (WIDTH*ZOOM, HEIGHT*ZOOM)
+size = (WIDTH * ZOOM, HEIGHT * ZOOM)
 screen = pygame.display.set_mode(size)
 
-import struct
-import zmq
 
 context = zmq.Context()
 socket = context.socket(zmq.REP)
 socket.bind("tcp://*:5570")
 
-def set_pixel(x, y, v):
+
+def set_pixel(x: int, y: int, v: int) -> None:
     # 1 bit per pixel
-    v = v/255.0
-    row = y / 7
+    v = v // 255
+    row = y // 7
     if row in [0, 1, 2, 9]:
-        c = (0xff*v, 0, 0)
+        c = (0xff * v, 0, 0)
     elif row in [3, 4, 5]:
-        c = (0, 0xff*v, 0)
+        c = (0, 0xff * v, 0)
     else:
-        c = (0xff*v, 0xa5*v, 0)
-    y += y / PHEIGHT * PPAD
-    screen.fill(c, (x*ZOOM, y*ZOOM, ZOOM, ZOOM))
+        c = (0xff * v, 0xa5 * v, 0)
+    y += y // PHEIGHT * PPAD
+    screen.fill(c, (x * ZOOM, y * ZOOM, ZOOM, ZOOM))
+
 
+BLIT_HEADER_SIZE = struct.calcsize('<Biiii')
 while True:
     for event in pygame.event.get():
         if event.type == pygame.QUIT:
@@ -44,22 +48,22 @@ while True:
         continue
 
     messages = socket.recv_multipart()
-    #print("New multimessage")
+    # print("New multimessage")
     for message in messages:
         if not message:
             break
         cmd, = struct.unpack_from('<B', message)
-        #print repr(message)
-        if cmd == 0: # set pixel
+        # print(repr(message))
+        if cmd == 0:  # set pixel
             cmd, x, y, v = struct.unpack_from('<BiiB', message)
-            #print("Received set pixel: %r, %r, %r, %r" % (cmd, x, y, v))
+            # print("Received set pixel: %r, %r, %r, %r" % (cmd, x, y, v))
             set_pixel(x, y, v)
-        elif cmd == 1: # blit
+        elif cmd == 1:  # blit
             cmd, x, y, w, h = struct.unpack_from('<Biiii', message)
-            #print("Received blit: %r, %r, %r, %r, %r" % (cmd, x, y, w, h))
+            # print("Received blit: %r, %r, %r, %r, %r" % (cmd, x, y, w, h))
             for r in range(h):
                 for c in range(w):
-                    set_pixel(x+c, y+r, ord(message[17+r*w+c]))
+                    set_pixel(x + c, y + r, message[BLIT_HEADER_SIZE + r * w + c])
         else:
             cmd, = struct.unpack_from('<B', message)
             print("Received unknown: %r" % (cmd,))