/****************************************************************************** ** Copyright (c) 2006-2018 Unified Automation GmbH All rights reserved. ** ** Software License Agreement ("SLA") Version 2.7 ** ** Unless explicitly acquired and licensed from Licensor under another ** license, the contents of this file are subject to the Software License ** Agreement ("SLA") Version 2.7, or subsequent versions ** as allowed by the SLA, and You may not copy or use this file in either ** source code or executable form, except in compliance with the terms and ** conditions of the SLA. ** ** All software distributed under the SLA is provided strictly on an ** "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, ** AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT ** LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ** PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the SLA for specific ** language governing rights and limitations under the SLA. ** ** Project: .NET based OPC UA Client Server SDK ** ** Description: OPC Unified Architecture Software Development Kit. ** ** The complete license agreement can be found here: ** http://unifiedautomation.com/License/SLA/2.7/ ******************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.IO; using System.Reflection; using System.Xml; using System.Xml.Serialization; using UnifiedAutomation.UaBase; using UnifiedAutomation.UaServer; namespace YourCompany.GettingStarted { /// /// A class that provides access to the underlying system. /// public class UnderlyingSystem : IDisposable { #region Constructors /// /// Initializes a new instance of the class. /// public UnderlyingSystem() { m_registers = new byte[4096]; m_blocks = new Dictionary(); } #endregion #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected void Dispose(bool disposing) { if (disposing) { if (m_simulationTimer != null) { m_simulationTimer.Dispose(); m_simulationTimer = null; } } } #endregion #region Public Methods /// /// Initializes this instance. /// public void Initialize() { // load the configuration file. Load(); // start the simulation timer. m_simulationTimer = new Timer(DoSimulation, null, 1000, 1000); } /// /// Gets the blockAddress configurations. /// /// public IEnumerable GetBlocks() { return m_blocks.Values; } /// /// Reads the tag value. /// /// The blockAddress. /// The tag. /// The value. null if no value exists. public object Read(int blockAddress, int tag) { lock (m_lock) { if (blockAddress < 0 || tag < 0) { return null; } if (blockAddress + tag > m_position - sizeof(int)) { return null; } BlockConfiguration controller = null; if (!m_blocks.TryGetValue(blockAddress, out controller)) { return null; } foreach (BlockProperty property in controller.Properties) { if (property.Offset == tag) { if (property.DataType == DataTypeIds.Double) { return (double)BitConverter.ToSingle(m_registers, blockAddress + tag); } if (property.DataType == DataTypeIds.Int32) { return BitConverter.ToInt32(m_registers, blockAddress + tag); } } } return null; } } /// /// Writes the tag value. /// /// The blockAddress. /// The tag. /// The value. /// /// True if the write was successful. /// public bool Write(int blockAddress, int tag, object value) { lock (m_lock) { if (blockAddress < 0 || tag < 0) { return false; } if (blockAddress + tag > m_position - sizeof(int)) { return false; } BlockConfiguration controller = null; if (!m_blocks.TryGetValue(blockAddress, out controller)) { return false; } foreach (BlockProperty property in controller.Properties) { if (property.Offset == tag) { if (!property.Writeable) { return false; } if (property.DataType == DataTypeIds.Double) { Write(blockAddress, tag, (double)value); return true; } if (property.DataType == DataTypeIds.Int32) { Write(blockAddress, tag, (int)value); return true; } } } return false; } } /// /// Starts the specified object id. /// /// The blockAddress. /// public StatusCode Start(int blockAddress) { lock (m_lock) { BlockConfiguration controller = null; if (!m_blocks.TryGetValue(blockAddress, out controller)) { return StatusCodes.BadNodeIdUnknown; } foreach (BlockProperty property in controller.Properties) { if (property.Name == "State") { Write(blockAddress, property.Offset, (int)1); break; } } return StatusCodes.Good; } } /// /// Stops the specified object id. /// /// The blockAddress. /// public StatusCode Stop(int blockAddress) { lock (m_lock) { BlockConfiguration controller = null; if (!m_blocks.TryGetValue(blockAddress, out controller)) { return StatusCodes.BadNodeIdUnknown; } foreach (BlockProperty property in controller.Properties) { if (property.Name == "State") { Write(blockAddress, property.Offset, (int)0); break; } } return StatusCodes.Good; } } /// /// Called when to start the simulation with a set point. /// /// The blockAddress. /// The temperature set point. /// The humdity set point. /// public StatusCode StartWithSetPoint(int blockAddress, double temperatureSetPoint, double humditySetPoint) { lock (m_lock) { BlockConfiguration controller = null; if (!m_blocks.TryGetValue(blockAddress, out controller)) { return StatusCodes.BadNodeIdUnknown; } foreach (BlockProperty property in controller.Properties) { if (property.Name == "TemperatureSetPoint") { Write(blockAddress, property.Offset, temperatureSetPoint); } else if (property.Name == "HumiditySetPoint") { Write(blockAddress, property.Offset, humditySetPoint); } else if (property.Name == "State") { Write(blockAddress, property.Offset, (int)1); } } return StatusCodes.Good; } } #endregion #region Private Method /// /// Loads the configuration for the system. /// private void Load() { foreach (string resourceName in Assembly.GetExecutingAssembly().GetManifestResourceNames()) { if (resourceName.EndsWith(".SystemConfiguration.xml")) { using (Stream istrm = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { XmlSerializer serializer = new XmlSerializer(typeof(Configuration)); m_configuration = (Configuration)serializer.Deserialize(istrm); } } } if (m_configuration.Controllers != null) { for (int ii = 0; ii < m_configuration.Controllers.Length; ii++) { ControllerConfiguration controller = m_configuration.Controllers[ii]; int blockAddress = m_position; int offset = m_position - blockAddress; BlockConfiguration data = new BlockConfiguration() { Address = blockAddress, Name = controller.Name, Type = controller.Type, Properties = new List() }; if (controller.Properties != null) { for (int jj = 0; jj < controller.Properties.Length; jj++) { ControllerProperty property = controller.Properties[jj]; NodeId dataTypeId = NodeId.Parse(property.DataType); string value = property.Value; Range range = null; if (!String.IsNullOrEmpty(property.Range)) { try { NumericRange nr = NumericRange.Parse(property.Range); range = new Range() { High = nr.End, Low = nr.Begin }; } catch (Exception) { range = null; } } data.Properties.Add(new BlockProperty() { Offset = offset, Name = controller.Properties[jj].Name, DataType = dataTypeId, Writeable = controller.Properties[jj].Writeable, Range = range }); switch ((uint)dataTypeId.Identifier) { case DataTypes.Int32: { Write(blockAddress, offset, (int)TypeUtils.Cast(value, BuiltInType.Int32)); offset += 4; break; } case DataTypes.Double: { Write(blockAddress, offset, (double)TypeUtils.Cast(value, BuiltInType.Double)); offset += 4; break; } } } } m_position += offset; m_blocks[blockAddress] = data; } } } /// /// Writes the specified offset. /// /// The offset. /// The value. private void Write(int blockAddress, int offset, int value) { byte[] bytes = BitConverter.GetBytes(value); Array.Copy(bytes, 0, m_registers, blockAddress + offset, bytes.Length); } /// /// Writes the specified offset. /// /// The offset. /// The value. private void Write(int blockAddress, int offset, double value) { byte[] bytes = BitConverter.GetBytes((float)value); Array.Copy(bytes, 0, m_registers, blockAddress + offset, bytes.Length); } /// /// Does the simulation. /// /// The state. private void DoSimulation(object state) { try { lock (m_lock) { foreach (var blockAddress in m_blocks) { for (int ii = 0; ii < blockAddress.Value.Properties.Count - 1; ii++) { string firstName = blockAddress.Value.Properties[ii].Name; string secondName = blockAddress.Value.Properties[ii + 1].Name; if (!secondName.StartsWith(firstName) || !secondName.EndsWith("SetPoint")) { continue; } int valueOffset = blockAddress.Value.Properties[ii].Offset; int setpointOffset = blockAddress.Value.Properties[ii + 1].Offset; double value = (double)Read(blockAddress.Key, valueOffset); double setpoint = (double)Read(blockAddress.Key, setpointOffset); Write(blockAddress.Key, valueOffset, Adjust(value, setpoint)); } } } } catch (Exception e) { TraceServer.Error(e, "Failed run simulation."); } } /// /// Adjusts the specified value. /// /// The value. /// The set point. /// private double Adjust(double value, double setPoint) { Random random = new Random(); double delta = (Math.Abs(setPoint - value) + 1) * random.NextDouble(); return value + ((random.Next() % 2 == 0) ? delta : -delta); } #endregion #region Configuration File Classes [XmlType(TypeName = "UnderlyingSystem.ControllerProperty", Namespace = "http://yourcompany.com/underlyingsystem")] public class ControllerProperty { [XmlElement(Order = 1)] public string Name { get; set; } [XmlElement(Order = 2)] public string DataType { get; set; } [XmlElement(Order = 3)] public string Value { get; set; } [XmlElement(Order = 4)] public bool Writeable { get; set; } [XmlElement(Order = 5)] public string Range { get; set; } } [XmlType(TypeName = "UnderlyingSystem.ControllerConfiguration", Namespace = "http://yourcompany.com/underlyingsystem")] public class ControllerConfiguration { [XmlElement(Order = 1)] public string Name { get; set; } [XmlElement(Order = 2)] public int Type { get; set; } [XmlElement(Order = 3)] public ControllerProperty[] Properties; } [XmlRoot(ElementName = "UnderlyingSystem.Configuration", Namespace = "http://yourcompany.com/underlyingsystem")] public class Configuration { [XmlElement(Order = 1)] public ControllerConfiguration[] Controllers; } #endregion #region Private Fields private object m_lock = new object(); private byte[] m_registers; private int m_position; private Dictionary m_blocks; private Configuration m_configuration; private Timer m_simulationTimer; #endregion } #region BlockProperty Class /// /// The configuration for a property of a blockAddress. /// public class BlockProperty { public int Offset; public string Name; public NodeId DataType; public bool Writeable; public Range Range; } #endregion #region BlockConfiguration Class /// /// The configuration for a blockAddress. /// public class BlockConfiguration { public int Address; public string Name; public int Type; public List Properties; } #endregion #region BlockType Class public static class BlockType { public const int AirConditioner = 1; public const int Furnace = 2; } #endregion }