//******************************************************************************************************// ModbusPoller.cs - Gbtc//// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 07/26/2016 - J. Ritchie Carroll// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.IO;using System.IO.Ports;using System.Linq;using System.Net;using System.Net.Sockets;using System.Reflection;using System.Security.Cryptography;using System.Text;using System.Threading;using System.Timers;using GSF;using GSF.Configuration;using GSF.Diagnostics;using GSF.IO;using GSF.Reflection;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using GSF.TimeSeries.Statistics;using Modbus.Device;using Modbus.Utility;using ModbusAdapters.Model;using Measurement = GSF.TimeSeries.Measurement;using Timer = System.Timers.Timer; #pragma warning disable SCS0006 // Weak hash#pragma warning disable SCS0018 // Path traversal namespace ModbusAdapters{ [Description("Modbus: Implements Modbus polling capabilities")] [EditorBrowsable(EditorBrowsableState.Advanced)] // Normally defined as an input device protocol public class ModbusPoller : InputAdapterBase { #region [ Members ] // Nested Types private enum DerivedType { String, Single, Double, UInt16, Int32, UInt32, Int64, UInt64 } private enum RecordType { DI, // Discrete Input CO, // Coil IR, // Input Register HR // Holding Register } private enum SequenceType { Read, Write } private class AddressRecord { public readonly RecordType Type; public readonly ushort Address; public AddressRecord(string recordType, ushort address) { Enum.TryParse(recordType, true, out Type); Address = address; } } private class DerivedValue { public readonly DerivedType Type; public readonly List<AddressRecord> AddressRecords; public DerivedValue(DerivedType type) { Type = type; AddressRecords = new List<AddressRecord>(); } public DerivedValue(string derivedType) { Enum.TryParse(derivedType, true, out Type); AddressRecords = new List<AddressRecord>(); } public ushort[] GetDataValues(Group[] groups) { ushort[] dataValues = new ushort[AddressRecords.Count]; for (int i = 0; i < dataValues.Length; i++) { ushort address = AddressRecords[i].Address; foreach (Group group in groups) { if (group.HasDataValue(address)) { dataValues[i] = group.DataValues[address - group.StartAddress]; break; } } } return dataValues; } } private class Group { public RecordType Type; public ushort StartAddress; public ushort PointCount; public ushort[] DataValues; public bool HasDataValue(ushort address) => address >= StartAddress && address <= StartAddress + PointCount - 1; } private class Sequence { public readonly SequenceType Type; public readonly List<Group> Groups = new List<Group>(); public Sequence(SequenceType type) { Type = type; } } // Define a IDevice implementation for to provide daily reports private class DeviceProxy : IDevice { private readonly ModbusPoller m_parent; public DeviceProxy(ModbusPoller parent) { m_parent = parent; } // Gets or sets total data quality errors of this <see cref="IDevice"/>. public long DataQualityErrors { get; set; } // Gets or sets total time quality errors of this <see cref="IDevice"/>. public long TimeQualityErrors { get; set; } // Gets or sets total device errors of this <see cref="IDevice"/>. public long DeviceErrors { get { return m_parent.m_deviceErrors; } set { // Ignoring updates } } // Gets or sets total measurements received for this <see cref="IDevice"/> - in local context "successful connections" per day. public long MeasurementsReceived { get { return m_parent.m_measurementsReceived; } set { // Ignoring updates } } // Gets or sets total measurements expected to have been received for this <see cref="IDevice"/> - in local context "attempted connections" per day. public long MeasurementsExpected { get { return m_parent.m_measurementsExpected; } set { // Ignoring updates } } // Gets or sets the number of measurements received while this <see cref="IDevice"/> was reporting errors. public long MeasurementsWithError { get { return 0; } set { // Ignoring updates } } // Gets or sets the number of measurements (per frame) defined for this <see cref="IDevice"/>. public long MeasurementsDefined { get { return m_parent.OutputMeasurements.Length; } set { // Ignoring updates } } } // Constants private const byte DefaultUnitID = 255; private const int DefaultPollingRate = 2000; private const int DefaultInterSequenceGroupPollDelay = 250; // Fields private readonly DeviceProxy m_deviceProxy; private IModbusMaster m_modbusConnection; private TcpClient m_tcpClient; private UdpClient m_udpClient; private SerialPort m_serialClient; private byte m_unitID; private int m_pollingRate; private int m_interSequenceGroupPollDelay; private Dictionary<MeasurementKey, DerivedValue> m_derivedValues; private readonly Dictionary<MeasurementKey, string> m_derivedStrings; private List<Sequence> m_sequences; private Timer m_pollingTimer; private ShortSynchronizedOperation m_pollingOperation; private long m_pollOperations; private long m_deviceErrors; private long m_measurementsReceived; private long m_measurementsExpected; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="ModbusPoller"/>. /// </summary> public ModbusPoller() { m_deviceProxy = new DeviceProxy(this); m_derivedStrings = new Dictionary<MeasurementKey, string>(); } #endregion #region [ Properties ] /// <summary> /// Gets or sets unit ID for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines unit ID for Modbus connection."), DefaultValue(DefaultUnitID)] public byte UnitID { get { return m_unitID; } set { m_unitID = value; } } /// <summary> /// Gets or sets polling rate, in milliseconds, for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines overall polling rate, in milliseconds, for Modbus connection."), DefaultValue(DefaultPollingRate)] public int PollingRate { get { return m_pollingRate; } set { m_pollingRate = value; } } /// <summary> /// Gets or sets inter sequence-group poll delay, in milliseconds, for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines inter sequence-group poll delay, in milliseconds, for Modbus connection."), DefaultValue(DefaultInterSequenceGroupPollDelay)] public int InterSequenceGroupPollDelay { get { return m_interSequenceGroupPollDelay; } set { m_interSequenceGroupPollDelay = value; } } /// <summary> /// Gets total read or derived measurements over the Modbus poller instance lifetime. /// </summary> public long MeasurementsReceived => m_measurementsReceived; /// <summary> /// Gets flag that determines if the data input connects asynchronously. /// </summary> /// <remarks> /// Derived classes should return true when data input source is connects asynchronously, otherwise return false. /// </remarks> protected override bool UseAsyncConnect => false; /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> public override bool SupportsTemporalProcessing => false; /// <summary> /// Returns the detailed status of the data input source. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.AppendFormat(" Unit ID: {0}", UnitID); status.AppendLine(); status.AppendFormat(" Polling Rate: {0:N0}ms", PollingRate); status.AppendLine(); status.AppendFormat("Inter Seq-Group Poll Delay: {0:N0}ms", InterSequenceGroupPollDelay); status.AppendLine(); status.AppendFormat(" Executed Poll Operations: {0:N0}", m_pollOperations); status.AppendLine(); status.AppendFormat(" Device Errors: {0:N0}", m_deviceErrors); status.AppendLine(); status.AppendFormat(" Measurements Received: {0:N0}", m_measurementsReceived); status.AppendLine(); status.AppendFormat(" Measurements Expected: {0:N0}", m_measurementsExpected); status.AppendLine(); status.AppendFormat(" Last Derived Strings: {0:N0} total", m_derivedStrings.Count); status.AppendLine(); foreach (KeyValuePair<MeasurementKey, string> item in m_derivedStrings) { status.AppendFormat("{0} = {1}", item.Key.ToString().PadLeft(10), item.Value); status.AppendLine(); } return status.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="ModbusPoller"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { StatisticsEngine.Unregister(m_deviceProxy); DisposeConnections(); if ((object)m_pollingTimer != null) { m_pollingTimer.Enabled = false; m_pollingTimer.Elapsed -= m_pollingTimer_Elapsed; m_pollingTimer.Dispose(); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } private void DisposeConnections() { m_tcpClient?.Dispose(); m_udpClient?.Dispose(); m_serialClient?.Dispose(); m_modbusConnection?.Dispose(); } /// <summary> /// Initializes <see cref="ModbusPoller" />. /// </summary> public override void Initialize() { base.Initialize(); ConnectionStringParser<ConnectionStringParameterAttribute> parser = new ConnectionStringParser<ConnectionStringParameterAttribute>(); parser.ParseConnectionString(ConnectionString, this); // Register downloader with the statistics engine StatisticsEngine.Register(this, "Modbus", "MOD"); StatisticsEngine.Register(m_deviceProxy, Name, "Device", "PMU"); // Attach to output measurements for Modbus device OutputMeasurements = ParseOutputMeasurements(DataSource, false, $"FILTER ActiveMeasurements WHERE Device = '{Name}'"); // Parse derived value expressions from defined signal reference fields m_derivedValues = OutputMeasurements.Select(measurement => measurement.Key).ToDictionary(key => key, key => { DataTable measurements = DataSource.Tables["ActiveMeasurements"]; DerivedValue derivedValue = null; DataRow[] records = measurements.Select($"ID = '{key}'"); if (records.Length > 0) derivedValue = ParseDerivedValue(records[0]["SignalReference"].ToNonNullString()); return derivedValue; }); m_sequences = new List<Sequence>(); Dictionary<string, string> settings = Settings; string setting; int sequenceCount = 0; if (settings.TryGetValue("sequenceCount", out setting)) int.TryParse(setting, out sequenceCount); for (int i = 0; i < sequenceCount; i++) { if (settings.TryGetValue($"sequence{i}", out setting)) { Dictionary<string, string> sequenceSettings = setting.ParseKeyValuePairs(); SequenceType sequenceType = SequenceType.Read; if (sequenceSettings.TryGetValue("sequenceType", out setting)) Enum.TryParse(setting, true, out sequenceType); Sequence sequence = new Sequence(sequenceType); int groupCount; Lines 1–500 of 1114. Load more
//****************************************************************************************************** // ModbusPoller.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 07/26/2016 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.IO; using System.IO.Ports; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Timers; using GSF; using GSF.Configuration; using GSF.Diagnostics; using GSF.IO; using GSF.Reflection; using GSF.Threading; using GSF.TimeSeries; using GSF.TimeSeries.Adapters; using GSF.TimeSeries.Statistics; using Modbus.Device; using Modbus.Utility; using ModbusAdapters.Model; using Measurement = GSF.TimeSeries.Measurement; using Timer = System.Timers.Timer; #pragma warning disable SCS0006 // Weak hash #pragma warning disable SCS0018 // Path traversal namespace ModbusAdapters { [Description("Modbus: Implements Modbus polling capabilities")] [EditorBrowsable(EditorBrowsableState.Advanced)] // Normally defined as an input device protocol public class ModbusPoller : InputAdapterBase { #region [ Members ] // Nested Types private enum DerivedType { String, Single, Double, UInt16, Int32, UInt32, Int64, UInt64 } private enum RecordType { DI, // Discrete Input CO, // Coil IR, // Input Register HR // Holding Register } private enum SequenceType { Read, Write } private class AddressRecord { public readonly RecordType Type; public readonly ushort Address; public AddressRecord(string recordType, ushort address) { Enum.TryParse(recordType, true, out Type); Address = address; } } private class DerivedValue { public readonly DerivedType Type; public readonly List<AddressRecord> AddressRecords; public DerivedValue(DerivedType type) { Type = type; AddressRecords = new List<AddressRecord>(); } public DerivedValue(string derivedType) { Enum.TryParse(derivedType, true, out Type); AddressRecords = new List<AddressRecord>(); } public ushort[] GetDataValues(Group[] groups) { ushort[] dataValues = new ushort[AddressRecords.Count]; for (int i = 0; i < dataValues.Length; i++) { ushort address = AddressRecords[i].Address; foreach (Group group in groups) { if (group.HasDataValue(address)) { dataValues[i] = group.DataValues[address - group.StartAddress]; break; } } } return dataValues; } } private class Group { public RecordType Type; public ushort StartAddress; public ushort PointCount; public ushort[] DataValues; public bool HasDataValue(ushort address) => address >= StartAddress && address <= StartAddress + PointCount - 1; } private class Sequence { public readonly SequenceType Type; public readonly List<Group> Groups = new List<Group>(); public Sequence(SequenceType type) { Type = type; } } // Define a IDevice implementation for to provide daily reports private class DeviceProxy : IDevice { private readonly ModbusPoller m_parent; public DeviceProxy(ModbusPoller parent) { m_parent = parent; } // Gets or sets total data quality errors of this <see cref="IDevice"/>. public long DataQualityErrors { get; set; } // Gets or sets total time quality errors of this <see cref="IDevice"/>. public long TimeQualityErrors { get; set; } // Gets or sets total device errors of this <see cref="IDevice"/>. public long DeviceErrors { get { return m_parent.m_deviceErrors; } set { // Ignoring updates } } // Gets or sets total measurements received for this <see cref="IDevice"/> - in local context "successful connections" per day. public long MeasurementsReceived { get { return m_parent.m_measurementsReceived; } set { // Ignoring updates } } // Gets or sets total measurements expected to have been received for this <see cref="IDevice"/> - in local context "attempted connections" per day. public long MeasurementsExpected { get { return m_parent.m_measurementsExpected; } set { // Ignoring updates } } // Gets or sets the number of measurements received while this <see cref="IDevice"/> was reporting errors. public long MeasurementsWithError { get { return 0; } set { // Ignoring updates } } // Gets or sets the number of measurements (per frame) defined for this <see cref="IDevice"/>. public long MeasurementsDefined { get { return m_parent.OutputMeasurements.Length; } set { // Ignoring updates } } } // Constants private const byte DefaultUnitID = 255; private const int DefaultPollingRate = 2000; private const int DefaultInterSequenceGroupPollDelay = 250; // Fields private readonly DeviceProxy m_deviceProxy; private IModbusMaster m_modbusConnection; private TcpClient m_tcpClient; private UdpClient m_udpClient; private SerialPort m_serialClient; private byte m_unitID; private int m_pollingRate; private int m_interSequenceGroupPollDelay; private Dictionary<MeasurementKey, DerivedValue> m_derivedValues; private readonly Dictionary<MeasurementKey, string> m_derivedStrings; private List<Sequence> m_sequences; private Timer m_pollingTimer; private ShortSynchronizedOperation m_pollingOperation; private long m_pollOperations; private long m_deviceErrors; private long m_measurementsReceived; private long m_measurementsExpected; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Creates a new <see cref="ModbusPoller"/>. /// </summary> public ModbusPoller() { m_deviceProxy = new DeviceProxy(this); m_derivedStrings = new Dictionary<MeasurementKey, string>(); } #endregion #region [ Properties ] /// <summary> /// Gets or sets unit ID for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines unit ID for Modbus connection."), DefaultValue(DefaultUnitID)] public byte UnitID { get { return m_unitID; } set { m_unitID = value; } } /// <summary> /// Gets or sets polling rate, in milliseconds, for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines overall polling rate, in milliseconds, for Modbus connection."), DefaultValue(DefaultPollingRate)] public int PollingRate { get { return m_pollingRate; } set { m_pollingRate = value; } } /// <summary> /// Gets or sets inter sequence-group poll delay, in milliseconds, for Modbus connection. /// </summary> [ConnectionStringParameter, Description("Defines inter sequence-group poll delay, in milliseconds, for Modbus connection."), DefaultValue(DefaultInterSequenceGroupPollDelay)] public int InterSequenceGroupPollDelay { get { return m_interSequenceGroupPollDelay; } set { m_interSequenceGroupPollDelay = value; } } /// <summary> /// Gets total read or derived measurements over the Modbus poller instance lifetime. /// </summary> public long MeasurementsReceived => m_measurementsReceived; /// <summary> /// Gets flag that determines if the data input connects asynchronously. /// </summary> /// <remarks> /// Derived classes should return true when data input source is connects asynchronously, otherwise return false. /// </remarks> protected override bool UseAsyncConnect => false; /// <summary> /// Gets the flag indicating if this adapter supports temporal processing. /// </summary> public override bool SupportsTemporalProcessing => false; /// <summary> /// Returns the detailed status of the data input source. /// </summary> public override string Status { get { StringBuilder status = new StringBuilder(); status.Append(base.Status); status.AppendFormat(" Unit ID: {0}", UnitID); status.AppendLine(); status.AppendFormat(" Polling Rate: {0:N0}ms", PollingRate); status.AppendLine(); status.AppendFormat("Inter Seq-Group Poll Delay: {0:N0}ms", InterSequenceGroupPollDelay); status.AppendLine(); status.AppendFormat(" Executed Poll Operations: {0:N0}", m_pollOperations); status.AppendLine(); status.AppendFormat(" Device Errors: {0:N0}", m_deviceErrors); status.AppendLine(); status.AppendFormat(" Measurements Received: {0:N0}", m_measurementsReceived); status.AppendLine(); status.AppendFormat(" Measurements Expected: {0:N0}", m_measurementsExpected); status.AppendLine(); status.AppendFormat(" Last Derived Strings: {0:N0} total", m_derivedStrings.Count); status.AppendLine(); foreach (KeyValuePair<MeasurementKey, string> item in m_derivedStrings) { status.AppendFormat("{0} = {1}", item.Key.ToString().PadLeft(10), item.Value); status.AppendLine(); } return status.ToString(); } } #endregion #region [ Methods ] /// <summary> /// Releases the unmanaged resources used by the <see cref="ModbusPoller"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { StatisticsEngine.Unregister(m_deviceProxy); DisposeConnections(); if ((object)m_pollingTimer != null) { m_pollingTimer.Enabled = false; m_pollingTimer.Elapsed -= m_pollingTimer_Elapsed; m_pollingTimer.Dispose(); } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } private void DisposeConnections() { m_tcpClient?.Dispose(); m_udpClient?.Dispose(); m_serialClient?.Dispose(); m_modbusConnection?.Dispose(); } /// <summary> /// Initializes <see cref="ModbusPoller" />. /// </summary> public override void Initialize() { base.Initialize(); ConnectionStringParser<ConnectionStringParameterAttribute> parser = new ConnectionStringParser<ConnectionStringParameterAttribute>(); parser.ParseConnectionString(ConnectionString, this); // Register downloader with the statistics engine StatisticsEngine.Register(this, "Modbus", "MOD"); StatisticsEngine.Register(m_deviceProxy, Name, "Device", "PMU"); // Attach to output measurements for Modbus device OutputMeasurements = ParseOutputMeasurements(DataSource, false, $"FILTER ActiveMeasurements WHERE Device = '{Name}'"); // Parse derived value expressions from defined signal reference fields m_derivedValues = OutputMeasurements.Select(measurement => measurement.Key).ToDictionary(key => key, key => { DataTable measurements = DataSource.Tables["ActiveMeasurements"]; DerivedValue derivedValue = null; DataRow[] records = measurements.Select($"ID = '{key}'"); if (records.Length > 0) derivedValue = ParseDerivedValue(records[0]["SignalReference"].ToNonNullString()); return derivedValue; }); m_sequences = new List<Sequence>(); Dictionary<string, string> settings = Settings; string setting; int sequenceCount = 0; if (settings.TryGetValue("sequenceCount", out setting)) int.TryParse(setting, out sequenceCount); for (int i = 0; i < sequenceCount; i++) { if (settings.TryGetValue($"sequence{i}", out setting)) { Dictionary<string, string> sequenceSettings = setting.ParseKeyValuePairs(); SequenceType sequenceType = SequenceType.Read; if (sequenceSettings.TryGetValue("sequenceType", out setting)) Enum.TryParse(setting, true, out sequenceType); Sequence sequence = new Sequence(sequenceType); int groupCount;