From 656fbb95d809d64b7ef98c7acb03b773e9f5ee79 Mon Sep 17 00:00:00 2001 From: Martin Teichmann <martin.teichmann@xfel.eu> Date: Tue, 9 Apr 2024 11:21:38 +0000 Subject: [PATCH] improve address management on ethercat bus now terminals can be initialized with either absolute or relative only, the other one being chosen sensibly. Also remove race condition when searching for free addresses. --- ebpfcat/ethercat.py | 34 ++++++++++++++++++++++++++++++---- ebpfcat/scripts.py | 20 ++++---------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py index 40cc708..3f68be5 100644 --- a/ebpfcat/ethercat.py +++ b/ebpfcat/ethercat.py @@ -302,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""" @@ -429,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.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, @@ -466,18 +482,28 @@ class Terminal: 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) diff --git a/ebpfcat/scripts.py b/ebpfcat/scripts.py index e8c090e..c534d50 100644 --- a/ebpfcat/scripts.py +++ b/ebpfcat/scripts.py @@ -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) -- GitLab