Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 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
de3d129
config_io: Initial commit
zultron May 19, 2022
ddbd99b
base class: Migrate YAML access to ConfigIO and `importlib` refs
zultron May 19, 2022
5d3d4cb
cia_301: Migrate YAML access to ConfigIO and `importlib` refs
zultron May 19, 2022
ba77ba8
cia_402: Migrate YAML access to ConfigIO and `importlib` refs
zultron Jun 5, 2022
d9a5441
errors: Migrate YAML access to ConfigIO and `importlib` refs
zultron May 19, 2022
2c9618f
errors: Use importlib to read device error data files
zultron Jun 3, 2022
752087e
cia_301: Add distributed clock configuration
zultron Jun 9, 2022
2697568
cia_301: Key SDO, DCs data by model_id, not name
zultron Jun 11, 2022
d12f0f7
cia_301: Update device config & tests
zultron Jun 15, 2022
6bf1cbc
cia_301: Add trivial test for lcec <complexEntry>
zultron Jun 20, 2022
1bf6c14
devices: Migrate YAML access to ConfigIO and `importlib` refs
zultron Jun 3, 2022
ff32bb0
cia_301: Fix tests to work without `test_category`
zultron Jun 20, 2022
c2b2914
ethercat: Migrate YAML and ESI XML to ConfigIO and `importlib` refs
zultron Jun 3, 2022
bd4a90c
ethercat: Parse ESI <Dc/> tags; redo file reading logic
zultron Jun 7, 2022
fa9ee6c
lcec: Migrate YAML and ESI XML to ConfigIO and `importlib` refs
zultron Jun 3, 2022
173e8f2
devices: Add DC tests
zultron Jun 11, 2022
d7a9ed4
mgr: Migrate YAML access to ConfigIO and `importlib` refs
zultron May 19, 2022
f17cbd8
ethercat: ESI partial support for PDO parsing
zultron Jun 14, 2022
55dbfba
mgr_ros: Migrate YAML access to ConfigIO and `importlib` refs
zultron May 19, 2022
9bcf0f7
lcec: Generate `ethercat.conf.xml` configuration from bus scan
zultron Jun 15, 2022
02edf82
mgr_hal: Migrate YAML access to ConfigIO and `importlib` refs
zultron Jun 3, 2022
663a4d6
lcec: Add finer control over <complexEntry> elements
zultron Jun 20, 2022
e502cda
mgr_ros_hal: Migrate YAML access to ConfigIO and `importlib` refs
zultron Jun 5, 2022
be32852
devices: Update Bogus device ESI descriptions
zultron Jun 15, 2022
b844905
devices: Add error message translations to Inovance drives
zultron Jun 23, 2022
facf102
cia_402: Fix tests for devices inheriting ErrorDevice
zultron Jun 24, 2022
9ffd5b7
base class: Add "fault" to device feedback_out interface
zultron Jun 28, 2022
249cb15
mgr: Fix tests for devices inheriting ErrorDevice
zultron Jun 24, 2022
d59d920
cia_301: Add 'fault' to feedback_out test cases
zultron Jun 28, 2022
0ee3b34
cia_402: Set `fault` feedback on status word fault bit
zultron Jun 29, 2022
15e0ca6
cia_301: Move device parameter init into config class
zultron Jun 29, 2022
898960d
cia_301: Separate out SDO loading to new method in tests
zultron Jul 1, 2022
7480d91
ethercat: Remove device param NV methods
zultron Jun 29, 2022
26705a0
ethercat: Override SDO loading in tests
zultron Jul 1, 2022
77f42c4
devices: Move Inovance device param NV methods to config class
zultron Jun 29, 2022
c196dc8
cia_402: Perform homing in Device class
zultron Jun 28, 2022
6a91681
base device class: Init interfaces in init(), not constructor
zultron Jul 1, 2022
fa28ba8
interface: Enable adding attributes after init
zultron Jul 1, 2022
3887646
mgr: Simplify init_devices()
zultron Jun 29, 2022
f3263b9
base device class: Add data types to feedback_out interface
zultron Jul 1, 2022
d140e42
cia_402: Add data types to all interfaces; make transition `int`
zultron Jul 1, 2022
b6bac0c
errors: Add types to feedback_out interface
zultron Jul 1, 2022
415d3be
base class: Add `DebugInterface` class for tests
zultron Jul 10, 2022
30919d3
base class: Updates to `test_read_update_write` test
zultron Jul 10, 2022
436af23
errors: Update for recent Interface changes
zultron Jul 10, 2022
14c73f4
cia_301: Update for recent Interface changes
zultron Jul 10, 2022
1c8a0e5
cia_402: Updates for recent Interface changes
zultron Jul 10, 2022
1bf7a4c
devices: Updates for recent Interfaces changes
zultron Jul 10, 2022
84d5597
ethercat: Updates for recent Interfaces changes
zultron Jul 10, 2022
90b4d8a
hal: Updates for recent Interface & test changes
zultron Jul 10, 2022
5c7ba1b
lcec: Updates for recent Interface changes
zultron Jul 10, 2022
fe8d25d
mgr: Updates for recent Interface changes
zultron Jul 10, 2022
5df9d61
mgr_hal: Updates for recent Interfaces changes
zultron Jul 11, 2022
5f88a0d
mgr_ros: Updates for recent Interfaces changes
zultron Jul 11, 2022
daa05a2
mgr_ros_hal: Updates for recent Interfaces changes
zultron Jul 11, 2022
bc365cb
hal: Clean up HAL component initialization
zultron Jul 27, 2022
0d501a4
hal: Don't create HAL pins for interface keys with no HAL type
zultron Jul 11, 2022
ef50255
hal: Create pins from interfaces
zultron Jul 1, 2022
0504849
hal: Remove IO pins & simplify logic
zultron Jul 13, 2022
3d54c69
mgr: Rework to expose per-device command_in attributes
zultron Jul 10, 2022
ab7797c
mgr: Move several keys from command_in to command_out interface
zultron Jul 11, 2022
3075bcf
mgr: Move device interface key creation to `init_devices` method
zultron Jul 11, 2022
69414e3
mgr_hal: Updates for recent changes in tests
zultron Jul 11, 2022
3bc44c5
mgr_hal: Updates for recent changes in init()
zultron Jul 11, 2022
ec4ff20
mgr_hal: Fix HAL pin init and add fb_out and cmd_in HAL pins
zultron Jul 11, 2022
5604cef
mgr_ros: Updates for recent changes in init()
zultron Jul 11, 2022
e41d225
mgr_ros_hal: Updates for recent changes in init()
zultron Jul 11, 2022
8730566
config_io: Add `resource_path()` methodc
zultron Sep 20, 2022
144f739
base class: Print YAML file path in tests
zultron Sep 20, 2022
aa3df9b
Merge remote-tracking branch 'origin/foxy-devel' into zultron/2022-09…
zultron Sep 22, 2022
e88697b
Merge branch 'zultron/2022-09-19-rebase_for_PRs-3-minor-core' into zu…
zultron Nov 3, 2022
6968245
Merge branch 'zultron/2022-09-19-rebase_for_PRs-4-config_io' into zul…
zultron Nov 3, 2022
be00110
Merge branch 'zultron/2022-09-19-rebase_for_PRs-5-pdo_config' into zu…
zultron Nov 3, 2022
5b5c28f
Merge branch 'zultron/2022-09-19-rebase_for_PRs-6-errors' into zultro…
zultron Nov 3, 2022
cb602b4
Merge branch 'zultron/2022-09-19-rebase_for_PRs-7-fault_handling+sdo_…
zultron Nov 3, 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
185 changes: 133 additions & 52 deletions hw_device_mgr/cia_301/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .data_types import CiA301DataType
from .command import CiA301Command, CiA301SimCommand
from .command import CiA301Command, CiA301SimCommand, CiA301CommandException
from .sdo import CiA301SDO
from ..logging import Logging
from functools import cached_property


