diff --git a/.gitignore b/.gitignore
index 65d326a70304165cbb700f42fe7d27b13ba926e6..9055c5a38dc53cc269bcc45cbe7c3632ad433bcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,10 +2,12 @@ node_modules/
 build/
 .cache/
 .DS_Store
+.idea
 *.log
 
 server/config.js
 front/src/config.js
+scripts/nfc-poll-wrapper-dummy.sh
 
 # *.pub
 # *.key
diff --git a/README.md b/README.md
index f466ae2ff1b499d5bed52554e9240d25c5e1d444..f952a63a62f26ca2ce3c585fb7d8a5df3fa773d8 100644
--- a/README.md
+++ b/README.md
@@ -4,20 +4,29 @@
 ## Installation
 
 ### Beginning
-- Create user `hermod`: `adduser hermod`
+- Create user `hermod`: (as root or `sudo`) `adduser hermod`
 - Put it in the same groups as pi (other than the `pi` group): `groups pi | sed 's/ /,/g'`, `usermod -a -G group1,group2,... hermod`
-change `pi` and `root` passwords
+- Change `pi` and `root` passwords
 - `sudo apt remove wolfram-engine minecraft-pi`
 - `sudo apt autoremove`
 - `sudo apt update && sudo apt upgrade && sudo reboot`
 - `sudo apt install git vim screen htop`
-- `sudo date -s '2018-02-15 2:26:00'` if needed
+- `sudo date -s '2018-02-15 2:26:00'` if needed (change the date to current date ofc)
 - Generate RSA key `ssh-keygen -t rsa -C "hermod.inno@gmail.com" -b 4096` and add it to gitlab
 - Clone project in `~`: `git clone git@gitlab.viarezo.fr:hermod/tv_panel.git`
 - `cp front/src/config.template.js front/src/config.js`
 - `cp server/config.template.js server/config.js`
 - Useful links: `ln -s ~/tv_panel/scripts/.screenrc ~/`, `ln -s ~/tv_panel/scripts/.vimrc ~/`
 
+### Telegram message on statup to get IP address
+- `cp scripts/waitForNetwork /root/scripts/waitForNetwork`
+- `cp scripts/startup-telegram-message /root/scripts/startup-telegram-message`
+- `sudo vim /root/scripts/startup-telegram-message` to add the token and chat id
+- Add in root crontab (`sudo crontab -e`):
+```
+@reboot sleep 1 && /root/scripts/waitForNetwork && /root/scripts/startup-telegram-message
+```
+
 ### Boot config
 `vim /boot/config.txt` to comment out `dtparam=audio=on` -> `#dtparam=audio=on` and add
 ```
@@ -50,14 +59,14 @@ After reboot or log out - log in, `npm install -g yarn`
 - `sudo ./test` to test
 - `follow ./python/README.md for python installation`
 
-Script without password for sudo:
-- `sudo mkdir /hermod_bin`
-- `cp tv_panel/scripts/statusRGB.py /hermod_bin/`
-- `sudo visudo` to add
+To display a status LED without root
+- `cp scripts/statusRGB_listener.py /root/scripts/statusRGB_listener.py` (from `tv_panel` directory)
+- Add in root crontab (`sudo crontab -e`):
 ```
