Example #1
0
    def __init__(self, address, port, active, session_id, name, event_handler=None, custom_connection_handler=None):
        EventProducer.__init__(self, event_handler)

        self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__)

        self.address = address
        self.port = port
        self.active = active
        self.sessionID = session_id
        self.name = name

        self.connected = False

        # repeating linktest variables
        self.linktestTimer = None
        self.linktestTimeout = 30

        # event and packet queues
        self.eventQueue = []
        self.packetQueue = []

        # hsms connection state fsm
        self.connectionState = Fysom({
            'initial': 'NOT_CONNECTED',
            'events': [
                {'name': 'connect', 'src': 'NOT_CONNECTED', 'dst': 'CONNECTED'},
                {'name': 'disconnect', 'src': ['CONNECTED', 'NOT_SELECTED', 'SELECTED'], 'dst': 'NOT_CONNECTED'},
                {'name': 'select', 'src': 'NOT_SELECTED', 'dst': 'SELECTED'},
                {'name': 'deselect', 'src': 'SELECTED', 'dst': 'NOT_SELECTED'},
                {'name': 'timeoutT7', 'src': 'NOT_SELECTED', 'dst': 'NOT_CONNECTED'},
            ],
            'callbacks': {
                'onNOT_SELECTED': self._on_state_connect,
                'onNOT_CONNECTED': self._on_state_disconnect,
                'onSELECTED': self._on_state_select,
            },
            'autoforward': [
                {'src': 'CONNECTED', 'dst': 'NOT_SELECTED'}
            ]
        })

        # setup connection
        if self.active:
            if custom_connection_handler is None:
                self.connection = HsmsActiveConnection(self.address, self.port, self.sessionID, self)
            else:
                self.connection = custom_connection_handler.create_connection(self.address, self.port, self.sessionID, self)
        else:
            if custom_connection_handler is None:
                self.connection = HsmsPassiveConnection(self.address, self.port, self.sessionID, self)
            else:
                self.connection = custom_connection_handler.create_connection(self.address, self.port, self.sessionID, self)
