Use a var in component expression


Recommended Posts

Need some help with using/accessing component names and variables in component expression.

Is it possible to use a local var in a component expression?

My own created symbol has an expression like:
MB_Faults[x] + (MB_FaultsAck[x])*2 + MB_Fires[x]*4 + MB_FiresAck[x]*8+ MB_Inhibit[x]*16

Now I have to replace the "x" every time for every symbol with the index of the register, which is a lot of work if the component is used few hundred times.

Would be great to use:
private x = 770
MB_Faults[x] + (MB_FaultsAck[x])*2 + MB_Fires[x]*4 + MB_FiresAck[x]*8+ MB_Inhibit[x]*16

or even better if I can use the component name as reference:
MB_Faults[MyName] + (MB_FaultsAck[MyName])*2 + MB_Fires[MyName]*4 + MB_FiresAck[MyName]*8+ MB_Inhibit[MyName]*16

Then I could load the values from a file.

But is it possible to get the component name in the expression?
 

Link to comment
Share on other sites

I'm not quite sure what you mean.  Do you mean retrieve the name of the component from script inside the component (like a Quick Sequence or Event)?   If so, there is a local variable called "ComponentName" that you can access from inside the component.

Or do you mean access a component by building its name from strings and variables?  In that case, use execute() or evaluate():

execute("component.myComponent" + x + ".strCaption = 'comp #" + x + " caption'")

 

Link to comment
Share on other sites

Thanks for your clear explanation. Think I was looking for ComponentName...

I have created quite some user defined components which animates according bits set in several data registers filled via modbus read sequences.
For each component I place on the screen I have to set the bit numbers (the "x" which refers to the index of the bit in the array) manually now which is very time consuming.

MB_Faults[x] + (MB_FaultsAck[x])*2 + MB_Fires[x]*4 + MB_FiresAck[x]*8+ MB_Inhibit[x]*16

I assume I can replace the "x" by ComponentName in the script inside the component and load a file which assigns the correct bit number to the ComponentName in memory.
Then my script does not have to change if I rename the components correctly.

Some where at startup I will load the "x" values for the components:
ComponentName = Value
L01D01 = 1
L01D02 = 2

etc...

Next part is script within the component for animation:
MB_Faults[ComponentName] + (MB_FaultsAck[ComponentName])*2 + MB_Fires[ComponentName]*4 + MB_FiresAck[ComponentName]*8+ MB_Inhibit[ComponentName]*16


I will test this and if it does not work then I will make a small sample project to explain better what I'm looking for.
The first project was more than 10.000 bits to assign... 
The one I'm working on now has more or less 2500 bits to assign...

Link to comment
Share on other sites

Ok... not sure if I understand this right...

If I create a user defined component, rename it to L01D01, and write in the Quick Sequence:
?"ComponentName: " ComponentName

Then I expect to see the component name printed in the Command/Alert window if I click on the component... nothing printed.
I expected to see: "ComponentName: L01D01"

If I leave out the last word then "ComponentName:" is printed.

Is this the right way to use ComponentName in a script within the component self?
 

Link to comment
Share on other sites

Hmm... I see that this was introduced in version 18.1 while I still use version 17.1.

Can I upgrade WITHOUT any problems with existing CTL documents created with 17.1?
Those applications are already installed and commissioned. Have to know if I can expect any troubles by upgrading to 18.1.

Link to comment
Share on other sites

There should not be problems with that upgrade, but truthfully I'm not sure your approach is the best anyway.  For one thing, you can't index on a string, so:

MB_Faults[ComponentName]

won't work because componentName isn't a number.

Also, I would use array math to combine your entire array of bits into the bytes as you are doing on an individual basis.  So where you are doing:

MB_Faults[x] + (MB_FaultsAck[x])*2 + MB_Fires[x]*4 + MB_FiresAck[x]*8+ MB_Inhibit[x]*16

you can actually just do:

MB_Status = MB_Faults + (MB_FaultsAck)*2 + MB_Fires*4 + MB_FiresAck*8+ MB_Inhibit*16

assuming all 5 of those variables are arrays of the same size and they line up.  Do this in some sequence to keep it up to date, then you can just use:

MB_Status[x]

A better way to do all this is not to use the component name, but rather to use a component property  which you create in the OnLoad event using CreateProperty() or CreateStringProperty().  These functions create a property of the component name that you can edit from script or the docking properties window (View-Properties) and that get saved with the document.  You could have one that holds the index into the MB_Status array.

If I then had a bunch of components to create that are the same except for, say, an index property, I would create the first one with the index property, call it, say MyComponent0, then duplicate it the required number of times.  This will auto-name the components MyComponent1, MyComponent2, etc.  Then I would create a simple sequence to set the "index" property:

for (private i = 0, i < 100, i++) // 100 components, 0-99
   execute("Component.MyComponent" + i + ".index = i")
endfor

You can use a similar script to change other properties of all the controls.

