讯飞智能救援赛后总结

前言

整个比赛差不多花了3、4个月,可算是结束了。名次上预估国二

开头先吐槽一下讯飞二代车的诸多硬件问题,麦克风扬声器摄像头总会检测不到、雷达漂移断点甚至在某个方向还有一片死区、小车wifi不稳定远程连接总是断开、开关机只能通断电源没法软件上操作不然那个b风扇就会狗叫个不停、陀螺仪的逆天输出原地不动的情况下还会在10cm范围内反复震荡(md小车最小通行距离也就50cm),整台车甚至在工作半小时后就会开始出现各种玄学问题。就这车,可以说给我带来了非常糟糕的比赛体验,但好在为了解决这些问题还是学到了新东西。

客观来说我觉得比赛的内容并不困难,主要的障碍还是小车的硬件环境。摄像头高度太低不知道是不是命题人有意为之,确实要比常规巡线困难不少。不过我主要还是做的寻找工具那块,初看赛题内心很糟糕,因为在此之前没有任何处理雷达数据的经验,最后只能尝试用计算几何的知识硬算(我计算几何学得很烂w_w)。

正文

具体来说,场地内,雷达能扫到的只有边界和板子,只要想办法过滤掉边界,对剩下的板子枚举就好了。问题就是怎么过滤掉边界,可以注意到边界为矩形(黄色区域)。

比赛场地示意图

只要小车在黄色区域内运动,雷达总会获取到这个矩形的信息,但是由于有板子的遮挡,你并不能完全拿到这个矩形,当你试图补全这个矩形,你就会发现,这个矩形刚好是雷达读取到的所有点的最小外接矩形。

那么问题就转变为:“给定一个子元素为(x,y)的点集,求它的最小外接矩形。”

对于这个问题,我的做法是先用Graham算法求出点集的凸包,再根据最优情况下至少有一条矩形边与凸包的边重合,枚举凸包的边求出矩形。

关于如何求解凸包和Graham算法的内容,可以看这篇文章数论小白都能看懂的平面凸包详解 - 洛谷专栏

求完凸包后,我们需要对于凸包的每条边,找出满足下面条件的矩形。

  1. 以这条边所在直线的一部分为一边
  2. 包含这个凸包的所有点
  3. 面积最小

答案即为这些矩形中面积最小的一个。

贴一下第一个条件的证明,另外两个很好理解,因为是外接矩形,它当然至少要包含所有的点,同时保持面积最小

怎么找这些矩形,这是个问题,看看这张图

image-20240802211332809

为了方便起见,当前正在考虑的边画成水平。

我们要分别求出最右、最上、最左的点(点的数量很少,枚举每一个凸包内的点即可,比如分别枚举ABCDQP),再加上当前边一定包含最下的点,这样便能得到矩形的长宽。

但是不喜欢枚举怎么办,其实还有一个O(n)的算法:旋转卡壳,具体可以看这篇文章 计算几何系列 —— 美妙的旋转卡壳算法~上_旋转卡壳法

当然也有另有一个选择:K-D Tree,K-D Tree 最典型的应用应该是求平面 k 远点对(传送门),但是我并没有尝试过,主要是后续对板子的点进行聚类会再一次用到KD树所以提一嘴

求出所有满足条件的矩形,取面积最小的即为最小外接矩形

然后把雷达上所以距离矩形边 x cm的点删掉(这个 x 可以自己定,因为雷达的点会在边界旁边跳变,所以最好设置一个死区)

到此为止你手上剩下的点只剩下板子了,用KD树或者并查集对剩下的点进行聚类(一开始我是打算用并查集做的,但是网上转了一圈发现大伙都用KD树,本着时间紧任务重的原则我也没瞎折腾)

那么板子的左右端点,相对于小车的位置信息等等全都拿到了,我们只需要在板子的两侧画一个60*60的框判断能否找到一个能使小车安全停泊的点,找到就告诉小车没找到跳过就好

按理说任务到此已经结束了,但是由于movebase的定位不准(我不得不怀疑是小车的硬件问题还是movebase的算法问题导致的),即使已经知道了板子准确的相对位置信息,小车仍旧无法准确停泊在目标点,这意味着我们需要pid控制额外的做一次位置校正。

