""" KIRA interface to receive UDP packets from a Keene Electronics IR-IP bridge. """ # pylint: disable=import-error import os import select import socket import sys import threading import time from . import utils from .constants import ( DEFAULT_HOST, DEFAULT_PORT ) class KiraReceiver(threading.Thread): """ Open a UDP socket to monitor for incoming IR-IP packets. """ def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT): """Construct a KIRA interface object.""" threading.Thread.__init__(self) self.stopped = threading.Event() self._callbacks = [] self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.host = host self.port = port self._socket.bind((host, port)) self.codeMap = {} self._state = 'idle' def registerCode(self, codeName, code, codeType=None): code = utils.mangleCode(code, codeType=codeType) if code: self.codeMap[code] = codeName def registerCallback(self, callback): self._callbacks.append(callback) def run(self): """Main loop of KIRA thread.""" while not self.stopped.isSet(): try: # if the current state is idle, just block and wait forever # if the current state is any other state, then a timeout of 200ms should # be reasonable in all cases. timeout = (self._state != 'idle') and 0.2 or None rdlist, _, _ = select.select([self._socket.fileno()], [], [], timeout) if not rdlist: if self._state != 'idle': self._state = 'idle' continue data = self._socket.recv(1024) if not data: # check if the socket is still valid try: os.fstat(recv._socket.fileno()) except socket.error: break continue code = utils.mangleIR(data, ignore_errors=True) codeName = self.codeMap.get(code) # some manufacturers repeat their IR codes several times in rapid # succession. by tracking the last code, we can eliminate redundant # state changes if codeName and (self._state != codeName): self._state = codeName for callback in self._callbacks: callback(codeName) except: time.sleep(0.1) def stop(self): self.stopped.set() # force receiver thread to wake from select sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect((self.host, self.port)) msg = "stop" datagram = sys.version_info[0] == 2 and bytes(msg) or bytes(msg, "utf-8") sock.send(datagram) sock.close() self.join() self._socket.close()