Home AMX User Forum AMXForums Archive Threads Tips and Tricks

Level_Event Programming Tips

What is the best way to do level programming. This should take into consideration that commands don't go when panels go offline and level feedback doesn't keep updating due simultaneous actual device feedback. Here is my UI and comm module for an AVR. It works but i want to know if it can be done better.
MODULE_NAME='Onkyo_AVR_UI'(DEV dvTP_DPS[],DEV vdvDevices,integer nCHAN_BTN[],INTEGER TP_LEVEL)
#INCLUDE 'Snapi.axi'
(***********************************************************)
(***********************************************************)
(*  FILE_LAST_MODIFIED_ON: 04/04/2006  AT: 11:33:16        *)
(***********************************************************)
(* System Type : NetLinx                                   *)
(***********************************************************)
(* REV HISTORY:                                            *)
(***********************************************************)
(*
    $History: $
*)
(***********************************************************)
(*          DEVICE NUMBER DEFINITIONS GO BELOW             *)
(***********************************************************)
DEFINE_DEVICE

(***********************************************************)
(*               CONSTANT DEFINITIONS GO BELOW             *)
(***********************************************************)
DEFINE_CONSTANT
CHAR PARAM_INPUT_CMD[13][3]=
     {
     '10',    //1 DVD
     '01',    //2 CBL/SAT
     '23',    //3 CD
     '02',    //4 Game1
     '05',     //5 PC
     '00',    //6 STB/DVR
     '04',    //7 Game2
     '22'     //8 Phono
     }

CHAR PARAM_SUR_MODE_CMD[13][3]=
     {
    '00',//1 'STEREO',
    '01',//2 'DIRECT',
    '0D',//3 'THEATER_DIMENSIONAL',
    '0E',//4 'ENHANCED_7',
    '42',//5 'THX_CINEMA',
    '43',//6 'THX_SURROUND_EX',
    '45',//7 'THX_GAMES',
    '80',//8 'PLII_MOVIE',
    '82',//9 'NEO6_CINEMA',
    '83',//10 'NEO6_MUSIC',
    '84',//11 'PLII_THX_CINEMA',
    '85' //12 'NEO6_THX_CINEMA'
     }

(***********************************************************)
(*              DATA TYPE DEFINITIONS GO BELOW             *)
(***********************************************************)
DEFINE_TYPE

(***********************************************************)
(*               VARIABLE DEFINITIONS GO BELOW             *)
(***********************************************************)
DEFINE_VARIABLE

VOLATILE  integer nLevel=0
VOLATILE INTEGER bNoLevelReset = FALSE
(***********************************************************)
(*               LATCHING DEFINITIONS GO BELOW             *)
(***********************************************************)
DEFINE_LATCHING

(***********************************************************)
(*       MUTUALLY EXCLUSIVE DEFINITIONS GO BELOW           *)
(***********************************************************)
DEFINE_START
rebuild_event()