这里有一个细节就是 雷达获取到的信息是相对于雷达坐标系的,如果不做进一步的坐标转换而直接使用雷达的信息做矫正,就有可能导致雷达本体是处在60×60cm框内,而小车的屁股(就是那两个后轮)处在框外 (国赛得分-10 ╥﹏╥)

杂记

CentOS7 部署饥荒服务器

服务器依赖

sudo yum update	

sudo yum -y install glibc.i686 libstdc++.i686 libcurl4-gnutls-dev.i686 libcurl.i686 screen

安装SteamCMD

cd /root && mkdir steamcmd && cd steamcmd

wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz

tar -xvzf steamcmd_linux.tar.gz
#启动安装程序
./steamcmd.sh

设置安装目录

force_install_dir /root/dstserver

login anonymous

# download app | update
app_update 343050 validate

映射游戏依赖

ln -s /usr/lib/libcurl.so.4 /home/dstserver/bin/lib32/libcurl-gnutls.so.4

启动脚本

cd /root/dstserver/bin

echo "./dontstarve_dedicated_server_nullrenderer -console -persistent_storage_root /root/dstsave -conf_dir dst -cluster World1 -shard Master" > master_start.sh

echo "./dontstarve_dedicated_server_nullrenderer -console -persistent_storage_root /root/dstsave -conf_dir dst -cluster World1 -shard Caves" > cave_start.sh

chmod +x master_start.sh cave_start.sh

输入 ./master_start.sh 启动服务器正常显示为

设置存档

  • 服务器token,放在 World1/cluster_token.txt

  • 特别注意.ini文件

# cluster.ini
[GAMEPLAY]
game_mode = survival
max_players = 6
pvp = false
pause_when_empty = true


[NETWORK]
lan_only_cluster = false
cluster_password = 123456
cluster_description = laoair
cluster_name = myworld
offline_cluster = false
cluster_language = zh
cluster_cloud_id = B949D4BA08006007


[MISC]
console_enabled = true


[SHARD]
shard_enabled = true
bind_ip = 127.0.0.1
master_ip = 127.0.0.1
#主从世界通信端口
master_port = 10888  
cluster_key = defaultPass

#Caves/server.ini
[NETWORK]
#地洞端口
server_port = 10998


[SHARD]
is_master = false
name = Caves
id = 1604992138
#务必加上,保证密钥正常
cluster_key = defaultPass

[ACCOUNT]
encode_user_path = true


[STEAM]
master_server_port = 27017
authentication_port = 8767

[NETWORK]
server_port = 10999


[SHARD]
is_master = true
#务必加上,保证密钥正常
cluster_key = defaultPass

[ACCOUNT]
encode_user_path = true

Mod设置

  • 查看mod ID:Master/modoverrides.lua

  • 将/root/dstserver/mods/dedicated_server_mods_setup.lua内容设置成对应数字编号

  • ServerModSetup("1216718131")
    ServerModSetup("1418746242")
    ServerModSetup("1595631294")
    
    --ServerModCollectionSetup("id")
    

管理员设置

  • /home/dstsave/dst/World1/adminlist.txt
  • 添加klei 用户ID ,一行一个ID

服务器更新

  • 游戏更新会导致服务器无法正常进入

  • 进入steamcmd更新服务器

  • ./steamcmd.sh
    
    login anonymous
    
    force_install_dir /root/dstserver (这里的/home/dstserver要改为你们自己的服务器路径)
    
    app_update 343050 validate
    
  • validate:验证游戏文件

[学习笔记]FreeRTOS

FreeRTOS

