Commit 83d9efe6 by lirandong

添加 蓝牙连接设备调试WiFi功能

parent a336a7f7
......@@ -35,6 +35,7 @@ class App extends Component {
config: Config = {
pages: [
'pages/home/device/index',
'pages/system/wifi_list/index',
'pages/home/tempaltes/index',
'pages/home/device/device_bind/index',
'pages/home/user/index',
......
......@@ -6,6 +6,7 @@ $text-color: #323233;
$input-color: #323233;
$primary-color: #1989fa;
$border-color: #ccc;
$text-colot: #ccc;
@mixin border-bottom {
border-style: solid;
......
import Taro, { arrayBuffer } from '@tarojs/taro'
import { bluetooth } from './adapter'
import { BLE_SERVICE_ID, WIFI_CHARACTERISTIC_ID, POST_WIFI_PASS_ID } from '.'
import { IWifiListItem } from '@/pages/home/device/device_list'
import { showMyToast, getAdverts, getBLEData, strToAb, ArrayBufferToString } from './utils'
class Bluetooth {
// protected deviceId = ''
protected advertisData = ''
/* 获取 WiFi 列表弧度回调 */
protected getWifiListCallBack: (wifiItem: IWifiListItem) => void
/** 打开蓝牙 */
async openBle(bluetoothCode: string): Promise<any> {
try {
// 关闭蓝牙设备
await this.closeBluetoothAdapter()
Taro.showLoading({ title: '打开蓝牙...' })
// 打开蓝牙设备
await bluetooth.openBluetoothAdapter()
return await new Promise((resolve, reject) => {
try {
bluetooth.onBluetoothDeviceFound(async ({ devices }) => {
Taro.showLoading({ title: '连接蓝牙...' })
for (const item of devices) {
if (getAdverts(item) === bluetoothCode) {
const { deviceId } = item
await this.createBLEConnection(deviceId)
resolve({ deviceId })
// break
}
}
// showMyToast({ title: '没找到对应蓝牙~' })
// Taro.hideLoading()
// reject('没找到对应蓝牙~')
})
} catch (error) {
Taro.hideLoading()
bluetooth.stopBluetoothDevicesDiscovery()
reject('没找到对应蓝牙~')
}
Taro.showLoading({ title: '搜索蓝牙...' })
bluetooth.startBluetoothDevicesDiscovery({ services: ['8888'] })
})
} catch (error) {
console.error({ error })
bluetooth.stopBluetoothDevicesDiscovery()
const { errCode } = error
if (errCode === 10001) {
showMyToast({ title: '请打开手机蓝牙~' })
}
return Promise.reject()
}
}
/** 连接蓝牙 */
async createBLEConnection(deviceId: string) {
await bluetooth.createBLEConnection({ deviceId })
bluetooth.stopBluetoothDevicesDiscovery()
showMyToast({ title: '蓝牙连接成功~' })
}
/** 打开蓝牙的监听功能 */
async openListenBle(deviceId: string) {
await bluetooth.notifyBLECharacteristicValueChange({
deviceId,
state: true,
serviceId: BLE_SERVICE_ID,
characteristicId: WIFI_CHARACTERISTIC_ID
})
this.listenBleValueChange(deviceId)
}
/** 设置监听获取WiFi列表的回调函数 */
async setGetWiFiListCallBack(callBack: (wifiItem: IWifiListItem) => void) {
this.getWifiListCallBack = callBack
}
/** 蓝牙的监听队列 */
listenBleValueChange(deviceId: string) {
// 来自设备端的通知
bluetooth.onBLECharacteristicValueChange(async ({ value, characteristicId }) => {
switch (characteristicId) {
case WIFI_CHARACTERISTIC_ID:
// 获取 WiFi 列表
this.formatWifiList(deviceId, value)
break
case WIFI_CHARACTERISTIC_ID:
// WiFi 连接状态回调
this.formatWifiLine(value)
default:
break
}
})
}
/** 整理WiFi列表的信息包 */
formatWifiList(deviceId: string, value: ArrayBuffer) {
let page = 0
const state = new Uint8Array(value)[0] // 获取蓝牙状态
// 判断第8位数是否是 1
if ((state & 0x80) !== 0) {
// 第8位数为 1, 设备端发送资源包
// 获取当前页
page = state & 0x3f
// 判断第8位数是否是 1
if ((state & 0x40) !== 0) {
// 新包
this.advertisData = getBLEData(value)
} else {
// 累加包
this.advertisData = getBLEData(value) + this.advertisData
}
if (page === 0) {
// 数据传输完毕
this.getWifiListCallBack && this.getWifiListCallBack(JSON.parse(this.advertisData))
}
bluetooth.writeBLECharacteristicValue({
deviceId,
serviceId: BLE_SERVICE_ID,
value: new Uint8Array([page]).buffer,
characteristicId: WIFI_CHARACTERISTIC_ID
})
} else {
// 第8位数为 0, 普通响应
console.log('第8位数为 0, 普通响应')
}
}
/** 整理WiFi连接信息 */
formatWifiLine(value: arrayBuffer) {
console.log('ArrayBufferToString', ArrayBufferToString(value))
}
/** 向蓝牙发送WiFi密码 */
async postWifiPassword(id: number, pass: string, deviceId: string) {
await bluetooth.notifyBLECharacteristicValueChange({
deviceId,
state: true,
serviceId: BLE_SERVICE_ID,
characteristicId: POST_WIFI_PASS_ID
})
return bluetooth.writeBLECharacteristicValue({
deviceId,
serviceId: BLE_SERVICE_ID,
value: strToAb(id + pass),
characteristicId: POST_WIFI_PASS_ID
})
}
/** 关闭蓝牙 */
closeBluetoothAdapter() {
return bluetooth.closeBluetoothAdapter()
}
}
export default new Bluetooth()
......@@ -7,3 +7,6 @@ export const BLE_SERVICE_ID = '000088A0-0000-1000-8000-00805F9B34FB'
/** 获取WiFi列表的特征值 uuid */
export const WIFI_CHARACTERISTIC_ID = '000088A1-0000-1000-8000-00805F9B34FB'
/** 提交WiFi密码特征值 uuid */
export const POST_WIFI_PASS_ID = '000088A2-0000-1000-8000-00805F9B34FB'
......@@ -62,6 +62,23 @@ export function getBLEState(buffer: ArrayBuffer) {
}
/** 解析蓝牙接收到的数据 */
export function getBLEData(buffer: ArrayBuffer): string {
export function getBLEData(buffer: ArrayBuffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer.slice(1, buffer.byteLength)))
}
/** 获取蓝牙的 */
export function getAdverts(BleInfo: Taro.onBluetoothDeviceFound.ParamParamPropDevicesItem): string {
const newAdvertisData = BleInfo.advertisData.slice(2, BleInfo.advertisData.byteLength)
return String.fromCharCode.apply(null, new Uint8Array(newAdvertisData))
}
/** 字符串转 ArrayBuffer */
export function strToAb(str: string) {
const codeArr: number[] = []
console.log({ str })
for (let i = 0; i < str.length; i++) {
codeArr.push(str.charCodeAt(i))
}
console.log({ codeArr })
return new Uint8Array(codeArr).buffer
}
......@@ -12,14 +12,14 @@
background-color: rgba($color: #000, $alpha: 0.7);
&-wrapper {
width: 580px;
width: 700px;
display: flex;
margin-top: -50px;
// margin-top: -50px;
padding-top: 36px;
border-radius: 8px;
flex-direction: column;
background-color: white;
// height: auto;
@include rn(height, 600px);
}
......
......@@ -10,9 +10,10 @@ type PageDispatchProps = {}
type PageOwnProps = {
/** 标题 */
title: string
title?: string
/** 内容 */
children: any
marginTop?: string
/** 取消文字 */
cancelText?: string | '取消'
confirmText?: string | '确定'
......@@ -36,18 +37,25 @@ class Modal extends Component {
}
render() {
const { title, children, confirmText, cancelText, onCancel, onConfirm } = this.props
console.log({ onConfirm })
const {
title,
children,
onCancel,
onConfirm,
cancelText,
confirmText,
marginTop = '-50px'
} = this.props
return (
<View className="modal" onClick={this.cancel}>
<View className="modal-wrapper">
<Text className="modal-title">{title || '标题'}</Text>
<View className="modal-wrapper" style={{ marginTop }}>
{title && <Text className="modal-title">{title || '标题'}</Text>}
<View className="modal-content-wrapper">{children}</View>
<View className="modal-bottom-btn">
<Text className="modal-cancel-text" onClick={onCancel}>
<Text className="modal-cancel-text" onClick={() => onCancel && onCancel()}>
{cancelText || '取消'}
</Text>
<Text className="modal-confirm-text" onClick={onConfirm}>
<Text className="modal-confirm-text" onClick={() => onConfirm && onConfirm()}>
{confirmText || '确定'}
</Text>
</View>
......
......@@ -10,6 +10,7 @@ import DeviceItem from '@/conpoments/device_item'
import { getFilmList } from '@/actions/asyncCounter'
import { WIFI_CHARACTERISTIC_ID, BLE_SERVICE_ID } from '@/common'
import { showMyToast, getBLEState, getBLEData } from '@/common/utils'
import Ble from '@/common/bluetooth'
import './index.scss'
export interface IDeviceItem {
......@@ -28,6 +29,13 @@ export interface IBluetoothServerListItem {
characteristics: Taro.getBLEDeviceCharacteristics.PromisedPropCharacteristics
}
export interface IWifiListItem {
id: number
rssi: number
ssid: string
type: string
}
type PageStateProps = {
list: any[]
count: number
......@@ -42,7 +50,6 @@ type PageOwnProps = {
}
type PageState = {
count: number
deviceCode: string
showModal: boolean
deviceList: IDeviceItem[]
......@@ -68,12 +75,9 @@ interface MyDevice {
)
class MyDevice extends Component {
protected page = 1
protected deviceId = ''
protected bluetoothServerList: IBluetoothServerListItem[] = []
constructor(props) {
constructor(props: any) {
super(props)
this.state = {
count: 0,
deviceCode: '',
deviceList: [],
showModal: false
......@@ -133,7 +137,8 @@ class MyDevice extends Component {
onlyFromCamera: true,
scanType: ['qrCode', 'barCode']
})
await this.bluetooth(result)
const { deviceId } = await Ble.openBle(result)
Taro.navigateTo({ url: `/pages/system/wifi_list/index?deviceId=${deviceId}` })
} catch (error) {
showMyToast({ result: error, title: '添加失败~' })
console.error(error)
......@@ -141,123 +146,6 @@ class MyDevice extends Component {
}
}
async bluetooth(bluetoothCode: string) {
try {
await bluetooth.closeBluetoothAdapter()
Taro.showLoading({ title: '打开蓝牙...' })
await bluetooth.openBluetoothAdapter()
bluetooth.onBluetoothDeviceFound(async ({ devices }) => {
Taro.showLoading({ title: '连接蓝牙...' })
for (const item of devices) {
const newAdvertisData = item.advertisData.slice(2, item.advertisData.byteLength)
const advertisData = String.fromCharCode.apply(null, new Uint8Array(newAdvertisData))
if (advertisData === bluetoothCode) {
this.createBLEConnection(item)
return
}
}
})
Taro.showLoading({ title: '搜索蓝牙...' })
await bluetooth.startBluetoothDevicesDiscovery({ services: ['8888'] })
} catch (error) {
console.error({ error })
bluetooth.stopBluetoothDevicesDiscovery()
const { errCode } = error
if (errCode === 10001) {
showMyToast({ title: '请打开手机蓝牙~' })
}
}
}
/** 连接蓝牙 */
async createBLEConnection(item) {
const { deviceId } = item
await bluetooth.createBLEConnection({ deviceId })
bluetooth.stopBluetoothDevicesDiscovery()
showMyToast({ title: '蓝牙连接成功~' })
this.deviceId = deviceId
this.getWiFiList()
}
/** 获取蓝牙服务列表 */
async getBLEDeviceServices() {
Taro.showLoading({ title: '获取蓝牙参数...' })
const { deviceId } = this
const { services } = await bluetooth.getBLEDeviceServices({ deviceId }) // 获取服务列表
Taro.showLoading({ title: '获取特征值...' })
const serveList: Array<Promise<Taro.getBLEDeviceCharacteristics.Promised>> = []
for (const iterator of services) {
const serve = bluetooth.getBLEDeviceCharacteristics({
deviceId,
serviceId: iterator.uuid
}) // 获取特征服务
serveList.push(serve)
}
const serveListRes = await Promise.all(serveList)
serveListRes.forEach(({ characteristics }, index) => {
this.bluetoothServerList.push({
characteristics,
serviceId: services[index].uuid
})
})
Taro.hideLoading()
}
async getWiFiList() {
try {
const { deviceId } = this
// 开启通讯
await bluetooth.notifyBLECharacteristicValueChange({
deviceId,
state: true,
serviceId: BLE_SERVICE_ID,
characteristicId: WIFI_CHARACTERISTIC_ID
})
// 来自设备端的通知
bluetooth.onBLECharacteristicValueChange(async ({ value }) => {
const state = getBLEState(value) // 获取蓝牙状态
let advertisData = ''
let page = 0
// 判断第8位数是否是 1
if ((state & 0x80) !== 0) {
// 第8位数为 1, 设备端发送资源包
// 获取当前页
page = state & 0x3f
// 判断第8位数是否是 1
if ((state & 0x40) !== 0) {
// 新包
advertisData = getBLEData(value)
} else {
// 累加包
advertisData += getBLEData(value)
}
await bluetooth.writeBLECharacteristicValue({
deviceId,
serviceId: BLE_SERVICE_ID,
value: new Uint8Array([page]).buffer,
characteristicId: WIFI_CHARACTERISTIC_ID
})
// console.log({ page })
// console.log(page === 0)
// console.log({ advertisData })
if (page === 0) {
// 数据传输完毕
console.log(JSON.parse(advertisData))
}
} else {
// 第8位数为 0, 普通响应
console.log('第8位数为 0, 普通响应')
}
})
} catch (error) {
console.error(error)
}
}
async pullingUp(done: any) {
this.page++
await this.getDate()
......
@import '@styles/var.scss';
.wifi {
width: 100%;
height: 100%;
&-scroll {
height: 100%;
}
&-content {
padding: 0 40px;
@include eject(box-sizing, border-box);
}
&-title {
width: 100%;
padding: 20px 0;
font-size: 24px;
color: $text-colot;
border: 0 solid $border-color;
border-bottom-width: 1px;
@include eject(display, block);
@include eject(box-sizing, border-box);
}
&-item {
display: flex;
flex-direction: row;
padding: 40px 0 40px 10px;
border: 0 solid $border-color;
border-bottom-width: 1px;
/* postcss-pxtransform rn eject enable */
&:last-of-type {
border: none;
}
/* postcss-pxtransform rn eject disable */
&-name {
flex: 1;
}
}
&-modal {
padding: 0 40px 0 40px;
@include eject(box-sizing, border-box);
&-title {
display: flex;
align-items: center;
margin-bottom: 60px;
justify-content: center;
}
&-tips {
padding-left: 20px;
@include eject(box-sizing, border-box);
}
&-password {
margin-top: 20px;
padding: 14px 20px;
margin-bottom: 60px;
border-radius: 12px;
border: 1px solid $border-color;
@include eject(box-sizing, border-box);
}
}
}
import Ble from '@/common/bluetooth'
import Modal from '@/conpoments/modal'
import { ComponentClass } from 'react'
import Taro, { Component, Config } from '@tarojs/taro'
import { View, Text, ScrollView, Input } from '@tarojs/components'
import './index.scss'
export interface IWifiListItem {
id: number
rssi: number
ssid: string
type: 'OPEN' | 'WPA'
}
type PageStateProps = {}
type PageDispatchProps = {}
type PageOwnProps = {}
type PageState = {
openModal: boolean
wifiList: IWifiListItem[]
}
type IProps = PageStateProps & PageDispatchProps & PageOwnProps
interface WifiList {
props: IProps
state: PageState
}
class WifiList extends Component {
config: Config = {
navigationBarTitleText: 'WLAN'
}
protected deviceId = ''
protected password = ''
protected advertisData = ''
protected bluetoothCode = ''
protected activeWLAN: IWifiListItem
constructor(props: any) {
super(props)
this.state = {
wifiList: [
// {
// id: 1111,
// rssi: 11122,
// ssid: 'Text11111',
// type: 'WPA'
// }
],
openModal: false
}
this.deviceId = this.$router.params.deviceId
this.openModal = this.openModal.bind(this)
this.onConfirm = this.onConfirm.bind(this)
this.cancelModal = this.cancelModal.bind(this)
this.setPasswprd = this.setPasswprd.bind(this)
}
componentDidMount() {
this.getWifiList()
}
async getWifiList() {
try {
/** 设置监听获取WiFi列表的回调函数 */
Ble.setGetWiFiListCallBack((wifiItem: IWifiListItem) => {
this.setState({
wifiList: [...this.state.wifiList, wifiItem]
})
})
/** 打开蓝牙的监听 */
Ble.openListenBle(this.deviceId)
} catch (error) {
console.error(error)
}
}
openModal(item: IWifiListItem) {
this.password = ''
this.activeWLAN = item
if (item.type === 'OPEN') {
this.onConfirm()
} else {
this.setState({ openModal: true })
}
}
async onConfirm() {
const { id } = this.activeWLAN
const { password, deviceId } = this
// console.log(id, deviceId)
// console.log(!id || !deviceId)
if (id === undefined || !deviceId) return
this.cancelModal()
console.log({ password, deviceId })
try {
Taro.showLoading({ title: '连接中...' })
await Ble.postWifiPassword(id, password, deviceId)
} catch (error) {
console.error(error)
}
Taro.hideLoading()
// console.log('onConfirm', this.password)
}
cancelModal() {
this.setState({ openModal: false })
}
setPasswprd(val: any) {
this.password = val.target.value
}
componentWillUnmount() {
Ble.closeBluetoothAdapter()
}
render() {
const { wifiList, openModal } = this.state
return (
<View className="wifi">
<ScrollView className="wifi-scroll" scrollY>
<View className="wifi-content">
<Text className="wifi-title">选取附近的WLAN</Text>
{wifiList.map(item => (
<View className="wifi-item" key={item.id} onClick={() => this.openModal(item)}>
<Text className="wifi-item-name">{item.ssid}</Text>
{item.type === 'WPA' && <Text></Text>}
</View>
))}
</View>
</ScrollView>
{openModal && (
<Modal
confirmText="连接"
marginTop="-200px"
onConfirm={this.onConfirm}
onCancel={this.cancelModal}
>
<View className="wifi-modal">
<View className="wifi-modal-title">
<Text>{this.activeWLAN.ssid}</Text>
</View>
<View className="wifi-modal-tips">
<Text>密码</Text>
</View>
<View className="wifi-modal-password">
<Input
maxLength={14}
placeholder="请输入密码"
onInput={this.setPasswprd}
onConfirm={this.onConfirm}
/>
</View>
</View>
</Modal>
)}
</View>
)
}
}
export default WifiList as ComponentClass<PageOwnProps, PageState>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment