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

news2025/6/7 3:08:11

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. 扩展性

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2402392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

兰亭妙微 | 医疗软件的界面设计能有多专业?

从医疗影像系统到手术机器人控制界面&#xff0c;从便携式病原体检测设备到多平台协同操作系统&#xff0c;兰亭妙微为众多医疗设备研发企业&#xff0c;打造了兼具专业性与可用性的交互界面方案。 我们不仅做设计&#xff0c;更深入理解医疗场景的实际需求&#xff1a; 对精…

前端原生构建交互式进度步骤组件(Progress Steps)

在现代网页设计中&#xff0c;进度步骤&#xff08;Progress Steps&#xff09; 是一种常见的 UI 模式&#xff0c;常用于引导用户完成注册流程、多步表单、教程或任何需要分步骤操作的场景。本文将带你从零开始构建一个美观且功能完整的 “进度步骤”组件&#xff0c;并详细讲…

【基于阿里云搭建数据仓库(离线)】Data Studio创建资源与函数

Data Studio支持在您的数据分析代码中引用自定义的资源和函数&#xff08;支持MaxCompute、EMR、CDH、Flink&#xff09;&#xff0c;您需要先创建或上传资源、函数至目标工作空间&#xff0c;上传后才可在该工作空间的任务中使用。您可参考本文了解如何使用DataWorks可视化方式…

web3-以太坊智能合约基础(理解智能合约Solidity)

以太坊智能合约基础&#xff08;理解智能合约/Solidity&#xff09; 无需编程经验&#xff0c;也可以帮助你了解Solidity独特的部分&#xff1b;如果本身就有相应的编程经验如java&#xff0c;python等那么学起来也会非常的轻松 一、Solidity和EVM字节码 实际上以太坊链上储存…

【C++项目】负载均衡在线OJ系统-2

文章目录 oj_server模块编写oj_server框架的搭建-oj_server/oj_server.cpp 路由框架 oj_model模块编写题目信息设置v1.文件版本-common/util.hpp boost库spilt函数的使用-oj_server/oj_model_file.hpp 文件版本model编写v2.mysql数据库版本1.mysql创建授权用户、建库建表录入操…

GC1809:高性能24bit/192kHz音频接收芯片解析

1. 芯片概述 GC1809 是数字音频接收芯片&#xff0c;支持IEC60958、S/PDIF、AES3等协议&#xff0c;集成8选1输入切换、低抖动时钟恢复和24bit DAC&#xff0c;适用于家庭影院、汽车音响等高保真场景。 核心特性 高精度&#xff1a;24bit分辨率&#xff0c;动态范围105dB&…

2025年06月05日Github流行趋势

项目名称&#xff1a;onlook 项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;16165今日star数&#xff1a;1757项目维护者&#xff1a;Kitenite, drfarrell, spartan-vutrannguyen, apps/devin-ai-integrat…

基于BI PaaS架构的衡石HENGSHI SENSE平台技术解析:重塑企业级数据分析基座

在数据驱动决策的时代&#xff0c;传统BI工具日益显露出扩展性弱、灵活性差、资源利用率低等痛点。衡石科技推出的HENGSHI SENSE平台&#xff0c;创新性地采用BI PaaS&#xff08;平台即服务&#xff09;架构&#xff0c;为企业构建了一个强大、开放、可扩展的数据分析基础设施…

【R语言编程绘图-plotly】

安装与加载 在R中使用plotly库前需要安装并加载。安装可以通过CRAN进行&#xff0c;使用install.packages()函数。加载库使用library()函数。 install.packages("plotly") library(plotly)测试库文件安装情况 # 安装并加载必要的包 if (!requireNamespace("p…

通信刚需,AI联手ethernet/ip转profinet网关打通工业技术难关

工业人工智能&#xff1a;食品和饮料制造商的实际用例通信刚需 了解食品饮料制造商如何利用人工智能克服业务挑战 食品和饮料制造商正面临劳动力短缺、需求快速变化、运营复杂性加剧以及通胀压力等挑战。如今&#xff0c;生产商比以往任何时候都更需要以更少的投入实现更高的…

