LattePanda

前言与Ubuntu18.04安装

​ 原本用的是树莓派3B,官方的desktop版本只有22.04版的,装上之后奇卡无比,浏览器都会闪退。但是这样也能用,之后装上了ROS2,惨痛的发现在终端编译都会闪退,所以尝试换成18.04(感觉对性能要求会低一些),正好18.04也与厂家代码更适配,所以尝试装了server版本,因为没有图形化界面,需要自己联网,安装图形化界面,过程比较曲折。可以参考这一篇博客

安装ROS的话我发现了一个神仙博主提供的一键安装命令,非常方便。

可是安装完了之后我悲惨的发现,就是树莓派性能不够,图形化界面装完了之后直接显示花屏。

​ 所以我干脆一鼓作气买了个LattePanda2,性能据说约等于两个树莓派4B,八个树莓派3B。是X86架构的,自带Win10,还附带了一个Leonardo型号的Arduino,真不错。

安装Ubuntu18.04

​ Tips:

  • 供电需要一个手机充电头和一个质量不错的数据线,至少2A。(之前树莓派先是随便找了一根线,结果一直显示供电不足)
  • 用作启动盘的U盘质量也要好一点,不然会一直刻录失败(一开始用的送的U盘,结果折腾半天)

可以参考这一篇文章(虽然是英文的,但是也可以看懂,而且说的很详细)

镜像下载链接

​ 接下来就是安装系统,具体可以参考上面的博客。有一点需要额外提一嘴,就是安装到后面发现一直是一个橙黄色背景,没有显示任何其他窗口,但是会发现能拖出鼠标,按键盘也有反应。其实是因为LattePanda内置一个屏幕,但是如果直接只用HDMI连显示屏的话这个是看不到的,相当于在左上角有一个看不见主屏幕,系统安装的操作窗口在那边。所以只能尝试用鼠标把那边的窗口拖过来,多试几次总能抓过来的😂。

远程桌面

  • 使用软件:No machine

​ 在同一个局域网下,主机上的客户端可以搜索到Ubuntu,双击连接。接下来需要输入用户和密码,就是对应Ubuntu上的用户名和密码。我设置的用户名是Anya,计算机名是Bonder,所以输入 anya密码

注意这里的用户名是不用输入大写的,区分了大小写反而会连接不上。

ROS1

安装melodic

工作空间配置

按照准备工作手册配置,不过还是会碰到不少问题。

手册链接

还要先安装一大堆包

  • geographic-msgs
  • moveit-core
  • world-canvas-msgs
  • moveit-visual-tools
  • moveit-ros-move-group
  • moveit-ros-planning-interface

中间出现了一些问题

E: 无法获得锁 /var/lib/dpkg/lock-frontend - open (11: 资源暂时不可用)
E: 无法获取 dpkg 前端锁 (/var/lib/dpkg/lock-frontend),是否有其他进程正占用它?

可以参考这一篇文章

里面的一些命令的解释:

无法定位软件包 ros-melodic-world-canvas-msgs

这样的话干脆直接在Github下载,然后粘贴到 /工作空间/src 下面,重命名为 world_canvas_msgs

corot/world_canvas_msgs: Messages and services for the semantic maps framework (github.com)

​ 但是编译出来报错,不知道是不是包不对,其他地方也找不到这个包了。这个包实际上也不是 melodic 版本的,不知道这个商家咋搞的。

​ 那这样一直卡着也不是个办法,所谓断臂求生,干脆直接把 /src里面的包都删掉,只留个驱动包,总算是成功编译。

RVIZ不显示图形化界面

​ 不管我怎么试,RVIZ只是显示应用图标,不显示图形化界面。最后当我想不管这个的时候,我右键了一下这个软件图标,发现可以看到这个小窗口,而且是动态的。我静默片刻,赫然一惊,又如触电一般想起来LattePanda内置有一个虚拟屏幕,我颤抖着将鼠标拽向左上角的虚空,试探性的拖着什么,还真拖出来了个窗口……哈哈哈哈!

​ 虽然我更改了主屏幕,但是我还是毅然决然的停用了这个内置屏幕,太害人了。

