| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425 |
- using Sres.Net.EEIP;
- using System;
- using System.Collections.Generic;
- using System.Data.Entity.Core.Metadata.Edm;
- using System.Linq;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using TeamAAS_VP.Models;
- namespace TeamAAS_VP.Core.RFID
- {
- /// <summary>
- /// SIG350 EtherNet/IP 通讯类 - 专为 RFID 应用设计
- /// 支持 SICK IO-Link RFID 读写头 (RFU63x, RFU62x 等)
- /// </summary>
- public class SIG350RFIDClient : IDisposable
- {
- #region 私有字段
- private readonly EEIPClient _client;
- private readonly string _ipAddress;
- private bool _isConnected;
- private bool _isIOModeActive;
- private CancellationTokenSource _ioCancelToken;
- private Task _ioReceiveTask;
- private readonly object _ioSync = new object();
- private O2TData _lastOutput = new O2TData();
- private T2OData _lastInput = new T2OData();
- private readonly HashSet<int> _enabledPorts = new HashSet<int> { 1 };
- private Rfh5xxReadOptions _readOptions = new Rfh5xxReadOptions();
- private volatile bool _disposed;
- private volatile bool _autoReconnectEnabled = true;
- private int _lastRpiMs = 10;
- private readonly object _reconnectSync = new object();
- private CancellationTokenSource _reconnectCts;
- private Task _reconnectTask;
- /// <summary>初始连接重试次数</summary>
- public int ConnectRetryCount { get; set; } = 10;
- /// <summary>初始连接重试间隔(毫秒)</summary>
- public int ConnectRetryDelayMs { get; set; } = 2000;
- /// <summary>启动 I/O 前回调(Management 用于配置端口/读模式,含 EIP 重连场景)。</summary>
- public Action BeforeStartRfidReading { get; set; }
- /// <summary>断线后自动重连基础间隔(毫秒),随失败次数递增,最大 30 秒</summary>
- public int ReconnectIntervalMs { get; set; } = 3000;
- // Assembly ID(EDS / 手册)
- private const ushort ASSEMBLY_CONFIG = 102; // 配置:端口 80 字节,完整含 DIO 为 130
- private const ushort ASSEMBLY_O2T = 100; // 输出 276 字节
- private const ushort ASSEMBLY_T2O = 101; // 输入 276 字节
- private const int PROCESS_DATA_PER_PORT = 32;
- private const int IO_ASSEMBLY_SIZE = 4 + 8 * (2 + PROCESS_DATA_PER_PORT); // 276
- private const int CONFIG_PORT_SIZE = 80; // 8 × 10
- private const int CONFIG_FULL_SIZE = 130; // 80 + layout + substitute + 8×6 DIO
- // IOL Status Bit7 = 过程数据有效 (PQ)
- private const byte IOL_STATUS_PQ_VALID = 0x80;
- private const byte IOL_STATUS_DEV_COM = 0x20;
- // RFH5xx HF ISO15693 PDI/PDO(手册 8.3.1)
- private const byte RFH5xxPdoStart = 0x01;
- private const byte RFH5xxTagPresent = 0x04;
- private const byte RFH5xxFault = 0x02;
- private const int RFH5xxCmdAutoRead = 1;
- private const int RFH5xxCmdRead = 3;
- private const int RFH5xxCmdReadUid = 5;
- private const int RFH5xxUidLength = 8;
- private const int RFH5xxMaxUserPayload = 28;
- // RFU61x UHF(保留兼容)
- private const byte RFU_TAG_PRESENT = 0x04;
- private const int RFU_LENGTH_INDEX = 3;
- private const int RFU_DATA_OFFSET = 4;
- private const int RFU_MAX_EPC_LEN = 28;
- BgEthernetIP Config;
- #endregion
- #region 事件
- public event EventHandler<RFIDTagDataEventArgs> OnTagDataReceived;
- public event Action<bool> OnConnectReceive;
- public event EventHandler<Exception> OnError;
- public event Action<string> OnLog;
- #endregion
- #region 构造函数
- public SIG350RFIDClient(BgEthernetIP config)
- {
- Config = config;
- _ipAddress = config.IP;
- _client = new EEIPClient();
- OnConnectReceive += HandleConnectionChanged;
- }
- #endregion
- #region 连接管理
- public bool Connect()
- {
- StopReconnectLoop();
- for (int i = 0; i < ConnectRetryCount; i++)
- {
- if (InternalConnect())
- {
- OnConnectReceive?.Invoke(true);
- return true;
- }
- if (i < ConnectRetryCount - 1)
- Thread.Sleep(ConnectRetryDelayMs);
- }
- OnConnectReceive?.Invoke(false);
- return false;
- }
- private bool InternalConnect()
- {
- try
- {
- if (_isConnected)
- return true;
- _client.RegisterSession(_ipAddress);
- _isConnected = true;
- OnLog?.Invoke($"成功连接到 SIG350 ({_ipAddress})");
- return true;
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"连接失败: {ex.Message}");
- OnError?.Invoke(this, ex);
- CleanupSessionOnly();
- return false;
- }
- }
- private void HandleConnectionChanged(bool connected)
- {
- if (connected || _disposed || !_autoReconnectEnabled)
- return;
- ScheduleReconnect();
- }
- private void ScheduleReconnect()
- {
- lock (_reconnectSync)
- {
- if (_disposed || !_autoReconnectEnabled)
- return;
- if (_reconnectTask != null && !_reconnectTask.IsCompleted)
- return;
- StopReconnectLoop();
- _reconnectCts = new CancellationTokenSource();
- var token = _reconnectCts.Token;
- _reconnectTask = Task.Run(() => ReconnectLoop(token), token);
- }
- }
- private void ReconnectLoop(CancellationToken token)
- {
- OnLog?.Invoke("EIP 连接断开,开始自动重连...");
- int attempt = 0;
- while (!token.IsCancellationRequested && !_disposed && _autoReconnectEnabled)
- {
- attempt++;
- try
- {
- if (TryRestoreSession())
- {
- OnLog?.Invoke($"EIP 重连成功(第 {attempt} 次)");
- OnConnectReceive?.Invoke(true);
- return;
- }
- OnLog?.Invoke($"EIP 重连失败(第 {attempt} 次),稍后重试");
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"EIP 重连异常: {ex.Message}");
- }
- int delay = Math.Min(ReconnectIntervalMs * Math.Min(attempt, 10), 30000);
- if (token.WaitHandle.WaitOne(delay))
- break;
- }
- }
- private bool TryRestoreSession()
- {
- CleanupSessionOnly();
- if (!InternalConnect())
- return false;
- if (_lastRpiMs <= 0)
- return true;
- try
- {
- StartRFIDReading(_lastRpiMs);
- return true;
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"EIP 重连后恢复 I/O 失败: {ex.Message}");
- CleanupSessionOnly();
- return false;
- }
- }
- private void StopReconnectLoop()
- {
- try
- {
- if (_reconnectCts != null)
- {
- _reconnectCts.Cancel();
- try
- {
- if (_reconnectTask != null)
- _reconnectTask.Wait(3000);
- }
- catch (AggregateException) { /* ignore */ }
- _reconnectCts.Dispose();
- }
- }
- catch { /* ignore */ }
- finally
- {
- _reconnectCts = null;
- _reconnectTask = null;
- }
- }
- public void Disconnect()
- {
- _autoReconnectEnabled = false;
- StopReconnectLoop();
- CleanupResources();
- }
- public bool IsConnected => _isConnected;
- public bool IsIOModeActive => _isIOModeActive;
- /// <summary>
- /// 当前启用监听的端口(1-8)。空集合表示不上报任何标签。
- /// </summary>
- public IReadOnlyCollection<int> EnabledPorts
- {
- get
- {
- lock (_ioSync)
- return _enabledPorts.OrderBy(p => p).ToList();
- }
- }
- /// <summary>
- /// 设置要读取的读写头端口(1-8)。
- /// </summary>
- public void SetEnabledPorts(IEnumerable<int> ports)
- {
- if (ports == null)
- throw new ArgumentNullException(nameof(ports));
- var next = new HashSet<int>();
- foreach (int p in ports)
- {
- if (p < 1 || p > 8)
- throw new ArgumentException($"端口号无效: {p},必须在1-8之间");
- next.Add(p);
- }
- lock (_ioSync)
- {
- _enabledPorts.Clear();
- foreach (int p in next)
- _enabledPorts.Add(p);
- }
- OnLog?.Invoke(next.Count == 0
- ? "未启用任何 RFID 端口"
- : $"已启用 RFID 端口: {string.Join(",", next.OrderBy(x => x))}");
- }
- private bool IsPortEnabled(int port)
- {
- lock (_ioSync)
- return _enabledPorts.Contains(port);
- }
- /// <summary>
- /// 配置 RFH5xx 读取内容:UID 或用户存储区(手册 8.3.1)。
- /// </summary>
- public void ConfigureReadOptions(Rfh5xxReadOptions options)
- {
- if (options == null)
- throw new ArgumentNullException(nameof(options));
- lock (_ioSync)
- _readOptions = options.Clone();
- }
- public Rfh5xxReadOptions GetReadOptions()
- {
- lock (_ioSync)
- return _readOptions.Clone();
- }
- #endregion
- #region 配置管理
- /// <summary>
- /// 配置 RFID 端口为 IO-Link 模式
- /// </summary>
- public void ConfigureRFIDPort(int port, PortMode mode = PortMode.IOLAutostart)
- {
- if (port < 1 || port > 8)
- throw new ArgumentException("端口号必须在1-8之间");
- try
- {
- var config = ReadConfiguration();
- ApplyPortMode(config, port, mode);
- WriteConfiguration(config);
- OnLog?.Invoke($"端口 {port} 已配置为 {mode}");
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"配置端口 {port} 失败: {ex.Message}");
- throw;
- }
- }
- /// <summary>
- /// 批量配置多个端口为 IO-Link 模式,并更新启用端口集合。
- /// </summary>
- public void ConfigureRFIDPorts(IEnumerable<int> ports, PortMode mode = PortMode.IOLAutostart)
- {
- if (ports == null)
- throw new ArgumentNullException(nameof(ports));
- var list = ports.Distinct().OrderBy(p => p).ToList();
- if (list.Count == 0)
- throw new ArgumentException("至少选择一个端口");
- foreach (int p in list)
- {
- if (p < 1 || p > 8)
- throw new ArgumentException($"端口号无效: {p},必须在1-8之间");
- }
- try
- {
- var config = ReadConfiguration();
- foreach (int port in list)
- ApplyPortMode(config, port, mode);
- WriteConfiguration(config);
- SetEnabledPorts(list);
- OnLog?.Invoke($"端口 [{string.Join(",", list)}] 已配置为 {mode}");
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"批量配置端口失败: {ex.Message}");
- throw;
- }
- }
- private static void ApplyPortMode(SIG350Configuration config, int port, PortMode mode)
- {
- config.PortMode[port - 1] = (byte)mode;
- config.IQBehavior[port - 1] = (byte)IQBehavior.NotSupported;
- config.ValidationBackup[port - 1] = 0;
- config.PortCycleTime[port - 1] = 0;
- }
- private SIG350Configuration ReadConfiguration()
- {
- byte[] data = ReadAssembly(ASSEMBLY_CONFIG);
- if (data == null || data.Length < CONFIG_PORT_SIZE)
- throw new InvalidOperationException($"读取配置失败,长度={data?.Length ?? 0},至少需要 {CONFIG_PORT_SIZE}");
- return ParseConfiguration(data);
- }
- private void WriteConfiguration(SIG350Configuration config)
- {
- if (config == null)
- throw new ArgumentNullException(nameof(config));
- // 按设备返回长度写回:>=130 用完整块,否则只写 EDS 规定的 80 字节端口配置
- int size = config.RawLength >= CONFIG_FULL_SIZE ? CONFIG_FULL_SIZE : CONFIG_PORT_SIZE;
- byte[] data = SerializeConfiguration(config, size);
- WriteAssembly(ASSEMBLY_CONFIG, data);
- }
- #endregion
- #region RFID 数据操作
- public void StartRFIDReading(int rpiMilliseconds = 10)
- {
- if (!_isConnected)
- throw new InvalidOperationException("未连接到SIG350");
- _lastRpiMs = Math.Max(1, rpiMilliseconds);
- if (_isIOModeActive)
- return;
- BeforeStartRfidReading?.Invoke();
- List<int> ports;
- lock (_ioSync)
- ports = _enabledPorts.OrderBy(p => p).ToList();
- if (ports.Count == 0)
- ports.Add(1);
- try
- {
- StartIOCommunication(rpiMilliseconds);
- EnablePortsOutput(ports);
- ApplyRfh5xxCommandForPorts(ports, oneShot: false);
- string modeText = _readOptions.Kind == RfidPayloadKind.Uid ? "Read UID" : "Auto-Read 用户区";
- OnLog?.Invoke($"RFID数据读取已启动 (端口: {string.Join(",", ports)}, RPI: {rpiMilliseconds}ms, 模式: {modeText})");
- }
- catch (Exception ex)
- {
- // ForwardOpen 已成功但后续失败时必须关掉,避免半开连接
- try { StopIOCommunication(); } catch { /* ignore */ }
- OnLog?.Invoke($"启动RFID读取失败: {ex.Message}");
- throw;
- }
- }
- public void StopRFIDReading()
- {
- try
- {
- if (_isIOModeActive)
- {
- try { DisableAllPortOutputs(); }
- catch { /* ignore */ }
- }
- }
- finally
- {
- StopIOCommunication();
- OnLog?.Invoke("RFID数据读取已停止");
- }
- }
- /// <summary>
- /// Autostart 模式下只需使能端口输出;不要向过程数据写入臆造命令字节,
- /// 以免覆盖 IO-Link 设备输出映像。
- /// </summary>
- private void EnablePortsOutput(IList<int> ports)
- {
- lock (_ioSync)
- {
- var output = CloneOutput(_lastOutput);
- for (int i = 0; i < 8; i++)
- {
- output.OutputEnable[i] = 0;
- Array.Clear(output.PortProcessData[i], 0, PROCESS_DATA_PER_PORT);
- }
- foreach (int port in ports)
- output.OutputEnable[port - 1] = 1;
- WriteOtIoData(SerializeO2TData(output));
- _lastOutput = output;
- }
- }
- private void DisableAllPortOutputs()
- {
- lock (_ioSync)
- {
- var output = CloneOutput(_lastOutput);
- for (int i = 0; i < 8; i++)
- {
- output.OutputEnable[i] = 0;
- Array.Clear(output.PortProcessData[i], 0, PROCESS_DATA_PER_PORT);
- }
- WriteOtIoData(SerializeO2TData(output));
- _lastOutput = output;
- }
- }
- /// <summary>
- /// 使能端口并向 RFH5xx 下发 PDO 命令(Read UID / Auto-Read)。
- /// </summary>
- private void ApplyRfh5xxCommandForPorts(IList<int> ports, bool oneShot)
- {
- if (ports == null || ports.Count == 0)
- return;
- lock (_ioSync)
- {
- var output = CloneOutput(_lastOutput);
- foreach (int port in ports)
- {
- output.OutputEnable[port - 1] = 1;
- WriteRfh5xxPdoToPort(output, port, _readOptions, start: true);
- }
- SendOutputData(output);
- _lastOutput = output;
- }
- }
- private void ApplyRfh5xxCommandForPort(int port, bool oneShot, bool start)
- {
- lock (_ioSync)
- {
- var output = CloneOutput(_lastOutput);
- output.OutputEnable[port - 1] = 1;
- WriteRfh5xxPdoToPort(output, port, _readOptions, start);
- SendOutputData(output);
- _lastOutput = output;
- }
- }
- /// <summary>RFH5xx PDO:Byte0 bit0=START, bit5~7=CMD;用户区 Byte1=块数, Byte3=起始块。</summary>
- /// <summary>
- /// RFH5xx 正确 PDO 命令格式:Byte0 = 命令码
- /// Read UID = 5,Auto-Read 用户区 = 1,单次读用户区 = 3
- /// </summary>
- private static void WriteRfh5xxPdoToPort(O2TData output, int port, Rfh5xxReadOptions options, bool start)
- {
- var pdo = new byte[32];
- if (!start)
- {
- // 停止:首字节写 0
- Array.Copy(pdo, 0, output.PortProcessData[port - 1], 0, 32);
- return;
- }
- // 命令码直接写入 Byte0
- int cmd = options.Kind == RfidPayloadKind.Uid
- ? RFH5xxCmdReadUid // 5
- : RFH5xxCmdAutoRead; // 1
- pdo[0] = (byte)cmd;
- // 用户区配置:Byte1=块数,Byte3=起始块地址
- if (options.Kind == RfidPayloadKind.UserMemory)
- {
- pdo[1] = (byte)(options.UserBlockCount & 0x1F);
- pdo[3] = options.UserBlockAddress;
- }
- Array.Copy(pdo, 0, output.PortProcessData[port - 1], 0, 32);
- }
- public RFIDTagData ReadRFIDTag(int port, int timeoutMs = 1000, Rfh5xxReadOptions options = null)
- {
- if (port < 1 || port > 8)
- throw new ArgumentException("端口号必须在1-8之间");
- Rfh5xxReadOptions opts = options ?? GetReadOptions();
- try
- {
- if (!_isIOModeActive)
- throw new InvalidOperationException("I/O通讯未启动,请先启动监听");
- // Read/ReadUID:翻转 START 触发一次(手册 8.3.1.2)
- ApplyRfh5xxCommandForPort(port, oneShot: opts.Kind == RfidPayloadKind.UserMemory, start: false);
- Thread.Sleep(50);
- ApplyRfh5xxCommandForPort(port, oneShot: opts.Kind == RfidPayloadKind.UserMemory, start: true);
- DateTime startTime = DateTime.Now;
- while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
- {
- var input = ReadInputData();
- var tag = TryExtractTag(input, port, opts);
- if (tag != null)
- return tag;
- Thread.Sleep(10);
- }
- return null;
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"读取RFID标签失败: {ex.Message}");
- throw;
- }
- finally
- {
- }
- }
- [Obsolete("RFH5xx 请使用 ReadRFIDTag;此方法保留兼容。")]
- public void SendRFIDCommand(int port, RFIDCommand command, byte[] data = null)
- {
- if (port < 1 || port > 8)
- throw new ArgumentException("端口号必须在1-8之间");
- try
- {
- lock (_ioSync)
- {
- var output = CloneOutput(_lastOutput);
- output.OutputEnable[port - 1] = 1;
- Array.Clear(output.PortProcessData[port - 1], 0, PROCESS_DATA_PER_PORT);
- if (data != null && data.Length > 0)
- {
- int copyLen = Math.Min(PROCESS_DATA_PER_PORT, data.Length);
- Array.Copy(data, 0, output.PortProcessData[port - 1], 0, copyLen);
- }
- // 过程数据第 0 字节作为简易触发(具体含义取决于读写头)
- if (command == RFIDCommand.StartRead)
- output.PortProcessData[port - 1][0] = 0x85;
- else if (command == RFIDCommand.StopRead)
- output.PortProcessData[port - 1][0] = 0x00;
- SendOutputData(output);
- _lastOutput = output;
- }
- OnLog?.Invoke($"RFID命令已发送到端口 {port}: {command}");
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"发送RFID命令失败: {ex.Message}");
- throw;
- }
- }
- public RFIDTagData ReadRFIDTag(int port, int timeoutMs = 1000)
- {
- return ReadRFIDTag(port, timeoutMs, null);
- }
- private RFIDTagData TryExtractTag(T2OData input, int port, Rfh5xxReadOptions options = null)
- {
- if (input == null || port < 1 || port > 8)
- return null;
- if (input.PortStatus == null || input.PortProcessData == null)
- return null;
- byte[] processData = input.PortProcessData[port - 1];
- if (processData == null)
- return null;
- byte status = input.PortStatus[port - 1];
- if ((status & IOL_STATUS_PQ_VALID) == 0)
- return null;
- byte[] tagData = ExtractRFIDData(processData, options ?? _readOptions);
- if (tagData == null || tagData.Length == 0)
- return null;
- return new RFIDTagData
- {
- Port = port,
- Data = tagData,
- Timestamp = DateTime.Now,
- Quality = status,
- PayloadKind = (options ?? _readOptions).Kind
- };
- }
- /// <summary>
- /// 从 IO-Link 32 字节 PDI 提取有效载荷(默认 RFH5xx,失败时尝试 RFU61x)。
- /// </summary>
- internal static byte[] ExtractRFIDData(byte[] processData, Rfh5xxReadOptions options = null)
- {
- if (processData == null || processData.Length == 0)
- return null;
- if (IsAllZero(processData))
- return null;
- if (options == null)
- options = new Rfh5xxReadOptions();
- byte[] rfh = ExtractRfh5xxPayload(processData, options);
- if (rfh != null && rfh.Length > 0)
- return rfh;
- return ExtractRfu61xPayload(processData);
- }
- /// <summary>RFH5xx:Tag=Byte0 bit2;UID=Byte4~11;用户区=Byte4 起。</summary>
- internal static byte[] ExtractRfh5xxPayload(byte[] pdi, Rfh5xxReadOptions options)
- {
- if (pdi == null || pdi.Length < 12 || options == null)
- return null;
- byte b0 = pdi[0];
- if ((b0 & RFH5xxTagPresent) == 0)
- return null;
- if ((b0 & RFH5xxFault) != 0)
- return null;
- if (options.Kind == RfidPayloadKind.Uid)
- {
- if (pdi.Length < 4 + RFH5xxUidLength)
- return null;
- byte[] uid = new byte[RFH5xxUidLength];
- Array.Copy(pdi, 4, uid, 0, RFH5xxUidLength);
- return IsAllZero(uid) ? null : uid;
- }
- int byteCount = options.UserBlockCount * options.UserBlockSizeBytes;
- if (byteCount <= 0)
- return null;
- byteCount = Math.Min(RFH5xxMaxUserPayload, Math.Min(byteCount, pdi.Length - 4));
- byte[] user = new byte[byteCount];
- Array.Copy(pdi, 4, user, 0, byteCount);
- return IsAllZero(user) ? null : user;
- }
- private static byte[] ExtractRfu61xPayload(byte[] processData)
- {
- if (processData.Length >= RFU_DATA_OFFSET + 1)
- {
- bool tagPresent = (processData[0] & RFU_TAG_PRESENT) != 0;
- int epcLen = processData[RFU_LENGTH_INDEX];
- if (tagPresent)
- {
- if (epcLen <= 0 || epcLen > RFU_MAX_EPC_LEN || epcLen > processData.Length - RFU_DATA_OFFSET)
- return null;
- if (IsAllZero(processData, RFU_DATA_OFFSET, epcLen))
- return null;
- byte[] epc = new byte[epcLen];
- Array.Copy(processData, RFU_DATA_OFFSET, epc, 0, epcLen);
- return epc;
- }
- // TagPresent=0 且 Length=0:RFU 空闲帧(无芯片),忽略状态/RSSI 噪声
- if (epcLen == 0)
- return null;
- }
- // 2) 其它设备:仅接受「首字节=长度 + 后面全是填充0」且长度合理(>=4)
- int dataLength = processData[0];
- if (dataLength >= 4 && dataLength < processData.Length)
- {
- int payloadEnd = 1 + dataLength;
- if (payloadEnd <= processData.Length
- && IsAllZero(processData, payloadEnd, processData.Length - payloadEnd)
- && !IsAllZero(processData, 1, dataLength))
- {
- byte[] result = new byte[dataLength];
- Array.Copy(processData, 1, result, 0, dataLength);
- return result;
- }
- }
- // 不再把“去尾零后的任意非零过程数据”当作标签
- return null;
- }
- private static bool IsAllZero(byte[] data)
- {
- return IsAllZero(data, 0, data.Length);
- }
- private static bool IsAllZero(byte[] data, int offset, int count)
- {
- if (data == null || count <= 0)
- return true;
- int end = Math.Min(data.Length, offset + count);
- for (int i = offset; i < end; i++)
- {
- if (data[i] != 0)
- return false;
- }
- return true;
- }
- #endregion
- #region I/O通讯 (隐式消息)
- private void StartIOCommunication(int rpiMilliseconds = 10)
- {
- if (!_isConnected)
- throw new InvalidOperationException("未连接到SIG350");
- if (_isIOModeActive)
- return;
- try
- {
- // RPI:界面单位是 ms,协议单位是 μs。至少 1ms,避免 Max(1000, ms) 把 10ms 误变成 1 秒。
- int rpiMs = Math.Max(1, rpiMilliseconds);
- uint rpiMicroseconds = (uint)rpiMs * 1000u;
- _client.O_T_InstanceID = (byte)ASSEMBLY_O2T;
- _client.T_O_InstanceID = (byte)ASSEMBLY_T2O;
- _client.O_T_Length = (ushort)IO_ASSEMBLY_SIZE;
- _client.T_O_Length = (ushort)IO_ASSEMBLY_SIZE;
- _client.O_T_RealTimeFormat = RealTimeFormat.Header32Bit;
- _client.T_O_RealTimeFormat = RealTimeFormat.Modeless;
- _client.O_T_OwnerRedundant = false;
- _client.T_O_OwnerRedundant = false;
- _client.O_T_Priority = Priority.Scheduled;
- _client.T_O_Priority = Priority.Scheduled;
- _client.O_T_VariableLength = false;
- _client.T_O_VariableLength = false;
- _client.O_T_ConnectionType = ConnectionType.Point_to_Point;
- _client.T_O_ConnectionType = ConnectionType.Point_to_Point;
- _client.RequestedPacketRate_O_T = rpiMicroseconds;
- _client.RequestedPacketRate_T_O = rpiMicroseconds;
- // 使用空配置实例,端口配置已通过显式 Assembly 102 写入
- _client.ConfigurationAssemblyInstanceID = 1;
- _client.ForwardOpen();
- // 初始化输出缓冲区(必须通过 setter 写回,getter 返回的是 Clone)
- lock (_ioSync)
- {
- _lastOutput = new O2TData();
- WriteOtIoData(SerializeO2TData(_lastOutput));
- }
- _isIOModeActive = true;
- _ioCancelToken = new CancellationTokenSource();
- _ioReceiveTask = Task.Run(() => IOReceiveLoop(_ioCancelToken.Token));
- OnLog?.Invoke($"I/O通讯已启动 (O2T/T2O={IO_ASSEMBLY_SIZE}字节, RPI={rpiMs}ms)");
- }
- catch (Exception ex)
- {
- _isIOModeActive = false;
- OnLog?.Invoke($"启动I/O通讯失败: {ex.Message}");
- throw;
- }
- }
- private void StopIOCommunication()
- {
- if (!_isIOModeActive)
- return;
- try
- {
- _ioCancelToken?.Cancel();
- try { _ioReceiveTask?.Wait(3000); }
- catch (AggregateException) { /* ignore cancel */ }
- try { _client.ForwardClose(); }
- catch (Exception ex) { OnLog?.Invoke($"ForwardClose: {ex.Message}"); }
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"停止I/O通讯异常: {ex.Message}");
- }
- finally
- {
- _isIOModeActive = false;
- _ioCancelToken?.Dispose();
- _ioCancelToken = null;
- _ioReceiveTask = null;
- }
- }
- private void IOReceiveLoop(CancellationToken token)
- {
- // 按端口记录上次上报指纹,标签离开后清掉,相同标签再次进入仍可上报
- var lastByPort = new string[8];
- while (!token.IsCancellationRequested && _isIOModeActive)
- {
- try
- {
- var input = TryReadInputData();
- if (input == null)
- {
- Thread.Sleep(10);
- continue;
- }
- try
- {
- // 显式读取一次输入Assembly,验证设备响应
- byte[] test = _client.AssemblyObject.getInstance(ASSEMBLY_T2O);
- if (test == null || test.Length == 0)
- throw new Exception("Assembly读取为空");
- }
- catch
- {
- OnLog?.Invoke("心跳检测失败,IO连接已断开");
- StopIOCommunication();
- CleanupSessionOnly();
- OnConnectReceive?.Invoke(false);
- break;
- }
- var changedTags = new List<RFIDTagData>();
- for (int port = 1; port <= 8; port++)
- {
- if (!IsPortEnabled(port))
- {
- lastByPort[port - 1] = null;
- continue;
- }
- var tag = TryExtractTag(input, port, _readOptions);
- if (tag == null)
- {
- lastByPort[port - 1] = null;
- continue;
- }
- string fingerprint = tag.ToHexString();
- if (fingerprint == lastByPort[port - 1])
- continue;
- lastByPort[port - 1] = fingerprint;
- changedTags.Add(tag);
- }
- if (changedTags.Count > 0)
- {
- var args = new RFIDTagDataEventArgs(input, changedTags);
- OnTagDataReceived?.Invoke(this, args);
- }
- Thread.Sleep(10);
- }
- catch (Exception ex)
- {
- if (token.IsCancellationRequested)
- break;
- OnLog?.Invoke($"接收I/O数据异常: {ex.Message}");
- OnError?.Invoke(this, ex);
- Thread.Sleep(100);
- }
- }
- }
- private void SendOutputData(O2TData outputData)
- {
- if (!_isIOModeActive)
- throw new InvalidOperationException("I/O通讯未启动");
- // 调用方若已持有 _ioSync,Monitor 可重入;此处统一走 setter 写回
- byte[] data = SerializeO2TData(outputData);
- lock (_ioSync)
- {
- WriteOtIoData(data);
- }
- }
- /// <summary>
- /// EEIP.NetStandard 的 O_T_IOData getter 返回 Clone,
- /// 直接改下标或 Array.Copy 到 getter 结果都不会写回内部缓冲区,必须走 setter。
- /// </summary>
- private void WriteOtIoData(byte[] data)
- {
- if (data == null)
- throw new ArgumentNullException(nameof(data));
- _client.O_T_IOData = data;
- }
- /// <summary>
- /// 尝试读取输入;缓冲区尚未就绪时返回 null,避免接收循环狂抛异常。
- /// </summary>
- private T2OData TryReadInputData()
- {
- if (!_isIOModeActive)
- return null;
- byte[] data = _client.T_O_IOData;
- if (data == null || data.Length < IO_ASSEMBLY_SIZE)
- return null;
- var parsed = ParseT2OData(data);
- lock (_ioSync)
- _lastInput = parsed;
- return parsed;
- }
- private T2OData ReadInputData()
- {
- if (!_isIOModeActive)
- throw new InvalidOperationException("I/O通讯未启动");
- var parsed = TryReadInputData();
- if (parsed == null)
- throw new InvalidOperationException("T2O 数据尚未就绪或长度不足");
- return parsed;
- }
- #endregion
- #region 数据序列化/反序列化
- private static T2OData ParseT2OData(byte[] data)
- {
- if (data == null || data.Length < IO_ASSEMBLY_SIZE)
- throw new ArgumentException($"输入数据无效,需要 {IO_ASSEMBLY_SIZE} 字节,实际 {data?.Length ?? 0}");
- var result = new T2OData();
- int offset = 0;
- result.DIStatus = data[offset++];
- offset++; // Reserved
- result.DIData = BitConverter.ToUInt16(data, offset);
- offset += 2;
- for (int port = 0; port < 8; port++)
- {
- result.PortStatus[port] = data[offset++];
- offset++; // Reserved
- Array.Copy(data, offset, result.PortProcessData[port], 0, PROCESS_DATA_PER_PORT);
- offset += PROCESS_DATA_PER_PORT;
- }
- return result;
- }
- private static byte[] SerializeO2TData(O2TData data)
- {
- if (data == null)
- throw new ArgumentNullException(nameof(data));
- // 手册: 4 + 8×(OutputEnable + Reserved + 32 PD) = 276
- byte[] result = new byte[IO_ASSEMBLY_SIZE];
- int offset = 0;
- result[offset++] = data.DOStatus;
- result[offset++] = 0; // Reserved
- byte[] doBytes = BitConverter.GetBytes(data.DOData);
- result[offset++] = doBytes[0];
- result[offset++] = doBytes[1];
- for (int port = 0; port < 8; port++)
- {
- result[offset++] = data.OutputEnable != null && port < data.OutputEnable.Length
- ? data.OutputEnable[port]
- : (byte)0;
- result[offset++] = 0; // Reserved
- if (data.PortProcessData != null
- && port < data.PortProcessData.Length
- && data.PortProcessData[port] != null)
- {
- int copyLen = Math.Min(PROCESS_DATA_PER_PORT, data.PortProcessData[port].Length);
- Array.Copy(data.PortProcessData[port], 0, result, offset, copyLen);
- }
- offset += PROCESS_DATA_PER_PORT;
- }
- return result;
- }
- private static SIG350Configuration ParseConfiguration(byte[] data)
- {
- if (data == null || data.Length < CONFIG_PORT_SIZE)
- throw new ArgumentException($"配置数据无效,至少需要 {CONFIG_PORT_SIZE} 字节");
- var config = new SIG350Configuration { RawLength = data.Length };
- int offset = 0;
- for (int port = 0; port < 8; port++)
- {
- config.PortMode[port] = data[offset++];
- config.ValidationBackup[port] = data[offset++];
- config.IQBehavior[port] = data[offset++];
- config.PortCycleTime[port] = data[offset++];
- config.VendorID[port] = BitConverter.ToUInt16(data, offset);
- offset += 2;
- // Device ID:手册为 UDINT,有效 24 bit
- config.DeviceID[port] = BitConverter.ToUInt32(data, offset) & 0x00FFFFFF;
- offset += 4;
- }
- // 完整配置 130 字节:layout + substitute + 8×6 DIO(中间无 Reserved)
- if (data.Length >= CONFIG_FULL_SIZE)
- {
- config.DIOProcessDataLayout = data[offset++];
- config.DOSubstituteMode = data[offset++];
- for (int port = 0; port < 8; port++)
- {
- config.DI_IQPinPolarity[port] = data[offset++];
- config.DI_CQPinPolarity[port] = data[offset++];
- config.DI_IQPinSignalFilter[port] = data[offset++];
- config.DI_CQPinSignalFilter[port] = data[offset++];
- config.DO_IQPinMode[port] = data[offset++];
- config.DO_CQPinMode[port] = data[offset++];
- }
- }
- return config;
- }
- private static byte[] SerializeConfiguration(SIG350Configuration config, int size)
- {
- if (size != CONFIG_PORT_SIZE && size != CONFIG_FULL_SIZE)
- size = size >= CONFIG_FULL_SIZE ? CONFIG_FULL_SIZE : CONFIG_PORT_SIZE;
- byte[] data = new byte[size];
- int offset = 0;
- for (int port = 0; port < 8; port++)
- {
- data[offset++] = config.PortMode[port];
- data[offset++] = config.ValidationBackup[port];
- data[offset++] = config.IQBehavior[port];
- data[offset++] = config.PortCycleTime[port];
- BitConverter.GetBytes(config.VendorID[port]).CopyTo(data, offset);
- offset += 2;
- BitConverter.GetBytes(config.DeviceID[port]).CopyTo(data, offset);
- offset += 4;
- }
- if (size >= CONFIG_FULL_SIZE)
- {
- data[offset++] = config.DIOProcessDataLayout;
- data[offset++] = config.DOSubstituteMode;
- for (int port = 0; port < 8; port++)
- {
- data[offset++] = config.DI_IQPinPolarity[port];
- data[offset++] = config.DI_CQPinPolarity[port];
- data[offset++] = config.DI_IQPinSignalFilter[port];
- data[offset++] = config.DI_CQPinSignalFilter[port];
- data[offset++] = config.DO_IQPinMode[port];
- data[offset++] = config.DO_CQPinMode[port];
- }
- }
- return data;
- }
- private byte[] ReadAssembly(ushort assemblyId)
- {
- byte[] data = _client.AssemblyObject.getInstance(assemblyId);
- OnLog?.Invoke($"读取 Assembly {assemblyId}: {data?.Length ?? 0} 字节");
- return data;
- }
- private void WriteAssembly(ushort assemblyId, byte[] data)
- {
- _client.AssemblyObject.setInstance(assemblyId, data);
- OnLog?.Invoke($"写入 Assembly {assemblyId}: {data?.Length ?? 0} 字节");
- }
- private static O2TData CloneOutput(O2TData src)
- {
- var dst = new O2TData
- {
- DOStatus = src.DOStatus,
- DOData = src.DOData
- };
- Array.Copy(src.OutputEnable, dst.OutputEnable, 8);
- for (int i = 0; i < 8; i++)
- Array.Copy(src.PortProcessData[i], dst.PortProcessData[i], PROCESS_DATA_PER_PORT);
- return dst;
- }
- #endregion
- #region 资源清理
- /// <summary>仅释放 EIP 会话,保留自动重连能力</summary>
- private void CleanupSessionOnly()
- {
- try
- {
- if (_isIOModeActive)
- StopIOCommunication();
- if (_isConnected)
- {
- try { _client.UnRegisterSession(); }
- catch (Exception ex) { OnLog?.Invoke($"UnRegisterSession: {ex.Message}"); }
- _isConnected = false;
- OnLog?.Invoke("EIP 会话已释放");
- }
- }
- catch (Exception ex)
- {
- OnLog?.Invoke($"EIP 会话释放异常: {ex.Message}");
- }
- }
- private void CleanupResources()
- {
- CleanupSessionOnly();
- }
- public void Dispose()
- {
- _disposed = true;
- _autoReconnectEnabled = false;
- StopReconnectLoop();
- CleanupResources();
- GC.SuppressFinalize(this);
- }
- #endregion
- }
- #region 枚举定义
- public enum IOConnectionType
- {
- ExclusiveOwner,
- InputOnly,
- ListenOnly
- }
- public enum PortMode
- {
- Deactivated = 0,
- IOLManual = 1,
- IOLAutostart = 2,
- DigitalInput = 3,
- DigitalOutput = 4
- }
- public enum IQBehavior
- {
- NotSupported = 0,
- DigitalInput = 1,
- DigitalOutput = 2
- }
- public enum RfidPayloadKind
- {
- /// <summary>RFH5xx Read UID(PDI Byte4~11,8 字节)</summary>
- Uid,
- /// <summary>RFH5xx 用户存储区(PDI Byte4 起,块地址/块数在 PDO 配置)</summary>
- UserMemory
- }
- /// <summary>RFH5xx 读取参数(SICK 操作说明 8.3.1)。</summary>
- public class Rfh5xxReadOptions
- {
- public RfidPayloadKind Kind { get; set; } = RfidPayloadKind.Uid;
- /// <summary>用户区起始块号(PDO Byte3)</summary>
- public byte UserBlockAddress { get; set; }
- /// <summary>读取块数量(PDO Byte1,1~31)</summary>
- public int UserBlockCount { get; set; } = 4;
- /// <summary>每块字节数:ICODE 多为 4,部分 IC 为 8</summary>
- public int UserBlockSizeBytes { get; set; } = 4;
- public Rfh5xxReadOptions Clone()
- {
- return new Rfh5xxReadOptions
- {
- Kind = Kind,
- UserBlockAddress = UserBlockAddress,
- UserBlockCount = UserBlockCount,
- UserBlockSizeBytes = UserBlockSizeBytes
- };
- }
- }
- public enum RFIDCommand
- {
- StartRead,
- StopRead,
- Write,
- Reset
- }
- #endregion
- #region 数据结构
- public class T2OData
- {
- public byte DIStatus { get; set; }
- public ushort DIData { get; set; }
- public byte[] PortStatus { get; set; } = new byte[8];
- public byte[][] PortProcessData { get; set; } = new byte[8][];
- public T2OData()
- {
- for (int i = 0; i < 8; i++)
- PortProcessData[i] = new byte[32];
- }
- }
- public class O2TData
- {
- public byte DOStatus { get; set; }
- public ushort DOData { get; set; }
- public byte[] OutputEnable { get; set; } = new byte[8];
- public byte[][] PortProcessData { get; set; } = new byte[8][];
- public O2TData()
- {
- for (int i = 0; i < 8; i++)
- PortProcessData[i] = new byte[32];
- }
- }
- public class SIG350Configuration
- {
- /// <summary>设备实际返回的配置长度(80 或 130)</summary>
- public int RawLength { get; set; }
- public byte[] PortMode { get; set; } = new byte[8];
- public byte[] ValidationBackup { get; set; } = new byte[8];
- public byte[] IQBehavior { get; set; } = new byte[8];
- public byte[] PortCycleTime { get; set; } = new byte[8];
- public ushort[] VendorID { get; set; } = new ushort[8];
- public uint[] DeviceID { get; set; } = new uint[8];
- public byte DIOProcessDataLayout { get; set; }
- public byte DOSubstituteMode { get; set; }
- public byte[] DI_CQPinPolarity { get; set; } = new byte[8];
- public byte[] DI_CQPinSignalFilter { get; set; } = new byte[8];
- public byte[] DI_IQPinPolarity { get; set; } = new byte[8];
- public byte[] DI_IQPinSignalFilter { get; set; } = new byte[8];
- public byte[] DO_IQPinMode { get; set; } = new byte[8];
- public byte[] DO_CQPinMode { get; set; } = new byte[8];
- }
- public class RFIDTagData
- {
- public int Port { get; set; }
- public byte[] Data { get; set; }
- public DateTime Timestamp { get; set; }
- public byte Quality { get; set; }
- public RfidPayloadKind PayloadKind { get; set; } = RfidPayloadKind.Uid;
- public string ToHexString()
- {
- if (Data == null) return string.Empty;
- return BitConverter.ToString(Data).Replace("-", " ");
- }
- public string ToASCIIString()
- {
- if (Data == null) return string.Empty;
- return Encoding.ASCII.GetString(Data);
- }
- public string ToUTF8String()
- {
- if (Data == null) return string.Empty;
- return Encoding.UTF8.GetString(Data);
- }
- }
- public class RFIDTagDataEventArgs : EventArgs
- {
- public T2OData Data { get; }
- public List<RFIDTagData> Tags { get; } = new List<RFIDTagData>();
- public RFIDTagDataEventArgs(T2OData data)
- : this(data, null)
- {
- }
- public RFIDTagDataEventArgs(T2OData data, IEnumerable<RFIDTagData> tags)
- {
- Data = data;
- if (tags != null)
- {
- Tags.AddRange(tags);
- return;
- }
- if (data == null || data.PortStatus == null || data.PortProcessData == null)
- return;
- for (int port = 0; port < 8; port++)
- {
- byte status = data.PortStatus[port];
- // 与 TryExtractTag 一致:必须 PQ 有效
- if ((status & 0x80) == 0)
- continue;
- byte[] processData = data.PortProcessData[port];
- byte[] tagBytes = SIG350RFIDClient.ExtractRFIDData(processData, new Rfh5xxReadOptions());
- if (tagBytes == null || tagBytes.Length == 0)
- continue;
- Tags.Add(new RFIDTagData
- {
- Port = port + 1,
- Data = tagBytes,
- Timestamp = DateTime.Now,
- Quality = status
- });
- }
- }
- }
- #endregion
- }
|