Example #2
0
class hsmsHandler(EventProducer):
    """Baseclass for creating Host/Equipment models. This layer contains the HSMS functionality. Inherit from this class and override required functions.

    :param address: IP address of remote host
    :type address: string
    :param port: TCP port of remote host
    :type port: integer
    :param active: Is the connection active (*True*) or passive (*False*)
    :type active: boolean
    :param session_id: session / device ID to use for connection
    :type session_id: integer
    :param name: Name of the underlying configuration
    :type name: string
    :param event_handler: object for event handling
    :type event_handler: :class:`secsgem.common.EventHandler`
    :param custom_connection_handler: object for connection handling (ie multi server)
    :type custom_connection_handler: :class:`secsgem.hsmsConnections.HsmsMultiPassiveServer`

    **Example**::

        import secsgem

        def S1F1Handler(connection, packet):
            print "S1F1 received"

        def onConnect(event, data):
            print "Connected"

        client = secsgem.hsmsHandler("10.211.55.33", 5000, True, 0, "test", eventHandler=secsgem.EventHandler(events={'RemoteConnected': onConnect}))
        client.registerCallback(1, 1, S1F1Handler)

        client.enable()

        time.sleep(3)

        client.disable()

    """
    def __init__(self, address, port, active, session_id, name, event_handler=None, custom_connection_handler=None):
        EventProducer.__init__(self, event_handler)

        self.logger = logging.getLogger(self.__module__ + "." + self.__class__.__name__)

        self.address = address
        self.port = port
        self.active = active
        self.sessionID = session_id
        self.name = name

        self.connected = False

        # repeating linktest variables
        self.linktestTimer = None
        self.linktestTimeout = 30

        # event and packet queues
        self.eventQueue = []
        self.packetQueue = []

        # hsms connection state fsm
        self.connectionState = Fysom({
            'initial': 'NOT_CONNECTED',
            'events': [
                {'name': 'connect', 'src': 'NOT_CONNECTED', 'dst': 'CONNECTED'},
                {'name': 'disconnect', 'src': ['CONNECTED', 'NOT_SELECTED', 'SELECTED'], 'dst': 'NOT_CONNECTED'},
                {'name': 'select', 'src': 'NOT_SELECTED', 'dst': 'SELECTED'},
                {'name': 'deselect', 'src': 'SELECTED', 'dst': 'NOT_SELECTED'},
                {'name': 'timeoutT7', 'src': 'NOT_SELECTED', 'dst': 'NOT_CONNECTED'},
            ],
            'callbacks': {
                'onNOT_SELECTED': self._on_state_connect,
                'onNOT_CONNECTED': self._on_state_disconnect,
                'onSELECTED': self._on_state_select,
            },
            'autoforward': [
                {'src': 'CONNECTED', 'dst': 'NOT_SELECTED'}
            ]
        })

        # setup connection
        if self.active:
            if custom_connection_handler is None:
                self.connection = HsmsActiveConnection(self.address, self.port, self.sessionID, self)
            else:
                self.connection = custom_connection_handler.create_connection(self.address, self.port, self.sessionID, self)
        else:
            if custom_connection_handler is None:
                self.connection = HsmsPassiveConnection(self.address, self.port, self.sessionID, self)
            else:
                self.connection = custom_connection_handler.create_connection(self.address, self.port, self.sessionID, self)

    def _on_state_connect(self, data):
        """Connection state model got event connect

        :param data: event attributes
        :type data: object
        """
        # start linktest timer
        self.linktestTimer = threading.Timer(self.linktestTimeout, self._on_linktest_timer)
        self.linktestTimer.start()

        # start select process if connection is active
        if self.active:
            system_id = self.sendSelectReq()
            self.waitforSelectRsp(system_id)

    def _on_state_disconnect(self, data):
        """Connection state model got event disconnect

        :param data: event attributes
        :type data: object
        """
        # stop linktest timer
        if self.linktestTimer:
            self.linktestTimer.cancel()

        self.linktestTimer = None

    def _on_state_select(self, data):
        """Connection state model got event select

        :param data: event attributes
        :type data: object
        """
        # send event
        self.fireEvent('HsmsSelected', {'connection': self})

        # notify hsms handler of selection
        if hasattr(self, '_onHsmsSelect') and callable(getattr(self, '_onHsmsSelect')):
            self._onHsmsSelect()

    def _on_linktest_timer(self):
        """Linktest time timed out, so send linktest request"""
        # send linktest request and wait for response
        system_id = self.sendLinktestReq()
        self.waitforLinktestRsp(system_id)

        # restart the timer
        self.linktestTimer = threading.Timer(self.linktestTimeout, self._on_linktest_timer)
        self.linktestTimer.start()

    def on_connection_established(self):
        """Connection was established"""
        # update connection state
        self.connected = True

        self.connectionState.connect()

        self.fireEvent("HsmsConnected", {'connection': self})

    def on_before_connection_closed(self):
        """Connection is about to be closed"""
        # send separate request
        self.sendSeparateReq()

    def on_connection_closed(self):
        """Connection was closed"""
        # update connection state
        self.connected = False
        self.connectionState.disconnect()

        self.fireEvent("HsmsDisconnected", {'connection': self})

    def _queuePacket(self, packet):
        """Add packet to event queue

        :param packet: received data packet
        :type packet: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        # add to event queue
        self.packetQueue.append(packet)

        # notify all that new event arrived
        for event in self.eventQueue:
            event.set()

    def on_connection_packet_received(self, packet):
        """Packet received by connection

        :param packet: received data packet
        :type packet: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        if packet.header.sType > 0:
            self.logger.info("< %s\n  %s", packet, hsmsSTypes[packet.header.sType])

            # check if it is a select request
            if packet.header.sType == 0x01:
                # if we are disconnecting send reject else send response
                if self.connection.disconnecting:
                    self.sendRejectRsp(packet.header.system, packet.header.sType, 4)
                else:
                    self.sendSelectRsp(packet.header.system)

                    # update connection state
                    self.connectionState.select()

            # check if it is a select response
            elif packet.header.sType == 0x02:
                # update connection state
                self.connectionState.select()

                # queue packet to notify waiting threads
                self._queuePacket(packet)

            # check if it is a deselect request
            elif packet.header.sType == 0x03:
                # if we are disconnecting send reject else send response
                if self.connection.disconnecting:
                    self.sendRejectRsp(packet.header.system, packet.header.sType, 4)
                else:
                    self.sendDeselectRsp(packet.header.system)
                    # update connection state
                    self.connectionState.deselect()

            elif packet.header.sType == 0x04:
                # update connection state
                self.connectionState.deselect()

                # queue packet to notify waiting threads
                self._queuePacket(packet)

            # check if it is a linktest request
            elif packet.header.sType == 0x05:
                # if we are disconnecting send reject else send response
                if self.connection.disconnecting:
                    self.sendRejectRsp(packet.header.system, packet.header.sType, 4)
                else:
                    self.sendLinktestRsp(packet.header.system)

            else:
                # queue packet if not handeled
                self._queuePacket(packet)
        else:
            if not self.connectionState.isstate("SELECTED"):
                self.logger.info("< %s", packet)
                self.logger.warning("received message when not selected")
                self.connection.send_packet(hsmsPacket(hsmsRejectReqHeader(packet.header.system, packet.header.sType, 4)))

                return True

            # redirect packet to hsms handler
            if hasattr(self, '_onHsmsPacketReceived') and callable(getattr(self, '_onHsmsPacketReceived')):
                self._onHsmsPacketReceived(packet)
            else:
                self.logger.info("< %s", packet)

    def _serializeData(self):
        """Returns data for serialization

        :returns: data to serialize for this object
        :rtype: dict
        """
        return {'address': self.address, 'port': self.port, 'active': self.active, 'sessionID': self.sessionID, 'name': self.name, 'connected': self.connected}

    def enable(self):
        """Enables the connection"""
        self.connection.enable()

    def disable(self):
        """Disables the connection"""
        self.connection.disable()

    def waitforStreamFunction(self, stream, function, is_control=False):
        """Wait for an incoming stream and function and return the receive data

        :param stream: number of stream to wait for
        :type stream: integer
        :param function: number of function to wait for
        :type function: integer
        :param is_control: is it a control packet
        :type is_control: bool
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        if is_control:
            # setup timeout to T6
            timeout = time.time() + self.connection.T6
        else:
            # setup timeout to T3
            timeout = time.time() + self.connection.T3

        # setup event for new item in queue
        event = threading.Event()
        self.eventQueue.append(event)

        found_packet = None

        while found_packet is None:
            for packet in self.packetQueue:
                if (packet.header.stream == stream) and (packet.header.function == function):
                    self.packetQueue.remove(packet)
                    found_packet = packet
                    break

            if found_packet is None:
                if event.wait(1):
                    event.clear()
                elif not self.connected or self.connection.disconnecting or time.time() > timeout:
                    return None

        self.eventQueue.remove(event)

        return found_packet

    def sendStreamFunction(self, packet):
        """Send the packet and wait for the response

        :param packet: packet to be sent
        :type packet: :class:`secsgem.secsFunctionBase.secsStreamFunction`
        """
        out_packet = hsmsPacket(hsmsStreamFunctionHeader(self.connection.get_next_system_counter(), packet.stream, packet.function, True, self.sessionID), packet.encode())
        self.connection.send_packet(out_packet)

    def waitforSystem(self, system, is_control=False):
        """Wait for an message with supplied system

        :param system: number of system to wait for
        :type system: integer
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        if not self.connected:
            self.logger.warning("handler not connected waiting for response for system {0}".format(system))
            return None

        if is_control:
            # setup timeout to T6
            timeout = time.time() + self.connection.T6
        else:
            # setup timeout to T3
            timeout = time.time() + self.connection.T3

        event = threading.Event()
        self.eventQueue.append(event)

        found_packet = None

        while found_packet is None:
            for packet in self.packetQueue:
                if packet.header.system == system:
                    self.packetQueue.remove(packet)
                    found_packet = packet
                    break

            if found_packet is None:
                if event.wait(1):
                    event.clear()
                elif not self.connected or self.connection.disconnecting or time.time() > timeout:
                    self.logger.warning("response for system {0} not received within timeout".format(system))
                    return None

        self.eventQueue.remove(event)

        return found_packet

    def sendAndWaitForResponse(self, packet):
        """Send the packet and wait for the response

        :param packet: packet to be sent
        :type packet: :class:`secsgem.secsFunctionBase.secsStreamFunction`
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        out_packet = hsmsPacket(hsmsStreamFunctionHeader(self.connection.get_next_system_counter(), packet.stream, packet.function, True, self.sessionID), packet.encode())
        self.connection.send_packet(out_packet)

        return self.waitforSystem(out_packet.header.system, (packet.stream == 0))

    def sendResponse(self, function, system):
        """Send response function for system

        :param function: function to be sent
        :type function: :class:`secsgem.secsFunctionBase.secsStreamFunction`
        :param system: system to reply to
        :type system: integer
        """
        out_packet = hsmsPacket(hsmsStreamFunctionHeader(system, function.stream, function.function, False, self.sessionID), function.encode())
        self.connection.send_packet(out_packet)

    def sendSelectReq(self):
        """Send a Select Request to the remote host

        :returns: System of the sent request
        :rtype: integer
        """
        system_id = self.connection.get_next_system_counter()

        packet = hsmsPacket(hsmsSelectReqHeader(system_id))
        self.connection.send_packet(packet)

        return system_id

    def sendSelectRsp(self, system_id):
        """Send a Select Response to the remote host

        :param system_id: System of the request to reply for
        :type system_id: integer
        """
        packet = hsmsPacket(hsmsSelectRspHeader(system_id))
        self.connection.send_packet(packet)

    def waitforSelectRsp(self, system_id):
        """Wait for an incoming Select Response

        :param system_id: System of the request to reply for
        :type system_id: integer
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        result = self.waitforSystem(system_id, True)

        return result

    def sendLinktestReq(self):
        """Send a Linktest Request to the remote host

        :returns: System of the sent request
        :rtype: integer
        """
        system_id = self.connection.get_next_system_counter()

        packet = hsmsPacket(hsmsLinktestReqHeader(system_id))
        self.connection.send_packet(packet)

        return system_id

    def sendLinktestRsp(self, system_id):
        """Send a Linktest Response to the remote host

        :param system_id: System of the request to reply for
        :type system_id: integer
        """
        packet = hsmsPacket(hsmsLinktestRspHeader(system_id))
        self.connection.send_packet(packet)

    def waitforLinktestRsp(self, system_id):
        """Wait for an incoming Linktest Response

        :param system_id: System of the request to reply for
        :type system_id: integer
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        return self.waitforSystem(system_id, True)

    def sendDeselectReq(self):
        """Send a Deselect Request to the remote host

        :returns: System of the sent request
        :rtype: integer
        """
        system_id = self.connection.get_next_system_counter()

        packet = hsmsPacket(hsmsDeselectReqHeader(system_id))
        self.connection.send_packet(packet)

        return system_id

    def sendDeselectRsp(self, system_id):
        """Send a Deselect Response to the remote host

        :param system_id: System of the request to reply for
        :type system_id: integer
        """
        packet = hsmsPacket(hsmsDeselectRspHeader(system_id))
        self.connection.send_packet(packet)

    def waitforDeselectRsp(self, system_id):
        """Wait for an incoming Deselect Response

        :param system_id: System of the request to reply for
        :type system_id: integer
        :returns: Packet that was received
        :rtype: :class:`secsgem.hsmsPackets.hsmsPacket`
        """
        result = self.waitforSystem(system_id, True)

        return result

    def sendRejectRsp(self, system_id, s_type, reason):
        """Send a Reject Response to the remote host

        :param system_id: System of the request to reply for
        :type system_id: integer
        :param s_type: s_type of rejected message
        :type s_type: integer
        :param reason: reason for rejection
        :type reason: integer
        """
        packet = hsmsPacket(hsmsRejectReqHeader(system_id, s_type, reason))
        self.connection.send_packet(packet)

    def sendSeparateReq(self):
        """Send a Separate Request to the remote host"""
        system_id = self.connection.get_next_system_counter()

        packet = hsmsPacket(hsmsSeparateReqHeader(system_id))
        self.connection.send_packet(packet)

        return system_id