Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Atlas Copco Open Protocol Interpreter

0.00/5 (No votes)
18 Aug 2019 1  
This article describes a DLL made to help the community when communicating with Tightening Controllers via Open Protocol.

Summary

  1. Introduction
    1. What is Open Protocol at All?
    2. Why OpenProtocolInterpreter was Created?
  2. Background
  3. The Library
    1. How It Was Built?
    2. Chaining through MIDs
    3. Customization
  4. Using the Library
    1. For a Simple Usage
      1. Translating Packages
      2. Building Packages
    2. Advanced Scenarios
    3. Tips
    4. Tests
  5. Contribute to the Library
  6. Next Steps
  7. Contributors

UPDATED TO VERSION 2.0.0 - 07/18/2018

  1. What's New?
  2. Customization
  3. Performance Comparison
    1. Test 1
    2. Test 2

UPDATED TO VERSION 2.1.0 - 11/08/2018

  1. What's New?

UPDATED TO VERSION 2.1.1 - 01/09/2019

  1. What's New?

UPDATED TO VERSION 2.2.0 - 04/08/2019

  1. What's New?
  2. Performance of Byte Array Parsing
  3. Special Announcement

UPDATED TO VERSION 3.0.0 - 04/08/2019

  1. What's New?
  2. Why a New Version?
  3. Breaking Changes
  4. New Features

Introduction

Before talking about OpenProtocolInterpreter library, let's give a simple review of what is Open Protocol at all.

What is Open Protocol at All?

Open Protocol, as the name says, is a protocol to communicate with Atlas Copco Tightening Controllers or whatever equipment implements that protocol. The most common Tightening Controllers you will hear from Atlas Copco company are PowerFocus4000 and PowerMacs.

Although, some other companies adhered to use the same protocol.

In few words, you will exchange packages via Ethernet or Serial by sending what is specified in every MID (MIDs are packages).

Why OpenProtocolInterpreter was Created?

Many reasons lead me to do that, such as helping the community because there is little about this on the internet.

I've worked for some time developing to integrate these guys to our system and one of the most boring/annoying things was substringing every package and generating some "workable" objects, or at least translate those string packages to something that was easier to work with.

By then, I decided to build a library that would do all those boring things to me, then I should only work with some objects which we should call MIDs.

Background

You might remember or have found this article made by Syed Shanu.

His article is a good introduction to what this article about the Open Protocol is all about, it might give you some knowledge to be aware of what we're talking about or solving in this article.

The Library

In few words, the library does only two things, build packages or translate them.

It's like building your MID by telling the library "Build it as a string, please!", and then it will do everything for you, so you can only send it up through your socket!

Or "Well, I've received a package, translate this for me, please!", and then it will get all those chars inside that string and give you an object to work with.

Check out repository in GitHub.

Only Revision 1 packages were implemented in this library, but yes, I'm looking forward to implementing other revisions.

How It Was Built?

For information and better use, it was basically done over the Chain Of Responsibility design pattern, that will iterate through MIDs and find the right one.

But isn't it "slow" to do that?

For sure it is, if you iterate through EVERY SINGLE MID, but some solution/workaround could be done.

Chaining through MIDs

Open protocol documentation divided MIDs by categories, such as: Job Messages, Tightening Messages, etc.

So the library is divided by that, once we know in which category it is, we only iterate through MIDs from that category.

Customization

Well, when I developed to work with these tightening controllers, I did not need to use every single MID from their docs, I would only implement by 20 or less (and there are a bunch of MIDs).

With this in mind, you can tell the library which MIDs you want to use, or at least, which MIDs you might need to translate when a package arrives.

Example:

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()
});

Using the Library

From now on, it will be a long way to explain this...

For a Simple Usage

Translating Packages

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;

The code above will give you the MID from a package (which is MID 0004), in open protocol docs, this mid has the following data fields:

  1. Failed MID
    • A previous mid you sent that somehow something got wrong with it
  2. Error Code
    • The error code telling you what really got wrong with it

What it has done is translate that big string into an object that you can work better with.

Notice that you can explicitly tell the library which MID is coming, when not explicitly, it would return you a "MID" class, which you would need to discover the mid by looking at its number and casting to the right MID, for example:

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>"
}


Building Packages

Once you want to translate those string packages, you will want to build up some responses too and send to controller, then you just need to do the following:

MID_0032 jobUploadRequest = new MID_0032();
jobUploadRequest.JobID = 1;

string package = jobUploadRequest.buildPackage();
//Generated package => 00220032001         01

You fill up your desired MID (in example MID 0032, Job Upload Request) and call buildPackage method.

Simple as that!

Advanced Scenarios

When I started implementing this library in my project, I realized that some MIDs would come asynchronously and I did not want to make a verification like the one above with lots of if elses or a switch case.

To solve that, I did the following:

Declared a delegate:

protected delegate void ReceivedCommandActionDelegate(ReceivedMIDEventArgs e);

ReceivedMIDEventArgs:

public class ReceivedMIDEventArgs : EventArgs
{
    public MID ReceivedMID { get; set; }
}

Created a method to register all those MID types by delegates:

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;
}

What I've done is register in a dictionary the correspondent delegate for a determined MID, once that is done, we just need to invoke the delegate everytime you face the desired MID:

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);
     }
}

This would call my registered delegate which I'm sure what mid it is.
For example, in my tightening (onTighteningReceived) delegate, we have the following:

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
}

What happens is that everytime a MID_0061 arrives, the onTighteningReceived method will be called, once you've already checked that this MID is 0061, you can explicitly convert it without 'fear'.

By doing this, you don't need all those IFs and Elses!

Tips

General Tip:

Rather register the desired MIDs over using the basic constructor. As shown in tests section below, it's faster and cleaner to use!

