Home AMX User Forum AMX General Discussion

Checksums in String Returns

I've got a plasma that requires checksums at the end of every string sent to it. I did a little research and found a checksum function written by one of the multitude of other Jeffs on the forum, and it is working fine. However, I've got a minor question about the return data from the Plasma.

Each return string from the plasma has a checksum at the end of it. I can't decide the most effective way to deal with that data. For the record, I can make this all work, but I have some free time right now and I'm trying to make the code elegant, pretty, and ideal.

1) I can use data.text in the data_event|string: handler. I can do something like
if (calcchecksum(left_string(data.text,length_string(data.text)-1))=right_string(data.text,1))

Unfortunately, I dont know what happens here if the string handler fires before the entire string shows up. Is this a problem?

2) I can use a buffer in the same string: handler. instead of being able to use a right_string to identify the checksum itself, I'll have to check every character against the previous string, and if that isn't the checksum, move one more character down the line. Haven't written code for this but I'm sure I could.

Here, I don't know what happens if I get two strings at once and the string handler only fires once. then my buffer still has stuff in it, but nothing is telling my code to process it.

3) I can use a buffer and put the same code I would be developing for option 2 in define_program. This would fire as long as my buffer isn't empty.

Downside here, I like to keep code out of define_program if at all possible. Maybe I could put this in a timeline, but how often do I really need to do this?


Maybe I'm overthinking all of this, and I should just stick with option 1. I'm still trying to learn the ideal way to do things. Still, I appreciate your help.

J

