Skip to content

Commit

Permalink
Ensure CustomChargingHook has an initial value.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Hale committed Sep 28, 2024
1 parent fe358d6 commit abdbeda
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 88 deletions.
70 changes: 7 additions & 63 deletions battery_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from ve_utils import unwrap_dbus_value
from dbusmonitor import DbusMonitor
from settableservice import SettableService
from data_merger import DataMerger
from collections import deque, namedtuple
import math
import functools
Expand Down Expand Up @@ -236,65 +237,6 @@ def __init__(self, unit, triggerPaths=None, action=None):
BATTERY_PATHS = {**AGGREGATED_BATTERY_PATHS, **ACTIVE_BATTERY_PATHS}


class DataMerger:
def __init__(self, config, service_name_resolver):
if isinstance(config, list):
# convert short-hand format
expanded_config = {service_name_resolver.resolve_service_name(serviceName): list(BATTERY_PATHS) for serviceName in config}
elif isinstance(config, dict):
expanded_config = {}
for k, v in config.items():
if not v:
v = list(BATTERY_PATHS)
expanded_config[service_name_resolver.resolve_service_name(k)] = v
elif config is None:
expanded_config = {}
else:
raise ValueError(f"Unsupported config object: {type(config)}")

self.service_names = list(expanded_config)

self.data_by_path = {}
for service_name, path_list in expanded_config.items():
for p in path_list:
path_values = self.data_by_path.get(p)
if path_values is None:
path_values = {}
self.data_by_path[p] = path_values
path_values[service_name] = None

def init_values(self, service_name, api):
paths_changed = []
for p, path_values in self.data_by_path.items():
if service_name in path_values:
path_values[service_name] = api.get_value(service_name, p)
paths_changed.append(p)
return paths_changed

def clear_values(self, service_name):
paths_changed = []
for p, path_values in self.data_by_path.items():
if service_name in path_values:
path_values[service_name] = None
paths_changed.append(p)
return paths_changed

def update_service_value(self, service_name, path, value):
path_values = self.data_by_path.get(path)
if path_values:
if service_name in path_values:
path_values[service_name] = value

def get_value(self, path):
path_values = self.data_by_path.get(path)
if path_values:
for service_name in self.service_names:
v = path_values.get(service_name)
if v is not None:
return v
return None


class ServiceNameResolver:
def __init__(self, conn):
self.conn = conn
Expand Down Expand Up @@ -405,8 +347,9 @@ def __init__(self, conn, serviceName, config):
scanPaths.remove('/Capacity')

serviceNameResolver = ServiceNameResolver(conn)
self._primaryServices = DataMerger(config.get("primaryServices"), serviceNameResolver)
self._auxiliaryServices = DataMerger(config.get("auxiliaryServices"), serviceNameResolver)
default_config_paths = list(BATTERY_PATHS)
self._primaryServices = DataMerger(config.get("primaryServices"), default_config_paths, serviceNameResolver)
self._auxiliaryServices = DataMerger(config.get("auxiliaryServices"), default_config_paths, serviceNameResolver)
otherServiceNames = set()
otherServiceNames.add("com.victronenergy.system")
otherServiceNames.add("com.victronenergy.settings")
Expand Down Expand Up @@ -861,7 +804,8 @@ def __init__(self, conn, serviceName, config, hook_config):
if not is_battery_service_name(name) and not is_hook(name) and not is_name(name):
raise ValueError(f"Invalid service name: {name}")

self._mergedServices = DataMerger(config, ServiceNameResolver(conn))
default_config_paths = list(BATTERY_PATHS)
self._mergedServices = DataMerger(config, default_config_paths, ServiceNameResolver(conn))

self.hooks = []
for name in list(config):
Expand Down Expand Up @@ -900,7 +844,7 @@ def register(self, timeout=0):
paths_changed.update(changed)

for hook in reversed(self.hooks):
changed = hook.init_values(self.monitor)
changed = hook.init_values()
paths_changed.update(changed)

self._batteries_changed()
Expand Down
59 changes: 59 additions & 0 deletions data_merger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import Dict, List

class DataMerger:
def __init__(self, config, default_config_paths: List[str], service_name_resolver):
if isinstance(config, list):
# convert short-hand format
expanded_config = {service_name_resolver.resolve_service_name(serviceName): default_config_paths for serviceName in config}
elif isinstance(config, dict):
expanded_config = {}
for k, v in config.items():
if not v:
v = default_config_paths
expanded_config[service_name_resolver.resolve_service_name(k)] = v
elif config is None:
expanded_config = {}
else:
raise ValueError(f"Unsupported config object: {type(config)}")

