HEX
Server: Apache/2.4.63 (Unix)
System: Linux Synopilou92 4.4.302+ #72806 SMP Mon Jul 21 23:16:00 CST 2025 x86_64
User: pilou92 (1026)
PHP: 8.0.30
Disabled: NONE
Upload Files
File: /volume1/docker/homeassistant2025/custom_components/tarif_edf/coordinator.py
"""Data update coordinator for the Tarif EDF integration."""
from __future__ import annotations

from datetime import timedelta, datetime, date
import time
import re
from typing import Any
import json
import logging
import requests
import csv

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator

from .const import (
    DEFAULT_REFRESH_INTERVAL,
    CONTRACT_TYPE_BASE,
    CONTRACT_TYPE_HPHC,
    CONTRACT_TYPE_TEMPO,
    TARIF_BASE_URL,
    TARIF_HPHC_URL,
    TARIF_TEMPO_URL,
    TEMPO_COLOR_API_URL,
    TEMPO_COLORS_MAPPING,
    TEMPO_DAY_START_AT,
    TEMPO_TOMRROW_AVAILABLE_AT,
    TEMPO_OFFPEAK_HOURS
)

_LOGGER = logging.getLogger(__name__)

def get_remote_file(url: str):
    return requests.get(
        url,
        stream=True,
        headers={
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
        },
    )

def str_to_time(str):
    return datetime.strptime(str, '%H:%M').time()

def time_in_between(now, start, end):
    if start <= end:
        return start <= now < end
    else:
        return start <= now or now < end

def get_tempo_color_from_code(code):
    return TEMPO_COLORS_MAPPING[code]


