def setUp(self): """Initialization""" self.state = self.States.STARTED # Sequence s = self.comaster.sequence if s < sequence.MIN.value or s > sequence.MAX.value: s = sequence.MIN.value self.input_buffer = MaraFrameReassembler()
class MaraClientProtocol(object, protocol.Protocol, TimeoutMixin): # Inherits from object the property new syntax class States(Names): STARTED = NamedConstant() CHECK_NEED_PEH = NamedConstant() SEND_PEH = NamedConstant() SEND_POLL = NamedConstant() WAITING_REPLY = NamedConstant() # Workis with deferred incomingDefered USER_COMMAND = NamedConstant() GAVE_UP = NamedConstant() CONNECTION_LOST = NamedConstant() incomingDefered = None _state = None @property def state(self): return self._state @state.setter def state(self, new_state): assert new_state in self.States.iterconstants( ), "Invalid state %s" % new_state # self.logger.info("State change %s -> %s", self._state, new_state) self._state = new_state def sendCotainer(self, container): """ Convenience method for publishing when data is sent """ # TODO: Publish COMASTER, STATE, DATA assert isinstance(container, Container) data = self.construct.build(container) self.logger.info("%s >> %s", self.state, upperhexstr(data)) self.transport.write(data) @property def comaster(self): """ Shortcut to comaster instance """ return self.factory.comaster def setUp(self): """Initialization""" self.state = self.States.STARTED # Sequence s = self.comaster.sequence if s < sequence.MIN.value or s > sequence.MAX.value: s = sequence.MIN.value self.input_buffer = MaraFrameReassembler() @property def active(self): """Flag that checks if the main loop can be executed""" return self.state not in (self.States.CONNECTION_LOST, self.States.GAVE_UP) @defer.inlineCallbacks def mainLoop(self): """ Main loop that executes the comunication. It tries to interleave every resposability the reactor has. """ while self.active: yield self.doPEH() replied = yield self.doPoll() if not replied: continue # Still online? whatNext = yield self.waitForNextPollOrUserCommands() self.transport.loseConnection() def waitForNextPollOrUserCommands(self): """ Created a defered that will be callbacked form somewhere else indicating what shuold be done. """ self.waitingDefered = defer.Deferred() reactor.callLater(self.comaster.poll_interval, self.waitingDefered.callback, None) return self.waitingDefered def connectionMade(self): """ Called by twsited when the connection is made. The main loop is not implemented here for clarity reasons and testabilty using the reactor. Calls setup. """ self.setUp() reactor.callLater(0, self.mainLoop) def buildPollContainer(self): """ Creates a mara container using information of the comaster reference. """ return Container( source=self.comaster.rs485_source, dest=self.comaster.rs485_destination, sequence=self.comaster.sequence, command=commands.POLL.value, payload_10=None, # No payload, ) def buildPeHContainer(self, timestamp): """ Creates a PEH container. """ container = Container( source=self.comaster.rs485_source, dest=0xFF, sequence=0xBB, command=commands.PEH.value, peh=timestamp) return container def pepreareToReceive(self): """ Check if the connection is able to recieve data, if not ConnectionLost is risen. Created """ if self.state == self.States.CONNECTION_LOST: raise ConnectionLost() self.input_buffer.reset() self.state = self.States.WAITING_REPLY # Incoming defered will not be completed until a FULL package is received # or timeout occurs (returning None) self.incomingDefered = defer.Deferred() self.setTimeout(self.comaster.poll_interval) return self.incomingDefered @defer.inlineCallbacks def doPoll(self): """ Sends Poll commands and waits for reply. It supports data to be chunked. It times out. :return bool: True if data could be retrieved from device, False otherwise. """ self.state = self.States.SEND_POLL tries, max_tries = 0, self.comaster.max_retry_before_offline while tries <= max_tries: try: self.pepreareToReceive() except ConnectionLost: self.setTimeout(None) defer.returnValue(False) # If it's not the first try, log it if tries: self.logger.debug("Retry: %s", tries) self.sendCotainer(self.buildPollContainer()) try: _str, package = yield self.incomingDefered self.setTimeout(None) try: yield threads.deferToThread(self.packageReceived, package) self.logger.info("Saved, next poll SEQ: %s", i2hex(self.comaster.sequence)) except Exception: self.logger.exception( "Package may be lost por partially saved:") defer.returnValue(True) # Return True so sleep is performed except FieldError, e: self.logger.warning("Construct error: %s", e) except Timeout: tries += 1 if tries > max_tries: self.state = self.States.GAVE_UP self.logger.critical( "Giving up POLL response. Retry exceeded!") defer.returnValue(False) except ConnectionLost: # Connection lost is set in handler since it's use is more general # self.state = self.States.CONNECTION_LOST defer.returnValue(False)
class MaraClientProtocol(object, protocol.Protocol, TimeoutMixin): # Inherits from object the property new syntax class States(Names): STARTED = NamedConstant() CHECK_NEED_PEH = NamedConstant() SEND_PEH = NamedConstant() SEND_POLL = NamedConstant() WAITING_REPLY = NamedConstant() # Workis with deferred incomingDefered USER_COMMAND = NamedConstant() GAVE_UP = NamedConstant() CONNECTION_LOST = NamedConstant() incomingDefered = None _state = None @property def state(self): return self._state @state.setter def state(self, new_state): assert new_state in self.States.iterconstants(), "Invalid state %s" % new_state # self.logger.info("State change %s -> %s", self._state, new_state) self._state = new_state def sendCotainer(self, container): """ Convenience method for publishing when data is sent """ # TODO: Publish COMASTER, STATE, DATA assert isinstance(container, Container) data = self.construct.build(container) self.logger.info("%s >> %s", self.state, upperhexstr(data)) self.transport.write(data) @property def comaster(self): """ Shortcut to comaster instance """ return self.factory.comaster def setUp(self): """Initialization""" self.state = self.States.STARTED # Sequence s = self.comaster.sequence if s < sequence.MIN.value or s > sequence.MAX.value: s = sequence.MIN.value self.input_buffer = MaraFrameReassembler() @property def active(self): """Flag that checks if the main loop can be executed""" return self.state not in (self.States.CONNECTION_LOST, self.States.GAVE_UP) @defer.inlineCallbacks def mainLoop(self): """ Main loop that executes the comunication. It tries to interleave every resposability the reactor has. """ while self.active: yield self.doPEH() replied = yield self.doPoll() if not replied: continue # Still online? whatNext = yield self.waitForNextPollOrUserCommands() self.transport.loseConnection() def waitForNextPollOrUserCommands(self): """ Created a defered that will be callbacked form somewhere else indicating what shuold be done. """ self.waitingDefered = defer.Deferred() reactor.callLater(self.comaster.poll_interval, self.waitingDefered.callback, None) return self.waitingDefered def connectionMade(self): """ Called by twsited when the connection is made. The main loop is not implemented here for clarity reasons and testabilty using the reactor. Calls setup. """ self.setUp() reactor.callLater(0, self.mainLoop) def buildPollContainer(self): """ Creates a mara container using information of the comaster reference. """ return Container( source=self.comaster.rs485_source, dest=self.comaster.rs485_destination, sequence=self.comaster.sequence, command=commands.POLL.value, payload_10=None, # No payload, ) def buildPeHContainer(self, timestamp): """ Creates a PEH container. """ container = Container( source=self.comaster.rs485_source, dest=0xFF, sequence=0xBB, command=commands.PEH.value, peh=timestamp ) return container def pepreareToReceive(self): """ Check if the connection is able to recieve data, if not ConnectionLost is risen. Created """ if self.state == self.States.CONNECTION_LOST: raise ConnectionLost() self.input_buffer.reset() self.state = self.States.WAITING_REPLY # Incoming defered will not be completed until a FULL package is received # or timeout occurs (returning None) self.incomingDefered = defer.Deferred() self.setTimeout(self.comaster.poll_interval) return self.incomingDefered @defer.inlineCallbacks def doPoll(self): """ Sends Poll commands and waits for reply. It supports data to be chunked. It times out. :return bool: True if data could be retrieved from device, False otherwise. """ self.state = self.States.SEND_POLL tries, max_tries = 0, self.comaster.max_retry_before_offline while tries <= max_tries: try: self.pepreareToReceive() except ConnectionLost: self.setTimeout(None) defer.returnValue(False) # If it's not the first try, log it if tries: self.logger.debug("Retry: %s", tries) self.sendCotainer(self.buildPollContainer()) try: _str, package = yield self.incomingDefered self.setTimeout(None) try: yield threads.deferToThread(self.packageReceived, package) self.logger.info("Saved, next poll SEQ: %s", i2hex(self.comaster.sequence)) except Exception: self.logger.exception("Package may be lost por partially saved:") defer.returnValue(True) # Return True so sleep is performed except FieldError, e: self.logger.warning("Construct error: %s", e) except Timeout: tries += 1 if tries > max_tries: self.state = self.States.GAVE_UP self.logger.critical("Giving up POLL response. Retry exceeded!") defer.returnValue(False) except ConnectionLost: # Connection lost is set in handler since it's use is more general # self.state = self.States.CONNECTION_LOST defer.returnValue(False)