Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8207612
Base tests: Minor updates
zultron Jun 8, 2022
04063cc
base class: Reset `feeback_in` interface in `ready()` method
zultron Jul 15, 2022
3f159f5
cia_301: Pass `**kwargs` through config to command class
zultron Sep 14, 2022
2ec70fe
lcec: Add option to suppress `ethercat` cmd stderr output
zultron Sep 14, 2022
0063009
cia_301: Add method to dump drive params to config object
zultron Sep 14, 2022
86f5e7a
lcec: Test fixture tweak
zultron Jun 11, 2022
541bb31
lcec: Accept negative numbers when setting int-type params
zultron Jul 21, 2022
06543b7
lcec: Add parsing of `ethercat upload -t string` output
zultron Sep 14, 2022
fb7286e
mgr: Catch KeyboardInterrupt in main loop
zultron Jun 24, 2022
d6c8ea8
cia_301: Redo device_config munging
zultron Jun 7, 2022
9e2db0a
cia_402: Tweak log messages
zultron Jul 15, 2022
dbc6679
cia_402: Don't print redundant "Goal not reached" logs
zultron Jul 21, 2022
e12f9ef
errors: Fix class bitrot
zultron Jun 24, 2022
9c8d297
errors: When error code changes, log error code & description
zultron Jul 21, 2022
f044723
devices: Update SV660 ESI, adding extra objects from manual
zultron Sep 13, 2022
4d5cb1d
device base class: Replace `index` attribute with `addr_slug`
zultron Jun 29, 2022
9829c98
hal: Generate HAL pin prefix from `address` attribute
zultron Jun 29, 2022
54d997b
hal: Conditionally use 64-bit int data types
zultron May 18, 2022
968030e
mgr: Following base class, remove `index` arg from `init()`
zultron Sep 19, 2022
aa3df9b
Merge remote-tracking branch 'origin/foxy-devel' into zultron/2022-09…
zultron Sep 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion hw_device_mgr/cia_301/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def scan_bus(self, bus=0):
"""Scan bus, returning list of addresses and IDs for each device."""

@abc.abstractmethod
def upload(self, address=None, index=None, subindex=0, datatype=None):
def upload(
self, address=None, index=None, subindex=0, datatype=None, **kwargs
):
"""Upload a value from a device SDO."""

@abc.abstractmethod
Expand All @@ -36,6 +38,7 @@ def download(
subindex=0,
value=None,
datatype=None,
**kwargs,
):
"""Download a value to a device SDO."""

Expand Down
53 changes: 35 additions & 18 deletions hw_device_mgr/cia_301/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .data_types import CiA301DataType
from .command import CiA301Command, CiA301SimCommand
from .command import CiA301Command, CiA301SimCommand, CiA301CommandException
from .sdo import CiA301SDO
from functools import cached_property


class CiA301Config:
Expand Down Expand Up @@ -98,28 +99,45 @@ def sdo_ix(cls, ix):
ix = (dtc.uint16(ix[0]), dtc.uint8(ix[1]))
return ix

@cached_property
def sdos(self):
assert self.model_id in self._model_sdos
return self._model_sdos[self.model_id].values()

def sdo(self, ix):
if isinstance(ix, self.sdo_class):
return ix
ix = self.sdo_ix(ix)
return self._model_sdos[self.model_id][ix]

def dump_param_values(self):
res = dict()
for sdo in self.sdos:
try:
res[sdo] = self.upload(sdo, stderr_to_devnull=True)
except CiA301CommandException as e:
# Objects may not exist, like variable length PDO mappings
self.logger.debug(f"Upload {sdo} failed: {e}")
pass
return res

#
# Param read/write
#

def upload(self, sdo):
def upload(self, sdo, **kwargs):
# Get SDO object
sdo = self.sdo(sdo)
res_raw = self.command().upload(
address=self.address,
index=sdo.index,
subindex=sdo.subindex,
datatype=sdo.data_type,
**kwargs,
)
return sdo.data_type(res_raw)

def download(self, sdo, val, dry_run=False):
def download(self, sdo, val, dry_run=False, **kwargs):
# Get SDO object
sdo = self.sdo(sdo)
# Check before setting value to avoid unnecessary NVRAM writes
Expand All @@ -128,6 +146,7 @@ def download(self, sdo, val, dry_run=False):
index=sdo.index,
subindex=sdo.subindex,
datatype=sdo.data_type,
**kwargs,
)
if sdo.data_type(res_raw) == val:
return # SDO value already correct
Expand All @@ -140,6 +159,7 @@ def download(self, sdo, val, dry_run=False):
subindex=sdo.subindex,
value=val,
datatype=sdo.data_type,
**kwargs,
)

#
Expand Down Expand Up @@ -187,24 +207,21 @@ def set_device_config(cls, config):
cls._device_config.clear()
cls._device_config.extend(config)