(***********************************************************)
(*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
(***********************************************************)
(* EXAMPLE: DEFINE_FUNCTION <RETURN_TYPE> <NAME> (<PARAMETERS>) *)
(* EXAMPLE: DEFINE_CALL '<NAME>' (<PARAMETERS>) *)

(***********************************************************)
(*                STARTUP CODE GOES BELOW                  *)
(***********************************************************)
DEFINE_EVENT
DATA_EVENT [dvTP_DPS]
{

    ONLINE:
    {

        bNoLevelReset = 0
    //send_level dvTP_DPS[get_last(dvTP_DPS)], TP_LEVEL,nLevel
    }
    OFFLINE:
    {
        bNoLevelReset = 1
    }

}

//---------------------------------------------------------------------------------
//
// EVENT TYPE:       DATA_EVENT for vdvDev
//                   SourceSelectComponent: data event
//
// PURPOSE:          This data event is used to listen for SNAPI component
//                   commands and track feedback for the SourceSelectComponent.
//
// LOCAL VARIABLES:  cHeader     :  SNAPI command header
//                   cParameter  :  SNAPI command parameter
//                   nParameter  :  SNAPI command parameter value
//                   cCmd        :  received SNAPI command
//
//---------------------------------------------------------------------------------
DATA_EVENT[vdvDevices]
{
    COMMAND :
    {
        // local variables
        STACK_VAR CHAR    cCmd[DUET_MAX_CMD_LEN]
        STACK_VAR CHAR    cHeader[DUET_MAX_HDR_LEN]
        STACK_VAR CHAR    cParameter[DUET_MAX_PARAM_LEN]
        STACK_VAR INTEGER nParameter
        STACK_VAR CHAR    cTrash[20]

        // get received SNAPI command
        cCmd = DATA.TEXT
    //denon_buffer=data.text
        // parse command header
        cHeader = DuetParseCmdHeader(cCmd)
        SWITCH (cHeader)
        {

            //----------------------------------------------------------
            // CODE-BLOCK For SourceSelectComponent
            //
            // The following case statements are used in conjunction
            // with the SourceSelectComponent code-block.
            //----------------------------------------------------------




            // SNAPI::INPUT-<sourceSelect>,<INPUTNumber>
            CASE 'INPUT' :
            {


            }


        }
    }


}



BUTTON_EVENT [dvTP_DPS,nCHAN_BTN]
{
   PUSH:
    {
    SWITCH(get_last(nChan_BTN))
    {
        CASE 1://Power ON
        {

        pulse[vdvDevices,PWR_ON]
        }
        CASE 2://Power Off
        {

        pulse[vdvDevices,PWR_OFF]
        }
        CASE 3:
        CASE 4:
        CASE 5:
        CASE 6:
        CASE 7:
        CASE 8:
        CASE 9:
        CASE 10:
               send_command vdvDevices ,"'INPUT-',PARAM_INPUT_CMD[get_last(nChan_BTN)-2]"
        CASE 11://Cyclic Volume Mute
        {

        pulse[vdvDevices,VOL_MUTE]
        }
        CASE 12:
        {
        DO_PUSH_TIMED (vdvDevices,VOL_UP,DO_PUSH_TIMED_INFINITE)
        }
        CASE 13:
        {

        DO_PUSH_TIMED (vdvDevices,VOL_DN,DO_PUSH_TIMED_INFINITE)
        }        
        CASE 14:
        CASE 15:
        CASE 16:
        CASE 17:
        CASE 18:
        CASE 19:
        CASE 20:
        CASE 21:
        CASE 22:
        CASE 23:
        CASE 24:
        CASE 25:
        CASE 26:
        CASE 27:
        send_command vdvDevices ,"'SURROUND-',PARAM_SUR_MODE_CMD[get_last(nChan_BTN)-13]"


    }

    }
    release:
    {
    SWITCH(get_last(nChan_BTN))
    {
        CASE 12:
        {
        do_release(vdvDevices,VOL_UP)
        }
        CASE 13:
        {

        do_release(vdvDevices,VOL_DN)
        }
        case 29:
        {
        send_level vdvDevices , vol_lvl,nLevel
        }

    }

    }
 /*   HOLD[3,REPEAT]:
    {
    SWITCH(get_last(nChan_BTN))
    {
        CASE 12:
        {
        do_push(vdvDevices,VOL_UP)
        }
        CASE 13:
        {

        do_push(vdvDevices,VOL_DN)
        }
    }
    }*/
}
LEVEL_EVENT[dvTP_DPS, TP_LEVEL]
{

        if (!bNoLevelReset)
        {
           nLevel = Level.value
        }

}

LEVEL_EVENT[vdvDevices, vol_lvl]
{
   if (!bNoLevelReset)
   {
    nLevel = level.value
    send_level dvTP_DPS, TP_LEVEL,nLevel

    }
}
LEVEL_EVENT[vdvDevices, 2]
{

    send_level dvTP_DPS, TP_LEVEL,level.value


}
(***********************************************************)
(*            THE ACTUAL PROGRAM GOES BELOW                *)
(***********************************************************)
DEFINE_PROGRAM
// feedback
{
    stack_var integer y
    for (y=1;y<=max_length_array(dvTP_DPS);y++)
    {
//    [dvTP_DPS[y],nCHAN_BTN[1]] = [vdvDevices,POWER_FB]
//    [dvTP_DPS[y],nCHAN_BTN[2]] =! [vdvDevices,POWER_FB]
    [dvTP_DPS[y],nCHAN_BTN[11]] = [vdvDevices,VOL_MUTE_FB]
    }
}
MODULE_NAME='Onkyo AVRTXNR818_Comm'(DEV dvAVR[],dev vdvAVR[])
(*******************

    (c) 2015 Favante Pvt. Ltd

    Array-based avr module
    INPUT state is also guaranteed, INPUT changes are memorized and sent once the AVR has warmed up.

    Supported SNAPI constants/channels for pulse[] control:

    PWR_ON
    PWR_OFF
    SOURCE_VIDEO1    - INPUT 'HDMI'


    Examples: pulse[vdvAVR[1],PWR_ON]          // turn AVR 1 on
            pulse[vdvAVR[4],SOURCE_VIDEO3]  // set AVR 4 INPUT to DVI

    SEND_COMMAND:

    INPUT=<HDMI1>

    SEND_STRING feedback:

    INPUT=<HDMI1>

    Feedback channels:

    255    = power
    254    = (ON=COOLING)
    253    = (ON=WARMING)



*******************)


#include 'snapi.axi'
#include 'math.axi'
#include '_LIB_STRING.axi'
//#define _DBG_

DEFINE_CONSTANT
cMIN_LEVEL_VALUE=0
cMAX_LEVEL_VALUE =64
cMAX_HEXLEVEL_VALUE =100
cTIMEOUT        =5
cMAX_TX_BUFFER        = $1F
cMAX_AVRS        = 1        // max AVRS

cPROPERTY_POWER        = 1
cPROPERTY_MUTE        = 2
cPROPERTY_INPUT        = 3
cPROPERTY_VOLUME        =4

cMAX_PROPERTY        = 4        // max AVR properties (must be <= length_array(sQUERY_PROPERTY))

cPOWER_OFF        = 0
cPOWER_ON        = 1

CHAR cSTX        [2]    = '!1'
cETX            = $0D
CHAR CMD_INPUT_SELECT[3] = 'SLI' ;

DEFINE_CONSTANT //COMMANDS & RESPONSES SURROUND MODES

CHAR CMD_SUR_MODE[3] = 'LMD' ;

DEFINE_TYPE
struct stBUFFER
{
    integer     P1
    integer     P2
    integer     TIMEOUT
    char    CMD[cMAX_TX_BUFFER][20]
    integer    PROPERTY_QUERY
    integer    USER_POWER
    char    PROPERTY[cMAX_PROPERTY][10]
}

DEFINE_VARIABLE
VOLATILE INTEGER bNoLevelReset = FALSE
volatile stBUFFER _AVR[cMAX_AVRS]

#warn 'add/delete available AVR properties here'
// new properties can be added/deleted by specifying their query string below
// set cMAX_PROPERTY equal to number of properties you want to query
volatile sQUERY_PROPERTY[][10]=
{
    'PWR',        //1
    'AMT',             //2
    'SLI',             //3
    'MVL'        //4

}



#if_defined _DBG_
DEFINE_FUNCTION char[25] dpstoa(dev dv)        // returns [D:P:S] of dv
{
    return ("'[',itoa(dv.number),':',itoa(dv.port),':',itoa(dv.system),']'")
}
#end_if

DEFINE_FUNCTION integer fnPROPERTY_INDEX(char PROPERTY[])
{
    stack_var integer y
    for (y=1;y<=cMAX_PROPERTY;y++)
    if (sQUERY_PROPERTY[y]=PROPERTY)
        return (y)

    return (0)
}

DEFINE_FUNCTION fnFLUSH_AVR()
{
    stack_var integer device,n
    stack_var char CMD[20],PROPERTY[10]

    for (device=1;device<=max_length_array(vdvAVR);device++)
    IF ((_AVR[device].P1<>_AVR[device].P2) AND (! _AVR[device].TIMEOUT))
    {
        CMD=_AVR[device].CMD[_AVR[device].P2]
        SEND_STRING dvAVR[device],CMD

        #if_defined _DBG_
        send_string 0,"'To AVR ',dpstoa(dvAVR[device]),': ',CMD"
        #end_if

        n=find_string(CMD,'QSTN',1)

        if (n)
        {//Line    107 (21:13:00):: String To [5001:5:2]-[!1SLIQSTN$0D]
        PROPERTY=MID_STRING(CMD, 3, 3)//CMD="'!1SLIQSTN',$0D",Property=SLI
        _AVR[device].PROPERTY_QUERY=fnPROPERTY_INDEX(PROPERTY)
        }
        else
        _AVR[device].PROPERTY_QUERY=0

        _AVR[device].P2++
        IF (_AVR[device].P2>cMAX_TX_BUFFER)
        _AVR[device].P2=1

        _AVR[device].TIMEOUT=cTIMEOUT
    }
}

DEFINE_FUNCTION fnLOAD_AVR (integer device,CHAR CMD[])            // commands to MATRIX transmit buffer
{
    _AVR[device].CMD[_AVR[device].P1]=CMD

    _AVR[device].P1++
    IF (_AVR[device].P1>cMAX_TX_BUFFER)
    _AVR[device].P1=1
}

DEFINE_FUNCTION fnTIMEOUT()
{
    stack_var integer device

    for (device=1;device<=max_length_array(vdvAVR);device++)
    if (_AVR[device].TIMEOUT)
        _AVR[device].TIMEOUT--
}
DEFINE_FUNCTION fnQUERY_PROPERTIES()
{
    stack_var integer device,PROP
    for (device=1;device<=max_length_array(vdvAVR);device++)
    {
    for (PROP=2;PROP<=cMAX_PROPERTY;PROP++)
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[PROP],'QSTN',cETX")
    }
}

DEFINE_FUNCTION fnQUERY_POWER()
{
    stack_var integer device,PROP
    for (device=1;device<=max_length_array(vdvAVR);device++)
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_POWER],'QSTN',cETX")

}