self.service_names: List[str] = list(expanded_config)

self.data_by_path: Dict[str, Dict[str, str]] = {}
for service_name, path_list in expanded_config.items():
for p in path_list:
path_values = self.data_by_path.get(p)
if path_values is None:
path_values = {}
self.data_by_path[p] = path_values
path_values[service_name] = None

def init_values(self, service_name: str, api):
paths_changed = []
for p, path_values in self.data_by_path.items():
if service_name in path_values:
path_values[service_name] = api.get_value(service_name, p)
paths_changed.append(p)
return paths_changed

def clear_values(self, service_name: str):
paths_changed = []
for p, path_values in self.data_by_path.items():
if service_name in path_values:
path_values[service_name] = None
paths_changed.append(p)
return paths_changed

def update_service_value(self, service_name: str, path: str, value):
path_values = self.data_by_path.get(path)
if path_values:
if service_name in path_values:
path_values[service_name] = value

def get_value(self, path: str):
path_values = self.data_by_path.get(path)
if path_values:
for service_name in self.service_names:
v = path_values.get(service_name)
if v is not None:
return v
return None
52 changes: 28 additions & 24 deletions hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def __init__(self, service_name, merger, customName):
self.customName = customName
self.merger.data_by_path["/CustomName"] = {service_name: None}

def init_values(self, api):
def init_values(self):
self.merger.update_service_value(self.service_name, "/CustomName", self.customName)
return ["/CustomName"]

Expand All @@ -20,32 +20,36 @@ def __init__(self, service_name, merger, ccls=None, dcls=None):
self.ccls = ccls
self.dcls = dcls

def init_values(self, api):
return []
def init_values(self):
initial_voltage = self.merger.get_value("/Dc/0/Voltage")
return self.update_cls(initial_voltage) if initial_voltage is not None else []

def update_service_value(self, dbusServiceName, dbusPath, value):
if dbusPath == "/Dc/0/Voltage":
voltage = self.merger.get_value(dbusPath)
paths_changed = []

ccl = None
if self.ccls:
for v, cl in self.ccls.items():
if voltage >= float(v):
ccl = float(cl)
if ccl is not None:
self.merger.update_service_value(self.service_name, "/Info/MaxChargeCurrent", ccl)
paths_changed.append("/Info/MaxChargeCurrent")

dcl = None
if self.dcls:
for v, cl in self.dcls.items():
if voltage >= float(v):
dcl = float(cl)
if dcl is not None:
self.merger.update_service_value(self.service_name, "/Info/MaxDischargeCurrent", dcl)
paths_changed.append("/Info/MaxDischargeCurrent")

return paths_changed
return self.update_cls(voltage)
else:
return []

def update_cls(self, voltage):
paths_changed = []

ccl = None
if self.ccls:
for v, cl in self.ccls.items():
if voltage >= float(v):
ccl = float(cl)
if ccl is not None:
self.merger.update_service_value(self.service_name, "/Info/MaxChargeCurrent", ccl)
paths_changed.append("/Info/MaxChargeCurrent")

dcl = None
if self.dcls:
for v, cl in self.dcls.items():
if voltage >= float(v):
dcl = float(cl)
if dcl is not None:
self.merger.update_service_value(self.service_name, "/Info/MaxDischargeCurrent", dcl)
paths_changed.append("/Info/MaxDischargeCurrent")

return paths_changed
30 changes: 30 additions & 0 deletions tests/hooks_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from data_merger import DataMerger
import hooks
import unittest


class SimpleServiceNameResolver:
def __init(self):
pass

def resolve_service_name(self, name):
return name


class HooksTest(unittest.TestCase):
def test_custom_charging_hook(self):
battery_name = "com.victronenergy.battery.test"
merger = DataMerger(["class:hooks.CustomChargingHook", battery_name], ["/Dc/0/Voltage", "/Info/MaxChargeCurrent"], SimpleServiceNameResolver())
config = {
"ccls": {
"13": 15,
"12": 10
}
}
hook = hooks.CustomChargingHook("class:hooks.CustomChargingHook", merger, **config)
merger.update_service_value(battery_name, "/Info/MaxChargeCurrent", 20)
merger.update_service_value(battery_name, "/Dc/0/Voltage", 12.6)
paths_changed = hook.update_service_value(battery_name, "/Dc/0/Voltage", 12.6)
self.assertListEqual(paths_changed, ["/Info/MaxChargeCurrent"])
self.assertEqual(merger.get_value("/Info/MaxChargeCurrent"), 10)

2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v3.6
v3.7

0 comments on commit abdbeda

Please sign in to comment.