From 5d55e9f3acc3fdbe8c2095f33f3189f43a2d5e35 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Wed, 11 Jun 2025 08:40:14 +0200 Subject: [PATCH 01/13] custom vehicle and charge point colors --- .../control/chargepoint/chargepoint_data.py | 1 + packages/control/ev/ev.py | 1 + .../measurement_logging/write_log.py | 155 ++++++++++-------- 3 files changed, 93 insertions(+), 64 deletions(-) diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py index e112386f0d..84e09fd5bb 100644 --- a/packages/control/chargepoint/chargepoint_data.py +++ b/packages/control/chargepoint/chargepoint_data.py @@ -179,6 +179,7 @@ class Config: configuration: Dict = field(default_factory=empty_dict_factory) ev: int = 0 name: str = "neuer Ladepunkt" + color: str = "#0000ff" type: Optional[str] = None template: int = 0 connected_phases: int = 3 diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index c548f5b25b..706ab6d001 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -78,6 +78,7 @@ class EvData: charge_template: int = field(default=0, metadata={"topic": "charge_template"}) ev_template: int = field(default=0, metadata={"topic": "ev_template"}) name: str = field(default="neues Fahrzeug", metadata={"topic": "name"}) + color: str = field(default="#17a2b8", metadata={"topic": "color"}) tag_id: List[str] = field(default_factory=empty_list_factory, metadata={ "topic": "tag_id"}) get: Get = field(default_factory=get_factory) diff --git a/packages/helpermodules/measurement_logging/write_log.py b/packages/helpermodules/measurement_logging/write_log.py index 0fe9447147..015fdb576e 100644 --- a/packages/helpermodules/measurement_logging/write_log.py +++ b/packages/helpermodules/measurement_logging/write_log.py @@ -20,77 +20,80 @@ # erstellt für jeden Tag eine Datei, die die Daten für den Langzeitgraph enthält. # Dazu werden alle 5 Min folgende Daten als json-Liste gespeichert: -# {"entries": [ -# { -# "timestamp": int, -# "date": str, -# "prices": { -# "grid": Preis für Netzbezug, -# "pv": Preis für PV-Strom, -# "bat": Preis für Speicherstrom -# } -# "cp": { -# "cp1": { -# "imported": Zählerstand in Wh, -# "exported": Zählerstand in Wh -# } -# ... (dynamisch, je nach konfigurierter Anzahl) -# "all": { -# "imported": Zählerstand in Wh, -# "exported": Zählerstand in Wh -# } -# } -# "ev": { -# "ev1": { -# "soc": int in % +# { +# "entries": [ +# { +# "timestamp": int, +# "date": str, +# "prices": { +# "grid": Preis für Netzbezug, +# "pv": Preis für PV-Strom, +# "bat": Preis für Speicherstrom # } -# ... (dynamisch, je nach konfigurierter Anzahl) -# } -# "counter": { -# "counter0": { -# "grid": bool, -# "imported": Wh, -# "exported": Wh +# "cp": { +# "cp1": { +# "imported": Zählerstand in Wh, +# "exported": Zählerstand in Wh +# } +# ... (dynamisch, je nach konfigurierter Anzahl) +# "all": { +# "imported": Zählerstand in Wh, +# "exported": Zählerstand in Wh +# } # } -# ... (dynamisch, je nach konfigurierter Anzahl) -# } -# "pv": { -# "all": { -# "exported": Wh +# "ev": { +# "ev1": { +# "soc": int in % +# } +# ... (dynamisch, je nach konfigurierter Anzahl) # } -# "pv0": { -# "exported": Wh +# "counter": { +# "counter0": { +# "grid": bool, +# "imported": Wh, +# "exported": Wh +# } +# ... (dynamisch, je nach konfigurierter Anzahl) # } -# ... (dynamisch, je nach konfigurierter Anzahl) -# } -# "bat": { -# "all": { -# "imported": Wh, -# "exported": Wh, -# "soc": int in % +# "pv": { +# "all": { +# "exported": Wh +# } +# "pv0": { +# "exported": Wh +# } +# ... (dynamisch, je nach konfigurierter Anzahl) # } -# "bat0": { -# "imported": Wh, -# "exported": Wh, -# "soc": int in % +# "bat": { +# "all": { +# "imported": Wh, +# "exported": Wh, +# "soc": int in % +# } +# "bat0": { +# "imported": Wh, +# "exported": Wh, +# "soc": int in % +# } +# ... (dynamisch, je nach konfigurierter Anzahl) # } -# ... (dynamisch, je nach konfigurierter Anzahl) -# } -# "sh": { -# "sh1": { -# "exported": Wh, -# "imported": Wh, -# wenn konfiguriert: -# "temp1": int in °C, -# "temp2": int in °C, -# "temp3": int in °C +# "sh": { +# "sh1": { +# "exported": Wh, +# "imported": Wh, +# wenn konfiguriert: +# "temp1": int in °C, +# "temp2": int in °C, +# "temp3": int in °C +# }, +# ... (dynamisch, je nach Anzahl konfigurierter Geräte) # }, -# ... (dynamisch, je nach Anzahl konfigurierter Geräte) -# }, -# "hc": {"all": {"imported": Wh # Hausverbrauch}} -# }], -# "names": "names": {"sh1": "", "cp1": "", "counter2": "", "pv3": ""} -# } +# "hc": {"all": {"imported": Wh # Hausverbrauch}} +# } +# ], +# "names": {"cp1": "", "counter2": "", "pv3": ""}, +# "colors": {"cp1": "", "counter2": "", "pv3": ""}, +# } class LogType(Enum): @@ -357,3 +360,27 @@ def get_names(elements: Dict, sh_names: Dict, valid_names: Optional[Dict] = None except (ValueError, KeyError, AttributeError): names.update({entry: entry}) return names + + +def get_colors(elements: Dict) -> Dict: + """ Ermittelt die Farben der Fahrzeuge und Ladepunkte, welche + in elements vorhanden sind und gibt diese als Dictionary zurück. + Parameter + --------- + elements: dict + Dictionary, das die Messwerte enthält. + """ + colors = {} + for group in elements.items(): + if group[0] not in ("ev", "cp"): + continue + for entry in group[1]: + if "all" != entry: + try: + if "ev" in entry: + colors.update({entry: data.data.ev_data[entry].data.color}) + elif "cp" in entry: + colors.update({entry: data.data.cp_data[entry].data.config.color}) + except (ValueError, KeyError, AttributeError): + colors.update({entry: "#000000"}) + return colors From 7bb1598651ddbae1fbc991b5cc77405ec034bdfe Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Wed, 11 Jun 2025 08:46:11 +0200 Subject: [PATCH 02/13] custom component colors --- .../helpermodules/measurement_logging/write_log.py | 10 +++++++--- packages/modules/common/component_setup.py | 3 ++- packages/modules/common/utils/component_parser.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/helpermodules/measurement_logging/write_log.py b/packages/helpermodules/measurement_logging/write_log.py index 015fdb576e..0166a23278 100644 --- a/packages/helpermodules/measurement_logging/write_log.py +++ b/packages/helpermodules/measurement_logging/write_log.py @@ -14,7 +14,7 @@ from helpermodules import timecheck from helpermodules.utils.json_file_handler import write_and_check from helpermodules.utils.topic_parser import decode_payload, get_index -from modules.common.utils.component_parser import get_component_name_by_id +from modules.common.utils.component_parser import get_component_name_by_id, get_component_color_by_id log = logging.getLogger(__name__) @@ -168,6 +168,7 @@ def save_log(log_type: LogType): entries = content["entries"] entries.append(new_entry) content["names"] = get_names(content["entries"][-1], sh_log_data.sh_names) + content["colors"] = get_colors(content["entries"][-1]) write_and_check(filepath, content) return content["entries"] except Exception: @@ -363,7 +364,7 @@ def get_names(elements: Dict, sh_names: Dict, valid_names: Optional[Dict] = None def get_colors(elements: Dict) -> Dict: - """ Ermittelt die Farben der Fahrzeuge und Ladepunkte, welche + """ Ermittelt die Farben der Fahrzeuge, Ladepunkte und Komponenten, welche in elements vorhanden sind und gibt diese als Dictionary zurück. Parameter --------- @@ -372,7 +373,7 @@ def get_colors(elements: Dict) -> Dict: """ colors = {} for group in elements.items(): - if group[0] not in ("ev", "cp"): + if group[0] not in ("ev", "cp", "counter", "pv", "bat"): continue for entry in group[1]: if "all" != entry: @@ -381,6 +382,9 @@ def get_colors(elements: Dict) -> Dict: colors.update({entry: data.data.ev_data[entry].data.color}) elif "cp" in entry: colors.update({entry: data.data.cp_data[entry].data.config.color}) + else: + id = entry.strip(string.ascii_letters) + colors.update({entry: get_component_color_by_id(int(id))}) except (ValueError, KeyError, AttributeError): colors.update({entry: "#000000"}) return colors diff --git a/packages/modules/common/component_setup.py b/packages/modules/common/component_setup.py index c4eaffe1a1..2ddf79a014 100644 --- a/packages/modules/common/component_setup.py +++ b/packages/modules/common/component_setup.py @@ -4,8 +4,9 @@ class ComponentSetup(Generic[T]): - def __init__(self, name: str, type: str, id: int, configuration: T) -> None: + def __init__(self, name: str, color: str, type: str, id: int, configuration: T) -> None: self.name = name + self.color = color self.info = {"manufacturer": None, "model": None} self.type = type self.id = id diff --git a/packages/modules/common/utils/component_parser.py b/packages/modules/common/utils/component_parser.py index 5dcbb226ac..4b3a27be95 100644 --- a/packages/modules/common/utils/component_parser.py +++ b/packages/modules/common/utils/component_parser.py @@ -18,6 +18,16 @@ def get_component_name_by_id(id: int): raise ValueError(f"Element {id} konnte keinem Gerät zugeordnet werden.") +def get_component_color_by_id(id: int): + for item in data.data.system_data.values(): + if isinstance(item, AbstractDevice): + for comp in item.components.values(): + if comp.component_config.id == id: + return comp.component_config.color + else: + raise ValueError(f"Element {id} konnte keinem Gerät zugeordnet werden.") + + def get_io_name_by_id(id: int): for item in data.data.system_data.values(): if isinstance(item, AbstractIoDevice): From a0f040922668bec964e3554f1823f023675268e5 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Wed, 11 Jun 2025 08:47:59 +0200 Subject: [PATCH 03/13] update config step 1 --- packages/helpermodules/update_config.py | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 8c196a0751..082939d2ed 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 116 + DATASTORE_VERSION = 117 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -370,6 +370,7 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/charge_template$", "^openWB/vehicle/[0-9]+/ev_template$", "^openWB/vehicle/[0-9]+/name$", + "^openWB/vehicle/[0-9]+/color$", "^openWB/vehicle/[0-9]+/info$", "^openWB/vehicle/[0-9]+/soc_module/calculated_soc_state$", "^openWB/vehicle/[0-9]+/soc_module/config$", @@ -571,6 +572,7 @@ class UpdateConfig: ("openWB/counter/config/consider_less_charging", counter_all.Config().consider_less_charging), ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), ("openWB/vehicle/0/name", "Standard-Fahrzeug"), + ("openWB/vehicle/0/color", "#17a2b8"), ("openWB/vehicle/0/info", {"manufacturer": None, "model": None}), ("openWB/vehicle/0/charge_template", ev.Ev(0).charge_template.data.id), ("openWB/vehicle/0/soc_module/config", NO_MODULE), @@ -3004,3 +3006,34 @@ def upgrade(topic: str, payload) -> Optional[dict]: return {topic: payload} self._loop_all_received_topics(upgrade) self._append_datastore_version(116) + + def upgrade_datastore_117(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + # add vehicle color to vehicle topics + if re.search("^openWB/vehicle/[0-9]+/name$", topic) is not None: + vehicle_color_topic = topic.replace("/name", "/color") + if vehicle_color_topic not in self.all_received_topics: + return {vehicle_color_topic: "#17a2b8"} + # add property "color" to charge points + if re.search("^openWB/chargepoint/[0-9]+/config$", topic) is not None: + config = decode_payload(payload) + if "color" not in config: + config.update({"color": "#17a2b8"}) + return {topic: config} + # add property "color" to components + if re.search("^openWB/system/device/[0-9]+/component/[0-9]+/config$", topic) is not None: + config = decode_payload(payload) + if "color" not in config: + if config.get("type").contains("counter"): + config.update({"color": "#dc3545"}) + elif config.get("type").contains("bat"): + config.update({"color": "#ffc107"}) + elif config.get("type").contains("inverter"): + config.update({"color": "#28a745"}) + else: + log.warning(f"Unknown component type {config.get('type')} for topic {topic}.") + config.update({"color": "#000000"}) + return {topic: config} + self._loop_all_received_topics(upgrade) + # ToDo: update already present log files with color information + self._append_datastore_version(117) From da5c9941577e6caa9f015f769321c3992483b15d Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 12 Jun 2025 09:22:02 +0200 Subject: [PATCH 04/13] fix upgrade config --- packages/helpermodules/update_config.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 082939d2ed..3e241dfc84 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3011,28 +3011,35 @@ def upgrade_datastore_117(self) -> None: def upgrade(topic: str, payload) -> Optional[dict]: # add vehicle color to vehicle topics if re.search("^openWB/vehicle/[0-9]+/name$", topic) is not None: + log.debug(f"Received vehicle name topic {topic}") vehicle_color_topic = topic.replace("/name", "/color") + log.debug(f"Checking for vehicle color topic {vehicle_color_topic}") if vehicle_color_topic not in self.all_received_topics: + log.debug(f"Adding vehicle color topic {vehicle_color_topic} with value '#17a2b8'") return {vehicle_color_topic: "#17a2b8"} # add property "color" to charge points if re.search("^openWB/chargepoint/[0-9]+/config$", topic) is not None: config = decode_payload(payload) + log.debug(f"Received charge point config topic {topic} with payload {payload}") if "color" not in config: - config.update({"color": "#17a2b8"}) + config.update({"color": "#0000ff"}) + log.debug(f"Added color to charge point config {config}") return {topic: config} # add property "color" to components if re.search("^openWB/system/device/[0-9]+/component/[0-9]+/config$", topic) is not None: config = decode_payload(payload) + log.debug(f"Received component config topic {topic} with payload {payload}") if "color" not in config: - if config.get("type").contains("counter"): + if "counter" in config.get("type").lower(): config.update({"color": "#dc3545"}) - elif config.get("type").contains("bat"): + elif "bat" in config.get("type").lower(): config.update({"color": "#ffc107"}) - elif config.get("type").contains("inverter"): + elif "inverter" in config.get("type").lower(): config.update({"color": "#28a745"}) else: log.warning(f"Unknown component type {config.get('type')} for topic {topic}.") config.update({"color": "#000000"}) + log.debug(f"Updated component config with color {config}") return {topic: config} self._loop_all_received_topics(upgrade) # ToDo: update already present log files with color information From 656d50a7c8bf0ce401de03294e2bb517ff2bf6c4 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 12 Jun 2025 13:22:21 +0200 Subject: [PATCH 05/13] fix component setup --- packages/modules/common/component_setup.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/modules/common/component_setup.py b/packages/modules/common/component_setup.py index 2ddf79a014..022ac6571c 100644 --- a/packages/modules/common/component_setup.py +++ b/packages/modules/common/component_setup.py @@ -4,10 +4,18 @@ class ComponentSetup(Generic[T]): - def __init__(self, name: str, color: str, type: str, id: int, configuration: T) -> None: + def __init__(self, name: str, type: str, id: int, configuration: T) -> None: self.name = name - self.color = color self.info = {"manufacturer": None, "model": None} self.type = type self.id = id self.configuration = configuration + if "counter" in type.lower(): + self.color = "#dc3545" + elif "bat" in type.lower(): + self.color = "#ffc107" + elif "inverter" in type.lower(): + self.color = "#28a745" + else: + # Default color for other types + self.color = "#000000" From 4b95554f7c34e238f5ec2c85552ae1efb24780ae Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 12 Jun 2025 13:22:44 +0200 Subject: [PATCH 06/13] extend setdata for new vehicle topic --- packages/helpermodules/setdata.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 455a8e4ad7..775d05682d 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -365,6 +365,8 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage): try: if "/name" in msg.topic: self._validate_value(msg, str) + elif "/color" in msg.topic: + self._validate_value(msg, str) elif "/info" in msg.topic: self._validate_value(msg, "json") elif "openWB/set/vehicle/set/vehicle_update_completed" in msg.topic: From 5917b61f3fac90fbe71a5a32a634764f7391eeb9 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 14 Jul 2025 10:30:47 +0200 Subject: [PATCH 07/13] use bootstrap primary color for charge points --- packages/control/chargepoint/chargepoint_data.py | 2 +- packages/helpermodules/update_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py index 84e09fd5bb..2fc7b7603a 100644 --- a/packages/control/chargepoint/chargepoint_data.py +++ b/packages/control/chargepoint/chargepoint_data.py @@ -179,7 +179,7 @@ class Config: configuration: Dict = field(default_factory=empty_dict_factory) ev: int = 0 name: str = "neuer Ladepunkt" - color: str = "#0000ff" + color: str = "#007bff" type: Optional[str] = None template: int = 0 connected_phases: int = 3 diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 3e241dfc84..eb6d4b4ccf 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3022,7 +3022,7 @@ def upgrade(topic: str, payload) -> Optional[dict]: config = decode_payload(payload) log.debug(f"Received charge point config topic {topic} with payload {payload}") if "color" not in config: - config.update({"color": "#0000ff"}) + config.update({"color": "#007bff"}) log.debug(f"Added color to charge point config {config}") return {topic: config} # add property "color" to components From 352194ad63ace1ca0d3866ecaedfed70950407dc Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 31 Jul 2025 09:20:23 +0200 Subject: [PATCH 08/13] update logfiles --- packages/helpermodules/update_config.py | 54 +++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index eb6d4b4ccf..f6ec74816b 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -1,3 +1,4 @@ +from concurrent.futures import ProcessPoolExecutor import copy from dataclasses import asdict import datetime @@ -2501,6 +2502,13 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(90) + BLACK = "#000000" + BLUE = "#007bff" + CYAN = "#17a2b8" + GREEN = "#28a745" + RED = "#dc3545" + YELLOW = "#ffc107" + def upgrade_datastore_91(self) -> None: def upgrade(topic: str, payload) -> Optional[dict]: if re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None: @@ -3008,6 +3016,13 @@ def upgrade(topic: str, payload) -> Optional[dict]: self._append_datastore_version(116) def upgrade_datastore_117(self) -> None: + def add_colors_to_logs(): + files = glob.glob(str(self.base_path / "data" / "daily_log") + "/*") + files.extend(glob.glob(str(self.base_path / "data" / "monthly_log") + "/*")) + files.sort() + with ProcessPoolExecutor() as executor: + executor.map(self.process_file, files) + def upgrade(topic: str, payload) -> Optional[dict]: # add vehicle color to vehicle topics if re.search("^openWB/vehicle/[0-9]+/name$", topic) is not None: @@ -3016,13 +3031,13 @@ def upgrade(topic: str, payload) -> Optional[dict]: log.debug(f"Checking for vehicle color topic {vehicle_color_topic}") if vehicle_color_topic not in self.all_received_topics: log.debug(f"Adding vehicle color topic {vehicle_color_topic} with value '#17a2b8'") - return {vehicle_color_topic: "#17a2b8"} + return {vehicle_color_topic: self.CYAN} # add property "color" to charge points if re.search("^openWB/chargepoint/[0-9]+/config$", topic) is not None: config = decode_payload(payload) log.debug(f"Received charge point config topic {topic} with payload {payload}") if "color" not in config: - config.update({"color": "#007bff"}) + config.update({"color": self.BLUE}) log.debug(f"Added color to charge point config {config}") return {topic: config} # add property "color" to components @@ -3031,16 +3046,41 @@ def upgrade(topic: str, payload) -> Optional[dict]: log.debug(f"Received component config topic {topic} with payload {payload}") if "color" not in config: if "counter" in config.get("type").lower(): - config.update({"color": "#dc3545"}) + config.update({"color": self.RED}) elif "bat" in config.get("type").lower(): - config.update({"color": "#ffc107"}) + config.update({"color": self.YELLOW}) elif "inverter" in config.get("type").lower(): - config.update({"color": "#28a745"}) + config.update({"color": self.GREEN}) else: log.warning(f"Unknown component type {config.get('type')} for topic {topic}.") - config.update({"color": "#000000"}) + config.update({"color": self.BLACK}) log.debug(f"Updated component config with color {config}") return {topic: config} self._loop_all_received_topics(upgrade) - # ToDo: update already present log files with color information + add_colors_to_logs() self._append_datastore_version(117) + + def process_file(self, file): + colors = {} + with open(file, "r+") as jsonFile: + content_raw = jsonFile.read() + content = json.loads(content_raw) + if "colors" in content: + return + for key in content["names"].keys(): + if "bat" in key: + colors[key] = self.YELLOW + elif "counter" in key: + colors[key] = self.RED + elif "cp" in key: + colors[key] = self.BLUE + elif "ev" in key: + colors[key] = self.CYAN + elif "inverter" in key: + colors[key] = self.GREEN + else: + colors[key] = self.BLACK + content["colors"] = colors + jsonFile.seek(0) + jsonFile.write(json.dumps(content)) + jsonFile.truncate() From 1b5f1a53e4e7f954ca21a25ca5f84379c09ed442 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 4 Aug 2025 12:58:01 +0200 Subject: [PATCH 09/13] rename color constants --- packages/helpermodules/update_config.py | 41 +++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index f6ec74816b..5772ab8d42 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -55,6 +55,14 @@ NO_MODULE = {"type": None, "configuration": {}} +# Default colors +COLOR_CHARGEPOINT = "#007bff" # Default color for charge points: blue +COLOR_VEHICLE = "#17a2b8" # Default color for vehicles: teal +COLOR_INVERTER = "#28a745" # Default color for inverters: green +COLOR_COUNTER = "#dc3545" # Default color for counters: red +COLOR_BATTERY = "#ffc107" # Default color for batteries: yellow +COLOR_UNKNOWN = "#000000" # Default color for unknown components: black + class UpdateConfig: @@ -2502,13 +2510,6 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) self._append_datastore_version(90) - BLACK = "#000000" - BLUE = "#007bff" - CYAN = "#17a2b8" - GREEN = "#28a745" - RED = "#dc3545" - YELLOW = "#ffc107" - def upgrade_datastore_91(self) -> None: def upgrade(topic: str, payload) -> Optional[dict]: if re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None: @@ -3030,14 +3031,14 @@ def upgrade(topic: str, payload) -> Optional[dict]: vehicle_color_topic = topic.replace("/name", "/color") log.debug(f"Checking for vehicle color topic {vehicle_color_topic}") if vehicle_color_topic not in self.all_received_topics: - log.debug(f"Adding vehicle color topic {vehicle_color_topic} with value '#17a2b8'") - return {vehicle_color_topic: self.CYAN} + log.debug(f"Adding vehicle color topic {vehicle_color_topic} with value '{COLOR_VEHICLE}'") + return {vehicle_color_topic: COLOR_VEHICLE} # add property "color" to charge points if re.search("^openWB/chargepoint/[0-9]+/config$", topic) is not None: config = decode_payload(payload) log.debug(f"Received charge point config topic {topic} with payload {payload}") if "color" not in config: - config.update({"color": self.BLUE}) + config.update({"color": COLOR_CHARGEPOINT}) log.debug(f"Added color to charge point config {config}") return {topic: config} # add property "color" to components @@ -3046,14 +3047,14 @@ def upgrade(topic: str, payload) -> Optional[dict]: log.debug(f"Received component config topic {topic} with payload {payload}") if "color" not in config: if "counter" in config.get("type").lower(): - config.update({"color": self.RED}) + config.update({"color": COLOR_COUNTER}) elif "bat" in config.get("type").lower(): - config.update({"color": self.YELLOW}) + config.update({"color": COLOR_BATTERY}) elif "inverter" in config.get("type").lower(): - config.update({"color": self.GREEN}) + config.update({"color": COLOR_INVERTER}) else: log.warning(f"Unknown component type {config.get('type')} for topic {topic}.") - config.update({"color": self.BLACK}) + config.update({"color": COLOR_UNKNOWN}) log.debug(f"Updated component config with color {config}") return {topic: config} self._loop_all_received_topics(upgrade) @@ -3069,17 +3070,17 @@ def process_file(self, file): return for key in content["names"].keys(): if "bat" in key: - colors[key] = self.YELLOW + colors[key] = COLOR_BATTERY elif "counter" in key: - colors[key] = self.RED + colors[key] = COLOR_COUNTER elif "cp" in key: - colors[key] = self.BLUE + colors[key] = COLOR_CHARGEPOINT elif "ev" in key: - colors[key] = self.CYAN + colors[key] = COLOR_VEHICLE elif "inverter" in key: - colors[key] = self.GREEN + colors[key] = COLOR_INVERTER else: - colors[key] = self.BLACK + colors[key] = COLOR_UNKNOWN content["colors"] = colors jsonFile.seek(0) jsonFile.write(json.dumps(content)) From 56020b497c6120903bc5d2725eef99bfe68f5abf Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Mon, 20 Oct 2025 13:16:27 +0200 Subject: [PATCH 10/13] upgrade method --- packages/helpermodules/update_config.py | 98 +++++++++++++------------ 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 5772ab8d42..4dbe9184f2 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -55,14 +55,6 @@ NO_MODULE = {"type": None, "configuration": {}} -# Default colors -COLOR_CHARGEPOINT = "#007bff" # Default color for charge points: blue -COLOR_VEHICLE = "#17a2b8" # Default color for vehicles: teal -COLOR_INVERTER = "#28a745" # Default color for inverters: green -COLOR_COUNTER = "#dc3545" # Default color for counters: red -COLOR_BATTERY = "#ffc107" # Default color for batteries: yellow -COLOR_UNKNOWN = "#000000" # Default color for unknown components: black - class UpdateConfig: @@ -3017,71 +3009,81 @@ def upgrade(topic: str, payload) -> Optional[dict]: self._append_datastore_version(116) def upgrade_datastore_117(self) -> None: + DEFAULT_COLORS = { + "CHARGEPOINT": "#007bff", + "VEHICLE": "#17a2b8", + "INVERTER": "#28a745", + "COUNTER": "#dc3545", + "BATTERY": "#ffc107", + "UNKNOWN": "#000000" + } + + def _add_colors_to_log(file): + colors = {} + with open(file, "r+") as jsonFile: + content_raw = jsonFile.read() + content = json.loads(content_raw) + if "colors" in content: + return + for key in content["names"].keys(): + if "bat" in key: + colors[key] = DEFAULT_COLORS["BATTERY"] + elif "counter" in key: + colors[key] = DEFAULT_COLORS["COUNTER"] + elif "cp" in key: + colors[key] = DEFAULT_COLORS["CHARGEPOINT"] + elif "ev" in key: + colors[key] = DEFAULT_COLORS["VEHICLE"] + elif "inverter" in key: + colors[key] = DEFAULT_COLORS["INVERTER"] + else: + colors[key] = DEFAULT_COLORS["UNKNOWN"] + content["colors"] = colors + jsonFile.seek(0) + jsonFile.write(json.dumps(content)) + jsonFile.truncate() + def add_colors_to_logs(): files = glob.glob(str(self.base_path / "data" / "daily_log") + "/*") files.extend(glob.glob(str(self.base_path / "data" / "monthly_log") + "/*")) files.sort() with ProcessPoolExecutor() as executor: - executor.map(self.process_file, files) + executor.map(_add_colors_to_log, files) def upgrade(topic: str, payload) -> Optional[dict]: # add vehicle color to vehicle topics if re.search("^openWB/vehicle/[0-9]+/name$", topic) is not None: - log.debug(f"Received vehicle name topic {topic}") + log.debug(f"Received vehicle name topic '{topic}'") vehicle_color_topic = topic.replace("/name", "/color") - log.debug(f"Checking for vehicle color topic {vehicle_color_topic}") + log.debug(f"Checking for vehicle color topic '{vehicle_color_topic}'") if vehicle_color_topic not in self.all_received_topics: - log.debug(f"Adding vehicle color topic {vehicle_color_topic} with value '{COLOR_VEHICLE}'") - return {vehicle_color_topic: COLOR_VEHICLE} + log.debug(f"Adding vehicle color topic '{vehicle_color_topic}'" + f" with value: '{DEFAULT_COLORS['VEHICLE']}'") + return {vehicle_color_topic: DEFAULT_COLORS['VEHICLE']} # add property "color" to charge points if re.search("^openWB/chargepoint/[0-9]+/config$", topic) is not None: config = decode_payload(payload) - log.debug(f"Received charge point config topic {topic} with payload {payload}") + log.debug(f"Received charge point config topic '{topic}' with payload: {payload}") if "color" not in config: - config.update({"color": COLOR_CHARGEPOINT}) - log.debug(f"Added color to charge point config {config}") + config.update({"color": DEFAULT_COLORS['CHARGEPOINT']}) + log.debug(f"Added color to charge point config: {config}") return {topic: config} # add property "color" to components if re.search("^openWB/system/device/[0-9]+/component/[0-9]+/config$", topic) is not None: config = decode_payload(payload) - log.debug(f"Received component config topic {topic} with payload {payload}") + log.debug(f"Received component config topic '{topic}' with payload: {payload}") if "color" not in config: if "counter" in config.get("type").lower(): - config.update({"color": COLOR_COUNTER}) + config.update({"color": DEFAULT_COLORS['COUNTER']}) elif "bat" in config.get("type").lower(): - config.update({"color": COLOR_BATTERY}) + config.update({"color": DEFAULT_COLORS['BATTERY']}) elif "inverter" in config.get("type").lower(): - config.update({"color": COLOR_INVERTER}) + config.update({"color": DEFAULT_COLORS['INVERTER']}) else: - log.warning(f"Unknown component type {config.get('type')} for topic {topic}.") - config.update({"color": COLOR_UNKNOWN}) - log.debug(f"Updated component config with color {config}") + log.warning(f"Unknown component type '{config.get('type')}' for topic '{topic}'.") + config.update({"color": DEFAULT_COLORS['UNKNOWN']}) + log.debug(f"Updated component config with color: {config}") return {topic: config} self._loop_all_received_topics(upgrade) add_colors_to_logs() self._append_datastore_version(117) - - def process_file(self, file): - colors = {} - with open(file, "r+") as jsonFile: - content_raw = jsonFile.read() - content = json.loads(content_raw) - if "colors" in content: - return - for key in content["names"].keys(): - if "bat" in key: - colors[key] = COLOR_BATTERY - elif "counter" in key: - colors[key] = COLOR_COUNTER - elif "cp" in key: - colors[key] = COLOR_CHARGEPOINT - elif "ev" in key: - colors[key] = COLOR_VEHICLE - elif "inverter" in key: - colors[key] = COLOR_INVERTER - else: - colors[key] = COLOR_UNKNOWN - content["colors"] = colors - jsonFile.seek(0) - jsonFile.write(json.dumps(content)) - jsonFile.truncate() From 27c9225de707654d7173d8ad861dba944d525846 Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Thu, 5 Mar 2026 08:21:07 +0100 Subject: [PATCH 11/13] update dynsec roles --- .../mosquitto/public/default-dynamic-security.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/config/mosquitto/public/default-dynamic-security.json b/data/config/mosquitto/public/default-dynamic-security.json index 5ee76266a0..87276085e0 100644 --- a/data/config/mosquitto/public/default-dynamic-security.json +++ b/data/config/mosquitto/public/default-dynamic-security.json @@ -1768,6 +1768,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientSend", + "topic": "openWB/set/vehicle/+/color", + "priority": 0, + "allow": true + }, { "acltype": "publishClientSend", "topic": "openWB/set/vehicle/+/charge_template", @@ -1846,6 +1852,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientReceive", + "topic": "openWB/vehicle/+/color", + "priority": 0, + "allow": true + }, { "acltype": "publishClientReceive", "topic": "openWB/vehicle/+/charge_template", From ab438b33598a2712d7ae3f978d31bd83fe40fd8e Mon Sep 17 00:00:00 2001 From: Lutz Bender Date: Tue, 10 Mar 2026 08:13:18 +0100 Subject: [PATCH 12/13] update dynsec role acls for vehicle color --- data/config/mosquitto/public/default-dynamic-security.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/config/mosquitto/public/default-dynamic-security.json b/data/config/mosquitto/public/default-dynamic-security.json index 87276085e0..9a4ecd1851 100644 --- a/data/config/mosquitto/public/default-dynamic-security.json +++ b/data/config/mosquitto/public/default-dynamic-security.json @@ -291,6 +291,12 @@ "topic": "openWB/vehicle/+/name", "priority": 0, "allow": true + }, + { + "acltype": "publishClientReceive", + "topic": "openWB/vehicle/+/color", + "priority": 0, + "allow": true } ] }, From 7c9bfb7271695da0d100302afcd3c23eecfc1380 Mon Sep 17 00:00:00 2001 From: benderl Date: Thu, 26 Mar 2026 15:24:20 +0100 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/helpermodules/update_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 4dbe9184f2..3887c462a1 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3034,7 +3034,7 @@ def _add_colors_to_log(file): colors[key] = DEFAULT_COLORS["CHARGEPOINT"] elif "ev" in key: colors[key] = DEFAULT_COLORS["VEHICLE"] - elif "inverter" in key: + elif "pv" in key: colors[key] = DEFAULT_COLORS["INVERTER"] else: colors[key] = DEFAULT_COLORS["UNKNOWN"] @@ -3073,11 +3073,12 @@ def upgrade(topic: str, payload) -> Optional[dict]: config = decode_payload(payload) log.debug(f"Received component config topic '{topic}' with payload: {payload}") if "color" not in config: - if "counter" in config.get("type").lower(): + component_type = (config.get("type") or "").lower() + if "counter" in component_type: config.update({"color": DEFAULT_COLORS['COUNTER']}) - elif "bat" in config.get("type").lower(): + elif "bat" in component_type: config.update({"color": DEFAULT_COLORS['BATTERY']}) - elif "inverter" in config.get("type").lower(): + elif "inverter" in component_type: config.update({"color": DEFAULT_COLORS['INVERTER']}) else: log.warning(f"Unknown component type '{config.get('type')}' for topic '{topic}'.")