EPICS Home

Experimental Physics and Industrial Control System


 
1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: StreamDevice processing issue
From: "Davis, Mark via Tech-talk" <[email protected]>
To: Mark Rivers <[email protected]>, "[email protected]" <[email protected]>
Date: Tue, 19 Mar 2019 13:50:18 -0400


On 3/18/2019 2:04 PM, Mark Rivers wrote:

 ...

For periodic records StreamDevice does a read with a timeout you define, so it won’t return until multiple packets are received as long as they come within the timeout.

 

Ø  However, this does not appear to be true for protocols that are linked to records using "I/O Intr".  For such protocols, StreamDevice appears to require a 1-to-1 relationship between the packets and the patterns in the "in" commands.  If you know the # and size of each packet, this can be used to define a working protocol (see example below).  But if those values change for whatever reason, then the protocols that depend on them will stop working.

 

I believe that is because StreamDevice does reads for I/O Intr scanned records with a timeout of 0.  So it will return immediately if there is no data available, or will return a single packet if one does arrive.  It will never wait for a second packet.


Which agrees with my assumptions.  The question is, how hard would it be to change that behavior?

This is still based entirely on my assumptions to date (I haven't looked at the code yet), but I expect the simplest approach would be to modify the matching algorithm for protocols running in the I/O Intr context so it is more consistent with what it already does for periodically processed protocols.  Something along these lines:
  • For each protocol, keep track of the following:
    • Where in the "in" pattern(s) testing for a match needs to resume when we get more input (next-to-match pointer)
    • The time the previously processed input was received
  • Each time we receive new input, perform the following for each I/O Intr protocol:
    • If part of the pattern(s) has been matched and it has been > ReadTimeout ms since the last input:
      • Reset the next-to-match pointer
    • If no partial match yet, or if it has been < ReadTimeout ms since the last input was received:
      • Check to see if the new input advances the match
        • If no partial match yet, then match must start at the beginning of the new input
        • Failing to match before the end of the input means it does NOT advance the match
      • If the match was not advanced, reset the next-to-match pointer
    • If all the patterns have been matched:
      • Complete processing for the protocol
      • Reset the next-to-match pointer
      • If a complete match occurred before we reached the end of the most recently read bytes:
        • Process the rest of the input for this protocol (in case the remaining bytes contain even the beginning of another match)

Even assuming no logic errors, this still has limitations.  Specifically, if the matching fails part way through a pattern, it would miss a full match that started in a previous read.  To avoid that possibility, this would have to be expanded to include use of a ring buffer through which all received bytes are processed.  The buffer would have to be large enough to insure we still have a copy of all the previously read bytes that might include the desired sequences of bytes, and we would need pointers for each protocol to track the range of bytes that make up a partial match.

All of which makes we wonder if the existing logic for periodically processed protocols already does this or if it has the same limitation as what I outlined above.

In any case:  I expect even this simplified approach would work for most real-world cases, still avoids any blocking reads, and would eliminate the existing packet-boundary limitation.

Another useful variation would be to add the ability for a protocol to limit the sequence of bytes to be examined to those received while processing another protocol (in particular one that is processed periodically).  This has the following advantages over checking all new input against all I/O Intr protocols:
  • It reduces overhead by limiting the processing of protocols to the times when you expect a match
  • It increases the usefulness of the I/O Intr mode by allowing I/O Intr protocols to be used in cases where they can't at present because they could match input that they were not meant to.  This can occur for devices that assume the client knows the context for a response because it had to have sent the request that triggered the response.
  • I suppose this approach could even be used to define a "tree" structure of I/O Intr protocols, avoiding the need to include in each protocol all the "nodes" that make up a long prefix that provides the context for each value (branch).  For devices that use a tree structure to specify the context of each value (devices that use SCPI come to mind), this could be used to further reduce the overhead by limiting the processing for each of the "branch" protocols to just the portion of the response that contains the desired value (although I admit many of the SCPI implementations I have seen try to use unique branch names so the entire "path" name is not required to avoid ambiguity).

The ability to define relationships between protocols to limit which sequence of bytes gets processed would require new syntax, so backward compatibility would not be an issue.  But the changes needed to address the packet-boundry limitation could alter the outcome for existing protocols, so that would need to be disabled by default.

I guess its time I started looking at the code to get some idea how much effort it would take to implent some of this.

Mark

Mark

From: [email protected] <[email protected]> On Behalf Of Davis, Mark via Tech-talk
Sent: Monday, March 18, 2019 10:44 AM
To: [email protected]
Subject: Re: StreamDevice processing issue

 

Hi all,

A (relatively) more concise and complete summary of my current issue with StreamDevice:


EPICS 3.14.12.2 IOC using StreamDevice 2.5 and asyn 4-32.

Creates connection to an NPort 6450 4-port serial/Ethernet server using this call:

     drvAsynIPPortConfigure("SCR_BTS35_-TMP_D1612", "192.168.54.218:4006")

(the port # was changed from the default so my development IOC rather than the live IOC can connect)

A 9600 baud RS-232 connection from the Moxa to the Edwards STP-iX3006 Turbomolecular pump.


The basics of the pump serial protocol:  (### = serial transmission block # starting at 001)
   IOC:  <STX>###?<cmd-char><ETX><8-bit-checksum>
  Pump:  <ACK>
  if < 255 bytes of data: 
      Pump:  <STX>###<space><cmd-char><data><ETX><8-bit-checksum>
  else:
      Pump:  <STX>###<space><cmd-char><data><ETB><8-bit-checksum>
  IOC:  <ACK>
  While > 255 data bytes left:
       Pump: <STX>###<another-255-bytes-data><ETB><8-bit-checksum>
       IOC:  <ACK>
  When <= 255 bytes left:
       Pump: <STX>###<remaining-data><ETX><8-bit-checksum>
       IOC:  <ACK>

When the total bytes in a transmission block > 112, the Moxa splits it in to multiple packets.

NOTE:  There is no evidence of any delay between the characters within a serial transmission block from the pump, yet even when I change settings in the Moxa that should result in larger packets (the max packet size is set to 1024, the time to wait for more bytes before sending a packet is set to 1000 ms, the delimiter character is set to <ETX>, ...), it always breaks up any data stream > 112 bytes in to separate packets.  If it were not for the fact that it (supposedly) supports a packet size up to 1024 bytes, I would assume that (as odd a # as that is) it was the max # of data bytes it could send in one packet.


The StreamDevice issue related to this:

For a protocol linked to a periodically processed record, StreamDevice will correctly match the pattern of the "in" commands to the contents of the transmission blocks from the pump, even when individual blocks have been split up in to multiple TCP packets. 

However, this does not appear to be true for protocols that are linked to records using "I/O Intr".  For such protocols, StreamDevice appears to require a 1-to-1 relationship between the packets and the patterns in the "in" commands.  If you know the # and size of each packet, this can be used to define a working protocol (see example below).  But if those values change for whatever reason, then the protocols that depend on them will stop working.

Example from my protocol file:

## times are in milliseconds
LockTimeout = 20000;  # max wait for exclusive access to the device
WriteTimeout =  500;  # max wait to be able to write to the device
ReplyTimeout = 1000;  # max wait until the start of a reply
ReadTimeout =  1000;  # max idle time after start of reply
PollPeriod =    200;  # How often to poll for new input for "I/O Intr" mode records
 
ExtraInput = Ignore;
MaxInput = 0;


##-----[ReadEventsWithTime]----------------------------------------------------

#===== Record for this protocol must process periodically =====
##--- StreamDevice properly matches the "in" patterns to the separate
##--- transmission blocks even when the contents of each block has
##--- been split across multiple TCP packets.
ReadEventsWT {
  out     "\x02" "001?}" "\x03%<xor8ff>";
  in  "\x06\x02" "001 }%253c" "\x17%*1c";
  out "\x06";
  in  "\x02" "002%*151c" "\x03%*1c";
  out "\x06";
}
#=====Records for these protocols must use "I/O Intr" SCAN =====
#--- The data for these are in the 1st packet of the 1st transmission block 
curNumErrs { in "\x02" "001 }%2x";       }  # current # of errors
maxNumErrs { in "\x02" "001 }%*2c%2x";   }  # max # of errors
 
err1Type   { in "\x02" "001 }%*4c%2x";   }  # Error 1 #
err1Date   { in "\x02" "001 }%*8c%6c";   }  # Error 1 date (YYMMDD)
err1Time   { in "\x02" "001 }%*14c%4c";  }  # Error 1 time (HHMM)
 
err2Type   { in "\x02" "001 }%*24c%2x";  }  # Error 2 #
err2Date   { in "\x02" "001 }%*28c%6c";  }  # Error 2 date (YYMMDD)
err2Time   { in "\x02" "001 }%*34c%4c";  }  # Error 2 time (HHMM)
 
err3Type   { in "\x02" "001 }%*44c%2x";  }  # Error 3 #
err3Date   { in "\x02" "001 }%*48c%6c";  }  # Error 3 date (YYMMDD)
err3Time   { in "\x02" "001 }%*54c%4c";  }  # Error 3 time (HHMM)
 
...
 
err6Type   { in "\x02" "001 }%*104c%2x"; }  # Error 6 #
##--- The following fail to match the remaining values in the 1st transmission block
##--- that are in the 2nd TCP packet
#err6Date   { in "\x02" "001 }%*108c%6c";  }  # Error 6 date (YYMMDD)
#err6Time   { in "\x02" "001 }%*114c%4c";  }  # Error 6 time (HHMM)
##--- The following work to get the desired values from the 1st transmission block
##--- that are in the 2nd TCP packet
err6Date   { in "\x02" "001 }%*106c";  in "%*2c%6c"; }  # Error 6 date (YYMMDD)
err6Time   { in "\x02" "001 }%*106c";  in "%*8c%4c"; }  # Error 6 time (HHMM)


Sample of the related records:

##=============================================================================
record(stringin, "_$(SYS)_$(SUB):$(DEV)_$(INST):ReadEventsWT") {
  field(DESC, "List of events/errors with times")
  field(SCAN, "5 second")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto ReadEventsWT $(PORT)")
}
##--------------------------------------------------------------
record(longin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_CNT") {
  field(DESC, "Number of HW err events")
  field(EGU,  "NumErrs")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto curNumErrs $(PORT)")
}
##--------------------------------------------------------------
record(longin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_TYP1") {
  field(DESC, "hdw error 1 event type")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err1Type $(PORT)")
}
record(stringin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_DT1") {
  field(DESC, "hdw error 1 event date")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err1Date $(PORT)")
}
record(stringin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_TM1") {
  field(DESC, "hdw error 1 event time")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err1Time $(PORT)")
}
 
...
 
##--------------------------------------------------------------
record(longin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_TYP6") {
  field(DESC, "hdw error 6 event type")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err6Type $(PORT)")
}
record(stringin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_DT6") {
  field(DESC, "hdw error 6 event date")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err6Date $(PORT)")
}
record(stringin, "$(SYS)_$(SUB):$(DEV)_$(INST):HWER_RD_TM6") {
  field(DESC, "hdw error 6 event time")
  field(SCAN, "I/O Intr")
  field(DTYP, "stream")
  field(INP,  "@Edwards_STP_TMP.proto err6Time $(PORT)")
}
 

Mark



References:
StreamDevice processing issue Davis, Mark via Tech-talk
Re: StreamDevice processing issue Dirk Zimoch via Tech-talk
Re: StreamDevice processing issue Davis, Mark via Tech-talk
Re: StreamDevice processing issue Davis, Mark via Tech-talk

Navigate by Date:
Prev: Re: Inter-module dependency between stream and calc Johnson, Andrew N. via Tech-talk
Next: Re: Inter-module dependency between stream and calc Konrad, Martin via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: StreamDevice processing issue Davis, Mark via Tech-talk
Next: Re: StreamDevice processing issue Lucock, Richard M via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024