添加环境变量

因为每次新打开一个终端就要source运行一下功能包的启动脚本,比较麻烦,所以干脆直接添加到环境变量当中,这样每次新开终端就会自动运行。
1
echo "source ~/[工作空间]/devel/setup.bash" >> ~/.bashrc

启动雷达

运行launch文件,雷达总算是可以正常工作了。

1
roslaunch ydlidar_ros_driver X2.launch

订阅雷达报文

可以通过`rostopic type [topic]`查看话题消息类型,雷达返回的报文类型是`<sensor_msgs::LaserScan>`,话题是`scan`

要注意的一点是在新终端使用`rostopic echo`的时候也要先`source [工作空间]/devel/setup.bash`,不然是会报错的。如果事先添加过环境变量的话就没有关系了。

ERROR:Cannot load message class foe [message]. Are your messages built?

报文格式

  • seq 是消息的顺序标识发布节点在发布消息时,会自动累加
  • stamp 是消息中与数据相关联的时间戳
  • frame_id 是消息中与数据相关联的参考系id
  • angle_min 起始角度(rad)
  • angle_max 结束角度(rad)
  • angle_increment 角度分辨率(rad)
  • time_increment 每个角度扫描时间
  • scan_time 扫描间隔
  • range_min 测量的最小距离
  • range_max 测量的最大距离
  • ranges 各个角度的测量距离
  • intensities 各个角度的强度值

雷达报文.png

报文解析

从0到n,均匀分为8组。假设雷达三角指向的方向为前方,那么这8组可以依次划分为 **back, back_right, right, front_right, front, front_left, left, back_left**

避障实现

大致知道雷达报文的意思之后,就可以写一个很简单的避障思路了。

根据八个方向的**ranges[]** 判断是否离障碍物过近,过近则将`unsigned char warn`的相应位置一,最后只需要用串口传输一个 **8字节**的数就可以帮助32进行避障了。

自己写呢主要是实现起来相对简单,而且自己用起来也比较方便,所以没有去研究使用更厉害的避障算法。不过还是先收集一个看起来很不错的博客

串口通讯

  可以先用cutecom测试一下串口功能,不过好像要设置为**CR/LF**才能正常通讯。**CR**表示回车,**LF**表示换行,也就是分别对应着**\r\n**,stm32上串口消息接收的结束标志是**\r\n**

​ 我这里使用的串口是/dev/ttyUSB0

自定义msg

find_package(

)

catkin_package(

)

​ find_package里面的是编译我们这个功能包时所依赖的包。而catkin_pckage里面的又是依赖的这些包所依赖的包。前者配置没问题而后者配置有问题时,可能导致编译无误但是运行时出错。所以又可以将这两个分别记为 编译时依赖运行时依赖

(按照autolabor官方手册配置)

功能包下新建msg目录,添加UART.msg

1
unsigned char warn_msg

package.xml添加包依赖

1
2
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

CmakeLists.txt添加msg配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
## 配置 msg 源文件
add_message_files(
FILES
Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo02_talker_listener
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)

debug_msg2.png

debug_msg1.png

需要在.xml里添加std_msg包;CmakeListsgenerate_messages 重复定义,需要删掉。

串口调用

因为是melodic版本,有对应的serial包,所以配置起来就比在noetic上面要方便不少。

1
sudo apt-get install ros-melodic-serial
调用自定义头文件serial_head.h

​ 为了方便在各个文件里调用串口,干脆直接写一个封装串口各个功能的头文件,供其他源文件调用。

​ 需要在/功能包/include/功能包名下创建头文件,为了方便在vscode编写代码,需要添加路径。

"/home/用户/工作空间/src/功能包/include/**"

​ 源文件里调用

1
#include "[包名]/[头文件].h"

​ 配置CmakeLists

1
2
3
include
${catkin_INCLUDE_DIRS}
)

发送数据

​ 串口包发送的数据需要是string类型。

​ 如果直接发送固定的字符串倒是没有什么问题,可以直接与32通讯。

1
2
string msgs = "123456\r\n";
ser.SendMsgs(msgs);

​ 但是实际上需要将u8类型的数据转化为string类型然后发送。

