Skip to content

Commit

Permalink
📦 NEW: Historical data as own device
Browse files Browse the repository at this point in the history
  • Loading branch information
unl0ck committed Dec 3, 2024
1 parent a5e699a commit 363b728
Show file tree
Hide file tree
Showing 30 changed files with 543 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .bumpversion-edge.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.bumpversion]
current_version = "2.11.14"
current_version = "2.11.15"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
Expand Down
17 changes: 17 additions & 0 deletions GridboxConnectorAddon-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
<!-- https://developers.home-assistant.io/docs/add-ons/presentation#keeping-a-changelog -->

## 2.11.15

### 🔨 Fixed

- Worked on create new device and fixed hopefully

## 2.11.11

### 🚀 Added

- Historical Data Device

### 🔨 Fixed

- bump viessmann-gridbox-connector to 1.6.0
- bump ha-mqtt-discoverable to 0.16.0

## 2.10.0

### 🚀 Added
Expand Down
106 changes: 81 additions & 25 deletions GridboxConnectorAddon-dev/GridboxConnector/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from importlib.resources import files
from utils import SensitiveDataFilter, get_bool_env
from telemetry import Telemetry
import threading
opens_file_path = '/data/options.json'
#logging.basicConfig(format='%(asctime)s %(filename)s:%(lineno)d %(levelname)s - %(message)s', level=logging.getLevelName(os.getenv('LOG_LEVEL', 'INFO')))
logger = logging.getLogger(__name__)
Expand All @@ -16,7 +17,6 @@
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Benutzerdefinierten Filter zum Logger hinzufügen
logger.addFilter(SensitiveDataFilter())

def load_gridbox_config():
Expand All @@ -26,17 +26,75 @@ def load_gridbox_config():
return data

def run_telemetry():
enable_telemetry = get_bool_env('ENABLE_TELEMETRY', False)
telemetry = None
if enable_telemetry:
#otel_server = os.getenv('TelemetryServer', "https://otel.helming.xyz")
#if otel_server == "":
otel_server = "https://otel.helming.xyz"
telemetry = Telemetry(otel_server, "homeassistant-addon-viessmann-gridbox")
telemetry.log_as_span("Telemetry enabled", level=logger.level)
try:
enable_telemetry = get_bool_env('ENABLE_TELEMETRY', False)
if enable_telemetry:
otel_server = os.getenv('TELEMETRY_SERVER', "https://otel.helming.xyz")
telemetry = Telemetry(otel_server, "homeassistant-addon-viessmann-gridbox-edge")
telemetry.log_as_span("Telemetry enabled", level=logger.level)
except Exception as e:
logger.error(f"Error while setting up telemetry: {e}")
return telemetry

def live_data_task(gridboxConnector:GridboxConnector, ha_viessmann_device:HAViessmannGridboxConnector, WAIT):
one_time_print = True
while True:
measurement = gridboxConnector.retrieve_live_data()
if len(measurement) > 0:
result = measurement[0]
ha_viessmann_device.update_sensors(result)
if one_time_print or logger.level == logging.DEBUG:
logger.info(result)
one_time_print = False
# Wait until fetch new values in seconds
else:
logger.warning("No data received")
gridboxConnector.init_auth()
time.sleep(WAIT)

def historical_data_task(gridboxConnector:GridboxConnector, ha_viessmann_historical_device:HAViessmannGridboxConnector, WAIT):
one_time_print = True

while True:
import time
from datetime import datetime, timedelta, timezone
now = datetime.now(timezone(timedelta(hours=1)))
now = now.replace(hour=0, minute=0, second=0, microsecond=0)

today = now.isoformat()
tomorrow = (now + timedelta(days=1)).isoformat()
measurement = gridboxConnector.retrieve_historical_data(today, tomorrow)
if len(measurement) > 0:
result = measurement[0]
total = result["total"]
ha_viessmann_historical_device.update_sensors(total)
if one_time_print or logger.level == logging.DEBUG:
logger.info(total)
one_time_print = False
else:
logger.warning("No data received")
gridboxConnector.init_auth()
time.sleep(WAIT)

