//app.js | |||||
App({ | |||||
onLaunch: function () { | |||||
} | |||||
}) |
{ | |||||
"pages": [ | |||||
"pages/index/index", | |||||
"pages/wifiList/wifiList" | |||||
], | |||||
"window": { | |||||
"navigationBarBackgroundColor": "#0082FE", | |||||
"navigationBarTextStyle": "white", | |||||
"navigationBarTitleText": "蓝牙连接Demo", | |||||
"backgroundColor": "#eeeeee", | |||||
"backgroundTextStyle": "light" | |||||
}, | |||||
"plugins": { | |||||
"sdkPlugin": { | |||||
"version": "1.1.2", | |||||
"provider": "wx17e93aad47cdae1a" | |||||
} | |||||
}, | |||||
"style": "v2", | |||||
"sitemapLocation": "sitemap.json" | |||||
} |
/**app.wxss**/ | |||||
view, | |||||
cover-view, | |||||
scroll-view, | |||||
swiper, | |||||
swiper-item, | |||||
movable-area, | |||||
movable-view, | |||||
button, | |||||
input, | |||||
textarea, | |||||
label, | |||||
navigator | |||||
{ | |||||
box-sizing: border-box; | |||||
} | |||||
page{ | |||||
--safe-bottom: env(safe-area-inset-bottom); | |||||
} | |||||
.container{ | |||||
position: relative; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
width: 100vw; | |||||
height: 100vh; | |||||
padding-bottom: var(--safe-bottom); | |||||
} | |||||
.flex{ | |||||
display: flex; | |||||
} | |||||
.flex_column{ | |||||
display: flex; | |||||
flex-direction: column; | |||||
} | |||||
.ai_center{ | |||||
align-items: center; | |||||
} | |||||
.ai_start{ | |||||
align-items: flex-start; | |||||
} | |||||
.ai_end{ | |||||
align-items: flex-end; | |||||
} | |||||
.jc_center{ | |||||
justify-content: center; | |||||
} | |||||
.jc_sa{ | |||||
justify-content: space-around; | |||||
} | |||||
.jc_sb{ | |||||
justify-content: space-between; | |||||
} | |||||
.jc_end{ | |||||
justify-content: flex-end; | |||||
} | |||||
.flex_center{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
} | |||||
.flex_center_sa{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-around; | |||||
} | |||||
.flex_center_sb{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
} |
const util = require("../../utils/util"); | |||||
const {inArray, ab2hex} = util | |||||
const plugin = requirePlugin("sdkPlugin").AiLink; | |||||
Page({ | |||||
data: { | |||||
showWriteInput: false, | |||||
devices: [ | |||||
// { | |||||
// deviceId: "02:03:04:05:06:07", | |||||
// name: "elink", | |||||
// localName: "elink", | |||||
// mac: "02:03:04:05:06:07", | |||||
// RSSI: -69, | |||||
// advertisServiceUUIDs:[ | |||||
// "FFE0","FFC0" | |||||
// ], | |||||
// analyzeDataText:"0102003241123413" | |||||
// } | |||||
], | |||||
connected: false, | |||||
chs: [], | |||||
cmd: '', | |||||
name: '', | |||||
deviceId: null, | |||||
historyList: [], | |||||
}, | |||||
onLoad: function () { | |||||
let historyList = [] | |||||
historyList = wx.getStorageSync('historyList') | |||||
this.setData({ | |||||
historyList, | |||||
}) | |||||
}, | |||||
toConnectWifi() { | |||||
this.offBLECharacteristicValueChange() | |||||
wx.navigateTo({ | |||||
url: '../wifiList/wifiList', | |||||
}) | |||||
}, | |||||
// 初始化蓝牙模块 | |||||
openBluetoothAdapter() { | |||||
wx.openBluetoothAdapter({ | |||||
success: (res) => { | |||||
console.log('openBluetoothAdapter success', res) | |||||
this.startBluetoothDevicesDiscovery() | |||||
}, | |||||
fail: (res) => { | |||||
if (res.errCode === 10001) { | |||||
wx.showToast({ | |||||
title: '请打开蓝牙', | |||||
icon: "none" | |||||
}) | |||||
wx.onBluetoothAdapterStateChange(function (res) { | |||||
console.log('onBluetoothAdapterStateChange', res) | |||||
if (res.available) { | |||||
this.startBluetoothDevicesDiscovery() | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
}) | |||||
}, | |||||
// 获取本机蓝牙适配器状态 | |||||
getBluetoothAdapterState() { | |||||
wx.getBluetoothAdapterState({ | |||||
success: (res) => { | |||||
console.log('getBluetoothAdapterState', res) | |||||
if (res.discovering) { | |||||
this.onBluetoothDeviceFound() | |||||
} else if (res.available) { | |||||
this.startBluetoothDevicesDiscovery() | |||||
} | |||||
} | |||||
}) | |||||
}, | |||||
// 开始搜寻附近的蓝牙外围设备 | |||||
startBluetoothDevicesDiscovery() { | |||||
if (this._discoveryStarted) { | |||||
return | |||||
} | |||||
this._discoveryStarted = true | |||||
wx.startBluetoothDevicesDiscovery({ | |||||
allowDuplicatesKey: true, | |||||
services: [ | |||||
"FFE0", | |||||
// "F0A0", // BM30广播模块需加上,如使用连接模块可忽略 | |||||
], | |||||
success: (res) => { | |||||
console.log('startBluetoothDevicesDiscovery success', res) | |||||
this.onBluetoothDeviceFound() | |||||
}, | |||||
}) | |||||
}, | |||||
// 停止搜寻附近的蓝牙外围设备 | |||||
stopBluetoothDevicesDiscovery() { | |||||
wx.stopBluetoothDevicesDiscovery() | |||||
}, | |||||
// 监听寻找到新设备的事件 | |||||
onBluetoothDeviceFound() { | |||||
wx.onBluetoothDeviceFound((res) => { | |||||
res.devices.forEach(device => { | |||||
if (!device.name && !device.localName) { | |||||
return | |||||
} | |||||
const foundDevices = this.data.devices | |||||
const idx = inArray(foundDevices, 'deviceId', device.deviceId) | |||||
const data = {} | |||||
// console.log(device) | |||||
// console.log(ab2hex(device.advertisData)) | |||||
// 此处判断是否BM30广播模块,如使用连接模块请删除此 if ,只保留 else 内容 | |||||
if (device.advertisServiceUUIDs[0].indexOf("F0A0") !== -1) { | |||||
let parseDataRes = plugin.parseBroadcastData(device.advertisData) | |||||
console.log(parseDataRes) | |||||
if (parseDataRes.status == 1) { | |||||
let analyzeData = plugin.analyzeBroadcastScaleData(parseDataRes) | |||||
console.log(analyzeData) | |||||
device.analyzeDataText = analyzeData.text | |||||
} | |||||
} else { | |||||
let buff = device.advertisData.slice(-6) | |||||
device.mac = new Uint8Array(buff) // 保存广播数据中的mac地址,这是由于iOS不直接返回mac地址 | |||||
let tempMac = Array.from(device.mac) | |||||
tempMac.reverse() | |||||
device.macAddr = ab2hex(tempMac, ':').toUpperCase() | |||||
} | |||||
if (idx === -1) { | |||||
data[`devices[${foundDevices.length}]`] = device | |||||
} else { | |||||
data[`devices[${idx}]`] = device | |||||
} | |||||
this.setData(data) | |||||
}) | |||||
}) | |||||
}, | |||||
// 连接低功耗蓝牙设备 | |||||
createBLEConnection(e) { | |||||
this._connLoading = true | |||||
wx.showLoading({ | |||||
title: '连接中', | |||||
}) | |||||
setTimeout(() => { | |||||
if (this._connLoading) { | |||||
this._connLoading = false | |||||
wx.hideLoading() | |||||
} | |||||
}, 6000) | |||||
const ds = e.currentTarget.dataset | |||||
const index = ds.index | |||||
// 保存当前连接的设备,注意不能从wxml的dataset中直接返回该对象,因为ArrarBuffer类型的数据无法保留 | |||||
this._device = this.data.devices[index] | |||||
console.log(this._device) | |||||
const deviceId = ds.deviceId | |||||
const name = ds.name | |||||
this.mac = ds.mac | |||||
wx.createBLEConnection({ | |||||
deviceId, | |||||
success: (res) => { | |||||
this.setData({ | |||||
connected: true, | |||||
name, | |||||
deviceId, | |||||
}) | |||||
console.log("createBLEConnection:success") | |||||
wx.stopBluetoothDevicesDiscovery() | |||||
this.onBLEConnectionStateChange() | |||||
this.getBLEDeviceServices(deviceId) | |||||
}, | |||||
fail: res => { | |||||
this._connLoading = false | |||||
wx.hideLoading() | |||||
wx.showToast({ | |||||
title: '连接失败', | |||||
icon: 'none' | |||||
}) | |||||
} | |||||
}) | |||||
// 连接上设备就可以停止蓝牙搜索,减少功耗。 | |||||
this.stopBluetoothDevicesDiscovery() | |||||
}, | |||||
onBLEConnectionStateChange() { | |||||
wx.onBLEConnectionStateChange((res) => { | |||||
console.log('wx.onBLEConnectionStateChange() ', res.deviceId, res.connected, res.errorCode, res.errorMsg) | |||||
// 该方法回调中可以用于处理连接意外断开等异常情况 | |||||
// console.log(`%c device ${res.deviceId} state has changed, connected: ${res.connected}`, 'color: #F26363') | |||||
if (!res.connected) { | |||||
setTimeout(()=>{ | |||||
wx.showToast({ | |||||
title: '连接已断开', | |||||
icon: 'none' | |||||
}) | |||||
},500) | |||||
wx.navigateBack() | |||||
this.setData({ | |||||
connected: false, | |||||
showWriteInput: false, | |||||
}) | |||||
} | |||||
}) | |||||
}, | |||||
// 断开与低功耗蓝牙设备的连接 | |||||
closeBLEConnection() { | |||||
wx.closeBLEConnection({ | |||||
deviceId: this._deviceId | |||||
}) | |||||
this.setData({ | |||||
connected: false, | |||||
chs: [], | |||||
showWriteInput: false, | |||||
}) | |||||
}, | |||||
// 获取蓝牙设备的 serviceId | |||||
getBLEDeviceServices(deviceId) { | |||||
wx.getBLEDeviceServices({ | |||||
deviceId, | |||||
success: (res) => { | |||||
for (let i = 0; i < res.services.length; i++) { | |||||
if (res.services[i].isPrimary && res.services[i].uuid.indexOf('FFE0') > -1) { | |||||
this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
}, | |||||
// 获取蓝牙设备某个服务中所有特征值(characteristic) | |||||
getBLEDeviceCharacteristics(deviceId, serviceId) { | |||||
this._deviceId = deviceId | |||||
this._serviceId = serviceId | |||||
this._device.serviceId = serviceId | |||||
wx.getBLEDeviceCharacteristics({ | |||||
deviceId, | |||||
serviceId, | |||||
success: (res) => { | |||||
console.log('getBLEDeviceCharacteristics success', res.characteristics) | |||||
// 这部分功能在插件的 initPlugin 中已实现,如需用到其中的 uuid 也可取消注释 | |||||
// for (let i = 0; i < res.characteristics.length; i++) { | |||||
// let item = res.characteristics[i] | |||||
// if (item.uuid.indexOf('0000FFE1') != -1) { | |||||
// this.uuid1 = item.uuid //下发数据 | |||||
// } else if (item.uuid.indexOf('0000FFE2') != -1) { | |||||
// this.uuid2 = item.uuid //监听数据 | |||||
// } else if (item.uuid.indexOf('0000FFE3') != -1) { | |||||
// this.uuid3 = item.uuid //写入设置 | |||||
// } | |||||
// } | |||||
// // 打开监听 | |||||
// wx.notifyBLECharacteristicValueChange({ | |||||
// deviceId, | |||||
// serviceId, | |||||
// characteristicId: this.uuid2, | |||||
// state: true, | |||||
// }) | |||||
// wx.notifyBLECharacteristicValueChange({ | |||||
// deviceId, | |||||
// serviceId, | |||||
// characteristicId: this.uuid3, | |||||
// state: true, | |||||
// }) | |||||
// 初始化插件 | |||||
plugin.initPlugin(res.characteristics, this._device) | |||||
wx.onBLECharacteristicValueChange((characteristic) => { | |||||
// 解析特征值,返回解密后的数据 | |||||
let bleData = plugin.parseBleData(characteristic.value) | |||||
console.log(bleData) | |||||
if (bleData.status == 0) { | |||||
console.log("握手成功") | |||||
this._connLoading = false | |||||
wx.hideLoading() | |||||
wx.showToast({ | |||||
title: '连接成功', | |||||
}) | |||||
setTimeout(()=>{ | |||||
this.toConnectWifi() | |||||
},500) | |||||
} else if (bleData.status == 1) { | |||||
let payload = bleData.data //对应协议中的payload数据,可以自行解析该数据 | |||||
// console.log(ab2hex(payload, ' ')) | |||||
// console.log(ab2hex(bleData.completeData, ' ')) | |||||
// 以体脂秤数据解析为例 | |||||
switch (payload[0]) { | |||||
/* | |||||
* 例如: A7 00 0E 05 01 00 01 F4 10 19 7A---------50.0kg | |||||
* 其中 01 00 01 F4 10 为 payload | |||||
* 具体指令请根据协议解析 | |||||
*/ | |||||
case 0x01: | |||||
case 0x02: | |||||
let weightValue = (payload[1] << 16) | (payload[2] << 8) | payload[3] | |||||
let decPoint = (payload[4] & 0xf0) >> 4 | |||||
let unit = payload[4] & 0x0f | |||||
// console.log("体重数值:" + weightValue) | |||||
// console.log("小数点:" + decPoint) | |||||
// console.log("单位:" + unit) | |||||
if (unit == 1) { // 单位为斤 | |||||
this._weight = weightValue / 2 | |||||
} else { | |||||
// ... 其他单位 | |||||
} | |||||
this._weight = weightValue / (decPoint * 10) // 除去小数点位数 | |||||
break; | |||||
// ... | |||||
/* | |||||
* 例如: A7 00 0E 03 07 02 30 4A 7A---------阻抗测量成功,阻抗 560Ω | |||||
* 其中 07 02 30 为 payload | |||||
* 具体指令请根据协议解析 | |||||
*/ | |||||
case 0x07: | |||||
this._adc = (payload[1] << 8) | payload[2] | |||||
break; | |||||
case 0x0A: | |||||
//测量完成 | |||||
let bodyData = plugin.getBodyData(1, 20, 170, this._weight, this._adc) // 体脂秤数据解析 | |||||
console.log("解析后的体脂数据: ", bodyData) | |||||
console.log(util.getWeightDisplay(170, this._weight)) | |||||
break; | |||||
} | |||||
const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId) | |||||
const data = {} | |||||
if (idx === -1) { | |||||
data[`chs[${this.data.chs.length}]`] = { | |||||
uuid: characteristic.characteristicId, | |||||
value: ab2hex(bleData.completeData, ' ') | |||||
} | |||||
} else { | |||||
data[`chs[${idx}]`] = { | |||||
uuid: characteristic.characteristicId, | |||||
value: ab2hex(bleData.completeData, ' ') | |||||
} | |||||
} | |||||
this.setData(data) | |||||
} else if (bleData.status == 2) { | |||||
console.log('A6-----^') | |||||
} | |||||
}) | |||||
}, | |||||
fail(res) { | |||||
console.error('getBLEDeviceCharacteristics', res) | |||||
} | |||||
}) | |||||
}, | |||||
writeBLECharacteristicValue(buffer, uuid, deviceId, serviceId) { | |||||
// 向蓝牙设备发送一个二进制流数据 | |||||
wx.writeBLECharacteristicValue({ | |||||
deviceId, | |||||
serviceId, | |||||
characteristicId: uuid, | |||||
value: buffer, | |||||
success(res) { | |||||
console.log('writeBLECharacteristicValue success', res) | |||||
console.log('下发指令==> ' + ab2hex(buffer)) | |||||
} | |||||
}) | |||||
}, | |||||
offBLECharacteristicValueChange() { | |||||
wx.offBLECharacteristicValueChange((res)=>{ | |||||
console.log(res) | |||||
}) | |||||
}, | |||||
closeBluetoothAdapter() { | |||||
wx.closeBluetoothAdapter() | |||||
this._discoveryStarted = false | |||||
}, | |||||
// 打开指令输入框 | |||||
showWriteInputView() { | |||||
this.setData({ | |||||
showWriteInput: true | |||||
}) | |||||
}, | |||||
// 关闭指令输入框 | |||||
hideWriteInputView() { | |||||
this.setData({ | |||||
showWriteInput: false | |||||
}) | |||||
}, | |||||
// 指令下发 | |||||
submitCmd(e, cmd) { | |||||
let arr = [] | |||||
let temp = [] | |||||
if (!cmd) { | |||||
cmd = this.data.cmd | |||||
} | |||||
console.log(cmd) | |||||
if(!cmd || !this.data.connected){ | |||||
return | |||||
} | |||||
if(cmd.indexOf(",") == -1){ | |||||
temp = cmd.split(" ") | |||||
} else { | |||||
temp = cmd.split(",") | |||||
} | |||||
let tempCmd = temp.join(' ') | |||||
for(let i = 0; i < temp.length; i++){ | |||||
arr[i] = parseInt(temp[i],16) | |||||
} | |||||
// let arr = [ | |||||
// 0xA6, | |||||
// 0x01, | |||||
// 0x28, | |||||
// 0x29, | |||||
// 0x6A, | |||||
// //A6 01 28 29 6A | |||||
// ] | |||||
if(arr[0] == 0xA6){ | |||||
let len = arr[1] | |||||
let payload = arr.slice(2, 2 + len) | |||||
plugin.sendDataOfA6(payload) | |||||
// let buff = new Uint8Array(arr).buffer | |||||
// this.writeBLECharacteristicValue(buff, this.uuid3, this._deviceId, this._serviceId) | |||||
} else if(arr[0] == 0xA7) { | |||||
let len = arr[3] | |||||
let payload = arr.slice(4, 4 + len) | |||||
plugin.sendDataOfA7(payload) | |||||
} | |||||
let historyList = wx.getStorageSync('historyList') || [] | |||||
let idx = historyList.findIndex(item => item.cmd == tempCmd) | |||||
if (idx < 0) { | |||||
historyList.push({cmd: tempCmd}) | |||||
} | |||||
this.setData({ | |||||
historyList | |||||
}) | |||||
wx.setStorage({ | |||||
data: historyList, | |||||
key: 'historyList', | |||||
}) | |||||
wx.showToast({ | |||||
title: '已发送', | |||||
icon: 'none' | |||||
}) | |||||
}, | |||||
history_delete(e) { | |||||
let index = e.currentTarget.dataset.index | |||||
this.data.historyList.splice(index, 1) | |||||
this.setData({ | |||||
historyList: this.data.historyList | |||||
}) | |||||
wx.setStorage({ | |||||
data: this.data.historyList, | |||||
key: 'historyList', | |||||
}) | |||||
}, | |||||
history_copy(e) { | |||||
let index = e.currentTarget.dataset.index | |||||
wx.setClipboardData({ | |||||
data: this.data.historyList[index].cmd | |||||
}) | |||||
}, | |||||
history_send(e) { | |||||
let index = e.currentTarget.dataset.index | |||||
let cmd = this.data.historyList[index].cmd | |||||
this.submitCmd(null, cmd) | |||||
}, | |||||
}); |
{ | |||||
"usingComponents": { | |||||
} | |||||
} |
<wxs module="utils"> | |||||
module.exports.max = function(n1, n2) { | |||||
return Math.max(n1, n2) | |||||
} | |||||
module.exports.len = function(arr) { | |||||
arr = arr || [] | |||||
return arr.length | |||||
} | |||||
</wxs> | |||||
<view class="container"> | |||||
<view class="header"> | |||||
<button bindtap="openBluetoothAdapter" style="font-size: 32rpx;line-height:100rpx;width: 100%;">开始扫描</button> | |||||
<button bindtap="stopBluetoothDevicesDiscovery" style="font-size: 32rpx;line-height:100rpx;width: 100%;">停止扫描</button> | |||||
<button bindtap="closeBluetoothAdapter" style="font-size: 32rpx;line-height:100rpx;width: 100%;">结束流程</button> | |||||
</view> | |||||
<view class="devices_summary">已发现 {{devices.length}} 个外围设备:</view> | |||||
<scroll-view class="device_list" scroll-y scroll-with-animation> | |||||
<view wx:for="{{devices}}" wx:key="index" | |||||
data-device-id="{{item.deviceId}}" | |||||
data-name="{{item.name || item.localName}}" | |||||
data-mac="{{item.mac}}" | |||||
data-index="{{index}}" | |||||
bindtap="createBLEConnection" | |||||
class="device_item" | |||||
hover-class="device_item_hover"> | |||||
<view style="font-size: 32rpx;"> | |||||
<text style="color:#000;font-weight:bold">{{item.name}}</text> | |||||
<text style="font-size:26rpx">(信号强度: {{item.RSSI}}dBm)</text> | |||||
</view> | |||||
<view style="font-size: 26rpx">mac地址: {{item.macAddr || item.deviceId}}</view> | |||||
<!-- <view style="font-size: 10px">Service数量: {{utils.len(item.advertisServiceUUIDs)}}</view> --> | |||||
<view style="font-size: 26rpx">广播数据:{{item.analyzeDataText}}</view> | |||||
</view> | |||||
</scroll-view> | |||||
<view class="connected_info" wx:if="{{connected}}"> | |||||
<view> | |||||
<text>已连接到 {{name}}</text> | |||||
<view class="operation"> | |||||
<!-- <button wx:if="{{canWrite}}" size="mini" bindtap="writeBLECharacteristicValue">写数据</button> --> | |||||
<button size="mini" bindtap="showWriteInputView" style="line-height:54rpx; font-size:28rpx;padding:0 20rpx;margin-right:10rpx" wx:if="{{0}}">写入指令</button> | |||||
<button size="mini" bindtap="toConnectWifi" style="line-height:54rpx; font-size:28rpx;padding:0 20rpx;margin-right:10rpx">连接wifi</button> | |||||
<button size="mini" bindtap="closeBLEConnection" style="line-height:54rpx; font-size:28rpx;padding:0 20rpx;">断开连接</button> | |||||
</view> | |||||
</view> | |||||
<view wx:for="{{chs}}" wx:key="index" style="font-size: 26rpx; margin-top: 20rpx;"> | |||||
<view>特性值: {{item.value}}</view> | |||||
</view> | |||||
</view> | |||||
<view class="writeInputView" wx:if="{{showWriteInput}}"> | |||||
<view class="writeInput"> | |||||
<input type="text" model:value="{{cmd}}"></input> | |||||
</view> | |||||
<view class="hint">请输入16进制数,Byte之间用空格或英文逗号分隔</view> | |||||
<view class="btns"> | |||||
<button bindtap="submitCmd" style="line-height:70rpx; font-size:30rpx;padding:0 50rpx;margin-right:30rpx;">发送</button> | |||||
<button bindtap="hideWriteInputView" style="line-height:70rpx; font-size:30rpx;padding:0 50rpx;">取消</button> | |||||
</view> | |||||
<view class="history"> | |||||
<view class="history_title">历史记录:</view> | |||||
<scroll-view class="history_wrap" scroll-y="true"> | |||||
<view class="history_item" wx:for="{{historyList}}" wx:key="index"> | |||||
<view class="history_cmd">{{item.cmd}}</view> | |||||
<view class="history_btns"> | |||||
<button size="mini" bindtap="history_send" data-index="{{index}}" style="line-height:54rpx; font-size:24rpx;padding:0 30rpx;">发送</button> | |||||
<button size="mini" bindtap="history_delete" data-index="{{index}}" style="line-height:54rpx; font-size:24rpx;padding:0 30rpx;margin-left:20rpx">删除</button> | |||||
</view> | |||||
</view> | |||||
</scroll-view> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
page { | |||||
background: #f0f0f0; | |||||
color: #333; | |||||
} | |||||
.container{ | |||||
padding-bottom: 200rpx; | |||||
} | |||||
.header{ | |||||
width: 100%; | |||||
} | |||||
.container button{ | |||||
padding-top: 0; | |||||
padding-bottom: 0; | |||||
border: 1px solid #ddd; | |||||
} | |||||
.devices_summary { | |||||
width: 100%; | |||||
line-height: 70rpx; | |||||
padding: 0 30rpx; | |||||
margin-top: 30rpx; | |||||
font-size: 32rpx; | |||||
} | |||||
.device_list { | |||||
flex: 1; | |||||
width: 100%; | |||||
height: calc(100vh - 600rpx); | |||||
margin-top: 0; | |||||
border: 1px solid #fff; | |||||
border-radius: 5px; | |||||
background: #fff; | |||||
} | |||||
.device_item { | |||||
line-height: 1.5; | |||||
padding: 10rpx 30rpx; | |||||
border-bottom: 1px solid #EEE; | |||||
} | |||||
.device_item_hover { | |||||
background-color: rgba(0, 0, 0, .1); | |||||
} | |||||
.connected_info { | |||||
position: fixed; | |||||
bottom: 0; | |||||
width: 100%; | |||||
height: 200rpx; | |||||
padding: 10px; | |||||
border-top: 1px solid #ddd; | |||||
background-color: #fff; | |||||
font-size: 14px; | |||||
box-shadow: 0px -4rpx 4rpx 0px rgba(0,0,0,.05); | |||||
} | |||||
.connected_info .operation { | |||||
position: absolute; | |||||
display: inline-block; | |||||
right: 30rpx; | |||||
} | |||||
.writeInputViewBg{ | |||||
position: fixed; | |||||
top: 0; | |||||
left: 0; | |||||
right: 0; | |||||
bottom: 0; | |||||
z-index: 1000; | |||||
background-color: rgba(0,0,0,0.5); | |||||
} | |||||
.writeInputView{ | |||||
position: fixed; | |||||
bottom: 200rpx; | |||||
left: 0; | |||||
z-index: 1001; | |||||
display: flex; | |||||
flex-direction: column; | |||||
align-items: center; | |||||
width: 100%; | |||||
height: calc(100vh - 500rpx); | |||||
background-color: #fff; | |||||
border-top: 1px solid #ddd; | |||||
} | |||||
.history{ | |||||
position: relative; | |||||
flex: 1; | |||||
width: 690rpx; | |||||
padding-top: 60rpx; | |||||
border: 1px solid #ddd; | |||||
margin: 30rpx; | |||||
} | |||||
.history_title{ | |||||
position: absolute; | |||||
top: 0; | |||||
left: 0; | |||||
padding: 0 30rpx; | |||||
line-height: 60rpx; | |||||
font-size: 30rpx; | |||||
} | |||||
.history_wrap{ | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
.history_item{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
width: 100%; | |||||
height: 72rpx; | |||||
padding: 0 30rpx; | |||||
border-top: 1px solid #ddd; | |||||
} | |||||
.history_cmd{ | |||||
flex: 1; | |||||
overflow: hidden; | |||||
text-overflow: ellipsis; | |||||
white-space: nowrap; | |||||
font-size: 24rpx; | |||||
} | |||||
.history_btns{ | |||||
height: 60rpx; | |||||
overflow: hidden; | |||||
} | |||||
.writeInput{ | |||||
margin-top: 30rpx; | |||||
width: 690rpx; | |||||
height: 80rpx; | |||||
border: 1rpx solid #ccc; | |||||
} | |||||
.writeInput input{ | |||||
width: 100%; | |||||
height: 100%; | |||||
padding: 10rpx; | |||||
font-size: 32rpx; | |||||
} | |||||
.hint{ | |||||
padding-top: 10rpx; | |||||
font-size: 24rpx; | |||||
color: #999; | |||||
} | |||||
.btns{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
width: 60%; | |||||
padding-bottom: 30rpx; | |||||
margin-top: 30rpx; | |||||
} |
// pages/wifiList/wifiList.js | |||||
const util = require("../../utils/util"); | |||||
const { | |||||
ab2hex | |||||
} = util; | |||||
const plugin = requirePlugin("sdkPlugin").AiLink; | |||||
Page({ | |||||
/** | |||||
* 页面的初始数据 | |||||
*/ | |||||
data: { | |||||
list: [], | |||||
wifi_connected: false, | |||||
conn_wifi: {}, | |||||
scanEnd: false, | |||||
totalAP: 0, | |||||
pwdModal: false, | |||||
pwdInput: '', | |||||
statusText: '', | |||||
statusList: ['', '', '已连接'] | |||||
}, | |||||
/** | |||||
* 生命周期函数--监听页面加载 | |||||
*/ | |||||
onLoad: function (options) { | |||||
let pages = getCurrentPages() | |||||
this._home = pages[pages.length - 2] | |||||
wx.onBLECharacteristicValueChange((characteristic) => { | |||||
// 解析特征值,返回解密后的数据 | |||||
let bleData = plugin.parseBleData(characteristic.value) | |||||
// console.log(bleData) | |||||
if (bleData.status == 0) { | |||||
console.log("握手成功") | |||||
} else if (bleData.status == 1) { | |||||
console.log('A7-----^') | |||||
} else if (bleData.status == 2) { | |||||
console.log(bleData) | |||||
console.log('A6-----^') | |||||
let payload = bleData.data | |||||
const result = ['成功', '失败', '不支持'] | |||||
switch (payload[0]) { | |||||
case 0x26: { | |||||
console.log('状态改变') | |||||
let bleStatus = payload[1] & 0x0f | |||||
let wifiStatus = payload[1] >> 4 | |||||
const bleStatusList = ['无连接', '已连接', '配对完成'] | |||||
const wifiStatusList = ['未连接', '连接失败', '连接的WiFi信号不好', '连接成功', '正在连接'] | |||||
console.log('蓝牙状态:' + bleStatusList[bleStatus]) | |||||
console.log('WiFi状态:' + wifiStatusList[wifiStatus]) | |||||
this.setData({ | |||||
statusText: '蓝牙状态:' + bleStatusList[bleStatus] + ' ,WiFi状态:' + wifiStatusList[wifiStatus] | |||||
}) | |||||
break | |||||
} | |||||
case 0x80: { | |||||
wx.hideLoading() | |||||
wx.showToast({ | |||||
title: '扫描AP' + result[payload[1]], | |||||
icon: 'none' | |||||
}) | |||||
console.log('扫描AP' + result[payload[1]]) | |||||
break; | |||||
} | |||||
case 0x81: { | |||||
let id = payload[1] | |||||
console.log('上报WiFi名===>') | |||||
let nameArr = payload.slice(2) | |||||
console.log('原始数据===>' + ab2hex(nameArr, ' ')) | |||||
let name = nameArr.length ? util.hex2str(nameArr) : '' | |||||
if (name) { | |||||
console.log('WiFi名===>' + name) | |||||
let index = this.data.list.findIndex(item => item.name == name) | |||||
if (index < 0) { | |||||
this.data.list.push({ | |||||
id, | |||||
name, | |||||
}) | |||||
} else { | |||||
this.data.list[index].name = name | |||||
} | |||||
this.setData({ | |||||
list: this.data.list | |||||
}) | |||||
} | |||||
break; | |||||
} | |||||
case 0x82: { | |||||
console.log('上报WiFi信息-----^') | |||||
let id = payload[1] | |||||
let index = this.data.list.findIndex(item => item.id == id) | |||||
let addr = payload.slice(2, 8) | |||||
let signal = payload[8] | |||||
let type = payload[9] | |||||
let status = payload[10] | |||||
if (index < 0) { | |||||
// this.data.list.push({ | |||||
// id, | |||||
// addr, | |||||
// signal, | |||||
// type, | |||||
// status, | |||||
// }) | |||||
} else { | |||||
this.data.list[index].addr = addr | |||||
this.data.list[index].signal = signal | |||||
this.data.list[index].type = type | |||||
this.data.list[index].status = status | |||||
this.setData({ | |||||
list: this.data.list | |||||
}) | |||||
} | |||||
break; | |||||
} | |||||
case 0x83: | |||||
let num = payload[1] | |||||
console.log('上报扫描完毕-----^') | |||||
this.setData({ | |||||
totalAP: num, | |||||
scanEnd: true | |||||
}) | |||||
break; | |||||
case 0x84: { | |||||
console.log('设置地址' + result[payload[1]]) | |||||
wx.showToast({ | |||||
title: '设置地址' + result[payload[1]], | |||||
icon: 'none' | |||||
}) | |||||
// if (this._conn_wifi.status > 0) { | |||||
// this.connectWifi() | |||||
// } | |||||
break; | |||||
} | |||||
case 0x86: { | |||||
console.log('设置密码' + result[payload[1]]) | |||||
wx.showToast({ | |||||
title: '设置密码' + result[payload[1]], | |||||
icon: 'none' | |||||
}) | |||||
if (payload[1] === 0) { | |||||
this.connectWifi() | |||||
this.closeModal() | |||||
} | |||||
break; | |||||
} | |||||
case 0x88: { | |||||
console.log('设置 wifi 状态' + result[payload[1]]) | |||||
wx.showToast({ | |||||
title: '设置 wifi 状态' + result[payload[1]], | |||||
icon: 'none' | |||||
}) | |||||
if (payload[1] === 0) { | |||||
if (this.data.wifi_connected) { // true 建立连接, false 断开连接 | |||||
this.data.list.forEach(item=>{ | |||||
if (item.name === this._conn_wifi.name) { | |||||
item.status = 2 | |||||
} else if (item.status == 2) { | |||||
item.status = 1 | |||||
} | |||||
}) | |||||
} | |||||
this.setData({ | |||||
conn_wifi: this._conn_wifi, | |||||
wifi_connected: this.data.wifi_connected, | |||||
list: this.data.list | |||||
}) | |||||
} else { | |||||
this.setData({ | |||||
wifi_connected: !this.data.wifi_connected | |||||
}) | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
}, | |||||
doSearch() { | |||||
let payload = [0x80, 0x01] | |||||
this.setData({ | |||||
scanEnd: false, | |||||
}) | |||||
wx.showLoading({ | |||||
title: '开始搜索', | |||||
}) | |||||
this.data.list = [] | |||||
plugin.sendDataOfA6(payload) | |||||
}, | |||||
tapItem(e) { | |||||
let index = e.currentTarget.dataset.index | |||||
let item = this.data.list[index] | |||||
console.log(item) | |||||
// 设置希望连接的 AP 地址 | |||||
let cmd = [0x84, ...item.addr] | |||||
plugin.sendDataOfA6(cmd) | |||||
this.showModal() | |||||
this._conn_wifi = item | |||||
}, | |||||
confirmPwd() { | |||||
if (!this.data.pwdInput || this.data.pwdInput.length < 8) { | |||||
wx.showToast({ | |||||
title: '密码有误', | |||||
icon: 'none' | |||||
}) | |||||
return | |||||
} | |||||
let pwdArr = util.str2hex(this.data.pwdInput) | |||||
if (pwdArr.length > 56) { | |||||
wx.showToast({ | |||||
title: '密码有误', | |||||
icon: 'none' | |||||
}) | |||||
return | |||||
} | |||||
if (pwdArr.length > 14) { // 超出14 byte 需分包 | |||||
console.log('密码超出14位===') | |||||
let pwdList = [] | |||||
let len = parseInt(pwdArr.length / 14) + 1 | |||||
for (let i = 0; i < len; i++) { | |||||
setTimeout(() => { | |||||
pwdList[i] = pwdArr.slice(i * 14, (i + 1) * 14) | |||||
let isEnd = +(i !== len - 1) | |||||
console.log(isEnd) | |||||
let cmd = [0x86, isEnd, ...pwdList[i]] | |||||
console.log(cmd) | |||||
plugin.sendDataOfA6(cmd) | |||||
}, 200 * i) | |||||
} | |||||
console.log(pwdArr) | |||||
console.log(pwdList) | |||||
} else { | |||||
// 设置希望连接的 AP 密码 | |||||
let cmd = [0x86, 0x00, ...pwdArr] | |||||
plugin.sendDataOfA6(cmd) | |||||
} | |||||
}, | |||||
connectWifi() { | |||||
// 发起 wifi 连接 | |||||
this.setData({ | |||||
wifi_connected: true | |||||
}) | |||||
let cmd = [0x88, 0x01] | |||||
plugin.sendDataOfA6(cmd) | |||||
}, | |||||
closeWifiConnection() { | |||||
// 断开 wifi 连接 | |||||
this.setData({ | |||||
wifi_connected: false | |||||
}) | |||||
let cmd = [0x88, 0x00] | |||||
plugin.sendDataOfA6(cmd) | |||||
}, | |||||
showModal() { | |||||
this.setData({ | |||||
pwdModal: true | |||||
}) | |||||
}, | |||||
closeModal() { | |||||
this.setData({ | |||||
pwdModal: false | |||||
}) | |||||
}, | |||||
stopAction() { | |||||
return false | |||||
}, | |||||
/** | |||||
* 生命周期函数--监听页面初次渲染完成 | |||||
*/ | |||||
onReady: function () { | |||||
}, | |||||
/** | |||||
* 生命周期函数--监听页面显示 | |||||
*/ | |||||
onShow: function () { | |||||
}, | |||||
/** | |||||
* 生命周期函数--监听页面隐藏 | |||||
*/ | |||||
onHide: function () { | |||||
}, | |||||
/** | |||||
* 生命周期函数--监听页面卸载 | |||||
*/ | |||||
onUnload: function () { | |||||
}, | |||||
/** | |||||
* 页面相关事件处理函数--监听用户下拉动作 | |||||
*/ | |||||
onPullDownRefresh: function () { | |||||
}, | |||||
/** | |||||
* 页面上拉触底事件的处理函数 | |||||
*/ | |||||
onReachBottom: function () { | |||||
}, | |||||
/** | |||||
* 用户点击右上角分享 | |||||
*/ | |||||
onShareAppMessage: function () { | |||||
} | |||||
}) |
{ | |||||
"usingComponents": {} | |||||
} |
<!--pages/wifiList/wifiList.wxml--> | |||||
<view class="container"> | |||||
<view class="header_button"> | |||||
<button bindtap="doSearch" style="font-size: 32rpx;line-height:100rpx;width: 100%;background:#fff">开始搜索WiFi</button> | |||||
</view> | |||||
<view class="wifi_title"> | |||||
<text>WiFi列表</text> | |||||
<text>-WiFi总数:{{list.length}}</text> | |||||
<view wx:if="{{0}}">扫描结束,本次扫描到WiFi数:{{totalAP}}</view> | |||||
</view> | |||||
<scroll-view scroll-y="true" class="wifi_list"> | |||||
<view class="wifi_item flex_center_sb" wx:for="{{list}}" wx:key='index' bindtap="tapItem" data-index="{{index}}" hover-class="item_hover"> | |||||
<view>{{item.name}}</view> | |||||
<view class="wifi_item_right">{{statusList[item.status]}}</view> | |||||
</view> | |||||
</scroll-view> | |||||
<view class="footer" wx:if="{{conn_wifi.id}}"> | |||||
<view> | |||||
<text>{{conn_wifi.name}} </text> | |||||
<text>{{wifi_connected?'已连接':'已断开连接'}}</text> | |||||
<view class="operation"> | |||||
<button size="mini" bindtap="connectWifi" style="line-height:54rpx; font-size:28rpx;padding:0 20rpx;margin-right:10rpx" wx:if="{{!wifi_connected}}">重新连接</button> | |||||
<button size="mini" bindtap="closeWifiConnection" style="line-height:54rpx; font-size:28rpx;padding:0 20rpx;">断开连接</button> | |||||
</view> | |||||
</view> | |||||
<view style="font-size: 26rpx; margin-top: 20rpx;" wx:if="{{statusText}}">{{statusText}}</view> | |||||
</view> | |||||
</view> | |||||
<view class="modal_wrapper {{pwdModal ? 'modal_wrapper_show': ''}}" catchtouchmove="stopAction"> | |||||
<view class="remindModal"> | |||||
<view class="remindModal_title">密码输入</view> | |||||
<view class="modal_input_wrap"> | |||||
<input model:value='{{pwdInput}}' class="modal_input" placeholder="请输入密码" placeholder-style="font-size:30rpx;color:#b4b4b4" /> | |||||
</view> | |||||
<view class="remindModal_btn"> | |||||
<view class="confirm_btn" bindtap="confirmPwd">确定</view> | |||||
<view class="remindModal_cancel_btn" bindtap="closeModal" hover-class="hover_tap">取消</view> | |||||
</view> | |||||
</view> | |||||
</view> |
/* pages/wifiList/wifiList.wxss */ | |||||
page{ | |||||
background: #f8f8f8; | |||||
} | |||||
.container{ | |||||
padding-bottom: 200rpx; | |||||
} | |||||
.container button{ | |||||
padding-top: 0; | |||||
padding-bottom: 0; | |||||
border: 1px solid #ddd; | |||||
} | |||||
.header_button{ | |||||
flex: 0 0 auto; | |||||
width: 100%; | |||||
} | |||||
.wifi_title{ | |||||
width: 100%; | |||||
padding: 40rpx 30rpx 20rpx; | |||||
line-height: 1.5; | |||||
} | |||||
.wifi_list{ | |||||
width: 100%; | |||||
height: calc(100vh - 400rpx); | |||||
border: 1px solid #ccc; | |||||
} | |||||
.wifi_item{ | |||||
height: 100rpx; | |||||
padding: 0 30rpx; | |||||
border-bottom: 1px solid #ddd; | |||||
background: #fff; | |||||
} | |||||
.wifi_item_right{ | |||||
color: #1977ff; | |||||
} | |||||
.footer { | |||||
position: fixed; | |||||
bottom: 0; | |||||
width: 100%; | |||||
height: 200rpx; | |||||
padding: 10px; | |||||
border-top: 1px solid #ddd; | |||||
background-color: #fff; | |||||
font-size: 14px; | |||||
box-shadow: 0px -4rpx 4rpx 0px rgba(0,0,0,.05); | |||||
} | |||||
.footer .operation { | |||||
position: absolute; | |||||
display: inline-block; | |||||
right: 30rpx; | |||||
} | |||||
.modal_wrapper{ | |||||
position: fixed; | |||||
top: 0; | |||||
left: 0; | |||||
right: 0; | |||||
z-index: -1; | |||||
background: rgba(0,0,0,0.5); | |||||
transition: .4s all; | |||||
height: 200vh; | |||||
opacity: 0; | |||||
} | |||||
.modal_wrapper_show{ | |||||
transform: translateY(-100vh); | |||||
opacity: 1; | |||||
overflow-x: hidden; | |||||
overflow-y: auto; | |||||
z-index: 1000; | |||||
} | |||||
.remindModal{ | |||||
position: absolute; | |||||
bottom: 50vh; | |||||
left: 50%; | |||||
z-index: 101; | |||||
transform: translate(-50%, 50%); | |||||
background: #fff; | |||||
width: 600rpx; | |||||
padding: 40rpx 60rpx; | |||||
border-radius: 20rpx; | |||||
} | |||||
.remindModal_title{ | |||||
padding: 10rpx 30rpx 50rpx; | |||||
text-align: center; | |||||
font-size: 32rpx; | |||||
} | |||||
.remindModal_content{ | |||||
font-size: 30rpx; | |||||
line-height: 1.6; | |||||
text-align: center; | |||||
padding-top: 30rpx; | |||||
min-height: 100rpx; | |||||
} | |||||
.remindModal_btn{ | |||||
margin-top: 80rpx; | |||||
margin-bottom: 10rpx; | |||||
} | |||||
.remindModal_cancel_btn{ | |||||
margin-top: 30rpx; | |||||
height: 80rpx; | |||||
line-height: 80rpx; | |||||
text-align: center; | |||||
color: #FA1111; | |||||
font-size: 30rpx; | |||||
} | |||||
.modal_input_wrap{ | |||||
width: 100%; | |||||
height: 80rpx; | |||||
padding: 0 10rpx; | |||||
border: 1px solid #dcdcdc; | |||||
border-radius: 10rpx; | |||||
} | |||||
.modal_input{ | |||||
width: 100%; | |||||
height: 100%; | |||||
padding: 10rpx; | |||||
font-size: 30rpx; | |||||
} | |||||
.confirm_btn{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
width: 480rpx; | |||||
height: 100rpx; | |||||
border: 1px solid transparent; | |||||
margin: auto; | |||||
background: #1977FF; | |||||
color: #fff; | |||||
font-size: 16px; | |||||
letter-spacing: 2rpx; | |||||
border-radius: 1000rpx; | |||||
white-space: nowrap; | |||||
} | |||||
.item_hover{ | |||||
background-color: rgba(0, 0, 0, .1); | |||||
} |
{ | |||||
"description": "项目配置文件", | |||||
"packOptions": { | |||||
"ignore": [] | |||||
}, | |||||
"setting": { | |||||
"urlCheck": true, | |||||
"es6": true, | |||||
"enhance": true, | |||||
"postcss": true, | |||||
"preloadBackgroundData": false, | |||||
"minified": true, | |||||
"newFeature": false, | |||||
"coverView": true, | |||||
"nodeModules": false, | |||||
"autoAudits": false, | |||||
"showShadowRootInWxmlPanel": true, | |||||
"scopeDataCheck": false, | |||||
"uglifyFileName": false, | |||||
"checkInvalidKey": true, | |||||
"checkSiteMap": true, | |||||
"uploadWithSourceMap": true, | |||||
"compileHotReLoad": true, | |||||
"useMultiFrameRuntime": true, | |||||
"useApiHook": true, | |||||
"useApiHostProcess": false, | |||||
"babelSetting": { | |||||
"ignore": [], | |||||
"disablePlugins": [], | |||||
"outputPath": "" | |||||
}, | |||||
"enableEngineNative": false, | |||||
"bundle": false, | |||||
"useIsolateContext": true, | |||||
"useCompilerModule": true, | |||||
"userConfirmedUseCompilerModuleSwitch": false, | |||||
"userConfirmedBundleSwitch": false, | |||||
"packNpmManually": false, | |||||
"packNpmRelationList": [], | |||||
"minifyWXSS": true | |||||
}, | |||||
"compileType": "miniprogram", | |||||
"libVersion": "2.16.0", | |||||
"appid": "wx17e93aad47cdae1a", | |||||
"projectname": "wifi_ble_demo", | |||||
"debugOptions": { | |||||
"hidedInDevtools": [] | |||||
}, | |||||
"scripts": {}, | |||||
"isGameTourist": false, | |||||
"simulatorType": "wechat", | |||||
"simulatorPluginLibVersion": {}, | |||||
"condition": { | |||||
"search": { | |||||
"list": [] | |||||
}, | |||||
"conversation": { | |||||
"list": [] | |||||
}, | |||||
"game": { | |||||
"list": [] | |||||
}, | |||||
"plugin": { | |||||
"list": [] | |||||
}, | |||||
"gamePlugin": { | |||||
"list": [] | |||||
}, | |||||
"miniprogram": { | |||||
"list": [] | |||||
} | |||||
} | |||||
} |
{ | |||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||||
"rules": [{ | |||||
"action": "allow", | |||||
"page": "*" | |||||
}] | |||||
} |
function inArray(arr, key, val) { | |||||
if (!arr || !arr.length || typeof arr != 'object' || !Array.isArray(arr)) { | |||||
return -1 | |||||
} | |||||
for (let i = 0; i < arr.length; i++) { | |||||
if (!key) { | |||||
if (arr[i] == val) { | |||||
return i | |||||
} | |||||
} else if (arr[i][key] === val) { | |||||
return i | |||||
} | |||||
} | |||||
return -1; | |||||
} | |||||
// ArrayBuffer转16进度字符串示例 | |||||
function ab2hex(buffer, split) { | |||||
var hexArr = Array.prototype.map.call( | |||||
new Uint8Array(buffer), | |||||
function (bit) { | |||||
return ('00' + bit.toString(16)).slice(-2) | |||||
} | |||||
) | |||||
return hexArr.join(split); | |||||
} | |||||
const {TextDecoder, TextEncoder} = require('./encoding') | |||||
function hex2str(arr) { | |||||
let decoder = new TextDecoder('utf8') | |||||
let uint8 = new Uint8Array(arr) | |||||
let res = decoder.decode(uint8) | |||||
return res | |||||
} | |||||
function str2hex(str) { | |||||
let encoder = new TextEncoder('utf8') | |||||
return encoder.encode(str) | |||||
} | |||||
module.exports = { | |||||
inArray, | |||||
ab2hex, | |||||
hex2str, | |||||
str2hex, | |||||
} |