原理解析
# 协议插件介绍
详情
协议插件是IoTCenter用以实现数据采集的业务单元,必须遵循一定的开发规范,该规范主要包括以下内容:
协议插件依赖于敢为网关数据采集服务(GWDataCenter),并作为其插件运行,GWDataCenter作为宿主程序,负责管理插件的加载、生命周期,并负责对实时数据进行存储,本身提供了一组读写实时数据的逻辑。
协议插件的.NET框架应保持与宿主程序一致,过低或过高的版本均可能导致插件无法运行,宿主将会主动停止运行。
协议插件的入口类名为CEquip,并必须重载init、GetData、GetYC、GetYX、SetParm五个模板方法。
命名:为了更好的标识协议插件的产权信息,建议使用公司代号作为统一命名,敢为软件对协议插件的命名规则为:
适用于公司范围的插件,基本命名规则为GW{行业协议}.{.NET 版本}.dll。
如果该协议插件为基于 .NET(包括且不限定于 .NET Core/.NET Strandard/.NET 6及后续版本),其命名格式为GW{行业协议}.STD.dll。
例如:GWJieLink.STD.dll,表示基于捷顺RestFulApi标准协议文档开发的协议插件。
程序包定义:创建好的协议插件,应在其项目包属性中,定义公司版权信息和产品包信息,且每个版本发布时,应升级相应的版本号。
# 运行示意图
运行示意图
下图为协议插件的运行示意图
插件运行前准备:该工作在数据采集平台调用插件前提前准备线程资源。
协议初始化(
init
):用于初始化线程资源,建立与设备侧的连接状态。协议初始化(
OnLoaded
):用于初始化初始化设备,实现设备连接通信。获取数据(
GetData)
:按照配置的通讯时间参数,定时从设备侧获取数据。解析遥测数据(
GetYC
):根据设备关联的遥测点,对采集的数据进行解析,并调用SetYCData
方法,将数据写入到物联网平台的共享内存状态中。解析遥信(
GetYX
):对设备遥信状态进行解析,并调用SetYXData
方法,并将实时设备遥信状态写入到宿主实时内存中。解析遥信(
GetEvent
):从当前设备连接中获取事件列表,事件发布如门禁设备的一些通行记录数据。。设置命令(
SetParm
):外部应用可通过调用设置,将状态回调到协议插件中,实现某些特定的功能。
# 重载方法介绍
# init() 初始化设备参数
init() 设备初始化方法
init() 方法用于初始化设备,在数据库中提前设备信息、开始对通讯设备建立连接等,是动态库最先被执行的方法。
定义:
public override bool init(EquipItem item)
使用示例:
/// <summary>
/// 初始化设备相关参数
/// 在界面添加完成后,会进入到该方法进行初始化
/// 之后再界面修改连接参数后,会再一次进入该方法。
/// </summary>
/// <param name="item">equip表对象属性</param>
/// <returns></returns>
public override bool init(EquipItem item)
{
/*
item.Equip_addr 设备地址
解释:通常存放设备的唯一标识或者设备的连接地址。这里需要根据具体的协议来区分,如果一对一的直连设备
item.communication_param 设备连接参数
解释:通常存放设备的连接信息,具体由当前协议插件来约定,在配置文档中写明即可。
item.Local_addr 通讯端口(也叫通讯线程),任意字符,不宜过长。
解释:在Equip表,你可能会发现不少设备的Local_addr字段可能都是空的,也可能都是一个具体的字符串。
我们按照该字段的值进行Group By归类后,就得到了同一个值的设备数量有多少个,这个就代表一个线程管控了多少个设备。
item.communication_time_param
解释:在设备线程组里面,一个设备多久通信一次,即多久采集一次数据,单位毫秒。
如果communication_time_param职能比较多,也可以将多个参数的拼接,此时需要自行处理拆分后再转换。
配置举例:假设1个线程管控10个设备,要求每个设备每秒采集一次数据,那么这个字段的值应不大于100毫秒。其他场景同理计算即可。
item.Reserve2 设备自定义参数
解释:一般一些连接参数较多,需要规范化存储时,可以将属性放到自定义参数中,直观一些。当然也可以使用其他字段去拼接起来,但不建议这样做。
在6.1版本中,该字段在数据库中存储的值为一个JSON格式的数据。
在低版本中可以按照JSON格式来存储这个数据。
*/
//获取设备连接通讯的间隔时间。
_ = int.TryParse(item.communication_time_param, out _sleepInterval);
/*
在构造连接参数数,根据实际情况,以下展示一个连接参数模型的赋值。
如果连接参数简单,也可以使用自定义连接参数,直接使用communication_param更好,减少配置项,这里需要开发人员自己确定好。
*/
if (!string.IsNullOrWhiteSpace(item.Reserve2))
{
var dictParams = JsonConvert.DeserializeObject<Dictionary<string, string>>(item.Reserve2);
_connectionConfig = new ConnectionConfig
{
ServerUrl = item.Equip_addr,
UserName = dictParams.TryGetValue("UserName", out var userName) ? userName : string.Empty,
Password = dictParams.TryGetValue("Password", out var password) ? password : string.Empty,
CertificatePath = dictParams.TryGetValue("CertificatePath", out var certPath) ? certPath : string.Empty,
CertificatePwd = dictParams.TryGetValue("CertificatePwd", out var certPwd) ? certPwd : string.Empty,
};
//我们可以定义多个事件名称的级别,命名方式如DefaultEventMessageLevel,如果未取到,默认值给0,但最好要区分好,因为使用0的事件级别很多场景都使用。
_ = int.TryParse(dictParams.TryGetValue("DefaultEventMessageLevel", out var defaultEventMessageLevelStr) ? defaultEventMessageLevelStr : "0", out _defaultEventMessageLevel);
}
return base.init(item);
}
工作原理:
CEquip
类里的方法会被不断循环执行,但init
方法只在设备第一次运行以及设备或测点(包括设置点)配置进行了更改后执行。同时在基类中提供了Onloaded
虚方法在第一次加载之后或者系统重置之后自动执行。比如可以将写日志的操作写在Onloaded
方法中。base.init(item)
是基类方法用于初始化。基类中默认支持Modbus
等协议。
解析:
Init()
方法在框架中会被循环调用,返回值类型为bool
类型,当其返回值为Ture
时代表设备初始化成功,并开始执行其他成员方法,为false
时则代表设备初始化失败,初始化失败时则阻塞此类中其他方法的运行。Equipitem
参数为GWDataCenter.DataCenter
命名空间下的Equipitem
类,并继承IComparable
接口,其包含了设备的所有属性,并聚合了数据库的操作类Database
和用于与设备通讯的SerialPort
类。DataCenter.WriteLogFile()
是GWDataCenter.DataCenter
命名空间下的DataCenter
类中的一个记录错误日志的方法,其日志记录在\IoTCenter\log\XLog.txt
文件中。item.communication_time_param
是Equipitem
类中的一个字段,它映射自数据库Equip
表的communication_time_param
字段,代表刷新间隔。
# OnLoaded() 设备连接初始化或实现设备重连
OnLoaded() 方法
OnLoaded()方法是一个在init方法执行成功后会被调用的函数。它不需要任何参数,可以在init()方法中获取所需的参数,然后在OnLoaded方法中进行业务操作。
定义:
public override bool OnLoaded()
使用示例:
/// <summary>
/// 设备连接初始化
/// 对于设备的连接地址,连接账号密码发生更改后,可以进行重连。
/// </summary>
/// <returns></returns>
public override bool OnLoaded()
{
//TODO 这里可以写于设备连接的具体代码了。根据_connectionConfig连接参数,去创建自己的连接对象。
ConnClientManager.Instance.CreateClientSession(_connectionConfig);
//返回默认值
return base.OnLoaded();
}
# GetData() 方法
GetData() 方法
初始化设备后,开始执行此方法,根据 CEquip 的工作任务,可以分为 ycp/yxp 表读取数据、setparm 表设置数据两大部分。一般来说,setparm 控制某些动作,这样会影响数据的准确性,因此 GetData() 方法在此控制程序的工作。
定义:
public override CommunicationState GetData(CEquipBase pEquip)
使用示例:
/// <summary>
/// 获取设备状态及实时数据
/// 注意要控制好该方法不要出异常,否则会出现设备一直处于初始化状态中
/// </summary>
/// <param name="pEquip">设备基类对象</param>
/// <returns></returns>
public override CommunicationState GetData(CEquipBase pEquip)
{
//通过等待间隔时间,来达到多久取一次的。
if (_sleepInterval > 0)
base.Sleep(_sleepInterval);
//当然开发者也可以在此次在增加相关业务逻辑。
//获取当前连接地址的状态
var equipStatus = ConnClientManager.Instance.GetClientSessionStatus(_connectionConfig.ServerUrl);
//如果连接状态正常,设置为在线
if (equipStatus)
{
//只有在线是才采集数据
_currentValue = ConnClientManager.Instance.GetCurrentValues(_connectionConfig.ServerUrl, pEquip.m_equip_no);
return CommunicationState.ok;
}
else
{
//否则设置离线
return CommunicationState.fail;
}
return base.GetData(pEquip);
}
解析:
CommunicationState
是一个状态枚举,代表当前数据解析成功,失败等状态信息,如果验证成功后则返回基类的GetData()
方法继续执行,失败则阻塞GetYC()
、GetYX()
,SetParm()
方法的运行。GetData
方法在运行时包含CEquipBase
类对象,它包含了所有测点信息,可以在其中做数据校验,因为在具体设备通讯中,需要对返回的数据进行验证。一般进行数据验证会从以下几个方面开始,如:设备地址(如果协议有定义的话)、返回数据长度、完整性校验(根据协议可能有CRC、和校验等)。如 Modbus 协议中,最后两个字节进行 CRC 校验,同时还需要对设备地址进行全面的校验,否则收到错误的数据包将导致实际设备异常响应。Sleep()
方法是CEquipBase
基类中提供用于休眠GetData()
方法。
注意
# GetYC() 遥测方法
GetYC() 遥测方法
定义:
public override bool GetYC(YcpTableRow r)
使用示例:
/// <summary>
/// 遥测点设置
/// </summary>
/// <param name="r">ycp表对象属性(不是全部)</param>
/// <returns></returns>
public override bool GetYC(YcpTableRow r)
{
/*
注意:在此处最好不用打印日志,因为这里会产生大量的日志,如果需要调试某个点位时,可以在自定义参数里面加参数,针对固定的遥测进行日志调试。
r.main_instruction 操作命令,如EquipCurrentInfo
r.minor_instruction 操作参数,如Temperature,Humidness等
r.Reserve2 自定义参数,以json结构存储,同设备的自定义参数一样。
在给遥测赋值时提供了诸多方法,支持单个类型,多元组类型,可以根据实际需要使用。
SetYCData(YcpTableRow r, object o);
SetYCDataNoRead(IQueryable<YcpTableRow> Rows);
SetYcpTableRowData(YcpTableRow r, float o);
SetYcpTableRowData(YcpTableRow r, (double, double, double, double, double, double) o);
SetYcpTableRowData(YcpTableRow r, string o);
SetYcpTableRowData(YcpTableRow r, int o);
SetYcpTableRowData(YcpTableRow r, double o);
SetYcpTableRowData(YcpTableRow r, (double, double, double, double, double, double, double) o);
SetYcpTableRowData(YcpTableRow r, (double, double) o);
SetYcpTableRowData(YcpTableRow r, (DateTime, double) o);
SetYcpTableRowData(YcpTableRow r, (double, double, double, double) o);
SetYcpTableRowData(YcpTableRow r, (double, double, double, double, double) o);
SetYcpTableRowData(YcpTableRow r, (double, double, double) o);
*/
/* 实时数据示例代码,可以根据自己的业务进行处理*/
if (_currentValue == null) return true;
try
{
//此处的Key值需要根据实际情况去处理。如果构造实时数据缓存字典是需要由开发去定义。
//总的来说,按照设备+遥测遥信的方式构造缓存数据是比较合理的。
string key = r.equip_no + "_" + r.main_instruction;
if (_currentValue.ContainsKey(key))
{
var objValue = _currentValue[key];
if (objValue == null) SetYCData(r, "");//此处不可以设置为null。
else SetYCData(r, objValue);
}
else
{
SetYCData(r, "***");
}
}
catch (Exception ex)
{
SetYCData(r, "测点赋值出现异常,请查看日志");
DataCenter.Write2Log($"记录报错日志:{ex}", LogLevel.Error);
}
//此处默认都返回true,否则设备会处于离线。
return true;
}
解析:
GetYC()
方法根据当前设备所拥有的遥测点数 N 条执行 N 次,参数YcpTableRow
表示映射自数据库ycp
遥测表的某一个实体即数据行。bool
表示为当前测点设置值是否成功,不成功则设备故障。r.main_instruction
表示获取数据库中的main_instruction
列的值,对应界面中的操作命令
可以根据不同测点配置的参数,判断该测点需要何种数据。SetYCData()
上传遥测值给上层应用,该方法有多个重载版本支持string
、int
、float
、double
、元组等多种数据类型的数据,其中元组类型支持指定时间插入double
值以及double
类型的元组(元组的中double类型元素的数量支持2-7个)。其方法原型为:
注意:
- 返回值
动态库开发中对于获取失败的测点应当返回false,不可将值设置为类似“无数据”。
public override bool GetYC(YcpTableRow r)
{
//获取数据失败
return false;
}
错误的写法
public override bool GetYC(YcpTableRow r)
{
//获取数据失败
SetYCData(r, "无数据");
return true;
}
- 数据类型
动态库开发中,获取到设备的值需要转换为对应数据类型,一般情况下,遥测值为double类型,遥信值为Bool类型;
正确的写法
public override bool GetYC(YcpTableRow r)
{
//获取到字符串值
var value = "67.8";
SetYCData(r, double.Parse(value));
return true;
}
错误的写法
public override bool GetYC(YcpTableRow r)
{
//获取到字符串值
var value = "67.8";
SetYCData(r, value);
return true;
}
# GetYX() 遥信方法
GetYX() 遥信方法
定义:
public override bool GetYX(YxpTableRow r)
使用示例:
/// <summary>
/// 遥信点设置
/// </summary>
/// <param name="r">yxp表对象属性(不是全部)</param>
/// <returns></returns>
public override bool GetYX(YxpTableRow r)
{
/*
注意:在此处最好不用打印日志,因为这里会产生大量的日志,如果需要调试某个点位时,可以在自定义参数里面加参数,针对固定的遥测进行日志调试。
r.main_instruction 操作命令,如EquipCurrentInfo
r.minor_instruction 操作参数,如Temperature,Humidness等
r.Reserve2 自定义参数,以json结构存储,同设备的自定义参数一样。
在给遥测赋值时提供了诸多方法,支持bool、string类型,正常使用bool就够了,特殊情况可自行处理。
SetYXData(YxpTableRow r, object o);
SetYxpTableRowData(YxpTableRow r, string o);
SetYxpTableRowData(YxpTableRow r, bool o);
*/
/* 实时数据示例代码,可以根据自己的业务进行处理*/
if (_currentValue == null) return false;
try
{
string key = r.equip_no + "_" + r.main_instruction;
if (_currentValue.ContainsKey(key))
{
var nodeIdObj = _currentValue[key];
if (nodeIdObj == null) SetYXData(r, "***");
else SetYXData(r, nodeIdObj);
}
else
{
SetYXData(r, "***");
}
}
catch (Exception ex)
{
SetYXData(r, "遥信赋值出现异常,请查看日志");
DataCenter.Write2Log($"记录报错日志:{ex}", LogLevel.Error);
}
return true;
}
解析:
YxpTableRow
映射了 yxp 表的数据行,bool
表示为当前测点设置状态是否成功,不成功则设备状态显示为故障(灰色灯)。GetYX()
方法根据当前设备所拥有的遥信点数 N 条执行 N 次。SetYXData()
表示为当前的测点设置状态,这个状态是一个bool
类型,返回True
则正常(绿灯),False
则警报(红灯)。该方法有两个重载版本分别支持bool
和string
类型的参数。
# SetParm() 设置方法
SetParm() 设置方法
定义:
public override bool SetParm(string MainInstruct, string MinorInstruct, string Value)
使用示例:
/// <summary>
/// 设备命令下发
/// </summary>
/// <param name="mainInstruct">操作命令</param>
/// <param name="minorInstruct">操作参数</param>
/// <param name="value">传入的值</param>
/// <returns></returns>
public override bool SetParm(string mainInstruct, string minorInstruct, string value)
{
/*
注意:建议在此处打印日志,便于记录由平台执行命令的情况,用于追溯命令下发情况。
mainInstruct 操作命令,如:Control
minorInstruct 操作参数,如:SetTemperature,SetHumidness
value 命令下发的参数值,如:22
*/
//获取设备实际执行的结果
dynamic controlResponse = ConnClientManager.Instance.WriteValueAsync(_connectionConfig.ServerUrl, mainInstruct, value);
//将执行结果对象转换成json字符串
var csResponse = JsonConvert.SerializeObject(controlResponse);
//给当前设置点赋值响应内容,用于北向转发时告知设备实际执行结果
this.equipitem.curSetItem.csResponse = csResponse;
//记录执行传参及响应结果到日志中,便于追溯。
string logMsg = string.Format("命令下发参数,设备号:{0},mainInstruct:{1},minorInstruct:{2},value:{3},下发执行结果:{4}",
this.equipitem.iEquipno, mainInstruct, minorInstruct, value, csResponse);
DataCenter.Write2Log(logMsg, LogLevel.Warn);
//根据设备执行状态,返回状态,对于发布订阅模式可直接返回true,在相关地方做好日志记录即可。
if (controlResponse.Code == 200) return true;
else return false;
}
解析:
SetParm()
方法用以从外部向协议插件内部进行参数设置,也用于向设备发送命令。提供映射
Setparm
表的三个字段设置命令
、设置参数
、设置值
用于提供命令参数和区分发送的命令。返回值
True
表示当前命令设置成功,False
表示设置失败。
备注:
如果插件运行失败,可在宿主程序GWHost1
的控制台输出中看到错误信息,此时根据该输出堆栈信息中针对性采取解决措施。
# GetEvent() 获取设备事件方法
GetEvent() 获取设备事件
定义:
public override bool GetEvent()
使用示例:
/// <summary>
/// 事件发布
/// 如门禁设备的一些通行记录数据。
/// 如果对事件记录实时性有非常高的要求,可以接收到事件后直接转。
/// </summary>
/// <returns></returns>
public override bool GetEvent()
{
//从当前设备连接中获取事件列表
_currentEvents = ConnClientManager.Instance.GetCurrentEvents(_connectionConfig.ServerUrl, this.m_equip_no);
if (_currentEvents == null) return true;
//假设_currentEvents对象每次都是新的数据,不存在旧数据,需开发者自行处理好.
foreach (var eventItem in _currentEvents)
{
//EquipEvent中的事件级别根据当前事件名称定义好的级别。便于北向上报数据时的甄别。
var evt = new EquipEvent(JsonConvert.SerializeObject(eventItem), "可以自定义的消息格式", (MessageLevel)_defaultEventMessageLevel, DateTime.Now);
EquipEventList.Add(evt);
}
_currentEvents = null; //循环完成后,将事件记录置空,避免下次重复产生相同的事件.
return true;
}
解析:
在设备dll框架中,原生就是支持事件获取的。重载基类的
GetEvent()
,就像重载GetYC和GetYX一样。获取到的事件也会出现在实时快照中。如无特殊情况,事件内容和事件联动内容最好保持一致,如果事件联动内容为空,则会自动把事件内容代入。
需要用事件做联动设置可参考以下配置
获取到的事件也会出现在实时快照中。如无特殊情况,事件内容和事件联动内容最好保持一致,如果事件联动内容为空,则会自动把事件内容代入。
然后在联动配置中进行设置,举例如图:
注意:联动类型要填evt,联动的时候会把事件传入到被设置的设备的strLinkageEvent变量,即200号设备的EquipItem对象的strLinkageEvent等于60000号设备的设备号+"#"+事件内容。比如60000号设备的事件联动内容是“王鹏进入公司大门”,则strLinkageEvent=“60000#王鹏进入公司大门”,之所以传入设备号,是便于其它程序根据设备号做不同的内容解析。 事件联动也支持正则表达式的判定,比如某个事件必须包含“张三”才触发联动,则对应的正则表达式为.张三.,保存在iycyx_type字段中,用{}括起来,也就是evt{.张三.}。
正则表达式后面如果用<>包含内容,则<>里面的内容会作为被设置动作的Value值传入,比如正则表达式为.张三.<欢迎张三来访>,那么事件中包含张三的时候,“欢迎张三来访”就会作为设置项的value值传入,这对人员进入联动信息发布的场景就可以直接设置。
更复杂的联动解析需要在程序中去完成,那就根据被联动设备dll中对EquipItem对象的strLinkageEvent值去做处理。要注意的是,项目中的个性化联动不要写在通用的设备dll中,另外写一个中间设备dll去做桥接。
# CEquipBase类
using GWDataCenter.Database;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
namespace GWDataCenter
{
/// <summary>
/// 设备动态库的基类,派生于IEquip
/// </summary>
public class CEquipBase : IEquip
{
int iSta_no, iEquip_no, iRetrytime;
bool b = true;
EquipItem myequipitem;
public List<YcpTableRow> ycprows;
public List<YxpTableRow> yxprows;
public EquipTableRow equiprow;
public string EquipNm;
public SerialPort serialport;
Dictionary<int, object> ycresults;
Dictionary<int, object> yxresults;
List<EquipEvent> equipEventlist;
object runSetParmFlag = (object)false;
object resetFlag = (object)false;
public Dictionary<int, bool> ycpdataflag = new Dictionary<int, bool>();
public Dictionary<int, bool> yxpdataflag = new Dictionary<int, bool>();
public string setparmexecutor = null;
bool bSetCacheData = false;//设备是否缓存标记,缓存意味着实时数据会记录到数据库,作为重启系统的初始数据
bool bFirstGetData = true;//第一次调用GetData
public bool RunSetParmFlag
{
get
{
lock(runSetParmFlag)
{
return (bool)runSetParmFlag;
}
}
set
{
lock(runSetParmFlag)
{
runSetParmFlag = (object)value;
}
}
}
public bool ResetFlag
{
get
{
lock (resetFlag)
{
return (bool)resetFlag;
}
}
set
{
lock (resetFlag)
{
resetFlag = (object)value;
}
}
}
public EquipItem equipitem
{
get
{
return myequipitem;
}
set
{
myequipitem = value;
serialport = myequipitem.serialport;
}
}
public EquipItem Equipitem
{
get
{
return DataCenter.GetEquipItem(iEquip_no);
}
}
public Dictionary<int, object> YCResults
{
get
{
return ycresults;
}
}
public Dictionary<int, object> YXResults
{
get
{
return yxresults;
}
}
public List<EquipEvent> EquipEventList
{
get
{
return equipEventlist;
}
}
public int m_sta_no
{
get
{
return iSta_no;
}
set
{
iSta_no = value;
}
}
public int m_equip_no
{
get
{
return iEquip_no;
}
set
{
iEquip_no = value;
}
}
public int m_retrytime
{
get
{
return iRetrytime;
}
set
{
iRetrytime = value;
}
}
public bool bCanConfirm2NormalState
{
get
{
return Equipitem.bCanConfirm2NormalState;
}
set
{
Equipitem.bCanConfirm2NormalState = value;
}
}
public string SetParmExecutor
{
get
{
return setparmexecutor;
}
set
{
setparmexecutor = value;
}
}
public CEquipBase()
{
ycresults = null;
yxresults = null;
equiprow= null;
ycresults = new Dictionary<int, object>();
yxresults = new Dictionary<int, object>();
equipEventlist = new List<EquipEvent>();
iRetrytime = 3;//默认通讯失败重试次数为3
}
/// <summary>
///
/// 在调用GetData 之前调用
/// </summary>
/// <param name="sta_no"></param>
/// <param name="equip_no"></param>
/// <param name="equip_addr"></param>
/// <returns></returns>
public virtual bool init(EquipItem item)
{
if (item == null)
{
GWDataCenter.DataCenter.Write2Log("CEquipBase调用init(EquipItem item)时,item==null)");
return false;
}
iSta_no = item.iStano;
iEquip_no = item.iEquipno;
m_retrytime = serialport.CommFaultReTryTime;
bSetCacheData = item.bSetCacheData;
// 设备第一次运行时调用;
//或者数据库配置进行了更改后调用
if (b || ResetFlag)
{
myequipitem = item;
// equiprow = db.GetDataRowOfEquip(iSta_no, iEquip_no);
//if (ResetFlag)
// ResetFlag = false;//这个复位在外部执行,而不是在这里,否则会导致dll里面的对应标识失去意义
equiprow = StationItem.db_Eqp.Where(m => m.equip_no == iEquip_no).FirstOrDefault();
if (equiprow == null)
return false;
// ycptable = db.GetDataTableOfYCP(iSta_no, iEquip_no);
List<YcpTableRow> rs = StationItem.db_Ycp.Where(m => m.equip_no == iEquip_no).ToList();
if (rs.Any())
{
ycprows = rs;
}
else
{
ycprows = null;
}
// yxptable = db.GetDataTableOfYXP(iSta_no, iEquip_no);
List<YxpTableRow> rs1 = StationItem.db_Yxp.Where(m => m.equip_no == iEquip_no).ToList();
if (rs1.Any())
{
yxprows = rs1;
}
else
{
yxprows = null;
}
EquipNm = equiprow.equip_nm;
b = false;
OnLoaded();
}
return true;
}
/// <summary>
/// 休眠
/// </summary>
/// <param name="t">休眠时间,单位:毫秒</param>
/// <param name="bBreak">当设置事件发生时,是否中断。在设置函数中休眠,不要中断。</param>
public void Sleep(int t, bool bBreak = true)
{
if (!equipitem.IsRageMode)//非急速模式,正常休眠,避免海量线程场景下急剧的资源消耗(10000线程可能会卡死)
{
Thread.Sleep(t);
return;
}
else//急速模式慎用,
{
if (!bBreak)
{
Thread.Sleep(t);
return;
}
int count = t / 10;
for (int k = 0; k < count; k++)
{
if (RunSetParmFlag)
break;
Thread.Sleep(10);
}
}
//if (!bBreak)
//{
// GWDataCenter.DataCenter.Delay(t);
// return;
//}
//for (int k = 0; k < t; k++)
//{
// if (RunSetParmFlag)
// break;
// GWDataCenter.DataCenter.Delay(1);
//}
}
/// <summary>
/// 获取设备数据
/// </summary>
/// <returns></returns>
public virtual CommunicationState GetData(CEquipBase pEquip)
{
try
{
if (RunSetParmFlag)
{
return CommunicationState.setreturn;
}
if (ycprows != null)
{
foreach (YcpTableRow r in ycprows)
{
///if have setparm, immediately return
if (RunSetParmFlag)
{
return CommunicationState.setreturn;
}
if (bFirstGetData && bSetCacheData)//第一次调用直接取缓存数据
{
SetYCDataFromCache(r.yc_no);
}
else
{
if (pEquip.GetYC(r))
{
}
else
{
return CommunicationState.fail;
}
}
}
}
if (yxprows != null)
{
foreach (YxpTableRow r in yxprows)
{
if (RunSetParmFlag)
{
return CommunicationState.setreturn;
}
if (bFirstGetData && bSetCacheData)//第一次调用直接取缓存数据
{
SetYXDataFromCache(r.yx_no);
}
else
{
if (pEquip.GetYX(r))
{
}
else
{
return CommunicationState.fail;
}
}
}
}
if (!pEquip.GetEvent())
return CommunicationState.fail;
if (bFirstGetData)
bFirstGetData = false;
}
catch (Exception e)
{
GWDataCenter.DataCenter.Write2Log(General.GetExceptionInfo(e));
MessageService.AddMessage(MessageLevel.Debug, General.GetExceptionInfo(e), m_equip_no, false);
return CommunicationState.fail;
}
return CommunicationState.ok;
}
/// <summary>
/// 加载完成处理函数,在第一次加载之后或者系统重置之后调用
/// </summary>
/// <param name="sender"></param>
public virtual bool OnLoaded()
{
return true;
}
public virtual bool GetYC(YcpTableRow r)
{
return false;
}
public virtual bool GetYX(YxpTableRow r)
{
return false;
}
public virtual bool GetEvent()
{
return true;
}
/// <summary>
///
/// </summary>
/// <param name="MainInstruct"></param>
/// <param name="MinorInstruct"></param>
/// <param name="Value"></param>
/// <returns></returns>
public virtual bool SetParm(string MainInstruct, string MinorInstruct, string Value)
{
return false;
}
public virtual bool Confirm2NormalState(string sYcYxType, int iYcYxNo)
{
return false;
}
public virtual bool CloseCommunication()
{
return true;
}
public void YCToPhysic(YcpTableRow r)
{
return;
}
public void YXToPhysic(YxpTableRow r)
{
return;
}
/// <summary>
/// 设置YC点为未取值状态
/// </summary>
/// <param name="ycp"></param>
public void SetYCDataNoRead(IQueryable<YcpTableRow> Rows)
{
if (Rows == null)
return;
foreach (YcpTableRow r in Rows)
{
int iycno = r.yc_no;
if (!ycpdataflag.ContainsKey(iycno))
{
ycpdataflag.Add(iycno, false);
}
else
ycpdataflag[iycno] = false;
}
}
/// <summary>
/// 设置YX点为未取值状态
/// </summary>
/// <param name="yxp"></param>
public void SetYXDataNoRead(IQueryable<YxpTableRow> Rows)
{
if (Rows == null)
return;
foreach (YxpTableRow r in Rows)
{
int iyxno = r.yx_no;
if (!yxpdataflag.ContainsKey(iyxno))
{
yxpdataflag.Add(iyxno, false);
}
else
yxpdataflag[iyxno] = false;
}
}
public void SetYcpTableRowData(YcpTableRow r, string o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, int o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, float o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, double o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<DateTime, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double, double, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double, double, double, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double, double, double, double, double> o)
{
SetYCData(r, o);
}
public void SetYcpTableRowData(YcpTableRow r, ValueTuple<double, double, double, double, double, double, double> o)
{
SetYCData(r, o);
}
public void SetYCData(YcpTableRow r, object o)
{
int iycno = r.yc_no;
lock (YCResults)
{
if (!YCResults.ContainsKey(iycno))
YCResults.Add(iycno, o);
else
YCResults[iycno] = o;
}
}
void SetYCDataFromCache(int iYcNo)
{
lock (YCResults)
{
if (!YCResults.ContainsKey(iYcNo))
YCResults.Add(iYcNo, myequipitem.YCValueCacheDict[iYcNo].Item1);
else
YCResults[iYcNo] = myequipitem.YCValueCacheDict[iYcNo].Item1;
}
}
void SetYXDataFromCache(int iYxNo)
{
lock (YXResults)
{
if (!YXResults.ContainsKey(iYxNo))
YXResults.Add(iYxNo, myequipitem.YXValueCacheDict[iYxNo].Item1);
else
YXResults[iYxNo] = myequipitem.YXValueCacheDict[iYxNo].Item1;
}
}
public object GetYCData(YcpTableRow r)
{
lock (YCResults)
{
int iycno = r.yc_no;
if (YCResults.ContainsKey(iycno))
{
return YCResults[iycno];
}
return null;
}
}
public void SetYxpTableRowData(YxpTableRow r, bool o)
{
SetYXData(r, o);
}
public void SetYxpTableRowData(YxpTableRow r, string o)
{
SetYXData(r, o);
}
public void SetYXData(YxpTableRow r, object o)
{
int iyxno = r.yx_no;
lock (YXResults)
{
if (!YXResults.ContainsKey(iyxno))
YXResults.Add(iyxno, o);
else
YXResults[iyxno] = o;
}
}
public object GetYXData(YxpTableRow r)
{
lock (YXResults)
{
int iyxno = r.yx_no;
if (YXResults.ContainsKey(iyxno))
{
return YXResults[iyxno];
}
return null;
}
}
}
}