Summary
- Introduction
- What is Open Protocol at All?
- Why OpenProtocolInterpreter was Created?
- Background
- The Library
- How It Was Built?
- Chaining through MIDs
- Customization
- Using the Library
- For a Simple Usage
- Translating Packages
- Building Packages
- Advanced Scenarios
- Tips
- Tests
- Contribute to the Library
- Next Steps
- Contributors
- What's New?
- Customization
- Performance Comparison
- Test 1
- Test 2
- What's New?
- What's New?
- What's New?
- Performance of Byte Array Parsing
- Special Announcement
- What's New?
- Why a New Version?
- Breaking Changes
- New Features
Before talking about OpenProtocolInterpreter
library, let's give a simple review of 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).
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.
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.
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 char
s 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.
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.
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.
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()
});
From now on, it will be a long way to explain this...
MIDIdentifier identifier = new MIDIdentifier();
string midPackage = @"00260004001 001802";
var myMid04 = identifier.IdentifyMid<MID_0004>(midPackage);
int myFailedMid = myMid04.FailedMid;
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:
- Failed MID
- A previous mid you sent that somehow something got wrong with it
- 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);
if (mid.HeaderData.Mid == MID_0005.MID)
{
MID_0005 myMid05 = mid as MID_0005;
Console.WriteLine
($"The accepted mid was <{myMid05.MIDAccepted}">);
}
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();
You fill up your desired MID (in example MID 0032, Job Upload Request) and call buildPackage
method.
Simple as that!
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 delegate
s:
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);
var action = this.onReceivedMID.FirstOrDefault(x => x.Key == mid.GetType());
if (action.Equals(default(KeyValuePair<Type, ReceivedCommandActionDelegate>)))
return;
action.Value(new ReceivedMIDEventArgs() { ReceivedMID = mid });
}
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;
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());
}
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 IF
s and Else
s!
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.
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
- All revisions were implemented in all Mids done in version 1.0.0
- Faster parsing
- Unit tests to ensure correct parsing
- Shorten
namespace
, therefore cleaner code for you - Library now available on NuGet
- Classes renamed to "
Mid
{mid number}" pattern, not MID_
{mid number} anymore
- 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.
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.
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
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 mid
s 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.
- New
Mid
s Added:
Mid 1201
Mid 1202
Mid 1203
- Primitive value converters added via constructor to Complex value converters
- Fix: Set
DataField
length right after setting its value
Big thanks to Karl D Rose who provided Mid
s to update on OpenProtocolInterpreter
library!
- New
Mid
s Added:
Mid 0006
Mid 0008
Big thanks again to Karl D Rose who provided the newest Mid
s to update on OpenProtocolInterpreter
library!
- New overload added to Mid class =>
Parse(byte[] package);
- New method added to Mid class =>
PackBytes();
- All revisions are now working with
byte[]
and ASCII string
; 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);
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; - Compatibility remains for all 2.X.X versions;
Also, a big shout for Josef Sobotka who helped in providing fixes!
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.
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!
- 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 - Pack methods added to
MidInterpreter
as Pack(Mid mid)
and PackBytes(Mid mid)
- Added extension methods to
MidInterpreter
to use the desired Mids/Message parsers - Now you can instantiate
MidIntepreter
telling if you're the Controller or the Integrator - Upgraded it from .NET Framework 4.5.1 to .NET standard 2.0 to include .NET Core projects
Basically because of an issue with parsing into the same Mid
instance, check the GitHub issue.
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)
});
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).
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!
There are still some Mid
s 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
- Update sample
- 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!