Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
Archivo: base.py Proyecto: D3f0/txscada
    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()
Ejemplo n.º 3
0
Archivo: base.py Proyecto: D3f0/txscada
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)
Ejemplo n.º 4
0
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)