Skip to content
Merged
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
12 changes: 7 additions & 5 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
log = logging.getLogger(__name__)
mqtt_log = logging.getLogger("mqtt")

TIMESTAMP_2100 = 4102441200 # 01.01.2100 00:00:00


class SetData:
def __init__(self,
Expand Down Expand Up @@ -385,7 +387,7 @@ def process_vehicle_topic(self, msg: mqtt.MQTTMessage):
self._validate_value(msg, int, [(0, float("inf"))])
elif ("/get/soc_request_timestamp" in msg.topic or
"/get/soc_timestamp" in msg.topic):
self._validate_value(msg, float)
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
elif "/get/soc" in msg.topic:
self._validate_value(msg, float, [(0, 100)])
elif "/get/range" in msg.topic:
Expand Down Expand Up @@ -557,7 +559,7 @@ def process_chargepoint_get_topics(self, msg):
self._validate_value(msg, str)
elif ("/get/error_timestamp" in msg.topic or
"/get/rfid_timestamp" in msg.topic):
self._validate_value(msg, float)
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
elif ("/get/fault_str" in msg.topic or
"/get/state_str" in msg.topic or
"/get/heartbeat" in msg.topic or
Expand Down Expand Up @@ -735,7 +737,7 @@ def process_general_topic(self, msg: mqtt.MQTTMessage):
"openWB/set/general/mqtt_bridge" in msg.topic):
self._validate_value(msg, bool)
elif "openWB/set/general/grid_protection_timestamp" in msg.topic:
self._validate_value(msg, float)
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
elif "openWB/set/general/grid_protection_random_stop" in msg.topic:
self._validate_value(msg, int, [(0, 90)])
elif "openWB/set/general/notifications/selected" in msg.topic:
Expand Down Expand Up @@ -786,7 +788,7 @@ def process_io_topic(self, msg: mqtt.MQTTMessage):
elif "get/fault_str" in msg.topic:
self._validate_value(msg, str)
elif "/timestamp" in msg.topic:
self._validate_value(msg, float)
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
else:
self.__unknown_topic(msg)
except Exception:
Expand Down Expand Up @@ -1026,7 +1028,7 @@ def process_system_topic(self, msg: mqtt.MQTTMessage):
elif "/config" in msg.topic:
self._validate_value(msg, "json")
elif "/error_timestamp" in msg.topic:
self._validate_value(msg, float, [(0, float("inf"))])
self._validate_value(msg, float, [(0, TIMESTAMP_2100)])
elif "/get/fault_state" in msg.topic:
self._validate_value(msg, int, [(0, 2)])
elif "/get/fault_str" in msg.topic:
Expand Down
6 changes: 3 additions & 3 deletions packages/modules/common/abstract_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class VehicleUpdateData:
efficiency: float = 90
soc_from_cp: Optional[float] = None
timestamp_soc_from_cp: Optional[int] = None
soc_timestamp: Optional[int] = None
last_soc_timestamp: Optional[int] = None
last_soc: float = None


@dataclass
Expand All @@ -24,6 +25,5 @@ class GeneralVehicleConfig:

@dataclass
class CalculatedSocState:
imported_start: Optional[float] = 0 # don't show in UI
last_imported: Optional[float] = 0 # don't show in UI
manual_soc: Optional[int] = None # don't show in UI
soc_start: float = 0 # don't show in UI
29 changes: 12 additions & 17 deletions packages/modules/common/configurable_vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ def update(self, vehicle_update_data: VehicleUpdateData):
log.debug(f"General Config {self.general_config}")
with SingleComponentUpdateContext(self.fault_state, self.__initializer):

if vehicle_update_data.imported is None:
self.calculated_soc_state.last_imported = None
Pub().pub(f"openWB/set/vehicle/{self.vehicle}/soc_module/calculated_soc_state",
asdict(self.calculated_soc_state))
source = self._get_carstate_source(vehicle_update_data)
if source == SocSource.NO_UPDATE:
log.debug("No soc update necessary.")
Expand All @@ -78,26 +82,18 @@ def update(self, vehicle_update_data: VehicleUpdateData):
log.debug("Mqtt uses legacy topics.")
return
log.debug(f"Requested start soc from {source.value}: {car_state.soc}%")

if (source != SocSource.CALCULATION or
(vehicle_update_data.imported and self.calculated_soc_state.imported_start is None)):
# Wenn nicht berechnet wurde, SoC als Start merken.
self.calculated_soc_state.imported_start = vehicle_update_data.imported
self.calculated_soc_state.soc_start = car_state.soc
Pub().pub(f"openWB/set/vehicle/{self.vehicle}/soc_module/calculated_soc_state",
asdict(self.calculated_soc_state))
if (vehicle_update_data.soc_timestamp is None or
vehicle_update_data.soc_timestamp <= car_state.soc_timestamp + 60):
if (vehicle_update_data.last_soc_timestamp is None or
vehicle_update_data.last_soc_timestamp <= car_state.soc_timestamp + 60):
# Nur wenn der SoC neuer ist als der bisherige, diesen setzen.
# Manche Fahrzeuge liefern in Ladepausen zwar einen SoC, aber manchmal einen alten.
# Die Pro liefert manchmal den SoC nicht, bis nach dem Anstecken das SoC-Update getriggert wird.
# Wenn Sie dann doch noch den alten SoC liefert, darf dieser nicht verworfen werden.
self.store.set(car_state)
elif vehicle_update_data.soc_timestamp > 1e10:
# car_state ist in ms geschrieben, dieser kann überschrieben werden
self.store.set(car_state)
else:
log.debug("Not updating SoC, because timestamp is older.")
self.calculated_soc_state.last_imported = vehicle_update_data.imported
Pub().pub(f"openWB/set/vehicle/{self.vehicle}/soc_module/calculated_soc_state",
asdict(self.calculated_soc_state))

def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSource:
# Kein SoC vom LP vorhanden oder erwünscht
Expand All @@ -106,7 +102,7 @@ def _get_carstate_source(self, vehicle_update_data: VehicleUpdateData) -> SocSou
self.calculated_soc_state.manual_soc is not None):
if isinstance(self.vehicle_config, ManualSoc):
# Wenn ein manueller SoC gesetzt wurde, diesen als neuen Start merken.
if self.calculated_soc_state.manual_soc is not None or self.calculated_soc_state.imported_start is None:
if self.calculated_soc_state.manual_soc is not None:
return SocSource.MANUAL
else:
if vehicle_update_data.plug_state:
Expand Down Expand Up @@ -134,8 +130,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source
return CarState(soc=calc_soc.calc_soc(
vehicle_update_data,
vehicle_update_data.efficiency,
self.calculated_soc_state.imported_start or vehicle_update_data.imported,
self.calculated_soc_state.soc_start,
self.calculated_soc_state.last_imported or vehicle_update_data.imported,
vehicle_update_data.battery_capacity))
elif source == SocSource.CP:
return CarState(soc=vehicle_update_data.soc_from_cp,
Expand All @@ -144,7 +139,7 @@ def _get_carstate_by_source(self, vehicle_update_data: VehicleUpdateData, source
if self.calculated_soc_state.manual_soc is not None:
soc = self.calculated_soc_state.manual_soc
else:
soc = self.calculated_soc_state.soc_start
raise ValueError("Manual soc source selected, but no manual soc set.")
self.calculated_soc_state.manual_soc = None
return CarState(soc)

Expand Down
44 changes: 16 additions & 28 deletions packages/modules/common/configurable_vehicle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,19 @@ def conf_vehicle_mqtt():
[
pytest.param(conf_vehicle_manual(), False, VehicleUpdateData(), CalculatedSocState(
manual_soc=34), SocSource.MANUAL, id="Manuell, neuer Start-SoC"),
pytest.param(conf_vehicle_manual(), False, VehicleUpdateData(plug_state=True), CalculatedSocState(
soc_start=34), SocSource.CALCULATION, id="Manuell berechnen"),
pytest.param(conf_vehicle_manual(), False, VehicleUpdateData(), CalculatedSocState(
soc_start=34), SocSource.NO_UPDATE, id="Manuell nicht aktualisieren, da nicht angesteckt"),
pytest.param(conf_vehicle_manual(), False, VehicleUpdateData(plug_state=True, last_soc=34), CalculatedSocState(
), SocSource.CALCULATION, id="Manuell berechnen"),
pytest.param(conf_vehicle_manual(), False, VehicleUpdateData(last_soc=34), CalculatedSocState(),
SocSource.NO_UPDATE, id="Manuell nicht aktualisieren, da nicht angesteckt"),
pytest.param(conf_vehicle_manual_from_cp(), True,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID),
CalculatedSocState(manual_soc=34), SocSource.MANUAL, id="Manuell mit SoC vom LP, neuer Start-SoC"),
pytest.param(conf_vehicle_manual_from_cp(), True,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID),
CalculatedSocState(soc_start=34), SocSource.CP, id="Manuell mit SoC vom LP, neuer LP-SoC"),
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_VALID, last_soc=34),
CalculatedSocState(), SocSource.CP, id="Manuell mit SoC vom LP, neuer LP-SoC"),
pytest.param(conf_vehicle_manual_from_cp(), True,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID),
CalculatedSocState(soc_start=34), SocSource.CALCULATION,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, last_soc=34),
CalculatedSocState(), SocSource.CALCULATION,
id="Manuell mit SoC vom LP, LP-SoC berechnen"),
pytest.param(conf_vehicle_api(), True, VehicleUpdateData(), CalculatedSocState(), SocSource.API, id="API"),
pytest.param(conf_vehicle_api_from_cp(), True, VehicleUpdateData(
Expand All @@ -112,8 +112,8 @@ def conf_vehicle_mqtt():
pytest.param(conf_vehicle_api_from_cp(), True, VehicleUpdateData(soc_from_cp=None),
CalculatedSocState(), SocSource.API, id="API mit SoC vom LP, kein LP-SoC"),
pytest.param(conf_vehicle_api_from_cp(), True,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID),
CalculatedSocState(soc_start=34), SocSource.CALCULATION,
VehicleUpdateData(soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID, last_soc=34),
CalculatedSocState(), SocSource.CALCULATION,
id="API mit SoC vom LP, LP-SoC berechnen"),
pytest.param(conf_vehicle_api_while_charging(), False, VehicleUpdateData(),
CalculatedSocState(), SocSource.API, id="API mit Berechnung, keine Ladung"),
Expand Down Expand Up @@ -141,11 +141,11 @@ def test_get_carstate_source(conf_vehicle: ConfigurableVehicle,
@pytest.mark.parametrize(
"vehicle_update_data, use_soc_from_cp, expected_calculated_soc_state, expected_call_count",
[
pytest.param(VehicleUpdateData(), False, CalculatedSocState(soc_start=42), 1, id="request only from api"),
pytest.param(VehicleUpdateData(imported=150), True, CalculatedSocState(
soc_start=42, imported_start=150), 1, id="request from api, not plugged"),
pytest.param(VehicleUpdateData(imported=200, plug_state=True), True, CalculatedSocState(
soc_start=42, imported_start=200), 1, id="request from api, recently plugged"),
pytest.param(VehicleUpdateData(last_soc=42), False, CalculatedSocState(), 1, id="request only from api"),
pytest.param(VehicleUpdateData(imported=150, last_soc=42), True, CalculatedSocState(
last_imported=150), 1, id="request from api, not plugged"),
pytest.param(VehicleUpdateData(imported=200, plug_state=True, last_soc=42), True,
CalculatedSocState(last_imported=200), 1, id="request from api, recently plugged"),
])
def test_update_api(vehicle_update_data,
use_soc_from_cp,
Expand Down Expand Up @@ -191,7 +191,7 @@ def test_1(monkeypatch):

# evaluation
assert mock_value_store.set.call_args[0][0].soc == 45
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=45)
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None)


def test_2(monkeypatch):
Expand All @@ -213,8 +213,6 @@ def test_2(monkeypatch):

# evaluation
assert mock_value_store.set.call_args_list[3][0][0].soc == 47
assert mock_calc_soc.call_args[0][3] == 45 # soc
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=45)


