diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py index 3c69e86a3cc540b26f87709d8c28fd964e0ebdd2..c02d1af81e18b68d1633916ba7fe9020cd73d984 100644 --- a/ebpfcat/ethercat.py +++ b/ebpfcat/ethercat.py @@ -128,8 +128,13 @@ class ObjectDescription: def __getitem__(self, idx): return self.entries[idx] + def __repr__(self): + return " ".join(f"[{k:X}: {v}]" for k, v in self.entries.items()) + class ObjectEntry: + name = None + def __init__(self, desc): self.desc = desc @@ -155,6 +160,13 @@ class ObjectEntry: return await self.desc.terminal.sdo_write(d, self.desc.index, self.valueInfo) + def __repr__(self): + if self.name is None: + return "[unread ObjectEntry]" + + return f'"{self.name}" {self.dataType}:{self.bitLength} ' \ + f'{self.objectAccess:X}' + def datasize(args, data): out = calcsize("<" + "".join(arg for arg in args if isinstance(arg, str))) @@ -221,11 +233,11 @@ class Packet: pos = 14 + self.DATAGRAM_HEADER ret = [] for cmd, bits, *dgram in self.data: - ret.append((data[pos-self.DATAGRAM_HEADER], - data[pos:pos+len(bits)], + ret.append(unpack("<Bxh6x", data[pos-self.DATAGRAM_HEADER:pos]) + + (data[pos:pos+len(bits)], unpack("<H", data[pos+len(bits):pos+len(bits)+2])[0])) pos += self.DATAGRAM_HEADER + self.DATAGRAM_TAIL - return ''.join(f"{i}: {c} {f} {d}\n" for i, (c, d, f) in enumerate(ret)) + return ''.join(f"{i}: {c} {a} {f} {d}\n" for i, (c, a, d, f) in enumerate(ret)) def full(self): """Is the data limit reached?""" @@ -322,6 +334,7 @@ class EtherCat(Protocol): out += b"\0" * data elif data is not None: out += data + assert isinstance(pos, int) and isinstance(offset, int) self.send_queue.put_nowait((cmd, out, idx, pos, offset, future)) ret = await future if data is None: @@ -333,6 +346,14 @@ class EtherCat(Protocol): else: return ret + async def count(self): + """Count the number of terminals on the bus""" + p = Packet() + p.append(ECCmd.APRD, b"\0\0", 0, 0, 0x10) + ret = await self.roundtrip_packet(p) + no, = unpack("<h", ret[16:18]) # number of terminals + return no + def connection_made(self, transport): """start the send loop once the connection is made""" transport.get_extra_info("socket").bind(self.addr) @@ -473,7 +494,7 @@ class Terminal: start, *args, **kwargs)) async def write(self, start, *args, **kwargs): - """write data from the terminal at offset `start` + """write data to the terminal at offset `start` see `EtherCat.roundtrip` for details on more parameters""" return (await self.ec.roundtrip(ECCmd.FPWR, self.position, @@ -691,55 +712,3 @@ class Terminal: oe.name = data[8:].decode("utf8") od.entries[i] = oe return ret - - -async def main(): - from .bpf import lookup_elem - - ec = EtherCat("eth0") - await ec.connect() - #map_fd = await install_ebpf2() - tin = Terminal() - tin.ec = ec - tout = Terminal() - tout.ec = ec - tdigi = Terminal() - tdigi.ec = ec - await gather( - tin.initialize(-4, 19), - tout.initialize(-2, 55), - tdigi.initialize(0, 22), - ) - print("tin") - #await tin.to_operational() - await tin.set_state(2) - print("tout") - await tout.to_operational() - print("reading odlist") - odlist2, odlist = await gather(tin.read_ODlist(), tout.read_ODlist()) - #oe = odlist[0x7001][1] - #await oe.write(1) - for o in odlist.values(): - print(hex(o.index), o.name, o.maxSub) - for i, p in o.entries.items(): - print(" ", i, p.name, "|", p.dataType, p.bitLength, p.objectAccess) - #sdo = await tin.sdo_read(o.index, i) - try: - sdo = await p.read() - if isinstance(sdo, int): - t = hex(sdo) - else: - t = "" - print(" ", sdo, t) - except RuntimeError as e: - print(" E", e) - print("set sdo") - oe = odlist[0x8010][7] - print("=", await oe.read()) - await oe.write(1) - print("=", await oe.read()) - print(tdigi.eeprom[10]) - -if __name__ == "__main__": - loop = get_event_loop() - loop.run_until_complete(main()) diff --git a/ebpfcat/scripts.py b/ebpfcat/scripts.py new file mode 100644 index 0000000000000000000000000000000000000000..7025076d7a413f2d6ff3f078c374583395382f8a --- /dev/null +++ b/ebpfcat/scripts.py @@ -0,0 +1,78 @@ +from argparse import ArgumentParser +import asyncio +from functools import wraps +from struct import unpack +import sys + +from .ethercat import EtherCat, Terminal, ECCmd + +def entrypoint(func): + @wraps(func) + def wrapper(): + asyncio.run(func()) + return wrapper + + +@entrypoint +async def scanbus(): + ec = EtherCat(sys.argv[1]) + await ec.connect() + no = await ec.count() + for i in range(no): + r, = await ec.roundtrip(ECCmd.APRD, -i, 0x10, "H", 44) + print(i, r) + +@entrypoint +async def info(): + parser = ArgumentParser( + prog = "ec-info", + description = "Retrieve information from an EtherCat bus") + + parser.add_argument("interface") + parser.add_argument("-t", "--terminal", type=int) + parser.add_argument("-i", "--ids", action="store_true") + parser.add_argument("-n", "--names", action="store_true") + parser.add_argument("-s", "--sdo", action="store_true") + parser.add_argument("-v", "--values", action="store_true") + args = parser.parse_args() + + ec = EtherCat(args.interface) + await ec.connect() + + if args.terminal is None: + terminals = range(await ec.count()) + else: +# print(await ec.roundtrip(ECCmd.FPRW, 7, 0x10, "H", 0)) + terminals = [args.terminal] + + terms = [Terminal() for t in terminals] + for t in terms: + t.ec = ec + await asyncio.gather(*(t.initialize(-i, i + 7) + for i, t in zip(terminals, terms))) + + for i, t in enumerate(terms): + print(f"terminal no {i}") + if args.ids: + print(f"{t.vendorId:X}:{t.productCode:X} " + f"revision {t.revisionNo:X} serial {t.serialNo}") + if args.names: + infos = t.eeprom[10] + i = 1 + while i < len(infos): + print(infos[i+1 : i+infos[i]+1].decode("ascii")) + i += infos[i] + 1 + + if args.sdo: + await t.to_operational() + ret = await t.read_ODlist() + for k, v in ret.items(): + print(f"{k:X}:") + for kk, vv in v.entries.items(): + print(f" {kk:X}: {vv}") + if args.values: + r = await vv.read() + if isinstance(r, int): + print(f" {r:10} {r:8X}") + else: + print(f" {r}") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..995d3a93148669770bc8c9e9a5789b7200803e19 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "ebpfcat" +version = "0.0.1" +dependencies = [] + +[project.scripts] +ec-scanbus = "ebpfcat.scripts:scanbus" +ec-info = "ebpfcat.scripts:info"