Skip to content

Commit 9c495df

Browse files
authored
Merge pull request #17 from zlite/master
Fixed example for RPLidar firmware 1.29
2 parents ad9d604 + 94fcbf9 commit 9c495df

File tree

3 files changed

+217
-55
lines changed

3 files changed

+217
-55
lines changed

README.rst

+6-15
Original file line numberDiff line numberDiff line change
@@ -33,33 +33,24 @@ Usage Example
3333
.. code-block:: python
3434
3535
import os
36-
from math import cos, sin, pi, floor
37-
import pygame
38-
from adafruit_circuitpython_rplidar import RPLidar
39-
40-
# Set up pygame and the display
41-
os.putenv('SDL_FBDEV', '/dev/fb1')
42-
pygame.init()
43-
lcd = pygame.display.set_mode((320,240))
44-
pygame.mouse.set_visible(False)
45-
lcd.fill((0,0,0))
46-
pygame.display.update()
36+
from math import floor
37+
from adafruit_rplidar import RPLidar
38+
4739
4840
# Setup the RPLidar
4941
PORT_NAME = '/dev/ttyUSB0'
50-
lidar = RPLidar(None, PORT_NAME)
42+
lidar = RPLidar(None, PORT_NAME, timeout=3)
5143
5244
# used to scale data to fit on the screen
5345
max_distance = 0
5446
5547
def process_data(data):
56-
# Do something useful with the data
57-
pass
48+
print(data)
5849
5950
scan_data = [0]*360
6051
6152
try:
62-
print(lidar.get_info())
53+
# print(lidar.get_info())
6354
for scan in lidar.iter_scans():
6455
for (_, angle, distance) in scan:
6556
scan_data[min([359, floor(angle)])] = distance

adafruit_rplidar.py

