Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c76d67b
updated dependencies
benderl Jan 20, 2025
b755ec9
Merge pull request #2162 from benderl/cards-display
benderl Jan 20, 2025
673ed27
Build Display Theme: Cards
benderl Jan 20, 2025
627ccc2
Colors theme display: Fixes, Reload/Shutdown-Buttons (#2161)
cshagen Jan 20, 2025
2a06771
Build Display Theme: Colors
benderl Jan 20, 2025
fcbf932
build ui
benderl Jan 20, 2025
be133d9
Merge pull request #2164 from benderl/build-ui
benderl Jan 20, 2025
812d3b6
fix loadmanagement error timer
LKuemmel Jan 20, 2025
12775cd
Merge pull request #2165 from LKuemmel/fix
LKuemmel Jan 20, 2025
6e3a3a7
fix different phases at the end of charging
LKuemmel Jan 22, 2025
ec68a89
Merge pull request #2168 from LKuemmel/fix
LKuemmel Jan 22, 2025
93f1762
fix deviating evse current
LKuemmel Jan 23, 2025
8996d1b
ammend
LKuemmel Jan 23, 2025
47c9346
Merge pull request #2169 from LKuemmel/fix
LKuemmel Jan 23, 2025
a0fb6d4
Zabbix monitoring (#2166)
ndrsnhs Jan 23, 2025
7a91346
fix pv 3p1p failed phase switch (#2173)
LKuemmel Jan 24, 2025
a70a100
fix display when no chargepoints are configured
cshagen Jan 25, 2025
d708e17
fix zoom function
cshagen Jan 27, 2025
42025f0
update dependencies
cshagen Jan 27, 2025
ec921f6
publish soc_timestamp (#2153)
MartinRinas Jan 27, 2025
aed1345
invert counter powers (#2175)
ndrsnhs Jan 27, 2025
ccfc5f4
Merge pull request #2174 from cshagen/web-fixes
benderl Jan 27, 2025
548287d
format install scripts
benderl Jan 27, 2025
d6ccee7
return timestamp as seconds, not milliseconds (#2177)
MartinRinas Jan 27, 2025
6fcf545
Merge pull request #2176 from benderl/fixes
benderl Jan 28, 2025
6281066
Fix Kostal SEM, secondary as second entry in chargepoint drop down (#…
LKuemmel Jan 29, 2025
12832a3
fix Sungrow fault text (#2178)
ndrsnhs Jan 29, 2025
2072197
fix pv charging: start charge after phase switch without switch on de…
LKuemmel Jan 29, 2025
ae1dc55
Solax g2+g4 (#2157)
ndrsnhs Jan 30, 2025
71a64a2
build UI (#2181)
LKuemmel Jan 30, 2025
1d26a84
Update version 2.1.7-RC.1
LKuemmel Jan 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 1 addition & 2 deletions packages/control/algorithm/surplus_controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ def _fix_deviating_evse_current(self, chargepoint: Chargepoint) -> float:
MAX_DEVIATION = 1.1
evse_current = chargepoint.data.get.evse_current
if evse_current and chargepoint.data.set.current != chargepoint.data.set.current_prev:
formatted_evse_current = evse_current if evse_current < 32 else evse_current / 100
offset = formatted_evse_current - max(chargepoint.data.get.currents)
offset = evse_current - max(chargepoint.data.get.currents)
if abs(offset) >= MAX_DEVIATION:
current_with_offset = chargepoint.data.set.current + offset
current = min(current_with_offset, chargepoint.data.control_parameter.required_current)
Expand Down
35 changes: 21 additions & 14 deletions packages/control/chargepoint/chargepoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from control.chargepoint.rfid import ChargepointRfidMixin
from control.ev.ev import Ev
from control import phase_switch
from control.chargepoint.chargepoint_state import ChargepointState
from control.chargepoint.chargepoint_state import CHARGING_STATES, ChargepointState
from helpermodules.phase_mapping import convert_single_evu_phase_to_cp_phase
from helpermodules.pub import Pub
from helpermodules import timecheck
Expand Down Expand Up @@ -361,9 +361,10 @@ def _is_phase_switch_required(self) -> bool:
self.data.control_parameter.state == ChargepointState.SWITCH_OFF_DELAY) and
# Nach Ablauf der Laden aktiv halten Zeit, sollte mit der vorgegebenen Phasenzahl geladen werden.
self.check_deviating_contactor_states(self.data.set.phases_to_use, self.data.get.phases_in_use)) or
# Vorgegebene Phasenzahl hat sich geändert
self.check_deviating_contactor_states(self.data.set.phases_to_use,
self.data.control_parameter.phases)) and
# Vorgegebene Phasenzahl hat sich geändert und es wird geladen
(self.check_deviating_contactor_states(self.data.set.phases_to_use,
self.data.control_parameter.phases) and
self.data.control_parameter.state in CHARGING_STATES)) and
# Wenn ein Soll-Strom vorgegeben ist, muss das Auto auch laden, damit umgeschaltet wird, sonst
# wird zB bei automatischer Umschaltung ständig versucht auf 1 Phase zurück zu schalten, wenn
# das Auto bei 3 Phasen voll ist.
Expand All @@ -383,9 +384,7 @@ def _is_phase_switch_required(self) -> bool:
# Umschaltung fehlgeschlagen
if self.data.set.phases_to_use != self.data.get.phases_in_use:
if data.data.general_data.data.chargemode_config.retry_failed_phase_switches:
if self.data.control_parameter.failed_phase_switches <= self.MAX_FAILED_PHASE_SWITCHES:
self.data.control_parameter.failed_phase_switches += 1
else:
if self.data.control_parameter.failed_phase_switches > self.MAX_FAILED_PHASE_SWITCHES:
phase_switch_required = False
self.set_state_and_log(
"Keine Phasenumschaltung, da die maximale Anzahl an Fehlversuchen erreicht wurde. Die "
Expand All @@ -396,6 +395,7 @@ def _is_phase_switch_required(self) -> bool:
"Keine Phasenumschaltung, da wiederholtes Anstoßen der Umschaltung in den übergreifenden "
"Ladeeinstellungen deaktiviert wurde. Die aktuelle "
"Phasenzahl wird bis zum Abstecken beibehalten.")
self.data.control_parameter.failed_phase_switches += 1
return phase_switch_required

STOP_CHARGING = ", dafür wird die Ladung unterbrochen."
Expand Down Expand Up @@ -518,8 +518,10 @@ def get_phases_by_selected_chargemode(self) -> int:
# Wenn keine Umschaltung verbaut ist, die Phasenzahl nehmen, mit der geladen wird. Damit werden zB auch
# einphasige EV an dreiphasigen openWBs korrekt berücksichtigt.
phases = self.data.get.phases_in_use or self.data.set.phases_to_use
elif (chargemode == 0 and (self.data.set.phases_to_use == self.data.get.phases_in_use or
self.data.get.phases_in_use == 0)):
elif self.data.control_parameter.state == ChargepointState.PERFORMING_PHASE_SWITCH:
phases = self.data.set.phases_to_use
log.debug(f"Umschaltung wird durchgeführt, Phasenzahl nicht ändern {phases}")
elif chargemode == 0:
# Wenn die Lademodus-Phasen 0 sind, wird die bisher genutzte Phasenzahl weiter genutzt,
# bis der Algorithmus eine Umschaltung vorgibt, zB weil der gewählte Lademodus eine
# andere Phasenzahl benötigt oder bei PV-Laden die automatische Umschaltung aktiv ist.
Expand All @@ -537,9 +539,6 @@ def get_phases_by_selected_chargemode(self) -> int:
# phases_target
phases = self.data.config.connected_phases
log.debug(f"Phasenzahl Lademodus: {phases}")
elif self.data.control_parameter.state == ChargepointState.PERFORMING_PHASE_SWITCH:
phases = self.data.set.phases_to_use
log.debug(f"Umschaltung wird durchgeführt, Phasenzahl nicht ändern {phases}")
else:
if chargemode == 0:
phases = self.data.control_parameter.phases
Expand Down Expand Up @@ -829,11 +828,19 @@ def cp_ev_chargemode_support_phase_switch(self) -> bool:
control_parameter.submode == Chargemode.PV_CHARGING and
data.data.general_data.get_phases_chargemode(Chargemode.SCHEDULED_CHARGING.value,
control_parameter.submode) == 0)
if ((data.data.general_data.data.chargemode_config.retry_failed_phase_switches and
self.data.control_parameter.failed_phase_switches > self.MAX_FAILED_PHASE_SWITCHES) or
(data.data.general_data.data.chargemode_config.retry_failed_phase_switches is False and
self.data.control_parameter.failed_phase_switches == 1)):
failed_phase_switches_reached = True
else:
failed_phase_switches_reached = False
return (self.cp_ev_support_phase_switch() and
self.data.get.charge_state and
(pv_auto_switch or scheduled_auto_switch) and
control_parameter.state == ChargepointState.CHARGING_ALLOWED or
control_parameter.state == ChargepointState.PHASE_SWITCH_DELAY)
(control_parameter.state == ChargepointState.CHARGING_ALLOWED or
control_parameter.state == ChargepointState.PHASE_SWITCH_DELAY) and
failed_phase_switches_reached is False)

def cp_ev_support_phase_switch(self) -> bool:
return (self.data.config.auto_phase_switch_hw and
Expand Down
3 changes: 2 additions & 1 deletion packages/control/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ def _get_loadmanagement_state(self) -> None:
if self.data.get.fault_state == FaultStateLevel.ERROR:
if self.data.set.error_timer is None:
self.data.set.error_timer = timecheck.create_timestamp()
if timecheck.check_timestamp(self.data.set.error_timer, self.MAX_EVU_ERROR_DURATION) is False:
return True
elif timecheck.check_timestamp(self.data.set.error_timer, self.MAX_EVU_ERROR_DURATION) is False:
for cp in connected_cps:
if self.num == data.data.counter_all_data.get_id_evu_counter():
data.data.cp_data[cp].set_state_and_log(
Expand Down
10 changes: 10 additions & 0 deletions packages/control/optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from helpermodules.timecheck import create_unix_timestamp_current_full_hour
from helpermodules.utils import thread_handler
from modules.common.configurable_tariff import ConfigurableElectricityTariff
from modules.common.configurable_monitoring import ConfigurableMonitoring

log = logging.getLogger(__name__)

Expand All @@ -23,11 +24,20 @@ def __init__(self):
try:
self.data = OptionalData()
self.et_module: ConfigurableElectricityTariff = None
self.monitoring_module: ConfigurableMonitoring = None
self.data.dc_charging = hardware_configuration.get_hardware_configuration_setting("dc_charging")
Pub().pub("openWB/optional/dc_charging", self.data.dc_charging)
except Exception:
log.exception("Fehler im Optional-Modul")

def monitoring_start(self):
if self.monitoring_module is not None:
self.monitoring_module.start_monitoring()

def monitoring_stop(self):
if self.mon_module is not None:
self.mon_module.stop_monitoring()

def et_provider_available(self) -> bool:
return self.et_module is not None and self.data.et.get.fault_state != 2

Expand Down
13 changes: 6 additions & 7 deletions packages/helpermodules/subdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,15 +689,14 @@ def process_optional_topic(self, var: optional.Optional, msg: mqtt.MQTTMessage):
# do not reconfigure monitoring if topic is received on startup
if self.event_subdata_initialized.is_set():
config = decode_payload(msg.payload)
if config["type"] is not None:
if config["type"] is None:
var.monitoring_stop()
var.monitoring_module = None
else:
mod = importlib.import_module(f".monitoring.{config['type']}.api", "modules")
config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config)
mod.create_config(config)
run_command(["sudo", "systemctl", "restart", "zabbix-agent2"], process_exception=True)
run_command(["sudo", "systemctl", "enable", "zabbix-agent2"], process_exception=True)
else:
run_command(["sudo", "systemctl", "stop", "zabbix-agent2"], process_exception=True)
run_command(["sudo", "systemctl", "disable", "zabbix-agent2"], process_exception=True)
var.monitoring_module = mod.create_monitoring(config)
var.monitoring_start()
else:
log.debug("skipping monitoring config on startup")
else:
Expand Down
25 changes: 24 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@


class UpdateConfig:
DATASTORE_VERSION = 73
DATASTORE_VERSION = 75
valid_topic = [
"^openWB/bat/config/configured$",
"^openWB/bat/config/power_limit_mode$",
Expand Down Expand Up @@ -1934,3 +1934,26 @@ def upgrade(topic: str, payload) -> None:
Pub().pub(topic, payload)
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 73)

def upgrade_datastore_73(self) -> None:
def upgrade(topic: str, payload) -> Optional[dict]:
# add manufacturer and model to components
if re.search("openWB/system/device/[0-9]+/component/[0-9]+/config", topic) is not None:
config_payload = decode_payload(payload)
if "info" not in config_payload:
config_payload.update({"info": {"manufacturer": None, "model": None}})
return {topic: config_payload}
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 74)

def upgrade_datastore_74(self) -> None:
def upgrade(topic: str, payload) -> None:
if re.search("openWB/system/device/[0-9]+", topic) is not None:
payload = decode_payload(payload)
# update firmware of Sungrow
if payload.get("type") == "solax":
if "version" not in payload["configuration"]:
payload["configuration"].update({"version": "g3"})
Pub().pub(topic, payload)
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 75)
Empty file.
22 changes: 22 additions & 0 deletions packages/modules/common/configurable_monitoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import logging
from typing import Callable


log = logging.getLogger(__name__)


class ConfigurableMonitoring():
def __init__(self,
start_initializer: Callable[[], None],
stop_initializer: Callable[[], None]) -> None:
try:
self._start_monitoring = start_initializer
self._stop_monitoring = stop_initializer
except Exception:
log.exception("Fehler im Monitoring Modul")

def start_monitoring(self):
self._start_monitoring()

def stop_monitoring(self):
self._stop_monitoring()
2 changes: 2 additions & 0 deletions packages/modules/common/evse.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def get_plug_charge_state(self) -> Tuple[bool, bool, float]:
str(state)+", Soll-Stromstärke: "+str(set_current))
plugged = state.plugged
charging = set_current > 0 if state.charge_enabled else False
if set_current > 32:
set_current = set_current / 100
return plugged, charging, set_current

def get_firmware_version(self) -> int:
Expand Down
3 changes: 3 additions & 0 deletions packages/modules/common/store/_car.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def update(self):
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/soc", self.state.soc, 2)
if self.state.range:
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/range", self.state.range, 2)
if self.state.soc_timestamp:
pub_to_broker("openWB/set/vehicle/"+str(self.vehicle_id)+"/get/soc_timestamp", self.state.soc_timestamp)

except Exception as e:
raise FaultState.from_exception(e)

Expand Down
7 changes: 6 additions & 1 deletion packages/modules/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@ def create_chargepoints_list(path_list):
return chargepoints

path_list = Path(_get_packages_path()/"modules"/"chargepoints").glob('**/chargepoint_module.py')
Pub().pub("openWB/set/system/configurable/chargepoints", create_chargepoints_list(path_list))
cp_list = create_chargepoints_list(path_list)
# Nach Umbennnung der "Externen openWB" zu "Secondary openWB" soll der Eintrag weiterhin an zweiter Stelle
# stehen
cp_list.remove({'value': 'external_openwb', 'text': 'Secondary openWB'})
cp_list.insert(1, {'value': 'external_openwb', 'text': 'Secondary openWB'})
Pub().pub("openWB/set/system/configurable/chargepoints", cp_list)

path_list = Path(_get_packages_path()/"modules" /
"chargepoints/internal_openwb").glob('**/chargepoint_module.py')
Expand Down
3 changes: 2 additions & 1 deletion packages/modules/devices/fox_ess/fox_ess/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def __init__(self, component_config: FoxEssCounterSetup) -> None:
def update(self, client: ModbusTcpClient_):
unit = self.component_config.configuration.modbus_id

powers = client.read_holding_registers(31026, [ModbusDataType.INT_16]*3, unit=unit)
powers = [val * -1 for val in
client.read_holding_registers(31026, [ModbusDataType.INT_16]*3, unit=unit)]
power = sum(powers)
frequency = client.read_holding_registers(31015, ModbusDataType.UINT_16, unit=unit) / 100
imported = client.read_holding_registers(32018, ModbusDataType.UINT_32, unit=unit) * 100
Expand Down
2 changes: 1 addition & 1 deletion packages/modules/devices/kostal/kostal_sem/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ def __init__(self,
self.name = name
self.type = type
self.id = id
self.configuration = configuration or KostalSemCounterConfiguration()
super().__init__(name, type, id, configuration or KostalSemCounterConfiguration())
20 changes: 10 additions & 10 deletions packages/modules/devices/solax/solax/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@
from modules.common.modbus import ModbusDataType
from modules.common.simcount import SimCounter
from modules.common.store import get_bat_value_store
from modules.devices.solax.solax.config import SolaxBatSetup
from modules.devices.solax.solax.config import SolaxBatSetup, Solax


class SolaxBat(AbstractBat):
def __init__(self,
device_id: int,
device_config: Solax,
component_config: Union[Dict, SolaxBatSetup],
tcp_client: modbus.ModbusTcpClient_,
modbus_id: int) -> None:
self.__device_id = device_id
self.__modbus_id = modbus_id
tcp_client: modbus.ModbusTcpClient_) -> None:
self.device_config = device_config
self.component_config = dataclass_from_dict(SolaxBatSetup, component_config)
self.__tcp_client = tcp_client
self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher")
self.sim_counter = SimCounter(self.device_config.id, self.component_config.id, prefix="speicher")
self.store = get_bat_value_store(self.component_config.id)
self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config))

