Chapter 1 起步

你是否纠结过:如何避免为各种常见的工业硬件编写“重复”的代码?

Itminus.Tags 是我编写的一套面向工业硬件通信场景的极简通信类库。无论底层通信是OpcUaS7、还是ModbusTcp,或者是串口、甚至可能是MQTTItminus.Tags 都能让你以统一的方式,编写横跨各种工业硬件的通信代码。它有如下几个设计理念:

  • 模块化+可扩展: 各模块之间互相独立,没有耦合。以.AddS7Support()的方式添加S7通信支持,以.AddComScannerSupport()方式添加串口扫码枪通信支持……没发现你想要的硬件支持?你可以自己轻易扩展。
  • 允许组合读写:可以用整体读写的方式来减少IO次数。
  • 刚好够用:只提供刚好够用的功能,简单易学,符合直觉,没有魔法。

这个类库是前些年我过年放假在家时编写的,主要目的是为了降低门槛(足够简单)、提高灵活性,后来经过逐步完善,成为一个免费开源项目,在这个过程中,也慢慢用到了公司的各个商业项目中。

在着手编写这个项目之前,我倾向于使用强类型的方式,在类型实例和一段连续的字节数组之间做递归式地自动映射,这种做法运行效率非常高,而且还有超级友好的类型提示,我曾偏爱这种代码风格接近十年。但不可否认,这也带来了两个坏处:

一是要求编程人员熟知内存布局,比如要求大家知道intC#中会占据4个字节,而在西门子PLC中占据2个字节;再比如,编程者需要知道西门子PLC的字符串会在头部附加两个字节,用来存储总字节数和有效字节数。这一点无疑拔高了编程门槛。

二是点表和程序代码一一对应的,这意味着动态修改比较麻烦。熟悉我的朋友都知道,我一贯强烈反对把修改代码的工作延迟到现场,但是我直到最近才认可这和提供灵活性不完全冲突。

快速上手

依赖包

首先,我们需要添加基础包依赖:

dotnet add package Itminus.Tags

然后,根据项目所需要通信的底层硬件,选择一个或者多个具体的实现包。目前内置的硬件支持包括:

驱动包名备注
西门子S7Itminus.Tags.S7支持
OpcUAItminus.Tags.OpcUa支持
华杰智控IO盒子Itminus.Tags.Hjzk支持
串口Itminus.Tags.ComScanner实验
ModbusTcpItminus.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==)联系我。或许在我有空的时候,大家可以畅聊人生。

由于个人精力有限,我只能优先实现我自己需要的特性。对于一些尚未支持的通信协议,我们鼓励你自己动手写实现。如果你自己不想花太多精力,也可以付费购买其他开发者的时间。