Creating A Comm Device From Scratch In Sequence


SteveMyres

Recommended Posts

Is it possible to create a new device from scratch at runtime and configure it in a sequence?

 

(I know you can set all the config stuff, but they're going to require a device name, and I don't know if I can create an entirely new device and name it, all in sequence)

 

If so, is it discussed anywhere or can you give a brief overview on it?

Link to comment
Share on other sites

Yes you can, but you can't then use the device in channels.  Its all object oriented.  To create a serial port you do:

 

myPort = new(CCommSerial)

 

and for Ethernet:

 

myPort = new(CCommEthernet)

 

the member variables for those objects are the same as if you had created it in a device (so, Port and Address for example for ethernet).  InitComm() is also a member of these objects.

 

Then to create a device you do:

 

myDevice = new(CCommDevice)

 

then you have to assign the port and protocol:

 

myDevice.PortObject = myPort

myDevice.ProtocolName = "ModbusTCP"

 

The device will get all the functions that go with the protocol.  Any port specific members you have to access through your port object (which of course you can access through the PortObject member of the device object).

 

Its up to you to manage these objects.

 

Note that you don't actually have to create a device object if you are going to script the protocol.  You only need the device object if you are going to use a built in protocol (or one you created as a ddp file).

Link to comment
Share on other sites

I notice that there is a de facto difference between serial and Ethernet when used to poll multiple remote devices.

 

For example, both Modbus RTU and Modbus/TCP are capable of a single master/client connecting to multiple slave/servers.  In DAQ Factory, to set up the Modbus RTU case, you'd create a port based on the COM port, and then a device.  The individual node numbers for the multiple remotes are entered in the channel table.  With Modbus/TCP, what differentiates the remotes is primarily the IP address, which is entered as a port config item.  In serial, the port (and device) refer to the single near end of the wire, where with Ethernet there is a port and device for each remote.

 

So in my case for example, where I'm trying to make the app reconfigurable for a variable number of remotes, the serial paradigm is easier because I create one port and device, then accommodate multiple remotes with their node numbers.  I don't have to create ports and devices dynamically, nor differentiate when referring to them.   That would also allow me to use channels, although I have to generate those dynamically.

 

It seems like it would be more convenient if Ethernet were set up like serial, and presumably the IP address would end up being a channel table entry.  Does this need to be the way it is now?  Is it a Windows limitation?

Link to comment
Share on other sites