Instances:

Instantiate the MIDIdentifier class just once, it takes some time to build all the chain thing, and you don't want to lose time instantiating it everytime a package arrives.

I'm the Controller:

Controller Implementation Tip: Always TRY to register used MIDs, not all Tightening Controllers use every available MID.

I'm the Integrator:

Integrator Implementation Tip: Always DO register used MIDs, I'm pretty sure you won't need all of them to your application.

Tests

I've made two types of tests, with and without customization:

Iterate 1000000 times translating MID_0504 (which is the last from the list), the results were:

//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

Iterate 1000000 times translating MID_0061 (which is one big MID, length 231), the results were:

//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

Version 2.0.0

What's New?

  1. All revisions were implemented in all Mids done in version 1.0.0
  2. Faster parsing
  3. Unit tests to ensure correct parsing
  4. Shorten namespace, therefore cleaner code for you
  5. Library now available on NuGet
  6. Classes renamed to "Mid{mid number}" pattern, not MID_{mid number} anymore
    1. Also MidIdentifier has been renamed to MidInterpreter

Be aware that it's a new version, so you won't be able to just update the DLL and run your project.

Customization

Inside each type/category of Mid (Job/Tightening/Vin/etc.), also there is a specific namespace for them, they have an interface, such as ITighteningMessages, which were used before as an internal interface.

But now, they're public, which means you can develop yours on Mid and inject then via MidInterpreter constructor, done that, the library will consider your mid as part of their set of mids.

Performance Comparison

Test 1

Version 1

I've made two types of tests, with and without customization:

Iterate 1000000 times translating Mid0504 (which is the last from the list), the results were:

//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

Version 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

Test 2

Version 1

Iterate 1000000 times translating Mid0061 (which is one big MID, length 231), the results were:

//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

Version 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

Notably, version 2 is faster than version 1 when we consider mids that have bigger length. Why? Because now, conversion of the properties happens when you execute the properties' get, not after parsing it.

"So it will convert everytime I execute a get?", no, it caches the value after the conversion, so we don't need to convert everytime.

Version 2.1.0

What's New?

  1. New Mids Added:
    1. Mid 1201
    2. Mid 1202
    3. Mid 1203
  2. Primitive value converters added via constructor to Complex value converters
  3. Fix: Set DataField length right after setting its value

Big thanks to Karl D Rose who provided Mids to update on OpenProtocolInterpreter library!

Version 2.1.1

What's New?

  1. New Mids Added:
    1. Mid 0006
    2. Mid 0008

Big thanks again to Karl D Rose who provided the newest Mids to update on OpenProtocolInterpreter library!

 

Version 2.2.0

What's New?

  1. New overload added to Mid class => Parse(byte[] package);
  2. New method added to Mid class => PackBytes();
  3. All revisions are now working with byte[] and ASCII string;
  4. Mid 0061 and 0065 now works for every revision.
    • Because of Strategy Options and other fields which are used as bytes, not ASCII string it wasn't possible to work with other revisions in Parse(string package);
  5. Mid 0061 and 0065 got their Parse(string package) overload obsolete, you can only use it for revisions 1 and 999, otherwise use Parse(byte[] package) overload;
  6. Compatibility remains for all 2.X.X versions;

Also, a big shout for Josef Sobotka who helped in providing fixes!

Performance of Byte Array Parsing

Running the same test as others mentioned above, we've got:

[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

Average time of parsing got a little bit higher, got little similar to Version 1, but we also need to mention that now the ASCII conversion of each field is done by the library.
If performance is needed, my tip is to use the Parse(string package) overload for all mids except 0061 and 0065 when revision different from 1 or 999 which only works with byte array.

Special Announcement

Like many of the programmers that work with automation uses C++ language, I'm looking forward to replicating this library to C++ programming language!

Can't give a date to you about when it will be released, but I'm excited to start this new project! So keep in touch!

Version 3.0.0

What's New?

  1. Reworked on how to instantiate a Mid class when parsing to fix a bug where the parse method updates the same instance instead of creating a new
  2. Pack methods added to MidInterpreter as Pack(Mid mid) and PackBytes(Mid mid)
  3. Added extension methods to MidInterpreter to use the desired Mids/Message parsers
  4. Now you can instantiate MidIntepreter telling if you're the Controller or the Integrator
  5. Upgraded it from .NET Framework 4.5.1 to .NET standard 2.0 to include .NET Core projects

 

Why a New Version?

Basically because of an issue with parsing into the same Mid instance, check the GitHub issue.

Breaking Changes

The only modification you'll need to apply to your project is the way you instantiate MidInterpreter!

Old: 

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()
});

New:

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)
            });

New Features

Now you can configure your MidIntepreter with the new extension methods, it provides more visibility to what you're instantiating. 
Also, you can configure interpreter informing that you are the Integrator or the Controller and the MidInterpreter instance will configure itself to handle every Mid you need (if Controller, it will handle every Integrator messages in Parse method and vice versa).

Contribute to the Library

It's been 2 years since the first release, I've seen some people very grateful for this library and this makes me happy with my work on it.

And of course, you're free to use this library on your projects!

Still Missing Mids

There are still some Mids which are not inside the library, but feel free to fork it on GitHub and add them.

They are:

  • 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. Update sample
  2. Create a Wiki
I've put in a lot of effort into this, mainly when I was working with Tightening Controllers. But now, since I'm not working with them anymore, I'm just doing it as a hobby and because I think that it will surely help people out.
It's important to say that I no longer have access to those controllers which I cannot "simulate" with a real controller.

That's all for now, I might take a little longer to update this library again and I probably won't post a new article about OpenProtocolInterpreter here, so, keep watching my github project!

Contributors

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here