Skip to content

Commit 13de653

Browse files
committed
Initial (untested) code for a lidar ros node.
1 parent 1c3ba54 commit 13de653

File tree

8 files changed

+242
-0
lines changed

8 files changed

+242
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2025 Bertrik Sikken <bertrik@sikken.nl>
3+
import threading
4+
from enum import Enum
5+
6+
import rclpy
7+
import serial
8+
from rclpy.node import Node
9+
from sensor_msgs.msg import LaserScan
10+
11+
12+
class FrameExtractor:
13+
class State(Enum):
14+
HEADER_1 = 1
15+
HEADER_2 = 2
16+
COLLECT = 3
17+
18+
def __init__(self) -> None:
19+
self.state = self.State.HEADER_1
20+
self.index = 0
21+
self.length = 0
22+
self.data = bytearray(60)
23+
24+
def add_data(self, b):
25+
self.data[self.index] = b
26+
self.index = self.index + 1
27+
28+
def process(self, b):
29+
""" processes one byte, returns True if a full frame was received """
30+
match self.state:
31+
case self.State.HEADER_1:
32+
self.index = 0
33+
if b == 0x55:
34+
self.add_data(b)
35+
self.state = self.State.HEADER_2
36+
37+
case self.State.HEADER_2:
38+
if b == 0xAA:
39+
self.add_data(b)
40+
self.length = len(self.data)
41+
self.state = self.State.COLLECT
42+
else:
43+
self.state = self.State.HEADER_1
44+
self.process(b)
45+
46+
case self.State.COLLECT:
47+
if self.index < self.length:
48+
self.add_data(b)
49+
if self.index == self.length:
50+
# done
51+
self.state = self.State.HEADER_1
52+
return True
53+
return False
54+
55+
def get_data(self):
56+
return self.data
57+
58+
59+
class MysteryLidar:
60+
def __init__(self, device):
61+
self.serial = serial.Serial(device, baudrate=230400, timeout=0.1)
62+
self.extractor = FrameExtractor()
63+
64+
def open(self) -> None:
65+
self.serial.open()
66+
67+
def poll(self) -> bytes | None:
68+
num = self.serial.in_waiting
69+
while num > 0:
70+
num = num - 1
71+
b = self.serial.read()
72+
done = self.extractor.process(b)
73+
if done:
74+
return self.extractor.get_data()
75+
return None
76+
77+
def close(self) -> None:
78+
self.serial.close()
79+
80+
81+
class LidarNode(Node):
82+
83+
def __init__(self):
84+
super().__init__('lidar')
85+
self.declare_parameter('device', value='/dev/ttyLIDAR')
86+
87+
device = self.get_parameter('device').get_parameter_value().string_value
88+
self.lidar = MysteryLidar(device)
89+
90+
topic_name = self.get_parameter('topic').get_parameter_value().string_value
91+
self.publisher = self.create_publisher(LaserScan, topic_name, 10)
92+
93+
self.thread = threading.Thread(target=self.node_task())
94+
self.thread.start()
95+
96+
def node_task(self):
97+
self.lidar.open()
98+
while rclpy.ok():
99+
frame = self.lidar.poll()
100+
if frame:
101+
self.publish_scan(frame)
102+
self.lidar.close()
103+
104+
def publish_scan(self, frame: bytes):
105+
msg = LaserScan()
106+
# TODO decode frame into msg
107+
self.publisher.publish(msg)
108+
109+
110+
def main(args=None):
111+
rclpy.init(args=args)
112+
lidar_node = LidarNode()
113+
rclpy.spin(lidar_node)
114+
lidar_node.destroy_node()
115+
rclpy.shutdown()
116+
117+
118+
if __name__ == '__main__':
119+
main()

ros_ws/src/lidar/package.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>lidar</name>
5+
<version>0.0.0</version>
6+
<description>Mystery LIDAR driver</description>
7+
<maintainer email="bertrik@sikken.nl">Bertrik Sikken</maintainer>
8+
<license>MIT</license>
9+
10+
<depend>std_msgs</depend>
11+
<depend>rclcpp</depend>
12+
13+
<test_depend>ament_copyright</test_depend>
14+
<test_depend>ament_flake8</test_depend>
15+
<test_depend>ament_pep257</test_depend>
16+
<test_depend>python3-pytest</test_depend>
17+
18+
<export>
19+
<build_type>ament_python</build_type>
20+
</export>
21+
</package>

ros_ws/src/lidar/resource/lidar

Whitespace-only changes.

ros_ws/src/lidar/setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[develop]
2+
script_dir=$base/lib/lidar
3+
[install]
4+
install_scripts=$base/lib/lidar

ros_ws/src/lidar/setup.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from setuptools import find_packages, setup
2+
3+
package_name = 'lidar'
4+
5+
setup(
6+
name=package_name,
7+
version='0.0.0',
8+
packages=find_packages(exclude=['test']),
9+
data_files=[
10+
('share/ament_index/resource_index/packages',
11+
['resource/' + package_name]),
12+
('share/' + package_name, ['package.xml']),
13+
],
14+
install_requires=['setuptools'],
15+
zip_safe=True,
16+
maintainer='bertrik',
17+
maintainer_email='bertrik@sikken.nl',
18+
description='Mystery LIDAR driver',
19+
license='MIT',
20+
tests_require=['pytest'],
21+
entry_points={
22+
'console_scripts': [
23+
],
24+
},
25+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2015 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_copyright.main import main
16+
import pytest
17+
18+
19+
# Remove the `skip` decorator once the source file(s) have a copyright header
20+
@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.')
21+
@pytest.mark.copyright
22+
@pytest.mark.linter
23+
def test_copyright():
24+
rc = main(argv=['.', 'test'])
25+
assert rc == 0, 'Found errors'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_flake8.main import main_with_errors
16+
import pytest
17+
18+
19+
@pytest.mark.flake8
20+
@pytest.mark.linter
21+
def test_flake8():
22+
rc, errors = main_with_errors(argv=[])
23+
assert rc == 0, \
24+
'Found %d code style errors / warnings:\n' % len(errors) + \
25+
'\n'.join(errors)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2015 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from ament_pep257.main import main
16+
import pytest
17+
18+
19+
@pytest.mark.linter
20+
@pytest.mark.pep257
21+
def test_pep257():
22+
rc = main(argv=['.', 'test'])
23+
assert rc == 0, 'Found code style errors / warnings'

0 commit comments

Comments
 (0)