def start_thread(target, args):
while True:
try:
thread = threading.Thread(target=target, args=args)
thread.start()
thread.join()
except Exception as e:
logger.error(f"Thread konnte nicht gestartet werden: {e}")
time.sleep(5) # Warte 5 Sekunden bevor der Thread neu gestartet wird


def start_live_thread(gridboxConnector, ha_device, WAIT):
logger.info("Start live thread")
start_thread(live_data_task, (gridboxConnector, ha_device, WAIT))

def start_historical_thread(gridboxConnector:GridboxConnector, ha_device, WAIT):
logger.info("Start historical thread")
start_thread(historical_data_task, (gridboxConnector, ha_device, WAIT))


def run_addon():
Expand Down Expand Up @@ -64,28 +122,26 @@ def run_addon():
exit(1)
gridbox_config["login"]["username"] = USER
gridbox_config["login"]["password"] = PASSWORD


logger.debug(gridbox_config["login"])
one_time_print = True


mqtt_settings = Settings.MQTT(host=mqtt_server, username=mqtt_user, password=mqtt_pw, port=mqtt_port)
viessmann_gridbox_connector = HAViessmannGridboxConnector(mqtt_settings)
gridboxConnector = GridboxConnector(gridbox_config)


while True:
measurement = gridboxConnector.retrieve_live_data()
if len(measurement) > 0:
result = measurement[0]
viessmann_gridbox_connector.update_sensors(result)
if one_time_print or logger.level == logging.DEBUG:
logger.info(result)
one_time_print = False
# Wait until fetch new values in seconds
else:
logger.warning("No data received")
gridboxConnector.init_auth()
time.sleep(WAIT)
viessmann_gridbox_device = HAViessmannGridboxConnector(mqtt_settings=mqtt_settings, logger=logger)
viessmann_gridbox_historical_device = HAViessmannGridboxConnector(mqtt_settings=mqtt_settings, device_name="Viessmann Gridbox Historical",device_identifiers="viessmann_gridbox_historical",logger=logger, prefix="historical")


gridboxConnector = GridboxConnector(gridbox_config)

logger.info("Setup Threads")
# Starte die Threads
threading.Thread(target=start_live_thread, args=(gridboxConnector, viessmann_gridbox_device, WAIT)).start()
threading.Thread(target=start_historical_thread, args=(gridboxConnector, viessmann_gridbox_historical_device, WAIT)).start()

if __name__ == '__main__':
telemetry = run_telemetry()
#telemetry = run_telemetry()
run_addon()
#run_test_log()
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ha_viessmann_battery import HAViessmannBattery
from ha_viessmann_ev_charging_station import HAViessmannEVChargingStation
from ha_viessmann_heater import HAViessmannHeater
import logging


class HAViessmannGridboxConnector:
Expand All @@ -24,52 +25,53 @@ class HAViessmannGridboxConnector:
self_sufficiency_rate_sensor: Sensor
battery_sum: HAViessmannBattery
heater_sensor: HAViessmannHeater
logger: logging.Logger

def __init__(self, mqtt_settings):
def __init__(self, mqtt_settings, device_name="Viessmann Gridbox", device_identifiers="viessmann_gridbox", device_manufacturer="Viessmann", device_model="Vitocharge 2.0",prefix="", logger=logging.getLogger(__name__)):
self.battery_sensor_dict = {}
self.ev_sensor_dict = {}
self.logger = logger
self.mqtt_settings = mqtt_settings
self.device_info = DeviceInfo(
name="Viessmann Gridbox", identifiers="viessmann_gridbox", manufacturer="Viessmann", model="Vitocharge 2.0")

production_sensor_info = SensorInfo(name="Production", device_class="power",
unique_id="gridbox_production", device=self.device_info, unit_of_measurement="W")
name=device_name, identifiers=device_identifiers, manufacturer=device_manufacturer, model=device_model)
self.logger.info(f"Device Info: {self.device_info}")
production_sensor_info = SensorInfo(name="Production", device_class="power", unique_id="gridbox_production"+prefix, device=self.device_info, unit_of_measurement="W")
production_settings = Settings(mqtt=mqtt_settings, entity=production_sensor_info)

