LCOV - code coverage report
Current view: top level - AP_Proximity - AP_Proximity_RPLidarA2.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 228 394 57.9 %
Date: 2026-09-09 11:02:08 Functions: 14 20 70.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : /*
      17             :  * ArduPilot device driver for SLAMTEC RPLIDAR A2 (16m range version)
      18             :  *
      19             :  * ALL INFORMATION REGARDING PROTOCOL WAS DERIVED FROM RPLIDAR DATASHEET:
      20             :  *
      21             :  * https://www.slamtec.com/en/Lidar
      22             :  * http://bucket.download.slamtec.com/63ac3f0d8c859d3a10e51c6b3285fcce25a47357/LR001_SLAMTEC_rplidar_protocol_v1.0_en.pdf
      23             :  *
      24             :  * Author: Steven Josefs, IAV GmbH
      25             :  * Based on the LightWare SF40C ArduPilot device driver from Randy Mackay
      26             :  *
      27             :  */
      28             : 
      29             : #include "AP_Proximity_config.h"
      30             : 
      31             : #if AP_PROXIMITY_RPLIDARA2_ENABLED
      32             : 
      33             : #include "AP_Proximity_RPLidarA2.h"
      34             : 
      35             : #include <AP_HAL/AP_HAL.h>
      36             : #include <AP_InternalError/AP_InternalError.h>
      37             : 
      38             : #include <ctype.h>
      39             : #include <stdio.h>
      40             : 
      41             : #define RP_DEBUG_LEVEL 0
      42             : 
      43             : #include <GCS_MAVLink/GCS.h>
      44             : #if RP_DEBUG_LEVEL
      45             :   #define Debug(level, fmt, args ...)  do { if (level <= RP_DEBUG_LEVEL) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, fmt, ## args); } } while (0)
      46             : #else
      47             :   #define Debug(level, fmt, args ...)
      48             : #endif
      49             : 
      50             : #define COMM_ACTIVITY_TIMEOUT_MS        200
      51             : 
      52             : // Commands
      53             : //-----------------------------------------
      54             : 
      55             : // Commands without payload and response
      56             : #define RPLIDAR_PREAMBLE               0xA5
      57             : #define RPLIDAR_CMD_STOP               0x25
      58             : #define RPLIDAR_CMD_SCAN               0x20
      59             : #define RPLIDAR_CMD_FORCE_SCAN         0x21
      60             : #define RPLIDAR_CMD_RESET              0x40
      61             : 
      62             : // Commands without payload but have response
      63             : #define RPLIDAR_CMD_GET_DEVICE_INFO    0x50
      64             : #define RPLIDAR_CMD_GET_DEVICE_HEALTH  0x52
      65             : 
      66             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
      67             : // Commands with payload and have response
      68             : #define RPLIDAR_CMD_EXPRESS_SCAN       0x82
      69             : #endif
      70             : 
      71             : extern const AP_HAL::HAL& hal;
      72             : 
      73       24819 : void AP_Proximity_RPLidarA2::update(void)
      74             : {
      75       24819 :     if (_uart == nullptr) {
      76           0 :         return;
      77             :     }
      78             : 
      79             :     // request device info 3sec after reset
      80             :     // required for S1 support that sends only 9 bytes after a reset (A1,A2 send 63)
      81       24819 :     uint32_t now_ms = AP_HAL::millis();
      82       24819 :     if ((_state == State::RESET) && (now_ms - _last_reset_ms > 3000)) {
      83           3 :         send_request_for_device_info();
      84           3 :         _state = State::AWAITING_RESPONSE;
      85           3 :         _byte_count = 0;
      86             :     }
      87             : 
      88       24819 :     get_readings();
      89             : 
      90             :     // check for timeout and set health status
      91       24819 :     if (AP_HAL::millis() - _last_distance_received_ms > COMM_ACTIVITY_TIMEOUT_MS) {
      92         344 :         set_status(AP_Proximity::Status::NoData);
      93             :         Debug(1, "LIDAR NO DATA");
      94         344 :         if (AP_HAL::millis() - _last_reset_ms > 10000) {
      95           0 :             reset_rplidar();
      96             :         }
      97             :     } else {
      98       24475 :         set_status(AP_Proximity::Status::Good);
      99             :     }
     100             : }
     101             : 
     102             : // get maximum distance (in meters) of sensor
     103        7437 : float AP_Proximity_RPLidarA2::distance_max_m() const
     104             : {
     105        7437 :     switch (model) {
     106          84 :     case Model::UNKNOWN:
     107          84 :         return 0.0f;
     108           0 :     case Model::A1:
     109           0 :         return 8.0f;
     110        4910 :     case Model::A2:
     111        4910 :         return 16.0f;
     112           0 :     case Model::A2M12:
     113             :     case Model::C1:
     114           0 :         return 12.0f;
     115           0 :     case Model::S1:
     116           0 :         return 40.0f;
     117        2443 :     case Model::S2:
     118        2443 :         return 50.0f;
     119             :     }
     120           0 :     return 0.0f;
     121             : }
     122             : 
     123             : // get minimum distance (in meters) of sensor
     124       85736 : float AP_Proximity_RPLidarA2::distance_min_m() const
     125             : {
     126       85736 :     switch (model) {
     127           9 :     case Model::UNKNOWN:
     128           9 :         return 0.0f;
     129       82136 :     case Model::A1:
     130             :     case Model::A2:
     131             :     case Model::A2M12:
     132             :     case Model::C1:
     133             :     case Model::S1:
     134       82136 :         return 0.2f;
     135        3591 :     case Model::S2:
     136        3591 :         return 0.05f;
     137             :     }
     138           0 :     return 0.0f;
     139             : }
     140             : 
     141           0 : void AP_Proximity_RPLidarA2::reset_rplidar()
     142             : {
     143             :     static const uint8_t tx_buffer[2] {RPLIDAR_PREAMBLE, RPLIDAR_CMD_RESET};
     144           0 :     _uart->write(tx_buffer, 2);
     145             :     Debug(1, "LIDAR reset");
     146             :     // To-Do: ensure delay of 8m after sending reset request
     147           0 :     _last_reset_ms =  AP_HAL::millis();
     148           0 :     reset();
     149           0 : }
     150             : 
     151             : // set Lidar into SCAN mode
     152           2 : void AP_Proximity_RPLidarA2::send_scan_mode_request()
     153             : {
     154             :     static const uint8_t tx_buffer[2] {RPLIDAR_PREAMBLE, RPLIDAR_CMD_SCAN};
     155           2 :     _uart->write(tx_buffer, 2);
     156             :     Debug(1, "Sent scan mode request");
     157           2 : }
     158             : 
     159             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     160             : // send EXPRESS_SCAN request for Dense mode
     161           1 : void AP_Proximity_RPLidarA2::send_express_scan_request()
     162             : {
     163           1 :     const uint8_t cmd = RPLIDAR_CMD_EXPRESS_SCAN;
     164           1 :     const uint8_t payload_size = 5;
     165           1 :     uint8_t payload[payload_size] {};
     166             : 
     167           1 :     uint8_t checksum = 0;
     168           1 :     checksum ^= RPLIDAR_PREAMBLE;
     169           1 :     checksum ^= cmd;
     170           1 :     checksum ^= payload_size;
     171           6 :     for (uint8_t i = 0; i < sizeof(payload); i++) {
     172           5 :         checksum ^= payload[i];
     173             :     }
     174             : 
     175             :     // 2 (preamble + cmd) + 1 (payload_size field) + payload_size + 1 (checksum)
     176             :     uint8_t tx_buffer[2 + 1 + payload_size + 1];
     177           1 :     tx_buffer[0] = RPLIDAR_PREAMBLE;
     178           1 :     tx_buffer[1] = cmd;
     179           1 :     tx_buffer[2] = payload_size;
     180           1 :     memcpy(&tx_buffer[3], payload, sizeof(payload));
     181           1 :     tx_buffer[3 + sizeof(payload)] = checksum;
     182             : 
     183           1 :     _uart->write(tx_buffer, sizeof(tx_buffer));
     184             :     Debug(1, "Sent EXPRESS (Dense) scan request");
     185           1 : }
     186             : #endif // AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     187             : 
     188             : // send request for sensor health
     189           0 : void AP_Proximity_RPLidarA2::send_request_for_health()                                    //not called yet
     190             : {
     191             :     static const uint8_t tx_buffer[2] {RPLIDAR_PREAMBLE, RPLIDAR_CMD_GET_DEVICE_HEALTH};
     192           0 :     _uart->write(tx_buffer, 2);
     193             :     Debug(1, "Sent health request");
     194           0 : }
     195             : 
     196             : // send request for device information
     197           3 : void AP_Proximity_RPLidarA2::send_request_for_device_info()
     198             : {
     199             :     static const uint8_t tx_buffer[2] {RPLIDAR_PREAMBLE, RPLIDAR_CMD_GET_DEVICE_INFO};
     200           3 :     _uart->write(tx_buffer, 2);
     201             :     Debug(1, "Sent device information request");
     202           3 : }
     203             : 
     204       81734 : void AP_Proximity_RPLidarA2::consume_bytes(uint16_t count)
     205             : {
     206       81734 :     if (count > _byte_count) {
     207           0 :         INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     208           0 :         _byte_count = 0;
     209           0 :         return;
     210             :     }
     211       81734 :     _byte_count -= count;
     212       81734 :     if (_byte_count) {
     213       65385 :         memmove((void*)&_payload[0], (void*)&_payload[count], _byte_count);
     214             :     }
     215             : }
     216             : 
     217           0 : void AP_Proximity_RPLidarA2::reset()
     218             : {
     219           0 :     _state = State::RESET;
     220           0 :     _byte_count = 0;
     221           0 :     _sync_error = 0;
     222             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     223           0 :     _use_dense_express = false;
     224           0 :     _express_stream_len = 0;
     225             : #endif
     226           0 : }
     227             : 
     228           0 : bool AP_Proximity_RPLidarA2::make_first_byte_in_payload(uint8_t desired_byte)
     229             : {
     230           0 :     if (_byte_count == 0) {
     231           0 :         return false;
     232             :     }
     233           0 :     if (_payload[0] == desired_byte) {
     234           0 :         return true;
     235             :     }
     236           0 :     for (auto i=1; i<_byte_count; i++) {
     237           0 :         if (_payload[i] == desired_byte) {
     238           0 :             consume_bytes(i);
     239           0 :             return true;
     240             :         }
     241             :     }
     242             :     // just not in our buffer.  Throw everything away:
     243           0 :     _byte_count = 0;
     244           0 :     return false;
     245             : }
     246             : 
     247       24819 : void AP_Proximity_RPLidarA2::get_readings()
     248             : {
     249             :     Debug(2, "             CURRENT STATE: %u ", (unsigned)_state);
     250             : 
     251             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     252             :     // Express mode reads directly into _express_stream, bypassing _payload
     253       24819 :     if (_state == State::AWAITING_EXPRESS_DATA) {
     254        8130 :         handle_express_data();
     255        8469 :         return;
     256             :     }
     257             : #endif
     258             : 
     259       16689 :     const uint32_t nbytes = _uart->available();
     260       16689 :     if (nbytes == 0) {
     261         339 :         return;
     262             :     }
     263       16350 :     const uint32_t bytes_to_read = MIN(nbytes, sizeof(_payload)-_byte_count);
     264       16350 :     if (bytes_to_read == 0) {
     265           0 :         INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     266           0 :         reset();
     267           0 :         return;
     268             :     }
     269       16350 :     const uint32_t bytes_read = _uart->read(&_payload[_byte_count], bytes_to_read);
     270       16350 :     if (bytes_read == 0) {
     271             :         // this is bad; we were told there were bytes available
     272           0 :         INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     273           0 :         reset();
     274           0 :         return;
     275             :     }
     276       16350 :     _byte_count += bytes_read;
     277             : 
     278       16350 :     uint32_t previous_loop_byte_count = UINT32_MAX;
     279       98085 :     while (_byte_count) {
     280       81735 :         if (_byte_count >= previous_loop_byte_count) {
     281             :             // this is a serious error, we should always consume some
     282             :             // bytes.  Avoid looping forever.
     283           0 :             INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     284           0 :             _uart = nullptr;
     285           0 :             return;
     286             :         }
     287       81735 :         previous_loop_byte_count = _byte_count;
     288             : 
     289       81735 :         switch(_state){
     290           0 :         case State::RESET: {
     291             :             // looking for 0x52 at start of buffer; the 62 following
     292             :             // bytes are "information"
     293           0 :             if (!make_first_byte_in_payload('R')) { // that's 'R' as in RPiLidar
     294           0 :                 return;
     295             :             }
     296           0 :             if (_byte_count < 63) {
     297           0 :                 return;
     298             :             }
     299             : #if RP_DEBUG_LEVEL
     300             :             // optionally spit out via mavlink the 63-bytes of cruft
     301             :             // that is spat out on device reset
     302             :             Debug(1, "Got RPLidar Information");
     303             :             char xbuffer[64]{};
     304             :             memcpy((void*)xbuffer, (void*)&_payload.information, 63);
     305             :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "RPLidar: (%s)", xbuffer);
     306             : #endif
     307             :             // 63 is the magic number of bytes in the spewed-out
     308             :             // reset data ... so now we'll just drop that stuff on
     309             :             // the floor.
     310           0 :             consume_bytes(63);
     311           0 :             send_request_for_device_info();
     312           0 :             _state = State::AWAITING_RESPONSE;
     313           0 :             continue;
     314             :         }
     315           6 :         case State::AWAITING_RESPONSE:
     316           6 :             if (_payload[0] != RPLIDAR_PREAMBLE) {
     317             :                 // this is a protocol error.  Reset.
     318           0 :                 reset();
     319           0 :                 return;
     320             :             }
     321             : 
     322             :             // descriptor packet has 7 byte in total
     323           6 :             if (_byte_count < sizeof(_descriptor)) {
     324           0 :                 return;
     325             :             }
     326             :             // identify the payload data after the descriptor
     327             :             static const _descriptor SCAN_DATA_DESCRIPTOR[] {
     328             :                 { RPLIDAR_PREAMBLE, 0x5A, 0x05, 0x00, 0x00, 0x40, 0x81 }
     329             :             };
     330             :             static const _descriptor HEALTH_DESCRIPTOR[] {
     331             :                 { RPLIDAR_PREAMBLE, 0x5A, 0x03, 0x00, 0x00, 0x00, 0x06 }
     332             :             };
     333             :             static const _descriptor DEVICE_INFO_DESCRIPTOR[] {
     334             :                 { RPLIDAR_PREAMBLE, 0x5A, 0x14, 0x00, 0x00, 0x00, 0x04 }
     335             :             };
     336             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     337             :             static const _descriptor EXPRESS_DATA_DESCRIPTOR[] {
     338             :                 { RPLIDAR_PREAMBLE, 0x5A, 0x54, 0x00, 0x00, 0x40, 0x85 }
     339             :             };
     340             : #endif
     341             :             Debug(2,"LIDAR descriptor found");
     342           6 :             if (memcmp((void*)&_payload[0], SCAN_DATA_DESCRIPTOR, sizeof(_descriptor)) == 0) {
     343           2 :                 _state = State::AWAITING_SCAN_DATA;
     344           4 :             } else if (memcmp((void*)&_payload[0], DEVICE_INFO_DESCRIPTOR, sizeof(_descriptor)) == 0) {
     345           3 :                 _state = State::AWAITING_DEVICE_INFO;
     346           1 :             } else if (memcmp((void*)&_payload[0], HEALTH_DESCRIPTOR, sizeof(_descriptor)) == 0) {
     347           0 :                 _state = State::AWAITING_HEALTH;
     348             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     349           1 :             } else if (_use_dense_express && memcmp((void*)&_payload[0], EXPRESS_DATA_DESCRIPTOR, sizeof(_descriptor)) == 0) {
     350           1 :                 _state = State::AWAITING_EXPRESS_DATA;
     351           1 :                 _express_stream_len = 0;
     352           1 :                 _sync_error = 0;
     353           1 :                 const uint16_t express_bytes = _byte_count - sizeof(_descriptor);
     354           1 :                 if (express_bytes != 0) {
     355           0 :                     memcpy(_express_stream, &_payload[sizeof(_descriptor)], express_bytes);
     356           0 :                     _express_stream_len = express_bytes;
     357             :                 }
     358           1 :                 _byte_count = 0;
     359           1 :                 break;
     360             : #endif
     361             :             } else {
     362             :                 // unknown descriptor.  Ignore it.
     363             :             }
     364           5 :             consume_bytes(sizeof(_descriptor));
     365           5 :             break;
     366             : 
     367           3 :         case State::AWAITING_DEVICE_INFO:
     368           3 :             if (_byte_count < sizeof(_payload.device_info)) {
     369           0 :                 return;
     370             :             }
     371           3 :             parse_response_device_info();
     372           3 :             consume_bytes(sizeof(_payload.device_info));
     373           3 :             break;
     374             : 
     375       81726 :         case State::AWAITING_SCAN_DATA:
     376       81726 :             if (_byte_count < sizeof(_payload.sensor_scan)) {
     377           0 :                 return;
     378             :             }
     379       81726 :             parse_response_data();
     380       81726 :             consume_bytes(sizeof(_payload.sensor_scan));
     381       81726 :             break;
     382             : 
     383           0 :         case State::AWAITING_HEALTH:
     384           0 :             if (_byte_count < sizeof(_payload.sensor_health)) {
     385           0 :                 return;
     386             :             }
     387           0 :             parse_response_health();
     388           0 :             consume_bytes(sizeof(_payload.sensor_health));
     389           0 :             break;
     390             : 
     391             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     392           0 :         case State::AWAITING_EXPRESS_DATA:
     393           0 :             break;  // handled above, before _payload read
     394             : #endif
     395           0 :         }
     396             :     }
     397             : }
     398             : 
     399           3 : void AP_Proximity_RPLidarA2::parse_response_device_info()
     400             : {
     401             :     Debug(1, "Received DEVICE_INFO");
     402           3 :     const char *device_type = "UNKNOWN";
     403           3 :     switch (_payload.device_info.model) {
     404           0 :     case 0x18:
     405           0 :         model = Model::A1;
     406           0 :         device_type = "A1";
     407           0 :         break;
     408           2 :     case 0x28:
     409           2 :         model = Model::A2;
     410           2 :         device_type = "A2";
     411           2 :         break;
     412           0 :     case 0x2C:
     413           0 :         model = Model::A2M12;
     414           0 :         device_type = "A2M12";
     415           0 :         break;
     416           0 :     case 0x41:
     417           0 :         model=Model::C1;
     418           0 :         device_type="C1";
     419           0 :         break;
     420           0 :     case 0x61:
     421           0 :         model = Model::S1;
     422           0 :         device_type = "S1";
     423           0 :         break;
     424           1 :     case 0x71:
     425           1 :         model = Model::S2;
     426           1 :         device_type = "S2";
     427           1 :         break;
     428           3 :     default:
     429             :         Debug(1, "Unknown device (%u)", _payload.device_info.model);
     430             :     }
     431           3 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "RPLidar %s hw=%u fw=%u.%u", device_type, _payload.device_info.hardware, _payload.device_info.firmware_minor, _payload.device_info.firmware_major);
     432             : 
     433             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     434             :     // enable Dense EXPRESS path
     435           3 :     _use_dense_express = (model == Model::S2);
     436           3 :     if (_use_dense_express) {
     437           1 :         _express_stream_len = 0;
     438           1 :         send_express_scan_request();
     439             :     } else {
     440           2 :         send_scan_mode_request();
     441             :     }
     442             : #else
     443             :     if (model == Model::S2) {
     444             :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "RPLidar S2 ExpressScan disabled");
     445             :         return;
     446             :     }
     447             :     send_scan_mode_request();
     448             : #endif
     449           3 :     _state = State::AWAITING_RESPONSE;
     450           3 : }
     451             : 
     452       81726 : void AP_Proximity_RPLidarA2::parse_response_data()
     453             : {
     454       81726 :     if (_sync_error) {
     455             :         // out of 5-byte sync mask -> catch new revolution
     456             :         Debug(1, "       OUT OF SYNC");
     457             :         // on first revolution bit 1 = 1, bit 2 = 0 of the first byte
     458           0 :         if ((_payload[0] & 0x03) == 0x01) {
     459           0 :             _sync_error = 0;
     460             :             Debug(1, "                  RESYNC");
     461             :         } else {
     462           0 :             return;
     463             :         }
     464             :     }
     465             :     Debug(2, "UART %02x %02x%02x %02x%02x", _payload[0], _payload[2], _payload[1], _payload[4], _payload[3]); //show HEX values
     466             :     // check if valid SCAN packet: a valid packet starts with startbits which are complementary plus a checkbit in byte+1
     467       81726 :     if (!((_payload.sensor_scan.startbit == !_payload.sensor_scan.not_startbit) && _payload.sensor_scan.checkbit)) {
     468             :         Debug(1, "Invalid Payload");
     469           0 :         _sync_error++;
     470           0 :         return;
     471             :     }
     472             : 
     473       81726 :     const float angle_sign = (params.orientation == 1) ? -1.0f : 1.0f;
     474       81726 :     const float angle_deg = wrap_360(_payload.sensor_scan.angle_q6/64.0f * angle_sign + params.yaw_correction);
     475       81726 :     const float distance_m = (_payload.sensor_scan.distance_q2/4000.0f);
     476             : #if RP_DEBUG_LEVEL >= 2
     477             :     const float quality = _payload.sensor_scan.quality;
     478             :     Debug(2, "   D%02.2f A%03.1f Q%0.2f", distance_m, angle_deg, quality);
     479             : #endif
     480       81726 :     _last_distance_received_ms = AP_HAL::millis();
     481       81726 :     if (!ignore_reading(angle_deg, distance_m)) {
     482       81726 :         const AP_Proximity_Boundary_3D::Face face = frontend.boundary.get_face(angle_deg);
     483             : 
     484       81726 :         if (face != _last_face) {
     485             :             // distance is for a new face, the previous one can be updated now
     486        6540 :             if (_last_distance_valid) {
     487        3818 :                 frontend.boundary.set_face_attributes(_last_face, _last_angle_deg, _last_distance_m, state.instance);
     488             :             } else {
     489             :                 // reset distance from last face
     490        2722 :                 frontend.boundary.reset_face(face, state.instance);
     491             :             }
     492             : 
     493             :             // initialize the new face
     494        6540 :             _last_face = face;
     495        6540 :             _last_distance_valid = false;
     496             :         }
     497       81726 :         if (distance_m > distance_min_m()) {
     498             :             // update shortest distance
     499       13847 :             if (!_last_distance_valid || (distance_m < _last_distance_m)) {
     500        8594 :                 _last_distance_m = distance_m;
     501        8594 :                 _last_distance_valid = true;
     502        8594 :                 _last_angle_deg = angle_deg;
     503             :             }
     504             :             // update OA database
     505       13847 :             database_push(_last_angle_deg, _last_distance_m);
     506             :         }
     507             :     }
     508             : }
     509             : 
     510           0 : void AP_Proximity_RPLidarA2::parse_response_health()
     511             : {
     512             :     // health issue if status is "3" ->HW error
     513           0 :     if (_payload.sensor_health.status == 3) {
     514             :         Debug(1, "LIDAR Error");
     515             :     }
     516             :     Debug(1, "LIDAR Healthy");
     517           0 : }
     518             : 
     519             : #if AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     520             : // verify Dense capsulated (Express) block checksum
     521        3387 : bool AP_Proximity_RPLidarA2::verify_cabin_checksum(const uint8_t *buf, size_t len)
     522             : {
     523        3387 :     if (buf == nullptr) {
     524           0 :         return false;
     525             :     }
     526             : 
     527        3387 :     uint8_t checksum = 0;
     528      281121 :     for (size_t i = sizeof(_express_header); i < len; i++) {
     529      277734 :         checksum ^= buf[i];
     530             :     }
     531             : 
     532        3387 :     const uint8_t expected_b0 = uint8_t((EXPRESS_SYNC1 << 4) | (checksum & 0x0F));
     533        3387 :     const uint8_t expected_b1 = uint8_t((EXPRESS_SYNC2 << 4) | ((checksum >> 4) & 0x0F));
     534             : 
     535        3387 :     return (buf[0] == expected_b0) && (buf[1] == expected_b1);
     536             : }
     537             : 
     538             : 
     539             : // parse Dense capsulated Express block
     540        3387 : void AP_Proximity_RPLidarA2::parse_response_express(const uint8_t *buf)
     541             : {
     542        3387 :     if (buf == nullptr) {
     543        1540 :         return;
     544             :     }
     545             : 
     546        3387 :     _last_distance_received_ms = AP_HAL::millis();
     547             : 
     548             :     // start_angle_q6: 16-bit, Q6 format at bytes [2..3]
     549        3387 :     const uint16_t start_angle_q6 = (uint16_t)buf[2] | ((uint16_t)buf[3] << 8);
     550        3387 :     const float start_angle_deg_raw = start_angle_q6 / 64.0f; // Q6 -> degrees
     551             : 
     552        3387 :     const float angle_sign = (params.orientation == 1) ? -1.0f : 1.0f;
     553             : 
     554             :     // cabins: 40 cabins * 2-byte distance (mm) starting at buf[4]
     555        3387 :     const uint8_t *cab = buf + 4;
     556             :     static constexpr int NUM_CABINS = 40;
     557             :     // S2 Dense Express default: 32000 samples/s at 10Hz = 0.1125 deg/cabin
     558             :     static constexpr float DENSE_CABIN_ANGLE_STEP_DEG = 0.1125f;
     559             : 
     560             :     // find the shortest valid cabin; rescan only if that cabin is ignored
     561        3387 :     const float dist_min_m_local = distance_min_m();
     562        3387 :     const uint8_t *cab_start = cab;
     563        3387 :     bool have_min = false;
     564        3387 :     float min_distance_m = 0.0f;
     565        3387 :     int min_i = 0;
     566             : 
     567      138867 :     for (int i = 0; i < NUM_CABINS; i++) {
     568      135480 :         const uint16_t dist_mm = (uint16_t)cab[0] | ((uint16_t)cab[1] << 8);
     569      135480 :         cab += 2;
     570      135480 :         if (dist_mm == 0) {
     571       79427 :             continue;
     572             :         }
     573       56053 :         const float distance_m = dist_mm * 0.001f;
     574       56053 :         if (distance_m <= dist_min_m_local) {
     575           0 :             continue;
     576             :         }
     577       56053 :         if (!have_min || distance_m < min_distance_m) {
     578       24248 :             have_min = true;
     579       24248 :             min_distance_m = distance_m;
     580       24248 :             min_i = i;
     581             :         }
     582             :     }
     583             : 
     584        3387 :     if (!have_min) {
     585        1540 :         return;
     586             :     }
     587             : 
     588        1847 :     const float min_cab_angle_raw = start_angle_deg_raw + min_i * DENSE_CABIN_ANGLE_STEP_DEG;
     589        1847 :     float min_angle_deg = wrap_360(min_cab_angle_raw * angle_sign + params.yaw_correction);
     590             : 
     591        1847 :     if (ignore_reading(min_angle_deg, min_distance_m)) {
     592             :         // fallback: rescan with per-cabin ignore filter
     593           0 :         have_min = false;
     594           0 :         cab = cab_start;
     595             : 
     596           0 :         for (int i = 0; i < NUM_CABINS; i++) {
     597           0 :             const uint16_t dist_mm = (uint16_t)cab[0] | ((uint16_t)cab[1] << 8);
     598           0 :             cab += 2;
     599           0 :             if (dist_mm == 0) {
     600           0 :                 continue;
     601             :             }
     602           0 :             const float distance_m = dist_mm * 0.001f;
     603           0 :             if (distance_m <= dist_min_m_local) {
     604           0 :                 continue;
     605             :             }
     606           0 :             const float cab_angle_raw = start_angle_deg_raw + i * DENSE_CABIN_ANGLE_STEP_DEG;
     607           0 :             const float angle_deg = wrap_360(cab_angle_raw * angle_sign + params.yaw_correction);
     608           0 :             if (ignore_reading(angle_deg, distance_m)) {
     609           0 :                 continue;
     610             :             }
     611           0 :             if (!have_min || distance_m < min_distance_m) {
     612           0 :                 have_min = true;
     613           0 :                 min_distance_m = distance_m;
     614           0 :                 min_angle_deg = angle_deg;
     615             :             }
     616             :         }
     617             : 
     618           0 :         if (!have_min) {
     619           0 :             return;
     620             :         }
     621             :     }
     622             : 
     623        1847 :     const AP_Proximity_Boundary_3D::Face face = frontend.boundary.get_face(min_angle_deg);
     624             : 
     625        1847 :     if (face != _last_face) {
     626             :         // distance is for a new face, the previous one can be updated now
     627         198 :         if (_last_distance_valid) {
     628         197 :             frontend.boundary.set_face_attributes(_last_face, _last_angle_deg, _last_distance_m, state.instance);
     629             :         } else {
     630             :             // reset distance from last face
     631           1 :             frontend.boundary.reset_face(face, state.instance);
     632             :         }
     633             : 
     634             :         // initialize the new face
     635         198 :         _last_face = face;
     636         198 :         _last_distance_valid = false;
     637             :     }
     638             : 
     639             :     // update shortest distance for this face
     640        1847 :     if (!_last_distance_valid || (min_distance_m < _last_distance_m)) {
     641         595 :         _last_distance_m = min_distance_m;
     642         595 :         _last_distance_valid = true;
     643         595 :         _last_angle_deg = min_angle_deg;
     644             :     }
     645             :     // update OA database
     646        1847 :     database_push(_last_angle_deg, _last_distance_m);
     647             : }
     648             : 
     649       13548 : bool AP_Proximity_RPLidarA2::express_time_exceeded(uint32_t start_us) const
     650             : {
     651             :     static constexpr uint32_t MAX_US = 1000;
     652       13548 :     return (AP_HAL::micros() - start_us) > MAX_US;
     653             : }
     654             : 
     655           0 : void AP_Proximity_RPLidarA2::ensure_express_stream_space(uint16_t need)
     656             : {
     657           0 :     if (_express_stream_len + need <= EXPRESS_STREAM_BUFFER_SIZE) {
     658           0 :         return;
     659             :     }
     660             : 
     661           0 :     uint16_t keep = EXPRESS_STREAM_BUFFER_SIZE / 2;
     662           0 :     keep = MIN(keep, _express_stream_len);
     663             : 
     664           0 :     if (keep == 0) {
     665           0 :         _express_stream_len = 0;
     666           0 :         _sync_error = 0;
     667             :         Debug(1, "EXPRESS stream overflow, dropping all data");
     668           0 :         return;
     669             :     }
     670             : 
     671           0 :     const uint16_t src = _express_stream_len - keep;
     672           0 :     if (src != 0) {
     673           0 :         memmove(_express_stream, _express_stream + src, keep);
     674             :     }
     675           0 :     _express_stream_len = keep;
     676           0 :     _sync_error = 0;
     677             :     Debug(1, "EXPRESS stream overflow, dropping old data");
     678             : }
     679             : 
     680        8130 : void AP_Proximity_RPLidarA2::handle_express_data()
     681             : {
     682        8130 :     const uint32_t start_us = AP_HAL::micros();
     683             : 
     684        8130 :     uint16_t bytes_read_total = 0;
     685             : 
     686       11517 :     while (_uart->available() && bytes_read_total < EXPRESS_MAX_BYTES_CONSUME) {
     687        3387 :         if (express_time_exceeded(start_us)) {
     688           0 :             break;
     689             :         }
     690             : 
     691        3387 :         const uint16_t remaining = uint16_t(EXPRESS_MAX_BYTES_CONSUME - bytes_read_total);
     692        3387 :         uint16_t space = EXPRESS_STREAM_BUFFER_SIZE - _express_stream_len;
     693        3387 :         if (space == 0) {
     694           0 :             ensure_express_stream_space(1);
     695           0 :             space = EXPRESS_STREAM_BUFFER_SIZE - _express_stream_len;
     696           0 :             if (space == 0) {
     697           0 :                 break;
     698             :             }
     699             :         }
     700        3387 :         const uint16_t to_read = MIN(space, remaining);
     701             : 
     702        3387 :         if (to_read == 0) {
     703           0 :             break;
     704             :         }
     705             : 
     706        3387 :         const uint16_t n = _uart->read(_express_stream + _express_stream_len, to_read);
     707        3387 :         if (n == 0) {
     708           0 :             break;
     709             :         }
     710             : 
     711        3387 :         _express_stream_len += n;
     712        3387 :         bytes_read_total += n;
     713             :     }
     714             : 
     715             :     // scan the stream buffer for valid headers and process full blocks
     716        8130 :     uint16_t idx = 0;
     717             : 
     718       11517 :     while (_express_stream_len >= idx + 2) {
     719        3387 :         if (express_time_exceeded(start_us)) {
     720           0 :             break;
     721             :         }
     722             : 
     723             :         // search for a header candidate (upper nibbles 0xA / 0x5)
     724        3387 :         while (idx + 1 < _express_stream_len) {
     725        3387 :             if (express_time_exceeded(start_us)) {
     726           0 :                 break;
     727             :             }
     728        3387 :             const uint8_t b0 = _express_stream[idx];
     729        3387 :             const uint8_t b1 = _express_stream[idx + 1];
     730        3387 :             if ((b0 >> 4) == EXPRESS_SYNC1 && (b1 >> 4) == EXPRESS_SYNC2) {
     731             :                 // header candidate found
     732        3387 :                 break;
     733             :             }
     734           0 :             idx++;
     735             :         }
     736             : 
     737        3387 :         if (express_time_exceeded(start_us)) {
     738           0 :             break;
     739             :         }
     740             : 
     741        3387 :         if (idx + 1 >= _express_stream_len) {
     742             :             // no header candidate found, drop all accumulated data
     743           0 :             if (_express_stream_len > 0) {
     744             :                 Debug(1, "EXPRESS: no header candidate, dropping %u bytes", unsigned(_express_stream_len));
     745           0 :                 _express_stream[0] = _express_stream[_express_stream_len - 1];
     746           0 :                 _express_stream_len = 1;
     747             :             } else {
     748           0 :                 _express_stream_len = 0;
     749             :             }
     750           0 :             _sync_error = 0;
     751           0 :             return;
     752             :         }
     753             : 
     754             :         // idx now points to a header candidate
     755        3387 :         if (_express_stream_len - idx < EXPRESS_BLOCK_SIZE) {
     756             :             // not enough bytes for a full block
     757             :             // move remaining bytes to the front and wait for more data
     758           0 :             if (idx > 0) {
     759           0 :                 memmove(_express_stream,
     760           0 :                         _express_stream + idx,
     761           0 :                         _express_stream_len - idx);
     762           0 :                 _express_stream_len -= idx;
     763             :             }
     764           0 :             return;
     765             :         }
     766             : 
     767             :         // pointer to the candidate block
     768        3387 :         uint8_t *blk = _express_stream + idx;
     769             : 
     770        3387 :         if (!verify_cabin_checksum(blk, EXPRESS_BLOCK_SIZE)) {
     771             :             // upper nibbles match but checksum is invalid
     772             :             // advance by one byte and try to resynchronise
     773             :             Debug(1, "EXPRESS/DENSE checksum error");
     774           0 :             _sync_error++;
     775             : 
     776           0 :             if (_sync_error > 10) {
     777           0 :                 reset_rplidar();
     778           0 :                 _express_stream_len = 0;
     779           0 :                 return;
     780             :             }
     781             : 
     782           0 :             idx++;
     783           0 :             continue;
     784             :         }
     785             : 
     786             :         // valid block found
     787        3387 :         _sync_error = 0;
     788        3387 :         parse_response_express(blk);
     789             : 
     790             :         // consume this block and look for the next one
     791        3387 :         idx += EXPRESS_BLOCK_SIZE;
     792             :     }
     793             : 
     794             :     // compact any remaining unprocessed bytes to the front of the buffer
     795        8130 :     if (idx > 0 && idx <= _express_stream_len) {
     796        3387 :         const uint16_t remaining = _express_stream_len - idx;
     797        3387 :         if (remaining > 0) {
     798           0 :             memmove(_express_stream,
     799           0 :                     _express_stream + idx,
     800             :                     remaining);
     801             :         }
     802        3387 :         _express_stream_len = remaining;
     803             :     }
     804             : }
     805             : #endif // AP_PROXIMITY_RPLIDAR_EXPRESSSCAN_ENABLED
     806             : 
     807             : #endif // AP_PROXIMITY_RPLIDARA2_ENABLED

Generated by: LCOV version 1.14