Comments

  • DHawthorneDHawthorne Posts: 4,584
    Yes, it's possible for the string handler to fire when you have an incomplete string, or sometimes when you have more than a single string. It's timer based, and can't be relied on for this kind of purpose. It's ideal for intra-processor messages (as in strings to and from a virtual), but not for physical devices.

    Use CREATE_BUFFER and parse it yourself.

    Me, I don't bother with checksums coming from a device unless the device is prone to errors. Most modern devices are not. I just ignore data I can't recognize.
  • JeffJeff Posts: 374
    if I parse it myself as a buffer, where do I put the parsing statements? Inside the string handler on the data_event?

    I had a situation a while back with something where occasionally I would get two strings at once, and the data_event string handler would only fire once. It would parse that bit, and get to the end, and leave data in the buffer. How do I make it fire again if there is more in the buffer?
  • DHawthorneDHawthorne Posts: 4,584
    As often as some like to suggest there is no use for DEFINE_PROGRAM, thats where I put it. You could put the call in the string handler, but you run the risk of there being data you haven't acted on.
    DEFINE_PROGRAM
    
    IF(LENGTH_STRING(sBuffer))
        doParse(sBuffer)
    
    /// or ...
    
    WHILE(FIND_STRING(sBuffer, sDelimiter, 1))
         doParse(REMOVE_STRING(sBuffer, sDelimiter, 1)
    

    If there is no delimiter, you can just check the length of your buffer, and test if it's the size of or greater than the size of the expected data, then use LEFT_STRING to strip it out. My least favorite is the type that use variable lengths and a start character, but you can test for that as well, it's just not as straightforward (and varies widely with the protocol).
  • SensivaSensiva Posts: 211
    TechNote #616

    Read this TechNote : Buffer Parsing in Netlinx

    Very helpful
  • JeffJeff Posts: 374
    Thanks so much, that explained things perfectly

    I had never even considered using a while loop there. I'm not entirely sure I've ever used a while loop . . . ever. That should work perfectly

    J
  • SensivaSensiva Posts: 211
    Make Sure

    Make sure the while loop will end someday :) or get you master stuck!!
    Jeff wrote:
    Thanks so much, that explained things perfectly

    I had never even considered using a while loop there. I'm not entirely sure I've ever used a while loop . . . ever. That should work perfectly

    J
  • DHawthorneDHawthorne Posts: 4,584
    Sensiva wrote:
    Make sure the while loop will end someday :) or get you master stuck!!

    Yes, that's very important. When testing a string for a delimiter, it's pretty safe, since it's either there or not, and if your while ends when it's not, you are good. If you fail, however, to remove that delimiter when you parse a packet - well, you have created an endless loop. If the structure of the while loop is such that you cannot be sure if it's ever going to exit, put a timer on it. Don't let it run more than a few seconds (and even that is a lot, since nothing else will happen meanwhile).
  • JeffJeff Posts: 374
    Yea, I'm staring at my code right now trying to figure out how to handle the fact that I have a variable delimiter that changes with each response.

    Worst case scenario, I'm only actually handling three different responses from the plasma, I could just put
    while (find_string(cPlasmaBuffer,"$0D",1) or find_string(cPlasmaBuffer,"$3F",1) or find_string(cPlasmaBuffer,"$7A",1))
    

    then put a switch|case inside it to handle each one, but I'd like to actually write something that will check for the checksum, not check for checksums that I've calculated.

    J
  • JeffJeff Posts: 374
    ok, so more thoughts.

    I've got a for loop written that checks for a delimiter
    define_variable
    
    non_volatile char cPlasmaBuffer[50]
    
    define_function integer calcchecksum(char cMsg[])
    {
        stack_var integer nLoop
        stack_var integer nCheckSum
        off[nCheckSum]
        for (nLoop=1;nLoop<=length_string(cMsg);nLoop++)
        {
            nCheckSum=((nCheckSum+cMsg[nLoop]) & $FF)
        }
        return nCheckSum
    }
    
    define_start
    
    create_buffer dvPlasma,cPlasmaBuffer
    
    data_event[dvPlasma]
    {
        string:
        {
            stack_var char cParsedBuffer[50]
            stack_var integer nLoop
            for (nLoop=2;nLoop<=length_string(cPlasmaBuffer);nLoop++)
            {
                if (cPlasmaBuffer[nLoop]=calcchecksum(left_string(cPlasmaBuffer,nLoop-1)))
                {
                    cParsedBuffer=remove_string(cPlasmaBuffer,"calcchecksum(left_string(cPlasmaBuffer,nLoop-1))",1)
                    //Do whatever I need to do with the message
                }
            }
        }
    }
    

    The downside of this is, this only fires once. I could take this code (slightly modified to use global vars) and put it in my define_program, and probably be quite happy with it.

    What I'd like to do, though, is find a way to combine this with a while loop such that it will run multiple times in the data_event, checking more than once for a delimiter once it has found one. I don't want to tie the while loop to the length of the string, just in case I get junk back from Plasma that doesn't end in a checksum, the while loop would get stuck . . . .

    The other question I have, is what happens to my for loop once I've performed remove_string inside it . . . . the loop itself is dependant upon the length of the string but I will have changed the length of the string.

    Thoughts?

    J
  • JeffJeff Posts: 374
    Alright, i've got a try that is cumbersome but I think will work. I don't have a way to test it at the moment, however. Does anyone know of a program/device/something that will let me send strings to my master of my own choosing?

    Here's what I've got. Everything from the last post is the same, so I'm just going to recreate the data_event.
    data_event[dvPlasma]
    {
        string:
        {
            stack_var char cParsedBuffer[50]
            stack_var integer nLoop
            stack_var integer nFound
            stack_var integer nParsing
            on[nParsing]
            while(nParsing)
            {
                if (length_string(cPlasmaBuffer)=0)
                {
                    off[nParsing]
                }
                for (nLoop=1;nLoop<=length_string(cPlasmaBuffer) or nFound;nLoop++)
                {
                    if (cPlasmaBuffer[nLoop]=calcchecksum(left_string(cPlasmaBuffer,nLoop-1)))
                    {
                        cParsedBuffer=remove_string(cPlasmaBuffer,"calcchecksum(left_string(cPlasmaBuffer,nLoop-1))",1)
                        on[nFound]
                        //Actually handle the response
                    }
                    else if (nLoop=length_string(cPlasmaBuffer)+1)
                    {
                        off[nParsing]
                    }
                }
                off[nFound]
            }
        }
    }
    

    Basically I have a variable that says I'm parsing. The while loop runs as long as that variable is on. That variable gets turned off if the Buffer is ever empty OR the we've checked every character to see if its a checksum and failed. One of those two should always happen, so the loop shouldn't get stuck. I can add something like a timeline to cancel the while loop after 3 seconds and clear the buffer if necessary.

    I've also got a variable that says we've found a checksum that stops the for loop. This way it restarts. If we don't restart the for loop from scratch, then a second checksum would never get found because it wouldn't be a checksum for the entirety of the message.

    My reasoning for trying to do all of this in the data event is that I don't want it running 100 times a second when I'm just not dealing with that much data from the Plasma.

    So do you guys think this will work, or am I just incredibly overcomplicating this? I'm beginning to consider DHawthorne's suggestion to ignore the checksum, but this also seemed like a good challenge to practice my programming skills on.

    J
  • SensivaSensiva Posts: 211
    Yeah...
    Jeff wrote:
    but this also seemed like a good challenge to practice my programming skills on.

    J

    You are right, and this better for your career. :)

    May be for this model of device, you won't need to process the CheckSum, but who knows, one day you are going to need it, so better get ready

    would you please tell us the Model number so i can get the protocol , and practice together :)

    I Love String Parsing :)
  • JeffJeff Posts: 374
    This TV is an NEC PX-42VM5HA, the protocol is located here

    J
  • Jeff wrote:
    Alright, i've got a try that is cumbersome but I think will work. I don't have a way to test it at the moment, however. Does anyone know of a program/device/something that will let me send strings to my master of my own choosing?

    Try NS2 / Diagnostics / Emulate a Device

    Another suggestion: you appear to be using some form of Hungarian Notation. If so, I suggest you use the form bFound for a flag value rather than nFound.

    The eponymous Hungarian cosmonaut intended that the prefix indicate the usage of the variable, not its nominal type. Here are the prefixes that I use:

    char sString[]
    integer nInteger
    long lLong
    slong slSignedLong
    sinteger siSignedInteger
    structure tType (* The name of the type, not an instance *)
    device dDevice
    devlev dlDevLev

    integer bBoolean
    integer tpButtonNumber
    integer pPopupNumber
    integer mModuleNumber

    I don't bother with a prefix for an instance of a custom struct unless it is commonly used.

    I also generally indicate arrays with a plural variable name: sNames[]
    Local variables like so: sMyName[]
    Arguments passed into a routine: sArgName[]

    Some folk use all caps for constants but I find them ugly and I don't find I need to distinguish constants from variables.

    It all helps with code comprehension.
  • JeffJeff Posts: 374
    Thanks for the suggestions, I'll have to start incorporating those. I can see it being useful to have a different prefix for integers and flags.

    I've solved a few problems here, and found one that I think is fairly hard to get around.

    The last piece of code I posted had
    else if (nLoop=length_string(cPlasmaBuffer+1))
    

    and was wrong. The +1 makes it not fire each time, it should just be
    else if (nLoop=length_string(cPlasmaBuffer))
    

    This original error came from a misunderstanding of how the loop structure worked (and partly not thinking things entirely through.

    Once you fix that error, you can send a single string that ends with a checksum on it to the master, and it will parse it correctly. You can also send two strings that both end in checksums to the master, and it will parse both of them correctly.


    However, I'm having two problems.


    Problem number 1 I think is user error. When I'm using "Emulate a Device" I'm putting the following (including quotes) into the box

    "$DF,$80,$60,$47,$01,$01,$08,$20,$81,$01,$60,$01,$00,$03"

    I've checked the String Expressions checkbox.

    In this instance, any hex character that appears after the $00 value just shows up as $00. The end of my cPlasmaBuffer, when I watch it in debug, shows up as $01,$00,$00, instead of $01,$00,$03. The actual protocol for my plasma includes several $00 characters . . . what am I doing wrong?




    The second problem I'm running into, is what happens if there's a bad character or incomplete string sent to the master from the Plasma. If you get a single bad character in the buffer, then you've got serious issues, because none of the checksums will calculate.

    I've got two possible solutions to that.

    1) After some amount of time, clear the buffer. I don't really like this because it involves a timeline, or a wait, and something about that really doesn't seem like what I'm trying to do. Also, I'm going to lose data that way, although I figure you have to agree to occasionally lose data if you want to be able to get rid of junk.

    2) Switch to using data.text instead of cPlasmaBuffer. Now my data.text will parse correctly every time, even if two strings get lumped together in data.text (which I've seen happen). However, if a string gets somehow broken in two different events (which I've not seen happen), I'd lose it completely.



    I'm leaning towards #2. I've solved my original big problem with data.text, which was that a second string would always get lost. Can anyone think of a reason I shouldn't do this, and if so, a better solution?

    Thanks for humoring me in this random quest of mine

    J
  • joelwjoelw Posts: 175
    Hi Jeff,

    Here is some food for thought on the transmit side. Don't worry about the variable naming convention, at the time I may have spent less than thirty minutes on it.
    MODULE_NAME='NEC PX-50XM5A' (DEV dvDev, DEVCHAN dvcDEV[], DEVCHAN dvcDev_Off[])
    
    //#DEFINE DEBUG
    
    (***********************************************************)
    (*               CONSTANT DEFINITIONS GO BELOW             *)
    (***********************************************************)
    INCLUDE 'constants'
    DEFINE_CONSTANT
    
    /* command index */
    CMD_POWER_ON 		= 1
    CMD_POWER_OFF 		= 2
    CMD_INP_SWITCH          = 3
    CMD_SPLIT		= 4
    
    /* communication constants */
    UA_PLASMA   = $60
    UA_EXT_CTRL = $80
    
    (***********************************************************)
    (*               VARIABLE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_VARIABLE
    
    CMD_TABLE[][12]=
    {
        {$9F,$4E,$00},	// power on
        {$9F,$4F,$00},	// power off
        {$DF,$47,$01},	// input switch
        {$DF,$07,$01}	// multi (split) screen select
    }
    
    /* command parameters */
    INP_VID1		= $01
    INP_VID2_RCA		= $02
    INP_VID2_SVID		= $03
    INP_DVD_HD1_RCA_COMP	= $05
    INP_DVD_HD2_5BNC_COMP	= $06
    INP_RGB1_HD15 		= $07
    INP_RGB2_5BNC 		= $08
    INP_RGB3_DVI		= $0C
    
    SPLIT_SINGLE		= $00
    SPLIT_SIDE_BY_SIDE	= $01
    
    SPLIT_ACTIVE_LEFT	= $20
    SPLIT_ACTIVE_RIGHT      = $21
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    DEFINE_FUNCTION CHAR CHKSUM(CHAR BUFF[])
    {
        STACK_VAR CHAR I, LEN
        STACK_VAR INTEGER CS
        
        LEN = LENGTH_ARRAY(BUFF)
        
        CS = 0
        FOR(i=1;i<=LEN;i++)
    	CS = CS + BUFF[i]
        
        RETURN TYPE_CAST(CS & $FF)
    }
    
    DEFINE_FUNCTION SEND_DATA(CHAR CMD1, CHAR UA1, CHAR UA2, CHAR CMD2, CHAR LEN, CHAR BUFF[])
    {
        STACK_VAR CHAR STR[15]
    
    #IF_DEFINED DEBUG
        STACK_VAR CHAR OUTSTR[50]
        STACK_VAR INTEGER i, j 
    #END_IF
        
        IF(LEN)
    	STR = "CMD1,UA1,UA2,CMD2,LEN,BUFF"
        ELSE
    	STR = "CMD1,UA1,UA2,CMD2,LEN"
    
        STR = "STR,CHKSUM(STR)"
    
    #IF_DEFINED DEBUG
    
        j = LENGTH_ARRAY(STR)
    
        OUTSTR = "'TX: '"
        FOR(i=1; i<=j; i++)
    	OUTSTR = "OUTSTR,FORMAT('%02X ',STR[i])"
        SEND_STRING 0,"OUTSTR"
    
    #END_IF
    
        SEND_STRING dvDEV,"STR"
    }
    
    DEFINE_FUNCTION SEND_CMD(CHAR IDX, CHAR BUFF[])
    {
        SEND_DATA (CMD_TABLE[IDX][1],UA_EXT_CTRL,UA_PLASMA,CMD_TABLE[IDX][2],CMD_TABLE[IDX][3],BUFF)
    }
    
    (***********************************************************)
    (*                THE EVENTS GO BELOW                      *)
    (***********************************************************)
    DEFINE_EVENT
    
    DATA_EVENT[dvDev]
    {
        ONLINE:
        {
    	SEND_COMMAND dvDev,'SET BAUD 9600,O,8,1'
    	SEND_COMMAND dvDev,'B9MOFF'
    	SEND_COMMAND dvDev,'CTSPSH OFF'
    	SEND_COMMAND dvDev,'HSOFF'
    	SEND_COMMAND dvDev,'XOFF'
    	SEND_COMMAND dvDev,'RXCLR'
    	SEND_COMMAND dvDev,'RXON'
        }
    }
    
    BUTTON_EVENT[dvcDEV]
    {
        PUSH:
        {
    	STACK_VAR PRESS
    	PRESS = BUTTON.INPUT.CHANNEL
    	SWITCH(PRESS)
    	{
    	    CASE bMON_OFF:	// power off
    	    {
    		SEND_CMD(CMD_POWER_OFF,"")
    		BREAK
    	    }
    	    CASE bMON_DUAL:	// l&r projector
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SIDE_BY_SIDE")
    		    SEND_CMD(CMD_SPLIT,"SPLIT_ACTIVE_LEFT")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB1_HD15")
    		    SEND_CMD(CMD_SPLIT,"SPLIT_ACTIVE_RIGHT")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB2_5BNC")
    		}
    		BREAK
    	    }
    	    CASE bMON_L_PROJ:
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB2_5BNC")
    		}
    		BREAK
    	    }
    	    CASE bMON_R_PROJ:
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{ 
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB1_HD15")
    		}
    		BREAK
    	    }
    	    CASE bMON_PROD:	// production
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_VID1")
    		}
    		BREAK
    	    }
    	    CASE bMON_PVW:	// preview
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_VID2_RCA")
    		}
    		BREAK
    	    }
    	}
        }
    }
    

    For debugging incoming serial data, this can be used to print incoming data.
    #IF_DEFINED DEBUG
    	stack_var instr[255]
    	integer i, len
    	
    	len = length_array(data.text)
    	instr = 'rx: '
    	for(i=1;i<=len;i++)
    	    instr="instr,format('%02X ',data.text[i])"
    
    	send_string 0,"instr"
    #END_IF
    

    Post up what you have for your incoming data parser. I will make some constructive comments on it.
  • JeffJeff Posts: 374
    Thanks a bunch . . . . .

    Its going to take me some time to go through all of that. I haven't actually gotten to that point in my code yet, almost the entirety of my code is posted earlier in this thread. I'll pull your code into NS2 a little later today and go through it to see what all you're doing.

    J
  • JeffJeff Posts: 374
    I've written a novel below. Don't start reading if you dont have like . . . 3 days

    This is turning into something much more involved than I originally anticipated. However, I think I'm getting close. I don't have the perfect solution I once envisioned, but I guess you can't always have perfection. I do have one statement that I think is correct, but I'd love to have someone prove wrong.

    There is no way to parse strings with checksums for delimiters in such a way that you are guaranteed never to miss a string.

    I think the recovery process from having an incomplete string or bad data is inherently going to cause you to lose a string or two. I see this as incredibly unlikely to happen, but the possibility is out there. However, I believe that I have reached a point such that you should only ever lose one string, if that were to happen.

    Here's what I've got.
    program_name='4105'
    
    define_device
    
    dvPlasma	=	5001:1:0
    
    define_variable
    
    non_volatile char cPlasmaBuffer[50]
    
    define_function integer calcchecksum(char cMsg[])
    {
    	stack_var integer nLoop
    	stack_var integer nCheckSum
    	off[nCheckSum]
    	for (nLoop=1;nLoop<=length_string(cMsg);nLoop++)
    	{
    		nCheckSum=((nCheckSum+cMsg[nLoop])& $FF)
    	}
    	return nCheckSum
    }
    
    define_function integer send_to_plasma(char cMsg[])
    {
    	send_string dvPlasma,"cMsg,calcchecksum(cMsg)"
    }
    
    define_start
    
    create_buffer dvPlasma,cPlasmaBuffer
    
    define_event
    
    data_event[dvPlasma]
    {
    	online:
    	{
    		send_command dvPlasma,"'SET BAUD 9600,O,8,1 485 Disable'"
    	}
    	string:
    	{
    		stack_var integer nParsing
    		stack_var integer nFound
    		stack_var integer nLoop
    		on[nParsing]
    		while (nParsing)
    		{
    			for (nLoop=1;nLoop<=length_string(cPlasmaBuffer);nLoop++)
    			{
    				if (cPlasmaBuffer[nLoop]=calcchecksum(left_string(cPlasmaBuffer,nLoop-1)))
    				{
    					cParsedBuffer=remove_string(cPlasmaBuffer,"calcchecksum(left_string(cPlasmaBuffer,nLoop-1))",1)
    					on[nFound]
    					//Parse the String Itself
    				}
    				else if (nLoop=length_string(cPlasmaBuffer))
    				{
    					off[nParsing]
    				}
    			}
    			if (!nFound and length_string(cPlasmaBuffer)>7)
    			{
    				clear_buffer cPlasmaBuffer
    			}
    			if (length_string(cPlasmaBuffer)=0)
    			{
    				off[nParsing]
    			}
    			off[nFound]
    		}
    	}
    }
    
    define_program
    

    Basically, I'm using the NetLinx buffer to capture the string from the plasma. If a string comes in, I turn on a flag that says nParsing, and I run a while loop for as long as nParsing is true. If at any point in time the buffer is empty, I turn nParsing off, and the while loop stops. If at any point in time I have checked every character in the string to see if it is a checksum, and none of them are, I turn nParsing off, and the while loop stops. There should be no instance in which one of those two events does not eventually come true. either I find a checksum and remove it from the string and it clears out the buffer, stopping the loop, or I get to the end and dont find a checksum, stopping the loop.

    If I get a single full string from the device, it will find a checksum, remove it from the buffer, and the loop stops

    If I get half of a string from the device, it will not find a checksum, and stop parsing. When the rest comes in, the loop will start again, and I will find the checksum the second time around.

    If I get a string and a half from the device, It'll find the checksum, remove it from the buffer, keep going, not find a checksum, and stop. I'll wait for the second half, and find the checksum after it comes in.

    If I get two whole strings from the device, it'll find the checksum, remove it from the buffer, keep going, find the checksum, remove it from the buffer, and stop.

    Here's the tricky part. If I get a single character somewhere, then it throws off all of the math that makes the checksums work, and I'll never find another checksum, which means I wont get data again. Thats no good. However, I don't want to clear the buffer whenever I get through and don't find a checksum, in case I've just got half of a string and am waiting for the rest.

    My solution, at least for the moment, was to check the protocol, and find that the longest possible string (including checksum) for my device, which was 7 characters long. I then set up an if statement after my for loop that says if we didn't find a checksum, and the buffer is longer than the longest string I could possibly get, that its time to clear the buffer and start over. Here is where I will likely lose some sort of response. On the other hand, if I'm getting bad data, or random characters, there's absolutely no way to avoid losing at least one response, so I think the fact that I may have it down to losing just one is a good thing.

    Here is the only problem I have right now with all of this logic. There is a line in the code that says
    for (nLoop=1;nLoop<=length_string(cPlasmaBuffer);nLoop++)
    

    This loop that I'm creating will keep running along the plasma buffer even after I've found a checksum and removed part of the buffer. I'd rather this not happen. I'd rather stop the for loop once a checksum is found, let the while loop get to the end, and start over because nParsing is still true (if indeed there are additional characters left in the buffer). I have a flag (nFound) that tells me whether or not I found something, so my first thought was that I could just change the for loop to read like this
    for (nLoop=1;nLoop<=length_string(cPlasmaBuffer) or !nFound;nLoop++)
    

    In this case, the loop should stop if nLoop becomes longer than the buffer (which it already does), or if nFound becomes true. However, thats not what happens. somehow once i added in the "or !nFound", the first part stopped working and nLoop quickly got up into the thousands. Am I forgetting something basic from Programmer I here? it compiles perfectly and runs, the loop just doesn't stop.

    Aside from that, can anyone think of something else I'm missing here? It seems like I've handled every eventuality except for that pesky for loop. If there's more that I'm missing, I'd love to hear it. Otherwise, I think this code could be lifted and reused repeatedly on anything that returns strings with checksums, as long as you changed the cutoff limit in the final if statement.

    Also, I'm still curious as to why my buffer goes crazy whenever one of the hex characters it gets is $00. Every character after that always turns to $00, and I don't know why. Is this a limitation of Emulating a Device, or will this come back to bite me when I actually hook this up to my Plasma?

    J
  • Jeff,

    Every protocol with (or without) a checksum that I have encountered has either a fixed string length or a byte with a data count to tell you how long the string will be.

    Which plasma is this?
  • joelwjoelw Posts: 175
    There is no way to parse strings with checksums for delimiters in such a way that you are guaranteed never to miss a string.

    There is never a guarantee that you receive a complete command, nor get them all. Something along the lines of what is the sound of a tree falling in a forest when you're not there?

    If your code really did run slower than the device's "transmit inter-packet delay", you would lose a packet. So unless you are sorting a big list or something you should be safe.

    Something to keep in mind some devices will respond to commands in bursts. This is no problem if you have a common end delimiter, but a bugger if the protocol doesn't. In my experiences with NEC product (15+ years) I have not seen any devices respond with broken up strings. So in the case of NEC it is safe to parse the data.text variable. Just keep in mind other non-NEC devices do behave differently, and you cannot rely on using data.text alone.

    This hasn't been tested but should provide some ideas, and may even work.
    MODULE_NAME='NEC PX-50XM5A' (DEV dvDev, DEVCHAN dvcDEV[])
    
    //#DEFINE DEBUG
    
    (***********************************************************)
    (*               CONSTANT DEFINITIONS GO BELOW             *)
    (***********************************************************)
    INCLUDE 'constants'
    DEFINE_CONSTANT
    
    /* communication constants */
    UA_PLASMA   = $60
    UA_EXT_CTRL = $80
    
    RESPONSE_CMD_INDEX = 4 	/* cmd = fourth byte in response packet */
    
    /* command index */
    CMD_POWER_ON 		= 1
    CMD_POWER_OFF 		= 2
    CMD_INP_SWITCH          = 3
    CMD_SPLIT		= 4
    CMD_RUNNING_SENSE       = 5
    CMD_INP_MODE_REQ        = 6
    CMD_MULTI_MODE_REQ      = 7
    
    (***********************************************************)
    (*               VARIABLE DEFINITIONS GO BELOW             *)
    (***********************************************************)
    DEFINE_VARIABLE
    
    CHAR CMD_TABLE[][$0C]=
    {
        {$9F,$4E,$00},	// power on
        {$9F,$4F,$00},	// power off
        {$DF,$47,$01},	// input switch
        {$DF,$07,$01},	// multi (split) screen select
        {$1F,$88,$00},      // running sense 
        {$1F,$41,$00},      // input mode request
        {$1F,$3B,$00}       // multi mode request
    }
    
    /* command parameters */
    CHAR INP_VID1			= $01
    CHAR INP_VID2_RCA		= $02
    CHAR INP_VID2_SVID		= $03
    CHAR INP_DVD_HD1_RCA_COMP	= $05
    CHAR INP_DVD_HD2_5BNC_COMP	= $06
    CHAR INP_RGB1_HD15 		= $07
    CHAR INP_RGB2_5BNC 		= $08
    CHAR INP_RGB3_DVI		= $0C
    
    CHAR SPLIT_SINGLE		= $00
    CHAR SPLIT_SIDE_BY_SIDE		= $01
    
    CHAR SPLIT_ACTIVE_LEFT		= $20
    CHAR SPLIT_ACTIVE_RIGHT      	= $21
    
    CHAR START_DELIMITER
    
    CHAR RUNNING_SENSE
    CHAR INPUT_MODE
    CHAR MULTI_MODE[3]
    
    (***********************************************************)
    (*        SUBROUTINE/FUNCTION DEFINITIONS GO BELOW         *)
    (***********************************************************)
    
    DEFINE_FUNCTION CHAR CHKSUM(CHAR BUFF[]) {
    STACK_VAR CHAR I, LEN
    STACK_VAR INTEGER CS
        
        LEN = LENGTH_ARRAY(BUFF)
        
        CS = 0
        FOR(i=1;i<=LEN;i++)
    	CS = CS + BUFF[i]
        
        RETURN TYPE_CAST(CS & $FF)
    }
    
    DEFINE_FUNCTION SEND_DATA(CHAR CMD1, CHAR UA1, CHAR UA2, CHAR CMD2, CHAR LEN, CHAR BUFF[]) {
    STACK_VAR CHAR STR[15]
    
    #IF_DEFINED DEBUG
        STACK_VAR CHAR OUTSTR[50]
        STACK_VAR INTEGER i, j 
    #END_IF
        
        IF(LEN)
    	STR = "CMD1,UA1,UA2,CMD2,LEN,BUFF"
        ELSE
    	STR = "CMD1,UA1,UA2,CMD2,LEN"
    
        STR = "STR,CHKSUM(STR)"
    
    #IF_DEFINED DEBUG
    
        j = LENGTH_ARRAY(STR)
    
        OUTSTR = "'TX: '"
        FOR(i=1; i<=j; i++)
    	OUTSTR = "OUTSTR,FORMAT('%02X ',STR[i])"
        SEND_STRING 0,"OUTSTR"
    
    #END_IF
    
        IF(STR[1] > $1F)			/* determine first byte to be returned */
    	START_DELIMITER = STR[1]-$60
        ELSE
    	START_DELIMITER = STR[1]+$60
    
        SEND_STRING dvDEV,"STR"
    }
    
    DEFINE_FUNCTION SEND_CMD(CHAR IDX, CHAR BUFF[]) {
    
        SEND_DATA (CMD_TABLE[IDX][1],UA_EXT_CTRL,UA_PLASMA,CMD_TABLE[IDX][2],CMD_TABLE[IDX][3],BUFF)
    }
    
    DEFINE_FUNCTION handleCmd(CHAR p[]) {
    
        SWITCH(p[1]) {
    
    	CASE $88 : {	/* running sense */	
    	    RUNNING_SENSE = p[3]
    	    BREAK
    	}
    	CASE $41 : {	/* input mode */
    	    INPUT_MODE = p[3]
    	    BREAK
    	}
    	CASE $3B : {     /* multi mode */
    	    MULTI_MODE[1] = p[3]
    	    MULTI_MODE[2] = p[4]
    	    MULTI_MODE[3] = p[5]
    	    BREAK
    	}
        }
    }
    
    DEFINE_FUNCTION INTEGER validateChecksum(CHAR p[]) {
    STACK_VAR CS, LEN
    
        LEN = LENGTH_STRING(p)
        CS = CHKSUM(LEFT_STRING(p,LEN-1))
    
        IF(CS == p[LEN])
    	RETURN 1
        ELSE
    	RETURN 0
    }
    
    DEFINE_FUNCTION handlePkt(CHAR p[]) {
    STACK_VAR start
        
        /* Look for start of packet *****************************/
    
        if(p[1] != START_DELIMITER) {	// invalid start byte, search for start byte, don't drop
    	start = FIND_STRING(p,START_DELIMITER,1)
    
    	if(start) {
    
    	    /* based on the protocol and device behavior, this code may never run - just in case */
    	    p = RIGHT_STRING(p,LENGTH_STRING(p)-start)	/* frame packet to valid start byte */
    	}
    	else {
    
    	    SEND_STRING 0,"'START DELIMITER NOT FOUND'"
    	    RETURN;
    	}
        }
        
        /* Validate packet fingerprint **************************/
        IF((p[2] == UA_PLASMA) && (p[3] == UA_EXT_CTRL))
        {
    	/* Validate Checksum or drop packet *****************/
    	IF(validateChecksum(p)) {	
    
    	    handleCmd(MID_STRING(p,RESPONSE_CMD_INDEX,LENGTH_STRING(p)-RESPONSE_CMD_INDEX))
    	} 
    	ELSE {
    
    	    SEND_STRING 0,"'INVALID CHECKSUM'"
    	}
        }
        ELSE
        {
    	SEND_STRING 0,"'INVALID PACKET'"
        }
    }
    
    (***********************************************************)
    (*                THE EVENTS GO BELOW                      *)
    (***********************************************************)
    DEFINE_EVENT
    
    DATA_EVENT[dvDev]
    {
        ONLINE:
        {
    	SEND_COMMAND dvDev,'SET BAUD 9600,O,8,1'
    	SEND_COMMAND dvDev,'B9MOFF'
    	SEND_COMMAND dvDev,'CTSPSH OFF'
    	SEND_COMMAND dvDev,'HSOFF'
    	SEND_COMMAND dvDev,'XOFF'
    	SEND_COMMAND dvDev,'RXCLR'
    	SEND_COMMAND dvDev,'RXON'
        }
        STRING:
        {
    	handlePkt(DATA.TEXT);
        }
    }
    
    BUTTON_EVENT[dvcDEV]
    {
        PUSH:
        {
    	STACK_VAR PRESS
    	PRESS = BUTTON.INPUT.CHANNEL
    	SWITCH(PRESS)
    	{
    	    CASE bREQ_PWR:	// request running sense
    	    {
    		SEND_CMD(CMD_RUNNING_SENSE,"")
    		BREAK
    	    }
    	    CASE bREQ_INP_MODE:	// request input mode
    	    {
    		SEND_CMD(CMD_INP_MODE_REQ,"")
    		BREAK
    	    }
    	    CASE bREQ_MULTI_MODE:	// request multi mode
    	    {
    		SEND_CMD(CMD_MULTI_MODE_REQ,"")
    		BREAK
    	    }
    	    CASE bMON_OFF:	// power off
    	    {
    		SEND_CMD(CMD_POWER_OFF,"")
    		BREAK
    	    }
    	    CASE bMON_DUAL:	// l&r projector
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SIDE_BY_SIDE")
    		    SEND_CMD(CMD_SPLIT,"SPLIT_ACTIVE_LEFT")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB1_HD15")
    		    SEND_CMD(CMD_SPLIT,"SPLIT_ACTIVE_RIGHT")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB2_5BNC")
    		}
    		BREAK
    	    }
    	    CASE bMON_L_PROJ:
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB2_5BNC")
    		}
    		BREAK
    	    }
    	    CASE bMON_R_PROJ:
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{ 
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_RGB1_HD15")
    		}
    		BREAK
    	    }
    	    CASE bMON_PROD:	// production
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_VID1")
    		}
    		BREAK
    	    }
    	    CASE bMON_PVW:	// preview
    	    {
    		SEND_CMD(CMD_POWER_ON,"")
    		WAIT 30 
    		{
    		    SEND_CMD(CMD_SPLIT,"SPLIT_SINGLE")
    		    SEND_CMD(CMD_INP_SWITCH,"INP_VID2_RCA")
    		}
    		BREAK
    	    }
    	}
        }
    }
    
    Also, I'm still curious as to why my buffer goes crazy whenever one of the hex characters it gets is $00. Every character after that always turns to $00, and I don't know why. Is this a limitation of Emulating a Device, or will this come back to bite me when I actually hook this up to my Plasma?J

    Keep in mind when you are using a non-ASCII protocol through an AMX com port you need to check for send_string escape sequences, and break up accordingly.
  • JeffJeff Posts: 374
    Jeff,

    Every protocol with (or without) a checksum that I have encountered has either a fixed string length or a byte with a data count to tell you how long the string will be.

    Which plasma is this?

    This TV is an NEC PX-42VM5HA, the protocol is located here

    The Power on command response (3FH 60H 80H 4EH 00H CKS) is 6 characters long. The Volume command response (7FH 60H 80H 7FH 02H DATA00 DATA01 CKS) is 8 characters long. The difference is any command with only one parameter (power on or off) is just 6 characters, anything with a variable (like which input to switch to) has more. From looking over things again, I'm noticed that I need to change my constant to 8 or I'll never get volume data. Ooops

    joelw wrote:
    Keep in mind when you are using a non-ASCII protocol through an AMX com port you need to check for send_string escape sequences, and break up accordingly.

    I have no idea what you just said. Sorry about that. Any chance you could reword your answer so it is clearer to me? I'm sending hex strings through Emulate a Device . . . . . and if I send $36,$4F,$47,$00,$80,$47,$03 I get $36,$4F,$47,$00,$00,$00,$00.
    joelw wrote:
    Something to keep in mind some devices will respond to commands in bursts. This is no problem if you have a common end delimiter, but a bugger if the protocol doesn't. In my experiences with NEC product (15+ years) I have not seen any devices respond with broken up strings. So in the case of NEC it is safe to parse the data.text variable. Just keep in mind other non-NEC devices do behave differently, and you cannot rely on using data.text alone.

    And while in this particular instance I am working with an NEC plasma, my goal in all of this is to be able to write a universal checksum parsing routine that will work on anything. I think I'm there, with two minor exceptions. That goofy "or" statement I want to put in my for loop, which isn't working, and the fact that I need a constant for the longest possible correct string length from the plasma.

    Again thanks for all of your help, I'm still playing with all of this . . . .

    J
  • joelwjoelw Posts: 175
    I have no idea what you just said. Sorry about that. Any chance you could reword your answer so it is clearer to me? I'm sending hex strings through Emulate a Device . . . . . and if I send $36,$4F,$47,$00,$80,$47,$03 I get $36,$4F,$47,$00,$00,$00,$00.

    This doesn't sound right. Are you checking the hardware output back into your PC @ 9600,8O1?

    What I was referring to prior was the RS232 escape sequences:
    Excerpt from NISeries.WebConsole-ProgrammingGuide.pdf
    If any of the 3 character combinations below are found anywhere within a SEND_STRING program instruction, they will be treated as a command and not the literal characters.


    So what you have to do if search for any occurrence, if found, break the string apart and issue multiple sends.
    And while in this particular instance I am working with an NEC plasma, my goal in all of this is to be able to write a universal checksum parsing routine that will work on anything. I think I'm there, with two minor exceptions.
    It's a good exercise you're putting yourself through, just keep in mind checksum vary. I depends on the Engineer that implemented the protocol. Some may not even implement, some will with a way to defeat it, some do it to match all their other obtuse protocols, etc.
    Again thanks for all of your help, I'm still playing with all of this . . . .

    I can tell you have a good attitude and you don't get frustrated easily. Keep it up!
  • JeffJeff Posts: 374
    joelw wrote:
    This doesn't sound right. Are you checking the hardware output back into your PC @ 9600,8O1?

    I wouldn't think I'm hitting hardware yet. I'm not connected to the plasma in any way. I'm using "Emulate a Device" within NetLinx studio to send random character strings to the master from 5001:1:0. I'm watching my buffer in debug mode when I send those strings, and anything after the $00 turns to $00.

    For example, the correct response to a power on command would be 3FH 60H 80H 4EH 00H CKS. If I were to calculate this checksum and then send the entire string, to my master, the buffer would read 3F,60,80,4E,0,0. The checksum would get converted to a 0.

    I haven't actually hooked any of this up to my Plasma yet because the Plasma is mounted on a wall in a 4th floor conference room and the NI-700 I ordered for the purpose has not yet arrived. I have an NI-3100 in my office hooked up to some stuff that I'm using for testing, but I can't easily rip it out, take it to the room, and test with the plasma, which is why I'm doing all of this theoretical work ahead of time.

    J
  • joelwjoelw Posts: 175
    Jeff wrote:
    I wouldn't think I'm hitting hardware yet. I'm not connected to the plasma in any way. I'm using "Emulate a Device" within NetLinx studio to send random character strings to the master from 5001:1:0. I'm watching my buffer in debug mode when I send those strings, and anything after the $00 turns to $00.

    For example, the correct response to a power on command would be 3FH 60H 80H 4EH 00H CKS. If I were to calculate this checksum and then send the entire string, to my master, the buffer would read 3F,60,80,4E,0,0. The checksum would get converted to a 0.
    This doesn't sound right. If you have time contact AMX tech support, they may be aware of the issue, or may submit it as a bug.

    If you have IP access to the unit, you might consider using IP to connect to the upstairs port in your program. This would allow you to test against the hardware, and would avoid the emulate issue you're seeing.
  • Jeff wrote:
    This TV is an NEC PX-42VM5HA, the protocol is located here

    The 5th byte is a count of the data bytes to follow, except when there is only one, when it is that data byte. Great work there, NEC engineer!

    The responses do not tell you what they are responding to. You have to know what was the last command sent in order to interpret them. This means that you will also know how long they will be.
  • JeffJeff Posts: 374
    The 5th byte is a count of the data bytes to follow, except when there is only one, when it is that data byte. Great work there, NEC engineer!

    It looks to me like when there is only one, the 5th byte is $00. Does that mean it doesn't actually send that byte?
    The responses do not tell you what they are responding to. You have to know what was the last command sent in order to interpret them. This means that you will also know how long they will be.

    It looks to me like they do tell you what they're responding to . . . . 3FH 60H 80H is the prefix for a command about power, 4EH says its powered on, 4FH says its powered off. Where are you getting the fact that it just sends an acknowledgement?

    J
  • Jeff wrote:
    It looks to me like when there is only one, the 5th byte is $00. Does that mean it doesn't actually send that byte?

    My reading of the protocol is that when there is only one byte of data, it is in the 5th byte.
    Jeff wrote:
    It looks to me like they do tell you what they're responding to . . . . 3FH 60H 80H is the prefix for a command about power, 4EH says its powered on, 4FH says its powered off. Where are you getting the fact that it just sends an acknowledgement?

    I observed that many of the responses are identical in the first 4 bytes.
Sign In or Register to comment.