自己攒op电脑版之IMU模块-GY95T

之前我写在知识星球上的,搬运到这里吧

在淘宝上买了一个 GY-95T九轴传感器 ,样子是这样的 👇
gy95t

店家发的程序是Windows下面的,焊接好后插上,找到COM口,选好就能用了。但是这在Linux下跑不了啊,更别说我们要用程序读取数据了,于是我打开了DeepSeek、ChatGPT、Grok…

经过一天折腾后,我终于可以在Linux下把玩这个小玩具了🐶

正文开始,要在Linux(Ubuntu系统,其他应该一样)下愉快的使用,需要下面几步

1. 检查串口通信模块

lsmod | grep -E 'pl2303|ch341'

如果没有输出,那么就执行sudo modprobe ch341

再用上面的检查一下,如果还是没有输出,你要在内核中开启这个了,下面的不用看了,去编译内核吧。。。

2. 删除 brltty

咱也不知道这玩意为啥和gy95t串口通信冲突了,查了一下是跟色盲有关的,一般人也用不到,删掉完事

sudo apt-get remove brltty

如果你恰好用到了,那么可以用 echo ‘deny-usb-ids 1a86:7523’ >> /etc/brltty.conf 避免冲突(我没测试,AI说的)

然后重启一下服务 sudo systemctl restart brltty

3. 添加 udev rules

添加这个可以创建一个 /dev/gy95t的节点,我们用这个避免和其他人发生冲突

1
2
echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0666", GROUP="dialout", SYMLINK+="gy95t"' |sudo tee /etc/udev/rules.d/99-gy95t.rules
sudo udevadm control --reload-rules && sudo udevadm trigger

搞完上面三步之后,下面的代码才能跑起来。代码主要来自 https://blog.csdn.net/m0_73694897/article/details/131273512


点我展开看代码

import serial # 导入串口库
import struct
import binascii
import time


# imu类
class IMU():
send_data = []
def init(self):
# 串口初始化
self.IMU_Usart = serial.Serial(
port = ‘/dev/gy95t’, # 根据自己的串口修改串口名
baudrate=115200, # 波特率
timeout = 0.001 # read_all按照一个timeout周期时间读取数据
)
# 接收数据初始化
self.ACC_X:float = 0.0 # X轴加速度
self.ACC_Y:float = 0.0 # Y轴加速度
self.ACC_Z:float = 0.0 # Z轴加速度
self.GYRO_X :float = 0.0 # X轴陀螺仪
self.GYRO_Y :float = 0.0 # Y轴陀螺仪
self.GYRO_Z :float = 0.0 # Z轴陀螺仪
self.roll :float = 0.0 # 横滚角
self.pitch :float = 0.0 # 俯仰角
self.yaw :float = 0.0 # 航向角
self.leve :float = 0.0 # 磁场校准精度
self.temp :float = 0.0 # 器件温度
self.MAG_X :float = 0.0 # 磁场X轴
self.MAG_Y :float = 0.0 # 磁场Y轴
self.MAG_Z :float = 0.0 # 磁场Z轴
self.Q0 :float = 0.0 # 四元数Q0.0
self.Q1 :float = 0.0 # 四元数Q1
self.Q2 :float = 0.0 # 四元数Q2
self.Q3 :float = 0.0 # 四元数Q3
# 判断串口是否打开成功
if self.IMU_Usart.isOpen():
print(“串口打开成功!”)
else:
print(“串口打开失败!”)

# 发送读取指令
self.Send_ReadCommand()

# 回调函数返回周期
time_period = 0.001


def Send_ReadCommand(self):

#发送读取IMU内部数据指令

#读寄存器例子,读取模块内部温度,主站发送帧为:A4 03 1B 02 C4
# | A4 | 03 | 1B | 02 | C4
# | 帧头ID | 读功能码 |起始寄存器| 寄存器数量 |校验和低 8 位


#0xA4:帧头 0x03:读取 0x08:第一个数据寄存器08 0x23:共35个数据寄存器 0xD2:前面数据的和取低八位
send_data = [0xA4,0x03,0x08,0x23,0xD2] #读35个寄存器需要发送的串口包
#send_data = [0xA4,0x03,0x08,0x1B,0xCA] #读27个寄存器需要发送的串口包,不发送串口包默认的接收状态
send_data=struct.pack(“%dB”%(len(send_data)),send_data) #解析成16进制
print(“向imu发送命令:”,send_data) #显示发送给imu的数据
self.IMU_Usart.write(send_data) #发送

