diff --git a/hepialight/boot.py b/hepialight/boot.py
new file mode 100644
index 0000000000000000000000000000000000000000..1fee5942580e9a3147d64599c312b874ac40ce53
--- /dev/null
+++ b/hepialight/boot.py
@@ -0,0 +1,6 @@
+# boot.py -- run on boot-up
+# can run arbitrary Python, but best to keep it minimal
+
+import mbed
+from userlib import *
+mbed.main('main.py') # main script to run after this one
\ No newline at end of file
diff --git a/hepialight/main.py b/hepialight/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..4384b2ffd2d7fe6426f2e13e0698a1fc2b6845c1
--- /dev/null
+++ b/hepialight/main.py
@@ -0,0 +1,180 @@
+id = [0,0]
+matsize = [0, 0]
+ 
+
+# Display and images functions
+def color16_to_32(color):
+    color = int(color)
+    r = ((color >> 11) & 0x1F) * 2**3
+    g = ((color >> 5) & 0x3F) * 2**2
+    b = (color & 0x1F) * 2**3
+    return r << 16 | g << 8 | b
+
+def disp_img_data(data):
+    for i in range(NBR_LIGNES):
+        for j in range(NBR_COLONNES):
+            allumer_led(j, NBR_LIGNES-1-i, data[i * NBR_COLONNES + j])
+
+def data_to_color16(data):
+    d_color16 = []
+
+    for i in range(0, len(data), 2):
+        d_color16.append(data[i] << 8 | data[i+1])
+    
+    return list(map(color16_to_32, d_color16))
+
+def display_id():
+    afficher_texte("{}, {}".format(id[0], id[1]), speed=0.01)
+ 
+def msg_to_str(msg):
+    return str(msg)[2:-1]
+ 
+# Used by the send_xy functions
+def send_passthrough_str(msg, x, y):
+    x = int(x)
+    y = int(y)
+
+    if x > id[0]:
+        d = E
+    elif x < id[0]:
+        d = O
+    elif y > id[1]:
+        d = N
+    else:
+        d = S
+
+    envoyer_msg(d, "PT;{},{};{}".format(x, y, msg))
+
+# Not meant to be used by the user
+def send_passthrough_bytes(msg, x, y):
+    x = int(x)
+    y = int(y)
+
+    if x > id[0]:
+        d = E
+    elif x < id[0]:
+        d = O
+    elif y > id[1]:
+        d = N
+    else:
+        d = S
+
+    envoyer_msg(d, msg)
+
+# Send commands to a specific matrix
+def send_xy(x, y, msg):
+    send_passthrough_str(msg, x, y)
+
+def send_text_xy(x, y, text, color=ROUGE, speed=0.1):
+    send_xy(x, y, "TEXT;{};{};{}".format(color, speed, text))
+
+def send_moving_xy(x, y, text, color=ROUGE, speed=0.1):
+    send_xy(x, y, "MOVING;{};{};{}".format(color, speed, text))
+
+
+# Check if there is a connected matrix in the d direction
+def check_presence(d):
+    envoyer_msg(d, "PING")
+
+    for x in range(10):
+        time.sleep(0.1)
+        ret = recevoir_msg(d)
+
+        if ret == b"PONG": return True
+
+    return False
+
+# Update the ID of the current matrix, and update the id of the neighbours
+def update_id(new_id):
+    global id
+
+    id[0] = int(new_id[0])
+    id[1] = int(new_id[1])
+
+    # The matrices which are in the x = 0 pos, update the id North and East.
+    # The others only broadcast East.
+    if (id[0] == 0):
+        envoyer_msg(N, "SET_ID:{},{}".format(id[0], id[1] + 1))
+    
+    envoyer_msg(E, "SET_ID:{},{}".format(id[0] + 1, id[1]))
+
+    if not check_presence(N) and not check_presence(E):
+        send_xy(0, 0, "MATSIZE;{},{}".format(id[0], id[1]))
+
+    display_id()
+
+
+
+# Handle the commands received via UART
+def handle_receive(msg, msg_bytes, d):
+ 
+    print("Received from {} : {}".format(d, msg))
+
+    if msg.startswith("SET_ID"):
+        tmp_id = msg[7:].split(",")
+        update_id(tmp_id)
+ 
+    elif msg == "PING":
+        envoyer_msg(d, "PONG")
+ 
+    elif msg == "DISP_ID":
+        display_id()
+
+    elif msg.startswith("PT;"):
+        msgs = msg.split(";")
+        m = msgs[2:]
+        m = ";".join(m)
+        x, y = msgs[1].split(",")
+
+        x = int(x)
+        y = int(y)
+
+        if x == id[0] and y == id[1]:
+            handle_receive(m, msg_bytes[7:], d)
+            return
+        
+        print("sending passthrough to {},{}: {}".format(x,y,m))
+        send_passthrough_bytes(msg_bytes, x, y)
+ 
+    elif msg.startswith("MATSIZE"):
+        x, y = msg.split(";")[-1].split(",")
+        print("Matsize : {} {}".format(x, y))
+        matsize = [int(x), int(y)]
+
+    elif msg.startswith("TEXT"):
+        msgs = msg.split(";")
+        color = int(msgs[1])
+        speed = float(msgs[2])
+        afficher_texte(msgs[3], color, speed)
+
+    elif msg.startswith("MOVING"):
+        msgs 	= msg.split(";")
+        color 	= int(msgs[1])
+        speed 	= float(msgs[2])
+        text 	= msgs[3]
+
+        time.sleep(speed * 11)
+        send_moving_xy(id[0] - 1, id[1], text, color=color, speed=speed)
+        afficher_texte(text, color, speed)
+
+    elif msg.startswith("IMAGE"):
+        print("IMAGE LEN : {}".format(len(msg_bytes[6:])))
+        img = data_to_color16(msg_bytes[6:])
+        disp_img_data(img)
+
+    elif msg == "OK":
+        update_id(id)
+
+
+# Main
+afficher_texte("Ready", VERT, speed=0.01)
+
+while True:
+
+    for d in [N, S, E, O]:
+        msg_bytes = recevoir_msg(d)
+
+        if msg_bytes == b"": continue
+
+        msg = msg_to_str(msg_bytes)
+        handle_receive(msg, msg_bytes, d)
\ No newline at end of file
diff --git a/hepialight/userlib.py b/hepialight/userlib.py
new file mode 100644
index 0000000000000000000000000000000000000000..decb87c3be779a7bd02b060ad962d9a5f5355ed1
--- /dev/null
+++ b/hepialight/userlib.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import time
+from Hepialight import touch, screen, accel, uart
+
+# Constantes
+NBR_LIGNES = 10
+NBR_COLONNES = 10
+
+# Couleurs
+ROUGE = 0xFF0000
+VERT = 0x00FF00
+BLEU = 0x0000FF
+
+_accel = accel()
+_seuil_accel = 0.3
+
+def delai(delai_en_sec):
+    time.sleep(delai_en_sec)
+
+def _colorToInt(hexColor):
+    return int(hexColor[1:], 16)
+
+_COLORS = {
+    "R": _colorToInt("#A2142F"),
+    "B": _colorToInt("#0072BD"),
+    "O": _colorToInt("#D95319"),
+    "Y": _colorToInt("#EDB120"),
+    "P": _colorToInt("#7E2F8E"),
+    "G": _colorToInt("#77AC30"),
+    "C": _colorToInt("#4DBEEE"),
+    ".": _colorToInt("#000000")
+}
+
+# Coordonnées
+N = 0
+S = 1
+E = 2
+O = 3
+
+
+# use https://goo.gl/gGYfJV to add new chars
+_TEXT_DICT = {
+    ' ': [],
+    'A': [14, 17, 17, 31, 17, 17, 17],
+    'B': [15, 17, 17, 31, 17, 17, 15],
+    'C': [14, 17, 1, 1, 1, 17, 14],
+    'D': [15, 17, 17, 17, 17, 17, 15],
+    'E': [31, 1, 1, 15, 1, 1, 31],
+    'F': [31, 1, 1, 15, 1, 1, 1],
+    'G': [14, 17, 1, 1, 25, 17, 30],
+    'H': [17, 17, 17, 31, 17, 17, 17],
+    'I': [31, 4, 4, 4, 4, 4, 31],
+    'J': [31, 16, 16, 16, 16, 17, 14],
+    'K': [17, 17, 9, 7, 9, 17, 17],
+    'L': [1, 1, 1, 1, 1, 1, 31],
+    'M': [17, 27, 27, 21, 21, 17, 17],
+    'N': [17, 19, 21, 21, 25, 17, 17],
+    'O': [14, 17, 17, 17, 17, 17, 14],
+    'P': [15, 17, 17, 17, 15, 1, 1],
+    'Q': [14, 17, 17, 17, 17, 21, 14, 4, 8],
+    'R': [15, 17, 17, 15, 9, 17, 17],
+    'S': [30, 1, 1, 14, 16, 16, 15],
+    'T': [31, 4, 4, 4, 4, 4, 4],
+    'U': [17, 17, 17, 17, 17, 17, 14],
+    'V': [17, 17, 17, 17, 10, 10, 4],
+    'W': [17, 17, 21, 21, 21, 10, 10],
+    'X': [17, 17, 10, 4, 10, 17, 17],
+    'Y': [17, 10, 4, 4, 4, 4, 4],
+    'Z': [31, 16, 8, 4, 2, 1, 31],
+    'a': [0, 0, 14, 16, 30, 17, 30],
+    'b': [1, 1, 13, 19, 17, 17, 15],
+    'c': [0, 0, 14, 17, 1, 17, 14],
+    'd': [16, 16, 30, 17, 17, 17, 14],
+    'e': [0, 0, 14, 17, 31, 1, 30],
+    'f': [24, 4, 30, 4, 4, 4, 31],
+    'g': [0, 0, 30, 17, 17, 25, 22, 16, 14],
+    'h': [1, 1, 15, 17, 17, 17, 17],
+    'i': [4, 0, 7, 4, 4, 4, 31],
+    'j': [16, 0, 28, 16, 16, 16, 16, 17, 14],
+    'k': [1, 1, 9, 9, 7, 9, 17],
+    'l': [7, 4, 4, 4, 4, 4, 31],
+    'm': [0, 0, 21, 31, 21, 21, 21],
+    'n': [0, 0, 13, 19, 17, 17, 17],
+    'o': [0, 0, 14, 17, 17, 17, 14],
+    'p': [0, 0, 13, 19, 17, 17, 15, 1, 1],
+    'q': [0, 0, 30, 17, 17, 25, 22, 16, 16],
+    'r': [0, 0, 27, 6, 2, 2, 15],
+    's': [0, 0, 30, 1, 14, 16, 15],
+    't': [0, 4, 31, 4, 4, 4, 24],
+    'u': [0, 0, 17, 17, 17, 25, 22],
+    'v': [0, 0, 17, 17, 10, 10, 4],
+    'w': [0, 0, 17, 21, 21, 10, 10],
+    'x': [0, 0, 17, 17, 14, 17, 17],
+    'y': [0, 0, 17, 17, 10, 10, 4, 4, 3],
+    'z': [0, 0, 31, 8, 4, 2, 31],
+    '0': [14, 25, 21, 21, 21, 19, 14],
+    '1': [4, 7, 4, 4, 4, 4, 31],
+    '2': [14, 17, 16, 14, 1, 1, 31],
+    '3': [14, 17, 16, 14, 16, 17, 14],
+    '4': [8, 12, 10, 9, 31, 8, 8],
+    '5': [31, 1, 1, 15, 16, 16, 15],
+    '6': [12, 2, 1, 15, 17, 17, 14],
+    '7': [31, 16, 8, 4, 2, 2, 2],
+    '8': [14, 17, 17, 14, 17, 17, 14],
+    '9': [14, 17, 17, 30, 16, 8, 6],
+    '.':[0, 0, 0, 0, 0, 4, 4],
+    ',':[0, 0, 0, 0, 0, 4, 4, 2],
+    ';':[0, 0, 4, 4, 0, 4, 4, 2],
+    '?':[14, 17, 16, 12, 0, 4, 4],
+    '!':[4, 4, 4, 4, 0, 4, 4],
+    '-':[0, 0, 0, 0, 14],
+    '_':[0, 0, 0, 0, 0, 0, 31],
+    '*':[0, 0, 10, 4, 10],
+    '+':[0, 0, 4, 4, 31, 4, 4],
+    '/':[16, 16, 8, 4, 4, 2, 2],
+    '\\':[2, 2, 4, 8, 8, 16, 16],
+    '<':[0, 0, 8, 4, 2, 4, 8],
+    '>':[0, 0, 2, 4, 8, 4, 2],
+    '#':[10, 10, 31, 10, 31, 10, 10],
+    '=':[0, 0, 0, 31, 0, 31],
+    "'":[8, 8, 4],
+    "%":[11, 11, 4, 2, 2, 13, 13, 0, 0],
+    "&":[6, 1, 1, 6, 5, 9, 22, 0, 0],
+    "@":[14, 17, 29, 27, 31, 1, 30, 0, 0],
+    "$":[4, 30, 5, 14, 20, 15, 4, 0, 0],
+}
+
+def afficher_texte(text, color=ROUGE, speed=0.1):
+    eteindre_tout()
+
+    width = 5
+    height = 9
+    h_offset = 9
+    spacewidth = 2
+
+    def printColumn(i, xpos, text):
+        if i < h_offset or i >= h_offset + len(text) * (width + spacewidth):
+            char = ' '
+            col = 0
+        else:
+            i -= h_offset
+            char = text[i // (width + spacewidth)]
+            col = i % (width + spacewidth)
+            if char not in _TEXT_DICT:
+                char = ' '
+        colbit = 1 << col
+        charMap = _TEXT_DICT[char]
+        for line in range(height):
+            colored = len(charMap)>line and charMap[line] & colbit
+            screen.set_led((xpos, height - 1 - line), color if colored else 0)
+
+    for step in range(h_offset + len(text) * (width + spacewidth) + 1):
+        for i in range(NBR_COLONNES):
+            printColumn(step + i, i, text)
+        delai(speed)
+
+def afficher_grille(grille):
+    arr = [_COLORS[c] for c in grille if c in _COLORS]
+    for i in range(NBR_LIGNES):
+        for j in range(NBR_COLONNES):
+            screen.set_led((j, NBR_LIGNES-1-i), arr[i * NBR_COLONNES + j])
+
+
+def allumer_tout(couleur):
+    for i in range(NBR_LIGNES):
+        for j in range(NBR_COLONNES):
+            screen.set_led((i, j), couleur)
+
+def eteindre_tout():
+    allumer_tout(0)
+
+def allumer_ligne(num_ligne, couleur):
+    for i in range(NBR_COLONNES):
+        screen.set_led((i, num_ligne), couleur)
+
+def allumer_colonne(num_colonne, couleur):
+    for j in range(NBR_LIGNES):
+        screen.set_led((num_colonne, j), couleur)
+
+def allumer_led(pos_x, pos_y, couleur):
+    screen.set_led((int(pos_x), int(pos_y)), couleur)
+
+def eteindre_led(pos_x, pos_y):
+    screen.set_led((pos_x, pos_y), 0)
+
+def penche_gauche():
+    return _accel.get_axis()[0] > _seuil_accel
+
+def accel_vertical():
+    return _accel.get_axis()[1]
+
+def accel_horizontal():
+    return _accel.get_axis()[0]
+
+def penche_droite():
+    return _accel.get_axis()[0] < -_seuil_accel
+
+def penche_avant():
+    return _accel.get_axis()[1] > _seuil_accel
+
+def penche_arriere():
+    return _accel.get_axis()[1] < -_seuil_accel
+
+def touche_bas_gauche():
+    return touch.read(0)
+
+def touche_bas_droite():
+    return touch.read(1)
+
+def touche_haut_gauche():
+    return touch.read(2)
+
+def touche_haut_droite():
+    return touch.read(3)
+
+def round(x):
+    return int(x+0.5)
+
+def ceil(x):
+    return x if int(x) == x else int(x+1)
+
+def floor(x):
+    return int(x+0.5)
+
+def abs(x):
+    return x if x>=0 else -x
+
+def max(x, y):
+    return x if x>y else y
+
+def min(x, y):
+    return x if x<y else y
+
+def envoyer_msg(ID, data):
+    '''Envoi une valeur vers une des carte voisines
+    :ID: quelle carte
+    :data: la valeur
+    '''
+
+    uart.send_to(ID, data)
+
+
+def recevoir_msg(ID):
+    '''Détecte si le pavé tactile supérieur droit est pressé
+    :returns: True si pressé, False sinon
+    '''
+
+    return uart.recv(ID)