Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a7cc61c
SDO: response->value_type = request->value_type
AntonOh May 22, 2026
4fe2bbf
add c++ library for deserializing uint8 arrays
elo0elo May 23, 2026
7358611
sdo_serializer.hpp: add serialization for bool and int8_t
elo0elo May 24, 2026
1325eb1
add tests for sdo_serializer
elo0elo May 24, 2026
6d98330
sdo_serializer.hpp: add serialization for int, uint, float, time and …
elo0elo May 24, 2026
32319e8
sdo_serializer.hpp: merge helper functions into generic function temp…
elo0elo May 24, 2026
a274c2c
sdo_serializer.hpp: add serialization for extended int, extended uint…
elo0elo May 24, 2026
69e3305
test_sdo_serializer.cpp: add tests for int, uint, float, time, domain…
elo0elo May 24, 2026
33dbe3b
sdo_serializer.hpp: add [[nodiscard]] to pure converting functions
elo0elo May 24, 2026
9a551e7
add aliases for EtherCAT and driver data types and enable implicit co…
elo0elo May 25, 2026
49ec361
sdo_serializer.hpp: add STRING data type
elo0elo May 25, 2026
145345e
test_sdo_serializer.cpp: add test for STRING(50) type
elo0elo May 25, 2026
a94c05e
infrastructure for python utility library
AntonOh May 26, 2026
d759642
start implementing marshalling for bit strings
AntonOh May 26, 2026
40bf0ca
fix test_sdo_serializer name overlap
AntonOh May 26, 2026
c61e2ff
BIT1 - BIT16 (de)serialization pass pytest
AntonOh May 27, 2026
d79ba51
BITARR8,16,32 (de)serialization pass pytest
AntonOh May 27, 2026
26178b8
signed ints (de)serialization pass pytest
AntonOh May 27, 2026
b7fd544
uints (de)serialization pass pytest
AntonOh May 27, 2026
fa0d13b
bool and generic words (de)serialization pass pytest
AntonOh May 28, 2026
412a621
adjust to correct mapping of uint8[] and add float serialization
AntonOh May 28, 2026
6ea6cf4
GUID and time48 (de)serialization pass pytest
AntonOh May 28, 2026
0d715f4
testing_node.cpp: add provisional sdo request
elo0elo May 29, 2026
9a9e08c
string (de)serialization passes pytest
AntonOh May 29, 2026
3af0193
rework handling of runtime errors
elo0elo May 29, 2026
0156313
add support for BIT, BITARR, WSTRING and ARRAY data types
elo0elo May 30, 2026
8150399
make bool encoding fit specification rules
AntonOh May 30, 2026
d25a51d
make GUID fit encoding rules
AntonOh May 30, 2026
3411ebb
testing_node.cpp: move sdo_read request into a separate function
elo0elo May 30, 2026
cf6e161
sdo_serializer.hpp: align bool serialization with ETG spec
elo0elo May 30, 2026
7956613
merge #57 c++ sdo serialization lib
elo0elo May 30, 2026
71a2fce
fix integration of sdo_serializer.hpp
elo0elo May 30, 2026
cf102f8
make time fit encoding rules
AntonOh May 30, 2026
f6d589e
add error handling options
AntonOh May 30, 2026
06123a5
testing_node.cpp: add sdo_write method
elo0elo May 31, 2026
b0ccf66
delete copied half finished c++ serializer
AntonOh Jun 1, 2026
4cb6706
delete cmakelists.txt mention of c++ sdo serialization lib
AntonOh Jun 1, 2026
f0b4139
sdo_serializer.hpp: make error messages for STRING more verbose
elo0elo Jun 1, 2026
4830b15
add python test node
AntonOh Jun 1, 2026
6a0be66
more reads
AntonOh Jun 1, 2026
309c648
sdo_serializer.hpp: fix error message
elo0elo Jun 1, 2026
670f5a3
remove unnecessary change
AntonOh Jun 1, 2026
79d7238
move into own package
AntonOh Jun 1, 2026
4610a4a
ec_manager.cpp: fix sdo_write
elo0elo Jun 1, 2026
978a5ac
encode VISIBLE_STRING as UTF-8
AntonOh Jun 1, 2026
b83a7f5
adapt psize in sdo_read dynamically to actual value
elo0elo Jun 1, 2026
72eb915
fix CMakeLists.txt
AntonOh Jun 2, 2026
0d4443c
better function docstrings
AntonOh Jun 2, 2026
9350b47
Merge pull request #61 from riserobotics/main
paulwetzel Jun 2, 2026
0280d96
provisional python package
AntonOh Jun 3, 2026
14d4211
update tests ASCII -> UTF-8
AntonOh Jun 3, 2026
59a2c7e
sdo_serializer.hpp: fix typo in data type alias
elo0elo Jun 3, 2026
d084442
replace double quotes
AntonOh Jun 3, 2026
a56baef
make sdo_serializer.hpp available to downstream packages
elo0elo Jun 3, 2026
1860e17
more docstrings + formatting
AntonOh Jun 24, 2026
b80f303
Merge remote-tracking branch 'origin/feature/58-research-and-integrat…
florian-pages Jul 1, 2026
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
18 changes: 18 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>py_sdo_serializer</name>
<version>0.0.0</version>
<description>Offers functions to serialize and deserialize EtherCAT base data types.</description>
<maintainer email="anton.ohler@gmail.com">a</maintainer>
<license>TODO: License declaration</license>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#!/usr/bin/env python3

# execute this using
# python3 path/to/this/file

import sys
import rclpy
from rclpy.node import Node
from rise_motion_messages.msg import MotorPositions
from rise_motion_messages.srv import EnableEthercatSrv, SDOReadSrv
from rise_motion_dev_ws.src.py_sdo_serialization.sdo_serializer import deserialize, serialize


