Skip to content

Commit

Permalink
Merge pull request #496 from MyElectricalData/feat/ha-production
Browse files Browse the repository at this point in the history
Feat/ha production
  • Loading branch information
m4dm4rtig4n authored Feb 15, 2024
2 parents 25c5126 + 9c3aa3c commit ed0ba55
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 54 deletions.
1 change: 1 addition & 0 deletions config.exemple.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ home_assistant_ws: # FOR ENERGY TAB
token: HOME_ASSISTANT_TOKEN_GENERATE_IN_PROFILE_TABS_(BOTTOM)
url: myhomeassistant.domain.fr
max_date: "2021-06-01"
purge: false
ssl:
gateway: true
certfile: ""
Expand Down
35 changes: 23 additions & 12 deletions src/models/ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,18 +667,29 @@ def datatable_daily(self, all_data, start_index, end_index, measurement_directio
hp = hp / 1000
hc_kw = f'<div id="{measurement_direction}_hc_{target}_{date_text}" class="">{hc}</div>'
hp_kw = f'<div id="{measurement_direction}_hp_{target}_{date_text}" class="">{hp}</div>'
day_data = [
date_text,
conso_w,
conso_kw,
hc_kw,
hp_kw,
temp_color,
fail_count,
cache_state,
self.datatable_button(measurement_direction, db_data)["cache"],
self.datatable_button(measurement_direction, db_data)["blacklist"],
]
if measurement_direction == "consumption":
day_data = [
date_text,
conso_w,
conso_kw,
hc_kw,
hp_kw,
temp_color,
fail_count,
cache_state,
self.datatable_button(measurement_direction, db_data)["cache"],
self.datatable_button(measurement_direction, db_data)["blacklist"],
]
else:
day_data = [
date_text,
conso_w,
conso_kw,
fail_count,
cache_state,
self.datatable_button(measurement_direction, db_data)["cache"],
self.datatable_button(measurement_direction, db_data)["blacklist"],
]
result.append(day_data)
index = index + 1
return result
Expand Down
12 changes: 8 additions & 4 deletions src/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,18 @@ def insert_daily(

def reset_daily(self, usage_point_id, date=None, mesure_type="consumption"):
data = self.get_daily_date(usage_point_id, date, mesure_type)
if mesure_type == "consumption":
table = ConsumptionDaily
else:
table = ProductionDaily
if data is not None:
values = {
ConsumptionDaily.value: 0,
ConsumptionDaily.blacklist: 0,
ConsumptionDaily.fail_count: 0,
table.value: 0,
table.blacklist: 0,
table.fail_count: 0,
}
unique_id = hashlib.md5(f"{usage_point_id}/{date}".encode("utf-8")).hexdigest()
self.session.execute(update(ConsumptionDaily, values=values).where(ConsumptionDaily.id == unique_id))
self.session.execute(update(table, values=values).where(table.id == unique_id))
self.session.flush()
return True
else:
Expand Down
188 changes: 175 additions & 13 deletions src/models/export_home_assistant_ws.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Import data in statistique recorder of Home Assistant."""

import json
import logging
import ssl
from datetime import datetime, timedelta
from pprint import pprint

import pytz
import websocket
Expand All @@ -15,7 +16,14 @@


class HomeAssistantWs:
"""Class to interact with Home Assistant WebSocket API."""

def __init__(self, usage_point_id):
"""Initialize the class with the usage point id.
Args:
usage_point_id (str): The usage point id
"""
self.ws = None
self.usage_point_id = usage_point_id
self.usage_point_id_config = DB.get_usage_point(self.usage_point_id)
Expand All @@ -25,6 +33,7 @@ def __init__(self, usage_point_id):
self.token = None
self.id = 1
self.purge = False
self.purge_force = True
self.current_stats = []
if self.load_config():
if self.connect():
Expand All @@ -37,6 +46,11 @@ def __init__(self, usage_point_id):
self.ws.close()

def load_config(self):
"""Load the Home Assistant WebSocket configuration from the configuration file.
Returns:
bool: True if the configuration is loaded, False otherwise
"""
self.config = CONFIG.home_assistant_ws()
if self.config is not None:
if "url" in self.config:
Expand All @@ -59,6 +73,11 @@ def load_config(self):
return True

def connect(self):
"""Connect to the Home Assistant WebSocket server.
Returns:
bool: True if the connection is successful, False otherwise
"""
try:
check_ssl = CONFIG.get("ssl")
sslopt = None
Expand All @@ -75,16 +94,21 @@ def connect(self):
logging.info("Authentification requise")
return self.authentificate()
return True
except Exception as e:
except Exception as _e:
self.ws.close()
logging.error(e)
logging.error(_e)
logging.critical("Connexion impossible vers Home Assistant")
logging.warning(
f" => ATTENTION, le WebSocket est également soumis au ban en cas de plusieurs échec d'authentification."
" => ATTENTION, le WebSocket est également soumis au ban en cas de plusieurs échec d'authentification."
)
logging.warning(f" => ex: 403: Forbidden")
logging.warning(" => ex: 403: Forbidden")

def authentificate(self):
"""Authenticate with the Home Assistant WebSocket server.
Returns:
bool: True if the authentication is successful, False otherwise
"""
data = {"type": "auth", "access_token": self.token}
auth_output = self.send(data)
if auth_output["type"] == "auth_ok":
Expand All @@ -95,6 +119,13 @@ def authentificate(self):
return False

def send(self, data):
"""Send data to the Home Assistant WebSocket server.
Args:
data (dict): The data to send
Returns:
dict: The output from the server
"""
self.ws.send(json.dumps(data))
self.id = self.id + 1
output = json.loads(self.ws.recv())
Expand All @@ -105,6 +136,11 @@ def send(self, data):
return output

def list_data(self):
"""List the data already cached in Home Assistant.
Returns:
dict: The list of data
"""
logging.info("Liste les données déjà en cache.")
import_statistics = {
"id": self.id,
Expand All @@ -118,6 +154,13 @@ def list_data(self):
return current_stats

def clear_data(self, statistic_ids):
"""Clear the data imported into Energy.
Args:
statistic_ids (list): The list of statistic ids
Returns:
dict: The output from clearing the data
"""
logging.info("Effacement des données importées dans Energy.")
for key in statistic_ids:
logging.info(f" - {key}")
Expand All @@ -132,6 +175,15 @@ def clear_data(self, statistic_ids):
return clear_stat

def get_data(self, statistic_ids, begin, end):
"""Get the data for a given period.
Args:
statistic_ids (list): The list of statistic ids
begin (datetime): The start of the period
end (datetime): The end of the period
Returns:
dict: The data for the period
"""
statistics_during_period = {
"id": self.id,
"type": "recorder/statistics_during_period",
Expand All @@ -143,15 +195,16 @@ def get_data(self, statistic_ids, begin, end):
stat_period = self.send(statistics_during_period)
return stat_period

def import_data(self):
def import_data(self): # noqa: C901
"""Import the data for the usage point into Home Assistant."""
logging.info(f"Importation des données du point de livraison : {self.usage_point_id}")
try:
plan = self.usage_point_id_config.plan.upper()
if self.usage_point_id_config.consumption_detail:
logging.info("Consommation")
measurement_direction = "consumption"
if "max_date" in self.config:
logging.warn(f"WARNING : Max date détecter {self.config['max_date']}")
logging.warning("WARNING : Max date détecter %s", self.config["max_date"])
begin = datetime.strptime(self.config["max_date"], "%Y-%m-%d")
detail = DB.get_detail_all(begin=begin, usage_point_id=self.usage_point_id, order_dir="desc")
else:
Expand Down Expand Up @@ -259,15 +312,14 @@ def import_data(self):
stats_euro[statistic_id]["data"][key]["sum"] = stats_euro[statistic_id]["sum"]

# CLEAN OLD DATA
if self.purge:
if self.purge or self.purge_force:
list_statistic_ids = []
for statistic_id, _ in stats_kwh.items():
list_statistic_ids.append(statistic_id)
self.clear_data(list_statistic_ids)
CONFIG.set("purge", False)

for statistic_id, data in stats_kwh.items():
# self.clear_data(statistic_id)
metadata = {
"has_mean": False,
"has_sum": True,
Expand All @@ -285,7 +337,6 @@ def import_data(self):
self.send(import_statistics)

for statistic_id, data in stats_euro.items():
# self.clear_data(statistic_id)
metadata = {
"has_mean": False,
"has_sum": True,
Expand All @@ -304,8 +355,119 @@ def import_data(self):

if self.usage_point_id_config.production_detail:
logging.info("Production")
logging.error("L'import de la production n'est pas fonctionnel pour l'instant.")
except Exception as e:
measure_type = "production"
if "max_date" in self.config:
logging.warning("WARNING : Max date détectée %s", self.config["max_date"])
begin = datetime.strptime(self.config["max_date"], "%Y-%m-%d")
detail = DB.get_detail_all(
begin=begin,
usage_point_id=self.usage_point_id,
measurement_direction="production",
order_dir="desc",
)
else:
detail = DB.get_detail_all(
usage_point_id=self.usage_point_id, measurement_direction="production", order_dir="desc"
)

cost = 0
last_year = None
last_month = None

stats_kwh = {}
stats_euro = {}
for data in detail:
year = int(f'{data.date.strftime("%Y")}')
if last_year is None or year != last_year:
logging.info(f"{year} :")
month = int(f'{data.date.strftime("%m")}')
if last_month is None or month != last_month:
logging.info(f"- {month}")
last_year = year
last_month = month
hour_minute = int(f'{data.date.strftime("%H")}{data.date.strftime("%M")}')
name = f"MyElectricalData - {self.usage_point_id} {measure_type}"
statistic_id = f"myelectricaldata:{self.usage_point_id}_{measure_type}"
value = data.value / (60 / data.interval)
cost = value * self.usage_point_id_config.production_price / 1000
date = TZ_PARIS.localize(data.date, "%Y-%m-%d %H:%M:%S").replace(minute=0, second=0, microsecond=0)
key = date.strftime("%Y-%m-%d %H:%M:%S")

# KWH
if statistic_id not in stats_kwh:
stats_kwh[statistic_id] = {"name": name, "sum": 0, "data": {}}
if key not in stats_kwh[statistic_id]["data"]:
stats_kwh[statistic_id]["data"][key] = {
"start": date.isoformat(),
"state": 0,
"sum": 0,
}
value = value / 1000
stats_kwh[statistic_id]["data"][key]["state"] = (
stats_kwh[statistic_id]["data"][key]["state"] + value
)
stats_kwh[statistic_id]["sum"] += value
stats_kwh[statistic_id]["data"][key]["sum"] = stats_kwh[statistic_id]["sum"]

# EURO
statistic_id = f"{statistic_id}_revenue"
if statistic_id not in stats_euro:
stats_euro[statistic_id] = {
"name": f"{name} Revenue",
"sum": 0,
"data": {},
}
if key not in stats_euro[statistic_id]["data"]:
stats_euro[statistic_id]["data"][key] = {
"start": date.isoformat(),
"state": 0,
"sum": 0,
}
stats_euro[statistic_id]["data"][key]["state"] += cost
stats_euro[statistic_id]["sum"] += cost
stats_euro[statistic_id]["data"][key]["sum"] = stats_euro[statistic_id]["sum"]

if self.purge or self.purge_force:
list_statistic_ids = []
for statistic_id, _ in stats_kwh.items():
list_statistic_ids.append(statistic_id)
self.clear_data(list_statistic_ids)
CONFIG.set("purge", False)

for statistic_id, data in stats_kwh.items():
metadata = {
"has_mean": False,
"has_sum": True,
"name": data["name"],
"source": "myelectricaldata",
"statistic_id": statistic_id,
"unit_of_measurement": "kWh",
}
import_statistics = {
"id": self.id,
"type": "recorder/import_statistics",
"metadata": metadata,
"stats": list(data["data"].values()),
}
self.send(import_statistics)

for statistic_id, data in stats_euro.items():
metadata = {
"has_mean": False,
"has_sum": True,
"name": data["name"],
"source": "myelectricaldata",
"statistic_id": statistic_id,
"unit_of_measurement": "EURO",
}
import_statistics = {
"id": self.id,
"type": "recorder/import_statistics",
"metadata": metadata,
"stats": list(data["data"].values()),
}
self.send(import_statistics)
except Exception as _e:
self.ws.close()
logging.error(e)
logging.error(_e)
logging.critical("Erreur lors de l'export des données vers Home Assistant")
Loading

0 comments on commit ed0ba55

Please sign in to comment.