class SP(object): """A Calvin serialport object""" def __init__(self, devicename, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, trigger, actor_id): self._trigger = trigger self._actor_id = actor_id self._port = SerialPort(RawProtocol(self), devicename, reactor, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts) def trigger(self): self._trigger(actor_ids=[self._actor_id]) def write(self, data): fdesc.writeToFD(self._port.fileno(), data) def read(self): data = self._port.protocol.data self._port.protocol.data = b"" return data def hasData(self): return len(self._port.protocol.data) def close(self): self._port.loseConnection()
class SP(object): """A Calvin serialport object""" def __init__(self, devicename, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, trigger, actor_id): self._trigger = trigger self._actor_id = actor_id self._port = SerialPort( RawProtocol(self), devicename, reactor, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts) def trigger(self): self._trigger(actor_ids=[self._actor_id]) def write(self, data): fdesc.writeToFD(self._port.fileno(), data) def read(self): data = self._port.protocol.data self._port.protocol.data = b"" return data def hasData(self): return len(self._port.protocol.data) def close(self): self._port.loseConnection()
class SerialTransport(DeviceTransport): #: Default config config = Instance(SerialConfig, ()) #: Connection port connection = Instance(SerialPort) #: Whether a serial connection spools depends on the device (configuration) always_spools = set_default(False) #: Wrapper _protocol = Instance(InkcutProtocol) def connect(self): try: config = self.config #: Save a reference self.protocol.transport = self #: Make the wrapper self._protocol = InkcutProtocol(self, self.protocol) self.connection = SerialPort( self._protocol, config.port, reactor, baudrate=config.baudrate, bytesize=config.bytesize, parity=SERIAL_PARITIES[config.parity], stopbits=config.stopbits, xonxoff=config.xonxoff, rtscts=config.rtscts ) log.debug("{} | opened".format(self.config.port)) except Exception as e: #: Make sure to log any issues as these tracebacks can get #: squashed by twisted log.error("{} | {}".format( self.config.port, traceback.format_exc() )) raise def write(self, data): if not self.connection: raise IOError("Port is not opened") log.debug("-> {} | {}".format(self.config.port, data)) if hasattr(data, 'encode'): data = data.encode() self._protocol.transport.write(data) def disconnect(self): if self.connection: log.debug("{} | closed".format(self.config.port)) self.connection.loseConnection() self.connection = None def __repr__(self): return self.config.port
class IoService(service.Service): name = "ioservice" def __init__(self, port, speed=115200): self.port = port self.speed = speed def startService(self): log.msg(system='IoService', format="service starting") self.protocol = IoProtocol(self) log.msg(system='IoService', format="opening serial port %(port)s", port=self.port) self.serial = SerialPort(self.protocol, self.port, reactor, baudrate=self.speed) self.lcd = Lcd(self) self.sonar = Sonar(self) self.protocol.register_callback(0x01, self.onHandshake) self.protocol.register_callback(0xee, self.onIoError) service.Service.startService(self) def stopService(self): log.msg(system='IoService', format="service stopping") self.serial.loseConnection() service.Service.stopService(self) def onHandshake(self, data): log.msg(system='IoService', format="Controller is alive") self.parent.triggerEvent('IO_HANDSHAKE') def onIoError(self, data): log.msg(system='IoService', format="Controller error, code %(code)#x", code=data[0]) self.parent.triggerEvent('IO_ERROR', data[0])
class ReconnectingSerialPort(Reconnector): """ A class for serial port connection that will retry the connection if the connection fails """ def __init__(self, reactor, protocol, port, *args, **kwargs): Reconnector.__init__(self, reactor) self.protocol = protocol self.port = port self.args = args self.kwargs = kwargs self.serialport = None def connect(self): try: self.serialport = SerialPort(self.protocol, self.port, self.reactor, *self.args, **self.kwargs) self.resetDelay() except SerialException: self.connectionFailed(Failure()) def connectionLost(self, reason): self.serialport = None self.retry() def connectionFailed(self, reason): self.serialport = None self.retry() def loseConnection(self, *args, **kwargs): ''' Lose connection to the serial port ''' self.stopTrying() if self.serialport: self.serialport.loseConnection(*args, **kwargs)
class KetiDriver(SmapDriver): CHANNELS = [('temperature', 'C', 'double'), ('humidity', '%RH', 'double'), ('light', 'lx', 'long'), ('pir', '#', 'long'), ('co2', 'ppm', 'long')] def setup(self, opts): self.port = opts.get('SerialPort', '/dev/ttyrd00') self.baud = int(opts.get('BaudRate', 115200)) self.namespace = opts.get('Namespace', None) if self.namespace: self.namespace = uuid.UUID(self.namespace) def uuid(self, serial_id, channel): # create consistent uuids for data based on the serial id key = serial_id + '-' + channel if self.namespace: return uuid.uuid5(self.namespace, key) else: return SmapDriver.uuid(self, key) def start(self): self.serial = SerialPort(KetiMoteReceiver(self), self.port, reactor, baudrate=self.baud) def stop(self): return self.serial.loseConnection() def create_channels(self, msg): if not self.get_collection('/' + str(msg['node_id'])): for (name, unit, dtype) in self.CHANNELS: self.add_timeseries('/' + str(msg['node_id']) + '/' + name, self.uuid(msg['serial_id'], name), unit, data_type=dtype) self.set_metadata( '/' + str(msg['node_id']), { 'Metadata/Instrument/PartNumber': str(msg['node_id']), 'Metadata/Instrument/SerialNumber': ':'.join(map(lambda x: hex(ord(x))[2:], msg['serial_id'])) }) def dataReceived(self, msg): self.create_channels(msg) for (name, _, __) in self.CHANNELS: if name in msg: self._add('/' + str(msg['node_id']) + '/' + name, msg[name])
class KetiDriver(SmapDriver): CHANNELS = [('temperature', 'C', 'double'), ('humidity', '%RH', 'double'), ('light', 'lx', 'long'), ('pir', '#', 'long'), ('co2', 'ppm', 'long')] def setup(self, opts): self.port = opts.get('SerialPort', '/dev/ttyrd00') self.baud = int(opts.get('BaudRate', 115200)) self.namespace = opts.get('Namespace', None) if self.namespace: self.namespace = uuid.UUID(self.namespace) def uuid(self, serial_id, channel): # create consistent uuids for data based on the serial id key = serial_id + '-' + channel if self.namespace: return uuid.uuid5(self.namespace, key) else: return SmapDriver.uuid(self, key) def start(self): self.serial = SerialPort(KetiMoteReceiver(self), self.port, reactor, baudrate=self.baud) def stop(self): return self.serial.loseConnection() def create_channels(self, msg): if not self.get_collection('/' + str(msg['node_id'])): for (name, unit, dtype) in self.CHANNELS: self.add_timeseries('/' + str(msg['node_id']) + '/' + name, self.uuid(msg['serial_id'], name), unit, data_type=dtype) self.set_metadata('/' + str(msg['node_id']), { 'Metadata/Instrument/PartNumber': str(msg['node_id']), 'Metadata/Instrument/SerialNumber': ':'.join(map(lambda x: hex(ord(x))[2:], msg['serial_id'])) }) def dataReceived(self, msg): self.create_channels(msg) for (name, _, __) in self.CHANNELS: if name in msg: self._add('/' + str(msg['node_id']) + '/' + name, msg[name])
class InstProtocol2200087(Protocol): """ This is a twisted protocol which handles serial communications with 2200087 multimeters. This protocol exists and operates within the context of a twisted reactor. Applications themselves built on twisted should be able to simply import this protocol (or its factory). If you would like the protocol to produce datapoints in a different format, this protocol should be sub-classed in order to do so. The changes necessary would likely begin in this class's frame_recieved() function. Synchronous / non-twisted applications should use the InstInterface2200087 class instead. The InstInterface2200087 class accepts a parameter to specify which protocol factory to use, in case you intend to subclass this protocol. :param port: Port on which the device is connected. Default '/dev/ttyUSB0'. :type port: str :param buffer_size: Length of the point buffer in the protocol. Default 100. :type buffer_size: int """ def __init__(self, port, buffer_size=100): self._buffer = "" self._frame_size = 14 self._point_buffer_size = buffer_size self.point_buffer = None self.reset_buffer() self._serial_port = port self._serial_transport = None self._frame_processor = process_chunk def reset_buffer(self): """ Resets the point buffer. Any data presently within it will be lost. """ self.point_buffer = deque(maxlen=self._point_buffer_size) def make_serial_connection(self): """ Creates the serial connection to the port specified by the instance's _serial_port variable and sets the instance's _serial_transport variable to the twisted.internet.serialport.SerialPort instance. """ self._serial_transport = SerialPort(self, self._serial_port, reactor, baudrate=2400, bytesize=8, parity='N', stopbits=1, timeout=5, xonxoff=0, rtscts=0) def break_serial_connection(self): """ Calls loseConnection() on the instance's _serial_transport object. """ self._serial_transport.loseConnection() def connectionMade(self): """ This function is called by twisted when a connection to the serial transport is successfully opened. """ pass def connectionLost(self, reason=connectionDone): """ This function is called by twisted when the connection to the serial transport is lost. """ print "Lost Connection to Device" print reason def dataReceived(self, data): """ This function is called by twisted when new bytes are received by the serial transport. This data is appended to the protocol's framing buffer, _buffer, and when the length of the buffer is longer than the frame size, that many bytes are pulled out of the start of the buffer and frame_recieved is called with the frame. This function also performs the initial frame synchronization by dumping any bytes in the beginning of the buffer which aren't the first byte of the frame. In its steady state, the protocol framing buffer will always have the beginning of a frame as the first element. :param data: The data bytes received :type data: str """ self._buffer += data while len(self._buffer) and self._buffer[0].encode('hex')[0] != '1': self._buffer = self._buffer[1:] while len(self._buffer) >= self._frame_size: self.frame_received(self._buffer[0:self._frame_size]) self._buffer = self._buffer[self._frame_size:] def frame_received(self, frame): """ This function is called by data_received when a full frame is received by the serial transport and the protocol. This function recasts the frame into the format used by the serialDecoder and then uses that module to process the frame into the final string. This string is then appended to the protocol's point buffer. This string is treated as a fully processed datapoint for the purposes of this module. :param frame: The full frame representing a single data point :type frame: str """ frame = [byte.encode('hex') for byte in frame] chunk = ' '.join(frame) point = self._frame_processor(chunk) self.point_buffer.append(point) def latest_point(self, flush=True): """ This function can be called to obtain the latest data point from the protocol's point buffer. The intended use of this function is to allow random reads from the DMM. Such a typical application will want to discard all the older data points (including the one returned), which it can do with flush=True. This function should only be called when there is data already in the protocol buffer, which can be determined using data_available(). This is a twisted protocol function, and should not be called directly by synchronous / non-twisted code. Instead, its counterpart in the InstInterface object should be used. :param flush: Whether to flush all the older data points. :type flush: bool :return: Latest Data Point as processed by the serialDecoder :rtype: str """ rval = self.point_buffer[-1] if flush is True: self.point_buffer.clear() return rval def next_point(self): """ This function can be called to obtain the next data point from the protocol's point buffer. The intended use of this function is to allow continuous streaming reads from the DMM. Such a typical application will want to pop the element from the left of the point buffer, which is what this function does. This function should only be called when there is data already in the protocol buffer, which can be determined using data_available(). This is a twisted protocol function, and should not be called directly by synchronous / non-twisted code. Instead, its counterpart in the InstInterface object should be used. :return: Next Data Point in the point buffer as processed by the serialDecoder :rtype: str """ return self.point_buffer.popleft() def next_chunk(self): """ This function can be called to obtain a copy of the protocol's point buffer with all but the latest point in protocol's point buffer. The intended use of this function is to allow continuous streaming reads from the DMM. Such a typical application will want to pop the elements from the left of the point buffer, which is what this function does. This function should only be called when there is data already in the protocol buffer, which can be determined using data_available(). This is a twisted protocol function, and should not be called directly by synchronous / non-twisted code. Instead, its counterpart in the InstInterface object should be used. :return: Copy of point_buffer with all but the latest_point :rtype: deque """ rval = self.point_buffer self.point_buffer = deque([rval.pop()], maxlen=self._point_buffer_size) return rval def data_available(self): """ This function can be called to read the number of data points waiting in the protocol's point buffer. This is a twisted protocol function, and should not be called directly by synchronous / non-twisted code. Instead, its counterpart in the InstInterface object should be used. :return: Number of points waiting in the protocol's point buffer :rtype: int """ return len(self.point_buffer)
class TrafficLightSerial(basic.LineReceiver, TrafficLight): delimiter = '\n'.encode('ascii') config_map = { "min_on_current": 0, "max_on_current": 1, "max_off_current": 2 } rx_timeout = 3 baud = 19200 @classmethod def open(cls, name, port, reset_pin=None, reactor=reactor): local_light = cls() local_light.setLogger(logging.getLogger(name)) serial = SerialPort(baudrate=cls.baud, deviceNameOrPortNumber=port, protocol=local_light, reactor=reactor) local_light.setSerial(serial) local_light.setPort(port) local_light.setReset(reset_pin) local_light.reactor = reactor local_light.sendUpdate() #reactor.call_later(cls.rx_timeout, local_light.timed_out) return local_light def reopen(self): ''' Tries to reestablish a serial link in case of HW issues ''' print(dir(self.serial)) self.serial = SerialPort(baudrate=self.baud, deviceNameOrPortNumber=self.port, protocol=self, reactor=self.reactor) def lineLengthExceeded(self, line): logging.error("Line length exceeded/codeDecodeErrorbaud rate error?") print(self.serial) self.serial.loseConnection() self.serial.stopReading() self.reactor.callLater(1, self.reopen) #self.reopen() def setPort(self, port): self.port = port def setSerial(self, serial): self.serial = serial def setReset(self, pin): if pin is None: self.reset_name = None return self.reset_name = "/sys/class/gpio/gpio{}/value".format(pin) try: with open("/sys/class/gpio/export", "w") as f: f.write("{}\n".format(pin)) except Exception as e: logging.error( "Could not open/write exports in gpiofs: {}".format(e)) def lineReceived(self, line): # Ignore blank lines if not line: return try: line = line.decode("ascii").strip() (self.state, self.batt_voltage, self.error_state, self.lamp_currents[0], self.lamp_currents[1], self.lamp_currents[2]) = line.split(" ") self.last_seen = time() except (ValueError, UnicodeDecodeError): logging.info("Received garbled line") # logging.warning("update myself: {}".format(self)) def setConfig(self, param, value): if param in self.config_map: cmd = "s{}={}".format(self.config_map[param], int(value)) self.sendLine(cmd) else: raise ValueError("Unknown parameter {}".format(param)) def sendUpdate(self): if self.give_way: self.sendLine("G".encode("ascii")) else: self.sendLine("g".encode("ascii")) if self.temp_error: self.sendLine("E".encode("ascii")) else: self.sendLine("e".encode("ascii")) def serviceWatchdog(self): self.sendUpdate()
class SerialNodesController(object): def __init__(self, oNodeControl): self._NodeControl = oNodeControl # list all serial ports: python -m serial.tools.list_ports self._ResetState = 10 self._connectSerialPort() def _connectSerialPort(self): self.Close() myProtocol = SerialNodesProtocol(self._NodeControl, OnReceive=self.OnMsgReceive) self._serialPort = SerialPort(myProtocol, self._NodeControl.nodeProps.get( 'serialnodes', 'serialport'), reactor, baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE) self._NodeControl.log.debug( 'Serial port: %s open.' % self._NodeControl.nodeProps.get('serialnodes', 'serialport')) sleep(1) def OnMsgReceive(self, RecMsg): myNodeID = '999' # self._NodeControl.nodeProps.get('system', 'nodeId') if str(RecMsg.ToAdress) == myNodeID: # for this node if ((int(RecMsg.Function) == 1) and (int(RecMsg.MsgValue) == 1)): self._NodeControl.log.debug("Deuropen signal (keypad)") self.doOpenDoor() self._NodeControl.MQTTPublish(sTopic="deur/keyopen", sValue="ON", iQOS=0, bRetain=False) reactor.callLater(self._ResetState, self.ResetState, topic="deur/keyopen") elif ((int(RecMsg.Function) == 2) and (int(RecMsg.MsgValue) == 1)): self._NodeControl.log.debug("Deuropen signal (RFID)") self.doOpenDoor() self._NodeControl.MQTTPublish(sTopic="deur/rfidopen", sValue="ON", iQOS=0, bRetain=False) reactor.callLater(self._ResetState, self.ResetState, topic="deur/rfidopen") elif ((int(RecMsg.Function) == 3) and (int(RecMsg.MsgValue) == 1)): self._NodeControl.log.debug("Deurbel signal.") self._NodeControl.MQTTPublish(sTopic="deur/ring", sValue="ON", iQOS=0, bRetain=False) reactor.callLater(self._ResetState, self.ResetState, topic="deur/ring") elif ((int(RecMsg.Function) == 4) and (int(RecMsg.MsgValue) == 1)): self._NodeControl.log.debug("Tampering Keypad") self._NodeControl.MQTTPublish(sTopic="tampering/keypad", sValue="ON", iQOS=0, bRetain=False) elif ((int(RecMsg.Function) == 4) and (int(RecMsg.MsgValue) == 0)): reactor.callLater(self._ResetState, self.ResetState, topic="tampering/keypad") def ResetState(self, topic): self._NodeControl.MQTTPublish(sTopic=topic, sValue="OFF", iQOS=0, bRetain=False) def SendMessage(self, sSerialMessage): try: self._serialPort.write(sSerialMessage) sleep(0.1) return True except Exception as exp: print(traceback.format_exc()) self._NodeControl.log.error("SendMessage error: %s." % traceback.format_exc()) return False def Close(self): try: self._serialPort.loseConnection() self._serialPort = None except: pass def doOpenDoor(self): if self._NodeControl.nodeProps.has_option('dooropener', 'active') and\ self._NodeControl.nodeProps.getboolean('dooropener', 'active'): idoorUnlockTime = 5 if self._NodeControl.nodeProps.has_option('dooropener', 'unlockTime'): idoorUnlockTime = float( self._NodeControl.nodeProps.get('dooropener', 'unlockTime')) self._NodeControl.log.debug("Door opening") wiringpi.digitalWrite(17, 1) self._NodeControl.log.debug("Door opening done") reactor.callLater(idoorUnlockTime, self.releaseDoorLockEvent) def releaseDoorLockEvent(self): self._NodeControl.log.debug("releaseDoorLockEvent") wiringpi.digitalWrite(17, 0) self._NodeControl.log.debug("Deur GPIO 17 off")
class DriveService(service.Service): name = "driveservice" def __init__(self, port, speed=115200): self.port = port self.speed = speed self.cal_x_cur = 0 self.cal_y_cur = 0 self.cal_x_eeprom = 0 self.cal_y_eeprom = 0 def startService(self): log.msg(system='DriveService', format="service starting") self.protocol = DriveProtocol(self) log.msg(system='DriveService', format="opening serial port %(port)s", port=self.port) self.serial = SerialPort(self.protocol, self.port, reactor, baudrate=self.speed) self.protocol.register_callback(0x01, self._onHandshake) self.protocol.register_callback(0x41, self._onReceiveCalibration) self.protocol.register_callback(0x42, self._onReceiveStatus) self.protocol.register_callback(0xee, self._onDriveError) service.Service.startService(self) def stopService(self): log.msg(system='DriveService', format="service stopping") self.serial.loseConnection() service.Service.stopService(self) def setMode(self, mode): self.protocol.cmd_mode(mode) def directJoystick(self, xpos, ypos): self.protocol.cmd_joystick(xpos, ypos) def setCalibration(self, xvalue, yvalue): self.protocol.cmd_calibrate_set(xvalue, yvalue) def storeCalibration(self): self.protocol.cmd_calibrate_store() def driveSelect(self, enable): self.protocol.cmd_driveselect(enable) def stop(self): self.protocol.cmd_joystick(0, 0) def estop(self): self.protocol.cmd_estop() def reset(self): self.protocol.cmd_reset() def getCalibration(self): self.protocol.req_calibration() self.calibration_d = defer.Deferred() return self.calibration_d def _onHandshake(self, data): log.msg(system='DriveService', format="Controller is alive") self.parent.triggerEvent('DRV_HANDSHAKE') def _onDriveError(self, data): log.msg(system='DriveService', format="Controller error, code %(code)#x", code=data[0]) self.parent.triggerEvent('DRV_ERROR', data[0]) def _onReceiveStatus(self, data): status = data[0]; xpos = data[1]; ypos = data[2]; xval = data[3]; yval = data[4]; self.parent.triggerEvent('DRV_STATUS', status, xpos, ypos, xval, yval) def _onReceiveCalibration(self, data): log.msg(system='DriveService', format="receivied calibration values") calibration = {} calibration['current_x'] = data[0] calibration['current_y'] = data[1] calibration['eeprom_x'] = data[2] calibration['eeprom_y'] = data[3] self.cal_x_cur = data[0] self.cal_y_cur = data[1] self.cal_x_eeprom = data[2] self.cal_y_eeprom = data[3] if self.calibration_d is not None: self.calibration_d.callback(calibration) self.calibration_d = None