-# Allow the scripts here to be run without sudo password
-hermod ALL=(ALL) NOPASSWD: /hermod_bin/statusRGB.py
+@reboot /usr/bin/python3 /root/scripts/statusRGB_listener.py
 ```
+- Reboot or run `/usr/bin/python3 /root/scripts/statusRGB_listener.py &` as root
+- To display a color for t seconds (float): `echo "55ee33 1" > /dev/rgb_pipe`
 
 ### Disable screen sleeping
 
@@ -72,7 +81,8 @@ hermod ALL=(ALL) NOPASSWD: /hermod_bin/statusRGB.py
 
 - Wiring: VCC -> 5V, GND -> GND, RX -> TX, TX -> RX
 - In `sudo raspi-config`, disable serial messages but enable serial interface
-```wget https://github.com/nfc-tools/libnfc/releases/download/libnfc-1.7.1/libnfc-1.7.1.tar.bz2
+```
+wget https://github.com/nfc-tools/libnfc/releases/download/libnfc-1.7.1/libnfc-1.7.1.tar.bz2
 tar xvjf libnfc-1.7.1.tar.bz2
 cd libnfc-1.7.1
 ./configure --prefix=/usr --sysconfdir=/etc
diff --git a/front/src/style.css b/front/src/style.css
index e0eaae40575493d59809380c5ab97a136e5a585b..29ef3ccc37d2d7821f18938dd90d8e761e5bc17c 100644
--- a/front/src/style.css
+++ b/front/src/style.css
@@ -55,10 +55,8 @@
   justify-content: space-between;
 }
 
-.image-text-bloc > .text-section > .text-element:first-child {
-  flex-grow: 1;
-}
-
 .image-text-bloc > .text-section > .text-element:last-child {
   white-space: nowrap;
+  flex-grow: 1;
+  text-align: right;
 }
diff --git a/scripts/.screenrc_tv_panel b/scripts/.screenrc_tv_panel
index 5688bc404564e47e733f01a208b10835b717e09e..20d6f0c20205718bf9ea1bba784e81d31680be60 100755
--- a/scripts/.screenrc_tv_panel
+++ b/scripts/.screenrc_tv_panel
@@ -29,8 +29,6 @@ bind f eval "hardstatus ignore"
 bind F eval "hardstatus alwayslastline"
 
 # Windows at startup
-screen -t "tvp_back" 0 bash -c 'cd /home/hermod/tv_panel && yarn start:prod'
-screen -t "tvp_front" 1 bash -c 'cd /home/hermod/tv_panel && yarn start:front'
-screen -t "tvp_chromium" 2 bash -c 'cd /home/hermod/tv_panel && DISPLAY=:0 ./front/start.sh'
-screen -t bash 3 bash
-select 3
+screen -t "tvp_chromium" 0 bash -c 'DISPLAY=:0 xdotool mousemove 10000 10000 && DISPLAY=:0 chromium-browser --kiosk --incognito --disable-gpu http://localhost:5000'
+screen -t bash 1 bash
+select 1
diff --git a/scripts/ecosystem.config.json b/scripts/ecosystem.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..a7576e3bf4741563faab7d9c70709267ec050e41
--- /dev/null
+++ b/scripts/ecosystem.config.json
@@ -0,0 +1,14 @@
+{
+  "apps": [
+    {
+      "script": "server/index.js",
+      "name": "tv-panel",
+      "instances": "1",
+      "exec_mode": "cluster",
+      "watch": false,
+      "env": {
+        "NODE_ENV": "production"
+      }
+    }
+  ]
+}
diff --git a/scripts/nfc-poll-wrapper.sh b/scripts/nfc-poll-wrapper.sh
new file mode 100755
index 0000000000000000000000000000000000000000..21ef17ef29b47cf5497408edb33afabac662a2dc
--- /dev/null
+++ b/scripts/nfc-poll-wrapper.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+while :
+do
+  res=$(nfc-poll | grep "UID " | cut -d: -f2 | sed 's/ //g' | xargs)
+  if [[ ! -z "$res" ]]
+  then
+    echo $res
+    echo "11ff11 0.2" > /dev/rgb_pipe
+    sleep 1
+  else
+    echo "ff1111 0.2" > /dev/rgb_pipe
+  fi
+done
diff --git a/scripts/statusRGB_listener.py b/scripts/statusRGB_listener.py
new file mode 100755
index 0000000000000000000000000000000000000000..fb7564a95678cd6fd38c981fb456d5379bcb6f51
--- /dev/null
+++ b/scripts/statusRGB_listener.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import time
+import subprocess
+
+from neopixel import Adafruit_NeoPixel, ws
+
+
+"""
+This script opens a pipe to access the RGB status LED by writing into it.
+When the script is running, you can use 'echo "11ee55 3" > PIPE_PATH'
+(PIPE_PATH defined below) to display the color 11ee55 for 3 seconds.
+
+It must be run as root.
+"""
+
+
+# LED strip configuration:
+LED_COUNT      = 1       # Number of LED pixels.
+LED_PIN        = 18      # GPIO pin connected to the pixels (18 uses PWM!).
+LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
+LED_DMA        = 10      # DMA channel to use for generating signal (try 10)
+LED_BRIGHTNESS = 255     # Set to 0 for darkest and 255 for brightest
+LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)
+LED_CHANNEL    = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53
+LED_STRIP      = ws.WS2811_STRIP_GRB   # Strip type and colour ordering
+
+# Pipe where the code listen for color commands
+PIPE_PATH = '/dev/rgb_pipe'
+
+
+def sanitize_color(s):
+    """Return (r, g, b) levels from html color format"""
+    hex_color = s.lstrip('#')
+    if len(hex_color) == 3:
+        hex_color = '{0}{0}{1}{1}{2}{2}'.format(*hex_color)
+    if len(hex_color) != 6:
+        return None
+
+    (r, g, b) = (int(x, 16) for x in [hex_color[i:i+2] for i in (0, 2, 4)])
+    return r, g, b
+
+
+def command_string_to_display(cs, strip):
+    """Display the color based on the 'color time' string"""
+    data = data0.split(' ')
+    colors = sanitize_color(data[0])
+    if colors is None:
+        return 1
+    r, g, b = colors
+    try:
+        t = float(data[1])
+    except:
+        t = -1
+
+    strip.setPixelColorRGB(0, r, g, b)
+    strip.show()
+    if t > 0:
+        time.sleep(t)
+        strip.setPixelColorRGB(0, 0, 0, 0)
+        strip.show()
+    return 0
+
+
+if __name__ == '__main__':
+    if os.path.exists(PIPE_PATH):
+        os.remove(PIPE_PATH)
+
+    os.mkfifo(PIPE_PATH)
+    os.system('chown root:hermod {0} && chmod g+rw {0}'.format(PIPE_PATH))  # rw access for hermod group
+
+    # Create NeoPixel object with appropriate configuration.
+    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA,
+                              LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
+
+    # Intialize the library (must be called once before other functions).
+    strip.begin()
+
+    # Listen to the pipe and display colors
+    while True:
+        data0 = subprocess.check_output(['cat', PIPE_PATH]).decode('utf-8')
+        command_string_to_display(data0, strip)
+
+    command_string_to_display('000000', strip)
diff --git a/scripts/update_server.sh b/scripts/update_server.sh
new file mode 100755
index 0000000000000000000000000000000000000000..31cff824509f91ca97832d96183eac539eb37ffc
--- /dev/null
+++ b/scripts/update_server.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+if [[ "$NODE_ENV" != "production" ]]
+then
+  read -p "The env if not production, this script can remove uncommitted changes. Are you sure you want to continue? [y/N] " prompt
+  if [[ $prompt == "y" || $prompt == "Y" || $prompt == "yes" || $prompt == "Yes" ]]
+  then
+    echo "Pursuing..."
+  else
+    echo "Aborted."
+    exit 1
+  fi
+fi
+
+# Pull code
+echo "Fetching the code..."
+git fetch
+# git reset --hard origin/master
+
+# Install deps
+echo "Installing dependencies..."
+yarn install --pure-lockfile
+
+# Build front
+echo "Building front-end..."
+yarn build:front
+
+# Restart server
+echo "Restarting the back-end server..."
+pm2 startOrRestart ./scripts/ecosystem.config.json
+
+# Restart screen
+echo "Restarting the screen..."
+screen -S TVPanelScreen -X quit
+./scripts/start_tv_panel.sh
diff --git a/scripts/waitForNetwork b/scripts/waitForNetwork
index 3c8192a1f97b26665a4df79a0b9d0100f38cfd21..8d0e610722484bc05b65e3a762e354d139b892e6 100755
--- a/scripts/waitForNetwork
+++ b/scripts/waitForNetwork
@@ -2,11 +2,13 @@
 
 while true
 do
+    echo "1111ff 0.1" > /dev/rgb_pipe
     ping 8.8.8.8 -c 3 -W 15 -q > /dev/null
     res=$?
     if [ "$res" = "0" ]
     then
 	echo "Connection established"
+	echo "1111ff 3" > /dev/rgb_pipe
 	break
     else
 	echo "Connection failed"
@@ -14,5 +16,4 @@ do
     sleep 5
 done
 
-
 exit 0
diff --git a/server/index.js b/server/index.js
index a9a740d2bb915c42911b6e26bbf0ccc144bb43cf..9361359b10407328dde9f07c8c4c55b56f09383c 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,46 +1,34 @@
-const fetch = require('node-fetch');
-
-const {
-  uuid, fontSize, rowHeight, port, api,
-} = require('./config');
-
-const dummyResponse = require('./dummyResponse.json');
-
-const io = require('socket.io')(port || 3000);
-const { createSignedJWT, interval } = require('./utils');
-
-const useDummy = false;
-
-io.of('/').on('connection', (socket) => {
-  socket.emit('config', { fontSize, rowHeight });
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const socketIO = require('socket.io');
+const { port } = require('./config');
+const socket = require('./socket');
+
+const indexPath = path.resolve(__dirname, '../front/build/index.html');
+
+const server = (req, res) => {
+  let url = req.url.replace(/(^\/|\/$)/g, '').trim();
+  if (url === '') {
+    url = 'index.html';
+  }
+  const askedPath = path.resolve(__dirname, `../front/build/${url}`);
+  const filePath = fs.existsSync(askedPath) ? askedPath : indexPath;
+  fs.readFile(filePath, (err, data) => {
+    if (err) {
+      res.writeHead(500);
+      res.end('Error loading index.html');
+      return;
+    }
+
+    res.writeHead(200);
+    res.end(data);
+  });
+};
 
-  const chrono = useDummy
-    ? interval(() => {
-      socket.emit('panel_data', dummyResponse);
-      return 15000;
-    }, 0)
-    : interval(
-      () =>
-        fetch(`${api.url}/${api.version}/screen/${uuid}`, {
-          headers: {
-            Autorization: `Bearer ${createSignedJWT()}`,
-          },
-        })
-          .then(rawRes => rawRes.json())
-          .then((res) => {
-            socket.emit('panel_data', res);
-            return res.ttl;
-          })
-          .catch(console.log),
-      0,
-    );
+const app = http.createServer(server);
+app.listen(port || 3000);
 
-  // Respond to date message with the date
-  socket.on('date', () => {
-    socket.emit('date', { date: Date.now() });
-  });
+const io = socketIO(app);
 
-  socket.on('disconnect', () => {
-    chrono.clear();
-  });
-});
+io.of('/').on('connection', socket);
diff --git a/server/socket.js b/server/socket.js
new file mode 100644
index 0000000000000000000000000000000000000000..dfe5cfe3a4b64bddfcaa532f71296c7da5e7eaa5
--- /dev/null
+++ b/server/socket.js
@@ -0,0 +1,75 @@
+const fetch = require('node-fetch');
+const path = require('path');
+
+const {
+  uuid, fontSize, rowHeight, api,
+} = require('./config');
+
+const { spawn } = require('child_process');
+
+const dummyResponse = require('./dummyResponse.json');
+
+const { createSignedJWT, interval } = require('./utils');
+
+const useDummy = false;
+
+let version;
+
+const checkVersion = (newVersion) => {
+  if (version && version !== newVersion) {
+    console.log('Update code');
+    const updateServer = spawn(path.resolve(__dirname, '../scripts/update_server.sh'));
+    updateServer.stdout.on('data', (data) => {
+      process.stdout.write(data);
+    });
+  }
+  version = newVersion;
+};
+
+const doScreenApiRequest = (socket, userid = null) => {
+  const query = userid ? `?userid=${userid}` : '';
+  return fetch(`${api.url}/${api.version}/screen/${uuid}${query}`, {
+    headers: {
+      Autorization: `Bearer ${createSignedJWT()}`,
+    },
+  })
+    .then(rawRes => rawRes.json())
+    .then(async (res) => {
+      checkVersion(res.version);
+      socket.emit('panel_data', res);
+      return res.ttl;
+    })
+    .catch(console.log);
+};
+
+const setChrono = (socket) => {
+  if (useDummy) {
+    return interval(() => socket.emit('panel_data', dummyResponse), 10000);
+  }
+  return interval(() => doScreenApiRequest(socket), 10000);
+};
+
+module.exports = (socket) => {
+  socket.emit('config', { fontSize, rowHeight });
+
+  const chrono = setChrono(socket);
+  chrono.startNow();
+
+  // Respond to date message with the date
+  socket.on('date', () => {
+    socket.emit('date', { date: Date.now() });
+  });
+
+  socket.on('disconnect', () => {
+    chrono.stop();
+  });
+
+  const badgeChild = spawn(path.resolve(__dirname, '../scripts/nfc-poll-wrapper.sh'));
+
+  badgeChild.stdout.on('data', (data0) => {
+    const userid = data0.toString().trim();
+    console.log(`child stdout: ${userid}`);
+
+    doScreenApiRequest(socket, userid).then(ttl => chrono.restart(ttl));
+  });
+};
diff --git a/server/utils.js b/server/utils.js
index 0716563e9fb2361df198888890de647cfe70f282..119080646c2956a563dfe87c3ff9deb354f7a8f3 100644
--- a/server/utils.js
+++ b/server/utils.js
@@ -2,14 +2,6 @@
 const jwt = require('jsonwebtoken');
 const { cert } = require('./config');
 
-const initialOutput = { id: null, clear: () => undefined, trigger: () => undefined };
-
-const replace = (toReplace, replacer) => {
-  Object.keys(replacer).forEach((x) => {
-    toReplace[x] = replacer[x];
-  });
-};
-
 function Timeout(fn, interval) {
   this.id = setTimeout(fn, interval);
   this.cleared = false;
@@ -19,7 +11,7 @@ function Timeout(fn, interval) {
   };
 }
 
-const interval = (fn, initialTTL, output = initialOutput) => {
+const interval = (fn, initialTTL, output = {}) => {
   const getTimeout = ttl =>
     new Timeout(async () => {
       let TTL;
@@ -27,10 +19,7 @@ const interval = (fn, initialTTL, output = initialOutput) => {
         if (this.cleared) {
           return;
         }
-        TTL = fn();
-        if (TTL instanceof Promise) {
-          TTL = await TTL;
-        }
+        TTL = await fn();
       } catch (error) {
         console.error(error);
         TTL = initialTTL;
@@ -41,18 +30,48 @@ const interval = (fn, initialTTL, output = initialOutput) => {
       const nextTTL = parseInt(TTL, 10) || initialTTL;
       interval(fn, nextTTL, output);
     }, ttl);
-  const getOutput = timeout => ({
-    id: timeout.id,
-    clear: () => timeout.clear(),
-    trigger: () => {
-      timeout.clear();
-      const newTimeout = getTimeout(0);
-      replace(output, getOutput(newTimeout));
-    },
-  });
 
-  const timeout = getTimeout(initialTTL);
-  replace(output, getOutput(timeout));
+  const getOutput = (timeout) => {
+    const stop = () => {
+      if (timeout) {
+        timeout.clear();
+      }
+    };
+    return {
+      start: (ttl) => {
+        if (timeout && !timeout.cleared) {
+          throw new Error('Interval is already started');
+        }
+        const newOutput = getOutput(getTimeout(ttl || initialTTL));
+        Object.assign(output, getOutput(newOutput));
+        return output;
+      },
+      startNow: () => {
+        if (timeout && !timeout.cleared) {
+          throw new Error('Interval is already started');
+        }
+        Object.assign(output, getOutput(getTimeout(0)));
+        return output;
+      },
+      stop,
+      restart: (ttl) => {
+        stop();
+        Object.assign(output, getOutput(getTimeout(ttl || initialTTL)));
+        return output;
+      },
+      restartNow: () => {
+        stop();
+        Object.assign(output, getOutput(getTimeout(0)));
+        return output;
+      },
+    };
+  };
+
+  if (Object.keys(output).length === 0) {
+    Object.assign(output, getOutput(null));
+  } else {
+    Object.assign(output, getOutput(getTimeout(initialTTL)));
+  }
   return output;
 };