Skip to content

Commit 9c8e803

Browse files
committed
Initial working lidar_node.
1 parent 13de653 commit 9c8e803

File tree

1 file changed

+56
-25
lines changed

1 file changed

+56
-25
lines changed

ros_ws/src/lidar/lidar/lidar_node.py

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#!/usr/bin/env python3
22
# Copyright 2025 Bertrik Sikken <bertrik@sikken.nl>
3+
import math
34
import threading
45
from enum import Enum
56

67
import rclpy
78
import serial
89
from rclpy.node import Node
910
from sensor_msgs.msg import LaserScan
11+
from std_msgs.msg import Header
1012

1113

1214
class FrameExtractor:
@@ -21,22 +23,22 @@ def __init__(self) -> None:
2123
self.length = 0
2224
self.data = bytearray(60)
2325

24-
def add_data(self, b):
26+
def __add_data(self, b):
2527
self.data[self.index] = b
26-
self.index = self.index + 1
28+
self.index += 1
2729

28-
def process(self, b):
30+
def process(self, b) -> bytes | None:
2931
""" processes one byte, returns True if a full frame was received """
3032
match self.state:
3133
case self.State.HEADER_1:
3234
self.index = 0
3335
if b == 0x55:
34-
self.add_data(b)
36+
self.__add_data(b)
3537
self.state = self.State.HEADER_2
3638

3739
case self.State.HEADER_2:
3840
if b == 0xAA:
39-
self.add_data(b)
41+
self.__add_data(b)
4042
self.length = len(self.data)
4143
self.state = self.State.COLLECT
4244
else:
@@ -45,15 +47,12 @@ def process(self, b):
4547

4648
case self.State.COLLECT:
4749
if self.index < self.length:
48-
self.add_data(b)
50+
self.__add_data(b)
4951
if self.index == self.length:
5052
# done
5153
self.state = self.State.HEADER_1
52-
return True
53-
return False
54-
55-
def get_data(self):
56-
return self.data
54+
return self.data
55+
return None
5756

5857

5958
class MysteryLidar:
@@ -62,52 +61,84 @@ def __init__(self, device):
6261
self.extractor = FrameExtractor()
6362

6463
def open(self) -> None:
65-
self.serial.open()
64+
if not self.serial.is_open:
65+
self.serial.open()
6666

6767
def poll(self) -> bytes | None:
6868
num = self.serial.in_waiting
6969
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()
70+
num -= 1
71+
b = self.serial.read()[0]
72+
result = self.extractor.process(b)
73+
if result:
74+
return result
7575
return None
7676

7777
def close(self) -> None:
7878
self.serial.close()
7979

8080

8181
class LidarNode(Node):
82-
8382
def __init__(self):
8483
super().__init__('lidar')
85-
self.declare_parameter('device', value='/dev/ttyLIDAR')
84+
self.declare_parameter('device', value='/dev/ttyUSB0')
85+
self.declare_parameter('topic', value='/scan')
86+
self.declare_parameter('frame_id', value='laser')
8687

8788
device = self.get_parameter('device').get_parameter_value().string_value
8889
self.lidar = MysteryLidar(device)
8990

90-
topic_name = self.get_parameter('topic').get_parameter_value().string_value
91-
self.publisher = self.create_publisher(LaserScan, topic_name, 10)
91+
self.frame_id = self.get_parameter('frame_id').get_parameter_value().string_value
92+
93+
topic = self.get_parameter('topic').get_parameter_value().string_value
94+
self.publisher = self.create_publisher(LaserScan, topic, 10)
9295

9396
self.thread = threading.Thread(target=self.node_task())
9497
self.thread.start()
9598

96-
def node_task(self):
99+
def node_task(self) -> None:
97100
self.lidar.open()
98101
while rclpy.ok():
99102
frame = self.lidar.poll()
100103
if frame:
101104
self.publish_scan(frame)
102105
self.lidar.close()
103106

104-
def publish_scan(self, frame: bytes):
105-
msg = LaserScan()
106-
# TODO decode frame into msg
107+
def publish_scan(self, frame: bytes) -> None:
108+
fsa = (frame[6] + (frame[7] << 8)) / 64.0 - 640
109+
lsa = (frame[56] + (frame[57] << 8)) / 64.0 - 640
110+
if lsa < fsa:
111+
lsa += 360
112+
step = (lsa - fsa) / 16
113+
114+
header = Header(frame_id=self.frame_id, stamp=self.get_clock().now().to_msg())
115+
msg = LaserScan(header=header)
116+
msg.angle_min = math.radians(fsa)
117+
msg.angle_max = math.radians(lsa)
118+
msg.angle_increment = math.radians(step)
119+
msg.range_min = 0.0
120+
msg.range_max = float(0x3FFF)
121+
# time between full 360 sweep: approx 6 rotations per second
122+
msg.scan_time = 1.0 / 6
123+
# time between rays: 6 fps, 36*16 rays per scan
124+
msg.time_increment = msg.scan_time * 10 / (360 * 16)
125+
126+
idx = 8
127+
for i in range(16):
128+
dist = (frame[idx] + (frame[idx + 1] << 8)) & 0x3FFF
129+
intensity = frame[idx + 2]
130+
idx += 3
131+
msg.ranges.append(float(dist))
132+
msg.intensities.append(float(intensity))
133+
107134
self.publisher.publish(msg)
108135

109136

110137
def main(args=None):
138+
# Set up a transformer using:
139+
# ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 base_link laser
140+
# Visualize using rvis2, add a 'LaserScan' display and choose topic '/scan' and style 'Points'
141+
#
111142
rclpy.init(args=args)
112143
lidar_node = LidarNode()
113144
rclpy.spin(lidar_node)

0 commit comments

Comments
 (0)