import socket import threading import logging import queue import contextlib import time DEFAULT_TIMEOUT = 0.5 class TimeoutException(Exception): pass class VortexConnection(object): def __init__(self, ip, port): self.logger = logging.getLogger(self.__class__.__name__) self.handlers = [self.on_message] self.queues = [] self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((ip, port)) self.fd = self.socket.makefile("rw") def loop_start(self): self.th = threading.Thread(target=self.loop) self.th.daemon = True self.th.start() def loop(self): print("Looping...") for line in self.readlines(): for h in self.handlers: h(line.strip()) def send(self, cmd): self.logger.debug(">> %r", cmd) self.fd.write(cmd + "\r") self.fd.flush() def readlines(self): buf = b"" data = True while data: try: data = self.socket.recv(512) except KeyboardInterrupt: raise except: time.sleep(0.1) self.logger.warning("Recv failed") continue if not data: raise Exception("Connection closed") buf += data while buf.find(b"\r") != -1: line, buf = buf.split(b"\r", 1) yield line def on_message(self, msg): self.logger.debug("<< %r", msg) for q in self.queues: q.put_nowait(msg) @contextlib.contextmanager def client(self): q = queue.Queue() self.queues.append(q) self.logger.debug("Adding queue...") try: yield q finally: self.logger.debug("Removing queue...") self.queues.remove(q) def call(self, cmd, timeout=DEFAULT_TIMEOUT, wait=True): with self.client() as q: self.send(cmd) if not wait: return start = time.time() while time.time() - start < timeout: try: m = q.get(timeout=timeout - (time.time() - start)) except queue.Empty: self.logger.debug("Request timeout") return yield m def discover(self, timeout=DEFAULT_TIMEOUT): return { resp[:3].decode(): VortexDevice(self, resp[:3].decode()) for resp in self.call("***PING", timeout=timeout) if resp[3:].decode() == "PONG" } def __getitem__(self, key): return VortexDevice(self, key) class VortexDevice(object): def __init__(self, connection, device_id): self.conn = connection self.device_id = device_id self.logger = logging.getLogger("vortex.%s" % (self.device_id,)) def __repr__(self): return "".format(self.device_id) def mute(self): self.call_single("GMUTEO1") def unmute(self): self.call_single("GMUTEO0") def call_single(self, *args, **kwargs): return next(self.call(*args, **kwargs), None) def call(self, cmd, expect=None, *args, **kwargs): if expect is None: expect = cmd.replace("?", "") for resp in self.conn.call(self.device_id + cmd, *args, **kwargs): self.logger.debug("Received: %r (%r)", resp, self.device_id) if resp[: len(self.device_id)] != self.device_id.encode(): continue resp = resp[len(self.device_id) :] if expect is None or resp[: len(expect)] == expect.encode(): yield resp if __name__ == "__main__": import logging logging.basicConfig(level=logging.DEBUG) c = VortexConnection("127.0.0.1", 10001) c.loop_start() d = c.discover() print(d) d["F00"].call_single("PING", "PONG") # for n in range(1000): # c['T00'].call_single('SSTEXT0,1,XD {}'.format(n), expect=False, timeout=0) # time.sleep(0.1)