def test_3(monkeypatch):
Expand All @@ -237,7 +235,6 @@ def test_3(monkeypatch):
# evaluation
assert mock_value_store.set.call_args_list[3][0][0].soc == 44
assert mock_calc_soc.call_count == 2
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_4(monkeypatch):
Expand All @@ -259,7 +256,6 @@ def test_4(monkeypatch):
# evaluation
assert mock_value_store.set.call_args_list[2][0][0].soc == 44
assert mock_calc_soc.call_count == 2
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_5(monkeypatch):
Expand All @@ -281,7 +277,6 @@ def test_5(monkeypatch):
# evaluation
assert mock_value_store.set.call_args_list[2][0][0].soc == 44
assert mock_calc_soc.call_count == 1
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_6(monkeypatch):
Expand All @@ -292,15 +287,13 @@ def test_6(monkeypatch):
mock_value_store = Mock(name="value_store")
monkeypatch.setattr(store, "get_car_value_store", Mock(return_value=mock_value_store))
c = conf_vehicle_manual_from_cp()
c.calculated_soc_state = CalculatedSocState(manual_soc=None, soc_start=42)

# execution
c.update(VehicleUpdateData(plug_state=True, soc_from_cp=45, timestamp_soc_from_cp=TIMESTAMP_SOC_INVALID))

