Home AMX User Forum NetLinx Studio

COMM Module Virtual Devices Help

I created my own COMM module for our NEC 52 inch LCD screens. I grew tired of having to look up all of the hex strings, so I made my own COMM module, dumped them all in there and let the COMM module do all of the work. It works great if I send one command at a time via a timeline in my main program. I found in the NEC manual, command strings must not be sent any faster than .6 seconds between each one. In order to get rid of the timelines in the main program and to make the COMM module do all of the work, I need to be able to rack and stack the commands and send them out one at a time with a .6 second wait between each one. I created 2 virtual monitors, and since right now the monitors work in unison with each other, I also created a VOLATILE DEV DISPLAYS[] = {vdvMonitor1, vdvMonitor2} for them.

When someone comes in and powers up the room, part of the power sequence involves the monitors:

SEND COMMAND DISPLAYS, " 'POWER=ON', 13,10"
SEND COMMAND DISPLAYS, " 'INPUT=VGA',13,10"
SEND COMMAND DISPLAYS. " 'AUDIO=2',13,10"
SEND COMMAND DISPLAYS, " 'VOLUME=89',13,10"

If I do a WAIT or create a timeline to stagger the commands, it works fine. If I blast them, I get mixed results. Inside the COMM module, I created a data event:
DATA EVENT[vdvDisplay]
{
         COMMAND:
          {
                 cCommand = (DATA.TEXT)
          }
}

In a simple form like that, and with the main program pausing between commands, I simply parse cCommand and send the proper hex string to the real display. It was when I started trying to get the COMM module to rack and stack the commands I started running into problems.

Inside the COMM module, I created another virtual device and did this:
DATA EVENT[vdvDisplay]
{
         COMMAND:
          {
                 cCommand = (DATA.TEXT)
                 SEND_STRING vdvPARSER, "cCommand"
          }
}


In order for that to work, I created a buffer for vdvPARSER and my thoughts were if the buffer were big enough, I could parse that info, one command at a time and send them .6 seconds apart from each other. Again, it worked fine with the main program waiting between sending commands to the virtual monitors.