JavaEE->多线程:定时器

定时器 约定一个时间&#xff0c;时间到了&#xff0c;执行某个代码逻辑&#xff08;进行网络通信时常见&#xff09; 客户端给服务器发送请求 之后就需要等待 服务器的响应&#xff0c;客户端不可能无限的等&#xff0c;需要一个最大的期限。这里“等待的最大时间”可以用定时…

<el-table>构建树形结构

最佳实践 el-table实现树形结构主要依靠row-key和tree-props来实现的。 &#x1f4ab; 无论是el-table实现的树形结构还是el-tree组件都是绑定的树形结构的数据&#xff0c;因此如果数据是扁平的话&#xff0c;需要进行树化。 代码 <template><div><el-table:d…

linux——磁盘和文件系统管理

1、磁盘基础简述 1.1 硬盘基础知识 硬盘&#xff08;Hard Disk Drive&#xff0c;简称 HDD&#xff09;是计算机常用的存储设备之一. p如果从存储数据的介质上来区分&#xff0c;硬盘可分为机械硬盘&#xff08;Hard Disk Drive, HDD&#xff09;和固态硬盘&#xff08;Soli…

云原生 DevOps 实践路线:构建敏捷、高效、可观测的交付体系

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、引言&#xff1a;DevOps 与云原生的深度融合 在传统软件工程范式下&#xff0c;开发与运维之间存在天然的壁垒。开发希望尽快…

gateway 网关 路由新增 (已亲测)

问题&#xff1a; 前端通过gateway调用后端接口&#xff0c;路由转发失败&#xff0c;提示404 not found 排查&#xff1a; 使用 { "href":"/actuator/gateway/routes", "methods":[ "POST", "GET" ] } 命令查看路由列表&a…

Python 训练营打卡 Day 33-神经网络

简单神经网络的流程 1.数据预处理&#xff08;归一化、转换成张量&#xff09; 2.模型的定义 继承nn.Module类 定义每一个层 定义前向传播流程 3.定义损失函数和优化器 4.定义训练过程 5.可视化loss过程 预处理补充&#xff1a; 分类任务中&#xff0c;若标签是整…

如何有效删除 iPhone 上的所有内容?

“在出售我的 iPhone 之前&#xff0c;我该如何清除它&#xff1f;我担心如果我卖掉它&#xff0c;有人可能会从我的 iPhone 中恢复我的信息。” 升级到新 iPhone 后&#xff0c;你如何处理旧 iPhone&#xff1f;你打算出售、以旧换新还是捐赠&#xff1f;无论你选择哪一款&am…

AI大模型学习三十二、飞桨AI studio 部署 免费Qwen3-235B与Qwen3-32B,并导入dify应用

一、说明 ‌Qwen3-235B 和 Qwen3-32B 的主要区别在于它们的参数规模和应用场景。‌ 参数规模 ‌Qwen3-235B‌&#xff1a;总参数量为2350亿&#xff0c;激活参数量为220亿‌。‌Qwen3-32B‌&#xff1a;总参数量为320亿‌。 应用场景 ‌Qwen3-235B‌&#xff1a;作为旗舰模型&a…

操作系统中的设备管理,Linux下的I/O

1. I/O软件分层 I/O 层次结构分为五层&#xff1a; 用户层 I/O 软件设备独立性软件设备驱动程序中断处理程序硬件 其中&#xff0c;设备独立性软件、设备驱动程序、中断处理程序属于操作系统的内核部分&#xff0c;即“I/O 系统”&#xff0c;或称“I/O 核心子系统”。 2.用…

LabVIEW与Modbus/TCP温湿度监控系统

基于LabVIEW 开发平台与 Modbus/TCP 通信协议&#xff0c;设计一套适用于实验室环境的温湿度数据采集监控系统。通过上位机与高精度温湿度采集设备的远程通信&#xff0c;实现多设备温湿度数据的实时采集、存储、分析及报警功能&#xff0c;解决传统人工采集效率低、环境适应性…