Home AMX User Forum NetLinx Studio

XAP 800 data parsing

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.

Comments

  • AMXJeffAMXJeff Posts: 450
    Parsing of the XAP800/XAP400

    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];
    DEFINE_TYPE
    //////////////////////////////////////////////////////////////
    // CLEARONE XAP800
    //////////////////////////////////////////////////////////////
    STRUCTURE _XAP800
    {
    	CHAR 	cPreset; 
    	
    	CHAR 	bMUTE_M[12];
    	FLOAT fGAIN_M[12];
    	CHAR 	bMUTE_O[12];
    	FLOAT fGAIN_O[12];
    	CHAR 	bMUTE_P[8];
    	FLOAT fGAIN_P[8];
    }
    
    
    //////////////////////////////////////////////////////////////
    // CLEARONE XAP400
    //////////////////////////////////////////////////////////////
    STRUCTURE _XAP400
    {
    	CHAR	cPreset;
    	
    	CHAR 	bDialing;
    	CHAR	bInuse;
    	CHAR	bPrivacy;
    	CHAR	bTelcoRX;
    	CHAR	bRinging;
    	
    	CHAR 	bMUTE_M[8];
    	FLOAT fGAIN_M[8];
    	CHAR 	bMUTE_O[8];
    	FLOAT fGAIN_O[8];
    	CHAR 	bMUTE_P[4];
    	FLOAT fGAIN_P[4];
    	
    	FLOAT fGAIN_T;
    	FLOAT fGAIN_R;
    }
    
    DEFINE_VARIABLE
    
    //
    VOLATILE CHAR 		XAP800_1_ADDRESS[] 					=		'#50';
    VOLATILE CHAR 		XAP400_1_ADDRESS[]					=		'#70';
    
    // XAPs
    VOLATILE _XAP800 	uXAP800_1;
    VOLATILE _XAP400 	uXAP400_1;
    
    
    //////////////////////////////////////////////////////////////
    // XAP PARSE RESPONSES
    //////////////////////////////////////////////////////////////
    DEFINE_FUNCTION XAPs_ParseResponse()
    {
      LOCAL_VAR CHAR cMsg[50], cReady[10], cGroup[1], cTempChannel[2], cChannel;
    	LOCAL_VAR FLOAT flGain;
      
      cMsg = REMOVE_STRING(cXAPBuffer, "CR,LF", 1);							// GOT COMPLETE MESSAGE
    
      IF (LENGTH_STRING(cMsg) != 0)
      {
        IF ([dvXAPs,DEBUGGING] && LENGTH_STRING(cMsg) > 3)
          SEND_STRING 0, "'From XAPs: ',cMsg";
        
        SET_LENGTH_STRING(cMsg,LENGTH_STRING(cMsg) - 2);        // STRIP BACK CR,LF
        
        SELECT
        {
    			// XAP800 #1
    			// PRESETS
          ACTIVE (FIND_STRING(cMsg, "XAP800_1_ADDRESS,' PRESET '",1)):
          {
            REMOVE_STRING(cMsg,"XAP800_1_ADDRESS,' PRESET '",1);
            
            uXAP800_1.cPreset = ATOI(REMOVE_STRING(cMsg,' ',1));
          }
    			
    			// '#XX MUTE c g x'
          ACTIVE (FIND_STRING(cMsg, "XAP800_1_ADDRESS,' MUTE '",1)):
          {
            REMOVE_STRING(cMsg,"XAP800_1_ADDRESS,' MUTE '",1);
            
            // WATCH FOR '*'
            cTempChannel = REMOVE_STRING(cMsg,' ',1);
    				cChannel = ATOI(cTempChannel);
    				
    				cGroup = GET_BUFFER_STRING(cMsg,2);
            
            SWITCH (cGroup)
            {
              CASE 'M':	uXAP800_1.bMUTE_M[cChannel] = cMsg = '1';
              CASE 'I':	uXAP800_1.bMUTE_M[cChannel] = cMsg = '1';
    					CASE 'L':	uXAP800_1.bMUTE_M[cChannel] = cMsg = '1';
              CASE 'O':	uXAP800_1.bMUTE_O[cChannel] = cMsg = '1';
    					CASE 'P':
    					{
    						cChannel = cTempChannel[1] - 'A' + 1;
    						
    						uXAP800_1.bMUTE_P[cChannel] = cMsg = '1';
    					}
            }
          }
    
          // '#XX GAIN c g x'
          ACTIVE (FIND_STRING(cMsg, "XAP800_1_ADDRESS,' GAIN '",1)):
          {
            REMOVE_STRING(cMsg,"XAP800_1_ADDRESS,' GAIN '",1);
            
            // WATCH FOR '*'
            cTempChannel = REMOVE_STRING(cMsg,' ',1);
    				cChannel = ATOI(cTempChannel);
    				
    				cGroup = GET_BUFFER_STRING(cMsg,2);
    				
    				flGain = ATOF(cMsg);
    				
            SWITCH (cGroup)
            {
              CASE 'M':	uXAP800_1.fGAIN_M[cChannel] = flGain;
              CASE 'I':	uXAP800_1.fGAIN_M[cChannel] = flGain;
    					CASE 'L':	uXAP800_1.fGAIN_M[cChannel] = flGain;
              CASE 'O':	uXAP800_1.fGAIN_O[cChannel] = flGain;
    					CASE 'P':
    					{
    						cChannel = cTempChannel[1] - 'A' + 1;
    						
    						uXAP800_1.fGAIN_P[cChannel] = flGain;
    					}
            }
          }
    			
    
    			// XAP400
    			// PRESETS
          ACTIVE (FIND_STRING(cMsg, "XAP400_1_ADDRESS,' PRESET '",1)):
          {
            REMOVE_STRING(cMsg,"XAP400_1_ADDRESS,' PRESET '",1);
            
            uXAP400_1.cPreset = ATOI(REMOVE_STRING(cMsg,' ',1));
          }
    			
    			// '#7X MUTE c g x'
          ACTIVE (FIND_STRING(cMsg, "XAP400_1_ADDRESS,' MUTE '",1)):
          {
            REMOVE_STRING(cMsg,"XAP400_1_ADDRESS,' MUTE '",1);
            
            // WATCH FOR '*'
            cTempChannel = REMOVE_STRING(cMsg,' ',1);
    				cChannel = ATOI(cTempChannel);
    				
    				cGroup = GET_BUFFER_STRING(cMsg,2);
            
            SWITCH (cGroup)
            {
              CASE 'M':	uXAP400_1.bMUTE_M[cChannel] 	= cMsg == '1';
              CASE 'I':	uXAP400_1.bMUTE_M[cChannel] 	= cMsg == '1';
    					CASE 'L':	uXAP400_1.bMUTE_M[cChannel] 	= cMsg == '1';
              CASE 'O':	uXAP400_1.bMUTE_O[cChannel] 	= cMsg == '1';
    					CASE 'T': uXAP400_1.bPrivacy  					= cMsg == '1';
    					CASE 'R': uXAP400_1.bTelcoRX						= cMsg == '1';
    					CASE 'P':
    					{
    						cChannel = cTempChannel[1] - 'A' + 1;
    						
    						uXAP400_1.bMUTE_P[cChannel] = cMsg = '1';
    					}
            }
    				
    				// MATCH OUTPUT XAP_PROGRAM_OUTPUTS
    				IF (cGroup == 'O' && cChannel == XAP_PROGRAM_OUTPUT)
    				{
    					XAPs_SetOutputMute(XAP400_1_ADDRESS, uXAP400_1.bMUTE_O, cChannel + 1, uXAP400_1.bMUTE_O[cChannel]);
    				}
    				
    				// MATCH VTC OUTPUT 1 TO 2
    				IF (cGroup == 'O' && cChannel == 1)
    					XAPs_SetOutputMute(XAP400_1_ADDRESS, uXAP400_1.bMUTE_O, cChannel + 1, uXAP400_1.bMUTE_O[cChannel]);
          }
    
          // '#7X GAIN c g x'
          ACTIVE (FIND_STRING(cMsg, "XAP400_1_ADDRESS,' GAIN '",1)):
          {
            REMOVE_STRING(cMsg,"XAP400_1_ADDRESS,' GAIN '",1);
            
            // WATCH FOR '*'
            cTempChannel = REMOVE_STRING(cMsg,' ',1);
    				cChannel = ATOI(cTempChannel);
    				
    				cGroup = GET_BUFFER_STRING(cMsg,2);
    				
    				flGain = ATOF(cMsg);
    				
            SWITCH (cGroup)
            {
              CASE 'M':	uXAP400_1.fGAIN_M[cChannel] 	= flGain;
              CASE 'I':	uXAP400_1.fGAIN_M[cChannel] 	= flGain;
    					CASE 'L':	uXAP400_1.fGAIN_M[cChannel] 	= flGain;
              CASE 'O':	uXAP400_1.fGAIN_O[cChannel] 	= flGain;
    					CASE 'T': uXAP400_1.fGAIN_T 						= flGain;
    					CASE 'R': uXAP400_1.fGAIN_R 						= flGain;
    					CASE 'P':
    					{
    						cChannel = cTempChannel[1] - 'A' + 1;
    						
    						uXAP400_1.fGAIN_P[cChannel] = flGain;
    					}
            }
    				
    				// MATCH OUTPUT XAP_PROGRAM_OUTPUTS
    				IF (cGroup == 'O' && cChannel == XAP_PROGRAM_OUTPUT)
    				{
    					XAPs_SetOutputLevel(XAP400_1_ADDRESS, uXAP400_1.fGAIN_O, cChannel + 1, uXAP400_1.fGAIN_O[cChannel]);
    				}
    				
    				// MATCH VTC OUTPUT 1 TO 2
    				IF (cGroup == 'O' && cChannel == 1)
    					XAPs_SetOutputLevel(XAP400_1_ADDRESS, uXAP400_1.fGAIN_O, cChannel + 1, uXAP400_1.fGAIN_O[cChannel]);
          }
    			
    			// #7X TE 1 x
          ACTIVE (FIND_STRING(cMsg, "XAP400_1_ADDRESS,' TE 1 '", 1)):
          {
    				REMOVE_STRING(cMsg, "XAP400_1_ADDRESS,' TE 1 '", 1)
    				
    				uXAP400_1.bInuse = cMsg == '1';
    				
    				CANCEL_WAIT 'XAP400_1 RINGING';
          }
    			
    			// #7X RING 1
    			ACTIVE (FIND_STRING(cMsg, "XAP400_1_ADDRESS,' RING 1'", 1)):
          {
    				REMOVE_STRING(cMsg, "XAP400_1_ADDRESS,' RING 1'", 1)
            
    				IF (uXAP400_1.bRinging == FALSE)
    				{
    					uATC.cAconfDialNumber = "" ;
    					MSLIB_VText(TPA, VTEXT_ATC_DIALING_NUMBER, 'Incoming Call...');
        
    					SEND_COMMAND TPA, 'ADBEEP';
    					SEND_COMMAND TPA, '@PPN-M_ATCIncoming';
    				}
    				
    				uXAP400_1.bRinging = TRUE;
    				
    				CANCEL_WAIT 'XAP400_1 RINGING';
    				WAIT 50 'XAP400_1 RINGING'
    				{
    					uXAP400_1.bRinging = FALSE;
    					
    					uATC.cAconfDialNumber = "";
    					MSLIB_VText(TPA, VTEXT_ATC_DIALING_NUMBER, ' ');
        
    					SEND_COMMAND TPA, '@PPF-M_ATCIncoming';
    				}
          }
    		}
    	}
    	ELSE
    	{
    		cReady = REMOVE_STRING(cXAPBuffer,"'> '",1);
    		
    		IF (LENGTH_STRING(cReady) > 0)
    		{
    			CANCEL_WAIT 'XAP BUSY';
    			bXAPBusy = FALSE;
    		}
    	}
    }
    
  • Thanks

    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?
  • HedbergHedberg Posts: 671

    cTempChannel = REMOVE_STRING(cMsg,' ',1) -- If I am correct, cTempChannel is going to equal "M 0".

    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'
    cChannel = ATOI(cTempChannel) -- ATOI is going to dump the "M " and return "0"

    Following on, cChannel is 1
    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"?

    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'
    CASE 'M': uXAP800_1.bMUTE_M[cChannel] = cMsg = '1'

    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:
    if(cmsg = '1')
    {
      uXAP800_1.bMUTE_M[cChannel] = 1
    }
    else
    {
        uXAP800_1.bMUTE_M[cChannel] = 0
    }
    
    -- 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

    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 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 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:
    DEFINE_VARIABLE
    
    //
    VOLATILE CHAR 		XAP800_1_ADDRESS[] 					=		'#50';
    VOLATILE CHAR 		XAP800_2_ADDRESS[] 					=		'#51';
    VOLATILE _XAP800 	uXAP800_1;
    VOLATILE _XAP800 	uXAP800_2;
    

    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.
  • Hedberg wrote: »
    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'

    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.

    Hedberg wrote: »
    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.

    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.
  • Functions 101

    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?
  • HedbergHedberg Posts: 671
    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.
    DATA_EVENT[dvXAP]
    {
    
        STRING:
        {
            cXAPBuffer= "cXAPBuffer,DATA.TEXT"
    
            WHILE (FIND_STRING(cXAPBuffer,"13,10",1)) 
            {
                 XAPs_ParseResponse()
            }
        }
    }
    
  • Thanks. The problem was all on my side. I left out one part. I had tried:
    DATA_EVENT[dvXAP]
    {
    
        STRING:
        {
            cXAPBuffer= "cXAPBuffer,DATA.TEXT"
            IF (LENGTH_STRING(cXAPBuffer) != 0)
            {
            XAPs_ParseResponse()
            }
        }
    }
    

    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.
  • "!=" is not valid in Netlinx for determining "not equal to".

    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
  • AMXJeffAMXJeff Posts: 450
    Actually... I hate to disagree, especially with you Chip... But != does work in this case...
    DEFINE_VARIABLE
    
    test[100] = 'hello';
    
    DEFINE_PROGRAM
    
    WAIT 100
    {
    	IF (LENGTH_STRING(test) != 0)
    		SEND_STRING 0,'PASS'
    	ELSE	
    		SEND_STRING 0,'FAIL'
    }
    
  • Awwww. Thanks Jeff. :) (& please tell Mark I said "hi")

    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
  • FUNCTIONS 101

    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.
  • Remove_String?

    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.
    WHILE (FIND_STRING(cXAPBuffer,"13,10",1))
    {
      sThisData = REMOVE_STRING(cXAPBuffer,"13,10",1)
      //Code to process sThisData goes below
    }
    

    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!
  • Oooooooo. That makes sense. That would explain everything locking up. Here is the code I am using, which is mostly the code AMXJeff was so kind to provide.
    DEFINE_TYPE
    
    STRUCTURE _XAP50		//First XAP
    {
        CHAR bMUTE_M[12];
        CHAR bMUTE_P[8];
        FLOAT fGAIN_P[8];
    }
    STRUCTURE _XAP51		//Second XAP
    {
        CHAR bMUTE_M[12];
        CHAR bMUTE_P[8];
        FLOAT fGAIN_P[8];
    }
    STRUCTURE _XAP60		//Telco XAP
    {
        CHAR bDIALING;
        CHAR bINUSE;
        CHAR bPRIVACY;
        CHAR bTELCORX;
        CHAR bRINGING;
    }
    
    DEFINE_VARIABLE
    
    CHAR XAP800_1_ADDRESS[]	=	'#50'
    CHAR XAP800_2_ADDRESS[]	=	'#51'
    CHAR XAP800_3_ADDRESS[]	=	'#60'
    _XAP50 uXAP800_1
    _XAP51 uXAP800_2
    _XAP60 uXAP800_3
    
    DEFINE_FUNCTION XAPS_ParseResponse()
    {
        LOCAL_VAR CHAR cMSG[50], cReady[10], cGroup[1], cTempChannel[2], cChannel
        LOCAL_VAR FLOAT flGAIN
        cMSG = REMOVE_STRING(cXAP_BUFF, 'CR,LF',1)		//REMOVE_STRING removes everything up to and including the specified characters
        IF (LENGTH_STRING(cMSG) != 0)			//If anything is in string cMSG
        {
    	SET_LENGTH_STRING(cMSG,LENGTH_STRING(cMSG) - 2)	//removes the CR and LF
    	SELECT
    	{
    	    ACTIVE(FIND_STRING(cMSG, "XAP800_1_ADDRESS,' MUTE '",1)):		//'#50 MUTE 1 M 0'
    	    {
    		REMOVE_STRING (cMSG,"XAP800_1_ADDRESS,' MUTE '",1)		//1 M 0
    		cTempChannel = REMOVE_STRING(cMSG,' ',1)		//1 
    		cChannel = ATOI(cTempChannel)		//1
    		cGroup = GET_BUFFER_STRING(cMSG,2)
    		SWITCH (cGroup)
    		{
    		    CASE 'M': uXAP800_1.bMUTE_M[cChannel] = cMSG = '1'
    		    CASE 'P':
    		    {
    			cChannel = cTempChannel[1] - 'A' + 1
    			uXAP800_1.bMUTE_P[cCHannel] = cMSG = '1'
    		    }
    		}
    	    }
    	    ACTIVE(FIND_STRING(cMSG, "XAP800_1_ADDRESS,' GAIN '",1)):
    	    {
    		REMOVE_STRING(cMSG, "XAP800_1_ADDRESS,' GAIN '",1)
    		cTempChannel = REMOVE_STRING(cMSG,' ',1)
    		cChannel = ATOI(cTempChannel)
    		cGroup = GET_BUFFER_STRING(cMSG,2)
    		flGAIN = ATOF(cMSG)
    		SWITCH (cGroup)
    		{
    		    CASE 'P':
    		    {
    			cChannel = cTempChannel[1] - 'A' + 1
    			uXAP800_1.fGAIN_P[cCHannel] = flGAIN
    		    }
    		    
    		}
    	    }
    	    ACTIVE(FIND_STRING(cMSG, "XAP800_2_ADDRESS,' MUTE '",1)):
    	    {
    		REMOVE_STRING (cMSG,"XAP800_2_ADDRESS,' MUTE '",1)
    		cTempChannel = REMOVE_STRING(cMSG,' ',1)
    		cChannel = ATOI(cTempChannel)
    		cGroup = GET_BUFFER_STRING(cMSG,2)
    		SWITCH (cGroup)
    		{
    		    CASE 'M': uXAP800_2.bMUTE_M[cChannel] = cMSG = '1'
    		    CASE 'P':
    		    {
    			cChannel = cTempChannel[1] - 'A' + 1
    			uXAP800_2.bMUTE_P[cCHannel] = cMSG = '1'
    		    }
    		}
    	    }
    	    ACTIVE(FIND_STRING(cMSG, "XAP800_2_ADDRESS,' GAIN '",1)):
    	    {
    		REMOVE_STRING(cMSG, "XAP800_2_ADDRESS,' GAIN '",1)
    		cTempChannel = REMOVE_STRING(cMSG,' ',1)
    		cChannel = ATOI(cTempChannel)
    		cGroup = GET_BUFFER_STRING(cMSG,2)
    		flGAIN = ATOF(cMSG)
    		SWITCH (cGroup)
    		{
    		    CASE 'P':
    		    {
    			cChannel = cTempChannel[1] - 'A' + 1
    			uXAP800_2.fGAIN_P[cCHannel] = flGAIN
    		    }
    		    
    		}
    	    }
    	}
        }
    }
    
    DATA_EVENT[dvXAP]				//XAP Event
    {
        ONLINE:
        {
    	SEND_COMMAND dvXAP,"'TSET BAUD 9600,N,8,1 485 DISABLE'"
    	SEND_STRING dvXAP,"'#60 TE 1',13"	//queries phone on / off hook status
    	WAIT 30
    	{
    	    SEND_STRING dvXAP,"'#50 GAIN A P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	    SEND_STRING dvXAP,"'#50 GAIN B P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	}
        }
        STRING:					//XAP buffer for data parsing
        {
    	//cXAP_BUFF = "cXAP_BUFF,DATA.TEXT"
    	WHILE (FIND_STRING(cXAP_BUFF,"13,10",1))
    	{
    	    XAPS_ParseResponse()
    	}	
    	
        }
    }
    


    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. ;)
  • AMXJeffAMXJeff Posts: 450
    The only comment I have is that you do not need to create a structure for each XAP800. The structure is a user defined type and can be used for multiple variables.

    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?
    DEFINE_TYPE
    
    STRUCTURE _XAP800	//ALL XAP
    {
        CHAR bMUTE_M[12];
        CHAR bMUTE_P[8];
        FLOAT fGAIN_P[8];
    }
    
    DEFINE_VARIABLE
    
    CHAR XAP800_1_ADDRESS[]	=	'#50'
    CHAR XAP800_2_ADDRESS[]	=	'#51'
    
    _XAP800 uXAP800_1
    _XAP800 uXAP800_2
    
    
  • My problem seems to be actually getting the data into the function. I had originally thought that since the XAP buffer was global, the data would automatically be available to the function for processing. And I thought this line, "cMSG = REMOVE_STRING(cXAP_BUFF, 'CR,LF',1)" would be the line that would pull the XAP buffer into the function. Once I could get the function functioning, I was hoping I could then mold it to how I need it, ie more parsing or less parsing for my usage. Last night I loaded up the code, exactly as I posted it here. The entire system locked up. Everything was visible in Studio in the Online Tree, and I could push a button on any of the TP's and watch the Input light on the Netlinx flash, but that would be all that would happen. The button would not do anything. It took a bit of troubleshooting, but I finally figured out if I commented out the line "WHILE (FIND_STRING(cXAP_BUFF,"13,10",1))" everything would work properly with the exception of feedback from the XAPs. It was suggested that particular line caused an endless loop.

    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.
  • AMXJeffAMXJeff Posts: 450
    I see the problem....

    // 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.
  • D'oh! I knew it was a missed crossed T or dotted I. I can't get in the room to try it until 1600EST. I changed the line from this:

    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:
    DATA_EVENT[dvXAP]				//XAP Event
    {
        ONLINE:
        {
    	SEND_COMMAND dvXAP,"'TSET BAUD 9600,N,8,1 485 DISABLE'"
    	SEND_STRING dvXAP,"'#60 TE 1',13"	//queries phone on / off hook status
    	WAIT 30
    	{
    	    SEND_STRING dvXAP,"'#50 GAIN A P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	    SEND_STRING dvXAP,"'#50 GAIN B P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	}
        }
        STRING:					//XAP buffer for data parsing
        {
    	//cXAP_BUFF = "cXAP_BUFF,DATA.TEXT"
    	WHILE (FIND_STRING(cXAP_BUFF,"13,10",1))
    	{
    	    XAPS_ParseResponse()
    	}	
    	
        }
    }
    

    and the STRING portion would only need cXAP_BUFF = "cXAP_BUFF,DATA.TEXT" or would that become unneccessary?
  • AMXJeffAMXJeff Posts: 450
    There is nothing wrong with the current string section..

    However, I would not use cXAP_BUFF = "cXAP_BUFF,DATA.TEXT"...

    I would use, a create buffer statement.
    DEFINE_START
    
    CREATE_BUFFER dvXAP, cXAP_BUFF
    
    DEFINE_EVENT
    
    DATA_EVENT[dvXAP]				//XAP Event
    {
        ONLINE:
        {
    	SEND_COMMAND dvXAP,"'TSET BAUD 9600,N,8,1 485 DISABLE'"
    	SEND_STRING dvXAP,"'#60 TE 1',13"	//queries phone on / off hook status
    	WAIT 30
    	{
    	    SEND_STRING dvXAP,"'#50 GAIN A P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	    SEND_STRING dvXAP,"'#50 GAIN B P ',ITOA((nLevel_Bargraph / 2) - 33),'A',13"
    	}
        }
        STRING:					//XAP buffer for data parsing
        {
    	WHILE (FIND_STRING(cXAP_BUFF,"13,10",1))
    	{
    	    XAPS_ParseResponse()
    	}		
        }
    }
    
  • This is what I have always had in my original code:
    DEFINE_VARIABLE
    
    VOLATILE_CHAR cXAP_BUFF[100]  //100 for testing. Adjust size accordingly once running
    
    
    DEFINE_START
    
    CREATE_BUFFER dvXAP, cXAP_BUFF
    

    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.
  • It Worked!!!! Sort of...

    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! :D

    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.
Sign In or Register to comment.