]> git.0d.be Git - django-panik-nonstop.git/blob - nonstop/management/commands/switch-jack.py
switch-jack: fix logging
[django-panik-nonstop.git] / nonstop / management / commands / switch-jack.py
1 import asyncio
2 import json
3 import logging
4
5 import aiohttp
6 from django.core.management.base import BaseCommand, CommandError
7
8 from nonstop.app_settings import app_settings
9
10 try:
11     import jack
12 except ImportError:
13     jack = None
14
15 logger = logging.getLogger('switch-jack')
16
17
18 class ClientWebSocketResponse(aiohttp.client_ws.ClientWebSocketResponse):
19     async def pong(self, message):
20         # update jack connections regularly, in case of jack ports going down momentarily
21         self.command.update_jack_connections(self.command.currently_active)
22         await super().pong(message)
23
24
25 class Command(BaseCommand):
26     help = 'jack source switch'
27
28     def handle(self, verbosity, **options):
29         if jack is None:
30             raise CommandError('missing jack module (install python3-jack-client?)')
31         self.verbosity = verbosity
32         asyncio.run(self.main())
33
34     async def main(self):
35         if not app_settings.SWITCH_WS_URL:
36             raise CommandError('missing switch_ws_url')
37         self.currently_active = None
38         sleep_duration = 0.2
39         ClientWebSocketResponse.command = self
40         while True:
41             try:
42                 async with aiohttp.ClientSession(ws_response_class=ClientWebSocketResponse) as session:
43                     async with session.ws_connect(app_settings.SWITCH_WS_URL, heartbeat=5) as ws:
44                         logger.info('waiting for messages')
45                         sleep_duration = 0.2  # reset sleep duration to baseline
46                         async for msg in ws:
47                             if msg.type == aiohttp.WSMsgType.TEXT:
48                                 try:
49                                     msg = json.loads(msg.data)
50                                 except ValueError:
51                                     continue
52                                 if msg.get('active') != self.currently_active:
53                                     self.currently_active = msg.get('active')
54                                     logger.info('setting source: %s', self.currently_active)
55                                     self.update_jack_connections(self.currently_active)
56                             elif msg.type == aiohttp.WSMsgType.ERROR:
57                                 break
58                     logger.debug('lost websocket connection')
59             except aiohttp.ClientError as e:
60                 logger.warning('websocket error (%s)' % e)
61                 sleep_duration *= 2
62                 await asyncio.sleep(min(sleep_duration, 5))
63             except asyncio.CancelledError:
64                 # most probably because of a keyboard interrupt, quit silently
65                 return
66
67     def update_jack_connections(self, active):
68         if active not in app_settings.SWITCH_IN_PORTS:
69             logger.info('unsupported source: %s', active)
70             return
71         out_ports = app_settings.SWITCH_OUT_PORTS
72         with jack.Client('switch-jack') as client:
73             known_ports = {x.name for x in client.get_ports(is_audio=True)}
74             for dports in out_ports.values():
75                 if any(x not in known_ports for x in dports):
76                     logger.error('unavailable destination ports %r', dports)
77                     continue
78                 for port_id, port_names in app_settings.SWITCH_IN_PORTS.items():
79                     if any(x not in known_ports for x in port_names):
80                         logger.error('unavailable source ports %r', port_names)
81                         continue
82                     try:
83                         if port_id == active:
84                             self.jack_connect(client, port_names[0], dports[0])
85                             self.jack_connect(client, port_names[1], dports[1])
86                         else:
87                             self.jack_disconnect(client, port_names[0], dports[0])
88                             self.jack_disconnect(client, port_names[1], dports[1])
89                     except jack.JackError as e:
90                         logger.error('jack error: %s' % e)
91
92     def jack_connect(self, client, in_port, out_port):
93         connections = [x.name for x in client.get_all_connections(in_port)]
94         if out_port not in connections:
95             if self.verbosity:
96                 logger.info('connecting %s and %s', in_port, out_port)
97             client.connect(in_port, out_port)
98
99     def jack_disconnect(self, client, in_port, out_port):
100         connections = [x.name for x in client.get_all_connections(in_port)]
101         if out_port in connections:
102             if self.verbosity:
103                 logger.info('disconnecting %s and %s', in_port, out_port)
104             client.disconnect(in_port, out_port)