Home AMX User Forum NetLinx Studio
Options

Outgoing String Queuing and Timeout

How do you guys normally cue your outgoing strings? This is what I came up with but I'm not sure if it's the most efficient. I want a timeout so a command doesn't get lost into thin air and I want to make sure not process when not needed and keep adding waits. This works but I wanted to see if there was a better way to do it. Thanks!
DEFINE_EVENT

LEVEL_EVENT[vdvTP,nLevChans]{ //Level Control
    STACK_VAR SINTEGER nTempLevel
    nTempLevel=((LEVEL.VALUE)-30)
    cNexCMDQue="cNexCMDQue,'SET 1 FDRLVL ',ITOA(nNexInstID[GET_LAST(nLevChans)]),' ',ITOA(nNexLevChan[GET_LAST(nLevChans)]),' ',ITOA(nTempLevel),10"
}

DATA_EVENT[dvDSP]{
    ONLINE:{
	SEND_COMMAND DATA.DEVICE,"'SET BAUD 38400,N,8,1 485 DISABLE'"
    }
    STRING:{
	IF (FIND_STRING(DATA.TEXT,'OK',1)||FIND_STRING(DATA.TEXT,'ERR',1)){
	    nNexBusy=0
	}
    }
}

DEFINE_FUNCTION fNexSendCMDQue(){ //Send the command!
    IF ((LENGTH_STRING(cNexCMDQue)!=0 && nNexBusy=0)||(LENGTH_STRING(cNexCMDQue)!=0 && nNexWait=1)){
	STACK_VAR CHAR cNexCMD[1000]
	CANCEL_WAIT 'NexWait'
	cNexCMD=REMOVE_STRING(cNexCMDQue,"10",1)
	SEND_STRING dvDSP,"cNexCMD"
	nNexBusy=1
	WAIT 5 'NexWait'{
	    nNexWait=1
	}
    }
}

DEFINE_PROGRAM
fNexSendCMDQue() //Run Parser

