class DeviceRoutine(CustomThread):

    def __init__(self, relay: int, gpio:int, ip_address: str, port: int, time_period_ms:int, start_after_ms:int):
        super().__init__(n_cycles=1, time_period_ms=time_period_ms, start_after_ms=start_after_ms)

        print(f'Created instance of DeviceRoutine@relay: {relay}, gpio: {gpio}, ip: {ip_address}, port: {port}')

        self._relay         = relay
        self._gpio          = gpio
        self._ip_address    = ip_address
        self._port          = port

        self._gpio_manager = None # [GpioManager]

        # State Machine
        self._routine_fun_dict = {
            DeviceRoutineStateEnum.DR_STATE_INIT:                   self._init_state_manager,
            DeviceRoutineStateEnum.DR_STATE_POLL_DEVICE:            self._poll_device_state_manager,
            DeviceRoutineStateEnum.DR_STATE_DEVICE_DISCONNECTED:    self._device_disconnected_state_manager,
            DeviceRoutineStateEnum.DR_STATE_WAIT_FOR_RECONNECTION:  self._wait_for_reconnection_state_manager,
            DeviceRoutineStateEnum.DR_STATE_STOP:                   self._stop_state_manager,
            DeviceRoutineStateEnum.DR_STATE_EXIT:                   self._exit_state_manager
        }


        self._routine_state = {
            "current"   : None,     #[MainAppStateEnum]
            "last"      : None      #[MainAppStateEnum]
        }

        self._tcpip_client_server                   = None  #[TcpIpClientServer]

        self._device_polling_timer                  = None  #[Timer] --> poll the device to get if alive or not
        self._device_alive_timeout_timer            = None  #[Timer] --> if timer elapsed the device is disconnected
        self._wait_for_reconnection_timeout_timer   = None  #[Timer] --> if the device is disconnected for a long time exit from test
        self._reset_timer                           = None  #[Timer] --> if the timer elapsed switch-on relay

        self._reset_time_min                        = None  #[int]

        self._cycle_counter                         = None  #[int]

        # Initialize all
        self._init_class()

        return

    # ---------------------------------------------------------------- #
    # ----------------------- Private Methods ------------------------ #
    # ---------------------------------------------------------------- #

    def _init_class(self):
        """ """
        # State Machine
        self._routine_state["current"]      = DeviceRoutineStateEnum.DR_STATE_INIT
        self._routine_state["last"]         = DeviceRoutineStateEnum.DR_STATE_INIT

        # TcpIp
        self._tcpip_client_server           = TcpIpClientServer(loop            =True,
                                                                time_period_ms  =TCPIP_SERVER_TIME_PERIOD_MS,
                                                                start_after_ms  =0,
                                                                socket_port     =self._port)

        # Relay Manager
        self._gpio_manager = GpioManager.get_instance()

        # Cycle Counter
        self._cycle_counter = 0

        pass

    # --------- State Machine --------- #
    def _store_last_state(self):
        # Store last state
        self._routine_state["last"] = self._routine_state["current"]
        return

    def _go_to_next_state(self, state):
        """"""
        # Store last state
        self._store_last_state()

        # Go to next state
        self._routine_state["current"] = state
        return

    def _init_state_manager(self):
        # Go to wait connection state
        print (f'[{self._timestamp()}] [{self._ip_address}] Init state')
       
        #Start tcpipserver
        self._tcpip_client_server.start()

        self._device_polling_timer                  = Timer()
        self._device_alive_timeout_timer            = Timer()
        self._wait_for_reconnection_timeout_timer   = Timer()
        self._reset_timer                           = Timer()

        # Go to next state
        self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_POLL_DEVICE)
        return


    def _poll_device_state_manager(self):
        if (self._routine_state["current"] == DeviceRoutineStateEnum.DR_STATE_POLL_DEVICE and
                self._routine_state["last"] != DeviceRoutineStateEnum.DR_STATE_POLL_DEVICE):
            
            # Increment cycle counter
            self._cycle_counter +=1
            
            self._device_polling_timer.start()
            self._device_alive_timeout_timer.start()

            # Store last state
            self._store_last_state()

        else:
            if self._device_polling_timer.elapsed_time_s(1) >= DEVICE_POLLING_TIME_PERIOD_S:

                self._tcpip_client_server.send_message_to(ip_address    = self._ip_address,
                                                          port          = self._port,
                                                          message       = f'{cc.ALIVE}?')

                self._device_polling_timer.reset()

            if self._reset_time_min is None:
                self._tcpip_client_server.send_message_to(ip_address=self._ip_address,
                                                          port=self._port,
                                                          message=f'{cc.RESET_TIME}?')

            if self._tcpip_client_server.is_data_available():
                ip, port, msg = self._tcpip_client_server.read()

                # It's a query
                if msg.find("?") > 0:
                    header, body = msg.split("?")

                    if header == cc.RELAY_OFF:
                        print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] relay off')
                        self._gpio_manager.relay_off(self._relay)
                        self._tcpip_client_server.send_message_to(ip_address=self._ip_address,
                                                                  port=self._port,
                                                                  message=f'{cc.RELAY_OFF}:{cc.YES}')
                    elif header == cc.RELAY_ON:
                        print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] relay on')
                        self._gpio_manager.relay_on(self._relay)
                        self._tcpip_client_server.send_message_to(ip_address=self._ip_address,
                                                                  port=self._port,
                                                                  message=f'{cc.RELAY_ON}:{cc.YES}')

                    elif header == cc.STOP_TEST:
                        self._tcpip_client_server.send_message_to(ip_address=self._ip_address,
                                                                  port=self._port,
                                                                  message=f'{cc.STOP_TEST}:{cc.YES}')

                        self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_STOP)


                elif msg.find(":") > 0:
                    header, body = msg.split(":")

                    if header == cc.ALIVE and body == cc.YES:                        
                        print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] [Cycle {self._cycle_counter}] alive')

                        #Device is alive, reset "alive" timer
                        self._device_alive_timeout_timer.reset()


                    elif header == f'{cc.RESET_TIME}':
                        # Set reset time with the value provided by device
                        if self._reset_time_min == None:
                            try:
                                self._reset_time_min = int(body)
                                # print (f'[{self._timestamp()}] reset time set to {self._reset_time_min} min.')
                            except:
                                pass


            # Device alive timeout timer elapsed: the device is off
            if self._device_alive_timeout_timer.elapsed_time_s(1) > DEVICE_ALIVE_TIMEOUT_S:
                
                print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] not alive')

                # Stop timers
                self._device_polling_timer.stop()
                self._device_alive_timeout_timer.stop()

                #Go to device disconnected state
                self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_DEVICE_DISCONNECTED)
        return


    def _device_disconnected_state_manager(self):
        if (self._routine_state["current"] == DeviceRoutineStateEnum.DR_STATE_DEVICE_DISCONNECTED and
                self._routine_state["last"] != DeviceRoutineStateEnum.DR_STATE_DEVICE_DISCONNECTED):
            print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] device disconnected state')

            # If the device hasn't provide the default value for reset, use a default.
            if self._reset_time_min is None:
                self._reset_time_min = DEFAULT_RESET_TIME_MIN

            # Start device reset timer
            self._reset_timer.start()

            # Store last state
            self._store_last_state()
        
        else:
            if self._reset_timer.elapsed_time_min(1) >= self._reset_time_min:
                print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] reset time ({self._reset_timer.elapsed_time_min(1)} min.) elapsed')
                #Relay on                
                print(f'\n[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] relay on')
                self._gpio_manager.relay_on(self._relay)
                self._reset_timer.stop()

                self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_WAIT_FOR_RECONNECTION)
        return

    def _wait_for_reconnection_state_manager(self):
        if (self._routine_state["current"] == DeviceRoutineStateEnum.DR_STATE_WAIT_FOR_RECONNECTION and
                self._routine_state["last"] != DeviceRoutineStateEnum.DR_STATE_WAIT_FOR_RECONNECTION):
            print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] wait for reconnection state')

            # Start device disconnected timeout and polling timer
            self._wait_for_reconnection_timeout_timer.start()
            self._device_polling_timer.start()

            # Store last state
            self._store_last_state()
        else:
            if self._device_polling_timer.elapsed_time_s(1) >= DEVICE_DISC_POLLING_TIME_PERIOD_S:
                self._tcpip_client_server.send_message_to(ip_address=self._ip_address,
                                                          port=self._port,
                                                          message=f'{cc.ALIVE}?')
                self._device_polling_timer.reset()

            if (self._wait_for_reconnection_timeout_timer.timer_status() == TimerStatusEnum.TS_START
                    and self._wait_for_reconnection_timeout_timer.elapsed_time_min(1) >= WAIT_FOR_RECONNECTION_TIMEOUT_MIN):
                print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] reconnection timeout ({self._reset_timer.elapsed_time_min(1)} min.) expired')
                # Stop timer
                self._device_polling_timer.stop()
                self._wait_for_reconnection_timeout_timer.stop()

                # Device off for too much time. Exit!
                self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_STOP)

            if self._tcpip_client_server.is_data_available():
                ip, port, msg = self._tcpip_client_server.read()

                if msg == f'{cc.ALIVE}:{cc.YES}':
                    print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] alive: elapsed ({self._wait_for_reconnection_timeout_timer.elapsed_time_min(1)} min.)')
                    # Stop timers
                    self._device_polling_timer.stop()
                    self._wait_for_reconnection_timeout_timer.stop()

                    # Go to poll device
                    self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_POLL_DEVICE)

        return

    def _stop_state_manager(self):
        if (self._routine_state["current"] == DeviceRoutineStateEnum.DR_STATE_STOP and
                self._routine_state["last"] != DeviceRoutineStateEnum.DR_STATE_STOP):
            print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] stop state')
            
            # Stop tcpip client-server
            self._tcpip_client_server.stop()
            
            # Relay on
            self._gpio_manager.relay_on(self._relay)

            # Store last state
            self._store_last_state()
        else:

            # Go to exit state
            self._go_to_next_state(DeviceRoutineStateEnum.DR_STATE_EXIT)
            pass

        return

    def _exit_state_manager(self):
        print (f'[{self._timestamp()}] [{self._ip_address}] [Channel {self._relay}] exit state')
        self._store_last_state()
        return

    def _main_app_state_machine_manager(self):
        # Get Function from Dictionary
        fun = self._routine_fun_dict.get(self._routine_state["current"])

        # Execute function
        fun()

        return

    def _timestamp(self) -> str:
        return datetime.now().strftime("%m/%d/%Y, %H:%M:%S")


    # ---------------------------------------------------------------- #
    # ------------------------ Public Methods ------------------------ #
    # ---------------------------------------------------------------- #
    def runnable(self):
        """ override runnable of its superclass """

        # print("*****************************************************")
        # print("***              Start State Machine              ***")
        # print("*****************************************************")
        
        exit_condition = False

        while not exit_condition:
            # Execute State Machine
            self._main_app_state_machine_manager()

            exit_condition = (self._routine_state["current"] == DeviceRoutineStateEnum.DR_STATE_EXIT and
                              self._routine_state["last"] == DeviceRoutineStateEnum.DR_STATE_EXIT)

            time.sleep(STATE_MACHINE_TIME_PERIOD_MS/1000)

        # print("*****************************************************")
        # print("***             End of State Machine              ***")
        # print("*****************************************************")

        return

    @property
    def relay(self):
        return self._relay
        
    @property
    def gpio(self):
        return self._gpio

    @property
    def ip_address(self):        
        return self._ip_address
    
    @property
    def port(self):
        return self._port
Esempio n. 2
0
    def bytes_available_rx(self):
        """ Check available bytes of data """
        self._bytes_available_rx = self.inWaiting()
        return self._bytes_available_rx


if __name__ == '__main__':
    import time
    from datetime import datetime
    from Timer import Timer
    test_serial = CustomSerial(port="COM19", baudrate=115200)
    test_serial.serial_init()
    #test_serial.serial_write("init")
    #test_serial.serial_write('read_tempiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii')
    counter = 0

    tm = Timer()
    tm.start()

    while True:
        test_serial.serial_write('read_temp\r')

        while not test_serial.bytes_available_rx:

            pass

        x = test_serial.serial_read().strip("\r\n")
        print((counter, tm.elapsed_time_s(2), x))
        counter = counter + 1
        time.sleep(0.1)