feat: configurable name. Readme revamped. Code formatted.
This commit is contained in:
29
README.md
29
README.md
@@ -1,29 +1,14 @@
|
|||||||
# Intercom
|
# Intercom
|
||||||
|
|
||||||
Comunicación VoIP entre dos raspberrys.
|
Comunicación VoIP entre dos raspberrys. La versión actual utiliza Mumble para la capa VoIP. La versión anterior usaba Parole.
|
||||||
|
|
||||||
## How to
|
## How to
|
||||||
|
|
||||||
1. Copiar Raspbian Lite en el SD usando Balena Etcher.
|
1. Clonar este repositorio.
|
||||||
2. Crear un archivo en la partición boot con el nombre ssh para activar SSHD.
|
2. Crear el symlink `intercom.yml` al archivo correspondiente.
|
||||||
3. Loguear en el raspberry (pass por defecto: raspberry) y ejecutar:
|
3. Activar el servicio intercom:
|
||||||
sudo systemctl enable ssh
|
```
|
||||||
sudo apt install libopus-dev build-essential libasound2-dev libogg-dev python3-gpiozero python3-pip
|
sudo cp intercom.service /etc/systemd/system
|
||||||
sudo pip3 install pyyaml
|
|
||||||
wget http://holdenc.altervista.org/parole/downloads/parole-010beta4.tgz
|
|
||||||
tar xf parole-010beta4.tgz
|
|
||||||
cd parole-010beta4/
|
|
||||||
make
|
|
||||||
sudo cp parole /usr/bin/
|
|
||||||
4. Crear /etc/systemd/system/parole.service y activarlo.
|
|
||||||
sudo chown root:root /etc/systemd/system/parole.service
|
|
||||||
sudo systemctl enable parole
|
|
||||||
5. Crear /etc/systemd/system/intercom.service y activarlo.
|
|
||||||
sudo chown root:root /etc/systemd/system/intercom.service
|
sudo chown root:root /etc/systemd/system/intercom.service
|
||||||
sudo systemctl enable intercom
|
sudo systemctl enable intercom
|
||||||
6. Añadir intercom en /etc/hosts
|
```
|
||||||
7. Copiar intercom.py e intercom.yml en la home. Editar intercom.yml
|
|
||||||
|
|
||||||
## Avoid PulseAudio for using parole directly
|
|
||||||
|
|
||||||
`pasuspender -- parole -c rba -d plughw:0,0``
|
|
||||||
|
|||||||
@@ -4,22 +4,30 @@ import socket
|
|||||||
NO_CALL = 0
|
NO_CALL = 0
|
||||||
INCOMING_CALL = 1
|
INCOMING_CALL = 1
|
||||||
OUTGOING_CALL = 2
|
OUTGOING_CALL = 2
|
||||||
OP_CALL = 'CALL'.encode()
|
OP_CALL = "CALL".encode()
|
||||||
OP_HANG = 'HANG'.encode()
|
OP_HANG = "HANG".encode()
|
||||||
OP_PING = 'PING'.encode()
|
OP_PING = "PING".encode()
|
||||||
OP_OK = 'OK'.encode()
|
OP_OK = "OK".encode()
|
||||||
OP_ERROR = 'ERROR'.encode()
|
OP_ERROR = "ERROR".encode()
|
||||||
|
|
||||||
|
|
||||||
def exec_mumble(deaf: bool):
|
def exec_mumble(deaf: bool):
|
||||||
subprocess.Popen(['mumble', 'rpc', 'deaf' if deaf else 'undeaf'])
|
subprocess.run(
|
||||||
subprocess.Popen(['mumble', 'rpc', 'mute' if deaf else 'unmute'])
|
["xvfb-run", "-n", "1", "mumble", "rpc", "deaf" if deaf else "undeaf"]
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
["xvfb-run", "-n", "2", "mumble", "rpc", "mute" if deaf else "unmute"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def deaf_mumble():
|
def deaf_mumble():
|
||||||
exec_mumble(True)
|
exec_mumble(True)
|
||||||
|
|
||||||
|
|
||||||
def undeaf_mumble():
|
def undeaf_mumble():
|
||||||
exec_mumble(False)
|
exec_mumble(False)
|
||||||
|
|
||||||
|
|
||||||
class CallManager:
|
class CallManager:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
@@ -29,31 +37,31 @@ class CallManager:
|
|||||||
def call(self, station):
|
def call(self, station):
|
||||||
try:
|
try:
|
||||||
s = socket.socket()
|
s = socket.socket()
|
||||||
print('host=', station['host'])
|
print("host=", station["host"])
|
||||||
print('port=', station['port'])
|
print("port=", station["port"])
|
||||||
s.connect((station['host'], station['port']))
|
s.connect((station["host"], station["port"]))
|
||||||
s.send(OP_CALL)
|
s.send(OP_CALL)
|
||||||
response = s.recv(1024)
|
response = s.recv(1024)
|
||||||
print('Call notice sent', 'response=', response.decode())
|
print("Call notice sent", "response=", response.decode())
|
||||||
s.close()
|
s.close()
|
||||||
undeaf_mumble()
|
undeaf_mumble()
|
||||||
self.state = OUTGOING_CALL
|
self.state = OUTGOING_CALL
|
||||||
self.station = station
|
self.station = station
|
||||||
station['red'].on()
|
station["red"].on()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error al realizar la llamada: ', e)
|
print("Error al realizar la llamada: ", e)
|
||||||
station['green'].off()
|
station["green"].off()
|
||||||
|
|
||||||
def incoming_call(self, station):
|
def incoming_call(self, station):
|
||||||
undeaf_mumble()
|
undeaf_mumble()
|
||||||
self.state = INCOMING_CALL
|
self.state = INCOMING_CALL
|
||||||
self.station = station
|
self.station = station
|
||||||
station['red'].on()
|
station["red"].on()
|
||||||
|
|
||||||
def hang(self):
|
def hang(self):
|
||||||
print('Hang! self.station', self.station)
|
print("Hang! self.station", self.station)
|
||||||
if self.station is not None:
|
if self.station is not None:
|
||||||
self.station['red'].off()
|
self.station["red"].off()
|
||||||
deaf_mumble()
|
deaf_mumble()
|
||||||
self.state = NO_CALL
|
self.state = NO_CALL
|
||||||
self.station = None
|
self.station = None
|
||||||
@@ -61,8 +69,8 @@ class CallManager:
|
|||||||
def notify_hang(self):
|
def notify_hang(self):
|
||||||
if self.station is not None:
|
if self.station is not None:
|
||||||
s = socket.socket()
|
s = socket.socket()
|
||||||
s.connect((self.station['host'], self.station['port']))
|
s.connect((self.station["host"], self.station["port"]))
|
||||||
s.send(OP_HANG)
|
s.send(OP_HANG)
|
||||||
response = s.recv(1024)
|
response = s.recv(1024)
|
||||||
print('Hang notify sent', 'response=', response.decode())
|
print("Hang notify sent", "response=", response.decode())
|
||||||
s.close()
|
s.close()
|
||||||
|
|||||||
9
intercom-arizkuren.yml
Normal file
9
intercom-arizkuren.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: arizkuren
|
||||||
|
port: 8111
|
||||||
|
stations:
|
||||||
|
- host: miki
|
||||||
|
port: 8111
|
||||||
|
button: 4
|
||||||
|
toogle button: True
|
||||||
|
red led: 26
|
||||||
|
green led: 12
|
||||||
9
intercom-uli.yml
Normal file
9
intercom-uli.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: uli
|
||||||
|
port: 8111
|
||||||
|
stations:
|
||||||
|
- host: rba
|
||||||
|
port: 8111
|
||||||
|
button: 25
|
||||||
|
toogle button: True
|
||||||
|
red led: 26
|
||||||
|
green led: 12
|
||||||
113
intercom.py
113
intercom.py
@@ -7,35 +7,55 @@ import yaml
|
|||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from call_manager import CallManager, NO_CALL, INCOMING_CALL, OUTGOING_CALL, OP_CALL, OP_HANG, OP_PING, OP_OK, OP_ERROR
|
from call_manager import (
|
||||||
|
CallManager,
|
||||||
|
NO_CALL,
|
||||||
|
INCOMING_CALL,
|
||||||
|
OUTGOING_CALL,
|
||||||
|
OP_CALL,
|
||||||
|
OP_HANG,
|
||||||
|
OP_PING,
|
||||||
|
OP_OK,
|
||||||
|
OP_ERROR,
|
||||||
|
)
|
||||||
|
|
||||||
CHECK_STATUS_INTERVAL = 120
|
CHECK_STATUS_INTERVAL = 120
|
||||||
|
|
||||||
|
|
||||||
config = yaml.safe_load(open("intercom.yml"))
|
config = yaml.safe_load(open("intercom.yml"))
|
||||||
port = config['port']
|
name = config["name"]
|
||||||
stations = config['stations']
|
port = config["port"]
|
||||||
|
stations = config["stations"]
|
||||||
callManager = CallManager(config)
|
callManager = CallManager(config)
|
||||||
|
|
||||||
|
|
||||||
def getstationbyip(ip):
|
def getstationbyip(ip):
|
||||||
for s in stations:
|
for s in stations:
|
||||||
if s['ip'] == ip:
|
if s["ip"] == ip:
|
||||||
return s
|
return s
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Escuchamos en port para peticiones de operaciones.
|
# Escuchamos en port para peticiones de operaciones.
|
||||||
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
|
||||||
def listen(call_manager):
|
def listen(call_manager):
|
||||||
serversocket.bind(('0.0.0.0', port))
|
serversocket.bind(("0.0.0.0", port))
|
||||||
print('Listening address', socket.gethostname(), 'on port', port)
|
print("Listening address", socket.gethostname(), "on port", port)
|
||||||
serversocket.listen(5)
|
serversocket.listen(5)
|
||||||
while True:
|
while True:
|
||||||
# accept connections from outside
|
# accept connections from outside
|
||||||
(clientsocket, ip_address) = serversocket.accept()
|
(clientsocket, ip_address) = serversocket.accept()
|
||||||
data = clientsocket.recv(1024)
|
data = clientsocket.recv(1024)
|
||||||
print('Connection from', ip_address, 'host=', clientsocket.getpeername(), 'data=', data.decode())
|
print(
|
||||||
|
"Connection from",
|
||||||
|
ip_address,
|
||||||
|
"host=",
|
||||||
|
clientsocket.getpeername(),
|
||||||
|
"data=",
|
||||||
|
data.decode(),
|
||||||
|
)
|
||||||
station = getstationbyip(ip_address[0])
|
station = getstationbyip(ip_address[0])
|
||||||
|
|
||||||
if data == OP_HANG:
|
if data == OP_HANG:
|
||||||
@@ -47,7 +67,7 @@ def listen(call_manager):
|
|||||||
call_manager.incoming_call(station)
|
call_manager.incoming_call(station)
|
||||||
clientsocket.send(OP_OK)
|
clientsocket.send(OP_OK)
|
||||||
elif data == OP_PING:
|
elif data == OP_PING:
|
||||||
station['green'].on()
|
station["green"].on()
|
||||||
clientsocket.send(OP_OK)
|
clientsocket.send(OP_OK)
|
||||||
else:
|
else:
|
||||||
clientsocket.send(OP_ERROR)
|
clientsocket.send(OP_ERROR)
|
||||||
@@ -59,23 +79,24 @@ def check_status(station):
|
|||||||
while True:
|
while True:
|
||||||
s = socket.socket()
|
s = socket.socket()
|
||||||
try:
|
try:
|
||||||
s.connect((station['host'], station['port']))
|
s.connect((station["host"], station["port"]))
|
||||||
s.send(OP_PING)
|
s.send(OP_PING)
|
||||||
if s.recv(1024) == OP_OK:
|
if s.recv(1024) == OP_OK:
|
||||||
station['green'].on()
|
station["green"].on()
|
||||||
else:
|
else:
|
||||||
station['red'].off()
|
station["red"].off()
|
||||||
station['green'].off()
|
station["green"].off()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
station['red'].off()
|
station["red"].off()
|
||||||
station['green'].off()
|
station["green"].off()
|
||||||
print('Check status failed! host=', station['host'])
|
print("Check status failed! host=", station["host"])
|
||||||
finally:
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
time.sleep(CHECK_STATUS_INTERVAL)
|
time.sleep(CHECK_STATUS_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
# Start mumble client
|
# Start mumble client
|
||||||
subprocess.Popen(['mumble', 'mumble://uli@miki'])
|
subprocess.Popen(["xvfb-run", "-n", "1", "mumble", "mumble://" + name + "@miki"])
|
||||||
|
|
||||||
# Start thread to wait for incoming hang petitions.
|
# Start thread to wait for incoming hang petitions.
|
||||||
thread_listen = Thread(target=listen, args=(callManager,))
|
thread_listen = Thread(target=listen, args=(callManager,))
|
||||||
@@ -84,18 +105,18 @@ thread_listen.start()
|
|||||||
|
|
||||||
# Power off leds and start threads to check other intercoms status.
|
# Power off leds and start threads to check other intercoms status.
|
||||||
for _station in stations:
|
for _station in stations:
|
||||||
_station['ip'] = socket.gethostbyname(_station['host'])
|
_station["ip"] = socket.gethostbyname(_station["host"])
|
||||||
|
|
||||||
_station['red'] = LED(_station['red led'])
|
_station["red"] = LED(_station["red led"])
|
||||||
_station['red'].off()
|
_station["red"].off()
|
||||||
|
|
||||||
_station['green'] = LED(_station['green led'])
|
_station["green"] = LED(_station["green led"])
|
||||||
_station['green'].off()
|
_station["green"].off()
|
||||||
|
|
||||||
_station['btn'] = Button(_station['button'])
|
_station["btn"] = Button(_station["button"])
|
||||||
_station['old button state'] = _station['btn'].is_pressed
|
_station["old button state"] = _station["btn"].is_pressed
|
||||||
_station['button state changed time'] = time.clock_gettime(time.CLOCK_MONOTONIC)
|
_station["button state changed time"] = time.clock_gettime(time.CLOCK_MONOTONIC)
|
||||||
_station['button pressed'] = False
|
_station["button pressed"] = False
|
||||||
|
|
||||||
thread_check = Thread(target=check_status, args=(_station,))
|
thread_check = Thread(target=check_status, args=(_station,))
|
||||||
thread_check.daemon = True
|
thread_check.daemon = True
|
||||||
@@ -105,17 +126,27 @@ for _station in stations:
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
for _station in stations:
|
for _station in stations:
|
||||||
new_input_state = _station['btn'].is_pressed
|
new_input_state = _station["btn"].is_pressed
|
||||||
# Al pulsar el interruptor cambia el estado.
|
# Al pulsar el interruptor cambia el estado.
|
||||||
elapsed_time = time.clock_gettime(time.CLOCK_MONOTONIC) - _station['button state changed time']
|
elapsed_time = (
|
||||||
if new_input_state != _station['old button state'] and elapsed_time > 0.2:
|
time.clock_gettime(time.CLOCK_MONOTONIC)
|
||||||
if not _station['toogle button']:
|
- _station["button state changed time"]
|
||||||
_station['button pressed'] = not _station['button pressed']
|
)
|
||||||
if _station['button pressed']:
|
if new_input_state != _station["old button state"] and elapsed_time > 0.2:
|
||||||
_station['old button state'] = new_input_state
|
if not _station["toogle button"]:
|
||||||
_station['button state changed time'] = time.clock_gettime(time.CLOCK_MONOTONIC)
|
_station["button pressed"] = not _station["button pressed"]
|
||||||
|
if _station["button pressed"]:
|
||||||
|
_station["old button state"] = new_input_state
|
||||||
|
_station["button state changed time"] = time.clock_gettime(
|
||||||
|
time.CLOCK_MONOTONIC
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
print('Interruptor pulsado callManager.state=', callManager.state, 'elapsed_time', elapsed_time)
|
print(
|
||||||
|
"Interruptor pulsado callManager.state=",
|
||||||
|
callManager.state,
|
||||||
|
"elapsed_time",
|
||||||
|
elapsed_time,
|
||||||
|
)
|
||||||
# Si no hay llamada en curso la hacemos.
|
# Si no hay llamada en curso la hacemos.
|
||||||
if callManager.state == NO_CALL:
|
if callManager.state == NO_CALL:
|
||||||
callManager.call(_station)
|
callManager.call(_station)
|
||||||
@@ -126,13 +157,15 @@ try:
|
|||||||
callManager.notify_hang()
|
callManager.notify_hang()
|
||||||
callManager.hang()
|
callManager.hang()
|
||||||
|
|
||||||
_station['old button state'] = new_input_state
|
_station["old button state"] = new_input_state
|
||||||
_station['button state changed time'] = time.clock_gettime(time.CLOCK_MONOTONIC)
|
_station["button state changed time"] = time.clock_gettime(
|
||||||
|
time.CLOCK_MONOTONIC
|
||||||
|
)
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
finally:
|
finally:
|
||||||
print('Close listeing port ', port)
|
print("Close listeing port ", port)
|
||||||
serversocket.close()
|
serversocket.close()
|
||||||
for _station in stations:
|
for _station in stations:
|
||||||
_station['red'].close()
|
_station["red"].close()
|
||||||
_station['green'].close()
|
_station["green"].close()
|
||||||
_station['btn'].close()
|
_station["btn"].close()
|
||||||
|
|||||||
15
intercom.yml
15
intercom.yml
@@ -1,15 +0,0 @@
|
|||||||
port: 8111
|
|
||||||
audio device: plughw:2,0
|
|
||||||
stations:
|
|
||||||
- host: rba
|
|
||||||
port: 8111
|
|
||||||
button: 25
|
|
||||||
toogle button: True
|
|
||||||
red led: 26
|
|
||||||
green led: 12
|
|
||||||
# - host: kuku
|
|
||||||
# port: 8111
|
|
||||||
# button: 18
|
|
||||||
# toogle button: False
|
|
||||||
# red led: 26
|
|
||||||
# green led: 2
|
|
||||||
@@ -3,7 +3,7 @@ Description=Mumble
|
|||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=xvfb-run mumble mumble://uli@miki
|
ExecStart=xvfb-run -n 1 mumble mumble://arizkuren@miki
|
||||||
WorkingDirectory=/home/pi
|
WorkingDirectory=/home/pi
|
||||||
StandardOutput=inherit
|
StandardOutput=inherit
|
||||||
StandardError=inherit
|
StandardError=inherit
|
||||||
|
|||||||
Reference in New Issue
Block a user