Tasks and Queues

  • 任务管理

  • 新建任务设置

    • Task Name: 设置任务的名称,它会基于此名字建立一个指向任务的句柄

    • Priority: 设置任务的优先级,高优先级的任务会抢占低优先级的任务

    • Stack Size: 设置任务栈的大小,用于储存任务相关的变量,关于堆栈详细内容请参考

    • Entry_Function:设置任务的入口函数,这个函数便是开发者实现任务功能的地方

    • Code Generation Option: IDE自动生产初始代码的选项,这里选择默认设置

    • Parameter: 传递给任务的参数

    • Allocation: 设置任务所在的地方,选择动态的话是生成在堆中,选择静态的话则生成在静态区

  • 关于任务

    • 一个任务包含Stack区(用于储存任务相关的变量)和任务控制块TCB区(Task Control Block,用于储存任务本身的设置)
    • img
    • 任务占用字节数 = TCB_size + (4 x Task stack size)
  • 使用

    • 实现任务函数的功能

    • *osKernelStart();*

      语句启动FreeRTOS的任务调度程序,单片机就运行起来了。

内存管理

分配方案

  • FreeRTOS提供了五种方案

  • Heap_1

    • 适用于系统不用删除任务或者内核对象
  • Heap_2

    • 适用于重复添加和删除相同任务的系统
    • 它会对大的内存块进行分割,但无法合并相连的内存块,容易导致内存的碎片化
  • Heap_3

    • 使用的场景不多
  • Heap_4

    • 更具普适性,也是默认配置
    • 适用于通用的应用(系统会添加和删除不同大小的任务)
  • Heap_5

    • 用不同的数组空间对内存进行分配
    • 是4的升级版

相关函数

  • size_t xPortGetFreeHeapSize( void );

    • 回当前堆中的空闲空间大小(单位是字节),可以用来优化堆空间大小
  • size_t xPortGetMinimumEverFreeHeapSize( void );

    • 返回在系统运行过程中堆空间的最小空闲空间,如果最小空闲空间很小的话可以考虑提高堆大小configTOTAL_HEAP_SIZE的值
    • 这个函数只能在Heap_4或者Heap_5下调用
  • void vApplicationMallocFailedHook( void );

    • 这是一个回调函数,需要用户自己实现。
    • 如果配置文件中configUSE_MALLOC_FAILED_HOOK 设置为1的话,当堆分配内存失败时会调用此函数。
    • 用户可以在此函数中进行错误处理。

任务管理

优先级模式

  • configUSE_PORT_OPTIMISED_TASK_SELECTION

    • 为0时为通用方式
    • 为1时为架构优化方式

任务创建

  • BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            uint16_t usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t 									*pxCreatedTask);
    
    • *pvTaskCode* 这是一个函数指针,指向执行任务的函数

    • pcName* 任务的描述名称,方便调试,不用的话可以设为Null

    • usStackDepth* 每个任务有自己的栈空间,这里根据任务占用需求设置栈空间的大小,单位是字(Word)

    • pvParameters 用于传递给任务的参数,不用的话可以设为Null

    • uxPriority* 设置任务的优先级,范围由0到(configMAX_PRIORITIES – 1)。数值越大,等级越高

    • pxCreatedTask* 任务的具柄(handle),通过具柄可以对任务进行设置,比如改变任务优先级等,不用可以设为Null

  • 函数的返回值有两个pdPasspdFailpdPass表示任务创建成功,相反pdFail表示创建失败,创建失败的原因大多是因为系统没有足够的堆空间来保存任务

空闲任务(Idle Task)

  • 空闲任务是调度器在vTaskStartScheduler函数调用启动后自动创建的一个任务。之前的任务调度演示图处于简化的目的并没有列出空闲任务。空闲任务具有最低的优先级0,当用户创建的任务都在阻塞状态或挂起状态时,空闲任务便得以执行。最低的优先级确保空闲任务不会抢占用户任务。同时空闲任务负责清理内核的资源,所以当有任务被删除后,应该保证空闲任务能运行清理和回收内核的资源。
  • 空闲任务可以绑定一个钩子任务(Task Hook),当空闲任务运行的时候钩子任务也会被自动调用。钩子任务里可以添加测量空闲任务运行时间的函数或者把系统放入低功耗模式的函数。***空闲任务运行的时间反映出了系统的可用计算资源,可以用于推算CPU的占用率。CPU的占用率过高的话可能会对系统的实时性有影响。
  • 钩子任务的函数原型是vApplicationIdleHook( void );

