2016-05-25 17:37:40 +02:00
|
|
|
# Serial port management for firmware communication
|
|
|
|
#
|
2019-06-26 20:30:02 +02:00
|
|
|
# Copyright (C) 2016-2019 Kevin O'Connor <kevin@koconnor.net>
|
2016-05-25 17:37:40 +02:00
|
|
|
#
|
|
|
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
2017-04-01 02:48:29 +02:00
|
|
|
import logging, threading
|
2016-05-25 17:37:40 +02:00
|
|
|
import serial
|
|
|
|
|
2016-12-10 01:04:30 +01:00
|
|
|
import msgproto, chelper, util
|
2016-05-25 17:37:40 +02:00
|
|
|
|
2017-03-09 03:24:27 +01:00
|
|
|
class error(Exception):
|
|
|
|
pass
|
|
|
|
|
2016-05-25 17:37:40 +02:00
|
|
|
class SerialReader:
|
2016-07-11 17:41:49 +02:00
|
|
|
BITS_PER_BYTE = 10.
|
2016-05-25 17:37:40 +02:00
|
|
|
def __init__(self, reactor, serialport, baud):
|
|
|
|
self.reactor = reactor
|
|
|
|
self.serialport = serialport
|
|
|
|
self.baud = baud
|
|
|
|
# Serial port
|
|
|
|
self.ser = None
|
|
|
|
self.msgparser = msgproto.MessageParser()
|
|
|
|
# C interface
|
|
|
|
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]')
|
|
|
|
# Threading
|
|
|
|
self.lock = threading.Lock()
|
|
|
|
self.background_thread = None
|
|
|
|
# Message handlers
|
2019-06-22 00:44:55 +02:00
|
|
|
self.handlers = {}
|
|
|
|
self.register_response(self._handle_unknown_init, '#unknown')
|
|
|
|
self.register_response(self.handle_output, '#output')
|
2016-05-25 17:37:40 +02:00
|
|
|
def _bg_thread(self):
|
|
|
|
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
|
|
|
|
params = self.msgparser.parse(response.msg[0:count])
|
|
|
|
params['#sent_time'] = response.sent_time
|
|
|
|
params['#receive_time'] = response.receive_time
|
2017-09-15 19:58:05 +02:00
|
|
|
hdl = (params['#name'], params.get('oid'))
|
2016-05-25 17:37:40 +02:00
|
|
|
try:
|
2019-06-10 01:12:24 +02:00
|
|
|
with self.lock:
|
|
|
|
hdl = self.handlers.get(hdl, self.handle_default)
|
|
|
|
hdl(params)
|
2016-05-25 17:37:40 +02:00
|
|
|
except:
|
|
|
|
logging.exception("Exception in serial callback")
|
2019-06-22 00:44:55 +02:00
|
|
|
def _get_identify_data(self, timeout):
|
|
|
|
# Query the "data dictionary" from the micro-controller
|
|
|
|
identify_data = ""
|
|
|
|
while 1:
|
2019-06-22 01:15:12 +02:00
|
|
|
msg = "identify offset=%d count=%d" % (len(identify_data), 40)
|
|
|
|
params = self.send_with_response(msg, 'identify_response')
|
2019-06-22 00:44:55 +02:00
|
|
|
if params['offset'] == len(identify_data):
|
|
|
|
msgdata = params['data']
|
|
|
|
if not msgdata:
|
|
|
|
# Done
|
|
|
|
return identify_data
|
|
|
|
identify_data += msgdata
|
|
|
|
if self.reactor.monotonic() > timeout:
|
|
|
|
raise error("Timeout during identify")
|
2016-05-25 17:37:40 +02:00
|
|
|
def connect(self):
|
2016-11-27 23:45:58 +01:00
|
|
|
# Initial connection
|
2016-05-25 17:37:40 +02:00
|
|
|
logging.info("Starting serial connect")
|
2019-06-22 00:54:56 +02:00
|
|
|
start_time = self.reactor.monotonic()
|
2016-11-29 23:44:37 +01:00
|
|
|
while 1:
|
2019-06-22 00:54:56 +02:00
|
|
|
connect_time = self.reactor.monotonic()
|
|
|
|
if connect_time > start_time + 150.:
|
|
|
|
raise error("Unable to connect")
|
2016-11-29 23:44:37 +01:00
|
|
|
try:
|
2017-05-08 16:29:59 +02:00
|
|
|
if self.baud:
|
|
|
|
self.ser = serial.Serial(
|
2019-02-10 17:51:29 +01:00
|
|
|
self.serialport, self.baud, timeout=0, exclusive=True)
|
2017-05-08 16:29:59 +02:00
|
|
|
else:
|
|
|
|
self.ser = open(self.serialport, 'rb+')
|
2017-08-14 22:18:54 +02:00
|
|
|
except (OSError, IOError, serial.SerialException) as e:
|
2017-09-27 17:43:14 +02:00
|
|
|
logging.warn("Unable to open port: %s", e)
|
2019-06-22 00:54:56 +02:00
|
|
|
self.reactor.pause(connect_time + 5.)
|
2016-11-29 23:44:37 +01:00
|
|
|
continue
|
2017-05-08 16:29:59 +02:00
|
|
|
if self.baud:
|
|
|
|
stk500v2_leave(self.ser, self.reactor)
|
2016-11-29 23:44:37 +01:00
|
|
|
self.serialqueue = self.ffi_lib.serialqueue_alloc(
|
|
|
|
self.ser.fileno(), 0)
|
|
|
|
self.background_thread = threading.Thread(target=self._bg_thread)
|
|
|
|
self.background_thread.start()
|
|
|
|
# Obtain and load the data dictionary from the firmware
|
2019-06-22 00:44:55 +02:00
|
|
|
try:
|
2019-06-22 00:54:56 +02:00
|
|
|
identify_data = self._get_identify_data(connect_time + 5.)
|
2019-06-22 00:44:55 +02:00
|
|
|
except error as e:
|
|
|
|
logging.exception("Timeout on serial connect")
|
2016-11-29 23:44:37 +01:00
|
|
|
self.disconnect()
|
|
|
|
continue
|
|
|
|
break
|
2016-11-27 23:45:58 +01:00
|
|
|
msgparser = msgproto.MessageParser()
|
|
|
|
msgparser.process_identify(identify_data)
|
|
|
|
self.msgparser = msgparser
|
2019-06-20 23:36:46 +02:00
|
|
|
self.register_response(self.handle_unknown, '#unknown')
|
2017-03-04 04:02:27 +01:00
|
|
|
# Setup baud adjust
|
2018-01-29 16:10:27 +01:00
|
|
|
mcu_baud = msgparser.get_constant_float('SERIAL_BAUD', None)
|
|
|
|
if mcu_baud is not None:
|
2016-11-27 23:45:58 +01:00
|
|
|
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
|
|
|
self.ffi_lib.serialqueue_set_baud_adjust(
|
|
|
|
self.serialqueue, baud_adjust)
|
2018-05-28 15:42:59 +02:00
|
|
|
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)
|
2016-05-25 17:37:40 +02:00
|
|
|
def connect_file(self, debugoutput, dictionary, pace=False):
|
|
|
|
self.ser = debugoutput
|
|
|
|
self.msgparser.process_identify(dictionary, decompress=False)
|
2016-07-11 17:41:49 +02:00
|
|
|
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
|
2017-09-19 03:41:00 +02:00
|
|
|
def set_clock_est(self, freq, last_time, last_clock):
|
2016-05-25 17:37:40 +02:00
|
|
|
self.ffi_lib.serialqueue_set_clock_est(
|
2017-09-19 03:41:00 +02:00
|
|
|
self.serialqueue, freq, last_time, last_clock)
|
2016-05-25 17:37:40 +02:00
|
|
|
def disconnect(self):
|
2017-03-09 04:01:52 +01:00
|
|
|
if self.serialqueue is not None:
|
|
|
|
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
|
|
|
if self.background_thread is not None:
|
|
|
|
self.background_thread.join()
|
|
|
|
self.ffi_lib.serialqueue_free(self.serialqueue)
|
|
|
|
self.background_thread = self.serialqueue = None
|
|
|
|
if self.ser is not None:
|
|
|
|
self.ser.close()
|
|
|
|
self.ser = None
|
2016-05-25 17:37:40 +02:00
|
|
|
def stats(self, eventtime):
|
|
|
|
if self.serialqueue is None:
|
|
|
|
return ""
|
2017-09-19 03:41:00 +02:00
|
|
|
self.ffi_lib.serialqueue_get_stats(
|
2016-05-25 17:37:40 +02:00
|
|
|
self.serialqueue, self.stats_buf, len(self.stats_buf))
|
2017-09-19 03:41:00 +02:00
|
|
|
return self.ffi_main.string(self.stats_buf)
|
2019-06-21 04:08:38 +02:00
|
|
|
def get_msgparser(self):
|
|
|
|
return self.msgparser
|
2019-06-22 01:35:34 +02:00
|
|
|
def get_default_command_queue(self):
|
|
|
|
return self.default_cmd_queue
|
2016-05-25 17:37:40 +02:00
|
|
|
# Serial response callbacks
|
2019-06-20 23:36:46 +02:00
|
|
|
def register_response(self, callback, name, oid=None):
|
2016-05-25 17:37:40 +02:00
|
|
|
with self.lock:
|
2019-06-26 16:31:15 +02:00
|
|
|
if callback is None:
|
|
|
|
del self.handlers[name, oid]
|
|
|
|
else:
|
|
|
|
self.handlers[name, oid] = callback
|
2016-05-25 17:37:40 +02:00
|
|
|
# Command sending
|
2018-02-27 20:16:16 +01:00
|
|
|
def raw_send(self, cmd, minclock, reqclock, cmd_queue):
|
|
|
|
self.ffi_lib.serialqueue_send(
|
|
|
|
self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock)
|
|
|
|
def send(self, msg, minclock=0, reqclock=0):
|
|
|
|
cmd = self.msgparser.create_command(msg)
|
|
|
|
self.raw_send(cmd, minclock, reqclock, self.default_cmd_queue)
|
2019-06-22 01:15:12 +02:00
|
|
|
def send_with_response(self, msg, response):
|
|
|
|
cmd = self.msgparser.create_command(msg)
|
2019-06-26 20:30:02 +02:00
|
|
|
src = SerialRetryCommand(self, response)
|
|
|
|
return src.get_response([cmd], self.default_cmd_queue)
|
2016-05-25 17:37:40 +02:00
|
|
|
def alloc_command_queue(self):
|
2016-11-30 07:58:45 +01:00
|
|
|
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
|
|
|
self.ffi_lib.serialqueue_free_commandqueue)
|
2016-05-25 17:37:40 +02:00
|
|
|
# Dumping debug lists
|
|
|
|
def dump_debug(self):
|
2017-09-27 17:54:10 +02:00
|
|
|
out = []
|
|
|
|
out.append("Dumping serial stats: %s" % (
|
|
|
|
self.stats(self.reactor.monotonic()),))
|
2016-05-25 17:37:40 +02:00
|
|
|
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))
|
2017-09-27 17:54:10 +02:00
|
|
|
out.append("Dumping send queue %d messages" % (scount,))
|
2016-05-25 17:37:40 +02:00
|
|
|
for i in range(scount):
|
|
|
|
msg = sdata[i]
|
|
|
|
cmds = self.msgparser.dump(msg.msg[0:msg.len])
|
2017-09-27 17:54:10 +02:00
|
|
|
out.append("Sent %d %f %f %d: %s" % (
|
2016-05-25 17:37:40 +02:00
|
|
|
i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds)))
|
2017-09-27 17:54:10 +02:00
|
|
|
out.append("Dumping receive queue %d messages" % (rcount,))
|
2016-05-25 17:37:40 +02:00
|
|
|
for i in range(rcount):
|
|
|
|
msg = rdata[i]
|
|
|
|
cmds = self.msgparser.dump(msg.msg[0:msg.len])
|
2017-09-27 17:54:10 +02:00
|
|
|
out.append("Receive: %d %f %f %d: %s" % (
|
2016-05-25 17:37:40 +02:00
|
|
|
i, msg.receive_time, msg.sent_time, msg.len, ', '.join(cmds)))
|
2017-09-27 17:54:10 +02:00
|
|
|
return '\n'.join(out)
|
2016-05-25 17:37:40 +02:00
|
|
|
# Default message handlers
|
2019-06-22 00:44:55 +02:00
|
|
|
def _handle_unknown_init(self, params):
|
|
|
|
logging.debug("Unknown message %d (len %d) while identifying",
|
|
|
|
params['#msgid'], len(params['#msg']))
|
2016-05-25 17:37:40 +02:00
|
|
|
def handle_unknown(self, params):
|
2017-09-27 17:43:14 +02:00
|
|
|
logging.warn("Unknown message type %d: %s",
|
|
|
|
params['#msgid'], repr(params['#msg']))
|
2016-05-25 17:37:40 +02:00
|
|
|
def handle_output(self, params):
|
2017-09-27 17:43:14 +02:00
|
|
|
logging.info("%s: %s", params['#name'], params['#msg'])
|
2016-05-25 17:37:40 +02:00
|
|
|
def handle_default(self, params):
|
2017-09-27 17:43:14 +02:00
|
|
|
logging.warn("got %s", params)
|
2016-11-30 07:58:45 +01:00
|
|
|
def __del__(self):
|
|
|
|
self.disconnect()
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
# Class to retry sending of a query command until a given response is received
|
|
|
|
class SerialRetryCommand:
|
2017-03-09 03:24:27 +01:00
|
|
|
TIMEOUT_TIME = 5.0
|
2016-05-25 17:37:40 +02:00
|
|
|
RETRY_TIME = 0.500
|
2019-06-26 20:30:02 +02:00
|
|
|
def __init__(self, serial, name, oid=None):
|
2016-05-25 17:37:40 +02:00
|
|
|
self.serial = serial
|
|
|
|
self.name = name
|
2017-03-05 21:00:15 +01:00
|
|
|
self.oid = oid
|
2019-06-26 20:30:02 +02:00
|
|
|
self.completion = serial.reactor.completion()
|
|
|
|
self.min_query_time = serial.reactor.monotonic()
|
|
|
|
self.serial.register_response(self.handle_callback, name, oid)
|
2016-05-25 17:37:40 +02:00
|
|
|
def handle_callback(self, params):
|
2019-06-26 20:30:02 +02:00
|
|
|
if params['#sent_time'] >= self.min_query_time:
|
|
|
|
self.min_query_time = self.serial.reactor.NEVER
|
|
|
|
self.serial.reactor.async_complete(self.completion, params)
|
|
|
|
def get_response(self, cmds, cmd_queue, minclock=0, minsystime=0.):
|
|
|
|
first_query_time = query_time = max(self.min_query_time, minsystime)
|
|
|
|
while 1:
|
|
|
|
for cmd in cmds:
|
|
|
|
self.serial.raw_send(cmd, minclock, minclock, cmd_queue)
|
|
|
|
params = self.completion.wait(query_time + self.RETRY_TIME)
|
|
|
|
if params is not None:
|
|
|
|
self.serial.register_response(None, self.name, self.oid)
|
|
|
|
return params
|
|
|
|
query_time = self.serial.reactor.monotonic()
|
|
|
|
if query_time > first_query_time + self.TIMEOUT_TIME:
|
|
|
|
self.serial.register_response(None, self.name, self.oid)
|
|
|
|
raise error("Timeout on wait for '%s' response" % (self.name,))
|
2016-05-25 17:37:40 +02:00
|
|
|
|
|
|
|
# Attempt to place an AVR stk500v2 style programmer into normal mode
|
2016-11-28 19:14:56 +01:00
|
|
|
def stk500v2_leave(ser, reactor):
|
2016-05-25 17:37:40 +02:00
|
|
|
logging.debug("Starting stk500v2 leave programmer sequence")
|
2016-12-10 01:04:30 +01:00
|
|
|
util.clear_hupcl(ser.fileno())
|
2016-05-25 17:37:40 +02:00
|
|
|
origbaud = ser.baudrate
|
|
|
|
# Request a dummy speed first as this seems to help reset the port
|
2016-06-11 22:35:48 +02:00
|
|
|
ser.baudrate = 2400
|
2016-05-25 17:37:40 +02:00
|
|
|
ser.read(1)
|
|
|
|
# Send stk500v2 leave programmer sequence
|
|
|
|
ser.baudrate = 115200
|
2017-02-06 19:31:34 +01:00
|
|
|
reactor.pause(reactor.monotonic() + 0.100)
|
2016-05-25 17:37:40 +02:00
|
|
|
ser.read(4096)
|
|
|
|
ser.write('\x1b\x01\x00\x01\x0e\x11\x04')
|
2017-02-06 19:31:34 +01:00
|
|
|
reactor.pause(reactor.monotonic() + 0.050)
|
2016-05-25 17:37:40 +02:00
|
|
|
res = ser.read(4096)
|
2017-09-27 17:43:14 +02:00
|
|
|
logging.debug("Got %s from stk500v2", repr(res))
|
2016-05-25 17:37:40 +02:00
|
|
|
ser.baudrate = origbaud
|
2017-03-09 04:26:10 +01:00
|
|
|
|
|
|
|
# Attempt an arduino style reset on a serial port
|
|
|
|
def arduino_reset(serialport, reactor):
|
2017-10-13 03:21:49 +02:00
|
|
|
# First try opening the port at a different baud
|
2019-02-10 17:51:29 +01:00
|
|
|
ser = serial.Serial(serialport, 2400, timeout=0, exclusive=True)
|
2017-03-09 04:26:10 +01:00
|
|
|
ser.read(1)
|
2017-04-01 02:48:29 +02:00
|
|
|
reactor.pause(reactor.monotonic() + 0.100)
|
2017-10-13 03:21:49 +02:00
|
|
|
# Then toggle DTR
|
2017-03-09 04:26:10 +01:00
|
|
|
ser.dtr = True
|
2017-04-01 02:48:29 +02:00
|
|
|
reactor.pause(reactor.monotonic() + 0.100)
|
2017-03-09 04:26:10 +01:00
|
|
|
ser.dtr = False
|
2017-04-01 02:48:29 +02:00
|
|
|
reactor.pause(reactor.monotonic() + 0.100)
|
2017-03-09 04:26:10 +01:00
|
|
|
ser.close()
|