Пример #1
0
    def configure(self, config):
        CompositeNode.configure(self, config)
        set_attribute(self, 'dev', REQUIRED, config)
        set_attribute(self, 'parity', parity_to_int('none'), config,
                      parity_to_int)
        set_attribute(self, 'baud', 9600, config, str)
        if self.baud == 'Custom':
            self.baud = -1  # flag for later processing of custom_baud
        else:
            self.baud = int(self.baud)  # normal path
        set_attributes(self, (('bits', 8), ('stop_bits', 1), ('debug', 0),
                              ('dump_cpl', 16)), config, int)
        set_attribute(self, 'custom_baud', 76800, config, int)
        set_attribute(self, 'flow_control', flowctl_to_int('none'), config,
                      flowctl_to_int)
        set_attribute(self, 'lock_directory', properties.get('VAR_LOCK'),
                      config, str)

        set_attribute(self, 'VINTR', '\x03', config, str)
        set_attribute(self, 'VQUIT', '\x1c', config, str)
        set_attribute(self, 'VERASE', '\x7f', config, str)
        set_attribute(self, 'VKILL', '\x15', config, str)
        set_attribute(self, 'VEOF', '\x04', config, str)
        set_attribute(self, 'VTIME', 0, config, int)
        set_attribute(self, 'VMIN', 1, config, int)
        set_attribute(self, 'VSWTC', '\x00', config, str)
        set_attribute(self, 'VSTART', '\x11', config, str)
        set_attribute(self, 'VSTOP', '\x13', config, str)
        set_attribute(self, 'VSUSP', '\x1a', config, str)
        set_attribute(self, 'VEOL', '\x00', config, str)
        set_attribute(self, 'VREPRINT', '\x12', config, str)
        set_attribute(self, 'VDISCARD', '\x0f', config, str)
        set_attribute(self, 'VWERASE', '\x17', config, str)
        set_attribute(self, 'VLNEXT', '\x16', config, str)
        set_attribute(self, 'VEOL2', '\x00', config, str)
        set_attribute(self, 'cc17', '\x00', config, str)
        set_attribute(self, 'cc18', '/', config, str)
        set_attribute(self, 'cc19', '\x00', config, str)
        set_attribute(self, 'cc20', '\x00', config, str)
        set_attribute(self, 'cc21', '\x00', config, str)
        set_attribute(self, 'cc22', '\x00', config, str)
        set_attribute(self, 'cc23', '\x00', config, str)
        set_attribute(self, 'cc24', '\x00', config, str)
        set_attribute(self, 'cc25', '\x00', config, str)
        set_attribute(self, 'cc26', '\x00', config, str)
        set_attribute(self, 'cc27', '\x00', config, str)
        set_attribute(self, 'cc28', '\x00', config, str)
        set_attribute(self, 'cc29', '\x00', config, str)
        set_attribute(self, 'cc30', '\x00', config, str)
        set_attribute(self, 'cc31', '\x00', config, str)

        self._devlock = DeviceLock(self.dev, self.lock_directory)

        if self.is_open():
            self._set_serial()
        return
Пример #2
0
 def configure(self,config):
     CompositeNode.configure(self,config)
     set_attribute(self, 'dev', REQUIRED, config)
     set_attribute(self, 'parity', parity_to_int('none'), 
                   config, parity_to_int)
     set_attribute(self, 'baud', 9600, config, str)
     if self.baud == 'Custom':
         self.baud = -1 # flag for later processing of custom_baud
     else:
          self.baud = int(self.baud) # normal path  
     set_attributes(self, (('bits',8), ('stop_bits',1),
                       ('debug',0), ('dump_cpl',16)), config, int)
     set_attribute(self, 'custom_baud', 76800, config, int)
     set_attribute(self, 'flow_control', flowctl_to_int('none'), 
                   config, flowctl_to_int)
     set_attribute(self, 'lock_directory', properties.get('VAR_LOCK'), config, str)
     
     set_attribute(self, 'VINTR', '\x03', config, str)
     set_attribute(self, 'VQUIT', '\x1c', config, str)
     set_attribute(self, 'VERASE', '\x7f', config, str)
     set_attribute(self, 'VKILL', '\x15', config, str)
     set_attribute(self, 'VEOF', '\x04', config, str)
     set_attribute(self, 'VTIME', 0, config, int)
     set_attribute(self, 'VMIN', 1, config, int)
     set_attribute(self, 'VSWTC', '\x00', config, str)
     set_attribute(self, 'VSTART', '\x11', config, str)
     set_attribute(self, 'VSTOP', '\x13', config, str)
     set_attribute(self, 'VSUSP', '\x1a', config, str)
     set_attribute(self, 'VEOL', '\x00', config, str)
     set_attribute(self, 'VREPRINT', '\x12', config, str)
     set_attribute(self, 'VDISCARD', '\x0f', config, str)
     set_attribute(self, 'VWERASE', '\x17', config, str)
     set_attribute(self, 'VLNEXT', '\x16', config, str)
     set_attribute(self, 'VEOL2', '\x00', config, str)
     set_attribute(self, 'cc17', '\x00', config, str)
     set_attribute(self, 'cc18', '/', config, str)
     set_attribute(self, 'cc19', '\x00', config, str)
     set_attribute(self, 'cc20', '\x00', config, str)
     set_attribute(self, 'cc21', '\x00', config, str)
     set_attribute(self, 'cc22', '\x00', config, str)
     set_attribute(self, 'cc23', '\x00', config, str)
     set_attribute(self, 'cc24', '\x00', config, str)
     set_attribute(self, 'cc25', '\x00', config, str)
     set_attribute(self, 'cc26', '\x00', config, str)
     set_attribute(self, 'cc27', '\x00', config, str)
     set_attribute(self, 'cc28', '\x00', config, str)
     set_attribute(self, 'cc29', '\x00', config, str)
     set_attribute(self, 'cc30', '\x00', config, str)
     set_attribute(self, 'cc31', '\x00', config, str)
     set_attribute(self, 'raw_mode', 0, config, as_boolean)
     self._devlock = DeviceLock(self.dev, self.lock_directory)
     
     if self.is_open():
         self._set_serial()