​ 但是不能直接像这样把数据转化为字符串类型。一开始的时候我就是这样做的,但是让ROS自己发送自己接收测试发现根本显示不出字符串,如果直接发送给32的话,32会直接停止运行。

1
2
3
4
5
6
7
8
9
10
11
string msgs;
unsigned char number=123;
msgs[0] = number/100 + '0';
number%=100;
msgs[1] = number/10 + '0';
number%=10;
msgs[2] = number + '0';
msgs[3] = '\r';
msgs[4] = '\n';
msgs[5] = '\0';
SendMsgs(msgs);

但是如果直接输入字符串就没有任何问题

1
2
string msgs = "123\r\n";
SendMsgs(msgs);

​ 很奇怪的是如果将两者的元素都分别打印出来的话明明是没有任何区别的,包括字符串结束符。

​ 不得不说用这玩意儿DEBUG是真心难受,卡得不得了。下次尽量要现在自己电脑上调好,直接移植过去。

​ 最后实现通过串口发送周围障碍情况给stm32,32依此完成运动规划。终于不用再碰这个鬼东西了,┭┮﹏┭┮

​ 当然如果想进一步发挥上位机功能,实现更高级的避障,建图等功能的话还是要碰┭┮﹏┭┮

ROS2

​ 直接跟着教程走基本没什么问题,可以得到雷达的报文,话题名为scan。当然前提是板子不能太卡,我之前用的树莓派3B直接编译到一半闪退,卡的不行,压根用不了。

通信协议

避障算法

cartographer

参考教程

cartographer ros使用指南-安装 - 创客智造

最新cartographer安装:使用ubuntu20.04 + ROS Noetic_yqziqian2的博客-CSDN博客

怎么感觉一步一个坑呀,几乎每句命令都伴随着报错,我真的拴Q了😢

git clone失败

fatal: 无法访问 ‘https://github.com/google/protobuf.git/':GnuTLS recv error (-110): The TLS connection was non-properly terminated.

明明有科学上网,而且git仓库的链接可以直接打开,为什么还是会显示无法访问呢?

解决git克隆项目出现fatal无法访问‘https://github.com/xxx/xxx‘Unsupported proxy syntax in‘127.0.0.1:8118‘_我对算法一无所知的博客-CSDN博客

通过这个博客可以解决(但是还是不太清楚为啥😢),虽然我list后没有显示作者说的两句话,但是运行之后确实可以正常运行了。

而且我后来重新安装了一次cartography,发现好像新建的工作空间需要再这样操作一次

rosdep 初始化失败

1
2
rosdep init
rosdep update

ERROR: cannot download default sources list from:
https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sources.list.d/20-default.list
Website may be down.

小鱼,我的超人😍(小鱼的一键安装ros也非常的nice)

https://mp.weixin.qq.com/s/VGs8oWdhHH6XsHcx21lN4Q

不知道什么错误

1
rosdepc install --from-paths src --ignore-src --rosdistro=noetic -y

ERROR: the following packages/stacks could not have their rosdep keys resolved
to system dependencies:
cartographer: [libabsl-dev] defined as “not available” for OS version [focal]

Error while installing dependencies: libabsl-dev not available · Issue #1726 · cartographer-project/cartographer_ros · GitHub

将`catkin_ws/src/cartography/package.xml`中第**46**行的`<depend>libabsl-dev</depend>`删除

节点配置与使用

安装完了包,Google官方的demo也能成功运行,接下来就是尝试使用自己的雷达数据进行见图。

2023-01-18 00-00-27 的屏幕截图.png

在一开始,不太清楚该怎么表达**如何调用这个包**,找了一些教程,反正是失败了。可能还有一个原因,我使用`touch`和`vim`创建文件,创建出来的文件虽然后缀一样,但是图标不一样,不知道最后编译错误会不会与这个有关。

还有一个教程,上来就说是因为cartography没有装好,建议重装。然后推荐了一个声称**百分百成功**的方法,我试了他的报错解决方法,报错更多了;然后又尝试他的**百分百成功**安装方法,结果第一个命令就给了我当头一棒。

