Skip to content

Commit 8d2c1e2

Browse files
committed
Extracted OpenWB Integration
Fixes #354
1 parent 370d07c commit 8d2c1e2

File tree

10 files changed

+300
-104
lines changed

10 files changed

+300
-104
lines changed

src/extractors/__init__.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import TYPE_CHECKING
5+
6+
from utils import value_in_range
7+
8+
if TYPE_CHECKING:
9+
from status_publisher.charge.chrg_mgmt_data_resp import (
10+
ChrgMgmtDataRespProcessingResult,
11+
)
12+
from status_publisher.vehicle.vehicle_status_resp import (
13+
VehicleStatusRespProcessingResult,
14+
)
15+
16+
LOG = logging.getLogger(__name__)
17+
18+
19+
def extract_electric_range(
20+
vehicle_status: VehicleStatusRespProcessingResult,
21+
charge_status: ChrgMgmtDataRespProcessingResult | None,
22+
) -> float | None:
23+
if (
24+
charge_status is not None
25+
and (raw_fuel_range_elec := charge_status.raw_fuel_range_elec) is not None
26+
and (actual_range := __validate_and_convert_electric_range(raw_fuel_range_elec))
27+
is not None
28+
):
29+
LOG.debug("Electric range derived from charge_status")
30+
return actual_range
31+
32+
if (raw_range := vehicle_status.fuel_range_elec) is not None and (
33+
actual_range := __validate_and_convert_electric_range(raw_range)
34+
) is not None:
35+
LOG.debug("Electric range derived from vehicle_status")
36+
return actual_range
37+
38+
LOG.warning("Could not extract a valid electric range")
39+
return None
40+
41+
42+
def extract_soc(
43+
vehicle_status: VehicleStatusRespProcessingResult,
44+
charge_status: ChrgMgmtDataRespProcessingResult | None,
45+
) -> float | None:
46+
if (
47+
charge_status is not None
48+
and (raw_soc := charge_status.raw_soc) is not None
49+
and (soc := __validate_and_convert_soc(raw_soc / 10.0)) is not None
50+
):
51+
LOG.debug("SoC derived from charge_status")
52+
return soc
53+
54+
if (raw_soc := vehicle_status.raw_soc) is not None and (
55+
soc := __validate_and_convert_soc(float(raw_soc))
56+
) is not None:
57+
LOG.debug("SoC derived from vehicle_status")
58+
return soc
59+
60+
LOG.warning("Could not extract a valid SoC")
61+
return None
62+
63+
64+
def __validate_and_convert_electric_range(raw_value: int) -> float | None:
65+
if value_in_range(raw_value, 1, 20460):
66+
return raw_value / 10.0
67+
return None
68+
69+
70+
def __validate_and_convert_soc(raw_value: float) -> float | None:
71+
if value_in_range(raw_value, 0, 100.0, is_max_excl=False):
72+
return raw_value
73+
return None

