Home AMX User Forum NetLinx Studio
Options

device arrays and functions

Hi there,

I seem to get intermittent results passing DEV arrays to functions depending on how
they are defined.

Suppose I have the following program:
DEFINE_VARIABLE
DEV vdTP = {TP1, TP2, TP3}

DEFINE_FUNCTION myFunc(DEV d[], integer b, char msg[]) {
SEND_COMMAND d, "'Text', ITOA(b), '-', msg"
}

DATA_EVENT[vdTP] {
STRING: {
stack_var DEV myDevArray[1]
myDevArray[1] = Data.Device
myFunc(vdTP, 100, 'message for 100')
myFunc(myDevArray, 101, 'message for 101')
}
}

In this case, 'message for 100' would appear on button 100 on all panels, but 'message for 101' wouldn't appear on the active Data.Device.

The only reason I'm using the stack_var myDevArray is so I can use the same functions for single panels and groups of panels. If there is another way to pass a single device or a device array to a user defined function (so the function can mimic the SEND_COMMAND feature) I'd happily use that).

Thanks for any help,
David

Comments

  • Options
    Joe HebertJoe Hebert Posts: 2,159
    In your example, the DEV array myDevArray has a length of 0 when the FUNCTION myFunc is called.

    After the declaration of myDevArray and before you call myFunc, add the line:

    SET_LENGTH_ARRAY(myDevArray,1)

    and you should get the results you are looking for.
    dnahman wrote:
    Suppose I have the following program:
    DEFINE_VARIABLE
    DEV vdTP = {TP1, TP2, TP3}

    DEFINE_FUNCTION myFunc(DEV d[], integer b, char msg[]) {
    SEND_COMMAND d, "'Text', ITOA(b), '-', msg"
    }

    DATA_EVENT[vdTP] {
    STRING: {
    stack_var DEV myDevArray[1]
    myDevArray[1] = Data.Device
    myFunc(vdTP, 100, 'message for 100')
    myFunc(myDevArray, 101, 'message for 101')
    }
    }

    In this case, 'message for 100' would appear on button 100 on all panels, but 'message for 101' wouldn't appear on the active Data.Device.
  • Options
    jjamesjjames Posts: 2,908
    My question would be: why even write a function? Calling a function is one line of code, and sending text to a panel (or panels) is also one line of code. Kind of defeats the purpose. But - at least you've got your answer so if you want to expand on your function, you know what you need to do.
  • Options
    jjames wrote:
    My question would be: why even write a function? Calling a function is one line of code, and sending text to a panel (or panels) is also one line of code. Kind of defeats the purpose.

    Woo hoo, big philosophical question. I hope I can answer it, let me try.

    I refer to simple functions that do "not very much" as "wrapper" functions - they wrap around basic language / environment / close to the metal functions which seem simple but can get messy:

    (a) It doesn't really cost very much to use a wrapper function instead of native code. You have to write the function as well, but you only have to write it once, and you can use it endlessly, year after year, confident in the knowledge that it will work.

    (b) The name of the function can reflect what you are achieving, instead of what the code is literally doing; and it can use your consistent self-documenting high-level terminology, instead of the abbreviated low-level terminology that the compiler writer invented.

    (c) The function can easily be changed do different things in different circumstances, and yet be called in the same way without the application code having to think about it or be aware of it.

    eg G3 and G4 touchpanels have a number of arbitrarily different commands; the day you have to mix a legacy G3 panel into a G4 environment is the day you will be grateful that all touchpanel commands go through one or a finite set of wrappers.

    (d) The function can be laden with self-checks, assertion checks, debug readouts. Just comment them out when you don't need them, and comment them in when you need to see what's happening.

    Add them when you need them - you only have to write them once, and they generally don't need debugging, because they are themselves calls to reliable wrapper functions.

    Assertion checking isn't much use post commissioning, but during the debug phase it's treasure. Now you can write your function calls as slapdashedly as you like, knowing that the function checks will pick up your blunders. You may feel that the processor load is uncalled for - and you may be right - but it's tiny.

    You can even pass a string to the wrapper on every invocation, which documents the purpose of the call or the routine that is calling it. The string is passed by reference, so it has zero processor load (yes really: it costs no significant processor time to pass a literal string to a function), but in a busy event-driven system, it is gold when you are trying to debug a problem.
  • Options
    Any kind of code that I think will be used in the future on some other job I try to put in a function, even if it is just one line, for exactly the reasons that NMarkRoberts pointed out. When I'm building a new project it's easier to just grab the functions I need then to dig through code to find all the send_commands or whatever. More and more I'm trying to make everything modular.
  • Options
    jjamesjjames Posts: 2,908
    You both have good points . . . I just don't see what the difference / benefit is:
    DEFINE_FUNCTION fnOpenPopup(DEV TP, CHAR _Page[12])
    {
       SEND_COMMAND TP,"'@PPN-',_Page"
    }
    // or . . .
    SEND_COMMAND dvTP,"'@PPN-PAGE1'"
    

    Granted, a very simple example, but if you're saying you'd rather use the function over the SEND_COMMAND line - I'm just not seeing the benefit except that it spells out what you're trying to do. The *only* benefit I could possibly ever see is wrapping (as said above) commands used for G3 & G4 panels. I know that it's a PITA to mix two different generation panels on a job, been there, done that.

    I use functions when I have a portion of code that I'm going to over and over again, such as string manipulation (only the STRING_REPLACE and STRING_REMOVE functions found on the forum a while back), system management, pop-up/page tracking, etc.

    It all boils down to style though and nothing is wrong with either way. But I would *guess* that it would drive a programmer at AMX nuts going through someone's code that references a function everytime you want to open up a page, or add two numbers together. But . . . it's all according to your style and ultimately doesn't matter. The main goal in every system is to make sure it works everytime the client hits a button.
  • Options
    A matter of style

    As I have always said there are as many ways to program as there are programmers. ;)

    Where are the programming police when you need them? :D

    You got to love the passion on this forum. Thanks for all the contributions. :)
  • Options
    Hi Joe,

    Thanks for your reply. As you pointed out, the set_length_array is absolutely necessary.

    But the other odd thing I have discovered is that when you populate myDevArray[1] with Button.Input.Device, or Data.Device it doesn't work, but if you populate it with a 10003:1:1 literal or a dvTP constant of the same value it does. But it turns out this has nothing to do with the DEV array, but something odd with Data.Device.

    In my ONLINE event for the touchpanel, I verified that:
    send_command dvTP, 'Text255-mytext' works,
    but
    send_command Data.Device, 'Text255-mytext' doesn't.

    I've got to be missing something really simple here, but I can't figure out what. I'm sure this has worked for me in the past. I'm using v2.3.0.0 of the compiler.

    Thanks for any insights!
    --david


    Joe Hebert wrote:
    In your example, the DEV array myDevArray has a length of 0 when the FUNCTION myFunc is called.

    After the declaration of myDevArray and before you call myFunc, add the line:

    SET_LENGTH_ARRAY(myDevArray,1)

    and you should get the results you are looking for.
  • Options
    mpullinmpullin Posts: 949
    wrapper functions

    I used to have a function for when I wanted to send a message for all touchpanels in the system.
    DEFINE_FUNCTION CMD_ALL_TP(CHAR cmd[]){ // send to all tp
        for(k=1; k<= numPANELS; k++){
    	SEND_COMMAND arrTP[k], cmd
        }
    }
    
    Then when I dried off behind the ears, I realized NetLinx builds you in a quick and easy to do this:
    DEFINE_FUNCTION CMD_ALL_TP(CHAR cmd[]){ // send to all tp
        //for(k=1; k<= numPANELS; k++){
    	//SEND_COMMAND arrTP[k], cmd
        //}
        SEND_COMMAND arrTP, cmd // equivilant
    }
    
    So I was able to just change my code in one place. A case where having a wrapper function made life much easier down the road. Even jjames' fnOpenPopup seems silly right now, but if it's being written inside a module, then the user could enter a check into this function to make sure the device the module is supposed to control is being used, before popping up the page in question. If only the MAX UI programmers had done this! ;)
  • Options
    Joe HebertJoe Hebert Posts: 2,159
    Hi David,

    I was able to get your original example to work via the STRING: handler after the SET_LENGTH_ARRAY command was added. I was also able to get the ONLINE event to work correctly for DATA.DEVICE.

    Can you post the offending code? I don't see anything wrong with what you've posted so far.

    Joe
    dnahman wrote:
    But the other odd thing I have discovered is that when you populate myDevArray[1] with Button.Input.Device, or Data.Device it doesn't work, but if you populate it with a 10003:1:1 literal or a dvTP constant of the same value it does. But it turns out this has nothing to do with the DEV array, but something odd with Data.Device.

    In my ONLINE event for the touchpanel, I verified that:
    send_command dvTP, 'Text255-mytext' works,
    but
    send_command Data.Device, 'Text255-mytext' doesn't.

    I've got to be missing something really simple here, but I can't figure out what. I'm sure this has worked for me in the past. I'm using v2.3.0.0 of the compiler.
  • Options
    Joe,

    I found the problem -- I had put a wait (10) in my online handler while trying to debug the original problem with the stack_var array (which you pointed out in your first reply). I think what was happening was the value of Data.Device was getting corrupted by the time that the body of the wait(10) got executed, and the send_command was going to the wrong place.

    So: same symptom, different cause from the original post.

    I'd love it if AMX implemented something akin to a fork() or a POSIX pthread. I've learned and forgotten this same lesson using waits more times than I can count.

    Thanks for your help,
    David
  • Options
    mpullinmpullin Posts: 949
    dnahman wrote:
    I'd love it if AMX implemented something akin to a fork() or a POSIX pthread.
    Most of us would give a first born child just to see pointers implemented! :)
  • Options
    Joe HebertJoe Hebert Posts: 2,159
    dnahman wrote:
    I found the problem -- I had put a wait (10) in my online handler while trying to debug the original problem with the stack_var array...
    Yeah, I've tripped over that one before also. Glad you are up and running.
Sign In or Register to comment.