Home AMX User Forum NetLinx Studio

NetLinx Coding best practice...

Hi,

I am looking at my 'macro' code where I am executing multiple events. e.g. Switching to DVD would...

Turn on DVD Player
Turn on Plasma
Switch AV processor to DVD
Switch Video processor to DVD
Turn down lights

I have lots of these routines as I am sure everybody else does too. Question I have is how best to code these.

I am using modules for each where each command is built into a button event. If I am looking to combine several actions should I assign another button event and attach a series of DO_PUSH commands?

Not sure if it really matters but would be interested in what is considered best practice as I have on more than one occassion written stuff that looks fine and technically is fine but due to netlinx oddities has thrown lots of runtime exceptions...

Comments

  • Timing is everything.

    Stacking a bunch of Do_Push statements is quick and dirty way to get a marco to run. Unfortunately you are using the mindset of the typical 'Smart' remote that has no timing capability.

    A better approach would be to develop a Timeline that will better trigger the individual commands in the proper sequence and time.

    For example, if you fire the plasma power on command the plasma may not immediately accept an input command. The goal is choreograph the control events to activate in an intelligent manner. You should also consider the results if the user selects a different marco (or any other button) while the first macro is running. Also, why continue to send a power on command if the device is already on?

    It would be very easy to spend a couple hour exploring this subject in an advanced training class.
  • DHawthorneDHawthorne Posts: 4,584
    I don't know that anyone can say there is a "best" way. It's often a matter of what you prefer, since several methods can get the job done. I generally use a combination of timelines and waits, depending on the device. Many TV's, projectors, and receivers have abit of a warmup time between when they turn on and when they will accept commands. Others will just pend the commands while warming up, and it doesn't matter how fast you send them. I will set a flag for the types of devices where it matters, queue up commands until they are ready, then turn the flag off. Most of the time, a named wait is all that is needed, and a WAIT_UNTIL before sending anything after the power-on. One of the nice things about WAIT_UNTIL is that if the condition evaluates to TRUE, there is no delay at all, so it can be effectively used to delay durning warmup, but is entirely transparent after the warmup has expired. If precise timing is needed, or more than one thing has to happen in sequnce (projector on, screen down, drapes open, status page popped up ...), then I'll put those events in a timeline ... but it's very much a matter of how each device works and what is needed for it to respond properly. If there is a choice between ways that work just fine, pick the simplest; there is little point in making the code more obscure than it needs to be.
  • hodeyphodeyp Posts: 104
    thanks, I was also looking as should i use 'DO_PUSH' at all?

    e.g. - I see no reason why a user should have to switch the plasma on at all, as it is entirely dependant on what source they will be using. As such this should just happen in the background.

    However, the module is expecting a button press from the touchpanel, I don't really want to fill the touchpanel with buttons that a user never need press.

    I suppose the question could be written, what is the best way to switch a device on without user interaction?
  • hodeyp wrote:
    thanks, I was also looking as should i use 'DO_PUSH' at all?
    I usually avoid the DO_PUSH unless there is no other way, but there is really nothing wrong using this keyword.
    hodeyp wrote:
    However, the module is expecting a button press from the touchpanel, I don't really want to fill the touchpanel with buttons that a user never need press.
    Instead of building your modules around a touch panel BUTTON_EVENT, you could just as easily use a CHANNEL_EVENT on a virtual device. That way your main code can PULSE a channel on the virtual device to activate the code for the physical device.

    Also, if I am understanding your question correctly, just because you use a DO_PUSH in your program you really don't need a physical button on the touch panel representing the DO_PUSH channel. There is no reason to put unnecessary buttons on a touch panel.

    hodeyp wrote:
    I suppose the question could be written, what is the best way to switch a device on without user interaction?
    As Dave said there is no best way. It is just a matter of style and functionality. My preference to use various CALLS or FUNCTIONS that can be activated by the BUTTON_EVENT. Like Dave, the WAIT_UNTIL statement is one of my favorite ways to manage conditional timing.
  • champchamp Posts: 261
    The AMX reccommended way of controlling every device is by making a module for the device and using a SEND_COMMAND to the virtual device.

    Search for 'NetLinx Programming Conventions' on the AMX site and check out the modules section.
    Also download any module and look at the accompanying API specification that comes with it.

    AMX also has a macro manager module but I find it far too bloated for standard use. It is definitely worth looking at though to get ideas.

    I wanted to create a standard way for my company to program and found that AMX have already written the documents. Start there and extrend their API.

    Also check out the code created by Design Express for ideas.
    Reading other peoples code is a great way to get ideas.
  • frthomasfrthomas Posts: 176
    hodeyp wrote:
    Not sure if it really matters but would be interested in what is considered best practice as I have on more than one occassion written stuff that looks fine and technically is fine but due to netlinx oddities has thrown lots of runtime exceptions...

    One thing to consider is that when doing a series of DO_PUSH (or sending a series of any other type of events for that matter), they will all be handled sequentially (one after the other). Now if the resulting code in your module does "stack" other events (like pulsing IR) and/or send RS232 commands, you have now a lot of events in the queue. If some of the code now expects feedback from a VSS2 or from RS232 and you have time outs defined, then this code has to take into account the queue depth and the delay in handling it. I think we found out in another thread the delay will apply to all events and waits but not to timeline events.

    This could explain why a module works perfectly in stand alone mode, but fails or otherwise behaves strangely when used in concert with others within macros. There's just too much stuff happening and the master runs late.

    In residential situations I believe there is a value in sequencing things properly. For example the video projector might spend its time "detecting" various signals while the scaler is booting, so you want to turn it on only once the scaler has booted. Ultimately you're bound to want more control than just throwing out the complete command set vaguely in order and hope for the best.

    A solution are timelines as mentioned, or the AMX "macro" system. They also have something in DXP home theater you can have a look at.

    I do have a complete ('bloated') sequencing system with automatic time outs and delays that can SEND_COMMANDs or IR or whatever. The sequence is built dynamically when needed, so it only includes required steps. Now that I have it is much simpler for me to use it than to bother with a timeline, but it has a cost in code size. I can also use it to program RS232 devices.
    It is my opinion that such a feature is such a basic building block of an automation system that I don't understand why it is not part of the NetLinx language.

    HTH

    Fred
  • champchamp Posts: 261
    From the Axcess programming manual

    "This keyword is useful only in limited situations, and its use is strongly discouraged."
  • champ wrote:
    From the Axcess programming manual

    "This keyword is useful only in limited situations, and its use is strongly discouraged."

    If this is in reference to DO_PUSH, it was given a new life (and almost a blessing?) in Netlinx. If you look in the Netlinx programming manuals, (or online help in NLStudio) you'll see that it doesn't have the disclaimer that the Axcess manual used to have.

    Haven't run into a situation where I've needed it, but it supposedly isn't the "bad thing" that it used to be in Axcess.

    - Chip
  • Don't worry about it.
    champ wrote:
    From the Axcess programming manual

    "This keyword is useful only in limited situations, and its use is strongly discouraged."

    As Chip said...no prolbems in NetLinx.

    The problem in Access is that while a DO_PUSH was in progress the implied DO_RELEASE needed to occur before accepting the next button push. It was difficult to manage DO_PUSH in Access without a thorough understanding of how MAIN LINE operates. Thus limited value unless you really knew what you were doing.
  • hodeyphodeyp Posts: 104
    cheers for all the feedback. Looks like there are many ways of acheiving the result I am looking for. I guess it is worth using for quick & dirty routines but will certainly avoid if more sensitive timing issues are required.

    thx again!
  • DHawthorneDHawthorne Posts: 4,584
    The reason DO_PUSH has taken on a new life is because many of the AMX provided modules use channel events extensively. There are times when a SEND_COMMAND is too cumbersome, so AMX has taken that into account. It's far easier to have a channel on a virtual, for example, that not only show you the power state, or mute condition, of a receiver, and lets you change that state as well. You don't want to just send an ON, that would interfere with real feedback, since the channel is shared. The solution is just to run a DO_PUSH on that devchan.

    The other time you would use them, and I think this is the more important one, is when you have a fair amount of stuff going on in a button event, but not enough to warrant creating a seperate function or CALL for it. Or perhaps you have a virtual panel with it's own button events. Do you really want to duplicate that button event code if you need it fired in another place? Just use a DO_PUSH instead. There is no need to retype the same code in three spots. Or, if those same three lines of code are buried inside a module, and are not accessible, but the button devchan itself is - then a DO_PUSH is the logical way to fire those three lines without needing to modify the module. I've used this with a CCTV controller. The panel to control the controller itself, of course, has a way to select cameras, and make sure whatever needs to be updated gets updated. But I wanted to switch to a specific camera under certain conditions, and still make sure all those updates and things happened the way they were supposed to (this controller has no real feedback, I had to track myself what camera was being displayed). A judicious DO_PUSH on the control panel devchan did the job neatly and nicely, ans as long as the controller module did it's job properly, I didn't have to worry about it elsewhere.

    It's all about avoiding code redundancy, in my book. Functions and CALL's (for those of us who haven't sworn off them) are fine in many applications, but sometimes a judicious DO_PUSH does the job even better. The real thing to avoid is a chain of DO_PUSH's that might wind up calling an endless loop, or call another DO_PUSH. They are like soy sauce - used them sparingly and carefully, and they can enhance your project. Too much, or used wrongly, and it's just nasty.
  • DHawthorne wrote:
    They are like soy sauce - used them sparingly and carefully, and they can enhance your project. Too much, or used wrongly, and it's just nasty.

    Poetry - sheer poetry... :)

    - Chip
  • GSLogicGSLogic Posts: 562
    I always setup all my major PUSH commands to trigger FUNCTIONs. If you setup the arguments correctly you can manipulate the code to do what ever you need.
  • DHawthorneDHawthorne Posts: 4,584
    GSLogic wrote:
    I always setup all my major PUSH commands to trigger FUNCTIONs. If you setup the arguments correctly you can manipulate the code to do what ever you need.
    True, but not always an option when working with a compiled .tko from AMX (or anywhere else, for that matter).
  • vegastechvegastech Posts: 369
    Is it possible to create a functions array? If that were possible, you could stack the button events or do a button/source array and then execute the function based on get-last...I would think you would still have to write each function first. - IS there a way to write an all-rooms function?
  • ericmedleyericmedley Posts: 4,177
    vegastech wrote: »
    Is it possible to create a functions array? If that were possible, you could stack the button events or do a button/source array and then execute the function based on get-last...I would think you would still have to write each function first. - IS there a way to write an all-rooms function?

    It's kinda hard to answer this question without some design scope or code.

    but one idea might be a function that has the things you're talking about in the call.

    For example:

    define_Function my_big_function( integer Room_ID, integer Command_ID, char etc...)
    {

    }

    So, in a way you're creating the same functionality as an array of functions but essentially storing the data table in the function itself.
  • vegastechvegastech Posts: 369
    Sorry, I guess some scope would be easier to work with. I was thinking of this:
    Turn on tv array device 1-8
    Set autopatch zone to source
    Set zone flag for tracking zone status
    Turn on ir device selected.
    I'm on my phone now, otherwise I would copy and paste some code I'm working on. I am using a home brewed version of some autopatch switcher code where I can select multiple rooms first, then select the device needed, and then have those zones all change. However, I'm at a standstill with determining if I should continue with my individual functions, or look at incorporating them all into the button events for the switcher. The problem with that becomes the handheld remotes that use the existing functions to do macros.
  • the8thstthe8thst Posts: 470
    Here is an excerpt from one of my functions for turning a zone on:
    //
    	define_function initVideoZone(integer tpIndex, integer nInput) {
    		local_var integer videoOutput
    		
    		if (!isValid(nInput,13)) {		// make sure input is between 1 and 13
    			return
    		}
    		
    		if (tpIndex < 100) {		// Called directly from TP button
    			setSourceBanner(tpIndex, nInput)
    			videoOutput = audTPinfo[tpIndex].videoOutput
    			audTPInfo[tpIndex].audioOutput[vidSwitcher[videoOutput].audioOutput] = 1
    		} else {					// Called indirectly from other function
    			videoOutput = tpIndex - 100
    		}
    		
    		if (!isValid(videoOutput,MAX_VID_OUTS)) {		// make sure vid out is between 1 and 12
    			return
    		}
    		
    		fnMainDebug('videoMain',__line__,"'initVideoZone: videoOutput = ',itoa(videoOutput),' nInput = ',itoa(nInput)")
    		
    		switch (videoOutput) {
    			case 4: {	// Family Room
    				send_string dvFamilyTV,"$02,'PON',$03"
    				send_command vdvFamilyAVR,"'POWER=ON'"
    				
    				if (!displaysPower[videoOutput]) {
    					if (nInput <= 12) {								// video inputs on video matrix
    						videoExecuteSwitch(nInput,videoOutput)
    						wait 40
    							send_command vdvFamilyAVR,"'INPUT=SWITCHER'"
    						wait 50
    							send_string dvFamilyTV,"$02,'IMS:SL3',$03"
    					} else if (nInput == 13) {						// Family dedicated bluray player
    						send_command dvBluray,"'SP',27"
    						wait 40
    							send_command vdvFamilyAVR,"'INPUT=DVD'"
    						wait 50
    							send_string dvFamilyTV,"$02,'IMS:SL1A',$03"
    					}
    				} else {
    					if (nInput <= 12) {
    						videoExecuteSwitch(nInput,videoOutput)
    						send_command vdvFamilyAVR,"'INPUT=SWITCHER'"
    						send_string dvFamilyTV,"$02,'IMS:SL3',$03"
    					} else {
    						send_command dvBluray,"'SP',27"
    						send_command vdvFamilyAVR,"'INPUT=DVD'"
    						send_string dvFamilyTV,"$02,'IMS:SL1A',$03"
    					}
    				}
    				
    				displaysPower[videoOutput] = 1
    				audioExecuteSwitch(audioInputMap[nInput], (vidSwitcher[videoOutput].audioOutput + 100))
    			}
    			default: { 	// All Standard Zones
    				videoExecuteSwitch(nInput,videoOutput)
    				
    				send_command dvDisplays[videoOutput], "'SP',nDisplayPowerOn"
    				
    				// set video
    				if (!displaysPower[videoOutput]) {
    					switch (videoOutput) {
    						case 1: wait 50 send_command dvDisplays[1],"'SP',nDisplayComp1"
    						case 2: wait 50 send_command dvDisplays[2],"'SP',nDisplayComp1"
    						case 3: wait 50 send_command dvDisplays[3],"'SP',nDisplayComp1"
    						case 5: wait 50 send_command dvDisplays[5],"'SP',nDisplayComp1"
    						case 6: wait 50 send_command dvDisplays[6],"'SP',nDisplayComp1"
    						case 7: wait 90 send_command dvDisplays[7],"'SP',nDisplayComp1"
    						case 8: wait 50 send_command dvDisplays[8],"'SP',nDisplayComp1"
    						case 9: wait 50 send_command dvDisplays[9],"'SP',nDisplayComp1"
    						case 10: wait 50 send_command dvDisplays[10],"'SP',nDisplayComp1"
    						case 11: wait 50 send_command dvDisplays[11],"'SP',nDisplayComp1"
    						case 12: wait 50 send_command dvDisplays[12],"'SP',nDisplayComp1"
    					}
    				} else {
    					send_command dvDisplays[videoOutput],"'SP',nDisplayComp1"
    				}
    				
    				displaysPower[videoOutput] = 1
    				audioExecuteSwitch(audioInputMap[nInput], (vidSwitcher[videoOutput].audioOutput + 100))
    			}
    		}
    		vidZoneStatus()
    	}
    

    All of the AVR power on waits (if needed) are handled by my AVR modules. All of the individual source power stats are handled in a separate function that is called elsewhere in the program.

    I did not include the structure declarations, variable declarations or external functions, but this is just to get an idea of how I handle this.

    It works pretty well in that I only have to edit the function for rooms with surround receivers or local sources. Otherwise I let everything fall through to the default case and it handles everything for me.
  • chillchill Posts: 186
    DHawthorne wrote: »
    ... Functions and CALL's (for those of us who haven't sworn off them)

    A define_call most certainly has its place. I've seen enough Other People's Code to say "why is he using a function here? It doesn't return a value!" I use functions for stuff like scaling a number, calculating a checksum, that sort of thing. But almost every one of my projects includes a call like "define_call 'shutdown' (integer room)".
    .
  • chillchill Posts: 186
    vegastech wrote: »
    Is it possible to create a functions array? ...

    In my experience, a function cannot return an array; it has to be a scalar. But maybe I've misunderstood the question.

    Actually you can return a CHAR array, but not any other kind
    .
  • the8thstthe8thst Posts: 470
    chill wrote: »
    A define_call most certainly has its place. I've seen enough Other People's Code to say "why is he using a function here? It doesn't return a value!" I use functions for stuff like scaling a number, calculating a checksum, that sort of thing. But almost every one of my projects includes a call like "define_call 'shutdown' (integer room)".
    .

    Is there a performance hit for using functions instead of calls?

    I prefer to use functions because I find the autocomplete and highlighting in NS3 to be much easier to deal with when using functions instead of calls. I also really like the popup that spells out which parameters the function is expecting when typing code and you do not get the same useful information when calling a call.
  • DHawthorneDHawthorne Posts: 4,584
    the8thst wrote: »
    Is there a performance hit for using functions instead of calls?

    I prefer to use functions because I find the autocomplete and highlighting in NS3 to be much easier to deal with when using functions instead of calls. I also really like the popup that spells out which parameters the function is expecting when typing code and you do not get the same useful information when calling a call.

    I don't think there is anything significant. Just use whatever you are most comfortable with, I doubt in the long run it will matter enough to be concerned about.
Sign In or Register to comment.