Comments

  • Options
    a_riot42a_riot42 Posts: 1,624
    You will likely get many different responses as everyone here has a different way of doing the same thing. For instance, I don't use define_program at all and others do so I wouldn't do what you are doing in define_program where you call your function. Your function will get called 200 times a second and to me that is ludicrous unless you have some compelling reason to do it that way.That function call will get thrown on the stack 200 times a second and that is a lot of overhead to just check a couple of variables.

    I think I would check the variables first and then call the function only if you need to, and you really need to only check the cNexCMDQue variable to determine whether the function should be called.
    define_function fNexSendCMDQue(){ //Send the command!
    {
      stack_var char cNexCMD[1000]
      cancel_wait 'NexWait'
      cNexCMD = remove_string(cNexCMDQue,"10",1)
      if (cNexCMD)    // you want to check that you aren't sending 0 no?
      {
        send_string dvDSP,"cNexCMD"
        nNexBusy=1
        wait 5 'NexWait'
        {
          nNexWait=1
        }
      }
    }
    
    DEFINE_PROGRAM
    
    if(length_string(cNexCMDQue))
    {
      if (!nNexBusy || nNexWait)
      {
        fNexSendCMDQue()
      }
    }
    


    I do somethign similar to this this way:
    if (isIdle)	
    {
      isIdle = 0
      send_string dvDevice, "sSomeCmds[iCmd],13"
      wait 5
      isIdle = 1
    }
    

    but it is inside a function so define_program isn't used. If you call the function more than twice a second your command won't get sent and I would print an error to the console. If you want to queue commands so they are not lost, set up a FIFO queue (array) and put buffered commands in there until they can be executed at the appropriate time. That has its own set of issues so beware.
    Paul
  • Options
    AuserAuser Posts: 506
    I tend to agree with Paul; keeping as much processing out of mainline as possible is a good thing.

    I tend to write queues that work much like a ring buffer. It's a little more complicated than what you've done but has the advantage of allowing differing strings to be added and extracted easily (not all Biamp commands will have the instance ID 10 for instance).

    You would do this by creating a two dimensional char array in which to store the strings:
    define_constant
    integer MAX_ITEMS  = 6
    integer MAX_WIDTH  = 22
    
    define_variable
    char  cNexCMDQue[MAX_ITEMS][MAX_ITEM_WIDTH]
    

    The queue variable could be represented somewhat similar to:
    [S][E][T][ ][1][ ][F][D][R][L][V][L][ ][1][0][ ][-][1][0][ ][ ][ ]
    [S][E][T][ ][1][ ][F][D][R][L][V][L][ ][1][0][ ][-][9][ ][ ][ ][ ]
    [S][E][T][ ][1][ ][F][D][R][L][V][L][ ][1][0][ ][-][8][ ][ ][ ][ ]
    [S][E][T][ ][1][ ][F][D][R][L][V][L][ ][1][0][ ][-][7][ ][ ][ ][ ]
    [S][E][T][ ][1][ ][F][D][R][L][V][L][ ][1][0][ ][-][6][ ][ ][ ][ ]
    [S][E][T][ ][1][ ][F][D][R][M][U][T][E][ ][1][1][ ][1][ ][ ][ ][ ]
    

    (Note the brackets are just there to indicate elements in the array).

    Then you just need to track the current input and output "slots" in the queue, making sure they wrap around to the start of the array when the end of the array (MAX_ITEMS) is reached. It's also handy to track the number of items in the queue to make sure that there can never be more items in the queue than there are slots (otherwise information will be lost). It also makes it immediately obvious if the queue is empty.

    Functions to add items to the queue ( cNexCMDQue[nNextInputItem]='whatever' ), retrieve the current item from the queue ( cNexCMD = cNexCMDQue[nNextOutputItem] ) and remove the current item from the queue ( nNextOutputItem++ ) are handy. Each of these functions need to update the variables which store the current input slot, output slot and number of items in the queue as appropriate.

    Once the queue's written, I tend to use timeline_create to periodically retry the current command or forward the next command if there's no acknowledgement from the device. I tend to find timeline_create, timeline_pause, timeline_set and timeline_restart infinitely more powerful than using waits.

    If the device returns a positive acknowledgement, you can forward the next command then and there assuming the device doesn't require a delay (which Biamps don't) and just set the timeline back to 0 to make sure it doesn't attempt to resend the command right away.

    Hope that provides some food for thought :)

    Edit: To avoid confusion, timeline_create creates a timeline, you then need to write a timeline_event to do whatever it is that you were planning to do when the timeline elapsed.
  • Options
    DHawthorneDHawthorne Posts: 4,584
    I don't like to use timelines for command queues because I would like them to be more flexible. Many serial devices, for example, may respond differently at different times, depending on the command and how much other stuff is going on, and it seems a waste to always be waiting the maximum time interval between commands. I initiate a WAIT on a send, then either let it time out, or cancel it if a response comes in sooner than the WAIT time out. That way the queue isn't getting bogged down.

    I also don't think it does any harm to call a function in mainline, as long as the function itself only does a simple test in the beginning to determine if the rest is going to fire. The overhead to test a variable, for example, is small enough as to be entirely inconsequential, no matter how many times it's called. Running a complex operation, on the other hand, could seriously bog things down, so you do have to be careful about it. Mainline is just another tool that, used properly, can be very effectively, and used improperly cause you a lot of trouble. I absolutely do not subscribe to the philosophy that it is "old school" and should be avoided at all costs. That's kind of like refusing to ever use a hand screwdriver because you own a power one. Just when you do use it, use it carefully. Lots of modern programming languages implement "safe" methods of doing things that are touted as "better" because they cause so much less problems. But a skilled programmer can use the "older" methods to great advantage, though they are more dangerous if misused.
  • Options
    ericmedleyericmedley Posts: 4,177
    DHawthorne wrote:
    I don't like to use timelines for command queues because I would like them to be more flexible. Many serial devices, for example, may respond differently at different times, depending on the command and how much other stuff is going on, and it seems a waste to always be waiting the maximum time interval between commands. I initiate a WAIT on a send, then either let it time out, or cancel it if a response comes in sooner than the WAIT time out. That way the queue isn't getting bogged down.

    I also don't think it does any harm to call a function in mainline, as long as the function itself only does a simple test in the beginning to determine if the rest is going to fire. The overhead to test a variable, for example, is small enough as to be entirely inconsequential, no matter how many times it's called. Running a complex operation, on the other hand, could seriously bog things down, so you do have to be careful about it. Mainline is just another tool that, used properly, can be very effectively, and used improperly cause you a lot of trouble. I absolutely do not subscribe to the philosophy that it is "old school" and should be avoided at all costs. That's kind of like refusing to ever use a hand screwdriver because you own a power one. Just when you do use it, use it carefully. Lots of modern programming languages implement "safe" methods of doing things that are touted as "better" because they cause so much less problems. But a skilled programmer can use the "older" methods to great advantage, though they are more dangerous if misused.

    I suppose too that it's all a matter of habit. I'm an older programmer and am used to putting function calls down in mainline. I've been dealing with trapping them so they don't fire multiple times for years. It's not difficult to do and I've never quite understood all the hullabaloo about it. Actually, I find it easier for simple things than setting up a function call and all the data you have to migrate into and out of it.

    I use several methods to create a queue. They are all variations of a FIFO with new messages being added to the end of the buffer. I tend to peel off data from the buffer and processing in another buffer to preserve the integrity of the main buffer. It eats up more memory but is very reliable.

    This also allows for reliable asynchronous comm.

    That's my flavor. That's how I roll...
  • Options
    AMXJeffAMXJeff Posts: 450
    Queue Library
    DEFINE_CONSTANT
    
    // MAX STUFF
    
    #IF_NOT_DEFINED MAX_CMD_LENGTH
    MAX_CMD_LENGTH  				= 50;
    #END_IF
    
    #IF_NOT_DEFINED MAX_QUEUE_ITEMS
    MAX_QUEUE_ITEMS					= 4;
    #END_IF
    (***********************************************************)
    (*              DATA TYPE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_TYPE
    
    STRUCTURE _QUEUE
    {
    	CHAR cQueue[MAX_QUEUE_ITEMS][MAX_CMD_LENGTH];
    	CHAR cLastCommandSent[MAX_CMD_LENGTH];
    	CHAR cQueueHead;
    	CHAR cQueueTail;
    	CHAR cQueueBusy;
    	CHAR cQueueHasItems;
    	CHAR cQueueReady;
    }
    
    (***********************************************************)
    (*               LATCHING DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_LATCHING
    
    (***********************************************************)
    (*       MUTUALLY EXCLUSIVE DEFINITIONS GO BELOW           *)
    (***********************************************************)
    DEFINE_MUTUALLY_EXCLUSIVE
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    (**************************************)
    (* Call Name: PushQueue               *)
    (* Function: Adds comamnd to the queue*)
    (* Return:   n/a 				 					    *)
    (**************************************)	
    DEFINE_FUNCTION ENQueue(_QUEUE uQueue,Char cCmd[])
    {    
      IF (uQueue.cQueueHead = MAX_LENGTH_ARRAY(uQueue.cQueue))
      {
        IF (uQueue.cQueueTail <> 1)
        {
          uQueue.cQueueHead = 1;
          uQueue.cQueue[uQueue.cQueueHead] = cCmd;
          ON[uQueue.cQueueHasItems];
        }
      }
      ELSE IF (uQueue.cQueueTail <> uQueue.cQueueHead + 1)
      {
        uQueue.cQueueHead++;
        uQueue.cQueue[uQueue.cQueueHead] = cCmd;
        ON[uQueue.cQueueHasItems];
      }
    }
    
    (**************************************)
    (* Call Name: PopQueue                *)
    (* Function: Removes from the queue   *)
    (* Return:   CHAR[]			 					    *)
    (**************************************)			
    DEFINE_FUNCTION CHAR[MAX_CMD_LENGTH] DEQueue(_QUEUE uQueue)
    {    
      IF (GetQueueReady(uQueue) == TRUE)
      {
    		SetQueueReady(uQueue,FALSE);
    		
        IF (uQueue.cQueueTail = MAX_LENGTH_ARRAY(uQueue.cQueue))
          uQueue.cQueueTail = 1;
        ELSE
          uQueue.cQueueTail++;
    			
        IF (uQueue.cQueueTail = uQueue.cQueueHead)
          OFF[uQueue.cQueueHasItems];
    		
    		uQueue.cLastCommandSent = uQueue.cQueue[uQueue.cQueueTail];
    		
    		RETURN uQueue.cLastCommandSent;
      }
    }
    
    (**************************************)
    (* Call Name: PeekQueue               *)
    (* Function: Shows Item from the queue*)
    (* Return:   CHAR[] 		 					    *)
    (**************************************)	
    DEFINE_FUNCTION CHAR[MAX_CMD_LENGTH] PeekQueue(_QUEUE uQueue)
    {    
    	STACK_VAR INTEGER nLastTail;
    	 
    	nLastTail = uQueue.cQueueTail
    	 
      IF (nLastTail = MAX_LENGTH_ARRAY(uQueue.cQueue))
    		nLastTail = 1
    	ELSE
    		nLastTail = nLastTail + 1
    		    
    	RETURN uQueue.cQueue[nLastTail];
    }
    
    (**************************************)
    (* Call Name: ForceQueueReady         *)
    (* Function: Forces Queue Ready       *)
    (* Return:   n/a 				 					    *)
    (**************************************)	
    DEFINE_FUNCTION SetQueueReady(_QUEUE uQueue,CHAR bBool)
    {    
    	IF (bBool == TRUE)
    	{
    		ON[uQueue.cQueueReady];
    		OFF[uQueue.cQueueBusy];
    	}	
    	ELSE	
    	{
    		OFF[uQueue.cQueueReady];
    		ON[uQueue.cQueueBusy];
    	}	
    }
    
    (**************************************)
    (* Call Name: InitQueue					      *)
    (* Function: Setup Queue				      *)
    (* Return:   n/a 				 					    *)
    (**************************************)	
    DEFINE_FUNCTION InitQueue(_QUEUE uQueue)
    {    
    	uQueue.cQueueHead  = 1;
    	uQueue.cQueueTail  = 1;
    	uQueue.cQueueBusy  = FALSE;
    	uQueue.cQueueReady = TRUE;
    }
    
    (**************************************)
    (* Call Name: GetQueueReady         	*)
    (* Function: Forces Queue Ready       *)
    (* Return:   n/a 				 					    *)
    (**************************************)	
    DEFINE_FUNCTION CHAR GetQueueReady(_QUEUE uQueue)
    {    
    	IF (uQueue.cQueueHasItems && uQueue.cQueueBusy == FALSE)
    		RETURN TRUE;
    	ELSE
    		RETURN FALSE;
    }
    
    (**************************************)
    (* Call Name: GetQueueEmpty         	*)
    (* Function: Test if Queue is Empty   *)
    (* Return:   n/a 				 					    *)
    (**************************************)	
    DEFINE_FUNCTION CHAR GetQueueEmpty(_QUEUE uQueue)
    {    
    	IF (uQueue.cQueueHasItems == FALSE)
    		RETURN TRUE;
    	ELSE
    		RETURN FALSE;
    }
    
    

    // Example
    DEFINE_VARIABLE
    
    VOLATILE _QUEUE uEMSMsgQueue;
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    (**************************************)
    (* Call Name: SendMessage			        *)
    (* Function: Sends Message to Device  *)
    (* Return:   n/a 				 					    *)
    (**************************************)
    DEFINE_FUNCTION SendMessage(CHAR cMsg[])
    {
    	SEND_STRING dvSocketClient,"cMsg,13";
    	SEND_STRING 0,"'SendMessage:',cMsg"
    	
    	SetQueueReady(uEMSMsgQueue,FALSE);
    	
    	WAIT 50 'NO RESPONSE'
    	{
    		SetQueueReady(uEMSMsgQueue,TRUE);
    	
    		//ENQueue(uEMSMsgQueue,uEMSMsgQueue.cLastCommandSent);
    		
    		SEND_STRING 0,"'No Response:',uEMSMsgQueue.cLastCommandSent"
    	}
    }
    
    
    (**************************************)
    (* Call Name: CommService            	*)
    (* Function: Manages Client Comm      *)
    (* Return:   n/a 				 					    *)
    (**************************************)
    DEFINE_FUNCTION CommService(_QUEUE uQueue)
    {
    	SELECT 
    	{
    		ACTIVE (GetQueueReady(uQueue) == TRUE && GetQueueEmpty(uQueue) == FALSE && bSocketConnected == FALSE):	
    		{
    			OpenSocket(cIPAddress, nIPPort);
    		}
    		ACTIVE (GetQueueReady(uQueue) == TRUE && GetQueueEmpty(uQueue) == FALSE && bSocketConnected == TRUE):
    		{
    			SendMessage(DEQueue(uQueue));
    		}
    	}	
    }
    
    DEFINE_FUNCTION ParseSocket(CHAR cMsg[])
    {
    	CANCEL_WAIT 'NO RESPONSE'
    	SetQueueReady(uEMSMsgQueue,TRUE);
    	
    	SEND_STRING 0,"'Response:',cMsg"
    }
    
    
    (***********************************************************)
    (*                STARTUP CODE GOES BELOW                  *)
    (***********************************************************)
    DEFINE_START
    
    InitQueue(uEMSMsgQueue);
    
    (***********************************************************)
    (*            THE ACTUAL PROGRAM GOES BELOW                *)
    (***********************************************************)
    DEFINE_PROGRAM
    
    CommService(uEMSMsgQueue);
    
    
  • Options
    a_riot42a_riot42 Posts: 1,624
    DHawthorne wrote:
    I don't like to use timelines for command queues because I would like them to be more flexible. Many serial devices, for example, may respond differently at different times, depending on the command and how much other stuff is going on, and it seems a waste to always be waiting the maximum time interval between commands. I initiate a WAIT on a send, then either let it time out, or cancel it if a response comes in sooner than the WAIT time out. That way the queue isn't getting bogged down.

    I also don't think it does any harm to call a function in mainline, as long as the function itself only does a simple test in the beginning to determine if the rest is going to fire. The overhead to test a variable, for example, is small enough as to be entirely inconsequential, no matter how many times it's called. Running a complex operation, on the other hand, could seriously bog things down, so you do have to be careful about it. Mainline is just another tool that, used properly, can be very effectively, and used improperly cause you a lot of trouble. I absolutely do not subscribe to the philosophy that it is "old school" and should be avoided at all costs. That's kind of like refusing to ever use a hand screwdriver because you own a power one. Just when you do use it, use it carefully. Lots of modern programming languages implement "safe" methods of doing things that are touted as "better" because they cause so much less problems. But a skilled programmer can use the "older" methods to great advantage, though they are more dangerous if misused.

    All what you say is true and I am certainly not recommending to anyone that they not use mainline. However, in my experience having code in mainline means that code executes asynchronously, with no control over timing so I don't use it. I don't things happening on their own unless I am doing it from non-mainline code. There is always a way to do what you are doing without using mainline and the most time consuming bugs I have run into have been errors in mainline code so I choose to not use it. Like you say it can be dangerous, and if you are careful you won't get burned. So I just don't use it at all so I don't have to be careful and never get burned :)
    When there is no mainline code I would assume mainline never gets run and therefore frees up cycles for other things. Does anyone know if this is the case?
    Paul
  • Options
    DHawthorneDHawthorne Posts: 4,584
    Mainline runs no matter what. It is he same as main() in C++ parlance. It is a constantly executing loop, and at the top of the loop, it checks for things like WAITs and events in the event queue. It also does a lot of other behind-the-scenes things you have no control over. There is little difference (if any) between checking a variable in mainline, or checking an event flag; they are both simple register comparisons. Events are simply a way to help you manage your program; the processor doesn't see much difference. Naturally, if you are running a full-blown function in mainline, that is going to eat up CPU time, but if you are just checking a flag, I don't think it matters. It should be noted, however, that sending commands to a device in NetLinx also eats up a lot of CPU, as it not only sends it out, but waits for a response; putting a SEND_COMMAND to a device in mainline is very, very bad.

    That said, it just makes sense to use events when your action is truly to be triggered by something happening. It helps keep everything organized, and it helps to understand the flow of the program. I am all for putting as much as you can in events; however, there are times when using mainline is appropriate and I wouldn't do any calisthenics to avoid it.
  • Options
    This might be a little off topic but rs232 control of nexia products is extremely reliable... i have never seen it drop any strings because of buffer overflow problems or anything. Even setting all 31 bands of an eq in one for loop (without timing of string sending) will not break this....
  • Options
    amdpoweramdpower Posts: 110
    Dave, are you speaking purely of SEND_COMMANDS and not SEND_STRINGS? Do you think it is OK to send STRINGS in DEFINE_PROGRAM as my example at the top or is this dangerous as well even with the variable check?

    On another note, I've got another problem. The panel is an 8400. For some reason, Occasionally, if I push a button (for which its state is tied to a variable) it takes like 3-4 seconds to light. The action occurs but state doesn't change. This is only OCCASIONALLY though. Usually, the button changes state immediately even if I run the tables on button pushes. I'm thinking it's the wireless dropping and coming back but I can't tell. My access point is only 30 feet away. I'll probably need to check my online events. I'm using a Virtual for the panel as well. I was worried I might be hanging the master.

    Yet another note... Hopefully this isn't getting off topic. The only other interface is a MIO Classic-D. Occasionally, after a program load, the keypad acts up and won't respond. I check my online tree and the device shows up but is missing the firmware version. I reboot via a power down (hard reboot) and the keypad is fine again. Firmware shows up in online tree. Another program load (soft reboot) and the firmware is missing in the online tree and the keypad won't respond again.

    Any thoughts?
  • Options
    DHawthorneDHawthorne Posts: 4,584
    SEND_COMMAND and SEND_STRING use the same mechanism internally, and both wait for an acknowledgment from the device. If your feedback uses them, it's best to put it in a timeline, unless your code tests to see if they are necessary before actually sending them out. It's easy enough to check; turn on device notifications and activate it for all devices, and see what is going on. If you see dozens of commands going out per second, you have a potential problem that really should be optimized.
  • Options
    amdpoweramdpower Posts: 110
    Oh, OK. Yeah, no problem there. I'm checking to make sure I have something in the buffer and it's not going out all the time. Thanks.
  • Options
    a_riot42a_riot42 Posts: 1,624
    DHawthorne wrote:
    That said, it just makes sense to use events when your action is truly to be triggered by something happening. It helps keep everything organized, and it helps to understand the flow of the program. I am all for putting as much as you can in events; however, there are times when using mainline is appropriate and I wouldn't do any calisthenics to avoid it.

    Thanks Dave, that is insightful. I would have thought that changing from mainline to events processing would have at least involved a context switch which can have rather high overhead so I was hoping to avoid that but it doesn't sound like it is possible. I would have also thought that the cpu task scheduler would have intelligence built in that would not run mainline if there was no code there, but it doesn't sound like that happens either. So much for optimization. I have found that feedback seems snappier and page flips from code occur faster the less code I have in define_program but I don't have any evidence to back that up.

    I will use define_program when it makes sense, its just that I so rarely find cases when it does. I would be curious to hear when you find it necessary to use it. The only time I tend to have code in mainline is when I have to do something according to the time and need to check it continually, but even then calling TIME 200 times a second doesn't seem efficient to me when you only need something to happen with second or minute granularity. In those cases I would rather set up a timeline that checks the time every 30 seconds and calls functions accordingly.

    Another thing I don't like about code in mainline is that if you have to do a send_command as a result of a status change of a variable you have to surround everything with if guards so that send_commands don't get sent continously so you end up with something like:
    if ( currentRoom != previousRoom)
    {
        previousRoom = currentRoom   // forget this line of code at your peril 
        send_command dvTP, "'TEXT1-', rooms[currentRoom]"
    }         
    

    The other trap that can catch you if you aren't careful is having code in define_program in more than one file. If there are any dependencies of the variables involved then the timing becomes an issue. I don't know in what order the processor runs code if there is more than one define_program section in your project, but it has burned me a couple of times so I just tend to avoid it altogether.

    If you have ever looked at the Kaleidescape module you will see they do feedback to all transport controls using define_program to run through 90 channels and update buttons accordingly. While it is a good module, I find that Kaleidescape buttons have the slowest feedback and I am guessing this is why.
    Thanks,
    Paul
  • Options
    AuserAuser Posts: 506
    This might be a little off topic but rs232 control of nexia products is extremely reliable... i have never seen it drop any strings because of buffer overflow problems or anything. Even setting all 31 bands of an eq in one for loop (without timing of string sending) will not break this....

    True in most cases, but not when dealing with the dialler portion of Nexia TC's or Audia TI-II cards. A crook firmware version which came out not so long ago had an issue which resulted in the dialler portion being almost uncontrollable. From memory they require something on the order of 600ms between commands or an error is returned even with working firmware versions.
  • Options
    DHawthorneDHawthorne Posts: 4,584
    a_riot42 wrote:
    I will use define_program when it makes sense, its just that I so rarely find cases when it does. I would be curious to hear when you find it necessary to use it. The only time I tend to have code in mainline is when I have to do something according to the time and need to check it continually, but even then calling TIME 200 times a second doesn't seem efficient to me when you only need something to happen with second or minute granularity. In those cases I would rather set up a timeline that checks the time every 30 seconds and calls functions accordingly.

    I don't know about necessary - but there are two types of cases were I regularly use mainline.

    One is for simple condition tracking. Say you have a power amp on a relay, and you want it on whenever the system is on, but there are a number of places where the system could be turned on in different ways. Since I am already setting a tracking variable when sources are selected, I'll put a line in mainline that reads [dvRelays, AMP_POWER] = (dvDeck <> 0:0:0). Then I don't have to go through the entire program looking for every time the system gets turned on, it just follows my variable. Since it is a physical device, the master tracks its state internally and only actually sends the command to fire it on or off when the condition changes (this will not work with a virtual device - those will wind up with the state command being sent every pass).

    The other is for buffer parsing. A STRING event is not always reliable; sometimes it only has part of the full string in it, sometimes it has more than one bit of feedback in it. I was looking at the output of a Runco projector the other day, and the STRING event fired for every character of the feedback, not the full string. I have seen other cases where my buffer, as created in CREATE_BUFFER had data in it, but a STRING event didn't fire for whatever reason. So instead, I'll put the call to parse the buffer in mainline; the parsing routine checks first if there is usable data in there, and exits immediately if not. This could as easily be done with a timeline, but I would prefer it to not be waiting the interval of the timeline if data is sitting there, and I suspect in a case like this the overhead of the timeline is greater.
Sign In or Register to comment.