def munge_config(self, config_raw):
@classmethod
def munge_config(cls, config_raw, position):
config_cooked = config_raw.copy()
# Convert model ID ints
model_id = (config_raw["vendor_id"], config_raw["product_code"])
model_id = cls.format_model_id(model_id)
config_cooked["vendor_id"], config_cooked["product_code"] = model_id
# Flatten out param_values key
pv = dict()
config_cooked["param_values"] = dict()
for ix, val in config_raw.get("param_values", dict()).items():
ix = self.sdo_class.parse_idx_str(ix)
ix = cls.sdo_class.parse_idx_str(ix)
if isinstance(val, list):
pos_ix = config_raw["positions"].index(self.position)
pos_ix = config_raw["positions"].index(position)
val = val[pos_ix]
pv[ix] = val
dtc = self.data_type_class
config_raw["vendor_id"] = dtc.uint32(config_raw["vendor_id"])
config_raw["product_code"] = dtc.uint32(config_raw["product_code"])
config_cooked = dict(
vendor_id=config_raw["vendor_id"],
product_code=config_raw["product_code"],
param_values=pv,
sync_manager=config_raw.get("sync_manager", dict()),
)
config_cooked["param_values"][ix] = val
# Return pruned config dict
return config_cooked

Expand All @@ -225,7 +242,7 @@ def config(self):
else:
raise KeyError(f"No config for device at {self.address}")
# Prune & cache config
self._config = self.munge_config(conf)
self._config = self.munge_config(conf, self.position)

# Return cached config
return self._config
Expand Down
12 changes: 6 additions & 6 deletions hw_device_mgr/cia_402/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ def get_feedback(self):
goal_reasons.append(f"state flag {flag_name} != {not flag_val}")

if not goal_reached:
fb_out.update(
goal_reached=False, goal_reason="; ".join(goal_reasons)
)
self.logger.debug(f"Device {self.address}: Goal not reached:")
goal_reason = "; ".join(goal_reasons)
fb_out.update(goal_reached=False, goal_reason=goal_reason)
if fb_out.changed("goal_reason"):
self.logger.debug(f"{self}: Goal not reached: {goal_reason}")
return fb_out

state_bits = {
Expand Down Expand Up @@ -492,13 +492,13 @@ def set_sim_feedback(self):
# Log changes
if self.sim_feedback.changed("control_mode_fb"):
cm = self.sim_feedback.get("control_mode_fb")
self.logger.info(f"{self} next control_mode_fb: 0x{cm:04X}")
self.logger.info(f"{self} sim control_mode_fb: 0x{cm:04X}")
if self.sim_feedback.changed("status_word"):
sw = self.sim_feedback.get("status_word")
flags = ",".join(k for k, v in sw_flags.items() if v)
flags = f" flags: {flags}" if flags else ""
self.logger.info(
f"{self} sim next status_word: 0x{sw:04X} {state} {flags}"
f"{self} sim status_word: 0x{sw:04X} {state} {flags}"
)

return sfb
Expand Down
24 changes: 21 additions & 3 deletions hw_device_mgr/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from .logging import Logging
from .interface import Interface
from .data_types import DataType
from functools import cached_property
import re


class Device(abc.ABC):
Expand Down Expand Up @@ -36,15 +38,15 @@ def __init__(self, address=None):
self.address = address
self.init_interfaces()

def init(self, index=None):
def init(self):
"""
Initialize device.

Subclasses may implement `init()` for extra initialization
outside the constructor. Implementations should always call
`super().init()`.
"""
self.index = index
pass

@classmethod
def merge_dict_attrs(cls, attr):
Expand All @@ -62,6 +64,21 @@ def merge_dict_attrs(cls, attr):
res.update(c_attr)
return res

slug_separator = "."

@cached_property
def addr_slug(self):
"""
Return a slug generated from the device address.

The slug is computed by separating numeric components of the
device `address` string with the `slug_separator` character,
default `.`, e.g. `(0,5)` -> `0.5`. This is intended to be
useful for inclusion into identifiers.
"""
addr_prefix = re.sub(r"[^0-9]+", self.slug_separator, str(self.address))
return addr_prefix.strip(self.slug_separator)

def init_interfaces(self):
intfs = self._interfaces = dict()
dt_name2cls = self.data_type_class.by_shared_name
Expand Down Expand Up @@ -95,6 +112,7 @@ def interface_changed(self, what, key, return_vals=False):

def read(self):
"""Read `feedback_in` from hardware interface."""
self._interfaces["feedback_in"].set()

def get_feedback(self):
"""Process `feedback_in` and return `feedback_out` interface."""
Expand Down Expand Up @@ -369,7 +387,7 @@ def read(self):
"""Read `feedback_in` from hardware interface."""
super().read()
sfb = self._interfaces["sim_feedback"].get()
self._interfaces["feedback_in"].set(**sfb)
self._interfaces["feedback_in"].update(**sfb)

def set_sim_feedback(self):
"""Simulate feedback from command and feedback."""
Expand Down
Loading