Home AMX User Forum NetLinx Studio

Program Communications

I would like to get some thoughts on inter-program communications styles some of you use. I like the use of modules to isolate specific functions but you can't seem to expose a module "API" that would allow you to pass data.

For example a thermostat module might handle the communications and current settings with the device, and might have functions like GetCurrentTemp() and SetNewSetpoint(), etc. Other modules would then be able to interact with these functions. A module talking to the security system might determine that a bunch of windows have been opened and automatically tell the thermostat to set its mode to off.

Creating a bunch of variables in the main program that tie module parameters together is one way, but that gets pretty messy as the program grows. I am starting to think that virtual devices coupled with a homegrown message protocol is probably the best way, a la Windows programming. One module could do a SEND_LEVEL to a device channel holding the thermostat setpoint, the thermostat module would then update the device and do a SEND_COMMAND as "NEWSPT ACTIVE" back to the virtual device so that other interested modules, a UI module for example, can update accordingly.

Any other ideas?

Thanks

Comments

  • That's a good idea Sonny, using a virtual device for communication between modules and the program and using your own send_commands.

    You can also use levels and channels on that virtual device. Channels for simple on/off stuff, or send_level to pass one variable value using LEVEL.VALUE.

    If you want to get fancy you can also use VARIABLE_TO_XML and XML_TO_VARIABLE. I haven't had a lot of practice doing this, but in theory you should be able to pass large structures and such. Also take a look at VARIABLE_TO_STRING and STRING_TO_VARIABLE keywords.
  • sonnysonny Posts: 208
    I have something I am working on with the XML stuff that I'm psyched about.

    One thing I haven't figured out how to do (I am a relative newbie to NetLinx programming) is how to get info from Virtual Devices or Touch Panels. i.e. is a channel on or off, what is the current level for a channel. All I have been able to figure out is a module is going to have to keep a variable to store this info. For example, a module sets a level on a lighting control to 30% via a "combined" virtual device. Later a web control comes online, I would like to grab the current level for the display.

    Any ideas?
  • The easiest way I've found is to do a SEND_LEVEL in your module to your global virtual device:

    SEND_LEVEL vdvGlobal,1,77

    Then do a level event in your program:

    LEVEL_EVENT[vdvGlobal,1]
    {
    nVariable = LEVEL.VALUE
    }

    nVariable would be 77 (ie 30%) in this example. If you wanted to update a bargraph or some such you could do it in the level_event as well.

    Although I've never done it, I think you could use VARIABLE_TO_STRING for structures and arrays, do a send_string to the global virtual device, then in the STRING : portion of the data_event do a string to variable to convert the data. That should work in theory, but like I said I've never implemented it before.

    For channel events you would do it like this in the module:

    ON[vdvGlobal,1]

    Then in the main code:

    CHANNEL_EVENT[vdvGlobal,1]
    {
    ON :
    {
    nVariable = 1
    }
    OFF :
    {
    nVariable = 0
    }
    }

    To update your TP you would do something like:

    DATA_EVENT[vdvWebPanel]
    {
    ONLINE:
    {
    SEND_LEVEL vdvWebPanel,1,nVariable
    }
    }
  • sonnysonny Posts: 208
    Thanks...

    That's not bad, basically create a structure that holds all pertinent data of a particular device, and broadcast it periodically, or in response to a command request.

    I appreciate the help.
  • DHawthorneDHawthorne Posts: 4,584
    What I've done is make an include file for the data I need to share, then pass the variables in that file as parameters to the modules that need it. I've specifically used this method for things like HVAC control, where I can put the area names in an array as well as the temperature and setback data. The include can set a constant to initalize the array sizes and makes it very easy to upgrade the system. For user settings and setback timers, you can make said data arrays persistent so you don't wipe them out every time you modify the code :).

    Another thing I've done is make virtual devices just to pass messages back and forth, but it seems like a lot of effort in some applications, and far more overhead to parse said messages.

    What I would really like to see is a better module implementation that will allow you to call functions internal to the module from outside of it, and give you a reurn value. That would eliminate the need for a lot of that; you could just query the module for what you needed when you needed it.
  • sonnysonny Posts: 208
    The ability for a module to expose its functions would be awesome. Right now take one of two approaches, just broadcast changes anytime they are made or send request messages to a module for data.
  • frthomasfrthomas Posts: 176
    Originally posted by sonny
    One thing I haven't figured out how to do (I am a relative newbie to NetLinx programming) is how to get info from Virtual Devices or Touch Panels. i.e. is a channel on or off, what is the current level for a channel.

    For some reason, you can get the state of a channel
    mystate = [mydev, mychan];

    but there is no way to get the state of a level, you have to keep a variable for it.

    Note that for channel, what you get is the feedback state and not necessarily the actual state. Unless you use DEFINE_MUTUALLY_EXCLUSIVE or other similar stuff it is the same.

    Fred
  • frthomasfrthomas Posts: 176
    Re: Program Communications
    Originally posted by sonny
    I would like to get some thoughts on inter-program communications styles some of you use. I like the use of modules to isolate specific functions but you can't seem to expose a module "API" that would allow you to pass data.

    The AMX philosophy is that a DEVICE API is made of channels, levels, and text (strings and commands).
    For the typical device around, there is a lot that can be done with channels and levels, but I also wished routines could be called.

    I create a module that has a virtual device for communication. I use channels/levels for all state functions, simplifying the API (for example, I do control blinds that can be set to a position between 1 and 255: that's way too fine for a TP so I have 3/4 states only: UP, DOWN and potentially one or two preset positions). Most of the time, the channel is SET to ON or OFF (from within the module), but for some functions it is PUSHED (for example, a time manager does this when sunset occurs).
    I use levels only for states with many positions (f.e. temperature, dimming). Under 20 pos I create channels (for example the surround state of the amp). Using channels, the state can be read from outside the module without having to create a variable that gets updated whenever the level changes (which IMHO is a waste of resources).

    I define some form of API to control the module using COMMANDs. A very simple API is to reuse the channels/levels defined. (i.e. if channel 25 indicates lamp XYZ is on/off, sending the string 25 as a command toggles lamp XYZ).

    Now I have a complicated system to LOCK the module while it is processing a command. I wish Netlinx could offer that. I explain:
    Most devices do provide some feedback to commands you send to them. So in your module, you receive command 25, you transform that into whatever must be sent to the real device and send it... and now you'd like to wait until you get the acknowledgement of the command from the device to update your internal (and external) state. Any external command received until this is finished is ignored. Ideally, it would be kept in the command event queue of the control virtual device, but there is no way to lock this queue. Therefore I have a home made API that manages all that, sends "sorry the device is locked, please retry later" replies to commands received during processing, etc.
    It works nicely: when pressing a button on a panel, the button stays lit until the command could be executed, including waiting for the device to become unlocked and retrying to send the command.

    If/when large amounts of data must be sent or I really need functions, then I do not use a module...

    Fred
  • With regards to intra-program communications, I have been using the marshalling protocol (either binary or XML all depending on how I am feeling that day), then saving the converted data to a file on the disc. There is one include file that all the related modules share which contains the common data type declarations as well as the write and read functions. As one module changes the data through a file write call, it notifies the others through a channel or send_command (both referenced from the common include file), which triggers the file read function. Advantages? It isolates the variable data to that module, and allows for persistence within a module. Using the XML protocol one can also edit the varaible data offline and through the channel or send_commands reload the new data into the modules. Disadavantages? Processor headroom maybe - but nothing noticeable so far. A bit of work, but if done "correctly" only the first time.
  • DHawthorneDHawthorne Posts: 4,584
    I've toyed with the marshalling protocol too, but keep hitting the 1M non-volitile memory ceiling. It also seems to be somewhat inefficient; I've concluded it best to limit its use to small data structures. Maybe my projects are just too dang big.
  • I declare as a volatile and source the data from the disc
  • patbpatb Posts: 140
    In general I use a method somewhat like what frthomas described but much more straightforward and simple - I wish AMX's modules would do something like this also, but alas they have decided on a method and are going to stick with it. Basically the easist thing to do with AMX is pulse IR channels or relay channels, so by using the channels on the RS232 device (or virtual device actually) in much the same way it allows simple communication in the main code and all of the complicated stuff is handled within the module. As far as the code goes, you treat it just like you would a standard IR device. Define all of your channels the same for the same type of device using AMX's standard IR channel definitions and you have a piece of mainline code that is literally identical for all devices.

    As far as the "LOCK" question of course we have to parse the commands and send them out in an orderly fashion, but I also use a channel on the device for that. While the channel is on all commands stack up in a queue and either a timer or real response turns off the channel so the next command goes out - pretty simple but it doesn't use up any memory by taking up a variable. The only time I need to use anything other than a channel is when I use a send command to send a specific numerical value (like lamp hours) back to the module. Anything that is a 3 or 4 state variable (like lamp state-on, off, warming, cooling) I still use a series of channels to indicate the state. If I need to send anything number specific to the module (inputs and outputs on a switcher), then I use the send command again in a standard format so it doesn't matter what the switcher is I always use the same message.
  • Passing a Structure to a Module

    Super Coders:

    Can anybody give me sample code that shows how to pass a structure into a module? I've got the structure defined in my program and I want to pass the structure to a module. The structure that I want to pass in is called "My_DATA"

    I can't seem to get the declaration right in the module so that it will compile. What I've currently got is:

    MODULE_NAME='Extron_Switcher_UI' (
    DEV vExtron_RS232,
    DEV TPArray[],
    DEV vDMS_Array[],
    MY_DATA Extron_Structure)


    Then I've got under Define_Type inside my Module which is the same structure that is defined in my main program.

    DEFINE_TYPE
    STRUCTURE MY_DATA
    {
    INTEGER nArgCount
    CHAR cParam[8][128]
    CHAR cRawData[255]
    CHAR cCommand
    CHAR cQuery
    }

    I could not find any examples of this in the Help in the Netlinks program.

    Thanks in advance.

    -- JDaggitt
  • frthomasfrthomas Posts: 176
    JDaggitt wrote:
    Super Coders:

    Can anybody give me sample code that shows how to pass a structure into a module?

    Pretty simple: you cannot. Only "intrinsic" types are supported as module variable types.

    Solution 1 is to forget your struct and pass the individual elements to the module (from the outside). If you do not need the variable "continuous data channel" (the fact that changing module parameter variables from inside or outside the module does change their value inside and outside), then you could use the struct from the outside, assign the individual elements in the parameters and re-build the struct inside the module. That would work for static things like configuration devices, etc, but of course changing a struct member won't change outside the module.

    Solution 2 is to use the marshalling protocol (STRING_TO_VAR) to transform the structure inside a binary string, send this string to the module using SEND_STRING or SEND_COMMAND, and have the module re-transform this string into a struct. It works, but it's kind of inefficient, sort of using cell phones as an expensive intercom between ground and first floor in your home :-)

    Given your struct, I would use solution 1. I only use 2 for complex data structures (like containing multiple arrays of strings).

    HTH

    Fred
Sign In or Register to comment.