From b44c40ca30c8078cf9639461d62b9943fd92c854 Mon Sep 17 00:00:00 2001
From: Martin Teichmann <martin.teichmann@xfel.eu>
Date: Tue, 9 Apr 2024 16:09:39 +0000
Subject: [PATCH] support SDOs in terminals

---
 ebpfcat/ebpfcat.py   | 34 +------------------------
 ebpfcat/ethercat.py  | 60 ++++++++++++++++++++++++++++++++++++++++++++
 ebpfcat/terminals.py | 18 +++++++++++--
 3 files changed, 77 insertions(+), 35 deletions(-)

diff --git a/ebpfcat/ebpfcat.py b/ebpfcat/ebpfcat.py
index 30291c4..c3ddf2d 100644
--- a/ebpfcat/ebpfcat.py
+++ b/ebpfcat/ebpfcat.py
@@ -28,7 +28,7 @@ from time import time
 from .arraymap import ArrayMap, ArrayGlobalVarDesc
 from .ethercat import (
     ECCmd, EtherCat, MachineState, Packet, Terminal, EtherCatError,
-    SyncManager)
+    Struct, SyncManager)
 from .ebpf import FuncId, MemoryDesc, SubProgram, prandom
 from .xdp import XDP, XDPExitCode, PacketVar as XDPPacketVar
 from .bpf import (
@@ -146,38 +146,6 @@ class PacketVar(MemoryDesc):
                 self._start(device) + Packet.ETHERNET_HEADER)
 
 
-class Struct:
-    """Define repetitive structures in a PDO
-
-    Some terminals, especially multi-channel terminals,
-    have repetitive structures in their PDO. Inherit from this
-    class to create a structure for them. Each instance
-    will then define one channel. It takes one parameter, which
-    is the offset in the CoE address space from the template
-    structure to the one of the channel.
-    """
-    device = None
-
-    def __new__(cls, *args):
-        return StructDesc(cls, *args)
-
-
-class StructDesc:
-    def __init__(self, struct, sm3=0, sm2=None):
-        self.struct = struct
-        if sm2 is None:
-            sm2 = sm3
-        self.position_offset = {SyncManager.OUT: sm2, SyncManager.IN: sm3}
-
-    def __get__(self, instance, owner):
-        if instance is None:
-            return self
-        ret = object.__new__(self.struct)
-        ret.position_offset = self.position_offset
-        ret.terminal = instance
-        return ret
-
-
 class TerminalVar:
     def __set__(self, instance, value):
         if isinstance(value, PacketVar):
diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py
index 3f68be5..30eef3f 100644
--- a/ebpfcat/ethercat.py
+++ b/ebpfcat/ethercat.py
@@ -477,6 +477,50 @@ class EtherCat(Protocol):
         self.wait_futures[index].set_result(data)
 
 
+class ServiceDesc:
+    def __init__(self, index, subidx):
+        self.index = index
+        self.subidx = subidx
+
+
+class Struct:
+    """Define repetitive structures in CoE objects
+
+    Some terminals, especially multi-channel terminals,
+    have repetitive structures in their CoE. Inherit from this
+    class to create a structure for them. Each instance
+    will then define one channel. It takes one parameter, which
+    is the offset in the CoE address space from the template
+    structure to the one of the channel.
+    """
+    device = None
+
+    def __new__(cls, *args):
+        return StructDesc(cls, *args)
+
+
+class StructDesc:
+    def __init__(self, struct, sm3=0, sm2=None):
+        self.struct = struct
+        if sm2 is None:
+            sm2 = sm3
+        self.position_offset = {SyncManager.OUT: sm2, SyncManager.IN: sm3}
+
+    def __get__(self, instance, owner):
+        if instance is None:
+            return self
+        if (ret := instance.__dict__.get(self.name)) is not None:
+            return ret
+        ret = object.__new__(self.struct)
+        ret.position_offset = self.position_offset
+        ret.terminal = instance
+        instance.__dict__[self.name] = ret
+        return ret
+
+    def __set_name__(self, owner, name):
+        self.name = name
+
+
 class Terminal:
     """Represent one terminal (*SubDevice* or *slave*) in the loop"""
     def __init__(self, ethercat):
@@ -625,6 +669,22 @@ class Terminal:
                 await parse(parse_eeprom(self.eeprom[50]), SyncManager.IN)
                 if 50 in self.eeprom else 0)
 
+    async def parse_sdos(self):
+        sdos = {}
+        for cls in self.__class__.__mro__:
+            for k, v in cls.__dict__.items():
+                if isinstance(v, ServiceDesc):
+                    setattr(self, k,
+                            await self.read_object_entry(v.index, v.subidx))
+                elif isinstance(v, StructDesc):
+                    struct = getattr(self, k)
+                    offset = struct.position_offset[SyncManager.IN]
+                    for kk, vv in struct.__class__.__dict__.items():
+                        if isinstance(vv, ServiceDesc):
+                            setattr(struct, kk,
+                                    await self.read_object_entry(
+                                        vv.index + offset, vv.subidx))
+
     async def set_state(self, state):
         """try to set the state, and return the new state"""
         await self.ec.roundtrip(ECCmd.FPWR, self.position, 0x0120, "H", state)
diff --git a/ebpfcat/terminals.py b/ebpfcat/terminals.py
index d57464a..791ccb6 100644
--- a/ebpfcat/terminals.py
+++ b/ebpfcat/terminals.py
@@ -15,7 +15,8 @@
 # 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, ProcessDesc, Struct
+from .ethercat import ServiceDesc, Struct
+from .ebpfcat import EBPFTerminal, PacketDesc, ProcessDesc
 
 
 class Generic(EBPFTerminal):
@@ -136,10 +137,16 @@ class EL5042(EBPFTerminal):
         status = ProcessDesc(0x6000, 1, "H")
         invalid = ProcessDesc(0x6000, 0xE)
 
+        statusbits = ServiceDesc(0x8008, 2)
+        crc_invert = ServiceDesc(0x8008, 3)
+        multiturn = ServiceDesc(0x8008, 0x15)
+        singleturn = ServiceDesc(0x8008, 0x16)
+        frequency = ServiceDesc(0x8008, 0x13)
+        polynomial = ServiceDesc(0x8008, 0x11)
+
     channel1 = Channel(0)
     channel2 = Channel(0x10)
 
-
 class EL6022(EBPFTerminal):
     class Channel(Struct):
         transmit_accept = PacketDesc(3, 0, 0)
@@ -172,6 +179,13 @@ class EL7041(EBPFTerminal):
     low_switch = ProcessDesc(0x6010, 0xd)
     stepcounter = ProcessDesc(0x6010, 0x14)
 
+    max_current = ServiceDesc(0x8010, 1)
+    max_voltage = ServiceDesc(0x8010, 3)
+    coil_resistance = ServiceDesc(0x8010, 4)
+    motor_emf = ServiceDesc(0x8010, 5)
+    invLogicLim1 = ServiceDesc(0x8012, 0x30)
+    invLogicLim2 = ServiceDesc(0x8012, 0x31)
+
 
 class EL7332(EBPFTerminal):
     compatibility = {(2, 0x1CA43052)}
-- 
GitLab