diff --git a/ebpfcat/ebpfcat.py b/ebpfcat/ebpfcat.py index a530fff0cf11ee435dfea2130c35a8051d0185e3..c3b927474d67ad34fe674b87902270aa9ac2e87c 100644 --- a/ebpfcat/ebpfcat.py +++ b/ebpfcat/ebpfcat.py @@ -28,33 +28,52 @@ from .bpf import ( class PacketDesc: - def __init__(self, position, size): + def __init__(self, sm, position, size): + self.sm = sm self.position = position self.size = size def __get__(self, instance, owner): if instance is None: return self - offset = instance.position_offset[self.position[0]] + offset = instance.position_offset[self.sm] if isinstance(instance, Struct): terminal = instance.terminal device = instance.device else: terminal = instance device = None - ret = PacketVar(terminal, (self.position[0], - self.position[1] + offset), self.size) + ret = PacketVar(terminal, self.sm, self.position + offset, self.size) if device is None: return ret else: return ret.get(device) - def __set__(self, instance, value): - offset = instance.position_offset[self.position[0]] - ret = PacketVar(instance.terminal, - (self.position[0], self.position[1] + offset), - self.size) - return ret.set(instance.device, value) + +class ProcessDesc: + def __init__(self, index, subindex, size=None): + self.index = index + self.subindex = subindex + self.size = size + + def __get__(self, instance, owner): + if instance is None: + return self + index = self.index + instance.position_offset[3] + if isinstance(instance, Struct): + terminal = instance.terminal + device = instance.device + else: + terminal = instance + device = None + sm, offset, size = terminal.pdos[index, self.subindex] + if self.size is not None: + size = self.size + ret = PacketVar(terminal, sm, offset, size) + if device is None: + return ret + else: + return ret.get(device) class PacketVar(MemoryDesc): @@ -66,8 +85,9 @@ class PacketVar(MemoryDesc): else: return self.size - def __init__(self, terminal, position, size): + def __init__(self, terminal, sm, position, size): self.terminal = terminal + self.sm = sm self.position = position self.size = size @@ -119,8 +139,8 @@ class PacketVar(MemoryDesc): return unpack_from("<" + self.size, data, start)[0] def _start(self, device): - base, offset = self.position - return device.sync_group.terminals[self.terminal][base] + offset + return device.sync_group.terminals[self.terminal][self.sm] \ + + self.position def fmt_addr(self, device): return ("B" if isinstance(self.size, int) else self.size, @@ -135,9 +155,9 @@ class Struct: class StructDesc: - def __init__(self, struct, *position_offset): + def __init__(self, struct, sm3=0, sm2=0): self.struct = struct - self.position_offset = position_offset + self.position_offset = {2: sm2, 3: sm3} def __get__(self, instance, owner): if instance is None: @@ -209,30 +229,32 @@ class Device(SubProgram): class EBPFTerminal(Terminal): compatibility = None - position_offset = 0, 0 - - def __init_subclass__(cls): - cls.pdo = {} - for c in cls.__mro__[::-1]: - for k, v in c.__dict__.items(): - if isinstance(v, PacketDesc): - cls.pdo[k] = v + position_offset = {2: 0, 3: 0} async def initialize(self, relative, absolute): await super().initialize(relative, absolute) if (self.compatibility is not None and (self.vendorId, self.productCode) not in self.compatibility): - raise RuntimeError("Incompatible Terminal") + raise RuntimeError( + f"Incompatible Terminal: {self.vendorId}:{self.productCode} " + f"({relative}, {absolute})") + await self.to_operational() + self.pdos = {} + if self.has_mailbox(): + await self.parse_pdos() def allocate(self, packet, readonly): + """allocate space in packet for the pdos of this terminal + + return a dict that contains the starting offset for each + sync manager""" + bases = {} if self.pdo_in_sz: - bases = [packet.size + packet.DATAGRAM_HEADER] + bases[3] = packet.size + packet.DATAGRAM_HEADER packet.append(ECCmd.FPRD, b"\0" * self.pdo_in_sz, 0, self.position, self.pdo_in_off) - else: - bases = [None] if self.pdo_out_sz: - bases.append(packet.size + packet.DATAGRAM_HEADER) + bases[2] = packet.size + packet.DATAGRAM_HEADER if readonly: packet.on_the_fly.append((packet.size, ECCmd.FPWR)) packet.append(ECCmd.NOP, b"\0" * self.pdo_out_sz, 0, @@ -357,7 +379,6 @@ class SyncGroup(SyncGroupBase): current_data = False # None is used to indicate FastSyncGroup async def run(self): - await gather(*[t.to_operational() for t in self.terminals]) self.current_data = self.asm_packet while True: self.ec.send_packet(self.current_data) @@ -406,9 +427,6 @@ class FastSyncGroup(SyncGroupBase, XDP): def start(self): self.allocate() self.ec.register_sync_group(self, self.packet) - self.monitor = ensure_future(gather(*[t.to_operational() - for t in self.terminals])) - return self.monitor def allocate(self): self.packet = Packet() diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py index 261ee20cb9b9c3ec84728a821d616376c1566b9b..6a2353b64b88e174077fa99a0b34dca31d3c02dc 100644 --- a/ebpfcat/ethercat.py +++ b/ebpfcat/ethercat.py @@ -30,7 +30,7 @@ from asyncio import ensure_future, Event, Future, gather, get_event_loop, Protoc from enum import Enum from random import randint from socket import socket, AF_PACKET, SOCK_DGRAM -from struct import pack, unpack, calcsize +from struct import pack, unpack, unpack_from, calcsize class ECCmd(Enum): NOP = 0 # No Operation @@ -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,8 @@ class EtherCat(Protocol): out += b"\0" * data elif data is not None: out += data + assert isinstance(pos, int) and isinstance(offset, int), \ + f"pos: {pos} offset: {offset}" self.send_queue.put_nowait((cmd, out, idx, pos, offset, future)) ret = await future if data is None: @@ -333,6 +347,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) @@ -379,6 +401,9 @@ class Terminal: self.mbx_lock = Lock() self.eeprom = await self.read_eeprom() + if 41 not in self.eeprom: + # no sync managers defined in eeprom + return await self.write(0x800, data=0x80) # empty out sync manager await self.write(0x800, data=self.eeprom[41]) self.mbx_out_off = self.mbx_out_sz = None @@ -403,22 +428,76 @@ class Terminal: s = await self.read(0x800, data=0x80) print(absolute, " ".join(f"{c:02x} {'|' if i % 8 == 7 else ''}" for i, c in enumerate(s))) - def parse_pdos(self): - def parse_pdo(s): + async def parse_pdos(self): + async def parse_eeprom(s): i = 0 + bitpos = 0 while i < len(s): - idx, e, sm, u1, u2, u3 = unpack("<HBBBBH", s[i:i+8]) - print(f"idx {idx:x} sm {sm} {u1:x} {u2:x} {u3:x}") + idx, e, sm, u1, u2, u3 = unpack_from("<HBbBBH", s, i) i += 8 for er in range(e): - bitsize, = unpack("<5xB2x", s[i:i+8]) - print(" bs", bitsize, s[i:i+8]) + idx, subidx, k1, k2, bits, = unpack("<HBBBB2x", s[i:i+8]) + if sm > 0: + yield idx, subidx, sm, bits i += 8 - if 50 in self.eeprom: - parse_pdo(self.eeprom[50]) - if 51 in self.eeprom: - parse_pdo(self.eeprom[51]) + async def parse_sdo(index, sm): + assignment = await self.sdo_read(index) + bitpos = 0 + for i in range(0, len(assignment), 2): + pdo, = unpack_from("<H", assignment, i) + if pdo == 0: + continue + count, = unpack("B", await self.sdo_read(pdo, 0)) + for j in range(1, count + 1): + bits, subidx, idx = unpack("<BBH", await self.sdo_read(pdo, j)) + yield idx, subidx, sm, bits + + async def parse(func): + bitpos = 0 + async for idx, subidx, sm, bits in func: + #print("k", idx, subidx, sm, bits) + if idx == 0: + pass + elif bits < 8: + self.pdos[idx, subidx] = (sm, bitpos // 8, bitpos % 8) + elif (bits % 8) or (bitpos % 8): + raise RuntimeError("PDOs must be byte-aligned") + else: + self.pdos[idx, subidx] = \ + (sm, bitpos // 8, + {8: "B", 16: "H", 32: "I", 64: "Q"}[bits]) + bitpos += bits + + self.pdos = {} + if self.has_mailbox(): + await parse(parse_sdo(0x1c12, 2)) + await parse(parse_sdo(0x1c13, 3)) + else: + if 50 in self.eeprom: + await parse(parse_eeprom(self.eeprom[50])) + if 51 in self.eeprom: + await parse(parse_eeprom(self.eeprom[51])) + + async def parse_pdo(self, index, sm): + assignment = await self.sdo_read(index) + bitpos = 0 + for i in range(0, len(assignment), 2): + pdo, = unpack_from("<H", assignment, i) + count, = unpack("B", await self.sdo_read(pdo, 0)) + for j in range(1, count + 1): + bits, subidx, idx = unpack("<BBH", await self.sdo_read(pdo, j)) + if idx == 0: + pass + elif bits < 8: + self.pdos[idx, subidx] = (sm, bitpos // 8, bitpos % 8) + elif (bits % 8) or (bitpos % 8): + raise RuntimeError("PDOs must be byte-aligned") + else: + self.pdos[idx, subidx] = \ + (sm, bitpos // 8, + {8: "B", 16: "H", 32: "I", 64: "Q"}[bits]) + bitpos += bits async def set_state(self, state): """try to set the state, and return the new state""" @@ -470,7 +549,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, @@ -507,11 +586,15 @@ class Terminal: return eeprom eeprom[hd] = await get_data(ws * 2) + def has_mailbox(self): + return self.mbx_out_off is not None and self.mbx_in_off is not None + async def mbx_send(self, type, *args, data=None, address=0, priority=0, channel=0): """send data to the mailbox""" status, = await self.read(0x805, "B") # always using mailbox 0, OK? if status & 8: raise RuntimeError("mailbox full, read first") + assert self.mbx_out_off is not None, "not send mailbox defined" await self.write(self.mbx_out_off, "HHBB", datasize(args, data), address, channel | priority << 6, @@ -527,6 +610,7 @@ class Terminal: while status & 8 == 0: # always using mailbox 1, OK? status, = await self.read(0x80D, "B") + assert self.mbx_in_off is not None, "not receive mailbox defined" dlen, address, prio, type, data = await self.read( self.mbx_in_off, "HHBB", data=self.mbx_in_sz - 6) return MBXType(type & 0xf), data[:dlen] @@ -562,7 +646,11 @@ class Terminal: raise RuntimeError(f"expected CoE, got {type}") coecmd, sdocmd, idx, subidx, size = unpack("<HBHBI", data[:10]) if coecmd >> 12 != CoECmd.SDORES.value: - raise RuntimeError(f"expected CoE SDORES (3), got {coecmd>>12:x}") + if subindex is None and coecmd >> 12 == CoECmd.SDOREQ.value: + return b"" # if there is no data, the terminal fails + raise RuntimeError( + f"expected CoE SDORES (3), got {coecmd>>12:x} " + f"for {index:X}:{9 if subindex is None else subindex:02X}") if idx != index: raise RuntimeError(f"requested index {index}, got {idx}") if sdocmd & 2: @@ -581,7 +669,8 @@ class Terminal: raise RuntimeError(f"expected CoE, got {type}") coecmd, sdocmd = unpack("<HB", data[:3]) if coecmd >> 12 != CoECmd.SDORES.value: - raise RuntimeError(f"expected CoE cmd SDORES, got {coecmd}") + raise RuntimeError( + f"expected CoE cmd SDORES, got {coecmd}") if sdocmd & 0xe0 != 0: raise RuntimeError(f"requested index {index}, got {idx}") if sdocmd & 1 and len(data) == 7: @@ -688,55 +777,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..887bcbe0ad37beaec48cf618ac14f66f1286a771 --- /dev/null +++ b/ebpfcat/scripts.py @@ -0,0 +1,86 @@ +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") + parser.add_argument("-p", "--pdo", 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: + # former terminal: don't listen! + # this does not work with all terminals, dunno why + 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}") + if args.pdo: + await t.to_operational() + await t.parse_pdos() + for (idx, subidx), (sm, pos, fmt) in t.pdos.items(): + print(f"{idx:4X}:{subidx:02X} {sm} {pos} {fmt}") diff --git a/ebpfcat/terminals.py b/ebpfcat/terminals.py index bb709a32a4f41c22f0a35d263f895a92cbd6ca12..085138cde094c48ef96487d6ca7e836b747d6602 100644 --- a/ebpfcat/terminals.py +++ b/ebpfcat/terminals.py @@ -15,7 +15,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from .ebpfcat import EBPFTerminal, PacketDesc, Struct +from .ebpfcat import EBPFTerminal, PacketDesc, ProcessDesc, Struct class Generic(EBPFTerminal): @@ -30,92 +30,96 @@ class Skip(EBPFTerminal): class EL1808(EBPFTerminal): compatibility = {(2, 118501458)} - ch1 = PacketDesc((0, 0), 0) - ch2 = PacketDesc((0, 0), 1) - ch3 = PacketDesc((0, 0), 2) - ch4 = PacketDesc((0, 0), 3) - ch5 = PacketDesc((0, 0), 4) - ch6 = PacketDesc((0, 0), 5) - ch7 = PacketDesc((0, 0), 6) - ch8 = PacketDesc((0, 0), 7) + ch1 = PacketDesc(3, 0, 0) + ch2 = PacketDesc(3, 0, 1) + ch3 = PacketDesc(3, 0, 2) + ch4 = PacketDesc(3, 0, 3) + ch5 = PacketDesc(3, 0, 4) + ch6 = PacketDesc(3, 0, 5) + ch7 = PacketDesc(3, 0, 6) + ch8 = PacketDesc(3, 0, 7) class EL2808(EBPFTerminal): compatibility = {(2, 184037458)} - ch1 = PacketDesc((1, 0), 0) - ch2 = PacketDesc((1, 0), 1) - ch3 = PacketDesc((1, 0), 2) - ch4 = PacketDesc((1, 0), 3) - ch5 = PacketDesc((1, 0), 4) - ch6 = PacketDesc((1, 0), 5) - ch7 = PacketDesc((1, 0), 6) - ch8 = PacketDesc((1, 0), 7) + ch1 = PacketDesc(2, 0, 0) + ch2 = PacketDesc(2, 0, 1) + ch3 = PacketDesc(2, 0, 2) + ch4 = PacketDesc(2, 0, 3) + ch5 = PacketDesc(2, 0, 4) + ch6 = PacketDesc(2, 0, 5) + ch7 = PacketDesc(2, 0, 6) + ch8 = PacketDesc(2, 0, 7) class EL4104(EBPFTerminal): - ch1_value = PacketDesc((1, 0), 'H') - ch2_value = PacketDesc((1, 2), 'H') - ch3_value = PacketDesc((1, 4), 'H') - ch4_value = PacketDesc((1, 6), 'H') + ch1_value = ProcessDesc(0x7000, 1) + ch2_value = ProcessDesc(0x7010, 1) + ch3_value = ProcessDesc(0x7020, 1) + ch4_value = ProcessDesc(0x7030, 1) class EL3164(EBPFTerminal): class Channel(Struct): - attrs = PacketDesc((0, 0), 'H') - value = PacketDesc((0, 2), 'H') + attrs = ProcessDesc(0x6000, 1, 'H') + value = ProcessDesc(0x6000, 0x11) channel1 = Channel(0) - channel2 = Channel(4) - channel3 = Channel(8) - channel4 = Channel(12) + channel2 = Channel(0x10) + channel3 = Channel(0x20) + channel4 = Channel(0x30) + + +class EK1101(EBPFTerminal): + compatibility = {(2, 72166482)} class EK1814(EBPFTerminal): - ch1 = PacketDesc((0, 0), 0) - ch2 = PacketDesc((0, 0), 1) - ch3 = PacketDesc((0, 0), 2) - ch4 = PacketDesc((0, 0), 3) - ch5 = PacketDesc((1, 0), 0) - ch6 = PacketDesc((1, 0), 1) - ch7 = PacketDesc((1, 0), 2) - ch8 = PacketDesc((1, 0), 3) + ch1 = PacketDesc(3, 0, 0) + ch2 = PacketDesc(3, 0, 1) + ch3 = PacketDesc(3, 0, 2) + ch4 = PacketDesc(3, 0, 3) + ch5 = PacketDesc(2, 0, 0) + ch6 = PacketDesc(2, 0, 1) + ch7 = PacketDesc(2, 0, 2) + ch8 = PacketDesc(2, 0, 3) class EL5042(EBPFTerminal): compatibility = {(2, 330444882)} class Channel(Struct): - position = PacketDesc((0, 2), "q") - warning = PacketDesc((0, 0), 0) - error = PacketDesc((0, 0), 1) - status = PacketDesc((0, 0), "H") + position = PacketDesc(3, 2, "q") + warning = PacketDesc(3, 0, 0) + error = PacketDesc(3, 0, 1) + status = PacketDesc(3, 0, "H") - channel1 = Channel(0, None, 0) - channel2 = Channel(10, None, 0x10) + channel1 = Channel(0, 0) + channel2 = Channel(10, 0x10) class EL6022(EBPFTerminal): class Channel(Struct): - transmit_accept = PacketDesc((0, 0), 0) - receive_request = PacketDesc((0, 0), 1) - init_accept = PacketDesc((0, 0), 2) - status = PacketDesc((0, 0), "H") - in_string = PacketDesc((0, 1), "23p") - - transmit_request = PacketDesc((1, 0), 0) - receive_accept = PacketDesc((1, 0), 1) - init_request = PacketDesc((1, 0), 2) - control = PacketDesc((1, 0), "H") - out_string = PacketDesc((1, 1), "23p") + transmit_accept = PacketDesc(3, 0, 0) + receive_request = PacketDesc(3, 0, 1) + init_accept = PacketDesc(3, 0, 2) + status = PacketDesc(3, 0, "H") + in_string = PacketDesc(3, 1, "23p") + + transmit_request = PacketDesc(2, 0, 0) + receive_accept = PacketDesc(2, 0, 1) + init_request = PacketDesc(2, 0, 2) + control = PacketDesc(2, 0, "H") + out_string = PacketDesc(2, 1, "23p") channel1 = Channel(0, 0) channel2 = Channel(24, 24) class EL7041(EBPFTerminal): - compatibility = {(2, 461451346)} - velocity = PacketDesc((1, 6), "h") - enable = PacketDesc((1, 4), 0) - status = PacketDesc((0, 6), "H") - low_switch = PacketDesc((0, 1), 7) - high_switch = PacketDesc((0, 1), 8) + compatibility = {(2, 461451346), (2, 461455442), (2, 460795986)} + velocity = PacketDesc(2, 6, "h") + enable = PacketDesc(2, 4, 0) + status = PacketDesc(3, 6, "H") + low_switch = PacketDesc(3, 1, 7) + high_switch = PacketDesc(3, 1, 8) 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"