diff --git a/ebpfcat/ebpfcat.py b/ebpfcat/ebpfcat.py index 015d19b5806d1530d6b11719c812cf5617f225c8..e77a3465626c2a570ddf8294264b93568defde55 100644 --- a/ebpfcat/ebpfcat.py +++ b/ebpfcat/ebpfcat.py @@ -27,7 +27,8 @@ from struct import pack, unpack, calcsize, pack_into, unpack_from from time import time from .arraymap import ArrayMap, ArrayGlobalVarDesc from .ethercat import ( - ECCmd, EtherCat, Packet, Terminal, EtherCatError, SyncManager) + ECCmd, EtherCat, MachineState, Packet, Terminal, EtherCatError, + SyncManager) from .ebpf import FuncId, MemoryDesc, SubProgram, prandom from .xdp import XDP, XDPExitCode, PacketVar as XDPPacketVar from .bpf import ( @@ -250,7 +251,7 @@ class EBPFTerminal(Terminal): (self.vendorId, self.productCode) not in self.compatibility): raise EtherCatError( f"Incompatible Terminal: {self.vendorId}:{self.productCode}") - await self.to_operational(2) + await self.to_operational(MachineState.PRE_OPERATIONAL) self.pdos = {} outbits, inbits = await self.parse_pdos() self.pdo_out_sz = int((outbits + 7) // 8) diff --git a/ebpfcat/ethercat.py b/ebpfcat/ethercat.py index ad8d39d4fbf3a84cf75efba77a82e14dfe97ca89..397cd56db452df517ce64410f34597998eea441e 100644 --- a/ebpfcat/ethercat.py +++ b/ebpfcat/ethercat.py @@ -135,6 +135,19 @@ class EEPROM(IntEnum): REVISION = 12 SERIAL_NO = 14 +class MachineState(Enum): + """The states of the EtherCAT state machine + + The states are in the order in which they should + be taken, BOOTSTRAP is at the end as this is a + state we usually do not go to. + """ + INIT = 1 + PRE_OPERATIONAL = 2 + SAFE_OPERATIONAL = 4 + OPERATIONAL = 8 + BOOTSTRAP = 3 + class SyncManager(Enum): OUT = 2 IN = 3 @@ -561,43 +574,37 @@ class Terminal: return ret async def get_state(self): - """get the current state and error flags""" - state, error = await self.ec.roundtrip(ECCmd.FPRD, self.position, - 0x0130, "H2xH") - return state, error + """get the current state, error flag and status word""" + state, status = await self.ec.roundtrip(ECCmd.FPRD, self.position, + 0x0130, "H2xH") + return MachineState(state & 0xf), bool(state & 0x10), status - async def to_operational(self, target=8): + async def to_operational(self, target=MachineState.OPERATIONAL): """try to bring the terminal to operational state this tries to push the terminal through its state machine to the - operational state. Note that even if it reaches there, the terminal + target state. Note that even if it reaches there, the terminal will quickly return to pre-operational if no packets are sent to keep - it operational. """ - order = [1, 2, 4, 8] - ret, error = await self.ec.roundtrip( - ECCmd.FPRD, self.position, 0x0130, "H2xH") - if ret & 0x10: + it operational. + + return the state, error flag and status before the operation.""" + order = list(MachineState) + state, error, status = ret = await self.get_state() + if error: await self.ec.roundtrip(ECCmd.FPWR, self.position, 0x0120, "H", 0x11) - ret, error = await self.ec.roundtrip(ECCmd.FPRD, self.position, - 0x0130, "H2xH") - pos = order.index(ret) - s = 0x11 - for state in order[pos+1:]: + state = MachineState.INIT + pos = order.index(state) + 1 + state = None + for current in order[pos:]: await self.ec.roundtrip(ECCmd.FPWR, self.position, - 0x0120, "H", state) - while s != state: - s, error = await self.ec.roundtrip(ECCmd.FPRD, self.position, - 0x0130, "H2xH") - if error != 0: - raise EtherCatError(f"AL register {error}") - if state >= target: - return - - async def get_error(self): - """read the error register""" - return (await self.ec.roundtrip(ECCmd.FPRD, self.position, - 0x0134, "H"))[0] + 0x0120, "H", current.value) + while current is not state: + state, error, status = await self.get_state() + if error: + raise EtherCatError(f"AL register error {status}") + if state.value >= target.value: + return ret async def read(self, start, *args, **kwargs): """read data from the terminal at offset `start` diff --git a/ebpfcat/ethercat_test.py b/ebpfcat/ethercat_test.py index 3447c4ab49bcba34d58c86689d9877db92717ac8..c691e5709dbfa805f85ff7bf315d6abe43478dbf 100644 --- a/ebpfcat/ethercat_test.py +++ b/ebpfcat/ethercat_test.py @@ -25,7 +25,7 @@ from unittest import TestCase, main, skip from .devices import AnalogInput, AnalogOutput, Motor from .terminals import EL4104, EL3164, EK1814, Skip -from .ethercat import ECCmd, Terminal +from .ethercat import ECCmd, MachineState, Terminal from .ebpfcat import ( FastSyncGroup, SyncGroup, TerminalVar, Device, EBPFTerminal, PacketDesc, SterilePacket) @@ -82,11 +82,14 @@ class MockTerminal(Terminal): await self.apply_eeprom() - async def to_operational(self, state=8): + async def to_operational(self, state=MachineState.OPERATIONAL): + assert isinstance(state, MachineState) + before = self.operational self.operational = state + return state, 0, before async def sdo_read(self, index, subindex=None): - assert self.operational >= 2 + assert self.operational.value >= 2 if subindex is None: r = b'' for i in count(1): diff --git a/ebpfcat/scripts.py b/ebpfcat/scripts.py index 7b2de851fb3847ed5047c9185e36173df212af52..0dd6d5067d00bcd5368119ecbe5989bcec15a258 100644 --- a/ebpfcat/scripts.py +++ b/ebpfcat/scripts.py @@ -6,7 +6,7 @@ from pprint import PrettyPrinter from struct import unpack import sys -from .ethercat import EtherCat, Terminal, ECCmd, EtherCatError +from .ethercat import EtherCat, MachineState, Terminal, ECCmd, EtherCatError def entrypoint(func): @wraps(func) @@ -73,7 +73,7 @@ async def info(): print(f"{k:2}: {v}\n {v.hex()}") if args.sdo: - await t.to_operational(2) + await t.to_operational(MachineState.PRE_OPERATIONAL) ret = await t.read_ODlist() for k, v in ret.items(): print(f"{k:X}:") @@ -91,7 +91,7 @@ async def info(): print(f" {r}") print(f" {r!r}") if args.pdo: - await t.to_operational(2) + await t.to_operational(MachineState.PRE_OPERATIONAL) await t.parse_pdos() for (idx, subidx), (sm, pos, fmt) in t.pdos.items(): print(f"{idx:4X}:{subidx:02X} {sm} {pos} {fmt}") @@ -168,7 +168,7 @@ async def create_test(): await t.initialize(-i, await ec.find_free_address()) sdo = {} if t.has_mailbox(): - await t.to_operational(2) + await t.to_operational(MachineState.PRE_OPERATIONAL) odlist = await t.read_ODlist() for k, v in odlist.items():