调度器

  • 模式设置:configUSE_PREEMPTION

    • 为1时是抢占式模式

      • configUSE_PREEMPTION(允许抢占) 1

        configUSE_TIME_SLICING(采用时间片) 1

      • 高优先级任务会立即执行

      • 抢占式无时间片调度使用的时候需要很谨慎

    • 为0时是合作式模式

      • configUSE_PREEMPTION(允许抢占) 0

        configUSE_TIME_SLICING(采用时间片) 任意值

      • 高优先级任务会在当前任务完成后执行

  • 调度器执行间隔:configTICK_RATE_HZ

    • 通常设为100HZ(时间片大小相应为10ms)
  • 延时函数 vTaskDelayUntil和vTaskDelay的参数需要的是延时的节拍数,不能直接设置延时时间,因此使用 pdMS_TO_TICKS 函数将时间转换为节拍数。

代码实例

int main(void)
{
  // 创建任务
  xTaskCreate( vTask1, "get sensor data", 200, NULL, 3, NULL );
  xTaskCreate( vTask2, "show sensor data", 200, NULL, 2, NULL );
  xTaskCreate( vTask3, "get user input", 200, NULL, 1, NULL );
  // 启动调度器
  vTaskStartScheduler();
  while (1)
  {
      ...
  }
}

运行机制

  • FreeRTOS通过xTaskCreate()函数创建任务,相当于安卓的onCreate()函数;
  • FreeRTOS通过vTaskSuspend()函数挂起任务,相当于安卓的onPause()函数;
  • FreeRTOS通过vTaskResume()函数恢复任务到就绪状态,相当于安卓的onResume()函数;
  • FreeRTOS通过vTaskDelete()函数删除任务,相当于安卓的onDestroy()函数。

队列管理

类别

  • *复制队列(Queue by copy)* 表示写入队列的数据每个字节都被完整复制到队列中了
  • *引用队列(Queue by reference)* 表示写入队列的是要写入数据的引用(或者说是一个指针指向所引用的数据)而并不是数据本身

常见API

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
  • uxQueueLength 队列包含数据的最大长度
  • uxItemSize 每个数据占用的字节大小
  • 创建队列用到的函数。函数的返回值是QueueHandle_t具柄类型,表示的是对所创建队列的一个引用句柄
  • 如果堆中没有足够空间给队列使用的话函数的返回值会是NULL。
BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • xQueue 队列的句柄,来自于xQueueCreate()的返回值
  • pvItemToQueue 所发送数据的引用,然后这些数据会被复制到队列中
  • xTicksToWait 队列如果满时发送任务的阻塞时间(block time),上文已经介绍过,可以通过pdMS_TO_TICKS()把时间转换成节拍数。如果设置为portMAX_DELAY的话任务将永远等待下去(需要FreeRTOSConfig.h头文件中INCLUDE_vTaskSuspend参数设置为1)
  • 返回值 发送数据成功时返回pdPASS,失败时返回errQUEUE_FULL
  • 用于将数据发送到队列(具体一点就是队列的尾部)。如果要在中断程序调用的话需要使用xQueueSendFromISR()函数。
BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • 将数据发送至队列头部
BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue,
TickType_t xTicksToWait )
  • 将数据发送至队列尾部
  • 等效xQueueSend()
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer,
TickType_t xTicksToWait )
  • xQueue 队列的具柄,来自于xQueueCreate()的返回值
  • pvBuffer 指向内存空间的一个引用,读取的数据会被复制到这片内存
  • xTicksToWait 队列如果空时接送任务的阻塞时间(block time),上文已经介绍过,可以通过pdMS_TO_TICKS()把时间转换成节拍数。如果设置为portMAX_DELAY的话任务将永远等待下去(需要FreeRTOSConfig.h头文件中INCLUDE_vTaskSuspend参数设置为1)
  • 返回值 接收数据成功时返回pdPASS,失败时返回errQUEUE_EMPTY
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue )
  • 用于获得队列中数据的数量

软件定时器

