466 lines
15 KiB
C#
466 lines
15 KiB
C#
using Siemens.Simatic.Simulation.Runtime;
|
|
using System;
|
|
|
|
namespace LibS7Adv
|
|
{
|
|
public class AdvCoSimulatorConnection : IPlcConnection
|
|
{
|
|
private IInstance? _instance;
|
|
private bool _isUpdating = false;
|
|
private DateTime _lastUpdateStartTime = DateTime.MinValue;
|
|
private readonly TimeSpan _updateTimeout = TimeSpan.FromSeconds(10);
|
|
|
|
public bool IsConnected { get; private set; }
|
|
public string Status { get; private set; } = "offline";
|
|
public string LastError { get; private set; } = "";
|
|
public ConnectionType ConnectionType => ConnectionType.AdvCoSimulator;
|
|
|
|
public bool Connect(string ipAddress, string instanceName)
|
|
{
|
|
try
|
|
{
|
|
_instance = SimulationRuntimeManager.CreateInterface(instanceName);
|
|
if (_instance != null)
|
|
{
|
|
_instance.OnSoftwareConfigurationChanged += Instance_OnSoftwareConfigurationChanged;
|
|
UpdateTagList();
|
|
Status = _instance.OperatingState.ToString();
|
|
IsConnected = true;
|
|
LastError = "";
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LastError = ex.Message;
|
|
Status = "offline";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
IsConnected = false;
|
|
Status = "offline";
|
|
_instance = null;
|
|
}
|
|
|
|
private void Instance_OnSoftwareConfigurationChanged(IInstance instance, SOnSoftwareConfigChangedParameter event_param)
|
|
{
|
|
_isUpdating = true;
|
|
_lastUpdateStartTime = DateTime.Now;
|
|
UpdateTagList();
|
|
}
|
|
|
|
private void UpdateTagList()
|
|
{
|
|
_isUpdating = true;
|
|
try
|
|
{
|
|
_instance?.UpdateTagList(ETagListDetails.IO | ETagListDetails.DB | ETagListDetails.M, true);
|
|
_isUpdating = false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LastError = ex.Message;
|
|
}
|
|
}
|
|
|
|
private bool CheckUpdateStatus()
|
|
{
|
|
if (_isUpdating)
|
|
{
|
|
if (DateTime.Now - _lastUpdateStartTime > _updateTimeout)
|
|
{
|
|
_lastUpdateStartTime = DateTime.Now;
|
|
UpdateTagList();
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool? ReadBool(string address)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus())
|
|
{
|
|
LastError = !IsConnected ? "Not Connected" : "";
|
|
return false;
|
|
}
|
|
|
|
var tag = PLCViewModel.ParseTagAddress(address);
|
|
if (tag.tagType == EDataType.Unknown)
|
|
{
|
|
return _instance?.ReadBool(address);
|
|
}
|
|
else if (tag.tagType == EDataType.Bool)
|
|
{
|
|
if (tag.areaType == EArea.Output)
|
|
return _instance?.OutputArea.ReadBit(tag.word_offset, tag.bit);
|
|
if (tag.areaType == EArea.Marker)
|
|
return _instance?.MarkerArea.ReadBit(tag.word_offset, tag.bit);
|
|
}
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, address);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool WriteBool(string address, bool value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus())
|
|
{
|
|
LastError = !IsConnected ? "Not Connected" : "";
|
|
return false;
|
|
}
|
|
|
|
var tag = PLCViewModel.ParseTagAddress(address);
|
|
if (tag.tagType == EDataType.Unknown)
|
|
{
|
|
_instance?.WriteBool(address, value);
|
|
}
|
|
else if (tag.tagType == EDataType.Bool)
|
|
{
|
|
if (tag.areaType == EArea.Input)
|
|
_instance?.InputArea.WriteBit(tag.word_offset, tag.bit, value);
|
|
if (tag.areaType == EArea.Output)
|
|
_instance?.OutputArea.WriteBit(tag.word_offset, tag.bit, value);
|
|
if (tag.areaType == EArea.Marker)
|
|
_instance?.MarkerArea.WriteBit(tag.word_offset, tag.bit, value);
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, address);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool ReadOutputBool(byte pByte, int pBit)
|
|
{
|
|
try
|
|
{
|
|
if (!CheckUpdateStatus()) return false;
|
|
return _instance?.OutputArea.ReadBit(pByte, (byte)pBit) ?? false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, $"Byte {pByte}, Bit {pBit}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void WriteInputBool(byte pByte, int pBit, bool value)
|
|
{
|
|
try
|
|
{
|
|
if (!CheckUpdateStatus()) return;
|
|
_instance?.InputArea.WriteBit(pByte, (byte)pBit, value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, $"Byte {pByte}, Bit {pBit}");
|
|
}
|
|
}
|
|
|
|
public string ReadNumber(string address, bool signed = false)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus())
|
|
{
|
|
LastError = !IsConnected ? "Not Connected" : "";
|
|
return "";
|
|
}
|
|
|
|
var tag = PLCViewModel.ParseTagAddress(address.Trim());
|
|
if (tag.tagType == EDataType.Unknown)
|
|
{
|
|
var data = _instance?.Read(address);
|
|
return data?.ToString() ?? "";
|
|
}
|
|
else if (tag.tagType == EDataType.Byte)
|
|
{
|
|
if (tag.areaType == EArea.Output)
|
|
{
|
|
var val = _instance?.OutputArea.ReadByte(tag.word_offset);
|
|
if (signed && val > 127)
|
|
return (val - 256).ToString();
|
|
return val?.ToString() ?? "";
|
|
}
|
|
if (tag.areaType == EArea.Marker)
|
|
{
|
|
var val = _instance?.MarkerArea.ReadByte(tag.word_offset);
|
|
if (signed && val > 127)
|
|
return (val - 256).ToString();
|
|
return val?.ToString() ?? "";
|
|
}
|
|
}
|
|
else if (tag.tagType == EDataType.Word)
|
|
{
|
|
if (tag.areaType == EArea.Output)
|
|
{
|
|
byte[] bytes = _instance?.OutputArea.ReadBytes(tag.word_offset, 2);
|
|
if (bytes != null)
|
|
{
|
|
Array.Reverse(bytes);
|
|
int value = signed ? BitConverter.ToInt16(bytes, 0) : BitConverter.ToUInt16(bytes, 0);
|
|
return value.ToString();
|
|
}
|
|
}
|
|
if (tag.areaType == EArea.Marker)
|
|
{
|
|
byte[] bytes = _instance?.MarkerArea.ReadBytes(tag.word_offset, 2);
|
|
if (bytes != null)
|
|
{
|
|
Array.Reverse(bytes);
|
|
int value = signed ? BitConverter.ToInt16(bytes, 0) : BitConverter.ToUInt16(bytes, 0);
|
|
return value.ToString();
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, address);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public bool WriteNumber(string address, object value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
|
|
// Intentar detectar el tipo de valor y usar el método específico apropiado
|
|
if (value is int intValue)
|
|
{
|
|
// Para enteros, intentamos WriteTagInt16 primero, luego WriteTagDInt si es necesario
|
|
if (intValue >= short.MinValue && intValue <= short.MaxValue)
|
|
{
|
|
return WriteTagInt16(address, intValue);
|
|
}
|
|
else
|
|
{
|
|
return WriteTagDInt(address, intValue);
|
|
}
|
|
}
|
|
else if (value is short shortValue)
|
|
{
|
|
return WriteTagInt16(address, shortValue);
|
|
}
|
|
else if (value is byte byteValue)
|
|
{
|
|
return WriteTagInt16(address, byteValue);
|
|
}
|
|
else
|
|
{
|
|
// Para otros tipos (incluidos float/double), usar WriteTag genérico
|
|
return WriteTag(address, value);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, address);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public object? ReadTag(string tagName)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return new SDataValue();
|
|
return _instance?.Read(tagName) ?? new SDataValue();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return new SDataValue();
|
|
}
|
|
}
|
|
|
|
public bool WriteTag(string tagName, object value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
if (value is SDataValue sValue)
|
|
{
|
|
_instance?.Write(tagName, sValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool? ReadTagBool(string tagName)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
return _instance?.ReadBool(tagName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool WriteTagBool(string tagName, bool value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
_instance?.WriteBool(tagName, value);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int? ReadTagInt16(string tagName)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return 0;
|
|
return _instance?.ReadInt16(tagName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public bool WriteTagInt16(string tagName, int value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
_instance?.WriteInt16(tagName, (short)value);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int? ReadTagDInt(string tagName)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return 0;
|
|
return _instance?.ReadInt32(tagName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public bool WriteTagDInt(string tagName, int value)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
_instance?.WriteInt32(tagName, value);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, tagName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public byte[]? ReadBytes(EArea area, uint offset, int length)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return null;
|
|
|
|
return area switch
|
|
{
|
|
EArea.Input => _instance?.InputArea.ReadBytes(offset, (uint)length),
|
|
EArea.Output => _instance?.OutputArea.ReadBytes(offset, (uint)length),
|
|
EArea.Marker => _instance?.MarkerArea.ReadBytes(offset, (uint)length),
|
|
_ => null
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, $"Area {area}, Offset {offset}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public bool WriteBytes(EArea area, uint offset, byte[] data)
|
|
{
|
|
try
|
|
{
|
|
if (!IsConnected || !CheckUpdateStatus()) return false;
|
|
|
|
switch (area)
|
|
{
|
|
case EArea.Input:
|
|
_instance?.InputArea.WriteBytes(offset, data);
|
|
break;
|
|
case EArea.Output:
|
|
_instance?.OutputArea.WriteBytes(offset, data);
|
|
break;
|
|
case EArea.Marker:
|
|
_instance?.MarkerArea.WriteBytes(offset, data);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleException(ex, $"Area {area}, Offset {offset}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void HandleException(Exception ex, string context)
|
|
{
|
|
if (ex.Message.Contains("NotUpToDate") ||
|
|
ex is SimulationRuntimeException sre && sre.RuntimeErrorCode == ERuntimeErrorCode.NotUpToDate)
|
|
{
|
|
_isUpdating = true;
|
|
_lastUpdateStartTime = DateTime.Now;
|
|
Task.Run(() => UpdateTagList());
|
|
LastError = $"{context}: Tags no actualizados, reintentando...";
|
|
}
|
|
else
|
|
{
|
|
LastError = $"{context}: {ex.Message}";
|
|
}
|
|
}
|
|
}
|
|
}
|