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 ...@@ -28,7 +28,7 @@ from time import time
from .arraymap import ArrayMap, ArrayGlobalVarDesc from .arraymap import ArrayMap, ArrayGlobalVarDesc
from .ethercat import ( from .ethercat import (
ECCmd, EtherCat, MachineState, Packet, Terminal, EtherCatError, ECCmd, EtherCat, MachineState, Packet, Terminal, EtherCatError,
SyncManager) Struct, SyncManager)
from .ebpf import FuncId, MemoryDesc, SubProgram, prandom from .ebpf import FuncId, MemoryDesc, SubProgram, prandom
from .xdp import XDP, XDPExitCode, PacketVar as XDPPacketVar from .xdp import XDP, XDPExitCode, PacketVar as XDPPacketVar
from .bpf import ( from .bpf import (
...@@ -146,38 +146,6 @@ class PacketVar(MemoryDesc): ...@@ -146,38 +146,6 @@ class PacketVar(MemoryDesc):
self._start(device) + Packet.ETHERNET_HEADER) 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: class TerminalVar:
def __set__(self, instance, value): def __set__(self, instance, value):
if isinstance(value, PacketVar): if isinstance(value, PacketVar):
......
...@@ -65,6 +65,7 @@ class ECDataType(Enum): ...@@ -65,6 +65,7 @@ class ECDataType(Enum):
obj._value_ = value obj._value_ = value
obj.fmt = fmt obj.fmt = fmt
return obj return obj
INVALID = 0, None
BOOLEAN = 0x1, "?" BOOLEAN = 0x1, "?"
INTEGER8 = 0x2, "b" INTEGER8 = 0x2, "b"
INTEGER16 = 0x3, "h" INTEGER16 = 0x3, "h"
...@@ -166,11 +167,12 @@ class ObjectDescription: ...@@ -166,11 +167,12 @@ class ObjectDescription:
class ObjectEntry: class ObjectEntry:
name = None name = None
def __init__(self, desc): def __init__(self, terminal, index):
self.desc = desc self.terminal = terminal
self.index = index
async def read(self): 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, if self.dataType in (ECDataType.VISIBLE_STRING,
ECDataType.UNICODE_STRING): ECDataType.UNICODE_STRING):
return ret.decode("utf8") return ret.decode("utf8")
...@@ -188,8 +190,7 @@ class ObjectEntry: ...@@ -188,8 +190,7 @@ class ObjectEntry:
else: else:
d = pack("<" + self.dataType.fmt, data) d = pack("<" + self.dataType.fmt, data)
return await self.desc.terminal.sdo_write(d, self.desc.index, return await self.terminal.sdo_write(d, self.index, self.valueInfo)
self.valueInfo)
def __repr__(self): def __repr__(self):
if self.name is None: if self.name is None:
...@@ -301,6 +302,7 @@ class EtherCat(Protocol): ...@@ -301,6 +302,7 @@ class EtherCat(Protocol):
""" """
self.addr = (network, 0x88A4, 0, 0, b"\xff\xff\xff\xff\xff\xff") self.addr = (network, 0x88A4, 0, 0, b"\xff\xff\xff\xff\xff\xff")
self.wait_futures = {} self.wait_futures = {}
self.used_addresses = set()
async def connect(self): async def connect(self):
"""connect to the EtherCAT loop""" """connect to the EtherCAT loop"""
...@@ -428,14 +430,29 @@ class EtherCat(Protocol): ...@@ -428,14 +430,29 @@ class EtherCat(Protocol):
return no return no
async def find_free_address(self): 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: while True:
i = randint(1000, 30000) i = randint(1000, 30000)
if i in self.used_addresses:
continue
self.used_addresses.add(i)
try: try:
await self.roundtrip(ECCmd.FPRD, i, 0x10, "H", 0) await self.roundtrip(ECCmd.FPRD, i, 0x10, "H", 0)
except EtherCatError: except EtherCatError:
return i # this address is not in use 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): async def eeprom_read(self, position, start):
"""read 4 bytes from the eeprom of terminal `position` at `start`""" """read 4 bytes from the eeprom of terminal `position` at `start`"""
while (await self.roundtrip(ECCmd.APRD, position, while (await self.roundtrip(ECCmd.APRD, position,
...@@ -460,23 +477,77 @@ class EtherCat(Protocol): ...@@ -460,23 +477,77 @@ class EtherCat(Protocol):
self.wait_futures[index].set_result(data) 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: class Terminal:
"""Represent one terminal (*SubDevice* or *slave*) in the loop""" """Represent one terminal (*SubDevice* or *slave*) in the loop"""
def __init__(self, ethercat): def __init__(self, ethercat):
self.ec = ethercat self.ec = ethercat
async def initialize(self, relative, absolute): async def initialize(self, relative=None, absolute=None):
"""Initialize the terminal """Initialize the terminal
this sets up the connection to the terminal we represent. this sets up the connection to the terminal we represent.
:param relative: the position of the terminal in the loop, :param relative: the position of the terminal in the loop,
a negative number counted down from 0 for the first terminal 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 :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 This also reads the EEPROM and sets up the sync manager as defined
therein. It still leaves the terminal in the init state. """ therein. It still leaves the terminal in the init state.
await self.ec.roundtrip(ECCmd.APWR, relative, 0x10, "H", absolute) """
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 self.position = absolute
await self.set_state(0x11) await self.set_state(0x11)
...@@ -598,6 +669,22 @@ class Terminal: ...@@ -598,6 +669,22 @@ class Terminal:
await parse(parse_eeprom(self.eeprom[50]), SyncManager.IN) await parse(parse_eeprom(self.eeprom[50]), SyncManager.IN)
if 50 in self.eeprom else 0) 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): async def set_state(self, state):
"""try to set the state, and return the new state""" """try to set the state, and return the new state"""
await self.ec.roundtrip(ECCmd.FPWR, self.position, 0x0120, "H", state) await self.ec.roundtrip(ECCmd.FPWR, self.position, 0x0120, "H", state)
...@@ -871,6 +958,24 @@ class Terminal: ...@@ -871,6 +958,24 @@ class Terminal:
raise EtherCatError(f"requested index {index}") raise EtherCatError(f"requested index {index}")
toggle ^= 0x10 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): async def read_ODlist(self):
idxes = await self.coe_request(CoECmd.SDOINFO, ODCmd.LIST_REQ, "H", 1) idxes = await self.coe_request(CoECmd.SDOINFO, ODCmd.LIST_REQ, "H", 1)
idxes = unpack("<" + "H" * int(len(idxes) // 2), idxes) idxes = unpack("<" + "H" * int(len(idxes) // 2), idxes)
...@@ -893,22 +998,12 @@ class Terminal: ...@@ -893,22 +998,12 @@ class Terminal:
od.entries = {} od.entries = {}
for i in range(1 if od.maxSub > 0 else 0, od.maxSub + 1): for i in range(1 if od.maxSub > 0 else 0, od.maxSub + 1):
try: try:
data = await self.coe_request(CoECmd.SDOINFO, ODCmd.OE_REQ, oe = await self.read_object_entry(od.index, i)
"HBB", od.index, i, 7)
except EtherCatError as e: except EtherCatError as e:
logging.info(f"problems reading SDO {od.index:x}:{i:x}:") logging.info(f"problems reading SDO {od.index:x}:{i:x}:")
continue continue
oe = ObjectEntry(od) if oe.dataType is ECDataType.INVALID:
oe.valueInfo, dataType, oe.bitLength, oe.objectAccess = \
unpack("<BxHHH", data[:8])
if dataType == 0:
continue 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 od.entries[i] = oe
return ret return ret
......
...@@ -48,12 +48,11 @@ async def info(): ...@@ -48,12 +48,11 @@ async def info():
terms = [Terminal(ec) for t in terminals] terms = [Terminal(ec) for t in terminals]
for t in terms: for t in terms:
t.ec = ec t.ec = ec
await asyncio.gather(*(t.initialize(-i, i + 7) await asyncio.gather(*(t.initialize(-i)
for i, t in zip(terminals, terms))) for i, t in zip(terminals, terms)))
else: else:
free = await ec.find_free_address()
term = Terminal(ec) term = Terminal(ec)
await term.initialize(-args.terminal, free) await term.initialize(-args.terminal)
terms = [term] terms = [term]
for i, t in enumerate(terms, args.terminal if args.terminal else 0): for i, t in enumerate(terms, args.terminal if args.terminal else 0):
...@@ -120,20 +119,9 @@ async def eeprom(): ...@@ -120,20 +119,9 @@ async def eeprom():
if args.terminal is None: if args.terminal is None:
return 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) t = Terminal(ec)
await t.initialize(-args.terminal, 7) await t.initialize(-args.terminal)
if args.read or args.check is not None: if args.read or args.check is not None:
r, = unpack("<4xI", await t._eeprom_read_one(0xc)) r, = unpack("<4xI", await t._eeprom_read_one(0xc))
...@@ -165,7 +153,7 @@ async def create_test(): ...@@ -165,7 +153,7 @@ async def create_test():
for i in range(no): for i in range(no):
t = Terminal() t = Terminal()
t.ec = ec t.ec = ec
await t.initialize(-i, await ec.find_free_address()) await t.initialize(-i)
sdo = {} sdo = {}
if t.has_mailbox(): if t.has_mailbox():
await t.to_operational(MachineState.PRE_OPERATIONAL) await t.to_operational(MachineState.PRE_OPERATIONAL)
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 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): class Generic(EBPFTerminal):
...@@ -136,10 +137,16 @@ class EL5042(EBPFTerminal): ...@@ -136,10 +137,16 @@ class EL5042(EBPFTerminal):
status = ProcessDesc(0x6000, 1, "H") status = ProcessDesc(0x6000, 1, "H")
invalid = ProcessDesc(0x6000, 0xE) 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) channel1 = Channel(0)
channel2 = Channel(0x10) channel2 = Channel(0x10)
class EL6022(EBPFTerminal): class EL6022(EBPFTerminal):
class Channel(Struct): class Channel(Struct):
transmit_accept = PacketDesc(3, 0, 0) transmit_accept = PacketDesc(3, 0, 0)
...@@ -172,6 +179,13 @@ class EL7041(EBPFTerminal): ...@@ -172,6 +179,13 @@ class EL7041(EBPFTerminal):
low_switch = ProcessDesc(0x6010, 0xd) low_switch = ProcessDesc(0x6010, 0xd)
stepcounter = ProcessDesc(0x6010, 0x14) 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): class EL7332(EBPFTerminal):
compatibility = {(2, 0x1CA43052)} compatibility = {(2, 0x1CA43052)}
......