Click here to Skip to main content
13,796,787 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

15K views
5 bookmarked
Posted 8 Jun 2017
Licenced MIT

The Atlas Copco Open Protocol Interpreter

, 13 Nov 2018
Rate this:
Please Sign up or sign in to vote.
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

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?
  2. Still Missing Mids
  3. Next Steps
  4. Contributors

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 were 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 foward to implement 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 translating 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); //Identifing 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

*Still need to add unit tests to Mids 120x

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

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 0006
  • Mid 0008
  • Mid 0009
  • Mid 0700
  • Mid 0900
  • Mid 0901
  • Mid 2500
  • Mid 2501
  • Mid 2504
  • Mid 2505
  • Mid 2600
  • Mid 2601
  • Mid 2602
  • Mid 2603
  • Mid 2604
  • Mid 2605
  • Mid 2606
  • Mid 9997
  • Mid 9998

  1. Update sample
  2. Add new overload for Parse method which will accept byte[] as parameter
  3. Add more testing for complex Mids/revisions
  4. 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, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Henrique Dal Bello
Software Developer Tech.Fit Apps
Brazil Brazil
No Biography provided

You may also be interested in...

Comments and Discussions

 
NewsMessage Closed Pin
18-Jul-18 23:18
memberMember 1391775118-Jul-18 23:18 
QuestionMID1201 and MID1202 Missing Pin
KarlDRose19-Apr-18 20:38
memberKarlDRose19-Apr-18 20:38 
AnswerRe: MID1201 and MID1202 Missing Pin
Henrique Dal Bello25-Apr-18 9:57
professionalHenrique Dal Bello25-Apr-18 9:57 
QuestionHey Hi Pin
Member 1365210830-Jan-18 5:41
memberMember 1365210830-Jan-18 5:41 
PraiseThanks for this! Pin
WoWBadash9-Nov-17 4:51
professionalWoWBadash9-Nov-17 4:51 
PraiseRe: Thanks for this! Pin
Henrique Dal Bello15-Jan-18 10:03
professionalHenrique Dal Bello15-Jan-18 10:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05 | 2.8.181207.3 | Last Updated 13 Nov 2018
Article Copyright 2017 by Henrique Dal Bello
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid