"""Suppoort for Ariston."""
import calendar
import copy
import datetime
import logging
import re
import threading
from typing import Union
import requests


class AristonHandler:
    """
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Ariston NET Remotethermo API

    'username' - mandatory username;

    'password' - mandatory password;

    'sensors' - list of wanted sensors to be monitored. Check class method api_data or method supported_sensors_get

    'period_request' - period to send requests (minimum is 30 seconds)

    'polling' - defines multiplication factor for waiting periods to get or set the data;

    'logging_level' - defines level of logging - allowed values [CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET=(default)]
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    """

    _VERSION = "2.0.12"

    _ARISTON_URL = "https://www.ariston-net.remotethermo.com"

    # API configuration
    _MAX_RETRIES = 5
    _GET_SENSORS_PERIOD_SECONDS = 30
    _SET_SENSORS_PERIOD_SECONDS = 30
    _MAX_ERRORS = 5
    _WAIT_PERIOD_MULTIPLYER = 5
    _TIMEOUT_MIN = 5
    _TIMEOUT_AV = 15
    _TIMEOUT_MAX = 25
    _TIME_SPLIT = 0.1

    # Log levels
    _LEVEL_CRITICAL = "CRITICAL"
    _LEVEL_ERROR = "ERROR"
    _LEVEL_WARNING = "WARNING"
    _LEVEL_INFO = "INFO"
    _LEVEL_DEBUG = "DEBUG"
    _LEVEL_NOTSET = "NOTSET"
    _LOGGING_LEVELS = [
        _LEVEL_CRITICAL,
        _LEVEL_ERROR,
        _LEVEL_WARNING,
        _LEVEL_INFO,
        _LEVEL_DEBUG,
        _LEVEL_NOTSET
    ]

    # All sensors names
    _PARAM_ACCOUNT_CH_GAS = "account_ch_gas"
    _PARAM_ACCOUNT_CH_ELECTRICITY = "account_ch_electricity"
    _PARAM_ACCOUNT_DHW_GAS = "account_dhw_gas"
    _PARAM_ACCOUNT_DHW_ELECTRICITY = "account_dhw_electricity"
    _PARAM_CH_ANTIFREEZE_TEMPERATURE = "ch_antifreeze_temperature"
    _PARAM_CH_MODE = "ch_mode"
    _PARAM_CH_SET_TEMPERATURE = "ch_set_temperature"
    _PARAM_CH_COMFORT_TEMPERATURE = "ch_comfort_temperature"
    _PARAM_CH_ECONOMY_TEMPERATURE = "ch_economy_temperature"
    _PARAM_CH_DETECTED_TEMPERATURE = "ch_detected_temperature"
    _PARAM_CH_DEROGA_TEMPERATURE = "ch_deroga_temperature"
    _PARAM_CH_PROGRAM = "ch_program"
    _PARAM_CH_WATER_TEMPERATURE = "ch_water_temperature"
    _PARAM_ERRORS_COUNT = "errors_count"
    _PARAM_DHW_COMFORT_FUNCTION = "dhw_comfort_function"
    _PARAM_DHW_MODE = "dhw_mode"
    _PARAM_DHW_PROGRAM = "dhw_program"
    _PARAM_DHW_SET_TEMPERATURE = "dhw_set_temperature"
    _PARAM_DHW_STORAGE_TEMPERATURE = "dhw_storage_temperature"
    _PARAM_DHW_COMFORT_TEMPERATURE = "dhw_comfort_temperature"
    _PARAM_DHW_ECONOMY_TEMPERATURE = "dhw_economy_temperature"
    _PARAM_MODE = "mode"
    _PARAM_OUTSIDE_TEMPERATURE = "outside_temperature"
    _PARAM_SIGNAL_STRENGTH = "signal_strength"
    _PARAM_UNITS = "units"
    _PARAM_THERMAL_CLEANSE_CYCLE = "dhw_thermal_cleanse_cycle"
    _PARAM_GAS_TYPE = "gas_type"
    _PARAM_GAS_COST = "gas_cost"
    _PARAM_ELECTRICITY_COST = "electricity_cost"
    _PARAM_CH_AUTO_FUNCTION = "ch_auto_function"
    _PARAM_CH_FLAME = "ch_flame"
    _PARAM_DHW_FLAME = "dhw_flame"
    _PARAM_FLAME = "flame"
    _PARAM_HEAT_PUMP = "heat_pump"
    _PARAM_HOLIDAY_MODE = "holiday_mode"
    _PARAM_INTERNET_TIME = "internet_time"
    _PARAM_INTERNET_WEATHER = "internet_weather"
    _PARAM_WEATHER = 'weather'
    _PARAM_THERMAL_CLEANSE_FUNCTION = "dhw_thermal_cleanse_function"
    _PARAM_CH_PILOT = "ch_pilot"
    _PARAM_UPDATE = "update"
    _PARAM_ONLINE_VERSION = "online_version"
    _PARAM_PRESSURE = "pressure"
    _PARAM_CH_FLOW_TEMP = 'ch_flow_temperature'
    _PARAM_CH_FIXED_TEMP = 'ch_fixed_temperature'
    _PARAM_CH_LAST_MONTH_GAS = 'ch_gas_last_month'
    _PARAM_CH_LAST_MONTH_ELECTRICITY = 'ch_electricity_last_month'
    _PARAM_DHW_LAST_MONTH_GAS = 'dhw_gas_last_month'
    _PARAM_DHW_LAST_MONTH_ELECTRICITY = 'dhw_electricity_last_month'
    _PARAM_CH_ENERGY_TODAY = 'ch_energy_today'
    _PARAM_CH_ENERGY_YESTERDAY = 'ch_energy_yesterday'
    _PARAM_DHW_ENERGY_TODAY = 'dhw_energy_today'
    _PARAM_DHW_ENERGY_YESTERDAY = 'dhw_energy_yesterday'
    _PARAM_CH_ENERGY_LAST_7_DAYS = 'ch_energy_last_7_days'
    _PARAM_DHW_ENERGY_LAST_7_DAYS = 'dhw_energy_last_7_days'
    _PARAM_CH_ENERGY_THIS_MONTH = 'ch_energy_this_month'
    _PARAM_CH_ENERGY_LAST_MONTH = 'ch_energy_last_month'
    _PARAM_DHW_ENERGY_THIS_MONTH = 'dhw_energy_this_month'
    _PARAM_DHW_ENERGY_LAST_MONTH = 'dhw_energy_last_month'
    _PARAM_CH_ENERGY_THIS_YEAR = 'ch_energy_this_year'
    _PARAM_CH_ENERGY_LAST_YEAR = 'ch_energy_last_year'
    _PARAM_DHW_ENERGY_THIS_YEAR = 'dhw_energy_this_year'
    _PARAM_DHW_ENERGY_LAST_YEAR = 'dhw_energy_last_year'
    _PARAM_CH_ENERGY2_TODAY = 'ch_energy2_today'
    _PARAM_CH_ENERGY2_YESTERDAY = 'ch_energy2_yesterday'
    _PARAM_DHW_ENERGY2_TODAY = 'dhw_energy2_today'
    _PARAM_DHW_ENERGY2_YESTERDAY = 'dhw_energy2_yesterday'
    _PARAM_CH_ENERGY2_LAST_7_DAYS = 'ch_energy2_last_7_days'
    _PARAM_DHW_ENERGY2_LAST_7_DAYS = 'dhw_energy2_last_7_days'
    _PARAM_CH_ENERGY2_THIS_MONTH = 'ch_energy2_this_month'
    _PARAM_CH_ENERGY2_LAST_MONTH = 'ch_energy2_last_month'
    _PARAM_DHW_ENERGY2_THIS_MONTH = 'dhw_energy2_this_month'
    _PARAM_DHW_ENERGY2_LAST_MONTH = 'dhw_energy2_last_month'
    _PARAM_CH_ENERGY2_THIS_YEAR = 'ch_energy2_this_year'
    _PARAM_CH_ENERGY2_LAST_YEAR = 'ch_energy2_last_year'
    _PARAM_DHW_ENERGY2_THIS_YEAR = 'dhw_energy2_this_year'
    _PARAM_DHW_ENERGY2_LAST_YEAR = 'dhw_energy2_last_year'
    _PARAM_CH_ENERGY_DELTA_TODAY = 'ch_energy_delta_today'
    _PARAM_CH_ENERGY_DELTA_YESTERDAY = 'ch_energy_delta_yesterday'
    _PARAM_DHW_ENERGY_DELTA_TODAY = 'dhw_energy_delta_today'
    _PARAM_DHW_ENERGY_DELTA_YESTERDAY = 'dhw_energy_delta_yesterday'
    _PARAM_CH_ENERGY_DELTA_LAST_7_DAYS = 'ch_energy_delta_last_7_days'
    _PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS = 'dhw_energy_delta_last_7_days'
    _PARAM_CH_ENERGY_DELTA_THIS_MONTH = 'ch_energy_delta_this_month'
    _PARAM_CH_ENERGY_DELTA_LAST_MONTH = 'ch_energy_delta_last_month'
    _PARAM_DHW_ENERGY_DELTA_THIS_MONTH = 'dhw_energy_delta_this_month'
    _PARAM_DHW_ENERGY_DELTA_LAST_MONTH = 'dhw_energy_delta_last_month'
    _PARAM_CH_ENERGY_DELTA_THIS_YEAR = 'ch_energy_delta_this_year'
    _PARAM_CH_ENERGY_DELTA_LAST_YEAR = 'ch_energy_delta_last_year'
    _PARAM_DHW_ENERGY_DELTA_THIS_YEAR = 'dhw_energy_delta_this_year'
    _PARAM_DHW_ENERGY_DELTA_LAST_YEAR = 'dhw_energy_delta_last_year'
    _PARAM_HEATING_FLOW_TEMP = "ch_heating_flow_temp"
    _PARAM_HEATING_FLOW_OFFSET = "ch_heating_flow_offset"
    _PARAM_COOLING_FLOW_TEMP = "ch_cooling_flow_temp"
    _PARAM_COOLING_FLOW_OFFSET = "ch_cooling_flow_offset"

    # Ariston parameter codes in the menu
    _ARISTON_DHW_COMFORT_TEMP = "U6_9_0"
    _ARISTON_DHW_COMFORT_FUNCTION = "U6_9_2"
    _ARISTON_DHW_TIME_PROG_COMFORT = "U6_9_1_0_0"
    _ARISTON_DHW_TIME_PROG_ECONOMY = "U6_9_1_0_1"
    _ARISTON_SIGNAL_STRENGHT = "U6_16_5"
    _ARISTON_INTERNET_TIME = "U6_16_6"
    _ARISTON_INTERNET_WEATHER = "U6_16_7"
    _ARISTON_CH_COMFORT_TEMP = "U6_3_1_0_0"
    _ARISTON_CH_COMFORT_TEMP_ZONE_2 = "U6_3_1_1_0"
    _ARISTON_CH_COMFORT_TEMP_ZONE_3 = "U6_3_1_2_0"
    _ARISTON_CH_ECONOMY_TEMP = "U6_3_1_0_1"
    _ARISTON_CH_ECONOMY_TEMP_ZONE_2 = "U6_3_1_1_1"
    _ARISTON_CH_ECONOMY_TEMP_ZONE_3 = "U6_3_1_2_1"
    _ARISTON_CH_AUTO_FUNCTION = "U6_3_3"
    _ARISTON_CH_WATER_TEMPERATURE = "U6_3_0_0"
    _ARISTON_THERMAL_CLEANSE_FUNCTION = "U6_9_5_0"
    _ARISTON_THERMAL_CLEANSE_CYCLE = "U6_9_5_1"
    _ARISTON_S_W_FUNCTION_ACTIVATION = "U6_3_5_0_0"
    _ARISTON_S_W_FUNCTION_ACTIVATION_ZONE_2 = "U6_3_5_1_0"
    _ARISTON_S_W_FUNCTION_ACTIVATION_ZONE_3 = "U6_3_5_2_0"
    _ARISTON_S_W_TEMPERATURE_THRESHOLD = "U6_3_5_0_1"
    _ARISTON_S_W_TEMPERATURE_THRESHOLD_ZONE_2 = "U6_3_5_1_1"
    _ARISTON_S_W_TEMPERATURE_THRESHOLD_ZONE_3 = "U6_3_5_2_1"
    _ARISTON_S_W_DELAY_TIME = "U6_3_5_0_2"
    _ARISTON_S_W_DELAY_TIME_ZONE_2 = "U6_3_5_1_2"
    _ARISTON_S_W_DELAY_TIME_ZONE_3 = "U6_3_5_2_2"
    _ARISTON_PROGRAM_MANUAL_ZONE_1 = "U0_0"
    _ARISTON_PROGRAM_MANUAL_ZONE_2 = "U0_1"
    _ARISTON_PROGRAM_MANUAL_ZONE_3 = "U0_2"
    _ARISTON_PROGRAM_MANUAL_ZONE_4 = "U0_3"
    _ARISTON_PROGRAM_MANUAL_ZONE_5 = "U0_4"
    _ARISTON_PROGRAM_MANUAL_ZONE_6 = "U0_5"
    _ARISTON_PROGRAM_MANUAL_DHW = "U0_6"
    _ARISTON_PV_OFFSET_DHW = "U6_15_2"
    _ARISTON_QUITET_MODE_HHP = "U6_15_0_1"
    _ARISTON_HYBRID_MODE = "U6_12_1"
    _ARISTON_ENERGY_MANAGER_LOGIC = "U6_12_0"
    _ARISTON_BUFFER_ACTIVATION = "U6_10_0"
    _ARISTON_BUFFER_COMFORT_SETPOINT = "U6_10_1"
    _ARISTON_BUFFER_SETPOINT_HEATING = "U6_10_2"
    _ARISTON_BUFFER_SETPOINT_MODE = "U6_10_5"
    _ARISTON_DHW_TANK_CHARGE_MODE = "U6_9_3"
    _ARISTON_CH_FIXED_TEMP = "U6_3_0_1"
    _ARISTON_CH_FIXED_TEMP_ZONE_2 = "U6_3_0_3"
    _ARISTON_CH_FIXED_TEMP_ZONE_3 = "U6_3_0_5"

    # Ariston parameters in Android api
    _ARISTON_PAR_PRESSURE = 'HeatingCircuitPressure'
    _ARISTON_PAR_CH_FLOW_TEMP = 'ChFlowSetpointTemp'
    _ARISTON_PAR_OUTSIDE_TEMP = 'OutsideTemp'
    _ARISTON_PAR_WEATHER = 'Weather'
    _ARISTON_PAR_PLANT_MODE = 'PlantMode'
    _ARISTON_PAR_HOLIDAY = 'Holiday'
    _ARISTON_PAR_FLAME = 'IsFlameOn'
    _ARISTON_PAR_DHW_TEMP = 'DhwTemp'
    _ARISTON_PAR_ZONE_HEAT_REQUEST = 'ZoneHeatRequest'
    _ARIZTON_PAR_ZONE_MODE = 'ZoneMode'
    _ARISTON_PAR_ZONE_DESIRED_TEMP = 'ZoneDesiredTemp'
    _ARISTON_PAR_ZONE_MEAS_TEMP = 'ZoneMeasuredTemp'
    _ARISTON_PAR_ZONE_DEROGA_TEMP = 'ZoneDeroga'
    _ARISTON_PAR_ZONE_CONFORT_TEMP = 'ZoneComfortTemp'
    _ARISTON_PAR_ZONE_PILOT = 'IsZonePilotOn'
    _ARISTON_PAR_ZONE_ECONOMY_TEMP = 'ZoneEconomyTemp'
    _ARISTON_PAR_AUTO_HEATING = "AutomaticThermoregulation"
    _ARISTON_PAR_HEATING_FLOW_TEMP = "HeatingFlowTemp"
    _ARISTON_PAR_HEATING_FLOW_OFFSET = "HeatingFlowOffset"
    _ARISTON_PAR_COOLING_FLOW_TEMP = "CoolingFlowTemp"
    _ARISTON_PAR_COOLING_FLOW_OFFSET = "CoolingFlowOffset"
    _ARISTON_PAR_DHW_MODE = "DhwMode"
    _ARISTON_PAR_DHW_STORAGE_TEMP = "DhwStorageTemperature"
    _ARISTON_PAR_DHW_COMFORT_TEMP = "DhwTimeProgComfortTemp"
    _ARISTON_PAR_DHW_ECONOMY_TEMP = "DhwTimeProgEconomyTemp"

    # Parameters in Android api within zone 0, mapping to parameter names
    _MAP_ARISTON_ZONE_0_PARAMS = {
        _PARAM_CH_FLOW_TEMP: _ARISTON_PAR_CH_FLOW_TEMP,
        _PARAM_PRESSURE: _ARISTON_PAR_PRESSURE,
        _PARAM_OUTSIDE_TEMPERATURE: _ARISTON_PAR_OUTSIDE_TEMP,
        _PARAM_WEATHER: _ARISTON_PAR_WEATHER,
        _PARAM_MODE: _ARISTON_PAR_PLANT_MODE,
        _PARAM_HOLIDAY_MODE: _ARISTON_PAR_HOLIDAY,
        _PARAM_FLAME: _ARISTON_PAR_FLAME,
        _PARAM_DHW_SET_TEMPERATURE: _ARISTON_PAR_DHW_TEMP,
        _PARAM_DHW_MODE: _ARISTON_PAR_DHW_MODE,
        _PARAM_DHW_COMFORT_TEMPERATURE: _ARISTON_PAR_DHW_COMFORT_TEMP,
        _PARAM_DHW_ECONOMY_TEMPERATURE: _ARISTON_PAR_DHW_ECONOMY_TEMP,
        _PARAM_DHW_STORAGE_TEMPERATURE: _ARISTON_PAR_DHW_STORAGE_TEMP,
    }
    # Parameters in Android api within zone 1, mapping to parameter names
    _MAP_ARISTON_MULTIZONE_PARAMS = {
        _PARAM_CH_FLAME: _ARISTON_PAR_ZONE_HEAT_REQUEST,
        _PARAM_CH_MODE: _ARIZTON_PAR_ZONE_MODE,
        _PARAM_CH_SET_TEMPERATURE: _ARISTON_PAR_ZONE_DESIRED_TEMP,
        _PARAM_CH_DETECTED_TEMPERATURE: _ARISTON_PAR_ZONE_MEAS_TEMP,
        _PARAM_CH_DEROGA_TEMPERATURE: _ARISTON_PAR_ZONE_DEROGA_TEMP,
        _PARAM_CH_COMFORT_TEMPERATURE: _ARISTON_PAR_ZONE_CONFORT_TEMP,
        _PARAM_CH_PILOT: _ARISTON_PAR_ZONE_PILOT,
        _PARAM_CH_ECONOMY_TEMPERATURE: _ARISTON_PAR_ZONE_ECONOMY_TEMP,
        _PARAM_HEATING_FLOW_TEMP: _ARISTON_PAR_HEATING_FLOW_TEMP,
        _PARAM_HEATING_FLOW_OFFSET: _ARISTON_PAR_HEATING_FLOW_OFFSET,
        _PARAM_COOLING_FLOW_TEMP: _ARISTON_PAR_COOLING_FLOW_TEMP,
        _PARAM_COOLING_FLOW_OFFSET: _ARISTON_PAR_COOLING_FLOW_OFFSET,
    }
    # Parameters in Web menu, mapping to parameter names
    _MAP_ARISTON_WEB_MENU_PARAMS = {
        _PARAM_INTERNET_TIME: _ARISTON_INTERNET_TIME,
        _PARAM_INTERNET_WEATHER: _ARISTON_INTERNET_WEATHER,
        _PARAM_THERMAL_CLEANSE_FUNCTION: _ARISTON_THERMAL_CLEANSE_FUNCTION,
        _PARAM_CH_AUTO_FUNCTION: _ARISTON_CH_AUTO_FUNCTION,
        _PARAM_DHW_COMFORT_FUNCTION: _ARISTON_DHW_COMFORT_FUNCTION,
        _PARAM_SIGNAL_STRENGTH: _ARISTON_SIGNAL_STRENGHT,
        _PARAM_THERMAL_CLEANSE_CYCLE: _ARISTON_THERMAL_CLEANSE_CYCLE,
        _PARAM_CH_WATER_TEMPERATURE: _ARISTON_CH_WATER_TEMPERATURE,
        # _PARAM_DHW_COMFORT_TEMPERATURE: _ARISTON_DHW_TIME_PROG_COMFORT,
        # _PARAM_DHW_ECONOMY_TEMPERATURE: _ARISTON_DHW_TIME_PROG_ECONOMY,
        _PARAM_CH_FIXED_TEMP: _ARISTON_CH_FIXED_TEMP,
    }
    _LIST_ARISTON_API_PARAMS = [
        *_MAP_ARISTON_ZONE_0_PARAMS.keys(),
        *_MAP_ARISTON_MULTIZONE_PARAMS.keys(),
        _PARAM_DHW_FLAME,
    ]
    _LIST_ARISTON_WEB_PARAMS = [
        *_MAP_ARISTON_WEB_MENU_PARAMS.keys(),
    ]
    # Sensors in error request
    _LIST_ERROR_PARAMS = [
        _PARAM_ERRORS_COUNT
    ]
    # Sensors in CH schedule program
    _LIST_CH_PROGRAM_PARAMS = [
        _PARAM_CH_PROGRAM
    ]
    # Sensors in DHW schedule program
    _LIST_DHW_PROGRAM_PARAMS = [
        _PARAM_DHW_PROGRAM
    ]
    # Sensors in last month energy
    _LIST_LAST_MONTH = [
        _PARAM_CH_LAST_MONTH_GAS,
        _PARAM_CH_LAST_MONTH_ELECTRICITY,
        _PARAM_DHW_LAST_MONTH_GAS,
        _PARAM_DHW_LAST_MONTH_ELECTRICITY,
    ]
    # Energy data
    _LIST_ENERGY = [
        _PARAM_CH_ENERGY_TODAY,
        _PARAM_CH_ENERGY_YESTERDAY,
        _PARAM_DHW_ENERGY_TODAY,
        _PARAM_DHW_ENERGY_YESTERDAY,
        _PARAM_CH_ENERGY_LAST_7_DAYS,
        _PARAM_DHW_ENERGY_LAST_7_DAYS,
        _PARAM_CH_ENERGY_THIS_MONTH,
        _PARAM_CH_ENERGY_LAST_MONTH,
        _PARAM_DHW_ENERGY_THIS_MONTH,
        _PARAM_DHW_ENERGY_LAST_MONTH,
        _PARAM_CH_ENERGY_THIS_YEAR,
        _PARAM_CH_ENERGY_LAST_YEAR,
        _PARAM_DHW_ENERGY_THIS_YEAR,
        _PARAM_DHW_ENERGY_LAST_YEAR,
        _PARAM_CH_ENERGY2_TODAY,
        _PARAM_CH_ENERGY2_YESTERDAY,
        _PARAM_DHW_ENERGY2_TODAY,
        _PARAM_DHW_ENERGY2_YESTERDAY,
        _PARAM_CH_ENERGY2_LAST_7_DAYS,
        _PARAM_DHW_ENERGY2_LAST_7_DAYS,
        _PARAM_CH_ENERGY2_THIS_MONTH,
        _PARAM_CH_ENERGY2_LAST_MONTH,
        _PARAM_DHW_ENERGY2_THIS_MONTH,
        _PARAM_DHW_ENERGY2_LAST_MONTH,
        _PARAM_CH_ENERGY2_THIS_YEAR,
        _PARAM_CH_ENERGY2_LAST_YEAR,
        _PARAM_DHW_ENERGY2_THIS_YEAR,
        _PARAM_DHW_ENERGY2_LAST_YEAR,
        _PARAM_CH_ENERGY_DELTA_TODAY,
        _PARAM_CH_ENERGY_DELTA_YESTERDAY,
        _PARAM_DHW_ENERGY_DELTA_TODAY,
        _PARAM_DHW_ENERGY_DELTA_YESTERDAY,
        _PARAM_CH_ENERGY_DELTA_LAST_7_DAYS,
        _PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS,
        _PARAM_CH_ENERGY_DELTA_THIS_MONTH,
        _PARAM_CH_ENERGY_DELTA_LAST_MONTH,
        _PARAM_DHW_ENERGY_DELTA_THIS_MONTH,
        _PARAM_DHW_ENERGY_DELTA_LAST_MONTH,
        _PARAM_CH_ENERGY_DELTA_THIS_YEAR,
        _PARAM_CH_ENERGY_DELTA_LAST_YEAR,
        _PARAM_DHW_ENERGY_DELTA_THIS_YEAR,
        _PARAM_DHW_ENERGY_DELTA_LAST_YEAR,
    ]

    # reverse mapping of Android api to sensor names
    _MAP_ARISTON_API_TO_PARAM = {value:key for key, value in _MAP_ARISTON_ZONE_0_PARAMS.items()}
    for key, value in _MAP_ARISTON_MULTIZONE_PARAMS.items():
        _MAP_ARISTON_API_TO_PARAM[value] = key
    # reverse mapping of Web menu items to sensor names
    _MAP_ARISTON_WEB_TO_PARAM = {value:key for key, value in _MAP_ARISTON_WEB_MENU_PARAMS.items()}

    # List of all sensors
    _SENSOR_LIST = [
        *_LIST_ARISTON_API_PARAMS,
        *_LIST_ARISTON_WEB_PARAMS,
        *_LIST_ERROR_PARAMS,
        *_LIST_CH_PROGRAM_PARAMS,
        *_LIST_DHW_PROGRAM_PARAMS,
        *_LIST_LAST_MONTH,
        *_LIST_ENERGY,
        ]
    
    # List of sensors allowed to be changed
    _SENSOR_SET_LIST_TEMP = [
        _PARAM_MODE,
        _PARAM_CH_MODE,
        _PARAM_CH_SET_TEMPERATURE,
        _PARAM_CH_COMFORT_TEMPERATURE,
        _PARAM_CH_ECONOMY_TEMPERATURE,
        _PARAM_CH_AUTO_FUNCTION,
        _PARAM_CH_WATER_TEMPERATURE,
        _PARAM_CH_FIXED_TEMP,
        _PARAM_DHW_SET_TEMPERATURE,
        _PARAM_DHW_COMFORT_TEMPERATURE,
        _PARAM_DHW_ECONOMY_TEMPERATURE,
        _PARAM_DHW_COMFORT_FUNCTION,
        _PARAM_THERMAL_CLEANSE_CYCLE,
        _PARAM_THERMAL_CLEANSE_FUNCTION,
        _PARAM_INTERNET_TIME,
        _PARAM_INTERNET_WEATHER,
        _PARAM_DHW_MODE,
    ]

    @staticmethod
    def append_param(sensor, multizone_map, final_list):
        if sensor in multizone_map:
            for zone in range(1, 7):
                final_list.append(f'{sensor}_zone{zone}')
        else:
            final_list.append(sensor)

    _SENSOR_SET_LIST = []
    for sensor in _SENSOR_SET_LIST_TEMP:
        append_param(sensor, _MAP_ARISTON_MULTIZONE_PARAMS, _SENSOR_SET_LIST)

    # Requests
    _REQUEST_MAIN = "main"
    _REQUEST_CH_SCHEDULE = "ch_schedule"
    _REQUEST_DHW_SCHEDULE = "dhw_schedule"
    _REQUEST_ERRORS = "errors"
    _REQUEST_ADDITIONAL = "additional_params"
    _REQUEST_LAST_MONTH = "last_month"
    _REQUEST_ENERGY = "energy"

    maim_sensors_list = []
    for sensor in _LIST_ARISTON_API_PARAMS:
        append_param(sensor, _MAP_ARISTON_MULTIZONE_PARAMS, maim_sensors_list)
    
    # Mapping of sensors to requests
    _MAP_REQUEST = {
        _REQUEST_MAIN: maim_sensors_list,
        _REQUEST_ADDITIONAL: _LIST_ARISTON_WEB_PARAMS,
        _REQUEST_CH_SCHEDULE: _LIST_CH_PROGRAM_PARAMS,
        _REQUEST_DHW_SCHEDULE: _LIST_DHW_PROGRAM_PARAMS,
        _REQUEST_ERRORS: _LIST_ERROR_PARAMS,
        _REQUEST_ENERGY: _LIST_ENERGY,
        _REQUEST_LAST_MONTH: _LIST_LAST_MONTH,
    }

    _MAP_SENSOR_TO_REQUEST = {}
    for request, sensor_list in _MAP_REQUEST.items():
        for sensor in sensor_list:
            _MAP_SENSOR_TO_REQUEST[sensor] = request

    # Priority lists of requests (first list is High prio and second is Low prio)
    _REQUESTS_SEQUENCE = [
        [
            _REQUEST_MAIN,
            _REQUEST_ADDITIONAL,
            _REQUEST_ERRORS
        ],
        [
            _REQUEST_CH_SCHEDULE,
            _REQUEST_DHW_SCHEDULE,
            _REQUEST_LAST_MONTH,
            _REQUEST_ENERGY
        ]
    ]

    # Keys used in structures
    _VALUE = 'value'
    _SET_VALUE = "set_value"
    _UNITS = 'units'
    _MIN = 'min'
    _MAX = 'max'
    _STEP = 'step'
    _OPTIONS = 'options'
    _OPTIONS_TXT = 'options_text'
    _ATTRIBUTES = "attributes"
    _ATTEMPT = "attempt"

    # Values data for data mapping from received data to readable format
    _WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
    _ON = "ON"
    _OFF = "OFF"
    _OFF_ON_NUMERAL = [0, 1]
    _OFF_ON_TEXT = [_OFF, _ON]
    _UNIT_KWH = 'kWh'

    _LOGGER = logging.getLogger(__name__)


    def _get_request_for_parameter(self, sensor):
        return self._MAP_SENSOR_TO_REQUEST[sensor]

    def _zone_sensor_name(self, sensor, zone):
        if sensor in self._MAP_ARISTON_MULTIZONE_PARAMS:
            return f'{sensor}_zone{zone}'
        else:
            return sensor

    def _zone_sensor_split(self, sensor):
        match = re.search('_zone[1-9]$', sensor)
        if match:
            origial = re.sub('_zone[1-9]$', '', sensor)
            if origial in self._MAP_ARISTON_MULTIZONE_PARAMS:
                return origial, int(sensor[-1])
        return sensor, 0

    def _reset_sensor(self, sensor):
        self._ariston_sensors[sensor] = dict()
        self._ariston_sensors[sensor][self._VALUE] = None
        self._ariston_sensors[sensor][self._UNITS] = None
        self._ariston_sensors[sensor][self._MIN] = None
        self._ariston_sensors[sensor][self._MAX] = None
        self._ariston_sensors[sensor][self._STEP] = None
        self._ariston_sensors[sensor][self._OPTIONS] = None
        self._ariston_sensors[sensor][self._OPTIONS_TXT] = None
        self._ariston_sensors[sensor][self._ATTRIBUTES] = {}


    def __init__(self,
                 username: str,
                 password: str,
                 sensors: list = None,
                 logging_level: str = _LEVEL_NOTSET,
                 period_get_request: int = _GET_SENSORS_PERIOD_SECONDS,
                 period_set_request: int = _SET_SENSORS_PERIOD_SECONDS,
                 set_max_retries: int = _MAX_RETRIES,
                 gw: str = "",
                 ) -> None:
        """
        Initialize API.
        """
        if sensors is None:
            sensors = list()

        if not isinstance(sensors, list):
            raise Exception("Invalid sensors type")

        if logging_level not in self._LOGGING_LEVELS:
            raise Exception("Invalid logging_level")

        if not isinstance(period_get_request, (int, float)) or period_get_request < self._GET_SENSORS_PERIOD_SECONDS:
            raise Exception(f"Period to get sensors must be a number higher than {self._GET_SENSORS_PERIOD_SECONDS}")

        if not isinstance(period_set_request, (int, float)) or period_set_request < self._SET_SENSORS_PERIOD_SECONDS:
            raise Exception(f"Period to set sensors must be a number higher than {self._SET_SENSORS_PERIOD_SECONDS}")

        if not isinstance(set_max_retries, int) or set_max_retries < 1:
            raise Exception(f"At least 1 retry to set data is expected")

        """
        Logging settings
        """
        self._logging_level = logging.getLevelName(logging_level)
        self._LOGGER.setLevel(self._logging_level)
        self._console_handler = logging.StreamHandler()
        self._console_handler.setLevel(self._logging_level)
        self._formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        self._console_handler.setFormatter(self._formatter)
        self._LOGGER.addHandler(self._console_handler)

        if sensors:
            for sensor in sensors:
                if sensor not in self._SENSOR_LIST:
                    self._LOGGER.warning(f"Unsupported sensor {sensor}")
                    sensors.remove(sensor)

        self._default_gw = gw
        self._user = username
        self._password = password
        self._get_period_time = period_get_request
        self._set_period_time = period_set_request
        self._max_set_retries = set_max_retries

        # clear read sensor values
        self._ariston_sensors = dict()
        self._subscribed_sensors_old_value = dict()
        for sensor in self._SENSOR_LIST:
            if sensor in self._MAP_ARISTON_MULTIZONE_PARAMS:
                for zone in range(1, 7):
                    ch_sensor = self._zone_sensor_name(sensor, zone=zone)
                    self._reset_sensor(ch_sensor)
                    self._subscribed_sensors_old_value[ch_sensor] = None
            else:
                self._reset_sensor(sensor)
                self._subscribed_sensors_old_value[sensor] = None
        
        # clear configuration data
        self._set_param = {}
        self._features = {}
        self._main_data = {}
        self._additional_data = {}
        self._error_data = {}
        self._ch_schedule_data = {}
        self._dhw_schedule_data = {}
        self._last_month_data = {}
        self._energy_use_data = {}
        self._zones = []

        self._last_dhw_storage_temp = None
        self._reset_set_requests()

        # initiate all other data
        self._errors = 0
        self._data_lock = threading.Lock()
        self._lock = threading.Lock()
        self._plant_id_lock = threading.Lock()
        self._session = requests.Session()
        self._login = False
        self._plant_id = ""
        self._started = False
        self._available = False
        self._ch_available = False
        self._dhw_available = False
        self._changing_data = False
        self._timer_periodic_read = threading.Timer(0, self._queue_get_data)
        self._timer_queue_delay = threading.Timer(0, self._control_availability_state, [self._REQUEST_MAIN])
        self._timer_set_delay = threading.Timer(0, self._preparing_setting_http_data)

        self._other_parameters = []
        for sensor in self._LIST_ARISTON_WEB_PARAMS:
            if sensor in sensors:
                self._other_parameters.append(self._MAP_ARISTON_WEB_MENU_PARAMS[sensor])
        
        # List of requests. First list is high priority requests and second list is low priority requests.
        # It affects frequency of the requests
        self._requests_lists = copy.deepcopy(self._REQUESTS_SEQUENCE)

        # If no sensors specified then no need to send the requests thus increasing frequency of fetching data for wanted sensors
        for request, sensor_list in self._MAP_REQUEST.items():
            if request != self._REQUEST_MAIN:
                # Main requests cannot be removed
                if not any(item in sensors for item in sensor_list):
                    if request in self._requests_lists[0]:
                        self._requests_lists[0].remove(request)
                    if request in self._requests_lists[1]:
                        self._requests_lists[1].remove(request)

        # At least 1 main request is present
        self._last_request = self._requests_lists[0][-1]
        if self._requests_lists[1]:
            # there are requests in low prio
            self._last_request_low_prio = self._requests_lists[1][-1]
        else:
            # no requests in low prio
            self._last_request_low_prio = None

        self._subscribed = list()
        self._subscribed_args = list()
        self._subscribed_kwargs = list()
        self._subscribed_thread = None

        self._subscribed2 = list()
        self._subscribed2_args = list()
        self._subscribed2_kwargs = list()
        self._subscribed2_thread = None

        self._LOGGER.info("API initiated")


    def subscribe_sensors(self, func, *args, **kwargs):
        """
        Subscribe to change of sensors value in:
            - sensor_values

        Function will be called when sensors' values are being changed.
        Actual changed values are being returned as a dictionary in a first argument.
        """
        self._subscribed.append(func)
        self._subscribed_args.append(args)
        self._subscribed_kwargs.append(kwargs)


    def subscribe_statuses(self, func, *args, **kwargs):
        """
        Subscribe to change of API statuses such as:
            - available
            - ch_available
            - dhw_available
            - setting_data

        Function will be called when statuses are being changed.
        Changed property names shall be returned as a dictionary in a first argument.
        """
        self._subscribed2.append(func)
        self._subscribed2_args.append(args)
        self._subscribed2_kwargs.append(kwargs)


    def _subscribers_sensors_inform(self):
        """
        Inform subscribers about changed sensors
        first argument is a dictionary of changed sensors
        """

        changed_data = dict()

        for sensor in self._ariston_sensors:
            if self._ariston_sensors[sensor][self._VALUE] != self._subscribed_sensors_old_value[sensor]:
                self._subscribed_sensors_old_value[sensor] = self._ariston_sensors[sensor][self._VALUE]
                changed_data[sensor] = copy.deepcopy(self._ariston_sensors[sensor])

        if changed_data:
            for iteration in range(len(self._subscribed)):
                self._subscribed_thread = threading.Timer(
                    self._TIME_SPLIT, self._subscribed[iteration], args=(changed_data, *self._subscribed_args[iteration]), kwargs=self._subscribed_kwargs[iteration])
                self._subscribed_thread.start()


    def _subscribers_statuses_inform(self):
        """Inform subscribers about changed API statuses"""
        old_available = self._available
        old_ch_available = self._ch_available
        old_dhw_available = self._dhw_available
        old_changing = self._changing_data

        changed_data = dict()

        self._available = self._errors <= self._MAX_ERRORS and self._login and self._plant_id != "" and self._main_data != {}

        if self._available and self._main_data != {} and \
            self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_SET_TEMPERATURE, 1)][self._VALUE] != None:
            self._ch_available = True
        else:
            self._ch_available = False

        if self._available and self._main_data != {} and \
            self._ariston_sensors[self._PARAM_DHW_SET_TEMPERATURE][self._VALUE] != None:
            self._dhw_available = True
        else:
            self._dhw_available = False

        if self._set_param != {}:
            self._changing_data = True
        else:
            self._changing_data = False

        if old_available != self._available:
            changed_data['available'] = self._available

        if old_ch_available != self._ch_available:
            changed_data['ch_available'] = self._ch_available

        if old_dhw_available != self._dhw_available:
            changed_data['dhw_available'] = self._dhw_available

        if old_changing != self._changing_data:
            changed_data['setting_data'] = self._changing_data

        if changed_data:
            for iteration in range(len(self._subscribed2)):
                self._subscribed2_thread = threading.Timer(
                    self._TIME_SPLIT, self._subscribed2[iteration], args=(changed_data, *self._subscribed2_args[iteration]), kwargs=self._subscribed2_kwargs[iteration])
                self._subscribed2_thread.start()


    def _json_validator(self, data, request_type):
        json_data = data.json()
        try:
            if isinstance(json_data, dict):
                if json_data == {}:
                    return False
                else:
                    return True
            if isinstance(json_data, list):
                if not json_data:
                    if request_type in (self._REQUEST_ERRORS):
                        return True
                    else:
                        return False
                else:
                    for item in json_data:
                        if not isinstance(item, dict):
                            return False
                    return True
            else:
                return False
        except KeyError:
            return False


    @classmethod
    def api_data(cls):
        """
        Get API data as a tuple:
          - API version
          - supported sensors by API (actual list of supported sensors by the model cannot be identified and must be chosen manually)
          - supported parameters to be changed by API (actual list of supported parameters by the model cannot be identified and must be chosen manually)
        """
        return cls._VERSION, cls._SENSOR_LIST, cls._SENSOR_SET_LIST


    @property
    def plant_id(self) -> str:
        """Return the unique plant_id."""
        return self._plant_id


    @property
    def available(self) -> bool:
        """Return if Aristons's API is responding."""
        return self._available


    @property
    def ch_available(self) -> bool:
        """Return if Aristons's API is responding and if there is data available for the CH."""
        return self._ch_available


    @property
    def dhw_available(self) -> bool:
        """Return if Aristons's API is responding and if there is data available for the DHW."""
        return self._dhw_available


    @property
    def version(self) -> str:
        """Return version of the API in use."""
        return self._VERSION


    @property
    def sensor_values(self) -> dict:
        """
        Return dictionary of sensors and their values.

        'value' key is used to fetch value of the specific sensor/parameter.
        Some sensors/parameters might return dictionaries.

        'units' key is used to fetch units of measurement for specific sensor/parameter.

        """
        return copy.deepcopy(self._ariston_sensors)


    @property
    def setting_data(self) -> bool:
        """Return if setting of data is in progress."""
        return self._changing_data


    @property
    def supported_sensors_get(self) -> set:
        """
        Return set of all supported sensors/parameters in API.
        Note that it is sensors supported by API, not the server, so some might never have valid values.
        """
        return copy.deepcopy(self._SENSOR_LIST)


    @property
    def supported_sensors_set(self) -> set:
        """
        Return set of all parameters that potentially can be set by API.
        Note that it is parameters supported by API, not the server, so some might be impossible to be set.
        use property 'supported_sensors_set_values' to find allowed values to be set.
        """
        return copy.deepcopy(self._SENSOR_SET_LIST)


    @property
    def supported_sensors_set_values(self) -> dict:
        """
        Return dictionary of sensors/parameters to be set and allowed values.
        Allowed values can be returned as:
            - dictionary with following keys:
                - 'min' is used to indicate minimum value in the range;
                - 'max' is used to indicate maximum value in the range;
                - 'step' is used to indicate step;
                - 'options' is list of options numeric;
                - 'options_text' is list of options literal;

        data from this property is used for 'set_http_data' method.
        """
        sensors_dictionary = copy.deepcopy(self._ariston_sensors)
        for parameter in self._ariston_sensors:
            if parameter in self._SENSOR_SET_LIST:
                del sensors_dictionary[parameter][self._VALUE]
                del sensors_dictionary[parameter][self._UNITS]
                del sensors_dictionary[parameter][self._ATTRIBUTES]
            else:
                del sensors_dictionary[parameter]
        return sensors_dictionary


    def _request_post(self, url, json_data, timeout=_TIMEOUT_MIN, error_msg=''):
        """ post request """
        try:
            resp = self._session.post(
                url,
                timeout=timeout,
                json=json_data,
                verify=True)
        except requests.exceptions.RequestException as ex:
            self._LOGGER.warning(f'{error_msg} exception: {ex}')
            raise Exception(f'{error_msg} exception: {ex}')
        if not resp.ok:
            self._LOGGER.warning(f'{error_msg} reply code: {resp.status_code}')
            self._LOGGER.warning(f'{resp.text}')
            raise Exception(f'{error_msg} reply code: {resp.status_code}')
        return resp


    def _request_get(self, url, timeout=_TIMEOUT_MIN, error_msg='', ignore_errors=False):
        try:
            resp = self._session.get(
                url,
                timeout=timeout,
                verify=True)
        except requests.exceptions.RequestException as ex:
            self._LOGGER.warning(f'{error_msg} exception: {ex}')
            if not ignore_errors:
                raise Exception(f'{error_msg} exception: {ex}')
        if not resp.ok:
            log_text = True
            if resp.status_code == 500:
                # Unsupported additional parameters are visible in the HTML reply, remove them if available
                for re_string in re.findall('Violated Postcondition.*menu', resp.text):
                    for sensor, menu_item in self._MAP_ARISTON_WEB_MENU_PARAMS.items():
                        html_item = menu_item.replace('U','').replace('_','.')
                        check_menu = f"&quot;{html_item}&quot;"
                        if check_menu in re_string:
                            self._LOGGER.error(f'Unsupported sensor {sensor} detected with menu item {menu_item}')
                            self._other_parameters.remove(menu_item)
                            if not self._other_parameters:
                                self._requests_lists[0].remove(self._REQUEST_ADDITIONAL)
                            log_text = False
            self._LOGGER.warning(f'{error_msg} reply code: {resp.status_code}')
            if log_text:
                self._LOGGER.warning(f'{resp.text}')
            if not ignore_errors:
                raise Exception(f'{error_msg} reply code: {resp.status_code}')
        return resp


    def _login_session(self):
        """Login to fetch Ariston Plant ID and confirm login"""
        if not self._login and self._started:
            # First login
            login_data = {
                "email": self._user,
                "password": self._password,
                "rememberMe": False,
                "language": "English_Us"
                }
            self._request_post(
                url=f'{self._ARISTON_URL}/R2/Account/Login?returnUrl=%2FR2%2FHome',
                json_data=login_data,
                error_msg='Login'
            )

            # Fetch plant IDs
            resp = self._request_get(
                url=f'{self._ARISTON_URL}/api/v2/remote/plants/lite',
                error_msg='Gateways'
            )
            gateways = [item['gwId'] for item in resp.json()]
            if self._default_gw:
                if self._default_gw not in gateways:
                    self._LOGGER.error(f'Specified gateway {self._default_gw} not found in {gateways}')
                    raise Exception(f'Specified gateway {self._default_gw} not found in {gateways}')
                else:
                    plant_id = self._default_gw
            else:
                if len(gateways) == 0:
                    self._LOGGER.error(f'At least one gateway is expected to be found')
                    raise Exception(f'At least one gateway is expected to be found')
                # Use first plant plant id
                plant_id = gateways[0]
            resp = self._request_get(
                url=f'{self._ARISTON_URL}/api/v2/remote/plants/{plant_id}/features?eagerMode=True',
                error_msg='Features'
            )
            features = resp.json()
            if plant_id:
                with self._plant_id_lock:
                    self._features = copy.deepcopy(features)
                    if self._features["zones"]:
                        self._zones = [item["num"] for item in self._features["zones"]]
                    self._plant_id = plant_id
                    self._gw_name = plant_id + '_'
                    self._login = True
                    self._LOGGER.info(f'Plant ID is {self._plant_id}')
        return


    def _get_visible_sensor_value(self, sensor):
        value = self._get_sensor_value(sensor)
        if sensor in self._set_param:
            if value == self._set_param[sensor][self._VALUE]:
                # Value is assumed to be set
                del self._set_param[sensor]
                self._subscribers_statuses_inform()
                self._reset_set_requests()
                self._LOGGER.debug(f'Sensor {sensor} value {value} matches expected set value')
            else:
                self._LOGGER.debug(f'Sensor {sensor} expected value {self._set_param[sensor][self._VALUE]} but actual value is {value}')
                value = self._set_param[sensor][self._VALUE]
        return value


    def _get_sensor_value(self, sensor):
        value = None
        request_type = self._get_request_for_parameter(sensor)
        if request_type == self._REQUEST_MAIN:
            original_sensor, zone = self._zone_sensor_split(sensor)
            for item in self._main_data["items"]:
                if original_sensor == self._MAP_ARISTON_API_TO_PARAM[item["id"]] and zone == item["zone"]:
                    value = item["value"]
                    if "options" in item:
                        use_index = item["options"].index(int(item["value"]))
                        if "optTexts" in item:
                            value = item["optTexts"][use_index]
                        elif item["options"] == self._OFF_ON_NUMERAL:
                            value = self._OFF_ON_TEXT[use_index]
                    break
        elif request_type == self._REQUEST_ADDITIONAL:
            for item in self._additional_data["data"]:
                if sensor == self._MAP_ARISTON_WEB_TO_PARAM[item["id"]]:
                    value = item["value"]
                    if "dropDownOptions" in item and item["dropDownOptions"]:
                        for option in item["dropDownOptions"]:
                            if option["value"] == item["value"]:
                                value = option["text"]
                                break
                    break
        if sensor == self._PARAM_DHW_FLAME:
            value = None
            try:
                increase_dhw_temp = None
                new_value = self._ariston_sensors[self._PARAM_DHW_STORAGE_TEMPERATURE][self._VALUE]
                if new_value:
                    increase_dhw_temp = False
                    if self._last_dhw_storage_temp is not None and \
                        self._last_dhw_storage_temp > 0 and \
                        new_value > 0:
                        if new_value > self._last_dhw_storage_temp:
                            increase_dhw_temp = True
                    elif self._last_dhw_storage_temp is not None and\
                        new_value > 0:
                        self._last_dhw_storage_temp = new_value
            except Exception:
                increase_dhw_temp = None
            ch_flame = None
            for zone in self._zones:
                ch_flame_zone = self._ariston_sensors[self._zone_sensor_name(self._PARAM_FLAME, zone)][self._VALUE]
                if ch_flame_zone in self._OFF_ON_TEXT:
                    if ch_flame is None or ch_flame == self._OFF:
                        ch_flame = ch_flame_zone
            if self._ariston_sensors[self._PARAM_FLAME][self._VALUE] in self._OFF_ON_TEXT and ch_flame in self._OFF_ON_TEXT:
                if self._ariston_sensors[self._PARAM_FLAME][self._VALUE] == self._OFF:
                    value = self._OFF
                elif ch_flame == self._OFF:
                    value = self._ON
                else:
                    # Unknown state of DHW
                    value = increase_dhw_temp
        return value


    def _schedule_attributes(self, scan_dictionary):
        attributes = {key: None for key in self._WEEKDAYS}
        for item in scan_dictionary:
            time_slices = []
            for slice in item["slices"]:
                if slice['temp'] == 0:
                    temp_name = "Economy"
                else:
                    temp_name = "Comfort"
                time_slices.append(f'From {slice["from"]//60:02}:{slice["from"]%60:02} {temp_name}')
            for day_num in item["days"]:
                attributes[self._WEEKDAYS[day_num]] = time_slices
        return attributes


    def _store_data(self, resp, request_type=""):
        """Store received dictionary"""
        if not self._json_validator(resp, request_type):
            self._LOGGER.warning(f"JSON did not pass validation for the request {request_type}")
            raise Exception(f"JSON did not pass validation for the request {request_type}")

        if request_type == self._REQUEST_MAIN:

            self._main_data = copy.deepcopy(resp.json())
            for item in self._main_data["items"]:
                try:
                    original_sensor = self._MAP_ARISTON_API_TO_PARAM[item["id"]]
                    zone = item["zone"]
                    sensor = self._zone_sensor_name(original_sensor, zone=zone)
                    self._ariston_sensors[sensor]
                    try:
                        self._ariston_sensors[sensor][self._VALUE] = self._get_visible_sensor_value(sensor)
                        if "min" in item:
                            self._ariston_sensors[sensor][self._MIN] = item["min"]
                        if "max" in item:
                            self._ariston_sensors[sensor][self._MAX] = item["max"]
                        if "step" in item:
                            self._ariston_sensors[sensor][self._STEP] = item["step"]
                        if "unit" in item and item["unit"]:
                            self._ariston_sensors[sensor][self._UNITS] = item["unit"]
                        if "options" in item:
                            self._ariston_sensors[sensor][self._OPTIONS] = copy.deepcopy(item["options"])
                            if "optTexts" in item:
                                self._ariston_sensors[sensor][self._OPTIONS_TXT] = copy.deepcopy(item["optTexts"])
                            elif item["options"] == self._OFF_ON_NUMERAL:
                                self._ariston_sensors[sensor][self._OPTIONS_TXT] = self._OFF_ON_TEXT
                    except Exception as ex:
                        self._LOGGER.warn(f"Issue reading {request_type} {sensor} {ex}")
                        self._reset_sensor(sensor)
                        continue
                except Exception as ex:
                    self._LOGGER.warn(f'Issue reading {request_type} {item["id"]}, {ex}')
                    continue

            # Extrapolate DHW Flame
            sensor = self._PARAM_DHW_FLAME
            dhw_flame = self._get_visible_sensor_value(sensor)
            self._ariston_sensors[sensor][self._VALUE] = dhw_flame
            if dhw_flame:
                self._ariston_sensors[sensor][self._OPTIONS] = self._OFF_ON_NUMERAL
                self._ariston_sensors[sensor][self._OPTIONS_TXT] = self._OFF_ON_TEXT
            else:
                self._ariston_sensors[sensor][self._OPTIONS] = None
                self._ariston_sensors[sensor][self._OPTIONS_TXT] = None

            # Fix min and Max for CH set temperature
            for zone in self._zones:
                self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_SET_TEMPERATURE, zone)][self._MIN] = \
                    self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone)][self._MIN]
                self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_SET_TEMPERATURE, zone)][self._MAX] = \
                    self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone)][self._MAX]
                self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_SET_TEMPERATURE, zone)][self._STEP] = \
                    self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone)][self._STEP]

        elif request_type == self._REQUEST_ERRORS:

            self._error_data = copy.deepcopy(resp.json())
            sensor = self._PARAM_ERRORS_COUNT
            try:
                # TEST DATA BELOW FOR PARSING PURPOSES
                # self._error_data = [{"gw":"F0AD4E0590BD","timestamp":"2022-07-14T10:55:04","fault":45,"mult":0,"code":"501","pri":1053500,"errDex":"No flame detected","res":False,"blk":True}]
                self._ariston_sensors[sensor][self._VALUE] = len(self._error_data)
                attributes = {}
                for index, item in enumerate(self._error_data):
                    attributes[f'Error_{index+1}'] = f'{item["timestamp"]}, {item["errDex"]}'
                self._ariston_sensors[sensor][self._ATTRIBUTES] = attributes
            except Exception as ex:
                self._LOGGER.warn(f'Issue reading {request_type} {sensor}, {ex}')
                self._reset_sensor(sensor)

        elif request_type == self._REQUEST_CH_SCHEDULE:

            self._ch_schedule_data = copy.deepcopy(resp.json())
            sensor = self._PARAM_CH_PROGRAM
            try:
                self._ariston_sensors[sensor][self._VALUE] = "Available"
                self._ariston_sensors[sensor][self._ATTRIBUTES] = self._schedule_attributes(self._ch_schedule_data["ChZn1"]["plans"])
            except Exception as ex:
                self._LOGGER.warn(f'Issue reading {request_type} {sensor}, {ex}')
                self._reset_sensor(sensor)

        elif request_type == self._REQUEST_DHW_SCHEDULE:

            self._dhw_schedule_data = copy.deepcopy(resp.json())
            sensor = self._PARAM_DHW_PROGRAM
            try:
                self._ariston_sensors[sensor][self._VALUE] = "Available"
                self._ariston_sensors[sensor][self._ATTRIBUTES] = self._schedule_attributes(self._dhw_schedule_data["Dhw"]["plans"])
            except Exception as ex:
                self._LOGGER.warn(f'Issue reading {request_type} {sensor}, {ex}')
                self._reset_sensor(sensor)

        elif request_type == self._REQUEST_ADDITIONAL:
            
            self._additional_data = copy.deepcopy(resp.json())
            for item in self._additional_data["data"]:
                try:
                    sensor = self._MAP_ARISTON_WEB_TO_PARAM[item["id"]]
                    self._ariston_sensors[sensor]
                    try:
                        self._ariston_sensors[sensor][self._VALUE] = self._get_visible_sensor_value(sensor)
                        if "min" in item:
                            self._ariston_sensors[sensor][self._MIN] = item["min"]
                        if "max" in item:
                            self._ariston_sensors[sensor][self._MAX] = item["max"]
                        if "increment" in item:
                            self._ariston_sensors[sensor][self._STEP] = item["increment"]
                        if "unitLabel" in item and item["unitLabel"]:
                            self._ariston_sensors[sensor][self._UNITS] = item["unitLabel"]
                        if "dropDownOptions" in item and item["dropDownOptions"]:
                            self._ariston_sensors[sensor][self._OPTIONS] = [option["value"] for option in item["dropDownOptions"]]
                            self._ariston_sensors[sensor][self._OPTIONS_TXT] = [option["text"] for option in item["dropDownOptions"]]
                    except Exception as ex:
                        self._LOGGER.warn(f"Issue reading {request_type} {sensor} {ex}")
                        self._reset_sensor(sensor)
                        continue
                except Exception as ex:
                    self._LOGGER.warn(f'Issue reading {request_type} {item["id"]}, {ex}')
                    continue

        elif request_type == self._REQUEST_LAST_MONTH:

            self._last_month_data = copy.deepcopy(resp.json())
            self._reset_sensor(self._PARAM_CH_LAST_MONTH_GAS)
            self._reset_sensor(self._PARAM_CH_LAST_MONTH_ELECTRICITY)
            self._reset_sensor(self._PARAM_DHW_LAST_MONTH_GAS)
            self._reset_sensor(self._PARAM_DHW_LAST_MONTH_ELECTRICITY)
            for item in self._last_month_data["LastMonth"]:
                try:
                    if item["use"] == 1:
                        if "gas" in item:
                            sensor = self._PARAM_CH_LAST_MONTH_GAS
                            self._ariston_sensors[sensor][self._VALUE] = item["gas"]
                            self._ariston_sensors[sensor][self._UNITS] = self._UNIT_KWH
                        if "elect" in item:
                            sensor = self._PARAM_CH_LAST_MONTH_ELECTRICITY
                            self._ariston_sensors[sensor][self._VALUE] = item["elect"]
                            self._ariston_sensors[sensor][self._UNITS] = self._UNIT_KWH
                    if item["use"] == 2:
                        if "gas" in item:
                            sensor = self._PARAM_DHW_LAST_MONTH_GAS
                            self._ariston_sensors[sensor][self._VALUE] = item["gas"]
                            self._ariston_sensors[sensor][self._UNITS] = self._UNIT_KWH
                        if "elect" in item:
                            sensor = self._PARAM_DHW_LAST_MONTH_ELECTRICITY
                            self._ariston_sensors[sensor][self._VALUE] = item["elect"]
                            self._ariston_sensors[sensor][self._UNITS] = self._UNIT_KWH
                except Exception as ex:
                    self._LOGGER.warn(f'Issue reading {request_type} {item["use"]} for last month, {ex}')
                    continue

        elif request_type == self._REQUEST_ENERGY:

            if self._energy_use_data:
                # old values are available
                sum_energy_old = 0
                sum_energy_new = 0
                for item in self._energy_use_data:
                    sum_energy_old += sum(item['v'])
                for item in resp.json():
                    sum_energy_new += sum(item['v'])
                if sum_energy_old > 0 and sum_energy_new == 0:
                    # if non-zero values are present and new value is zero - ignore it 
                    return

            self._energy_use_data = copy.deepcopy(resp.json())
            this_month = datetime.date.today().month
            this_year = datetime.date.today().year
            this_day = datetime.date.today().day
            this_day_week = datetime.date.today().weekday()
            this_hour = datetime.datetime.now().hour
            CH_ENERGY = 7
            DHW_ENERGY = 10
            CH_ENERGY2 = 1
            DHW_ENERGY2 = 2
            CH_ENERGY_DELTA = 20
            DHW_ENERGY_DELTA = 21
            # 2hour during scanning is decreased by 2 at the beginning
            if this_hour % 2 == 1:
                # odd value means we calculate even value and add 2 hours due to following decrease
                this_2hour = (this_hour // 2) * 2 + 2
            else:
                # we assume that previous 2 hours would be used
                this_2hour = this_hour + 2
            try:
                (
                    self._ariston_sensors[self._PARAM_CH_ENERGY_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    CH_ENERGY,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_CH_ENERGY_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for CH, {ex}')
                self._reset_sensor(self._PARAM_CH_ENERGY_TODAY)
                self._reset_sensor(self._PARAM_CH_ENERGY_YESTERDAY)
                self._reset_sensor(self._PARAM_CH_ENERGY_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_CH_ENERGY_THIS_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY_LAST_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY_THIS_YEAR)
                self._reset_sensor(self._PARAM_CH_ENERGY_LAST_YEAR)
            try:
                (
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    DHW_ENERGY,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for DHW, {ex}')
                self._reset_sensor(self._PARAM_DHW_ENERGY_TODAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY_YESTERDAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_DHW_ENERGY_THIS_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY_LAST_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY_THIS_YEAR)
                self._reset_sensor(self._PARAM_DHW_ENERGY_LAST_YEAR)
            try:
                (
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    CH_ENERGY2,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY2_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for CH 2, {ex}')
                self._reset_sensor(self._PARAM_CH_ENERGY2_TODAY)
                self._reset_sensor(self._PARAM_CH_ENERGY2_YESTERDAY)
                self._reset_sensor(self._PARAM_CH_ENERGY2_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_CH_ENERGY2_THIS_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY2_LAST_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY2_THIS_YEAR)
                self._reset_sensor(self._PARAM_CH_ENERGY2_LAST_YEAR)
            try:
                (
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    DHW_ENERGY2,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY2_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for DHW 2, {ex}')
                self._reset_sensor(self._PARAM_DHW_ENERGY2_TODAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_YESTERDAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_THIS_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_LAST_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_THIS_YEAR)
                self._reset_sensor(self._PARAM_DHW_ENERGY2_LAST_YEAR)
            try:
                (
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    CH_ENERGY_DELTA,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_CH_ENERGY_DELTA_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for CH 2, {ex}')
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_TODAY)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_YESTERDAY)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_THIS_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_LAST_MONTH)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_THIS_YEAR)
                self._reset_sensor(self._PARAM_CH_ENERGY_DELTA_LAST_YEAR)
            try:
                (
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_TODAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_YESTERDAY][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_MONTH][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_YEAR][self._VALUE],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_TODAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_YESTERDAY][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_MONTH][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_YEAR][self._ATTRIBUTES],
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_YEAR][self._ATTRIBUTES],
                    found_key,
                ) = self._get_energy_data(
                    DHW_ENERGY_DELTA,
                    this_year=this_year,
                    this_month=this_month,
                    this_day=this_day,
                    this_day_week=this_day_week,
                    this_2hour=this_2hour)
                if found_key:
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_TODAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_YESTERDAY][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_MONTH][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_THIS_YEAR][self._UNITS] = self._UNIT_KWH
                    self._ariston_sensors[self._PARAM_DHW_ENERGY_DELTA_LAST_YEAR][self._UNITS] = self._UNIT_KWH
            except Exception as ex:
                self._LOGGER.warn(f'Issue handling energy used for DHW 2, {ex}')
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_TODAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_YESTERDAY)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_LAST_7_DAYS)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_THIS_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_LAST_MONTH)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_THIS_YEAR)
                self._reset_sensor(self._PARAM_DHW_ENERGY_DELTA_LAST_YEAR)

        self._subscribers_sensors_inform()


    def _get_energy_data(self, k_num, this_year, this_month, this_day, this_day_week, this_2hour):
        energy_today = 0
        energy_yesterday = 0
        energy_last_7_days = 0
        energy_this_month = 0
        energy_last_month = 0
        energy_this_year = 0
        energy_last_year = 0
        energy_today_attr = {}
        energy_yesterday_attr = {}
        energy_last_7_days_attr = {}
        energy_this_month_attr = {}
        energy_last_month_attr = {}
        energy_this_year_attr = {}
        energy_last_year_attr = {}
        hour_text = "{}_{}_{:02}_{:02}"
        weekday_text = "{}_{}_{:02}_{}"
        month_text = "{}_{}_{:02}"
        year_text = "{}_{}"
        found_key = False
        for item in self._energy_use_data:
            if item["k"] == k_num:
                found_key = True
                scan_month = this_month
                scan_year = this_year
                scan_day = this_day
                scan_day_week = this_day_week
                scan_2hour = this_2hour
                scan_break = 0
                if item['p'] == 1:
                    prev_day, prev_month, prev_year, _ = self._get_prev_day(day=this_day, month=this_month, year=this_year, scan_break=0)
                    prev_day_2, prev_month_2, prev_year_2, _ = self._get_prev_day(day=prev_day, month=prev_month, year=prev_year, scan_break=0)
                    use_day, use_month, use_year = this_day, this_month, this_year
                    if this_2hour == 2:
                        midnight = True
                    else:
                        midnight = False
                    for value in reversed(item['v']):
                        scan_2hour, scan_break = self._get_prev_hour(hour=scan_2hour, scan_break=scan_break)
                        if midnight and scan_break == 1:
                            # ignore first break
                            scan_break = 0
                            use_day, use_month, use_year = prev_day, prev_month, prev_year
                            prev_day, prev_month, prev_year = prev_day_2, prev_month_2, prev_year_2
                            midnight = False
                        if scan_break == 0:
                            energy_today_attr[hour_text.format(use_year, calendar.month_abbr[use_month], use_day, scan_2hour)] = value
                            energy_today += value
                        elif scan_break == 1:
                            energy_yesterday_attr[hour_text.format(prev_year, calendar.month_abbr[prev_month], prev_day, scan_2hour)] = value
                            energy_yesterday += value
                if item['p'] == 2:
                    for value in reversed(item['v']):
                        scan_day, scan_month, scan_year, _ = self._get_prev_day(day=scan_day, month=scan_month, year=scan_year, scan_break=0)
                        scan_day_week = self._get_prev_day_week(day=scan_day_week)
                        energy_last_7_days_attr[weekday_text.format(scan_year, calendar.month_abbr[scan_month], scan_day, calendar.day_abbr[scan_day_week])] = value
                        energy_last_7_days += value
                if item['p'] == 3:
                    energy_this_month_attr[month_text.format(this_year, calendar.month_abbr[this_month], this_day)] = energy_today
                    energy_this_month += energy_today
                    for value in reversed(item['v']):
                        scan_day, scan_month, scan_year, scan_break = self._get_prev_day(day=scan_day, month=scan_month, year=scan_year, scan_break=scan_break)
                        if scan_break == 0:
                            energy_this_month_attr[month_text.format(scan_year, calendar.month_abbr[scan_month], scan_day)] = value
                            energy_this_month += value
                        elif scan_break == 1:
                            energy_last_month_attr[month_text.format(scan_year, calendar.month_abbr[scan_month], scan_day)] = value
                            energy_last_month += value
                if item['p'] == 4:
                    energy_this_year_attr[year_text.format(this_year, calendar.month_abbr[this_month])] = energy_this_month
                    energy_this_year += energy_this_month
                    for value in reversed(item['v']):
                        scan_month, scan_year, scan_break = self._get_prev_month(month=scan_month, year=scan_year, scan_break=scan_break)
                        if scan_break == 0:
                            energy_this_year_attr[year_text.format(scan_year, calendar.month_abbr[scan_month])] = value
                            energy_this_year += value
                        elif scan_break == 1:
                            energy_last_year_attr[year_text.format(scan_year, calendar.month_abbr[scan_month])] = value
                            energy_last_year += value
        if not found_key:
            energy_today = None
            energy_yesterday = None
            energy_last_7_days = None
            energy_this_month = None
            energy_last_month = None
            energy_this_year = None
            energy_last_year = None
        return (
            energy_today,
            energy_yesterday,
            energy_last_7_days,
            energy_this_month,
            energy_last_month,
            energy_this_year,
            energy_last_year,
            energy_today_attr,
            energy_yesterday_attr,
            energy_last_7_days_attr,
            energy_this_month_attr,
            energy_last_month_attr,
            energy_this_year_attr,
            energy_last_year_attr,
            found_key
        )


    def _get_prev_month(self, month, year, scan_break):
        if month > 1:
            return month - 1, year, scan_break
        else:
            return 12, year - 1, scan_break + 1


    def _get_prev_day(self, day, month, year, scan_break):
        if day > 1:
            return day - 1, month, year, scan_break
        else:
            if month > 1:
                return calendar.monthrange(year=year, month=month - 1)[1], month - 1, year, scan_break + 1
            else:
                return calendar.monthrange(year=year, month=12)[1], 12, year - 1, scan_break + 1


    def _get_prev_day_week(self, day):
        if day > 0:
            return day - 1
        else:
            return 6


    def _get_prev_hour(self, hour, scan_break):
        if hour > 0:
            return hour - 2, scan_break
        else:
            return 22, scan_break + 1


    def _get_http_data(self, request_type=""):
        """Common fetching of http data"""
        self._login_session()
        if self._login and self._plant_id != "":

            if request_type == self._REQUEST_MAIN:

                request_data = {
                    "useCache": False,
                    "items": [],
                    "features": self._features
                    }
                for param in self._MAP_ARISTON_ZONE_0_PARAMS.values():
                    request_data['items'].append({"id": param, "zn":0})
                if self._zones:
                    for zone in self._zones:
                        for param in self._MAP_ARISTON_MULTIZONE_PARAMS.values():
                            request_data['items'].append({"id": param, "zn":zone})
                with self._data_lock:
                    resp = self._request_post(
                        url=f'{self._ARISTON_URL}/api/v2/remote/dataItems/{self._plant_id}/get?umsys=si',
                        json_data=request_data,
                        timeout=self._TIMEOUT_MAX,
                        error_msg="Main read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_ERRORS:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/api/v2/busErrors?gatewayId={self._plant_id}&blockingOnly=False&culture=en-US',
                        timeout=self._TIMEOUT_AV,
                        error_msg="Errors read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_CH_SCHEDULE:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/api/v2/remote/timeProgs/{self._plant_id}/ChZn1?umsys=si',
                        timeout=self._TIMEOUT_AV,
                        error_msg="CH Schedule read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_DHW_SCHEDULE:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/api/v2/remote/timeProgs/{self._plant_id}/Dhw?umsys=si',
                        timeout=self._TIMEOUT_AV,
                        error_msg="DHW Schedule read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_ADDITIONAL:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/R2/PlantMenu/Refresh?id={self._plant_id}&paramIds={",".join(self._other_parameters)}',
                        timeout=self._TIMEOUT_AV,
                        error_msg="Additional data read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_LAST_MONTH:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/api/v2/remote/reports/{self._plant_id}/energyAccount',
                        timeout=self._TIMEOUT_AV,
                        error_msg="Last month data read"
                    )
                    self._store_data(resp, request_type)

            elif request_type == self._REQUEST_ENERGY:

                with self._data_lock:
                    resp = self._request_get(
                        url=f'{self._ARISTON_URL}/api/v2/remote/reports/{self._plant_id}/consSequencesApi8?usages=Ch%2CDhw&hasSlp=False',
                        timeout=self._TIMEOUT_AV,
                        error_msg="Energy data read"
                    )
                    self._store_data(resp, request_type)

        else:
            self._LOGGER.warning(f"Not properly logged in to read {request_type}")
            raise Exception(f"Not properly logged in to read {request_type}")
        self._LOGGER.info(f'Data read for {request_type}')
        return True


    def _queue_get_data(self):
        """Queue all request items"""
        with self._data_lock:
            # schedule next get request
            if self._errors >= self._MAX_ERRORS:
                # give a little rest to the system if too many errors
                retry_in = self._get_period_time * self._WAIT_PERIOD_MULTIPLYER
            else:
                # work as usual
                retry_in = self._get_period_time
            self._timer_periodic_read.cancel()
            if not self.available or self._errors > 0:
                # Initial or error situation, use main request
                if self.available and self._last_request == self._REQUEST_ADDITIONAL and self._last_request in self._requests_lists[0]:
                    # Potential error with parameters where they are removed 1 by 1 request
                    request_to_send = self._last_request
                else:
                    request_to_send = self._requests_lists[0][0]
            else:
                if self._set_requests[self._REQUEST_MAIN]:
                    # Changing parameters
                    request_to_send = self._REQUEST_MAIN
                elif self._set_requests[self._REQUEST_ADDITIONAL]:
                    # Changing parameters
                    request_to_send = self._REQUEST_ADDITIONAL
                elif self._last_request in self._requests_lists[0]:
                    # High prio more frequent (e.g. current temperatures, flame, errors)
                    last_index = self._requests_lists[0].index(self._last_request)
                    if len(self._requests_lists[0]) <= last_index + 1:
                        # Last request was the last item
                        if self._requests_lists[1]:
                            # There are requests
                            last_index_low_prio = self._requests_lists[1].index(self._last_request_low_prio)
                            if len(self._requests_lists[1]) <= last_index_low_prio + 1:
                                # Last request in low prio item
                                request_to_send = self._requests_lists[1][0]
                            else:
                                request_to_send = self._requests_lists[1][last_index_low_prio + 1]
                            self._last_request_low_prio = request_to_send
                        else:
                            # No low prio requests
                            request_to_send = self._requests_lists[0][0]
                    else:
                        request_to_send = self._requests_lists[0][last_index + 1]
                else:
                    # Low prio less frequent requests (e.g. energy use)
                    request_to_send = self._requests_lists[0][0]
            self._last_request = request_to_send

            if self._started:
                self._LOGGER.info(f'Shall send next request in {retry_in} seconds, current request is {request_to_send}')
                self._timer_queue_delay = threading.Timer(self._TIME_SPLIT, self._control_availability_state, [request_to_send])
                self._timer_queue_delay.start()
                self._timer_periodic_read = threading.Timer(retry_in, self._queue_get_data)
                self._timer_periodic_read.start()
                

    def _error_detected(self):
        """Error detected"""
        with self._lock:
            was_online = self.available
            self._errors += 1
            self._subscribers_statuses_inform()
            self._LOGGER.warning(f"Connection errors: {self._errors}")
            offline = not self.available
        if offline and was_online:
            self._clear_data()
            self._LOGGER.error("Ariston is offline: Too many errors")


    def _no_error_detected(self):
        """No errors detected"""
        with self._lock:
            was_offline = not self.available
            self._errors = 0
            self._subscribers_statuses_inform()
        if was_offline:
            self._LOGGER.info("No more errors")


    def _control_availability_state(self, request_type=""):
        """Control component availability"""
        try:
            result_ok = self._get_http_data(request_type)
            self._LOGGER.info(f"ariston action ok for {request_type}")
        except Exception as ex:
            self._error_detected()
            self._LOGGER.warning(f"ariston action nok for {request_type}: {ex}")
            return
        if result_ok:
            self._no_error_detected()
        return


    def _preparing_setting_http_data(self):
        """Preparing and setting http data"""
        self._login_session()
        with self._data_lock:
            if self._available and self._set_param:

                set_additional_params = []
                parameters = [key for key in self._set_param.keys()]

                for parameter in parameters:

                    try:

                        original_parameter, zone = self._zone_sensor_split(parameter)
                        set_value = self._set_param[parameter][self._SET_VALUE]
                        self._LOGGER.info(f'Setting {parameter} new value {self._set_param[parameter][self._VALUE]} [{set_value}]')
                        
                        if original_parameter == self._PARAM_MODE:

                            old_value = self._string_option_to_number(parameter, self._get_sensor_value(parameter))
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/plantData/{self._plant_id}/mode',
                                json_data={"new": set_value,"old": old_value},
                                error_msg='Set Mode',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_CH_MODE:

                            old_value = self._string_option_to_number(parameter, self._get_sensor_value(parameter))
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/zones/{self._plant_id}/{zone}/mode',
                                json_data={"new": set_value,"old": old_value},
                                error_msg='Set CH Mode',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_DHW_MODE:

                            old_value = self._string_option_to_number(parameter, self._get_sensor_value(parameter))
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/plantData/{self._plant_id}/dhwMode',
                                json_data={"new": set_value,"old": old_value},
                                error_msg='Set DHW Mode',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_CH_SET_TEMPERATURE:
                            
                            comfort_old = self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone))
                            comfort_new = self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone)][self._VALUE]
                            economy_old= self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_ECONOMY_TEMPERATURE, zone)) 
                            economy_new = self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_ECONOMY_TEMPERATURE, zone)][self._VALUE]
                            set_temp = self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_SET_TEMPERATURE, zone))
                            if set_temp == economy_old and self._get_sensor_value(self._PARAM_CH_MODE) == "Time program":
                                economy_new = set_value
                            else:
                                comfort_new = set_value
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/zones/{self._plant_id}/{zone}/temperatures?umsys=si',
                                json_data={"new":{"comf": comfort_new, "econ": economy_new}, "old":{"comf": comfort_old, "econ": economy_old}},
                                error_msg='Set CH Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_CH_COMFORT_TEMPERATURE:
                            
                            comfort_old = self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone))
                            economy_old= self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_ECONOMY_TEMPERATURE, zone)) 
                            economy_new = self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_ECONOMY_TEMPERATURE, zone)][self._VALUE]
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/zones/{self._plant_id}/{zone}/temperatures?umsys=si',
                                json_data={"new":{"comf": set_value, "econ": economy_new}, "old":{"comf": comfort_old, "econ": economy_old}},
                                error_msg='Set CH Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_CH_ECONOMY_TEMPERATURE:
                            
                            comfort_old = self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone))
                            comfort_new = self._ariston_sensors[self._zone_sensor_name(self._PARAM_CH_COMFORT_TEMPERATURE, zone)][self._VALUE]
                            economy_old= self._get_sensor_value(self._zone_sensor_name(self._PARAM_CH_ECONOMY_TEMPERATURE, zone)) 
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/zones/{self._plant_id}/{zone}/temperatures?umsys=si',
                                json_data={"new":{"comf": comfort_new, "econ": set_value}, "old":{"comf": comfort_old, "econ": economy_old}},
                                error_msg='Set CH Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_DHW_SET_TEMPERATURE:

                            old_value = self._get_sensor_value(parameter) 
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/plantData/{self._plant_id}/dhwTemp?umsys=si',
                                json_data={"new": set_value,"old": old_value},
                                error_msg='Set DHW Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_DHW_COMFORT_TEMPERATURE:

                            comfort_old = self._get_sensor_value(self._PARAM_DHW_COMFORT_TEMPERATURE) 
                            economy_old= self._get_sensor_value(self._PARAM_DHW_ECONOMY_TEMPERATURE) 
                            economy_new = self._ariston_sensors[self._PARAM_DHW_ECONOMY_TEMPERATURE][self._VALUE]
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/plantData/{self._plant_id}/dhwTimeProgTemperatures?umsys=si',
                                json_data={"new":{"comf": set_value, "econ": economy_new}, "old":{"comf": comfort_old, "econ": economy_old}},
                                error_msg='Set DHW Comfort Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter == self._PARAM_DHW_ECONOMY_TEMPERATURE:

                            comfort_old = self._get_sensor_value(self._PARAM_DHW_COMFORT_TEMPERATURE)
                            comfort_new = self._ariston_sensors[self._PARAM_DHW_COMFORT_TEMPERATURE][self._VALUE]
                            economy_old= self._get_sensor_value(self._PARAM_DHW_ECONOMY_TEMPERATURE) 
                            self._request_post(
                                url=f'{self._ARISTON_URL}/api/v2/remote/plantData/{self._plant_id}/dhwTimeProgTemperatures?umsys=si',
                                json_data={"new":{"comf": comfort_new, "econ": set_value}, "old":{"comf": comfort_old, "econ": economy_old}},
                                error_msg='Set DHW Economy Temperature',
                                timeout=self._TIMEOUT_AV
                            )
                            break

                        elif original_parameter in self._LIST_ARISTON_WEB_PARAMS:

                            # Many parameters in one request
                            
                            set_additional_params.append(
                                {
                                    "id": self._MAP_ARISTON_WEB_MENU_PARAMS[parameter],
                                    "value": set_value,
                                    "prevValue": self._string_option_to_number(parameter, self._get_sensor_value(parameter))
                                }
                            )

                        else:
                            self._LOGGER.error(f"Unsupported parameter to set {parameter}")
                            raise Exception(f"Unsupported parameter to set {parameter}")

                    except Exception as ex:
                        self._LOGGER.warning(f"Problem setting {parameter}: {ex}")
                        del self._set_param[parameter]
                        continue

                    self._set_param[parameter][self._ATTEMPT] += 1
                    if self._set_param[parameter][self._ATTEMPT] > self._max_set_retries:
                        del self._set_param[parameter]

                else:
                    try:
                        if set_additional_params:
                            self._request_post(
                                url=f'{self._ARISTON_URL}/R2/PlantMenu/Submit/{self._plant_id}',
                                json_data=set_additional_params,
                                error_msg='Set additional parameters',
                                timeout=self._TIMEOUT_AV
                            )
                    except Exception as ex:
                        self._LOGGER.warning(f"Problem setting multiple parameters: {ex}")

                self._subscribers_sensors_inform()
                self._subscribers_statuses_inform()
                self._reset_set_requests()

                if self._set_param:
                    self._timer_set_delay.cancel()
                    if self._started:
                        self._LOGGER.info(f"Attempting to set parameter values in {self._set_period_time} seconds")
                        self._timer_set_delay = threading.Timer(self._set_period_time, self._preparing_setting_http_data)
                        self._timer_set_delay.start()
                

    def _reset_set_requests(self):
        self._set_requests = {request: False for request in self._MAP_REQUEST}
        for parameter in self._set_param:
            self._set_requests[self._get_request_for_parameter(parameter)] = True


    def _is_digit_string(self, text):
        try:
            value = float(text)
            return value
        except ValueError:
            return None


    def _string_option_to_number(self, sensor, value):
        if self._ariston_sensors[sensor][self._OPTIONS_TXT]:
            index = self._ariston_sensors[sensor][self._OPTIONS_TXT].index(value)
            return self._ariston_sensors[sensor][self._OPTIONS][index]
        return self._ariston_sensors[sensor][self._VALUE]


    def set_http_data(self, **parameter_list: Union[str, int, float, bool]) -> None:
        """
        Set data over http, where **parameter_list excepts parameters and wanted values.

        Supported parameters and their values are specified in supported_sensors_set_values method

        Supported values must be viewed in the property 'supported_sensors_set_values',
        which are generated dynamically based on reported values.

        Example:
            set_http_data(mode='OFF',internet_time="ON")
        """

        if self._main_data != {}:
            with self._data_lock:
                # First check values and pre-process the value
                bad_values = {}
                for parameter, value in parameter_list.items():
                    if parameter not in self._SENSOR_SET_LIST:
                        bad_values[parameter] = value
                        continue
                    if self._ariston_sensors[parameter][self._OPTIONS_TXT] != None:
                        if value in self._ariston_sensors[parameter][self._OPTIONS_TXT]:
                            set_value = self._string_option_to_number(parameter, value)
                            if value != self._ariston_sensors[parameter][self._VALUE]:
                                self._set_param[parameter] = {self._VALUE: value, self._SET_VALUE: set_value, self._ATTEMPT: 0}
                                self._ariston_sensors[parameter][self._VALUE] = value
                        else:
                            bad_values[parameter] = value
                    if self._is_digit_string(value) != None:
                        value = self._is_digit_string(value)
                        if self._ariston_sensors[parameter][self._MIN] != None and \
                            self._ariston_sensors[parameter][self._MAX] != None and \
                            self._ariston_sensors[parameter][self._STEP] != None and \
                            value >= self._ariston_sensors[parameter][self._MIN] and \
                            value <= self._ariston_sensors[parameter][self._MAX]:
                            if self._ariston_sensors[parameter][self._STEP] == 0.5:
                                value = round(value * 2.0) / 2.0
                            else:
                                value = round(value)
                            if value != self._ariston_sensors[parameter][self._VALUE]:
                                self._set_param[parameter] = {self._VALUE: value, self._SET_VALUE: value, self._ATTEMPT: 0}
                                self._ariston_sensors[parameter][self._VALUE] = value
                        else:
                            bad_values[parameter] = value

                self._timer_set_delay.cancel()
                if self._started:
                    self._timer_set_delay = threading.Timer(self._TIME_SPLIT, self._preparing_setting_http_data)
                    self._timer_set_delay.start()

                if bad_values:
                    self._LOGGER.error(f"Unsupported parameters to be set: {bad_values}")
                    raise Exception(f"Unsupported parameters to be set: {bad_values}")

        else:
            self._LOGGER.warning("Connection data error, problem to set data")
            raise Exception("Connection data error, problem to set data")

    def _clear_data(self):
        with self._plant_id_lock:
            self._login = False
        self._features = {}
        self._main_data = {}
        self._additional_data = {}
        self._error_data = {}
        self._ch_schedule_data = {}
        self._dhw_schedule_data = {}
        self._set_param = {}
        self._last_month_data = {}
        self._energy_use_data = {}
        self._last_dhw_storage_temp = None
        self._zones = []
        for sensor in self._ariston_sensors:
            self._reset_sensor(sensor)
        self._reset_set_requests()
        self._subscribers_sensors_inform()
        self._subscribers_statuses_inform()

    def start(self) -> None:
        """Start communication with the server."""
        self._started = True
        self._LOGGER.info("Connection started")
        self._timer_periodic_read = threading.Timer(self._TIME_SPLIT, self._queue_get_data)
        self._timer_periodic_read.start()


    def stop(self) -> None:
        """Stop communication with the server."""
        self._started = False
        self._timer_periodic_read.cancel()
        self._timer_queue_delay.cancel()

        if self._login and self.available:
            self._request_get(
                url=f'{self._ARISTON_URL}/R2/Account/Logout',
                error_msg="Logout",
                ignore_errors=True
            )
        self._session.close()
        self._clear_data()
        self._subscribers_statuses_inform()
        self._LOGGER.info("Connection stopped")
