class OSCManager(object): PKT_SPLIT = 65000 def __init__(self, hostlisten='127.0.0.1', portlisten=33217, hostconnect=None, portconnect=None): self.hostlisten = hostlisten self.portlisten = portlisten self.hostconnect = hostconnect self.portconnect = portconnect self.server = None self.transport = None self.protocol = None self.dispatcher = Dispatcher() self.client_connection_sender_timer = None self.user_on_connection_timeout = None self.connected_hosts = dict() self.callbacks = dict() self.cmd_queue = [] @staticmethod def generate_uid(): return ''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(16)) async def init(self, loop=asyncio.get_event_loop(), on_connection_timeout=None, on_init_ok=None, _error_notify=True): if not self.transport: try: _LOGGER.info( f"OSC trying to init conpars={self.hostlisten}:{self.portlisten} -> {self.hostconnect}:{self.portconnect}" ) self.user_on_connection_timeout = on_connection_timeout self.server = AsyncIOOSCUDPServer( (self.hostlisten, self.portlisten), self.dispatcher, loop) if self.client_connection_sender_timer: self.client_connection_sender_timer = None self.dispatcher.map('/*', self.device_callback, needs_reply_address=True) self.transport, self.protocol = await self.server.create_serve_endpoint( ) except (Exception, OSError) as exception: _LOGGER.error(f"OSC init exception {traceback.format_exc()}") if on_init_ok and _error_notify: on_init_ok(exception) self.client_connection_sender_timer = Timer( 1, partial(self.init, loop=loop, on_connection_timeout=on_connection_timeout, on_init_ok=on_init_ok, _error_notify=False)) return try: if on_init_ok: on_init_ok(None) if self.hostconnect: self.connection_sender_timer_init(0) self.handle(COMMAND_CONNECTION, self.on_command_connection) except Exception: _LOGGER.error(f'OSC post init error {traceback.format_exc()}') async def send_client_command_connection(self): # _LOGGER.debug("Connecting") try: hp = (self.hostconnect, self.portconnect) self.on_command_connection(hp, self.portconnect, timeout=1) except Exception: _LOGGER.error(f'Connection send error: {traceback.format_exc()}') self.connection_sender_timer_init() def connection_handler_timer_init(self, hp, intv=6): hpstr = f'{hp[0]}:{hp[1]}' if hpstr in self.connected_hosts: d = self.connected_hosts[hpstr] if d['timer']: d['timer'].cancel() d['timer'] = Timer(intv, partial(self.set_connection_timeout, hp=hp)) def connection_sender_timer_init(self, intv=2.9): if self.client_connection_sender_timer: self.client_connection_sender_timer.cancel() # _LOGGER.debug(f'Rearm timer connect send {intv}') self.client_connection_sender_timer = Timer( intv, self.send_client_command_connection) def on_connection_timeout(self, hp, timeout): if not timeout: self.process_cmd_queue() if self.user_on_connection_timeout: self.user_on_connection_timeout(hp, timeout) def on_command_connection(self, hp, portlisten, timeout=False, sender=None): # _LOGGER.debug(f'On command connection type={type(portlisten)}') conn_from = hp hp = (hp[0], portlisten) hpstr = f'{hp[0]}:{hp[1]}' send_command = self.hostconnect is None or timeout new_connection = not timeout rearm_timer = not timeout if hpstr not in self.connected_hosts: self.connected_hosts[hpstr] = dict(hp=hp, conn_from=conn_from, timeout=timeout, timer=None, client=SimpleUDPClient( hp[0], hp[1])) rearm_timer = True elif not timeout and self.connected_hosts[hpstr]['timeout']: self.connected_hosts[hpstr]['timeout'] = False self.connected_hosts[hpstr]['conn_from'] = conn_from # _LOGGER.debug('Setting timeout to false') else: new_connection = False if new_connection: self.on_connection_timeout(hp, False) _LOGGER.info(f'Connection to {hp[0]}:{hp[1]} estabilished') if send_command: # _LOGGER.debug(f'Sending connect command as {"client" if self.hostconnect else "server"} to {hp[0]}:{hp[1]} (port={self.portlisten})') self.connected_hosts[hpstr]['client'].send_message( COMMAND_CONNECTION, (self.portlisten, )) if rearm_timer: self.connection_handler_timer_init(hp=hp) async def set_connection_timeout(self, hp=None): hpstr = f'{hp[0]}:{hp[1]}' if hpstr in self.connected_hosts: notifytimeout = True if self.hostconnect: if self.connected_hosts[hpstr]['timeout'] is not True: self.connected_hosts[hpstr]['timeout'] = True else: notifytimeout = False else: del self.connected_hosts[hpstr] if notifytimeout: self.on_connection_timeout(hp, True) _LOGGER.info(f'Connection to {hp[0]}:{hp[1]} lost') def deserialize(self, args): if len(args) == 1 and args[0] == '()': return tuple() args = list(args) for i, s in enumerate(args): args[i] = SerializableDBObj.deserialize(args[i], args[i]) return tuple(args) def device_callback(self, client_address, address, *oscs): if address != COMMAND_CONNECTION: _LOGGER.debug( f'Received cmd={address} cla={client_address} par={str(oscs)}') warn = True uid = '' if address in self.callbacks: item = None if len(oscs) > 0 and isinstance( oscs[0], str) and oscs[0] in self.callbacks[address]: _LOGGER.debug(f'Found device command (uid={oscs[0]})') item = self.callbacks[address][oscs[0]] uid = oscs[0] pars = oscs[1:] warn = False if '' in self.callbacks[address]: item = self.callbacks[address][''] uid = '' pars = oscs warn = False if item: if not isinstance(item['split'], bool) and address != COMMAND_SPLIT: if not pars or not isinstance(pars[0], str): return mo = re.search(r'^#([0-9]+)/([0-9]+)#(.*)', pars[0]) if mo: n1 = int(mo.group(1)) n2 = int(mo.group(2)) n3 = item['split'] if n1 == n3 + 1 or n1 == n3 or n1 == 1: item['split'] = n1 if n1 == 1: item['strsplit'] = mo.group(3) elif n1 == n3 + 1: item['strsplit'] += mo.group(3) self.send(COMMAND_SPLIT, n1, n2, uid=uid) if n1 != n2 and item['t']: item['t'].cancel() item['t'] = Timer( 30, partial(self.unhandle_by_timer, address, uid)) else: return if n1 != n2: return else: item['split'] = 0 pars = tuple(json.loads(item['strsplit'])) else: _LOGGER.warning( 'String is not splitted when split expected') return if item['t']: _LOGGER.debug( f'Cancelling unhandle timer add={address} uid={uid}') item['t'].cancel() self.unhandle_device(address, uid) try: fixedpars = item[ 'a'] if address != COMMAND_CONNECTION else ( client_address, ) + item['a'] item['f'](*fixedpars, *self.deserialize(pars), sender=client_address) except Exception: _LOGGER.error( f'Handler({fixedpars}, {pars} [{self.deserialize(pars)}]) error {traceback.format_exc()}' ) if warn: _LOGGER.warning( f'Handler not found for {address} (uid={uid}) ({self.callbacks})' ) def call_confirm_callback(self, *args, confirm_callback=None, confirm_params=(), timeout=False, uid='', sender=None): _LOGGER.debug( f'Calling confirm_callback with cp={confirm_params} args={args}') self.unhandle_device(COMMAND_CONFIRM, uid) confirm_callback(*confirm_params, *args, timeout=timeout) def send_device(self, address, uid, *args, do_split=False, confirm_callback=None, confirm_params=(), timeout=-1, dest=None): args = (uid, ) + args self.send(address, *args, uid=uid, do_split=do_split, confirm_callback=confirm_callback, confirm_params=confirm_params, timeout=timeout, dest=None) def uninit(self): if self.transport: self.transport.close() self.transport = None for _, x in self.callbacks.items(): for _, y in x.items(): if y['t']: y['t'].cancel() for _, x in self.connected_hosts.items(): if x['timer']: x['timer'].cancel() if self.client_connection_sender_timer: self.client_connection_sender_timer.cancel() self.client_connection_sender_timer = None self.user_on_connection_timeout = None def process_cmd_queue(self): if len(self.cmd_queue): hpstr = f'{self.hostconnect}:{self.portconnect}' if not self.hostconnect or ( hpstr in self.connected_hosts and not self.connected_hosts[hpstr]['timeout']): el = self.cmd_queue.pop(0) args = ('()', ) if not el['args'] else el['args'] if 'handles' in el: for p in el['handles']: self.handle_device(p['address'], p['uid'], p['callback'], *p['args'], **p['kwargs'], last_sent=time()) for _, d in self.connected_hosts.items(): # if el['address'] != COMMAND_CONNECTION: # _LOGGER.debug(f'Maybe Sending {el["dest"]} = {hpstr}') if not el['dest'] or d['conn_from'] == el['dest']: if el['address'] != COMMAND_CONNECTION: _LOGGER.debug( f'Sending[{d["hp"][0]}:{d["hp"][1]}] {el["address"]} -> {args}' ) d['client'].send_message(el['address'], args) self.process_cmd_queue() def call_split_callback(self, *args, timeout=False, uid='', item=None, last_sent=0, sender=None): idx = 0 if not uid else 1 timeout = timeout or item['splits'] != args[ idx + 1] or item['split'] != args[idx + 0] return self.send_split( retry=item['retry'], uid=uid, last_sent=last_sent, strsplit=item['strsplit'], split=item['split'], splits=item['splits'], currentsplit='' if not timeout else item['currentsplit'], sendto=item['sendto'], dest=item['dest']) def send_split(self, retry=-1, uid='', strsplit='', split=0, splits=0, last_sent=0, currentsplit='', sendto=COMMAND_CONFIRM, dest=None, handles=None): if not currentsplit: split = split + 1 if split > splits: return False retry = 0 currentsplit = f'#{split}/{splits}#{strsplit[0:OSCManager.PKT_SPLIT]}' strsplit = strsplit[OSCManager.PKT_SPLIT:] else: retry = retry + 1 _LOGGER.info( f'Timeout detected passed = {time()-last_sent} Split {split} / {splits} Retry {retry}' ) if retry >= 10: return False args = [uid, currentsplit] if uid else [currentsplit] item = dict(timeout=0.5 * (retry + 1), retry=retry, do_split=True, split=split, dest=dest, strsplit=strsplit, currentsplit=currentsplit, splits=splits, sendto=sendto) if handles is None: handles = [] handles.append( dict(address=COMMAND_SPLIT, uid=uid, args=(), callback=partial(self.call_split_callback, uid=uid, item=item), kwargs=item)) self.cmd_queue.append( dict(address=sendto, dest=dest, args=tuple(args), handles=handles)) self.process_cmd_queue() return True def send(self, address, *args, confirm_callback=None, confirm_params=(), do_split=False, timeout=-1, uid='', dest=None): if confirm_callback: _LOGGER.debug(f'Adding handle for COMMAND_CONFIRM tim={timeout}') handles = [ dict(address=COMMAND_CONFIRM, uid=uid, callback=partial(self.call_confirm_callback, uid=uid, confirm_params=confirm_params, confirm_callback=confirm_callback), kwargs=dict(timeout=timeout, do_split=do_split), args=()) ] else: handles = [] args = list(args) for i, s in enumerate(args): if isinstance(s, SerializableDBObj): args[i] = s.serialize() if do_split: strsplit = json.dumps(args[(1 if uid else 0):]) n1 = len(strsplit) n2 = n1 // OSCManager.PKT_SPLIT + (1 if n1 % OSCManager.PKT_SPLIT else 0) self.send_split(uid=uid, dest=dest, strsplit=strsplit, splits=n2, sendto=address, handles=handles) else: self.cmd_queue.append( dict(dest=dest, address=address, args=tuple(args), handles=handles)) self.process_cmd_queue() async def unhandle_by_timer(self, address, uid): if address in self.callbacks and uid in self.callbacks[address]: _LOGGER.debug(f'unhandling by timeout add={address}, uid={uid}') item = self.callbacks[address][uid] del self.callbacks[address][uid] try: if 'last_sent' in item: kwargs = dict(last_sent=item['last_sent']) else: kwargs = dict() item['f'](*item['a'], timeout=True, **kwargs) except Exception: _LOGGER.error(f'Handler error {traceback.format_exc()}') _LOGGER.debug(f'Handler exited add={address}, uid={uid}') # def some_callback(address: str, *osc_arguments: List[Any]) -> None: # def some_callback(address: str, fixed_argument: List[Any], *osc_arguments: List[Any]) -> None: def handle_device(self, address, uid, callback, *args, timeout=-1, do_split=False, **kwargs): self.unhandle_device(address, uid) d = self.callbacks[address] if address in self.callbacks else dict() if timeout > 0: t = Timer(timeout, partial(self.unhandle_by_timer, address, uid)) else: t = None if do_split: if address == COMMAND_SPLIT: kwargs = dict(**kwargs) else: kwargs = dict(split=0, strsplit='') else: kwargs = dict(split=False) d[uid] = dict(f=callback, a=args, t=t, **kwargs) self.callbacks[address] = d _LOGGER.debug( f'Handle Added add={address}, uid={uid} timeout={timeout} result={self.callbacks}' ) def unhandle_device(self, address, uid): if address in self.callbacks and uid in self.callbacks[address]: if self.callbacks[address][uid]['t']: self.callbacks[address][uid]['t'].cancel() del self.callbacks[address][uid] _LOGGER.debug( f'Handle removed add={address}, uid={uid} result={self.callbacks}' ) def handle(self, address, callback, *args, timeout=-1, do_split=False): self.handle_device(address, '', callback, *args, timeout=timeout, do_split=do_split) def unhandle(self, address): self.unhandle_device(address, '')
class DeviceNotiication(object): __state_formatter__ = StateFormatter(colmin=None, colmax=None, colerror=None, col=None, pre='', post='', timeout='---') def __init__(self, dm, idnot, builder): self.dm = dm self.title = self.dm.get_device().get_alias() self.idnot = idnot self.dm_formatter = dm.get_notification_formatter() self.timer = None self.current_formatter = None self.builder = builder self.last_notify_ms = time() * 1000 self.last_txt = '' def format(self, **kwargs): nowms = time() * 1000 notify_every_ms = self.builder.notify_every_ms if self.dm.is_connected_state(): f = self.dm_formatter timeout = 7 else: f = self.__state_formatter__ timeout = 45 if f is not self.current_formatter or\ f is self.__state_formatter__ or\ notify_every_ms == 0 or\ nowms - self.last_notify_ms >= notify_every_ms: self.current_formatter = f self.last_notify_ms = nowms txt = '' for types, obj in kwargs.items(): if types == f.type: txt = f.format(obj) if txt: break if txt: if self.timer: self.timer.cancel() self.timer = Timer(timeout, self.clear) self._notify(txt) def _notify(self, txt): if txt != self.last_txt: self.last_txt = txt b = self.builder b.set_service_notification( self.idnot, b.build_service_notification(self.title, txt, idnot=self.idnot)) b.set_summary_notification() def clear(self): if self.timer: self.timer.cancel() self.timer = None self._notify(self.current_formatter.set_timeout())
class AndroidAliveChecker(object): def __init__(self, loop, on_result, timeout=1.2): self.timeout = timeout self.loop = loop self.on_result = on_result self.timer = None self.was_started = False if platform == 'android': from jnius import autoclass from android.broadcast import BroadcastReceiver self.BroadcastReceiver = BroadcastReceiver self.context = autoclass( 'org.kivy.android.PythonActivity').mActivity self.Intent = autoclass('android.content.Intent') self.br = None def on_broadcast(self, context, intent): _LOGGER.info('Broadcast received') self.loop.call_soon_threadsafe(self.on_timeout, False) def on_pause(self): if self.br: self.was_started = True self.stop() else: self.was_started = False def on_resume(self): if self.was_started: self.was_started = False self.start() def on_timeout(self, timeout_detected=True): _LOGGER.info(f'Presence received = timeout={timeout_detected}') self.stop() self.on_result(not timeout_detected) def start(self, bytimer=False): _LOGGER.info( f'Starting {bytimer} timerNone={self.timer is None} brNone={self.br is None}' ) try: if not self.timer: if bytimer: self.timer = Timer(bytimer, self.start) elif not self.br: if platform == 'android': self.br = self.BroadcastReceiver( self.on_broadcast, actions=[PRESENCE_RESPONSE_ACTION]) self.br.start() _LOGGER.info( f'Sending intent {PRESENCE_REQUEST_ACTION}') self.context.sendBroadcast( self.Intent(PRESENCE_REQUEST_ACTION)) self.timer = Timer(self.timeout, self.on_timeout) else: self.on_result(True) except Exception: _LOGGER.error(f'Start error {traceback.format_exc()}') def stop(self): if self.timer: self.timer.cancel() self.timer = None if self.br: self.br.stop() self.br = None