mirror of
https://github.com/Livox-SDK/livox_ros_driver.git
synced 2023-04-06 15:49:55 +08:00
feature:add offset time for every point
fix:fix directory tree bug in the license file change:update custom msg format in README.md
This commit is contained in:
18
README.md
18
README.md
@@ -1,12 +1,12 @@
|
|||||||
# livox ros driver
|
# Livox ROS Driver
|
||||||
|
|
||||||
### Run livox ros driver
|
### Run livox ros driver
|
||||||
|
|
||||||
livox_ros_driver is a new ros package under the Livox-SDK/Livox-SDK-ROS/src directory, which is designed to gradually become the standard driver package for livox devices in the ros environment. The driver offers users a wealth of options:
|
livox_ros_driver is a new ros package under the Livox-SDK/Livox-SDK-ROS/src directory, which is designed to gradually become the standard driver package for livox devices in the ros environment. The driver offers users a wealth of options:
|
||||||
|
|
||||||
1. Publish pointcloud2 format point cloud and automatically load rviz;
|
1. Publish pointcloud2 format point cloud and automatically load rviz.
|
||||||
|
|
||||||
for example:
|
for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
roslaunch livox_ros_driver livox_lidar_rviz.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
roslaunch livox_ros_driver livox_lidar_rviz.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
||||||
@@ -18,9 +18,9 @@ or
|
|||||||
roslaunch livox_ros_driver livox_hub_rviz.launch bd_list:="hub_broadcast_code"
|
roslaunch livox_ros_driver livox_hub_rviz.launch bd_list:="hub_broadcast_code"
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Publish pointcloud2 format point cloud only;
|
2. Publish pointcloud2 format point cloud only.
|
||||||
|
|
||||||
for example:
|
for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
roslaunch livox_ros_driver livox_lidar.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
roslaunch livox_ros_driver livox_lidar.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
||||||
@@ -34,9 +34,9 @@ roslaunch livox_ros_driver livox_hub.launch bd_list:="hub_broadcast_code"
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
3. Publish livox custom format point cloud;
|
3. Publish livox custom format point cloud.
|
||||||
|
|
||||||
for example:
|
for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
roslaunch livox_ros_driver livox_lidar_msg.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
roslaunch livox_ros_driver livox_lidar_msg.launch bd_list:="broadcast_code1&broadcast_code2&broadcast_code3"
|
||||||
@@ -48,12 +48,11 @@ or
|
|||||||
roslaunch livox_ros_driver livox_hub_msg.launch bd_list:="hub_broadcast_code"
|
roslaunch livox_ros_driver livox_hub_msg.launch bd_list:="hub_broadcast_code"
|
||||||
```
|
```
|
||||||
|
|
||||||
livox custom msg format:
|
livox custom msg format:
|
||||||
|
|
||||||
```
|
```
|
||||||
Header header # ROS standard message header
|
Header header # ROS standard message header
|
||||||
uint64 timebase # The time of first point
|
uint64 timebase # The time of first point
|
||||||
uint32 timestep # Time interval between adjacent point clouds
|
|
||||||
uint32 point_num # Total number of pointclouds
|
uint32 point_num # Total number of pointclouds
|
||||||
uint8 lidar_id # Lidar device id number
|
uint8 lidar_id # Lidar device id number
|
||||||
uint8[3] rsvd # Reserved use
|
uint8[3] rsvd # Reserved use
|
||||||
@@ -61,6 +60,7 @@ CustomPoint[] points # Pointcloud data
|
|||||||
```
|
```
|
||||||
pointcloud format:
|
pointcloud format:
|
||||||
```
|
```
|
||||||
|
uint32 offset_time # offset time relative to the base time
|
||||||
float32 x # X axis, unit:m
|
float32 x # X axis, unit:m
|
||||||
float32 y # Y axis, unit:m
|
float32 y # Y axis, unit:m
|
||||||
float32 z # Z axis, unit:m
|
float32 z # Z axis, unit:m
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
const uint64_t kPacketTimeGap = 1000000; // 1ms = 1000000ns
|
const uint64_t kPacketTimeGap = 1000000; // 1ms = 1000000ns
|
||||||
const uint64_t kMaxPacketTimeGap = 1700000; // the threshold of packet continuous
|
const uint64_t kMaxPacketTimeGap = 1700000; // the threshold of packet continuous
|
||||||
|
const uint64_t kDeviceDisconnectThreshold = 1000000000; // the threshold of device disconect
|
||||||
const uint64_t kNsPerSecond = 1000000000; // 1s = 1000000000ns
|
const uint64_t kNsPerSecond = 1000000000; // 1s = 1000000000ns
|
||||||
const uint32_t kPublishIntervalMs = 50; // unit:ms
|
const uint32_t kPublishIntervalMs = 50; // unit:ms
|
||||||
|
|
||||||
@@ -156,12 +157,20 @@ void InitStoragePacketPool(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t QueuePop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
static void QueueProPop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
||||||
uint32_t mask = queue->mask;
|
uint32_t mask = queue->mask;
|
||||||
uint32_t rd_idx = queue->rd_idx & mask;
|
uint32_t rd_idx = queue->rd_idx & mask;
|
||||||
|
|
||||||
memcpy(storage_packet, queue->storage_packet[rd_idx], sizeof(StoragePacket));
|
memcpy(storage_packet, queue->storage_packet[rd_idx], sizeof(StoragePacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void QueuePopUpdate(StoragePacketQueue* queue) {
|
||||||
queue->rd_idx++;
|
queue->rd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t QueuePop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
||||||
|
QueueProPop(queue, storage_packet);
|
||||||
|
QueuePopUpdate(queue);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -236,6 +245,15 @@ static uint64_t GetStoragePacketTimestamp(StoragePacket* packet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t GetPointInterval(uint32_t device_type) {
|
||||||
|
if ((kDeviceTypeLidarTele == device_type) || \
|
||||||
|
(kDeviceTypeLidarHorizon == device_type)) {
|
||||||
|
return 4167; // 4167 ns
|
||||||
|
} else {
|
||||||
|
return 10000; // ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* for pointcloud convert process */
|
/* for pointcloud convert process */
|
||||||
static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_num, \
|
static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_num, \
|
||||||
uint8_t handle) {
|
uint8_t handle) {
|
||||||
@@ -251,7 +269,7 @@ static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_nu
|
|||||||
|
|
||||||
StoragePacket storage_packet;
|
StoragePacket storage_packet;
|
||||||
while (published_packet < packet_num) {
|
while (published_packet < packet_num) {
|
||||||
QueuePop(queue, &storage_packet);
|
QueueProPop(queue, &storage_packet);
|
||||||
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
||||||
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
||||||
|
|
||||||
@@ -278,6 +296,7 @@ static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_nu
|
|||||||
cloud->points.push_back(point);
|
cloud->points.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueuePopUpdate(queue);
|
||||||
last_timestamp = timestamp;
|
last_timestamp = timestamp;
|
||||||
++published_packet;
|
++published_packet;
|
||||||
}
|
}
|
||||||
@@ -294,6 +313,8 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
uint64_t timestamp = 0;
|
uint64_t timestamp = 0;
|
||||||
uint64_t last_timestamp = 0;
|
uint64_t last_timestamp = 0;
|
||||||
uint32_t published_packet = 0;
|
uint32_t published_packet = 0;
|
||||||
|
uint32_t point_interval = GetPointInterval(lidars[handle].info.type);
|
||||||
|
uint32_t packet_offset_time = 0; // ns
|
||||||
|
|
||||||
/* init livox custom msg */
|
/* init livox custom msg */
|
||||||
livox_ros_driver::CustomMsg livox_msg;
|
livox_ros_driver::CustomMsg livox_msg;
|
||||||
@@ -303,30 +324,33 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
++msg_seq;
|
++msg_seq;
|
||||||
livox_msg.header.stamp = ros::Time::now();
|
livox_msg.header.stamp = ros::Time::now();
|
||||||
livox_msg.timebase = 0;
|
livox_msg.timebase = 0;
|
||||||
livox_msg.timestep = 10000; // 10us = 10^4ns;
|
|
||||||
livox_msg.point_num = 0;
|
livox_msg.point_num = 0;
|
||||||
livox_msg.lidar_id = handle;
|
livox_msg.lidar_id = handle;
|
||||||
|
|
||||||
StoragePacket storage_packet;
|
StoragePacket storage_packet;
|
||||||
while (published_packet < packet_num) {
|
while (published_packet < packet_num) {
|
||||||
QueuePop(queue, &storage_packet);
|
QueueProPop(queue, &storage_packet);
|
||||||
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
||||||
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
||||||
|
|
||||||
timestamp = GetStoragePacketTimestamp(&storage_packet);
|
timestamp = GetStoragePacketTimestamp(&storage_packet);
|
||||||
if (published_packet && \
|
if (published_packet && \
|
||||||
((timestamp - last_timestamp) > kMaxPacketTimeGap)) {
|
((timestamp - last_timestamp) > kDeviceDisconnectThreshold)) {
|
||||||
ROS_INFO("packet loss : %ld", timestamp);
|
ROS_INFO("packet loss : %ld", timestamp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!livox_msg.timebase) {
|
if (!livox_msg.timebase) {
|
||||||
livox_msg.timebase = timestamp; // to us
|
livox_msg.timebase = timestamp; // to us
|
||||||
ROS_DEBUG("[%d]:%ld", handle, livox_msg.timebase);
|
packet_offset_time = 0; // first packet
|
||||||
|
ROS_DEBUG("[%d]:%ld %d", handle, livox_msg.timebase, point_interval);
|
||||||
|
} else {
|
||||||
|
packet_offset_time = (uint32_t)(timestamp - livox_msg.timebase);
|
||||||
}
|
}
|
||||||
livox_msg.point_num += storage_packet.point_num;
|
livox_msg.point_num += storage_packet.point_num;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < storage_packet.point_num; i++) {
|
for (uint32_t i = 0; i < storage_packet.point_num; i++) {
|
||||||
livox_ros_driver::CustomPoint point;
|
livox_ros_driver::CustomPoint point;
|
||||||
|
point.offset_time = packet_offset_time + i*point_interval;
|
||||||
point.x = raw_points->x/1000.0f;
|
point.x = raw_points->x/1000.0f;
|
||||||
point.y = raw_points->y/1000.0f;
|
point.y = raw_points->y/1000.0f;
|
||||||
point.z = raw_points->z/1000.0f;
|
point.z = raw_points->z/1000.0f;
|
||||||
@@ -336,6 +360,7 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
livox_msg.points.push_back(point);
|
livox_msg.points.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueuePopUpdate(queue);
|
||||||
last_timestamp = timestamp;
|
last_timestamp = timestamp;
|
||||||
++published_packet;
|
++published_packet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
const uint64_t kPacketTimeGap = 1000000; // 1ms = 1000000ns
|
const uint64_t kPacketTimeGap = 1000000; // 1ms = 1000000ns
|
||||||
const uint64_t kMaxPacketTimeGap = 1700000; // the threshold of packet continuous
|
const uint64_t kMaxPacketTimeGap = 1700000; // the threshold of packet continuous
|
||||||
|
const uint64_t kDeviceDisconnectThreshold = 1000000000; // the threshold of device disconect
|
||||||
const uint64_t kNsPerSecond = 1000000000; // 1s = 1000000000ns
|
const uint64_t kNsPerSecond = 1000000000; // 1s = 1000000000ns
|
||||||
const uint32_t kPublishIntervalMs = 50; // unit:ms
|
const uint32_t kPublishIntervalMs = 50; // unit:ms
|
||||||
|
|
||||||
@@ -154,12 +155,20 @@ void InitStoragePacketPool(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t QueuePop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
static void QueueProPop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
||||||
uint32_t mask = queue->mask;
|
uint32_t mask = queue->mask;
|
||||||
uint32_t rd_idx = queue->rd_idx & mask;
|
uint32_t rd_idx = queue->rd_idx & mask;
|
||||||
|
|
||||||
memcpy(storage_packet, queue->storage_packet[rd_idx], sizeof(StoragePacket));
|
memcpy(storage_packet, queue->storage_packet[rd_idx], sizeof(StoragePacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void QueuePopUpdate(StoragePacketQueue* queue) {
|
||||||
queue->rd_idx++;
|
queue->rd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t QueuePop(StoragePacketQueue* queue, StoragePacket* storage_packet) {
|
||||||
|
QueueProPop(queue, storage_packet);
|
||||||
|
QueuePopUpdate(queue);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -231,6 +240,15 @@ static uint64_t GetStoragePacketTimestamp(StoragePacket* packet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t GetPointInterval(uint32_t device_type) {
|
||||||
|
if ((kDeviceTypeLidarTele == device_type) || \
|
||||||
|
(kDeviceTypeLidarHorizon == device_type)) {
|
||||||
|
return 4167; // 4167 ns
|
||||||
|
} else {
|
||||||
|
return 10000; // ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* for pointcloud convert process */
|
/* for pointcloud convert process */
|
||||||
static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_num, \
|
static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_num, \
|
||||||
uint8_t handle) {
|
uint8_t handle) {
|
||||||
@@ -246,7 +264,7 @@ static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_nu
|
|||||||
|
|
||||||
StoragePacket storage_packet;
|
StoragePacket storage_packet;
|
||||||
while (published_packet < packet_num) {
|
while (published_packet < packet_num) {
|
||||||
QueuePop(queue, &storage_packet);
|
QueueProPop(queue, &storage_packet);
|
||||||
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
||||||
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
||||||
|
|
||||||
@@ -273,6 +291,7 @@ static uint32_t PublishPointcloud2(StoragePacketQueue* queue, uint32_t packet_nu
|
|||||||
cloud->points.push_back(point);
|
cloud->points.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueuePopUpdate(queue);
|
||||||
last_timestamp = timestamp;
|
last_timestamp = timestamp;
|
||||||
++published_packet;
|
++published_packet;
|
||||||
}
|
}
|
||||||
@@ -289,6 +308,8 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
uint64_t timestamp = 0;
|
uint64_t timestamp = 0;
|
||||||
uint64_t last_timestamp = 0;
|
uint64_t last_timestamp = 0;
|
||||||
uint32_t published_packet = 0;
|
uint32_t published_packet = 0;
|
||||||
|
uint32_t point_interval = GetPointInterval(lidars[handle].info.type);
|
||||||
|
uint32_t packet_offset_time = 0; // ns
|
||||||
|
|
||||||
/* init livox custom msg */
|
/* init livox custom msg */
|
||||||
livox_ros_driver::CustomMsg livox_msg;
|
livox_ros_driver::CustomMsg livox_msg;
|
||||||
@@ -298,30 +319,33 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
++msg_seq;
|
++msg_seq;
|
||||||
livox_msg.header.stamp = ros::Time::now();
|
livox_msg.header.stamp = ros::Time::now();
|
||||||
livox_msg.timebase = 0;
|
livox_msg.timebase = 0;
|
||||||
livox_msg.timestep = 10000; // 10us = 10^4ns;
|
|
||||||
livox_msg.point_num = 0;
|
livox_msg.point_num = 0;
|
||||||
livox_msg.lidar_id = handle;
|
livox_msg.lidar_id = handle;
|
||||||
|
|
||||||
StoragePacket storage_packet;
|
StoragePacket storage_packet;
|
||||||
while (published_packet < packet_num) {
|
while (published_packet < packet_num) {
|
||||||
QueuePop(queue, &storage_packet);
|
QueueProPop(queue, &storage_packet);
|
||||||
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
LivoxEthPacket* raw_packet = reinterpret_cast<LivoxEthPacket *>(storage_packet.raw_data);
|
||||||
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
LivoxRawPoint* raw_points = reinterpret_cast<LivoxRawPoint *>(raw_packet->data);
|
||||||
|
|
||||||
timestamp = GetStoragePacketTimestamp(&storage_packet);
|
timestamp = GetStoragePacketTimestamp(&storage_packet);
|
||||||
if (published_packet && \
|
if (published_packet && \
|
||||||
((timestamp - last_timestamp) > kMaxPacketTimeGap)) {
|
((timestamp - last_timestamp) > kDeviceDisconnectThreshold)) {
|
||||||
ROS_INFO("packet loss : %ld", timestamp);
|
ROS_INFO("packet loss : %ld", timestamp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!livox_msg.timebase) {
|
if (!livox_msg.timebase) {
|
||||||
livox_msg.timebase = timestamp; // to us
|
livox_msg.timebase = timestamp; // to us
|
||||||
ROS_DEBUG("[%d]:%ld", handle, livox_msg.timebase);
|
packet_offset_time = 0; // first packet
|
||||||
|
ROS_DEBUG("[%d]:%ld %d", handle, livox_msg.timebase, point_interval);
|
||||||
|
} else {
|
||||||
|
packet_offset_time = (uint32_t)(timestamp - livox_msg.timebase);
|
||||||
}
|
}
|
||||||
livox_msg.point_num += storage_packet.point_num;
|
livox_msg.point_num += storage_packet.point_num;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < storage_packet.point_num; i++) {
|
for (uint32_t i = 0; i < storage_packet.point_num; i++) {
|
||||||
livox_ros_driver::CustomPoint point;
|
livox_ros_driver::CustomPoint point;
|
||||||
|
point.offset_time = packet_offset_time + i*point_interval;
|
||||||
point.x = raw_points->x/1000.0f;
|
point.x = raw_points->x/1000.0f;
|
||||||
point.y = raw_points->y/1000.0f;
|
point.y = raw_points->y/1000.0f;
|
||||||
point.z = raw_points->z/1000.0f;
|
point.z = raw_points->z/1000.0f;
|
||||||
@@ -331,6 +355,7 @@ static uint32_t PublishCustomPointcloud(StoragePacketQueue* queue, uint32_t pack
|
|||||||
livox_msg.points.push_back(point);
|
livox_msg.points.push_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueuePopUpdate(queue);
|
||||||
last_timestamp = timestamp;
|
last_timestamp = timestamp;
|
||||||
++published_packet;
|
++published_packet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
Header header # ROS standard message header
|
Header header # ROS standard message header
|
||||||
uint64 timebase # The time of first point
|
uint64 timebase # The time of first point
|
||||||
uint32 timestep # Time interval between adjacent point clouds
|
|
||||||
uint32 point_num # Total number of pointclouds
|
uint32 point_num # Total number of pointclouds
|
||||||
uint8 lidar_id # Lidar device id number
|
uint8 lidar_id # Lidar device id number
|
||||||
uint8[3] rsvd # Reserved use
|
uint8[3] rsvd # Reserved use
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Livox costom pointcloud format.
|
# Livox costom pointcloud format.
|
||||||
|
|
||||||
|
uint32 offset_time # offset time relative to the base time
|
||||||
float32 x # X axis, unit:m
|
float32 x # X axis, unit:m
|
||||||
float32 y # Y axis, unit:m
|
float32 y # Y axis, unit:m
|
||||||
float32 z # Z axis, unit:m
|
float32 z # Z axis, unit:m
|
||||||
|
|||||||
Reference in New Issue
Block a user