def Read_data(self):
#读取数据

# 初始化数据
counter = 0
Recv_flag = 0
Read_buffer = []
# 接收数据至缓存区
Read_buffer=self.IMU_Usart.read(40) # 我们需要读取的是40个寄存器数据,即40个字节
# 状态机判断收包数据是否准确
while(1):
data_len = 0
if not Read_buffer:
print(“no data, retry”)
time.sleep(0.1)
return
# 第1帧是否是帧头ID 0xA4
if (counter == 0):
if(Read_buffer[0] != 0xA4):
break

# 第2帧是否是读功能码 0x03
elif (counter == 1):
if(Read_buffer[1] != 0x03):
counter=0
break

# 第3帧判断起始帧
elif (counter == 2):
if(Read_buffer[2] < 0x2c):
start_reg=Read_buffer[2]
else:
counter=0

# 第4帧判断帧有多少数量
elif (counter == 3):
if((start_reg+Read_buffer[3]) < 0x2C): # 最大寄存器为2C 大于0x2C说明数据肯定错了
data_len=Read_buffer[3]
else:
counter=0
break

else:
if(data_len+5==counter):
#print(‘Recv done!’)
Recv_flag=1

# 收包完毕
if(Recv_flag):
Recv_flag = 0
sum = 0
#print(Read_buffer) # Read_buffer中的是byte数据字节流,用struct包解包
data_inspect = str(binascii.b2a_hex(Read_buffer)) # data是将数据转化为原本的按照16进制的数据
#print(data_inspect)
try: # 如果接收数据无误,则执行数据解算操作
for i in range(2,80,2): # 根据手册,检验所有帧之和低八位是否等于末尾帧

sum += int(data_inspect[i:i+2],16)

if (str(hex(sum))[-2:] == data_inspect[80:82]): # 如果数据检验没有问题,则进入解包过程
#print(‘the Rev data is right’)

# 数据低八位在前,高八位在后
#print(Read_buffer[4:-1])
unpack_data = struct.unpack(‘<hhhhhhhhhBhhhhhhhh’,Read_buffer[4:-1])
# 切片并将其解析为我们所需要的数据,切出我们所需要的数据部分
g = 9.8

self.ACC_X = unpack_data[0]/2048
g # unit m/s^2
self.ACC_Y = unpack_data[1]/2048 g
self.ACC_Z = unpack_data[2]/2048
g
self.GYRO_X = unpack_data[3]/16.4 # unit 度/s
self.GYRO_Y = unpack_data[4]/16.4
self.GYRO_Z = unpack_data[5]/16.4
self.roll = unpack_data[6]/100
self.pitch = unpack_data[7]/100
self.yaw = unpack_data[8]/100
self.level = unpack_data[9]
self.temp = unpack_data[10]/100
self.MAG_X = unpack_data[11]/1000 # unit Gaos
self.MAG_Y = unpack_data[12]/1000
self.MAG_Z = unpack_data[13]/1000
self.Q0 = unpack_data[14]/10000
self.Q1 = unpack_data[15]/10000
self.Q2 = unpack_data[16]/10000
self.Q3 = unpack_data[17]/10000
print(self.dict)
except KeyboardInterrupt:
if serial != None:
print(“关闭串口端口”)
self.IMU_Usart.close()
except:
print(“接收的数据有错误!!”)
counter=0
break
else:
counter += 1 # 遍历整个接收数据的buffer

def timer_callback(self):

# —-读取IMU的内部数据———————————–
try:
count = self.IMU_Usart.inWaiting()
if count > 0:
self.Read_data()


# 发布sensor_msgs/Imu 数据类型
imu_data = Imu()
imu_data.header.frame_id = “map”
imu_data.header.stamp = self.get_clock().now().to_msg()
imu_data.linear_acceleration.x = self.ACC_X
imu_data.linear_acceleration.y = self.ACC_Y
imu_data.linear_acceleration.z = self.ACC_Z
imu_data.angular_velocity.x = self.GYRO_X 3.1415926 / 180.0 # unit transfer to rad/s
imu_data.angular_velocity.y = self.GYRO_Y
3.1415926 / 180.0
imu_data.angular_velocity.z = self.GYRO_Z * 3.1415926 / 180.0
imu_data.orientation.x = self.Q0
imu_data.orientation.y = self.Q1
imu_data.orientation.z = self.Q2
imu_data.orientation.w = self.Q3
self.publisher_.publish(imu_data) # 发布imu的数据

