﻿import requests
import re
from time import sleep

"""
Voetsch climate chamber HTTP control
"""


class VT4002:

    # define the temperature that should be after cool down
    COOL_DOWN_TEMPERATURE = 20
    COOL_DOWN_TARGET_ACCURACY = 1
    COOL_DOWN_SETTLING_TIME = 60

    def __init__(self, ip_address, username, password):
        """
        Implements the basic operation of the Vötsch VT4002 climate chamber

        Arguments:
            ip_address: The IP address of the climate chamber
            username: username of a user that is allowed to control the climate chamber
            password: the associated password

        Returns:
            A climate chamber object

        Raises:
            TIMEOUT
            WRONG CREDENTIALS
        """

        # store the credentials so that methods can access them
        self.__ip_address = ip_address
        self.__username = username
        self.__password = password

        # we try to connect to the climate chamber just to see if there is an error
        url = 'http://' + str(self.__ip_address) + '/simpac/set/c3k_set_de.plc'
        self.__send_request(url)

    def __send_request(self, url, payload=''):
        """
        Internal function that is used for the http communication with the climate chamber

        Args:
            payload (Dict[str, str]): you can pass GET arguments that are submitted to the climate chamber
                {'key1': 'value1', 'key2': 'value2'}

        Returns:
            string: The response from the climate chamber

        Raises:
            HTTPError: If there is a problem when communicating with the climate chamber
        """

        # send the request to the climate chamber
        response = requests.get(url,
                                params=payload,
                                auth=(self.__username, self.__password),
                                timeout=1)

        # raise an error if there is something wrong with the communication
        # http status 4xx or 5xx
        response.raise_for_status()

        # return the response from the climate chamber
        return response.text

    def __get_temperatures(self):
        """get actual and target temperature"""

        # send the request to the chamber
        url = 'http://' + str(self.__ip_address) + '/simpac/macros/getdata.plc'
        response = self.__send_request(url)

        # filter all numbers from the respnse text
        parts = re.findall(r"[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?", response)

        # pick out the temperatures we need
        target_temperature = round(float(parts[6]), 3)
        actual_temperature = round(float(parts[7]), 3)

        return [target_temperature, actual_temperature]

    def get_chamber_status(self):

        # define variables we use to store status information
        chamber_status = None

        # Send the http request to the climate chamber
        url = 'http://' + str(self.__ip_address) + '/simpac/set/c3k_set_de.plc'
        response = self.__send_request(url)

        # find the lines with the temperature values and parse it
        for line in response.splitlines():

            # check for the chamber status
            if line.startswith('<img src="../image/'):
                # extract the status from the html line
                parts = line.split('"')
                chamber_status = parts[3]

                # print('The chamber status is: ' + str(chamber_status))

        return chamber_status

    def set_target_temperature(self, temperature):
        """
        Sets a new temperature target value

        Args:
            temperature (float): Define the new temperature target value
        """

        # define a new temperature value
        url = 'http://' + str(self.__ip_address) + '/simpac/set/c3k_set_de.plc'
        payload = {'tag_value': str(temperature), 'tag_name': 'HMI.CV[1].SP'}

        # send the request to set the new temperature
        self.__send_request(url, payload)

    def get_target_temperature(self):
        values = self.__get_temperatures()
        return float(values[0])

    def get_actual_temperature(self):
        values = self.__get_temperatures()
        return float(values[1])

    def enable(self):
        # define the enable command
        url = 'http://' + str(self.__ip_address) + '/simpac/set/c3k_set_de.plc'
        payload = {'tag_value': '16#1', 'tag_name': 'HMI.ChbMode.StartMode'}

        # send the request to turn the climate chamber on
        self.__send_request(url, payload)

    def disable(self):
        # define the disable command
        url = 'http://' + str(self.__ip_address) + '/simpac/set/c3k_set_de.plc'
        payload = {'tag_value': '16#0', 'tag_name': 'HMI.ChbMode.StartMode'}

        # send the request to turn the climate chamber off
        self.__send_request(url, payload)

    def go_to_temperature(self, target_temperature, target_accuracy=0.2, settling_time=60):

        # set the new target temperature
        self.set_target_temperature(target_temperature)

        # enable the climate chamber
        self.enable()

        lower_limit = target_temperature - target_accuracy
        upper_limit = target_temperature + target_accuracy
        stability_counter = 0

        # wait until we reach the target temperature
        while True:
            # get the actual temperature
            actual_temperature = self.get_actual_temperature()

            # if we are within the target range increase the counter
            if lower_limit < actual_temperature < upper_limit:
                stability_counter += 1

                # if the value is stable long enough
                if stability_counter >= settling_time:
                    break

            else:
                stability_counter = 0

            # print('Temp: ' + str(actual_temperature) + ' °c'
            #       + '  /  Target: (' + str(target_temperature) + '+-' + str(target_accuracy) + ') °c'
            #       + '  /  In target range for: ' + str(stability_counter) + ' s')

            sleep(1)

        # disable the climate chamber
        # self.disable()

    def cool_down(self):
        """Cool down the climate chamber so you can safely open it"""
        self.go_to_temperature(self.COOL_DOWN_TEMPERATURE, self.COOL_DOWN_TARGET_ACCURACY, self.COOL_DOWN_SETTLING_TIME)
