Files
endo_an_2/pages/modbus/modbus.vue
T
2026-06-14 11:33:33 +08:00

559 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<!-- 通信模式切换 -->
<view style="padding-top: 60px;">
<uni-row :gutter="8">
<uni-col :span="4" :offset="1">
<uni-section title="通信模式" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="10">
<radio-group @change="onModeChange" style="display:flex;gap:20px;align-items:center;">
<label style="display:flex;align-items:center;">
<radio value="serial" :checked="mode === 'serial'" />串口直连
</label>
<label style="display:flex;align-items:center;">
<radio value="tcp" :checked="mode === 'tcp'" />网口转485
</label>
</radio-group>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="saveMode">
<text>保存模式</text>
</view>
</uni-col>
</uni-row>
</view>
<!-- RS485 配置根据模式切换 -->
<view :style="{ 'paddingTop': '20px' }">
<!-- 串口模式显示RS485串口配置 -->
<view v-if="mode === 'serial'">
<uni-row :gutter="8">
<uni-col :span="4" :offset="1">
<uni-section title="RS485" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="5">
<uni-data-select
placeholder="请选择串口"
v-model="rs485PathKey"
@change="rs485Pathchange"
:localdata="list"
:clear="false"
></uni-data-select>
</uni-col>
<uni-col :span="5">
<uni-data-select
placeholder="请选择波特率"
v-model="rs485Baudrate"
:localdata="baudrateList"
:clear="false"
></uni-data-select>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="rs485Connect" v-if="rs485State == false">
<text>连接</text>
</view>
<view style="color: red;" class="ant-btn ant-btn-lg" @click="rs485Disconnect" v-else>
<text>断开</text>
</view>
<view style="margin-left: 10px;color:red;" class="ant-btn ant-btn-lg" @click="rs485Save">
<text>保存</text>
</view>
</uni-col>
</uni-row>
<!-- RS485 指令发送 -->
<uni-row :gutter="12" style="margin-top: 10px;">
<uni-col :span="4" :offset="1">
<uni-section title="指令" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="8">
<view class="uni-form-item uni-column">
<input type="text" class="uni-input" v-model="cmd_485" placeholder="自定义指令" />
</view>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="send485Data">
<text>发送</text>
</view>
</uni-col>
</uni-row>
</view>
<!-- TCP模式显示TCP连接配置 -->
<view v-if="mode === 'tcp'">
<uni-row :gutter="8">
<uni-col :span="4" :offset="1">
<uni-section title="TCP连接" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="5">
<input type="text" class="uni-input" v-model="tcpHost" placeholder="IP地址" />
</uni-col>
<uni-col :span="3">
<input type="number" class="uni-input" v-model="tcpPort" placeholder="端口" />
</uni-col>
<uni-col :span="5">
<view class="ant-btn ant-btn-lg" @click="saveTcpConfig">
<text>保存参数</text>
</view>
</uni-col>
<uni-col :span="4">
<view class="ant-btn ant-btn-lg" @click="tcpConnect" v-if="tcpState == false">
<text>TCP连接</text>
</view>
<view style="color: red;" class="ant-btn ant-btn-lg" @click="tcpDisconnect" v-else>
<text>TCP断开</text>
</view>
</uni-col>
</uni-row>
<!-- TCP 指令发送 -->
<uni-row :gutter="12" style="margin-top: 10px;">
<uni-col :span="4" :offset="1">
<uni-section title="指令" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="8">
<view class="uni-form-item uni-column">
<input type="text" class="uni-input" v-model="cmd_tcp" placeholder="TCP自定义指令" />
</view>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="sendTcpData">
<text>发送</text>
</view>
</uni-col>
</uni-row>
</view>
</view>
<!-- RS232 串口配置一直保留 -->
<view :style="{ 'marginTop': '20px' }">
<uni-row :gutter="8">
<uni-col :span="4" :offset="1">
<uni-section title="RS232" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="5">
<uni-data-select
placeholder="请选择串口"
v-model="rs232PathKey"
@change="rs232Pathchange"
:localdata="list"
:clear="false"
></uni-data-select>
</uni-col>
<uni-col :span="5">
<uni-data-select
:clear="false"
placeholder="请选择波特率"
v-model="rs232Baudrate"
:localdata="baudrateList"
></uni-data-select>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="rs232Connect" v-if="rs232State == false">
<text>连接</text>
</view>
<view style="color: red;" class="ant-btn ant-btn-lg" @click="rs232Disconnect" v-else>
<text>断开</text>
</view>
<view style="margin-left: 10px;color:red;" class="ant-btn ant-btn-lg" @click="rs232Save">
<text>保存</text>
</view>
</uni-col>
</uni-row>
<!-- RS232 指令发送 -->
<uni-row :gutter="12" style="margin-top: 10px;">
<uni-col :span="4" :offset="1">
<uni-section title="指令" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="8">
<view class="uni-form-item uni-column">
<input type="text" class="uni-input" v-model="cmd_232" placeholder="自定义指令" />
</view>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="send232Data">
<text>发送</text>
</view>
</uni-col>
</uni-row>
</view>
<!-- 快捷控制 -->
<view :style="{ 'marginTop': '20px' }">
<uni-row :gutter="12">
<uni-col :span="4" :offset="1">
<uni-section title="控制" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="16">
<view class="ant-btn ant-btn-lg" @click="open(1)">
<text>开风机</text>
</view>
<view class="ant-btn ant-btn-lg" @click="open(2)">
<text>开照明</text>
</view>
<view class="ant-btn ant-btn-lg" @click="open(3)">
<text>开真空</text>
</view>
<view class="ant-btn ant-btn-lg" @click="open(4)">
<text>开紫外</text>
</view>
<view class="ant-btn ant-btn-lg" @click="open(5)">
<text>开等离子</text>
</view>
</uni-col>
</uni-row>
<uni-row :gutter="12">
<uni-col :span="16" :offset="5">
<view class="ant-btn ant-btn-lg" @click="close(1)">
<text>关风机</text>
</view>
<view class="ant-btn ant-btn-lg" @click="close(2)">
<text>关照明</text>
</view>
<view class="ant-btn ant-btn-lg" @click="close(3)">
<text>关真空</text>
</view>
<view class="ant-btn ant-btn-lg" @click="close(4)">
<text>关紫外</text>
</view>
<view class="ant-btn ant-btn-lg" @click="close(5)">
<text>关等离子</text>
</view>
</uni-col>
</uni-row>
</view>
<!-- 快捷读取 -->
<view :style="{ 'marginTop': '20px' }">
<uni-row :gutter="12">
<uni-col :span="4" :offset="1">
<uni-section title="读取" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="12">
<view class="ant-btn ant-btn-lg" @click="temp">
<text>温湿度</text>
</view>
<view class="ant-btn ant-btn-lg" @click="pressure">
<text>压差</text>
</view>
</uni-col>
</uni-row>
</view>
<!-- 接收数据展示 -->
<view :style="{ 'marginTop': '20px', 'paddingBottom': '40px' }">
<uni-row :gutter="12">
<uni-col :span="4" :offset="1">
<uni-section title="接收数据" titleColor="#fff"></uni-section>
</uni-col>
<uni-col :span="10">
<view class="receive-item">
<view v-for="(item, index) in received" :key="index">
{{ item }}
</view>
</view>
</uni-col>
<uni-col :span="6">
<view class="ant-btn ant-btn-lg" @click="clearReceived">
<text>清空</text>
</view>
</uni-col>
</uni-row>
</view>
</view>
</template>
<script>
import storage from '@/utils/storage.js'
import cmd from '@/utils/cmd.js'
import tcp485 from '@/utils/tcp485.js'
export default {
data() {
return {
mode: 'serial',
tcpHost: '',
tcpPort: '',
tcpState: false, // TCP连接状态
RS485: undefined,
received: [],
list: [],
baudrateList: [
{ value: 4800, text: '4800' },
{ value: 9600, text: '9600' },
{ value: 19200, text: '19200' },
{ value: 38400, text: '38400' },
{ value: 57600, text: '57600' },
{ value: 115200, text: '115200' },
],
rs485State: false,
rs485Path: '',
rs485PathKey: '',
rs485Baudrate: '',
RS232: undefined,
rs232State: false,
rs232Path: '',
rs232PathKey: '',
rs232Baudrate: '',
cmd_485: '010300000002C40B',
cmd_232: '',
cmd_tcp: '0306000100011828',
}
},
onLoad() {
this.mode = storage.get('rs485Mode') || 'serial'
// 分别读取串口配置和TCP配置
const rs485Tcp = storage.get('rs485Tcp') || {}
this.tcpHost = rs485Tcp.host || ''
this.tcpPort = rs485Tcp.port || ''
this.RS485 = getApp().globalData.RS485
this.RS232 = getApp().globalData.RS232
this.getDevice()
},
onUnload() {
this.tcpDisconnect()
if (this.rs232State) this.rs232Disconnect()
if (this.rs485State) this.rs485Disconnect()
},
methods: {
// ========== 模式切换 ==========
onModeChange(e) {
this.mode = e.detail.value
},
saveMode() {
storage.set('rs485Mode', this.mode)
uni.showToast({ title: '已保存,重启后生效', icon: 'none' })
},
saveTcpConfig() {
const rs485Tcp = storage.get('rs485Tcp') || {}
rs485Tcp.host = this.tcpHost
rs485Tcp.port = this.tcpPort
storage.set('rs485Tcp', rs485Tcp)
uni.showToast({ title: 'TCP参数已保存', icon: 'none' })
},
// ========== TCP连接控制 ==========
async tcpConnect() {
if (this.tcpState) return
const host = this.tcpHost || (storage.get('rs485Tcp') || {}).host
const port = this.tcpPort || (storage.get('rs485Tcp') || {}).port
if (!host || !port) {
uni.showToast({ title: '请先填写IP和端口', icon: 'none' })
return
}
tcp485.setHost(host, port)
try {
await tcp485.open()
this.tcpState = true
// 注册数据回调,收到数据展示在页面
tcp485.onStartAutoReadData((hexStr) => {
console.log('autoread:', hexStr)
this.received.unshift(hexStr)
})
uni.showToast({ title: 'TCP连接成功', icon: 'success' })
} catch (err) {
console.error('[modbus] TCP连接失败', err)
uni.showToast({ title: 'TCP连接失败', icon: 'none' })
}
},
tcpDisconnect() {
if (!this.tcpState) return
tcp485.stopReadPortData()
tcp485.close()
this.tcpState = false
uni.showToast({ title: 'TCP已断开', icon: 'none' })
},
// ========== RS485串口 ==========
getDevice() {
const list = this.RS485.getAllDevices()
if (list.length) {
this.list = list.map((i, key) => {
return { value: key, text: i }
})
}
// 自动连接已保存的串口(从rs485读取,保持原有存储不变)
let rs485Conf = storage.get('rs485')
if (rs485Conf && rs485Conf.path) {
let cur = this.list.find(i => i.text == rs485Conf.path)
if (cur) {
this.rs485PathKey = cur.value
this.rs485Path = rs485Conf.path
this.rs485Baudrate = rs485Conf.baudrate
this.rs485Connect()
}
}
let rs232Conf = storage.get('rs232')
if (rs232Conf && rs232Conf.path) {
let cur = this.list.find(i => i.text == rs232Conf.path)
if (cur) {
this.rs232PathKey = cur.value
this.rs232Path = rs232Conf.path
this.rs232Baudrate = rs232Conf.baudrate
this.rs232Connect()
}
}
},
rs485Pathchange(e) {
this.rs485Path = this.list[e].text
},
rs232Pathchange(e) {
this.rs232Path = this.list[e].text
},
rs485Save() {
storage.set('rs485', { path: this.rs485Path, baudrate: this.rs485Baudrate })
uni.showToast({ title: '保存成功', icon: 'none' })
},
rs485Connect() {
this.RS485.setPath(this.rs485Path)
this.RS485.setBaudrate(this.rs485Baudrate)
this.rs485State = this.RS485.open()
if (this.rs485State) {
this.RS485.onStartAutoReadData((res) => {
this.received.unshift(this.RS485.byte2HexString(res))
})
}
},
rs485Disconnect() {
this.RS485.stopReadPortData()
this.RS485.close()
this.rs485State = false
},
rs232Connect() {
this.RS232.setPath(this.rs232Path)
this.RS232.setBaudrate(this.rs232Baudrate)
this.rs232State = this.RS232.open()
if (this.rs232State) {
this.RS232.onStartAutoReadData((res) => {
this.received.unshift(this.RS232.byte2HexString(res))
})
}
},
rs232Disconnect() {
this.RS232.stopReadPortData()
this.RS232.close()
this.rs232State = false
},
rs232Save() {
storage.set('rs232', { path: this.rs232Path, baudrate: this.rs232Baudrate })
uni.showToast({ title: '保存成功', icon: 'none' })
},
send485Data() {
this.RS485.sendDataString(this.cmd_485)
},
send232Data() {
this.RS232.sendDataString(this.cmd_232)
},
sendTcpData() {
if (!this.tcpState) {
uni.showToast({ title: '请先连接TCP', icon: 'none' })
return
}
tcp485.sendDataString(this.cmd_tcp)
},
clearReceived() {
this.received = []
},
// ========== 快捷控制 ==========
open(key) {
switch(key) {
case 1:
this.RS232.sendDataString('5AA5EE0101')
break
case 2:
this.RS232.sendDataString('5AA5EE0201')
break
case 3:
this.RS232.sendDataString('5AA5EE0301')
break
case 4:
this.RS232.sendDataString('5AA5EE0401')
break
case 5:
this.RS232.sendDataString('5AA5EE0501')
break
default:
break
}
},
close(key) {
switch(key) {
case 1:
this.RS232.sendDataString('5AA5EE0100')
break
case 2:
this.RS232.sendDataString('5AA5EE0200')
break
case 3:
this.RS232.sendDataString('5AA5EE0300')
break
case 4:
this.RS232.sendDataString('5AA5EE0400')
break
case 5:
this.RS232.sendDataString('5AA5EE0500')
break
default:
break
}
},
temp() {
this.RS485.sendDataString('020300000002C438')
},
pressure() {
this.RS485.sendDataString('01030001000295CB')
},
}
}
</script>
<style scoped>
.page{
color: #fff;
text-align: left;
}
:deep(.uni-select){
min-height: 40px;
color: rgba(0, 0, 0, .85);
}
.uni-input{
height: 40px;
border: 1px solid #d9d9d9;
background-color: #fff;
color: rgba(0, 0, 0, .85);
text-align: left;
padding: 0 6px;
white-space: 6px;
}
:deep(.uni-section){
background-color: transparent;
}
.page :deep(.uni-section .uni-section-header__content){
line-height: 20px;
font-weight: 800;
text-align: right;
}
:deep(.uni-section .uni-section-header){
padding: 10px;
}
.ant-btn:hover,
.ant-btn:focus,
.ant-btn:active{
color: rgba(0, 0, 0, 0.85);
}
.receive-item{
padding-left: 10px;
height: 400px;
overflow-y: auto;
}
</style>