Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • teichman/ebpfcat
1 result
Show changes
Commits on Source (4)
......@@ -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):
......
......@@ -65,6 +65,7 @@ class ECDataType(Enum):
obj._value_ = value
obj.fmt = fmt
return obj
INVALID = 0, None
BOOLEAN = 0x1, "?"
INTEGER8 = 0x2, "b"
INTEGER16 = 0x3, "h"
......@@ -166,11 +167,12 @@ class ObjectDescription:
class ObjectEntry:
name = None
def __init__(self, desc):
self.desc = desc
def __init__(self, terminal, index):
self.terminal = terminal
self.index = index
async def read(self):
ret = await self.desc.terminal.sdo_read(self.desc.index, self.valueInfo)
ret = await self.terminal.sdo_read(self.index, self.valueInfo)
if self.dataType in (ECDataType.VISIBLE_STRING,
ECDataType.UNICODE_STRING):
return ret.decode("utf8")
......@@ -188,8 +190,7 @@ class ObjectEntry:
else:
d = pack("<" + self.dataType.fmt, data)
return await self.desc.terminal.sdo_write(d, self.desc.index,
self.valueInfo)
return await self.terminal.sdo_write(d, self.index, self.valueInfo)
def __repr__(self):
if self.name is None:
......@@ -301,6 +302,7 @@ class EtherCat(Protocol):
"""
self.addr = (network, 0x88A4, 0, 0, b"\xff\xff\xff\xff\xff\xff")
self.wait_futures = {}
self.used_addresses = set()
async def connect(self):
"""connect to the EtherCAT loop"""
......@@ -428,14 +430,29 @@ class EtherCat(Protocol):
return no
async def find_free_address(self):
"""Find an absolute address currently not in use"""
"""Find an absolute address not in use
an address once returned by this method is assumed to be used in the
future and will never be handed out again"""
while True:
i = randint(1000, 30000)
if i in self.used_addresses:
continue
self.used_addresses.add(i)
try:
await self.roundtrip(ECCmd.FPRD, i, 0x10, "H", 0)
except EtherCatError:
return i # this address is not in use
async def assigned_address(self, position):
"""return the set adress of terminal at position, if none set one"""
ret, = await self.roundtrip(ECCmd.APRD, position, 0x10, "H", 0)
if ret != 0:
return ret
ret = await self.find_free_address()
await self.ec.roundtrip(ECCmd.APWR, position, 0x10, "H", ret)
return ret
async def eeprom_read(self, position, start):
"""read 4 bytes from the eeprom of terminal `position` at `start`"""
while (await self.roundtrip(ECCmd.APRD, position,
......@@ -460,23 +477,77 @@ 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):
self.ec = ethercat
async def initialize(self, relative, absolute):
async def initialize(self, relative=None, absolute=None):
"""Initialize the terminal
this sets up the connection to the terminal we represent.
:param relative: the position of the terminal in the loop,
a negative number counted down from 0 for the first terminal
If None, we assume the address is already initialized
:param absolute: the number used to identify the terminal henceforth
If None take a free one
If only one parameter is given, it is taken to be an absolute
position, the terminal address is supposed to be already initialized.
This also reads the EEPROM and sets up the sync manager as defined
therein. It still leaves the terminal in the init state. """
await self.ec.roundtrip(ECCmd.APWR, relative, 0x10, "H", absolute)
therein. It still leaves the terminal in the init state.
"""
assert relative is not None or absolute is not None
if absolute is None:
absolute = await self.ec.find_free_address()
if relative is not None:
await self.ec.roundtrip(ECCmd.APWR, relative, 0x10, "H", absolute)
self.position = absolute
await self.set_state(0x11)
......@@ -598,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)
......@@ -871,6 +958,24 @@ class Terminal:
raise EtherCatError(f"requested index {index}")
toggle ^= 0x10
async def read_object_entry(self, index, subidx):
"""read a object entry from the CoE self description"""
data = await self.coe_request(CoECmd.SDOINFO, ODCmd.OE_REQ,
"HBB", index, subidx, 7)
oe = ObjectEntry(self, index)
oe.valueInfo, dataType, oe.bitLength, oe.objectAccess = \
unpack_from("<BxHHH", data)
assert subidx == oe.valueInfo
oe.dataTypeOriginal = dataType
if dataType < 2048:
oe.dataType = ECDataType(dataType)
elif oe.bitLength == 8:
oe.dataType = ECDataType.UNSIGNED8
else:
oe.dataType = dataType
oe.name = data[8:].decode("utf8")
return oe
async def read_ODlist(self):
idxes = await self.coe_request(CoECmd.SDOINFO, ODCmd.LIST_REQ, "H", 1)
idxes = unpack("<" + "H" * int(len(idxes) // 2), idxes)
......@@ -893,22 +998,12 @@ class Terminal:
od.entries = {}
for i in range(1 if od.maxSub > 0 else 0, od.maxSub + 1):
try:
data = await self.coe_request(CoECmd.SDOINFO, ODCmd.OE_REQ,
"HBB", od.index, i, 7)
oe = await self.read_object_entry(od.index, i)
except EtherCatError as e:
logging.info(f"problems reading SDO {od.index:x}:{i:x}:")
continue
oe = ObjectEntry(od)
oe.valueInfo, dataType, oe.bitLength, oe.objectAccess = \
unpack("<BxHHH", data[:8])
if dataType == 0:
if oe.dataType is ECDataType.INVALID:
continue
assert i == oe.valueInfo
if dataType < 2048:
oe.dataType = ECDataType(dataType)
else:
oe.dataType = dataType
oe.name = data[8:].decode("utf8")
od.entries[i] = oe
return ret
......
......@@ -48,12 +48,11 @@ async def info():
terms = [Terminal(ec) for t in terminals]
for t in terms:
t.ec = ec
await asyncio.gather(*(t.initialize(-i, i + 7)
await asyncio.gather(*(t.initialize(-i)
for i, t in zip(terminals, terms)))
else:
free = await ec.find_free_address()
term = Terminal(ec)
await term.initialize(-args.terminal, free)
await term.initialize(-args.terminal)
terms = [term]
for i, t in enumerate(terms, args.terminal if args.terminal else 0):
......@@ -120,20 +119,9 @@ async def eeprom():
if args.terminal is None:
return
terminals = range(await ec.count())
else:
# former terminal: don't listen!
# this does not work with all terminals, dunno why
try:
await ec.roundtrip(ECCmd.FPRW, 7, 0x10, "H", 0)
except EtherCatError:
print('fine: no not used yet')
else:
print('had to silence former listener')
terminals = [args.terminal]
t = Terminal(ec)
await t.initialize(-args.terminal, 7)
await t.initialize(-args.terminal)
if args.read or args.check is not None:
r, = unpack("<4xI", await t._eeprom_read_one(0xc))
......@@ -165,7 +153,7 @@ async def create_test():
for i in range(no):
t = Terminal()
t.ec = ec
await t.initialize(-i, await ec.find_free_address())
await t.initialize(-i)
sdo = {}
if t.has_mailbox():
await t.to_operational(MachineState.PRE_OPERATIONAL)
......
......@@ -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)}
......