Source code for fx2.format

import os
import io
import re


__all__ = ['input_data', 'output_data']


[docs]def autodetect(file): """ Autodetect file format based on properties of a given file object. Returns `"ihex"` for `.hex`, `.ihex` and `.ihx` file extensions, `"bin"` for `.bin` file extension, `"hex"` if ``file`` is a TTY, and raises :class:`ValueError` otherwise. """ basename, extname = os.path.splitext(file.name) if extname in [".hex", ".ihex", ".ihx"]: return "ihex" elif extname in [".bin"]: return "bin" elif file.isatty(): return "hex" else: raise ValueError("Specify file format explicitly")
[docs]def flatten_data(data, *, fill=0x00): """ Flatten a list of ``(addr, chunk)`` pairs, such as that returned by :func:`input_data`, to a flat byte array, such as that accepted by :func:`output_data`. """ data_flat = bytearray([fill]) * max([addr + len(chunk) for (addr, chunk) in data]) for (addr, chunk) in data: data_flat[addr:addr+len(chunk)] = chunk return data_flat
[docs]def diff_data(old, new): """ Compute the difference between ``old`` and ``new`` byte arrays, and return a list of ``(addr, chunk)`` pairs containing only the bytes that are changed between ``new`` and ``old``. """ diff = [] cpos = None cchunk = bytearray() for (pos, (oldb, newb)) in enumerate(zip(old, new)): if oldb != newb: if cpos is None: cpos = pos elif cpos + len(cchunk) != pos: diff.append((cpos, bytes(cchunk))) cchunk.clear() cpos = pos cchunk.append(newb) if len(cchunk) > 0: diff.append((cpos, bytes(cchunk))) if len(new) > len(old): diff.append((len(old), new[len(old):])) return diff
[docs]def output_data(file, data, fmt="auto", offset=0): """ Write Intel HEX, hexadecimal, or binary ``data`` to ``file``. :param data: A byte array (``bytes``, ``bytearray`` and ``list`` of ``(addr, chunk)`` pairs are all valid). :param fmt: ``"ihex"`` for Intel HEX, ``"hex"`` for hexadecimal, ``"bin"`` for binary, or ``"auto"`` for autodetection via :func:`autodetect`. :param offset: Offset the data by specified amount of bytes. """ if isinstance(file, io.TextIOWrapper): file = file.buffer if fmt == "auto": fmt = autodetect(file) if fmt == "bin": if isinstance(data, list): data = flatten_data(data) file.write(data) elif fmt == "hex": if isinstance(data, list): data = flatten_data(data) n = 0 for n, byte in enumerate(data): file.write("{:02x}".format(byte).encode()) if n > 0 and n % 16 == 15: file.write(b"\n") elif n > 0 and n % 8 == 7: file.write(b" ") else: file.write(b" ") if n % 16 != 15: file.write(b"\n") elif fmt == "ihex": if not isinstance(data, list): data = [(offset, data)] def write_record(record): record.append((~sum(record) + 1) & 0xff) file.write(b":") file.write(bytes(record).hex().upper().encode()) file.write(b"\n") bankoff = 0 for (addr, chunk) in data: pos = 0 while pos < len(chunk): recoff = addr + pos if bankoff != recoff >> 16: bankoff = recoff >> 16 write_record([ 2, 0x00, # dummy 0x00, # dummy 0x04, # Extended Linear Address (bankoff >> 8) & 0xff, (bankoff >> 0) & 0xff, ]) recdata = chunk[pos:pos + 0x10] write_record([ len(recdata), (recoff >> 8) & 0xff, (recoff >> 0) & 0xff, 0x00, # Data *list(recdata) ]) pos += len(recdata) write_record([ 0, 0x00, # dummy 0x00, # dummy 0x01, # End Of File ])
[docs]def input_data(file_or_data, fmt="auto", offset=0): """ Read Intel HEX, hexadecimal, or binary data from ``file_or_data``. If ``file_or_data`` is a string, it is treated as hexadecimal. Otherwise, the format is determined by the ``fmt`` argument. Raises :class:`ValueError` if the input data has invalid format. Returns a list of ``(address, data)`` chunks. :param fmt: ``"ihex"`` for Intel HEX, ``"hex"`` for hexadecimal, ``"bin"`` for binary, or ``"auto"`` for autodetection via :func:`autodetect`. :param offset: Offset the data by specified amount of bytes. """ if isinstance(file_or_data, io.TextIOWrapper): file_or_data = file_or_data.buffer if isinstance(file_or_data, str): fmt = "hex" data = file_or_data.encode() else: data = file_or_data.read() if fmt == "auto": fmt = autodetect(file_or_data) if fmt == "bin": return [(offset, data)] elif fmt == "hex": try: hexdata = re.sub(r"\s*", "", data.decode()) bindata = bytes.fromhex(hexdata) except ValueError as e: raise ValueError("Invalid hexadecimal data") return [(offset, bindata)] elif fmt == "ihex": RE_HDR = re.compile(rb":([0-9a-f]{8})", re.I) RE_WS = re.compile(rb"\s*") segoff = 0 bankoff = 0 resoff = 0 resbuf = [] res = [] pos = 0 while pos < len(data): match = RE_HDR.match(data, pos) if match is None: raise ValueError("Invalid record header at offset {}".format(pos)) *rechdr, = bytes.fromhex(match.group(1).decode()) reclen, recoffh, recoffl, rectype = rechdr recdatahex = data[match.end(0):match.end(0)+(reclen+1)*2] if len(recdatahex) < (reclen + 1) * 2: raise ValueError("Truncated record at offset {}".format(pos)) try: *recdata, recsum = bytes.fromhex(recdatahex.decode()) except ValueError: raise ValueError("Invalid record data at offset {}".format(pos)) if sum(rechdr + recdata + [recsum]) & 0xff != 0: raise ValueError("Invalid record checksum at offset {}".format(pos)) if rectype == 0x01: break elif rectype in (0x02, 0x04): res.append((offset + resoff + segoff + bankoff, resbuf)) # If we switch segments/banks, we know there is a discontinuity, so # make no assumption about previous position or buffer contents. resoff = 0 resbuf = [] if rectype == 0x02: segoff = ((recdata[0] << 8) | recdata[1]) << 4 elif rectype == 0x04: bankoff = ((recdata[0] << 8) | recdata[1]) << 16 else: assert False elif rectype == 0x05: pass elif rectype == 0x00: recoff = (recoffh << 8) | recoffl if resoff + len(resbuf) == recoff: resbuf += recdata else: if len(resbuf) > 0: res.append((offset + resoff + segoff + bankoff, resbuf)) resoff = recoff resbuf = recdata else: raise ValueError("Unknown record type {:02x} at offset {}".format(rectype, pos)) match = RE_WS.match(data, match.end(0) + len(recdatahex)) pos = match.end(0) # Handle last record that was seen before Record Type 0x01. if len(resbuf) > 0: res.append((offset + resoff + segoff + bankoff, resbuf)) return res