前言:最近有需求需要对接一个WiFi设备,基本逻辑操作是,设备和手机同时连接同一个WiFi,然后手机和设备进行局域网通信。当操作设备时,设备会向手机发送数据包,小程序接受到数据包需要解释成十六进制,然后进行数据解析,根据需要的内容进行数据处理。
思路图
开发思路解析
前期条件
- 确保设备和手机连接同一个路由
- 设备会默认自动连接设定的WiFi名称和密码
- 其实路由器也可以是手机热点,反正形成局域网就行
UPD广播
- 需要知道设备会不会自动发送广播,需要知道广播端口是多少,然后小程序使用UDP去监听端口
例如:设备会默认向公共频段255.255.255.255广播端口8088发送消息,那我们只需要这样写监听即可
const udp = wx.createUDPSocket()
udp.bind(8088)
udp.onMessage((res)=>{})
如果需要发送信息才会向小程序回传信息,这样写
const udp = wx.createUDPSocket()
// 如果手机或者调试工具没写端口会报错,下面需要自己写一个随机端口号即可
udp.bind()
udp.onMessage((res)=>{})
udp.send({
address: '255.255.255.255', // 示例
port: 8848, // 示例
message: 'hello, how are you' // 很多设备接收都需要转成16进制,后面写转换方法
})
- 通过上面的UDP消息监听,会接收到设备返回的数据包括ip地址和端口(这里主要拿ip地址,端口有些设备是写固定的),拿到ip后通过TCP去连接和监听TCP消息
const tcp = wx.createTCPSocket()
tcp.connect({address: '192.168.193.2', port: 8848})
TCPSocket.onMessage((res) => {})
- 通过以上步骤,我们已经完成了设备之间的链接与通信了。
相关算法或者数据转换( 重点难点在于buffer转换 )
- 发送与接收,十进制与十六进制相互转换
// 进制转换 fromBase - 当前进制,toBase - 将要变成的进制
function convertBase(number, fromBase, toBase) {
const decimalNumber = parseInt(number, fromBase)
return decimalNumber.toString(toBase)
}
- 十六进制转换成buffer数组,数据包上传参数
// 示例:data = 'FF FF 01 00 0E 01 00 08 00 01 00 00 FE FE'
function switchBuffer(data) {
let arr = data.split(' ')
let length = arr.length
let array = new Int8Array(length);
arr.map((item, index) => {
array[index] = Number(convertBase(arr[index], 16, 10))
})
return array.buffer;
}
- buffer转换成十六进制,数据返回解析数据包
// arrayBuffer设备返回的数据
function bufferToString(arrayBuffer) {
let unit8Arr = new Uint8Array(arrayBuffer);
const array = []
unit8Arr.map((item) => {
// 这里用到了十进制转换十六进制
array.push(convertBase(item, 10, 16).toUpperCase().padStart(2, '0'))
})
return array;
}
// 这里没有用通用方法解析,是因为测试过,解析不稳定,会出现数据包内容有点错误,下面就是网上找到的解析数据包的方法,有需要可以用
// let encodedString = String.fromCharCode.apply(null, unit8Arr)
// let str = escape(encodedString)
// let handStr = str.replace(/^%|%$/, '');
- 获取数组最大的值
function numMaxFn (arr) {
return Math.max(...arr)
}
注意点
- TCP连接后,需要保持心跳包一直动,否则会断链,心跳包可以是设备发送,也可以小程序发
- TCP连接成功后,不要立马发送消息给设备(可以延长1s左右),发送太快会导致设备TCP断链(目前我会出现这个问题,还排除了很久很久,很意外的问题)
- 用小程序测试工具不好测试,因为测试工具不能创建多个udp和tcp,所以连接一次就要刷新项目,在连接
- 其它具体都是逻辑问题,数据包解析,这些都要和通信协议进行处理即可了
代码呈现
- 文件结构
- 页面index.js
import {init} from '../../utils/connetc'
const app = getApp()
Page({
data: {
},
onLoad() {
init()
},
})
- 连接相关代码connect.js
const tcpStart = 'FF FF 01 00 0E 01 00 08 00 01 00 00 FE FE'
let UDPSocket = null
let TCPSocket = null
export const init = () => {
UDPSocket = wx.createUDPSocket()
TCPSocket = wx.createTCPSocket()
// const ports = Math.floor(Math.random() * (60000 - 10000 + 1)) + 10000
const p = UDPSocket.bind(9090)
console.log(p, 'udp端口')
onUDPFn()
onTcpFn()
}
// 监听upd端口-事件
export const onUDPFn = () => {
UDPSocket.onError((err) => {
console.error(err, 'udp-错误')
})
UDPSocket.onClose((err) => {
console.log(err, 'upd-Err')
})
UDPSocket.onMessage((res) => {
let strArr = tool.bufferToString(res.message)
console.log('UDP消息', res, strArr)
let udpRemoteInfo = res.remoteInfo
// 连接tcp
tcpConnect(udpRemoteInfo)
})
}
// 监听tcp
export const onTcpFn = () => {
if (!TCPSocket) TCPSocket = wx.createTCPSocket()
TCPSocket.onClose(() => {
})
TCPSocket.onConnect(() => {
console.log('tcp-连接成功')
// 连接成功后,发送消息
TCPSocket.write(tool.switchBuffer(tcpStart))
})
TCPSocket.onError((err) => {
})
TCPSocket.onMessage((res) => {
let strArr = tool.bufferToString(res.message)
console.log('tcp数据', res, strArr)
})
}
// 连接tcp
export const tcpConnect = (udpRemoteInfo) => {
if (!TCPSocket) TCPSocket = wx.createTCPSocket()
if (!udpRemoteInfo.address) return
TCPSocket &&
TCPSocket.connect({
address: udpRemoteInfo.address,
port: 8089,
timeout: 15000
})
}
- 算法相关tool.js
/*
* @Description: cpr设备连接相关
* @Date: 2023-12-21 11:40:04
*/
// 进制转换 fromBase - 当前进制,toBase - 将要变成的进制
function convertBase(number, fromBase, toBase) {
const decimalNumber = parseInt(number, fromBase)
return decimalNumber.toString(toBase)
}
// 校验和计算,返回16进制2字节
function checksum(data) {
let arr = data.split(' ')
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += Number(convertBase(arr[i], 16, 10))
}
let sum16 = convertBase(sum, 10, 16)
let bit4 = padZero(sum16)
return addSpace(bit4)
}
// 补位,如果不足4位字符串,需开头补0
function padZero(numStr) {
return numStr.padStart(4, '0')
}
// 4位字符串中间加空格,与之前数据保持一致
function addSpace(numStr) {
return numStr.slice(0, 2) + ' ' + numStr.slice(2)
}
// 拼接完整上传数据字符串
function pingStr(data) {
let str = `${data} ${checksum(data)} FE FE`
return str
}
// 16进制转换成buffer数组,上传参数
function switchBuffer(data) {
let arr = data.split(' ')
let length = arr.length
let array = new Int8Array(length);
arr.map((item, index) => {
array[index] = Number(convertBase(arr[index], 16, 10))
})
return array.buffer;
}
// buffer转换成十六进制
function bufferToString(arrayBuffer) {
let unit8Arr = new Uint8Array(arrayBuffer);
// let encodedString = String.fromCharCode.apply(null, unit8Arr)
// let str = escape(encodedString)
// let handStr = str.replace(/^%|%$/, '');
const array = []
unit8Arr.map((item) => {
array.push(convertBase(item, 10, 16).toUpperCase().padStart(2, '0'))
})
return array;
}
// 数组取最大值
function numMaxFn (arr) {
return Math.max(...arr)
}
module.exports = {
pingStr: pingStr,
switchBuffer: switchBuffer,
bufferToString: bufferToString,
numMaxFn: numMaxFn,
}
结束
- 总结保持细心和耐心,问题都是一步一步解决的,不要焦急~
- 小程序代码链接
- 喜欢就点赞下吧,鼓励作者,也方便需要时找到~