Пример #3
0
    def configure(self, config):
        CompositeNode.configure(self, config)
        set_attribute(self, "dev", REQUIRED, config)
        set_attribute(self, "parity", parity_to_int("none"), config, parity_to_int)
        set_attribute(self, "baud", 9600, config, str)
        if self.baud == "Custom":
            self.baud = -1  # flag for later processing of custom_baud
        else:
            self.baud = int(self.baud)  # normal path
        set_attributes(self, (("bits", 8), ("stop_bits", 1), ("debug", 0), ("dump_cpl", 16)), config, int)
        set_attribute(self, "custom_baud", 76800, config, int)
        set_attribute(self, "flow_control", flowctl_to_int("none"), config, flowctl_to_int)
        set_attribute(self, "lock_directory", properties.get("VAR_LOCK"), config, str)

        set_attribute(self, "VINTR", "\x03", config, str)
        set_attribute(self, "VQUIT", "\x1c", config, str)
        set_attribute(self, "VERASE", "\x7f", config, str)
        set_attribute(self, "VKILL", "\x15", config, str)
        set_attribute(self, "VEOF", "\x04", config, str)
        set_attribute(self, "VTIME", 0, config, int)
        set_attribute(self, "VMIN", 1, config, int)
        set_attribute(self, "VSWTC", "\x00", config, str)
        set_attribute(self, "VSTART", "\x11", config, str)
        set_attribute(self, "VSTOP", "\x13", config, str)
        set_attribute(self, "VSUSP", "\x1a", config, str)
        set_attribute(self, "VEOL", "\x00", config, str)
        set_attribute(self, "VREPRINT", "\x12", config, str)
        set_attribute(self, "VDISCARD", "\x0f", config, str)
        set_attribute(self, "VWERASE", "\x17", config, str)
        set_attribute(self, "VLNEXT", "\x16", config, str)
        set_attribute(self, "VEOL2", "\x00", config, str)
        set_attribute(self, "cc17", "\x00", config, str)
        set_attribute(self, "cc18", "/", config, str)
        set_attribute(self, "cc19", "\x00", config, str)
        set_attribute(self, "cc20", "\x00", config, str)
        set_attribute(self, "cc21", "\x00", config, str)
        set_attribute(self, "cc22", "\x00", config, str)
        set_attribute(self, "cc23", "\x00", config, str)
        set_attribute(self, "cc24", "\x00", config, str)
        set_attribute(self, "cc25", "\x00", config, str)
        set_attribute(self, "cc26", "\x00", config, str)
        set_attribute(self, "cc27", "\x00", config, str)
        set_attribute(self, "cc28", "\x00", config, str)
        set_attribute(self, "cc29", "\x00", config, str)
        set_attribute(self, "cc30", "\x00", config, str)
        set_attribute(self, "cc31", "\x00", config, str)

        self._devlock = DeviceLock(self.dev, self.lock_directory)

        if self.is_open():
            self._set_serial()
        return
