Closed IanRobo75 closed 4 years ago
Thanks Ian - is it possible to create a minimal example, it could be some issue with P/Invoke.
The API call for destroy should be plc_tag_destroy(tagHandle)
-
var tagHandle = plctag.plc_tag_create("protocol=ab_eip&gateway=192.168.0.10&path=1,0&cpu=LGX&elem_size=4&elem_count=1&name=MY_DINT", 1000);
while (plctag.plc_tag_status(tagHandle) == 1)
{
Thread.Sleep(100);
}
var statusBeforeRead = plctag.plc_tag_status(tagHandle);
if (statusBeforeRead != 0)
{
Console.WriteLine($"Something went wrong {statusBeforeRead}");
}
plctag.plc_tag_read(tagHandle, 1000);
while (plctag.plc_tag_status(tagHandle) == 1)
{
Thread.Sleep(100);
}
var statusAfterRead = plctag.plc_tag_status(tagHandle);
if (statusAfterRead != 0)
{
Console.WriteLine($"Something went wrong {statusAfterRead}");
}
var theValue = plctag.plc_tag_get_uint32(tagHandle, 0);
plctag.plc_tag_destroy(tagHandle);
I will be releasing the core library with the above changes for Long Int over the weekend. Hopefully @jkoplo or @timyhac can release a new version of the C# library including that soon after I release the core library.
Version 2.1.10 is released. This should have support for PCCC long integers in the C core library.
0.0.9-alpha of libplctag.NativeImport has been released that includes these binaries.
@IanRobo75 - in some testing I've found that if you set the target platform to x86 then NativeImport will not be able to interop with the x64 binary despite running on an x64 machine. This could be related to your BadImageFormatException.
Using 0.0.9-alpha of libplctag.NativeImport… L9:5 read fine from MicroLogix, so PCCC long integers OK.
Cheers, Ian Ian Robinson Machine Safety & Automation Engineer [swarmIQ_colour 20%]
From: timyhac notifications@github.com Sent: Sunday, July 26, 2020 12:09 PM To: libplctag/libplctag.NET libplctag.NET@noreply.github.com Cc: IanRobo75 ian@acompleteloss.com; Mention mention@noreply.github.com Subject: Re: [libplctag/libplctag.NET] SLC Strings - PLCTAG_ERR_TOO_SMALL (#58)
0.0.9-alpha of libplctag.NativeImport has been released that includes these binaries.
@IanRobo75https://github.com/IanRobo75 - in some testing I've found that if you set the target platform to x86 then NativeImport will not be able to interop with the x64 binary despite running on an x64 machine. This could be related to your BadImageFormatException.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/libplctag/libplctag.NET/issues/58#issuecomment-663918925, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AIY6RY6JM3XZKESXJJTTNT3R5NXYXANCNFSM4PB255VQ.
Thanks for testing!
Will the NativeImport wrapper get the string code soon-ish? And I assume the final C# wrapper would run the tag_get_string_length, then plc_tag_get_string_char for those number of characters and return the complete c# string to the user?
Hi @IanRobo75, the string API is still being discussed. I have not started implementing it yet in the core library.
OK.
In my main app I'm now testing libplctag 0.0.27-alpha10.
in my build folder (...\bin\Debug) I renamed libtag.dll and plctag.dll and rebuilt the app; the new files were created correctly (libplctag.dll dated Friday, and plctag.dll dated at build time).
When I run my program it adds the tag "B3:0" ok, then when adding the tag "F8:0" the program [exe] crashes, error code 0xc0000409 [= STATUS_STACK_BUFFER_OVERRUN].
The offending line... plcReadGroups.Add(new Tag(System.Net.IPAddress.Parse(IPAddress), "", (libplctag.CpuType)CPUType, TagSize(TypeStart), CheckName(groupStart), TIMEOUT, readIndex + 1));
Note: here I'm actually creating and adding the tag to a list, but that's been working fine up to now. So the important tag part, putting the parsed parts in square brackets for display here, is... Tag([192.168.5.151], "", [MicroLogix], 4, "F8:0", 5000, 1)
Hi Ian - that seems to be a .NET exception - is that right?
Does it repeatedly throw the error on adding the same tag (i.e. on consecutive runs)? Can you create that tag by itself in a standalone console application? If you add a different tag at the same point in time (even duplicating another tag should be ok) - does it throw? Are tags being added in parallel/multithreaded? Are you able to create that Tag using NativeImport?
I haven't come across this error before. It seems to be something related to the native binaries.. https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655
Hi @IanRobo75 - I've put together a string marshalling class and would appreciate if you could take a look at the implementation before it is released. https://github.com/libplctag/libplctag.NET/blob/generics-timyhac/src/libplctag/DataTypes/StringMarshaller.cs#L178
You can see that there is different logic for each PLC type. There isn't an implementation for Plc5, Slc500, or Micro800 - but there is for ControlLogix, LogixPccc and MicroLogix.
Any feedback you've got for this would be great.
will test at some point, just really busy currently. Also. aren't PLC5, & SLC500 PCCC anyway so would be the same as MicroLogix?
OK, just getting back onto this.
Frist issue, just reading through StringMarsahhler.cs before I start....
override public int? ElementSize
{
get
{
switch (PlcType)
{
case PlcType.ControlLogix: return 88;
case PlcType.Plc5: return 88;
case PlcType.Slc500: return 25;
case PlcType.LogixPccc: return 84;
case PlcType.Micro800: return 256;
case PlcType.MicroLogix: return 256;
default: throw new NotImplementedException();
}
}
}
PLC5, SLC500, and MicroLogix all use strings of 82 characters. With the length bytes header assumedly they are all 84 bytes long, which makes sense because they all are LogixPCCC really.
I'll modify accordingly for my tests with MicroLogix.
Thanks @IanRobo75 !
So, I've cloned the github code, libplctag/libplctag.NET, (libplctag alpha 13 & libplctag.NativeImport 11 beta).
Doing a new test referencing & using the LibPlcTag class in a forms app, just reading an integer, N7:2, from a MicroLogix...
using System; using System.Windows.Forms; using System.Threading; using libplctag; ......
var myTag = new Tag()
{
Name = "N7:2",
Gateway = "192.168.5.151",
Path = "",
PlcType = PlcType.MicroLogix,
Protocol = Protocol.ab_eip,
ElementSize = 2,
ElementCount = 1
};
//Check that tag gets created properly
while (myTag.GetStatus() == Status.Pending)
Thread.Sleep(100);
if (myTag.GetStatus() != Status.Ok)
throw new LibPlcTagException(myTag.GetStatus()); //<<<<<< GET THE ERROR HERE
myTag.Read();
.....
I get the error 'libplctag.LibPlcTagException: 'ErrorNotFound' [HResult: -2146233088 ]
However, my NativeImport 11 beta test app read data fine from the same PLC....
using libplctag.NativeImport; ...
int elementCount = 1;
int elementSize = int.Parse(tbSize.Text); //=2
string tagname = tbAddress.Text; //="N7:2"
string path = "protocol=ab_eip" +
"&gateway=192.168.5.151" +
"&cpu=micrologix" +
"&elem_size=" + elementSize.ToString() +
"&elem_count =" + elementCount.ToString() +
"&name=" + tagname +
"&debug=4";
//clear result textbox on form
tbResult.Clear();
// check the library version.
if (plctag.plc_tag_check_lib_version(VER_MAJOR, VER_MINOR, VER_PATCH) != (int)STATUS_CODES.PLCTAG_STATUS_OK)
{
Debug.Print("Required compatible library version 2.1.0 not available!\n");
return;
}
//create tag
Int32 tag = plctag.plc_tag_create(path, 5000);
//check could be created
if (tag < 0)
{
Debug.Print("ERROR " + plctag.plc_tag_decode_error(tag) + " : Could not create tag!\n");
tbResult.Text += "ERROR " + plctag.plc_tag_decode_error(tag) + " : Could not create tag!" + Environment.NewLine;
return;
}
//check for error
if (plctag.plc_tag_status(tag) != (int)STATUS_CODES.PLCTAG_STATUS_OK)
{
Debug.Print("Error setting up tag internal state. Error " + plctag.plc_tag_decode_error(tag) + "\n");
plctag.plc_tag_destroy(tag);
return;
}
int rc;
if (elementSize <= 4)
{
rc = plctag.plc_tag_read(tag, 5000);
if (rc != (int)STATUS_CODES.PLCTAG_STATUS_OK)
{
Debug.Print("ERROR: Unable to read the data! Got error code " + rc.ToString() + " : " + plctag.plc_tag_decode_error(rc) + "\n");
plctag.plc_tag_destroy(tag);
return;
}
}
Int32 iData = 0;
switch (elementSize)
{
case 2:
iData = plctag.plc_tag_get_int16(tag, 0);
tbResult.Text += tagname + " data = " + iData.ToString() + Environment.NewLine;
break;
case 4:
iData = plctag.plc_tag_get_int32(tag, 0);
tbResult.Text += tagname + " data = " + iData.ToString() + Environment.NewLine;
break;
default: //string
//rc = plctag.plc_tag_get_string_Length(tag, 0);
break;
}
....
Hi @IanRobo75 - seems like the tag creation isn't working.
What is in master at the moment doesn't have a version number. If you cloned this commit then you'll also need to make a call to Initialize().
What is the attribute string generated by the library? You can set a breakpoint here to find out.
You don't need to provide a blank string if the path is intended to be unset, leaving it as null is OK.
Yes, Initialise sorted it.
OK, testing reading strings...
Relevant parts of my code: //-----------------------------------------------------------------------------
var myTag = new Tag()
{
Name = "ST102:0",
Gateway = "192.168.5.151",
Path = "",
PlcType = PlcType.MicroLogix,
Protocol = Protocol.ab_eip,
ElementSize = 84,
ElementCount = 1
}; //84 = 2 bytes length + 82 chars
myTag.Initialize();
myTag.Read();
StringMarshaller str = new StringMarshaller()
{
PlcType = PlcType.MicroLogix
};
string myResultString = str.DecodeOne(myTag, 0, out int strSize);
//-----------------------------------------------------------------------------
Works great once I changed the following in StringMarsheller.cs, particularly MicroLogixDecode:
//----------------------------------------------------------------------------
const int MAX_CONTROLLOGIX_STRING_LENGTH = 82; //82 characters max in string
const int MAX_LOGIXPCCC_STRING_LENGTH = 82; //82 characters max in string
const int MAX_MICRO800_STRING_LENGTH = 80; //80 characters max in string
override public int? ElementSize
{
get
{
switch (PlcType)
{
case PlcType.Plc5:
case PlcType.Slc500:
case PlcType.LogixPccc:
case PlcType.MicroLogix:
return 84; //2 bytes length + 82 chars
case PlcType.ControlLogix:
return 88; //4 bytes length + 82 chars + 2 padding;
case PlcType.Micro800:
return 82; //2 bytes length + 80 chars
default: throw new NotImplementedException();
}
}
}
string MicroLogixDecode(Tag tag, int offset)
{
var apparentStringLength = (int)tag.GetInt16(offset);
var actualStringLength = Math.Min(apparentStringLength, MAX_LOGIXPCCC_STRING_LENGTH);
var readLength = actualStringLength + (actualStringLength % 2); //read 1 more if odd number
var asciiEncodedString = new byte[actualStringLength];
for (int ii = 0; ii < readLength - 1; ii+=2)
{
asciiEncodedString[ii] = tag.GetUInt8(offset + 2 + ii + 1);
if ((apparentStringLength % 2 == 0) || (ii < (actualStringLength - 1)))
//don't do for last char in odd number string (i.e. don't get the /0)
{
asciiEncodedString[ii + 1] = tag.GetUInt8(offset + 2 + ii);
}
}
return Encoding.ASCII.GetString(asciiEncodedString);
}
//-----------------------------------------------------------------------------
The fixes in MicroLogixDecode are required otherwise you get errors writing 1 byte past the length of asciiEncodedString. Also I've stepped in 2 bytes and dealt with the odd number strings.
I believe MicroLogixDecode should also work for PLC5 & SLC (also PCCC). I assume they are byte-swapped as well.
The size of a Micro800 string (atomic type SHORT_STRING) I got from reading a Kepware Micro800 driver manual. 256 chars did seem excessive and unlikely.
I will set up the C API to use 82 characters for Micro800, but I thought I saw them return a single byte for the count word. I have not had access to a Micro800 for a long time.
Do, you are right. Just opened up a CCW project for a Micro800 and strings are 255 characters, so assumedly the first byte is the length as you thought. Micro800's are terrible anyway, should be avoided.
Thanks for checking. I have not had the "pleasure" of using one in a project.
I remember that at least one PLC type returned strings without any padding. Might have been Micro800?
SLC500 String: Just tested strings with an old SLC5/05 we have. They work exactly the same as for MicroLogix, as expected. In fact, I selected a MicroLogix at the plc type to prove it. I noted however, that SLC's don't have the long Integer type, I'd forgotten that, been a while. That will be the same for PLC5's as well (i.e. strings the same, and no long integers).
using System; using System.Text;
namespace libplctag.DataTypes
{
public class StringPlcMapper : PlcMapperBase
const int MAX_CONTROLLOGIX_STRING_LENGTH = 82;
const int MAX_LOGIXPCCC_STRING_LENGTH = 82;
override public int? ElementSize
{
get
{
switch (PlcType)
{
case PlcType.ControlLogix: return 88;
case PlcType.Plc5: return 84;
case PlcType.Slc500: return 84;
case PlcType.LogixPccc: return 84;
case PlcType.Micro800: return 256; //To be Confirmed
case PlcType.MicroLogix: return 84;
default: throw new NotImplementedException();
}
}
}
override public string Decode(Tag tag, int offset)
{
switch (PlcType)
{
case PlcType.Plc5:
case PlcType.Slc500:
case PlcType.LogixPccc:
case PlcType.MicroLogix:
return LogixPcccDecode(tag, offset);
case PlcType.Micro800:
return Micro800Decode(tag, offset);
case PlcType.ControlLogix:
return ControlLogixDecode(tag, offset);
default: throw new NotImplementedException();
}
}
override public void Encode(Tag tag, int offset, string value)
{
switch (PlcType)
{
case PlcType.Plc5:
case PlcType.Slc500:
case PlcType.LogixPccc:
case PlcType.MicroLogix:
LogixPcccEncode(tag, offset, value); break;
case PlcType.Micro800:
Micro800Encode(tag, offset, value); break;
case PlcType.ControlLogix:
ControlLogixEncode(tag, offset, value); break;
default: break;
}
}
string ControlLogixDecode(Tag tag, int offset)
{
var apparentStringLength = tag.GetInt32(offset);
var actualStringLength = Math.Min(apparentStringLength, MAX_CONTROLLOGIX_STRING_LENGTH);
var asciiEncodedString = new byte[actualStringLength];
for (int ii = 0; ii < actualStringLength; ii++)
{
asciiEncodedString[ii] = tag.GetUInt8(offset + 4 + 2 + ii);
}
return Encoding.ASCII.GetString(asciiEncodedString);
}
void ControlLogixEncode(Tag tag, int offset, string value)
{
if (value.Length > MAX_CONTROLLOGIX_STRING_LENGTH)
throw new ArgumentException("String length exceeds maximum for a tag of type STRING");
var asciiEncodedString = Encoding.ASCII.GetBytes(value);
tag.SetInt16(offset, Convert.ToInt16(value.Length));
for (int ii = 0; ii < asciiEncodedString.Length; ii++)
{
tag.SetUInt8(offset + 4 + 2 + ii, Convert.ToByte(asciiEncodedString[ii]));
}
}
string Micro800Decode(Tag tag, int offset)
{
throw new NotImplementedException();
}
void Micro800Encode(Tag tag, int offset, string value)
{
throw new NotImplementedException();
}
string LogixPcccDecode(Tag tag, int offset)
{
var apparentStringLength = (int)tag.GetInt16(offset);
var actualStringLength = Math.Min(apparentStringLength, MAX_LOGIXPCCC_STRING_LENGTH);
var readLength = actualStringLength + (actualStringLength % 2); //read 1 more if odd number
var asciiEncodedString = new byte[actualStringLength];
for (int ii = 0; ii < readLength - 1; ii+=2)
{
asciiEncodedString[ii] = tag.GetUInt8(offset + 2 + ii + 1);
if ((apparentStringLength % 2 == 0) || (ii < (actualStringLength - 1)))
//don't do for last char in odd number string (i.e. don't get the /0)
{
asciiEncodedString[ii + 1] = tag.GetUInt8(offset + 2 + ii);
}
}
return Encoding.ASCII.GetString(asciiEncodedString);
}
void LogixPcccEncode(Tag tag, int offset, string value)
{
if (value.Length > MAX_LOGIXPCCC_STRING_LENGTH)
throw new ArgumentException("String length exceeds maximum for a tag of type STRING");
var writeLength = value.Length + (value.Length % 2); //add 1 to write length if odd
var asciiEncodedString = Encoding.ASCII.GetBytes(value);
tag.SetInt16(offset, Convert.ToInt16(value.Length));
for (int ii = 0; ii < (writeLength - 1); ii+=2)
{
if ((value.Length % 2 == 0) || (ii < (writeLength - 2)))
//if odd number string then set penultimate char (1 after string end) as /00
{
tag.SetUInt8(offset + 2 + ii, Convert.ToByte(asciiEncodedString[ii + 1]));
}
else
{
tag.SetUInt8(offset + 2 + ii, 0x00);
}
tag.SetUInt8(offset + 2 + ii + 1, Convert.ToByte(asciiEncodedString[ii]));
}
}
}
}
Very nice! Do you want to open a PR for this or do you want me to just copy it from here?
Your timing is good, we are very close to releasing a public beta on nuget that's closer to a first 'stable' release.
you copy it in.
Cheers, Ian Ian Robinson Machine Safety & Automation Engineer [swarmIQ_colour 20%]
From: jkoplo notifications@github.com Sent: Wednesday, September 2, 2020 3:59 PM To: libplctag/libplctag.NET libplctag.NET@noreply.github.com Cc: IanRobo75 ian@acompleteloss.com; Mention mention@noreply.github.com Subject: Re: [libplctag/libplctag.NET] SLC Strings - PLCTAG_ERR_TOO_SMALL (#58)
Very nice! Do you want to open a PR for this or do you want me to just copy it from here?
Your timing is good, we are very close to releasing a public beta on nuget that's closer to a first 'stable' release.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/libplctag/libplctag.NET/issues/58#issuecomment-685277873, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AIY6RYZM4RBJXREDUGSALGDSDW7JRANCNFSM4PB255VQ.
Is reading strings from a MicroLogix (SLC plc type) supported?
I'm reading from and writing to other data types fine, but when reading a string, e.g. ST102:0, I get the error PLCTAG_ERR_TOO_SMALL. Element size is 88, so seems right, confused why the error.