diff --git a/ebpfcat/ebpfcat.py b/ebpfcat/ebpfcat.py index b2be0eef6c824a208f02611c83928969d3051c5b..c3b927474d67ad34fe674b87902270aa9ac2e87c 100644 --- a/ebpfcat/ebpfcat.py +++ b/ebpfcat/ebpfcat.py @@ -49,11 +49,31 @@ class PacketDesc: else: return ret.get(device) - def __set__(self, instance, value): - offset = instance.position_offset[self.sm] - ret = PacketVar(instance.terminal, self.sm, self.position + 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): @@ -218,6 +238,10 @@ class EBPFTerminal(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 @@ -355,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) @@ -404,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 a4d211d4398c98ef263b1e1a92e1b75caa48982f..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 @@ -428,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""" @@ -592,6 +646,8 @@ class Terminal: raise RuntimeError(f"expected CoE, got {type}") coecmd, sdocmd, idx, subidx, size = unpack("<HBHBI", data[:10]) if coecmd >> 12 != CoECmd.SDORES.value: + 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}") diff --git a/ebpfcat/scripts.py b/ebpfcat/scripts.py index 5b0ee2cbaa6afb831ae41cc459c074197a949a27..887bcbe0ad37beaec48cf618ac14f66f1286a771 100644 --- a/ebpfcat/scripts.py +++ b/ebpfcat/scripts.py @@ -80,4 +80,7 @@ async def info(): else: print(f" {r}") if args.pdo: - t.parse_pdos() + 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 331a15f786ac7963b1f50b9839394c41308b3057..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): @@ -54,21 +54,21 @@ class EL2808(EBPFTerminal): class EL4104(EBPFTerminal): - ch1_value = PacketDesc(2, 0, 'H') - ch2_value = PacketDesc(2, 2, 'H') - ch3_value = PacketDesc(2, 4, 'H') - ch4_value = PacketDesc(2, 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(3, 0, 'H') - value = PacketDesc(3, 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):