uniapp+vue3实现CK通信协议(基于jjc-tcpTools)

发布于:2025-06-07 ⋅ 阅读:(14) ⋅ 点赞:(0)

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
}

关键点说明

  1. 完全适配 jjc-tcpTools 插件

    • 严格按照插件提供的 API 进行封装
    • 支持字符串和字节数组两种通信模式
    • 实现了服务端和客户端的所有基本操作
  2. CK 协议实现

    • 按照文档规范构建协议帧
    • 实现了 CRC16 校验
    • 封装了常用命令如锁控制、设备查询等
  3. 组件集成

    • 提供直观的设备控制界面
    • 显示通信日志
    • 管理连接状态
  4. 错误处理

    • 基本的错误检测和日志记录
    • CRC 校验确保数据完整性
  5. 扩展性

    • 可以轻松添加更多协议命令
    • 支持多设备管理