diff options
Diffstat (limited to 'klippy/serialhdl.py')
-rw-r--r-- | klippy/serialhdl.py | 251 |
1 files changed, 150 insertions, 101 deletions
diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index fc884638..97169373 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -8,9 +8,11 @@ import serial import msgproto, chelper, util + class error(Exception): pass + class SerialReader: def __init__(self, reactor, warn_prefix=""): self.reactor = reactor @@ -22,71 +24,77 @@ class SerialReader: self.ffi_main, self.ffi_lib = chelper.get_ffi() self.serialqueue = None self.default_cmd_queue = self.alloc_command_queue() - self.stats_buf = self.ffi_main.new('char[4096]') + self.stats_buf = self.ffi_main.new("char[4096]") # Threading self.lock = threading.Lock() self.background_thread = None # Message handlers self.handlers = {} - self.register_response(self._handle_unknown_init, '#unknown') - self.register_response(self.handle_output, '#output') + self.register_response(self._handle_unknown_init, "#unknown") + self.register_response(self.handle_output, "#output") # Sent message notification tracking self.last_notify_id = 0 self.pending_notifications = {} + def _bg_thread(self): - response = self.ffi_main.new('struct pull_queue_message *') + response = self.ffi_main.new("struct pull_queue_message *") while 1: self.ffi_lib.serialqueue_pull(self.serialqueue, response) count = response.len if count < 0: break if response.notify_id: - params = {'#sent_time': response.sent_time, - '#receive_time': response.receive_time} + params = { + "#sent_time": response.sent_time, + "#receive_time": response.receive_time, + } completion = self.pending_notifications.pop(response.notify_id) self.reactor.async_complete(completion, params) continue params = self.msgparser.parse(response.msg[0:count]) - params['#sent_time'] = response.sent_time - params['#receive_time'] = response.receive_time - hdl = (params['#name'], params.get('oid')) + params["#sent_time"] = response.sent_time + params["#receive_time"] = response.receive_time + hdl = (params["#name"], params.get("oid")) try: with self.lock: hdl = self.handlers.get(hdl, self.handle_default) hdl(params) except: - logging.exception("%sException in serial callback", - self.warn_prefix) + logging.exception("%sException in serial callback", self.warn_prefix) + def _error(self, msg, *params): raise error(self.warn_prefix + (msg % params)) + def _get_identify_data(self, eventtime): # Query the "data dictionary" from the micro-controller identify_data = b"" while 1: msg = "identify offset=%d count=%d" % (len(identify_data), 40) try: - params = self.send_with_response(msg, 'identify_response') + params = self.send_with_response(msg, "identify_response") except error as e: - logging.exception("%sWait for identify_response", - self.warn_prefix) + logging.exception("%sWait for identify_response", self.warn_prefix) return None - if params['offset'] == len(identify_data): - msgdata = params['data'] + if params["offset"] == len(identify_data): + msgdata = params["data"] if not msgdata: # Done return identify_data identify_data += msgdata - def _start_session(self, serial_dev, serial_fd_type=b'u', client_id=0): + + def _start_session(self, serial_dev, serial_fd_type=b"u", client_id=0): self.serial_dev = serial_dev self.serialqueue = self.ffi_main.gc( - self.ffi_lib.serialqueue_alloc(serial_dev.fileno(), - serial_fd_type, client_id), - self.ffi_lib.serialqueue_free) + self.ffi_lib.serialqueue_alloc( + serial_dev.fileno(), serial_fd_type, client_id + ), + self.ffi_lib.serialqueue_free, + ) self.background_thread = threading.Thread(target=self._bg_thread) self.background_thread.start() # Obtain and load the data dictionary from the firmware completion = self.reactor.register_callback(self._get_identify_data) - identify_data = completion.wait(self.reactor.monotonic() + 5.) + identify_data = completion.wait(self.reactor.monotonic() + 5.0) if identify_data is None: logging.info("%sTimeout on connect", self.warn_prefix) self.disconnect() @@ -94,117 +102,121 @@ class SerialReader: msgparser = msgproto.MessageParser(warn_prefix=self.warn_prefix) msgparser.process_identify(identify_data) self.msgparser = msgparser - self.register_response(self.handle_unknown, '#unknown') + self.register_response(self.handle_unknown, "#unknown") # Setup baud adjust - if serial_fd_type == b'c': - wire_freq = msgparser.get_constant_float('CANBUS_FREQUENCY', None) + if serial_fd_type == b"c": + wire_freq = msgparser.get_constant_float("CANBUS_FREQUENCY", None) else: - wire_freq = msgparser.get_constant_float('SERIAL_BAUD', None) + wire_freq = msgparser.get_constant_float("SERIAL_BAUD", None) if wire_freq is not None: - self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue, - wire_freq) - receive_window = msgparser.get_constant_int('RECEIVE_WINDOW', None) + self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue, wire_freq) + receive_window = msgparser.get_constant_int("RECEIVE_WINDOW", None) if receive_window is not None: self.ffi_lib.serialqueue_set_receive_window( - self.serialqueue, receive_window) + self.serialqueue, receive_window + ) return True + def connect_canbus(self, canbus_uuid, canbus_nodeid, canbus_iface="can0"): - import can # XXX + import can # XXX + txid = canbus_nodeid * 2 + 256 - filters = [{"can_id": txid+1, "can_mask": 0x7ff, "extended": False}] + filters = [{"can_id": txid + 1, "can_mask": 0x7FF, "extended": False}] # Prep for SET_NODEID command try: uuid = int(canbus_uuid, 16) except ValueError: uuid = -1 - if uuid < 0 or uuid > 0xffffffffffff: + if uuid < 0 or uuid > 0xFFFFFFFFFFFF: self._error("Invalid CAN uuid") - uuid = [(uuid >> (40 - i*8)) & 0xff for i in range(6)] - CANBUS_ID_ADMIN = 0x3f0 + uuid = [(uuid >> (40 - i * 8)) & 0xFF for i in range(6)] + CANBUS_ID_ADMIN = 0x3F0 CMD_SET_NODEID = 0x01 set_id_cmd = [CMD_SET_NODEID] + uuid + [canbus_nodeid] - set_id_msg = can.Message(arbitration_id=CANBUS_ID_ADMIN, - data=set_id_cmd, is_extended_id=False) + set_id_msg = can.Message( + arbitration_id=CANBUS_ID_ADMIN, data=set_id_cmd, is_extended_id=False + ) # Start connection attempt logging.info("%sStarting CAN connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: - if self.reactor.monotonic() > start_time + 90.: + if self.reactor.monotonic() > start_time + 90.0: self._error("Unable to connect") try: - bus = can.interface.Bus(channel=canbus_iface, - can_filters=filters, - bustype='socketcan') + bus = can.interface.Bus( + channel=canbus_iface, can_filters=filters, bustype="socketcan" + ) bus.send(set_id_msg) except (can.CanError, os.error, IOError) as e: - logging.warning("%sUnable to open CAN port: %s", - self.warn_prefix, e) - self.reactor.pause(self.reactor.monotonic() + 5.) + logging.warning("%sUnable to open CAN port: %s", self.warn_prefix, e) + self.reactor.pause(self.reactor.monotonic() + 5.0) continue - bus.close = bus.shutdown # XXX - ret = self._start_session(bus, b'c', txid) + bus.close = bus.shutdown # XXX + ret = self._start_session(bus, b"c", txid) if not ret: continue # Verify correct canbus_nodeid to canbus_uuid mapping try: - params = self.send_with_response('get_canbus_id', 'canbus_id') - got_uuid = bytearray(params['canbus_uuid']) + params = self.send_with_response("get_canbus_id", "canbus_id") + got_uuid = bytearray(params["canbus_uuid"]) if got_uuid == bytearray(uuid): break except: - logging.exception("%sError in canbus_uuid check", - self.warn_prefix) - logging.info("%sFailed to match canbus_uuid - retrying..", - self.warn_prefix) + logging.exception("%sError in canbus_uuid check", self.warn_prefix) + logging.info("%sFailed to match canbus_uuid - retrying..", self.warn_prefix) self.disconnect() + def connect_pipe(self, filename): logging.info("%sStarting connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: - if self.reactor.monotonic() > start_time + 90.: + if self.reactor.monotonic() > start_time + 90.0: self._error("Unable to connect") try: fd = os.open(filename, os.O_RDWR | os.O_NOCTTY) except OSError as e: - logging.warning("%sUnable to open port: %s", - self.warn_prefix, e) - self.reactor.pause(self.reactor.monotonic() + 5.) + logging.warning("%sUnable to open port: %s", self.warn_prefix, e) + self.reactor.pause(self.reactor.monotonic() + 5.0) continue - serial_dev = os.fdopen(fd, 'rb+', 0) + serial_dev = os.fdopen(fd, "rb+", 0) ret = self._start_session(serial_dev) if ret: break + def connect_uart(self, serialport, baud, rts=True): # Initial connection logging.info("%sStarting serial connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: - if self.reactor.monotonic() > start_time + 90.: + if self.reactor.monotonic() > start_time + 90.0: self._error("Unable to connect") try: - serial_dev = serial.Serial(baudrate=baud, timeout=0, - exclusive=True) + serial_dev = serial.Serial(baudrate=baud, timeout=0, exclusive=True) serial_dev.port = serialport serial_dev.rts = rts serial_dev.open() except (OSError, IOError, serial.SerialException) as e: - logging.warning("%sUnable to open serial port: %s", - self.warn_prefix, e) - self.reactor.pause(self.reactor.monotonic() + 5.) + logging.warning("%sUnable to open serial port: %s", self.warn_prefix, e) + self.reactor.pause(self.reactor.monotonic() + 5.0) continue stk500v2_leave(serial_dev, self.reactor) ret = self._start_session(serial_dev) if ret: break + def connect_file(self, debugoutput, dictionary, pace=False): self.serial_dev = debugoutput self.msgparser.process_identify(dictionary, decompress=False) self.serialqueue = self.ffi_main.gc( - self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), b'f', 0), - self.ffi_lib.serialqueue_free) + self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), b"f", 0), + self.ffi_lib.serialqueue_free, + ) + def set_clock_est(self, freq, conv_time, conv_clock, last_clock): self.ffi_lib.serialqueue_set_clock_est( - self.serialqueue, freq, conv_time, conv_clock, last_clock) + self.serialqueue, freq, conv_time, conv_clock, last_clock + ) + def disconnect(self): if self.serialqueue is not None: self.ffi_lib.serialqueue_exit(self.serialqueue) @@ -217,20 +229,27 @@ class SerialReader: for pn in self.pending_notifications.values(): pn.complete(None) self.pending_notifications.clear() + def stats(self, eventtime): if self.serialqueue is None: return "" - self.ffi_lib.serialqueue_get_stats(self.serialqueue, - self.stats_buf, len(self.stats_buf)) + self.ffi_lib.serialqueue_get_stats( + self.serialqueue, self.stats_buf, len(self.stats_buf) + ) return str(self.ffi_main.string(self.stats_buf).decode()) + def get_reactor(self): return self.reactor + def get_msgparser(self): return self.msgparser + def get_serialqueue(self): return self.serialqueue + def get_default_command_queue(self): return self.default_cmd_queue + # Serial response callbacks def register_response(self, callback, name, oid=None): with self.lock: @@ -238,68 +257,95 @@ class SerialReader: del self.handlers[name, oid] else: self.handlers[name, oid] = callback + # Command sending def raw_send(self, cmd, minclock, reqclock, cmd_queue): - self.ffi_lib.serialqueue_send(self.serialqueue, cmd_queue, - cmd, len(cmd), minclock, reqclock, 0) + self.ffi_lib.serialqueue_send( + self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock, 0 + ) + def raw_send_wait_ack(self, cmd, minclock, reqclock, cmd_queue): self.last_notify_id += 1 nid = self.last_notify_id completion = self.reactor.completion() self.pending_notifications[nid] = completion - self.ffi_lib.serialqueue_send(self.serialqueue, cmd_queue, - cmd, len(cmd), minclock, reqclock, nid) + self.ffi_lib.serialqueue_send( + self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock, nid + ) params = completion.wait() if params is None: self._error("Serial connection closed") return params + def send(self, msg, minclock=0, reqclock=0): cmd = self.msgparser.create_command(msg) self.raw_send(cmd, minclock, reqclock, self.default_cmd_queue) + def send_with_response(self, msg, response): cmd = self.msgparser.create_command(msg) src = SerialRetryCommand(self, response) return src.get_response([cmd], self.default_cmd_queue) + def alloc_command_queue(self): - return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(), - self.ffi_lib.serialqueue_free_commandqueue) + return self.ffi_main.gc( + self.ffi_lib.serialqueue_alloc_commandqueue(), + self.ffi_lib.serialqueue_free_commandqueue, + ) + # Dumping debug lists def dump_debug(self): out = [] - out.append("Dumping serial stats: %s" % ( - self.stats(self.reactor.monotonic()),)) - sdata = self.ffi_main.new('struct pull_queue_message[1024]') - rdata = self.ffi_main.new('struct pull_queue_message[1024]') - scount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 1, - sdata, len(sdata)) - rcount = self.ffi_lib.serialqueue_extract_old(self.serialqueue, 0, - rdata, len(rdata)) + out.append("Dumping serial stats: %s" % (self.stats(self.reactor.monotonic()),)) + sdata = self.ffi_main.new("struct pull_queue_message[1024]") + rdata = self.ffi_main.new("struct pull_queue_message[1024]") + scount = self.ffi_lib.serialqueue_extract_old( + self.serialqueue, 1, sdata, len(sdata) + ) + rcount = self.ffi_lib.serialqueue_extract_old( + self.serialqueue, 0, rdata, len(rdata) + ) out.append("Dumping send queue %d messages" % (scount,)) for i in range(scount): msg = sdata[i] - cmds = self.msgparser.dump(msg.msg[0:msg.len]) - out.append("Sent %d %f %f %d: %s" % ( - i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds))) + cmds = self.msgparser.dump(msg.msg[0 : msg.len]) + out.append( + "Sent %d %f %f %d: %s" + % (i, msg.receive_time, msg.sent_time, msg.len, ", ".join(cmds)) + ) out.append("Dumping receive queue %d messages" % (rcount,)) for i in range(rcount): msg = rdata[i] - cmds = self.msgparser.dump(msg.msg[0:msg.len]) - out.append("Receive: %d %f %f %d: %s" % ( - i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds))) - return '\n'.join(out) + cmds = self.msgparser.dump(msg.msg[0 : msg.len]) + out.append( + "Receive: %d %f %f %d: %s" + % (i, msg.receive_time, msg.sent_time, msg.len, ", ".join(cmds)) + ) + return "\n".join(out) + # Default message handlers def _handle_unknown_init(self, params): - logging.debug("%sUnknown message %d (len %d) while identifying", - self.warn_prefix, params['#msgid'], len(params['#msg'])) + logging.debug( + "%sUnknown message %d (len %d) while identifying", + self.warn_prefix, + params["#msgid"], + len(params["#msg"]), + ) + def handle_unknown(self, params): - logging.warning("%sUnknown message type %d: %s", - self.warn_prefix, params['#msgid'], repr(params['#msg'])) + logging.warning( + "%sUnknown message type %d: %s", + self.warn_prefix, + params["#msgid"], + repr(params["#msg"]), + ) + def handle_output(self, params): - logging.info("%s%s: %s", self.warn_prefix, - params['#name'], params['#msg']) + logging.info("%s%s: %s", self.warn_prefix, params["#name"], params["#msg"]) + def handle_default(self, params): logging.warning("%sgot %s", self.warn_prefix, params) + # Class to send a query command and return the received response class SerialRetryCommand: def __init__(self, serial, name, oid=None): @@ -308,19 +354,19 @@ class SerialRetryCommand: self.oid = oid self.last_params = None self.serial.register_response(self.handle_callback, name, oid) + def handle_callback(self, params): self.last_params = params - def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0, - retry=True): + + def get_response(self, cmds, cmd_queue, minclock=0, reqclock=0, retry=True): retries = 5 - retry_delay = .010 + retry_delay = 0.010 if not retry: retries = 0 while 1: for cmd in cmds[:-1]: self.serial.raw_send(cmd, minclock, reqclock, cmd_queue) - self.serial.raw_send_wait_ack(cmds[-1], minclock, reqclock, - cmd_queue) + self.serial.raw_send_wait_ack(cmds[-1], minclock, reqclock, cmd_queue) params = self.last_params if params is not None: self.serial.register_response(None, self.name, self.oid) @@ -331,7 +377,8 @@ class SerialRetryCommand: reactor = self.serial.reactor reactor.pause(reactor.monotonic() + retry_delay) retries -= 1 - retry_delay *= 2. + retry_delay *= 2.0 + # Attempt to place an AVR stk500v2 style programmer into normal mode def stk500v2_leave(ser, reactor): @@ -345,12 +392,13 @@ def stk500v2_leave(ser, reactor): ser.baudrate = 115200 reactor.pause(reactor.monotonic() + 0.100) ser.read(4096) - ser.write(b'\x1b\x01\x00\x01\x0e\x11\x04') + ser.write(b"\x1b\x01\x00\x01\x0e\x11\x04") reactor.pause(reactor.monotonic() + 0.050) res = ser.read(4096) logging.debug("Got %s from stk500v2", repr(res)) ser.baudrate = origbaud + def cheetah_reset(serialport, reactor): # Fysetc Cheetah v1.2 boards have a weird stateful circuitry for # configuring the bootloader. This sequence takes care of disabling it for @@ -377,6 +425,7 @@ def cheetah_reset(serialport, reactor): reactor.pause(reactor.monotonic() + 0.100) ser.close() + # Attempt an arduino style reset on a serial port def arduino_reset(serialport, reactor): # First try opening the port at a different baud |