Variable passing in modules and module triggering
Irvine_Kyle
Posts: 67
Here's a little quandry for everyone. What's the best way to extract variables from a module to be used in your main source?
for example: Say we have a variable iARM_MODE_STAT embedded in a module which indicates the arm status of a security system. Now I want to do something like
IF((iARM_MODE_STAT=32) || (iARM_MODE_STAT=64))
{
//do something
}
but the above is in my main source. How would I approach this? Right now what I'd do is rewrite the module to include some kind of feedback on a virtual device so that in the module...
iARM_MODE_STAT=32
ON[vdvMDL_FEEDBACK,32]
Then a seperate variable in main source would be based of the feedback on [vdvMDL_FEEDBACK,32]
Isn't there a better way?
for example: Say we have a variable iARM_MODE_STAT embedded in a module which indicates the arm status of a security system. Now I want to do something like
IF((iARM_MODE_STAT=32) || (iARM_MODE_STAT=64))
{
//do something
}
but the above is in my main source. How would I approach this? Right now what I'd do is rewrite the module to include some kind of feedback on a virtual device so that in the module...
iARM_MODE_STAT=32
ON[vdvMDL_FEEDBACK,32]
Then a seperate variable in main source would be based of the feedback on [vdvMDL_FEEDBACK,32]
Isn't there a better way?
0
Comments
There are several ways to do this with each way having positives and negatives.
1. For a simple variable or array, declare it in the main module and pass it as an argument to the module(s) that need to use it or change it. Modifying it in the module makes the modified value available to the main module as well as any modules that share it.
2. Virtual device channels - as you noted.
3. Virtual device levels - since levels are arbitrary byte, integer, or other data type values, this is a handy way to pass data since it does not require polling to see if a value changes - use a LEVEL_EVENT.
4. Use VARIABLE_TO_STRING or XML_TO_STRING to encode structures or other data types that can not be passed directly to a module and then SEND_STRING the values when they change to a virtual device declared in the main program or another module. In the STRING handler of the DATA_EVENT for the virtual device, convert the string to the variable and use the values. This is tedious and I have seen some cases where the encodings do not work on all embedded data types in a structure but for the most part this is how you marshal data back and forth between modules when structures and large blocks of data are involved.
There are probably other ways as well but these are few techniques I commonly use. Hope it helps.
You could use the LEVEL approach in your example to 'pass' an integer state value between your main program and your module. In this case, there is a simpler solution that could be used if you were sampling the variable in question in Mainline. If you declared something like:
VOLATILE INTEGER nArmState
in the Main Module and then performed a SWITCH statement on the various values of it you are interested in, then simply pass nArmState as an INTEGER argument to any module that needs visibility to the value or that needs to change it. Then, as the value is changed, its value will be reflected in the other locations where it is used. This depends on having code in Mainline that samples the value (a polling approach) and is therefore somewhat inefficient. This is where using an EVENT such as a LEVEL event or a STRING event in the marshalling example excels - it provides notification of a value change and therefore is as efficient as possible. Another option is to pass the variable as an argument and then use an event such as a CHANNEL_EVENT on a virtual device to signal to other modules that the value has changed and to therefore examine it. This eliminates the need to poll the variable for changes in value and is more efficient.
To elaborate on the STRING/XML approach, if you have a lot of data to pass between modules and particularly if the data is in the form of a structure (which can not be an argument to a module), routines exist for converting the structure to either STRING or XML format. You would use VARIABLE_TO_STRING() or VARIABLE_TO_XML() to create a CHAR data type representation of the structure. You can then send the resulting CHAR array (STRING) to any module you wish by doing a SEND_STRING of the converted value. In the receiving module, in your DATA_EVENT handler, you define a STRING handler and when you receive the string, you convert it back to the variable/structure using the inverse function (either STRING or XML depending on how it was converted in the sender). Of course, your variable or structure definition must be identical between the sender and receiver or it will not work. This technique is commonly used for passing large amounts of data typically data contained in structures with mixed data types.
In practice XML is way more verbose than binary (what VARIABLE_TO_STRING does), and there is little or no benefit I can think of to do such exchanges using XML *within* netlinx modules. The only one I can think of is the data is human readable which can help in debugging maybe...
Now if you use that method to store data on the DOC of Netlinx (for persistent storage) then using XML would allow you to upload an updated version (easy to do again since it is human readable).
And if you want to exchange data with a non Netlinx host, then using XML has the important benefit of being decodable/encodable by the receiver, whereas binary may not be (very OS/CPU related).
So I'd use VARIABLE_TO_STRING between modules, maybe VARIABLE_TO_XML for disk saves and certainly for communication with non netlinx servers.
Fred
I tend to use the rule of thumb that if more than, say, 10 values are of external interest for the particular variable, I use a level, otherwise a set of channels.
The problems with levels is that you can't read their value. That is, either you recreate the variable inside main code and use the level_event to update its value, or you are limited on acting upon changes.
A channel, however, can be tested at any time using [Dev, channel] *AND* generates an event. More efficient IMHO.
So in your case Irvine, since you seem to be only interested in 2 values of iARM_MODE_STAT (32 and 64), I'd create two channels to indicate each value. In fact, your code seems to indicate you are really worried about the case where iARM_MODE_STAT is 32 OR 64 so you could create a single channel iARM_MODE_STAT_IS_32_OR_64 and act on that in main code.
Fred
Another thing I do sometimes is I put a string handler in a module that parses a simple command set and sends back a string with a response. But then you have to parse that too in your main program, so I limit it to really simple data.
Dave,
Thanks for your point on obtaining the value of a level. I frequently hear Netlinx programmers talk about the limitation of not being able to read the level - the lack of a GET_LEVEL or similar command. However, with LEVEL_EVENTS providing notification when a value changes, you could get the level from the event if that is the only time you really care about the value. However, as you point out, the CREATE_LEVEL method allows you to always have access to the latest value for a level. Since the Master updates the level value in the specified variable for you automatically, this is certainly more efficient than using a GET_LEVEL command even if one existed. The problem with reading a level value is that you would have to know when to do it (when it changes) and this is exactly what the level event was created to accomplish.
I also agree with you regarding callable functions in Java and the ability to obtain data from other modules. This will be a nice extension to have for the language.
I agree CREATE_LEVEL avoids to add to the level event the line
my_level_variable = Level.value;
But I think you are missing the point. Let's imagine you have a level that indicates the amount of outside light (in lux). Now there are things you'd want to do when that goes below some level, for example, turning on the lights, lowering blinds, etc. For that LEVEL events are perfect.
Now for example in the home theater, blinds are lowered whenever the projector is on, but you'd like to have an intelligent program that only raises the blinds when switching off the projector if there's light outside. Now the EVENT is the projector/theater switch off, and in there I'd like to do something like:
if (there's light outside) raise blinds;
"there's light outside" is level>somevalue. But in this BUTTON_EVENT I can't read the level. So I need a variable to keep track of its changes and cache the last LEVEL_EVENT.
And this is inefficient because whatever device updates the level has the variable (or at least access to the value somehow). NetLinx has it as well because otherwise it would'nt know if the event must be sent or not (if you send twice the same level value, only one event gets sent). And the "user" program must have it again just to read it. So that is 3 copies of the same variable.
If levels where "readable", we could do with a single one (the level).
That's what NetLinx programmers mean when complaining about the level limitation, and I think it is a very valid one.
Fred
Fred,
Let's just agree to disagree on this one. I understand your point regarding the second use of a level value. I see it as more of a variable scope issue than a level issue.
In your example regarding the limitations of not being able to read a level, you could:
1. Declare a LEVEL_EVENT in multiple modules and cache the value locally - this is not the most efficient method of course
2. Use CREATE_LEVEL in multiple modules to have Netlinx track the value for you - this is more efficient than (1) but as you noted, it is less than the perfect case of having the level defined and updated in only place
3. My solution to your problem would be to define the variable in the Main Program, pass that variable as an argument to any routine that requires it, and then define your LEVEL_EVENT in whatever module you prefer. This way, there is only one LEVEL_EVENT handler, one CREATE_LEVEL if you chose that route, and multiple modules are free to share the value of the level. This approach addresses your issue of efficiency - one copy of the variable, one event handler or one variable for Netlinx to update, and the value can be freely shared between modules
Also, as Dave pointed out in an earlier post, the ability to have callable modules will also make this easier to accomplish since a callable module that tracks and maintains the LEVEL value could be called from any module. In this case, you would be creating your own READ_LEVEL or GET_LEVEL function using a callable module.
I do agree with you that at times it has seemed very counterintuitive to not have the ability to read the value of a level but I guess I have spent so much time working around it that I don't think about it much anymore. If we had it as an option, it certainly would make certain coding situations easier to handle but I do think we have adequate constructs to work around the lack of it. Perhaps AMX will add it to Netlinx so we can't debate the topic any longer. :-)
create a virtual device such as vdvMDL_FB or something
then pass this to the module. Within the module you can use a SEND_COMMAND vdvMDL_FB, "'command-do_something_different"
then in main source you create a DATA_EVENT[vdvMDL_FB] and use the COMMAND: handler to parse out the data and act appropriately. So far this has worked great for my instance. Makes things easily readable and unlimited in scope.
So you have to be careful about this with multiple master systems (heh, unless it changed on a recent firmware update and I haven't realized it). My work-around has been to use channels on virtuals as a flag, set the channel with master 2, and when master 1 sees the channel come on, it acts and then turns it off.
I define the virtual device on the other master on the master where i'm sending the command from.
For example in the code for system 1 I would do something like:
vdvREMOTE_DEVICE = 34001:1:2
And then Send_Command vdvREMOTE_DEVICE,"'POWER=1'"
Then on system 2 I would have:
vdvDEVICE = 34001:1:0
and then:
DATA_EVENT[vdvDEVICE]
{
COMMAND:
{
}
}
Would pick off the command.
Maybe I'm just misunterstanding what you want to do.
Dave,
When I first saw your post about problems with SEND_STRING and SEND_COMMAND in master to master mode, I was concerned that if it was broken, it needed to get fixed! Like Alex, I have done a lot of LEVELs and CHANNELs in master to master without problems. I wrote a quick test program this morning to send a command to one master under a button PUSH and for the other master to respond to the command with a string response using some of the data in the command. Thankfully, it worked as expected on various firmware levels certainly back to 117.
Is it possible you did not make a URL entry either programmatically or using Studio? Without the URL entry, the masters certainly won't be able to communicate and it is easy to overlook this detail sometimes.