import argparse import logging import os import serial kCrc16Polynomial = 0x1021 kCrc16Preset = 0xFFFF kDefaultSerialPort = None kDefaultBaudrate = 115200 kDefaultChunkSize = 0x20 logger = logging.getLogger(__name__) def crc16(data): crc = kCrc16Preset for c in data: crc ^= c << 8 for j in range(8): mix = crc & 0x8000 crc <<= 1 if mix: crc ^= kCrc16Polynomial crc &= 0xFFFF return crc def getserial(port=None, baudrate=None, **kwargs): if port is not None: return serial.Serial(port, baudrate) # return first /dev/ttyACMx found for i in range(10): try: return serial.Serial(f"/dev/ttyACM{i}", baudrate) except serial.SerialException: pass raise RuntimeError("No serial device available.") def ftpserve(file, device, chunksize=0x20): with open(file, "rb") as f: data = f.read() device.reset_input_buffer() logger.info(f"Serving file {file}") for i in range(0, len(data), chunksize): device.write(data[i : i + chunksize]) r = device.read() if r != b"\x42": raise RuntimeError(f"wrong response {r}") logger.info(f"ack for chunk @{i}") logger.info(f"Done serving file {file}") crc = crc16(data) logger.info(f"File length: {len(data):04x}, crc16: {crc:04x}") def parse_args(): parser = argparse.ArgumentParser(description="Serve a file to ftpget.") parser.add_argument("file", help="file to serve") parser.add_argument("--port", help="path to the serial port") parser.add_argument("--baudrate", default=kDefaultBaudrate) parser.add_argument("--chunksize", default=kDefaultChunkSize) return parser.parse_args() def main(): logging.basicConfig(level=logging.INFO) args = parse_args() device = getserial(**vars(args)) ftpserve(args.file, device, args.chunksize) if __name__ == "__main__": try: main() except KeyboardInterrupt: pass