之前是一个关于如何绘制心电图的代码,后续需求是添加ECG的网格背景图也是利用canvas绘制的
先看代码:
<template>
<view>
<page-head :title="title"></page-head>
<!-- 心电图显示区 -->
<view class="displayarea">
心电图显示区
<view class="container">
<view class="box2">
<canvas canvas-id="line" style="width: 100%; height: 500px;"></canvas>
</view>
<view class="box1">
<canvas canvas-id="ecg" style="width: 100%; height: 500px;"></canvas>
</view>
</view>
</view>
<!-- 遮罩 -->
<!-- 按钮 -->
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button type="primary" :disabled="disabled" @click="openBluetoothAdapter">连接蓝牙</button>
<button type="primary" :disabled="!disabled" @click="closeBLEConnection">断开蓝牙设备</button>
<button type="primary" :disabled="!disabled" @click="closeBluetoothAdapter">关闭蓝牙模块</button>
</view>
</view>
</view>
</template>
<script>
let num = 0;
let timer = null;
export default {
data() {
return {
title: 'bluetooth',
disabled: false,
deviceId: '',
serviceId: '0000FFF0-0000-1000-8000-00805F9B34FB',
writeCharacteristicsId: '',
connectCharacteristicsId: '',
characteristicId: '0000FFF1-0000-1000-8000-00805F9B34FB',
heartRateData: [],
macAddress: '',
macValue: '',
valueChangeData: {
value: ''
},
extraLine: [],
};
},
onLoad() {
this.setOnBLECharacteristicValueChange();
// 自动连接蓝牙获取心电图,如果不需要直接注释掉
this.openBluetoothAdapter();
},
// 在页面退出的时候变为竖屏
onHide() {
uni.setScreenOrientation({ orientation: 'portrait' })
},
onShow() {
const ctx = uni.createCanvasContext('ecg', this);
if (timer != null) {
clearInterval(timer);
}
timer = setInterval(() => {
if (this.heartRateData.length > 0) {
this.drawLine(ctx, this.heartRateData)
}
}, 50)
setTimeout(() => {
this.drawGrid(); // 页面准备好时绘制网格图
}, 2000)
// 启动横屏
uni.setScreenOrientation({
orientation: 'landscape'
})
},
beforeDestroy() {
clearInterval(timer);
timer = null;
this.closeBLEConnection()
uni.closeBluetoothAdapter()
},
methods: {
drawGrid() {
const canvasId = 'line';
this.drawSmallGrid(canvasId);
this.drawMediumGrid(canvasId);
this.drawBigGrid(canvasId);
},
// 绘制小网格
drawSmallGrid(canvasId) {
const ctx = uni.createCanvasContext(canvasId, this);
ctx.setStrokeStyle('#FFDFDF'); // 设置线条颜色
ctx.setLineWidth(1); // 设置线条宽度
for (let x = 0.5; x < 750; x += 3) { // 以 3px 为间隔绘制纵线
ctx.moveTo(x, 0);
ctx.lineTo(x, 750);
}
for (let y = 0.5; y < 750; y += 3) { // 以 3px 为间隔绘制横线
ctx.moveTo(0, y);
ctx.lineTo(750, y);
}
ctx.stroke(); // 进行绘制
ctx.draw(true); // 保留之前的绘制内容
},
// 绘制中等网格
drawMediumGrid(canvasId) {
const ctx = uni.createCanvasContext(canvasId, this);
ctx.setStrokeStyle('#FFBFBF'); // 设置线条颜色
ctx.setLineWidth(1); // 设置线条宽度
for (let x = 0.5; x < 750; x += 15) { // 以 15px 为间隔绘制纵线
ctx.moveTo(x, 0);
ctx.lineTo(x, 750);
}
for (let y = 0.5; y < 750; y += 15) { // 以 15px 为间隔绘制横线
ctx.moveTo(0, y);
ctx.lineTo(750, y);
}
ctx.stroke(); // 进行绘制
ctx.draw(true); // 保留之前的绘制内容
},
// 绘制大网格
drawBigGrid(canvasId) {
const ctx = uni.createCanvasContext(canvasId, this);
ctx.setStrokeStyle('#e0514b'); // 设置线条颜色
ctx.setLineWidth(1); // 设置线条宽度
for (let x = 0.5; x < 750; x += 75) { // 以 75px 为间隔绘制纵线
ctx.moveTo(x, 0);
ctx.lineTo(x, 750);
}
for (let y = 0.5; y < 750; y += 75) { // 以 75px 为间隔绘制横线
ctx.moveTo(0, y);
ctx.lineTo(750, y);
}
ctx.stroke(); // 进行绘制
ctx.draw(true); // 保留之前的绘制内容
},
moveHandle() {},
/**
* 关闭遮罩*/
maskclose() {
this.maskShow = false;
},
/**
* 选择设备*/
queryDevices() {
// this.newDeviceLoad = true;
this.showMaskType = 'device';
this.maskShow = true;
},
tapQuery(item) {
if (this.showMaskType === 'device') {
this.$set(this.disabled, 4, false);
if (this.equipment.length > 0) {
this.equipment[0] = item;
} else {
this.equipment.push(item);
}
this.newDeviceLoad = false;
}
if (this.showMaskType === 'service') {
this.$set(this.disabled, 6, false);
if (this.servicesData.length > 0) {
this.servicesData[0] = item;
} else {
this.servicesData.push(item);
}
}
if (this.showMaskType === 'characteristics') {
this.$set(this.disabled, 7, false);
if (this.characteristicsData.length > 0) {
this.characteristicsData[0] = item;
} else {
this.characteristicsData.push(item);
}
}
this.maskShow = false;
},
/**
* 第一步
* 初始化蓝牙设备
*
* */
openBluetoothAdapter() {
// 设置 监听蓝牙适配器状态变化事件
console.log("第一步");
this.disabled = true
// this.setOnBluetoothAdapterStateChange()
uni.openBluetoothAdapter({
success: e => {
console.log('openBluetoothAdapter success', e);
this.startBluetoothDevicesDiscovery()
},
fail: e => {
console.log(e)
console.log('初始化蓝牙失败,错误码:' + (e.errCode || e.errMsg));
if (e.errCode !== 0) {
initTypes(e.errCode, e.errMsg);
}
}
});
},
/**
* 第二步,
* 监听蓝牙适配器状态变化事件
*/
setOnBluetoothAdapterStateChange() {
console.log("第二步");
uni.onBluetoothAdapterStateChange((res) => {
console.log("第二步", res);
if (res.available) {
// 蓝牙适配器 ,则开始搜索蓝牙设备
this.startBluetoothDevicesDiscovery()
}
})
},
/**
* 第二步,
* 开始搜索蓝牙设备
* */
startBluetoothDevicesDiscovery() {
console.log("第三步");
// 搜索之前监听寻找到新设备的事件
this.onBluetoothDeviceFound();
uni.startBluetoothDevicesDiscovery({
success: e => {
},
fail: e => {
console.log('搜索蓝牙设备失败,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
/**
* 第三步
* 寻找到新设备
*
* */
onBluetoothDeviceFound() {
console.log("第四步");
uni.onBluetoothDeviceFound(res => {
console.log(res);
// 监听寻找到新设备的事件
for (var i = 0; i < res.devices.length; i++) {
let device = res.devices[i]
// 根据设备名称找到对应的设备,然后链接设备
if (device.name && device.name == 'FSC-BT1036-LE-09F8') {
this.deviceId = device.deviceId
// 停止设备查找
this.stopBluetoothDevicesDiscovery()
uni.showLoading({
title: '连接蓝牙...'
});
uni.createBLEConnection({
deviceId: this.deviceId,
success: res => {
uni.hideLoading()
setTimeout(() => {
this.getBLEDeviceServices().then(res => {
console.log(res);
return this.getBLEDeviceCharacteristics()
}).then(res => {
console.log(res);
return this.bleNotifyAndlisten()
}).then(res => {
console.log(res);
// 需要延时 再去调用读取数据
setTimeout(() => {
this.readBLECharacteristicValue()
}, 1000);
})
}, 1000)
},
fail: (err) => {
uni.hideLoading()
uni.showModal({
title: "温馨提示",
content: '"蓝牙连接失败'
})
}
});
}
}
});
},
/**
* 第四步
* 连接低功耗蓝牙设备
* */
createBLEConnection(deviceId) {
return
},
/**
* 第五步
* 获取蓝牙所有服务
*/
getBLEDeviceServices() {
return new Promise((reslove, reject) => {
uni.getBLEDeviceServices({
deviceId: this.deviceId,
success: res => {
console.log(res);
reslove("[ok-5].获取蓝牙所有服务成功!")
},
fail: err => {
reject(err)
}
})
})
},
/**
* 第六步
* 获取该蓝牙设备某个服务的特征
*/
getBLEDeviceCharacteristics() {
return new Promise((reslove, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: this.deviceId,
serviceId: this.serviceId,
success: res => {
console.log('getBLEDeviceCharacteristics success');
console.log(res);
let characteristics = res.characteristics
for (let i = 0; i < characteristics.length; i++) {
let item = characteristics[i]
if (item.properties.write) {
this.writeCharacteristicsId = item.uuid;
}
if (item.properties.notify || item.properties.indicate) {
this.connectCharacteristicsId = item.uuid
}
}
console.log(this.writeCharacteristicsId, this.connectCharacteristicsId);
reslove("[ok-6].获取蓝牙下该服务的特征成功!")
},
fail: err => {
console.log('getBLEDeviceCharacteristics fail', err);
reject(err)
}
})
})
},
/**
* 第七步
* 启用低功耗蓝牙设备特征值变化时的 notify 功能,
*/
bleNotifyAndlisten() {
return new Promise((reslove, reject) => {
uni.notifyBLECharacteristicValueChange({
state: true,
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.connectCharacteristicsId,
success: res => {
console.log('notifyBLECharacteristicValueChange success', res);
reslove("[ok-7]. notify 启动成功")
},
fail: err => {
console.log('notifyBLECharacteristicValueChange err', err);
reject(err)
}
})
})
},
/**
* 第八步
* 发起读数据请求
* 读取低功耗蓝牙设备的特征值的二进制数据值。注意:必须设备的特征值支持 read 才可以成功调用 */
readBLECharacteristicValue() {
console.log("第八步");
uni.readBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.connectCharacteristicsId,
success: (res) => {
console.log('读取设备数据值成功');
console.log(JSON.stringify(res));
},
fail: (res) => {
console.log('读取设备数据值失败,错误码:' + res.errCode);
// if (res.errCode !== 0) {
// initTypes(e.errCode);
// }
}
});
//this.onBLECharacteristicValueChange();
},
/**
* 第九步,解析数据
* 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
*
* */
setOnBLECharacteristicValueChange() {
let that = this
// 必须在这里的回调才能获取
uni.onBLECharacteristicValueChange((res) => {
console.log("第九步");
console.log('监听低功耗蓝牙设备的特征值变化事件成功');
console.log(
`characteristic ${res.characteristicId} has changed, now is ${JSON.stringify(res)}`);
const unit8Array = new Uint8Array(res.value);
// console.log("unit8Array: ", unit8Array);
// 获取canvas绘图上下文
// let heartRate = that.heartRateData;
if (this.heartRateData.length >= 100) {
this.heartRateData.shift();
}
this.heartRateData.push(unit8Array);
// that.heartRateData = heartRate;
console.log("----heartRate", this.heartRateData)
console.log("length", this.heartRateData.length)
this.macAddress = res.deviceId;
this.macValue = this.ab2hex(res.value);
this.valueChangeData.value = this.ab2hex(res.value);
this.extraLine.push(this.macValue);
this.valueChangeData = this.extraLine.join(' \n ');
});
},
/**
* 停止搜索蓝牙设备*/
stopBluetoothDevicesDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: e => {},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
}
});
},
/**
* 断开与低功耗蓝牙设备的连接*/
closeBLEConnection() {
uni.closeBLEConnection({
deviceId: this.deviceId,
success: res => {
console.log(res);
console.log('断开低功耗蓝牙成功:' + res.errMsg);
this.$set(this.disabled, 1, false);
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, true);
this.$set(this.disabled, 6, true);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.$set(this.disabled, 9, true);
this.equipment = [];
this.servicesData = [];
this.characteristicsData = [];
},
fail: e => {
console.log('断开低功耗蓝牙成功,错误码:' + e.errCode);
if (e.errCode !== 0) {
initTypes(e.errCode);
}
}
});
},
// 数据解析
ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
},
// 绘制波形图
drawLine(ctx) {
const width = 600;
const height = 600;
// 设置波形图样式
ctx.setStrokeStyle('#000');
ctx.setLineWidth(1);
ctx.setLineCap("round")
ctx.clearRect(0, 0, width, height);
// 清除Canvas并绘制新的波形图
ctx.beginPath();
ctx.moveTo(-10, 0); // 将起点移动到Canvas的中间位置
for (let i = 0; i < this.heartRateData.length; i++) {
const y = (Number(this.heartRateData[i][0]) + (Number(this.heartRateData[i][1]) << 8) + (Number(this
.heartRateData[i][2]) << 16) + (Number(
this.heartRateData[i][3]) << 24));
ctx.lineTo(i * 5, -y / 260 + 250); // 将数据绘制 n 到Canvas上,放大50倍以适应Canvas高度
}
ctx.stroke();
ctx.draw();
},
/**
*断开蓝牙模块*/
closeBluetoothAdapter(OBJECT) {
uni.closeBluetoothAdapter({
success: res => {
console.log('断开蓝牙模块成功');
this.isStop = true;
this.$set(this.disabled, 0, false);
this.$set(this.disabled, 1, true);
this.$set(this.disabled, 2, true);
this.$set(this.disabled, 3, true);
this.$set(this.disabled, 4, true);
this.$set(this.disabled, 5, true);
this.$set(this.disabled, 6, true);
this.$set(this.disabled, 7, true);
this.$set(this.disabled, 8, true);
this.$set(this.disabled, 9, true);
this.$set(this.disabled, 10, true);
this.equipment = [];
this.servicesData = [];
this.characteristicsData = [];
this.valueChangeData = {};
this.adapterState = [];
this.searchLoad = false;
toast('断开蓝牙模块');
}
});
}
}
};
/**
* 判断初始化蓝牙状态*/
function initTypes(code, errMsg) {
switch (code) {
case 10000:
toast('未初始化蓝牙适配器');
break;
case 10001:
toast('未检测到蓝牙,请打开蓝牙重试!');
break;
case 10002:
toast('没有找到指定设备');
break;
case 10003:
toast('连接失败');
break;
case 10004:
toast('没有找到指定服务');
break;
case 10005:
toast('没有找到指定特征值');
break;
case 10006:
toast('当前连接已断开');
break;
case 10007:
toast('当前特征值不支持此操作');
break;
case 10008:
toast('其余所有系统上报的异常');
break;
case 10009:
toast('Android 系统特有,系统版本低于 4.3 不支持 BLE');
break;
default:
toast(errMsg);
}
}
/**
* 弹出框封装 */
function toast(content, showCancel = false) {
uni.showModal({
title: '提示',
content,
showCancel
});
}
</script>
<style>
.uni-title {
/* width: 100%; */
/* height: 80rpx; */
text-align: center;
}
.displayarea {}
.uni-mask {
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex;
align-items: center;
width: 100%;
background: rgba(0, 0, 0, 0.6);
padding: 0 30rpx;
box-sizing: border-box;
}
.uni-scroll_box {
height: 70%;
background: #fff;
border-radius: 20rpx;
}
.uni-list-box {
margin: 0 20rpx;
padding: 15rpx 0;
border-bottom: 1px #f5f5f5 solid;
box-sizing: border-box;
}
.uni-list:last-child {
border: none;
}
.uni-list_name {
font-size: 30rpx;
color: #333;
white-space: pre-wrap;
}
.uni-list_item {
font-size: 24rpx;
color: #555;
line-height: 1.5;
}
.uni-success_box {
position: absolute;
left: 0;
bottom: 0;
min-height: 100rpx;
width: 100%;
background: #fff;
box-sizing: border-box;
border-top: 1px #eee solid;
}
.uni-success_sub {
/* width: 100%%; */
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
}
.uni-close_button {
padding: 0 20rpx;
height: 60rpx;
line-height: 60rpx;
background: #ce3c39;
color: #ffffff;
border-radius: 10rpx;
}
.uni-success_content {
height: 600rpx;
margin: 30rpx;
margin-top: 0;
border: 1px #eee solid;
padding: 30rpx;
}
.uni-content_list {
padding-bottom: 10rpx;
border-bottom: 1px #f5f5f5 solid;
}
.uni-tips {
text-align: center;
font-size: 24rpx;
color: #666;
}
.heartrate {
width: 200rpx;
height: 150rpx;
line-height: 150rpx;
background-color: #ce3c39;
border: 0;
color: #fff;
}
.box1 {
position: absolute;
left: 0px;
top: 50px;
width: 750px;
height: 750px;
}
.box2 {
position: absolute;
left: 0px;
top: 50px;
width: 750px;
height: 750
}
.uni-btn-v{
}
</style>
首先我们需要把两个分开分别ecg的背景图和心电图加蓝牙,分别写在两个盒子里写。
这里先看看代码,后续我会继续发代码解读。