Пример #4
0
class Port(CompositeNode):
    ##
    # Dump message to a string.
    # @param msg The string or array to dump.
    # @param hdr The string to prepend to each line.
    # @param offset The index to start dumping from.
    #
    def dump_tostring(self, msg, hdr=None, offset=0):
        return dump_tostring(msg, hdr, offset, self.dump_cpl)

    ##
    # Dump message to standard out.
    # @param msg The string or array to dump.
    # @param hdr The string to prepend to each line.
    # @param offset The index to start dumping from.
    #
    def dump(self, msg, hdr=None, offset=0):
        return dump(msg, hdr, offset, self.dump_cpl)

    ##
    # Instanciate an unconfigured Port ION.
    #
    def __init__(self):
        CompositeNode.__init__(self)
        self._internal_lock = threading.Lock()
        self._lock = threading.RLock()
        self._devlock = None
        self.poll = None
        self.file = None
        self._buf = array.array("c")
        self._lock_requests = 0

        self._blocking = 0
        return

    ##
    # Configure a Port instance.
    #
    # @param config The port's configuration dictionary.
    # @key 'name' The name to associate with the Port.
    # @required
    # @key 'parent' The parent ION (typically '/ion').
    # @required
    # @key 'dev' The linux device to open (e.g. '/dev/ttyS0')
    # @required
    # @key 'parity' The type of parity bit to expect and generate.
    # @value 'none' No parity
    # @value 'odd' Odd parity
    # @value 'even' Even parity
    # @default 'none'
    # @key 'flow_control' The type of flow control to use.
    # @value 'none' No flow control.
    # @value 'hardware' Hardware flow control.
    # @value 'software' Software flow control.
    # @value 'both' Use harware and software flow control.
    # @default 'none'
    # @key 'baud' Set the baud rate to the specified value.
    # @defualt 9600
    # @key 'bits' The number of bits per character.
    # @defualt 8
    # @key 'stop_bits' The number of stop bits per character
    # @value 1;2
    # @default 1
    # @key 'debug' Echo data read and written to standard out in a human
    #              readable format.
    # @default 0
    # @key 'dump_cpl' The number of debug characters to dump per line.
    # @default 16
    # @todo Determin if it's OK that a float is rounded to an int.  E.g.
    #       1.1 becomes 1, which may be valid.
    # @todo Determin if it's OK that the None instance is converted to
    #       The string 'none', which is a valid parity.
    def configure(self, config):
        CompositeNode.configure(self, config)
        set_attribute(self, "dev", REQUIRED, config)
        set_attribute(self, "parity", parity_to_int("none"), config, parity_to_int)
        set_attribute(self, "baud", 9600, config, str)
        if self.baud == "Custom":
            self.baud = -1  # flag for later processing of custom_baud
        else:
            self.baud = int(self.baud)  # normal path
        set_attributes(self, (("bits", 8), ("stop_bits", 1), ("debug", 0), ("dump_cpl", 16)), config, int)
        set_attribute(self, "custom_baud", 76800, config, int)
        set_attribute(self, "flow_control", flowctl_to_int("none"), config, flowctl_to_int)
        set_attribute(self, "lock_directory", properties.get("VAR_LOCK"), config, str)

        set_attribute(self, "VINTR", "\x03", config, str)
        set_attribute(self, "VQUIT", "\x1c", config, str)
        set_attribute(self, "VERASE", "\x7f", config, str)
        set_attribute(self, "VKILL", "\x15", config, str)
        set_attribute(self, "VEOF", "\x04", config, str)
        set_attribute(self, "VTIME", 0, config, int)
        set_attribute(self, "VMIN", 1, config, int)
        set_attribute(self, "VSWTC", "\x00", config, str)
        set_attribute(self, "VSTART", "\x11", config, str)
        set_attribute(self, "VSTOP", "\x13", config, str)
        set_attribute(self, "VSUSP", "\x1a", config, str)
        set_attribute(self, "VEOL", "\x00", config, str)
        set_attribute(self, "VREPRINT", "\x12", config, str)
        set_attribute(self, "VDISCARD", "\x0f", config, str)
        set_attribute(self, "VWERASE", "\x17", config, str)
        set_attribute(self, "VLNEXT", "\x16", config, str)
        set_attribute(self, "VEOL2", "\x00", config, str)
        set_attribute(self, "cc17", "\x00", config, str)
        set_attribute(self, "cc18", "/", config, str)
        set_attribute(self, "cc19", "\x00", config, str)
        set_attribute(self, "cc20", "\x00", config, str)
        set_attribute(self, "cc21", "\x00", config, str)
        set_attribute(self, "cc22", "\x00", config, str)
        set_attribute(self, "cc23", "\x00", config, str)
        set_attribute(self, "cc24", "\x00", config, str)
        set_attribute(self, "cc25", "\x00", config, str)
        set_attribute(self, "cc26", "\x00", config, str)
        set_attribute(self, "cc27", "\x00", config, str)
        set_attribute(self, "cc28", "\x00", config, str)
        set_attribute(self, "cc29", "\x00", config, str)
        set_attribute(self, "cc30", "\x00", config, str)
        set_attribute(self, "cc31", "\x00", config, str)

        self._devlock = DeviceLock(self.dev, self.lock_directory)

        if self.is_open():
            self._set_serial()
        return

    def seconds_per_character(self):
        return float(self.bits_per_character(True)) / float(self.bits_per_second())

    def bits_per_second(self):
        if self.baud == -1:
            return self.custom_baud
        return self.baud

    def bits_per_character(self, total=False):
        bits = self.bits
        if total:
            bits += 1  # Start bit.
            bits += self.stop_bits
            parity = parity_to_int(self.parity)
            if parity:  # 0 == No parity bit, other values indicate the type
                bits += 1
        return bits

    ##
    # Returns a dictionary of the port's attributes
    #
    # @return Configuration dictionary.
    #
    def configuration(self):
        config = CompositeNode.configuration(self)
        get_attribute(self, "dev", config)
        get_attribute(self, "parity", config, int_to_parity)
        get_attribute(self, "flow_control", config, int_to_flowctl)
        get_attributes(self, ("baud", "bits", "stop_bits", "debug", "dump_cpl"), config, str)
        get_attribute(self, "custom_baud", config, int)
        get_attribute(self, "lock_directory", config)

        get_attribute(self, "VTIME", config, int)
        get_attribute(self, "VMIN", config, int)

        if 0:  # we don't need to see this stuff in the Node Browser Bug 6066
            get_attribute(self, "VINTR", config, str)
            get_attribute(self, "VQUIT", config, str)
            get_attribute(self, "VERASE", config, str)
            get_attribute(self, "VKILL", config, str)
            get_attribute(self, "VEOF", config, str)
            get_attribute(self, "VSWTC", config, str)
            get_attribute(self, "VSTART", config, str)
            get_attribute(self, "VSTOP", config, str)
            get_attribute(self, "VSUSP", config, str)
            get_attribute(self, "VEOL", config, str)
            get_attribute(self, "VREPRINT", config, str)
            get_attribute(self, "VDISCARD", config, str)
            get_attribute(self, "VWERASE", config, str)
            get_attribute(self, "VLNEXT", config, str)
            get_attribute(self, "VEOL2", config, str)
            get_attribute(self, "cc17", config, str)
            get_attribute(self, "cc18", config, str)
            get_attribute(self, "cc19", config, str)
            get_attribute(self, "cc20", config, str)
            get_attribute(self, "cc21", config, str)
            get_attribute(self, "cc22", config, str)
            get_attribute(self, "cc23", config, str)
            get_attribute(self, "cc24", config, str)
            get_attribute(self, "cc25", config, str)
            get_attribute(self, "cc26", config, str)
            get_attribute(self, "cc27", config, str)
            get_attribute(self, "cc28", config, str)
            get_attribute(self, "cc29", config, str)
            get_attribute(self, "cc30", config, str)
            get_attribute(self, "cc31", config, str)
        return config

    ##
    # Prevent other threads from reading or writing to the port until it's
    # unlocked().
    #
    # Used to synchronize access to the port.
    #
    def lock(self):
        self._lock.acquire()
        return

    ##
    # Release the inner most lock on the port by the current thread.
    #
    # Used to synchronize access to the port.
    #
    def unlock(self):
        self._lock.release()
        return

    ##
    # Use locks to safely apply a function.
    #
    # @param func  Reference to function to apply.
    # @param *args Variable number of arguments to pass to
    #              <code>func</code>.
    #
    # @return Result returned from <code>func</code>.
    #
    def _safe_apply(self, func, *args):
        result = None
        self._internal_lock.acquire()
        try:
            result = apply(func, args)
        finally:
            self._internal_lock.release()
        return result

    ##
    # Reads port into buffer, removes buffer data, returns copy
    # of buffered data.
    #
    # @return All information from buffer.
    #
    def _drain(self):
        if self.is_open():
            self._write_internal_buffer(self._read_port())
        buffer = array.array("c")
        self._read_internal_buffer(buffer, len(self._buf))
        self._buf = array.array("c")
        return buffer

    ##
    # Read the port.
    #
    # @return Array containing data read from the port.
    #
    def _read_port(self):
        buffer = array.array("c")
        while self.poll.poll(0):
            buffer.fromstring(self.file.read())
        return buffer

    def _read(self, buffer, count, timeout):
        count = count - self._read_internal_buffer(buffer, count)
        t_end = now() + timeout
        ms = int(timeout * 1000)
        while count and ms >= 0:
            poll_list = self.poll.poll(ms)
            for (file_descriptor, event) in poll_list:
                if event == select.POLLIN:
                    self._write_internal_buffer(self._read_port())
                    count = count - self._read_internal_buffer(buffer, count)
            ms = int((t_end - now()) * 1000)
        if count:
            raise ETimeout

    def _read_upto(self, buffer, list, timeout):
        count = self._scan_for(list)
        if count != None:
            self._read_internal_buffer(buffer, count)
            return self._buf.pop(0)
        t_end = now() + timeout
        ms = int(timeout * 1000)
        while ms >= 0:
            poll_list = self.poll.poll(ms)
            for (file_descriptor, event) in poll_list:
                if event == select.POLLIN:
                    self._write_internal_buffer(self._read_port())
                    count = self._scan_for(list)
                    if count != None:
                        self._read_internal_buffer(buffer, count)
                        return self._buf.pop(0)
                    else:
                        self._read_internal_buffer(buffer, len(self._buf))
            ms = int((t_end - now()) * 1000)
        raise ETimeout

    def _read_including(self, buffer, list, timeout):
        item = self._read_upto(buffer, list, timeout)
        buffer.fromstring(item)
        return item

    ##
    # Read up to <code>count</code> bytes of data from buffered data.
    #
    # @param buffer  The buffer to fill.
    # @param count  The number of bytes to try and put in the buffer.
    # @return Number of bytes read.
    #
    def _read_internal_buffer(self, buffer, count):
        start_count = len(buffer)
        if len(self._buf) <= count:
            buffer.fromstring(self._buf.tostring())
            del (self._buf[:])
        else:
            buffer.fromstring(self._buf.tostring()[:count])
            del (self._buf[:count])
        return len(buffer) - start_count

    ##
    # Add data to buffer.
    #
    # @param data  String or array containing data to
    #              write to buffer.
    #
    def _write_internal_buffer(self, data):
        if type(data) == types.StringType:
            self._buf.fromstring(data)
        else:
            self._buf.extend(data)

    def _write(self, buffer):
        buffer.tofile(self.file)

    ##
    # Scans internal buffer for the occurence on any
    # character in <code>list</code>
    #
    # @param list  List containing characters that we
    #              are looking for.
    #
    def _scan_for(self, list):
        index = None
        for item in list:
            if item in self._buf:
                new_index = self._buf.index(item)
                if index == None or new_index < index:
                    index = new_index
        return index

    ##
    # Check to see if the port is open.
    #
    # @return True if the port is currently open.
    # @see open()
    #
    def is_open(self):
        return self.file != None

    def _set_serial(self):
        iflags = 0
        oflags = 0
        cflags = CREAD | CLOCAL | HUPCL
        lflags = 0

        # Set baud:
        if self.baud != -1:  # if custom baud is NOT specified, proceed normally:
            try:
                baud = getattr(termios, "B%u" % self.baud)
            except AttributeError:
                raise EInvalidValue, ("baud", self.baud)
        else:  # else, set up for given custom baud:
            baud = getattr(termios, "B38400")  # to be used in flags[] below...
            try:
                set_custom_baud(self.file.fileno(), self.custom_baud)
            except ValueError:  # all other exceptions are passed to caller
                print "Bad custom baud rate: %u" % self.custom_baud
                msglog.log("Port", msglog.types.ERR, "Bad custom baud rate submitted: %u." % self.custom_baud)

        # Set bits.
        if self.bits < 5 or self.bits > 8:
            raise EInvalidValue, ("bits", self.bits)
        cflags = cflags | getattr(termios, "CS%u" % self.bits)

        # Set parity.
        if self.parity:
            iflags = iflags | INPCK
            cflags = cflags | PARENB
            if self.parity == 1:
                cflags = cflags | PARODD
            elif self.parity != 2:
                raise EInvalidValue, ("parity", self.parity)
        else:
            iflags = iflags | IGNPAR

        # Set flow control options.
        if self.flow_control:
            if self.flow_control & 0x1:
                cflags |= CRTSCTS  # Enable hardware flow control.
            if self.flow_control & 0x2:
                iflags |= IXON  # Enable software flow control.
                iflags |= IXOFF

        # Set stop bits.
        if self.stop_bits == 2:
            cflags |= CSTOPB
        elif not self.stop_bits == 1:
            raise EInvalidValue, ("stop_bits", self.stop_bits)

        # Configure port according to the configuration.
        flags = tcgetattr(self.file.fileno())
        self.old_flags = flags[:]  # make full copy of list
        flags[0] = iflags
        flags[1] = oflags
        flags[2] = cflags
        flags[3] = lflags
        flags[4] = baud
        flags[5] = baud
        flags[6][VINTR] = self.VINTR
        flags[6][VQUIT] = self.VQUIT
        flags[6][VERASE] = self.VERASE
        flags[6][VKILL] = self.VKILL
        flags[6][VEOF] = self.VEOF
        flags[6][VMIN] = VMIN
        flags[6][VTIME] = VTIME
        flags[6][VSWTC] = self.VSWTC
        flags[6][VSTART] = self.VSTART
        flags[6][VSTOP] = self.VSTOP
        flags[6][VSUSP] = self.VSUSP
        flags[6][VEOL] = self.VEOL
        flags[6][VREPRINT] = self.VREPRINT
        flags[6][VDISCARD] = self.VDISCARD
        flags[6][VWERASE] = self.VWERASE
        flags[6][VLNEXT] = self.VLNEXT
        flags[6][VEOL2] = self.VEOL2
        flags[6][17] = self.cc17
        flags[6][18] = self.cc18
        flags[6][19] = self.cc19
        flags[6][20] = self.cc20
        flags[6][21] = self.cc21
        flags[6][22] = self.cc22
        flags[6][23] = self.cc23
        flags[6][24] = self.cc24
        flags[6][25] = self.cc25
        flags[6][26] = self.cc26
        flags[6][27] = self.cc27
        flags[6][28] = self.cc28
        flags[6][29] = self.cc29
        flags[6][30] = self.cc30
        flags[6][31] = self.cc31
        tcsetattr(self.file.fileno(), TCSANOW, flags)

        # We rely on non-blocking I/O by default, but BACnet data link drivers
        # (Ethernet, IP, and RS485-based MS/TP) rely on blocking ops:
        if self._blocking == 0:
            flags = fcntl.fcntl(self.file.fileno(), fcntl.F_GETFL)
            fcntl.fcntl(self.file.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
        return

    ##
    # @todo Add retries and a timeout instead of one shot and fail.
    def _create_lock_file(self, retries=0, retry_rate=1.0):
        self._devlock.acquire(retries, retry_rate)

    def _delete_lock_file(self):
        try:
            self._devlock.release()
        except:
            msglog.exception()

    ##
    # Open the port for read/write access, using the configured attributes.
    # @see #configure()
    #
    # @todo Add a timeout to the open for acquiring the lock.
    def open(self, blocking=0):
        if self.debug:
            print "Port.open()."
        if self.is_open():
            raise EAlreadyOpen
        self._create_lock_file()
        self._blocking = blocking  # save user's desired blocking mode

        # A file object is used as a convenience, especially for array's tofile
        # and fromfile methods.  A buffer size of 0 should disable all C FILE
        # buffering.
        self.file = open(self.dev, "w+b", 0)
        self.poll = select.poll()
        self.poll.register(self.file, select.POLLIN)
        self._set_serial()

    ##
    # Close the port, disallowing read/write access.
    #
    def close(self):
        if self.debug:
            print "Port.close."
        if not self.is_open():
            raise ENotOpen
        if hasattr(self, "old_flags"):
            tcsetattr(self.file.fileno(), TCSANOW, self.old_flags)
        self._delete_lock_file()
        self.file.close()
        self.file = None
        self.poll = None
        # Ensure that internal buffer is empty.
        self._buf = array.array("c")

    ##
    # Ensure that all queued ouput bytes are transmitted.
    #
    def flush(self):
        if self.debug:
            print "Port.flush()."
        self.file.flush()

    ##
    # Send a break on the port.
    #
    # @param duration The period, in seconds, to send the break.
    #                 The duration is rounded up to the next 0.25 second
    #                 quantum.  The actual duration may be up to twice as
    #                 as long as requested.
    # @default 0.25
    #
    def sendbreak(self, duration=0):
        duration = int((duration * 4) + 0.999)
        self._internal_lock.acquire()
        try:
            tcsendbreak(self.file.fileno(), duration)
        finally:
            self._internal_lock.release()

    ##
    # Disregard all queued input bytes.
    #
    # @return buffer containing disregarded queued bytes.
    #
    def drain(self):
        buffer = self._safe_apply(self._drain)
        if self.debug:
            self.dump(buffer, "D< ")
        return buffer

    ##
    # Poll port for input.
    #
    # @param timeout_ms Number of miliseconds to wait
    #                   for input before timing out.
    # @return 1 If input is available, 0 if action
    #           timedout.
    #
    def poll_input(self, timeout_ms=None):
        if self.debug:
            print "Port.poll_input(%s)." % str(timeout_ms)
        if len(self._buf):
            return 1
        result = self.poll.poll(timeout_ms)
        for fd, event in result:
            if event == select.POLLIN:
                return 1
        return 0

    ##
    # Write a string or array to the serial port.
    #
    # @param buffer  The string or array to transmit.
    #
    def write(self, buffer):
        if not type(buffer) is array.ArrayType:
            # This is a convenience that may be removed.
            buffer = array.array("c", buffer)
        if self.debug:
            print "Port.write(self, buffer):"
            self.dump(buffer, " > ")
        return self._safe_apply(self._write, buffer)

    ##
    # Read up to count bytes from the serial port.
    #
    # @param buffer The target array object into which bytes are appended.
    # @param count The maximum number of bytes to read.
    # @param timeout The maximun number of seconds to wait for count bytes.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read(self, buffer, count, timeout):
        if self.debug:
            print "Port.read(self, buffer, %d, %f):" % (count, timeout)
            offset = len(buffer)
        result = self._safe_apply(self._read, buffer, count, timeout)
        if self.debug:
            self.dump(buffer, " < ", offset)
        return result

    ##
    # Read all bytes from the serial port upto, but excluding, any characters
    # in the list.  Characters in the list are discarded.
    # @param buffer The target array object into which bytes are appended.
    # @param list The list of characters that terminate the read.
    # @param timeout The maximun number of seconds to wait to read up to a
    #                termination character.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read_upto(self, buffer, list, timeout):
        if self.debug:
            offset = len(buffer)
        c = self._safe_apply(self._read_upto, buffer, list, timeout)
        if self.debug:
            self.dump(buffer, " < ", offset)
            self.dump(c, "X< ")
        return c

    ##
    # Read all bytes from the serial port upto, and including, any characters
    # in the list.
    # @param buffer The target array object into which bytes are appended.
    # @param list The list of characters that terminate the read.
    # @param timeout The maximun number of seconds to wait to read up to a
    #                termination character.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read_including(self, buffer, list, timeout):
        if self.debug:
            offset = len(buffer)
        c = self._safe_apply(self._read_including, buffer, list, timeout)
        if self.debug:
            self.dump(buffer, " < ", offset)
        return c
Пример #5
0
class Port(CompositeNode):
    ##
    # Dump message to a string.
    # @param msg The string or array to dump.
    # @param hdr The string to prepend to each line.
    # @param offset The index to start dumping from.
    #
    def dump_tostring(self, msg, hdr=None, offset=0):
        return dump_tostring(msg, hdr, offset, self.dump_cpl)

    ##
    # Dump message to standard out.
    # @param msg The string or array to dump.
    # @param hdr The string to prepend to each line.
    # @param offset The index to start dumping from.
    #
    def dump(self, msg, hdr=None, offset=0):
        return dump(msg, hdr, offset, self.dump_cpl)

    ##
    # Instanciate an unconfigured Port ION.
    #
    def __init__(self):
        CompositeNode.__init__(self)
        self._internal_lock = threading.Lock()
        self._lock = threading.RLock()
        self._devlock = None
        self.poll = None
        self.file = None
        self._buf = array.array('c')
        self._lock_requests = 0

        self._blocking = 0
        return

    ##
    # Configure a Port instance.
    #
    # @param config The port's configuration dictionary.
    # @key 'name' The name to associate with the Port.
    # @required
    # @key 'parent' The parent ION (typically '/ion').
    # @required
    # @key 'dev' The linux device to open (e.g. '/dev/ttyS0')
    # @required
    # @key 'parity' The type of parity bit to expect and generate.
    # @value 'none' No parity
    # @value 'odd' Odd parity
    # @value 'even' Even parity
    # @default 'none'
    # @key 'flow_control' The type of flow control to use.
    # @value 'none' No flow control.
    # @value 'hardware' Hardware flow control.
    # @value 'software' Software flow control.
    # @value 'both' Use harware and software flow control.
    # @default 'none'
    # @key 'baud' Set the baud rate to the specified value.
    # @defualt 9600
    # @key 'bits' The number of bits per character.
    # @defualt 8
    # @key 'stop_bits' The number of stop bits per character
    # @value 1;2
    # @default 1
    # @key 'debug' Echo data read and written to standard out in a human
    #              readable format.
    # @default 0
    # @key 'dump_cpl' The number of debug characters to dump per line.
    # @default 16
    # @todo Determin if it's OK that a float is rounded to an int.  E.g.
    #       1.1 becomes 1, which may be valid.
    # @todo Determin if it's OK that the None instance is converted to
    #       The string 'none', which is a valid parity.
    def configure(self, config):
        CompositeNode.configure(self, config)
        set_attribute(self, 'dev', REQUIRED, config)
        set_attribute(self, 'parity', parity_to_int('none'), config,
                      parity_to_int)
        set_attribute(self, 'baud', 9600, config, str)
        if self.baud == 'Custom':
            self.baud = -1  # flag for later processing of custom_baud
        else:
            self.baud = int(self.baud)  # normal path
        set_attributes(self, (('bits', 8), ('stop_bits', 1), ('debug', 0),
                              ('dump_cpl', 16)), config, int)
        set_attribute(self, 'custom_baud', 76800, config, int)
        set_attribute(self, 'flow_control', flowctl_to_int('none'), config,
                      flowctl_to_int)
        set_attribute(self, 'lock_directory', properties.get('VAR_LOCK'),
                      config, str)

        set_attribute(self, 'VINTR', '\x03', config, str)
        set_attribute(self, 'VQUIT', '\x1c', config, str)
        set_attribute(self, 'VERASE', '\x7f', config, str)
        set_attribute(self, 'VKILL', '\x15', config, str)
        set_attribute(self, 'VEOF', '\x04', config, str)
        set_attribute(self, 'VTIME', 0, config, int)
        set_attribute(self, 'VMIN', 1, config, int)
        set_attribute(self, 'VSWTC', '\x00', config, str)
        set_attribute(self, 'VSTART', '\x11', config, str)
        set_attribute(self, 'VSTOP', '\x13', config, str)
        set_attribute(self, 'VSUSP', '\x1a', config, str)
        set_attribute(self, 'VEOL', '\x00', config, str)
        set_attribute(self, 'VREPRINT', '\x12', config, str)
        set_attribute(self, 'VDISCARD', '\x0f', config, str)
        set_attribute(self, 'VWERASE', '\x17', config, str)
        set_attribute(self, 'VLNEXT', '\x16', config, str)
        set_attribute(self, 'VEOL2', '\x00', config, str)
        set_attribute(self, 'cc17', '\x00', config, str)
        set_attribute(self, 'cc18', '/', config, str)
        set_attribute(self, 'cc19', '\x00', config, str)
        set_attribute(self, 'cc20', '\x00', config, str)
        set_attribute(self, 'cc21', '\x00', config, str)
        set_attribute(self, 'cc22', '\x00', config, str)
        set_attribute(self, 'cc23', '\x00', config, str)
        set_attribute(self, 'cc24', '\x00', config, str)
        set_attribute(self, 'cc25', '\x00', config, str)
        set_attribute(self, 'cc26', '\x00', config, str)
        set_attribute(self, 'cc27', '\x00', config, str)
        set_attribute(self, 'cc28', '\x00', config, str)
        set_attribute(self, 'cc29', '\x00', config, str)
        set_attribute(self, 'cc30', '\x00', config, str)
        set_attribute(self, 'cc31', '\x00', config, str)

        self._devlock = DeviceLock(self.dev, self.lock_directory)

        if self.is_open():
            self._set_serial()
        return

    def seconds_per_character(self):
        return (float(self.bits_per_character(True)) /
                float(self.bits_per_second()))

    def bits_per_second(self):
        if self.baud == -1:
            return self.custom_baud
        return self.baud

    def bits_per_character(self, total=False):
        bits = self.bits
        if total:
            bits += 1  # Start bit.
            bits += self.stop_bits
            parity = parity_to_int(self.parity)
            if parity:  # 0 == No parity bit, other values indicate the type
                bits += 1
        return bits

    ##
    # Returns a dictionary of the port's attributes
    #
    # @return Configuration dictionary.
    #
    def configuration(self):
        config = CompositeNode.configuration(self)
        get_attribute(self, 'dev', config)
        get_attribute(self, 'parity', config, int_to_parity)
        get_attribute(self, 'flow_control', config, int_to_flowctl)
        get_attributes(self,
                       ('baud', 'bits', 'stop_bits', 'debug', 'dump_cpl'),
                       config, str)
        get_attribute(self, 'custom_baud', config, int)
        get_attribute(self, 'lock_directory', config)

        get_attribute(self, 'VTIME', config, int)
        get_attribute(self, 'VMIN', config, int)

        if 0:  # we don't need to see this stuff in the Node Browser Bug 6066
            get_attribute(self, 'VINTR', config, str)
            get_attribute(self, 'VQUIT', config, str)
            get_attribute(self, 'VERASE', config, str)
            get_attribute(self, 'VKILL', config, str)
            get_attribute(self, 'VEOF', config, str)
            get_attribute(self, 'VSWTC', config, str)
            get_attribute(self, 'VSTART', config, str)
            get_attribute(self, 'VSTOP', config, str)
            get_attribute(self, 'VSUSP', config, str)
            get_attribute(self, 'VEOL', config, str)
            get_attribute(self, 'VREPRINT', config, str)
            get_attribute(self, 'VDISCARD', config, str)
            get_attribute(self, 'VWERASE', config, str)
            get_attribute(self, 'VLNEXT', config, str)
            get_attribute(self, 'VEOL2', config, str)
            get_attribute(self, 'cc17', config, str)
            get_attribute(self, 'cc18', config, str)
            get_attribute(self, 'cc19', config, str)
            get_attribute(self, 'cc20', config, str)
            get_attribute(self, 'cc21', config, str)
            get_attribute(self, 'cc22', config, str)
            get_attribute(self, 'cc23', config, str)
            get_attribute(self, 'cc24', config, str)
            get_attribute(self, 'cc25', config, str)
            get_attribute(self, 'cc26', config, str)
            get_attribute(self, 'cc27', config, str)
            get_attribute(self, 'cc28', config, str)
            get_attribute(self, 'cc29', config, str)
            get_attribute(self, 'cc30', config, str)
            get_attribute(self, 'cc31', config, str)
        return config

    ##
    # Prevent other threads from reading or writing to the port until it's
    # unlocked().
    #
    # Used to synchronize access to the port.
    #
    def lock(self):
        self._lock.acquire()
        return

    ##
    # Release the inner most lock on the port by the current thread.
    #
    # Used to synchronize access to the port.
    #
    def unlock(self):
        self._lock.release()
        return

    ##
    # Use locks to safely apply a function.
    #
    # @param func  Reference to function to apply.
    # @param *args Variable number of arguments to pass to
    #              <code>func</code>.
    #
    # @return Result returned from <code>func</code>.
    #
    def _safe_apply(self, func, *args):
        result = None
        self._internal_lock.acquire()
        try:
            result = apply(func, args)
        finally:
            self._internal_lock.release()
        return result

    ##
    # Reads port into buffer, removes buffer data, returns copy
    # of buffered data.
    #
    # @return All information from buffer.
    #
    def _drain(self):
        if self.is_open():
            self._write_internal_buffer(self._read_port())
        buffer = array.array('c')
        self._read_internal_buffer(buffer, len(self._buf))
        self._buf = array.array('c')
        return buffer

    ##
    # Read the port.
    #
    # @return Array containing data read from the port.
    #
    def _read_port(self):
        buffer = array.array('c')
        while self.poll.poll(0):
            buffer.fromstring(self.file.read())
        return buffer

    def _read(self, buffer, count, timeout):
        count = count - self._read_internal_buffer(buffer, count)
        t_end = now() + timeout
        ms = int(timeout * 1000)
        while count and ms >= 0:
            poll_list = self.poll.poll(ms)
            for (file_descriptor, event) in poll_list:
                if event == select.POLLIN:
                    self._write_internal_buffer(self._read_port())
                    count = count - self._read_internal_buffer(buffer, count)
            ms = int((t_end - now()) * 1000)
        if count:
            raise ETimeout

    def _read_upto(self, buffer, list, timeout):
        count = self._scan_for(list)
        if count != None:
            self._read_internal_buffer(buffer, count)
            return self._buf.pop(0)
        t_end = now() + timeout
        ms = int(timeout * 1000)
        while ms >= 0:
            poll_list = self.poll.poll(ms)
            for (file_descriptor, event) in poll_list:
                if event == select.POLLIN:
                    self._write_internal_buffer(self._read_port())
                    count = self._scan_for(list)
                    if count != None:
                        self._read_internal_buffer(buffer, count)
                        return self._buf.pop(0)
                    else:
                        self._read_internal_buffer(buffer, len(self._buf))
            ms = int((t_end - now()) * 1000)
        raise ETimeout

    def _read_including(self, buffer, list, timeout):
        item = self._read_upto(buffer, list, timeout)
        buffer.fromstring(item)
        return item

    ##
    # Read up to <code>count</code> bytes of data from buffered data.
    #
    # @param buffer  The buffer to fill.
    # @param count  The number of bytes to try and put in the buffer.
    # @return Number of bytes read.
    #
    def _read_internal_buffer(self, buffer, count):
        start_count = len(buffer)
        if len(self._buf) <= count:
            buffer.fromstring(self._buf.tostring())
            del (self._buf[:])
        else:
            buffer.fromstring(self._buf.tostring()[:count])
            del (self._buf[:count])
        return len(buffer) - start_count

    ##
    # Add data to buffer.
    #
    # @param data  String or array containing data to
    #              write to buffer.
    #
    def _write_internal_buffer(self, data):
        if type(data) == types.StringType:
            self._buf.fromstring(data)
        else:
            self._buf.extend(data)

    def _write(self, buffer):
        buffer.tofile(self.file)

    ##
    # Scans internal buffer for the occurence on any
    # character in <code>list</code>
    #
    # @param list  List containing characters that we
    #              are looking for.
    #
    def _scan_for(self, list):
        index = None
        for item in list:
            if item in self._buf:
                new_index = self._buf.index(item)
                if index == None or new_index < index:
                    index = new_index
        return index

    ##
    # Check to see if the port is open.
    #
    # @return True if the port is currently open.
    # @see open()
    #
    def is_open(self):
        return self.file != None

    def _set_serial(self):
        iflags = 0
        oflags = 0
        cflags = CREAD | CLOCAL | HUPCL
        lflags = 0

        # Set baud:
        if (self.baud !=
                -1):  # if custom baud is NOT specified, proceed normally:
            try:
                baud = getattr(termios, 'B%u' % self.baud)
            except AttributeError:
                raise EInvalidValue, ('baud', self.baud)
        else:  # else, set up for given custom baud:
            baud = getattr(termios, 'B38400')  # to be used in flags[] below...
            try:
                set_custom_baud(self.file.fileno(), self.custom_baud)
            except ValueError:  # all other exceptions are passed to caller
                print 'Bad custom baud rate: %u' % self.custom_baud
                msglog.log(
                    'Port', msglog.types.ERR,
                    'Bad custom baud rate submitted: %u.' % self.custom_baud)

        # Set bits.
        if self.bits < 5 or self.bits > 8:
            raise EInvalidValue, ('bits', self.bits)
        cflags = cflags | getattr(termios, 'CS%u' % self.bits)

        # Set parity.
        if self.parity:
            iflags = iflags | INPCK
            cflags = cflags | PARENB
            if self.parity == 1:
                cflags = cflags | PARODD
            elif self.parity != 2:
                raise EInvalidValue, ('parity', self.parity)
        else:
            iflags = iflags | IGNPAR

        # Set flow control options.
        if self.flow_control:
            if self.flow_control & 0x1:
                cflags |= CRTSCTS  # Enable hardware flow control.
            if self.flow_control & 0x2:
                iflags |= IXON  # Enable software flow control.
                iflags |= IXOFF

        # Set stop bits.
        if self.stop_bits == 2:
            cflags |= CSTOPB
        elif not self.stop_bits == 1:
            raise EInvalidValue, ('stop_bits', self.stop_bits)

        # Configure port according to the configuration.
        flags = tcgetattr(self.file.fileno())
        self.old_flags = flags[:]  # make full copy of list
        flags[0] = iflags
        flags[1] = oflags
        flags[2] = cflags
        flags[3] = lflags
        flags[4] = baud
        flags[5] = baud
        flags[6][VINTR] = self.VINTR
        flags[6][VQUIT] = self.VQUIT
        flags[6][VERASE] = self.VERASE
        flags[6][VKILL] = self.VKILL
        flags[6][VEOF] = self.VEOF
        flags[6][VMIN] = VMIN
        flags[6][VTIME] = VTIME
        flags[6][VSWTC] = self.VSWTC
        flags[6][VSTART] = self.VSTART
        flags[6][VSTOP] = self.VSTOP
        flags[6][VSUSP] = self.VSUSP
        flags[6][VEOL] = self.VEOL
        flags[6][VREPRINT] = self.VREPRINT
        flags[6][VDISCARD] = self.VDISCARD
        flags[6][VWERASE] = self.VWERASE
        flags[6][VLNEXT] = self.VLNEXT
        flags[6][VEOL2] = self.VEOL2
        flags[6][17] = self.cc17
        flags[6][18] = self.cc18
        flags[6][19] = self.cc19
        flags[6][20] = self.cc20
        flags[6][21] = self.cc21
        flags[6][22] = self.cc22
        flags[6][23] = self.cc23
        flags[6][24] = self.cc24
        flags[6][25] = self.cc25
        flags[6][26] = self.cc26
        flags[6][27] = self.cc27
        flags[6][28] = self.cc28
        flags[6][29] = self.cc29
        flags[6][30] = self.cc30
        flags[6][31] = self.cc31
        tcsetattr(self.file.fileno(), TCSANOW, flags)

        # We rely on non-blocking I/O by default, but BACnet data link drivers
        # (Ethernet, IP, and RS485-based MS/TP) rely on blocking ops:
        if (self._blocking == 0):
            flags = fcntl.fcntl(self.file.fileno(), fcntl.F_GETFL)
            fcntl.fcntl(self.file.fileno(), fcntl.F_SETFL,
                        flags | os.O_NONBLOCK)
        return

    ##
    # @todo Add retries and a timeout instead of one shot and fail.
    def _create_lock_file(self, retries=0, retry_rate=1.0):
        self._devlock.acquire(retries, retry_rate)

    def _delete_lock_file(self):
        try:
            self._devlock.release()
        except:
            msglog.exception()

    ##
    # Open the port for read/write access, using the configured attributes.
    # @see #configure()
    #
    # @todo Add a timeout to the open for acquiring the lock.
    def open(self, blocking=0):
        if self.debug:
            print 'Port.open().'
        if self.is_open():
            raise EAlreadyOpen
        self._create_lock_file()
        self._blocking = blocking  # save user's desired blocking mode

        # A file object is used as a convenience, especially for array's tofile
        # and fromfile methods.  A buffer size of 0 should disable all C FILE
        # buffering.
        self.file = open(self.dev, 'w+b', 0)
        self.poll = select.poll()
        self.poll.register(self.file, select.POLLIN)
        self._set_serial()

    ##
    # Close the port, disallowing read/write access.
    #
    def close(self):
        if self.debug:
            print 'Port.close.'
        if not self.is_open():
            raise ENotOpen
        if hasattr(self, 'old_flags'):
            tcsetattr(self.file.fileno(), TCSANOW, self.old_flags)
        self._delete_lock_file()
        self.file.close()
        self.file = None
        self.poll = None
        # Ensure that internal buffer is empty.
        self._buf = array.array('c')

    ##
    # Ensure that all queued ouput bytes are transmitted.
    #
    def flush(self):
        if self.debug:
            print 'Port.flush().'
        self.file.flush()

    ##
    # Send a break on the port.
    #
    # @param duration The period, in seconds, to send the break.
    #                 The duration is rounded up to the next 0.25 second
    #                 quantum.  The actual duration may be up to twice as
    #                 as long as requested.
    # @default 0.25
    #
    def sendbreak(self, duration=0):
        duration = int((duration * 4) + 0.999)
        self._internal_lock.acquire()
        try:
            tcsendbreak(self.file.fileno(), duration)
        finally:
            self._internal_lock.release()

    ##
    # Disregard all queued input bytes.
    #
    # @return buffer containing disregarded queued bytes.
    #
    def drain(self):
        buffer = self._safe_apply(self._drain)
        if self.debug:
            self.dump(buffer, 'D< ')
        return buffer

    ##
    # Poll port for input.
    #
    # @param timeout_ms Number of miliseconds to wait
    #                   for input before timing out.
    # @return 1 If input is available, 0 if action
    #           timedout.
    #
    def poll_input(self, timeout_ms=None):
        if self.debug:
            print 'Port.poll_input(%s).' % str(timeout_ms)
        if len(self._buf):
            return 1
        result = self.poll.poll(timeout_ms)
        for fd, event in result:
            if event == select.POLLIN:
                return 1
        return 0

    ##
    # Write a string or array to the serial port.
    #
    # @param buffer  The string or array to transmit.
    #
    def write(self, buffer):
        if not type(buffer) is array.ArrayType:
            # This is a convenience that may be removed.
            buffer = array.array('c', buffer)
        if self.debug:
            print 'Port.write(self, buffer):'
            self.dump(buffer, ' > ')
        return self._safe_apply(self._write, buffer)

    ##
    # Read up to count bytes from the serial port.
    #
    # @param buffer The target array object into which bytes are appended.
    # @param count The maximum number of bytes to read.
    # @param timeout The maximun number of seconds to wait for count bytes.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read(self, buffer, count, timeout):
        if self.debug:
            print 'Port.read(self, buffer, %d, %f):' % (count, timeout)
            offset = len(buffer)
        result = self._safe_apply(self._read, buffer, count, timeout)
        if self.debug:
            self.dump(buffer, ' < ', offset)
        return result

    ##
    # Read all bytes from the serial port upto, but excluding, any characters
    # in the list.  Characters in the list are discarded.
    # @param buffer The target array object into which bytes are appended.
    # @param list The list of characters that terminate the read.
    # @param timeout The maximun number of seconds to wait to read up to a
    #                termination character.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read_upto(self, buffer, list, timeout):
        if self.debug:
            offset = len(buffer)
        c = self._safe_apply(self._read_upto, buffer, list, timeout)
        if self.debug:
            self.dump(buffer, ' < ', offset)
            self.dump(c, 'X< ')
        return c

    ##
    # Read all bytes from the serial port upto, and including, any characters
    # in the list.
    # @param buffer The target array object into which bytes are appended.
    # @param list The list of characters that terminate the read.
    # @param timeout The maximun number of seconds to wait to read up to a
    #                termination character.
    #                Floats are accepted to support non-integer values.
    # @throws ?
    # @return The bytes read.
    #
    def read_including(self, buffer, list, timeout):
        if self.debug:
            offset = len(buffer)
        c = self._safe_apply(self._read_including, buffer, list, timeout)
        if self.debug:
            self.dump(buffer, ' < ', offset)
        return c