Home AMX User Forum NetLinx Studio

DATA.TEXT and IP Messages

Greetings,

We all know that DATA.TEXT can return incomplete serial messages. I have always assumed that the same rules apply to IP messages and parsed accordingly.

I have been working with several Telnet IP-communicating devices lately and have noticed that each trigger of the string data_events returns a complete message.

What has been others' experiences with incoming IP strings?

Thanks.

Comments

  • DHawthorneDHawthorne Posts: 4,584
    I've never had a problem with IP strings or with intra-system strings/commands (as in master-to-master). I believe the issue with device strings is that of timing: the RS-232 port assumes the packet is complete if it sees a pause of a certain length and generates the event (I had this quasi-confirmed once by a tech at AMX ... and I say quasi because he was a tech, not an engineer). Those kinds of pauses are far more likely with RS-232 chips and hardware that may get bogged down, where an IP string has a built in terminus to show it's finished. So, the bottom line is I don't think you need to worry about it with IP.
  • ericmedleyericmedley Posts: 4,177
    Greetings,

    We all know that DATA.TEXT can return incomplete serial messages. I have always assumed that the same rules apply to IP messages and parsed accordingly.

    I have been working with several Telnet IP-communicating devices lately and have noticed that each trigger of the string data_events returns a complete message.

    What has been others' experiences with incoming IP strings?

    Thanks.

    For IP comm my general rule is to cache all IP messages and not rely on the Data_event to trigger any parsing code. I've just found that its best to be patient and wait for the whole message. Since a lot of IP stuff gets broken up between packets. So, I just keep adding IP messages to a cache/buffer and look for terminators that then act as the event that triggers the parsing. I also then peel off the data into another buffer. That's just how I roll...
  • viningvining Posts: 4,368
    I see no reason why IP devices wouldn't respond in a single data.text event if the entire transmission is under the 1500 byte MTU limit for ethernet but of course that depends on how the device transmitting goes about it.

    I think like most of us if it ain't internal AMX comms allways append to a local, global or created buffer but if the returns are large like when web scrapping it's best to store the rx data in your var but trigger (find_string) on the data.text which limits the string you have to search through to a max of 1500 bytes of the latest data event.
  • AMXJeffAMXJeff Posts: 450
    IP Communication and DATA.TEXT

    Although you normally get one complete packet at a time with IP, DATA.TEXT has a fixed size of 2048 bytes. So if your packet length exceed that, you will get multiple events with 2048 bytes at a time until the packet response has totally been reported to the data event.

    I always try to use create_buffers for RS232 and IP communication, and parse them inside the data_events.

    Jeff
  • the8thstthe8thst Posts: 470
    I often use data.text in the offline event of IP devices when the packets are smaller than 2048 and have never had a problem.
  • viningvining Posts: 4,368
    2048 is he max size of DATA.TEXT but a standard ethernet frame has a max of 1500 bytes for the payload + 18 bytes for the frame header. So as long as the data coming from the "IP" device can fit into 1 frame, 1500 bytes, it can and should arrive in a single event. Of course sending devices can frame their packets any way they want. If a device wants to send only a 100 byte payload at a time it can send that 100 bytes along with the header even if the total payload is greater than 100 bytes yet still under the max 1500 bytes permitted. Obviously it's more effecient to maximize the payload but device engineers can do what ever the heck they want except exceed the max limit unless they go to jumbo frames which can hold a 9000 byte payload.

    http://en.wikipedia.org/wiki/Ethernet_frame

    http://en.wikipedia.org/wiki/Jumbo_frame
  • TurnipTruckTurnipTruck Posts: 1,485
    Good info guys. Thanks. In my opinion, grabbing the message from data.text is more efficient than filling a buffer and examining its contents for delimiters, etc. I am working on an IP comm module now and will test out how well it works when assuming that each string data_event contains a whole message. In the case of this device, all expected messages are well under 100 bytes.
  • I've programmed one system using IP with serial commands (e.g. RS232 projector connected to a serial to ethernet controller). I came across a problem recently and have been scratching my head as to why my string parsing isn't working. I noticed that when I perform a DATA.TEXT on the string event, I only get ONE Byte at a time and I try to perform a FIND_STRING but never goes anywhere. I even went as far as concatenating my DATA.TEXT (STRING = "STRING,DATA.TEXT"), but still to no avail. The projector I was using did not end its strings with any kind of common byte (e.g. $0D - carriage return). Any suggestions?
  • viningvining Posts: 4,368
    Show a link to the protocol, there's almost always someway to figure the end. Sometimes you need to base parsing on a known start byte and then the length byte That follows in a fixed position after that and that byte will tell you how many bytes to wait for.
  • The protocol does not share a particular 'ending character'. The device I'm using responds in three different ways; first, where the size of the string is 2 byte long; second, where the size of the string is 3 bytes long; finally, where the size of the string is 4 bytes long. There is no common byte that they share and if I were to parse it to one particular byte, I would be excluding any bytes I need after the fact. Here is an example of my code:
    STRING:{
        sPROJ5 = "sPROJ5,DATA.TEXT"
        SEND_STRING 0,"sPROJ5"
        //lamp hour
        IF( FIND_STRING(sPROJ5,"$00,$2F,'??'",1) ){ 
    	//statement
        }
        IF(COMPARE_STRING(sPROJ5,"$01,'?'")){ //error
    	//statement
        }
        ELSE IF(COMPARE_STRING(sPROJ5,"$00,$01")){ //power on
    	//statement
        }
        ELSE IF(COMPARE_STRING(sPROJ5,"$00,$55,$02")){ 
    	//statement
        }
    }
    

    Now, what I've done in the past, I would FIND_STRING on a common byte like $0D and then after I was finished with the string, REMOVE_STRING to clear its contents. Using the code above with this device, I am unable to look for a byte to say its the complete my string. If I look for a $01, in one response it's at the beginning of the string, thus ignoring the rest of the strings; for another response it's at the end of the string. The response that has 4 bytes, I am looking at the last two bytes (and in this example) to get my lamp hours.

    p.s. the reason I concatenated my incoming string is because a straight DATA.TEXT would only read one byte at a time, over-writing the previous value, thus not allowing me to build the full string.
  • viningvining Posts: 4,368
    Again, post a link or the protocol itself so folks here can take a look.

    I understand there is no delimeter to key your parsing on but usually there are other known factors to work with. Sometimes you have to base your parsing on the the string type you just sent. Command returns often differ from query returns so you might have to check the command last sent to determine how to parse the string received. Of course you could be SOL if the device provides unsolicited responses but even then you can key on the first couple of bytes to determine the type of message you're dealing with and then if they're nice they'll provide a length byte in a fixed positon relative to the start byte which may vary.

    If we couuld look at the syntax we might be able to help you find a pattern.


  • I've attached the protocol for the projector. Below is a sample of my code.
    DEFINE_CONSTANT
    (*   DELL S500wi PORJECTOR CONSTANTS   *)
    sDELL_SYS_STATUS[]= {$BE,$EF,$10,$05,$00,$46,$7E,$11,$11,$01,$00,$FF}		//system power status
    sDELL_PWR_ON[]    = {$BE,$EF,$10,$05,$00,$C6,$FF,$11,$11,$01,$00,$01}		//power on
    sDELL_PWR_OFF[]   = {$BE,$EF,$10,$05,$00,$0C,$3E,$11,$11,$01,$00,$18}		//power off
    
    sDELL_V_MUTE_STATUS[]= {$BE,$EF,$10,$05,$00,$2D,$FE,$11,$11,$01,$00,$65}	//video mute status
    sDELL_V_MUTE_ON[]    = {$BE,$EF,$10,$05,$00,$02,$7E,$11,$11,$01,$00,$0F}	//video mute on
    sDELL_V_MUTE_OFF[]   = {$BE,$EF,$10,$05,$00,$ED,$3F,$11,$11,$01,$00,$64}	//video mute off
    
    sDELL_VOL_STATUS[] = {$BE,$EF,$10,$05,$00,$F2,$7F,$11,$11,$01,$00,$4F}		//get volume level
    sDELL_VOL_SET[]    = {$BE,$EF,$10,$06,$00,$18,$DB,$11,$11,$02,$00,$68}		//set volume level
    sDELL_VOL_UP[]     = {$BE,$EF,$10,$05,$00,$00,$FE,$11,$11,$01,$00,$09}		//volume +
    sDELL_VOL_DOWN[]   = {$BE,$EF,$10,$05,$00,$01,$BE,$11,$11,$01,$00,$0A}		//volume -
    sDELL_MUTE_STATUS[]= {$BE,$EF,$10,$05,$00,$EE,$FF,$11,$11,$01,$00,$61}		//volume mute status
    sDELL_MUTE_ON[]    = {$BE,$EF,$10,$05,$00,$C3,$FF,$11,$11,$01,$00,$0D}		//volume mute on
    sDELL_MUTE_OFF[]   = {$BE,$EF,$10,$05,$00,$3E,$7E,$11,$11,$01,$00,$5F}		//volume mute off
    
    sDELL_SOURCE[]  = {$BE,$EF,$10,$05,$00,$DC,$BF,$11,$11,$01,$00,$26}		//gets current input source
    sDELL_VGAA_IN[] = {$BE,$EF,$10,$05,$00,$CC,$FF,$11,$11,$01,$00,$19}		//VGA-A input
    sDELL_HDMI_IN[] = {$BE,$EF,$10,$05,$00,$3A,$3E,$11,$11,$01,$00,$50}		//HDMI input
    sDELL_AUDA_IN[] = {$BE,$EF,$10,$06,$00,$09,$DB,$11,$11,$02,$00,$54,$01}		//Audio-A 3.5mm input
    sDELL_AUDB_IN[] = {$BE,$EF,$10,$06,$00,$09,$DB,$11,$11,$02,$00,$54,$02}		//Audio-B RCA (L/R) input
    
    sDELL_LAMP_HR[] = {$BE,$EF,$10,$05,$00,$DA,$7F,$11,$11,$01,$00,$2F}		//lamp hours
    
    DEFINE_EVENT //-----------------------------------------------------------
    (*  PROJECTOR EVENTS  *)
    DATA_EVENT[dvPROJ5]{
        ONLINE:{
    	nCLIENT_244_ONLINE = TRUE
    	SEND_STRING 0,"'244 PROJECTOR ONLINE'"
        }
        OFFLINE:{
    	nCLIENT_244_ONLINE = FALSE
    	SEND_STRING 0,"'244 PROJECTOR OFFLINE'"
    	fnCLIENT_OPEN_244()
        }
        STRING:{
    	LOCAL_VAR CHAR ETX[1]
    	LOCAL_VAR CHAR BYTE_1[1]
    	LOCAL_VAR CHAR BYTE_2[1]
    	LOCAL_VAR INTEGER LSB
    	LOCAL_VAR INTEGER MSB
    	LOCAL_VAR INTEGER HRS
    	sPROJ5 = "sPROJ5,DATA.TEXT"  //sPROJ5 is a buffer 
    	SEND_STRING 0,"sPROJ5"
    	//lamp hour
    	IF( COMPARE_STRING(sPROJ5,"$00,$2F,'??'") ){ //lamp hours ?
    	    BYTE_1 = RIGHT_STRING(sPROJ5,2)
    	    BYTE_2 = RIGHT_STRING(sPROJ5,1)
    	    LSB = ATOI(BYTE_1)
    	    MSB = ATOI(BYTE_2)
    	    HRS = (256*MSB) + LSB
    	    SEND_STRING 0,"'LAMP QUERY'"
    	    SEND_COMMAND dvTP5,"'^TXT-1,0,LAMP HR: ',ITOA(HRS)"
    	}
    	IF(COMPARE_STRING(sPROJ5,"$01,'?'")){ //error
    	    ETX = RIGHT_STRING(sPROJ5,1)
    	    SEND_STRING 0,"'ERROR- ',sPROJ5"
    	    REMOVE_STRING(sPROJ5,ETX,1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$01")){ //power on
    	    SEND_STRING 0,"'POWER ON- ',sPROJ5"
    	    nPROJ5_PWR = TRUE
    	    REMOVE_STRING(sPROJ5,"$00,$01",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$18")){ //power off
    	    SEND_STRING 0,"'POWER OFF- ',sPROJ5"
    	    nPROJ5_PWR = FALSE
    	    REMOVE_STRING(sPROJ5,"$00,$18",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$64")){ 	//
    	    SEND_STRING 0,"'V MUTE OFF'"
    	    nPROJ5_MUTE = FALSE
    	    REMOVE_STRING(sPROJ5,"$00,$64",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$0F")){ 	//
    	    SEND_STRING 0,"'V MUTE ON'"
    	    nPROJ5_MUTE = TRUE
    	    REMOVE_STRING(sPROJ5,"$00,$0F",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$19")){ 	//current source x
    	    SEND_STRING 0,"'SOURCE VGA'"
    	    nPROJ5_INPUT = 1 //VGA-A
    	    REMOVE_STRING(sPROJ5,"$00,$19",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$50")){ 	//current source x
    	    SEND_STRING 0,"'SOURCE HDMI'"
    	    nPROJ5_INPUT = 2 //HDMI
    	    REMOVE_STRING(sPROJ5,"$00,$50",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$54,$01")){ REMOVE_STRING(sPROJ5,"$00,$54,$01",1) }
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$55,$02")){ REMOVE_STRING(sPROJ5,"$00,$54,$02",1) }
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$5F")){ //currect volume mute x 
    	    SEND_STRING 0,"'MUTE OFF'"
    	    nPROJ5_VOL_MUTE = FALSE
    	    REMOVE_STRING(sPROJ5,"$00,$5F",1)
    	}
    	ELSE IF(COMPARE_STRING(sPROJ5,"$00,$0D")){ //currect volume mute x 
    	    SEND_STRING 0,"'MUTE ON'"
    	    nPROJ5_VOL_MUTE = TRUE
    	    REMOVE_STRING(sPROJ5,"$00,$0D",1)
    	}
    
    
        }
        ONERROR:{
    	STACK_VAR INTEGER nER_NUM
    	SEND_STRING 0,"'ERROR #: ',ITOA(DATA.NUMBER)"
    	IF(nER_NUM ==  4){ SEND_STRING 0,"'UNKNOWN HOST'" }
    	IF(nER_NUM ==  7){ SEND_STRING 0,"'CONNECTION TIMED OUT'" }
    //	IF(nER_NUM == 14){ IP_CLIENT_CLOSE(dvPROJ5.PORT) }
    //	IF(nER_NUM == 17){ IP_CLIENT_OPEN(dvPROJ5.PORT,IPTLS2_ADDR,COM1,1) }
        }
    }
    
    
  • arbelaezc,

    The way I have dealt with this type of device is to first separate the code for it into a module. The commands are sent to the module from the main program like many of the old-style AMX modules. So to turn the projector on, I send a command to the virtual device of 'POWER=1".

    Within the module, I set up a queuing system where a command is received from the main program and then added to the queue. For a case like this, I would add a string to the front of the actual "BEEF" command to tag the type of command that was being sent. For example, a power setting command would have something like 'PWR' in front of the command being written to the queue. A power status request would have something like "PR?" as the prefix. So you would prefix the commands with the type such as "INP" for input, "ASP" for aspect-ratio and "LM?" for a lamp hour request.

    At the end of the standard device command I add my own end of command character. You need to look for a character that does not occur in the standard command structure. As an example, I may add a hex FF to the end of the command.

    When you pull a command out of the queue, you use the $FF to delimit the REMOVE_STRING function call. At that point, you have the entire command with the 3-byte command type on the front of the command and the $FF at the back.

    Remove the first three bytes and save them in a variable such as cCMD_SENT[] for the actual device's Data_Event. I use the LEFT_STRING to place the 3 bytes into a variable and then just use three GET_BUFFER_CHAR to remove them. Then I set the string length of the command to be one less than the actual length in order to strip off the $FF and do a SEND_STRING to the projector.

    I generally set a timeout so that if I don't get a response back from the projector I can either resend or discard the command.

    In the DATA_EVENT for the device, when I receive a string I look for the command type that I last issued in the cCMD_SENT variable. That lets me know how to pick apart the returned string to store the info that I need.

    From there I send a response back to the device. So if I get a lamp time request i will send a string back to the main program from the module's virtual device that contains something like 'LAMPTIME=99'

    Since I use the same basic command structure for all projectors, I can swap a projector with minimal changes to the main program.
  • viningvining Posts: 4,368
    Looks like you only need to know if you sent a write command, a system query, a lamp query or a firmware query. I could be wrong but it looks like if you send a write command you just get a single byte returned. A system query returns 3 bytes, lamp query 4 bytes and firmware returns 7 bytes. So if you know what you sent you know how to parse. Looks like it doesn't send unsolicated FB either so you should be good to go.

    Just create a select active or switch case based of the last string type sent. Plus you know the first byte will almost always be $00 unless the sting you sent is invalid or has errors.
Sign In or Register to comment.