feat: configurable name. Readme revamped. Code formatted.

This commit is contained in:
2026-04-14 12:48:41 +02:00
parent 066535f5c1
commit 61017757fa
7 changed files with 126 additions and 97 deletions

View File

@@ -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``

View File

@@ -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
View 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
View 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

View File

@@ -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()

View File

@@ -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

View File

@@ -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