Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
f24c52b
lcec: Add `ethercat.conf.xml` PDO entry `scale`+`offset` attributes
zultron Apr 26, 2023
c173a78
async_task_queue.py: Run formatters & fix pydocstyle issues
zultron Apr 27, 2023
05916b0
base class: Run formatters
zultron Apr 27, 2023
3f59f24
cia_301: Run formatters & fix pyflake errors
zultron Apr 27, 2023
0cc5b10
cia_402: Run formatters & fix pyflake errors
zultron Apr 27, 2023
38a770c
errors: Run formatters
zultron Apr 27, 2023
bf3c46d
ethercat: Run formatters & fix pyflake errors
zultron Apr 27, 2023
ff7cf84
hal: Run formatters
zultron Apr 27, 2023
24ff02c
devices: Run formatters
zultron Apr 27, 2023
bd2021b
lcec: Run formatters
zultron Apr 27, 2023
d3b4653
mgr: Run formatters & fix pyflake errors
zultron Apr 27, 2023
c641e5f
mgr_ros: Run formatters
zultron Apr 27, 2023
f8b7dd5
mgr_ros_hal: Run formatters
zultron Apr 27, 2023
88db231
cia_301: Fix param update logic
zultron Apr 19, 2023
2cc6198
hw_device_mgr: Tweak Inovance SV660 sim drive inheritance
zultron Feb 27, 2024
b4dd276
Split out `test_read_update_write` tests into separate modules
zultron Feb 26, 2024
a8a4517
Enable read_update_write tests to stabilize after one update
zultron Feb 27, 2024
bcd5713
Update test_read_update_write fixtures to set initial device state
zultron Feb 28, 2024
5f5393c
Improve logging
zultron Feb 28, 2024
0d8ffe9
Update pre-commit hooks and run formatters
zultron Feb 28, 2024
c7f5da3
Fix tests after pytest version update
zultron Feb 28, 2024
549c0ea
cia_402: Expose drive setpoint ack from drive
zultron Mar 11, 2024
3887b5f
cia_402: No-op improve HM and PP control_word flag interface
zultron Mar 11, 2024
a67454a
Enable devices to request mgr fast track the next update cycle
zultron Mar 11, 2024
394d411
cia_402: Fast track the next update cycle on PP move request
zultron Mar 12, 2024
0493cda
cia_402: Fast track update when PP move request is active
zultron Mar 12, 2024
025b25b
lcec: Add `LCECConfig.gen_ethercat_xml(devs=devs)` arg
zultron Mar 13, 2024
1c3207c
fixup! Enable devices to request mgr fast track the next update cycle
zultron Mar 14, 2024
a105cc9
cia_402: Fix tests for PP move fasttrack
zultron Mar 14, 2024
f2c5cb4
cia_301: In sim command, add optional sleep time in seconds
zultron Mar 21, 2024
132543c
cia_301: Improve param logging; simplify download() method
zultron Mar 25, 2024
078409b
mgr: Allow fault to preempt init state
zultron Mar 21, 2024
0d68456
mgr: Only require devices be online to exit init state
zultron Mar 21, 2024
32f4ce6
mgr: Allow top-level mgr to leave init state at any time
zultron Mar 21, 2024
1b3e47f
Add `reset_fault` to `command_in` interface
zultron Mar 26, 2024
97f0b38
cia_301: Rework param updates
zultron Mar 25, 2024
91bdb0e
cia_402: Don't overwrite lower layer status
zultron Mar 26, 2024
db9bab2
Fix goal state logging
zultron Mar 26, 2024
58eca80
Remove random bits of unneeded code
zultron Mar 26, 2024
d9de222
mgr: Fix transitions out of init state
zultron Mar 26, 2024
d395b9e
Inovance SV660: Clear battery encoder errors on reset
zultron Mar 26, 2024
c020028
cia_301: Increase goal timeout when writing params
zultron Mar 26, 2024
7b1be09
Add device class `init_class()` method
zultron Apr 3, 2024
190ad1f
Fix tests after cia_301 param update rework
zultron Apr 3, 2024
0437edc
cia_301: Fix param init
zultron Apr 8, 2024
5305987
cia_402: Track status word following error bit
zultron Apr 9, 2024
5fdc477
setup.py: Only build `multilatency` HAL comp for ROS 2
zultron Apr 10, 2024
76755d3
cia_402: Stay in QUICK STOP ACTIVE after fault command
zultron Apr 16, 2024
3fd687e
cia_402: Fix tests after QUICK STOP ACTIVE change
zultron May 7, 2024
5fe7bd3
devices: Fix swapped test class categories
zultron May 8, 2024
41d1c29
base device class: Allow controlled overriding interfaces
zultron May 8, 2024
5a26a29
devices: Re-add `test_read_update_write()` test
zultron May 8, 2024
259abdf
devices: Ignore Inovance SV660 error code least significant word
zultron May 7, 2024
156ba5a
Add SV670N ESI file
dspins Sep 11, 2024
6c13a83
Merged in Add-SV670N-ESI-xml-file (pull request #2)
dspins Sep 11, 2024
f6e1340
add SV670N py/yaml, disable duplicate xml tag
Sep 17, 2024
a9f3fcb
update sv670 py for sim
Sep 18, 2024
c1c2384
Merged in PP-4572-add-SV670N (pull request #3)
Sep 27, 2024
8e8b89a
PP-4571 - add missing SV670N params
Oct 2, 2024
679dd00
add 2006-08h
Oct 2, 2024
731ab6c
Merged in PP-4571-more-SV670N-updates (pull request #4)
Oct 2, 2024
07d63b6
PP-4619 - ignore ecat alias 9999
Oct 15, 2024
4031142
Merged in PP-4619-ignore-alias-9999 (pull request #5)
Nov 1, 2024
5a3aa2b
PP-4605 - add PV and PT wrappers
Nov 8, 2024
0b5d958
disable mode false-positives for now
Nov 11, 2024
067d181
sim updates
Nov 12, 2024
bef33f2
restore control_mode warning
Nov 21, 2024
f00c64d
Revert "PP-4619 - ignore ecat alias 9999"
zultron Nov 22, 2024
8df413d
Clean up & dedup SV670 configs
zultron Nov 22, 2024
58d95ef
cia_301, cia_402: Log more information about state changes
zultron Nov 12, 2024
bc3dba6
cia_402: Don't attempt state transitions until params are written
zultron Nov 14, 2024
8e6a40c
Implement drive "shutdown" command
zultron Nov 22, 2024
93adc51
Merged in PP-4605-add-PV-and-PT-wrappers (pull request #6)
Dec 16, 2024
1c911c2
cia_301, cia_402: Log more information about state changes
zultron Nov 12, 2024
71023dd
cia_402: Don't attempt state transitions until params are written
zultron Nov 14, 2024
a8ef41d
Revert "PP-4619 - ignore ecat alias 9999"
zultron Nov 22, 2024
a2722db
Clean up & dedup SV670 configs
zultron Nov 22, 2024
71e720a
Implement drive "shutdown" command
zultron Nov 22, 2024
fbe5703
fixup! Implement drive "shutdown" command
zultron Feb 3, 2025
88e7ac1
Run formatters
zultron Mar 4, 2025
65c7940
cia_301: Tweak bus scan log message
zultron Feb 4, 2025
7313276
logging: Add `get_logger()` convenience function
zultron Feb 6, 2025
4bb8e2d
cia_301.command: Add `format_address` convenience function
zultron Feb 6, 2025
ee92d9e
cia_301.config: Add `CiA301ConfigException`
zultron Feb 6, 2025
2350f35
Add `CachedAttrMixin` to help clear stale device info after changes
zultron Feb 12, 2025
436a664
ethercat, lcec: Add routines to set EtherCAT device alias
zultron Feb 5, 2025
5548642
cia_301: Unconfuse `get_device()` and `scan_bus()` kwargs
zultron Feb 19, 2025
be39f44
lcec: Fix `ethercat` command problems when alias is zero
zultron Feb 24, 2025
001e924
Merge branch 'zultron/2024-11-22-drive_shutdown' into HEAD
zultron Mar 7, 2025
8b7d15f
fixup! lcec: Fix `ethercat` command problems when alias is zero
zultron Mar 25, 2025
244eae9
cia_301: Stop param updates during shutdown
zultron Mar 25, 2025
8f1ce21
Merged in 770MXE_support_for_new_Inovance_STD60N_driver_for_microarc4…
dspins Apr 12, 2025
0a89d32
Merge commit 'fbe57036e4' into HEAD
zultron Apr 19, 2025
31fa934
merge zultron/2025-05-01-release2-develop2-merge to develop2
May 5, 2025
0e70fd5
Merged in restore-develop2 (pull request #9)
May 5, 2025
d7ae787
Merged in PP-4678-deprecate-velocity-torque-requests (pull request #10)
May 22, 2025
35a1d0a
cia_402: Fix drive transition from quick stop
zultron Jul 22, 2025
f029d3d
cia_402: Add `quick_stop` pin to command QUICK STOP ACTIVE mode
zultron Jul 25, 2025
a7bc25c
cia_402: When commanding switch on disabled, don't quick stop
zultron Jul 29, 2025
5da0030
devices: Add SV660N E939.0 error
zultron Jul 30, 2025
c3d18b2
lcec: Add `ethercat.conf.xml` PDO entry `scale`+`offset` attributes
zultron Apr 26, 2023
9ebe11f
cia_301: Fix some config address confusion
zultron May 1, 2023
9bb46ef
lcec: Add `overlappingPdos` attr to ethercat.conf.xml slave tag
zultron May 1, 2023
8c49bca
cia_301: Add `overlappingPdos` key to test data
zultron May 1, 2023
415d550
Fix setting of overlappingPdos flag.
kylc May 1, 2023
0c929dc
lcec: Rename overlapping PDOs config key
zultron May 2, 2023
34b7c44
base class: Add sim junction box to tests
zultron May 2, 2023
3e80bea
cia_301: Add sim junction box to tests
zultron May 2, 2023
665750b
cia_402: Add sim junction box to tests
zultron May 2, 2023
4a38fdc
devices: Add sim junction box and IO module classes
zultron May 2, 2023
451d0b1
errors: Add sim junction box to tests
zultron May 2, 2023
609a695
hal: Add sim junction box to tests
zultron May 2, 2023
ffe246f
ethercat: Add sim junction box to tests
zultron May 2, 2023
ab74dca
lcec: Support devices with no sync managers or object dictionaries
zultron May 2, 2023
27fbcd7
mgr: Don't send commands to non-CiA402 devices
zultron May 2, 2023
56354b4
mgr_ros: Fix exception when handling exception in main loop
zultron May 2, 2023
5b59d46
ethercat: No-op remove comment
zultron May 10, 2023
29bb53b
cia_301: Allow per-device PDO entry values in device config
zultron May 14, 2023
77e58e0
lcec: Update test for per-drive PDO `scale` values
zultron May 14, 2023
771a1b0
.github: Update CI & fix build failures
zultron May 15, 2023
1d116e5
docker: Fix `python-attrs-pip` rosdep key
zultron May 15, 2023
8eb79b9
pre-commit: Remove inapplicable checks
zultron May 15, 2023
edbff9f
mgr_ros: Treat empty sim device data path as unset
zultron May 22, 2023
ce1a87b
Fix ROS packaging
zultron Aug 4, 2025
c3762ae
mgr_ros: Log sim device data YAML file path
zultron Aug 4, 2025
c9011a4
Update pre-commit and fix formatting errors
zultron Aug 6, 2025
a8b992d
Fix tests for "cia_402: [...] switch on disabled, don't quick stop"
zultron Aug 6, 2025
f0e39a6
mgr_ros: Fix sim_device_data handling
zultron Aug 6, 2025
c753669
cia_301: Fix logic for devices that don't need params set
zultron Aug 7, 2025
96a3b72
Update pre-commit hooks & GH Actions
zultron Aug 7, 2025
f577727
Merge remote-tracking branch 'origin/humble-devel' into zultron/2025-…
zultron Aug 8, 2025
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
# free up a lot of stuff from /usr/local
sudo rm -rf /usr/local
df -h
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Cache upstream workspace
uses: pat-s/always-upload-cache@v2.1.5
with:
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
uses: ros-industrial/industrial_ci@master
env: ${{ matrix.env }}
- name: Upload test artifacts (on failure)
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
if: failure() && (steps.ici.outputs.run_target_test || steps.ici.outputs.target_test_results)
with:
name: test-results-${{ matrix.env.IMAGE }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/format.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/setup-python@v3
- name: Install black
run: sudo -H pip3 install black
- uses: pre-commit/action@v3.0.0
- uses: pre-commit/action@v3.0.1
id: precommit
- name: Upload pre-commit changes
if: failure() && steps.precommit.outcome == 'failure'
Expand Down
13 changes: 0 additions & 13 deletions .github/workflows/upstream_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,6 @@ cat >$UPSTREAM_ROSDEP_YML <<-EOF
ubuntu: [machinekit-hal-dev]
EOF

# FIXME Waiting for these to shake out:
# https://github.com/ros/rosdistro/pull/36888
# https://github.com/ros/rosdistro/pull/36893
cat >>$UPSTREAM_ROSDEP_YML <<-EOF
python-attrs-pip:
debian:
pip:
packages: [attrs]
ubuntu:
pip:
packages: [attrs]
EOF

echo "yaml file://$UPSTREAM_ROSDEP_YML" > \
/etc/ros/rosdep/sources.list.d/10-local.list
rosdep update
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: check-ast
- id: fix-byte-order-marker
Expand All @@ -22,14 +22,14 @@ repos:
args: [-l, "80"]
types: [python]

- repo: https://github.com/myint/docformatter
rev: v1.7.5
- repo: https://github.com/PyCQA/docformatter
rev: v1.7.7
hooks:
- id: docformatter
args: [--pre-summary-newline, --in-place]

- repo: https://github.com/pycqa/flake8
rev: 6.1.0
rev: 7.3.0
hooks:
- id: flake8

Expand Down
205 changes: 103 additions & 102 deletions hw_device_mgr/async_task_queue.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,35 @@
import threading
import queue
from functools import cached_property, lru_cache
from functools import lru_cache


class AsyncTaskQueue:
"""
Generic class to process commands in an asynchronous queue.

Multiple instances may `enqueue` commands for a singleton worker
instance processing those in a thread with the `process_queue`
command (implemented in subclasses).
Multiple instances with separate queues `enqueue` commands. A single worker
thread rotates among queues, pulling off the next command and executing it.
"""

# Class-level dict of singleton queues
queues = dict()
# Idle wait time
idle_wait = 0.1 # s

@classmethod
@lru_cache
def _get_queue(cls, name):
return cls.queues.setdefault(name, queue.Queue())

@cached_property
def cmd_queue(self):
"""Property returning the command queue singleton instance."""
return self._get_queue("cmd")

@cached_property
def progress_queue(self):
"""Property returning the progress queue singleton instance."""
return self._get_queue("progress")
# Class-level singleton queue data
_queue_names = list()
_cmd_queues = dict()
_errors = dict()
_queue_stop = dict()

def __init__(self):
self.cmd_version = 0
_queues = list()

#
# Worker-related methods
# Class methods: worker thread is managed by the class itself
#
# There should only ever be a single worker thread running, even if many
# AsyncTaskQueue instances are enqueuing commands.
#

def process_cmd(self, cmd):
"""
Process one command from command queue.

Main worker function to be implemented in subclasses.
"""
pass

def _work(self):
"""
Worker thread loop callback.

Pop commands off command queue and pass to `process_cmd` method.
Repeat untill `join` method is called.
"""
while True:
cmd_version, cmd = self.cmd_queue.get()
self.process_cmd(cmd)
self.progress_queue.put(cmd_version)
self.cmd_queue.task_done()
@classmethod
@lru_cache
def lock(cls):
return threading.Lock()

@classmethod
@lru_cache # Only needs to run once per class
Expand All @@ -71,70 +41,101 @@ def start(cls):
but will only ever create a single worker instance running a
single thread.
"""
if hasattr(cls, "_worker_instance"):
assert type(cls._worker_instance) is cls # Subclass sanity
return # Already started
cls._worker_instance = cls()
cls._worker_instance._start()
assert not hasattr(cls, "_thread") # Sanity
cls._thread = threading.Thread(target=cls._work, daemon=True)
cls._stop = threading.Event()
cls._have_data = threading.Event()
cls._have_error = threading.Event()
cls._thread.start()

@lru_cache # Only needs to run once per worker instance
def _start(self):
# Start the new thread from the worker instance
threading.Thread(target=self._work, daemon=True).start()
@classmethod
def _work(cls):
"""
Worker thread loop callback.

#
# Command-side-related methods
#
# Multiple instances may exist, but must all be running in the same
# (probably main) thread.
Pop commands off command queue and executes method with args.
Repeat untill `join` method is called.
"""
while not cls._stop.is_set():
cls._have_data.wait()
for cmd_queue in cls._queues:
cmd_queue.churn()
with cls.lock(): # Synchronize queue check & have_data clear
if all(i.empty for i in cls._queues):
cls._have_data.clear() # All cmd queues empty

@classmethod
def _bump_cmd_version(cls):
# Bump the global (class-level) command version and return it
if not hasattr(cls, "_cmd_version"):
cls._cmd_version = 0
cls._cmd_version += 1
return cls._cmd_version

def enqueue(self, cmd):
"""Enqueue one command."""
# Automatically start thread
self.start()
# Set local command version to incremented global version
ver = self.cmd_version = self._bump_cmd_version()
self.cmd_queue.put((ver, cmd))
def join(cls):
"""Block until queue processed and join worker thread."""
assert hasattr(cls, "_thread")
# Unblock worker thread & signal to stop
with cls.lock(): # Synchronize stop & have_data set
cls._stop.set()
cls._have_data.set()
for stop in cls._queue_stop.values():
stop.set()
# Join queue
for cmd_queue in cls._queues:
cmd_queue.join_queue()
# Join worker thread
cls._thread.join()

@classmethod
def _get_progress_version(cls):
if not hasattr(cls, "_progress_version"):
cls._progress_version = 0
# Process the progress queue
progress = cls._get_queue("progress")
#
# Instance methods: Named instances
#

def __init__(self, name):
self.name = name
self.queue = queue.Queue()
self.error = None
self.stop = threading.Event()
self._queues.append(self)

def churn(self):
"""Pop command off queue and execute it."""
if self.queue.empty():
return
if self.stop.is_set():
return
method, args, kwargs = self.queue.get()
try:
while True:
cls._progress_version = progress.get(block=False)
progress.task_done()
except queue.Empty:
pass
return cls._progress_version
method(*args, **kwargs)
except Exception as e:
# Put exception in errors & stop queue
with self.lock():
self.stop.set()
self.error = (e, method, args, kwargs)
self._have_error.set()
self.queue.task_done()

@property
def progress_version(self):
"""Return version of most recent processed command."""
return self._get_progress_version()
def empty(self):
return self.queue.empty()

def all_cmds_complete(self):
"""
Return `True` if all commands enqueued by this instance were processed.
def join_queue(self):
"""Flush and stop queue."""
self.flush_queue_and_clear_error()
self.queue.join()

This may return `True` for one instance while returning `False`
for another instance with commands still waiting to be
processed.
"""
return self.progress_version >= self.cmd_version
def enqueue(self, method, *args, **kwargs):
"""Enqueue one command."""
self.start() # Automatically start class thread worker
with self.lock(): # Synchronize queue put and have_data set
assert not self.stop.is_set()
self.queue.put((method, args, kwargs))
self._have_data.set()

def join(self):
"""Join worker thread after queue is drained; blocks."""
self.cmd_queue.join() # Wait for worker to drain cmd queue & join
self.all_cmds_complete() # Drain progress queue
self.progress_queue.join() # & join
@property
def started(self):
return hasattr(self, "_thread")

def flush_queue_and_clear_error(self):
with self.lock():
self.stop.set() # No queue processing while flushing
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
self.error = None
if self.started and not any(i.error for i in self._queues):
self._have_error.clear()
self.stop.clear()
14 changes: 14 additions & 0 deletions hw_device_mgr/cached_attr_mgr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CachedAttrMixin:
def clear_cached_properties(self, *args):
"""
Clear `cached_property` and `lru_func` values.

Subclasses should remove cached values defined in their class by
overloading this function:

def clear_cached_properties(self, *args):
super().clear_cached_properties("prop1", "prop2", *args)
"""
props = set(args)
for prop in props:
self.__dict__.pop(prop, None)
16 changes: 0 additions & 16 deletions hw_device_mgr/cia_301/async_params.py

This file was deleted.

Loading
Loading