Home AMX User Forum NetLinx Modules & Duet Modules

Modules for Dummies 101

The Dummy is me. I'm trying to graduate from one-off hard-coded jobs that I have been cranking out the last few years. Oh, they work, but as we are getting into larger jobs and more frequent jobs I am finding the need for more a more efficient and modular way to assemble these projects.

I think I want to keep on writing the drivers for the devices, but want to package that code in a way that allows me to:

(1) Efficiently plug it into another project.
(2) Control multiple devices with multiple UI's without repeating the same chunks of code throughout.

Are modules the way to go? I cannot seem to quite wrap my head around them, although I have studied almost every post in here that talks about them.

Specifically:

What parameters get passed to what, and in do they pass in both directions?

A lot of you pass arrays to the module to talk to it, but the Duet modules seem to respond to text-based commands, which seems simpler to me. (The idea of getting the index of a button array instead of the button channel throws me for a loop).

It looks like multiple module instances deals with the multiple identical devices, but how do you keep track of the feedback data for each one?


I have looked at a lot of examples, but it is hard to tell what is happening when the module comes as a .tko or .jar. Is there a simple example which has viewable source code I can examine? Also, I have been looking at examples for single devices only. Your complete jobs with many devices must contain tens of modules if you have a comm and UI module for each device or type of device.

Feel free to direct me to any post that already has the answers!

Thanks for your time.

Greg

