Home AMX User Forum NetLinx Studio

Event triggered by variable change

Howdy Everyone,

I'm looking for a way to trigger an event/function if a variable is changed. This would be applied to an array of structures so having active monitoring of every single variable would easily create a massive amount of code as well as not being flexible for updates.

I've thought about using virtual devices but run into problems with storing LONGs and CHAR arrays that are somehow tied to that virtual device (and thusly monitored for changes).

These must be events or function calls from the trigger due to GUI updates as well as IP commands being sent to many other devices.

I'm trying to avoid the following:
sSTATUS = 'STARTING_UP'
CALL 'UPDATE_STATUS'
...
sSTATUS = 'STARTED'
CALL 'UPDATE_STATUS'

Any ideas? I'm thinking Duet can do this with listeners but that would be a hard push to get with this being the only reason.

--
Mitch

Comments

  • Joe HebertJoe Hebert Posts: 2,159
    mlschuh wrote: »
    I'm looking for a way to trigger an event/function if a variable is changed.
    Good idea but I don?t think anything exists right now.
    I?ve always thought a PROPERTY_EVENT would make Netlinx a more powerful language. Something like this:
    PROPERTY_EVENT[someVariable] {
    
       GET: {
       
       }
       
       SET: {
       
       }
    
    }
    

    Is that along the lines you are thinking?
  • mlschuhmlschuh Posts: 12
    Yup, that's what I would be looking for. However, in an ideal world, I'd love it if we could also expand structures to be more like classes with built-in functions.
    STRUCTURE type_PERSON
    {
    	INTEGER AGE
    	CALL 'setAGE' (INTEGER)
    }
    
    DEFINE_CALL 'setAGE' (INTEGER newAGE)
    {
    	type_PERSON.AGE = newAGE
    	//WHERE type_PERSON IS THE SPECIFIC STRUCT INSTANCE
    }
    
    DEFINE_VARIABLE
    type_PERSON ANDY
    
    BUTTON_EVENT[]
    {
    	ANDY.setAGE(10)
    	tmp = ANDY.AGE	//RETURNS 10
    }
    

    I come from a C/C++ so am a bit bias towards their layout of classes.

    That way it will work within structures themselves and you don't need to make odd arrays if you want to modify variables within structures (if it's even possible to make those arrays, I haven't put much thought into it yet).
  • TurnipTruckTurnipTruck Posts: 1,485
    It may be old school, but you could do this in define program.
    DEFINE_PROGRAM
    IF (nVariable<>nValue)
      {
      fnDoWhatever()
      nVariable=nValue
      }
    
  • jjamesjjames Posts: 2,908
    I use WAIT_UNTIL when I need do something when a variable changes. You can cancel them (CANCEL_WAIT_UNTIL) and even have them expire after a certain amount of time (TIMED_WAIT_UNTIL).

    Example:
    CASE nSRC_CD:
    {
    	WAIT_UNTIL (CDC.Power)
    	{
    		// Do stuff with the CD changer
    	}
    }
    
  • mlschuhmlschuh Posts: 12
    jjames wrote: »
    I use WAIT_UNTIL when I need do something when a variable changes. You can cancel them (CANCEL_WAIT_UNTIL) and even have them expire after a certain amount of time (TIMED_WAIT_UNTIL).

    Example:
    CASE nSRC_CD:
    {
    	WAIT_UNTIL (CDC.Power)
    	{
    		// Do stuff with the CD changer
    	}
    }
    

    I'm confused, what triggers the CASE? Would you just have a WAIT_UNTIL set on every variable you want to monitor?
  • jjamesjjames Posts: 2,908
    mlschuh wrote: »
    I'm confused, what triggers the CASE? Would you just have a WAIT_UNTIL set on every variable you want to monitor?

    Sorry - that was just pulled out of my code in the power on sequence. The CASE was which source (in this instance - CD) was selected in the function.

    Basically, when someone selects the CD source, I'm going to WAIT_UNTIL the CD changer is powered up (variable changed elsewhere in code when it receives a specific string back from it), and then do some specific things. You can add a name to it to cancel it if a difference source is selected.

    Recently I used TIMED_WAITs with three driveway sensors. There's one at the entry (near the street), one in front of the house and one near the garage. I set a variable once the first sensor was tripped, and then did a TIMED_WAIT_UNTIL until either sensor two or three was tripped. Once one of those triggers fired, the variable changed and fired specific code. Of course all of the waits were named and could be canceled.
  • mlschuhmlschuh Posts: 12
    Gotcha, but all of the WAITs are still triggered by other events whereas I'm looking to trigger events when variables change. I like the idea and will certainly add it to my bag of tricks but not quite what I'm looking for.

    --Mitch
  • jjamesjjames Posts: 2,908
    mlschuh wrote: »
    Gotcha, but all of the WAITs are still triggered by other events whereas I'm looking to trigger events when variables change. I like the idea and will certainly add it to my bag of tricks but not quite what I'm looking for.

    --Mitch
    Do your variables change by magic? I believe in order for something to change the variable you're looking at . . . an event has to happen right?

    Or - just put it in DEFINE_PROGRAM like earlier suggested. What you're looking for is achievable - just not in a .NET style.
  • mlschuhmlschuh Posts: 12
    Debugging aside, yeah, they do change when events happen. But, like I said in the original post, I'm looking for a way to clean the code, increase reliability, and decrease the amount of code needed when a change needs to be made.

    I'd love to put them all as IFs in mainline but I'm looking at around 15 properties for ~20 zones which is at least 300 lines of just if statements, along with all of the temporary variables to go along with it and a nightmare to adjust if I add a single property to the structure.

    Netlinx is just so close to being a very, very powerful language but I guess I have to look towards Duet to really pull out all of the stops.
  • a_riot42a_riot42 Posts: 1,624
    mlschuh wrote: »
    Debugging aside, yeah, they do change when events happen. But, like I said in the original post, I'm looking for a way to clean the code, increase reliability, and decrease the amount of code needed when a change needs to be made.

    I'd love to put them all as IFs in mainline but I'm looking at around 15 properties for ~20 zones which is at least 300 lines of just if statements, along with all of the temporary variables to go along with it and a nightmare to adjust if I add a single property to the structure.

    Netlinx is just so close to being a very, very powerful language but I guess I have to look towards Duet to really pull out all of the stops.

    You can kind of fake 'listeners' using virtual devices and channel_events. But I am wondering if there might be an issue with your design if what you are trying to achieve doesn't present any elegant solution. What is it you are trying to achieve exactly?
    Paul
  • mlschuhmlschuh Posts: 12
    Hi Paul,

    Here's just a quick sample structure:
    STRUCTURE type_ZONE
        {
        INTEGER	readyComplete
        INTEGER	standbyComplete
        INTEGER	state
        INTEGER	stateText[8][24]
        }
    

    In various locations throughout the remainder of the code, all of the various integers change. When these variables change, I need to add the changes to logs as well as send the change to a central controller (Not AMX). There are also many, many zones (this particular one is 21) so a structure array is the best way that I've found to work with everything.

    Other structures contain LONGs and CHAR arrays so I sadly cannot use virtual devices.

    The workaround is to just call the update function every time the variable change, which is what I'm doing now. Not the ideal, but that's all I've found so far.

    --Mitch
  • truetrue Posts: 307
    IMO NetLinx is pretty far from being a powerful language. There's lots of deficiencies. There's no way to extend it, for example. This issue is just one of the minor things I wish it had.

    I wish AMX would open up the compiler so I could write some patches against it. A lot of the issues are syntax that would be easy to do, some are features like these.
  • Spire_JeffSpire_Jeff Posts: 1,917
    I'm not sure that this is what you are looking for, but have you considered using a virtual device with multiple ports (1 port for each zone) in conjunction with char arrays?

    I am thinking that there are a finite number of states, so as an example, I will use inputs 1-10 and off.
    define_constant 
    char ZoneName[20][25] = {'Room 101', 'Room 201',...};
    char StateName[11][25] = { 'Input 1','Input 2', .. 'Input 3', Off};
    integer StateChans[11] = {101,102,....,111};
    define_device 
    vdvZone1 = 33001:1:0;
    vdvZone2 = 33001:2:0;
    vdvZone3 = 33001:3:0;
    ...
    
    define_variable
    vdvZones[] = {vdvZone1, vdvZone2, vdvZone3,...};
    
    Define_event
    button_event[dvTPs,SelectSrc]{
     push:{
       stack_var integer x;
       stack_var integer y;
       x = get_last(dvTPs);
       y = get_last(SelectSrc);
       on[vdvZones[x],StateChans[y]];
     }
    }
    channel_event[vdvZones,StateChans]{
       on:{
         stack_var integer x;
         stack_var integer y;
         stack_var integer z;
         x = get_last(vdvZones);
         y = get_last(StateChans);
    
         send_string dvCentralServer,"ZoneName[x],' : ', StateName[y]";
         fnDoStateChange(x,y); 
         for(z=length_array(StateChans);z;z--){//Could be handled by define_mutually_exclusive
            if(z != y)
              off[vdvZones[x],StateChans[z]];
         }
       }
    }
    
    


    I am not sure this will line up to what you need, but it helps me to think of the virtual device as a structure of pointers and values if you will. You get multiple elements by grouping the channel codes. Such as channel 200 is a boolean indicator for power off, chans 201-220 are inputs, level 1 is volume level, channel 26 is Mute indicator, and so on.
    Using the channels is nice because if the same input is selected again, the event won't trigger again. You can also access the channel states for conditionals ... if([vdvDevice,200]). Levels can be nice as the event only occurs if the level is different, but they can be a little tricky as the level drops to 0 when a device goes offline. You also have to do a little more work to get access to the value of the level outside of a level event, but it is not too difficult.

    Jeff
  • mlschuhmlschuh Posts: 12
    My Solution

    Just a quick followup on what I ended up doing.

    Ultimatly, what I was looking for was a variable watcher. As I kept looking for an answer, a way to impliment classes in Netlinx started to form in my mind and I pushed around some test code and sure enough, 95% of what I would need to do exists. However, the remaining 5% ground everything to a halt and made it much more trouble that it was worth (declaring extra variables, having includes in addition to modules, etc), so I canned that idea.

    The solution that I came up with isn't the greatest way to do it, and I'm sure I'll get scolded by someone, but I created a module that gets passed a devchan (read: virtual 'listener' device) and a variable name. That module contains the if in the mainline (like TurnipTruck suggested) and manages the temporary check variable. It then just pulses the devchan when the variable changes. A grand total of 4 lines of code. However, I think it will help with organization within the actual code by making it so you only need to declare your devchan (or device/channel pair however you want) and then just defining the module rather than having to manage the temporary variable and the if statements.

    To me, it's right on the border of lazy vs useful, but I found it much easier to essentially say "Hey, I want a listener on here!" rather than actually building the listener every time.

    When I get this fully implemented in my system, I'll run some speed checks to make sure that the if's aren't loading down anything too much (I highly doubt it, but it can't hurt to check). PM me if you would be interested in the results.

    Oh, and the full motivation behind this: create a standard event driven programming layout that can coexist with other types of programming environments. Namely, Medialon.

    --Mitch
  • DHawthorneDHawthorne Posts: 4,584
    I'm pretty sure you can use channel events for simple Boolean values. For more complex variable, or strings, I just make another variable that holds the last known state, then compare them regularly (mainline or a timeline, depending on how much of it I have going on). If the two are different, I fire off my action, then make them the same. That way you can also force the event if you need to (like in a panel online event) by setting the comparison variable to something impossible so next pass they will be nonequivalent again.
  • mlschuhmlschuh Posts: 12
    Dave,

    That's exactly what I ended up doing.

    -Mitch
Sign In or Register to comment.