DEFINE_FUNCTION fnPARSE(integer device,char data[])
{
    stack_var char val[10]

    if (_AVR[device].PROPERTY_QUERY)
    {

    if (length_array(data)<=12)
    {
//!1SLI03$1A

        val=mid_string(data,6,FIND_STRING(data, "$1A", 1)-6)

         if (find_string(data,"sQUERY_PROPERTY[_AVR[device].PROPERTY_QUERY]",1))
        {
            // check if data has changed
            if (val<>_AVR[device].PROPERTY[_AVR[device].PROPERTY_QUERY])
            switch (_AVR[device].PROPERTY_QUERY)
            {
            case cPROPERTY_POWER:_AVR[device].PROPERTY[cPROPERTY_POWER]=val
            case cPROPERTY_MUTE:_AVR[device].PROPERTY[cPROPERTY_MUTE]=val
            case cPROPERTY_INPUT:_AVR[device].PROPERTY[cPROPERTY_INPUT]=val
            case cPROPERTY_VOLUME:
            {
                _AVR[device].PROPERTY[cPROPERTY_VOLUME]=itoa(hextoi(val))

                SEND_LEVEL vdvAVR[device], 2, fnScale_Range(hextoi(val), cMIN_LEVEL_VALUE, cMAX_HEXLEVEL_VALUE, DUET_MIN_LEVEL_VALUE, DUET_MAX_LEVEL_VALUE  )
            }


            // send 'query_property=val' back to program
            default:send_string vdvAVR,"sQUERY_PROPERTY[_AVR[device].PROPERTY_QUERY],'-',val"
            }
        }
        //_AVR[device].PROPERTY[_AVR[device].PROPERTY_QUERY]=val

        // set initial power state on boot up
        if ((_AVR[device].PROPERTY_QUERY=cPROPERTY_POWER) && (_AVR[device].USER_POWER=$FFFF))
            _AVR[device].USER_POWER= (_AVR[device].PROPERTY[cPROPERTY_POWER]=='01')

        _AVR[device].PROPERTY_QUERY=0
    }
    }
}