This is what I have:
DATA_EVENT [vdvPARSER]
{
       STRING:
       {
            WHILE (FIND_STRING(cPARSER_BUFF, "13,10",1)
            {
                  do something
            }
       }
}

I was thinking with the WHILE loop, the module would continue processing the parser buff, one command at a time, until the buffer was empty. Something like:
cMSG = REMOVE_STRING(cPARSER_BUFF, "13,10",1)

Inside the COMM module, I am having problems with where to put the WAIT. It seems no matter where I put it, all I have successfully done is locked up the program or caused it to stop after rx-ing the first command. What am I missing? Is there a better way I should be doing this?

Comments

  • DHawthorneDHawthorne Posts: 4,584
    NetLinx doesn't multi-thread. If a while loop is active, nothing else is. My solution for parsing buffers is to put the command to check for a block of usable data right in DEFINE_PROGRAM. If nothing is there, it's a minor tick to do the test, and no loop tying things up since by the nature of DEFINE_PROGRAM, it will only process one block of data per mainline pass, where your while loop will try to do the whole thing.
  • staticatticstaticattic Posts: 200
    "Oh I see," said the blind man. That would explain the weird results when the commands get blasted all at once. It wasn't the placement of my WAIT in the COMM module that was dorking things up, it was the WHILE loop. When the main program was sending commands one at a time with a one second wait between each, that would explain why the WHILE loop was working. The buffer would reciveve something causing the WHILE loop to run, after parsing, the buffer would be empty, therefore ending the WHILE loop. On the other hand, if the commands all popped in at once, the WHILE loop would get stuck since it would technically never be satisfied.

    Thanks for the push in the right direction. I'll re-do it and let you know what happens. Basically, all I need to do is veify I have a proper command, and take action. So if the commands all come in at once, like:

    POWER=ON,13,10AUDIO=2,13,10VOLUME=89,13,10

    I want it to first parse the POWER command. Wait .6 seconds, then check to see if more data needs to be parsed. If there is something else there, then do it again and continue until there are no more commands. The thought of using Mainline totally slipped my mind. I'll give it a shot. Thanks again.
  • Each SEND_COMMAND into the virtual device will create a DATA_EVENT..COMMAND. That works well also if you do 10 or more send_commands in a row.

    The job of the comm module now is, to pick up the commands, and then to create the control strings for the physical device, queueing them, checking for the dependencies of device's state and the serial handling. The amount of commands the module can buffer for sending, depends on the size of the module internal transmit queue.

    For example, if a projector needs power on, waiting 20 seconds to get powered, and then to switch it to VGA input, the main program simply sends the commands 'POWER=1' and 'INPUT=VGA'. Everything else is managed inside the module.

    Other example may be a matrix control module. If you have to set 20 routes by one push, the main program simply sends the 20 commands in a row into the module. The module will receive 20 single events, translate them to the real protocol,manage to queue them, and handle the sending based on the serial handling, serial feedbacks etc.
  • staticatticstaticattic Posts: 200
    Fixed It!

    OK, this is what I did. First, I got rid of the "13,10" after each command. Since I am writing the COMM module and making my own protocol, I made the delimiter an exclamation point, shorter and easier to code. Inside the COMM module I created 2 functions. One puts the commands in a que and the other grabs a command once a second. The one that grabs the commands parses them and determines the appropriate hex string to send to the real device. I don't care about feedback from the real device, but I suppose I should incorporate that eventually. In the meantime, here's a Cliff's Notes version of what I have. Any pointers would be greatly appreciated. Thanks again for the replies and push in the right direction.

    Main Program:
    DEFINE_DEVICE
    
    dvDisplay1     =     5001:3:0
    dvDisplay2     =     5001:4:0
    
    
    vdvDisplay1    =     33001:1:0
    vdvDisplay2    =     33002:1:0
    
    DEFINE_CONSTANT
    
    VOLATILE DEV dvDISPLAYS[]     =     {vdvDisplay1, vdvDisplay2}
    
    
    DEFINE_START
    
    DEFINE_MODULE 'NEC_COMM_v2' NEC_ControlsComm1(vdvDisplay1, dvDisplay1)
    DEFINE_MODULE 'NEC_COMM_v2' NEC_ControlsComm2(vdvDisplay2, dvDisplay2)
    

    And the COMM Module:
    MODULE_NAME='NEC_COMM_v2 (DEV vdvDisplay, DEV dvDisplay)
    
    
    DEFINE_VARIABLE
    
    VOLATILE CHAR cCMD_QUE[50]
    VOLATILE CHAR cCMD[15]
    
    
    
    DEFINE_FUNCTION QUE_CMD (CHAR cCMD[])
    {
         IF ((LENGTH_STRING(cCMD_QUEUE) + LENGTH_STRING(cCMD)) <= MAX_LENGTH_ARRAY(cCMD_QUEUE))
         {
               cCMD_QUEUE = "cCMD_QUEUE,cCMD"
         }
    }
    
    DEFINE_FUNCTION SEND_CMD()
    {
         LOCAL_VAR_CHAR cMSG[50], cPARSED[15]
         IF (FIND_STRING(cCMD_QUEUE, '!',1)
         {
               cPARSED = REMOVE_STRING(cCMD_QUEUE,'!',1)
               SET_LENGTH_STRING(cPARSED, LENGTH_STRING(cPARSED) - 1)
               SWITCH(REMOVE_STRING(cPARSED,'=',1))
               {
                      CASE 'POWER=':
                      {
                              IF(FIND_STRING(cPARSED,'ON',1))
                              {
                                    cMSG = the Power On Hex string
                              }
                      }
                      CASE so on and so on
                      {
                              Do more stuff
                      }
               }
               SEND_STRING dvDisplay, "cMSG"
               cPARSED = ' '
               cMSG = ' '
         }
    }
    
    
    DEFINE_EVENT
    
    DATA_EVENT[vdvDisplay]
    {
           cCMD = (DATA.TEXT)
           QUE_CMD(cCMD)
    }
    
    
    DEFINE_PROGRAM
    
    WAIT 10
    {
         SEND_CMD()
    }
    

    Future improvements to the Jedi powers of this module will incorporate feedback from the devices to verify a proper commands was RX'd and a check that verifies the command is complete before sending it out to the real device.
  • ROOROO Posts: 46
    Que management

    Hi Jeff,
    Another idea for your module would be to include a priority insertion that would place a command in the front of the que instead of the end. I found it handy when the data to be sent out in a que (checking a bunch of status), got in the way of the user change (to kill audio feedback). Depending on the device you're dealing with a couple of seconds delay may be more then you want.

    Just an idea.

    ROO
  • Spire_JeffSpire_Jeff Posts: 1,917
    Just a couple of suggestions:

    First, if you are looking for speed and ease of parsing, consider switching to using a virtual device as the interface to the comm module:

    in Comm Module:
    channel_event[vdvVirtual,27]{
      on:{
      fnQueueCommand('[Power On Hex Code]');
     }
    }
    channel_event[vdvVirtual,28]{
      on:{
      fnQueueCommand('[Power Off Hex Code]');
     }
    }
    

    in Main code:
    button_event[dvTp,TurnOnRoom]{
      push:{
       pulse[vdvVirtualTv1,27] 
       pulse[vdvVirtualTv2,27]
      }
    }
    button_event[dvTp,TurnOffRoom]{
      push:{
       pulse[vdvVirtualTv1,28] 
       pulse[vdvVirtualTv2,28]
      }
    }
    

    This eliminates the needs for string processing all together between the main program and the comm module (unless you have something that doesn't work well as being channel events or level events.

    Second, consider using an indexed queue. Check out this http://amxforums.com/showthread.php?t=5372&highlight=queue link. I eventually posted some sample code of the queue. Ignore the dev component of the queue as it is not needed in most normal circumstances.


    Jeff
  • kbeattyAMXkbeattyAMX Posts: 358
    I typically don't run a command cue with actual commands. I run a cue of pointers and each pointer is given a specific wait time to hold the flag for parsing the command cue. Each command also has priority. If a command of 'PowerOff' is issued, the current cue is dumped and POF is inserted. If a "PowerOn" is issued and a POF is still in the cue, then the POF is removed from the cue and the device stays on. The pointer points to an array of commands. Using this method I can modify the module to meet the needs of almost any display.
Sign In or Register to comment.