class HMTLServer(): address = ('localhost', 6000) # Default logging color LOGGING_COLOR = TimedLogger.RED def __init__(self, serial_device, address, device_scan=False, logger=True): self.ser = serial_device self.address = address self.logger = TimedLogger(self.ser.serial.start_time, textcolor=self.LOGGING_COLOR) if not logger: self.logger.disable() self.terminate = False # TODO: Is this needed at all? If so should the SerialBuffer handle synchronization? self.serial_cv = threading.Condition() self.conn = None self.listener = None if device_scan: self.scanner = DeviceScanner(self, True) self.scanner.start() else: self.scanner = None def get_connection(self): try: self.logger.log("Waiting for connection") self.listener = Listener(self.address, authkey='secret password') self.conn = self.listener.accept() self.logger.log("Connection accepted from %s:%d" % self.listener.last_accepted) except KeyboardInterrupt: print("Exiting") self.terminate = True def handle_msg(self, item): if item.data == SERVER_EXIT: self.logger.log("* Received exit signal *") self.conn.send(SERVER_ACK) self.close() elif item.data == SERVER_DATA_REQ: self.logger.log("* Recieved data request") item = self.get_data_msg() if item: self.conn.send(item.data) else: self.conn.send(None) else: # Forward the message to the device self.serial_cv.acquire() self.send_data(item.data) self.serial_cv.release() # Reply with acknowledgement self.conn.send(SERVER_ACK) def send_data(self, data): self.serial_cv.acquire() self.ser.send_and_confirm(data, False) self.serial_cv.release() # Wait for and handle incoming connections def listen(self): self.logger.log("Server started") self.get_connection() while not self.terminate: try: data = self.conn.recv() item = InputItem.from_data(data) self.logger.log("Received: %s" % item) self.handle_msg(item) self.logger.log("Acked: %s" % item) except (EOFError, IOError): # Attempt to reconnect self.logger.log("Lost connection") self.listener.close() self.get_connection() except Exception as e: # Close the connection on uncaught exception self.logger.log("Exception during listen") self.close() raise e def close(self): self.listener.close() if self.conn: self.conn.close() self.terminate = True def get_data_msg(self, timeout=0.25): """Listen on the serial device for a properly formatted data message""" self.serial_cv.acquire() self.logger.log("Starting data request") time_limit = time.time() + timeout item = None while True: # Try to get a message with low timeout item = self.ser.get_message(timeout=0.1) if item and item.is_hmtl: self.logger.log("Received response: %s:\n%s" % (item, HMTLprotocol.decode_data(item.data))) break # Check for timeout if time.time() > time_limit: self.logger.log("Data request time limit exceeded") item = None break self.serial_cv.release() return item
class HMTLClient(): address = HMTLprotocol.BROADCAST verbose = False def __init__(self, address='localhost', port=6000, hmtladdress=None, verbose=False, logger=True, authenticate=True): self.logger = TimedLogger() if not logger: self.logger.disable() self.address = hmtladdress self.verbose = verbose address = (address, port) try: if authenticate: authkey = b'secret password' else: authkey = None self.conn = Client(address, authkey=authkey) except Exception as e: raise Exception("Failed to connect to '%s'" % (str(address))) random.seed() print("HMTLClient initialized") def close(self): self.conn.close() def send(self, msg): if (self.verbose): self.logger.log(" - Sending %s" % (hexlify(msg))) self.conn.send(msg) def get_ack(self): msg = self.conn.recv() if (self.verbose): self.logger.log(" - Received: '%s' '%s'" % (msg, hexlify(msg))) if (msg == server.SERVER_ACK): return True else: return False def send_and_ack(self, msg, expect_response=False): self.send(msg) # Wait for message acknowledgement if self.verbose: self.logger.log(" - Waiting on ack") # Wait for an ack from the server has_ack = False while True: if self.get_ack(): has_ack = True break if has_ack and expect_response: # If a response is expected then request the response data from the # server, repeating until the message indicates that there is no # more data. messages = [] headers = [] more_data = True while more_data: more_data = False # Request response data msg = self.get_response_data() messages.append(msg) if self.verbose: if msg: self.logger.log(" - Received data response: '%s':\n%s" % (hexlify(msg), HMTLprotocol.decode_data(msg))) else: self.logger.log(" - Failed to receive data response") try: # Attempt to decode the message decoded_msg = HMTLprotocol.msg_to_headers(msg) if decoded_msg: headers.append(decoded_msg) # Check if the message indicate that there are # additional messages expected. hdr = decoded_msg[0] if isinstance(hdr, HMTLprotocol.MsgHdr): if hdr.more_data(): # Request another message more_data = True else: headers.append(None) except Exception as e: print("Exception decoding messages: %s" % (str(e))) headers.append(None) return messages, headers return [None, None] def get_response_data(self): '''Request and attempt to retrieve response data''' self.conn.send(server.SERVER_DATA_REQ) #TODO: This should pickle some object with parameters like timeout msg = self.conn.recv() return msg def send_exit(self): self.send_and_ack(server.SERVER_EXIT)