不得已,我重装了这个包,按照下面教程走一边后依然会报错。

https://www.guyuehome.com/25696

报错的内容好像是与之前一样的,难道要重蹈覆辙吗,已经倒腾一天了。其实这个报错的重点不是下面的两段,而是上面的

CMake Error: The following variables are used in this project, but they are set to NOTFOUND.(2021.02.22)

缺少了Google-mock,解决方法的话如下

编译 cartographer_ros 出现 CMake Error: The following variables are used in this project, but they are.._锡城筱凯的博客-CSDN博客

成功编译之后尝试运行

[FATAL] [1674008037.110688039]: F0118 10:13:57.000000 24832 lua_parameter_dictionary.cc:83] Check failed: status == 0 (2 vs. 0) [string “— Copyright 2016 The Cartographer Authors…”]:49: attempt to index global ‘SPARSE_POSE_GRAPH’ (a nil value)

cartographer attempt to index global ‘SPARSE_POSE_GRAPH‘ (a nil value)_憨憨2号的博客-CSDN博客

POSE_GRAPH.optimization_problem.huber_scale = 1e2

POSE_GRAPH.optimize_every_n_scans = 35

POSE_GRAPH.constraint_builder.min_score = 0.65

[FATAL] [1674008655.614286059]: F0118 10:24:15.000000 29610 lua_parameter_dictionary.cc:399] Check failed: HasKey(key) Key ‘use_nav_sat’ not in dictionary:

[FATAL] [1674008934.975178862]: F0118 10:28:54.000000 31121 lua_parameter_dictionary.cc:399] Check failed: HasKey(key) Key ‘use_landmarks’ not in dictionary:

[FATAL] [1674009242.870707094]: F0118 10:34:02.000000 31954 lua_parameter_dictionary.cc:399] Check failed: HasKey(key) Key ‘publish_frame_projected_to_2d’ not in dictionary:

有很多像这样类似的报错,都是因为**lua**配置文件里缺少了相关参数,对照**revo_lds.lua**这个demo文件补齐即可。
1
2
3
catkin_make_isolated --install --use-ninja
source install_isolated/setup.bash
roslaunch cartographer_ros cartographer_demo_delta_lidar.launch

cartographer建图参数配置详细说明_非晚非晚的博客-CSDN博客_cartographer参数配置

这个博客介绍了各个配置参数的含义

map_frame = “map”,—生成的地图坐标系
tracking_frame = “base_link”,—跟踪的坐标系,可以是imu、小车、雷达
published_frame = “base_link”,—cartographer正在发布pose的坐标,一般就是小车
odom_frame = “odom”,—cartographer的里程计坐标系
provide_odom_frame = false,— cartographer是否发布里程计坐标
publish_frame_projected_to_2d = true,—是否无滚动、俯仰或z偏移
use_odometry = false,—订阅里程计
use_nav_sat = false,—订阅GPS
use_landmarks = false,—订阅路标
num_laser_scans = 1,—订阅雷达格式以及数量
num_multi_echo_laser_scans = 0,— 订阅雷达格式以及数量
num_subdivisions_per_laser_scan = 1,—分割扫描点云
num_point_clouds = 0,— 订阅雷达格式以及数量
lookup_transform_timeout_sec = 0.2,—tf2查找变换超时(s)
submap_publish_period_sec = 0.3,—发布子图实时间间隔(s)
pose_publish_period_sec = 5e-3,—发布pose时间间隔(s)
trajectory_publish_period_sec = 30e-3,—发布轨迹标记间隔(s,这里为30ms)
rangefinder_sampling_ratio = 1.,—以下5个参数为传感器采样比例
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,

[ WARN] [1674485462.194546450]: W0123 22:51:02.000000 40556 range_data_collator.cc:82] Dropped 1 earlier points.

发布里程计信息

https://zhuanlan.zhihu.com/p/158077889

启动文件

  • hello_plus_ws
  • balance_tf里tf2P_pub_sub;
  • nav_demo里send_goal;
  • nav_demo里(amcl)
  • nav_demo里map_server
  • ydlidar_ws
  • ydlidar_ros_driver里X2.launch
  • ydlidar_ros_driver里anya_trans:里程计,目标点
  • cartography_ws
  • cartographer_ros cartographer_demo_delta_lidar.launch
  • No transform from [base_link] to frame [map]
  • No transform from [laser] to frame [map]

