Source

pysecsgem / secsgem / client.py

Full commit
'''
This Module Implement a SECS client (ie a Host)

Created on 3 juin 2012

'''

__author__ = 'Gabriel AHTUNE'
__version__ = '0.9'


import logging
import argparse
import socket
import time
from threading import Timer
from .structures import SGMessage
from .transports import HSMS, STYPE_TABLE
from .abc import SECSGEMEntity


class Client(SECSGEMEntity):
    """
    This class implements a SECS CLIENT (ie a host)
    """

    def __init__(self, host, port, transport=HSMS, sessionid=0, heartbeat=15,
                 timeout_5=10, com_auto=True):
        self.logger = logging.getLogger("Host")

        SECSGEMEntity.__init__(self, transport, sessionid)
        self.daemon = True
        self.name = "SECS Host"
        self.host = host
        self.port = port
        self.closed = False
        self.heartbeat = heartbeat
        self.heartbeat_send_and_schedule()
        self.com_auto = com_auto
        self.link = None

        self.timeout_3 = 45           # Reply Timeout
        self.timeout_5 = timeout_5    # Connection Separation Timeout
        self.timeout_6 = 5            # Control Transaction Timeout
        self.timeout_7 = 10           # Not Selected timeout
        self.timeout_8 = 5            # Network Intercharacter Timeout

    def connect(self):
        """
        Connect to the equipment
        """

        self.link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.link.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        while not self.connected and not self.closed:
            try:
                self.link.connect((self.host, self.port))
                self.connected = True
                self.feedstate('Connected')
                self.send(self.transport.handshakes())
                self.logger.info("Select request sent")
            except:
                self.feedstate('Connection Failed')
                self.logger.exception("Equipment connection failed")
                time.sleep(self.timeout_5)

    def run(self):
        """
        Start the thread
        """

        while not self.connected and not self.closed:
            self.connect()
            self.listenandfeed()
            time.sleep(1)

    def listenandfeed(self):
        """
        Listen to the Equipment, feed to the listeners the incoming message
        """

        while self.connected and not self.closed:
            for data in self.transport.chunker(self.link.recv_into):
                unpacked = self.transport.unpack(data)
                if not self.com_auto:
                    self.feed(unpacked)
                    continue
                if unpacked.SType == STYPE_TABLE["Data Message"]:
                    msg = self.transport.unpack_data(unpacked)
                    self.transport.currentsbytes = unpacked.SBytes
                    self.feed(msg)
                elif unpacked.SType == STYPE_TABLE["Select.rsp"]:
                    self.connected = True
                    self.feed(unpacked)
                else:
                    ans = self.transport.communication_reply(unpacked)
                    self.feed(unpacked)
                    if ans:
                        self.send(ans)

            self.connected = False
            self.logger.info("Disconnected from Equipment")
            self.link.close()

    def heartbeat_send_and_schedule(self):
        """
        Send a heartbeat and schedule the next heartbeat
        """

        if not self.closed:
            if self.heartbeat:
                hearttime = Timer(self.heartbeat,
                                  self.heartbeat_send_and_schedule)
                hearttime.daemon = True
                hearttime.start()

        if self.connected and not self.closed:
            self.send(self.transport.heartbeats())
            self.logger.info("Heartbeat request sent")

    def close(self):
        """
        Close the connection and terminate the thread
        """

        if self.connected:
            self.send(self.transport.goodbye())
            self.logger.info("Deselect request sent")
            self.feedstate('Disconnected')
            self.link.shutdown(socket.SHUT_RDWR)
            self.link.close()
        self.closed = True
        self.connected = False

    def send_raw(self, data):
        """
        Send the raw data to the equipment
        """

        try:
            self.link.send(data)
        except socket.error:
            self.logger.exception("Fail to send {}".format(data))
        except:
            self.logger.error("No clients connected")
            self.connected = False

    def send(self, msg):
        """
        Pack the message and send it to the equipment
        """
        if self.connected:
            if type(msg) is SGMessage:
                msg = self.transport.data(
                    msg.stream,
                    msg.function,
                    msg.W,
                    msg.content,
                    msg.SBytes
                )
            pmsg = self.transport.pack(msg)
            self.send_raw(pmsg)
            return msg
        else:
            self.logger.info("Message not sent: {}".format(str(msg)))

if __name__ == '__main__':
    PARSER = argparse.ArgumentParser(
        description='create a quick SECS Host',
        epilog='Tested on Linux, python 3.2'
    )
    PARSER.add_argument('--host', type=str, default='127.0.0.1',
                        help='specify the host to connect to')
    PARSER.add_argument('--port', type=int, default=5000,
                        help='specify the server port number')
    PARSER.add_argument('--heartbeat', type=int, default=0,
                        help='period of heartbeat in second')
    ARGS = PARSER.parse_args()

    CLIENT = Client(ARGS.host, ARGS.port, heartbeat=ARGS.heartbeat)

    def print_(string):
        """print for pylint"""
        print(string)

    CLIENT.addlistener(print_)
    CLIENT.start()
    try:
        while True:
            input('$> ')
    except KeyboardInterrupt:
        CLIENT.close()
        print('\nCtrl+C pressed -- Quitting')