Dynamic variables
Hi there,
First post and my first 'big' project.
Let me explain:
I must program a exhibition place where 7 different MIO CLASSIC TP's will control 7 different stands.
Each stand has one or two LCD screens where some video will be displayed.
The question:
I'm want to make some functions to reduce the code.
For example:
In 'fnPOWER" I want to do the following:
What I want is a dynamic variable, so that in GET_LAST I can call every MIO_?_POWER in the program suplyed by the function call.
Can anybody suply me with some tips/hints/tricks?
Thanks.
First post and my first 'big' project.
Let me explain:
I must program a exhibition place where 7 different MIO CLASSIC TP's will control 7 different stands.
Each stand has one or two LCD screens where some video will be displayed.
The question:
I'm want to make some functions to reduce the code.
For example:
BUTTON_EVENT[MIO_1_POWER] // VOLUME UP
{
PUSH:
{
fnPOWER(1,dvPHILIPS_BDL4231_1);
}
}
In 'fnPOWER" I want to do the following:
DEFINE_FUNCTION fnPOWER(INTEGER TP, DEV DEVICE) // CONTROL THE POWER OFF THE LCD SCREEN
{
LOCAL_VAR POWER
POWER = GET_LAST("'MIO_',TP,'_POWER'")
SWITCH(POWER)
{
CASE 0:
{
TEST = 'Hi I'm on'
}
CASE 1:
{
TEST = 'Hmm, I'm turned off'
}
}
}
What I want is a dynamic variable, so that in GET_LAST I can call every MIO_?_POWER in the program suplyed by the function call.
Can anybody suply me with some tips/hints/tricks?
Thanks.
0
Comments
GET_LAST() only works in the button event. You've got use GET_LAST in the button event, and pass that result into the function as a parameter. But I'm not sure whether you're trying to trap the POWER button event from multiple panels, or trying to control multiple devices with the power button.
The GET_LAST is really an indexing function. If you have a group of buttons defined:
integer MIO_1_POWER = 27 //aribitrary button channel number defined in panel integer MIO_2_POWER = 33 integer MIO_3_POWER = 41 integer MIO_4_POWER = 52 integer MIO_5_POWER = 54 integer MIO_6_POWER = 67 integer MIO_7_POWER = 92 integer button_set_power[] = { MIO_1_POWER, //INDEX 1 MIO_2_POWER, //INDEX 2 MIO_3_POWER, //INDEX 3 MIO_4_POWER, //INDEX 4 MIO_5_POWER, //INDEX 5 MIO_6_POWER, //INDEX 6 MIO_7_POWER //INDEX 7 } BUTTON_EVENT[some_tp,button_set_power] { PUSH: { fnPOWERCONTROL(GET_LAST(button_set_power)) } } DEFINE_FUNCTION fnPOWER(INTEGER button_index) { // CONTROL POWER //Still not sure what you're trying to do here... //Does each button represent power off or on specifically, or just power toggle? //Note that one you start with indexing, you need to essentially index everything //Like creating a DEV array of all the touchpanels //And another DEV array of all the devices it actually needs to control //And of course arrays of the buttons //And they all have to match one another }Anyway, that should at least get you started with using GET_LAST() to dynamically get an index number that you can then use to control a device ( PULSE all_devices_array[button_index],some_data) assuming you have a matching device array.
Small correction. get_last does work anywhere, but it is recommended to be used in the button event only.
Paul
How is it that get_last can be made to work improperly? Can it return an array index which does not correspond to the most recent? For example, if I have a function and in that function I want to identify the touch panel (included in a device array) which most recently had a button pressed, wouldn't get_last be appropriate to use in the function for that purpose? I believe that I've successfully used get_last in that manner.
Jeff
Okay . . . to argue back . . . .
all I read was blah-blah-blah-blah-blah-blah.
"If you can't answer a man's argument, all is not lost; you can still call him vile names." -Elbert Hubbard.
See, that's your problem. You shouldn't be reading the words, you should taste the words and see the sounds, and hear your food. I guess I need to explain everything.
Jeff
P.S.
"If you ignore all of the rules of logic, the only way to loose an argument is to stop arguing." - Me
I have wondered about this but haven't had time to test it. Since Netlinx is single threaded wouldn't the processor have to have finished the thread in which get_last was called before processing the next button event? If so, get_last will work anywhere in code and always return the correct value.
http://amxforums.com/showthread.php?t=1670&highlight=get_last+level+events
Joe Hebert wrote:
I can't say I've tried it so I don't know how valid this is, Joe, what's the verdict?
For what it?s worth, I?m on the side of the fence that only uses GET_LAST inside an event trigger because to me that?s where it makes the most sense (and that?s the way I was trained.)
Here?s a real life example. Most jobs I work on are route any source to any zone (and I?m sure there are many out there that do the same thing) so I always have a general function called:
DEFINE_FUNCTION fnRouteSource (integer source, integer zone) { }That function can be called from several different parts in code; from a touch panel button push, keypad button push, IR input from a handheld remote, a timer of some sort, a garage door opening, blah blah blah, so using GET_LAST inside of the function wouldn?t do me any good. What I normally do is something like this:
BUTTON_EVENT[dvTPs,nSourceButtons] { PUSH: { INTEGER tp //the TP that triggered the event INTEGER zone //the current zone the TP is controlling INTEGER src //the source that needs to get routed tp = GET_LAST(dvTPs) zone = sTPInfo[tp].CurrentZone src = nSourceMap[GET_LAST(nSourceButtons)] fnRouteSource(src,zone) } }I then have the same type of events for an array of remotes and buttons, or an array of keypads and buttons, and so on. If I need the value of GET_LAST I grab it when the event fires, do what I want with it, and then pass the data as a parameter(s) to a function.
I?d like to hear from those sitting on the other side of the GET_LAST fence and ask why they prefer using GET_LAST outside an event. Maybe I?m missing something I never thought of.
I have only used it inside an event, but it would be nice to be able to use it elsewhere if need be. I have a feeling it was rather naively written though, meaning that its just a pointer to the top of the button queue. It likely isn't reentrant, so you can probably get situations like this:
TP1 button push gets queued
TP2 button push gets queued
Button event handler for TP1 gets executed and calls get_last, but now the pointer has moved to point to TP2 so the wrong index is returned.
Even if you only use it in a button event, I doubt this is any guarantee of correctness. Will get_last return the correct index for this button event?
define_variable integer ui define_event button_event[dvTPs, 1] { push: { wait random_number(100) ui = get_last(dvTPs) } }Paul
So to answer your question, yes GET_LAST will return correctly. But not necessarily the way you would want it to. Computers are stupid like that, they follow directions.
Can you give an example of where? Just curious.
DEFINE_FUNCTION fnTEST(INTEGER nVAR) { SEND_STRING 0, "ITOA(nVAR),' was send to the controller'"; } ... BUTTON_EVENT[dvTP,1] { PUSH: { FOR(nLOOP=1;nLOOP<=5;nLOOP++) WAIT 5 fnTEST(nLOOP); } }I can pretty much guarantee without testing that 5 will be sent to the controller. I didn't write this code in NS, so the formatting may be off, and there may be a syntax error if you try to compile - but I say give this a shot. WAITs are tricky at first - because you have to remember their limitations, just like the HOLD event. You cannot expect GET_LAST to return the results you'd expect in a PUSH or RELEASE event in a HOLD event, considering a HOLD is just a PUSH with a WAIT_UNTIL (<insert time here>) and a while loop if REPEAT is specified.But . . . I question why even use a GET_LAST outside of an event? I really can't comprehend why anyone would do such a thing. Why not just pass a parameter if you desire its use in a function / call?
If I'm not making sense, it's because of of the late night Taco Bell run I just had. Their food is poisonous.
I?m not going anywhere tonight, there?s a big red parallelogram heading our way. I hope it?s not the same storm that just wiped out Cedar Rapids, Iowa today. What?s with this frickin? weather? Man.
You doubt with much certainty? Is that even possible?
All the waits would get cancelled except the last one so 5 gets sent, but I don't get the point you are trying to make here.
What about if you want to play different sounds on the push and release of different groups of buttons? To do this the way you suggest all the button events would have to look like this:
button_event[dvTPs, 1] { push: { stack_var integer tp, btn tp = get_last(dvTPs) btn = get_last(iBtns) pressSound(tp) // more button processing here } release: { stack_var integer tp, btn tp = get_last(dvTPs) btn = get_last(iBtns) releaseSound(tp) // more button processing here } } button_event[dvTPs, 2] { push: { stack_var integer tp, btn tp = get_last(dvTPs) btn = get_last(iBtns) pressSound(tp) // more button processing here } release: { stack_var integer tp, btn tp = get_last(dvTPs) btn = get_last(iBtns) releaseSound(tp) // more button processing here } } define_function pressSound(integer tp) { send_command dvTPs[tp], "'@SOU-press.mp3'" } define_function releaseSound(integer tp) { send_command dvTPs[tp], "'@SOU-release.mp3'" }Personally I would rather write it like this:
button_event[dvTPs, 1] { push: { pressSound() // more button processing here } release: { releaseSound() // more button processing here } } button_event[dvTPs, 2] { push: { pressSound() // more button processing here } release: { releaseSound() // more button processing here } } define_function pressSound() { stack_var integer tp tp = get_last(dvTPs) send_command dvTPs[tp], "'@SOU-press.mp3'" } define_function releaseSound() { stack_var integer tp tp = get_last(dvTPs) send_command dvTPs[tp], "'@SOU-release.mp3'" }Well, to answer a question in the begining:
There is one controller:
5 MIO Classic panels
5 LCD screens.
Every panel must control 1 LCD screen. I must control soundlevel and the power of the screen.
The point is that the code is pretty the same each time and the only difference is a digit that change in a line.
To get to the point: how can I setup my code so that I can reduce my programming code.
That's why I was thinking about a dynamicly variable in a function
As far as embedded data object like DATA.DEVICE or BUTTON.INPUT.CHANNEL they act like stack_vars and only hold a value during the duration of the event code is executing. Put it in a wait and the value is zero when the wait executes.
jjames wrote: Even if you want to pass it to a function if a wait is involved anywhere you need to place the GET_LAST value in a local var to hold the value or pass the value. Of course this too can change if some one pushes a button that triggers the same code block while the wait is pending.
Petar wrote: scroll down to Joe's code and use the GET_LAST to determine the index of you TP in your TP array and then the BUTTON index of your button array. That way one block of code can handle all TPs and obviously all buttons. You can then use a SWITCH CASE or SELECT ACTIVE to add things that may be TP (zone) specific if you choose.
Thanks! Because of the amount of reply's I didn't correctly read that part
Only one thing: what kind of variable is 'nSourceButtons' in this case?
This is what I meant with the WAITs; you'd simply do this:
BUTTON_EVENT[dv_TP,nSRC_BTNS] { PUSH: { STACK_VAR INTEGER nPNL; nPNL = GET_LAST(dv_TP) SWITCH (nPNL): { CASE 1:WAIT 20 fnDO_SOMETHING(1); CASE 2:WAIT 20 fnDO_SOMETHING(2); CASE 3:WAIT 20 fnDO_SOMETHING(3); CASE 4:WAIT 20 fnDO_SOMETHING(4); CASE 5:WAIT 20 fnDO_SOMETHING(5); } } }This way, if touch panels 1, 3, and 4 press something all within two seconds, they execute properly. And look, I'm using a STACK_VAR. *Gasp!* I believe this is the recommended way of calling something with the use a WAIT - embellish it all you want (i.e. cancel_waits, wait_untils, etc.), but you get the jist. The idea is to evaluate the GET_LAST immediately, then do your wait. Then GET_LAST will work like a charm with your WAITs.
I'm one of those pessimists that think it's the end of the world, don't get me started. Haha!
If as you say that all the WAITs are cancelled except for the last one then if you have the following code:
nFlash wouldn?t ever toggle its value since every pass though mainline would cancel the WAIT. However, that?s not the case. The WAIT is put into the queue on the first pass and all subsequent passes are ignored until the WAIT expires. If you watch nFlash you?ll see it toggle once a second.
DEFINE_CONSTANT INTEGER nSourceButtons[] = {1,2,3,4,5}Just a technicality... but you could also make it a variable as well.
VOLATILE INTEGER nSourceButtons[] = {1,2,3,4,5}
What you need is to create a DEV array of all your panels, and a matching DEV array of your actual devices.
DEFINE_DEVICE dvLCD1 = 5001:1:0 dvLCD2 = 5001:2:0 dvLCD3 = 5001:3:0 dvLCD4 = 5001:4:0 dvLCD5 = 5001:5:0 dvTP1 = 10001:1:0 dvTP2 = 10002:1:0 dvTP3 = 10003:1:0 dvTP4 = 10004:1:0 dvTP5 = 10005:1:0 DEFINE_CONSTANT button_power_on = 12 //totally arbitrary button assignments button_power_off = 13 button_vol_up = 14 button_vol_down = 15 DEFINE_VARIABLE dev lcd_all = { dvLCD1, //Array index of this device is 1 dvLCD2, //Index 2 dvLCD3, //and so on, I hope you get the indexing concept! dvLCD4, dvLCD5 } dev tp_all = { dvTP1, //Array index of this TP is 1 dvTP2, //and so on with the indexing... dvTP3, //so that you have matching indexes dvTP4, //for the panels and the devices! dvTP5 } //FUNCTIONS AND CALLS DEFINE_FUNCTION fnPOWER_ON(INTEGER this_index) { SEND_STRING lcd_all[this_index],"'k a 1 1',$0D" //this would power up an LG display (or not) :-) } //add 3 more functions for power off and volume up and down DEFINE_EVENTS BUTTON_EVENT[tp_all,button_power_off] { //all panels use the same channel number for power off button push: { fnPOWER_ON(GET_LAST(tp_all)) //This sends the panel INDEX number to the function } } //add 3 more button events for power off and volume up and downSo, in this example, you're matching the panel index to the device index. You get the index of the panel with the GET_LAST() in your button_event, and pass that off to the function, which will identify which device in your LCD device array you want to send the string to.
You only need one button_event trigger for each distinct action for all devices and all panels, and one function to handle the communications for all devices. But it comes at a cost of lots of array definitions! For really small projects, it might not be worth it. I'll leave it to you to decide whether I've "saved programming code" in this example or not. But even for just 4 distinct events for each of 5 devices I think indexing does reduce the overall size of the program.
Once you've got the hang of this example, the next thing to try is indexing your button set so you can have just a single BUTTON_EVENT that handles all of the actions you need. And whittling down the functions to just one function (hint: match an indexed set of commands to your index of actions, pass the button index, and viola! one function will send the correct command for each action).
As an aside note, I'm really sorry to have started a huge discussion about the particulars of GET_LAST when my intent was only to help Petar wrap his head around indexing as a way to reduce code. Yes, it does return some value no matter where you put it in the code, so it does "work" anywhere. Of course I was implying "work reliably" - in the sense that if you limit the use of GET_LAST to actual events (button event, etc.), it will always return the value you expect.
If you use GET_LAST() in functions and waits, you will likely get what you expect when you're testing the system, but when you put it in production and really have several people pounding away at several panels at the same time, the system is likely to do some unexpected things.
BUTTON_EVENT[dvTPs,nSourceButtons] { PUSH: { INTEGER tp //the TP that triggered the event INTEGER zone //the current zone the TP is controlling INTEGER src //the source that needs to get routed tp = GET_LAST(dvTPs) zone = sTPInfo[tp].CurrentZone src = nSourceMap[GET_LAST(nSourceButtons)] fnRouteSource(src,zone) }and make it shorter.
BUTTON_EVENT[dvTPs,nSourceButtons] { PUSH: { fnRouteSource(nSourceMap[GET_LAST(nSourceButtons)],sTPInfo[GET_LAST(dvTPs)].CurrentZone) }Of course I would go with the 1st version (Joe's) since it's more readable. Less code is of course less code but not necassarily better code or code that is easier to follow six months down the road.
fogled@mizzou wrote:
That's what makes the forum fun and the more times you beat a subject to death the more we can all learn or re-affirm what we think we know and since most days I can't tell what I actually know from what I think I know or what I think I don't know from what I know I actually don't know this continued beating of the dead horse stuff actually still helps me.
If you need to work on the GET_LAST value, copy it with a LOCAL_VAR because as others rightly point out, the value of GET_LAST can change very quickly - particularly with multiple users. At least if you copy it you can be sure it's the right val.
jjames's code below is how I would do it too.
...With or without waits? - At least you have the option to do something like that if you need to using this method....
BUTTON_EVENT[dv_TP,nSRC_BTNS]
{
PUSH:
{
STACK_VAR INTEGER nPNL;
nPNL = GET_LAST(dv_TP)
SWITCH (nPNL):
{
CASE 1:WAIT 20 fnDO_SOMETHING(1);
CASE 2:WAIT 20 fnDO_SOMETHING(2);
CASE 3:WAIT 20 fnDO_SOMETHING(3);
CASE 4:WAIT 20 fnDO_SOMETHING(4);
CASE 5:WAIT 20 fnDO_SOMETHING(5);
}
}
}
You are right NetLinx is single threaded and one event has to be played out before another can begin. The problem is how are you using that code. Is the code being called directly from a button event and only a button event with no waits or timeline delays? then yes the GET_LAST function will always return the correct value. Once you add in a wait or timeline delay then all bets are off (I think even long FOR loops or WHILE loops will add enough of a delay in some situations). The other problem/bug is when using GET_LAST in a hold statement. When you press or release a button GET_LAST gets updated. When you hold a button GET_LAST does not get updated. We had some code where the user would press and then hold a button. In the time period between the press and the hold another button somewhere else was pressed. GET_LAST now changes focus to the other panel. When the event handler tried to run the code in the first buttons hold statement the GET_LAST function was now wrong (or not what we were expecting) and we did not get the desired results. We were actually controlling functions in the second room. This was very difficult to track down because every thing would work 99.99% of the time.
I hope that helps to understand a little bit more.
Dan
Dan, I'm sure you've already figured it out with whatever way your doing it, but just to open it up to everyone, here's how I'd do such a thing (setting a preset without a HOLD event, and using GET_LAST() properly.)
BUTTON_EVENT[dv_TP,nTUNER_PRESET_BTNS] // Tuner preset select { PUSH: { LOCAL_VAR INTEGER nPANEL LOCAL_VAR INTEGER nIND nIND = GET_LAST(nTUNER_PRESET_BTNS) nPANEL = GET_LAST(dv_TP); // lock out other users from setting a preset at the same time IF(!nTUNER_LOCK2) { ON[nTUNER_LOCK2] // Wait 3 seconds WAIT 30 'RADIO PRESET' { // Turn on lock to indicate hold ON[nLOCK2] // Set preset cTUNER_PRESET[nIND] = cSomeVariableWithText // Set preset & do any other fancy stuff such as displaying feedback of the current // preset on the button you're pressing, or double beeping, or displaying // a scary picture to scare the crap out of the client. // you can also add a delay so that nothing can happen to the preset RIGHT AWAY WAIT 20 OFF[nLOCK2] } } } RELEASE: { STACK_VAR INTEGER nIND STACK_VAR INTEGER nPNL nPNL = GET_LAST(dv_TP); nIND = GET_LAST(nTUNER_PRESET_BTNS) // Cancel wait so preset saving doesn't occur CANCEl_WAIT 'RADIO PRESET' OFF[nTUNER_LOCK2] // If the wait hasn't been executed, go to selected preset IF(!nLOCK2) { // send it to the tuner, or whatever SEND_STRING dvRADIO, "cTUNER_PRE[nIND]" } } }I got around it a different way. I keep track of what ui was returned in the get_last in the push and use it in the hold. This seems to work fine without too much extra code or blocking other users out. If two users try and do a hold on the same device, Netlinx nicely takes care of that automatically.
Paul
Yeah . . . nothing is perfect.
Don't get me wrong, I could switch that easily over to allow other panels to do stuff, this is just one of the ways I would do it. It is the easiest, and I don't know about you, but typically we don't have gobs of people running around setting their presets all at once, so it's not a problem
But, in true fashion of the forum, it seems to always be a debate of most efficient code rather than most practical, straight forward, or "easiest" code.