Home AMX User Forum NetLinx Studio

Module Creation - Best Practices

I am creating some modules and I was wondering about best practices in handling/passing variables.

For example, I've made a module for a Biamp Nexia unit. I am controlling and tracking the unit through the module (I use a send_string to a virtual device with something like "Single Fader Mute <Off> <1>". The module receives this, processes it, and then sends the appropriate string to the Nexia unit and the Nexia unit sends the appropriate response back to the module where the Module processes the response.

Currently, in the define_module section I pass all the variables to be updated such as mute status, gain status, etc. Each grouping is a separate array (Mutes, Gains, Matrix, Other) and the module then updates those values based on return strings. As my module gets more complex I'm having to send quite a few different sets of arrays and I was wondering, is there an easier way to do this?

Comments

  • HedbergHedberg Posts: 671
    Probably you should take a look at the documentation for a couple AMX modules to see how it's done. Basically, any strings you want to send to the module gets sent via send_command rather than send_string. Status information between your module and the rest of your code gets handled through channel events and level events. You don't really send variables between your module and the rest of your code -- the channel events and level events take care of that.

    Of course, you can do whatever you want and design your modules any way you want, but there are good reasons to write your modules to function in the "standard" way.
  • ericmedleyericmedley Posts: 4,177
    To amplify the previous post... I would try to avoid passing any variables into the communication module. Just pass in a virtual device that sends and receives instructions to the device to be controlled and a standardized set of feedback from the device.

    Any variable initiatin you need should be passed in as a command. You will find after doing his for a while you will come up with some standards on commands.

    An easy example of this is the baud rate of a serial device. I never hard code the baud rate in he module because I don't know how many times I've seen a manufacturer change baud rates thus breaking my module.

    Another thing too that will make your UI code better is two things:

    1). Make sure your UI code can handle multiple panels with he possibility of differing ports on ech panel and
    2) the ability of the UI code to NOT send feedback to panels not currently controlling the device in question. (Which, of course, implies the UI having the ability to trck when a panel is in control and even a hierarchy in case you allow multiple simultaneous controllers or require only one at a time.

    Those are my suggestions.
  • tomktomk Posts: 24
    A heads-up if you're using channels and levels with your own virtual devices: by default it will only give you channels 1-255 and levels 1-8 on a single port. Trying to use any higher numbers will result in mysterious failure to operate. See SET_VIRTUAL_CHANNEL_COUNT and SET_VIRTUAL_LEVEL_COUNT in the NetLinx language reference guide.

    For setting string values (or numerical values not suited to levels) have a look at the ^TXT-type methods used for setting button strings in touch panels. The exact format varies between modules but schemes like these seem to be popular:
    SEND_COMMAND dv,"opcode,address,value";
    (When sending a numerical "address" be very careful to define whether you're sending it as a binary byte 0-255 or as an ASCII integer itoa(address) '0', '123', '1024' etc.)

    For setting more complex values you can use commands and strings again, except load them into a structure and use VARIABLE_TO_STRING to convert it to a form that can be sent to the module. It can then turn it back into a structure using STRING_TO_VARIABLE.
  • a_riot42a_riot42 Posts: 1,624
    There are many ways to skin this cat, but I think the basic rules of thumb are to use channels for on/off states, levels for variable states (0-100), and send_commands for commands with arguments that need to be parsed. I try to use channels and levels only as much as possible, and only send_commands when I have to. But its a fluid situation. If you have a variable with 4 states like a projector power on/off/warm/cool, you can either use 4 channels, 1 level, or a send_command. I use channels for that usually but my UI module will update the UI with a send_command so there are no hard and fast rules.

    I never use send_string to communicate with a module. For comm modules I usually only have 2 arguments, the device and the virtual device, but for UI modules there can be 3 to 8 arguments. Any feedback goes to the virtual and main code listens for changes on that virtual. Since I author most of my own modules, I often break my own rules depending on the situation. For instance, I'll hard code the baud rate and just change it in the module if it needs to change rather than passing it in, sometime I pass in the button channels, other times hard code them. Best to flexible and not force yourself into corners.
    Paul
  • JasonSJasonS Posts: 229
    I personally hate UI Modules, and the integer arrays that go with them. I write channel events and channel feedback into my comm modules, trying to use SNAPI channels as much as possible. Any text feedback is handled by catching strings coming out of the comm module and sending them to the appropriate touch panel. This makes controlling a device very easy from main code, you have a port on each touchpanel dedicated to the device to be controlled, and "forward" the pushes. It is very easy to do redirection and to reuse Device pages for multiple devices.
    Button_Event [tpDevice, 0]
    {
        TO [vdvDevice, Button.Input.Channel]
    }
    

    Then all you need is a loop wherever you prefer to do feedback.
    FOR (i = 1; i <= 255; i++)
    {
        [tpDevice, i] = [vdvDevice,i]
    }
    
    tomk wrote:
    For setting more complex values you can use commands and strings again, except load them into a structure and use VARIABLE_TO_STRING to convert it to a form that can be sent to the module. It can then turn it back into a structure using STRING_TO_VARIABLE.

    This is the method I use in my nexia/audia module. I have a common structure between my comm module and an include. All the addresses of the nexia blocks I want to communicate with are stored in the include. At startup I generate and store all the block addresses' variable to string value, any command sent to the module for controlling a nexia block has its variable to string appended to the end. The module decodes the string and has all the neccessary info to assemble the address portion of the command. When I first wrote the code it was an experiment to see if it would be fast enough to be usefull, suprisingly it is. When any information is recevied by the module from the nexia the address is variable to string encoded and the data and address are sent back to the include, where the address is decoded and checked against the database and stored in a "values" database if a match is found. All the values for any audia/nexia block (excluding text) can be stored in 8 integers, 4 signed integers, a long, and a float.
  • ericmedleyericmedley Posts: 4,177
    JasonS wrote: »
    I personally hate UI Modules, and the integer arrays that go with them. I write channel events and channel feedback into my comm modules, trying to use SNAPI channels as much as possible. Any text feedback is handled by catching strings coming out of the comm module and sending them to the appropriate touch panel. This makes controlling a device very easy from main code, you have a port on each touchpanel dedicated to the device to be controlled, and "forward" the pushes. It is very easy to do redirection and to reuse Device pages for multiple devices.
    Button_Event [tpDevice, 0]
    {
        TO [vdvDevice, Button.Input.Channel]
    }
    

    Then all you need is a loop wherever you prefer to do feedback.
    FOR (i = 1; i <= 255; i++)
    {
        [tpDevice, i] = [vdvDevice,i]
    }
    



    This is the method I use in my nexia/audia module. I have a common structure between my comm module and an include. All the addresses of the nexia blocks I want to communicate with are stored in the include. At startup I generate and store all the block addresses' variable to string value, any command sent to the module for controlling a nexia block has its variable to string appended to the end. The module decodes the string and has all the neccessary info to assemble the address portion of the command. When I first wrote the code it was an experiment to see if it would be fast enough to be usefull, suprisingly it is. When any information is recevied by the module from the nexia the address is variable to string encoded and the data and address are sent back to the include, where the address is decoded and checked against the database and stored in a "values" database if a match is found. All the values for any audia/nexia block (excluding text) can be stored in 8 integers, 4 signed integers, a long, and a float.

    I agree with your dislike of UI modules. I tend to like keeping them as Includes and what I'd call semi-customizable code. For one, there always seems to be Something that needs to be customized in one UI to another. So, I really try and keep the UI functions fairly standardized in regards to how they communicate to the Comm modules. but they're fairly free to be customized for how that then gets to and from the UI.
  • a_riot42a_riot42 Posts: 1,624
    There doesn't seem to be any one best way. I prefer to not control devices from main code at all, only modules, so once they are written, I don't have to look at them or write any code to control that device ever again. Its plug and play, sort of, unless the user wants something unusual, the new firmware has bugs that require workarounds, a different territory has different requirements, or any other number of wrenches get thrown at me. This is why I like flexibility over 'standards'.
    Paul
  • JasonSJasonS Posts: 229
    I've had an internal argument going with myself for the last year trying to convince myself that I would be better off adopting AMX SNAPI standard commands for all my modules so I don't have to write RMS monitor modules for them. I haven't convinced myself yet.
  • rfletcherrfletcher Posts: 217
    JasonS wrote: »
    I've had an internal argument going with myself for the last year trying to convince myself that I would be better off adopting AMX SNAPI standard commands for all my modules so I don't have to write RMS monitor modules for them. I haven't convinced myself yet.

    I really wish someone had actually told me SNAPI existed when I was at the programmer courses (or maybe they did and I missed it). By the time I found about it I had already developed my own standard and written many modules to it. I have thus far not been able to convince myself that the time/effort to convert all my modules to SNAPI would be in any way worth it.
  • rfletcherrfletcher Posts: 217
    One thing I do that I find helps speed up the programming process a bit is to create an include file that goes with my COM modules. I'll stick descriptive constants for any constant values used in communication with the module and create functions that encapsulate most or all of the send_commands used by the module. That way I can use autocomplete for those functions and also get compile-time errors on typos instead of having to figure out why something isn't working at run-time even though I can see it in the code (damn you brain and your background correction of spelling mistakes :p).

    There are only two circumstances where I pass any variables to a COM module other than the physical and virtual devices. One, if the module talks to other modules it'll have additional parameters for those virtual devices, and two, the ClearOne ConvergePro module I wrote. There was no way I was going to create a device with over 1900 levels or a device with 43 ports each with 45 levels to provide feedback from the matrix in those boxes. :eek: Instead I pass in a couple of arrays, one for mute state and one for level value.
  • rfletcherrfletcher Posts: 217
    JasonS wrote: »
    This is the method I use in my nexia/audia module. I have a common structure between my comm module and an include. All the addresses of the nexia blocks I want to communicate with are stored in the include. At startup I generate and store all the block addresses' variable to string value, any command sent to the module for controlling a nexia block has its variable to string appended to the end. The module decodes the string and has all the neccessary info to assemble the address portion of the command. When I first wrote the code it was an experiment to see if it would be fast enough to be usefull, suprisingly it is. When any information is recevied by the module from the nexia the address is variable to string encoded and the data and address are sent back to the include, where the address is decoded and checked against the database and stored in a "values" database if a match is found. All the values for any audia/nexia block (excluding text) can be stored in 8 integers, 4 signed integers, a long, and a float.

    It's always interesting to see all the different ways people solve the same problems. When I sat down to build a module for Nexia/Audia control I couldn't come up with any solution for dealing with the flexible architecture that I liked in a single module (probably because I never remember that things like string_to_variable exist :D), so I wound up dispersing it into multiple modules. One central module handles all the communications back and forth, and then sub-modules for the different types of blocks you want to control. The main module keeps track of what sub-modules you've declared and sorts feedback from the Nexia/Audia to the appropriate sub-module.
  • a_riot42a_riot42 Posts: 1,624
    rfletcher wrote: »
    I really wish someone had actually told me SNAPI existed when I was at the programmer courses (or maybe they did and I missed it). By the time I found about it I had already developed my own standard and written many modules to it. I have thus far not been able to convince myself that the time/effort to convert all my modules to SNAPI would be in any way worth it.

    Like most standards its just agreed arbitrariness, so your standard is probably just as "good" as SNAPIs.
    Paul
  • DHawthorneDHawthorne Posts: 4,584
    JasonS wrote: »
    I've had an internal argument going with myself for the last year trying to convince myself that I would be better off adopting AMX SNAPI standard commands for all my modules so I don't have to write RMS monitor modules for them. I haven't convinced myself yet.

    SNAPI is a mess, because it has to be comprehensive, and most of the time, we don't need comprehensive control. For example, if I'm doing a surround receiver, I need source selection, volume, and surround mode ... I don't need, except in special cases, graphic EQ or individual channel levels, because I do that all when I set it up and don't touch it later. As a result, I've kind of created my own API rules, because I don't need every device to have UI access to every available function. I want it streamlined.
  • DHawthorneDHawthorne Posts: 4,584
    But anyway, on the main thread topic, I *only* pass parameters to my modules that are needed outside the module, and anything I can keep internal, I keep internal. If I can pass it with a channel or a level on a virtual, I'll do that. All my modules use a virtual interface, and, as I said above, I have my own API on how to call up functions which I do pretty much exclusively through SEND_COMMANDs. I will roll a UI module if I have to, but mostly I try to keep it as simple as possible and use SEND_COMMAND to talk to the module, and the STRING event on the virtual to pass stuff back to the calling program.

    I absolutely cannot stand putting running code in an INCLUDE (just a personal preference). I almost never have only one of a device in a system, and the built-in instancing of module code is far too convenient to ignore when you have half a dozen cable boxes, or whatever, on a system.
Sign In or Register to comment.