grid_sensor_info = SensorInfo(name="Grid", device_class="power", unique_id="gridbox_grid", device=self.device_info, unit_of_measurement="W")
grid_sensor_info = SensorInfo(name="Grid", device_class="power", unique_id="gridbox_grid"+prefix, device=self.device_info, unit_of_measurement="W")
grid_settings = Settings(mqtt=mqtt_settings, entity=grid_sensor_info)

photovoltaic_sensor_info = SensorInfo(name="Photovoltaic", device_class="power", unique_id="gridbox_photovoltaic", device=self.device_info, unit_of_measurement="W")
photovoltaic_sensor_info = SensorInfo(name="Photovoltaic", device_class="power", unique_id="gridbox_photovoltaic"+prefix, device=self.device_info, unit_of_measurement="W")
photovoltaic_settings = Settings(mqtt=mqtt_settings, entity=photovoltaic_sensor_info)

# consumption
consumption_household_sensor_info = SensorInfo(name="Consumption", device_class="power", unique_id="gridbox_consumption_household", device=self.device_info, unit_of_measurement="W")
consumption_household_sensor_info = SensorInfo(name="Consumption", device_class="power", unique_id="gridbox_consumption_household"+prefix, device=self.device_info, unit_of_measurement="W")
consumption_household_settings = Settings(mqtt=mqtt_settings, entity=consumption_household_sensor_info)

total_consumption_household_sensor_info = SensorInfo(name="Total Consumption", device_class="power", unique_id="total_consumption_household", device=self.device_info, unit_of_measurement="W")
total_consumption_household_sensor_info = SensorInfo(name="Total Consumption", device_class="power", unique_id="total_consumption_household"+prefix, device=self.device_info, unit_of_measurement="W")
total_consumption_household_settings = Settings(mqtt=mqtt_settings, entity=total_consumption_household_sensor_info)

# Direct Consumption
direct_consumption_household_sensor_info = SensorInfo(name="DirectConsumptionHousehold", device_class="power", unique_id="gridbox_direct_consumption_household", device=self.device_info, unit_of_measurement="W")
direct_consumption_household_sensor_info = SensorInfo(name="DirectConsumptionHousehold", device_class="power", unique_id="gridbox_direct_consumption_household"+prefix, device=self.device_info, unit_of_measurement="W")
direct_consumption_household_settings = Settings(mqtt=mqtt_settings, entity=direct_consumption_household_sensor_info)

direct_consumption_heatpump_sensor_info = SensorInfo(name="DirectConsumptionHeatPump", device_class="power", unique_id="gridbox_direct_consumption_heatpump", device=self.device_info, unit_of_measurement="W")
direct_consumption_heatpump_sensor_info = SensorInfo(name="DirectConsumptionHeatPump", device_class="power", unique_id="gridbox_direct_consumption_heatpump"+prefix, device=self.device_info, unit_of_measurement="W")
direct_consumption_heatpump_settings = Settings(mqtt=mqtt_settings, entity=direct_consumption_heatpump_sensor_info)

direct_consumption_ev_sensor_info = SensorInfo(name="DirectConsumptionEV", device_class="power", unique_id="gridbox_direct_consumption_ev", device=self.device_info, unit_of_measurement="W")
direct_consumption_ev_sensor_info = SensorInfo(name="DirectConsumptionEV", device_class="power", unique_id="gridbox_direct_consumption_ev"+prefix, device=self.device_info, unit_of_measurement="W")
direct_consumption_ev_settings = Settings(mqtt=mqtt_settings, entity=direct_consumption_ev_sensor_info)

direct_consumption_rate_sensor_info = SensorInfo(name="DirectConsumptionRate", device_class="power_factor", unique_id="gridbox_direct_consumption_rate", device=self.device_info, unit_of_measurement="%")
direct_consumption_rate_sensor_info = SensorInfo(name="DirectConsumptionRate", device_class="power_factor", unique_id="gridbox_direct_consumption_rate"+prefix, device=self.device_info, unit_of_measurement="%")
direct_consumption_rate_settings = Settings(mqtt=mqtt_settings, entity=direct_consumption_rate_sensor_info)