Link to comment
Share on other sites

Thanks for the explanation. I do not get all of it yet, but will definately play with the setup as you advise.

I know that the ComponentName is not a number but I was thinking to use define statement to assign a number to the ComponentName.
For example... 
At startup sequence:
L01D01 = 1

 

Link to comment
Share on other sites

That won't work because ComponentName returns a string.  So if ComponentName is "myComponent" doing:

MB_Faults[componentName]

would be the same as:

MB_Faults["myComponent"]

not

MB_Faults[myComponent]

You would have to do:

MB_Faults[evaluate(myComponent)]

and given the # of elements, you should avoid evaluate() because it is pretty slow.  

 

Link to comment
Share on other sites

Please ignore the previous post... accidently pressed save.
Complete post is below.

 

Thanks for the explanation. I do not get all of it yet, but will definately play with the setup as you advise as soon I get some spare time.

I discovered that the ComponentName is not a number and can not be used like this, but I was thinking to use define statement to assign a number to a tag same as the componentname.
For example... 
At startup sequence:
define L01D01 = 1
define L01D02 = 2
define L08D34 = 6
define L10D80 = 8
define L16D45 = 9

Now I would place some components on the screen and name them:
component 1: L01D01
component 2: L01D02
component 3: L08D34
etc...

The idea was to use in the component expression: 
x= evaluate(ComponentName)
MB_Faults[x] + (MB_FaultsAck[x])*2 + MB_Fires[x]*4 + MB_FiresAck[x]*8+ MB_Inhibit[x]*16

Not sure if this will work. Have to install 18.1 to test this.


I can not use the counting method because the name of the component, is the tag as used in the field and that is not in sequence.
Later I would like to add display of the ComponentName in a popup text box when the mouse pointer is on top of the symbol. That would be very nice...

Edited by Msmax
Link to comment
Share on other sites

11 minutes ago, AzeoTech said:

That won't work because ComponentName returns a string.  So if ComponentName is "myComponent" doing:

MB_Faults[componentName]

would be the same as:

MB_Faults["myComponent"]

not

MB_Faults[myComponent]

You would have to do:

MB_Faults[evaluate(myComponent)]

and given the # of elements, you should avoid evaluate() because it is pretty slow.  

 

OK.. then I have to forget evaluate. System must be fast responsive... don't want a slow system. 
Customer will not be happy!

Link to comment
Share on other sites

Well, you could get around it by using a local variable in the component and call evaluate() from the OnLoad().  Then evaluate() is only used once.   So, something like:

local index = evaluate(componentName)

Then you can use index in the expression.  Index is just a number so will execute quite fast, especially compared to evaluate().

Note, the OnLoad() is only calculated when the component is created (i.e. when first displayed).  You can trigger this event after the fact using component.myComponent.redoLoadEvent()

For speed though I would also make sure and do what I said about calculating a combined result for the entire array:

MB_Status = MB_Faults + (MB_FaultsAck)*2 + MB_Fires*4 + MB_FiresAck*8+ MB_Inhibit*16

and then the expression in component just becomes:

MB_Status[index]

 

Link to comment
Share on other sites

5 minutes ago, AzeoTech said:

Well, you could get around it by using a local variable in the component and call evaluate() from the OnLoad().  Then evaluate() is only used once.   So, something like:

local index = evaluate(componentName)

Then you can use index in the expression.  Index is just a number so will execute quite fast, especially compared to evaluate().

Note, the OnLoad() is only calculated when the component is created (i.e. when first displayed).  You can trigger this event after the fact using component.myComponent.redoLoadEvent()

For speed though I would also make sure and do what I said about calculating a combined result for the entire array:

MB_Status = MB_Faults + (MB_FaultsAck)*2 + MB_Fires*4 + MB_FiresAck*8+ MB_Inhibit*16

and then the expression in component just becomes:

MB_Status[index]

 

Ok. I get the first part... that is clear for me now. Will need 18.1 to test that.

The second part is interesting..
MB_Status = MB_Faults + MB_FaultsAck*2 + MB_Fires*4 + MB_FiresAck*8+ MB_Inhibit*16

This will calculate all in 1 line without using counters, indexes etc.?? 
I can do this already with 17.1, this will save me a lot of time for sure. Only have to change 1 index in stead of 4 for each component.
All arrays are the same size and the indexes do match. 
Thanks!

Link to comment
Share on other sites

Yes, DAQFactory as an interpreted language is not particularly fast.  Script is compiled, but it is compiled into pseudo code and it still has to parse symbol names in real time which, relative to true compiled languages is quite slow.  A single line of script might take 100 microseconds or more to execute, depending on the processor.  But, because it is interpreted means that it can do a lot of things that truly compiled languages won't handle, such as editing parts of the script while the rest of the application is still running.  The big disadvantage of course is that it is slow, especially when doing big loops.  So, if you needed to add 1 to an array of 1000 values you would normally do something like this in most languages:

for (private i = 0, i < numrows(x), i++)
   x = x + 1
endfor

The above of course works in DAQFactory, but on my workstation for example, which is pretty speedy, it still takes 0.115 seconds to do an array of 1000 points.  Fortunately DAQFactory has lots of functionality for working with arrays where the work is done at a low level and is super fast, thus avoiding the necessity to create a loop in script.  In this case, instead of the code above, I could just do:

x = x + 1

and it would do it to the entire array in one single statement.   For that, it takes DAQFactory only 75 microseconds on my machine, less actually because I had to time a loop doing the above 100,000 times to get that number, and that means the number includes the time for the for() loop as well.

Anyhow, the bottom line is to always try and do operations that can apply to the whole array (and most operators / functions support that) rather than loop through.  There are a lot of tricks you can do with boolean math for this sort of thing.  For example, to determine if one or more values of x are > 3, you would just do:

max(x > 3)

In this case, x > 3 returns an array of 1's and 0's, one for each element of x corresponding to whether it is > 3.  Then doing max() just tells us if any of those elements are 1 and returns 1 if so, and 0 if not.

A couple other useful array functions are search() and filter().

I suggest reviewing 4.5-4.12 of the user's guide when you have a chance.

 

Link to comment
Share on other sites

  • 2 years later...

I need a bit of help in retrieving a value using the component name in OnLoad event and evaluate.

The next code is in a sequence which is run at start of the document:
Define L001D001 = 1
Define L001D002 = 2
Define L001D004 = 3


The next code is in the OnLoad event of the component:
?"Onload Event Start"

local Index1 = evaluate(ComponentName)
? "OnLoad Event1: " + Index1

local Index2 = evaluate(L001D001)
? "OnLoad Event2: " + Index2


Upon loading the page I only see the text "Onload Event Start" in the Command/Alert window and nothing else..

If I assign a value directly to the local var Index1 then the second line is also shown with 1 at the end.
local Index1 = 1
? "OnLoad Event1: " + Index1


If I enter ?L001D001 in the command window then the result "1" is shown, which is what I expect.

Also... if I put the next code in the "Action - Quick Sequence" of the component then the result is also what I expect upon a click.
local Index1 = evaluate(ComponentName)
? "OnLoad Event1: " + Index1

So, it seems that the line "local Index1 = evaluate(ComponentName)" does not do what I expect it to do if written in the OnLoad event of a component.

Any idea's how to make this work?

 

Link to comment
Share on other sites

OK, a few things here:

1) What is the component name?  It is probably best not to rely on the name of the component, but instead create a member variable (property) of the component that you set for each component. (using CreateProperty())

2) you cannot predict the order in which DAQFactory will start things.  In other words, you have no way to know that the auto-start Sequence is actually going to run before the page is loaded, if the page is also marked the initial page.  So, it is entirely possible that stuff just hasn't been initialized yet because OnLoad is getting called before your sequence runs.  This is why I always only have a single sequence marked auto-start and then start other sequences from there.  In your case, OnLoad is done when the document loads, so you really can't control when it executes, and it almost certainly is executing before the sequence runs.  I'm not exactly sure what you are trying to achieve (use case) so I can't offer an alternative solution.

Link to comment
Share on other sites

I have more then 2500 components with 4 status level for each... +10.000 bits to assign to have the component animated in the desired colors.
All info is available in an Excel sheet which represents the Modbus registers.
Tag name, description, modbus absolute bit in register

From the Excel sheet I can generate text files which contains all the "defines" and list of the page the component belongs to (for animation on top level screen so user can click on the component on the overview screen and the next level page will show), which I then can load in Daqview.

The idea is to have a universal symbol which I can place on the pages and just rename  to the tag name.
The script inside the component will assign the bit number to a local variable (derived from the componentname and the define statements) and animation will work if the bits are set/reset in the registers. Saves me a lot of work to assign all the bits manually to every component.

I will make a small test document with few components and upload here.

And you are right.. the problem is that the defines are not yet loaded when the OnLoad() event in the component executes.
If I copy the component from one to another page then the script shows the Indexes as coded for the copy component.

Link to comment
Share on other sites

Yeah, OnLoad really was just designed for the createProperties() and AddListener() functions.  You can't reliably do anything else because you can't predict when it runs.  I am fairly certain it runs after sequences are loaded, but before auto-start sequences are run, but its possible that could change in future releases so I wouldn't want to rely on it. 

A better, reliable, solution would be to create a message that your components receive that trigger your code, then, once your auto-start sequence is complete, you could trigger that message, and thus the desired code.  7.16 in the user's guide talks about the OnMessage event.  Multiple components can receive the same message from a single SendMessage call, you just have to do an AddListener() function call (mentioned in 7.15) in your OnLoad() for each component.

The bonus part of that, is that you could, technically, change your Excel sheet, and retrigger the message and the components would update without having to reload the document.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.