Cartographer保存地图

cartographer 保存地图_菜鸟&攻城狮的博客-CSDN博客

先进入工作空间下:source install_isolated/setup.bash

以下步骤保存地图:

完成轨迹, 不接受进一步的数据:

rosservice call /finish_trajectory 0

序列化保存其当前状态:

rosservice call /write_state “{filename: ‘${HOME}/Downloads/mymap.pbstream’}”

将pbstream转换为pgm和yaml

rosrun cartographer_ros cartographer_pbstream_to_ros_map -map_filestem=${HOME}/Downloads/mymap -pbstream_filename=${HOME}/Downloads/mymap.pbstream -resolution=0.05

地图文件所在的目录:与工作空间同目录下的Downloads目录下。

anya_sub

1
2
3
 open_flag[pos_x][pos_y] = true;

if(p->next != NULL) {p->next->last = p;}

多机共享话题

配置

车载计算机和地面站连接至同一个局域网,利用ifconfig查看各自的IP地址,在各自的`~/.bashrc`添加配置。需要在两个计算机里选择一个作为Master,这里我选择地面站。

车载计算机

1
2
export ROS_HOSTNAME=[车载计算机IP]
export ROS_MASTER_URI=[MasterURI]

地面站

1
2
export ROS_NAME=[地面站IP]
export ROS_MASTER_URI=[MasterURI]
比方说地面站是**192.168.0.102**,车载计算机是**192.168.0.105**,所以这样添加
1
2
3
4
5
6
地面站
export ROS_HOSTNAME=192.168.0.102
export ROS_MASTER_URI=http://192.168.0.102:11311
车载计算机
export ROS_HOSTNAME=192.168.0.105
export ROS_MASTER_URI=http://192.168.0.102:11311

最后还要各自运行一下新配置

1
source ~/.bashrc

bash: export: “=”: 不是有效的标识符
bash: export: “192.168.0.102”: 不是有效的标识符

出现这样的报错是因为习惯性的在等号两边加了空格,删掉即可。

测试

只需要在选择的**Master**上运行roscore,两个节点分在两个机器上运行
1
2
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key

我的ros版本一个是noetic,一个是melodic,双向均可正常控制。

给/home扩容

**cartography**和它的示例包内存太大了,当然也因为当时装双系统的时候没经验,给ubuntu分的空间太少了,总之现在就是home仅剩几兆内存了,示例包直接下载失败。

大致有两种方法,一种是将空闲磁盘挂载到**home**下,但是这个需要新建一个文件夹作为新空间。我cartography装了半天才装上,谁知道如果直接移动过去又会不会出什么坑人的问题,所以只能选择第二种。

第二种用到的工具是ubuntu下的**gparted**,不幸的是一般情况下只能将磁盘分区左右拓展,可是我的根目录左右并没有可用空闲空间,而且还有很多分区是上锁的,无法操作,但是也不是没有办法。
  • 首先需要制作一个ubuntu启动盘,我这个是20.04,我的启动盘装的是22.04,这都没有什么关系。

  • 在电脑开机时进入BIOS界面(我的是F2或FN+F2,反正我都按了)

  • 将开机首选项设置为U盘,之后进入U盘里的系统

  • 选择 try Ubuntu,进入试用系统。

  • 打开gparted,在这种情况下,分区是没有上锁的,可以重新分配分区的大小,左右移动分区。将别的地方分一点空闲空间,一步步挪到home旁边,然后就可以拓展了。

TF配置与使用

先要安装一些包

1
2
3
4
5
sudo apt-get install ros-noetic-tf
sudo apt-get install ros-noetic-geometry-msgs
sudo apt-get install ros-noetic-tf2
sudo apt-get install ros-noetic-tf2-ros
sudo apt-get install ros-noetic-tf2-geometry-msgs

ROS TF原理和使用方法_月光下的向日葵的博客-CSDN博客_ros tf