# Self Consumption
self_supply_sensor_info = SensorInfo(name="SelfSupply", device_class="power",unique_id="gridbox_self_supply", device=self.device_info, unit_of_measurement="W")
self_supply_sensor_info = SensorInfo(name="SelfSupply", device_class="power",unique_id="gridbox_self_supply"+prefix, device=self.device_info, unit_of_measurement="W")
self_supply_settings = Settings(mqtt=mqtt_settings, entity=self_supply_sensor_info)

self_consumption_rate_sensor_info = SensorInfo(name="SelfConsumptionRate", device_class="power_factor", unique_id="gridbox_self_consumption_rate", device=self.device_info, unit_of_measurement="%")
self_consumption_rate_sensor_info = SensorInfo(name="SelfConsumptionRate", device_class="power_factor", unique_id="gridbox_self_consumption_rate"+prefix, device=self.device_info, unit_of_measurement="%")
self_consumption_rate_settings = Settings(mqtt=mqtt_settings, entity=self_consumption_rate_sensor_info)

self_sufficiency_rate_sensor_info = SensorInfo(name="SelfSufficiencyRate", device_class="power_factor", unique_id="gridbox_self_sufficiency_rate", device=self.device_info, unit_of_measurement="%")
self_sufficiency_rate_sensor_info = SensorInfo(name="SelfSufficiencyRate", device_class="power_factor", unique_id="gridbox_self_sufficiency_rate"+prefix, device=self.device_info, unit_of_measurement="%")
self_sufficiency_rate_settings = Settings(mqtt=mqtt_settings, entity=self_sufficiency_rate_sensor_info)

# Instantiate the sensors
Expand All @@ -79,13 +81,13 @@ def __init__(self, mqtt_settings):
self.photovoltaic_sensor = Sensor(photovoltaic_settings)

# Battery sum
self.battery_sum = HAViessmannBattery(mqtt_settings, self.device_info, "sum", "")
self.battery_sum = HAViessmannBattery(mqtt_settings, self.device_info, "sum", ""+prefix)

# Heater
self.heater_sensor = HAViessmannHeater(mqtt_settings, self.device_info, "", "")
self.heater_sensor = HAViessmannHeater(mqtt_settings, self.device_info, "", ""+prefix)

# EV
self.ev_sum = HAViessmannEVChargingStation(mqtt_settings, self.device_info, "sum", "")
self.ev_sum = HAViessmannEVChargingStation(mqtt_settings, self.device_info, "sum", ""+prefix)

# Consumption
self.consumption_household_sensor = Sensor(consumption_household_settings)
Expand All @@ -102,14 +104,24 @@ def __init__(self, mqtt_settings):
def update_sensors(self, measurement: dict):
if "production" in measurement:
self.production_sensor.set_state(measurement.get("production", ""))
else:
self.logger.warning("No production data received")
if "grid" in measurement:
self.grid_sensor.set_state(measurement.get("grid", ""))
else:
self.logger.warning("No grid data received")
if "photovoltaic" in measurement:
self.photovoltaic_sensor.set_state(measurement.get("photovoltaic", ""))
else:
self.logger.warning("No photovoltaic data received")
if "consumption" in measurement:
self.consumption_household_sensor.set_state(measurement.get("consumption", ""))
else:
self.logger.warning("No consumption data received")
if "totalConsumption" in measurement:
self.total_consumption_household_sensor.set_state(measurement.get("totalConsumption", ""))
else:
self.logger.warning("No total consumption data received")
if "directConsumptionHousehold" in measurement:
self.direct_consumption_household_sensor.set_state(float(measurement.get("directConsumptionHousehold", "0")))
if "directConsumptionHeatPump" in measurement:
Expand Down
51 changes: 51 additions & 0 deletions GridboxConnectorAddon-dev/GridboxConnector/import unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import unittest
import logging
from unittest.mock import Mock, patch
from .utils import SensitiveDataFilter, get_bool_env

