From 61017757fae17846f26dbb7f0762e8757e96c5a3 Mon Sep 17 00:00:00 2001 From: Eneko Nieto Date: Tue, 14 Apr 2026 12:48:41 +0200 Subject: [PATCH] feat: configurable name. Readme revamped. Code formatted. --- README.md | 29 +++-------- call_manager.py | 46 ++++++++++------- intercom-arizkuren.yml | 9 ++++ intercom-uli.yml | 9 ++++ intercom.py | 113 ++++++++++++++++++++++++++--------------- intercom.yml | 15 ------ mumble.service | 2 +- 7 files changed, 126 insertions(+), 97 deletions(-) create mode 100644 intercom-arizkuren.yml create mode 100644 intercom-uli.yml delete mode 100644 intercom.yml diff --git a/README.md b/README.md index 60cc2d4..6126e6d 100644 --- a/README.md +++ b/README.md @@ -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`` + ``` diff --git a/call_manager.py b/call_manager.py index a952f07..5a6c03a 100644 --- a/call_manager.py +++ b/call_manager.py @@ -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() diff --git a/intercom-arizkuren.yml b/intercom-arizkuren.yml new file mode 100644 index 0000000..150b05b --- /dev/null +++ b/intercom-arizkuren.yml @@ -0,0 +1,9 @@ +name: arizkuren +port: 8111 +stations: + - host: miki + port: 8111 + button: 4 + toogle button: True + red led: 26 + green led: 12 diff --git a/intercom-uli.yml b/intercom-uli.yml new file mode 100644 index 0000000..2fc188f --- /dev/null +++ b/intercom-uli.yml @@ -0,0 +1,9 @@ +name: uli +port: 8111 +stations: + - host: rba + port: 8111 + button: 25 + toogle button: True + red led: 26 + green led: 12 diff --git a/intercom.py b/intercom.py index 9fb836f..44ff7bd 100644 --- a/intercom.py +++ b/intercom.py @@ -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() diff --git a/intercom.yml b/intercom.yml deleted file mode 100644 index 77b63a0..0000000 --- a/intercom.yml +++ /dev/null @@ -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 diff --git a/mumble.service b/mumble.service index 4498990..8316dad 100644 --- a/mumble.service +++ b/mumble.service @@ -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