文章用于学习记录
文章目录
前言
尝试 App Inventor、uni-app 以及微信小程序三种方式进行蓝牙开发,最后选择微信小程序进行蓝牙开发。
一、App Inventor
-
App Inventor 是一款 Android 小工具制作软件,使用积木编程,非常简单易上手。
-
-
制作 APP 的操作主要分组件设计和逻辑设计二部分
-
组件设计主要是对用户界面的控件(如按钮等) 的布局设计,
-
逻辑设计是对控件添加上逻辑功能,然后通过客户端(如蓝牙) 进行传送控制信息数据,
-
组件设计和逻辑设计完成后,再点击打包 APK 在手机上直接安装,安装好后,就可以进入控制界面,系统用户登陆界面如下图所示:
但这块的逻辑部分功能并未实现。
二、uni-app
-
开发工具:HBuilder X
-
以安卓 App 的方式运行(需要连接数据线)
-
可实现连接蓝牙发送控制指令
使用蓝牙进行数据传输的大概思路如下: 初始化:打开蓝牙模块; 搜索:检测附近存在的设备,找到目标设备; 通讯:获取目标设备的服务值和特征值,看是否能监听、读写数据; 发送指令:发送前后左右等控制指令;
三、微信小程序
3.1 示例&应用
- 采用 forEach() 的 return 语句实现 continue 关键字效果(跳过当前循环迭代并继续执行下一次迭代)
- 解决蓝牙(未知设备)过滤问题
- forEach() 应用
- 通过判断设备的名称,当设备名称为"JDY-24M"时,将其设备 ID 保存在变量 deviceId 中,并进行打印输出。
3.2 服务值与特征值
- 在蓝牙中,服务值(Service UUIDs)和特征值(Characteristic UUIDs)是用于标识蓝牙设备和其功能的唯一标识符。
- 服务值:服务值用于标识蓝牙设备提供的一组相关功能或服务。一个蓝牙设备可以包含一个或多个服务。
- 特征值:特征值用于标识服务中的某个具体特征或数据。一个服务可以包含一个或多个特征值。
- 每个服务都有一个唯一的服务值,通常以 UUID(Universally Unique Identifier)的形式表示。
- 服务值可以用来描述设备的整体功能或者区分不同类型的服务。
- 特征值也有唯一的 UUID 来进行标识,特征值可以表示传感器数据、配置选项、控制命令等。
3.3 控制指令
- 蓝牙控制指令是用于与蓝牙设备进行通信和控制的指令。
- 这些指令通常以字节流或字符串的形式发送到蓝牙设备,以执行特定的操作或获取设备的信息。
3.4 测试
-
串口调试助手用于与串口设备进行通信、调试和测试。
-
串口设置:设置串口的通信参数,如串口号、波特率(Baud Rate)、数据位(Data Bits)等。
-
数据发送和接收:输入要发送的数据并发送到串口,同时显示接收到的数据。
-
数据解析和显示:支持对接收到的串口数据进行解析和显示,例如十六进制显示等。
3.5 十六进制转换
function ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}
代码具体含义:
这段代码定义了一个函数ab2hex,用于将数组缓冲区(buffer)转换为十六进制字符串。
'ab2hex' 函数接受一个参数 buffer,表示要进行转换的数组缓冲区;
在函数内部,使用 Array.prototype.map 方法对 buffer 进行遍历,创建一个新的数组 hexArr;
在遍历过程中,对于 buffer 中的每个元素(bit),使用匿名函数进行处理;
匿名函数将每个元素转换为两位的十六进制字符串,并使用 ('00' + bit.toString(16)).slice(-2) 的方式实现补零操作;
bit.toString(16) 将当前元素转换为十六进制字符串;
('00' + bit.toString(16)) 在转换的字符串前面添加两个零,确保字符串的长度为两位;
.slice(-2) 从字符串的末尾截取最后两位,以确保字符串长度不超过两位;
Array.prototype.map.call 将匿名函数应用于 buffer 中的每个元素,并返回一个新的数组 hexArr;
最后,通过函数返回 hexArr.join('') 将转换后的十六进制字符串数组连接成一个字符串,并将其作为函数的返回值;
3.6 问题解决
- Keil 编译报错
- Missing Complier Version 5
- 将 v5 版本的编译器 ARMCC 复制到 keil 安装目录下的 ARM 文件夹里即可;
四、虚拟摇杆
4.1 界面布局
参考 微信小程序之虚拟摇杆练习,实现手指拖动图片任意移动并限定在一个圆形范围内,当松开手指时图片返回到中心点。在视图容器中添加一个背景图片、一个摇杆图片,分别设置图片样式的宽、高,实现图片叠加的效果。
根据变量 leftLooks 和 topLooks 动态调整图片左偏移和上偏移,同时,绑定了 bindtouchend、bindtouchmove、bindtap 事件分别对应触摸结束、触摸移动和触摸点击的事件处理函数,并在底部添加了三个文本框显示虚拟摇杆的中心位置、当前滑动位置和摇杆的偏转角度。
4.2 代码示例
<view class="relative">
<!-- 背景图片 -->
<image style="width: 100px; height: 100px;" class="pic_background" src="../../yaogan_di.png"></image>
<!-- 摇杆图片 -->
<image style="width: 80px; height: 80px;left:{{leftLooks}}px;top:{{topLooks}}px;" bindtouchend='ImageReturn' bindtouchmove='ImageTouchMove' class="pic_tou" src="../../yaogan_tou.png " bindtap="ImageTouch"></image>
<div class="box">
<text class="log-item">虚拟摇杆中心:(160,310)</text>
<text class="log-item">当前滑动位置:({{leftLooks}},{{topLooks}})</text>
<text class="log-item">摇杆偏转角度:{{angle}}°</text>
</div>
</view>
4.3 摇杆角度
原先摇杆角度存在象限(正负)之分,若要根据角度发送控制指令,出现度数重复的情况下不易处理。
故采取把摇杆地图看成两个半圆,当摇杆移动到左边,角度为0度或360度(因为360度为一圈,已经绕回到起点);当摇杆移动到上方,角度为90度;当表示摇杆移动到右边,角度为180度;当表示摇杆移动到下方,角度为270度。
4.4 代码示例
Page({
data: {
logs: [],
StartX: '160',
StartY: '310',
leftLooks: '',
topLooks: '',
radius: '60',
angle: '',
},
onLoad: function () {},
// 函数在摇杆图片被点击时触发,打印出"点击"的信息
ImageTouch: function (e) {
console.log("点击");
},
// 函数在摇杆图片被拖动时触发,首先获取触摸点的坐标,
ImageTouchMove: function (e) {
var self = this;
// 通过 e.touches[0].clientX 和 e.touches[0].clientY 获取相对于页面的触摸点的 X 和 Y 坐标,
// 并减去40来调整坐标位置(可能是根据图片大小进行的调整)
var touchX = e.touches[0].clientX - 40;
var touchY = e.touches[0].clientY - 40;
// 将触摸点的坐标传递给GetPosition函数,以获取移动后的摇杆位置和角度信息
var movePos = self.GetPosition(touchX, touchY);
// console.log("接触坐标:(" + touchX + "," + touchY + ")");
// console.log("图片坐标:(" + movePos.posX + "," + movePos.posY + ")");
// 将移动后的摇杆位置和角度信息更新到页面的数据中,从而实现实时显示
self.setData({
leftLooks: movePos.posX,
topLooks: movePos.posY,
angle: Math.round(movePos.angle)
});
console.log("角度:" + Math.round(movePos.angle));
},
// 在触摸结束时触发,将摇杆图片的位置和角度重置为初始状态
ImageReturn: function (e) {
// 将当前上下文的 this 引用保存到变量 self 中,以便在函数内部使用
var self = this;
// 更新页面的数据,将 leftLooks 和 topLooks 设置为初始位置 self.data.StartX 和 self.data.StartY,
// 将角度 angle 设置为空字符串"",当触摸结束时,摇杆图片的位置将回到初始位置,角度信息将被清空
self.setData({
leftLooks: self.data.StartX,
topLooks: self.data.StartY,
angle: ""
});
},
// 用于计算摇杆图片的位置和角度信息,
// 函数接受touchX和touchY作为参数,表示当前触摸点的坐标
GetPosition: function (touchX, touchY) {
var self = this;
// 计算触摸点与摇杆中心点的水平距离 DValue_X 和垂直距离 Dvalue_Y,以及两者的欧几里得距离 Dvalue_Z。
var DValue_X = touchX - self.data.StartX;
var Dvalue_Y = touchY - self.data.StartY;
var Dvalue_Z = Math.sqrt(DValue_X * DValue_X + Dvalue_Y * Dvalue_Y);
// 调用 self.GetAngle 方法计算摇杆的偏转角度,并将 DValue_X 和 Dvalue_Y 作为参数传递
self.GetAngle(DValue_X, Dvalue_Y);
// 计算触摸点与摇杆中心点的夹角,并将其转换为角度制
var angle = Math.atan2(Dvalue_Y, DValue_X) * 180 / Math.PI;
// 将负角度转换为正角度,确保角度在0到360之间
angle = (angle + 360) % 360; // 将负角度转换为正角度
var imageX, imageY;
// 在范围内,将 imageX 和 imageY 设置为触摸点的坐标 touchX 和 touchY。
if (Dvalue_Z <= self.data.radius) {
imageX = touchX;
imageY = touchY;
} else {
// 若超出范围,计算缩放比例 ratio,并根据比例将 DValue_X 和 Dvalue_Y 乘以比例后再加上摇杆中心点的坐标(160, 310),得到缩放后的坐标 imageX 和 imageY。
var ratio = self.data.radius / Dvalue_Z;
imageX = DValue_X * ratio + 160;
imageY = Dvalue_Y * ratio + 310;
}
// 使用 Math.round 将 imageX 和 imageY 四舍五入为整数,并将计算得到的位置和角度信息以对象的形式返回。
imageX = Math.round(imageX);
imageY = Math.round(imageY);
return { posX: imageX, posY: imageY, angle: angle };
},
// 用于计算摇杆的偏转角度
// 函数接受 DValue_Y 和 Dvalue_X 作为参数,分别表示触摸点的垂直距离和水平距离
GetAngle: function (DValue_Y, Dvalue_X) {
var self = this;
// 条件判断检查 DValue_Y 是否为非零值,避免出现除以零的情况。
if (DValue_Y != 0) {
// 计算 angleTan 的反正切值,并将结果保存在变量ang中。
var angleTan = Dvalue_X / DValue_Y;
var ang = Math.atan(angleTan);
// 将 ang 乘以180 / Math.PI 进行角度制的转换,得到角度值 angs
var angs = ang * 180 / Math.PI;
// 将 angs 四舍五入为最接近的整数,得到最终的偏转角度 result
var result = Math.round(angs);
// 将偏转角度result设置为页面数据中的angle属性,实现数据的更新和绑定
self.setData({
angle: result
});
console.log(result);
}
}
});
4.5、效果展示
总结
以上就是整个蓝牙开发过程以及虚拟摇杆的实现与改进总结。