Atlas Copco Open Protocol Interpreter





5.00/5 (12投票s)
本文介绍了一个DLL,旨在帮助社区通过Open Protocol与紧固控制器进行通信。
摘要
更新至2.0.0版本 - 2018年7月18日
更新至2.1.0版本 - 2018年11月8日
更新至2.1.1版本 - 2019年1月9日
更新至2.2.0版本 - 2019年4月8日
更新至3.0.0版本 - 2019年4月8日
引言
在讨论OpenProtocolInterpreter
库之前,让我们简单回顾一下Open Protocol究竟是什么。
Open Protocol究竟是什么?
Open Protocol顾名思义,是一个用于与Atlas Copco紧固控制器或实现了该协议的任何设备的通信协议。Atlas Copco公司最常见的紧固控制器是PowerFocus4000和PowerMacs。
尽管如此,一些其他公司也采用了相同的协议。
简而言之,您将通过以太网或串行通信交换数据包,发送在每个MID(MID是数据包)中指定的内容。
为何创建OpenProtocolInterpreter?
我这样做有很多原因,例如帮助社区,因为互联网上关于这方面的信息很少。
我花了一些时间开发集成这些设备到我们的系统,其中最无聊/烦人的事情之一就是分割每个数据包并生成一些“可用的”对象,或者至少将那些string
类型的数据包转换为更容易处理的内容。
那时,我决定构建一个库来完成所有这些无聊的工作,这样我只需要处理一些对象,我们称之为MID。
背景
您可能还记得或找到Syed Shanu撰写的这篇文章。
他的文章很好地介绍了本文关于Open Protocol的内容,它可能会为您提供一些知识,让您了解我们在本文中讨论或解决的问题。
库
简而言之,该库只做两件事:构建数据包或解析数据包。
就像告诉库“请将它构建成一个字符串!”来构建您的MID,然后它会为您做一切,您只需将其通过套接字发送出去!
或者“好吧,我收到一个数据包,请帮我解析它!”,然后它会获取string
中的所有char
,并给您一个可以处理的对象。
请在GitHub上查看仓库。
此库仅实现了Revision 1的数据包,但是的,我期待实现其他修订版。
它是如何构建的?
为了信息和更好的使用,它基本上是基于责任链设计模式构建的,该模式将遍历MID并找到正确的MID。
但这会不会“慢”?
当然会,如果您遍历每一个MID,但有一些解决方案/变通方法可以实现。
通过MID进行链式处理
Open Protocol文档将MID按类别划分,例如:Job Messages、Tightening Messages等。
所以库也是按此划分的,一旦我们知道它属于哪个类别,我们只需要遍历该类别下的MID。
自定义
好吧,当我开发与这些紧固控制器一起工作时,我不需要使用他们文档中的每一个MID,我只会实现20个或更少(而MID的数量很多)。
考虑到这一点,您可以告诉库您想使用哪些MID,或者至少,当数据包到达时您可能需要解析哪些MID。
示例
MIDIdentifier identifier = new MIDIdentifier(new MID[]
{
new MID_0001(),
new MID_0002(),
new MID_0003(),
new MID_0004(),
new MID_0061(),
new MID_0500(),
new MID_0501(),
new MID_0502(),
new MID_0503(),
new MID_0504()
});
使用库
从现在开始,解释这些内容将是一段漫长的过程……
简单使用
解析数据包
MIDIdentifier identifier = new MIDIdentifier();
string midPackage = @"00260004001 001802";
var myMid04 = identifier.IdentifyMid<MID_0004>(midPackage);
//MID 0004 is an error mid which contains which MID Failed and its error code
//Int value of the Failed Mid
int myFailedMid = myMid04.FailedMid;
//An enum with Error Code
MID_0004.ErrorCode errorCode = myMid04.ErrorCode;
上面的代码将从数据包中为您提供MID(即MID 0004),在Open Protocol文档中,这个MID具有以下数据字段:
- 失败的MID
- 您之前发送的一个MID,但不知何故出了问题
- 错误代码
- 错误代码,告诉您真正出了什么问题
它所做的是将那个大的string
解析成一个您可以更好地处理的对象。
请注意,您可以明确地告诉库要解析哪个MID,如果不明确,它将返回一个“MID
”类,您需要通过查看其编号来识别MID并转换为正确的MID,例如:
string mid05 = @"00240005001 0018";
var mid = identifier.IdentifyMid(mid05); //Identifying mid package
if (mid.HeaderData.Mid == MID_0005.MID)
{
MID_0005 myMid05 = mid as MID_0005; //Casting to the right mid
Console.WriteLine
($"The accepted mid was <{myMid05.MIDAccepted}">); //"The accepted mid was <15>"
}
构建数据包
当您想解析那些string
类型的数据包时,您也会想要构建一些响应并发送给控制器,那么您只需要这样做:
MID_0032 jobUploadRequest = new MID_0032();
jobUploadRequest.JobID = 1;
string package = jobUploadRequest.buildPackage();
//Generated package => 00220032001 01
填写您想要的MID(例如MID 0032,Job Upload Request)并调用buildPackage
方法。
就这么简单!
高级场景
当我开始在我的项目中实现这个库时,我意识到一些MID会异步到达,而我不想像上面那样进行验证,有很多if elses
或switch
语句。
为了解决这个问题,我做了以下事情:
声明了一个delegate
protected delegate void ReceivedCommandActionDelegate(ReceivedMIDEventArgs e);
ReceivedMIDEventArgs
:
public class ReceivedMIDEventArgs : EventArgs
{
public MID ReceivedMID { get; set; }
}
创建了一个方法来通过delegate
注册所有这些MID类型
protected Dictionary<Type, ReceivedCommandActionDelegate> RegisterOnAsyncReceivedMIDs()
{
var receivedMids = new Dictionary<Type, ReceivedCommandActionDelegate>();
receivedMids.Add(typeof(MID_0005),
new ReceivedCommandActionDelegate(this.onCommandAcceptedReceived));
receivedMids.Add(typeof(MID_0004),
new ReceivedCommandActionDelegate(this.onErrorReceived));
receivedMids.Add(typeof(MID_0071),
new ReceivedCommandActionDelegate(this.onAlarmReceived));
receivedMids.Add(typeof(MID_0061),
new ReceivedCommandActionDelegate(this.onTighteningReceived));
receivedMids.Add(typeof(MID_0035),
new ReceivedCommandActionDelegate(this.onJobInfoReceived));
return receivedMids;
}
我所做的是在dictionary
中注册一个确定的MID
对应的delegate
,一旦完成,我们就只需要在每次遇到所需的MID
时调用delegate
。
protected void onPackageReceived(string message)
{
try
{
var mid = this.DriverManager.IdentifyMidFromPackage(message);
//Get Registered delegate for the MID that was identified
var action = this.onReceivedMID.FirstOrDefault(x => x.Key == mid.GetType());
if (action.Equals(default(KeyValuePair<Type, ReceivedCommandActionDelegate>)))
return; //Stop if there is no delegate registered for the message that arrived
action.Value(new ReceivedMIDEventArgs() { ReceivedMID = mid }); //Call delegate
}
catch (Exception ex)
{
Console.log(ex.Message);
}
}
这将调用我注册的delegate
,我确信它是哪个mid
。
例如,在我的紧固(onTighteningReceived
)委托中,我们有以下内容:
protected void onTighteningReceived(ReceivedMIDEventArgs e)
{
try
{
MID_0061 tighteningMid = e.ReceivedMID as MID_0061; //Converting to the right mid
//This method just send the ack from tightening mid
this.buildAndSendAcknowledge(tighteningMid);
Console.log("TIGHTENING ARRIVED")
}
catch (Exception ex)
{
Console.log(ex.Message);
}
}
protected void buildAndSendAcknowledge(MID mid)
{
this.tcpClient.GetStream().Write
(new MID_0062().buildPackage()); //Send acknowledge to controller
}
每次到达MID_0061
时,都会调用onTighteningReceived
方法。一旦您已经确认这个MID
是0061
,您就可以明确地转换它而不用“担心”。
通过这样做,您就不需要所有这些IF
和Else
了!
技巧
一般性提示最好使用基本构造函数注册所需的MID。如下面的测试部分所示,使用它更快且更清晰!
实例只需实例化MIDIdentifier类一次,它需要一些时间来构建所有链式结构,而且您不希望每次数据包到达时都花费时间实例化它。
我是控制器控制器实现提示:始终尝试注册使用的MID,并非所有紧固控制器都使用所有可用的MID。
我是集成者集成者实现提示:始终执行注册使用的MID,我敢肯定您的应用程序不需要所有这些。
测试
我做了两种类型的测试,有和没有自定义
迭代1000000
次解析MID_0504
(这是列表中的最后一个),结果是:
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MIDIdentifier: 00:00:00.0022074
[CustomMIDs] Total Elapsed: 10.22:23:25.2787898
[CustomMIDs] Average Elapsed Time: 00:00:00.9446052
[AllMIDs] Elapsed time to construct MIDIdentifier: 00:00:01.8860502
[AllMIDs] Total Elapsed: 29.22:39:17.5526723
[AllMIDs] Average Elapsed Time: 00:00:02.5871575
迭代1000000
次解析MID_0061
(这是一个很大的MID
,长度为231
),结果是:
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MIDIdentifier: 00:00:00.0020455
[CustomMIDs] Total Elapsed: 16.09:18:39.8904689
[CustomMIDs] Average Elapsed Time: 00:00:01.4159198
[AllMIDs] Elapsed time to construct MIDIdentifier: 00:00:02.8094992
[AllMIDs] Total Elapsed: 51.14:35:01.2355715
[AllMIDs] Average Elapsed Time: 00:00:04.45890
2.0.0版本
有什么新功能?
- 所有修订版都已在1.0.0版本中实现的所有MID中实现。
- 更快的解析
- 单元测试以确保正确的解析
- 缩短
namespace
,因此代码对您来说更清晰 - 库现已在NuGet上可用
- 类名已重命名为“
Mid
{mid number}”模式,不再是MID_
{mid number}MidIdentifier
也已重命名为MidInterpreter
请注意,这是一个新版本,因此您无法直接更新DLL并运行您的项目。
自定义
在每个Mid
类型/类别(Job/Tightening/Vin/etc.)中,也有一个特定的命名空间用于它们,它们有一个接口,例如ITighteningMessages
,以前用作internal interface
。
但现在它们是public
的,这意味着您可以在Mid
中开发自己的MID并将其通过MidInterpreter
构造函数注入。完成此操作后,该库将将您的MID视为其MID集的一部分。
性能比较
测试1
版本 1
我做了两种类型的测试,有和没有自定义
迭代1000000
次解析Mid0504
(这是列表中的最后一个),结果是:
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MidInterpreter: 00:00:00.0022074
[CustomMIDs] Total Elapsed: 10.22:23:25.2787898
[CustomMIDs] Average Elapsed Time: 00:00:00.9446052
[AllMIDs] Elapsed time to construct MidInterpreter: 00:00:01.8860502
[AllMIDs] Total Elapsed: 29.22:39:17.5526723
[AllMIDs] Average Elapsed Time: 00:00:02.5871575
版本 2
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MidInterpreter: 00:00:00.0035564
[CustomMIDs] Total Elapsed: 10.12:55:10.8787240
[CustomMIDs] Average Elapsed Time: 00:00:00.9105108
[AllMIDs] Elapsed time to construct MidInterpreter: 00:00:01.7611799
[AllMIDs] Total Elapsed: 27.16:49:50.0695101
[AllMIDs] Average Elapsed Time: 00:00:02.3933900
测试2
版本 1
迭代1000000
次解析Mid0061
(这是一个很大的MID
,长度为231
),结果是:
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MidInterpreter: 00:00:00.0020455
[CustomMIDs] Total Elapsed: 16.09:18:39.8904689
[CustomMIDs] Average Elapsed Time: 00:00:01.4159198
[AllMIDs] Elapsed time to construct MidInterpreter: 00:00:02.8094992
[AllMIDs] Total Elapsed: 51.14:35:01.2355715
[AllMIDs] Average Elapsed Time: 00:00:04.45890
版本 2
//Only added 3 types of message categories
[CustomMIDs] Elapsed time to construct MidInterpreter: 00:00:00.0036097
[CustomMIDs] Total Elapsed: 7.06:01:01.9197343
[CustomMIDs] Average Elapsed Time: 00:00:00.6264619
[AllMIDs] Elapsed time to construct MidInterpreter: 00:00:01.2399709
[AllMIDs] Total Elapsed: 37.16:39:59.9146398
[AllMIDs] Average Elapsed Time: 00:00:03.2567999
值得注意的是,当考虑长度较大的mid
时,版本2比版本1更快。为什么?因为现在,属性的转换发生在您执行属性的get
时,而不是在解析之后。
“那么它会在我每次执行get
时转换吗?”不是的,它在转换后会缓存该值,所以我们不必每次都转换。
版本 2.1.0
有什么新功能?
- 新增
Mid
Mid 1201
Mid 1202
Mid 1203
- 将原始值转换器通过构造函数添加到复杂值转换器
- 修复:设置值后立即设置
DataField
长度
非常感谢Karl D Rose提供了Mid
来更新OpenProtocolInterpreter
库!
2.1.1版本
有什么新功能?
- 新增
Mid
Mid 0006
Mid 0008
再次非常感谢Karl D Rose提供了最新的Mid
来更新OpenProtocolInterpreter
库!
2.2.0版本
有什么新功能?
- 在Mid类中添加了新的重载 =>
Parse(byte[] package);
- 在Mid类中添加了新方法 =>
PackBytes();
- 所有修订版现在都支持
byte[]
和ASCIIstring
; Mid 0061
和0065
现在支持所有修订版。- 由于Strategy Options和其他字段被用作字节而不是ASCII字符串,因此不可能在
Parse(string package);
中使用其他修订版。
- 由于Strategy Options和其他字段被用作字节而不是ASCII字符串,因此不可能在
Mid 0061
和0065
的Parse(string package) overload 已被弃用
,您只能将其用于修订版1和999,否则请使用Parse(byte[] package)
重载;- 与所有2.X.X版本保持兼容;
此外,非常感谢Josef Sobotka为提供修复!
字节数组解析的性能
运行与上面提到的其他测试相同的测试,我们得到:
[CustomMIDs] Elapsed time to construct MidInterpreter: 00:00:00.0038251
[CustomMIDs] Total Elapsed: 18.05:07:08.5850770
[CustomMIDs] Average Elapsed Time: 00:00:01.5736285
[AllMIDs] Elapsed time to construct MidInterpreter: 00:00:03.1105645
[AllMIDs] Total Elapsed: 54.08:28:10.0691517
[AllMIDs] Average Elapsed Time: 00:00:04.6960900
平均解析时间略有增加,与版本1相似,但我们也需要提到现在字段的ASCII转换由库完成。
如果需要性能,我的建议是使用Parse(string package)
重载处理所有MID,除了0061
和0065
(当修订版不是1或999时,它们仅支持字节数组)之外。
特别公告
正如自动化领域许多程序员使用C++语言一样,我期待将这个库移植到C++编程语言!
无法给出发布日期,但我很高兴开始这个新项目!敬请关注!
版本 3.0.0
有什么新功能?
- 重构了解析
Mid
类时如何实例化,以修复parse
方法更新同一实例而不是创建新实例的bug。 MidInterpreter
中添加了Pack(Mid mid)
和PackBytes(Mid mid)
方法。- 向
MidInterpreter
添加了扩展方法,以使用所需的MID/消息解析器。 - 现在您可以实例化
MidIntepreter
,并指定您是Controller还是Integrator。 - 已从.NET Framework 4.5.1升级到.NET Standard 2.0,以包含.NET Core项目。
为什么需要新版本?
主要是因为解析到同一个Mid
实例存在问题,请查看GitHub问题。
重大更改
您需要对项目进行的唯一修改是实例化MidInterpreter
的方式!
旧的:
var interpreter = new MidInterpreter(new MID[] { new MID_0001(), new MID_0002(), new MID_0003(), new MID_0004(), new MID_0061(), new MID_0500(), new MID_0501(), new MID_0502(), new MID_0503(), new MID_0504() });
新建
var interpreter = new MidInterpreter().UseAllMessages(new Type[] { typeof(Mid0001), typeof(Mid0002), typeof(Mid0003), typeof(Mid0004), typeof(Mid0061), typeof(Mid0500), typeof(Mid0501), typeof(Mid0502), typeof(Mid0503), typeof(Mid0504) });
新功能
现在您可以使用新的扩展方法配置您的MidIntepreter
,它提供了更多的可见性来了解您正在实例化什么。
此外,您可以通过告知是集成者还是控制器来配置解释器,MidInterpreter
实例将自行配置以处理您需要的所有Mid
(如果是控制器,它将在Parse
方法中处理所有集成者消息,反之亦然)。
为库贡献力量
自首次发布以来已经两年了,我看到一些人对此库非常感激,这让我对我在这方面的努力感到高兴。
当然,您可以在您的项目中使用这个库!
仍缺失的MID
库中仍有一些Mid
未包含,但请随时在GitHub上fork并添加它们。
它们是:
Mid 0009
Mid 0700
Mid 0900
Mid 0901
Mid 2500
Mid 2501
Mid 2505
Mid 2600
Mid 2601
Mid 2602
Mid 2603
Mid 2604
Mid 2605
Mid 2606
Mid 9997
Mid 9998
后续步骤
- 更新示例
- 创建Wiki
我在这方面付出了很多努力,尤其是在我从事紧固控制器工作的时候。但现在,由于我不再与它们打交道,我只是将其作为爱好,并且我认为它肯定会帮助人们。
重要的是要说,我不再能访问那些我无法用真实控制器“模拟”的控制器。
目前就这些了,我可能需要更长的时间才能再次更新这个库,并且可能不会在这里发布关于OpenProtocolInterpreter
的新文章,所以,请继续关注我的GitHub项目!
贡献者
- Karl D Rose
- Josef Sobotka