# evaluation
assert mock_value_store.set.call_args_list[0][0][0].soc == 44
assert mock_calc_soc.call_count == 1
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_7(monkeypatch):
Expand All @@ -318,7 +311,6 @@ def test_7(monkeypatch):

# evaluation
assert mock_value_store.set.call_args[0][0].soc == 42
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_8(monkeypatch):
Expand All @@ -336,7 +328,6 @@ def test_8(monkeypatch):

# evaluation
assert mock_value_store.set.call_args[0][0].soc == 42
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)


def test_9(monkeypatch):
Expand All @@ -355,7 +346,6 @@ def test_9(monkeypatch):
# evaluation
assert mock_value_store.set.call_args_list[1][0][0].soc == 46
assert mock_calc_soc.call_count == 1
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=45)


def test_10(monkeypatch):
Expand All @@ -370,7 +360,6 @@ def test_10(monkeypatch):

# evaluation
assert mock_value_store.set.call_args_list[0][0][0].soc == 42
assert c.calculated_soc_state == CalculatedSocState(soc_start=42)


def test_11(monkeypatch):
Expand All @@ -390,4 +379,3 @@ def test_11(monkeypatch):
# evaluation
assert mock_value_store.set.call_args_list[1][0][0].soc == 44
assert mock_calc_soc.call_count == 1
assert c.calculated_soc_state == CalculatedSocState(manual_soc=None, soc_start=42)
3 changes: 2 additions & 1 deletion packages/modules/update_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ def _get_vehicle_update_data(self, ev_num: int) -> VehicleUpdateData:
battery_capacity=battery_capacity,
soc_from_cp=soc_from_cp,
timestamp_soc_from_cp=timestamp_soc_from_cp,
soc_timestamp=soc_timestamp)
last_soc_timestamp=soc_timestamp,
last_soc=ev.data.get.soc)

