"# Python Modbus \u5e93\u5b8c\u5168\u6307\u5357\uff1a\u4ece\u5165\u95e8\u5230\u7cbe\u901a\n\n> 2026-03-01 | 2026-03-01\n> https:\/\/www.modbus.cn\/en\/40579.html\n\n**Modbus\u7f16\u7a0b\u5f00\u53d1**\n\n---\n\nPython Modbus\u5e93\u5b8c\u5168\u6307\u5357\uff1a\u4ece\u5165\u95e8\u5230\u7cbe\u901a\n\n## \u5f15\u8a00\uff1a\u4e3a\u4ec0\u4e48\u9009\u62e9Python\u8fdb\u884cModbus\u5f00\u53d1\uff1f\n\n\u5728\u5de5\u4e1a\u7269\u8054\u7f51\u548c\u81ea\u52a8\u5316\u9886\u57df\uff0cPython\u51ed\u501f\u5176\u7b80\u6d01\u7684\u8bed\u6cd5\u3001\u4e30\u5bcc\u7684\u5e93\u751f\u6001\u7cfb\u7edf\u548c\u5f3a\u5927\u7684\u793e\u533a\u652f\u6301\uff0c\u5df2\u7ecf\u6210\u4e3a\u5f00\u53d1\u8005\u7684\u9996\u9009\u8bed\u8a00\u4e4b\u4e00\u3002\u5bf9\u4e8eModbus\u534f\u8bae\u5f00\u53d1\uff0cPython\u63d0\u4f9b\u4e86\u591a\u4e2a\u6210\u719f\u7684\u5f00\u6e90\u5e93\uff0c\u80fd\u591f\u6ee1\u8db3\u4ece\u7b80\u5355\u8bbe\u5907\u901a\u4fe1\u5230\u590d\u6742\u5de5\u4e1a\u7cfb\u7edf\u96c6\u6210\u7684\u5404\u79cd\u9700\u6c42\u3002\n\n\u672c\u6587\u5c06\u5168\u9762\u4ecb\u7ecdPython\u4e2d\u4e3b\u6d41\u7684Modbus\u5e93\uff0c\u5305\u62ecpymodbus\u3001minimalmodbus\u3001uModbus\u7b49\uff0c\u901a\u8fc7\u5b9e\u9645\u4ee3\u7801\u793a\u4f8b\u548c\u6700\u4f73\u5b9e\u8df5\uff0c\u5e2e\u52a9\u60a8\u5feb\u901f\u638c\u63e1Python Modbus\u5f00\u53d1\u7684\u6838\u5fc3\u6280\u80fd\u3002\n\n## \u4e00\u3001\u4e3b\u6d41Python Modbus\u5e93\u5bf9\u6bd4\n\n### 1.1 pymodbus\uff1a\u529f\u80fd\u6700\u5168\u9762\u7684\u9009\u62e9\n\n**\u7279\u70b9\uff1a**\n- \u652f\u6301Modbus TCP\u3001RTU\u3001ASCII\u7b49\u591a\u79cd\u534f\u8bae\n- \u540c\u65f6\u652f\u6301\u5ba2\u6237\u7aef\uff08\u4e3b\u7ad9\uff09\u548c\u670d\u52a1\u5668\uff08\u4ece\u7ad9\uff09\u6a21\u5f0f\n- \u5f02\u6b65\/\u540c\u6b65\u4e24\u79cd\u7f16\u7a0b\u6a21\u5f0f\n- \u6d3b\u8dc3\u7684\u793e\u533a\u548c\u6301\u7eed\u66f4\u65b0\n\n**\u9002\u7528\u573a\u666f\uff1a**\n- \u590d\u6742\u7684\u5de5\u4e1a\u63a7\u5236\u7cfb\u7edf\n- \u9700\u8981\u540c\u65f6\u4f5c\u4e3a\u4e3b\u7ad9\u548c\u4ece\u7ad9\u7684\u573a\u666f\n- \u9ad8\u6027\u80fd\u8981\u6c42\u7684\u5e94\u7528\n\n### 1.2 minimalmodbus\uff1a\u8f7b\u91cf\u7ea7\u89e3\u51b3\u65b9\u6848\n\n**\u7279\u70b9\uff1a**\n- \u4ee3\u7801\u7b80\u6d01\uff0c\u6613\u4e8e\u5b66\u4e60\u548c\u4f7f\u7528\n- \u4e13\u6ce8\u4e8eModbus RTU\u534f\u8bae\n- \u4f9d\u8d56\u5c11\uff0c\u90e8\u7f72\u7b80\u5355\n- \u9002\u5408\u5d4c\u5165\u5f0f\u7cfb\u7edf\u548c\u8d44\u6e90\u53d7\u9650\u73af\u5883\n\n**\u9002\u7528\u573a\u666f\uff1a**\n- \u7b80\u5355\u7684\u8bbe\u5907\u901a\u4fe1\n- \u5d4c\u5165\u5f0f\u7cfb\u7edf\u548c\u5355\u677f\u8ba1\u7b97\u673a\n- \u5feb\u901f\u539f\u578b\u5f00\u53d1\n\n### 1.3 uModbus\uff1a\u73b0\u4ee3\u5316\u8bbe\u8ba1\n\n**\u7279\u70b9\uff1a**\n- \u73b0\u4ee3\u5316\u7684API\u8bbe\u8ba1\n- \u652f\u6301\u5f02\u6b65\u7f16\u7a0b\n- \u826f\u597d\u7684\u7c7b\u578b\u63d0\u793a\n- \u6a21\u5757\u5316\u67b6\u6784\n\n**\u9002\u7528\u573a\u666f\uff1a**\n- \u9700\u8981\u5f02\u6b65\u5904\u7406\u7684\u5e94\u7528\n- \u5927\u578b\u9879\u76ee\u5f00\u53d1\n- \u9700\u8981\u826f\u597d\u4ee3\u7801\u7ef4\u62a4\u6027\u7684\u573a\u666f\n\n## \u4e8c\u3001pymodbus\u6df1\u5ea6\u89e3\u6790\n\n### 2.1 \u5b89\u88c5\u548c\u57fa\u7840\u914d\u7f6e\n\n```\n# \u5b89\u88c5pymodbus\npip install pymodbus\n\n# \u6216\u8005\u5b89\u88c5\u5305\u542b\u5f02\u6b65\u652f\u6301\u7684\u7248\u672c\npip install pymodbus[asyncio]\n\n```\n\n### 2.2 Modbus TCP\u5ba2\u6237\u7aef\u793a\u4f8b\n\n```\nfrom pymodbus.client import ModbusTcpClient\n\n# \u521b\u5efaTCP\u5ba2\u6237\u7aef\nclient = ModbusTcpClient(\n    host='192.168.1.100',  # \u8bbe\u5907IP\u5730\u5740\n    port=502,              # Modbus TCP\u7aef\u53e3\n    timeout=3,             # \u8d85\u65f6\u65f6\u95f4\uff08\u79d2\uff09\n    retries=3              # \u91cd\u8bd5\u6b21\u6570\n)\n\ntry:\n    # \u8fde\u63a5\u8bbe\u5907\n    if client.connect():\n        print(\"\u6210\u529f\u8fde\u63a5\u5230Modbus\u8bbe\u5907\")\n\n        # \u8bfb\u53d6\u4fdd\u6301\u5bc4\u5b58\u5668\uff08\u5730\u57400\uff0c\u6570\u91cf10\uff09\n        result = client.read_holding_registers(address=0, count=10, slave=1)\n        if not result.isError():\n            print(f\"\u8bfb\u53d6\u5230\u7684\u5bc4\u5b58\u5668\u503c: {result.registers}\")\n        else:\n            print(f\"\u8bfb\u53d6\u5931\u8d25: {result}\")\n\n        # \u5199\u5165\u5355\u4e2a\u4fdd\u6301\u5bc4\u5b58\u5668\uff08\u5730\u57405\uff0c\u503c1234\uff09\n        result = client.write_register(address=5, value=1234, slave=1)\n        if not result.isError():\n            print(\"\u5bc4\u5b58\u5668\u5199\u5165\u6210\u529f\")\n\n        # \u8bfb\u53d6\u7ebf\u5708\u72b6\u6001\uff08\u5730\u57400\uff0c\u6570\u91cf8\uff09\n        result = client.read_coils(address=0, count=8, slave=1)\n        if not result.isError():\n            print(f\"\u7ebf\u5708\u72b6\u6001: {result.bits}\")\n\nfinally:\n    # \u5173\u95ed\u8fde\u63a5\n    client.close()\n\n```\n\n### 2.3 Modbus RTU\u5ba2\u6237\u7aef\u793a\u4f8b\n\n```\nfrom pymodbus.client import ModbusSerialClient\n\n# \u521b\u5efaRTU\u5ba2\u6237\u7aef\nclient = ModbusSerialClient(\n    method='rtu',          # \u534f\u8bae\u7c7b\u578b\uff08rtu\u6216ascii\uff09\n    port='\/dev\/ttyUSB0',   # \u4e32\u53e3\u8bbe\u5907\n    baudrate=9600,         # \u6ce2\u7279\u7387\n    bytesize=8,            # \u6570\u636e\u4f4d\n    parity='N',            # \u6821\u9a8c\u4f4d\uff08N:\u65e0, E:\u5076, O:\u5947\uff09\n    stopbits=1,            # \u505c\u6b62\u4f4d\n    timeout=1              # \u8d85\u65f6\u65f6\u95f4\n)\n\ntry:\n    if client.connect():\n        print(\"\u6210\u529f\u8fde\u63a5\u5230\u4e32\u53e3\u8bbe\u5907\")\n\n        # \u6279\u91cf\u8bfb\u53d6\u8f93\u5165\u5bc4\u5b58\u5668\n        result = client.read_input_registers(address=0, count=20, slave=1)\n        if not result.isError():\n            data = result.registers\n            # \u5904\u7406\u6570\u636e\uff1a\u5047\u8bbe\u524d10\u4e2a\u662f\u6e29\u5ea6\u503c\uff08\u9700\u8981\u9664\u4ee510\uff09\n            temperatures = [value\/10.0 for value in data[:10]]\n            print(f\"\u6e29\u5ea6\u6570\u636e: {temperatures}\")\n\nfinally:\n    client.close()\n\n```\n\n### 2.4 \u5f02\u6b65\u5ba2\u6237\u7aef\u793a\u4f8b\n\n```\nimport asyncio\nfrom pymodbus.client import AsyncModbusTcpClient\n\nasync def read_device_data():\n    # \u521b\u5efa\u5f02\u6b65\u5ba2\u6237\u7aef\n    client = AsyncModbusTcpClient(\n        host='192.168.1.100',\n        port=502\n    )\n\n    await client.connect()\n\n    try:\n        # \u540c\u65f6\u8bfb\u53d6\u591a\u4e2a\u8bbe\u5907\u7684\u6570\u636e\n        tasks = []\n        for slave_id in [1, 2, 3]:\n            task = client.read_holding_registers(\n                address=0, \n                count=10, \n                slave=slave_id\n            )\n            tasks.append(task)\n\n        # \u5e76\u884c\u6267\u884c\u6240\u6709\u8bfb\u53d6\u4efb\u52a1\n        results = await asyncio.gather(*tasks)\n\n        for i, result in enumerate(results):\n            if not result.isError():\n                print(f\"\u8bbe\u5907{i+1}\u6570\u636e: {result.registers}\")\n\n    finally:\n        client.close()\n\n# \u8fd0\u884c\u5f02\u6b65\u51fd\u6570\nasyncio.run(read_device_data())\n\n```\n\n## \u4e09\u3001minimalmodbus\u5feb\u901f\u4e0a\u624b\n\n### 3.1 \u57fa\u7840\u4f7f\u7528\n\n```\nimport minimalmodbus\nimport serial\n\n# \u521b\u5efa\u4eea\u5668\u5bf9\u8c61\ninstrument = minimalmodbus.Instrument(\n    port='\/dev\/ttyUSB0',   # \u4e32\u53e3\u8bbe\u5907\n    slaveaddress=1,        # \u4ece\u7ad9\u5730\u5740\n    mode='rtu'             # \u534f\u8bae\u6a21\u5f0f\n)\n\n# \u914d\u7f6e\u4e32\u53e3\u53c2\u6570\ninstrument.serial.baudrate = 9600\ninstrument.serial.bytesize = 8\ninstrument.serial.parity = serial.PARITY_NONE\ninstrument.serial.stopbits = 1\ninstrument.serial.timeout = 1.0  # \u79d2\n\n# \u8bfb\u53d6\u4fdd\u6301\u5bc4\u5b58\u5668\ntry:\n    # \u8bfb\u53d6\u5355\u4e2a\u5bc4\u5b58\u5668\uff08\u5730\u574030001\uff09\n    temperature = instrument.read_register(\n        registeraddress=0,  # \u5bc4\u5b58\u5668\u5730\u5740\uff080\u5bf9\u5e9430001\uff09\n        functioncode=3,     # \u529f\u80fd\u78013\uff1a\u8bfb\u4fdd\u6301\u5bc4\u5b58\u5668\n        signed=True         # \u6709\u7b26\u53f7\u6574\u6570\n    )\n    print(f\"\u6e29\u5ea6: {temperature}\u00b0C\")\n\n    # \u8bfb\u53d6\u591a\u4e2a\u5bc4\u5b58\u5668\n    values = instrument.read_registers(\n        registeraddress=0,\n        number_of_registers=5,\n        functioncode=3\n    )\n    print(f\"\u591a\u4e2a\u5bc4\u5b58\u5668\u503c: {values}\")\n\n    # \u5199\u5165\u5355\u4e2a\u5bc4\u5b58\u5668\n    instrument.write_register(\n        registeraddress=10,\n        value=100,\n        functioncode=6\n    )\n\nexcept Exception as e:\n    print(f\"\u901a\u4fe1\u9519\u8bef: {e}\")\n\n```\n\n### 3.2 \u9ad8\u7ea7\u529f\u80fd\n\n```\n# \u81ea\u5b9a\u4e49\u5b57\u8282\u987a\u5e8f\uff08\u5927\u7aef\/\u5c0f\u7aef\uff09\ninstrument.byteorder = minimalmodbus.BYTEORDER_BIG\n\n# \u8bbe\u7f6e\u8c03\u8bd5\u6a21\u5f0f\uff08\u663e\u793a\u539f\u59cb\u901a\u4fe1\u6570\u636e\uff09\ninstrument.debug = True\n\n# \u4f7f\u7528\u4e0d\u540c\u7684\u7cbe\u5ea6\u548c\u7f29\u653e\u56e0\u5b50\ndef read_temperature_with_precision():\n    # \u5047\u8bbe\u6e29\u5ea6\u5bc4\u5b58\u5668\u5b58\u50a8\u7684\u662f\u5b9e\u9645\u503c*10\n    raw_value = instrument.read_register(0, 3)\n    temperature = raw_value \/ 10.0\n    return temperature\n\n# \u6279\u91cf\u8bfb\u53d6\u4f18\u5316\ndef batch_read_registers(addresses):\n    \"\"\"\u6279\u91cf\u8bfb\u53d6\u591a\u4e2a\u5bc4\u5b58\u5668\uff0c\u4f18\u5316\u901a\u4fe1\u6548\u7387\"\"\"\n    results = {}\n    for addr in addresses:\n        try:\n            value = instrument.read_register(addr, 3)\n            results[addr] = value\n        except Exception as e:\n            results[addr] = f\"\u9519\u8bef: {e}\"\n    return results\n\n```\n\n## \u56db\u3001uModbus\u73b0\u4ee3\u5316\u5f00\u53d1\n\n### 4.1 \u5f02\u6b65\u670d\u52a1\u5668\u793a\u4f8b\n\n```\nimport asyncio\nfrom umodbus.server.tcp import RequestHandler, get_server\nfrom umodbus.conf import ModbusDataType\n\n# \u5b9a\u4e49\u6570\u636e\u5b58\u50a8\ndata_store = {\n    'coils': {0: False, 1: True, 2: False},\n    'discrete_inputs': {0: True, 1: False},\n    'holding_registers': {\n        0: 100,\n        1: 200,\n        2: 300\n    },\n    'input_registers': {\n        0: 400,\n        1: 500\n    }\n}\n\nasync def handle_request(slave_id, function_code, address, quantity):\n    \"\"\"\u5904\u7406Modbus\u8bf7\u6c42\"\"\"\n    if function_code == 1:  # \u8bfb\u7ebf\u5708\n        return [data_store['coils'].get(addr, False) \n                for addr in range(address, address + quantity)]\n\n    elif function_code == 3:  # \u8bfb\u4fdd\u6301\u5bc4\u5b58\u5668\n        return [data_store['holding_registers'].get(addr, 0)\n                for addr in range(address, address + quantity)]\n\n    elif function_code == 6:  # \u5199\u5355\u4e2a\u5bc4\u5b58\u5668\n        # \u5728\u5b9e\u9645\u5e94\u7528\u4e2d\uff0c\u8fd9\u91cc\u4f1a\u66f4\u65b0\u6570\u636e\u5b58\u50a8\n        return True\n\n    return None\n\nasync def main():\n    # \u521b\u5efaTCP\u670d\u52a1\u5668\n    server = get_server(\n        RequestHandler,\n        data_store=data_store,\n        request_handler=handle_request\n    )\n\n    # \u542f\u52a8\u670d\u52a1\u5668\n    await server.serve_forever()\n\n# \u8fd0\u884c\u670d\u52a1\u5668\nasyncio.run(main())\n\n```\n\n## \u4e94\u3001\u5b9e\u9645\u5e94\u7528\u6848\u4f8b\n\n### 5.1 \u5de5\u4e1a\u6e29\u5ea6\u76d1\u63a7\u7cfb\u7edf\n\n```\nimport time\nfrom datetime import datetime\nimport json\nfrom pymodbus.client import ModbusTcpClient\n\nclass TemperatureMonitor:\n    def __init__(self, device_configs):\n        \"\"\"\n        \u521d\u59cb\u5316\u6e29\u5ea6\u76d1\u63a7\u7cfb\u7edf\n        device_configs: \u8bbe\u5907\u914d\u7f6e\u5217\u8868\n        \"\"\"\n        self.devices = []\n        for config in device_configs:\n            client = ModbusTcpClient(\n                host=config['ip'],\n                port=config.get('port', 502),\n                timeout=config.get('timeout', 3)\n            )\n            self.devices.append({\n                'client': client,\n                'name': config['name'],\n                'slave_id': config.get('slave_id', 1),\n                'registers': config['registers']\n            })\n\n        self.data_history = []\n\n    def read_all_temperatures(self):\n        \"\"\"\u8bfb\u53d6\u6240\u6709\u8bbe\u5907\u7684\u6e29\u5ea6\u6570\u636e\"\"\"\n        readings = []\n\n        for device in self.devices:\n            client = device['client']\n\n            if not client.connect():\n                print(f\"\u65e0\u6cd5\u8fde\u63a5\u5230\u8bbe\u5907: {device['name']}\")\n                continue\n\n            try:\n                device_readings = {}\n                for reg_name, reg_config in device['registers'].items():\n                    result = client.read_holding_registers(\n                        address=reg_config['address'],\n                        count=reg_config.get('count', 1),\n                        slave=device['slave_id']\n                    )\n\n                    if not result.isError():\n                        raw_value = result.registers[0]\n                        # \u5e94\u7528\u8f6c\u6362\u516c\u5f0f\uff08\u5982\u9664\u4ee510\u5f97\u5230\u5b9e\u9645\u6e29\u5ea6\uff09\n                        actual_value = raw_value \/ reg_config.get('scale', 1.0)\n                        device_readings[reg_name] = {\n                            'raw': raw_value,\n                            'actual': actual_value,\n                            'unit': reg_config.get('unit', '\u00b0C')\n                        }\n\n                readings.append({\n                    'device': device['name'],\n                    'timestamp': datetime.now().isoformat(),\n                    'readings': device_readings\n                })\n\n            except Exception as e:\n                print(f\"\u8bfb\u53d6\u8bbe\u5907{device['name']}\u65f6\u51fa\u9519: {e}\")\n            finally:\n                client.close()\n\n        # \u4fdd\u5b58\u5230\u5386\u53f2\u8bb0\u5f55\n        self.data_history.extend(readings)\n\n        # \u53ea\u4fdd\u7559\u6700\u8fd11000\u6761\u8bb0\u5f55\n        if len(self.data_history) &gt; 1000:\n            self.data_history = self.data_history[-1000:]\n\n        return readings\n\n    def check_alarms(self, readings):\n        \"\"\"\u68c0\u67e5\u62a5\u8b66\u6761\u4ef6\"\"\"\n        alarms = []\n\n        for device_reading in readings:\n            for sensor_name, sensor_data in device_reading['readings'].items():\n                value = sensor_data['actual']\n\n                # \u7b80\u5355\u62a5\u8b66\u903b\u8f91\n                if value &gt; 80:  # \u9ad8\u6e29\u62a5\u8b66\n                    alarms.append({\n                        'device': device_reading['device'],\n                        'sensor': sensor_name,\n                        'value': value,\n                        'type': 'high_temperature',\n                        'message': f\"\u6e29\u5ea6\u8fc7\u9ad8: {value}\u00b0C\",\n                        'timestamp': device_reading['timestamp']\n                    })\n                elif value &lt; 10:  # \u4f4e\u6e29\u62a5\u8b66\n                    alarms.append({\n                        'device': device_reading['device'],\n                        'sensor': sensor_name,\n                        'value': value,\n                        'type': 'low_temperature',\n                        'message': f\"\u6e29\u5ea6\u8fc7\u4f4e: {value}\u00b0C\",\n                        'timestamp': device_reading['timestamp']\n                    })\n\n        return alarms\n\n    def export_data(self, filename='temperature_data.json'):\n        \"\"\"\u5bfc\u51fa\u6570\u636e\u5230JSON\u6587\u4ef6\"\"\"\n        with open(filename, 'w', encoding='utf-8') as f:\n            json.dump({\n                'history': self.data_history,\n                'export_time': datetime.now().isoformat()\n            }, f, ensure_ascii=False, indent=2)\n\n        print(f\"\u6570\u636e\u5df2\u5bfc\u51fa\u5230: {filename}\")\n\n# \u4f7f\u7528\u793a\u4f8b\nif __name__ == \"__main__\":\n    # \u8bbe\u5907\u914d\u7f6e\n    device_configs = [\n        {\n            'name': '\u9505\u70891',\n            'ip': '192.168.1.101',\n            'registers': {\n                'temperature': {'address': 0, 'scale': 10.0, 'unit': '\u00b0C'},\n                'pressure': {'address': 1, 'scale': 100.0, 'unit': 'kPa'}\n            }\n        },\n        {\n            'name': '\u53cd\u5e94\u91dc2',\n            'ip': '192.168.1.102',\n            'registers': {\n                'temperature': {'address': 0, 'scale': 10.0, 'unit': '\u00b0C'},\n                'ph_value': {'address': 1, 'scale': 100.0, 'unit': 'pH'}\n            }\n        }\n    ]\n\n    # \u521b\u5efa\u76d1\u63a7\u5668\n    monitor = TemperatureMonitor(device_configs)\n\n    # \u76d1\u63a7\u5faa\u73af\n    try:\n        while True:\n            print(f\"\\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] \u5f00\u59cb\u8bfb\u53d6\u6570\u636e...\")\n\n            # \u8bfb\u53d6\u6570\u636e\n            readings = monitor.read_all_temperatures()\n\n            # \u663e\u793a\u6570\u636e\n            for reading in readings:\n                print(f\"\\n\u8bbe\u5907: {reading['device']}\")\n                for sensor, data in reading['readings'].items():\n                    print(f\"  {sensor}: {data['actual']} {data['unit']}\")\n\n            # \u68c0\u67e5\u62a5\u8b66\n            alarms = monitor.check_alarms(readings)\n            if alarms:\n                print(\"\\n\u26a0\ufe0f \u62a5\u8b66\u4fe1\u606f:\")\n                for alarm in alarms:\n                    print(f\"  {alarm['message']}\")\n\n            # \u6bcf5\u79d2\u8bfb\u53d6\u4e00\u6b21\n            time.sleep(5)\n\n    except KeyboardInterrupt:\n        print(\"\\n\u76d1\u63a7\u505c\u6b62\")\n        # \u5bfc\u51fa\u6570\u636e\n        monitor.export_data()\n\n```\n\n### 5.2 \u6570\u636e\u53ef\u89c6\u5316\u754c\u9762\n\n```python\nimport tkinter as tk\nfrom tkinter import ttk\nimport threading\nimport time\nfrom datetime import datetime\nfrom pymodbus.client import ModbusTcpClient\n\nclass ModbusMonitorGUI:\n    def **init**(self, root):\n        self.root = root\n        self.root.title(\"Modbus\u8bbe\u5907\u76d1\u63a7\u7cfb\u7edf\")\n        self.root.geometry(\"800x600\")\n\n```\n    # \u8bbe\u5907\u914d\u7f6e\n    self.devices = [\n        {'name': 'PLC_1', 'ip': '192.168.1.100', 'port': 502, 'slave_id': 1},\n        {'name': 'PLC_2', 'ip': '192.168.1.101', 'port': 502, 'slave_id': 2}\n    ]\n\n    # \u6570\u636e\u5b58\u50a8\n    self.data = {}\n    for device in self.devices:\n        self.data[device['name']] = {\n            'registers': {},\n            'last_update': None,\n            'status': '\u79bb\u7ebf'\n        }\n\n    # \u521b\u5efa\u754c\u9762\n    self.setup_ui()\n\n    # \u542f\u52a8\u76d1\u63a7\u7ebf\u7a0b\n    self.monitoring = True\n    self.monitor_thread = threading.Thread(target=self.monitor_devices)\n    self.monitor_thread.daemon = True\n    self.monitor_thread.start()\n\ndef setup_ui(self):\n    \"\"\"\u8bbe\u7f6e\u7528\u6237\u754c\u9762\"\"\"\n    # \u521b\u5efa\u4e3b\u6846\u67b6\n    main_frame = ttk.Frame(self.root, padding=\"10\")\n    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))\n\n    # \u8bbe\u5907\u72b6\u6001\u533a\u57df\n    status_frame = ttk.LabelFrame(main_frame, text=\"\u8bbe\u5907\u72b6\u6001\", padding=\"10\")\n    status_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))\n\n    self.status_labels = {}\n    for i, device in enumerate(self.devices):\n        label = ttk.Label(status_frame, text=f\"{device['name']}: \u79bb\u7ebf\")\n        label.grid(row=i, column=0, sticky=tk.W, padx=5, pady=2)\n        self.status_labels[device['name']] = label\n\n    # \u6570\u636e\u5c55\u793a\u533a\u57df\n    data_frame = ttk.LabelFrame(main_frame, text=\"\u5b9e\u65f6\u6570\u636e\", padding=\"10\")\n    data_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))\n\n    # \u521b\u5efaTreeview\u663e\u793a\u6570\u636e\n    columns = ('\u8bbe\u5907', '\u5bc4\u5b58\u5668', '\u5730\u5740', '\u503c', '\u65f6\u95f4')\n    self.tree = ttk.Treeview(data_frame, columns=columns, show='headings', height=15)\n\n    for col in columns:\n        self.tree.heading(col, text=col)\n        self.tree.column(col, width=100)\n\n    # \u6dfb\u52a0\u6eda\u52a8\u6761\n    scrollbar = ttk.Scrollbar(data_frame, orient=tk.VERTICAL, command=self.tree.yview)\n    self.tree.configure(yscrollcommand=scrollbar.set)\n\n    self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))\n    scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))\n\n    # \u63a7\u5236\u6309\u94ae\u533a\u57df\n    button_frame = ttk.Frame(main_frame)\n    button_frame.grid(row=2, column=0, sticky=(tk.W, tk.E))\n\n    ttk.Button(button_frame, text=\"\u5237\u65b0\u6570\u636e\", command=self.manual_refresh).pack(side=tk.LEFT, padx=5)\n    ttk.Button(button_frame, text=\"\u5bfc\u51fa\u6570\u636e\", command=self.export_data).pack(side=tk.LEFT, padx=5)\n    ttk.Button(button_frame, text=\"\u505c\u6b62\u76d1\u63a7\", command=self.stop_monitoring).pack(side=tk.LEFT, padx=5)\n\n    # \u914d\u7f6e\u7f51\u683c\u6743\u91cd\n    main_frame.columnconfigure(0, weight=1)\n    main_frame.rowconfigure(1, weight=1)\n    data_frame.columnconfigure(0, weight=1)\n    data_frame.rowconfigure(0, weight=1)\n\ndef read_device_data(self, device_config):\n    \"\"\"\u8bfb\u53d6\u5355\u4e2a\u8bbe\u5907\u7684\u6570\u636e\"\"\"\n    client = ModbusTcpClient(\n        host=device_config['ip'],\n        port=device_config['port'],\n        timeout=3\n    )\n\n    try:\n        if client.connect():\n            # \u8bfb\u53d6\u5bc4\u5b58\u56680-19\n            result = client.read_holding_registers(\n                address=0,\n                count=20,\n                slave=device_config['slave_id']\n            )\n\n            if not result.isError():\n                return {\n                    'status': '\u5728\u7ebf',\n                    'data': result.registers,\n                    'timestamp': datetime.now()\n                }\n            else:\n                return {\n                    'status': '\u8bfb\u53d6\u9519\u8bef',\n                    'data': None,\n                    'timestamp': datetime.now()\n                }\n        else:\n            return {\n                'status': '\u8fde\u63a5\u5931\u8d25',\n                'data': None,\n                'timestamp': datetime.now()\n            }\n\n    except Exception as e:\n        return {\n            'status': f'\u5f02\u5e38: {str(e)}',\n            'data': None,\n            'timestamp': datetime.now()\n        }\n    finally:\n        client.close()\n\ndef monitor_devices(self):\n    \"\"\"\u76d1\u63a7\u8bbe\u5907\u7ebf\u7a0b\"\"\"\n    while self.monitoring:\n        for device in self.devices:\n            result = self.read_device_data(device)\n\n            # \u66f4\u65b0\u6570\u636e\u5b58\u50a8\n            self.data[device['name']].update({\n                'registers': result['data'] or {},\n                'last_update': result['timestamp'],\n                'status': result['status']\n            })\n\n            # \u66f4\u65b0UI\uff08\u9700\u8981\u5728\u4e3b\u7ebf\u7a0b\u4e2d\u6267\u884c\uff09\n            self.root.after(0, self.update_ui, device['name'], result)\n\n        # \u6bcf2\u79d2\u66f4\u65b0\u4e00\u6b21\n        time.sleep(2)\n\ndef update_ui(self, device_name, result):\n    \"\"\"\u66f4\u65b0\u7528\u6237\u754c\u9762\"\"\"\n    # \u66f4\u65b0\u72b6\u6001\u6807\u7b7e\n    status_text = f\"{device_name}: {result['status']}\"\n    if result['timestamp']:\n        status_text += f\" ({result['timestamp'].strftime('%H:%M:%S')})\"\n\n    self.status_labels[device_name].config(text=status_text)\n\n    # \u66f4\u65b0\u989c\u8272\n    if result['status'] == '\u5728\u7ebf':\n        self.status_labels[device_name].config(foreground='green')\n    elif '\u9519\u8bef' in result['status'] or '\u5931\u8d25' in result['status']:\n        self.status_labels[device_name].config(foreground='red')\n    else:\n        self.status_labels[device_name].config(foreground='orange')\n\n    # \u66f4\u65b0\u6570\u636e\u8868\u683c\n    if result['data']:\n        # \u6e05\u9664\u65e7\u6570\u636e\n        for item in self.tree.get_children():\n            if self.tree.item(item)['values'][0] == device_name:\n                self.tree.delete(item)\n\n        # \u6dfb\u52a0\u65b0\u6570\u636e\n        for i, value in enumerate(result['data']):\n            self.tree.insert('', 'end', values=(\n                device_name,\n                f'400{i+1:02d}',\n                i,\n                value,\n                result['timestamp'].strftime('%H:%M:%S') if result['timestamp'] else ''\n            ))\n\ndef manual_refresh(self):\n    \"\"\"\u624b\u52a8\u5237\u65b0\u6570\u636e\"\"\"\n    for device in self.devices:\n        result = self.read_device_data(device)\n        self.update_ui(device['name'], result)\n\ndef export_data(self):\n    \"\"\"\u5bfc\u51fa\u6570\u636e\u5230\u6587\u4ef6\"\"\"\n    import json\n    from datetime import datetime\n\n    export_data = {\n        'export_time': datetime.now().isoformat(),\n        'devices': {}\n    }\n\n    for device_name, device_data in self.data.items():\n        export_data['devices'][device_name] = {\n            'status': device_data['status'],\n            'last_update': device_data['last_update'].isoformat() if device_data['last_update'] else None,\n            'registers': device_data['registers']\n        }\n\n    filename = f\"modbus_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json\"\n    with open(filename, 'w', encoding='utf-8') as f:\n        json.dump(export_data, f, ensure_ascii=False, indent=2)\n\n    # \u663e\u793a\u5bfc\u51fa\u6210\u529f\u6d88\u606f\n    from tkinter import messagebox\n    messagebox.showinfo(\"\u5bfc\u51fa\u6210\u529f\", f\"\u6570\u636e\u5df2\u5bfc\u51fa\u5230: {filename}\")\n\ndef stop_monitoring(self):\n    \"\"\"\u505c\u6b62\u76d1\u63a7\"\"\"\n    self.monitoring = False\n    self.root.quit()\n\n```\n\n\u8fd0\u884cGUI\u5e94\u7528\n\nif **name** == \"**main**\":\n    root = tk.Tk()\n    app = ModbusMonitorGUI(root)\n    root.mainloop()\n\n---\n*modbus.cn*\n"