# FILE: GridboxConnectorAddon-edge/GridboxConnector/test_utils.py


class TestSensitiveDataFilter(unittest.TestCase):
def setUp(self):
self.filter = SensitiveDataFilter()

def test_filter(self):
record = logging.LogRecord(name="test", level=logging.INFO, pathname="", lineno=0, msg='{"username": "user", "password": "pass"}', args=(), exc_info=None)
self.filter.filter(record)
self.assertIn('"username": "***"', record.msg)
self.assertIn('"password": "***"', record.msg)

def test_filter_no_sensitive_data(self):
record = logging.LogRecord(name="test", level=logging.INFO, pathname="", lineno=0, msg='{"data": "value"}', args=(), exc_info=None)
self.filter.filter(record)
self.assertIn('"data": "value"', record.msg)

def test_filter_invalid_json(self):
record = logging.LogRecord(name="test", level=logging.INFO, pathname="", lineno=0, msg='Invalid JSON', args=(), exc_info=None)
self.filter.filter(record)
self.assertEqual(record.msg, 'Invalid JSON')

class TestGetBoolEnv(unittest.TestCase):
@patch('os.getenv', return_value="true")
def test_get_bool_env_true(self, mock_getenv):
self.assertTrue(get_bool_env("TEST_VAR"))

@patch('os.getenv', return_value="false")
def test_get_bool_env_false(self, mock_getenv):
self.assertFalse(get_bool_env("TEST_VAR"))

@patch('os.getenv', return_value=None)
def test_get_bool_env_default(self, mock_getenv):
self.assertFalse(get_bool_env("TEST_VAR"))

@patch('os.getenv', return_value="1")
def test_get_bool_env_one(self, mock_getenv):
self.assertTrue(get_bool_env("TEST_VAR"))

@patch('os.getenv', return_value="0")
def test_get_bool_env_zero(self, mock_getenv):
self.assertFalse(get_bool_env("TEST_VAR"))

if __name__ == '__main__':
unittest.main()
1 change: 0 additions & 1 deletion GridboxConnectorAddon-dev/GridboxConnector/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def filter(self, record):
# Das modifizierte Dictionary zurück in einen String konvertieren
record.msg = json.dumps(literal_msg)
except Exception as e:
# Wenn die Nachricht kein JSON ist, nichts tun
logging.error(f"Error filtering sensitive data: {e}")
pass
return True
Expand Down
2 changes: 1 addition & 1 deletion GridboxConnectorAddon-dev/cloudSettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.10.1",
"version": "2.11.15",
"urls": {
"login": "https://gridx.eu.auth0.com/oauth/token",
"gateways": "https://api.gridx.de/gateways",
Expand Down
2 changes: 1 addition & 1 deletion GridboxConnectorAddon-dev/config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config
name: Viessmann Gridbox Connector (dev)
version: "2.10.1"
version: "2.11.15"
slug: "gridbox_connector_dev"
description: Development version of Viessmann Gridbox Connector
url: "https://github.com/unl0ck/homeassistant-addon-viessmann-gridbox/tree/main/GridboxConnectorAddon-dev"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export USERNAME=$(bashio::config 'username')
export PASSWORD=$(bashio::config 'password')
export LOG_LEVEL=$(bashio::config 'log_level')
export ENABLE_TELEMETRY=$(bashio::config 'enable_telemetry')
export TELEMETRY_SERVER=$(bashio::config 'OverrideTelemetryUrl')
ls -lash /data
cd /build/
ls -lash
Expand Down
2 changes: 1 addition & 1 deletion GridboxConnectorAddon-dev/rootfs/share/cloudSettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.10.1",
"version": "2.11.15",
"urls": {
"login": "https://gridx.eu.auth0.com/oauth/token",
"gateways": "https://api.gridx.de/gateways",
Expand Down
Loading

0 comments on commit 363b728

Please sign in to comment.