src/handlers/vehicle.py

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from integrations import IntegrationException
1313
from integrations.abrp.api import AbrpApi
1414
from integrations.home_assistant.discovery import HomeAssistantDiscovery
15+
from integrations.openwb import OpenWBIntegration
1516
from integrations.osmand.api import OsmAndApi
1617
import mqtt_topics
1718
from saic_api_listener import MqttGatewayAbrpListener, MqttGatewayOsmAndListener
@@ -61,8 +62,9 @@ def __init__(
6162
self.vehicle_state = vehicle_state
6263
self.__ha_discovery = self.__setup_ha_discovery(vehicle_state, vin_info, config)
6364

64-
self.__setup_abrp(config, vin_info)
65-
self.__setup_osmand(config, vin_info)
65+
self.openwb_integration = self.__setup_openwb(config, vin_info, publisher)
66+
self.abrp_api = self.__setup_abrp(config, vin_info)
67+
self.osmand_api = self.__setup_osmand(config, vin_info)
6668
self.__vehicle_info_publisher = VehicleInfoPublisher(
6769
self.vin_info, self.publisher, self.vehicle_prefix
6870
)
@@ -74,7 +76,18 @@ def __init__(
7476
vehicle_prefix=self.vehicle_prefix,
7577
)
7678

77-
def __setup_abrp(self, config: Configuration, vin_info: VehicleInfo) -> None:
79+
def __setup_openwb(
80+
self, config: Configuration, vin_info: VehicleInfo, publisher: Publisher
81+
) -> OpenWBIntegration | None:
82+
charging_station = config.charging_stations_by_vin.get(vin_info.vin, None)
83+
if not charging_station:
84+
return None
85+
return OpenWBIntegration(
86+
charging_station=charging_station,
87+
publisher=publisher,
88+
)
89+
90+
def __setup_abrp(self, config: Configuration, vin_info: VehicleInfo) -> AbrpApi:
7891
if vin_info.vin in self.configuration.abrp_token_map:
7992
abrp_user_token = self.configuration.abrp_token_map[vin_info.vin]
8093
else:
@@ -83,14 +96,15 @@ def __setup_abrp(self, config: Configuration, vin_info: VehicleInfo) -> None:
8396
abrp_api_listener = MqttGatewayAbrpListener(self.publisher)
8497
else:
8598
abrp_api_listener = None
86-
self.abrp_api = AbrpApi(
99+
return AbrpApi(
87100
self.configuration.abrp_api_key, abrp_user_token, listener=abrp_api_listener
88101
)
89102

90-
def __setup_osmand(self, config: Configuration, vin_info: VehicleInfo) -> None:
103+
def __setup_osmand(
104+
self, config: Configuration, vin_info: VehicleInfo
105+
) -> OsmAndApi | None:
91106
if not self.configuration.osmand_server_uri:
92-
self.osmand_api = None
93-
return
107+
return None
94108

95109
if config.publish_raw_osmand_data:
96110
api_listener = MqttGatewayOsmAndListener(self.publisher)
@@ -99,7 +113,7 @@ def __setup_osmand(self, config: Configuration, vin_info: VehicleInfo) -> None:
99113
osmand_device_id = self.configuration.osmand_device_id_map.get(
100114
vin_info.vin, vin_info.vin
101115
)
102-
self.osmand_api = OsmAndApi(
116+
return OsmAndApi(
103117
server_uri=self.configuration.osmand_server_uri,
104118
device_id=osmand_device_id,
105119
listener=api_listener,
@@ -171,7 +185,6 @@ async def __polling(self) -> None:
171185
)
172186
else:
173187
LOG.debug("Skipping EV-related updates as the vehicle is not an EV")
174-
charge_status = None
175188

176189
self.vehicle_state.update_data_conflicting_in_vehicle_and_bms(
177190
vehicle_status_processing_result, charge_status_processing_result
@@ -180,6 +193,9 @@ async def __polling(self) -> None:
180193
self.vehicle_state.mark_successful_refresh()
181194
LOG.info("Refreshing vehicle status succeeded...")
182195

196+
self.__refresh_openwb(
197+
vehicle_status_processing_result, charge_status_processing_result
198+
)
183199
await self.__refresh_abrp(charge_status, vehicle_status)
184200
await self.__refresh_osmand(charge_status, vehicle_status)
185201

@@ -196,10 +212,21 @@ def __should_complete_configuration(self, start_time: datetime.datetime) -> bool
196212
and datetime.datetime.now() > start_time + datetime.timedelta(seconds=10)
197213
)
198214

215+
def __refresh_openwb(
216+
self,
217+
vehicle_status_processing_result: VehicleStatusRespProcessingResult,
218+
charge_status_processing_result: ChrgMgmtDataRespProcessingResult | None,
219+
) -> None:
220+
if self.openwb_integration is None:
221+
return
222+
self.openwb_integration.update_openwb(
223+
vehicle_status_processing_result, charge_status_processing_result
224+
)
225+
199226
async def __refresh_osmand(
200227
self,
201228
charge_status: ChrgMgmtDataResp | None,
202-
vehicle_status: VehicleStatusResp | None,
229+
vehicle_status: VehicleStatusResp,
203230
) -> None:
204231
if not self.osmand_api:
205232
return
@@ -215,9 +242,7 @@ async def __refresh_osmand(
215242
LOG.info(f"OsmAnd not refreshed, reason {response}")
216243

217244
async def __refresh_abrp(
218-
self,
219-
charge_status: ChrgMgmtDataResp | None,
220-
vehicle_status: VehicleStatusResp | None,
245+
self, charge_status: ChrgMgmtDataResp | None, vehicle_status: VehicleStatusResp
221246
) -> None:
222247
abrp_refreshed, abrp_response = await self.abrp_api.update_abrp(
223248
vehicle_status, charge_status
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
from typing import TYPE_CHECKING
5+
6+
import extractors
7+
from integrations.openwb.charging_station import ChargingStation
8+
9+
if TYPE_CHECKING:
10+
from publisher.core import Publisher
11+
from status_publisher.charge.chrg_mgmt_data_resp import (
12+
ChrgMgmtDataRespProcessingResult,
13+
)
14+
from status_publisher.vehicle.vehicle_status_resp import (
15+
VehicleStatusRespProcessingResult,
16+
)
17+
18+
LOG = logging.getLogger(__name__)
19+
20+
__all__ = [
21+
"ChargingStation",
22+
"OpenWBIntegration",
23+
]
24+
25+
26+
class OpenWBIntegration:
27+
def __init__(
28+
self, *, charging_station: ChargingStation, publisher: Publisher
29+
) -> None:
30+
self.__charging_station = charging_station
31+
self.__publisher = publisher
32+
33+
def update_openwb(
34+
self,
35+
vehicle_status: VehicleStatusRespProcessingResult,
36+
charge_status: ChrgMgmtDataRespProcessingResult | None,
37+
) -> None:
38+
range_topic = self.__charging_station.range_topic
39+
electric_range = extractors.extract_electric_range(
40+
vehicle_status, charge_status
41+
)
42+
if electric_range is not None and range_topic is not None:
43+
LOG.info("OpenWB Integration published range to %f", range_topic)
44+
self.__publisher.publish_float(
45+
key=range_topic,
46+
value=electric_range,
47+
no_prefix=True,
48+
)
49+
50+
soc_topic = self.__charging_station.soc_topic
51+
soc = extractors.extract_soc(vehicle_status, charge_status)
52+
if soc is not None and soc_topic is not None:
53+
LOG.info("OpenWB Integration published SoC to %f", soc_topic)
54+
self.__publisher.publish_float(
55+
key=soc_topic,
56+
value=soc,
57+
no_prefix=True,
58+
)

src/mqtt_gateway.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,25 +150,11 @@ async def setup_vehicle(
150150
account_prefix = (
151151
f"{self.configuration.saic_user}/{mqtt_topics.VEHICLES}/{vin_info.vin}"
152152
)
153-
charging_station = self.get_charging_station(vin_info.vin)
154-
if charging_station and charging_station.soc_topic:
155-
LOG.debug(
156-
"SoC of %s for charging station will be published over MQTT topic: %s",
157-
vin_info.vin,
158-
charging_station.soc_topic,
159-
)
160-
if charging_station and charging_station.range_topic:
161-
LOG.debug(
162-
"Range of %s for charging station will be published over MQTT topic: %s",
163-
vin_info.vin,
164-
charging_station.range_topic,
165-
)
166153
vehicle_state = VehicleState(
167154
self.publisher,
168155
self.__scheduler,
169156
account_prefix,
170157
vin_info,
171-
charging_station,
172158
charge_polling_min_percent=self.configuration.charge_dynamic_polling_min_percentage,
173159
)
174160
vehicle_handler = VehicleHandler(

src/publisher/mqtt_publisher.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ async def connect(self) -> None:
5555
ssl_context = ssl.create_default_context()
5656
cert_uri = self.configuration.tls_server_cert_path
5757
if cert_uri:
58-
LOG.debug(
59-
f"Using custom CA file {cert_uri}"
60-
)
58+
LOG.debug(f"Using custom CA file {cert_uri}")
6159
ssl_context.load_verify_locations(cafile=cert_uri)
6260
ssl_context.check_hostname = False
6361
else:

0 commit comments

Comments
 (0)