| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- using System;
- using System.ComponentModel;
- using System.Globalization;
- using System.Threading.Tasks;
- using HslCommunication;
- using HslCommunication.ModBus;
- using Newtonsoft.Json;
- using TeamAAS.Communication.Attributes;
- using TeamAAS.Communication.Base;
- using TeamAAS.Communication.Interfaces;
- namespace TeamAAS.Communication.PLCs
- {
- [Communication("Modbus TCP", "PLC", "HslCommunication ModbusTcpNet (HR/IR/C/DI)")]
- public class ModbusTcpPlc : PlcCommunicationBase
- {
- [JsonIgnore] private ModbusTcpNet _master;
- [JsonIgnore] private bool _connected;
- public override string PlcVendor => "Modbus TCP";
- private string _ipAddress = "127.0.0.1";
- [Category("II.连接配置"), DisplayName("IP 地址"), Description("从站 IP 地址")]
- public string IpAddress
- {
- get { return _ipAddress; }
- set { if (SetProperty(ref _ipAddress, value)) Notify(nameof(EndpointUrl)); }
- }
- private int _port = 502;
- [Category("II.连接配置"), DisplayName("端口"), Description("从站端口(默认 502)")]
- public int Port
- {
- get { return _port; }
- set { if (SetProperty(ref _port, value)) Notify(nameof(EndpointUrl)); }
- }
- public override string EndpointUrl
- {
- get { return $"tcp://{IpAddress}:{Port}"; }
- set
- {
- if (!string.IsNullOrWhiteSpace(value) && value.StartsWith("tcp://"))
- {
- var uri = new Uri(value);
- IpAddress = uri.Host;
- Port = uri.Port;
- }
- }
- }
- [JsonIgnore, Browsable(false)]
- public override bool IsConnected => _connected;
- public override event Action<object, bool> ConnectChangedEvent;
- public override event Action<object, string> DataReceivedEvent;
- public ModbusTcpPlc() { }
- public override void Connect()
- {
- if (string.IsNullOrWhiteSpace(IpAddress))
- throw new InvalidOperationException("Modbus TCP IP 不能为空。");
- if (Port <= 0)
- throw new InvalidOperationException("Modbus TCP 端口必须大于 0。");
- Disconnect();
- _master = new ModbusTcpNet { IpAddress = IpAddress, Port = Port, Station = Station, ConnectTimeOut = 2000 };
- var result = _master.ConnectServer();
- if (!result.IsSuccess)
- {
- _master = null;
- throw new InvalidOperationException("Modbus TCP 连接失败: " + result.Message);
- }
- Driver = _master;
- _connected = true;
- ConnectChangedEvent?.Invoke(this, true);
- Notify(nameof(IsConnected));
- }
- public override Task ConnectAsync() => Task.Run(() => Connect());
- public override void Disconnect()
- {
- _connected = false;
- Driver = null;
- if (_master != null)
- {
- try { _master.ConnectClose(); } catch { }
- _master = null;
- }
- ConnectChangedEvent?.Invoke(this, false);
- Notify(nameof(IsConnected));
- }
- public override void Dispose() => Disconnect();
- private enum ModbusArea { Coil, DiscreteInput, HoldingRegister, InputRegister }
- private (ModbusArea Area, string Address) ParseUnifiedAddress(string address)
- {
- if (string.IsNullOrWhiteSpace(address))
- throw new ArgumentException("Modbus 地址不能为空。", nameof(address));
- var input = address.Trim().ToUpperInvariant();
- var atIdx = input.IndexOf('@');
- if (atIdx >= 0) input = input.Substring(0, atIdx);
- var colonIdx = input.IndexOf(':');
- if (colonIdx < 0) return (ModbusArea.HoldingRegister, input);
- var areaText = input.Substring(0, colonIdx).Trim();
- var offset = input.Substring(colonIdx + 1).Trim();
- switch (areaText)
- {
- case "C": case "COIL": return (ModbusArea.Coil, offset);
- case "DI": case "INPUT": return (ModbusArea.DiscreteInput, offset);
- case "HR": case "HOLDING": return (ModbusArea.HoldingRegister, offset);
- case "IR": case "INPUTREGISTER": return (ModbusArea.InputRegister, offset);
- default: return (ModbusArea.HoldingRegister, input);
- }
- }
- // Modbus 的 Coil/Discrete 区域和 HoldingRegister/InputRegister 用不同的 Driver 方法,
- // 且只读区域不支持写入。覆写 Read/Write 做路由。
- public override object ReadValue(string address, DebugDataType type, ushort length = 1)
- {
- if (Driver == null) throw new InvalidOperationException("Modbus TCP 尚未连接。");
- var (area, addr) = ParseUnifiedAddress(address);
- if (area == ModbusArea.Coil)
- {
- // Coil 区域:只能 Bool 类型
- if (type != DebugDataType.Bool)
- throw new InvalidOperationException("Coil 区域(C:) 只支持 Bool 类型读取");
- ushort len = Math.Max(length, (ushort)1);
- if (len == 1) { var r = _master.ReadCoil(addr); if (!r.IsSuccess) throw new InvalidOperationException(r.Message); return r.Content; }
- var r2 = _master.ReadCoil(addr, len); if (!r2.IsSuccess) throw new InvalidOperationException(r2.Message); return r2.Content;
- }
- if (area == ModbusArea.DiscreteInput)
- {
- if (type != DebugDataType.Bool)
- throw new InvalidOperationException("DiscreteInput 区域(DI:) 只支持 Bool 类型读取");
- ushort len = Math.Max(length, (ushort)1);
- if (len == 1) { var r = _master.ReadDiscrete(addr); if (!r.IsSuccess) throw new InvalidOperationException(r.Message); return r.Content; }
- var r2 = _master.ReadDiscrete(addr, len); if (!r2.IsSuccess) throw new InvalidOperationException(r2.Message); return r2.Content;
- }
- // HoldingRegister / InputRegister:走基类默认(Driver.ReadXxx)
- return base.ReadValue(address, type, length);
- }
- public override void WriteValue(string address, object value, DebugDataType type, ushort length = 1)
- {
- if (Driver == null) throw new InvalidOperationException("Modbus TCP 尚未连接。");
- var (area, addr) = ParseUnifiedAddress(address);
- if (area == ModbusArea.DiscreteInput || area == ModbusArea.InputRegister)
- throw new InvalidOperationException($"{area} 为只读区域,不支持写入。");
- if (area == ModbusArea.Coil)
- {
- if (type != DebugDataType.Bool)
- throw new InvalidOperationException("Coil 区域(C:) 只支持 Bool 类型写入");
- var r = _master.Write(addr, Convert.ToBoolean(value)); if (!r.IsSuccess) throw new InvalidOperationException(r.Message);
- return;
- }
- // HoldingRegister:走基类默认
- base.WriteValue(address, value, type, length);
- }
- }
- }
|