class CiA301Config:
Expand All @@ -26,13 +28,18 @@ class CiA301Config:
command_class = CiA301Command
sdo_class = CiA301SDO

init_params_nv = True

logger = Logging(__name__)

# Mapping of model_id to a dict of (index, subindex) to SDO object
_model_sdos = dict()
# Mapping of model_id to a dict of (index, subindex) to DC object
_model_dcs = dict()

def __init__(self, address=None, model_id=None):
self.address = address
self.model_id = self.format_model_id(model_id)
self._config = None

@classmethod
def format_model_id(cls, model_id):
Expand Down Expand Up @@ -98,48 +105,81 @@ 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]

@classmethod
def add_device_dcs(cls, dcs_data):
"""Add device model distributed clock descriptions."""
for model_id, dcs in dcs_data.items():
assert isinstance(dcs, list)
cls._model_dcs[model_id] = dcs
assert None not in cls._model_dcs

def dcs(self):
"""Get list of distributed clocks for this device."""
return self._model_dcs[self.model_id]

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, force=False, **kwargs):
# Get SDO object
sdo = self.sdo(sdo)
# Check before setting value to avoid unnecessary NVRAM writes
res_raw = self.command().upload(
address=self.address,
index=sdo.index,
subindex=sdo.subindex,
datatype=sdo.data_type,
)
if sdo.data_type(res_raw) == val:
return # SDO value already correct
if not force:
# Check before setting value to avoid unnecessary NVRAM writes
res_raw = self.command().upload(
address=self.address,
index=sdo.index,
subindex=sdo.subindex,
datatype=sdo.data_type,
**kwargs,
)
if sdo.data_type(res_raw) == val:
return # SDO value already correct
if dry_run:
self.logger.info(f"Dry run: download {val} to {sdo}")
return
self.logger.info(f"{self} param download {sdo} = {val}")
self.command().download(
address=self.address,
index=sdo.index,
subindex=sdo.subindex,
value=val,
datatype=sdo.data_type,
**kwargs,
)