class TarifEdfDataUpdateCoordinator(TimestampDataUpdateCoordinator):
    """Data update coordinator for the Tarif EDF integration."""

    config_entry: ConfigEntry
    tempo_prices = []

    def __init__(
        self, hass: HomeAssistant, entry: ConfigEntry
    ) -> None:
        """Initialize the coordinator."""
        super().__init__(
            hass=hass,
            logger=_LOGGER,
            name=entry.title,
            update_interval=timedelta(minutes=1),
        )
        self.config_entry = entry

    async def get_tempo_day(self, date):
        date_str = date.strftime('%Y-%m-%d')
        now = datetime.now().time()
        check_limit = str_to_time(TEMPO_TOMRROW_AVAILABLE_AT)

        for price in self.tempo_prices:
            codeJour = price['codeJour']
            if "dateJour" in price and price['dateJour'] == date_str and  \
                (codeJour in [1,2,3] or (codeJour == 0 and now < check_limit)):
                return price

        url = f"{TEMPO_COLOR_API_URL}/{date_str}"
        response = await self.hass.async_add_executor_job(get_remote_file, url)
        response_json = response.json()

        self.tempo_prices.append(response_json)

        return response_json

    async def _async_update_data(self) -> dict[Platform, dict[str, Any]]:
        """Get the latest data from Tarif EDF and updates the state."""
        data = self.config_entry.data
        previous_data = None if self.data is None else self.data.copy()

        if previous_data is None:
            self.data = {
                "contract_power": data['contract_power'],
                "contract_type": data['contract_type'],
                "last_refresh_at": None,
                "tarif_actuel_ttc": None
            }

        fresh_data_limit = datetime.now() - timedelta(days=self.config_entry.options.get("refresh_interval", DEFAULT_REFRESH_INTERVAL))

        tarif_needs_update = self.data['last_refresh_at'] is None or self.data['last_refresh_at'] < fresh_data_limit

        self.logger.info('EDF tarif_needs_update '+('yes' if tarif_needs_update else 'no'))

        if tarif_needs_update:
            if data['contract_type'] == CONTRACT_TYPE_BASE:
                url = TARIF_BASE_URL
            elif data['contract_type'] == CONTRACT_TYPE_HPHC:
                    url = TARIF_HPHC_URL
            elif data['contract_type'] == CONTRACT_TYPE_TEMPO:
                    url = TARIF_TEMPO_URL

            response = await self.hass.async_add_executor_job(get_remote_file, url)
            parsed_content = csv.reader(response.content.decode('utf-8').splitlines(), delimiter=';')
            rows = list(parsed_content)

            for row in rows:
                if row[1] == '' and row[2] == data['contract_power']:
                    if data['contract_type'] == CONTRACT_TYPE_BASE:
                        self.data['base_fixe_ttc'] = float(row[4].replace(",", "." ))
                        self.data['base_variable_ttc'] = float(row[6].replace(",", "." ))
                    elif data['contract_type'] == CONTRACT_TYPE_HPHC:
                        self.data['hphc_fixe_ttc'] = float(row[4].replace(",", "." ))
                        self.data['hphc_variable_hc_ttc'] = float(row[6].replace(",", "." ))
                        self.data['hphc_variable_hp_ttc'] = float(row[8].replace(",", "." ))
                    elif data['contract_type'] == CONTRACT_TYPE_TEMPO:
                        self.data['tempo_fixe_ttc'] = float(row[4].replace(",", "." ))
                        self.data['tempo_variable_hc_bleu_ttc'] = float(row[6].replace(",", "." ))
                        self.data['tempo_variable_hp_bleu_ttc'] = float(row[8].replace(",", "." ))
                        self.data['tempo_variable_hc_blanc_ttc'] = float(row[10].replace(",", "." ))
                        self.data['tempo_variable_hp_blanc_ttc'] = float(row[12].replace(",", "." ))
                        self.data['tempo_variable_hc_rouge_ttc'] = float(row[14].replace(",", "." ))
                        self.data['tempo_variable_hp_rouge_ttc'] = float(row[16].replace(",", "." ))

                    self.data['last_refresh_at'] = datetime.now()

                    break
            response.close

        if data['contract_type'] == CONTRACT_TYPE_TEMPO:
            today = date.today()
            yesterday = today - timedelta(days=1)
            tomorrow = today + timedelta(days=1)

            tempo_yesterday = await self.get_tempo_day(yesterday)
            tempo_today = await self.get_tempo_day(today)
            tempo_tomorrow = await self.get_tempo_day(tomorrow)

            yesterday_color = get_tempo_color_from_code(tempo_yesterday['codeJour'])
            today_color = get_tempo_color_from_code(tempo_today['codeJour'])
            tomorrow_color = get_tempo_color_from_code(tempo_tomorrow['codeJour'])

            self.data['tempo_couleur_hier'] = yesterday_color
            self.data['tempo_couleur_aujourdhui'] = today_color
            self.data['tempo_couleur_demain'] = tomorrow_color

            currentColorCode = tempo_yesterday['codeJour']

            if datetime.now().time() >= str_to_time(TEMPO_DAY_START_AT):
                self.logger.info("Using today's tempo prices")
                currentColorCode = tempo_today['codeJour']
            else:
                self.logger.info("Using yesterday's tempo prices")

            if currentColorCode in [1, 2, 3]:
                color = get_tempo_color_from_code(currentColorCode)
                self.data['tempo_couleur'] = color
                self.data['tempo_variable_hp_ttc'] = self.data[f"tempo_variable_hp_{color}_ttc"]
                self.data['tempo_variable_hc_ttc'] = self.data[f"tempo_variable_hc_{color}_ttc"]
                self.data['last_refresh_at'] = datetime.now()

        default_offpeak_hours = None
        if data['contract_type'] == CONTRACT_TYPE_TEMPO:
            default_offpeak_hours = TEMPO_OFFPEAK_HOURS
        off_peak_hours_ranges = self.config_entry.options.get("off_peak_hours_ranges", default_offpeak_hours)

        if data['contract_type'] == CONTRACT_TYPE_BASE:
            self.data['tarif_actuel_ttc'] = self.data['base_variable_ttc']
        elif data['contract_type'] in [CONTRACT_TYPE_HPHC, CONTRACT_TYPE_TEMPO] and off_peak_hours_ranges is not None:
            contract_type_key = 'hphc' if data['contract_type'] == CONTRACT_TYPE_HPHC else 'tempo'
            tarif_actuel = self.data[contract_type_key+'_variable_hp_ttc']
            now = datetime.now().time()
            for range in off_peak_hours_ranges.split(','):
                if not re.match(r'([0-1]?[0-9]|2[0-3]):[0-5][0-9]-([0-1]?[0-9]|2[0-3]):[0-5][0-9]', range):
                    continue

                hours = range.split('-')
                start_at = str_to_time(hours[0])
                end_at = str_to_time(hours[1])

                if time_in_between(now, start_at, end_at):
                    tarif_actuel = self.data[contract_type_key+'_variable_hc_ttc']
                    break

            self.data['tarif_actuel_ttc'] = tarif_actuel

        self.logger.info('EDF Tarif')
        self.logger.info(self.data)

        return self.data