You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:基于Flutter开发Web平台Modbus TCP程序的实现方案

Flutter Web 开发 Modbus TCP 程序的解决方案

问题原因

你使用的modbus_client_tcp这类库依赖 Dart 的dart:io模块实现 TCP 连接,但 Flutter Web 运行在浏览器沙箱环境中,受浏览器安全限制,无法直接调用原生 TCP Socket API,仅支持 WebSocket 通信,因此在 Chrome 中会出现连接失败的情况。

可行解决方案

目前最通用的解决方式是搭建WebSocket 转 Modbus TCP 的代理服务器,让 Flutter Web 通过 WebSocket 与代理通信,代理再与 Modbus 设备建立 TCP 连接并转发数据。

1. 搭建代理服务器(Node.js 示例)

用 Node.js 快速实现一个轻量代理,依赖ws(WebSocket 库)和modbus-serial(Modbus 客户端库):

const WebSocket = require('ws');
const ModbusRTU = require('modbus-serial');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  const client = new ModbusRTU();
  
  ws.on('message', async (data) => {
    try {
      const { ip, port, unitId, registerAddr } = JSON.parse(data);
      
      // 连接Modbus设备
      if (!client.isOpen) {
        await client.connectTCP(ip, { port: port });
        client.setID(unitId);
      }
      
      // 读取保持寄存器(单次读1个)
      const response = await client.readHoldingRegisters(registerAddr, 1);
      ws.send(JSON.stringify({ success: true, value: response.data[0] }));
    } catch (err) {
      ws.send(JSON.stringify({ success: false, error: err.message }));
      if (client.isOpen) client.close();
    }
  });
  
  ws.on('close', () => {
    if (client.isOpen) client.close();
  });
});

console.log('WebSocket代理服务器运行在 ws://localhost:8080');

安装依赖:npm install ws modbus-serial,运行:node proxy.js

2. Flutter Web 端代码示例

修改原有代码,改用 WebSocket 与代理服务器通信:

import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:html' as html;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _ipController = TextEditingController(text: '192.168.1.100');
  final _portController = TextEditingController(text: '502');
  final _registerController = TextEditingController(text: '4098');
  final _unitIdController = TextEditingController(text: '15');
  String _result = '';

  Future<void> _readHoldingRegister() async {
    setState(() => _result = '正在连接...');
    try {
      // 连接代理服务器的WebSocket
      final ws = html.WebSocket('ws://localhost:8080');
      
      ws.onOpen.listen((_) {
        // 发送请求参数到代理(注意寄存器地址转换:4xxxx类型需减去40001)
        ws.send(jsonEncode({
          'ip': _ipController.text,
          'port': int.parse(_portController.text),
          'unitId': int.parse(_unitIdController.text),
          'registerAddr': int.parse(_registerController.text) - 40001,
        }));
      });
      
      ws.onMessage.listen((event) {
        final res = jsonDecode(event.data);
        setState(() {
          if (res['success']) {
            // 转换为实际值(对应原代码的multiplier:0.1)
            final value = res['value'] * 0.1;
            _result = '寄存器值: ${value.toStringAsFixed(1)} V';
          } else {
            _result = '错误: ${res['error']}';
          }
        });
        ws.close();
      });
      
      ws.onError.listen((error) {
        setState(() => _result = '连接代理失败: $error');
        ws.close();
      });
      
      ws.onClose.listen((_) => setState(() => _result += '\n连接已关闭'));
      
    } catch (e) {
      setState(() => _result = '异常: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Modbus TCP Web 读取器')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(controller: _ipController, decoration: const InputDecoration(labelText: '设备IP')),
            TextField(controller: _portController, decoration: const InputDecoration(labelText: '设备端口'), keyboardType: TextInputType.number),
            TextField(controller: _unitIdController, decoration: const InputDecoration(labelText: 'Unit ID'), keyboardType: TextInputType.number),
            TextField(controller: _registerController, decoration: const InputDecoration(labelText: '寄存器地址'), keyboardType: TextInputType.number),
            const SizedBox(height: 20),
            ElevatedButton(onPressed: _readHoldingRegister, child: const Text('读取寄存器')),
            const SizedBox(height: 20),
            Text(_result),
          ],
        ),
      ),
    );
  }
}

注意事项

  • 代理服务器需与 Modbus 设备处于同一网络,或具备访问设备的网络权限
  • 生产环境需为代理服务器添加身份验证、请求限流、异常重试等增强逻辑
  • 若你的 Modbus 设备支持直接 WebSocket 通信(部分工业设备支持 Modbus over WebSocket),可跳过代理直接连接

内容的提问来源于stack exchange,提问作者Camiu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:44:50