mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 18:51:28 +08:00
添加西门子S7协议插件
This commit is contained in:
@@ -0,0 +1 @@
|
||||
global using System;
|
@@ -0,0 +1,226 @@
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
public enum S7Area : byte
|
||||
{
|
||||
PE = 0x81,
|
||||
PA = 0x82,
|
||||
MK = 0x83,
|
||||
DB = 0x84,
|
||||
CT = 0x1C,
|
||||
TM = 0x1D,
|
||||
AI = 0X06,
|
||||
AQ = 0x07,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 西门子PLC地址数据信息
|
||||
/// </summary>
|
||||
public class SiemensAddress : DeviceAddressBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据块代码
|
||||
/// </summary>
|
||||
public byte DataCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB块数据信息
|
||||
/// </summary>
|
||||
public ushort DbBlock { get; set; }
|
||||
|
||||
|
||||
public static int CalculateAddressStarted(string address, bool isCounterOrTimer = false)
|
||||
{
|
||||
if (address.IndexOf('.') < 0)
|
||||
{
|
||||
return isCounterOrTimer ? Convert.ToInt32(address) : Convert.ToInt32(address) * 8;
|
||||
}
|
||||
|
||||
string[] strArray = address.Split('.');
|
||||
return (Convert.ToInt32(strArray[0]) * 8) + Convert.ToInt32(strArray[1]);
|
||||
}
|
||||
|
||||
|
||||
public static OperResult<SiemensAddress> ParseFrom(string address)
|
||||
{
|
||||
return ParseFrom(address, 0);
|
||||
}
|
||||
|
||||
public static OperResult<SiemensAddress> ParseFrom(string address, int length)
|
||||
{
|
||||
SiemensAddress s7AddressData = new SiemensAddress();
|
||||
try
|
||||
{
|
||||
address = address.ToUpper();
|
||||
s7AddressData.Length = length;
|
||||
s7AddressData.DbBlock = 0;
|
||||
if (address.StartsWith("AI"))
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.AI;
|
||||
if (address.StartsWith("AIX") || address.StartsWith("AIB") || address.StartsWith("AIW") || address.StartsWith("AID"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(3));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
}
|
||||
else if (address.StartsWith("AQ"))
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.AQ;
|
||||
if (address.StartsWith("AQX") || address.StartsWith("AQB") || address.StartsWith("AQW") || address.StartsWith("AQD"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(3));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
}
|
||||
else if (address[0] == 'I')
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.PE;
|
||||
if (address.StartsWith("IX") || address.StartsWith("IB") || address.StartsWith("IW") || address.StartsWith("ID"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1));
|
||||
}
|
||||
}
|
||||
else if (address[0] == 'Q')
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.PA;
|
||||
if (address.StartsWith("QX") || address.StartsWith("QB") || address.StartsWith("QW") || address.StartsWith("QD"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1));
|
||||
}
|
||||
}
|
||||
else if (address[0] == 'M')
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.MK;
|
||||
if (address.StartsWith("MX") || address.StartsWith("MB") || address.StartsWith("MW") || address.StartsWith("MD"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1));
|
||||
}
|
||||
}
|
||||
else if (address[0] == 'D' || address.Substring(0, 2) == "DB")
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.DB;
|
||||
string[] strArray = address.Split('.');
|
||||
s7AddressData.DbBlock = address[1] != 'B' ? Convert.ToUInt16(strArray[0].Substring(1)) : Convert.ToUInt16(strArray[0].Substring(2));
|
||||
string address1 = address.Substring(address.IndexOf('.') + 1);
|
||||
if (address1.StartsWith("DBX") || address1.StartsWith("DBB") || address1.StartsWith("DBW") || address1.StartsWith("DBD"))
|
||||
{
|
||||
address1 = address1.Substring(3);
|
||||
}
|
||||
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address1);
|
||||
}
|
||||
else if (address[0] == 'T')
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.TM;
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1), true);
|
||||
}
|
||||
else if (address[0] == 'C')
|
||||
{
|
||||
s7AddressData.DataCode = (byte)S7Area.CT;
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (address[0] != 'V')
|
||||
{
|
||||
return new OperResult<SiemensAddress>("不支持的类型");
|
||||
}
|
||||
|
||||
s7AddressData.DataCode = (byte)S7Area.DB;
|
||||
s7AddressData.DbBlock = 1;
|
||||
if (address.StartsWith("VB") || address.StartsWith("VW") || address.StartsWith("VD") || address.StartsWith("VX"))
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(2));
|
||||
}
|
||||
else
|
||||
{
|
||||
s7AddressData.AddressStart = CalculateAddressStarted(address.Substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<SiemensAddress>(ex.Message);
|
||||
}
|
||||
return OperResult.CreateSuccessResult<SiemensAddress>(s7AddressData);
|
||||
}
|
||||
|
||||
public override void Parse(string address, int length)
|
||||
{
|
||||
OperResult<SiemensAddress> from = ParseFrom(address, length);
|
||||
if (!from.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddressStart = from.Content.AddressStart;
|
||||
Length = from.Content.Length;
|
||||
DataCode = from.Content.DataCode;
|
||||
DbBlock = from.Content.DbBlock;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (DataCode == (byte)S7Area.TM)
|
||||
{
|
||||
return "T" + AddressStart.ToString();
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.CT)
|
||||
{
|
||||
return "C" + AddressStart.ToString();
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.AI)
|
||||
{
|
||||
return "AI" + GetActualStringAddress(AddressStart);
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.AQ)
|
||||
{
|
||||
return "AQ" + GetActualStringAddress(AddressStart);
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.PE)
|
||||
{
|
||||
return "I" + GetActualStringAddress(AddressStart);
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.PA)
|
||||
{
|
||||
return "Q" + GetActualStringAddress(AddressStart);
|
||||
}
|
||||
|
||||
if (DataCode == (byte)S7Area.MK)
|
||||
{
|
||||
return "M" + GetActualStringAddress(AddressStart);
|
||||
}
|
||||
|
||||
return DataCode == (byte)S7Area.DB ? "DB" + DbBlock.ToString() + "." + GetActualStringAddress(AddressStart) : AddressStart.ToString();
|
||||
}
|
||||
|
||||
private static string GetActualStringAddress(int addressStart)
|
||||
{
|
||||
return addressStart % 8 == 0 ? (addressStart / 8).ToString() : string.Format("{0}.{1}", addressStart / 8, addressStart % 8);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com/S7NetPlus/s7netplus/blob/develop/S7.Net/Types/DateTime.cs
|
||||
/// Contains the methods to convert between <see cref="T:System.DateTime"/> and S7 representation of datetime values.
|
||||
/// </summary>
|
||||
public static class DateTime
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMinimumDateTime = new System.DateTime(1990, 1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum <see cref="T:System.DateTime"/> value supported by the specification.
|
||||
/// </summary>
|
||||
public static readonly System.DateTime SpecMaximumDateTime = new System.DateTime(2089, 12, 31, 23, 59, 59, 999);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a <see cref="T:System.DateTime"/> value from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>A <see cref="T:System.DateTime"/> object representing the value read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not 8 or any value in <paramref name="bytes"/>
|
||||
/// is outside the valid range of values.</exception>
|
||||
public static System.DateTime FromByteArray(byte[] bytes)
|
||||
{
|
||||
return FromByteArrayImpl(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an array of <see cref="T:System.DateTime"/> values from bytes.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Input bytes read from PLC.</param>
|
||||
/// <returns>An array of <see cref="T:System.DateTime"/> objects representing the values read from PLC.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the length of
|
||||
/// <paramref name="bytes"/> is not a multiple of 8 or any value in
|
||||
/// <paramref name="bytes"/> is outside the valid range of values.</exception>
|
||||
public static System.DateTime[] ToArray(byte[] bytes)
|
||||
{
|
||||
if (bytes.Length % 8 != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Length,
|
||||
$"Parsing an array of DateTime requires a multiple of 8 bytes of input data, input data is '{bytes.Length}' long.");
|
||||
|
||||
var cnt = bytes.Length / 8;
|
||||
var result = new System.DateTime[bytes.Length / 8];
|
||||
|
||||
for (var i = 0; i < cnt; i++)
|
||||
result[i] = FromByteArrayImpl(new ArraySegment<byte>(bytes, i * 8, 8));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static System.DateTime FromByteArrayImpl(IList<byte> bytes)
|
||||
{
|
||||
if (bytes.Count != 8)
|
||||
throw new ArgumentOutOfRangeException(nameof(bytes), bytes.Count,
|
||||
$"Parsing a DateTime requires exactly 8 bytes of input data, input data is {bytes.Count} bytes long.");
|
||||
|
||||
int DecodeBcd(byte input) => 10 * (input >> 4) + (input & 0b00001111);
|
||||
|
||||
int ByteToYear(byte bcdYear)
|
||||
{
|
||||
var input = DecodeBcd(bcdYear);
|
||||
if (input < 90) return input + 2000;
|
||||
if (input < 100) return input + 1900;
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(bcdYear), bcdYear,
|
||||
$"Value '{input}' is higher than the maximum '99' of S7 date and time representation.");
|
||||
}
|
||||
|
||||
int AssertRangeInclusive(int input, byte min, byte max, string field)
|
||||
{
|
||||
if (input < min)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is lower than the minimum '{min}' allowed for {field}.");
|
||||
if (input > max)
|
||||
throw new ArgumentOutOfRangeException(nameof(input), input,
|
||||
$"Value '{input}' is higher than the maximum '{max}' allowed for {field}.");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
var year = ByteToYear(bytes[0]);
|
||||
var month = AssertRangeInclusive(DecodeBcd(bytes[1]), 1, 12, "month");
|
||||
var day = AssertRangeInclusive(DecodeBcd(bytes[2]), 1, 31, "day of month");
|
||||
var hour = AssertRangeInclusive(DecodeBcd(bytes[3]), 0, 23, "hour");
|
||||
var minute = AssertRangeInclusive(DecodeBcd(bytes[4]), 0, 59, "minute");
|
||||
var second = AssertRangeInclusive(DecodeBcd(bytes[5]), 0, 59, "second");
|
||||
var hsec = AssertRangeInclusive(DecodeBcd(bytes[6]), 0, 99, "first two millisecond digits");
|
||||
var msec = AssertRangeInclusive(bytes[7] >> 4, 0, 9, "third millisecond digit");
|
||||
var dayOfWeek = AssertRangeInclusive(bytes[7] & 0b00001111, 1, 7, "day of week");
|
||||
|
||||
return new System.DateTime(year, month, day, hour, minute, second, hsec * 10 + msec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="T:System.DateTime"/> value to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The DateTime value to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representation of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the value of
|
||||
/// <paramref name="dateTime"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime dateTime)
|
||||
{
|
||||
byte EncodeBcd(int value)
|
||||
{
|
||||
return (byte)((value / 10 << 4) | value % 10);
|
||||
}
|
||||
|
||||
if (dateTime < SpecMinimumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is before the minimum '{SpecMinimumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
if (dateTime > SpecMaximumDateTime)
|
||||
throw new ArgumentOutOfRangeException(nameof(dateTime), dateTime,
|
||||
$"Date time '{dateTime}' is after the maximum '{SpecMaximumDateTime}' supported in S7 date time representation.");
|
||||
|
||||
byte MapYear(int year) => (byte)(year < 2000 ? year - 1900 : year - 2000);
|
||||
|
||||
int DayOfWeekToInt(DayOfWeek dayOfWeek) => (int)dayOfWeek + 1;
|
||||
|
||||
return new[]
|
||||
{
|
||||
EncodeBcd(MapYear(dateTime.Year)),
|
||||
EncodeBcd(dateTime.Month),
|
||||
EncodeBcd(dateTime.Day),
|
||||
EncodeBcd(dateTime.Hour),
|
||||
EncodeBcd(dateTime.Minute),
|
||||
EncodeBcd(dateTime.Second),
|
||||
EncodeBcd(dateTime.Millisecond / 10),
|
||||
(byte) (dateTime.Millisecond % 10 << 4 | DayOfWeekToInt(dateTime.DayOfWeek))
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of <see cref="T:System.DateTime"/> values to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="dateTimes">The DateTime values to convert.</param>
|
||||
/// <returns>A byte array containing the S7 date time representations of <paramref name="dateTime"/>.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when any value of
|
||||
/// <paramref name="dateTimes"/> is before <see cref="P:SpecMinimumDateTime"/>
|
||||
/// or after <see cref="P:SpecMaximumDateTime"/>.</exception>
|
||||
public static byte[] ToByteArray(System.DateTime[] dateTimes)
|
||||
{
|
||||
var bytes = new List<byte>(dateTimes.Length * 8);
|
||||
foreach (var dateTime in dateTimes) bytes.AddRange(ToByteArray(dateTime));
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,369 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
internal partial class SiemensHelper
|
||||
{
|
||||
#region 验证
|
||||
//internal static OperResult<byte[]> AnalysisReadBit(byte[] content)
|
||||
//{
|
||||
// int length = 1;
|
||||
// if (content.Length < 21 || content[20] != 1)
|
||||
// return new OperResult<byte[]>("数据块长度校验失败");
|
||||
// byte[] numArray = new byte[length];
|
||||
// if (content[21] == byte.MaxValue && content[22] == 3)//Bit:3;Byte:4;Counter或者Timer:9
|
||||
// {
|
||||
// numArray[0] = content[25];//+4
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return new OperResult<byte[]>((int)content[21] + GetCpuError(content[21]));
|
||||
// }
|
||||
// return OperResult.CreateSuccessResult<byte[]>(numArray);
|
||||
//}
|
||||
|
||||
internal static OperResult<byte[]> AnalysisReadByte(byte[] sends, byte[] content)
|
||||
{
|
||||
int length = 0;
|
||||
int itemLen = (sends.Length - 19) / 12;
|
||||
|
||||
for (int index = 0; index < itemLen; index++)
|
||||
{
|
||||
if (sends[22 + (index * 12)] >= (byte)S7WordLength.Word)
|
||||
{
|
||||
length += ((sends[23 + (index * 12)] * 256) + sends[24 + (index * 12)]) * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
length += (sends[23 + (index * 12)] * 256) + sends[24 + (index * 12)];
|
||||
}
|
||||
}
|
||||
|
||||
if (content.Length < 21 || content[20] != itemLen)
|
||||
{
|
||||
return new OperResult<byte[]>("数据块长度校验失败");
|
||||
}
|
||||
|
||||
byte[] dataArray = new byte[length];
|
||||
int index1 = 0;
|
||||
int dataIndex = 0;
|
||||
for (int index2 = 21; index2 < content.Length; index2++)
|
||||
{
|
||||
if (index2 + 1 < content.Length)
|
||||
{
|
||||
int s7len = 0;
|
||||
if (sends[22 + (index1 * 12)] >= (byte)S7WordLength.Word)
|
||||
{
|
||||
s7len = ((sends[23 + (index1 * 12)] * 256) + sends[24 + (index1 * 12)]) * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
s7len = (sends[23 + (index1 * 12)] * 256) + sends[24 + (index1 * 12)];
|
||||
}
|
||||
if (content[index2] == byte.MaxValue && content[index2 + 1] == 4)//Bit:3;Byte:4;Counter或者Timer:9
|
||||
{
|
||||
Array.Copy(content, index2 + 4, dataArray, dataIndex, s7len);
|
||||
index2 += s7len + 3;
|
||||
dataIndex += s7len;
|
||||
index1++;
|
||||
}
|
||||
else if (content[index2] == byte.MaxValue && content[index2 + 1] == 9)//Counter或者Timer:9
|
||||
{
|
||||
int num = (content[index2 + 2] * 256) + content[index2 + 3];
|
||||
if (num % 3 == 0)
|
||||
{
|
||||
for (int index3 = 0; index3 < num / 3; index3++)
|
||||
{
|
||||
Array.Copy(content, index2 + 5 + (3 * index3), dataArray, dataIndex, 2);
|
||||
dataIndex += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index4 = 0; index4 < num / 5; index4++)
|
||||
{
|
||||
Array.Copy(content, index2 + 7 + (5 * index4), dataArray, dataIndex, 2);
|
||||
dataIndex += 2;
|
||||
}
|
||||
}
|
||||
index2 += num + 4;
|
||||
index1++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new OperResult<byte[]>((int)content[index2] + GetCpuError(content[index2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return OperResult.CreateSuccessResult(dataArray);
|
||||
|
||||
}
|
||||
|
||||
internal static OperResult<byte[]> AnalysisWrite(byte[] content)
|
||||
{
|
||||
if (content.Length < 22)
|
||||
{
|
||||
return new OperResult<byte[]>(content) { Message = "未知错误" };
|
||||
}
|
||||
|
||||
byte err = content[21];
|
||||
if (err == byte.MaxValue)
|
||||
{
|
||||
return new OperResult<byte[]>((int)content[21] + GetCpuError(content[21]));
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult(content);
|
||||
}
|
||||
|
||||
}
|
||||
private static string GetCpuError(ushort Error)
|
||||
{
|
||||
switch (Error)
|
||||
{
|
||||
case 0x05: return "地址超限";
|
||||
case 0x06: return "返回长度无效";
|
||||
case 0x07: return "数据大小不匹配";
|
||||
case 0x0a:
|
||||
case 0xd209: return "数据块不存在";
|
||||
case 0x8500: return "超出PDU大小";
|
||||
case 0xdc01: return "无效的值";
|
||||
case 0x8104: return "功能不可用";
|
||||
case 0xd241: return "需要密码";
|
||||
case 0xd602: return "无效密码";
|
||||
case 0xd604:
|
||||
case 0xd605: return "没有设置密码或已清除";
|
||||
default:
|
||||
return "未知错误";
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#region 获取报文
|
||||
internal static OperResult<byte[]> GetReadCommand(SiemensAddress[] siemensAddress)
|
||||
{
|
||||
if (siemensAddress == null)
|
||||
{
|
||||
return new OperResult<byte[]>("地址为null");
|
||||
}
|
||||
int len = siemensAddress.Length <= 19 ? siemensAddress.Length : throw new Exception("读取数量大于19!");
|
||||
int telegramLen = len * 12 + 19;
|
||||
int parameterLen = len * 12 + 2;
|
||||
|
||||
byte[] numArray = new byte[telegramLen];
|
||||
|
||||
Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length);
|
||||
numArray[2] = (byte)(telegramLen / 256);
|
||||
numArray[3] = (byte)(telegramLen % 256);
|
||||
numArray[13] = (byte)(parameterLen / 256);
|
||||
numArray[14] = (byte)(parameterLen % 256);
|
||||
numArray[18] = (byte)len;
|
||||
|
||||
for (int index = 0; index < len; index++)
|
||||
{
|
||||
Array.Copy(S7_MULRD_ITEM, 0, numArray, 19 + (index * 12), S7_MULRD_ITEM.Length);
|
||||
if (siemensAddress[index].DataCode == (byte)S7WordLength.Counter || siemensAddress[index].DataCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
numArray[22 + (index * 12)] = siemensAddress[index].DataCode;
|
||||
numArray[23 + (index * 12)] = (byte)(siemensAddress[index].Length / 256);
|
||||
numArray[24 + (index * 12)] = (byte)(siemensAddress[index].Length % 256);
|
||||
}
|
||||
else
|
||||
{
|
||||
numArray[22 + (index * 12)] = (byte)S7WordLength.Byte;
|
||||
numArray[23 + (index * 12)] = (byte)(siemensAddress[index].Length / 256);
|
||||
numArray[24 + (index * 12)] = (byte)(siemensAddress[index].Length % 256);
|
||||
}
|
||||
numArray[25 + (index * 12)] = (byte)(siemensAddress[index].DbBlock / 256U);
|
||||
numArray[26 + (index * 12)] = (byte)(siemensAddress[index].DbBlock % 256U);
|
||||
numArray[27 + (index * 12)] = siemensAddress[index].DataCode;
|
||||
numArray[28 + (index * 12)] = (byte)(siemensAddress[index].AddressStart / 256 / 256 % 256);
|
||||
numArray[29 + (index * 12)] = (byte)(siemensAddress[index].AddressStart / 256 % 256);
|
||||
numArray[30 + (index * 12)] = (byte)(siemensAddress[index].AddressStart % 256);
|
||||
}
|
||||
return OperResult.CreateSuccessResult(numArray);
|
||||
}
|
||||
|
||||
internal static OperResult<byte[]> GetWriteBitCommand(SiemensAddress address, bool data)
|
||||
{
|
||||
int len = 1;
|
||||
int telegramLen = 16 + 19 + len;//最后的1是写入值的byte数量
|
||||
int parameterLen = 12 + 2;
|
||||
|
||||
byte[] numArray = new byte[telegramLen];
|
||||
|
||||
Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length);
|
||||
numArray[2] = (byte)(telegramLen / 256);
|
||||
numArray[3] = (byte)(telegramLen % 256);
|
||||
numArray[13] = (byte)(parameterLen / 256);
|
||||
numArray[14] = (byte)(parameterLen % 256);
|
||||
numArray[15] = (byte)((4 + len) / 256);
|
||||
numArray[16] = (byte)((4 + len) % 256);
|
||||
numArray[17] = 5;
|
||||
numArray[18] = (byte)1;
|
||||
//写入Item与读取大致相同
|
||||
numArray[19] = (byte)18;
|
||||
numArray[20] = (byte)10;
|
||||
numArray[21] = (byte)16;
|
||||
numArray[22] = (byte)S7WordLength.Bit;
|
||||
numArray[23] = (byte)(len / 256);
|
||||
numArray[24] = (byte)(len % 256);
|
||||
numArray[25] = (byte)(address.DbBlock / 256U);
|
||||
numArray[26] = (byte)(address.DbBlock % 256U);
|
||||
numArray[27] = (byte)address.DataCode;
|
||||
numArray[28] = (byte)(address.AddressStart / 256 / 256);
|
||||
numArray[29] = (byte)(address.AddressStart / 256);
|
||||
numArray[30] = (byte)(address.AddressStart % 256);
|
||||
//后面跟的是写入的数据信息
|
||||
numArray[31] = 0;
|
||||
numArray[32] = 3;//Bit:3;Byte:4;Counter或者Timer:9
|
||||
numArray[33] = (byte)(len / 256);
|
||||
numArray[34] = (byte)(len % 256);
|
||||
numArray[35] = (byte)(data ? 1 : 0);
|
||||
|
||||
return OperResult.CreateSuccessResult(numArray);
|
||||
|
||||
}
|
||||
|
||||
internal static OperResult<byte[]> GetWriteByteCommand(SiemensAddress address, byte[] data)
|
||||
{
|
||||
int len = data.Length;
|
||||
int telegramLen = 16 + 19 + len;//最后的1是写入值的byte数量
|
||||
int parameterLen = 12 + 2;
|
||||
|
||||
byte[] numArray = new byte[telegramLen];
|
||||
|
||||
Array.Copy(S7_MULRW_HEADER, 0, numArray, 0, S7_MULRW_HEADER.Length);
|
||||
numArray[2] = (byte)(telegramLen / 256);
|
||||
numArray[3] = (byte)(telegramLen % 256);
|
||||
numArray[13] = (byte)(parameterLen / 256);
|
||||
numArray[14] = (byte)(parameterLen % 256);
|
||||
numArray[15] = (byte)((4 + len) / 256);
|
||||
numArray[16] = (byte)((4 + len) % 256);
|
||||
numArray[17] = 5;
|
||||
numArray[18] = (byte)1;
|
||||
//写入Item与读取大致相同
|
||||
numArray[19] = (byte)18;
|
||||
numArray[20] = (byte)10;
|
||||
numArray[21] = (byte)16;
|
||||
numArray[22] = (byte)S7WordLength.Byte;
|
||||
numArray[23] = (byte)(len / 256);
|
||||
numArray[24] = (byte)(len % 256);
|
||||
|
||||
numArray[25] = (byte)(address.DbBlock / 256U);
|
||||
numArray[26] = (byte)(address.DbBlock % 256U);
|
||||
numArray[27] = (byte)address.DataCode;
|
||||
numArray[28] = (byte)(address.AddressStart / 256 / 256);
|
||||
numArray[29] = (byte)(address.AddressStart / 256);
|
||||
numArray[30] = (byte)(address.AddressStart % 256);
|
||||
//后面跟的是写入的数据信息
|
||||
numArray[31] = 0;
|
||||
numArray[32] = 4;//Bit:3;Byte:4;Counter或者Timer:9
|
||||
numArray[33] = (byte)(len * 8 / 256);
|
||||
numArray[34] = (byte)(len * 8 % 256);
|
||||
data.CopyTo(numArray, 35);
|
||||
|
||||
return OperResult.CreateSuccessResult(numArray);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串
|
||||
|
||||
internal static async Task<OperResult<string>> ReadString(
|
||||
SiemensS7PLC plc, string address, Encoding encoding)
|
||||
{
|
||||
//先读取一次获取长度,再读取实际值
|
||||
if (plc.CurrentPlc != SiemensEnum.S200Smart)
|
||||
{
|
||||
var result1 = await plc.ReadAsync(address, 2);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
return result1.Copy<string>();
|
||||
}
|
||||
if (result1.Content[0] == (byte)0 || result1.Content[0] == byte.MaxValue)
|
||||
{
|
||||
return new OperResult<string>("在PLC中不是字符串类型");
|
||||
}
|
||||
var result2 = await plc.ReadAsync(address, 2 + result1.Content[1]);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
return result2.Copy<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult(encoding.GetString(result2.Content, 2, result2.Content.Length - 2));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result1 = await plc.ReadAsync(address, 1);
|
||||
if (!result1.IsSuccess)
|
||||
return result1.Copy<string>();
|
||||
var result2 = await plc.ReadAsync(address, 1 + result1.Content[0]);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
return result2.Copy<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateSuccessResult(encoding.GetString(result2.Content, 1, result2.Content.Length - 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static async Task<OperResult> Write(SiemensS7PLC plc, string address, string value, Encoding encoding)
|
||||
{
|
||||
if (value == null)
|
||||
value = string.Empty;
|
||||
byte[] inBytes = encoding.GetBytes(value);
|
||||
//if (encoding == Encoding.Unicode)
|
||||
// inBytes = inBytes.BytesReverseByWord();
|
||||
if (plc.CurrentPlc != SiemensEnum.S200Smart)
|
||||
{
|
||||
OperResult<byte[]> result = await plc.ReadAsync(address, 2);
|
||||
if (!result.IsSuccess) return result;
|
||||
if (result.Content[0] == byte.MaxValue) return new OperResult<string>("在PLC中不是字符串类型");
|
||||
if (result.Content[0] == 0) result.Content[0] = 254;
|
||||
if (value.Length > result.Content[0]) return new OperResult<string>("写入值长度超限");
|
||||
return await plc.WriteAsync(
|
||||
address,
|
||||
DataHelper.SpliceArray(new byte[2] { result.Content[0], (byte)value.Length },
|
||||
inBytes
|
||||
));
|
||||
}
|
||||
return await plc.WriteAsync(address, DataHelper.SpliceArray<byte>(new byte[1]
|
||||
{
|
||||
(byte) value.Length
|
||||
}, inBytes));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public enum S7WordLength : byte
|
||||
{
|
||||
Bit = 0x01,
|
||||
Byte = 0x02,
|
||||
Char = 0x03,
|
||||
Word = 0x04,
|
||||
Int = 0x05,
|
||||
DWord = 0x06,
|
||||
DInt = 0x07,
|
||||
Real = 0x08,
|
||||
Counter = 0x1C,
|
||||
Timer = 0x1D,
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
internal partial class SiemensHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// S7连读写请求头(包含ISO头和COTP头)
|
||||
/// </summary>
|
||||
static byte[] S7_MULRW_HEADER = {
|
||||
0x03,0x00,
|
||||
0x00,0x1f, // 报文长度(item.len*12+19,注意:根据传入读取item数量更改)
|
||||
0x02,0xf0, 0x80, //COTP信息
|
||||
0x32, // S7协议ID
|
||||
0x01, // 类型,请求命令
|
||||
0x00,0x00, // 冗余识别
|
||||
0x00,0x01, // 序列号
|
||||
0x00,0x0e, // parameter长度(item.len*12+2,注意:根据传入读取item数量更改)
|
||||
0x00,0x00, // Data Length+4 ,写入时填写,读取时为0
|
||||
0x04, // 4 Read Var, 5 Write Var ,注意更改
|
||||
0x01, // Item数量(item.len,注意:根据传入读取item数量更改)
|
||||
};
|
||||
|
||||
// S7变量多读Item
|
||||
static byte[] S7_MULRD_ITEM = {
|
||||
0x12, // Var 规范.
|
||||
0x0a, // 剩余的字节长度
|
||||
0x10, // Syntax ID
|
||||
(byte)S7WordLength.Byte, // 相关的数据长度代码(注意:根据传入的变量更改)
|
||||
0x00,0x01, // 数据长度 (注意:根据传入的变量更改)
|
||||
0x00,0x00, // DB编号 (注意:根据传入的变量更改)
|
||||
0x84, // 数据块类型 (注意:根据传入的变量更改)
|
||||
0x00,0x00,0x00 // 数据块偏移量 (注意:根据传入的变量更改)
|
||||
};
|
||||
|
||||
|
||||
// ISO连接请求报文(也包含ISO头和COTP头)
|
||||
internal static byte[] ISO_CR = {
|
||||
// TPKT (RFC1006 Header)
|
||||
0x03, // RFC 1006 ID (3)
|
||||
0x00, // 保留 0
|
||||
0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
// COTP (ISO 8073 Header)
|
||||
0x11, // PDU Size Length
|
||||
0xE0, // CR -连接请求ID
|
||||
0x00, 0x00, // Dst Reference
|
||||
0x00, 0x01, // Src Reference
|
||||
0x00, // Class + Options Flags
|
||||
//对于S7200/Smart ,下面参数也需要重写
|
||||
0xC0, // PDU Max Length ID
|
||||
0x01, 0x0A, // PDU Max Length
|
||||
0xC1, // Src TSAP Identifier
|
||||
0x02, // Src TSAP Length (2 bytes)
|
||||
0x01, 0x00, // Src TSAP (需重写)
|
||||
0xC2, // Dst TSAP Identifier
|
||||
0x02, // Dst TSAP Length (2 bytes)
|
||||
0x01, 0x02 // Dst TSAP (需重写)
|
||||
};
|
||||
// ISO连接请求报文(也包含ISO头和COTP头)
|
||||
internal static byte[] ISO_CR200 = {
|
||||
// TPKT (RFC1006 Header)
|
||||
0x03, // RFC 1006 ID (3)
|
||||
0x00, // 保留 0
|
||||
0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
// COTP (ISO 8073 Header)
|
||||
0x11, // PDU Size Length
|
||||
0xE0, // CR -连接请求ID
|
||||
0x00, 0x00, // Dst Reference
|
||||
0x00, 0x01, // Src Reference
|
||||
0x00, // Class + Options Flags
|
||||
|
||||
//对于S7200/Smart
|
||||
0xC1,0x02,
|
||||
0x4D,0x57, //LOCALTASP
|
||||
0xC2,
|
||||
0x02,
|
||||
0x4D,0x57, //DESTTASP
|
||||
0xC0,
|
||||
0x01,0x09
|
||||
|
||||
};
|
||||
// ISO连接请求报文(也包含ISO头和COTP头)
|
||||
internal static byte[] ISO_CR200SMART = {
|
||||
// TPKT (RFC1006 Header)
|
||||
0x03, // RFC 1006 ID (3)
|
||||
0x00, // 保留 0
|
||||
0x00, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
0x16, // 数据包长度 (整个框架、有效载荷和TPDU包括在内)
|
||||
// COTP (ISO 8073 Header)
|
||||
0x11, // PDU Size Length
|
||||
0xE0, // CR -连接请求ID
|
||||
0x00, 0x00, // Dst Reference
|
||||
0x00, 0x01, // Src Reference
|
||||
0x00, // Class + Options Flags
|
||||
|
||||
//对于S7200/Smart
|
||||
0xC1,0x02,
|
||||
0x10,0x00,//LOCALTASP
|
||||
0xC2,
|
||||
0x02,
|
||||
0x03,0x00,//DESTTASP
|
||||
0xC0,
|
||||
0x01,0x0A
|
||||
};
|
||||
// PDU获取报文(也包含ISO头和COTP头)
|
||||
internal static byte[] S7_PN = {
|
||||
0x03, 0x00, 0x00, 0x19,
|
||||
0x02, 0xf0, 0x80, // TPKT + COTP
|
||||
0x32, 0x01, 0x00, 0x00,
|
||||
|
||||
//这里对于S7200/Smart需要重写
|
||||
0x04, 0x00, 0x00, 0x08,
|
||||
|
||||
0x00, 0x00, 0xf0, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01,
|
||||
0x01,0xE0 // PDU Length Requested 这里默认480字节,对于S7200/Smart 960字节
|
||||
};
|
||||
// PDU获取报文(也包含ISO头和COTP头)
|
||||
internal static byte[] S7200_PN = {
|
||||
0x03, 0x00, 0x00, 0x19,
|
||||
0x02, 0xf0, 0x80, // TPKT + COTP
|
||||
0x32, 0x01, 0x00, 0x00,
|
||||
|
||||
0x00,0x00,0x00,0x08,
|
||||
|
||||
0x00, 0x00, 0xf0, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01,
|
||||
0x01,0xE0 // PDU Length Requested 这里默认960字节
|
||||
};
|
||||
internal static byte[] S7200SMART_PN = {
|
||||
0x03, 0x00, 0x00, 0x19,
|
||||
0x02, 0xf0, 0x80, // TPKT + COTP
|
||||
0x32, 0x01, 0x00, 0x00,
|
||||
|
||||
0xCC,0xC1,0x00,0x08,
|
||||
|
||||
0x00, 0x00, 0xf0, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01,
|
||||
0x01,0xE0 // PDU Length Requested 这里默认960字节
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
public class SiemensMessage : MessageBase, IMessage
|
||||
{
|
||||
public override int HeadBytesLength => 4;
|
||||
|
||||
public override bool CheckHeadBytes(byte[] token)
|
||||
{
|
||||
HeadBytes = token;
|
||||
byte[] headBytes = HeadBytes;
|
||||
if (headBytes == null || headBytes.Length < 4)
|
||||
BodyLength = 0;
|
||||
int length = (HeadBytes[2] * 256) + HeadBytes[3] - 4;
|
||||
if (length < 0)
|
||||
length = 0;
|
||||
BodyLength = length;
|
||||
return HeadBytes != null && HeadBytes[0] == 3 && HeadBytes[1] == 0;
|
||||
}
|
||||
|
||||
protected override void SendBytesThen()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
public enum SiemensEnum
|
||||
{
|
||||
S200,
|
||||
S200Smart,
|
||||
|
||||
S300,
|
||||
S400,
|
||||
|
||||
S1200,
|
||||
S1500,
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,352 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
|
||||
using TouchSocket.Core;
|
||||
using TouchSocket.Resources;
|
||||
using TouchSocket.Sockets;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
/// <summary>
|
||||
/// 相关命令含义源自网络资料/Shrap7/s7netplus
|
||||
/// </summary>
|
||||
public partial class SiemensS7PLC : ReadWriteDevicesTcpClientBase
|
||||
{
|
||||
|
||||
public SiemensS7PLCDataHandleAdapter DataHandleAdapter = new();
|
||||
private SiemensEnum _currentPlc = SiemensEnum.S1200;
|
||||
private int pdu_length = 0;
|
||||
private byte plc_rack = 0;
|
||||
private byte plc_slot = 0;
|
||||
private byte[] ISO_CR;
|
||||
private byte[] S7_PN;
|
||||
public SiemensEnum CurrentPlc => _currentPlc;
|
||||
/// <summary>
|
||||
/// 传入PLC类型,程序内会改变相应PLC类型的S7协议LocalTSAP, RemoteTSAP等
|
||||
/// </summary>
|
||||
/// <param name="tcpClient"></param>
|
||||
/// <param name="siemensPLCEnum"></param>
|
||||
public SiemensS7PLC(TcpClient tcpClient, SiemensEnum siemensPLCEnum) : base(tcpClient)
|
||||
{
|
||||
_currentPlc = siemensPLCEnum;
|
||||
RegisterByteLength = 1;
|
||||
ThingsGatewayBitConverter = new ThingsGatewayBitConverter(EndianType.Big);
|
||||
ISO_CR = new byte[22];
|
||||
S7_PN = new byte[25];
|
||||
Array.Copy(SiemensHelper.ISO_CR, ISO_CR, ISO_CR.Length);
|
||||
Array.Copy(SiemensHelper.S7_PN, S7_PN, S7_PN.Length);
|
||||
switch (siemensPLCEnum)
|
||||
{
|
||||
case SiemensEnum.S1200:
|
||||
ISO_CR[21] = 0x00;
|
||||
break;
|
||||
case SiemensEnum.S300:
|
||||
ISO_CR[21] = 0x02;
|
||||
break;
|
||||
case SiemensEnum.S400:
|
||||
ISO_CR[21] = 0x03;
|
||||
ISO_CR[17] = 0x00;
|
||||
break;
|
||||
case SiemensEnum.S1500:
|
||||
ISO_CR[21] = 0x00;
|
||||
break;
|
||||
case SiemensEnum.S200Smart:
|
||||
ISO_CR = SiemensHelper.ISO_CR200SMART;
|
||||
S7_PN = SiemensHelper.S7200SMART_PN;
|
||||
break;
|
||||
case SiemensEnum.S200:
|
||||
ISO_CR = SiemensHelper.ISO_CR200;
|
||||
S7_PN = SiemensHelper.S7200_PN;
|
||||
break;
|
||||
|
||||
}
|
||||
tcpClient.Connected += Connected;
|
||||
}
|
||||
|
||||
|
||||
#region 设置
|
||||
/// <summary>
|
||||
/// 获取或设置DstTSAP,在非200系列时有效
|
||||
/// </summary>
|
||||
public byte ConnectionType
|
||||
{
|
||||
get => ISO_CR[20];
|
||||
set
|
||||
{
|
||||
if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ISO_CR[20] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远程TSAP,需重新连接
|
||||
/// </summary>
|
||||
public int DestTSAP
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart ?
|
||||
(ISO_CR[17] * 256) + ISO_CR[18] :
|
||||
(ISO_CR[20] * 256) + ISO_CR[21];
|
||||
}
|
||||
set
|
||||
{
|
||||
//TODO:这里优化为ThingsGatewayBitConverter
|
||||
if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart)
|
||||
{
|
||||
ISO_CR[17] = BitConverter.GetBytes(value)[1];
|
||||
ISO_CR[18] = BitConverter.GetBytes(value)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
ISO_CR[20] = BitConverter.GetBytes(value)[1];
|
||||
ISO_CR[21] = BitConverter.GetBytes(value)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地TSAP,需重新连接
|
||||
/// </summary>
|
||||
public int LocalTSAP
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart ?
|
||||
(ISO_CR[13] * 256) + ISO_CR[14] :
|
||||
(ISO_CR[16] * 256) + ISO_CR[17];
|
||||
}
|
||||
set
|
||||
{
|
||||
//TODO:这里优化为ThingsGatewayBitConverter
|
||||
if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart)
|
||||
{
|
||||
ISO_CR[13] = BitConverter.GetBytes(value)[1];
|
||||
ISO_CR[14] = BitConverter.GetBytes(value)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
ISO_CR[16] = BitConverter.GetBytes(value)[1];
|
||||
ISO_CR[17] = BitConverter.GetBytes(value)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int PDULength => pdu_length;
|
||||
|
||||
/// <summary>
|
||||
/// 机架号,需重新连接
|
||||
/// </summary>
|
||||
public byte Rack
|
||||
{
|
||||
get => plc_rack;
|
||||
set
|
||||
{
|
||||
plc_rack = value;
|
||||
if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ISO_CR[21] = (byte)((plc_rack * 0x20) + plc_slot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 槽号,需重新连接
|
||||
/// </summary>
|
||||
public byte Slot
|
||||
{
|
||||
get => plc_slot;
|
||||
set
|
||||
{
|
||||
plc_slot = value;
|
||||
if (_currentPlc == SiemensEnum.S200 || _currentPlc == SiemensEnum.S200Smart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ISO_CR[21] = (byte)((plc_rack * 0x20) + plc_slot);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync();
|
||||
var commandResult = GetReadByteCommand(address, length);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
List<byte> bytes = new();
|
||||
foreach (var item in commandResult.Content)
|
||||
{
|
||||
var result = TcpClient.GetWaitingClient(new()).SendThenResponse(item, TimeOut, token);
|
||||
if (result.RequestInfo is MessageBase collectMessage)
|
||||
{
|
||||
if (collectMessage.IsSuccess)
|
||||
bytes.AddRange(collectMessage.Content);
|
||||
else
|
||||
return OperResult.CreateFailedResult<byte[]>(collectMessage);
|
||||
}
|
||||
}
|
||||
return bytes.Count > 0 ? OperResult.CreateSuccessResult(bytes.ToArray()) : new OperResult<byte[]>(TouchSocketStatus.UnknownError.GetDescription());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return OperResult.CreateFailedResult<byte[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<byte[]>(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取变长字符串
|
||||
/// </summary>
|
||||
public async Task<OperResult<string>> ReadString(string address, Encoding encoding)
|
||||
{
|
||||
return await SiemensHelper.ReadString(this, address, encoding);
|
||||
}
|
||||
|
||||
public async Task<OperResult<System.DateTime>> ReadDateTime(string address)
|
||||
{
|
||||
return ByteTransformHelper.GetResultFromBytes(await ReadAsync(address, 8), ThingsGateway.Foundation.Adapter.Siemens.DateTime.FromByteArray);
|
||||
}
|
||||
|
||||
public async Task<OperResult<System.DateTime>> ReadDate(string address)
|
||||
{
|
||||
return (await this.ReadAsync(address, 2)).
|
||||
Then(m => OperResult.CreateSuccessResult(ThingsGateway.Foundation.Adapter.Siemens.DateTime.SpecMinimumDateTime.AddDays(
|
||||
ThingsGatewayBitConverter.ToUInt16(m, 0)))
|
||||
);
|
||||
}
|
||||
|
||||
public override void SetDataAdapter()
|
||||
{
|
||||
DataHandleAdapter = new();
|
||||
TcpClient.SetDataHandlingAdapter(DataHandleAdapter);
|
||||
}
|
||||
|
||||
|
||||
public override Task<OperResult> WriteAsync(string address, string value, Encoding encoding, CancellationToken token = default)
|
||||
{
|
||||
return SiemensHelper.Write(this, address, value, encoding);
|
||||
}
|
||||
|
||||
public override async Task<OperResult> WriteAsync(string address, byte[] value, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ConnectAsync();
|
||||
var commandResult = GetWriteByteCommand(address, value);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
List<ResponsedData> bytes = new();
|
||||
foreach (var item in commandResult.Content)
|
||||
{
|
||||
ResponsedData result = TcpClient.GetWaitingClient(new()).SendThenResponse(item, TimeOut, token);
|
||||
bytes.Add(result);
|
||||
}
|
||||
return OperResult.CreateSuccessResult(bytes.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateFailedResult<bool[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<bool[]>(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<OperResult> WriteAsync(string address, bool[] value, CancellationToken token = default)
|
||||
{
|
||||
if (value.Length > 1)
|
||||
{
|
||||
return new OperResult("不支持多写");
|
||||
}
|
||||
try
|
||||
{
|
||||
await ConnectAsync();
|
||||
|
||||
var commandResult = GetWriteBitCommand(address, value[0]);
|
||||
if (commandResult.IsSuccess)
|
||||
{
|
||||
var result = TcpClient.GetWaitingClient(new()).SendThenResponse(commandResult.Content, TimeOut, token);
|
||||
return OperResult.CreateSuccessResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperResult.CreateFailedResult<bool[]>(commandResult);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new OperResult<bool[]>(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void Connected(ITcpClient client, MsgEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result1 = SendThenResponse(ISO_CR);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
Logger?.Error(client.GetIPPort() + "ISO初始化失败");
|
||||
return;
|
||||
}
|
||||
var result2 = SendThenResponse(S7_PN);
|
||||
if (!result2.IsSuccess)
|
||||
{
|
||||
Logger?.Error(client.GetIPPort() + "初始化失败");
|
||||
return;
|
||||
}
|
||||
pdu_length = ThingsGatewayBitConverter.ToUInt16(result2.Content.SelectLast(2), 0);
|
||||
pdu_length = pdu_length < 200 ? 200 : pdu_length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Exception(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<OperResult> WriteDateTime(string address, System.DateTime dateTime)
|
||||
{
|
||||
return await WriteAsync(address, ThingsGateway.Foundation.Adapter.Siemens.DateTime.ToByteArray(dateTime));
|
||||
}
|
||||
|
||||
public async Task<OperResult> WriteDate(string address, System.DateTime dateTime)
|
||||
{
|
||||
return await base.WriteAsync(address, Convert.ToUInt16((dateTime - ThingsGateway.Foundation.Adapter.Siemens.DateTime.SpecMinimumDateTime).TotalDays));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
public class SiemensS7PLCDataHandleAdapter : ReadWriteDevicesTcpDataHandleAdapter<SiemensMessage>
|
||||
{
|
||||
public override byte[] PackCommand(byte[] command)
|
||||
{
|
||||
return command;
|
||||
}
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
}
|
||||
|
||||
protected override OperResult<byte[]> UnpackResponse(
|
||||
byte[] send,
|
||||
byte[] response)
|
||||
{
|
||||
if (response[2] * 256 + response[3] == 7)
|
||||
{
|
||||
return new OperResult<byte[]>(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
//已请求方为准,分开返回类型校验
|
||||
switch (send[17])
|
||||
{
|
||||
case 0x04:
|
||||
return SiemensHelper.AnalysisReadByte(send, response);
|
||||
case 0x05:
|
||||
return SiemensHelper.AnalysisWrite(response);
|
||||
}
|
||||
return OperResult.CreateSuccessResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected override SiemensMessage GetInstance()
|
||||
{
|
||||
return new SiemensMessage();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
|
||||
namespace ThingsGateway.Foundation.Adapter.Siemens
|
||||
{
|
||||
public partial class SiemensS7PLC : ReadWriteDevicesTcpClientBase
|
||||
{
|
||||
private static OperResult<byte[]> GetWriteBitCommand(string address, bool data)
|
||||
{
|
||||
OperResult<SiemensAddress> result = SiemensAddress.ParseFrom(address);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return result.Copy<byte[]>();
|
||||
}
|
||||
return SiemensHelper.GetWriteBitCommand(result.Content, data);
|
||||
}
|
||||
private OperResult<List<byte[]>> GetReadByteCommand(string address, int length)
|
||||
{
|
||||
OperResult<SiemensAddress> from = SiemensAddress.ParseFrom(address, length);
|
||||
if (!from.IsSuccess) return from.Copy<List<byte[]>>();
|
||||
ushort num1 = 0;
|
||||
var listBytes = new List<byte[]>();
|
||||
while (num1 < length)
|
||||
{
|
||||
//pdu长度,重复生成报文,直至全部生成
|
||||
ushort num2 = (ushort)Math.Min(length - num1, pdu_length);
|
||||
from.Content.Length = num2;
|
||||
var result = GetReadByteCommand(new SiemensAddress[1] { from.Content });
|
||||
if (!result.IsSuccess) return result.Copy<List<byte[]>>();
|
||||
listBytes.AddRange(result.Content);
|
||||
num1 += num2;
|
||||
if (from.Content.DataCode == (byte)S7WordLength.Timer || from.Content.DataCode == (byte)S7WordLength.Counter)
|
||||
{
|
||||
from.Content.AddressStart += num2 / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
from.Content.AddressStart += num2 * 8;
|
||||
}
|
||||
}
|
||||
return OperResult.CreateSuccessResult(listBytes);
|
||||
}
|
||||
|
||||
private OperResult<List<byte[]>> GetReadByteCommand(SiemensAddress[] siemensAddress)
|
||||
{
|
||||
if (siemensAddress.Length <= 19)
|
||||
{
|
||||
return ByteTransformHelper.GetResultFromBytes(SiemensHelper.GetReadCommand(siemensAddress), m => new List<byte[]>() { m });
|
||||
}
|
||||
|
||||
List<byte[]> byteList = new List<byte[]>();
|
||||
List<SiemensAddress[]> s7AddressDataArrayList = siemensAddress.ArraySplitByLength(19);
|
||||
for (int index = 0; index < s7AddressDataArrayList.Count; ++index)
|
||||
{
|
||||
var result = GetReadByteCommand(s7AddressDataArrayList[index]);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
byteList.AddRange(result.Content);
|
||||
}
|
||||
return OperResult.CreateSuccessResult(byteList);
|
||||
}
|
||||
|
||||
private OperResult<List<byte[]>> GetWriteByteCommand(string address, byte[] value)
|
||||
{
|
||||
return SiemensAddress.ParseFrom(address).Then(m => GetWriteByteCommand(m, value));
|
||||
}
|
||||
|
||||
private OperResult<List<byte[]>> GetWriteByteCommand(SiemensAddress address, byte[] value)
|
||||
{
|
||||
int length1 = value.Length;
|
||||
ushort index = 0;
|
||||
List<byte[]> bytes = new List<byte[]>();
|
||||
while (index < length1)
|
||||
{
|
||||
//pdu长度,重复生成报文,直至全部生成
|
||||
ushort length2 = (ushort)Math.Min(length1 - index, pdu_length);
|
||||
byte[] data = ThingsGatewayBitConverter.ToByte(value, index, length2);
|
||||
OperResult<byte[]> result1 = SiemensHelper.GetWriteByteCommand(address, data);
|
||||
if (!result1.IsSuccess)
|
||||
{
|
||||
return result1.Copy<List<byte[]>>();
|
||||
}
|
||||
bytes.Add(result1.Content);
|
||||
index += length2;
|
||||
address.AddressStart += length2 * 8;
|
||||
}
|
||||
return OperResult.CreateSuccessResult(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latestMajor</LangVersion>
|
||||
<TargetFrameworks>net462;net6.0;net7.0</TargetFrameworks>
|
||||
<Version>1.2.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ThingsGateway.Foundation\ThingsGateway.Foundation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -10,49 +10,20 @@ namespace ThingsGateway.Foundation.Tests
|
||||
{
|
||||
public class Modbus
|
||||
{
|
||||
private ITestOutputHelper _output;
|
||||
TcpClient client;
|
||||
SerialClient serialClient;
|
||||
private TouchSocketConfig config;
|
||||
private ModbusRtuOverTcp RtuTcpClient;
|
||||
private ModbusRtu RtuClient;
|
||||
private ModbusRtuOverTcp RtuTcpClient;
|
||||
private ModbusRtuOverUdp RtuUdpClient;
|
||||
SerialClient serialClient;
|
||||
private ModbusTcp TcpClient;
|
||||
private ModbusUdp UdpClient;
|
||||
private ITestOutputHelper _output;
|
||||
public Modbus(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
[Fact]
|
||||
public async Task RtuTcpReadTest()
|
||||
{
|
||||
ModbusRtuTcpClient();
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
string address = $"4{i * 2 + 1};DATA=ABCD;";
|
||||
var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat);
|
||||
var test = await RtuTcpClient.ReadAsync(address, 1);
|
||||
var data = byteConverter.ToInt16(test.Content, 0);
|
||||
_output.WriteLine(data.ToJson());
|
||||
}
|
||||
|
||||
for (int i = 100; i < 110; i++)
|
||||
{
|
||||
string address = $"4{i * 2 + 1};TEXT=UTF8;LEN=4";
|
||||
var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat);
|
||||
var test = await RtuTcpClient.ReadAsync(address, length / RtuTcpClient.RegisterByteLength);
|
||||
var data = byteConverter.ToString(test.Content, 0, length);
|
||||
_output.WriteLine(data.ToJson());
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
_output.WriteLine("<22>ܺ<EFBFBD>ʱ<EFBFBD><CAB1>" + stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
}
|
||||
[Fact]
|
||||
public async Task RtuReadTest()
|
||||
{
|
||||
ModbusRtuClient();
|
||||
@@ -80,6 +51,36 @@ namespace ThingsGateway.Foundation.Tests
|
||||
stopwatch.Stop();
|
||||
_output.WriteLine("<22>ܺ<EFBFBD>ʱ<EFBFBD><CAB1>" + stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RtuTcpReadTest()
|
||||
{
|
||||
ModbusRtuTcpClient();
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
string address = $"4{i * 2 + 1};DATA=ABCD;";
|
||||
var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat);
|
||||
var test = await RtuTcpClient.ReadAsync(address, 1);
|
||||
var data = byteConverter.ToInt16(test.Content, 0);
|
||||
_output.WriteLine(data.ToJson());
|
||||
}
|
||||
|
||||
for (int i = 100; i < 110; i++)
|
||||
{
|
||||
string address = $"4{i * 2 + 1};TEXT=UTF8;LEN=4";
|
||||
var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, RtuTcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat);
|
||||
var test = await RtuTcpClient.ReadAsync(address, length / RtuTcpClient.RegisterByteLength);
|
||||
var data = byteConverter.ToString(test.Content, 0, length);
|
||||
_output.WriteLine(data.ToJson());
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
_output.WriteLine("<22>ܺ<EFBFBD>ʱ<EFBFBD><CAB1>" + stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
}
|
||||
[Fact]
|
||||
public async Task TcpReadTest()
|
||||
@@ -102,6 +103,19 @@ namespace ThingsGateway.Foundation.Tests
|
||||
|
||||
}
|
||||
|
||||
private void ModbusRtuClient()
|
||||
{
|
||||
config = new TouchSocketConfig();
|
||||
config.SetSerialProperty(new() { PortName = "COM6" })
|
||||
.SetBufferLength(300);
|
||||
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
serialClient = config.Container.Resolve<SerialClient>();
|
||||
serialClient.Setup(config);
|
||||
RtuClient = new(serialClient);
|
||||
RtuClient.Station = 1;
|
||||
RtuClient.TimeOut = 5000;
|
||||
}
|
||||
|
||||
private void ModbusRtuTcpClient()
|
||||
{
|
||||
config = new TouchSocketConfig();
|
||||
@@ -154,19 +168,5 @@ namespace ThingsGateway.Foundation.Tests
|
||||
UdpClient.Station = 1;
|
||||
UdpClient.TimeOut = 5000;
|
||||
}
|
||||
|
||||
private void ModbusRtuClient()
|
||||
{
|
||||
config = new TouchSocketConfig();
|
||||
config.SetSerialProperty(new() { PortName = "COM6" })
|
||||
.SetBufferLength(300);
|
||||
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
serialClient = config.Container.Resolve<SerialClient>();
|
||||
serialClient.Setup(config);
|
||||
RtuClient = new(serialClient);
|
||||
RtuClient.Station = 1;
|
||||
RtuClient.TimeOut = 5000;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
67
src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs
Normal file
67
src/Plugins/ThingsGateway.Foundation.Tests/Siemens.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using ThingsGateway.Foundation.Adapter.Siemens;
|
||||
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ThingsGateway.Foundation.Tests
|
||||
{
|
||||
public class Siemens
|
||||
{
|
||||
private ITestOutputHelper _output;
|
||||
TcpClient client;
|
||||
private TouchSocketConfig config;
|
||||
private SiemensS7PLC TcpClient;
|
||||
public Siemens(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task TcpReadTest()
|
||||
{
|
||||
S7TcpClient();
|
||||
Stopwatch stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
string address = $"M1.{i + 1};";
|
||||
var byteConverter = ByteConverterHelper.GetTransByAddress(ref address, TcpClient.ThingsGatewayBitConverter, out int length, out BcdFormat bcdFormat);
|
||||
var test = await TcpClient.ReadAsync(address, 2);
|
||||
if (test .IsSuccess)
|
||||
{
|
||||
var data = byteConverter.ToBoolean(test.Content, 0);
|
||||
_output.WriteLine(data.ToJson());
|
||||
}
|
||||
else
|
||||
{
|
||||
_output.WriteLine(test.ToJson());
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
_output.WriteLine("<22>ܺ<EFBFBD>ʱ<EFBFBD><CAB1>" + stopwatch.Elapsed.TotalSeconds);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void S7TcpClient()
|
||||
{
|
||||
config = new TouchSocketConfig();
|
||||
config.SetRemoteIPHost(new IPHost("127.0.0.1:102"))
|
||||
.SetBufferLength(300);
|
||||
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
client = config.Container.Resolve<TcpClient>();
|
||||
client.Setup(config);
|
||||
TcpClient = new(client,SiemensEnum.S1500);
|
||||
TcpClient.ConnectTimeOut = 5000;
|
||||
TcpClient.TimeOut = 5000;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -26,6 +26,7 @@
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.Modbus\ThingsGateway.Foundation.Adapter.Modbus.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.OPCDA\ThingsGateway.Foundation.Adapter.OPCDA.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.OPCUA\ThingsGateway.Foundation.Adapter.OPCUA.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
@@ -18,8 +18,11 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ThingsGateway.Web.Foundation\ThingsGateway.Web.Foundation.csproj" />
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.Modbus\ThingsGateway.Foundation.Adapter.Modbus.csproj" />
|
||||
<ProjectReference Include="..\..\ThingsGateway.Web.Foundation\ThingsGateway.Web.Foundation.csproj" >
|
||||
<IncludeAssets>Compile</IncludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.Modbus\ThingsGateway.Foundation.Adapter.Modbus.csproj" >
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
11
src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs
Normal file
11
src/Plugins/ThingsGateway.Siemens/GlobalUsings.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
global using System.Net;
|
||||
|
||||
global using ThingsGateway.Foundation.Adapter.Siemens;
|
||||
global using ThingsGateway.Web.Foundation;
|
||||
|
||||
global using TouchSocket.Core;
|
||||
global using TouchSocket.Sockets;
|
||||
|
||||
|
87
src/Plugins/ThingsGateway.Siemens/S7.cs
Normal file
87
src/Plugins/ThingsGateway.Siemens/S7.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Text;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public abstract class S7 : DriverBase
|
||||
{
|
||||
|
||||
protected SiemensS7PLC _plc;
|
||||
|
||||
protected S7(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[DeviceProperty("连接超时时间", "")] public ushort ConnectTimeOut { get; set; } = 3000;
|
||||
[DeviceProperty("默认解析顺序", "")] public DataFormat DataFormat { get; set; }
|
||||
[DeviceProperty("IP", "")] public string IP { get; set; } = "127.0.0.1";
|
||||
[DeviceProperty("端口", "")] public int Port { get; set; } = 102;
|
||||
public override IThingsGatewayBitConverter ThingsGatewayBitConverter { get => _plc?.ThingsGatewayBitConverter; }
|
||||
[DeviceProperty("读写超时时间", "")] public ushort TimeOut { get; set; } = 3000;
|
||||
|
||||
|
||||
[DeviceMethod("ReadDate", "")]
|
||||
public Task<OperResult<System.DateTime>> ReadDate(string address)
|
||||
{
|
||||
return _plc?.ReadDate(address);
|
||||
}
|
||||
|
||||
[DeviceMethod("ReadString", "")]
|
||||
public Task<OperResult<string>> ReadString(string address, Encoding encoding)
|
||||
{
|
||||
return _plc?.ReadString(address, encoding);
|
||||
}
|
||||
|
||||
|
||||
[DeviceMethod("WriteDate", "")]
|
||||
public Task<OperResult> WriteDate(string address, System.DateTime dateTime)
|
||||
{
|
||||
return _plc?.WriteDate(address, dateTime);
|
||||
}
|
||||
[DeviceMethod("WriteDateTime", "")]
|
||||
public Task<OperResult> WriteDateTime(string address, System.DateTime dateTime)
|
||||
{
|
||||
return _plc?.WriteDateTime(address, dateTime);
|
||||
}
|
||||
|
||||
public override void AfterStop()
|
||||
{
|
||||
_plc?.Disconnect();
|
||||
}
|
||||
|
||||
public override async Task BeforStart()
|
||||
{
|
||||
await _plc.ConnectAsync();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_plc.Disconnect();
|
||||
}
|
||||
|
||||
public override bool IsSupportAddressRequest()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public override OperResult<List<DeviceVariableSourceRead>> LoadSourceRead(List<CollectVariableRunTime> deviceVariables)
|
||||
{
|
||||
Init(null);
|
||||
_plc.Connect();
|
||||
var data= deviceVariables.LoadSourceRead(_logger, ThingsGatewayBitConverter, _plc);
|
||||
_plc?.Disconnect();
|
||||
return data;
|
||||
}
|
||||
|
||||
public override async Task<OperResult> WriteValueAsync(CollectVariableRunTime deviceVariable, string value)
|
||||
{
|
||||
return await _plc.WriteAsync(deviceVariable.DataType, deviceVariable.VariableAddress, value);
|
||||
}
|
||||
|
||||
protected override async Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _plc.ReadAsync(address, length);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
211
src/Plugins/ThingsGateway.Siemens/S7Helper.cs
Normal file
211
src/Plugins/ThingsGateway.Siemens/S7Helper.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
internal static class S7Helper
|
||||
{
|
||||
internal static OperResult<List<DeviceVariableSourceRead>> LoadSourceRead(this List<CollectVariableRunTime> deviceVariables, ILogger _logger, IThingsGatewayBitConverter byteConverter, SiemensS7PLC siemensS7Net)
|
||||
{
|
||||
var result = new List<DeviceVariableSourceRead>();
|
||||
try
|
||||
{
|
||||
//需要先剔除额外信息,比如dataformat等
|
||||
foreach (var item in deviceVariables)
|
||||
{
|
||||
var address = item.VariableAddress;
|
||||
|
||||
IThingsGatewayBitConverter transformParameter = ByteConverterHelper.GetTransByAddress(
|
||||
ref address, byteConverter, out int length, out BcdFormat bCDFormat);
|
||||
item.ThingsGatewayBitConverter = transformParameter;
|
||||
item.StringLength = length;
|
||||
item.StringBcdFormat = bCDFormat;
|
||||
item.VariableAddress = address;
|
||||
|
||||
int bitIndex = 0;
|
||||
string[] addressSplits = new string[] { address };
|
||||
if (address.IndexOf('.') > 0)
|
||||
{
|
||||
addressSplits = address.SplitDot();
|
||||
try
|
||||
{
|
||||
if ((addressSplits.Length == 2 && !address.ToUpper().Contains("DB")) || (addressSplits.Length >= 3 && address.ToUpper().Contains("DB")))
|
||||
bitIndex = Convert.ToInt32(addressSplits.Last());
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "自动分包方法获取Bit失败");
|
||||
}
|
||||
}
|
||||
item.Index = bitIndex;
|
||||
}
|
||||
//按读取间隔分组
|
||||
var tags = deviceVariables.GroupBy(it => it.IntervalTime);
|
||||
foreach (var item in tags)
|
||||
{
|
||||
Dictionary<SiemensAddress, CollectVariableRunTime> map = item.ToDictionary(it =>
|
||||
{
|
||||
|
||||
var lastLen = it.DataTypeEnum.GetByteLength(); ;
|
||||
if (lastLen <= 0)
|
||||
{
|
||||
if (it.DataTypeEnum.GetNetType() == typeof(bool))
|
||||
{
|
||||
lastLen = 2;
|
||||
}
|
||||
else if (it.DataTypeEnum.GetNetType() == typeof(string))
|
||||
{
|
||||
lastLen = it.StringLength;
|
||||
}
|
||||
else if (it.DataTypeEnum.GetNetType() == typeof(object))
|
||||
{
|
||||
lastLen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
var s7Address = SiemensAddress.ParseFrom(it.VariableAddress);
|
||||
if (s7Address.IsSuccess)
|
||||
{
|
||||
if ((s7Address.Content.DataCode == (byte)S7WordLength.Counter || s7Address.Content.DataCode == (byte)S7WordLength.Timer) && lastLen == 1)
|
||||
{
|
||||
lastLen = 2;
|
||||
}
|
||||
}
|
||||
//这里把每个变量的应读取长度都写入变量地址实体中
|
||||
return SiemensAddress.ParseFrom(it.VariableAddress, (ushort)lastLen).Content;
|
||||
|
||||
});
|
||||
|
||||
//获取变量的地址
|
||||
var modbusAddressList = map.Keys.ToList();
|
||||
|
||||
//获取S7数据代码
|
||||
var functionCodes = modbusAddressList.Select(t => t.DataCode).Distinct();
|
||||
foreach (var functionCode in functionCodes)
|
||||
{
|
||||
//相同数据代码的变量集合
|
||||
var modbusAddressSameFunList = modbusAddressList
|
||||
.Where(t => t.DataCode == functionCode);
|
||||
//相同数据代码的变量集合中的不同DB块
|
||||
var stationNumbers = modbusAddressSameFunList
|
||||
.Select(t => t.DbBlock).Distinct();
|
||||
foreach (var stationNumber in stationNumbers)
|
||||
{
|
||||
var addressList = modbusAddressSameFunList.Where(t => t.DbBlock == stationNumber)
|
||||
.ToDictionary(t => t, t => map[t]);
|
||||
//循环对数据代码,站号都一样的变量进行分配连读包
|
||||
var tempResult = LoadSourceRead(addressList, functionCode, item.Key, siemensS7Net);
|
||||
//添加到总连读包
|
||||
result.AddRange(tempResult.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "自动分包失败");
|
||||
}
|
||||
return OperResult.CreateSuccessResult(result);
|
||||
}
|
||||
|
||||
private static OperResult<List<DeviceVariableSourceRead>> LoadSourceRead(Dictionary<SiemensAddress, CollectVariableRunTime> addressList, int functionCode, int timeInterval, SiemensS7PLC siemensS7Net)
|
||||
{
|
||||
|
||||
List<DeviceVariableSourceRead> sourceReads = new List<DeviceVariableSourceRead>();
|
||||
//实际地址与长度排序
|
||||
var addresss = addressList.Keys.OrderBy(it =>
|
||||
{
|
||||
int address = 0;
|
||||
if (it.DataCode == (byte)S7WordLength.Counter || it.DataCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
address = it.AddressStart * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
address = it.AddressStart / 8;
|
||||
}
|
||||
return address + it.Length;
|
||||
}).ToList();
|
||||
var minAddress = addresss.First().AddressStart;
|
||||
var maxAddress = addresss.Last().AddressStart;
|
||||
while (maxAddress >= minAddress)
|
||||
{
|
||||
//这里直接避免末位变量长度超限的情况,pdu长度-8
|
||||
int readLength = siemensS7Net.PDULength == 0 ? 200 : siemensS7Net.PDULength - 8;
|
||||
List<SiemensAddress> tempAddress = new();
|
||||
if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
tempAddress = addresss.Where(t => t.AddressStart >= minAddress && ((t.AddressStart) + t.Length) <= ((minAddress) + readLength)).ToList();
|
||||
while ((tempAddress.Last().AddressStart * 2) + tempAddress.Last().Length - (tempAddress.First().AddressStart * 2) > readLength)
|
||||
{
|
||||
tempAddress.Remove(tempAddress.Last());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempAddress = addresss.Where(t => t.AddressStart >= minAddress && ((t.AddressStart) + t.Length) <= ((minAddress) + readLength)).ToList();
|
||||
while ((tempAddress.Last().AddressStart / 8) + tempAddress.Last().Length - (tempAddress.First().AddressStart / 8) > readLength)
|
||||
{
|
||||
tempAddress.Remove(tempAddress.Last());
|
||||
}
|
||||
}
|
||||
|
||||
//读取寄存器长度
|
||||
int lastAddress = 0;
|
||||
int firstAddress = 0;
|
||||
if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
lastAddress = tempAddress.Last().AddressStart * 2;
|
||||
firstAddress = tempAddress.First().AddressStart * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastAddress = tempAddress.Last().AddressStart / 8;
|
||||
firstAddress = tempAddress.First().AddressStart / 8;
|
||||
}
|
||||
var sourceLen = lastAddress + tempAddress.Last().Length - firstAddress;
|
||||
DeviceVariableSourceRead sourceRead = new DeviceVariableSourceRead(timeInterval);
|
||||
sourceRead.Address = tempAddress.OrderBy(it => it.AddressStart).First().ToString();
|
||||
sourceRead.Length = sourceLen.ToString();
|
||||
foreach (var item in tempAddress)
|
||||
{
|
||||
var readNode = addressList[item];
|
||||
if (functionCode == (byte)S7WordLength.Counter || functionCode == (byte)S7WordLength.Timer)
|
||||
{
|
||||
if (readNode.DataTypeEnum == DataTypeEnum.Bool)
|
||||
{
|
||||
readNode.Index = (((item.AddressStart * 2) - (tempAddress.First().AddressStart * 2)) * 8) + readNode.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
readNode.Index = (item.AddressStart * 2) - (tempAddress.First().AddressStart * 2) + readNode.Index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (readNode.DataTypeEnum == DataTypeEnum.Bool)
|
||||
{
|
||||
readNode.Index = (((item.AddressStart / 8) - (tempAddress.First().AddressStart / 8)) * 8) + readNode.Index;
|
||||
}
|
||||
else
|
||||
{
|
||||
readNode.Index = (item.AddressStart / 8) - (tempAddress.First().AddressStart / 8) + readNode.Index;
|
||||
}
|
||||
}
|
||||
sourceRead.DeviceVariables.Add(readNode);
|
||||
addresss.Remove(item);
|
||||
}
|
||||
sourceReads.Add(sourceRead);
|
||||
if (addresss.Count > 0)
|
||||
minAddress = addresss.First().AddressStart;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return OperResult.CreateSuccessResult(sourceReads);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
28
src/Plugins/ThingsGateway.Siemens/S7_1200.cs
Normal file
28
src/Plugins/ThingsGateway.Siemens/S7_1200.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_1200 : S7
|
||||
{
|
||||
public S7_1200(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S1200);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
28
src/Plugins/ThingsGateway.Siemens/S7_1500.cs
Normal file
28
src/Plugins/ThingsGateway.Siemens/S7_1500.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_1500 : S7
|
||||
{
|
||||
public S7_1500(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S1500);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
28
src/Plugins/ThingsGateway.Siemens/S7_200.cs
Normal file
28
src/Plugins/ThingsGateway.Siemens/S7_200.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_200 : S7
|
||||
{
|
||||
public S7_200(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S200);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
42
src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs
Normal file
42
src/Plugins/ThingsGateway.Siemens/S7_200SMART.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_200SMART : S7
|
||||
{
|
||||
public S7_200SMART(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S200Smart);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
//[Method("HotStart", "热启动")]
|
||||
//public OperResult HotStart()
|
||||
//{
|
||||
// return _plc?.HotStart();
|
||||
//}
|
||||
//[Method("ColdStart", "冷启动")]
|
||||
//public OperResult ColdStart()
|
||||
//{
|
||||
// return _plc?.ColdStart();
|
||||
//}
|
||||
//[Method("Stop", "停止")]
|
||||
//public OperResult Stop()
|
||||
//{
|
||||
// return _plc?.Stop();
|
||||
//}
|
||||
}
|
||||
}
|
28
src/Plugins/ThingsGateway.Siemens/S7_300.cs
Normal file
28
src/Plugins/ThingsGateway.Siemens/S7_300.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_300 : S7
|
||||
{
|
||||
public S7_300(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S300);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
28
src/Plugins/ThingsGateway.Siemens/S7_400.cs
Normal file
28
src/Plugins/ThingsGateway.Siemens/S7_400.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ThingsGateway.Siemens
|
||||
{
|
||||
public class S7_400 : S7
|
||||
{
|
||||
public S7_400(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
TouchSocketConfig.SetRemoteIPHost(new IPHost(IPAddress.Parse(IP), Port))
|
||||
.SetBufferLength(1024);
|
||||
client = TouchSocketConfig.Container.Resolve<TcpClient>();
|
||||
((TcpClient)client).Setup(TouchSocketConfig);
|
||||
}
|
||||
//载入配置
|
||||
_plc = new((TcpClient)client, SiemensEnum.S400);
|
||||
_plc.DataFormat = DataFormat;
|
||||
_plc.ConnectTimeOut = ConnectTimeOut;
|
||||
_plc.TimeOut = TimeOut;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latestMajor</LangVersion>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>1.2.0</Version>
|
||||
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
|
||||
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command=" set dir="$(SolutionDir)ThingsGateway.Web.Server\bin\$(Configuration)\$(TargetFramework)\Plugins\$(AssemblyName)"
 if not exist %25dir%25 md %25dir%25 
copy "$(TargetDir)*Siemens*.dll" %25dir%25


" />
|
||||
</Target>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ThingsGateway.Web.Foundation\ThingsGateway.Web.Foundation.csproj" >
|
||||
<IncludeAssets>Compile</IncludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -1,5 +1,71 @@
|
||||
{
|
||||
"RECORDS": [
|
||||
{
|
||||
"Id": "395093826302213",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_1200",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.726",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "395093826302214",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_1500",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.728",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "395093826302215",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_200",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.728",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "395093826302216",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_200SMART",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.728",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "395093826302217",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_300",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.728",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "395093826302218",
|
||||
"AssembleName": "ThingsGateway.Siemens.S7_400",
|
||||
"FileName": "ThingsGateway.Siemens",
|
||||
"DriverTypeEnum": "Collect",
|
||||
"FilePath": "Plugins\\ThingsGateway.Siemens\\ThingsGateway.Siemens.dll",
|
||||
"CreateTime": "2023-03-12 20:20:55.728",
|
||||
"CreateUser": "superAdmin",
|
||||
"CreateUserId": "212725263002001",
|
||||
"IsDelete": "0"
|
||||
},
|
||||
{
|
||||
"Id": "389003388313861",
|
||||
"AssembleName": "ThingsGateway.Modbus.ModbusRtuOverTcp",
|
||||
|
@@ -148,17 +148,25 @@
|
||||
StringNumber tab;
|
||||
List<CollectDevice> CollectDevices = new();
|
||||
List<UploadDevice> UploadDevices = new();
|
||||
List<string> OtherMethods = new();
|
||||
Dictionary<long, List<string>> OtherMethods = new();
|
||||
void DeviceChanged(long devId)
|
||||
{
|
||||
if (devId > 0)
|
||||
OtherMethods = this.GetBackgroundService<CollectDeviceHostService>().GetDeviceMethods(devId);
|
||||
{
|
||||
var data = this.GetBackgroundService<CollectDeviceHostService>().GetDeviceMethods(devId);
|
||||
OtherMethods.AddOrUpdate(devId,data);
|
||||
}
|
||||
else
|
||||
OtherMethods = new();
|
||||
}
|
||||
|
||||
RenderFragment GetRenderFragment(VariableEditInput context)
|
||||
{
|
||||
if(!OtherMethods.ContainsKey(context.DeviceId))
|
||||
{
|
||||
DeviceChanged(context.DeviceId);
|
||||
}
|
||||
|
||||
RenderFragment renderFragment = null;
|
||||
renderFragment +=
|
||||
@<div>
|
||||
@@ -247,17 +255,24 @@
|
||||
<MSubheader Class="font-weight-black"> @(context.Description(x => x.WriteExpressions)) </MSubheader>
|
||||
<MTextField Dense Outlined HideDetails="@("auto")" @bind-Value=@context.WriteExpressions />
|
||||
</MCol>
|
||||
|
||||
@if(OtherMethods.ContainsKey(context.DeviceId))
|
||||
{
|
||||
|
||||
<MCol Md=6 Cols=12 class="px-1">
|
||||
<MSubheader Class="font-weight-black"> @(context.Description(x => x.OtherMethod)) </MSubheader>
|
||||
<MSelect Class="mr-3" @bind-Value="context.OtherMethod" Outlined
|
||||
Items=@(OtherMethods) Clearable
|
||||
<MSubheader Class="font-weight-black"> @(context.Description(x => x.OtherMethod)) </MSubheader>
|
||||
<MSelect Class="mr-3" @bind-Value="context.OtherMethod" Outlined
|
||||
Items=@(OtherMethods[context.DeviceId]) Clearable
|
||||
MenuProps="@(props => { props.Auto = true; props.OffsetY = true; })"
|
||||
ItemText=@((u) =>u)
|
||||
ItemValue=@(u =>u)
|
||||
HideDetails=@("auto") Height="30"
|
||||
Dense>
|
||||
</MSelect>
|
||||
</MCol>
|
||||
Dense>
|
||||
</MSelect>
|
||||
</MCol>
|
||||
|
||||
}
|
||||
|
||||
</MRow>
|
||||
|
||||
</MCard>
|
||||
|
@@ -357,7 +357,6 @@
|
||||
</PModal>
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
private string _collectDeviceGroup;
|
||||
private string _uploadDeviceGroup;
|
||||
@@ -369,19 +368,21 @@
|
||||
List<UploadDeviceCore> _uploadDeviceCores = new();
|
||||
void collectDeviceQuery()
|
||||
{
|
||||
_collectDeviceCores = CollectDeviceHostService.CollectDeviceCores.WhereIf(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _collectDeviceGroup).ToList();
|
||||
_collectDeviceCores = CollectDeviceHostService?.CollectDeviceCores?.WhereIf(!_collectDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _collectDeviceGroup).ToList();
|
||||
}
|
||||
void uploadDeviceQuery()
|
||||
{
|
||||
_uploadDeviceCores = UploadDeviceHostService.UploadDeviceCores.WhereIf(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _uploadDeviceGroup).ToList();
|
||||
_uploadDeviceCores = UploadDeviceHostService?.UploadDeviceCores?.WhereIf(!_uploadDeviceGroup.IsNullOrEmpty(), a => a.Device.DeviceGroup == _uploadDeviceGroup).ToList();
|
||||
}
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_collectDeviceGroups = CollectDeviceService.GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
|
||||
_uploadDeviceGroups = UploadDeviceService.GetCacheList()?.Select(a => a.DeviceGroup)?.Where(a => a != null).Distinct()?.ToList();
|
||||
collectDeviceQuery();
|
||||
uploadDeviceQuery();
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
|
||||
private bool isDownExport;
|
||||
[Inject]
|
||||
IJSRuntime JS { get; set; }
|
||||
@@ -424,8 +425,7 @@
|
||||
DelayTimer.Elapsed += timer_Elapsed;
|
||||
DelayTimer.AutoReset = true;
|
||||
DelayTimer.Start();
|
||||
collectDeviceQuery();
|
||||
uploadDeviceQuery();
|
||||
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
private BootstrapDynamicComponent _importComponent;
|
||||
@@ -459,6 +459,7 @@
|
||||
isRestart = true;
|
||||
StateHasChanged();
|
||||
await Task.Run(async () => await CollectDeviceHostService.UpDeviceThread(devId));
|
||||
collectDeviceQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -480,6 +481,7 @@
|
||||
isRestart = true;
|
||||
StateHasChanged();
|
||||
await Task.Run(() => UploadDeviceHostService.UpDeviceThread(devId));
|
||||
uploadDeviceQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -512,6 +514,8 @@
|
||||
|
||||
StateHasChanged();
|
||||
await Task.Run(async () => await CollectDeviceHostService.RestartDeviceThread());
|
||||
collectDeviceQuery();
|
||||
uploadDeviceQuery();
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@@ -66,6 +66,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventSource", "EventSource"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.RabbitMQ", "Plugins\ThingsGateway.RabbitMQ\ThingsGateway.RabbitMQ.csproj", "{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Foundation.Adapter.Siemens", "Plugins\ThingsGateway.Foundation.Adapter.Siemens\ThingsGateway.Foundation.Adapter.Siemens.csproj", "{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThingsGateway.Siemens", "Plugins\ThingsGateway.Siemens\ThingsGateway.Siemens.csproj", "{12D7E735-8DA0-47FB-849F-84C965E4C746}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Siemens", "Siemens", "{98BB0E39-0629-4619-A01B-4BEB0F8DE879}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -218,6 +224,22 @@ Global
|
||||
{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -246,6 +268,9 @@ Global
|
||||
{4141C657-56B0-40D9-BA52-9F53A6D9E338} = {D64E48AD-2937-4227-8270-63AEF3008374}
|
||||
{968A381B-E354-4089-984D-7B32DF2A470D} = {D1DFBF11-65DE-49C8-9AE9-BFDFB66E129A}
|
||||
{7D38A1C3-56FA-41CE-B541-9FF1B5A632F7} = {968A381B-E354-4089-984D-7B32DF2A470D}
|
||||
{A0C38DE3-56FD-4ECC-9599-372A4716A4C8} = {98BB0E39-0629-4619-A01B-4BEB0F8DE879}
|
||||
{12D7E735-8DA0-47FB-849F-84C965E4C746} = {98BB0E39-0629-4619-A01B-4BEB0F8DE879}
|
||||
{98BB0E39-0629-4619-A01B-4BEB0F8DE879} = {D1DFBF11-65DE-49C8-9AE9-BFDFB66E129A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C44BAB06-0E6E-46A5-84A1-C9C94533DD4D}
|
||||
|
Reference in New Issue
Block a user