def update(self) -> None:
with self.__tcp_client:
power = self.__tcp_client.read_input_registers(22, ModbusDataType.INT_16, unit=self.__modbus_id)
soc = self.__tcp_client.read_input_registers(28, ModbusDataType.UINT_16, unit=self.__modbus_id)
unit = self.device_config.configuration.modbus_id

# kein Speicher für Versionen G2 und G4
power = self.__tcp_client.read_input_registers(0x0016, ModbusDataType.INT_16, unit=unit)
soc = self.__tcp_client.read_input_registers(0x001C, ModbusDataType.UINT_16, unit=unit)

imported, exported = self.sim_counter.sim_count(power)
bat_state = BatState(
Expand Down
7 changes: 6 additions & 1 deletion packages/modules/devices/solax/solax/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from typing import Optional

from modules.common.component_setup import ComponentSetup
from modules.devices.solax.solax.version import SolaxVersion
from ..vendor import vendor_descriptor


class SolaxConfiguration:
def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: int = 502):
def __init__(self, modbus_id: int = 1,
ip_address: Optional[str] = None,
port: int = 502,
version: SolaxVersion = SolaxVersion.G3):
self.modbus_id = modbus_id
self.ip_address = ip_address
self.port = port
self.version = version


class Solax:
Expand Down
Loading