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
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
1. Copiar Raspbian Lite en el SD usando Balena Etcher.
2. Crear un archivo en la partición boot con el nombre ssh para activar SSHD.
3. Loguear en el raspberry (pass por defecto: raspberry) y ejecutar:
sudo systemctl enable ssh
sudo apt install libopus-dev build-essential libasound2-dev libogg-dev python3-gpiozero python3-pip
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.
1. Clonar este repositorio.
2. Crear el symlink `intercom.yml` al archivo correspondiente.
3. Activar el servicio intercom:
```
sudo cp intercom.service /etc/systemd/system
sudo chown root:root /etc/systemd/system/intercom.service
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
INCOMING_CALL = 1
OUTGOING_CALL = 2
OP_CALL = 'CALL'.encode()
OP_HANG = 'HANG'.encode()
OP_PING = 'PING'.encode()
OP_OK = 'OK'.encode()
OP_ERROR = 'ERROR'.encode()
OP_CALL = "CALL".encode()
OP_HANG = "HANG".encode()
OP_PING = "PING".encode()
OP_OK = "OK".encode()
OP_ERROR = "ERROR".encode()
def exec_mumble(deaf: bool):
subprocess.Popen(['mumble', 'rpc', 'deaf' if deaf else 'undeaf'])
subprocess.Popen(['mumble', 'rpc', 'mute' if deaf else 'unmute'])
subprocess.run(
["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():
exec_mumble(True)
def undeaf_mumble():
exec_mumble(False)
class CallManager:
def __init__(self, config):
self.config = config
@@ -29,31 +37,31 @@ class CallManager:
def call(self, station):
try:
s = socket.socket()
print('host=', station['host'])
print('port=', station['port'])
s.connect((station['host'], station['port']))
print("host=", station["host"])
print("port=", station["port"])
s.connect((station["host"], station["port"]))
s.send(OP_CALL)
response = s.recv(1024)
print('Call notice sent', 'response=', response.decode())
print("Call notice sent", "response=", response.decode())
s.close()
undeaf_mumble()
self.state = OUTGOING_CALL
self.station = station
station['red'].on()
station["red"].on()
except Exception as e:
print('Error al realizar la llamada: ', e)
station['green'].off()
print("Error al realizar la llamada: ", e)
station["green"].off()
def incoming_call(self, station):
undeaf_mumble()
self.state = INCOMING_CALL
self.station = station
station['red'].on()
station["red"].on()
def hang(self):
print('Hang! self.station', self.station)
print("Hang! self.station", self.station)
if self.station is not None:
self.station['red'].off()
self.station["red"].off()
deaf_mumble()
self.state = NO_CALL
self.station = None
@@ -61,8 +69,8 @@ class CallManager:
def notify_hang(self):
if self.station is not None:
s = socket.socket()
s.connect((self.station['host'], self.station['port']))
s.connect((self.station["host"], self.station["port"]))
s.send(OP_HANG)
response = s.recv(1024)
print('Hang notify sent', 'response=', response.decode())
print("Hang notify sent", "response=", response.decode())
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 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
config = yaml.safe_load(open("intercom.yml"))
port = config['port']
stations = config['stations']
name = config["name"]
port = config["port"]
stations = config["stations"]
callManager = CallManager(config)
def getstationbyip(ip):
for s in stations:
if s['ip'] == ip:
if s["ip"] == ip:
return s
return None
# Escuchamos en port para peticiones de operaciones.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def listen(call_manager):
serversocket.bind(('0.0.0.0', port))
print('Listening address', socket.gethostname(), 'on port', port)
serversocket.bind(("0.0.0.0", port))
print("Listening address", socket.gethostname(), "on port", port)
serversocket.listen(5)
while True:
# accept connections from outside
(clientsocket, ip_address) = serversocket.accept()
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])
if data == OP_HANG:
@@ -47,7 +67,7 @@ def listen(call_manager):
call_manager.incoming_call(station)
clientsocket.send(OP_OK)
elif data == OP_PING:
station['green'].on()
station["green"].on()
clientsocket.send(OP_OK)
else:
clientsocket.send(OP_ERROR)
@@ -59,23 +79,24 @@ def check_status(station):
while True:
s = socket.socket()
try:
s.connect((station['host'], station['port']))
s.connect((station["host"], station["port"]))
s.send(OP_PING)
if s.recv(1024) == OP_OK:
station['green'].on()
station["green"].on()
else:
station['red'].off()
station['green'].off()
station["red"].off()
station["green"].off()
except socket.error:
station['red'].off()
station['green'].off()
print('Check status failed! host=', station['host'])
station["red"].off()
station["green"].off()
print("Check status failed! host=", station["host"])
finally:
s.close()
time.sleep(CHECK_STATUS_INTERVAL)
# 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.
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.
for _station in stations:
_station['ip'] = socket.gethostbyname(_station['host'])
_station["ip"] = socket.gethostbyname(_station["host"])
_station['red'] = LED(_station['red led'])
_station['red'].off()
_station["red"] = LED(_station["red led"])
_station["red"].off()
_station['green'] = LED(_station['green led'])
_station['green'].off()
_station["green"] = LED(_station["green led"])
_station["green"].off()
_station['btn'] = Button(_station['button'])
_station['old button state'] = _station['btn'].is_pressed
_station['button state changed time'] = time.clock_gettime(time.CLOCK_MONOTONIC)
_station['button pressed'] = False
_station["btn"] = Button(_station["button"])
_station["old button state"] = _station["btn"].is_pressed
_station["button state changed time"] = time.clock_gettime(time.CLOCK_MONOTONIC)
_station["button pressed"] = False
thread_check = Thread(target=check_status, args=(_station,))
thread_check.daemon = True
@@ -105,17 +126,27 @@ for _station in stations:
try:
while True:
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.
elapsed_time = time.clock_gettime(time.CLOCK_MONOTONIC) - _station['button state changed time']
if new_input_state != _station['old button state'] and elapsed_time > 0.2:
if not _station['toogle button']:
_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)
elapsed_time = (
time.clock_gettime(time.CLOCK_MONOTONIC)
- _station["button state changed time"]
)
if new_input_state != _station["old button state"] and elapsed_time > 0.2:
if not _station["toogle button"]:
_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
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.
if callManager.state == NO_CALL:
callManager.call(_station)
@@ -126,13 +157,15 @@ try:
callManager.notify_hang()
callManager.hang()
_station['old button state'] = new_input_state
_station['button state changed time'] = time.clock_gettime(time.CLOCK_MONOTONIC)
_station["old button state"] = new_input_state
_station["button state changed time"] = time.clock_gettime(
time.CLOCK_MONOTONIC
)
time.sleep(0.02)
finally:
print('Close listeing port ', port)
print("Close listeing port ", port)
serversocket.close()
for _station in stations:
_station['red'].close()
_station['green'].close()
_station['btn'].close()
_station["red"].close()
_station["green"].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
[Service]
ExecStart=xvfb-run mumble mumble://uli@miki
ExecStart=xvfb-run -n 1 mumble mumble://arizkuren@miki
WorkingDirectory=/home/pi
StandardOutput=inherit
StandardError=inherit