+179-40
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1212
* Author(s): Dave Astels
1313
* Based on https://github.com/SkoltechRobotics/rplidar by Artyom Pavlov
14+
* and updates from https://github.com/Roboticia/RPLidar by Julien JEHL
1415
1516
Implementation Notes
1617
--------------------
@@ -23,16 +24,17 @@
2324
* Adafruit CircuitPython firmware for the supported boards:
2425
https://github.com/adafruit/circuitpython/releases
2526
26-
Version 0.0.1 does NOT support CircuitPython. Future versions will.
27+
The Current Version does NOT support CircuitPython. Future versions will.
2728
"""
2829

2930
import struct
3031
import sys
3132
import time
3233
import warnings
34+
from collections import namedtuple
3335

3436
# pylint:disable=invalid-name,undefined-variable,global-variable-not-assigned
35-
# pylint:disable=too-many-arguments,raise-missing-from
37+
# pylint:disable=too-many-arguments,raise-missing-from,too-many-instance-attributes
3638

3739
__version__ = "0.0.0-auto.0"
3840
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RPLIDAR.git"
@@ -46,16 +48,12 @@
4648
STOP_BYTE = b"\x25"
4749
RESET_BYTE = b"\x40"
4850

49-
SCAN_BYTE = b"\x20"
50-
FORCE_SCAN_BYTE = b"\x21"
51-
5251
DESCRIPTOR_LEN = 7
5352
INFO_LEN = 20
5453
HEALTH_LEN = 3
5554

5655
INFO_TYPE = 4
5756
HEALTH_TYPE = 6
58-
SCAN_TYPE = 129
5957

6058
# Constants & Command to start A2 motor
6159
MAX_MOTOR_PWM = 1023
@@ -68,6 +66,18 @@
6866
2: "Error",
6967
}
7068

69+
SCAN_TYPE_NORMAL = 0
70+
SCAN_TYPE_FORCE = 1
71+
SCAN_TYPE_EXPRESS = 2
72+
73+
_SCAN_TYPES = (
74+
{"byte": b"\x20", "response": 129, "size": 5},
75+
{"byte": b"\x21", "response": 129, "size": 5},
76+
{"byte": b"\x82", "response": 130, "size": 84},
77+
)
78+
79+
express_packet = namedtuple("express_packet", "distance angle new_scan start_angle")
80+
7181

7282
class RPLidarException(Exception):
7383
"""Basic exception class for RPLidar"""
@@ -88,6 +98,17 @@ def _process_scan(raw):
8898
return new_scan, quality, angle, distance
8999

90100

101+
def _process_express_scan(data, new_angle, frame):
102+
new_scan = (new_angle < data.start_angle) & (frame == 1)
103+
angle = (
104+
data.start_angle
105+
+ ((new_angle - data.start_angle) % 360) / 32 * frame
106+
- data.angle[frame - 1]
107+
) % 360
108+
distance = data.distance[frame - 1]
109+
return new_scan, None, angle, distance
110+
111+
91112
class RPLidar:
92113
"""Class for communicating with RPLidar rangefinder scanners"""
93114

@@ -97,6 +118,12 @@ class RPLidar:
97118
timeout = 1 #: Serial port timeout
98119
motor = False #: Is motor running?
99120
baudrate = 115200 #: Baudrate for serial port
121+
scanning = False
122+
descriptor_size = 0
123+
scan_type = SCAN_TYPE_NORMAL
124+
express_frame = 32
125+
express_data = False
126+
express_old_data = None
100127

101128
def __init__(self, motor_pin, port, baudrate=115200, timeout=1, logging=False):
102129
"""Initialize RPLidar object for communicating with the sensor.
@@ -152,7 +179,6 @@ def connect(self):
152179
parity=serial.PARITY_NONE,
153180
stopbits=serial.STOPBITS_ONE,
154181
timeout=self.timeout,
155-
dsrdtr=True,
156182
)
157183
except serial.SerialException as err:
158184
raise RPLidarException(
@@ -253,7 +279,7 @@ def info(self):
253279
if dtype != INFO_TYPE:
254280
raise RPLidarException("Wrong response data type")
255281
raw = self._read_response(dsize)
256-
serialnumber_bytes = struct.unpack("BBBBBBBBBBBBBBBB", raw[4:])
282+
serialnumber_bytes = struct.unpack("B" * len(raw[4:]), raw[4:])
257283
serialnumber = "".join(reversed(["%02x" % b for b in serialnumber_bytes]))
258284
data = {
259285
"model": raw[0],
@@ -294,13 +320,67 @@ def health(self):
294320

295321
def clear_input(self):
296322
"""Clears input buffer by reading all available data"""
323+
if self.scanning:
324+
raise RPLidarException("Clearing not allowed during active scanning!")
325+
self._serial_port.flushInput()
326+
self.express_frame = 32
327+
self.express_data = False
328+
329+
def start(self, scan_type=SCAN_TYPE_NORMAL):
330+
"""Start the scanning process
331+
Parameters
332+
----------
333+
scan : normal, force or express.
334+
"""
335+
if self.scanning:
336+
raise RPLidarException("Scanning already running!")
337+
# Start the scanning process, enable laser diode and the
338+
# measurement system
339+
status, error_code = self.health
340+
self.log("debug", "Health status: %s [%d]" % (status, error_code))
341+
if status == _HEALTH_STATUSES[2]:
342+
self.log(
343+
"warning",
344+
"Trying to reset sensor due to the error. "
345+
"Error code: %d" % (error_code),
346+
)
347+
self.reset()
348+
status, error_code = self.health
349+
if status == _HEALTH_STATUSES[2]:
350+
raise RPLidarException(
351+
"RPLidar hardware failure. " "Error code: %d" % error_code
352+
)
353+
elif status == _HEALTH_STATUSES[1]:
354+
self.log(
355+
"warning",
356+
"Warning sensor status detected! " "Error code: %d" % (error_code),
357+
)
358+
cmd = _SCAN_TYPES[scan_type]["byte"]
359+
self.log("info", "starting scan process in %s mode" % scan_type)
360+
361+
if scan_type == "express":
362+
self._send_payload_cmd(cmd, b"\x00\x00\x00\x00\x00")
363+
else:
364+
self._send_cmd(cmd)
365+
366+
dsize, is_single, dtype = self._read_descriptor()
367+
if dsize != _SCAN_TYPES[scan_type]["size"]:
368+
raise RPLidarException("Wrong info reply length")
369+
if is_single:
370+
raise RPLidarException("Not a multiple response mode")
371+
if dtype != _SCAN_TYPES[scan_type]["response"]:
372+
raise RPLidarException("Wrong response data type")
373+
self.descriptor_size = dsize
374+
self.scan_type = scan_type
375+
self.scanning = True
297376

298377
def stop(self):
299378
"""Stops scanning process, disables laser diode and the measurement
300379
system, moves sensor to the idle state."""
301380
self.log("info", "Stopping scanning")
302381
self._send_cmd(STOP_BYTE)
303382
time.sleep(0.001)
383+
self.scanning = False
304384
self.clear_input()
305385

306386
def reset(self):
@@ -309,8 +389,9 @@ def reset(self):
309389
self.log("info", "Resetting the sensor")
310390
self._send_cmd(RESET_BYTE)
311391
time.sleep(0.002)
392+
self.clear_input()
312393

313-
def iter_measurements(self, max_buf_meas=500):
394+
def iter_measurements(self, max_buf_meas=500, scan_type=SCAN_TYPE_NORMAL):
314395
"""Iterate over measurements. Note that consumer must be fast enough,
315396
otherwise data will be accumulated inside buffer and consumer will get
316397
data with increasing lag.
@@ -334,37 +415,11 @@ def iter_measurements(self, max_buf_meas=500):
334415
In millimeter unit. Set to 0 when measurement is invalid.
335416
"""
336417
self.start_motor()
337-
status, error_code = self.health
338-
self.log("debug", "Health status: %s [%d]" % (status, error_code))
339-
if status == _HEALTH_STATUSES[2]:
340-
self.log(
341-
"warning",
342-
"Trying to reset sensor due to the error. "
343-
"Error code: %d" % (error_code),
344-
)
345-
self.reset()
346-
status, error_code = self.health
347-
if status == _HEALTH_STATUSES[2]:
348-
raise RPLidarException(
349-
"RPLidar hardware failure. " "Error code: %d" % error_code
350-
)
351-
elif status == _HEALTH_STATUSES[1]:
352-
self.log(
353-
"warning",
354-
"Warning sensor status detected! " "Error code: %d" % (error_code),
355-
)
356-
cmd = SCAN_BYTE
357-
self._send_cmd(cmd)
358-
dsize, is_single, dtype = self._read_descriptor()
359-
if dsize != 5:
360-
raise RPLidarException("Wrong info reply length")
361-
if is_single:
362-
raise RPLidarException("Not a multiple response mode")
363-
if dtype != SCAN_TYPE:
364-
raise RPLidarException("Wrong response data type")
418+
if not self.scanning:
419+
self.start(scan_type)
420+
365421
while True:
366-
raw = self._read_response(dsize)
367-
self.log_bytes("debug", "Received scan response: ", raw)
422+
dsize = self.descriptor_size
368423
if max_buf_meas:
369424
data_in_buf = self._serial_port.in_waiting
370425
if data_in_buf > max_buf_meas * dsize:
@@ -374,7 +429,50 @@ def iter_measurements(self, max_buf_meas=500):
374429
"Clearing buffer..." % (data_in_buf // dsize, max_buf_meas),
375430
)
376431
self._serial_port.read(data_in_buf // dsize * dsize)
377-
yield _process_scan(raw)
432+
if self.scan_type == SCAN_TYPE_NORMAL:
433+
raw = self._read_response(dsize)
434+
self.log_bytes("debug", "Received scan response: ", raw)
435+
yield _process_scan(raw)
436+
elif self.scan_type == SCAN_TYPE_EXPRESS:
437+
if self.express_frame == 32:
438+
self.express_frame = 0
439+
if not self.express_data:
440+
self.log("debug", "reading first time bytes")
441+
self.express_data = ExpressPacket.from_string(
442+
self._read_response(dsize)
443+
)
444+
445+
self.express_old_data = self.express_data
446+
self.log(
447+
"debug",
448+
"set old_data with start_angle %f"
449+
% self.express_old_data.start_angle,
450+
)
451+
self.express_data = ExpressPacket.from_string(
452+
self._read_response(dsize)
453+
)
454+
self.log(
455+
"debug",
456+
"set new_data with start_angle %f"
457+
% self.express_data.start_angle,
458+
)
459+
460+
self.express_frame += 1
461+
self.log(
462+
"debug",
463+
"process scan of frame %d with angle : "
464+
"%f and angle new : %f"
465+
% (
466+
self.express_frame,
467+
self.express_old_data.start_angle,
468+
self.express_data.start_angle,
469+
),
470+
)
471+
yield _process_express_scan(
472+
self.express_old_data,
473+
self.express_data.start_angle,
474+
self.express_frame,
475+
)
378476

379477
def iter_measurments(self, max_buf_meas=500):
380478
"""For compatibility, this method wraps `iter_measurements`"""
@@ -414,3 +512,44 @@ def iter_scans(self, max_buf_meas=500, min_len=5):
414512
scan = []
415513
if quality > 0 and distance > 0:
416514
scan.append((quality, angle, distance))
515+
516+
517+
class ExpressPacket(express_packet):
518+
"""Class representing a Express type Packet"""
519+
520+
sync1 = 0xA
521+
sync2 = 0x5
522+
sign = {0: 1, 1: -1}
523+
524+
@classmethod
525+
def from_string(cls, data):
526+
"""Decode and Instantiate the class from a string packet"""
527+
packet = bytearray(data)
528+
529+
if (packet[0] >> 4) != cls.sync1 or (packet[1] >> 4) != cls.sync2:
530+
raise ValueError("try to parse corrupted data ({})".format(packet))
531+
532+
checksum = 0
533+
for b in packet[2:]:
534+
checksum ^= b
535+
if checksum != (packet[0] & 0b00001111) + ((packet[1] & 0b00001111) << 4):
536+
raise ValueError("Invalid checksum ({})".format(packet))
537+
538+
new_scan = packet[3] >> 7
539+
start_angle = (packet[2] + ((packet[3] & 0b01111111) << 8)) / 64
540+
541+
d = a = ()
542+
for i in range(0, 80, 5):
543+
d += ((packet[i + 4] >> 2) + (packet[i + 5] << 6),)
544+
a += (
545+
((packet[i + 8] & 0b00001111) + ((packet[i + 4] & 0b00000001) << 4))
546+
/ 8
547+
* cls.sign[(packet[i + 4] & 0b00000010) >> 1],
548+
)
549+
d += ((packet[i + 6] >> 2) + (packet[i + 7] << 6),)
550+
a += (
551+
((packet[i + 8] >> 4) + ((packet[i + 6] & 0b00000001) << 4))
552+
/ 8
553+
* cls.sign[(packet[i + 6] & 0b00000010) >> 1],
554+
)
555+
return cls(d, a, new_scan, start_angle)

0 commit comments

Comments
 (0)