XAP 800 data parsing
staticattic
Posts: 200
I am working in a conference room that has 2 XAP 800's for the audio. They are also using a XAP TH2 for telco purposes. I can control all of them just fine. What I am having problems with is parsing the data I get back. For the gain or phone hook status, I simply created a DATA_EVENT to fire off whenever "TE" (Telco Enable) or "GAIN" (volume level) are changed. No problems there. I am having problems with the status of the mics. There are 10 floor mics and 3 ceiling mics. On the TPs the customer wants a simple status check of the mics. If ANY of the floor mics are open, the TPs will say something like "floor mics open". If ANY of the ceiling mics are open, "ceiling mics open" will also light up. If they are all closed, the opposite is true. I can queary the status of the mics by sending this string:
#50 MUTE 1 M
If mic 1 is muted, the response back will be:
#50 MUTE 1 M 1
That applies to all of the mics. I can create a DATA_EVENT to look for the word "MUTE" in the XAP buffer, however, when I do that, it gets stuck in a loop since the queary and the reply both contain the word "MUTE". I need to queary all 13 mics and report the status. So, if floor mic status contains anything other than 1, at least one of them is on and "floor mics open" should light. If all of them equal 1, the "floor mics off" should light. What would be a good way to handle this? I am thinking a FOR loop would work here, but I am not sure how to implement it. Any help would be greatly appreciated. Thanks.
#50 MUTE 1 M
If mic 1 is muted, the response back will be:
#50 MUTE 1 M 1
That applies to all of the mics. I can create a DATA_EVENT to look for the word "MUTE" in the XAP buffer, however, when I do that, it gets stuck in a loop since the queary and the reply both contain the word "MUTE". I need to queary all 13 mics and report the status. So, if floor mic status contains anything other than 1, at least one of them is on and "floor mics open" should light. If all of them equal 1, the "floor mics off" should light. What would be a good way to handle this? I am thinking a FOR loop would work here, but I am not sure how to implement it. Any help would be greatly appreciated. Thanks.
0
Comments
You can do this one better by making the variables "uXAP800_1" and "uXAP400_1" arrays and parsing the address of the XAP for the array index, you will have to add one to that value because the XAP address scheme is zero based and our variable arrays start at index 1.
VOLATILE _XAP800 uXAP800[MAX_XAP_800];
VOLATILE _XAP400 uXAP400[MAX_XAP_400];
Thank you very much for the push in the right direction. I'd like to write it out in paragraph form to make sure I understand it.
The first part sets up a structure to contain variables and assign data to those variables. You created arrays in your structure to hold that data, like bMUTE_M[12]. Then a variable was created for adressing the XAPs, for example #50.
Following a "mic mute" queary or change, this is what will happen:
cMsg = REMOVE_STRING(cXAPBuffer, "CR,LF", 1); ---This will remove everything from the buffer up and including the CR and LF and assign it to cMsg. So, at this point cMsg is going to equal something like "#50 MUTE 1 M 0 CR, LF"
IF (LENGTH_STRING(cMsg) != 0) --verifies if anything is in cMsg
SET_LENGTH_STRING(cMsg,LENGTH_STRING(cMsg) - 2) -- now cMsg is going to equal something like "#50 MUTE 1 M 0"
ACTIVE (FIND_STRING(cMsg, "XAP800_1_ADDRESS,' MUTE '",1)): --This is going to check (and should find) in the string held in cMsg for "#50 MUTE ". Once it verifies, it will execute: REMOVE_STRING(cMsg,"XAP800_1_ADDRESS,' MUTE '",1). At this point, cMsg is going to look like: "1 M 0".
cTempChannel = REMOVE_STRING(cMsg,' ',1) -- If I am correct, cTempChannel is going to equal "M 0".
cChannel = ATOI(cTempChannel) -- ATOI is going to dump the "M " and return "0"
cGroup = GET_BUFFER_STRING(cMsg,2) -- cGroup is going to need to equal "M" in order for the code to work. Unless I skipped something somewhere, I thought cMsg was still equal to "1 M 0"?
CASE 'M': uXAP800_1.bMUTE_M[cChannel] = cMsg = '1' -- I don't quite understand this line either. Going back to what I know about structures, we are using a dot operator to assign a value to bMUTE_M which is 0. But how it is putting it all back together, I don't get. bMUTE_M[12] was declared as a part of structure _XAP800 and we are assigning a value of 1 to position 0. I am assuming since I have 13 mics, I need to make something like: bMUTE_M[13]. This will give bMUTE_M (after a queary of all mics) a value of something like: 0000100010010 (if mics 5, 9, and 12 had their mutes on). Is that correct? From that, all I will need to do would be to check the values held in bMUTE_M and make my touchpanel feedback act accordingly?
Am I at least close?
I haven't studied this carefully, but I think you're mistaken here. remove_string returns the string removed and the string that it operates on is thereby truncated. So, after the remove_string function, what you have is :
cTempChannel = '1'
and
cMsg = 'M 0'
Following on, cChannel is 1
The string '1 ' was removed from cmsg so: cmsg = 'M 0'
get_buffer_string removes the specified number of characters from the object string and returns that string. so after this function:
cGroup = 'M'
and
cmsg = '0'
this is a simple(sort of) assignment statement. To more easily understand it write it as:
uXAP800_1.bMUTE_M[cChannel] = (cMsg = '1')
You are setting the array value to the value of the logical expression (cmsg = '1')
That value is 1 (true) if cmsg == '1' and 0 (false) if cmsg = '0' those are the only possible values that cmsg can contain at this point (if a proper mute status report came from the XAP).
so, it can be written as:
Actually, we are asigning a value of zero to array element 1. cChannel (which is an integer, I believe) is 1 and the mute is off, so the value is zero.
I think you are very close to understanding how the sample code runs and I think you are almost all the way to understanding your problem. But, you need to look at how your system is put together and configured. An XAP800, I believe, has 12 inputs and 12 outputs. Of the 12 inputs, it has only 8 mic inputs and then four line-level inputs. Inputs 1-8 are mic inputs and 9-12 are line-level. The 'I' group contains all channels but the 'M' group only contains channels 1-8. The 'L' group contains channels 9 - 12. The way that the code sample you are looking at works is to extract mute and gain information from any channel in the "M" group (channels 1-8), the 'L' group(9-12) or the I group (1 - 12) and store the value of the mute status in the appropriate array element of the bMUTE_M array.
So, you have 13 mics attached -- they are not all hooked up to the mic channels of one XAP800, which is why you have two XAP800s, I suppose. Probably, eight mics are hooked up to XAP800 aka #50 and five hooked to XAP800 aka #51. So, to use the code sample you will need to do something like:
and then teach the parsing function to distinguish between the two. You will then have two bMute_M arrays full of the input(mic or line) mute data which you can analyze for your purposes.
ClearOne devices are very easy to program and their return strings are very easy to parse. But, from time to time, a lot of information may be coming back from the XAPs and you have to watch out. For example, if you are trying to provide feedback to your touch panels for the status of mute and gain on all input and output channels, you have 48 statuses to keep track of. To the best of my knowledge, it's not possible to get 48 status request sent to an XAP, get 48 reponses, and parse them all and update your touchpanels just any old random time that you please. You very well may need a queue to control the status requests and allow for sufficient time to get the response back from the XAP. Also, watch out for presets because the XAP800 will spit out a ton of information after a preset has been executed.
Perhaps you can figure out smarter ways to control these things than I have. You might even consider using the AMX modules -- they will work though they are very, very bloated, in my opinion, and for something as simple as an XAP800, I think they are overkill.
Good luck with this -- there's a lot of great stuff in that code block AMX Jeff provided and should provide a solid basis for interpreting the return strings from an XAP whether you write an include file, or your own module. It's also a fine example of a structured programming approach to buffer parsing in general.
D'oh! You are correct sir. To quote the handy, dandy AMX handbook:
"The REMOVE_STRING keyword is similar to the FIND_STRING keyword. However, when Axcess finds the sequence it is looking for, it extracts every character up to and including the sequence. All other characters move up to fill in the space."
I got it right with the first REMOVE_STRING:
cMsg = REMOVE_STRING(cXAPBuffer, "CR,LF", 1); ---This will remove everything from the buffer up and including the CR and LF and assign it to cMsg. So, at this point cMsg is going to equal something like "#50 MUTE 1 M 0 CR, LF"
I guess I got twisted up. So, that being the case:
ACTIVE (FIND_STRING(cMsg, "XAP800_1_ADDRESS,' MUTE '",1)):
{
REMOVE_STRING(cMsg,"XAP800_1_ADDRESS,' MUTE '",1); -- This REMOVE_STRING, since it is not assigning anything to a variable, is going to remove everything up to the word "MUTE " and send it off to the bit bucket. So cMsg is going to look like: "1 M 0".
cTempChannel = REMOVE_STRING(cMsg,' ',1); -- This REMOVE_STRING, like you said, is going to remove everything up to and including the first space and assign it to cTempChannel: "1 "
cChannel = ATOI(cTempChannel); -- ATOI is going to be so kind as to return a single integer as the result. So, cChannel is going to equal "1".
I have it now. A careless mistake on my part.
They are easy to control, it was the feedback that has been giving me a hard time. Simply checking to see if the phone was on or off the hook was easy enough as a DATA_EVENT. You are right, the XAPs spit out a ton of data. They really slam the NetLinx when checking for gain levels. For room volume, all I control are 2 processor levels. If I give them up and down buttons, one set controls both, it works just fine. If I give them an active bargraph, the NetLinx will totally wig out. One thing I have thought about trying, was giving them one set of up and down buttons and using a PUSH to raise the volume and a HOLD[x] to continue ramping the volume. I can then send the feedback to an informational only bargraph. Hopefully, that will be slow enough to process by the NetLinx. I can't wait to be able to get back in that room and do more coding and testing. Thanks Hedberg and AMXJeff for all of your help.
I wrote my own Function by reverse engineering the example given. It did not work. I even cut and pasted the example directly into my code and none of the variables moved. Is there something I need to do to pass the XAP buffer into the function? The XAP buffer is global, so I thought it would automatically be availabale to the Function. Did I miss a step somewhere?
Look at TN 616.
To use the function provided by AMX Jeff, try something like this: (I have not tested the function Jeff provided, but it looks pretty good and if there is a problem, it should be sortable.) Some people suggest that you use create_buffer instead of the string expression functionality. Whatever integers your boat.
That did not do anything. Then I took out the IF portion all together and I got an invalid argument error (or something like that). I'll be able to get back in that room Tuesday. By then all of my frustrations should be gone. Thank you, everyone, for all of the help.
Try "<>".
That said, based on your code there is no need to test for length. For your data event to trigger, DATA.TEXT MUST have content in it, and therefore cXAPBuffer is guaranteed to have content after your concatenation is done...
- Chip
I thought there had been a thread not too long ago about != not being valid or working. Like, "if (x != y)" passed the compiler okay, but the code that was actually generated didn't work the way one would expect... IIRC, it translated to if y was non-zero, the condition passed... Like it ignored the x entirely and translated to "if (!(0) = y)".
- Chip
When I put this line in my code, it locked up the system.
WHILE (FIND_STRING(cXAPBuffer,"13,10",1))
I know I am overlooking something somewhere, but I am really getting frustrated with XAPs. Nothing I seem to do will get any data to the function. I can see the buffer from the XAP and it has data in it. If a do a data event and FIND_STRING, like a queary of the telephone status, on hook or off hook for example, my feedback works just fine. When I comment out the FIND_STRINGs and try to use the FUNCTION instead, I get nothing. I have also started getting an UNKNOWN NODE error when I try to compile that points to one of the lines in the function. I will send a copy of my code later. RIght now I am in an area that does not allow us to bring in memory sticks and my laptop does not have a floppy drive. When I get home and connect it to the internet, I'll send it. Thanks.
Inside the while loop you need to make sure you delete the "13,10" or the while loop is true forever and the master locks up. Endless Loop!
i.e.
My guess is in the XAPs_ParseResponse() function is a line similar to what I typed above. When you remove XAPs_ParseResponse() there is nothing to clear the data out of cXAPBuffer so your while loop always finds the "13,10"
Hope this helps!
I really did not change much from AMXJeff's example. I wrote my own, had no results, so I went back to his example. His had more data in it than I really need to parse, so I trimmed it down a bit, but other than that, I left it how I found it. I really think I am forgetting to dot an I or cross a T somewhere. The endless loop suggestion provided really makes sense. I hate that I have to wait until the evening before I can work in the room and have no internet connection or a phone. Thanks again for all of the help on this. I think I have used my monthly quota for questions on this one issue.
As far as the code is concerned it looks like it should work, are you having problems with it, or do you just want to make sure it is ready for testing when your on site?
I have pulled every thing into the debugger to watch for changes and nothing moves. The only thing that moves is cXAP_BUFF. Last night while driving home, I thought about making cMSG, cReady, cGroup, cTempChannel, and cChannel global instead of local just for grins and giggles. However, since they are not STACK_VARs, debugger should be able to display them. Like I said, I think I have forgotten to dot an I or cross a T somewhere.
// This is not working...
cMSG = REMOVE_STRING(cXAP_BUFF, 'CR,LF',1)
'CR,LF' is string literial. Orginal code had this "CR,LF".
DEFINE_CONSTANT
CR = 13
LF = 10
So either change to "13,10" or define the constants as above and change the single quotes for double quotes.
cMSG = REMOVE_STRING(cXAP_BUFF, 'CR,LF',1)
to this:
cMSG = REMOVE_STRING(cXAP_BUFF, "13,10",1)
That being the case, going back to my original assumption, since cXAP_BUFF is global, the function should automatically parse anything that finds its way into the XAP buffer. Is that a correct assumption? If so, then my data event for the XAP should only need the online portion of this:
and the STRING portion would only need cXAP_BUFF = "cXAP_BUFF,DATA.TEXT" or would that become unneccessary?
However, I would not use cXAP_BUFF = "cXAP_BUFF,DATA.TEXT"...
I would use, a create buffer statement.
I had commented in and out the DATA.TEXT line just trying different things. Then Hedberg brought up TN616, which I had read before, I just never paid much attention to it until Hedberg referenced it. I guess in my haste, I had forgot the original plan to leave the DATA.TEXT line commented out. Per the TN:
Parsing is never performed directly on the global structure element DATA.TEXT, because it is not guaranteed to contain a complete reply. This only contains the data that led to the triggering of the event. Multiple data events may run before a complete reply has been returned. To handle this, we are creating a local character array (named Buffer) and concatenating new serial data to this buffer.
Also note that, as mentioned in the comments, you could create a buffer for this device in DEFINE_START instead of concatenating your own local character array with DATA.TEXT. The advantage to using CREATE_BUFFER is that the buffer is managed by the Netlinx operating system instead of by the interpreted Netlinx code. The results are the same, but your code will run slightly faster, because it is not having to manage the buffer.
Thank you and everyone else for all of the help. I hope I wasn't too much of a thorn in the side. In 2 more hours, or so, I can get in the room and get to work on this. Thanks again. I'll let you know how it goes.
fGAIN_P shows up in debugger as "0,0,0,0,0,0,0,0" and the first two places will change accordingly to match the gain level. bMUTE_M and bMUTE_P show up in the debugger having a length of 0 and never move. I can work with that though. A little more tweaking and I should be rocking!
One interesting thing I experienced with the gain, having a multistate bargraph that adjusts its level in sync with the actual gain appears to overload the Netlinx processor. ClearOne suggests not doing that since the ClearOne slams the Netlinx with data. So, I went the opposite way. Instead of making the bargraph match the gain level, I made the gain level match the bargraph. A smoke and mirrors magic trick, but it works. Since the TP's are really the only interface with the XAP unless we connect a laptop to it serially for maintenance, as far as the user is concerned, they should never know the difference.