#
Expand Down Expand Up @@ -172,8 +212,7 @@ def set_device_config(cls, config):
- `pdo_mapping`: PDO mapping SM types only; `dict`:
- `index`: Index of PDO mapping object
- `entries`: Dictionary objects to be mapped; `dict`:
- `index`: Index of dictionary object
- `subindex`: Subindex of dictionary object (default 0)
- `index`: Index of dictionary object, e.g. "6041h" or "1A00-03h"
- `name`: Name, a handle for the data object
- `bits`: Instead of `name`, break out individual bits,
names specified by a `list`
Expand All @@ -187,52 +226,94 @@ 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

@property
@classmethod
def gen_config(cls, model_id, address):
bus, position = address
# Find matching config
for conf in cls._device_config:
if "vendor_id" not in conf:
continue # In tests only
if model_id != (conf["vendor_id"], conf["product_code"]):
continue
if bus != conf["bus"]:
continue
if position not in conf["positions"]:
continue
break
else:
raise KeyError(f"No config for device at {address}")
# Prune & return config
return cls.munge_config(conf, position)

@cached_property
def config(self):
if self._config is None:
# Find matching config
for conf in self._device_config:
if "vendor_id" not in conf:
continue # In tests only
if self.model_id != (conf["vendor_id"], conf["product_code"]):
continue
if self.bus != conf["bus"]:
continue
if self.position not in conf["positions"]:
continue
break
else:
raise KeyError(f"No config for device at {self.address}")
# Prune & cache config
self._config = self.munge_config(conf)

# Return cached config
return self._config

def write_config_param_values(self):
return self.gen_config(self.model_id, self.address)

def get_device_params_nv(self):
"""
Return whether device is in non-volatile params mode.

Drives with parameter volatile/non-volatile mode must overload
this.
"""
return False

def set_device_params_nv(self, nv=True, dry_run=False):
"""
Set device params to non-volatile/volatile mode.

Drives with parameter volatile/non-volatile mode must overload
this.
"""
pass