def _filter_failed_store_threads(self, threads_store: List[Thread]) -> List[Thread]:
ev_data = copy.deepcopy(subdata.SubData.ev_data)
Expand Down
13 changes: 6 additions & 7 deletions packages/modules/vehicles/common/calc_soc/calc_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@

def calc_soc(vehicle_update_data: VehicleUpdateData,
efficiency: int,
imported_start: float,
soc_start: float,
last_imported: float,
battery_capacity: float) -> float:
imported_since_start = vehicle_update_data.imported - imported_start
energy_battery_gain = imported_since_start * efficiency / 100
imported_since_last_soc = vehicle_update_data.imported - last_imported
energy_battery_gain = imported_since_last_soc * efficiency / 100
battery_soc_gain = (energy_battery_gain / battery_capacity) * 100
soc = soc_start + battery_soc_gain
soc = vehicle_update_data.last_soc + battery_soc_gain
log.debug(
f"SoC-Gain: (({imported_since_start/1000}kWh charged * {efficiency}% efficiency) / "
f"SoC-Gain: (({imported_since_last_soc/1000}kWh charged * {efficiency}% efficiency) / "
f"{battery_capacity/1000}kWh battery-size) * 100 = {battery_soc_gain}%",
)
log.debug(f"{soc_start}% + {energy_battery_gain / 1000}kWh = {soc}%")
log.debug(f"{vehicle_update_data.last_soc}% + {energy_battery_gain / 1000}kWh = {soc}%")
if soc > 100:
log.warning(f"Calculated SoC of {soc}% exceeds maximum and is limited to 100%! Check your settings!")
soc = 100
Expand Down
3 changes: 2 additions & 1 deletion packages/modules/vehicles/common/calc_soc/calc_soc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

def test_calc_soc():
# setup & execution
soc = calc_soc(VehicleUpdateData(imported=10000), 90, 0, 12.6, 100000)
soc = calc_soc(VehicleUpdateData(imported=10000, last_soc=12.6),
efficiency=90, last_imported=0, battery_capacity=100000)

# evaluation
assert soc == 21.6