class TestNode(Node):
def __init__(self, increment: int):
super().__init__("python_sdo_test_node")
self.increment = increment
self.valid_positions = False
self.motor_pos: list[int] = []

self.get_logger().info("Starting TestNode")

self.input_sub = self.create_subscription(
MotorPositions,
"motor_feedback",
self._feedback_cb,
10,
)

self.output_pub = self.create_publisher(MotorPositions, "motor_commands", 10)

self.publish_timer = self.create_timer(0.001, self._publish_cb) # 1 ms

self.enable_client = self.create_client(EnableEthercatSrv, "enable_ethercat")

def __del__(self):
self.get_logger().info("Bye :)")

# ---------------------------------------------------------------------------
# Callbacks
# ---------------------------------------------------------------------------

def _feedback_cb(self, msg: MotorPositions):
if not self.valid_positions:
self.get_logger().info("Got feedback")
self.valid_positions = True
self.motor_pos = list(msg.positions)

def _publish_cb(self):
if not self.valid_positions:
return
response = MotorPositions()
response.positions = [p + self.increment for p in self.motor_pos]
self.output_pub.publish(response)

# ---------------------------------------------------------------------------
# Service calls
# ---------------------------------------------------------------------------

def request_enable_ethercat(self) -> bool:
"""Returns True on success (mirrors the C++ int == 0 success check)."""
self.get_logger().info(f"Incrementing motor position with {self.increment}")
self.get_logger().info("Requesting Enable Ethercat")

while not self.enable_client.wait_for_service(timeout_sec=1.0):
if not rclpy.ok():
self.get_logger().error("Interrupted while waiting for enable_ethercat service.")
return False
self.get_logger().info("Waiting for enable_ethercat service...")

request = EnableEthercatSrv.Request()
request.enable = True

future = self.enable_client.call_async(request)
rclpy.spin_until_future_complete(self, future)

if future.result() is None:
self.get_logger().error("enable_ethercat service call failed.")
return False

self.get_logger().info("Done requesting")
return bool(future.result().status_enable)

def sdo_read(
self,
device_id: int,
index: int,
subindex: int,
value_type: int = 0,
*,
type_name: str | None = None,
base_data_type: str | None = None,
):
"""
Call the sdo_read service and deserialize the result.

Returns the deserialized value, or None on error.
"""
client = self.create_client(SDOReadSrv, "sdo_read")

while not client.wait_for_service(timeout_sec=1.0):
if not rclpy.ok():
self.get_logger().error("Interrupted while waiting for sdo_read service.")
return None
self.get_logger().info("Waiting for sdo_read service...")

request = SDOReadSrv.Request()
request.device_id = device_id
request.index = index
request.subindex = subindex
request.value_type = value_type

future = client.call_async(request)
rclpy.spin_until_future_complete(self, future)

if future.result() is None:
self.get_logger().error("sdo_read service call failed.")
return None

raw: list[int] = list(future.result().value)
result = deserialize(raw, name=type_name, base_data_type=base_data_type)

if result == -1:
self.get_logger().error("sdo deserialization failed for index 0x%04X sub %d", index, subindex)
return None

return result


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

def main():
increment = int(sys.argv[1]) if len(sys.argv) >= 2 else 10

rclpy.init()
node = TestNode(increment)

# Keep calling until EtherCAT is enabled
while not node.request_enable_ethercat():
pass

for p in [
[1,0x1008,0,"VISIBLE_STRING"], [1,0x1000,0,"UDINT"],
[1,0x1005,0,"DINT"], [1,0x1018,1,"UDINT"]]:
# Read device name (object 0x1008, sub 0) as a VISIBLE_STRING
result = node.sdo_read(
device_id=p[0],
index=p[1],
subindex=p[2],
type_name=p[3],
)

if result == -1:
rclpy.get_logger("rclcpp").error(f"SDO read {p[1]} {p[3]} failed")
else:
node.get_logger().info(f"sdo read value {p[1]} {p[3]}: %s", str(result))

rclpy.spin(node)
rclpy.shutdown()


if __name__ == "__main__":
main()
Empty file.
4 changes: 4 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/py_sdo_serializer
[install]
install_scripts=$base/lib/py_sdo_serializer
30 changes: 30 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from setuptools import find_packages, setup

package_name = 'py_sdo_serializer'

setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='a',
maintainer_email='anton.ohler@gmail.com',
description='Offers functions to serialize and deserialize EtherCAT base data types.',
license='TODO: License declaration',
extras_require={
'test': [
'pytest',
],
},
entry_points={
'console_scripts': [
],
},
tests_require=['pytest'],
)
25 changes: 25 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/test/test_copyright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2015 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ament_copyright.main import main
import pytest


# Remove the `skip` decorator once the source file(s) have a copyright header
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
@pytest.mark.copyright
@pytest.mark.linter
def test_copyright():
rc = main(argv=['.', 'test'])
assert rc == 0, 'Found errors'
25 changes: 25 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/test/test_flake8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ament_flake8.main import main_with_errors
import pytest


@pytest.mark.flake8
@pytest.mark.linter
def test_flake8():
rc, errors = main_with_errors(argv=[])
assert rc == 0, \
'Found %d code style errors / warnings:\n' % len(errors) + \
'\n'.join(errors)
23 changes: 23 additions & 0 deletions rise_motion_dev_ws/src/py_sdo_serializer/test/test_pep257.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2015 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ament_pep257.main import main
import pytest


@pytest.mark.linter
@pytest.mark.pep257
def test_pep257():
rc = main(argv=['.', 'test'])
assert rc == 0, 'Found code style errors / warnings'
Loading
Loading