“出差两个月的泊车”
正文
- 由于上海疫情结束,6月底出发去浙江实车联调新车型蓝牙RPA泊车功能,功能第一版基本开发完成,正好有时间整理,也正好梳理一下思路。
- 首先说一下蓝牙泊车的设计思路,主要是分成几个模块组成:
- 第一个模块,发送蓝牙信号的封装
- 第二个模块,接收蓝牙信号的解析、封装
- 第三个模块,视图渲染层
- 第三个模块,动画渲染层
- 整理难点:
- 难点1,蓝牙二进制流不是一次性发送,拆包,根据分包数据,处理丢包数据、组合分包数据
- 难点2,车位渲染,性能损耗
- 难点3,自选车位功能,需要把客户端位置转化为大端蓝牙二进制数据
- 难点4,解决异常状态定时器存在不消失问题,可能造成安全隐患
- 难点5,检测信号跳变
流程图
- 开搞前先构思下流程图
- 泊车流程图如下:
上代码(命名已脱敏)
发送蓝牙信号
- 根据文档把对应的信号,抽象成enum,根据enum实现对应的信号发送。
enum RPACMD { case RPA_StartParking // 开启泊车 case RPA_StopParking // 停止泊车 } // 调用 HZBleControl.manager.sendRPAParkingCommand(cmd:.RPA_StartParking) class func sendParkingCmd(cmd:RPACMD) { var data = Data() switch cmd { case .RPA_StartParking: data.append(contentsOf: [0x2A, 0x01, 0x01]) case .RPA_StopParking: data.append(contentsOf: [0x2A, 0x01, 0x00]) } let carId = "车辆唯一id" // 发送指令 BleKit.sharedInstance().sendData(data, carId: carId) }
接收蓝牙信号的解析
- 挑一个逻辑相对复杂的来说下,蓝牙每200毫秒会发送一次信号,三次信号组合后才是一组完整数据。
- 所以要处理丢包逻辑,组合逻辑。
- 核心代码如下
// 校验当前蓝牙返回数据
private func checkFrameData(_ frame:Int) -> Bool {
if HZRPADataHandle.shared.frameData.count == 0 && frame == 1 {
return true
} else if HZRPADataHandle.shared.frameData.count == 1 && frame == 2 {
return true
} else if HZRPADataHandle.shared.frameData.count == 2 && frame == 3 {
return true
} else {
//遇到丢包等问题
return false
}
}
// (RPADataModel, Bool) bool为false为不完整数据,不做回调处理
public class func analysisRPASignal(_ data: Data) -> (RPADataModel, Bool) {
// 第几帧数据 1、2泊车的 // 第二字节 这一帧数据多长
let frame = data.subdata(in: 0..<1).oneByteToInt()
let result = RPADataHandle.shared.checkFrameData(frame)
if result == true {
RPADataHandle.shared.frameData.append(data)
if RPADataHandle.shared.frameData.count == 3 {
var frameDataTop = RPADataHandle.shared.frameData[0]
var frameDataCenter = RPADataHandle.shared.frameData[1]
var frameDataBottom = RPADataHandle.shared.frameData[2]
// 删除前两字节数据
frameDataTop.removeFirst(2)
frameDataCenter.removeFirst(2)
frameDataBottom.removeFirst(2)
frameDataTop.append(frameDataCenter)
frameDataTop.append(frameDataBottom)
//重新初始化 不重新初始化会导致Data是从第三位开始 按index取值会出现数组越界
RPADataHandle.shared.bleDataSource = Data(frameDataTop)
RPADataHandle.shared.frameData.removeAll()
}
} else {
//丢包处理
RPADataHandle.shared.bleDataSource = Data()
RPADataHandle.shared.frameData.removeAll()
}
let bleData = RPADataHandle.shared.bleDataSource
if bleData.count >= 301 {
let model = HZRPADataModel()
model.RPA_ADCS4_RPA_FunctionMode = bleData.subdata(in: 0..<1).oneByteToInt()
...
return model
}
}
大小端蓝牙转换
- 业务上通过本地UI映射的数据,以车端信号的方式传输给车端蓝牙,然后进行信号发起。
- 核心代码如下
// 将从本地解析出来的模拟蓝牙信号数值转换成Data类型 private func changeIntToData(_ num: Int) -> Data { var number = num let data: Data = Data(bytes: &number, count: 8) return data } // byteSwapped 字节对调 private func swapUInt16Data(data : Data) -> Data { var mdata = data let count = data.count / MemoryLayout<UInt16>.size // UnsafeMutablePointer<UInt16> 已弃用 mdata.withUnsafeMutableBytes { (i16ptr: UnsafeMutableRawBufferPointer) in for i in 0..<count { if i > 1 { i16ptr[i] = i16ptr[i].byteSwapped } } } return mdata }
蓝牙跳变监听
- 由于泊车的UI逻辑和一般的页面渲染不同,需要根据信号的跳变实时刷新UI,所以UI模块的拆分极其重要,不然就会出现层级问题,以及事件遮挡等问题。
- 我的逻辑是首先通过信号的校验和组合设置完成model,再通过model的didset方法监听值通过block回调给页面的逻辑
- 核心代码如下
//通过 model值的更改,保证信号为最新信号
@objc public var dataModel: HZRPADataModel = HZRPADataModel() {
didSet{
//跳变处理
// 复制
HZRPADataHandle.shared.sourceModel = dataModel
// 车位处理
if let callBack = spaceCallback {
callBack()
}
if oldValue.RPA_ADCS11_PA_WorkSts == .error {
//中断跳变 不再继续执行
return
}
if oldValue.RPA_ADCS4_RPA_FunctionMode != sourceModel.RPA_ADCS4_RPA_FunctionMode {
//RPA_ADCU6_RPA_FunctionMode 监听
if let callBack = functionModeCallback {
callBack(dataModel)
}
}
}
}
结束
欢迎交流。
—— Ade