1. TCP 服务封装 (tcpService.js)
export class TcpService {
constructor() {
this.connections = uni.requireNativePlugin('jjc-tcpTools')
this.clients = new Map() // 存储客户端连接
this.servers = new Map() // 存储服务端实例
}
// 创建 TCP 服务端 (字符串模式)
createStringServer(port, onCreated, onData, onConnect) {
this.connections.createTcpServer(
port,
(res) => {
console.log('Server created:', res)
this.servers.set(port, { type: 'string' })
onCreated?.(res)
},
(res) => {
console.log('Received data:', res)
this._addClient(res.ip, res.port)
onData?.(res)
},
(res) => {
console.log('New connection:', res)
this._addClient(res.ip, res.port)
onConnect?.(res)
}
)
}
// 创建 TCP 服务端 (字节数组模式)
createByteServer(port, onCreated, onData) {
this.connections.createByteTcpServer(
port,
(res) => {
console.log('Byte server created:', res)
this.servers.set(port, { type: 'byte' })
onCreated?.(res)
},
(res) => {
console.log('Received byte data:', res)
this._addClient(res.ip, res.port)
onData?.(res)
}
)
}
// 创建 TCP 客户端 (字符串模式)
createStringClient(ip, port, onCreated, onData) {
this.connections.createTcpClient(
ip,
port,
(res) => {
console.log('Client connected:', res)
this.clients.set(`${ip}:${port}`, { type: 'string' })
onCreated?.(res)
},
(res) => {
console.log('Client received:', res)
onData?.(res)
}
)
}
// 创建 TCP 客户端 (字节数组模式)
createByteClient(ip, port, onCreated, onData) {
this.connections.createByteTcpClient(
ip,
port,
(res) => {
console.log('Byte client connected:', res)
this.clients.set(`${ip}:${port}`, { type: 'byte' })
onCreated?.(res)
},
(res) => {
console.log('Byte client received:', res)
onData?.(res)
}
)
}
// 发送数据 (服务端群发字符串)
sendToAllClients(port, message, callback) {
this.connections.sendTcpMSG2Client(
port,
message,
(res) => {
console.log('Send to all:', res)
callback?.(res)
}
)
}
// 发送数据 (服务端指定客户端发字符串)
sendToClients(port, clients, message, callback) {
this.connections.sendTcpMSG2Clients(
port,
JSON.stringify(clients),
message,
(res) => {
console.log('Send to clients:', res)
callback?.(res)
}
)
}
// 发送数据 (客户端发字符串)
sendToServer(ip, port, message, callback) {
this.connections.sendTcpMSG2Server(
ip,
port,
message,
(res) => {
console.log('Send to server:', res)
callback?.(res)
}
)
}
// 发送字节数据 (服务端群发)
sendBytesToAllClients(port, byteArray, callback) {
this.connections.sendTcpByteMSG2Client(
port,
byteArray,
(res) => {
console.log('Send bytes to all:', res)
callback?.(res)
}
)
}
// 发送字节数据 (服务端指定客户端发)
sendBytesToClients(port, clients, byteArray, callback) {
this.connections.sendTcpByteMSG2Clients(
port,
JSON.stringify(clients),
byteArray,
(res) => {
console.log('Send bytes to clients:', res)
callback?.(res)
}
)
}
// 发送字节数据 (客户端发)
sendBytesToServer(ip, port, byteArray, callback) {
this.connections.sendTcpMSGByte2Server(
ip,
port,
byteArray,
(res) => {
console.log('Send bytes to server:', res)
callback?.(res)
}
)
}
// 设置客户端重连时间
setReconnectTime(ip, port, interval, callback) {
this.connections.setReConnectTime(
ip,
port,
interval,
(res) => {
console.log('Set reconnect time:', res)
callback?.(res)
}
)
}
// 关闭客户端
closeClient(ip, port, callback) {
this.connections.closeTcpClient(
ip,
port,
(res) => {
console.log('Client closed:', res)
this.clients.delete(`${ip}:${port}`)
callback?.(res)
}
)
}
// 关闭服务端
closeServer(port, callback) {
this.connections.closeTcpServer(
port,
(res) => {
console.log('Server closed:', res)
this.servers.delete(port)
callback?.(res)
}
)
}
// 私有方法:添加客户端记录
_addClient(ip, port) {
const key = `${ip}:${port}`
if (!this.clients.has(key)) {
this.clients.set(key, { ip, port })
}
}
}
// 单例模式导出
export const tcpService = new TcpService()
2. CK协议适配器 (ckProtocol.js)
import { tcpService } from './tcpService'
import { crc16 } from './crc16'
// 命令类型枚举
const CMD_TYPE = {
LOCK_CONTROL: 0x01,
// ...其他命令类型
}
export class CKProtocol {
constructor() {
this.cabinetNo = 0 // 默认柜号
this.serialNo = 0 // 序列号计数器
}
// 初始化连接
init(ip, port = 5460) {
this.serverIp = ip
this.serverPort = port
// 创建字节数组模式的客户端连接
tcpService.createByteClient(
ip,
port,
(res) => {
console.log('Connected to CK device:', res)
this.isConnected = res.result === 'success'
},
(res) => {
this.handleDeviceResponse(res)
}
)
}
// 构建协议帧
buildFrame(cmdType, cmdTag, data = null) {
const magic = 0x1799 // 上位机→主板通信
const dataLength = data ? data.length : 0
const buffer = new ArrayBuffer(11 + dataLength)
const view = new DataView(buffer)
// 填充帧头
view.setUint16(0, magic, false)
view.setUint8(2, this.cabinetNo)
view.setUint32(3, this.serialNo++, false)
view.setUint8(7, cmdType)
view.setUint8(8, cmdTag)
view.setUint16(9, dataLength, false)
// 填充数据区
if (data && dataLength > 0) {
for (let i = 0; i < dataLength; i++) {
view.setUint8(11 + i, data[i])
}
}
// 计算CRC16
const crc = crc16(new Uint8Array(buffer.slice(0, 11 + dataLength)))
// 创建最终字节数组
const fullFrame = new Uint8Array(13 + dataLength)
fullFrame.set(new Uint8Array(buffer), 0)
fullFrame.set([crc >> 8, crc & 0xFF], 11 + dataLength)
return fullFrame
}
// 发送命令
sendCommand(cmdType, cmdTag, data = null) {
if (!this.isConnected) {
console.error('Not connected to device')
return false
}
const frame = this.buildFrame(cmdType, cmdTag, data)
tcpService.sendBytesToServer(
this.serverIp,
this.serverPort,
Array.from(frame),
(res) => {
console.log('Command sent:', res)
}
)
return true
}
// 处理设备响应
handleDeviceResponse(response) {
try {
// 将响应数据转换为Uint8Array
const data = new Uint8Array(response.msg.split(',').map(Number))
// 解析响应帧
if (data.length < 12) {
throw new Error('Invalid response length')
}
const view = new DataView(data.buffer)
const magic = view.getUint16(0, false)
const cabinetNo = view.getUint8(2)
const serialNo = view.getUint32(3, false)
const cmdType = view.getUint8(7)
const cmdTag = view.getUint8(8)
const result = view.getUint8(9)
const dataLength = view.getUint16(10, false)
// 验证CRC
const receivedCrc = view.getUint16(12 + dataLength, false)
const calculatedCrc = crc16(data.slice(0, 12 + dataLength))
if (receivedCrc !== calculatedCrc) {
throw new Error('CRC check failed')
}
// 提取数据区
let responseData = null
if (dataLength > 0) {
responseData = data.slice(12, 12 + dataLength)
}
// 返回解析结果
return {
magic,
cabinetNo,
serialNo,
cmdType,
cmdTag,
result,
dataLength,
data: responseData
}
} catch (error) {
console.error('Failed to parse device response:', error)
return null
}
}
// 锁控命令
controlLock(lockNo, action) {
const tagMap = {
open: 0x01,
close: 0x03,
status: 0x02,
openAll: 0x06
}
const tag = tagMap[action]
if (tag === undefined) return false
const data = action === 'openAll' ? null : new Uint8Array([lockNo])
return this.sendCommand(CMD_TYPE.LOCK_CONTROL, tag, data)
}
// 查询设备信息
queryDeviceInfo(infoType) {
const tagMap = {
mac: 0x01,
hardwareVersion: 0x02,
softwareVersion: 0x03,
firmwareTime: 0x04,
uptime: 0x05
}
const tag = tagMap[infoType]
if (tag === undefined) return false
return this.sendCommand(CMD_TYPE.QUERY_INFO, tag)
}
// 其他协议命令...
}
// 单例模式导出
export const ckProtocol = new CKProtocol()
3. Vue组件中使用 (DeviceControl.vue)
<template>
<view class="device-control">
<view class="connection-section">
<input v-model="serverIp" placeholder="设备IP" />
<input v-model="serverPort" placeholder="端口号" type="number" />
<button @click="connectDevice" :disabled="isConnected">连接</button>
<button @click="disconnectDevice" :disabled="!isConnected">断开</button>
</view>
<view class="command-section">
<view class="command-group">
<text class="group-title">锁控制</text>
<button @click="openLock(1)">开锁1</button>
<button @click="closeLock(1)">关锁1</button>
<button @click="openAllLocks">开所有锁</button>
</view>
<view class="command-group">
<text class="group-title">设备查询</text>
<button @click="queryMac">查询MAC</button>
<button @click="queryVersion">查询版本</button>
</view>
</view>
<view class="log-section">
<text class="section-title">通信日志</text>
<scroll-view class="log-content" scroll-y>
<view v-for="(log, index) in logs" :key="index" class="log-item">
{{ log }}
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { ckProtocol } from '@/api/ckProtocol'
const serverIp = ref('192.168.1.100')
const serverPort = ref('5460')
const isConnected = ref(false)
const logs = ref([])
// 连接设备
const connectDevice = () => {
addLog(`正在连接 ${serverIp.value}:${serverPort.value}...`)
ckProtocol.init(serverIp.value, parseInt(serverPort.value))
// 模拟连接成功 (实际应该通过回调事件)
setTimeout(() => {
isConnected.value = true
addLog('连接成功')
}, 1000)
}
// 断开连接
const disconnectDevice = () => {
tcpService.closeClient(serverIp.value, serverPort.value, (res) => {
isConnected.value = false
addLog('已断开连接')
})
}
// 锁控制命令
const openLock = (lockNo) => {
addLog(`发送开锁命令: 锁${lockNo}`)
ckProtocol.controlLock(lockNo, 'open')
}
const closeLock = (lockNo) => {
addLog(`发送关锁命令: 锁${lockNo}`)
ckProtocol.controlLock(lockNo, 'close')
}
const openAllLocks = () => {
addLog('发送开所有锁命令')
ckProtocol.controlLock(null, 'openAll')
}
// 查询命令
const queryMac = () => {
addLog('发送查询MAC地址命令')
ckProtocol.queryDeviceInfo('mac')
}
const queryVersion = () => {
addLog('发送查询版本命令')
ckProtocol.queryDeviceInfo('softwareVersion')
}
// 添加日志
const addLog = (message) => {
const timestamp = new Date().toLocaleTimeString()
logs.value.unshift(`[${timestamp}] ${message}`)
if (logs.value.length > 100) {
logs.value.pop()
}
}
</script>
<style>
.device-control {
padding: 20px;
}
.connection-section {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
.connection-section input {
flex: 1;
min-width: 120px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.connection-section button {
min-width: 80px;
}
.command-section {
margin-bottom: 20px;
}
.command-group {
margin-bottom: 15px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 8px;
}
.group-title {
display: block;
font-weight: bold;
margin-bottom: 8px;
}
.command-group button {
margin-right: 10px;
margin-bottom: 8px;
}
.log-section {
margin-top: 20px;
}
.section-title {
font-weight: bold;
margin-bottom: 8px;
}
.log-content {
height: 200px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.log-item {
margin-bottom: 4px;
padding-bottom: 4px;
border-bottom: 1px solid #e0e0e0;
}
</style>
4. CRC16工具 (crc16.js)
// CRC-16/XMODEM (x16 + x12 + x5 + 1)
export function crc16(data) {
let crc = 0x0000
for (let i = 0; i < data.length; i++) {
crc ^= data[i] << 8
for (let j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021
} else {
crc <<= 1
}
}
}
return crc & 0xFFFF
}
关键点说明
完全适配 jjc-tcpTools 插件:
- 严格按照插件提供的 API 进行封装
- 支持字符串和字节数组两种通信模式
- 实现了服务端和客户端的所有基本操作
CK 协议实现:
- 按照文档规范构建协议帧
- 实现了 CRC16 校验
- 封装了常用命令如锁控制、设备查询等
组件集成:
- 提供直观的设备控制界面
- 显示通信日志
- 管理连接状态
错误处理:
- 基本的错误检测和日志记录
- CRC 校验确保数据完整性
扩展性:
- 可以轻松添加更多协议命令
- 支持多设备管理