65.9K
CodeProject 正在变化。 阅读更多。
Home

Atlas Copco Open Protocol Interpreter

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2017年6月9日

MIT

10分钟阅读

viewsIcon

75056

本文介绍了一个DLL,旨在帮助社区通过Open Protocol与紧固控制器进行通信。

摘要

  1. 引言
    1. Open Protocol究竟是什么?
    2. 为何创建OpenProtocolInterpreter?
  2. 背景
    1. 它是如何构建的?
    2. 通过MID进行链式处理
    3. 自定义
  3. 使用库
    1. 简单使用
      1. 解析数据包
      2. 构建数据包
    2. 高级场景
    3. 技巧
    4. 测试
  4. 为库贡献力量
  5. 后续步骤
  6. 贡献者

更新至2.0.0版本 - 2018年7月18日

  1. 有什么新功能?
  2. 自定义
  3. 性能比较
    1. 测试1
    2. 测试2

更新至2.1.0版本 - 2018年11月8日

  1. 有什么新功能?

更新至2.1.1版本 - 2019年1月9日

  1. 有什么新功能?

更新至2.2.0版本 - 2019年4月8日

  1. 有什么新功能?
  2. 缺失MID字节数组解析性能
  3. 特别公告

更新至3.0.0版本 - 2019年4月8日

  1. 有什么新功能?
  2. 为什么需要新版本?
  3. 特别公告重大变更
  4. 新功能

引言

在讨论OpenProtocolInterpreter库之前,让我们简单回顾一下Open Protocol究竟是什么。

Open Protocol究竟是什么?

Open Protocol顾名思义,是一个用于与Atlas Copco紧固控制器或实现了该协议的任何设备的通信协议。Atlas Copco公司最常见的紧固控制器是PowerFocus4000PowerMacs

尽管如此,一些其他公司也采用了相同的协议。

简而言之,您将通过以太网或串行通信交换数据包,发送在每个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具有以下数据字段:

  1. 失败的MID
    • 您之前发送的一个MID,但不知何故出了问题
  2. 错误代码
    • 错误代码,告诉您真正出了什么问题

它所做的是将那个大的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 elsesswitch语句。

为了解决这个问题,我做了以下事情:

声明了一个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方法。一旦您已经确认这个MID0061,您就可以明确地转换它而不用“担心”。

通过这样做,您就不需要所有这些IFElse了!

技巧

一般性提示

最好使用基本构造函数注册所需的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. 所有修订版都已在1.0.0版本中实现的所有MID中实现。
  2. 更快的解析
  3. 单元测试以确保正确的解析
  4. 缩短namespace,因此代码对您来说更清晰
  5. 库现已在NuGet上可用
  6. 类名已重命名为“Mid{mid number}”模式,不再是MID_{mid number}
    1. 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

有什么新功能?

  1. 新增Mid
    1. Mid 1201
    2. Mid 1202
    3. Mid 1203
  2. 将原始值转换器通过构造函数添加到复杂值转换器
  3. 修复:设置值后立即设置DataField长度

非常感谢Karl D Rose提供了Mid来更新OpenProtocolInterpreter库!

2.1.1版本

有什么新功能?

  1. 新增Mid
    1. Mid 0006
    2. Mid 0008

再次非常感谢Karl D Rose提供了最新的Mid来更新OpenProtocolInterpreter库!

 

2.2.0版本

有什么新功能?

  1. 在Mid类中添加了新的重载 => Parse(byte[] package);
  2. 在Mid类中添加了新方法 => PackBytes();
  3. 所有修订版现在都支持byte[]和ASCII string
  4. Mid 00610065现在支持所有修订版。
    • 由于Strategy Options和其他字段被用作字节而不是ASCII字符串,因此不可能在Parse(string package);中使用其他修订版。
  5. Mid 00610065Parse(string package) overload 已被弃用,您只能将其用于修订版1和999,否则请使用Parse(byte[] package)重载;
  6. 与所有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,除了00610065(当修订版不是1或999时,它们仅支持字节数组)之外。

特别公告

正如自动化领域许多程序员使用C++语言一样,我期待将这个库移植到C++编程语言!

无法给出发布日期,但我很高兴开始这个新项目!敬请关注!

版本 3.0.0

有什么新功能?

  1. 重构了解析Mid类时如何实例化,以修复parse方法更新同一实例而不是创建新实例的bug。
  2. MidInterpreter中添加了Pack(Mid mid)PackBytes(Mid mid)方法。
  3. MidInterpreter添加了扩展方法,以使用所需的MID/消息解析器。
  4. 现在您可以实例化MidIntepreter,并指定您是Controller还是Integrator
  5. 已从.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

  1. 更新示例
  2. 创建Wiki
我在这方面付出了很多努力,尤其是在我从事紧固控制器工作的时候。但现在,由于我不再与它们打交道,我只是将其作为爱好,并且我认为它肯定会帮助人们。
重要的是要说,我不再能访问那些我无法用真实控制器“模拟”的控制器。

目前就这些了,我可能需要更长的时间才能再次更新这个库,并且可能不会在这里发布关于OpenProtocolInterpreter的新文章,所以,请继续关注我的GitHub项目

贡献者

© . All rights reserved.