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 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()
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
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
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