DEFINE_FUNCTION fnPOWER()
{

    // if power state differs from user intended, and we're not booting
    if ((_AVR[cMAX_AVRS].USER_POWER<>_AVR[cMAX_AVRS].PROPERTY[cPROPERTY_POWER]) && (_AVR[cMAX_AVRS].USER_POWER<>$FFFF))
        switch (_AVR[cMAX_AVRS].USER_POWER)
        {
        case cPOWER_ON:
        {
            fnLOAD_AVR(cMAX_AVRS,"cSTX,'PWR01',cETX")
            fnLOAD_AVR(cMAX_AVRS,"cSTX,sQUERY_PROPERTY[cPROPERTY_POWER],'QSTN',cETX")
        }
        case cPOWER_OFF:
        {
            fnLOAD_AVR(cMAX_AVRS,"cSTX,'PWR00',cETX")
            fnLOAD_AVR(cMAX_AVRS,"cSTX,sQUERY_PROPERTY[cPROPERTY_POWER],'QSTN',cETX")
        }
        }

}

DEFINE_FUNCTION fnSET_INPUT(integer device,char INPUT[])
{

    // switch video if power on
    if ((_AVR[device].PROPERTY[cPROPERTY_POWER]=='01'))
    {
        if (input<>_AVR[device].PROPERTY[cPROPERTY_INPUT])
        fnLOAD_AVR(device,"cSTX, CMD_INPUT_SELECT,INPUT,cETX")

        _AVR[device].PROPERTY[cPROPERTY_INPUT]=INPUT
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_INPUT],'QSTN',cETX")
    }
}
DEFINE_FUNCTION fnSET_VOLUME(integer device,char volume[])
{

    // switch volume if power on
    if ((_AVR[device].PROPERTY[cPROPERTY_POWER]=='01'))
    {
        volume=itoa( fnScale_Range(atol(volume), DUET_MIN_LEVEL_VALUE, DUET_MAX_LEVEL_VALUE, cMIN_LEVEL_VALUE, cMAX_LEVEL_VALUE  ))

        volume=   sPad (volume, '0', 2, 0)

        _AVR[device].PROPERTY[cPROPERTY_VOLUME]=volume
        fnLOAD_AVR(device,"cSTX,'MVL',volume,cETX")

        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")

    }

    //
}

