From 5e319c553ca37c0ca61cb4ec54726e61965e35b9 Mon Sep 17 00:00:00 2001 From: rleidner Date: Fri, 3 Jan 2025 13:34:58 +0100 Subject: [PATCH 1/3] remove smarteq module - support stopped by mercedes --- packages/modules/vehicles/smarteq/README.txt | 5 - packages/modules/vehicles/smarteq/__init__.py | 0 packages/modules/vehicles/smarteq/api.py | 441 ------------------ packages/modules/vehicles/smarteq/config.py | 24 - packages/modules/vehicles/smarteq/soc.py | 36 -- 5 files changed, 506 deletions(-) delete mode 100644 packages/modules/vehicles/smarteq/README.txt delete mode 100755 packages/modules/vehicles/smarteq/__init__.py delete mode 100755 packages/modules/vehicles/smarteq/api.py delete mode 100755 packages/modules/vehicles/smarteq/config.py delete mode 100755 packages/modules/vehicles/smarteq/soc.py diff --git a/packages/modules/vehicles/smarteq/README.txt b/packages/modules/vehicles/smarteq/README.txt deleted file mode 100644 index 7a956c7fad..0000000000 --- a/packages/modules/vehicles/smarteq/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -Dieses SOC-Modl ist von der javascipt Implementierung des smartEQ Adapters von iobroker inspiriert. - -requirements: bs4, pkce - - diff --git a/packages/modules/vehicles/smarteq/__init__.py b/packages/modules/vehicles/smarteq/__init__.py deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/packages/modules/vehicles/smarteq/api.py b/packages/modules/vehicles/smarteq/api.py deleted file mode 100755 index 327d5c5185..0000000000 --- a/packages/modules/vehicles/smarteq/api.py +++ /dev/null @@ -1,441 +0,0 @@ -#!/usr/bin/env python3 - -import logging -from typing import Union -import asyncio -import json -import os -import time -import datetime -import pickle -import copy - -from helpermodules.utils.error_handling import ImportErrorContext -with ImportErrorContext(): - import bs4 -with ImportErrorContext(): - import pkce - -from modules.common import req -from modules.common.component_state import CarState -from modules.common.store import RAMDISK_PATH -from modules.vehicles.smarteq.config import SmartEQ - -date_fmt = '%Y-%m-%d %H:%M:%S' -# refreshToken_exp_days = 7 # 7 days before refreshToken expires a new refreshToken shall be stored -initialToken = '1.2.3' - -log = logging.getLogger(__name__) - -# Constants -BASE_URL = "https://id.mercedes-benz.com" -OAUTH_URL = BASE_URL + "/as/authorization.oauth2" -LOGIN_URL = BASE_URL + "/ciam/auth/login" -TOKEN_URL = BASE_URL + "/as/token.oauth2" -# STATUS_URL = "https://oneapp.microservice.smart.com" -STATUS_URL = "https://oneapp.microservice.smart.mercedes-benz.com" -REDIRECT_URI = STATUS_URL -SCOPE = "openid+profile+email+phone+ciam-uid+offline_access" -CLIENT_ID = "70d89501-938c-4bec-82d0-6abb550b0825" -GUID = "280C6B55-F179-4428-88B6-E0CCF5C22A7C" -ACCEPT_LANGUAGE = "de-de" - -TOKENS_REFRESH_THRESHOLD = 3600 -SSL_VERIFY_AUTH = True -SSL_VERIFY_STATUS = True - - -# helper functions -def nested_key_exists(element: dict, *keys: str) -> bool: - # Check if *keys (nested) exists in `element` (dict). - if not isinstance(element, dict): - raise AttributeError('nested_key_exists() expects dict as first argument - got type ' + str(type(element))) - if len(keys) == 0: - raise AttributeError('nested_key_exists() expects at least two arguments, one given.') - - _element = element - for key in keys: - try: - _element = _element[key] - except KeyError: - return False - return True - - -class Api: - - def __init__(self, vehicle: int): - self.log = logging.getLogger(__name__) - self.storeFile = str(RAMDISK_PATH) + '/soc_smarteq_store_vh_' + str(vehicle) - # LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() - # logging.basicConfig(level=LOGLEVEL) - - # self.method keeps a high level trach of actions - self.method = '' - self.soc_ts = 'n/a' - # self.store is read from ramdisk at start and saved at end. - # currently is contains: - # Tokens: refresh- and access-tokens of OAUTH - # refresh_timestamp: epoch of last refresh_tokens. - - # self.session = requests.session() - self.session = req.get_http_session() - - self.load_store() - self.oldTokens = copy.deepcopy(self.store['Tokens']) - self.init = True - - def load_store(self): - try: - with open(self.storeFile, "rb") as tf: - self.store = pickle.load(tf) - if 'Tokens' not in self.store: - self.store['Tokens'] = {} - self.store['refresh_timestamp'] = int(0) - except FileNotFoundError: - log.warning("init: no store file found; full connect required") - self.store = {} - self.store['Tokens'] = {} - self.store['refresh_timestamp'] = int(0) - except Exception: - log.exception("init: loading stored data failed, file: " + self.storeFile) - self.store = {} - self.store['Tokens'] = {} - self.store['refresh_timestamp'] = int(0) - - def write_store(self): - try: - tf = open(self.storeFile, "wb") - except Exception: - log.exception("write_store") - os.system("sudo rm -f " + self.storeFile) - tf = open(self.storeFile, "wb") - pickle.dump(self.store, tf) - tf.close() - try: - os.chmod(self.storeFile, 0o666) - except Exception: - log.exception("chmod_store") - os.system("sudo chmod 0666 " + self.storeFile) - - # ===== get resume string ====== - def get_resume(self) -> str: - response_type = "code" - self.code_verifier, self.code_challenge = pkce.generate_pkce_pair() - self.code_challenge_method = "S256" - url = OAUTH_URL + '?client_id=' + CLIENT_ID + '&response_type=' + response_type + '&scope=' + SCOPE - url = url + '&redirect_uri=' + REDIRECT_URI - url = url + '&code_challenge=' + self.code_challenge + '&code_challenge_method=' + self.code_challenge_method - headers = { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Accept-Language": ACCEPT_LANGUAGE, - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_5_1 like Mac OS X)\ - AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" - } - - try: - response = self.session.get(url, headers=headers, verify=SSL_VERIFY_AUTH) - soup = bs4.BeautifulSoup(response.text, 'html.parser') - - for cd in soup.findAll(text=True): - if "CDATA" in cd: - self.log.debug("get_resume: cd.CData= " + str(cd)) - for w in cd.split(','): - if w.find("const initialState = ") != -1: - iS = w - if iS: - js = iS.split('{')[1].split('}')[0].replace('\\', '').replace('\\"', '"').replace('""', '"') - self.resume = js[1:len(js)-1].split(':')[1][2:] - self.log.debug("get_resume: resume = " + self.resume) - except Exception: - log.exception('get_resume') - return self.resume - - # login to website, return (intermediate) token - def login(self) -> str: - url = LOGIN_URL + "/pass" - headers = { - "Content-Type": "application/json", - "Accept": "application/json, text/plain, */*", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_5_1 like Mac OS X)\ - AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", - "Referer": LOGIN_URL, - "Accept-Language": ACCEPT_LANGUAGE - } - data = json.dumps({'username': self.username, - 'password': self.password, - 'rememberMe': 'true'}) - - try: - response = self.session.post(url, headers=headers, data=data, verify=SSL_VERIFY_AUTH) - self.log.debug("login: status_code = " + str(response.status_code)) - if response.status_code >= 400: - self.log.error("login: failed, status_code = " + str(response.status_code) + - ", check username/password") - token = "" - else: - result_json = json.loads(str(bs4.BeautifulSoup(response.text, 'html.parser'))) - self.log.debug("login: result_json:\n" + json.dumps(result_json)) - token = result_json['token'] - self.log.debug("login: token = " + token) - except Exception: - log.exception('login') - token = "" - return token - - # get code - def get_code(self) -> str: - url = BASE_URL + '/' + self.resume - headers = { - "Content-Type": "application/x-www-form-urlencoded", - "Accept": "application/json, text/plain, */*", - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_5_1 like Mac OS X)\ - AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", - "Referer": LOGIN_URL, - "Accept-Language": ACCEPT_LANGUAGE, - } - data = json.dumps({'token': self.token}) - - try: - response = self.session.post(url, headers=headers, data=data, verify=SSL_VERIFY_AUTH) - code = response.url.split('?')[1].split('=')[1] - self.log.debug("get_code: code=" + code) - except Exception: - log.exception("get_code") - return code - - # get Tokens - def get_tokens(self) -> dict: - self.method += " 3-full (re)connect" - self.resume = self.get_resume() - self.token = self.login() - if self.token == "": - self.log.error("login: Login failed - check username/password") - return "" - code = self.get_code() - if code == "": - self.log.warn("get_tokens: get_code failed") - return {} - url = TOKEN_URL - headers = { - "Accept": "*/*", - "User-Agent": "sOAF/202108260942 CFNetwork/978.0.7 Darwin/18.7.0", - "Accept-Language": ACCEPT_LANGUAGE, - "Content-Type": "application/x-www-form-urlencoded", - } - data = "grant_type=authorization_code&code=" + code + "&code_verifier=" + self.code_verifier +\ - "&redirect_uri=" + REDIRECT_URI + "&client_id=" + CLIENT_ID - - try: - response = self.session.post(url, headers=headers, data=data, verify=SSL_VERIFY_AUTH) - Tokens = json.loads(response.text) - if not Tokens['access_token']: - self.log.warn("get_tokens: no access_token found") - Tokens = {} - else: - self.log.debug("Tokens=\n" + json.dumps(Tokens, indent=4)) - except Exception: - log.exception('get_tokens') - return Tokens - - # refresh tokens - def refresh_tokens(self) -> dict: - self.method += " 2-refresh_tokens" - url = TOKEN_URL - newTokens = {} - headers = { - "Accept": "*/*", - "User-Agent": "sOAF/202108260942 CFNetwork/978.0.7 Darwin/18.7.0", - "Accept-Language": ACCEPT_LANGUAGE, - "Content-Type": "application/x-www-form-urlencoded", - } - data = "grant_type=refresh_token&client_id=" + CLIENT_ID + "&refresh_token=" +\ - self.store['Tokens']['refresh_token'] - - try: - response = self.session.post(url, - headers=headers, - data=data, - verify=SSL_VERIFY_AUTH, - allow_redirects=False, - timeout=(30, 30)) - - newTokens = json.loads(response.text) - if 'error' in newTokens and newTokens['error'] == 'unauthorized': - self.log.warning("refresh_tokens: error: " + newTokens['error'] + ', ' + newTokens['error_description']) - if 'access_token' not in newTokens: - self.log.debug("refresh_tokens: new access_token not found") - newTokens['access_token'] = "" - if 'refresh_token' not in newTokens: - self.log.debug("refresh_tokens: new refresh_token not found") - newTokens['refresh_token'] = "" - self.log.debug("refresh_tokens: newTokens=\n" + json.dumps(newTokens, indent=4)) - except Exception: - log.exception('refresh_tokens') - newTokens['access_token'] = "" - newTokens['refresh_token'] = "" - return newTokens - - # reconnect to Server - def reconnect(self) -> dict: - # check if we have a refresh token and last refresh was more then 1h ago (3600s) - if 'refresh_token' in self.store['Tokens']: - now = int(time.time()) - secs_since_refresh = now - self.store['refresh_timestamp'] - if secs_since_refresh > TOKENS_REFRESH_THRESHOLD: - # try to refresh tokens - new_tokens = self.refresh_tokens() - self.store['refresh_timestamp'] = int(time.time()) - _ref = True - else: - # keep existing tokens - return self.store['Tokens'] - else: - self.log.debug("reconnect: refresh_token not found in self.store['Tokens']=" + - json.dumps(self.store['Tokens'], indent=4)) - new_tokens = {'refresh_token': '', 'access_token': ''} - _ref = False - self.log.debug("reconnect: new_tokens=" + json.dumps(new_tokens, indent=4)) - if new_tokens['access_token'] == '': - if _ref: - self.log.warning("reconnect: refresh access_token failed, try full reconnect") - Tokens = self.get_tokens() - else: - self.log.debug("reconnect: refresh access_token successful") - Tokens = self.store['Tokens'] # replace expired access and refresh token by new tokens - for key in new_tokens: - Tokens[key] = new_tokens[key] - self.log.debug("reconnect: replace Tokens[" + key + "], new value: " + str(Tokens[key])) - - return Tokens - - # get Soc,range of Vehicle - def get_status(self, vin: str) -> int: - self.method += " 1-get_status" - if self.init: - url = STATUS_URL + "/seqc/v0/vehicles/" + vin +\ - "/init-data?requestedData=BOTH&countryCode=DE&locale=de-DE" - else: - url = STATUS_URL + "/seqc/v0/vehicles/" + vin + "/refresh-data" - self.init = False - - headers = { - "accept": "*/*", - "accept-language": "de-DE;q=1.0", - "authorization": "Bearer " + self.store['Tokens']['access_token'], - "x-applicationname": CLIENT_ID, - "user-agent": "Device: iPhone 6; OS-version: iOS_12.5.1; App-Name: smart EQ control; App-Version: 3.0;\ - Build: 202108260942; Language: de_DE", - "guid": GUID - } - - try: - response = self.session.get(url, headers=headers, verify=SSL_VERIFY_STATUS) - res = json.loads(response.text) - res_json = json.dumps(res, indent=4) - if (nested_key_exists(res, 'precond', 'data', 'soc', 'value') and - nested_key_exists(res, 'precond', 'data', 'rangeelectric', 'value')): - _ts = res['precond']['data']['soc']['ts'] - self.soc_ts = datetime.datetime.fromtimestamp(_ts).strftime('%Y-%m-%d %H:%M:%S') - _ts = res['precond']['data']['rangeelectric']['ts'] - rng_ts = datetime.datetime.fromtimestamp(_ts).strftime('%Y-%m-%d %H:%M:%S') - res_json = "{\nsoc: " + json.dumps(res['precond']['data']['soc'], indent=4).replace("\n", "\n ") - res_json += ",\nsoc.timestamp: \"" + self.soc_ts + "\"" - res_json += ",\nrangeelectric: " +\ - json.dumps(res['precond']['data']['rangeelectric'], indent=4).replace("\n", "\n ") - res_json += ",\nrange.timestamp: \"" + rng_ts + "\"\n}" - if rng_ts > self.soc_ts: - self.soc_ts = rng_ts - try: - soc = int(res['precond']['data']['soc']['value']) - range = float(res['precond']['data']['rangeelectric']['value']) - self.log.debug("get_status: result json:\n" + res_json) - except Exception: - soc = -1 - range = 0.0 - elif 'error' in res and res['error'] == 'unauthorized': - self.log.warning("get_status: access_token expired - try refresh") - self.log.debug("get_status: error - result json:\n" + res_json) - soc = -1 - range = 0.0 - - except Exception: - log.exception('get_status') - self.log.debug("get_status: result:\n" + res_json) - soc = -1 - range = 0.0 - if "Vehicle not found" in res_json: - soc = -2 - range = 0.0 - return soc, range - - # fetch soc in 3 stages: - # 1. get_status via stored access_token - # 2. if expired: refresh_access_token using id and refresh token, then get_status - # 3. if refresh token expired: login, get tokens, then get_status - async def _fetch_soc(self, - conf: SmartEQ, - vehicle: int) -> Union[int, float]: - self.username = conf.configuration.user_id - self.password = conf.configuration.password - self.vin = conf.configuration.vin - self.vehicle = vehicle - - try: - soc = -1 - range = 0.0 - if 'refresh_token' in self.store['Tokens']: - self.store['Tokens'] = self.reconnect() - if 'access_token' in self.store['Tokens']: - soc, range = self.get_status(self.vin) - if soc > 0: - self.log.debug("fetch_soc: 1st attempt successful") - else: - self.log.debug("fetch_soc: 1st attempt failed - soc=" + str(soc)) - - if soc == -1: - self.log.debug("fetch_soc: (re)connecting ...") - self.store['Tokens'] = self.reconnect() - if 'access_token' in self.store['Tokens']: - soc, range = self.get_status(self.vin) - if soc > 0: - self.log.debug("fetch_soc: 2nd attempt successful") - else: - self.log.warning("fetch_soc: 2nd attempt failed - soc=" + str(soc)) - range = 0.0 - else: - self.log.error("fetch_soc: (re-)connect failed") - soc = 0 - range = 0.0 - elif soc == -2: - self.log.error("get_status failed, verify VIN ...") - soc = 0 - range = 0.0 - - except Exception: - log.exception("fetch_soc: exception, (re-)connecting ...") - self.store['Tokens'] = self.reconnect() - if 'access_token' in self.store['Tokens']: - soc, range = self.get_status(self.vin) - self.log.info(" SOC/Range: " + str(soc) + '%/' + str(range) + - '@' + self.soc_ts + - ', Method: ' + self.method) - - if self.store['Tokens'] != self.oldTokens: - self.log.debug("reconnect: tokens changed, store token file") - self.write_store() - - return soc, range - - -def fetch_soc(conf: SmartEQ, vehicle: int) -> CarState: - - # prepare and call async method - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - # get soc, range from server - a = Api(vehicle) - soc, range = loop.run_until_complete(a._fetch_soc(conf, vehicle)) - - return CarState(soc, range) diff --git a/packages/modules/vehicles/smarteq/config.py b/packages/modules/vehicles/smarteq/config.py deleted file mode 100755 index e1f5411041..0000000000 --- a/packages/modules/vehicles/smarteq/config.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Optional - - -class SmartEQConfiguration: - def __init__(self, - user_id: Optional[str] = None, # show in UI - password: Optional[str] = None, # show in UI - vin: Optional[str] = None # show in UI - # refreshToken: Optional[str] = None # DON'T show in UI! - ): - self.user_id = user_id - self.password = password - self.vin = vin - # self.refreshToken = refreshToken - - -class SmartEQ: - def __init__(self, - name: str = "SmartEQ", - type: str = "smarteq", - configuration: SmartEQConfiguration = None) -> None: - self.name = name - self.type = type - self.configuration = configuration or SmartEQConfiguration() diff --git a/packages/modules/vehicles/smarteq/soc.py b/packages/modules/vehicles/smarteq/soc.py deleted file mode 100755 index 6fb4592b2f..0000000000 --- a/packages/modules/vehicles/smarteq/soc.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import List - -import logging - -from helpermodules.cli import run_using_positional_cli_args -from modules.common import store -from modules.common.abstract_device import DeviceDescriptor -from modules.common.abstract_vehicle import VehicleUpdateData -from modules.common.component_state import CarState -from modules.common.configurable_vehicle import ConfigurableVehicle -from modules.vehicles.smarteq import api -from modules.vehicles.smarteq.config import SmartEQ, SmartEQConfiguration - - -log = logging.getLogger(__name__) - - -def create_vehicle(vehicle_config: SmartEQ, vehicle: int): - def updater(vehicle_update_data: VehicleUpdateData) -> CarState: - return api.fetch_soc(vehicle_config, vehicle) - return ConfigurableVehicle(vehicle_config=vehicle_config, component_updater=updater, vehicle=vehicle) - -# def smarteq_update(user_id: str, password: str, vin: str, refreshToken: str, charge_point: int): - - -def smarteq_update(user_id: str, password: str, vin: str, charge_point: int): - log.debug("smarteq: user_id="+user_id+"vin="+vin+"charge_point="+str(charge_point)) - store.get_car_value_store(charge_point).store.set( - api.fetch_soc(SmartEQ(configuration=SmartEQConfiguration(user_id, password, vin)), charge_point)) - - -def main(argv: List[str]): - run_using_positional_cli_args(smarteq_update, argv) - - -device_descriptor = DeviceDescriptor(configuration_factory=SmartEQ) From 416296cd11db44e1b3601eb022eb2d6984927bf1 Mon Sep 17 00:00:00 2001 From: rleidner Date: Mon, 6 Jan 2025 18:26:10 +0100 Subject: [PATCH 2/3] update_config.py: if soc_module is configured as smarteq, replace by NO_MODULE --- packages/helpermodules/update_config.py | 87 +++++-------------------- 1 file changed, 17 insertions(+), 70 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 05104ae220..7702f92534 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -8,7 +8,11 @@ import time from typing import List, Optional from paho.mqtt.client import Client as MqttClient, MQTTMessage - +from control.bat_all import BatConsiderationMode +from control.chargepoint.charging_type import ChargingType +from control.counter import get_counter_default_config +from control.general import ChargemodeConfig +from control.optional_data import Ocpp import dataclass_utils from control.chargepoint.chargepoint_template import get_chargepoint_template_default @@ -29,14 +33,8 @@ from helpermodules.utils.run_command import run_command from helpermodules.utils.topic_parser import decode_payload, get_index, get_second_index from control import counter_all -from control.bat_all import BatConsiderationMode -from control.chargepoint.charging_type import ChargingType -from control.counter import get_counter_default_config -from control.ev.charge_template import get_charge_template_default -from control.ev import ev -from control.ev.ev_template import EvTemplateData -from control.general import ChargemodeConfig, Prices -from control.optional_data import Ocpp +from control import ev +from control.general import Prices from modules.common.abstract_vehicle import GeneralVehicleConfig from modules.common.component_type import ComponentType from modules.devices.sungrow.sungrow.version import Version @@ -51,7 +49,7 @@ class UpdateConfig: - DATASTORE_VERSION = 70 + DATASTORE_VERSION = 66 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/config/power_limit_mode$", @@ -93,7 +91,8 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/control_parameter/limit$", "^openWB/chargepoint/[0-9]+/control_parameter/prio$", "^openWB/chargepoint/[0-9]+/control_parameter/required_current$", - "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_last_phase_switch$", + "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_auto_phase_switch$", + "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_perform_phase_switch$", "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_switch_on_off$", "^openWB/chargepoint/[0-9]+/control_parameter/used_amount_instant_charging$", "^openWB/chargepoint/[0-9]+/control_parameter/phases$", @@ -104,7 +103,6 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/get/fault_state$", "^openWB/chargepoint/[0-9]+/get/fault_str$", "^openWB/chargepoint/[0-9]+/get/frequency$", - "^openWB/chargepoint/[0-9]+/get/max_evse_current$", "^openWB/chargepoint/[0-9]+/get/plug_state$", "^openWB/chargepoint/[0-9]+/get/phases_in_use$", "^openWB/chargepoint/[0-9]+/get/exported$", @@ -262,7 +260,6 @@ class UpdateConfig: "^openWB/optional/int_display/theme$", "^openWB/optional/int_display/only_local_charge_points", "^openWB/optional/led/active$", - "^openWB/optional/monitoring/config$", "^openWB/optional/rfid/active$", "^openWB/optional/ocpp/config$", @@ -303,7 +300,6 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/get/force_soc_update$", "^openWB/vehicle/[0-9]+/get/range$", "^openWB/vehicle/[0-9]+/get/soc$", - "^openWB/vehicle/[0-9]+/get/soc_request_timestamp$", "^openWB/vehicle/[0-9]+/get/soc_timestamp$", "^openWB/vehicle/[0-9]+/match_ev/selected$", "^openWB/vehicle/[0-9]+/match_ev/tag_id$", @@ -409,7 +405,6 @@ class UpdateConfig: "^openWB/system/configurable/devices_components$", "^openWB/system/configurable/electricity_tariffs$", "^openWB/system/configurable/display_themes$", - "^openWB/system/configurable/monitoring$", "^openWB/system/configurable/ripple_control_receivers$", "^openWB/system/configurable/soc_modules$", "^openWB/system/configurable/web_themes$", @@ -458,9 +453,9 @@ class UpdateConfig: ("openWB/vehicle/0/ev_template", ev.Ev(0).ev_template.et_num), ("openWB/vehicle/0/tag_id", ev.Ev(0).data.tag_id), ("openWB/vehicle/0/get/soc", ev.Ev(0).data.get.soc), - ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", - min_current=10))), - ("openWB/vehicle/template/charge_template/0", get_charge_template_default()), + ("openWB/vehicle/template/ev_template/0", asdict(ev.EvTemplateData(name="Standard-Fahrzeug-Profil", + min_current=10))), + ("openWB/vehicle/template/charge_template/0", ev.get_charge_template_default()), ("openWB/general/charge_log_data_config", get_default_charge_log_columns()), ("openWB/general/chargemode_config/instant_charging/phases_to_use", 3), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), @@ -517,7 +512,6 @@ class UpdateConfig: ("openWB/optional/int_display/theme", dataclass_utils.asdict(CardsDisplayTheme())), ("openWB/optional/int_display/only_local_charge_points", False), ("openWB/optional/led/active", False), - ("openWB/optional/monitoring/config", NO_MODULE), ("openWB/optional/ocpp/config", dataclass_utils.asdict(Ocpp())), ("openWB/optional/rfid/active", False), ("openWB/system/backup_cloud/config", NO_MODULE), @@ -1848,58 +1842,11 @@ def upgrade_datastore_64(self) -> None: def upgrade_datastore_65(self) -> None: def upgrade(topic: str, payload) -> None: - if re.search("openWB/system/device/[0-9]+", topic) is not None: + if re.search("openWB/vehicle/[0-9]+/soc_module/config", topic) is not None: payload = decode_payload(payload) - # update firmware of Sungrow - if payload.get("type") == "sungrow" and "firmware" not in payload["configuration"]: - payload["configuration"].update({"firmware": "v111"}) + # replace smarteq soc module by no_module + if payload.get("type") == "smarteq": + payload = NO_MODULE Pub().pub(topic, payload) self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 66) - - def upgrade_datastore_66(self) -> None: - def upgrade(topic: str, payload) -> None: - if re.search("openWB/system/device/[0-9]+", topic) is not None: - payload = decode_payload(payload) - # add type - if payload.get("type") == "huawei" and "type" not in payload["configuration"]: - payload["configuration"].update({"type": "s_dongle"}) - Pub().pub(topic, payload) - self._loop_all_received_topics(upgrade) - self.__update_topic("openWB/system/datastore_version", 67) - - def upgrade_datastore_67(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if "openWB/general/chargemode_config/phase_switch_delay" == topic: - if decode_payload(payload) < 5: - return {"openWB/general/chargemode_config/phase_switch_delay": 5} - self._loop_all_received_topics(upgrade) - self.__update_topic("openWB/system/datastore_version", 68) - - def upgrade_datastore_68(self) -> None: - def upgrade(topic: str, payload) -> None: - if re.search("openWB/system/device/[0-9]+", topic) is not None: - payload = decode_payload(payload) - index = get_index(topic) - if payload.get("type") == "discovergy": - for component_topic, component_payload in self.all_received_topics.items(): - if re.search(f"openWB/system/device/{index}/component/[0-9]+/config", - component_topic) is not None: - config_payload = decode_payload(component_payload) - if "info" not in config_payload: - config_payload.update({"info": {"manufacturer": None, "model": None}}) - return {component_topic: config_payload} - self._loop_all_received_topics(upgrade) - self.__update_topic("openWB/system/datastore_version", 69) - - def upgrade_datastore_69(self) -> None: - def upgrade(topic: str, payload) -> Optional[dict]: - if (re.search("openWB/vehicle/template/charge_template/[0-9]+/chargemode/scheduled_charging/plans/[0-9]+", - topic) is not None or - re.search("openWB/vehicle/template/charge_template/[0-9]+/time_charging/plans/[0-9]+", - topic) is not None): - payload = decode_payload(payload) - payload["id"] = int(get_second_index(topic)) - return {topic: payload} - self._loop_all_received_topics(upgrade) - self.__update_topic("openWB/system/datastore_version", 70) From 9019550e4cbfc8184779ff9c77b9fad66c5ae8b9 Mon Sep 17 00:00:00 2001 From: rleidner Date: Mon, 6 Jan 2025 19:12:50 +0100 Subject: [PATCH 3/3] sync with current master --- packages/helpermodules/update_config.py | 92 +++++++++++++++++++++---- 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 7702f92534..06fc051efb 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -8,11 +8,7 @@ import time from typing import List, Optional from paho.mqtt.client import Client as MqttClient, MQTTMessage -from control.bat_all import BatConsiderationMode -from control.chargepoint.charging_type import ChargingType -from control.counter import get_counter_default_config -from control.general import ChargemodeConfig -from control.optional_data import Ocpp + import dataclass_utils from control.chargepoint.chargepoint_template import get_chargepoint_template_default @@ -33,8 +29,14 @@ from helpermodules.utils.run_command import run_command from helpermodules.utils.topic_parser import decode_payload, get_index, get_second_index from control import counter_all -from control import ev -from control.general import Prices +from control.bat_all import BatConsiderationMode +from control.chargepoint.charging_type import ChargingType +from control.counter import get_counter_default_config +from control.ev.charge_template import get_charge_template_default +from control.ev import ev +from control.ev.ev_template import EvTemplateData +from control.general import ChargemodeConfig, Prices +from control.optional_data import Ocpp from modules.common.abstract_vehicle import GeneralVehicleConfig from modules.common.component_type import ComponentType from modules.devices.sungrow.sungrow.version import Version @@ -49,7 +51,7 @@ class UpdateConfig: - DATASTORE_VERSION = 66 + DATASTORE_VERSION = 71 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/config/power_limit_mode$", @@ -91,8 +93,7 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/control_parameter/limit$", "^openWB/chargepoint/[0-9]+/control_parameter/prio$", "^openWB/chargepoint/[0-9]+/control_parameter/required_current$", - "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_auto_phase_switch$", - "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_perform_phase_switch$", + "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_last_phase_switch$", "^openWB/chargepoint/[0-9]+/control_parameter/timestamp_switch_on_off$", "^openWB/chargepoint/[0-9]+/control_parameter/used_amount_instant_charging$", "^openWB/chargepoint/[0-9]+/control_parameter/phases$", @@ -103,6 +104,7 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/get/fault_state$", "^openWB/chargepoint/[0-9]+/get/fault_str$", "^openWB/chargepoint/[0-9]+/get/frequency$", + "^openWB/chargepoint/[0-9]+/get/max_evse_current$", "^openWB/chargepoint/[0-9]+/get/plug_state$", "^openWB/chargepoint/[0-9]+/get/phases_in_use$", "^openWB/chargepoint/[0-9]+/get/exported$", @@ -260,6 +262,7 @@ class UpdateConfig: "^openWB/optional/int_display/theme$", "^openWB/optional/int_display/only_local_charge_points", "^openWB/optional/led/active$", + "^openWB/optional/monitoring/config$", "^openWB/optional/rfid/active$", "^openWB/optional/ocpp/config$", @@ -300,6 +303,7 @@ class UpdateConfig: "^openWB/vehicle/[0-9]+/get/force_soc_update$", "^openWB/vehicle/[0-9]+/get/range$", "^openWB/vehicle/[0-9]+/get/soc$", + "^openWB/vehicle/[0-9]+/get/soc_request_timestamp$", "^openWB/vehicle/[0-9]+/get/soc_timestamp$", "^openWB/vehicle/[0-9]+/match_ev/selected$", "^openWB/vehicle/[0-9]+/match_ev/tag_id$", @@ -405,6 +409,7 @@ class UpdateConfig: "^openWB/system/configurable/devices_components$", "^openWB/system/configurable/electricity_tariffs$", "^openWB/system/configurable/display_themes$", + "^openWB/system/configurable/monitoring$", "^openWB/system/configurable/ripple_control_receivers$", "^openWB/system/configurable/soc_modules$", "^openWB/system/configurable/web_themes$", @@ -453,9 +458,9 @@ class UpdateConfig: ("openWB/vehicle/0/ev_template", ev.Ev(0).ev_template.et_num), ("openWB/vehicle/0/tag_id", ev.Ev(0).data.tag_id), ("openWB/vehicle/0/get/soc", ev.Ev(0).data.get.soc), - ("openWB/vehicle/template/ev_template/0", asdict(ev.EvTemplateData(name="Standard-Fahrzeug-Profil", - min_current=10))), - ("openWB/vehicle/template/charge_template/0", ev.get_charge_template_default()), + ("openWB/vehicle/template/ev_template/0", asdict(EvTemplateData(name="Standard-Fahrzeug-Profil", + min_current=10))), + ("openWB/vehicle/template/charge_template/0", get_charge_template_default()), ("openWB/general/charge_log_data_config", get_default_charge_log_columns()), ("openWB/general/chargemode_config/instant_charging/phases_to_use", 3), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), @@ -512,6 +517,7 @@ class UpdateConfig: ("openWB/optional/int_display/theme", dataclass_utils.asdict(CardsDisplayTheme())), ("openWB/optional/int_display/only_local_charge_points", False), ("openWB/optional/led/active", False), + ("openWB/optional/monitoring/config", NO_MODULE), ("openWB/optional/ocpp/config", dataclass_utils.asdict(Ocpp())), ("openWB/optional/rfid/active", False), ("openWB/system/backup_cloud/config", NO_MODULE), @@ -1841,6 +1847,64 @@ def upgrade_datastore_64(self) -> None: self.__update_topic("openWB/system/datastore_version", 65) def upgrade_datastore_65(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") == "sungrow" and "firmware" not in payload["configuration"]: + payload["configuration"].update({"firmware": "v111"}) + Pub().pub(topic, payload) + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 66) + + def upgrade_datastore_66(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/system/device/[0-9]+", topic) is not None: + payload = decode_payload(payload) + # add type + if payload.get("type") == "huawei" and "type" not in payload["configuration"]: + payload["configuration"].update({"type": "s_dongle"}) + Pub().pub(topic, payload) + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 67) + + def upgrade_datastore_67(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if "openWB/general/chargemode_config/phase_switch_delay" == topic: + if decode_payload(payload) < 5: + return {"openWB/general/chargemode_config/phase_switch_delay": 5} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 68) + + def upgrade_datastore_68(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/system/device/[0-9]+", topic) is not None: + payload = decode_payload(payload) + index = get_index(topic) + if payload.get("type") == "discovergy": + for component_topic, component_payload in self.all_received_topics.items(): + if re.search(f"openWB/system/device/{index}/component/[0-9]+/config", + component_topic) is not None: + config_payload = decode_payload(component_payload) + if "info" not in config_payload: + config_payload.update({"info": {"manufacturer": None, "model": None}}) + return {component_topic: config_payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 69) + + def upgrade_datastore_69(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if (re.search("openWB/vehicle/template/charge_template/[0-9]+/chargemode/scheduled_charging/plans/[0-9]+", + topic) is not None or + re.search("openWB/vehicle/template/charge_template/[0-9]+/time_charging/plans/[0-9]+", + topic) is not None): + payload = decode_payload(payload) + payload["id"] = int(get_second_index(topic)) + return {topic: payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 70) + + def upgrade_datastore_70(self) -> None: def upgrade(topic: str, payload) -> None: if re.search("openWB/vehicle/[0-9]+/soc_module/config", topic) is not None: payload = decode_payload(payload) @@ -1849,4 +1913,4 @@ def upgrade(topic: str, payload) -> None: payload = NO_MODULE Pub().pub(topic, payload) self._loop_all_received_topics(upgrade) - self.__update_topic("openWB/system/datastore_version", 66) + self.__update_topic("openWB/system/datastore_version", 71)