Outgoing String Queuing and Timeout
amdpower
Posts: 110
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
0
Comments
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.
I do somethign similar to this this way:
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
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:
The queue variable could be represented somewhat similar to:
(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.
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...
// Example
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
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.
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?
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:
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
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.
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.