这个比较的简洁,可惜的是编译出来报错,从报错信息也看不出来是因为啥,先放一放好了

5.1.2 静态坐标变换 · Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程

奥特学园我的超人😍

最开始学ros的时候也是跟的这个教程,我感觉这里面肯定有TF的使用教程,果然找到了。

基本上没啥问题,不过**5.1.2**里面给的**订阅方**的代码里面**46**行INFO那里少了个`%s`,补上即可。

还有一点需要注意的是,程序写完了,在**CMakeLists**里面添加可执行文件和link library的时候,不能随意摆放代码块的位置,什么地方放什么配置注释里都有写。应该是因为我把它们放在了`catkin_package{}`和`include_directories{}`前面,虽然编译出来没有报错,但是运行的时候会显示找不到可执行文件。

[rosrun] Couldn’t find executable named tf2_pub below /home/stonewu/hello_plus_ws/src/balance_tf

将一切准备就绪后,启动最终的launch文件,rviz却依然显示不出东西。然后**tf2_sub**的终端的**INFO**显示*程序异常*后来发现,只要运行雷达的**launch**文件就会导致这个问题。打开雷达的**launch**文件,发现在最后添加了关于TF的配置。将其注释掉之后,就不会导致这个问题了。

键盘控制

ros发布不同类型的消息

我以前一直以为pub一个消息是比较随意的一件事,可是实际上还是有些不方便的。

https://www.guyuehome.com/14319#:~:text=%E9%A6%96%E5%85%88%E6%98%AF%E5%8F%91%E5%B8%83%E5%99%A8%E7%A8%8B%E5%BA%8F%201%3A%20%23include%20%22std_msgs%2FInt8.h%22%20%E4%BB%A3%E6%9B%BF%E4%BA%86%20%23include%20%22std_msgs%2FString.h%22%20%EF%BC%8E%E8%BF%99%E8%A1%A8%E6%98%8E%E4%BA%86%EF%BC%8C%E6%AF%8F%E4%B8%80%E7%A7%8D%E4%B8%8D%E5%90%8C%E7%9A%84%E6%B6%88%E6%81%AF%E9%83%BD%E6%9C%89%E8%87%AA%E5%B7%B1%E7%9A%84%E5%A4%B4%E6%96%87%E4%BB%B6%EF%BC%8C%E5%A6%82%E6%9E%9C%E6%88%91%E4%BB%AC%E8%A6%81%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84%E6%B6%88%E6%81%AF%EF%BC%8C%E5%B0%B1%E9%A6%96%E5%85%88%E8%A6%81%E5%8C%85%E5%90%AB%E5%AE%83%E6%89%80%E5%9C%A8%E7%9A%84%E5%A4%B4%E6%96%87%E4%BB%B6%EF%BC%8E%E5%83%8F,std_msgs%3A%3AString%20%EF%BC%8C%E5%AE%9A%E4%B9%89publisher%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E5%AE%83%E9%9C%80%E8%A6%81%E5%8F%91%E5%B8%83%E4%BB%80%E4%B9%88%E6%B6%88%E6%81%AF%E6%98%AF%E9%9C%80%E8%A6%81%E6%8C%87%E5%AE%9A%E6%98%8E%E7%A1%AE%E7%9A%84%EF%BC%8C%E4%B9%8B%E5%89%8D%E6%98%AFros%E4%B8%AD%E7%9A%84string%E9%82%A3%E4%B9%88%E7%8E%B0%E5%9C%A8%E5%B0%B1%E8%87%AA%E7%84%B6%E6%8D%A2%E6%88%90ros%E4%B8%AD%E7%9A%84int8%E4%BA%86%203%3A%20std_msgs%3A%3AInt8%20msg%20%E6%9B%BF%E6%8D%A2%E4%BA%86%20std_msgs%3A%3AString%20msg.

别忘了要引入消息类型的头文件,还要专门使用头文件里的typedef去定义变量。

运行后发现方向上下左右每个按键对应着三个ascii字符。

  • ☝:27 91 65

  • 👇:27 91 66

  • 右:27 91 67

  • 左:27 91 68

map_server

简单测试