# ——————————————————–
#print(‘imu数据发布中’)

except KeyboardInterrupt:
if serial != None:
print(“关闭串口端口”)
self.IMU_Usart.close()

#——————————————————–


def main(args=None):

# 变量初始化———————————————
imu = IMU()
while True:
imu.Read_data()
time.sleep(0.1)



if name == ‘main‘:
main()

执行后就可以看到下面的数据,表面可以正常读取

1
2
3
4
5
6
7
8
9
10
11
12
bird@bird-GE60-2OC-2OE:~$ python3 usart.py 
串口打开成功!
向imu发送命令: b'\xa4\x03\x08#\xd2'
sleep 0.1s
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.899609375, 'ACC_Y': -3.3735351562500004, 'ACC_Z': 9.1109375, 'GYRO_X': 0.12195121951219513, 'GYRO_Y': 0.0, 'GYRO_Z': 0.06097560975609757, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.16, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.45, 'Q0': 1.2694, 'Q1': -0.1786, 'Q2': -0.2382, 'Q3': 0.992, 'level': 128}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.90439453125, 'ACC_Y': -3.3687500000000004, 'ACC_Z': 9.120507812500001, 'GYRO_X': -0.06097560975609757, 'GYRO_Y': 0.0, 'GYRO_Z': 0.0, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.17, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.447, 'Q0': 1.2694, 'Q1': -0.1786, 'Q2': -0.2382, 'Q3': 0.992, 'level': 128}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.90439453125, 'ACC_Y': -3.3735351562500004, 'ACC_Z': 9.120507812500001, 'GYRO_X': 0.12195121951219513, 'GYRO_Y': 0.0, 'GYRO_Z': 0.06097560975609757, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.21, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.447, 'Q0': 1.2694, 'Q1': -0.1786, 'Q2': -0.2382, 'Q3': 0.992, 'level': 128}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.9139648437500001, 'ACC_Y': -3.3591796875, 'ACC_Z': 9.11572265625, 'GYRO_X': 0.0, 'GYRO_Y': 0.0, 'GYRO_Z': 0.0, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.24, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.45, 'Q0': 1.2694, 'Q1': -0.1786, 'Q2': -0.2382, 'Q3': 0.992, 'level': 127}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.9091796875000001, 'ACC_Y': -3.3639648437500003, 'ACC_Z': 9.1109375, 'GYRO_X': 0.06097560975609757, 'GYRO_Y': 0.0, 'GYRO_Z': 0.06097560975609757, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.19, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.45, 'Q0': 1.2694, 'Q1': -0.1786, 'Q2': -0.2382, 'Q3': 0.992, 'level': 127}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.92353515625, 'ACC_Y': -3.3591796875, 'ACC_Z': 9.120507812500001, 'GYRO_X': 0.0, 'GYRO_Y': 0.0, 'GYRO_Z': -0.06097560975609757, 'roll': -20.27, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.21, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.448, 'Q0': 1.2694, 'Q1': -0.1785, 'Q2': -0.2382, 'Q3': 0.992, 'level': 128}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.92353515625, 'ACC_Y': -3.3639648437500003, 'ACC_Z': 9.11572265625, 'GYRO_X': 0.06097560975609757, 'GYRO_Y': -0.06097560975609757, 'GYRO_Z': 0.06097560975609757, 'roll': -20.26, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.17, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.448, 'Q0': 1.2694, 'Q1': -0.1784, 'Q2': -0.2382, 'Q3': 0.9921, 'level': 128}
{'IMU_Usart': Serial<id=0x7895341a3f10, open=True>(port='/dev/gy95t', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=0.001, xonxoff=False, rtscts=False, dsrdtr=False), 'ACC_X': 0.9091796875000001, 'ACC_Y': -3.3591796875, 'ACC_Z': 9.10615234375, 'GYRO_X': 0.0, 'GYRO_Y': 0.0, 'GYRO_Z': 0.06097560975609757, 'roll': -20.26, 'pitch': -5.35, 'yaw': 76.97, 'leve': 0.0, 'temp': 26.22, 'MAG_X': -0.596, 'MAG_Y': -0.059, 'MAG_Z': -1.449, 'Q0': 1.2694, 'Q1': -0.1784, 'Q2': -0.2382, 'Q3': 0.9921, 'level': 128}