def initialize_params(self, dry_run=False):
if self.init_params_nv:
# To save NVRAM wear, don't write if all params are correct
all_correct = True
for sdo, value in self.config["param_values"].items():
if self.upload(sdo) != value:
all_correct = False
break
if all_correct:
self.logger.info(f"{self} param values already correct")
return
# Save current NV mode setting & set NV mode
self._old_device_params_nv = self.get_device_params_nv()
self.logger.info(f"{self} setting device params in NV mode)")
self.set_device_params_nv(dry_run=dry_run)
else:
self.logger.info(f"{self} setting device params in volatile mode)")

# Something needs changing
for sdo, value in self.config["param_values"].items():
self.download(sdo, value)
self.download(sdo, value, dry_run=dry_run, force=True)

if self.init_params_nv and not self._old_device_params_nv:
self.logger.info(
f"{self} returning device params to volatile mode)"
)
self.set_device_params_nv(
nv=self._old_device_params_nv, dry_run=dry_run
)

#
# Scan bus device config factory
Expand Down
29 changes: 23 additions & 6 deletions hw_device_mgr/cia_301/device.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ..device import Device, SimDevice
from .config import CiA301Config, CiA301SimConfig
from .data_types import CiA301DataType
from functools import cached_property, lru_cache


class CiA301Device(Device):
Expand All @@ -19,6 +20,9 @@ class CiA301Device(Device):
feedback_in_data_types = dict(online="bit", oper="bit")
feedback_in_defaults = dict(online=False, oper=False)

feedback_out_data_types = feedback_in_data_types
feedback_out_defaults = feedback_in_defaults

def __init__(self, address=None, **kwargs):
if isinstance(address, self.config_class):
self.config = address
Expand All @@ -30,6 +34,7 @@ def __init__(self, address=None, **kwargs):
super().__init__(address=address, **kwargs)

@classmethod
@lru_cache
def device_model_id(cls):
"""
Return unique device model identifier.
Expand All @@ -40,7 +45,7 @@ def device_model_id(cls):
model_id = cls.vendor_id, cls.product_code
return cls.config_class.format_model_id(model_id)

@property
@cached_property
def model_id(self):
return self.device_model_id()

Expand All @@ -49,8 +54,9 @@ def set_device_config(cls, device_config):
assert device_config
cls.config_class.set_device_config(device_config)

def write_config_param_values(self):
self.config.write_config_param_values()
def init(self, **kwargs):
super().init(**kwargs)
self.config.initialize_params()

def get_feedback(self):
fb_out = super().get_feedback()
Expand Down Expand Up @@ -80,8 +86,8 @@ def log_operational_changes(self):
def munge_sdo_data(cls, sdo_data):
# Turn per-model name SDO data from YAML into per-model_id SDO data
res = dict()
for model_name, sd in sdo_data.items():
device_cls = cls.get_model_by_name(model_name)
for model_id, sd in sdo_data.items():
device_cls = cls.get_model(model_id)
model_id = device_cls.device_model_id()
res[model_id] = sd
assert res
Expand All @@ -98,6 +104,16 @@ def add_device_sdos(cls, sdo_data):
"""
cls.config_class.add_device_sdos(cls.munge_sdo_data(sdo_data))

@classmethod
def add_device_dcs(cls, dcs_data):
"""
Configure device distributed clocks.

Pass to the `Config` class the information needed to configure
DCs for this `model_id`.
"""
cls.config_class.add_device_dcs(dcs_data)

@classmethod
def get_device(cls, address=None, **kwargs):
registry = cls._address_registry.setdefault(cls.name, dict())
Expand Down Expand Up @@ -178,10 +194,11 @@ def sim_device_data_address(cls, sim_device_data):
return model_id

@classmethod
def init_sim(cls, *, sim_device_data, sdo_data):
def init_sim(cls, *, sim_device_data, sdo_data, dcs_data):
super().init_sim(sim_device_data=sim_device_data)
sim_device_data = cls._sim_device_data[cls.category]
cls.add_device_sdos(sdo_data)
cls.add_device_dcs(dcs_data)
cls.config_class.init_sim(sim_device_data=sim_device_data)

def set_sim_feedback(self, **kwargs):
Expand Down
Loading