DEFINE_FUNCTION fnSET_SURROUNDMODES(integer device,char SURROUNDMODES[])
{

    // switch SURROUNDMODES if power on
    if ((_AVR[device].PROPERTY[cPROPERTY_POWER]=='01'))
    {
    fnLOAD_AVR(device,"cSTX, CMD_SUR_MODE,SURROUNDMODES,cETX")

    }
}
DEFINE_START
{
    stack_var integer device

    for (device=1;device<=max_length_array(vdvAVR);device++)
    {
    _AVR[device].P1=1
    _AVR[device].P2=1
    _AVR[device].TIMEOUT=0

    // to prevent the boot zero value of the user_power property from turning off the AVR at boot
    // we set an invalid user power state at boot
    // this will be set to the actual power fb once the AVR has been queried
    // the invalid user state prevents the module from attempting to turn the AVR off until
    // until the module has obtained a valid power status reply
    _AVR[device].USER_POWER=$FFFF
    }
}

DEFINE_EVENT
data_event [vdvAVR]
{
    command:
    {
    char temp[25]
    integer device
    device=get_last(vdvAVR)

    temp=remove_string(data.text,'-',1)
    select
    {
        active (temp='INPUT-'):fnSET_INPUT(device,upper_string(data.text))
        active (temp='SURROUND-'):fnSET_SURROUNDMODES(device,upper_string(data.text))
    }
    }
}

channel_event [vdvAVR,0]
{
    ON:
    {
    stack_var integer device
    device =get_last(vdvAVR)

    switch (channel.channel)
    {
        case PWR_ON:
        {
        _AVR[device].USER_POWER=cPOWER_ON
        fnLOAD_AVR(device,"cSTX,'PWR01',cETX")
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_POWER],'QSTN',cETX")
        }
        case PWR_OFF:
        {
        _AVR[device].USER_POWER=cPOWER_OFF
        fnLOAD_AVR(device,"cSTX,'PWR00',cETX")
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_POWER],'QSTN',cETX")

        }
        case VOL_MUTE:
        {
        if(_AVR[device].PROPERTY[cPROPERTY_MUTE]=='01')
        {
            fnLOAD_AVR(device,"cSTX,'AMT00',cETX")
            _AVR[device].PROPERTY[cPROPERTY_MUTE]='00'
        }
        else
        {
            fnLOAD_AVR(device,"cSTX,'AMT01',cETX")
            _AVR[device].PROPERTY[cPROPERTY_MUTE]='01'
        }
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_MUTE],'QSTN',cETX")
        fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
        }


    }
    }
}