启用

  • 为了启用软件定时器,需要在头文件FreeRTOSConfig.h中设置configUSE_TIMERS的值为1.

分类

软件定时器在FreeRTOS中分为两个类型

  • 一次性定时器(One-shot timer)

    • 一次性定时器启动后只会执行一次回掉函数;
  • 自动重载定时器(Auto-reload timer)

    • 自动重载定时器会周期性地执行回调函数

种类

软件定时器的状态有以下两种

  • 静止装态(Dormant)
  • 运行状态(Running)

处于静止装态的定时器不会执行回调函数,可以通过调用定时器的具柄启用;

处于运行状态的定时器会在设定的时间间隔(相对于定时器进入运行状态后)到达后调用回调函数。

一次性定时器会在执行回调函数后进入静止状态

而自动重载定时器会在执行回调函数后重新进入运行状态。

时间服务任务

  • 优先级configTIMER_TASK_PRIORITY

    • 定时器的回调函数调用FreeRTOS的API函数的话会导致时间服务任务管理进入阻塞状态
  • 栈大小configTIMER_TASK_STACK_DEPTH

  • 针对软件定时器的命令会通过命令队列发送到时间服务任务。

CubeMX配置

  • type

    • 选择一次性定时器或者自动重载定时器

常用API

void ATimerCallback( TimerHandle_t xTimer );
  • xTimer 是软件定时器的具柄。

  • 注意

    • 回调函数的代码应该避免调用FreeRTOS的API函数防止进入阻塞状态
    • 一些FreeRTOS的API函数会使回调函数进入阻塞状态转而执行其他任务导致定时器的实时性不确定,容易产生意外错误或者超时
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
  • pcTimerName 定时器的名称,用于调试用
  • xTimerPeriodInTicks 定时器的周期,dMS_TO_TICKS() 可把时间转成节拍数
  • uxAutoReload 设置为pdTRUE为自动重载定时器,设置为pdFALSE为一次性定时器
  • pvTimerID 定时器的ID,可以用于各种目的
  • pxCallbackFunction 定时器执行的回调函数
  • 返回值 返回值为NULL表示创建失败,非NULL表示定时器的具柄
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait )
  • xTimer 要执行定时器的具柄
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示启动定时器成功;返回值为pdFALSE表示失败
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID )
  • xTimer 要执行定时器的具柄
  • pvNewID 设定的pvTimerID的值
  • 用于设置定时器的pvTimerID 属性
void *pvTimerGetTimerID( TimerHandle_t xTimer );
  • xTimer 要执行定时器的具柄
  • 返回值 pvTimerID的值
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );
  • xTimer 要执行定时器的具柄
  • xNewTimerPeriodInTicks 新的定时器的周期,dMS_TO_TICKS() 可把时间转成节拍数
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示成功;返回值为pdFALSE表示失败
  • 用于改变定时器的运行周期
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait ); 
  • xTimer 要执行定时器的具柄
  • xTicksToWait 如果命令队列为满时函数进入阻塞状态等待命令队列有空的时间
  • 返回值 返回值为pdPASS表示成功;返回值为pdFALSE表示失败
  • 用于重置定时器,定时器会重新运行并以此计算新的时间间隔

中断管理

  • void

资源管理

信号量

  • 种类

    • 二进制信号

    • 计数信号

    • 互斥量

      • 类似线程锁
      • 上锁保护资源,处理完资源后再释放锁
    • 关键区

      • 强制执行宏定义区间内的代码

常用API

SemaphoreHandle_t xSemaphoreCreateBinary( void )
  • 创建一个二进制信号量,如果返回值是NULL或所创建信号量的具柄
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );
  • 创建一个计数信号量,如果返回值是NULL或所创建信号量的具柄
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait )
  • *xSemaphore* 信号量的句柄
  • *xTicksToWait* 等待节拍数,设置为0的话如果信号量不可用的话会直接返回
  • 返回值为pdPASS表示成功获取了信号量,返回值为pdFALSE表示获取信号量失败
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • *xSemaphore* 信号量的句柄
  • 返回值为pdPASS表示成功给予信号量,返回值为pdFALSE表示给予信号量失败
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken )
  • 中断版本