Its not a windows limitation, and in fact the new LabJack T7 driver is setup to put the IP address right in the channel table (under 5.90).  The fact that modbus uses an ID (D#) to identify multiple devices is a feature of the protocol (Modbus), not the transport layer (serial, ethernet).  We split the two so that you can apply any protocol to any transport layer.  This is especially effective when using serial to Ethernet converters, as you might have a ModbusRTU device connected over Ethernet through one of these converters.

 

That said, I should add that it is not always true under Modbus that you have only one device on the other side of an IP address.  The simplest example is when using a serial to ethernet converter.  So, let's say you have a multidrop 485 network on ModbusRTU.  You wire that into an RS485 to Ethernet converter, then have DAQFactory communicate with the converter over Ethernet using a RAW port on the converter (so no virtual comm port driver).  Using the RAW port is the preferred method as it doesn't require you to install additional software (which can be a failure point).  In DAQFactory you create an Ethernet port, combine that with the ModbusRTU protocol to create your device.  When you create your channels, you use the D# column to specify which device on the 485 chain you want.  This goes into the Modbus protocol frame itself and has nothing to do with how that data gets to the devices.

Link to comment
Share on other sites

Having trouble with this.  I instantiate both the port and device objects, and they respond "Object(s)" to ?MyPort or ?MyDevice, so far so good.  I set MyDevice.PortObject = MyPort, no errors.  MyDevice.ProtocolName = "ModbusTCP", no problem.

 

So now I need to set specific comm parameters, in this case primarily IP address and port (502 for ModbusTCP).  Neither CCommSerial nor CCommEthernet appears in the manual, so I can't get the member names there.  I try MyPort.toJson() or MyDevice.toJson() and they both just tell me their ClassName, with no listing of member variables.  ?MyDevice.PortObject just returns Object(s), as does ?MyDevice.PortObject.toJson().

 

Where is the list of the member names for those two classes, and why does it not show up using toJson()?

Link to comment
Share on other sites

As I said in the original post:

 

"the member variables for those objects are the same as if you had created it in a device (so, Port and Address for example for ethernet).  InitComm() is also a member of these objects."

 

Dynamic creation of comm objects is an undocumented fringe feature created to solve a particular problem for a particular customer who paid for this.  As such, it doesn't necessarily support all functions, like toJson, which really only works on user objects, because that feature wasn't needed.  Since it doesn't support all expected features its not considered a complete feature and thus remains undocumented, but I still offer it as a solution for advanced users such as yourself who can work through it.  

Link to comment
Share on other sites

Dynamic creation of comm objects is an undocumented fringe feature created to solve a particular problem for a particular customer who paid for this.  As such, it doesn't necessarily support all functions, like toJson, which really only works on user objects, because that feature wasn't needed.  Since it doesn't support all expected features its not considered a complete feature and thus remains undocumented, but I still offer it as a solution for advanced users such as yourself who can work through it. 

 

 

 

OK, that makes sense.  I assumed user objects were just the result of giving the user access to the same mechanism as used by the internal objects, and that if to/from Json worked on one group it would inherently work on the other.   That's fine.

 

 

"the member variables for those objects are the same as if you had created it in a device (so, Port and Address for example for ethernet).

 

 

Well, that's what I thought, so I created corresponding but uniquely named devices using the GUI to browse them and enumerate the member variable and function names.  I can't seem to browse the port object, and browsing the device object, the only member objects I see are FromJson, GetLocalThreadStatus, PortName, PortObject, ProtocolName, StartLocalThread, StopLocalThread, strDevice, and and ToJson.  I can't find Address, which should be in the port object because that's where it's configured, but I can't seem to browse the port object.

Link to comment
Share on other sites

OK, so I assumed the port and address member names were "Port" and "Address" and that port was a number (502 for Modbus/TCP) and that Address was a string, in the form "192.168.99.1", and that those were the only variables I needed to set.

 

DF allowed me to set both of those and returned the expected values when queried.  I executed MyDevice.InitComm() and called one of the member functions which would poll the device.  No errors but no return value array, and no traffic light blinking on my Ethernet bridge.  The port isn't listed for selection in the port monitor window, probably because it's one I created for myself, but I can't confirm the transmit string.  Probably isn't one, because of hte lack of traffic lights on the Ethernet hardware.

 

Identical setup works if I create the device conventionally.

Link to comment
Share on other sites

I think the problem was that I put in a dummy IP, thinking it wouldn't matter, but since it was unreachable, it must have affected the member variables that were visible while browsing.  I started over with a new port and device, even configured with a wrong IP address, but in the local subnet, and the missing variables show up while browsing the device.  I wanted to model starting with a wrong address because I'm thinking of creating dummy port/devices, then filling them out rather than creating dynamically from scratch.

Link to comment
Share on other sites

Your instructions are clear, but I still can't seem to get it talking.  I must be doing something equally simple, wrong.  Here's what I'm running.  No errors, but no polling either.

 

In addition to what you see, I also tried setting PLC1.Address and PLC1.Port, using ? rather than assigning the return to a variable (can't see how this would make any difference because I don't see any Tx's at all), and setting R1.AddressType = 2  (what the example device was set to), with no impact on the results.

global PLC1 = new(CCommEthernet)

global R1 = new(CCommDevice)
R1.PortObject = PLC1
R1.ProtocolName = "ModbusTCP"
R1.Address = "192.168.99.1"
R1.Port = 502
R1.InitComm()


while(1)
   global D1 = Device.R1.ReadHoldingS16(1,1,10)
   delay(3)
   endwhile
Link to comment
Share on other sites

Reread what I originally posted....

 

"Any port specific members you have to access through your port object"

 

You are accessing port specific members through your CCommDevice object.  CCommDevice doesn't have an "Address" or "Port" member, only CCommEthernet does.  Same with the InitComm() function.  Its a member function of CCommEthernet, not CCommDevice.  Only protocol specific functions fall through to CCommDevice because you never create a protocol object.

Link to comment
Share on other sites

Right, but like I posted, that was one of the things I did, with no improvement (except I never applied InitComm to the port).

 

Also note that Address and Port are listed as members in the popup while browsing the device, and that I've been unable so far to browse a port object, so that was why I thought they must be accessed via the device despite being port characteristics.

 

Are you saying that's all you see that's wrong?  If so, I'm confused, because that was one of the things I tried, unless it's that I never tried PLC1.InitComm(),  I'll try that and post my results.

Link to comment
Share on other sites

Alright, tried this, but it still doesn't seem to poll the device.   On initial execution, there are 3 Tx's, each followed by an Rx, all in about a second, then nothing.  In addition to what you see, I also tried putting the PLC1.InitComm() after configuring the device, but still no polling.

global PLC1 = new(CCommEthernet)
PLC1.Address = "192.168.99.1"
PLC1.Port = 502
PLC1.InitComm()

global R1 = new(CCommDevice)
R1.PortObject = PLC1
R1.ProtocolName = "ModbusTCP"

while(1)
   global D1 = Device.R1.ReadHoldingS16(1,1,10)
   delay(3)
   endwhile
Link to comment
Share on other sites

What do you mean by browsing the device?  If you are dynamically creating these objects, the only way you should reference or view them is through script.  Not through the user interface, and not through the Device "object".  You are creating these dynamically in your own variables, not in the lists that DAQFactory maintains for the Quick - Device configuration.

 

So, the problem now is that you are doing:

 

Device.R1.ReadHolding...()

 

when you should just be doing 

 

R1.ReadHolding...()

 

I tested it and the dynamic stuff works fine.  I changed the IP to the AzeoTech website and port to 80, then did R1.write("GET /" + chr(10) + chr(10)) and was then able to do ? r1.read(10) and get a response from the server.

Link to comment
Share on other sites

Oh OK, didn't realize I was no longer supposed to use the Device. notation.  That's probably the only remaining problem.  I definitely wasn't trying to suggest it didn't work.  If it didn't, you'd know by now.

 

As you noted, the variables and functions mirror those from a conventionally created device, so I was "browsing the device" by using a similar GUI-created Device as a list of the member variables and functions.  When you type in the Command/Alert window, the popup appears listing the valid values for the next segment, so I can use that as a list of member variables and functions in the sequence-created objects.

Link to comment
Share on other sites

OK, getting rid of the "Device." notation was the final hurdle.

 

Now this works:

for(private idx = 1, idx < MaxRanchCount, idx++)
   execute("global Controller" + idx + "= new(CCommEthernet)")
   execute("Controller" + idx + ".Port = 502")
   execute("Controller" + idx + ".Timeout = 1000")
   execute("global RanchComm" + idx + " = new(CCommDevice)")
   execute("RanchComm" + idx + ".PortObject = Controller" + idx)
   execute("RanchComm" + idx + ".ProtocolName = 'ModbusTCP'")
   endfor

...but I'd prefer a version with Controller[idx] and RanchComm[idx], both because it'll be easier to reference later and I prefer the resulting notation anyway, but that version didn't seem to work although I believe I've created arrays of objects before.

 

Is it possible?

Link to comment
Share on other sites

Yes, you can store objects in an array.  The trick is that you can't call functions on those objects directly from the array.  You have to copy the reference to the object to a private variable and call from there.

 

So, if you have Controller[idx] and RanchComm[idx] you can't do:

 

Controller[idx].ReadHoldingU16(...)

 

but instead have to do:

 

private ob = controller[idx]

ob.ReadHoldingU16()

 

The issue is the fact that there are two sets of brackets, one [] for the subsetting, and one () for the function.  This is a deficiency in the parser and another reason why OOP is a non-documented feature of DAQFactory.  We have fixed these issues (and made OOP a lot better) in the new script parser, but we are not quite ready to move to it yet.

 

In the meantime, just use the pattern I mentioned.  Note that you don't need that if just referencing member variables (unless they are arrays).  You only need them when you have more than one set of [] or () in a symbol.

Link to comment
Share on other sites

The parser change is actually a major enhancement.  It changes the internals significantly in ways that will make DAQFactory more flexible and more stable.  The script engine will have a lot more oop, much more like javascript (but without javascript's issues) and allow things like anonymous classes, and arrays of mixed types.  Its likely to be the 6.0 release.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.