Skip to content
Snippets Groups Projects
Commit a9f44745 authored by Aymeric Bernard's avatar Aymeric Bernard
Browse files

Merge branch 'feat/badge-reader' into 'master'

Feat/badge reader

See merge request hermod/tv_panel!14
parents 835203e4 0c51969e
Branches
No related tags found
No related merge requests found
...@@ -2,10 +2,12 @@ node_modules/ ...@@ -2,10 +2,12 @@ node_modules/
build/ build/
.cache/ .cache/
.DS_Store .DS_Store
.idea
*.log *.log
server/config.js server/config.js
front/src/config.js front/src/config.js
scripts/nfc-poll-wrapper-dummy.sh
# *.pub # *.pub
# *.key # *.key
...@@ -4,20 +4,29 @@ ...@@ -4,20 +4,29 @@
## Installation ## Installation
### Beginning ### 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` - 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 remove wolfram-engine minecraft-pi`
- `sudo apt autoremove` - `sudo apt autoremove`
- `sudo apt update && sudo apt upgrade && sudo reboot` - `sudo apt update && sudo apt upgrade && sudo reboot`
- `sudo apt install git vim screen htop` - `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 - 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` - Clone project in `~`: `git clone git@gitlab.viarezo.fr:hermod/tv_panel.git`
- `cp front/src/config.template.js front/src/config.js` - `cp front/src/config.template.js front/src/config.js`
- `cp server/config.template.js server/config.js` - `cp server/config.template.js server/config.js`
- Useful links: `ln -s ~/tv_panel/scripts/.screenrc ~/`, `ln -s ~/tv_panel/scripts/.vimrc ~/` - 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 ### Boot config
`vim /boot/config.txt` to comment out `dtparam=audio=on` -> `#dtparam=audio=on` and add `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` ...@@ -50,14 +59,14 @@ After reboot or log out - log in, `npm install -g yarn`
- `sudo ./test` to test - `sudo ./test` to test
- `follow ./python/README.md for python installation` - `follow ./python/README.md for python installation`
Script without password for sudo: To display a status LED without root
- `sudo mkdir /hermod_bin` - `cp scripts/statusRGB_listener.py /root/scripts/statusRGB_listener.py` (from `tv_panel` directory)
- `cp tv_panel/scripts/statusRGB.py /hermod_bin/` - Add in root crontab (`sudo crontab -e`):
- `sudo visudo` to add
``` ```
# Allow the scripts here to be run without sudo password @reboot /usr/bin/python3 /root/scripts/statusRGB_listener.py
hermod ALL=(ALL) NOPASSWD: /hermod_bin/statusRGB.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 ### Disable screen sleeping
...@@ -72,7 +81,8 @@ hermod ALL=(ALL) NOPASSWD: /hermod_bin/statusRGB.py ...@@ -72,7 +81,8 @@ hermod ALL=(ALL) NOPASSWD: /hermod_bin/statusRGB.py
- Wiring: VCC -> 5V, GND -> GND, RX -> TX, TX -> RX - Wiring: VCC -> 5V, GND -> GND, RX -> TX, TX -> RX
- In `sudo raspi-config`, disable serial messages but enable serial interface - 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 tar xvjf libnfc-1.7.1.tar.bz2
cd libnfc-1.7.1 cd libnfc-1.7.1
./configure --prefix=/usr --sysconfdir=/etc ./configure --prefix=/usr --sysconfdir=/etc
......
...@@ -55,10 +55,8 @@ ...@@ -55,10 +55,8 @@
justify-content: space-between; 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 { .image-text-bloc > .text-section > .text-element:last-child {
white-space: nowrap; white-space: nowrap;
flex-grow: 1;
text-align: right;
} }
...@@ -29,8 +29,6 @@ bind f eval "hardstatus ignore" ...@@ -29,8 +29,6 @@ bind f eval "hardstatus ignore"
bind F eval "hardstatus alwayslastline" bind F eval "hardstatus alwayslastline"
# Windows at startup # Windows at startup
screen -t "tvp_back" 0 bash -c 'cd /home/hermod/tv_panel && yarn start:prod' 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 "tvp_front" 1 bash -c 'cd /home/hermod/tv_panel && yarn start:front' screen -t bash 1 bash
screen -t "tvp_chromium" 2 bash -c 'cd /home/hermod/tv_panel && DISPLAY=:0 ./front/start.sh' select 1
screen -t bash 3 bash
select 3
{
"apps": [
{
"script": "server/index.js",
"name": "tv-panel",
"instances": "1",
"exec_mode": "cluster",
"watch": false,
"env": {
"NODE_ENV": "production"
}
}
]
}
#!/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
#!/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)
#!/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
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
while true while true
do do
echo "1111ff 0.1" > /dev/rgb_pipe
ping 8.8.8.8 -c 3 -W 15 -q > /dev/null ping 8.8.8.8 -c 3 -W 15 -q > /dev/null
res=$? res=$?
if [ "$res" = "0" ] if [ "$res" = "0" ]
then then
echo "Connection established" echo "Connection established"
echo "1111ff 3" > /dev/rgb_pipe
break break
else else
echo "Connection failed" echo "Connection failed"
...@@ -14,5 +16,4 @@ do ...@@ -14,5 +16,4 @@ do
sleep 5 sleep 5
done done
exit 0 exit 0
const fetch = require('node-fetch'); const http = require('http');
const fs = require('fs');
const { const path = require('path');
uuid, fontSize, rowHeight, port, api, const socketIO = require('socket.io');
} = require('./config'); const { port } = require('./config');
const socket = require('./socket');
const dummyResponse = require('./dummyResponse.json');
const indexPath = path.resolve(__dirname, '../front/build/index.html');
const io = require('socket.io')(port || 3000);
const { createSignedJWT, interval } = require('./utils'); const server = (req, res) => {
let url = req.url.replace(/(^\/|\/$)/g, '').trim();
const useDummy = false; if (url === '') {
url = 'index.html';
io.of('/').on('connection', (socket) => { }
socket.emit('config', { fontSize, rowHeight }); 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 const app = http.createServer(server);
? interval(() => { app.listen(port || 3000);
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,
);
// Respond to date message with the date const io = socketIO(app);
socket.on('date', () => {
socket.emit('date', { date: Date.now() });
});
socket.on('disconnect', () => { io.of('/').on('connection', socket);
chrono.clear();
});
});
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));
});
};
...@@ -2,14 +2,6 @@ ...@@ -2,14 +2,6 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { cert } = require('./config'); 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) { function Timeout(fn, interval) {
this.id = setTimeout(fn, interval); this.id = setTimeout(fn, interval);
this.cleared = false; this.cleared = false;
...@@ -19,7 +11,7 @@ function Timeout(fn, interval) { ...@@ -19,7 +11,7 @@ function Timeout(fn, interval) {
}; };
} }
const interval = (fn, initialTTL, output = initialOutput) => { const interval = (fn, initialTTL, output = {}) => {
const getTimeout = ttl => const getTimeout = ttl =>
new Timeout(async () => { new Timeout(async () => {
let TTL; let TTL;
...@@ -27,10 +19,7 @@ const interval = (fn, initialTTL, output = initialOutput) => { ...@@ -27,10 +19,7 @@ const interval = (fn, initialTTL, output = initialOutput) => {
if (this.cleared) { if (this.cleared) {
return; return;
} }
TTL = fn(); TTL = await fn();
if (TTL instanceof Promise) {
TTL = await TTL;
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
TTL = initialTTL; TTL = initialTTL;
...@@ -41,18 +30,48 @@ const interval = (fn, initialTTL, output = initialOutput) => { ...@@ -41,18 +30,48 @@ const interval = (fn, initialTTL, output = initialOutput) => {
const nextTTL = parseInt(TTL, 10) || initialTTL; const nextTTL = parseInt(TTL, 10) || initialTTL;
interval(fn, nextTTL, output); interval(fn, nextTTL, output);
}, ttl); }, ttl);
const getOutput = timeout => ({
id: timeout.id, const getOutput = (timeout) => {
clear: () => timeout.clear(), const stop = () => {
trigger: () => { if (timeout) {
timeout.clear(); timeout.clear();
const newTimeout = getTimeout(0); }
replace(output, getOutput(newTimeout)); };
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;
},
};
};
const timeout = getTimeout(initialTTL); if (Object.keys(output).length === 0) {
replace(output, getOutput(timeout)); Object.assign(output, getOutput(null));
} else {
Object.assign(output, getOutput(getTimeout(initialTTL)));
}
return output; return output;
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment