17ec681f3Smrg#!/usr/bin/env python3 27ec681f3Smrg# 37ec681f3Smrg# Copyright © 2020 Google LLC 47ec681f3Smrg# 57ec681f3Smrg# Permission is hereby granted, free of charge, to any person obtaining a 67ec681f3Smrg# copy of this software and associated documentation files (the "Software"), 77ec681f3Smrg# to deal in the Software without restriction, including without limitation 87ec681f3Smrg# the rights to use, copy, modify, merge, publish, distribute, sublicense, 97ec681f3Smrg# and/or sell copies of the Software, and to permit persons to whom the 107ec681f3Smrg# Software is furnished to do so, subject to the following conditions: 117ec681f3Smrg# 127ec681f3Smrg# The above copyright notice and this permission notice (including the next 137ec681f3Smrg# paragraph) shall be included in all copies or substantial portions of the 147ec681f3Smrg# Software. 157ec681f3Smrg# 167ec681f3Smrg# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 177ec681f3Smrg# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 187ec681f3Smrg# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 197ec681f3Smrg# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 207ec681f3Smrg# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 217ec681f3Smrg# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 227ec681f3Smrg# IN THE SOFTWARE. 237ec681f3Smrg 247ec681f3Smrgimport argparse 257ec681f3Smrgfrom datetime import datetime, timezone 267ec681f3Smrgimport queue 277ec681f3Smrgimport serial 287ec681f3Smrgimport threading 297ec681f3Smrgimport time 307ec681f3Smrg 317ec681f3Smrg 327ec681f3Smrgclass SerialBuffer: 337ec681f3Smrg def __init__(self, dev, filename, prefix, timeout = None): 347ec681f3Smrg self.filename = filename 357ec681f3Smrg self.dev = dev 367ec681f3Smrg 377ec681f3Smrg if dev: 387ec681f3Smrg self.f = open(filename, "wb+") 397ec681f3Smrg self.serial = serial.Serial(dev, 115200, timeout=timeout if timeout else 10) 407ec681f3Smrg else: 417ec681f3Smrg self.f = open(filename, "rb") 427ec681f3Smrg 437ec681f3Smrg self.byte_queue = queue.Queue() 447ec681f3Smrg self.line_queue = queue.Queue() 457ec681f3Smrg self.prefix = prefix 467ec681f3Smrg self.timeout = timeout 477ec681f3Smrg self.sentinel = object() 487ec681f3Smrg 497ec681f3Smrg if self.dev: 507ec681f3Smrg self.read_thread = threading.Thread( 517ec681f3Smrg target=self.serial_read_thread_loop, daemon=True) 527ec681f3Smrg else: 537ec681f3Smrg self.read_thread = threading.Thread( 547ec681f3Smrg target=self.serial_file_read_thread_loop, daemon=True) 557ec681f3Smrg self.read_thread.start() 567ec681f3Smrg 577ec681f3Smrg self.lines_thread = threading.Thread( 587ec681f3Smrg target=self.serial_lines_thread_loop, daemon=True) 597ec681f3Smrg self.lines_thread.start() 607ec681f3Smrg 617ec681f3Smrg # Thread that just reads the bytes from the serial device to try to keep from 627ec681f3Smrg # buffer overflowing it. If nothing is received in 1 minute, it finalizes. 637ec681f3Smrg def serial_read_thread_loop(self): 647ec681f3Smrg greet = "Serial thread reading from %s\n" % self.dev 657ec681f3Smrg self.byte_queue.put(greet.encode()) 667ec681f3Smrg 677ec681f3Smrg while True: 687ec681f3Smrg try: 697ec681f3Smrg b = self.serial.read() 707ec681f3Smrg if len(b) > 0: 717ec681f3Smrg self.byte_queue.put(b) 727ec681f3Smrg elif self.timeout: 737ec681f3Smrg self.byte_queue.put(self.sentinel) 747ec681f3Smrg break 757ec681f3Smrg except Exception as err: 767ec681f3Smrg print(self.prefix + str(err)) 777ec681f3Smrg self.byte_queue.put(self.sentinel) 787ec681f3Smrg break 797ec681f3Smrg 807ec681f3Smrg # Thread that just reads the bytes from the file of serial output that some 817ec681f3Smrg # other process is appending to. 827ec681f3Smrg def serial_file_read_thread_loop(self): 837ec681f3Smrg greet = "Serial thread reading from %s\n" % self.filename 847ec681f3Smrg self.byte_queue.put(greet.encode()) 857ec681f3Smrg 867ec681f3Smrg while True: 877ec681f3Smrg line = self.f.readline() 887ec681f3Smrg if line: 897ec681f3Smrg self.byte_queue.put(line) 907ec681f3Smrg else: 917ec681f3Smrg time.sleep(0.1) 927ec681f3Smrg 937ec681f3Smrg # Thread that processes the stream of bytes to 1) log to stdout, 2) log to 947ec681f3Smrg # file, 3) add to the queue of lines to be read by program logic 957ec681f3Smrg 967ec681f3Smrg def serial_lines_thread_loop(self): 977ec681f3Smrg line = bytearray() 987ec681f3Smrg while True: 997ec681f3Smrg bytes = self.byte_queue.get(block=True) 1007ec681f3Smrg 1017ec681f3Smrg if bytes == self.sentinel: 1027ec681f3Smrg self.read_thread.join() 1037ec681f3Smrg self.line_queue.put(self.sentinel) 1047ec681f3Smrg break 1057ec681f3Smrg 1067ec681f3Smrg # Write our data to the output file if we're the ones reading from 1077ec681f3Smrg # the serial device 1087ec681f3Smrg if self.dev: 1097ec681f3Smrg self.f.write(bytes) 1107ec681f3Smrg self.f.flush() 1117ec681f3Smrg 1127ec681f3Smrg for b in bytes: 1137ec681f3Smrg line.append(b) 1147ec681f3Smrg if b == b'\n'[0]: 1157ec681f3Smrg line = line.decode(errors="replace") 1167ec681f3Smrg 1177ec681f3Smrg time = datetime.now().strftime('%y-%m-%d %H:%M:%S') 1187ec681f3Smrg print("{endc}{time} {prefix}{line}".format( 1197ec681f3Smrg time=time, prefix=self.prefix, line=line, endc='\033[0m'), flush=True, end='') 1207ec681f3Smrg 1217ec681f3Smrg self.line_queue.put(line) 1227ec681f3Smrg line = bytearray() 1237ec681f3Smrg 1247ec681f3Smrg def get_line(self): 1257ec681f3Smrg line = self.line_queue.get() 1267ec681f3Smrg if line == self.sentinel: 1277ec681f3Smrg self.lines_thread.join() 1287ec681f3Smrg return line 1297ec681f3Smrg 1307ec681f3Smrg def lines(self): 1317ec681f3Smrg return iter(self.get_line, self.sentinel) 1327ec681f3Smrg 1337ec681f3Smrg 1347ec681f3Smrgdef main(): 1357ec681f3Smrg parser = argparse.ArgumentParser() 1367ec681f3Smrg 1377ec681f3Smrg parser.add_argument('--dev', type=str, help='Serial device') 1387ec681f3Smrg parser.add_argument('--file', type=str, 1397ec681f3Smrg help='Filename for serial output', required=True) 1407ec681f3Smrg parser.add_argument('--prefix', type=str, 1417ec681f3Smrg help='Prefix for logging serial to stdout', nargs='?') 1427ec681f3Smrg 1437ec681f3Smrg args = parser.parse_args() 1447ec681f3Smrg 1457ec681f3Smrg ser = SerialBuffer(args.dev, args.file, args.prefix or "") 1467ec681f3Smrg for line in ser.lines(): 1477ec681f3Smrg # We're just using this as a logger, so eat the produced lines and drop 1487ec681f3Smrg # them 1497ec681f3Smrg pass 1507ec681f3Smrg 1517ec681f3Smrg 1527ec681f3Smrgif __name__ == '__main__': 1537ec681f3Smrg main() 154