-
Notifications
You must be signed in to change notification settings - Fork 197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LocalNode improvements for TPDOs #524
base: master
Are you sure you want to change the base?
LocalNode improvements for TPDOs #524
Conversation
I can also pull in grantweiss-RaymondCorp/canopen@2305314 if someone wants to confirm this is correct canopen behavior. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition, but I think the implementation could be simplified by re-using more existing code. And some style nit-picks for consistency, see below.
canopen/node/local.py
Outdated
def _tpdo_configuration_write(self, index, subindex, od, data): | ||
if 0x1800 <= index <= 0x19FF: | ||
# Only allowed to edit pdo configuration in pre-op | ||
if self.nmt.state != "PRE-OPERATIONAL": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not true in general, but we could make it a requirement for this library's implementation. Please indicate clearly if it should be the latter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I almost had it, depending on what level we want to support (variable vs dynamic) it can be changed in pre-op or operational. https://www.can-cia.org/can-knowledge/pdo-protocol-1. I expanded it to allow while in operational. This still leaves out the PDO mapping procedure...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only restriction I found in the standard is that subindex 03 "shall not be changed while the PDO exists (bit 31 of sub-index 01h is set to 0b)." So as long as the library's implementation does support dynamic reconfiguration (which I think it does?) we have no reason to restrict its usage IMHO.
The mentioned restriction as well as the PDO mapping procedure should be handled in the PDO code, but that's definitely not a blocker for this PR.
Simplify applying changes to PDO mapping by using pdo.read(from_od=True).
@acolomb Thank you for looking over everything, I think I addressed it all. |
if 0x1800 <= index <= 0x19FF: | ||
# TPDO Communication parameter changed | ||
tpdoNum = (index - 0x1800) + 1 | ||
self._tpdo_configuration_write(tpdoNum) | ||
elif 0x1A00 <= index <= 0x1BFF: | ||
# TPDO Mapping parameter changed | ||
tpdoNum = (index - 0x1A00) + 1 | ||
self._tpdo_configuration_write(tpdoNum) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be shortened a bit because the ranges are adjacent. You only need one range check and one modulo to find the TPDO number. But feel free to ignore this if you think it's not an improvement.
@@ -34,6 +34,8 @@ def __init__( | |||
self.add_write_callback(self.nmt.on_write) | |||
self.emcy = EmcyProducer(0x80 + self.id) | |||
|
|||
self.nmt.add_state_change_callback(self._nmt_state_changed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering whether this should be made opt-in. There is certainly existing code using LocalNode
which already manages the TPDO transmissions externally, because the library didn't use to do it. With this change, these users could end up with duplicated transmissions, or at least things stepping on each others' toes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the preferred way to achieve this? A keyword argument in the LocalNode
constructor?
Would the _tpdo_configuration_write
call inside set_data
also be opt-in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think an additional argument auto_tpdo: bool = False
would be fine, with appropriate documentation (docstring) of course.
And while we're at it, I think the _nmt_state_changed
name does not convey what the function does. It should rather be named _tpdo_auto_start_stop
, then this line will clearly express what the connection is (NMT state --> TPDO start).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the
_tpdo_configuration_write
call insideset_data
also be opt-in?
No, I think if someone writes to the local node's objects, it is quite expected to affect the PDO settings. Actually even for RPDOs. Of course our processing of these parameters is quite rudimentary now and not really standard compliant, but that can be improved step by step.
def _nmt_state_changed(self, old_state, new_state): | ||
if new_state == "OPERATIONAL": | ||
for pdo in self.tpdo.map.values(): | ||
if pdo.enabled: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a matter of taste, but I would prefer an inverted check and a continue
here. Makes for less indentation and doesn't stretch the else branch possibly out of sight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this will generate warnings for any TPDO not configured as periodic, right? Those should probably rather be skipped silently.
except Exception: | ||
logger.exception("Unknown error starting %s", pdo.name) | ||
else: | ||
logger.info("%s not enabled", pdo.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now this logging is probably too much. Worst case, it will be emitted for all 512 PDOs. That's too much even on DEBUG level. I'd just drop this log call.
try: | ||
pdo.read(from_od=True) | ||
except: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we want to silently swallow any and all exceptions here?
self.local_node.nmt.state = 'PRE-OPERATIONAL' | ||
|
||
# Attempt to re-enable tpdo 1 via sdo writing | ||
PDO_NOT_VALID = 1 << 31 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible to re-use canopen.pdo.base.PDO_NOT_VALID
?
# Disable tpdo 1 | ||
self.local_node.tpdo[1].enabled = False | ||
self.local_node.tpdo[1].cob_id = 0 | ||
self.local_node.tpdo[1].period = 0.5 # manually assign a period |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand that comment. Just above, you do the same without a comment. Is this intended to test mix & match between direct assignment and SDO based changes?
Isn't it using the value from the inhibit time object anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't use inhibit time currently. I have a different branch for this, I wasn't sure if I should try to pull it in with this stuff. Yes the intention here was to test to make sure the SDOs are correctly being propagated to the PDO configuration stack. grantweiss-RaymondCorp@0a9f50d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach of falling back to event_timer
looks reasonable. But I agree we don't have to do it all in this PR, but can iterate with more improvements / features once this is merged.
|
||
# Attempt to re-enable tpdo 1 via sdo writing | ||
PDO_NOT_VALID = 1 << 31 | ||
odDefaultVal = self.local_node.object_dictionary["Transmit PDO 0 communication parameters.COB-ID use by TPDO 1"].default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use the numeric index here, as it's less fragile and shorter. There's already a typo in that description, and fixing it would break the test 😉
|
||
self.remote_node.sdo["Transmit PDO 0 communication parameters.COB-ID use by TPDO 1"].raw = enabledCobId | ||
|
||
# Transition to operational |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two comments don't really help. The test code should be simple enough to not need an explanation.
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #524 +/- ##
==========================================
+ Coverage 68.86% 72.01% +3.15%
==========================================
Files 26 26
Lines 3112 3148 +36
Branches 526 536 +10
==========================================
+ Hits 2143 2267 +124
+ Misses 831 747 -84
+ Partials 138 134 -4
|
Local Node improvements: