You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

619 lines
24 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using HslCommunication.BasicFramework;
using HslCommunication.Core;
using HslCommunication.Serial;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HslCommunication.Profinet.Keyence
{
/// <summary>
/// 基恩士KV上位链路串口通信的对象,适用于Nano系列串口数据,以及L20V通信模块
/// </summary>
/// <remarks>
/// 地址的输入的格式说明如下:
/// <list type="table">
/// <listheader>
/// <term>地址名称</term>
/// <term>地址代号</term>
/// <term>示例</term>
/// <term>地址进制</term>
/// <term>字操作</term>
/// <term>位操作</term>
/// <term>KV-7500/7300</term>
/// <term>KV-5500/5000/3000</term>
/// <term>KV Nano</term>
/// </listheader>
/// <item>
/// <term>输入继电器</term>
/// <term>X</term>
/// <term>X100,X1A0</term>
/// <term>16</term>
/// <term>√</term>
/// <term>√</term>
/// <term>R00000~R99915</term>
/// <term>R00000~R99915</term>
/// <term>R00000R59915</term>
/// </item>
/// <item>
/// <term>输出继电器</term>
/// <term>Y</term>
/// <term>Y100,Y1A0</term>
/// <term>16</term>
/// <term>√</term>
/// <term>√</term>
/// <term>R00000~R99915</term>
/// <term>R00000~R99915</term>
/// <term>R00000R59915</term>
/// </item>
/// <item>
/// <term>内部辅助继电器</term>
/// <term>MR</term>
/// <term>MR100,M200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term>MR00000~MR99915</term>
/// <term>MR00000~MR99915</term>
/// <term>MR00000MR59915</term>
/// </item>
/// <item>
/// <term>数据存储器</term>
/// <term>DM</term>
/// <term>DM100,DM200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term>DM00000~DM65534</term>
/// <term>DM00000~DM65534</term>
/// <term>DM00000DM32767</term>
/// </item>
/// <item>
/// <term>定时器(当前值)</term>
/// <term>TN</term>
/// <term>TN100,TN200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term>T0000~T3999</term>
/// <term>T0000~T3999</term>
/// <term>T000T511</term>
/// </item>
/// <item>
/// <term>定时器(接点)</term>
/// <term>TS</term>
/// <term>TS100,TS200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term>T0000~T3999</term>
/// <term>T0000~T3999</term>
/// <term>T000T511</term>
/// </item>
/// <item>
/// <term>计数器(当前值)</term>
/// <term>CN</term>
/// <term>CN100,CN200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>×</term>
/// <term>C0000~C3999</term>
/// <term>C0000~C3999</term>
/// <term>C000C255</term>
/// </item>
/// <item>
/// <term>计数器(接点)</term>
/// <term>CS</term>
/// <term>CS100,CS200</term>
/// <term>10</term>
/// <term>√</term>
/// <term>√</term>
/// <term>C0000~C3999</term>
/// <term>C0000~C3999</term>
/// <term>C000C255</term>
/// </item>
/// </list>
/// </remarks>
public class KeyenceNanoSerial : SerialDeviceBase<KeyenceNanoByteTransform>
{
#region Constructor
/// <summary>
/// 实例化基恩士的串口协议的通讯对象
/// </summary>
public KeyenceNanoSerial()
{
WordLength = 1;
}
/// <summary>
/// 初始化后建立通讯连接
/// </summary>
/// <returns>是否初始化成功</returns>
protected override OperateResult InitializationOnOpen()
{
// 建立通讯连接{CR/r}
var result = ReadBase(_buildConnectCmd);
if (!result.IsSuccess) return result;
return OperateResult.CreateSuccessResult();
}
#endregion
#region Check Response
/// <summary>
/// 校验读取返回数据状态
/// </summary>
/// <param name="ack"></param>
/// <returns></returns>
private OperateResult CheckPlcReadResponse(byte[] ack)
{
if (ack.Length == 0) return new OperateResult(StringResources.Language.MelsecFxReceiveZore);
if (ack[0] == 0x45) return new OperateResult(StringResources.Language.MelsecFxAckWrong + " Actual: " + SoftBasic.ByteToHexString(ack, ' '));
if ((ack[ack.Length - 1]!=0x0A) &&(ack[ack.Length -2]!=0x0D)) return new OperateResult(StringResources.Language.MelsecFxAckWrong + " Actual: " + SoftBasic.ByteToHexString(ack, ' '));
return OperateResult.CreateSuccessResult();
}
/// <summary>
/// 校验写入返回数据状态
/// </summary>
/// <param name="ack"></param>
/// <returns></returns>
private OperateResult CheckPlcWriteResponse(byte[] ack)
{
if (ack.Length == 0) return new OperateResult(StringResources.Language.MelsecFxReceiveZore);
return OperateResult.CreateSuccessResult();
}
#endregion
#region Read Support
/// <summary>
/// 建立读取指令
/// </summary>
/// <param name="address">软元件地址</param>
/// <param name="length">读取长度</param>
/// <returns>是否建立成功</returns>
private OperateResult<byte[]> BuildReadCommand(string address, ushort length)
{
var addressResult = KvCalculateWordStartAddress(address);
if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(addressResult);
ushort startAddress = addressResult.Content;
StringBuilder StrCommand = new StringBuilder();
StrCommand.Append("RDS"); //批量读取
StrCommand.Append(" "); //空格符
StrCommand.Append(address); //软元件地址如DM100
StrCommand.Append(" "); //空格符
StrCommand.Append(length.ToString());
StrCommand.Append("\r"); //结束符
byte[] _PLCCommand = Encoding.ASCII.GetBytes(StrCommand.ToString().ToCharArray());
return OperateResult.CreateSuccessResult(_PLCCommand);
}
/// <summary>
/// 读取设备的short类型的数据
/// </summary>
/// <param name="address">起始地址</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<short> ReadInt16(string address)
{
var result= ReadInt16(address, 1);
if (!result.IsSuccess) return OperateResult.CreateFailedResult<short>(result);
return OperateResult.CreateSuccessResult(result.Content[0]);
}
/// <summary>
/// 读取设备的short类型的数组
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">数组长度</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<short[]> ReadInt16(string address, ushort length)
{
address += ".S";
return base.ReadInt16(address, length);
}
/// <summary>
/// 读取设备的ushort数据类型的数据
/// </summary>
/// <param name="address">起始地址</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<ushort> ReadUInt16(string address)
{
var result = ReadUInt16(address, 1);
if (!result.IsSuccess) return OperateResult.CreateFailedResult<ushort>(result);
return OperateResult.CreateSuccessResult(result.Content[0]);
}
/// <summary>
/// 读取设备的ushort类型的数组
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">数组长度</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<ushort[]> ReadUInt16(string address, ushort length)
{
address += ".U";
return base.ReadUInt16(address, length);
}
/// <summary>
/// 读取设备的int类型的数据
/// </summary>
/// <param name="address">起始地址</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<int> ReadInt32(string address)
{
var result = ReadInt32(address, 1);
if (!result.IsSuccess) return OperateResult.CreateFailedResult<int>(result);
return OperateResult.CreateSuccessResult(result.Content[0]);
}
/// <summary>
/// 读取设备的int类型的数组
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">数组长度</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<int[]> ReadInt32(string address, ushort length)
{
address += ".L";
return base.ReadInt32(address, length);
}
/// <summary>
/// 读取设备的uint类型的数据
/// </summary>
/// <param name="address">起始地址</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<uint> ReadUInt32(string address)
{
var result = ReadUInt32(address, 1);
if (!result.IsSuccess) return OperateResult.CreateFailedResult<uint>(result);
return OperateResult.CreateSuccessResult(result.Content[0]);
}
/// <summary>
/// 读取设备的uint类型的数组
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="length">数组长度</param>
/// <returns>带成功标志的结果数据对象</returns>
public new OperateResult<uint[]> ReadUInt32(string address, ushort length)
{
address += ".D";
return base.ReadUInt32(address, length);
}
/// <summary>
/// 从PLC中读取想要的数据返回读取结果
/// </summary>
public override OperateResult<byte[]> Read(string address, ushort length)
{
// 获取指令
OperateResult<byte[]> command = BuildReadCommand(address, length);
if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);
// 核心交互
OperateResult<byte[]> read = ReadBase(command.Content);
if (!read.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(read);
// 反馈检查
OperateResult ackResult = CheckPlcReadResponse(read.Content);
if (!ackResult.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(ackResult);
// 数据提炼
return ExtractActualData(read.Content);
}
/// <summary>
/// 成批读取Bool值
/// </summary>
/// <param name="address">地址信息</param>
/// <param name="length">数组长度</param>
/// <returns>带成功标志的结果数据对象</returns>
public OperateResult<bool[]> ReadBool(string address, ushort length)
{
var strBuffer = Encoding.Default.GetString(Read(address, length).Content).Split(' ');
var result = new bool[strBuffer.Length];
for (int i = 0; i < length; i++)
{
result[i] = strBuffer[i] == "1" ? true : false;
}
return OperateResult.CreateSuccessResult(result);
}
/// <summary>
/// 读取单个Bool值
/// </summary>
/// <param name="address">地址信息</param>
/// <returns>带成功标志的结果数据对象</returns>
public OperateResult<bool> ReadBool(string address)
{
OperateResult<bool[]> read = ReadBool(address, 1);
if (!read.IsSuccess) return OperateResult.CreateFailedResult<bool>(read);
return OperateResult.CreateSuccessResult<bool>(read.Content[0]);
}
#endregion
#region Write Support
/// <summary>
/// 写入转换后的数据值
/// </summary>
/// <param name="address">软元件地址</param>
/// <param name="value">转换后的Byte[]数据</param>
/// <returns>是否成功写入的结果</returns>
public override OperateResult Write(string address, byte[] value)
{
// 获取写入
OperateResult<byte[]> command = BuildWriteCommand(address, value);
if (!command.IsSuccess) return command;
// 核心交互
OperateResult<byte[]> read = ReadBase(command.Content);
if (!read.IsSuccess) return read;
// 结果验证
OperateResult checkResult = CheckPlcWriteResponse(read.Content);
if (!checkResult.IsSuccess) return checkResult;
return OperateResult.CreateSuccessResult();
}
/// <summary>
/// 写入位数据的通断,支持的类型参考文档说明
/// </summary>
/// <param name="address">地址信息</param>
/// <param name="value">是否为通</param>
/// <returns>是否写入成功的结果对象</returns>
public OperateResult Write(string address, bool value)
{
//value=true时:指令尾部命令为" 1 1";value=false:指令尾部命令为" 1 0";
var byteTemp = value ? new byte[] {0x20, 0x31,0x20,0x31 } : new byte[] { 0x20, 0x31, 0x20, 0x30 };
// 先获取指令
OperateResult<byte[]> command = BuildWriteCommand(address, byteTemp);
if (!command.IsSuccess) return command;
// 和串口进行核心的数据交互
OperateResult<byte[]> read = ReadBase(command.Content);
if (!read.IsSuccess) return read;
// 检查结果是否正确
OperateResult checkResult = CheckPlcWriteResponse(read.Content);
if (!checkResult.IsSuccess) return checkResult;
return OperateResult.CreateSuccessResult();
}
/// <summary>
/// 建立写入指令
/// </summary>
/// <param name="address">软元件地址</param>
/// <param name="value">转换后的数据</param>
/// <returns></returns>
private OperateResult<byte[]> BuildWriteCommand(string address, byte[] value)
{
var addressResult = KvCalculateWordStartAddress(address);
if (!addressResult.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(addressResult);
ushort startAddress = addressResult.Content;
StringBuilder StrCommandHead = new StringBuilder();
StrCommandHead.Append("WRS"); //批量读取
StrCommandHead.Append(" "); //空格符
StrCommandHead.Append(address); //软元件地址如DM100可加格式后缀如DM100.S表示有符号16位十进制
byte[] _CommandHead = Encoding.ASCII.GetBytes(StrCommandHead.ToString().ToCharArray());
byte[] result = new byte[value.Length + _CommandHead.Length + 1];
Array.Copy(_CommandHead, result, _CommandHead.Length);
Array.Copy(value, 0, result, _CommandHead.Length, value.Length);
result[result.Length - 1] = 0x0d;
return OperateResult.CreateSuccessResult(result); // Return
}
#endregion
#region Private Member
private byte[] _buildConnectCmd = new byte[3] { 0x43, 0x52, 0x0d }; // 建立通讯连接{CR/r}
private byte[] _writeOkReturn = new byte[] { 0x4f, 0x4b, 0x0d, 0x0a }; // 写入数据成功返回指令
#endregion
#region Object Override
/// <summary>
/// 返回表示当前对象的字符串
/// </summary>
/// <returns>字符串信息</returns>
public override string ToString( )
{
return $"KeyenceNanoSerial[{PortName}:{BaudRate}]";
}
#endregion
#region Static Method Helper
/// <summary>
/// 从PLC反馈的数据进行提炼操作
/// </summary>
/// <param name="response">PLC反馈的真实数据</param>
/// <returns>数据提炼后的真实数据</returns>
public static OperateResult<byte[]> ExtractActualData(byte[] response)
{
try
{
string strResponse = Encoding.Default.GetString(response);
byte[] data = new byte[response.Length - 2];
Array.Copy(response, data, response.Length - 2);
return OperateResult.CreateSuccessResult(data);
}
catch (Exception ex)
{
return new OperateResult<byte[]>()
{
Message = "Extract Msg" + ex.Message + Environment.NewLine +
"Data: " + BasicFramework.SoftBasic.ByteToHexString(response)
};
}
}
/// <summary>
/// 返回读取的地址及长度信息
/// </summary>
/// <param name="address">读取的地址信息</param>
/// <returns>带起始地址的结果对象</returns>
private static OperateResult<ushort> KvCalculateWordStartAddress(string address)
{
// 初步解析,失败就返回
var analysis = KvAnalysisAddress(address);
if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<ushort>(analysis);
// 二次解析
ushort startAddress = analysis.Content2;
return OperateResult.CreateSuccessResult(startAddress);
}
/// <summary>
/// 解析数据地址成不同的Keyence地址类型
/// </summary>
/// <param name="address">数据地址</param>
/// <returns>地址结果对象</returns>
private static OperateResult<KeyenceDataType, ushort> KvAnalysisAddress(string address)
{
var result = new OperateResult<KeyenceDataType, ushort>();
try
{
//删除软元件后缀如DM100.L—>DM100
if (address.Contains("."))
{
address = address.Substring(0, address.Length - 2);
}
switch (address[0])
{
case 'M':
case 'm':
{
result.Content1 = KeyenceDataType.M;
if (address[1] == 'R' || address[1] == 'r')
{
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.M.FromBase);
}
else
{
result.Content2 = Convert.ToUInt16(address.Substring(1), KeyenceDataType.M.FromBase);
}
break;
}
case 'R':
case 'r':
{
result.Content1 = KeyenceDataType.R;
result.Content2 = Convert.ToUInt16(address.Substring(1), KeyenceDataType.R.FromBase);
break;
}
case 'X':
case 'x':
{
result.Content1 = KeyenceDataType.X;
result.Content2 = Convert.ToUInt16(address.Substring(1), 8);
break;
}
case 'Y':
case 'y':
{
result.Content1 = KeyenceDataType.Y;
result.Content2 = Convert.ToUInt16(address.Substring(1), 8);
break;
}
case 'D':
case 'd':
{
result.Content1 = KeyenceDataType.D;
if (address[1] == 'M' || address[1] == 'm')
{
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.D.FromBase);
}
else
{
result.Content2 = Convert.ToUInt16(address.Substring(1), KeyenceDataType.D.FromBase);
}
break;
}
case 'T':
case 't':
{
if (address[1] == 'N' || address[1] == 'n')
{
result.Content1 = KeyenceDataType.TN;
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.TN.FromBase);
break;
}
else if (address[1] == 'S' || address[1] == 's')
{
result.Content1 = KeyenceDataType.TS;
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.TS.FromBase);
break;
}
else
{
throw new Exception(StringResources.Language.NotSupportedDataType);
}
}
case 'C':
case 'c':
{
if (address[1] == 'N' || address[1] == 'n')
{
result.Content1 = KeyenceDataType.CN;
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.CN.FromBase);
break;
}
else if (address[1] == 'S' || address[1] == 's')
{
result.Content1 = KeyenceDataType.CS;
result.Content2 = Convert.ToUInt16(address.Substring(2), KeyenceDataType.CS.FromBase);
break;
}
else
{
throw new Exception(StringResources.Language.NotSupportedDataType);
}
}
default: throw new Exception(StringResources.Language.NotSupportedDataType);
}
}
catch (Exception ex)
{
result.Message = ex.Message;
return result;
}
result.IsSuccess = true;
return result;
}
#endregion
}
}