<template>
|
<view>
|
<uv-navbar title="添加设备" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="#3B74EE"
|
titleStyle="color:#fff"></uv-navbar>
|
|
<view class="page_bg">
|
<view class="searching_ico" @click="getBluetoothDevices">
|
<image src="/static/img/ico_1.png" mode="widthFix"></image>
|
</view>
|
|
<view class="searching_text">
|
<view class="name">
|
搜索中
|
</view>
|
<view class="text">
|
持续自动搜索中...
|
</view>
|
</view>
|
</view>
|
|
<view class="bluetooth" v-if="bluetoothList.length != 0">
|
<view class="bluetooth_title">
|
<image class="ico" src="/static/img/ico_2.png" mode="widthFix"></image>
|
<text>已找到设备</text>
|
</view>
|
<view class="bluetooth_list">
|
<view class="item" v-for="(item,index) in bluetoothList" :key="index">
|
<image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image>
|
<view class="info">
|
<view class="name">
|
{{item.device.name}}
|
</view>
|
<view class="btn">
|
<uv-button type="primary" shape="circle" text="连接" size="small"
|
@click="createBLEConnection(item.device.address)"></uv-button>
|
|
</view>
|
|
</view>
|
</view>
|
</view>
|
</view>
|
|
</view>
|
</template>
|
|
<script>
|
import {
|
BleLib
|
} from '@/uni_modules/android-ble'
|
var lib = new BleLib()
|
|
export default {
|
data() {
|
return {
|
searchIng: true,
|
bluetoothList: [],
|
deviceId: '', //设备id
|
serviceId: '', //服务uuid
|
characteristicId: '', //特征uuid
|
characteristicId1: '',
|
characteristicId2: '',
|
characteristicId3: '',
|
bluetooth: false
|
}
|
},
|
onLoad() {
|
|
},
|
onShow() {
|
this.bluetooth = lib.isEnabled();
|
if (this.bluetooth) {
|
console.log('蓝牙开启', this.bluetooth)
|
this.getBluetoothDevices()
|
} else {
|
console.log('蓝牙关闭', this.bluetooth)
|
}
|
},
|
onUnload() {
|
//lib.close();
|
},
|
methods: {
|
// 蓝牙扫描
|
getBluetoothDevices() {
|
console.log('蓝牙扫描----')
|
const that = this
|
var isconnect = lib.isConnected();
|
console.log('蓝牙是否连接----',isconnect)
|
if(isconnect){
|
console.log('蓝牙是否连接----',isconnect)
|
var device = lib.getConnectMac();
|
console.log('device----',device)
|
}
|
lib.startScanBleDevice(15000, function(res) {
|
if (res.data.device.name === 'CH9140BLE2U') {
|
that.bluetoothList.push(res.data);
|
console.log(res.data)
|
}
|
})
|
},
|
// 连接蓝牙
|
createBLEConnection(address) {
|
console.log('address-----',address)
|
const that = this
|
const isconnect = lib.isConnected();
|
console.log('蓝牙是否连接----',isconnect)
|
// if(isconnect){
|
// const device = lib.getConnectMac();
|
// console.log('获取已连接设备id----',device)
|
// that.commandCHEC()
|
// }
|
// else{
|
lib.connect(address, false, function(res) {
|
console.log('连接', res)
|
// type==0 表示连接成功
|
if (res.type == 0) {
|
// 扫描蓝牙与特征值
|
lib.scanServices(function(resSevice) {
|
console.log('特征值--------', resSevice)
|
that.characteristicId1 = resSevice.data[2].characteristics[0].uuid;
|
that.characteristicId2 = resSevice.data[2].characteristics[1].uuid;
|
that.characteristicId3 = resSevice.data[2].characteristics[2].uuid;
|
that.onNotityReadBleData()
|
setTimeout(() => {
|
console.log("延迟执行这段代码");
|
that.setBaud()
|
//that.commandCHEC()
|
}, 2000);
|
});
|
// lib.setMtu(512, function(resMtu) {
|
// console.log('设置mtu(仅安卓)', resMtu)
|
// })
|
|
|
} else {
|
if(res.message == 'bluetooth is connected'){
|
//that.commandCHEC()
|
}
|
else{
|
uni.showToast({
|
title: '蓝牙连接失败',
|
duration: 2000,
|
icon: 'none'
|
});
|
}
|
}
|
})
|
// }
|
},
|
// 开启服务消息通知
|
onNotityReadBleData() {
|
const that = this
|
console.log(that.characteristicId1)
|
lib.onNotityReadBleData(lib.getSericUUID(),
|
that.characteristicId1,
|
true,
|
function(res) {
|
console.log('开启服务消息通知', res.data)
|
|
uni.$uv.debounce(that.extractEncryptedIDWithCheck(res.data), 2000)
|
}
|
)
|
},
|
// 构建指令数据
|
commandCHEC() {
|
//555555555555555555557e41434845430095977e
|
// 构建指令数据
|
const startBytes = new Array(10).fill(0x55); // 10个启动字节
|
const frameHeader = 0x7E; // 帧头
|
const packageType = 0x41; // 包类型
|
const command = [0x43, 0x48, 0x45, 0x43]; // "CHEC"的ASCII码
|
const reserved = 0x00; // 保留字节
|
|
// 组合数据
|
const data = [
|
...startBytes,
|
frameHeader,
|
packageType,
|
...command,
|
reserved
|
];
|
|
// 计算CRC校验码(从包头段开始计算)
|
const crc = this.calcCRC16(data.slice(11));
|
|
// 添加CRC和帧尾
|
data.push((crc >> 8) & 0xFF); // CRC高8位
|
data.push(crc & 0xFF); // CRC低8位
|
data.push(frameHeader); // 帧尾
|
|
setTimeout(() => {
|
this.writeDataToBle(data)
|
}, 2000);
|
},
|
// 设置波特率
|
setBaud() {
|
var that = this
|
var d = "06000900009600000801009F"
|
lib.writeStringDataToBle(
|
lib.getSericUUID(),
|
that.characteristicId2,
|
d,
|
function(res) {
|
console.log('设置波特率', res)
|
that.commandCHEC()
|
}
|
)
|
},
|
|
// 发送写入指令
|
writeDataToBle(b) {
|
var that = this
|
lib.writeDataToBle(
|
lib.getSericUUID(),
|
that.characteristicId2,
|
b,
|
function(res) {
|
console.log('发送16进制数组', res)
|
|
}
|
)
|
},
|
writeStringDataToBle() {
|
var that = this
|
var d = "555555555555555555557e41434845430095977e"
|
lib.writeStringDataToBle(
|
lib.getSericUUID(),
|
that.characteristicId2,
|
d,
|
function(res) {
|
console.log('发送16进制字符数据', res)
|
}
|
)
|
},
|
// 16进制数据转二位数据
|
hexStringToArrayBuffer(hexStr) {
|
const bytes = new Uint8Array(hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16)));
|
return bytes.buffer;
|
},
|
// 二位数据转16进制数据
|
ab2hex(buffer) {
|
const hexArr = Array.prototype.map.call(
|
new Uint8Array(buffer),
|
function(bit) {
|
return ('00' + bit.toString(16)).slice(-2)
|
}
|
)
|
return hexArr.join('')
|
},
|
calcCRC16(data) {
|
// 创建查表
|
const table = this.buildCRC16Table();
|
let crc = 0;
|
|
// 计算CRC
|
for (let i = 0; i < data.length; i++) {
|
crc = ((crc << 8) & 0xFF00) ^ table[((crc >> 8) ^ data[i]) & 0xFF];
|
}
|
|
return crc & 0xFFFF;
|
},
|
buildCRC16Table() {
|
const table = new Array(256);
|
const poly = 0x1021; // CRC-CCITT多项式
|
|
for (let i = 0; i < 256; i++) {
|
let nData = i << 8;
|
let nAccum = 0;
|
|
for (let j = 0; j < 8; j++) {
|
if ((nData ^ nAccum) & 0x8000) {
|
nAccum = (nAccum << 1) ^ poly;
|
} else {
|
nAccum <<= 1;
|
}
|
nData <<= 1;
|
}
|
table[i] = nAccum & 0xFFFF;
|
}
|
return table;
|
},
|
// 判断返回数据是否正确 并提取加密id
|
extractEncryptedIDWithCheck(buffer) {
|
const hexArr = Array.prototype.map.call(
|
new Uint8Array(buffer),
|
function(bit) {
|
return ('00' + bit.toString(16)).slice(-2)
|
}
|
)
|
const responseHex = hexArr.join('').slice(20)
|
console.log('responseHex---',responseHex)
|
// 1. 提取返回标志位置(第11字节,即下标:30~31)
|
const returnFlagHex = responseHex.substring(12, 14).toUpperCase();
|
console.log('returnFlagHex---',returnFlagHex)
|
if (returnFlagHex === "30") {
|
// 正常,提取加密ID(从下标 32 开始,取 40 位
|
const encryptedID = responseHex.substring(14, 55).toUpperCase();
|
console.log('encryptedID---',encryptedID)
|
uni.showToast({
|
title: encryptedID,
|
duration: 2000,
|
icon: 'none'
|
});
|
} else if (returnFlagHex === "31") {
|
// 异常,无加密ID
|
uni.showToast({
|
title: '智能壳工作异常,返回标志0x31',
|
duration: 2000,
|
icon: 'none'
|
});
|
lib.close();
|
} else {
|
// 未知标志
|
uni.showToast({
|
title: '智能壳工作异常,未知返回标志',
|
duration: 2000,
|
icon: 'none'
|
});
|
lib.close();
|
}
|
},
|
// 解密函数
|
decryptSmartShellID(encryptedHex) {
|
if (encryptedHex.length !== 40) {
|
console.error('加密ID长度必须为40个十六进制字符(20字节)');
|
return null;
|
}
|
|
const key = [
|
0x14, 0x15, 0x0A, 0x0C, 0x24, 0x01, 0x0B, 0x13, 0x44, 0x02,
|
0x0F, 0x00, 0x0E, 0x0D, 0x47, 0x2A, 0x5E, 0x32, 0x32, 0x11
|
];
|
|
const decrypted = [];
|
|
for (let i = 0; i < 20; i++) {
|
const byteHex = encryptedHex.substr(i * 2, 2);
|
const encryptedByte = parseInt(byteHex, 16);
|
const decryptedByte = (encryptedByte - key[i] + 256) % 256;
|
decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase());
|
}
|
|
return decrypted.join(' ');
|
},
|
// 加密函数
|
encryptSmartShellID(originalHex) {
|
if (originalHex.length !== 40) {
|
console.error('原始ID长度必须为40位十六进制字符串(20字节)');
|
return null;
|
}
|
|
// 加密密钥(20 字节)
|
const key = [
|
0x14, 0x15, 0x0A, 0x0C, 0x24, 0x01, 0x0B, 0x13, 0x44, 0x02,
|
0x0F, 0x00, 0x0E, 0x0D, 0x47, 0x2A, 0x5E, 0x32, 0x32, 0x11
|
];
|
|
const encrypted = [];
|
|
for (let i = 0; i < 20; i++) {
|
const byteHex = originalHex.substr(i * 2, 2);
|
const originalByte = parseInt(byteHex, 16);
|
const encryptedByte = (originalByte + key[i]) % 256; // 忽略进位
|
encrypted.push(encryptedByte.toString(16).padStart(2, '0').toUpperCase());
|
}
|
|
return encrypted.join('');
|
}
|
|
|
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.bluetooth {
|
padding: 0 30rpx;
|
|
.bluetooth_list {
|
.item {
|
background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%);
|
box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4);
|
border-radius: 20rpx;
|
border: 2rpx solid #FFFFFF;
|
padding: 22rpx;
|
display: flex;
|
align-items: stretch;
|
justify-content: space-between;
|
margin-top: 24rpx;
|
|
.ph {
|
display: block;
|
flex-shrink: 0;
|
width: 230rpx;
|
height: 170rpx;
|
border-radius: 20rpx;
|
margin-right: 24rpx;
|
}
|
|
.info {
|
width: 100%;
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
|
.name {
|
font-weight: 500;
|
font-size: 27rpx;
|
color: #000000;
|
line-height: 50rpx;
|
}
|
|
.btn {
|
width: 160rpx;
|
margin-top: 10rpx;
|
}
|
}
|
}
|
}
|
|
.bluetooth_title {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-top: 20rpx;
|
|
text {
|
display: block;
|
width: 100%;
|
font-weight: 500;
|
font-size: 34rpx;
|
color: rgba(0, 0, 0, 0.7);
|
line-height: 38rpx;
|
}
|
|
.ico {
|
flex-shrink: 0;
|
width: 40rpx;
|
margin-right: 10rpx;
|
}
|
}
|
}
|
|
.searching_text {
|
text-align: center;
|
background: url('/static/img/bg_3.png') no-repeat center top;
|
background-size: 100% auto;
|
box-sizing: border-box;
|
padding-top: 140rpx;
|
margin-top: -100rpx;
|
height: 300rpx;
|
|
.name {
|
font-weight: 500;
|
font-size: 36rpx;
|
color: #000000;
|
line-height: 34rpx;
|
}
|
|
.text {
|
font-size: 26rpx;
|
color: rgba(0, 0, 0, 0.5);
|
line-height: 34rpx;
|
margin-top: 30rpx;
|
}
|
}
|
|
.searching_ico {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding-top: 100rpx;
|
}
|
|
.page_bg {
|
width: 100%;
|
background: linear-gradient(3deg, #72E5FF 0%, #3B74EE 98%, #3B74EE 100%);
|
}
|
|
.page_bg1 {
|
width: 100%;
|
min-height: calc(100vh - var(--window-bottom));
|
background: url('/static/img/bg_2.jpg') no-repeat center top;
|
background-size: 100% auto;
|
}
|
</style>
|