-
Notifications
You must be signed in to change notification settings - Fork 215
LocalNode improvements for TPDOs #524
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
base: master
Are you sure you want to change the base?
Changes from 5 commits
e184bf5
853f7eb
290a358
b5e546f
822009c
43e32a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,9 @@ 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) | ||
| self.add_write_callback(self._tpdo_configuration_write) | ||
|
|
||
| def associate_network(self, network): | ||
| self.network = network | ||
| self.sdo.network = network | ||
|
|
@@ -127,3 +130,39 @@ def _find_object(self, index, subindex): | |
| raise SdoAbortedError(0x06090011) | ||
| obj = obj[subindex] | ||
| return obj | ||
|
|
||
| def _nmt_state_changed(self, old_state, new_state): | ||
| if new_state == "OPERATIONAL": | ||
| for i, pdo in self.tpdo.map.items(): | ||
| if pdo.enabled: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| try: | ||
| pdo.start() | ||
| logger.info(f"Successfully started TPDO {i}") | ||
acolomb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| except ValueError: | ||
| logger.warning(f"Failed to start TPDO {i} due to missing period") | ||
| except Exception as e: | ||
| logger.error(f"Unknown error starting TPDO {i}: {str(e)}") | ||
acolomb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| else: | ||
| logger.info(f"TPDO {i} not enabled") | ||
| elif old_state == "OPERATIONAL": | ||
acolomb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.tpdo.stop() | ||
|
|
||
| def _tpdo_configuration_write(self, index, subindex, od, data): | ||
| if 0x1800 <= index <= 0x19FF: | ||
acolomb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| # Only allowed to edit pdo configuration in pre-op | ||
| if self.nmt.state != "PRE-OPERATIONAL": | ||
|
||
| logger.warning("Tried to configure tpdo when not in pre-op") | ||
| return | ||
|
|
||
| if subindex == 0x01: | ||
| if len(data) == 4: | ||
| tpdoNum = (index - 0x1800) + 1 | ||
| if tpdoNum in self.tpdo.map.keys(): | ||
| PDO_NOT_VALID = 1 << 31 | ||
| RTR_NOT_ALLOWED = 1 << 30 | ||
|
|
||
| cob_id = int.from_bytes(data, 'little') & 0x7FF | ||
|
|
||
| self.tpdo.map[tpdoNum].cob_id = cob_id & 0x1FFFFFFF | ||
| self.tpdo.map[tpdoNum].enabled = cob_id & PDO_NOT_VALID == 0 | ||
| self.tpdo.map[tpdoNum].rtr_allowed = cob_id & RTR_NOT_ALLOWED == 0 | ||
acolomb marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,37 @@ def test_save(self): | |
| self.remote_node.pdo.save() | ||
| self.local_node.pdo.save() | ||
|
|
||
| def test_send_pdo_on_operational(self): | ||
| self.local_node.tpdo[1].period = 0.5 | ||
|
|
||
| self.local_node.nmt.state = 'INITIALISING' | ||
| self.local_node.nmt.state = 'PRE-OPERATIONAL' | ||
| self.local_node.nmt.state = 'OPERATIONAL' | ||
|
|
||
| self.assertNotEqual(self.local_node.tpdo[1]._task, None) | ||
|
|
||
| def test_config_pdo(self): | ||
| # 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The approach of falling back to |
||
|
|
||
| self.local_node.nmt.state = 'INITIALISING' | ||
| self.local_node.nmt.state = 'PRE-OPERATIONAL' | ||
|
|
||
| # Attempt to re-enable tpdo 1 via sdo writing | ||
| PDO_NOT_VALID = 1 << 31 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible to re-use |
||
| odDefaultVal = self.local_node.object_dictionary["Transmit PDO 0 communication parameters.COB-ID use by TPDO 1"].default | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😉 |
||
| enabledCobId = odDefaultVal & ~PDO_NOT_VALID # Ensure invalid bit is not set | ||
|
|
||
| self.remote_node.sdo["Transmit PDO 0 communication parameters.COB-ID use by TPDO 1"].raw = enabledCobId | ||
|
|
||
| # Transition to operational | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| self.local_node.nmt.state = 'OPERATIONAL' | ||
|
|
||
| # Ensure tpdo automatically started with transition | ||
| self.assertNotEqual(self.local_node.tpdo[1]._task, None) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
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
LocalNodewhich 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.Uh oh!
There was an error while loading. Please reload this page.
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
LocalNodeconstructor?Would the
_tpdo_configuration_writecall insideset_dataalso 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 = Falsewould be fine, with appropriate documentation (docstring) of course.And while we're at it, I think the
_nmt_state_changedname 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.
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.