Files

196 lines
6.7 KiB
C#
Raw Permalink 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.
using System;
using System.Text.RegularExpressions;
using IoTClient.Clients.PLC;
using IoTClient.Common.Enums;
namespace StandardScene.Signal.Plc
{
/// <summary>
/// 西门子 S7 长连接封装(IoTClient)。一个机构一个实例,由对应 Worker 独占。
/// </summary>
/// <remarks>
/// 字节地址兼容老 Hsl 写法 <c>DB1.0</c> 与 IoTClient 写法 <c>DB1.DBB0</c>
/// 心跳位地址保持原样(如 <c>DB1.DBX2.0</c>),不做 DBB 转换。
/// 连接失败时读/写返回 false,由 Worker 标断线,不在这里抛给扫车线程。
/// </remarks>
public sealed class SiemensPlcSession : IDisposable
{
/// <summary>匹配 Hsl 风格「DB块号.字节偏移」,捕获组 1=DB2=偏移。</summary>
private static readonly Regex BareDbByte = new Regex(@"^DB(\d+)\.(\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private readonly SiemensVersion _version;
private readonly string _ip;
private readonly int _port;
private readonly byte _slot;
private SiemensClient _client;
private readonly object _sync = new object();
/// <param name="version">西门子系列名,如 S7_1500;空或无法解析时默认 S7_1500。</param>
/// <param name="ip">机构 IP。</param>
/// <param name="port">S7 端口,通常 102。</param>
/// <param name="slot">机架槽号,S7-1500 常见 1。</param>
public SiemensPlcSession(string version, string ip, int port, int slot)
{
_version = ParseVersion(version);
_ip = ip ?? "";
_port = port > 0 ? port : 102;
_slot = (byte)Math.Max(0, slot);
}
/// <summary>当前客户端是否已 Open 成功。未创建客户端时为 false。</summary>
public bool Connected
{
get
{
lock (_sync) return _client?.Connected == true;
}
}
/// <summary>惰性创建客户端并 Open。已连接则直接 true;失败返回 false 不抛。</summary>
public bool EnsureOpen()
{
lock (_sync)
{
try
{
if (_client == null)
_client = new SiemensClient(_version, _ip, _port, _slot);
if (_client.Connected)
return true;
var r = _client.Open();
return r != null && r.IsSucceed;
}
catch
{
return false;
}
}
}
/// <summary>读一字节。地址会先规范化;未连接或失败时 value=0 且返回 false。</summary>
public bool TryReadByte(string address, out byte value)
{
value = 0;
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
return false;
try
{
var r = _client.ReadByte(NormalizeByteAddress(address));
if (r == null || !r.IsSucceed)
return false;
value = r.Value;
return true;
}
catch
{
return false;
}
}
/// <summary>写一字节(进入中/到位等)。失败返回 false。</summary>
public bool TryWriteByte(string address, byte value)
{
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
return false;
try
{
var r = _client.Write(NormalizeByteAddress(address), value);
return r != null && r.IsSucceed;
}
catch
{
return false;
}
}
/// <summary>从起始字节连续读 length 字节,用于工位 BOOL 拆位。</summary>
public bool TryReadBytes(string startAddress, ushort length, out byte[] data)
{
data = Array.Empty<byte>();
if (string.IsNullOrWhiteSpace(startAddress) || length == 0 || !EnsureOpen())
return false;
try
{
var r = _client.Read(NormalizeByteAddress(startAddress), length, false);
if (r == null || !r.IsSucceed || r.Value == null)
return false;
data = r.Value;
return true;
}
catch
{
return false;
}
}
/// <summary>读心跳位。地址不做 DBB 转换。</summary>
public bool TryReadBool(string address, out bool value)
{
value = false;
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
return false;
try
{
var r = _client.ReadBoolean(address.Trim());
if (r == null || !r.IsSucceed)
return false;
value = r.Value;
return true;
}
catch
{
return false;
}
}
/// <summary>写心跳位(通常写当前值的反码,让 PLC 看到翻转)。</summary>
public bool TryWriteBool(string address, bool value)
{
if (string.IsNullOrWhiteSpace(address) || !EnsureOpen())
return false;
try
{
var r = _client.Write(address.Trim(), value);
return r != null && r.IsSucceed;
}
catch
{
return false;
}
}
/// <summary>关闭并丢弃客户端,下次 EnsureOpen 会新建。</summary>
public void Close()
{
lock (_sync)
{
try { _client?.Close(); }
catch { }
_client = null;
}
}
public void Dispose() => Close();
/// <summary>
/// <c>DB1.0</c> → <c>DB1.DBB0</c>;已是 DBB/DBW/DBX 的地址原样返回。
/// </summary>
internal static string NormalizeByteAddress(string address)
{
var addr = address.Trim();
var m = BareDbByte.Match(addr);
return m.Success ? $"DB{m.Groups[1].Value}.DBB{m.Groups[2].Value}" : addr;
}
/// <summary>空或无法解析时默认 S7_1500。允许把 S7-1500 写成带连字符。</summary>
internal static SiemensVersion ParseVersion(string version)
{
if (string.IsNullOrWhiteSpace(version))
return SiemensVersion.S7_1500;
return Enum.TryParse(version.Replace("-", "_"), true, out SiemensVersion parsed)
? parsed
: SiemensVersion.S7_1500;
}
}
}