Device.py 7.66 KB
from django.conf import settings
import socket
import configparser
import abc
import select
import utils.Logger as L
import time
import os
from common.models import Log

DEBUG_FILE = True
RECONNECT_TIMEOUT_SECONDS = 20

'''
    Generic object for the communication with all the devices
'''


class DeviceController():
    __metaclass__ = abc.ABCMeta
    logger = L.setupLogger("DeviceLogger", "Devices")
    name = ""
    config_file = "../config/socket_config.ini"
    config = None
    connected = False
    sock = None
    ip = None
    port = None

    def __init__(self, device_name):
        self.name = device_name
        self.getConfig()
        self.getConnection(device_name)
        self.connect()


    @abc.abstractmethod
    def set(self, command: str, *args):
        return

    @abc.abstractmethod
    def do(self, command: str, *args):
        return

    @abc.abstractmethod
    def get(self, command: str, *args):
        return

    @abc.abstractmethod
    def getStatus(self):
        return

    def sendMessage(self, message: str):
        if (not self.isConnected()):
            # (EP) NON, return 0 !!!
            #return (1)
            return False
        # (EP) Here we are still not sure that we are connected to the device (it may be down and we do not know)
        try:
            readable, writable, exceptional = select.select([], [self.sock], [self.sock], 0)
            if not (writable or exceptional):
                raise (Exception("Socket not writable"))
            self.sock.send(bytes(message, "UTF-8"))
        except Exception as e:
            if (settings.DEBUG):
                print("(ENV) Could not send message (on socket) to "+self.name+" : " + message + " -> " + str(e))
                #EP TODO: bugfix: ce log ne fait rien du tout !!
                self.log(self.name, "Could not send message on socket : " + message + " -> " + str(e))

                i = 0
                while self.reconnect() is False and i < RECONNECT_TIMEOUT_SECONDS:
                    i += 1
                if i == RECONNECT_TIMEOUT_SECONDS:
                    self.device_is_dead()
            # (EP) NON, 1 = true !!!
            #return (1)
            return False
        # (EP) NON, 0 = false !!!
        #return (0)
        Log.objects.create(agent=self.name, message='Message sent : ' + message)
        return True

    def isError(self, message: str):
        #EP
        #if (message == "FAILED" or message == "NOT_SET" or message == "DISCONNECTED"):
        if (message == "FAILED" or message.startswith("NOT_SET") or message == "DISCONNECTED"):
            return (True)
        return (False)

    # MIND: Does not mean "is connected now" but "WAS connected once"
    def isConnected(self):
        if (not self.sock or not self.connected):
            # (EP) NON, c'est totalement illisible !!! on n'est plus en 1970
            #if (self.connect()): return (False)
            print("something is wrong with this device, now trying to connect again...")
            if not self.connect(): return False
            #self.connected = True
        return True

    def readBytes(self, size) -> str:
        if (not self.isConnected()):
            return ("NOT_SET2")
        try:
            readable, writable, exceptional = select.select([self.sock], [], [self.sock], 0)
            if not (readable or exceptional):
                #ret = self.sock.recv(size).decode()
                raise (Exception("KO: socket error or not implemented command"))

            ret = self.sock.recv(size).decode()
            if (not ret):
                if (settings.DEBUG):
                    self.log(self.name, "Connection has been killed")
                self.connected = False
                return ("DISCONNECTED")
        except Exception as e:
            if (settings.DEBUG):
                self.log(self.name, "Socket not readable : " + str(e))
            return (str(e))
        return (ret)

    def blockAndReadBytes(self, size) -> str:
        self.sock.setblocking(1)
        message = self.readBytes(size)
        Log.objects.create(agent=self.name, message='Message received : ' + message)
        return (message)

    def blockAndReadMessage(self) -> str:
        return (self.blockAndReadBytes(1024))

    def setBlocking(self, flag):
        if (not self.sock):
            return (1)
        self.sock.setblocking(flag)
        return (0)

    # TODO: maybe read more than 1024 bytes ?????
    def readMessage(self) -> str:
        message = str(self.readBytes(1024))
        #os.system("echo lol >> /home/portos/IRAP/pyros/src/POURKOICAMARCHPA")
        Log.objects.create(agent=self.name, message='Message received : ' + message)
        return message

    def getConnection(self, device_name):
        self.ip = self.config.get(device_name, "ip")
        self.port = int(self.config.get(device_name, "port"))
        return (0)

    def getConfig(self):
        self.config = configparser.ConfigParser()
        self.config.read(self.config_file)
        return (0)

    def log(self, device_name: str, message: str):
        Log.objects.create(agent=self.name, message=message)
        if DEBUG_FILE and settings.DEBUG:
            self.logger.info("From device : " + device_name + " -> " + message)
        return (0)

    def connect(self):
        if (self.ip is None or self.port is None):
            self.log(self.name, "Ip or Port not initialized")
            # (EP) It's a serious bug, so raise exception !!!
            #return (1)
            raise (Exception(self.name +"Controller has no ip or port to connect to"))
        try:
            # Create a TCP/IP socket
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # Connect the socket to the port on the server
            r = self.sock.connect_ex((self.ip, self.port))
            if (r != 0):
                raise Exception("sock.connect_ex failed : " + os.strerror(r))

            '''
            Socket set to non blocking (by default, it is blocking)
            In non-blocking mode, if a recv() call doesn’t find any data, 
            or if a send() call can’t immediately dispose of the data, 
            an error exception is raised. 
            (In blocking mode, the calls block until they can proceed) 
            s.setblocking(0) is equivalent to s.settimeout(0.0); 
            s.setblocking(1) is equivalent to s.settimeout(None).
            '''
            self.sock.setblocking(0)
        except Exception as e:
            print(e)
            print("FAILED TO CONNECT TO DEVICE {}".format(self.name))
            if (settings.DEBUG):
                #TODO: Bugfix: Ce log ne fait rien !
                self.log(self.name, "Failed to connect to " + str(self.ip) + ":" + str(self.port) + " -> " + str(e))
            # (EP) return TRUE??? (for python, 1=true !!!)
            #return (1)
            return False
        self.connected = True
        # (EP) return FALSE ??? (for python, 0=false !!!)
        #return (0)
        return True

    def reconnect(self):

        self.getConfig()
        self.getConnection(self.name)
        try:
            print("\nTrying to reconnect to " + str(self.name) + "...")
            self.log(self.name, "Trying to reconnect to " + self.name)
            if not self.connect():
                raise Exception("Reconnect to " + self.name + " failed")
            return True
        except Exception as e:
            print(str(e))
            time.sleep(1)
            return False

    '''
        Function called when the connection with the device is lost for too lon time and the max timeout for reconnection
        RECONNECT_TIMEOUT_SECONDS is reached 
    '''

    def device_is_dead(self):
        print("\nCan't establish connection with the " + self.name + ", timeout expired")