namespace SHJX.Service.PortClient.TempController { /// /// 台达DTC1000V温控器通信控制类 /// public class DeltaDTC { private static readonly ILogger logger = LogFactory.BuildLogger(typeof(SerialPortImp)); private DeltaDTC() { RetrieveDatas = ""; LastReadPVtime = DateTime.Now; LastReadSVtime = DateTime.Now; LastReadOutputTime = DateTime.Now; CurrTempPv = 0; CurrTempSv = 0; } private static readonly object obj_locker = new(); public static DeltaDTC StartNew { get { lock (obj_locker) { return new(); } } } public DeltaDTC SetAddrNum(byte nodeID) { AddrNum = nodeID; return this; } #region protected properties /// /// 温控器RS485通信号 /// private byte AddrNum { get; set; } /// /// 最小查询间隔,毫秒 /// protected const int IntervalMin = 3000; /// /// 发送指令后收到的响应数据 /// private string RetrieveDatas { get; set; } /// /// 记录最后一次查询PV温度的时间,避免频繁查询 /// private DateTime LastReadPVtime { get; set; } /// /// 记录最后一次查询SV温度的时间,避免频繁查询 /// private DateTime LastReadSVtime { get; set; } /// /// 输出量最后查询时间 /// private DateTime LastReadOutputTime { get; set; } /// /// 最近一次读到的实测温度 /// private double CurrTempPv { get; set; } /// /// 最近一次读到的设置温度 /// private double CurrTempSv { get; set; } /// /// 当前输出量 /// private double CurrTempOutput { get; set; } #endregion #region protected methods /// /// 发送ASCII指令给温控器 /// /// 功能码+数据+LRC码 protected string SendCommand(string cmd) { if (!Rs485Client.IsValid) { return string.Empty; } RetrieveDatas = ""; //Modbus ASCII格式:[:地址 功能码 数据地址 数据长度 LRC码 \r\n],如:[:-01-03-1000-0002-EA-CRLF] var msg = $":{cmd}\r\n"; return Rs485Client.SendData(msg, "\r\n"); } /// /// 从寄存器startpos,读取1个字节数据 /// /// 起始位置,4个字符长度,如"1000" /// 读取结果 protected double ReadByte(string memAddr) { double retval = -1; try { if (Rs485Client.IsValid) { // 读数指令 string cmd = AddrNum.ToString("X").PadLeft(2, '0') + "03" + memAddr + "0001"; cmd += LRC(cmd); RetrieveDatas = SendCommand(cmd); #region 解析温度值 if (!string.IsNullOrEmpty(RetrieveDatas) && RetrieveDatas.Length >= (13)) // 例如 ": 06 03 02 00C8 CF \CR\LF" { string strPre = string.Format(":{0}03", AddrNum.ToString("X").PadLeft(2, '0')); if (RetrieveDatas.IndexOf(strPre) >= 0) { RetrieveDatas = RetrieveDatas.Remove(0, 5); // 移除":0103" int rlen = Convert.ToInt16(RetrieveDatas.Substring(0, 2), 16); // 数据长度 RetrieveDatas = RetrieveDatas.Remove(0, 2); // 数据长度 if (RetrieveDatas.Length >= 4) { retval = Convert.ToInt16(RetrieveDatas.Substring(0, 4), 16); RetrieveDatas = RetrieveDatas.Remove(0, 4); } } } #endregion RetrieveDatas = ""; } } catch (Exception ex) { logger.LogError(ex.ToString()); } return retval; } /// /// 从寄存器读取startpos开始的length字节数据 /// /// 起始位置,4个字符长度,如"1000" /// 读取长度,不大于6 /// protected double[] ReadBytes(string startpos, int length) { if (length > 6) { length = 6; } double[] retval = new double[length]; try { if (Rs485Client.IsValid) { // 读数指令 string cmd = AddrNum.ToString("X").PadLeft(2, '0') + "03" + startpos + length.ToString("X").PadLeft(4, '0'); cmd += LRC(cmd); string resdata = SendCommand(cmd); #region 解析读数值 if (!string.IsNullOrEmpty(resdata) && resdata.Length >= (9 + length * 4)) // 例如 ": 06 03 02 00C8 CF \CR\LF" { string strPre = string.Format(":{0}03", AddrNum.ToString("X").PadLeft(2, '0')); if (resdata.IndexOf(strPre) >= 0) { resdata = resdata.Remove(0, 5); // 移除":0103" int rlen = Convert.ToInt16(resdata.Substring(0, 2), 16); // 数据长度 resdata = resdata.Remove(0, 2); // 数据长度 for (int i = 0; i < length; i++) { if (resdata.Length < 4) break; retval[i] = Convert.ToInt16(resdata.Substring(0, 4), 16); resdata = resdata.Remove(0, 4); } } } #endregion } } catch (Exception ex) { logger.LogError(ex.ToString()); } return retval; } /// /// 写入数据到指定地址 /// /// 写入地址,4位字符 /// 要写入的数据 /// 成功=true;失败=false protected bool WriteBytes(string startpos, string datas) { bool bRet = false; try { if (Rs485Client.IsValid) { string cmd = string.Format("{0}06{1}{2}", AddrNum.ToString("X").PadLeft(2, '0'), startpos, datas); cmd += LRC(cmd); string resDatas = SendCommand(cmd); // 确定是否成功,返回字符串与查询指令相同 if (!string.IsNullOrEmpty(resDatas) && resDatas.IndexOf(cmd) >= 0) { bRet = true; } } } catch (Exception ex) { logger.LogError(ex.ToString()); } return bRet; } #endregion #region static CRC /// /// 计算CRC校验码,16位 /// /// 指令 /// 校验码数值 public static UInt32 CRC16(byte[] buff, int startpos = 0, int length = -1) { UInt32 crcval = 0xFFFF; if (length < 1 || length > buff.Length) { length = buff.Length; } int i = startpos; while (length > 0) { crcval ^= buff[i++]; for (int j = 0; j < 8; j++) { if ((crcval & 0x01) > 0) crcval = (crcval >> 1) ^ 0xa001; else crcval >>= 1; } length--; } return crcval; } /// /// 计算LRC校验码,字节码 /// /// /// public static byte LRC(byte[] auchMsg) { byte uchLRC = 0; foreach (byte item in auchMsg) { uchLRC += item; } return (byte)((uchLRC ^ 0xFF) + 1); } /// /// 计算LRC校验码,字符串 /// /// /// public static string LRC(string auchMsg) { int uchLRC = 0; for (int i = 0; i < auchMsg.Length; i++) { string chs = auchMsg.Substring(i, 2); uchLRC += Convert.ToInt32(chs, 16); i++; } byte btlrc = (byte)((uchLRC ^ 0xFF) + 1); return btlrc.ToString("X").PadLeft(2, '0'); } #endregion #region PID参数读写 /// /// 读取PID参数中的比例Pd /// /// 比例值0.0~9999.0 public double ReadPidPb() { int startpos = 0x1009; return ReadByte(startpos.ToString("X").PadLeft(4, '0')) / 10.0; } /// /// 读取PID参数中的积分Ti /// /// 积分值0~9999 public int ReadPidTi() { int startpos = 0x100A; return (int)ReadByte(startpos.ToString("X").PadLeft(4, '0')); } /// /// 读取PID参数中的微分Td /// /// 微分值0~9999 public int ReadPidTd() { int startpos = 0x100B; return (int)ReadByte(startpos.ToString("X").PadLeft(4, '0')); } /// /// 读取PID参数中的积分量默认值 /// /// 积分量默认值,0.0~100.0% public double ReadPidTiDeft() { int startpos = 0x100C; return ReadByte(startpos.ToString("X").PadLeft(4, '0')) / 10.0; } /// /// 写入PID参数中的比例Pb /// /// 比例值,0.0~9999.0 /// 成功=true;失败=false public bool WritePidPb(double newval) { int startpos = 0x1009; return WriteBytes(startpos.ToString("X").PadLeft(4, '0'), ((Int32)(newval * 10)).ToString("X").PadLeft(4, '0')); } /// /// 写入PID参数中的积分Ti /// /// 积分值,0~9999 /// 成功=true;失败=false public bool WritePidTi(int newval) { int startpos = 0x100A; return WriteBytes(startpos.ToString("X").PadLeft(4, '0'), newval.ToString("X").PadLeft(4, '0')); } /// /// 写入PID参数中的微分Td /// /// 微分值,0~9999 /// 成功=true;失败=false public bool WritePidTd(int newval) { int startpos = 0x100B; return WriteBytes(startpos.ToString("X").PadLeft(4, '0'), newval.ToString("X").PadLeft(4, '0')); } /// /// 写入PID参数中的积分量默认值 /// /// 积分量默认值,0.0~100.0% /// 成功=true;失败=false public bool WritePidTiDeft(double newval) { int startpos = 0x100C; return WriteBytes(startpos.ToString("X").PadLeft(4, '0'), ((Int32)(newval * 10)).ToString("X").PadLeft(4, '0')); } /// /// 读取一个通道的pid参数值 /// /// 比例带,0.0~9999.0 /// 积分值,0~9999 /// 微分值,0~9999 /// 积分量默认值,0.0~100.0 (%) public void ReadPid(ref double pb, ref int ti, ref int td, ref double tideft) { pb = ReadPidPb(); ti = ReadPidTi(); td = ReadPidTd(); tideft = ReadPidTiDeft(); } /// /// 写入一个通道的pid参数值 /// /// 比例带(P),0.0~9999.0 /// 积分值(I),0~9999 /// 微分值(D),0~9999 /// 积分量默认值,0.0~100.0 (%) /// public bool WritePid(double pb, int ti, int td, double tideft) { bool retval = true; retval = retval && WritePidPb(pb); retval = retval && WritePidTi(ti); retval = retval && WritePidTd(td); retval = retval && WritePidTiDeft(tideft); return retval; } #endregion #region public methods /// /// 读取实际温度 /// /// public double ReadPV() { #region 控制查询频率,避免频繁查询 if (DateTime.Now.Subtract(LastReadPVtime).TotalMilliseconds < IntervalMin) { return CurrTempPv; } LastReadPVtime = DateTime.Now; #endregion try { if (Rs485Client.IsValid) { DateTime tmstart = DateTime.Now; // 读取设定温度 string addr = "1000"; string cmd = string.Format("{0}03{1}0001", AddrNum.ToString("X").PadLeft(2, '0'), addr); cmd += LRC(cmd); RetrieveDatas = SendCommand(cmd); #region 解析温度值 if (!string.IsNullOrEmpty(RetrieveDatas) && RetrieveDatas.Length >= 15) { string strPre = string.Format(":{0}03", AddrNum.ToString("X").PadLeft(2, '0')); if (RetrieveDatas.IndexOf(strPre) >= 0) { RetrieveDatas = RetrieveDatas.Remove(0, 5); // 移除":0103" RetrieveDatas = RetrieveDatas.Remove(0, 2); if (RetrieveDatas.Length >= 4) { CurrTempPv = Convert.ToInt16(RetrieveDatas.Substring(0, 4), 16) / 10.0; RetrieveDatas = RetrieveDatas.Remove(0, 4); //if (this.LogProgram != null) { this.LogProgram.Info(".." + this.AddrNum.ToString() + ".."); } } } } #endregion RetrieveDatas = ""; logger.LogInformation("dtc start query pv = " + DateTime.Now.Subtract(tmstart).TotalMilliseconds.ToString()); } } catch (Exception ex) { logger.LogError(ex.ToString()); } return CurrTempPv; } /// /// 读取设定温度 /// /// public double ReadSV() { #region 控制查询频率,避免频繁查询 if (DateTime.Now.Subtract(LastReadSVtime).TotalMilliseconds < IntervalMin) { return CurrTempSv; } LastReadSVtime = DateTime.Now; #endregion try { if (Rs485Client.IsValid) { // 读取设定温度 string addr = "1001"; string cmd = string.Format("{0}03{1}0001", AddrNum.ToString("X").PadLeft(2, '0'), addr); cmd += LRC(cmd); RetrieveDatas = SendCommand(cmd); #region 解析温度值 if (!string.IsNullOrEmpty(RetrieveDatas) && RetrieveDatas.Length >= 15) { string strPre = string.Format(":{0}03", AddrNum.ToString("X").PadLeft(2, '0')); if (RetrieveDatas.IndexOf(strPre) >= 0) { RetrieveDatas = RetrieveDatas.Remove(0, 5); // 移除":0103" RetrieveDatas = RetrieveDatas.Remove(0, 2); if (RetrieveDatas.Length >= 4) { CurrTempSv = Convert.ToInt16(RetrieveDatas.Substring(0, 4), 16) / 10.0; RetrieveDatas = RetrieveDatas.Remove(0, 4); } } } #endregion RetrieveDatas = ""; } } catch (Exception ex) { logger.LogError(ex.ToString()); } return CurrTempSv; } /// /// 写入设定温度 /// /// /// public bool WriteSV(double tempVal) { bool bRet = false; try { if (Rs485Client.IsValid) { string addr = "1001"; string strtmp = ((Int32)(tempVal * 10)).ToString("X").PadLeft(4, '0');//2000转为十六进制7D0,左边补0,直到长度为4 if (strtmp.Length > 4) { strtmp = strtmp.Substring(strtmp.Length - 4, 4); }//若长度大于4,取后4位 string cmd = string.Format("{0}06{1}{2}", AddrNum.ToString("X").PadLeft(2, '0'), addr, strtmp);//端口地址+06+1001+07D0+校验 cmd += LRC(cmd);//0706100107D00B--cmd RetrieveDatas = SendCommand(cmd); // 确定是否成功 if (!string.IsNullOrEmpty(RetrieveDatas) && RetrieveDatas.Length > 0) { if (RetrieveDatas == string.Format(":{0}\r\n", cmd)) { bRet = true; } } RetrieveDatas = ""; } } catch (Exception ex) { logger.LogError(ex.ToString()); } return bRet; } /// /// 读取PV和SV两个值 /// /// 实际温度 /// 设定温度 public void ReadPVnSV(ref double pv, ref double sv) { DateTime tmstart = DateTime.Now; #region 控制查询频率,避免频繁查询 if (DateTime.Now.Subtract(LastReadPVtime).TotalMilliseconds < IntervalMin && DateTime.Now.Subtract(LastReadSVtime).TotalMilliseconds < IntervalMin) { pv = CurrTempPv; sv = CurrTempSv; return; } #endregion try { if (Rs485Client.IsValid) { // 读取2个温度值(2个word) string addr = "1000"; string cmd = string.Format("{0}03{1}0002", AddrNum.ToString("X").PadLeft(2, '0'), addr); cmd += LRC(cmd); RetrieveDatas = SendCommand(cmd); #region 解析温度值 if (!string.IsNullOrEmpty(RetrieveDatas) && RetrieveDatas.Length >= 17) { string strPre = string.Format(":{0}03", AddrNum.ToString("X").PadLeft(2, '0')); if (RetrieveDatas.IndexOf(strPre) >= 0) { RetrieveDatas = RetrieveDatas.Remove(0, 5); // 移除起始符信息":0103" RetrieveDatas = RetrieveDatas.Remove(0, 2); // 移除2位length信息 if (RetrieveDatas.Length >= 4) { // 解析1000H数据 CurrTempPv = Convert.ToInt16(RetrieveDatas.Substring(0, 4), 16) / 10.0; RetrieveDatas = RetrieveDatas.Remove(0, 4); } if (RetrieveDatas.Length >= 4) { // 解析1001H数据 CurrTempSv = Convert.ToInt16(RetrieveDatas.Substring(0, 4), 16) / 10.0; RetrieveDatas = RetrieveDatas.Remove(0, 4); } } } #endregion RetrieveDatas = ""; } pv = CurrTempPv; sv = CurrTempSv; LastReadPVtime = LastReadSVtime = DateTime.Now; } catch (UnauthorizedAccessException exo) { logger.LogError(exo.ToString()); pv = CurrTempPv; sv = CurrTempSv; LastReadPVtime = LastReadSVtime = DateTime.Now; } catch (Exception ex) { logger.LogError(ex.ToString()); pv = CurrTempPv; sv = CurrTempSv; LastReadPVtime = LastReadSVtime = DateTime.Now; } } /// /// 读取输出量 /// /// public double ReadOutput() { #region 控制查询频率,避免频繁查询 if (DateTime.Now.Subtract(LastReadOutputTime).TotalMilliseconds < IntervalMin * 3) { return CurrTempOutput; } LastReadOutputTime = DateTime.Now; #endregion int startpos = 0x1012; CurrTempOutput = ReadByte(startpos.ToString("X").PadLeft(4, '0')); return CurrTempOutput; } /// /// 写入输出量(仅温控器处于手动控制模式下有效) /// /// /// public bool WriteOutput(double newVal) { int startpos = 0x1012; string datas = ((Int32)(newVal * 10)).ToString("X").PadLeft(4, '0'); return WriteBytes(startpos.ToString("X").PadLeft(4, '0'), datas); } #endregion } }