SemaphoreHandle_t xSemaphoreCreateMutex( void )

END

[学习笔记]Socket通信

Socket通信

base on py,by laoair

2023/3/5

客户端(sender)

创建Socket

import socket   #for sockets
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket Created'
  • socket(地址簇,类型)

    • 地址簇:AF_INET ---- ipv4

    • 类型

      • SOCK_STREAM,TCP传输控制协议
      • SOCK_DGRAM ,UDP通讯协议

连接服务器

s.connect( (remote_ip , port) )

发送数据

  • s.sendall(message)

    • message:string类型
    • 返回值是发送字节的数量
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

接收数据

  • s.recv(bufsize)

    • bufsize指定最多可以接收的数量
    • 数据以字符串形式返回
reply = s.recv(4096)
  • 缓冲区是4096个字节

关闭

s.close()

服务器端 (recevier)

绑定 Socket

  • s.bind((HOST, PORT))

    • 使用元组(host, port) 的形式表示地址
  • bind函数 本地连接

import socket
import sys

HOST = ''  
PORT = 8888 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

监听

  • s.listen(10)

    • 指定可以挂起的最大连接数量。这个参数的值最小为1,般设置为5。
    • 最多同时监听10个连接
s.listen(10)

接受连接

  • conn, addr = s.accept()

    • conn 是新的套接字对象,可以理解为会话实例
    • addr是连接客户端的地址,内容为[ip,port]
    • 一般是阻塞态
#wait to accept a connection - blocking call
conn, addr = s.accept()

#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

接收数据

#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)

关闭

conn.close()
s.close()

服务器代码

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data: 
        break

    conn.sendall(reply)

conn.close()
s.close()

END

树莓派ROS安装指南

树莓派原生系统安装ROS

  • 经测试Debian11/10都可以使用
  • 版本不同,记得修改对应名字
  • 该方法使用源码安装,可避开apt搜索不到的问题

设置ROS库(安装密钥+包索引)

ROS1:ros | 镜像站使用帮助

ROS2:ros2 | 镜像站使用帮助

安装引导

sudo apt install -y python-rosdep python-rosinstall-generator python-wstool python-rosinstall build-essential cmake

初始化

  • init 之前根据 传送门 的指导 修改URL
  • 防止被墙导致报错
sudo rosdep init
rosdep update

下载源码

  • 创建
mkdir -p ~/ros_catkin_ws
cd ~/ros_catkin_ws
  • 下载源码

    • 这里可能会被墙,可以手动将rosinstall文件内的URL下载到本地解压即可
    • 记得 修改 交换空间
    • 无GUI(成功率高,但是没有乌龟,计算图之类的)
    rosinstall_generator ros_comm --rosdistro melodic --deps --wet-only --tar > melodic-ros_comm-wet.rosinstall
    wstool init src melodic-ros_comm-wet.rosinstall
    
    mkdir -p ~/ros_catkin_ws/external_src
    cd ~/ros_catkin_ws/external_src
    wget    http://sourceforge.net/projects/assimp/files/assimp-3.1/assimp-3.1.1_no_test_models.zip/download -O assimp-3.1.1_no_test_models.zip
    unzip assimp-3.1.1_no_test_models.zip
    cd assimp-3.1.1
    cmake .
    make
    sudo make install
    
    • 完整版(推荐下载)
    • 下载失败时可以下载到本地 ,然后改一下URL
    rosinstall_generator desktop --rosdistro melodic --deps --wet-only --tar > melodic-desktop-wet.rosinstall
    wstool init src melodic-desktop-wet.rosinstall
    
    • wstool update -j4 -t src
      
  • 安装rosdep依赖

cd ~/ros_catkin_ws
rosdep install -y --from-paths src --ignore-src --rosdistro melodic -r --os=debian:buster

编译+安装

sudo ./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release --install-space /opt/ros/melodic

更新环境变量

# 当前窗口
source /opt/ros/melodic/setup.bash

# 系统变量
echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc

乌龟测试

roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key