Chapter 1 起步
你是否纠结过:如何避免为各种常见的工业硬件编写“重复”的代码?
Itminus.Tags 是我编写的一套面向工业硬件通信场景的极简通信类库。无论底层通信是OpcUa、S7、还是ModbusTcp,或者是串口、甚至可能是MQTT,Itminus.Tags 都能让你以统一的方式,编写横跨各种工业硬件的通信代码。它有如下几个设计理念:
- 模块化+可扩展: 各模块之间互相独立,没有耦合。以
.AddS7Support()的方式添加S7通信支持,以.AddComScannerSupport()方式添加串口扫码枪通信支持……没发现你想要的硬件支持?你可以自己轻易扩展。 - 允许组合读写:可以用整体读写的方式来减少IO次数。
- 刚好够用:只提供刚好够用的功能,简单易学,符合直觉,没有魔法。
这个类库是前些年我过年放假在家时编写的,主要目的是为了降低门槛(足够简单)、提高灵活性,后来经过逐步完善,成为一个免费且开源项目,在这个过程中,也慢慢用到了公司的各个商业项目中。
在着手编写这个项目之前,我倾向于使用强类型的方式,在类型实例和一段连续的字节数组之间做递归式地自动映射,这种做法运行效率非常高,而且还有超级友好的类型提示,我曾偏爱这种代码风格接近十年。但不可否认,这也带来了两个坏处:
一是要求编程人员熟知内存布局,比如要求大家知道
int在C#中会占据4个字节,而在西门子PLC中占据2个字节;再比如,编程者需要知道西门子PLC的字符串会在头部附加两个字节,用来存储总字节数和有效字节数。这一点无疑拔高了编程门槛。二是点表和程序代码一一对应的,这意味着动态修改比较麻烦。熟悉我的朋友都知道,我一贯强烈反对把修改代码的工作延迟到现场,但是我直到最近才认可这和提供灵活性不完全冲突。
快速上手
依赖包
首先,我们需要添加基础包依赖:
dotnet add package Itminus.Tags
然后,根据项目所需要通信的底层硬件,选择一个或者多个具体的实现包。目前内置的硬件支持包括:
| 驱动 | 包名 | 备注 |
|---|---|---|
| 西门子S7 | Itminus.Tags.S7 | 支持 |
| OpcUA | Itminus.Tags.OpcUa | 支持 |
| 华杰智控IO盒子 | Itminus.Tags.Hjzk | 支持 |
| 串口 | Itminus.Tags.ComScanner | 实验 |
| ModbusTcp | Itminus.Tags.ModbusTcp | 实验 |
| 卓兰IO盒子 | Itminus.Tags.ZLan | 实验 |
Itminus.Tags支持的协议和硬件会持续增加。如果暂时没有你想要的,可以自由扩展实现你自己的驱动。
然后向注册相关服务和硬件支持:
services.AddTagsProjectServices(builder =>
{
builder.AddS7Support();
// 增加其它更多的硬件支持,比如:
// builder.AddComScanner();
// ...
});
如果你需要使用
Rx.NET,可以添加扩展包: Itminus.Tags.RxExtensions
项目构建和运行
让我们构建一个项目,并将它跑起来:
using var scope = this._ssf.CreateScope();
var sp = scope.ServiceProvider;
// 提供项目运行目录,其中有通信点表和可能用到的插件
var dir ="D:/manufacture/pl01/";
// 构建项目
var factory = sp.GetRequiredService<ITagsProjectFactory>();
using var proj = factory.Create(dir!, root: null);
// ...可选:hooks
// 运行项目
await proj.RunAsync(ct);
这里目录下通常会有一份测点索引(index.xml),内容类似于:
<?xml version="1.0" encoding="utf-8"?>
<root>
<!-- 通道,可以配置多个-->
<Channel name="S7-1" driver="S7" >
<IpAddr>localhost</IpAddr>
<Rack>0</Rack>
<Slot>1</Slot>
</Channel>
<!-- 测点配置,可以配置多个 -->
<TagGrp name="CallMat" isEntry="true" isEnabled="true" channel="S7-1" scanInterval="10">
<TagGrp name="1#">
<TagGrp name="songliao">
<TagCbnt name="PLC" address="DB1990.0">
<Tag name="叫料请求" address="$$10.0" type="BIT" access="RO"></Tag>
<Tag name="进站响应" address="$$10.1" type="BIT" access="RO"></Tag>
<Tag name="超时响应" address="$$10.2" type="BIT" access="RO"></Tag>
<Tag name="叫料序号" address="$$12" type="INT32" access="RO" endian="BigEndian"></Tag>
</TagCbnt>
<TagCbnt name="MST" address="DB1991.0" access="RW">
<Tag name="叫料应答" address="$$10.0" type="BIT"></Tag>
<Tag name="进站请求" address="$$10.1" type="BIT"></Tag>
<Tag name="超时请求" address="$$10.2" type="BIT"></Tag>
<Tag name="状态" address="$$12" type="INT16" endian="BigEndian"></Tag>
<Tag name="NG原因" address="$$14" type="INT16" endian="BigEndian"></Tag>
<Tag name="叫料编号" address="$$16" type="STR" maxlen="24"></Tag>
</TagCbnt>
</TagGrp>
<TagGrp name="paomian">
<TagCbnt name="PLC" address="DB1990.0">
<Tag name="叫料请求" address="$$30.0" type="BIT" access="RO"></Tag>
<Tag name="进站响应" address="$$30.1" type="BIT" access="RO"></Tag>
<Tag name="超时响应" address="$$30.2" type="BIT" access="RO"></Tag>
<Tag name="叫料序号" address="$$32" type="INT32" access="RO" endian="BigEndian"></Tag>
</TagCbnt>
<TagCbnt name="MST" address="DB1991.0" access="RW">
<Tag name="叫料应答" address="$$60.0" type="BIT"></Tag>
<Tag name="进站请求" address="$$60.1" type="BIT"></Tag>
<Tag name="超时请求" address="$$60.2" type="BIT"></Tag>
<Tag name="状态" address="$$62" type="INT16" endian="BigEndian"></Tag>
<Tag name="NG原因" address="$$64" type="INT16" endian="BigEndian"></Tag>
<Tag name="叫料编号" address="$$66" type="STR" maxlen="24"></Tag>
</TagCbnt>
</TagGrp>
</TagGrp>
</TagGrp>
<!-- 逻辑配置,可以配置0~N个 -->
<!--<Logicet>Samples.Plugin1.dll</Logicet>-->
</root>
除了使用约定的index.xml,我们也可以用程序的方式自己构建出一个XML节点,然后传递给factory.Create(dir!, root: null)这里的root参数,这样项目工厂就不会去搜寻index.xml文件了。
备注
如果我的代码能帮到你,我们希望你也能在力所能及的情况下,回馈开源社区。程序员的生涯也许是非常短暂的,但是一旦你的作品开始发出微弱的光芒,就能轻松照亮无数的后来者快步前行。
如果需要和我探讨编程技术,可以通过(aXRtaW51c0Bob3RtYWlsLmNvbQ==)联系我。或许在我有空的时候,大家可以畅聊人生。
由于个人精力有限,我只能优先实现我自己需要的特性。对于一些尚未支持的通信协议,我们鼓励你自己动手写实现。如果你自己不想花太多精力,也可以付费购买其他开发者的时间。