Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/control/chargepoint/chargepoint_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class Config:
configuration: Dict = field(default_factory=empty_dict_factory)
ev: int = 0
name: str = "neuer Ladepunkt"
color: str = "#007bff"
type: Optional[str] = None
template: int = 0
connected_phases: int = 3
Expand Down
1 change: 1 addition & 0 deletions packages/control/ev/ev.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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)
Expand Down
161 changes: 96 additions & 65 deletions packages/helpermodules/measurement_logging/write_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,83 +14,86 @@
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__)

# 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):
Expand Down Expand Up @@ -165,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:
Expand Down Expand Up @@ -357,3 +361,30 @@ 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, Ladepunkte und Komponenten, 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", "counter", "pv", "bat"):
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})
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
2 changes: 2 additions & 0 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
85 changes: 84 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from concurrent.futures import ProcessPoolExecutor
import copy
from dataclasses import asdict
import datetime
Expand Down Expand Up @@ -57,7 +58,7 @@

class UpdateConfig:

DATASTORE_VERSION = 101
DATASTORE_VERSION = 102

valid_topic = [
"^openWB/bat/config/bat_control_permitted$",
Expand Down Expand Up @@ -360,6 +361,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$",
Expand Down Expand Up @@ -523,6 +525,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),
Expand Down Expand Up @@ -2625,3 +2628,83 @@ def upgrade(topic: str, payload) -> None:
Pub().pub(topic, payload)
self._loop_all_received_topics(upgrade)
self._append_datastore_version(101)

def upgrade_datastore_102(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(_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}'")
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}'"
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}")
if "color" not in 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}")
if "color" not in config:
if "counter" in config.get("type").lower():
config.update({"color": DEFAULT_COLORS['COUNTER']})
elif "bat" in config.get("type").lower():
config.update({"color": DEFAULT_COLORS['BATTERY']})
elif "inverter" in config.get("type").lower():
config.update({"color": DEFAULT_COLORS['INVERTER']})
else:
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(102)
9 changes: 9 additions & 0 deletions packages/modules/common/component_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ def __init__(self, name: str, type: str, id: int, configuration: T) -> 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"
10 changes: 10 additions & 0 deletions packages/modules/common/utils/component_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down