关于如何在op上使用,你应该会了吧 🌹🐔

上次写完竟然被质疑能不能用 😓

有些代码还是要自己写的,这个python的只是gy95t这一款传感器使用的一种方式,用i2c接入的就是另外的代码了

关于怎么接入op,可以参考官方的代码 https://github.com/commaai/openpilot/blob/c3bba7431ab1b6dd1b2e5b0e937a446b606e4e67/tools/sim/lib/simulated_sensors.py#L23-L40

很简单,就不写了 🌹🐔

给魔方派3等Ubuntu设备添加高通GPU加速

之前发过给小米平板5运行openpilot的文章,里面用到了qcom的gpu驱动,效果还不错, 现在有用魔方派3的小伙伴也需要,所以分享出来。Happy Hacking!

跑起来有几个条件要满足:

  1. 系统是ubuntu 24.04 (Debian 13也可以)
  2. 设备是高通CPU
  3. 不要安装开源驱动,例如mesa的

安装高通驱动

我们用到高通的官方ppa源,安装方法如下:

1
2
3
4
sudo apt install software-properties-common
sudo add-apt-repository ppa:ubuntu-qcom-iot/qcom-noble-ppa
sudo apt update
sudo apt install qcom-adreno-cl-dev qcom-adreno-cl1 clinfo

如果报错了,说明你之前安装了opencl的,那么需要卸载

1
sudo apt remove opencl-headers opencl-c-headers

然后再装一次qcom-adreno-cl-dev qcom-adreno-cl1,执行clinfo出来很多东西,有Platform Name QUALCOMM Snapdragon(TM)就说明识别到了。
如果没有的话切root执行一下clinfo,可以出来的话需要改一下/dev/dri/renderD128的权限为660
sudo chmod 660 /dev/dri/renderD128,再用普通用户执行clinfo看看是不是好了。

修改op代码

  1. 注意:使用了高通cl驱动后,会导致openpilot的encoderd无法使用,注释掉即可。
  • SConstruct 里面的system/loggerd/SConscript 注释掉
  • system/manager/process_config.py 把encoderd那一行注释掉
  1. 启用GPU加速
  • selfdrive/modeld/SConscript 把for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:里面的默认执行设备改为GPU=1 IMAGE=0,可以参考我的。0.10后面的版本改为了DEV=GPU IMAGE=0
  • selfdrive/modeld/modeld.py 里面把os.environ['LLVM'] = '1'和下面一行JIT删掉,改为os.environ['GPU'] = '1',可以参考我的. 新版本的把os.environ['DEV'] = 'QCOM' if TICI else 'CPU'改为os.environ['DEV'] = 'QCOM' if TICI else 'GPU' 即可
  • selfdrive/modeld/dmonitoringmodeld.py 一样的改法

至此使用高通gpu跑openpilot完成。

如何在rk3588上运行openpilot[1]

本文来自 https://birdzhang.xyz/2024/04/14/如何在rk3588上运行openpilot-1/ 转载请注明出处

1. 前言

2024-07-03 更新,现在的openpilot已经对aarch64设备有良好的支持,不用再去改很多SConstruct进行修改了,👍

使用rk3588跑openpilot的难点,主要在模型的适配上(相机部分已经很好的解决了,之前没有visionipc的时候是比较复杂的),以及兼容aarch64系统的打包等等

2. 系统适配

笔者使用的是 https://github.com/Joshua-Riek/ubuntu-rockchip 修改过的,可以兼容各个厂商出的rk3588板子,方面好用。安装好git等常用工具后,把openpilot的代码clone下来,然后执行tools/ubuntu_setup.sh即可安装上绝大多数的依赖。

对于rk3588,还有一些特殊的依赖需要安装,比如mpp、gstreamer部分,以及opencv需要重新编译添加gstreamer支持。笔者使用的是opencv_headless,参考 https://github.com/opencv/opencv/issues/21804

使用的gstreamer依赖:

1
2
3
4
5
6
7
8
9
10
sudo apt-get install --no-install-recommends \
gstreamer1.0-gl \
gstreamer1.0-opencv \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-ugly \
gstreamer1.0-tools \
libgstreamer-plugins-base1.0-dev \
libgstreamer1.0-0 \
libgstreamer1.0-dev

对于rknn的运行环境,参考瑞芯微的文档即可

3. 模型适配

模型适配主要分为两个部分,一个是模型本身,一个是模型加载的代码。这里我们只讲模型执行的部分(因为我对模型量化不了解🤦‍♂️

现在comma公司刻意简化模型的runner,我们可以根据onnxmodel.py很方便的改出rknnmodel.py。

首先我们定义一个字典,把模型的参数都放在里面,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
model_inputs = {
"road": {
"input_names": ['input_imgs', 'big_input_imgs', 'desire', 'traffic_convention', 'nav_features', 'nav_instructions', 'features_buffer'],
"input_shapes": {
'input_imgs': [1, 12, 128, 256],
'big_input_imgs': [1, 12, 128, 256],
'desire': [1, 100, 8],
'traffic_convention': [1, 2],
'nav_features': [1, 256],
'nav_instructions': [1,150],
'features_buffer': [1, 99, 128]
},
"input_dtypes": {
'input_imgs': np.float32,
'big_input_imgs': np.float32,
'desire': np.float32,
'traffic_convention': np.float32,
'nav_features': np.float32,
'nav_instructions': np.float32,
'features_buffer': np.float32
}
},
"nav":{
"input_names": ['input_img'],
"input_shapes": {
"input_img":[1,1,256,256]
},
"input_dtypes": {
"input_img":np.float32
}
},
"dmonitor":{
"input_names": ['input_img', 'calib'],
"input_shapes": {
"input_img":[1,1382400],
"calib": [1,3]
},
"input_dtypes": {
"input_img":np.float32,
"calib": np.float32
}
}
}

再定义一个key,根据模型来区分,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
self.search_key = ""
# self.input_names = [x.name for x in self.session.get_inputs()]
# self.input_shapes = {x.name: [1, *x.shape[1:]] for x in self.session.get_inputs()}
# self.input_dtypes = {x.name: ORT_TYPES_TO_NP_TYPES[x.type] for x in self.session.get_inputs()}
if "nav" in path:
self.search_key = "nav"
elif "dmonitor" in path:
self.search_key = "dmonitor"
elif "supercombo" in path:
self.search_key = "road"
self.input_names = model_inputs.get(self.search_key).get("input_names")
self.input_shapes = model_inputs.get(self.search_key).get("input_shapes")
self.input_dtypes = model_inputs.get(self.search_key).get("input_dtypes")

剩下的应该你都懂了怎么使用了😛。

4. SConscript修改

rk3588明显是aarch64架构的,和C3一样的架构,但不是高通的,用不到SNPE,所以我们既要兼容一部分larch64的,也有一部分linux aarch64的。

例如添加/usr/lib/aarch64-linux-gnu的path,注释掉'system/camerad/SConscript',重新编译third_party/acados等等,需要一些尝试

5. 运行

假如都改好了后,运行./launch_openpilot.sh应该就会看到界面了

如果遇到其他问题,欢迎在评论区留言。如果有比较多人关注,会出第二期详细的内容。

okcaros移植到红米note8及体验

最近okcaros开放第三方移植了,我就简单试了一下,有点惊喜

okcaros官网 https://www.okcaros.com/zh ,实现原理是通过更改usb协议,欺骗carplay来映射安卓手机的内容,思路还是挺好的

适配过程比较简单,给kernel“打个补丁”,引入一下okcar代码,编译就完事了(实际是在AMD r7-5800上面,16G内存的虚拟机上,要跑12+小时😂

上两张图

—————2024年04月14日 16点32分—————

继续上次没写完的内容。代码已经合并到okcaros的仓库了,可以clone下来直接编译了,代码参见 https://github.com/okcar-os?q=ginkgo&type=all&language=&sort=

当然,既然合并到上游仓库了,自然是提供直接下载的 https://download.okcaros.com/devices/ginkgo/builds

另外,发现有windows下的安装工具了,https://okcar-cdn.okcarbox.com/app/okcaros_installer_1.0.0.exe

关于香橙派5运行flowpilot

香橙派5是一款使用瑞芯微rk3588的开发板,有3个usb接口(1个type-c和type-a公用,虽然4个其实3个),一个hdmi接口,一个千兆网口,等等

废话不多说,下面是如何安装flowpilot

前置条件

  • 起码一个usb摄像头
  • 一块屏幕,HDMI的或者mipi dsi的都行
  • 其他的就是能让Ubuntu系统启动所必须的硬件了,如硬盘或emmc或sd卡,散热装置等

安装系统

这里我使用的是 https://github.com/Joshua-Riek/ubuntu-rockchip

如果你是用sd卡,那么只需要把系统dd进去即可
其他的参考官方的烧录方法,此处不赘述

安装flowpilot

这里基本是按照 https://github.com/flowdriveai/flowpilot/wiki/Installation 方法进行的,除此之外还要安装一些额外的包

1
2
3
sudo apt install ffmpeg libavformat-dev libavcodec-dev libswscale-dev \
libssl-dev libcurl4-openssl-dev ocl-icd-opencl-dev libgflags-dev \
libstdc++-12-dev libprotobuf-dev protobuf-compiler

源码可以参考我更改的fork https://github.com/0312birdzhang/flowpilot ,主要修改了一些兼容性,以及替换了opencl为系统自带的,还有就是编译了aarch64上面的几个libraries

配置摄像头和车型,更改 launch_flowpilot_new.sh,进入flowpilot的目录下,运行pipenv shell,然后执行launch_flowpilot_new.sh即可

体验

总体来说只能说跑起来了,离日常使用还比较遥远,例如设备发热严重(没有用到rknpu,使用tnn跑只能调用GPU性能要比调用NPU差点,需要写JNI来调用rknpu跑,暂时没精力研究这部分了)、还没有驾驶员监控、上游进度缓慢等等,所以我也转向原生openpilot上了。

记录一次Signal使用代理的过程

在网上找了很多方法,按照传统的思路,在cmd里面设置如下命令然后启动

1
2
set HTTP_PROXY=http://127.0.0.1:1081
set HTTPS_PROXY=http://127.0.0.1:1081

结果并不行,然后我发现了这个 https://github.com/signalapp/Signal-Desktop/pull/1855

1
2
3
set HTTPS_PROXY=http://127.0.0.1:1081
set WSS_PROXY=http://127.0.0.1:1081
set ALL_PROXY=http://127.0.0.1:1081

然后将Signal.exe拖进cmd,enter后就可以正常了。

Tomcat多实例session共享方案

Tomcat实现多实例session共享的方案还挺多的,up主使用了三种,最终选择了tomcat自带的Cluster集群方案。下面来说一下这三种方案的优缺点。

up主用的tomcat7,至于为什么还是7这么老的版本,因为高版本的对get请求有一些字符校验
另外,这些都是不用改java代码的,其他的没做研究

tomcat-redis-session-manager

这个应该是最常见的方案了,我们随便一搜就是这个,但是代码有些坑。。。

如: 这个 还有这个 等等,还有两个致命的bug,一是session保留时间过长,里面有一段代码 session.setMaxInactiveInterval(getMaxInactiveInterval() * 1000); ,这里多保留了1000倍的时间。二是每次访问都会有一条session记录保留,非常的耗费redis内存。

虽然上述俩bug本up主已经修复了,但是发现还会丢session,估计是redis的驱逐问题,也懒得去调试了。

redisson

本up主发现这个的时候以为终于得救了,看一下人家的官网 https://redisson.pro/ ,还有商业版,就觉得很靠谱。事实证明还是too young too naive啊

因为突然有一天,同事说你这个接口好慢,然后我发现整个机器负载都很高了,访问网站直接卡成狗,看了一下日志,jvm崩溃了 java.lang.OutOfMemoryError: GC overhead limit exceeded

上面那个不管是丢session还是保留时间太长,但是不至于把tomcat搞死。好家伙,这个直接把jvm干崩溃了,OOM可还行

Tomcat Cluster

这里有一个很详细的教程 http://xstarcd.github.io/wiki/Java/tomcat_cluster.html ,我就不赘述了。

注意,我直接贴他的tomcat配置发现有看不见的空行还是啥的,可以去tomcat网站复制。

注意2,一定要在web.xml中添加<distributable />

使用两天了,暂时没发现问题,有待后续观察。