44This module contains the websocket connection class.
55"""
66
7- from typing import Dict , Tuple , Optional , Union
7+ from typing import Any , Callable , Dict , Tuple , Optional , Union
88from threading import Lock
99import json
1010from websocket import create_connection , WebSocket # type: ignore
@@ -79,6 +79,21 @@ def _handle_reply(self, command: str, rawreply: Union[str, bytes]):
7979 except json .JSONDecodeError as err :
8080 raise IOError (f"Invalid response received: { rawreply !r} " ) from err
8181
82+ def _handle_event_reply (self , event_name : str , rawreply : Union [str , bytes ]):
83+ try :
84+ reply = json .loads (rawreply )
85+ if event_name not in reply :
86+ return None
87+ response_data = reply [event_name ]
88+ result = response_data ["result" ]
89+ state , message = self ._handle_result (result )
90+ value = response_data .get ("value" )
91+ if state == "Ok" :
92+ return value
93+ _raise_error (state , message , value )
94+ except json .JSONDecodeError as err :
95+ raise IOError (f"Invalid response received: { rawreply !r} " ) from err
96+
8297 def _handle_result (
8398 self , result : Union [str , Dict [str , str ]]
8499 ) -> Tuple [str , Optional [str ]]:
@@ -105,6 +120,73 @@ def connect(self):
105120 self ._ws = None
106121 raise
107122
123+ def subscribe_events (
124+ self ,
125+ command : str ,
126+ event_name : str ,
127+ callback : Callable [[Any ], Optional [bool ]],
128+ arg = None ,
129+ ):
130+ """
131+ Start a subscription and call `callback` for each incoming event value.
132+
133+ This method blocks until the callback returns `False`, or an exception
134+ is raised. A `StopSubscription` command is sent before returning.
135+
136+ Args:
137+ command (str): Subscription command to send.
138+ event_name (str): Name of event messages to listen for.
139+ callback: Function called with each event payload.
140+ arg: Optional parameter to send with the subscription command.
141+ """
142+ if not callable (callback ):
143+ raise TypeError ("callback must be callable" )
144+ if self ._ws is None :
145+ raise IOError ("Not connected to CamillaDSP" )
146+
147+ subscribed = False
148+ try :
149+ with self ._lock :
150+ if arg is not None :
151+ query = json .dumps ({command : arg })
152+ else :
153+ query = json .dumps (command )
154+ self ._ws .send (query )
155+ rawrepl = self ._ws .recv ()
156+ except Exception as err :
157+ self ._ws = None
158+ raise IOError ("Lost connection to CamillaDSP" ) from err
159+
160+ self ._handle_reply (command , rawrepl )
161+ subscribed = True
162+
163+ try :
164+ while True :
165+ if self ._ws is None :
166+ raise IOError ("Lost connection to CamillaDSP" )
167+ try :
168+ with self ._lock :
169+ raw_event = self ._ws .recv ()
170+ except Exception as err :
171+ self ._ws = None
172+ raise IOError ("Lost connection to CamillaDSP" ) from err
173+
174+ event_data = self ._handle_event_reply (event_name , raw_event )
175+ if event_data is None :
176+ continue
177+ should_continue = callback (event_data )
178+ if should_continue is False :
179+ break
180+ finally :
181+ if subscribed and self ._ws is not None :
182+ try :
183+ with self ._lock :
184+ self ._ws .send (json .dumps ("StopSubscription" ))
185+ rawrepl = self ._ws .recv ()
186+ self ._handle_reply ("StopSubscription" , rawrepl )
187+ except Exception :
188+ self ._ws = None
189+
108190 def disconnect (self ):
109191 """
110192 Close the connection to the websocket.
0 commit comments