button_event [vdvAVR,0]
{
    PUSH:
    {
    bNoLevelReset = 1
    }
    Release:
    {
    stack_var integer device
    device =get_last(vdvAVR)

    switch (button.input.channel)
    {
        case VOL_UP:
        {
        fnLOAD_AVR(device,"cSTX,'MVLUP',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_MUTE],'QSTN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
        }
        case VOL_DN:
        {
        fnLOAD_AVR(device,"cSTX,'MVLDOWN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_MUTE],'QSTN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
        }
    }
    bNoLevelReset = 0
    fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
    }
  HOLD[3,Repeat]:
    {
    stack_var integer device
    device =get_last(vdvAVR)
    bNoLevelReset = 1
    switch (button.input.channel)
    {
        case VOL_UP:
        {
        fnLOAD_AVR(device,"cSTX,'MVLUP',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_MUTE],'QSTN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
        }
        case VOL_DN:
        {
        fnLOAD_AVR(device,"cSTX,'MVLDOWN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_MUTE],'QSTN',cETX")
        //fnLOAD_AVR(device,"cSTX,sQUERY_PROPERTY[cPROPERTY_VOLUME],'QSTN',cETX")
        }
    }

    }
}
DATA_EVENT [dvAVR]
{
    ONLINE:
    {
    stack_var integer device
    device =get_last(dvAVR)

    SEND_COMMAND dvAVR,'TSET BAUD 9600,N,8,1'
    SEND_COMMAND dvAVR[device],'HSOFF'                // hardware handshaking disabled
    SEND_COMMAND dvAVR[device],'XOFF'                    // software handshaking enabled

    }
    STRING:
    {
    stack_var integer device
    device =get_last(dvAVR)

    #if_defined _DBG_
        send_string 0,"'From AVR ',dpstoa(dvAVR[device]),': ',data.text"
    #end_if

    if (find_string(data.text,"$1A",1))
    {
        _AVR[device].TIMEOUT=0
        fnPARSE(device,data.text)
    }
    }
}
LEVEL_EVENT[vdvAVR, VOL_LVL]
{
    stack_var integer device
    device =get_last(vdvAVR)

//   if ((_AVR[device].PROPERTY_QUERY<>cPROPERTY_VOLUME))
    fnSET_VOLUME(device,itoa(level.value))
}
DEFINE_PROGRAM
wait 1 fnTIMEOUT()
fnFLUSH_AVR()


    if (!bNoLevelReset)
    {
    wait 20
    {
        fnQUERY_POWER()    // check power status
        fnPOWER()        // follow intended user request for power
    }

    // query all other properties
    wait 100 fnQUERY_PROPERTIES()

    }
// feedback
{
    stack_var integer y
    for (y=1;y<=max_length_array(vdvAVR);y++)
    {
    // power fb

    [vdvAVR[y],POWER_FB]= (_AVR[y].PROPERTY[cPROPERTY_POWER]=='01')
    [vdvAVR[y],VOL_MUTE_FB]= (_AVR[y].PROPERTY[cPROPERTY_MUTE]=='01')
    }
}

Comments

  • a_riot42a_riot42 Posts: 1,624
    The answer to your question is yes it can be done better. Personally I would remove everything in define_program for starters and make everything event based. I don't get what you are doing here:
    LEVEL_EVENT[vdvDevices, vol_lvl]
    {    
    if (!bNoLevelReset)  
    {    
    nLevel = level.value    
    send_level dvTP_DPS, TP_LEVEL,nLevel    
     }
    }
    
    LEVEL_EVENT[vdvDevices, 2]
    {      
    send_level dvTP_DPS, TP_LEVEL,level.value  
    }
    
    You seem to have two level events with almost identical code in them. Typically my receiver modules don't deal with UIs in them, only the device. If the main code wants to track something so it can update a panel, then it should listen to the event its interested in and do the appropriate thing outside the module. The module only updates the virtual device. As for updating panels that aren't online, you don't really need to worry about that. The firmware won't send events to panels that are offline. Paul
  • ajish.rajuajish.raju Posts: 185
    I need to clean up my code for the identical code since i had written the code to update the panels.

    Regarding define_program, it has become a habit to do it this way but event based is more better way to go forward.
  • ericmedleyericmedley Posts: 4,177
    My typical method for updating level feedback (or any feedback for that matter) is basically as follows.

    I create event(s) to track when panels are online or not.
    I do all my UI navigation from code - so, I track what page/popups are currently open on any given panel at any given time

    I create a function for each feedback update for a single panel (ex: fn_Update_HVAC_HeatPoint(integer tp_id) )
    I create a companion function to do all UI (ex: fn_Update_HVAC_HeatPoint_All() )

    the _all function is merely a loop that runs through all the UI calling the single panel function.

    in these functions I first test if the panel is online. Then if so, I test to see if the panel is on the UI page/popups pertinent to this particular piece of feedback info. If so, then do whatever update(s) to the feedback. (levels, button states, text buttons, etc...)

    something like:
    define_function fn_Update_HVAC_HeatPoint(integer tp_id){
      if(UI_Current_Mode=UI_Mode_HVAC_01){
        // send all the levels, update the text
       // show/hide buttons, update button feedback
      // notify the proper authorities and fashion news outlets of the change.
      } // def_fun
    
    define_function fn_Update_HVAC_HeatPoint_All(){
      stack_var integer tp_loop;
      for(tp_loop=length_array(TPs);tp_loop;tp_loop--){
        if(TP_Online[tp_loop]){
          fn_Update_HVAC_HeatPoint(tp_loop);
          }
        } // for
      }// def_fun
    

    Having all this framework setup Events drive the feedback runs. You call the appropriate function depending upon the need.

    So, for example if the HVAC Heat point changes (I almost always use a module for HVAC comm, so I get a channel event or level event as a change indicator) I update the value(s) for the HVAC trcking then call the fn_..._All() function since all the UI need the new information. Of course, if they are not online or not currently on the page/popup displaying HVAC Heatpoints, it gets skipped.

    the other events that might call this stuff up are 1) Panel has navigated to the HVAC page or 2) Panel has come online.

    in these cases you only need to call the root function since you're only updating a single UI and not all.

    It may seem like a lot of coding at first, but if you think about how things work, it is much quicker for a processor to just run logic and code than it is for it to communicate to the outside world. So, the more you can do in code to cut down on unnecessary communication to panels the better. It might bother you at night that secretly the panel's Heat Point set is not technically correct it "didn't happen" until the client actually calls up the page to see it. So, you just run the functions to update the panel just before calling up the page/popup they are wanting to see. the slight delay prior to them hitting the HVAC button and the page/popup is actually much shorter than the time it takes the message to be sent from the master to the UI over ethernet. (around 20-50 ms)

    That's how I do it.
  • RaphayoRaphayo Posts: 111
    Eric,

    I think i will use your approach in future for loop, good idea doing it reversed.
  • ericmedleyericmedley Posts: 4,177
    Raphayo wrote: »
    Eric,

    I think i will use your approach in future for loop, good idea doing it reversed.


    Backwards Loops is an "Old Saw" here on the forum. They are elegant, fast and preferable when possible. There are times where you do have to go forwards, however.
  • ericmedley wrote: »


    Backwards Loops is an "Old Saw" here on the forum. They are elegant, fast and preferable when possible. There are times where you do have to go forwards, however.

    Thank you, Eric, that is a very useful "Old Saw". We have lots of things that need to go forward, still the backwards loop is preferable. When it must or should go forward, I use a Forward Backward Loop, something like:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
       STACK_VAR INTEGER Max;
       STACK_VAR INTEGER TP_Loop;
       STACK_VAR INTEGER TP_Num;
       Max = LENGTH_ARRAY(TPs)
       TP_Num = 1;
       FOR(TP_Loop = Max; TP_Loop; TP_Loop--) {
        IF(TP_Online[TP_Num]) {
           fn_Update_HVAC_HeatPoint (TP_Num)
           }
        TP_Num++
        }
       }    // Best so far ...
    

    Is there is a better way?

    Even with the extra math to make the Backward Loop go forward, it is so much faster than how it was done previously:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
       STACK_VAR INTEGER TP_Num;
       FOR(TP_Num = 1; TP_Num <= LENGTH_ARRAY(TPs); TP_Num++) {
        IF(TP_Online[TP_Num]) {
           fn_Update_HVAC_HeatPoint (TP_Num)
           }
        }
       }    // Original, it worked
    

    But, before I knew about Backward Loops, or at least how to make them go forward, I knew there must be a better way so I had tried:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
       STACK_VAR INTEGER Max;
       STACK_VAR INTEGER TP_Num;
       Max = LENGTH_ARRAY(TPs) + 1;
       FOR(TP_Num = 1; TP_Num < Max; TP_Num++) {
        IF(TP_Online[TP_Num]) {
           fn_Update_HVAC_HeatPoint (TP_Num)
           }
        }
       }    // Better, since we are only pulling up the array (or structure) once.
    

    which was better than the original, but not as good as the Forward Backwards Loop. Or, is it called the Backwards Backwards Loop?

    All of this looping reminds me of a question. Do "<=" and ">=" cost more processor time than the other "single" relational operators? I am guessing they are not as efficient as one single relational operator but more efficient than two single relational operators ( perhaps a bit like "++" and "--").
  • ericmedleyericmedley Posts: 4,177
    TechmoTim wrote: »

    Thank you, Eric, that is a very useful "Old Saw". We have lots of things that need to go forward, still the backwards loop is preferable. When it must or should go forward, I use a Forward Backward Loop, something like:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
    STACK_VAR INTEGER Max;
    STACK_VAR INTEGER TP_Loop;
    STACK_VAR INTEGER TP_Num;
    Max = LENGTH_ARRAY(TPs)
    TP_Num = 1;
    FOR(TP_Loop = Max; TP_Loop; TP_Loop--) {
    IF(TP_Online[TP_Num]) {
    fn_Update_HVAC_HeatPoint (TP_Num)
    }
    TP_Num++
    }
    } // Best so far ...
    

    Is there is a better way?

    Even with the extra math to make the Backward Loop go forward, it is so much faster than how it was done previously:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
    STACK_VAR INTEGER TP_Num;
    FOR(TP_Num = 1; TP_Num <= LENGTH_ARRAY(TPs); TP_Num++) {
    IF(TP_Online[TP_Num]) {
    fn_Update_HVAC_HeatPoint (TP_Num)
    }
    }
    } // Original, it worked
    

    But, before I knew about Backward Loops, or at least how to make them go forward, I knew there must be a better way so I had tried:
    DEFINE_FUNCTION fn_Update_HVAC_HeatPoint_All () {
    STACK_VAR INTEGER Max;
    STACK_VAR INTEGER TP_Num;
    Max = LENGTH_ARRAY(TPs) + 1;
    FOR(TP_Num = 1; TP_Num < Max; TP_Num++) {
    IF(TP_Online[TP_Num]) {
    fn_Update_HVAC_HeatPoint (TP_Num)
    }
    }
    } // Better, since we are only pulling up the array (or structure) once.
    

    which was better than the original, but not as good as the Forward Backwards Loop. Or, is it called the Backwards Backwards Loop?

    All of this looping reminds me of a question. Do "<=" and ">=" cost more processor time than the other "single" relational operators? I am guessing they are not as efficient as one single relational operator but more efficient than two single relational operators ( perhaps a bit like "++" and "--").



    The main thing that makes the loop go faster is the center item in the for loop declaration.

    for(nloop=len_array;<this>nloop<this>;nloop--)

    This section ultimately is looking for a "True" or "False" ("1" or "0")

    The flexibility of the programming environment allows us to put a pretty complex statement in this section (if we desire) to determine what "done" looks like. However, this flexibility comes at a cost. Any math/conditionals we throw in there result in a side trip down Math Operation Way.... It's this that can actually add up over the span of the loop. For example ">=" is really two full operations. plus the final "Is it true or false" operation when it's all done.

    So, the fewer amount of math (or none in the case of ;nloop; none) results in fewer trips to the math operator.

    Now, putting more math inside the loop to make it run forwards doesn't necessarily help in that you are still making multiple trips to Math Operator.

    In Netlinx, I find that in most cases - the only way a reverse loop is useful is when you really don't care to use the nloop for any conditionals. If all I'm doing is running through and array of devices it doesn't really matter which one goes first or last. It will happen so quickly nobody will care.

    But, If I'm parsing through a large array of a CSV file, and that file's cells are number incrementally, and I need to match up my loop count with the cell IDs of the CSV, then I do care and the forward loop results in the fewest possible trips to the Math processor.

    There's nothing really magical about it at all. It's, as you essentially say, just thinking it through...
  • mstocummstocum Posts: 120
    TechmoTim wrote: »
    All of this looping reminds me of a question. Do "<=" and ">=" cost more processor time than the other "single" relational operators? I am guessing they are not as efficient as one single relational operator but more efficient than two single relational operators ( perhaps a bit like "++" and "--").

    While I can't say for certain with NetLinx, on most systems, a > comparison and a >= comparison are identical in terms of speed. The real benefit of doing for(i=length_array(x); i>0; i--) compared to for(i=1; i<=length_array(x); i++) is that in for (<A>; <B>; <C>) <A> is only called on the before the first pass through the loop. <B> is called at the start of every loop, before it executes, and <C> is called at the end of every loop after it executes. In the "backwards loop" approach you're only calling length_array(x) once and doing a simple variable comparison on each loop, where in the traditional approach, you're calling it before every loop and still doing the comparison. Comparisons are cheap, but function calls are expensive. If you prefer the normal approach, the following would work:
    stack_var integer arraySize;
    stack_var integer i;
    
    arraySize = length_array(x);
    for (i = 1; i <= arraySize; i++) {
        // do stuff
    }
    

    Technically that would be a hair slower, as you're doing an extra assignment, and comparisons to a constant may be a hair cheaper than to a variable (although it wouldn't surprise me if in NetLinx they're not). At that point, however, you're chasing the wrong optimizations. The NI processors are slow, but they're not THAT slow.
Sign In or Register to comment.