Comments

  • kbeattyAMXkbeattyAMX Posts: 358
    Stuff I repeat a lot in the same job and need to use on other jobs is what I write modules for. Normally all of my display control goes to modules and I use send commands to interact with them. Sending commands to the virtual device controls it. Receiving strings from the virtual device gives me text feedback. pulsing a channel on the virtual device also controls it but monitoring channels on the real device gives me channel feedback. I use simple commands like SEND_COMMAND vdvDisplay,'ON' And you can guess how I turn it off... Multiple instances of the module is delineated by different devices and virtual devices used in the module. One module maybe using 33001:1:0 and another instance of the module may be using 33002:1:0. So just a data_event separates the feedback.
  • Spire_JeffSpire_Jeff Posts: 1,917
    I use a mix of include files and modules to complete the jobs I am working on. Modules have some quirks and the way I handle my UI control, I found that using modules made reboots take forever (about 50 minutes on one job). The reason was because I was passing in an arrays of touch panels. Modules need to create their own memory space to deal with the devices and things got out of control quickly :)

    As for device communications, modules work well and I use them often. Here is an example of a device module:
    MODULE_NAME='JVC_DLA-HD1_Comm'(dev vdvPROJ, dev dvPROJ)
    (***********************************************************)
    (* System Type : NetLinx                                   *)
    (***********************************************************)
    (* REV HISTORY:                                            *)
    (***********************************************************)
    
    (***********************************************************)
    (*          DEVICE NUMBER DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_DEVICE
    
    (***********************************************************)
    (*               CONSTANT DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_CONSTANT
    
    CHAR 	HEAD_OPERATION = 33;
    CHAR 	HEAD_REFERENCE = 63;
    CHAR	HEAD_RESPONSE	=	64;
    
    CHAR	UNIT_ID[]	=	{137,01};
    
    CHAR	CMD_CHECK_CONN[]	=	{0,0};
    CHAR	CMD_POWER[]	=	{80,87};
    CHAR	CMD_INPUT[]	=	{73,80};
    CHAR	CMD_REMOTE[]	=	{82,67};
    
    CHAR	POWER_ON	=	49;
    CHAR	POWER_OFF	=	48;
    
    CHAR	INPUT_S_VIDEO	=	48;
    CHAR	INPUT_VIDEO	=	49;
    CHAR	INPUT_COMP	=	50;
    CHAR	INPUT_HDMI1	=	54;
    CHAR	INPUT_HDMI2	=	55;
    
    CHAR 	REMOTE_ASPECT[]	=	{55,51,55,55};
    
    CHAR 	END_CMD	=	10;
    
    	
    (***********************************************************)
    (*              DATA TYPE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_TYPE
    
    (***********************************************************)
    (*               VARIABLE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_VARIABLE
    INTEGER nPOWER_STATE = 0
    INTEGER nPROJ_COMM = 0
    
    
    (***********************************************************)
    (*               LATCHING DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_LATCHING
    
    (***********************************************************)
    (*       MUTUALLY EXCLUSIVE DEFINITIONS GO BELOW           *)
    (***********************************************************)
    DEFINE_MUTUALLY_EXCLUSIVE
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    (* EXAMPLE: DEFINE_FUNCTION <RETURN_TYPE> <NAME> (<PARAMETERS>) *)
    (* EXAMPLE: DEFINE_CALL '<NAME>' (<PARAMETERS>) *)
    DEFINE_CALL 'POWER ON PROJ'
    {
    	SEND_STRING dvPROJ,"HEAD_OPERATION,UNIT_ID,CMD_POWER,POWER_ON,END_CMD";
    	WAIT 310 nPOWER_STATE = 1
    	WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    }
    (***********************************************************)
    (*                STARTUP CODE GOES BELOW                  *)
    (***********************************************************)
    DEFINE_START
    
    (***********************************************************)
    (*                THE EVENTS GO BELOW                      *)
    (***********************************************************)
    DEFINE_EVENT
    DATA_EVENT[dvPROJ]
    {
      ONLINE:
       {
         SEND_COMMAND dvPROJ, 'TSET BAUD 19200,N,8,1 485 DISABLE'
         
       }
    	STRING:
    		{
    			nPROJ_COMM = 1
    			CANCEL_WAIT 'PROJ COMM'
    		}
     
    }
    
    DATA_EVENT[vdvPROJ]
    {
      COMMAND:
      {
    		SELECT
    		{
    			ACTIVE(FIND_STRING(DATA.TEXT,'POWER=0',1)):
    				{
    				  CANCEL_WAIT_UNTIL 'POWER'
    					SEND_STRING dvPROJ,"33,137,01,80,87,48,10";
    					nPOWER_STATE = 2
    					WAIT 310  nPOWER_STATE = 0
    					WAIT 30 'PROJ COMM' nPROJ_COMM=0
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'POWER=1',1)):
    				{
    					IF(nPOWER_STATE <> 1)
    					{
    						WAIT_UNTIL (nPOWER_STATE == 0) 'POWER'
    							{
    								CALL 'POWER ON PROJ'
    							}
    					}
    					ELSE
    					{
    						SEND_STRING dvPROJ,"33,137,01,0,0,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    					}
    					
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'INPUT=1',1))://Composite
    				{
    					CANCEL_WAIT_UNTIL 'INPUT'
    					IF(nPOWER_STATE == 0)
    						CALL 'POWER ON PROJ'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,73,80,49,10";
    						WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,49,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    						
    					}
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'INPUT=2',1))://svid
    				{
    					CANCEL_WAIT_UNTIL 'INPUT'
    					IF(nPOWER_STATE == 0)
    						CALL 'POWER ON PROJ'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,73,80,48,10";
    						WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,48,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    					}
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'INPUT=4',1))://DVI/HDMI
    				{
    					CANCEL_WAIT_UNTIL 'INPUT'
    					IF(nPOWER_STATE == 0)
    						CALL 'POWER ON PROJ'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,73,80,54,10";
    						WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,54,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    					}
    				}
    
    			ACTIVE(FIND_STRING(DATA.TEXT,'INPUT=5',1))://comp1
    				{
    					CANCEL_WAIT_UNTIL 'INPUT'
    					IF(nPOWER_STATE == 0)
    						CALL 'POWER ON PROJ'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,73,80,50,10";
    						WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,50,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    					}
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'INPUT=6',1)): //HDMI2
    				{
    					CANCEL_WAIT_UNTIL 'INPUT'
    					IF(nPOWER_STATE == 0)
    						CALL 'POWER ON PROJ'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,73,80,55,10";
    						WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,55,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    					}
    				}
    			ACTIVE(FIND_STRING(DATA.TEXT,'FORMAT=T',1)): //16:9 Format
    				{
    					CANCEL_WAIT_UNTIL 'SCRN_FORMAT'
    					WAIT_UNTIL (nPOWER_STATE == 1) 'SCRN_FORMAT'
    					{
    						SEND_STRING dvPROJ,"33,137,01,82,67,55,51,55,55,10";
    						WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    					}
    				}
    				
    		}
    		
      }
    }
    channel_event[vdvProj,27]{//Power On
    	on:{
    		IF(nPOWER_STATE <> 1)
    		{
    			WAIT_UNTIL (nPOWER_STATE == 0) 'POWER'
    				{
    					CALL 'POWER ON PROJ'
    				}
    		}
    		ELSE
    		{
    			SEND_STRING dvPROJ,"33,137,01,0,0,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    		}
    	}
    }
    channel_event[vdvProj,28]{//Power Off
    	on:{
    		CANCEL_WAIT_UNTIL 'POWER'
    		SEND_STRING dvPROJ,"33,137,01,80,87,48,10";
    		nPOWER_STATE = 2
    		WAIT 310  nPOWER_STATE = 0
    		WAIT 30 'PROJ COMM' nPROJ_COMM=0
    	}
    }
    channel_event[vdvProj,81]{//Input 1
    	on:{
    		CANCEL_WAIT_UNTIL 'INPUT'
    		IF(nPOWER_STATE == 0)
    			CALL 'POWER ON PROJ'
    		WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    		{
    			SEND_STRING dvPROJ,"33,137,01,73,80,49,10";
    			WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,49,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    			
    		}
    	}
    }
    channel_event[vdvProj,82]{
    	on:{
    		CANCEL_WAIT_UNTIL 'INPUT'
    		IF(nPOWER_STATE == 0)
    			CALL 'POWER ON PROJ'
    		WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    		{
    			SEND_STRING dvPROJ,"33,137,01,73,80,48,10";
    			WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,48,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    		}
    	}
    }
    channel_event[vdvProj,84]{
    	on:{
    		CANCEL_WAIT_UNTIL 'INPUT'
    		IF(nPOWER_STATE == 0)
    			CALL 'POWER ON PROJ'
    		WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    		{
    			SEND_STRING dvPROJ,"33,137,01,73,80,54,10";
    			WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,54,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    		}
    	}
    }
    channel_event[vdvProj,85]{
    	on:{
    		CANCEL_WAIT_UNTIL 'INPUT'
    		IF(nPOWER_STATE == 0)
    			CALL 'POWER ON PROJ'
    		WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    		{
    			SEND_STRING dvPROJ,"33,137,01,73,80,50,10";
    			WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,50,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    		}
    	}
    }
    channel_event[vdvProj,86]{
    	on:{
    		CANCEL_WAIT_UNTIL 'INPUT'
    		IF(nPOWER_STATE == 0)
    			CALL 'POWER ON PROJ'
    		WAIT_UNTIL (nPOWER_STATE == 1) 'INPUT'
    		{
    			SEND_STRING dvPROJ,"33,137,01,73,80,55,10";
    			WAIT 10 SEND_STRING dvPROJ,"33,137,01,73,80,55,10";
    			WAIT 30 'PROJ COMM' nPROJ_COMM=0
    
    		}
    	}
    }
    
    (***********************************************************)
    (*            THE ACTUAL PROGRAM GOES BELOW                *)
    (***********************************************************)
    DEFINE_PROGRAM
    [vdvPROJ,251] = nPROJ_COMM
    [vdvPROJ,255] = nPOWER_STATE
    

    I added the channel_event activation to demonstrate how you can use channels to do things as well. The reason I prefer channel events is that you don't have to employee string functions which can be processor intensive. There are some situations where string commands make sense, but normally you are doing a lot of extra work.

    Jeff
  • gregrgregr Posts: 54
    Thanks, Ken and Jeff.

    Both of you are saying the same thing, and it makes sense to me for the most part. I can see where channels are more processor efficient, but seem to be harder to keep track of. I'd like to avoid 50 minute re-boots if possible! :(

    As to channels, Ken mentions monitoring channels on the real device, but these would not exist as such on non-AMX devices, no? I assume you would have to monitor the data_event for the real device, then parse and turn that into channels?

    And Jeff, the channels on your virtual device puzzled me for a bit. I guess any defined device can be considered to have channels, even though the actual device has no idea what channels are?

    I reckon I'll just have to slog through my first module until it does what I need. There's probably no such thing as writing it perfect the first time.
  • kbeattyAMXkbeattyAMX Posts: 358
    gregr wrote: »
    Thanks, Ken and Jeff.

    Both of you are saying the same thing, and it makes sense to me for the most part. I can see where channels are more processor efficient, but seem to be harder to keep track of. I'd like to avoid 50 minute re-boots if possible! :(

    As to channels, Ken mentions monitoring channels on the real device, but these would not exist as such on non-AMX devices, no? I assume you would have to monitor the data_event for the real device, then parse and turn that into channels?

    And Jeff, the channels on your virtual device puzzled me for a bit. I guess any defined device can be considered to have channels, even though the actual device has no idea what channels are?

    I reckon I'll just have to slog through my first module until it does what I need. There's probably no such thing as writing it perfect the first time.

    When I say real device, I mean the actual AMX device that is controlling the MFG device. For example, a projector is connected to 232 port 1. The real device if the device is addressed 5001, 5001:1:0. the feedback is reported to this device. So in the module you might do this...
    if (find_string(data.text,'PWR',1)) //response is either PWR1 or PWR0
    {
    [dvDevice,1] = atoi(data.text)
    }
    This is reporting the outcome of the find_string to the channel of the real device.
  • Spire_JeffSpire_Jeff Posts: 1,917
    gregr wrote: »
    And Jeff, the channels on your virtual device puzzled me for a bit. I guess any defined device can be considered to have channels, even though the actual device has no idea what channels are?

    The device that I am using channels on is a virtual device. There are limits, but by default the device will have 255 channels. The modules job is to act on channel changes by the main program and change the state of the channels based on feedback from the device (generally speaking of course :) ). The virtual device is sort of the gateway between your main code and the actual device. Ideally, you want all of the same type of device to work off of the same "language" on the virtual device.
    For example, channel 27 is always power on for my devices. Inside the module, the module converts that channel 27 into the appropriate command and sends the command to the actual device. I highly recommend following AMX standards when possible, then you can just substitute their comm module when one is available and you won't have to do a lot of recoding.
    Maybe this will help...Think of a channel as a binary shared variable. If you need more than a binary variable, you can use a level for integer sharing, but I tend to avoid levels for most things. I like to use channels because it is easy to get their current state and act accordingly. ie: [dvTP,TvPower] = [vdvTvModule,255].

    Jeff
  • a_riot42a_riot42 Posts: 1,624
    Spire_Jeff wrote: »
    I found that using modules made reboots take forever (about 50 minutes on one job). The reason was because I was passing in an arrays of touch panels.

    I can't see how this can be. 50 minutes to reboot a controller? Sounds like you have an infinite while loop or something else going on. Its possible to get loops between the controller and devices as well. I pass touch panel references to modules all the time and haven't seen this. Are you sure there wasn't something else going on there?
    Paul
  • Spire_JeffSpire_Jeff Posts: 1,917
    a_riot42 wrote: »
    I can't see how this can be. 50 minutes to reboot a controller? Sounds like you have an infinite while loop or something else going on. Its possible to get loops between the controller and devices as well. I pass touch panel references to modules all the time and haven't seen this. Are you sure there wasn't something else going on there?
    Paul

    I am almost positive it was caused by an array of touch panels. I had an array of 60 touch panels and I had something like 8 instances of the module. The processor would eventually boot up and run properly, so it was not an infinite loop. I figured out it might be the touch panel array as I had long boot times in the past, but they were in the 20-25 minute range. It just so happened that the job with the really long boot time had less going on than previous jobs, (fewer other modules and code) so I was really confused when it took that long to boot. I took the code out of the module and just ran it in the main program (with an array for the variables as I recall) and boot time dropped to a couple minutes. (Duet modules :) ).

    This was a few firmwares ago, so I am not sure if it is still an issue, but if I get a chance, I will load the old modules and verify.

    Jeff
  • truetrue Posts: 307
    gregr wrote: »
    ...I reckon I'll just have to slog through my first module until it does what I need. There's probably no such thing as writing it perfect the first time.
    It took me a good year or so before I really started writing good modules. Didn't help that I didn't know what DME did until 6 months in writing code and managing it manually... but anyway, it did take some time thinking about how modules should work, how code could be reusable and how device control should be abstracted from UI control so you aren't alone.

    When I write modules for devices, I have a device communications module; for those devices that interface directly with a user, in addition to this module I'll have UI modules which communicate with the comms module. I've got my own text-based command scheme that is similar between all of my modules, and use similar channel and level feedback between modules. (such as for when a receiver input turns on, I can, with a channel_event, then tell that input's device to turn on if not on, etc.) So should I want to swap, say, a Sony receiver for a Denon, all I have to do is change my comm and UI module defines and be done with it. Or if I want to add a touchpanel, I load another module for that new touchpanel and I'm done. That's really the advantage to well-written UI modularization and standardization :)

    Remember that variables passed into modules are by reference, so if it helps with your design, you can use a common variable to share data between modules or between main code and modules. I use this with my old 422 Viewstat modules so I can, say, use the UI module for touchpanel control of one tstat at a time, and have a very simple loop in my main program send current temperatures of all thermostats each minute. If I wanted to, I could write another UI module that allows me to modify all thermostats at the same time without touching any of the other code. Keeping UI seperate from everything else can really help with code reuse, and doing it with modules is easy.

    Regarding string functions and parsing, I wrote a function that splits a string on a delimiter (such as a space or a comma) and puts it in a passed multidimensional array. So when working with protocols that have a fixed delimiter from devices, or when working with my simple protocol ('power off' or 'input dvd' or 'volume mute off'), it's as easy as looking at values in an array. (arr[1] == 'volume', arr[2] == 'mute', arr[3] == 'off') It works similar to PHP's explode(), with a couple of key differences. If anyone thinks they could use this function for easier module writing I'll post it, or if you want an example simple module/UI pair I could post that too...

    edit: NetLinx uses 1-based arrays. oops. fixed to help against confusion.
  • gregrgregr Posts: 54
    Didn't help that I didn't know what DME did until 6 months in writing code and managing it manually...

    Uh...DME??
    When I write modules for devices, I have a device communications module; for those devices that interface directly with a user, in addition to this module I'll have UI modules which communicate with the comms module.

    So would you have a separate Comm and UI module for every different type of controlled device? I'm trying to envision what a completed job would look like. Seems like for a typical job, you could have 20+ modules?
    or if you want an example simple module/UI pair I could post that too...
    That'd be great if you could.

    Thanks all-
    Greg
  • truetrue Posts: 307
    Lost my first reply...let's try this again.
    gregr wrote: »
    Uh...DME??
    define_mutually_exclusive - I was managing groups manually.
    gregr wrote: »
    So would you have a separate Comm and UI module for every different type of controlled device? I'm trying to envision what a completed job would look like. Seems like for a typical job, you could have 20+ modules?
    It could. It really depends on panel count and different device types. The modules are used for talking to devices and tying UI to devices, and the main code is used mostly for tying devices together. However, some simple device modules I have written either have no UI component and I write it per job or they accept a tp array (typically modules that only have channel feedback may do this).
    gregr wrote: »
    That'd be great if you could.
    EDIT: Misread what you were confirming you wanted... I'll get it for you soon.
    gregr wrote: »
    Thanks all-
    Greg
Sign In or Register to comment.