Chapter 4 逻辑组件
逻辑组件是我们编写业务代码的地方。
internal class HeartBeatLogicet : LogicetBase
{
public HeartBeatLogicet(IReadOnlyList<ITagChannel> channels, ITagGrp tags, int robot)
: base(channels, tags)
{
this.Robot = robot;
this._heartbeatReq = this.Tags.SelectTag("PLC/信号区-1#/心跳请求");
this._heartbeatAck = this.Tags.SelectTag("MST/信号区-1#/心跳响应");
}
public override int Order => 100;
public int Robot { get; }
private ITag _heartbeatReq;
private ITag _heartbeatAck;
public override bool MatchEntry(ITagGrp entry) => entry.Name == "信号交互";
public override Task ProcessAsync(ITagGrp entry, ITagChannel? thisChannel)
{
this._heartbeatAck.Value = this._heartbeatReq.GetTagValue<bool>();
return Task.CompletedTask;
}
}
组件编写
逻辑组件并不一定要继承自LogicetBase,只需要实现ILogiect即可。LogicetBase本身只是一层非常薄的抽象类,避免让你写规定了的无聊属性。
MatchEntry(ITagGrp entry): 用来表示本逻辑组件能否在指定入口 Loop 中执行。Order: (如果能被执行的话)本逻辑组件在的执行顺序。序号越小,越会被优先执行。ProcessAsync(ITagGrp entry, ITagChannel? thisChannel): 具体的业务逻辑。
逻辑组件的实例一旦被创建,在整个ITagsProject的生命期内都会被重用。
注册
逻辑组件需要注册到测点项目中,才能被执行。
手动注册
proj.Logicets.Add(new HeartBeatLogicet(proj.Channels, proj.Tags, 1));
自动注册
<root>
<!-- ... -->
<!-- 动态指定程序集 -->
<Logicet>Samples.Plugin1/Samples.Plugin1.dll</Logicet>
<Logicet>Samples.Plugin2/Samples.Plugin2.dll</Logicet>
</root>
注意上面的逻辑组件,我们可以定义0~n个逻辑组件(<Logicet/>),只需指定*.dll文件路径,即可自动完成动态加载程序集、自动注册其中的ILogicet的公开(public)且具体(Concrete)的实现。
逻辑组件的动态管理
Itminus.Tags会在 ITagsProject 初始化时,动态加载相关程序集、并构造ILogicet实例;最后在Dispose()时,自动卸载不再需要的相关程序集。
开发者可以注册自己的 ILogicetCreator,来控制如何构造相关Logicet实例,如果未提供这样的自定义实现,Itminus.Tags会回退到默认实现。
组件的运行
还记得我们之前说过的运行机制吗?
针对每个入口(TagGrp),都有一个ITagGrpRunner执行 [扫描->处理 ->输出] loop:
- 扫描: 从底层读取
- 处理: 依次执行各
Logicet的ProcessAsync(entry, thisChannel) - 输出: 向底层刷写
这里我有意模仿了PLC的工作原理,请注意这是一个重大抉择: 我们有意放弃了(也许存在的)性能提升和花活,来换取所有的逻辑组件都是串行轮询的。
注意:由于所有逻辑组件都是规定的顺序串行轮询的,所以,在任何时候,都应该让
ProcessAsync(entry, thisChannel)这个方法尽早结束,否则,串行约束会让后续的逻辑组件长时间得不到执行。