From 661838cf967500ed89323ca9838a46039f11e8e1 Mon Sep 17 00:00:00 2001 From: vuffiraa72 Date: Sun, 13 Apr 2025 14:17:12 +0200 Subject: [PATCH 1/4] add vwgroup --- packages/modules/vehicles/vwgroup/__init__.py | 0 .../vehicles/{vwid => vwgroup}/socutils.py | 0 packages/modules/vehicles/vwgroup/vwgroup.py | 138 ++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100755 packages/modules/vehicles/vwgroup/__init__.py rename packages/modules/vehicles/{vwid => vwgroup}/socutils.py (100%) create mode 100644 packages/modules/vehicles/vwgroup/vwgroup.py diff --git a/packages/modules/vehicles/vwgroup/__init__.py b/packages/modules/vehicles/vwgroup/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/packages/modules/vehicles/vwid/socutils.py b/packages/modules/vehicles/vwgroup/socutils.py similarity index 100% rename from packages/modules/vehicles/vwid/socutils.py rename to packages/modules/vehicles/vwgroup/socutils.py diff --git a/packages/modules/vehicles/vwgroup/vwgroup.py b/packages/modules/vehicles/vwgroup/vwgroup.py new file mode 100644 index 0000000000..683baf2235 --- /dev/null +++ b/packages/modules/vehicles/vwgroup/vwgroup.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +from datetime import datetime +from json import dumps +import logging +from time import mktime, time +from typing import Union +from modules.common.store import RAMDISK_PATH +from modules.vehicles.vwgroup.socutils import socUtils + +date_fmt = '%Y-%m-%d %H:%M:%S' +ts_fmt = '%Y-%m-%dT%H:%M:%S' + +refreshToken_exp_days = 7 # 7 days before refreshToken expires a new refreshToken shall be stored +initialToken = '1.2.3' + + +class VwGroup(object): + def __init__(self, conf, vehicle): + self.log = logging.getLogger(__name__) + self.su = socUtils() + self.user_id = conf.configuration.user_id + self.password = conf.configuration.password + self.vin = conf.configuration.vin + self.refreshToken = conf.configuration.refreshToken + self.replyFile = 'soc_' + str(conf.type) + '_reply_vh_' + str(vehicle) + self.accessTokenFile = str(RAMDISK_PATH) + '/soc_' + str(conf.type) + '_accessToken_vh_' + str(vehicle) + self.accessToken_old = {} + self.vehicle = vehicle + self.conf = conf + + # convert utc timestamp to local time + def utc2local(self, utc): + epoch = mktime(utc.timetuple()) + offset = datetime.fromtimestamp(epoch) - datetime.utcfromtimestamp(epoch) + return utc + offset + + # async method, called from sync fetch_soc, required because libvwid/libskoda expect async environment + async def request_data(self, library) -> Union[int, float, str]: + library.set_vin(self.vin) + library.set_credentials(self.user_id, self.password) + library.set_jobs(['charging']) + library.tokens = {} + library.headers = {} + + # initialize refreshToken + try: + if self.refreshToken is None: + self.log.debug("set refreshToken to initial value") + library.tokens['refreshToken'] = initialToken + else: + library.tokens['refreshToken'] = self.refreshToken + + except Exception: + self.log.debug("refreshToken initialization exception: set refreshToken_old to initial value") + library.tokens['refreshToken'] = initialToken + + self.refreshTokenOld = library.tokens['refreshToken'] # remember current refreshToken + + # initialize accessToken + self.accessTokenOld = self.su.read_token_file(self.accessTokenFile) + if self.accessTokenOld is None: + self.log.debug('set accessToken to initial value') + self.accessTokenOld = initialToken + library.tokens['accessToken'] = self.accessTokenOld # initialize tokens in vwid + library.headers['Authorization'] = 'Bearer %s' % library.tokens["accessToken"] + + # get status from VW server + self.data = await library.get_status() + if (self.data): + if self.su.keys_exist(self.data, 'userCapabilities', 'capabilitiesStatus', 'error'): + self.log.error("Server Error: \n" + + dumps(self.data['userCapabilities']['capabilitiesStatus']['error'], + ensure_ascii=False, indent=4)) + + if self.su.keys_exist(self.data, 'charging', 'batteryStatus'): + self.log.debug("batteryStatus: \n" + + dumps(self.data['charging']['batteryStatus'], + ensure_ascii=False, indent=4)) + + try: + self.soc = int(self.data['charging']['batteryStatus']['value']['currentSOC_pct']) + self.range = float(self.data['charging']['batteryStatus']['value']['cruisingRangeElectric_km']) + soc_tsZ = self.data['charging']['batteryStatus']['value']['carCapturedTimestamp'] + soc_tsdtZ = datetime.strptime(soc_tsZ, ts_fmt + "Z") + soc_tsdtL = self.utc2local(soc_tsdtZ) + self.soc_tsX = datetime.timestamp(soc_tsdtL) + self.soc_ts = datetime.strftime(soc_tsdtL, ts_fmt) + except Exception as e: + self.log.exception("soc/range/soc_ts field missing exception: e=" + str(e)) + self.soc = 0 + self.range = 0.0 + self.soc_ts = "" + self.soc_tsX = time() + + # decision logic - shall a new refreshToken be stored? + self.store_refreshToken = False + self.refreshTokenNew = library.tokens['refreshToken'] + + if self.refreshTokenOld != initialToken: + try: + self.expOld, self.expOld_dt = self.su.get_token_expiration(self.refreshTokenOld, date_fmt) + self.now = int(time()) + expirationThreshold = self.expOld - refreshToken_exp_days * 86400 + + if expirationThreshold < self.now: + self.log.debug('RefreshToken: expiration in less than ' + + str(refreshToken_exp_days) + ' days on ' + self.expOld_dt + ', store new token') + self.store_refreshToken = True + except Exception as e: + self.log.debug("refreshToken decode exception: e=" + str(e)) + self.store_refreshToken = True # no old refreshToken, store new refreshToken anyway + + else: + self.log.debug("Old refreshToken expires on " + self.expOld_dt + ", keep it") + elif self.refreshTokenNew != initialToken: + self.store_refreshToken = True # no old refreshToken, store new refreshToken anyway + + if self.store_refreshToken: # refreshToken needs to be stored in config json + try: + self.expNew, self.expNew_dt = self.su.get_token_expiration(self.refreshTokenNew, date_fmt) + self.log.debug("store new refreshToken, expires on " + self.expNew_dt) + except Exception as e: + self.log.debug("new refreshToken decode exception, e=" + str(e)) + self.log.debug("new refreshToken=" + str(self.refreshTokenNew)) + + confDict = self.conf.__dict__ + confDict.pop('name') + confDict['configuration'] = self.conf.configuration.__dict__ + self.su.write_token_mqtt( + "openWB/set/vehicle/" + self.vehicle + "/soc_module/config", + self.refreshTokenNew, + self.conf.__dict__) + + if (library.tokens['accessToken'] != self.accessTokenOld): # modified accessToken? + self.su.write_token_file(self.accessTokenFile, library.tokens['accessToken']) + + return self.soc, self.range, self.soc_ts, self.soc_tsX From b9c02230bcede03139b91de37bc2f1ce47395d81 Mon Sep 17 00:00:00 2001 From: vuffiraa72 Date: Sun, 13 Apr 2025 14:17:23 +0200 Subject: [PATCH 2/4] add skoda --- packages/modules/vehicles/skoda/__init__.py | 0 packages/modules/vehicles/skoda/api.py | 33 +++ packages/modules/vehicles/skoda/config.py | 28 +++ packages/modules/vehicles/skoda/libskoda.py | 254 ++++++++++++++++++++ packages/modules/vehicles/skoda/soc.py | 43 ++++ 5 files changed, 358 insertions(+) create mode 100755 packages/modules/vehicles/skoda/__init__.py create mode 100755 packages/modules/vehicles/skoda/api.py create mode 100755 packages/modules/vehicles/skoda/config.py create mode 100755 packages/modules/vehicles/skoda/libskoda.py create mode 100755 packages/modules/vehicles/skoda/soc.py diff --git a/packages/modules/vehicles/skoda/__init__.py b/packages/modules/vehicles/skoda/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/packages/modules/vehicles/skoda/api.py b/packages/modules/vehicles/skoda/api.py new file mode 100755 index 0000000000..33cfb805b8 --- /dev/null +++ b/packages/modules/vehicles/skoda/api.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import aiohttp +from asyncio import new_event_loop, set_event_loop +from typing import Union +from modules.vehicles.skoda import libskoda +from modules.vehicles.skoda.config import Skoda +from modules.vehicles.vwgroup.vwgroup import VwGroup + + +class api(VwGroup): + + def __init__(self, conf: Skoda, vehicle: int): + super().__init__(conf, vehicle) + + # async method, called from sync fetch_soc, required because libvwid/libskoda expect async environment + async def _fetch_soc(self) -> Union[int, float, str]: + async with aiohttp.ClientSession() as self.session: + skoda = libskoda.skoda(self.session) + return await super().request_data(skoda) + + +def fetch_soc(conf: Skoda, vehicle: int) -> Union[int, float, str]: + + # prepare and call async method + loop = new_event_loop() + set_event_loop(loop) + + # get soc, range from server + a = api(conf, vehicle) + soc, range, soc_ts, soc_tsX = loop.run_until_complete(a._fetch_soc()) + + return soc, range, soc_ts, soc_tsX diff --git a/packages/modules/vehicles/skoda/config.py b/packages/modules/vehicles/skoda/config.py new file mode 100755 index 0000000000..d91a690596 --- /dev/null +++ b/packages/modules/vehicles/skoda/config.py @@ -0,0 +1,28 @@ +from typing import Optional + + +class SkodaConfiguration: + 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! + calculate_soc: bool = False # show in UI + ): + self.user_id = user_id + self.password = password + self.vin = vin + self.refreshToken = refreshToken + self.calculate_soc = calculate_soc + + +class Skoda: + def __init__(self, + name: str = "Skoda", + type: str = "skoda", + official: bool = False, + configuration: SkodaConfiguration = None) -> None: + self.name = name + self.type = type + self.official = official + self.configuration = configuration or SkodaConfiguration() diff --git a/packages/modules/vehicles/skoda/libskoda.py b/packages/modules/vehicles/skoda/libskoda.py new file mode 100755 index 0000000000..89b7b3a284 --- /dev/null +++ b/packages/modules/vehicles/skoda/libskoda.py @@ -0,0 +1,254 @@ +# A Python class to communicate with the "Skoda Connect" API. +# Adapted the libvwid.py module to skoda interface + +import secrets +import logging +import json +import uuid +import base64 +import hashlib + +from helpermodules.utils.error_handling import ImportErrorContext +with ImportErrorContext(): + import lxml.html + +# Constants +LOGIN_BASE = "https://identity.vwgroup.io/oidc/v1" +LOGIN_HANDLER_BASE = "https://identity.vwgroup.io" +API_BASE = "https://mysmob.api.connect.skoda-auto.cz/api" +CLIENT_ID = "7f045eee-7003-4379-9968-9355ed2adb06@apps_vw-dilab_com" + + +class skoda: + def __init__(self, session): + self.session = session + self.headers = {} + self.log = logging.getLogger(__name__) + self.jobs_string = 'all' + + def form_from_response(self, text): + page = lxml.html.fromstring(text) + elements = page.xpath('//form//input[@type="hidden"]') + form = {x.attrib['name']: x.attrib['value'] for x in elements} + return (form, page.forms[0].action) + + def password_form(self, text): + page = lxml.html.fromstring(text) + elements = page.xpath('//script') + + # Todo: Find more elegant way parse this... + objects = {} + for a in elements: + if (a.text) and (a.text.find('window._IDK') != -1): + text = a.text.strip() + text = text[text.find('\n'):text.rfind('\n')].strip() + for line in text.split('\n'): + try: + (name, val) = line.strip().split(':', 1) + except ValueError: + continue + val = val.strip('\', ') + objects[name] = val + + json_model = json.loads(objects['templateModel']) + + if ('errorCode' in json_model): + self.log.error("Login error: %s", json_model['errorCode']) + return False + + try: + # Generate form + form = {} + form['relayState'] = json_model['relayState'] + form['hmac'] = json_model['hmac'] + form['email'] = json_model['emailPasswordForm']['email'] + form['_csrf'] = objects['csrf_token'] + + # Generate URL action + action = '/signin-service/v1/%s/%s'\ + % (json_model['clientLegalEntityModel']['clientId'], json_model['postAction']) + + return (form, action) + + except KeyError: + self.log.exception("Missing fields in response from Skoda API") + return False + + def set_vin(self, vin): + self.vin = vin + + def set_credentials(self, username, password): + self.username = username + self.password = password + + def set_jobs(self, jobs): + self.jobs_string = ','.join(jobs) + + def get_code_challenge(self): + code_verifier = secrets.token_urlsafe(64).replace('+', '-').replace('/', '_').replace('=', '') + code_challenge = base64.b64encode(hashlib.sha256(code_verifier.encode('utf-8')).digest()) + code_challenge = code_challenge.decode('utf-8').replace('+', '-').replace('/', '_').replace('=', '') + return (code_verifier, code_challenge) + + async def connect(self, username, password): + self.set_credentials(username, password) + return (await self.reconnect()) + + async def reconnect(self): + # Get code challenge and verifier + code_verifier, code_challenge = self.get_code_challenge() + + # Get authorize page + _scope = 'address badge birthdate cars driversLicense dealers email mileage mbb nationalIdentifier' + _scope = _scope + ' openid phone profession profile vin' + payload = { + 'client_id': CLIENT_ID, + 'scope': _scope, + 'response_type': 'code id_token', + 'nonce': secrets.token_urlsafe(12), + 'redirect_uri': 'myskoda://redirect/login/', + 'state': str(uuid.uuid4()), + 'code_challenge': code_challenge, + 'code_challenge_method': 'S256' + } + + response = await self.session.get(LOGIN_BASE + '/authorize', params=payload) + if response.status >= 400: + self.log.error(f"Authorize: Non-2xx response ({response.status})") + # Non 2xx response, failed + return False + + # Fill form with email (username) + (form, action) = self.form_from_response(await response.read()) + form['email'] = self.username + response = await self.session.post(LOGIN_HANDLER_BASE+action, data=form) + if response.status >= 400: + self.log.error("Email: Non-2xx response") + return False + + # Fill form with password + (form, action) = self.password_form(await response.read()) + form['password'] = self.password + url = LOGIN_HANDLER_BASE + action + response = await self.session.post(url, data=form, allow_redirects=False) + + # Can get a 303 redirect for a "terms and conditions" page + if (response.status == 303): + url = response.headers['Location'] + if ("terms-and-conditions" in url): + # Get terms and conditions page + url = LOGIN_HANDLER_BASE + url + response = await self.session.get(url, data=form, allow_redirects=False) + (form, action) = self.form_from_response(await response.read()) + + url = LOGIN_HANDLER_BASE + action + response = await self.session.post(url, data=form, allow_redirects=False) + + self.log.warn("Agreed to terms and conditions") + else: + self.log.error("Got unknown 303 redirect") + return False + + # Handle every single redirect and stop if the redirect + # URL uses the weconnect adapter. + while (True): + url = response.headers['Location'] + if (url.split(':')[0] == "myskoda"): + if not ('id_token' in url): + self.log.error("Missing id token") + return False + # Parse query string + query_string = url.split('#')[1] + query = {x[0]: x[1] for x in [x.split("=") for x in query_string.split("&")]} + break + + if (response.status != 302): + self.log.error("Not redirected, status %u" % response.status) + return False + + response = await self.session.get(url, data=form, allow_redirects=False) + + self.headers = dict(response.headers) + + # Get final token + params = { + 'tokenType': 'CONNECT' + } + payload = { + 'code': query['code'], + 'redirectUri': "myskoda://redirect/login/", + 'verifier': code_verifier + } + response = await self.session.post(API_BASE + '/v1/authentication/exchange-authorization-code', + params=params, json=payload) + if response.status >= 400: + self.log.error("Login: Non-2xx response") + # Non 2xx response, failed + return False + self.tokens = await response.json() + + # Update header with final token + self.headers['Authorization'] = 'Bearer %s' % self.tokens["accessToken"] + + # Success + return True + + async def refresh_tokens(self): + if not self.headers: + return False + + params = { + 'tokenType': 'CONNECT' + } + # Use the refresh token + payload = { + 'token': self.tokens["refreshToken"] + } + + response = await self.session.post(API_BASE + '/v1/authentication/refresh-token', params=params, json=payload) + if response.status >= 400: + return False + self.tokens = await response.json() + + # Use the newly received access token + self.headers['Authorization'] = 'Bearer %s' % self.tokens["accessToken"] + + return True + + async def get_status(self): + status_url = f"{API_BASE}/v2/vehicle-status/{self.vin}/driving-range" + response = await self.session.get(status_url, headers=self.headers) + + # If first attempt fails, try to refresh tokens + if response.status >= 400: + self.log.debug("Refreshing tokens") + if await self.refresh_tokens(): + response = await self.session.get(status_url, headers=self.headers) + + # If refreshing tokens failed, try a full reconnect + if response.status >= 400: + self.log.info("Reconnecting") + if await self.reconnect(): + response = await self.session.get(status_url, headers=self.headers) + else: + self.log.error("Reconnect failed") + return {} + + if response.status >= 400: + self.log.error("Get status failed") + return {} + + status_data = await response.json() + self.log.debug(f"Status data from Skoda API: {status_data}") + + return { + 'charging': { + 'batteryStatus': { + 'value': { + 'currentSOC_pct': status_data['primaryEngineRange']['currentSoCInPercent'], + 'cruisingRangeElectric_km': status_data['primaryEngineRange']['remainingRangeInKm'], + 'carCapturedTimestamp': status_data['carCapturedTimestamp'].split('.')[0] + 'Z', + } + } + } + } diff --git a/packages/modules/vehicles/skoda/soc.py b/packages/modules/vehicles/skoda/soc.py new file mode 100755 index 0000000000..c2de41bb2d --- /dev/null +++ b/packages/modules/vehicles/skoda/soc.py @@ -0,0 +1,43 @@ +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.skoda import api +from modules.vehicles.skoda.config import Skoda, SkodaConfiguration + + +log = logging.getLogger(__name__) + + +def fetch(vehicle_update_data: VehicleUpdateData, config: Skoda, vehicle: int) -> CarState: + soc, range, soc_ts, soc_tsX = api.fetch_soc(config, vehicle) + log.info("Result: soc=" + str(soc)+", range=" + str(range) + "@" + soc_ts) + return CarState(soc=soc, range=range, soc_timestamp=soc_tsX) + + +def create_vehicle(vehicle_config: Skoda, vehicle: int): + def updater(vehicle_update_data: VehicleUpdateData) -> CarState: + return fetch(vehicle_update_data, vehicle_config, vehicle) + return ConfigurableVehicle(vehicle_config=vehicle_config, + component_updater=updater, + vehicle=vehicle, + calc_while_charging=vehicle_config.configuration.calculate_soc) + + +def skoda_update(user_id: str, password: str, vin: str, refreshToken: str, charge_point: int): + log.debug("skoda: user_id="+user_id+"vin="+vin+"charge_point="+str(charge_point)) + store.get_car_value_store(charge_point).store.set( + fetch(None, Skoda(configuration=SkodaConfiguration(user_id, password, vin, refreshToken)), charge_point)) + + +def main(argv: List[str]): + run_using_positional_cli_args(skoda_update, argv) + + +device_descriptor = DeviceDescriptor(configuration_factory=Skoda) From 14387cde08ff0b4e24f1d80bf71805951da380c4 Mon Sep 17 00:00:00 2001 From: vuffiraa72 Date: Sun, 13 Apr 2025 14:37:49 +0200 Subject: [PATCH 3/4] vwid use vwgroup --- packages/modules/vehicles/vwid/api.py | 152 ++------------------------ 1 file changed, 12 insertions(+), 140 deletions(-) diff --git a/packages/modules/vehicles/vwid/api.py b/packages/modules/vehicles/vwid/api.py index bf4198c294..30d69946b9 100755 --- a/packages/modules/vehicles/vwid/api.py +++ b/packages/modules/vehicles/vwid/api.py @@ -1,151 +1,23 @@ #!/usr/bin/env python3 -from logging import getLogger -from typing import Union -from modules.vehicles.vwid import libvwid import aiohttp from asyncio import new_event_loop, set_event_loop -from time import time, mktime -from datetime import datetime -from json import dumps -from modules.common.store import RAMDISK_PATH +from typing import Union +from modules.vehicles.vwid import libvwid from modules.vehicles.vwid.config import VWId -from modules.vehicles.vwid.socutils import socUtils - -date_fmt = '%Y-%m-%d %H:%M:%S' -ts_fmt = '%Y-%m-%dT%H:%M:%S' -refreshToken_exp_days = 7 # 7 days before refreshToken expires a new refreshToken shall be stored -initialToken = '1.2.3' - -log = getLogger(__name__) +from modules.vehicles.vwgroup.vwgroup import VwGroup -# convert utc timestamp to local time -def utc2local(utc): - epoch = mktime(utc.timetuple()) - offset = datetime.fromtimestamp(epoch) - datetime.utcfromtimestamp(epoch) - return utc + offset +class api(VwGroup): + def __init__(self, conf: VWId, vehicle: int): + super().__init__(conf, vehicle) -class api: - - def __init__(self): - self.su = socUtils() - pass - - # async method, called from sync fetch_soc, required because libvwid expects async environment - async def _fetch_soc(self, - conf: VWId, - vehicle: int) -> Union[int, float, str]: - self.user_id = conf.configuration.user_id - self.password = conf.configuration.password - self.vin = conf.configuration.vin - self.refreshToken = conf.configuration.refreshToken - self.replyFile = 'soc_vwid_reply_vh_' + str(vehicle) - self.accessTokenFile = str(RAMDISK_PATH) + '/soc_vwid_accessToken_vh_' + str(vehicle) - self.accessToken_old = {} - + # async method, called from sync fetch_soc, required because libvwid expect async environment + async def _fetch_soc(self) -> Union[int, float, str]: async with aiohttp.ClientSession() as self.session: - self.w = libvwid.vwid(self.session) - self.w.set_vin(self.vin) - self.w.set_credentials(self.user_id, self.password) - self.w.set_jobs(['charging']) - self.w.tokens = {} - self.w.headers = {} - - # initialize refreshToken - try: - if self.refreshToken is None: - log.debug("set refreshToken to initial value") - self.w.tokens['refreshToken'] = initialToken - else: - self.w.tokens['refreshToken'] = self.refreshToken - - except Exception: - log.debug("refreshToken initialization exception: set refreshToken_old to initial value") - self.w.tokens['refreshToken'] = initialToken - - self.refreshTokenOld = self.w.tokens['refreshToken'] # remember current refreshToken - - # initialize accessToken - self.accessTokenOld = self.su.read_token_file(self.accessTokenFile) - if self.accessTokenOld is None: - log.debug('set accessToken to initial value') - self.accessTokenOld = initialToken - self.w.tokens['accessToken'] = self.accessTokenOld # initialize tokens in vwid - self.w.headers['Authorization'] = 'Bearer %s' % self.w.tokens["accessToken"] - - # get status from VW server - self.data = await self.w.get_status() - if (self.data): - if self.su.keys_exist(self.data, 'userCapabilities', 'capabilitiesStatus', 'error'): - log.error("Server Error: \n" - + dumps(self.data['userCapabilities']['capabilitiesStatus']['error'], - ensure_ascii=False, indent=4)) - - if self.su.keys_exist(self.data, 'charging', 'batteryStatus'): - log.debug("batteryStatus: \n" + - dumps(self.data['charging']['batteryStatus'], - ensure_ascii=False, indent=4)) - - try: - self.soc = int(self.data['charging']['batteryStatus']['value']['currentSOC_pct']) - self.range = float(self.data['charging']['batteryStatus']['value']['cruisingRangeElectric_km']) - soc_tsZ = self.data['charging']['batteryStatus']['value']['carCapturedTimestamp'] - soc_tsdtZ = datetime.strptime(soc_tsZ, ts_fmt + "Z") - soc_tsdtL = utc2local(soc_tsdtZ) - self.soc_tsX = datetime.timestamp(soc_tsdtL) - self.soc_ts = datetime.strftime(soc_tsdtL, ts_fmt) - except Exception as e: - log.exception("soc/range/soc_ts field missing exception: e=" + str(e)) - self.soc = 0 - self.range = 0.0 - self.soc_ts = "" - self.soc_tsX = time() - - # decision logic - shall a new refreshToken be stored? - self.store_refreshToken = False - self.refreshTokenNew = self.w.tokens['refreshToken'] - - if self.refreshTokenOld != initialToken: - try: - self.expOld, self.expOld_dt = self.su.get_token_expiration(self.refreshTokenOld, date_fmt) - self.now = int(time()) - expirationThreshold = self.expOld - refreshToken_exp_days * 86400 - - if expirationThreshold < self.now: - log.debug('RefreshToken: expiration in less than ' + - str(refreshToken_exp_days) + ' days on ' + self.expOld_dt + ', store new token') - self.store_refreshToken = True - except Exception as e: - log.debug("refreshToken decode exception: e=" + str(e)) - self.store_refreshToken = True # no old refreshToken, store new refreshToken anyway - - else: - log.debug("Old refreshToken expires on " + self.expOld_dt + ", keep it") - else: - self.store_refreshToken = True # no old refreshToken, store new refreshToken anyway - - if self.store_refreshToken: # refreshToken needs to be stored in config json - try: - self.expNew, self.expNew_dt = self.su.get_token_expiration(self.refreshTokenNew, date_fmt) - log.debug("store new refreshToken, expires on " + self.expNew_dt) - except Exception as e: - log.debug("new refreshToken decode exception, e=" + str(e)) - log.debug("new refreshToken=" + str(self.refreshTokenNew)) - - confDict = conf.__dict__ - confDict.pop('name') - confDict['configuration'] = conf.configuration.__dict__ - self.su.write_token_mqtt( - "openWB/set/vehicle/" + vehicle + "/soc_module/config", - self.refreshTokenNew, - conf.__dict__) - - if (self.w.tokens['accessToken'] != self.accessTokenOld): # modified accessToken? - self.su.write_token_file(self.accessTokenFile, self.w.tokens['accessToken']) - - return self.soc, self.range, self.soc_ts, self.soc_tsX + vwid = libvwid.vwid(self.session) + return await super().request_data(vwid) def fetch_soc(conf: VWId, vehicle: int) -> Union[int, float, str]: @@ -155,7 +27,7 @@ def fetch_soc(conf: VWId, vehicle: int) -> Union[int, float, str]: set_event_loop(loop) # get soc, range from server - a = api() - soc, range, soc_ts, soc_tsX = loop.run_until_complete(a._fetch_soc(conf, vehicle)) + a = api(conf, vehicle) + soc, range, soc_ts, soc_tsX = loop.run_until_complete(a._fetch_soc()) return soc, range, soc_ts, soc_tsX From 1814aa64d960601311d19b9855cc61bf2d20947e Mon Sep 17 00:00:00 2001 From: vuffiraa72 Date: Sat, 19 Apr 2025 10:03:03 +0200 Subject: [PATCH 4/4] add wiki --- docs/SoC-Skoda-settings-1.PNG | Bin 0 -> 26522 bytes docs/SoC-Skoda-settings-2.PNG | Bin 0 -> 25032 bytes docs/SoC-Skoda.md | 82 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 docs/SoC-Skoda-settings-1.PNG create mode 100644 docs/SoC-Skoda-settings-2.PNG create mode 100644 docs/SoC-Skoda.md diff --git a/docs/SoC-Skoda-settings-1.PNG b/docs/SoC-Skoda-settings-1.PNG new file mode 100644 index 0000000000000000000000000000000000000000..892f4d24e66f65d987d279b9a32133498e085fc7 GIT binary patch literal 26522 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

{eRS7y(*c zxZL$_q}=UixFKdz44^GPo9pQACxySjI(1bw?8Par{|bC%Bd@`77-56i6Tgd&9tBX{ z-T;1!d1mcj5cYx(3P8-~r@{sjO449(qOaV)97>MoOBZ1z(zPB>oAL=z03Wp$_Ujj9x^GmojI^&&hsgU>#;YU%#l1)p?R z-7~1$1A4ct7M*Z$jous)%jrHuDR7r^2ftXS$-^hZl>r8C+dD19kklE`3OoA@$rdPE z(msv;i^1R1{l+ri;*NI6pEWNY=#IuBKv#nAi{2c0v?f-*@pJw;qk=QnGPcQY$mg8K ztF3Mhnzg@LfjZs&vJ5<|lAy%G$J1IXpx&5?GCnJT2H{ zm7Nc-TZ(-ms^+%EvRQ55Gm*LlGOA$yiJ|m!Qn(JQB7NO+&cAFt(qR=gvSqoRPS~7^ zvfUMG7ejL~?+MjvIS@KfbLTUbBtEe}=G$3@J=3xD_wzvALFM)ylGd3(Xoj5B7A9v> zr2#+Ptsn!Sev(GH;0uE9qfa)nCLRDTCbeDI`rg&?noGG=^z3RnsH^cuQD8$rsLkZ0 z!F1bWL)kB+@TyLqcL)%Exq+bDxOvDvfGqWM&m`FZAo$I6xEQz#YGs*UYPC7_#-1A& zyYD9axa>GLVzWoV?&)&F)c66!Bjbo2cfFKzMIASWQNk@ZGD zjNHDHl^)%NvbY13ZVqFI9%VjEHvwxqe0h)(#jA=ZX-J1V2VFZ?y_}tqemkuK8*SAXvk_4_uU zt|Ek3UGF&N-m}P$e-rjBN!r7%ujv}boN&P{!~+x^KX@wgMC0gd;W9&K&yOxPG%}T$ zM;%EDFTu|{WP9wIGyuW&OCc0S zR^Srz3xJrO7Tyo+UqIL7pmM@gnUyp_&> z{NwVUIQ|(ER-TM7GY{5_d6g}Em(5{0%VfdJbF0JtIi&d1m(N?9aCrl`*n*5KtEz_6 zlWc?O^1t*_13j>Dx_>Kpcm04=nOnfSB^}6^ZJg55M0-U8vx~(hMY$n zxRKs9*CdC~&qnx5T2Ju!dtTFnhS~LOy@XtNivAv^p!?S*VogAPQks`-Jvbaer{nV- z69_+sCttlZ!0@Vm#y)LQS0_G|+)Abbm=k-5`Nfz2?Ae!Jne;-(x`M?~HK=Z+5 zt;NP?cQwVfZb0vZ1>SkKJiZs4&yJROPSQxueF2e2lhlwTjqOChR{k{>X3k&mp=p5D zxE{=!dvAt+c(|!neIi>g<|fwwt11-yC4dKb2X07z`q$n= z%NcWx7t+J4k@(^TDNN_y3j%vq;2*_$&7?-^s-b<19nK^%7!T2 z1Gaq!uHIa>9m#ieG^k%TfAO+l$oS^;H$Dgy>t%xOkX~!LZICp8^KouhT3hIWQH2E2 zvD?hUzMSGw2U7?ITLMwL?yvj*$*4=bAZd^-&aE1rqCSt>(TWlVVzl);I!y?@qs!?kW-if+8QFGxR>{;HhYZdU~GQ%3Md%#)z`IQ{6p`lIS(I^-E@n zyJ`I(UhDI16~IxkBfO!8J54lR-bxiI|Bs&Qy?J!hDatt;#0P2DUu%d)>;w=|3D`H{5<~ylt>{AZT6LaSk>eTXcgs(jDPcU zQgqAMn>@z~crZ`i3eg>Pu)=|=xt;%6rCrS{Kj1!80No!`du_Ytip#jV-$XWUIMXEe zKOhkRSH+b4$Pvj7da!@C5GY%>{2@g82wm6Gs*mk_=buUD))zK$2K@?v0_3N*JX>B1 z+Q$%Wa7xBCuKxqu9oj=mOj)YG-+3hUyWIb4K#t*#o*p!7?^Futw*vHhzmcEkBN+^L zSLy1}A|*U|Ponar$|9g$hwVzg702I;t@I3}lh54^_yt*C>;Mw-i~x_1bU1qwMDF)k zq%{ZOpw;5bUB!QcuPtB14&j2^=7UysP5@A!p#=hpglf4S24E-Sy8mzUCO9uywN?ZT z3I)Cv0Lp75Di*a$30VpJ#Uzkmb&PEALH|eo{a?`N)M|UKl1{7;E&o2k;V%7@FjJNq znz*WSo%W|Bpe+8mKlXassbl6dqQd--(Lb*rgNW`$T7N$NsX0YBmcQjys5Y-80bAi` zzQXjZGfY*BSmA*G>GJ=r`|Iwwh%sb@As!w6UGx_~{+}+p$bS(DXcc^}9W` zzVu73{=4O`_pM;p2#?xstvylAOZoi_={Y`U(@%9v!YK$|va3{hY<;dttbul6TeJK} zc3b+|ihU+CPLpEhNA%lx$H)dD*%^*g&U*EL}IV zpBD67ko7os{~m)Ew6D>1_3RxxG2b3k##!p}GeB8zz};jk+qNA>s(@umTL;D4R_Nyn zH4AJ7bq1{BlSXE;o)LquS-6*&IJXhrHShYp#fXXeJbnZi7(K`1CJzQS+sc_k@6|B$qe;q7-YrOMOAneG$s+a| z$X^)m($0;HePFWv6A(AmG?Cz-q||3;{czFm6+BhR=J_a<9}2Gk*{y&(gTWx0)m=s< z;UI)6EGJeIqG*pe?I50K3*vyD6W0v|!a9L-CSDD`>t*@JpfFWG9o3K66pxkT z?cUT6{8wAKRGC|U?#&qO{|-P`Mbf|7R2T=3%yc+-wCxvEfA9^*QeCfi?x#VKi2Rp0z1-?=I31AK z>QBsrd%lzU^(J0vd-9~YCL#WOvp9i+<^}g;3?vM?%s(fRpObryVX7`6ig!h}*kh1v z{8H;HgOp_#U1-Gx}6u~JoaJe&QO%Oz>31K zMpbOm{!ikwq~_X8rCT%x^o+*BLBl={2ekh}wqN{dCeV z)M3+0z>;QUCkOB4QApu|3A63DXA%=3|HwRe{{&#s8fK=P4YOhx?r~O*6ubDVwxpq1z}IBP{>#~(ZU$s6I&*3E8NPGhdlabpu^5L83scb$KJ_XLply8P^rpJmBf!{qgWaKjP4KYTChvB(xX&?s6e&P!My5;2m*WBNF>bZ zk5v6&Fp^Mks;v1Bku>eRX+OSok z`Zxcaghw`its3l4DRWz&U-=hEJlgJWNIdZx7@`3L3+H^GO(9#aRQ}x4PpkPn5XZ$X z1VBV-FhX1TVj9Bu$SrUZr%Wa-L9@V_vQ#1-Bv%MpW##huv=!z864 zQHyDcV7f02u~nmsRJKZ_kLZE4voOtv40rNx34Xs#z^>2K7?_TJ z-l_^}PsI42LEUXV#T@=NC&&YL1XCpjLKC{FPN(60qPiwV90>q z407){{gN4x9*ek=|Qi1nB)uC+&nUSklvnAzK?SoFb5H1{rS3K09YS z7-^0FoT#{O&z6BQ{bldbXKf6Ir!KAh+TYIw&xHDRf{b_}j|b4zs!!v)9o;c^0&I5< zdiGZiBIWbDAOL7j`KmZ^qKV$h4ESj3OlcMg-33xd7%*ugoj-M{cQ>-ED zc>o4yKO4IF#)lZX>v(26r5;-VmQFh&iutJc1Cd$iwGLp}z1!40e|tM^IYPewPU3U~ zytDsvuP6Yf|A#;J~qZgu43B!2J0M!CNin|xDp zWl^e_?1~4Rq=37YMhxmwq)deE@JWceKm6_Cb&MeyG_Pu2-fX0S2jxdmn81qzQ%6i5YT=BKocNJ+* zGpp-&7_r<-Hcn`2PW+xCG&xHx(?9Rm209K*>2S~_>~DJd zu@}4%Ri%3p8P%#l=eVtRfOh`ao_3z!{oxnQI~86(-QU^&B=y1hJalXe_)i-A|4KUl z-`5N^{}=r6pR#-x0gc(;wE3|?xY((Gm*#8#Pg*BSA2`VDF7i7rcY1&H;M@5@Vf}%Q zz4YB(%IZ3Vi=B4) z26(xL<^!lvSNb=wZ`zE9&~4D&sM-sR^n&{r(Liz}ts|gqaszO5hTBZs_H3t_RU?Ut z)Z*F;?9vR~>lqx&BLlz|jo_I96OyPQzL(m&P#N?$-}U9X>HOhkf-n7<)zthbhy%nG zB54)wz1PSpkbOkUkPZml$Vnk2Z>}q@(c>(taE*Rjew9bX9Rax*yD%Iyn*k&_oGH1n z3kpf&(S^=(VZOH3Y}vy5lzo zBA8pr?d*~!4BtZ72?3d08`f7QBgP<&NWp$-`)HfY`faCZ{1~~*Y>8`k%ckJf%1Hm3%*~f<#^!#p z@)ODNsf)M+pG4{&;jEJXJ&tDNemr^d4i~#K-4N>pzqBnEnL+Rk_;{v+CwTJG_Py4z zSejc8$&KoMVZ@AQQ8(>jEp@ar$xUS0o#@1`tYj>5=x8cq&2wIg6FZ=P2Ms}+%TmU(^?rZR8IbMgG%-*Y=rkkTAITC2(oVx&G} zeJ1ewcmA)d%Y``mDfZ7^b-})Dh7bu#OLm@lnL7lt(VTmSp9DYZM=YlwXxpZfd+5F~ z4U$xnio)G-kLQ(gnx+eNkJ`;MckE(!?u*+@(m$Ys_jl`M6`KmGbg~+0c>o*Ej!A!E&vwV|571xu7gcy9!PIGI{D4xS zRBE4s>6K4(*_eTpWDQ4-&Tt%Zmu&D{p!?fcg%*O%8m6ju=df9X zS!swcpRZ2NWy}fzg_vsQm*7nqPc%sL)mU#n7|C)pb5E?kbOnNj$a26z>};cYP~IMG za|P}4U8t6Mh2ZT(y;fVazye7)wP1>E!~{pur-Kasn2fYQlKl{O5=4%-scDDy98hqs zvyydG#hAaZG)u|KM_Af{+Fr$uIDNLcWlwV+Y$H2apxdj4<1j^yyX2M`qYzxIc!8{I=C~4Y$4rg@J2S)L6-ULtQDbHqBfOXU{s{I-@37s+Q~F!L z4h~wqDt0?t-!&&PRZ^P;?6J~Rs!r>plT!&l?A*T4CaSriR)4;sKYCzZX#jT4@2~F) zye?vsurVxBZnc>bue!S!-(r&9R5bHAzeB#KW*Lx^>+QtkwtOkN;_QyJ8M>$OU1z9s ztBMHdaq!LH4FB2+ee7+Ur4)yx#5ODK1UHhwm*)AOxi8msq-}vr?ww{Dz5#P`Rt3Yo zV0GPtIS#-d%a(rqbYy(?xBHAVC|g(R@Kf6fpBxjUSTr1j$GX__v#Ba4KJ^$_A!&Tn z?}KY23ep$s+p;C~a4cSEWF;R;#o0{Zh2TxuBQYx}Pm)~IX^tWa6A(&?Z_rs=@r=|(#dtLZPQ zZkM|C_V8RB?RIx#mKBT5SDMTTf2UsiwGYQRo$S*UP!;-ckV8iK#e4ja^m9@MWg(Zn zd-Fs-DEvHr3$&yUxI5J>6AFaPDCr<2UP##P!H5HZB>mK$Bs~Kwk+LZwP8^y}e1nTk zQNb^iP!L=^G~d4fzjWjh7{cm3!Oy1c-jgIM#E*c&@4a-&IAQ92Yi1dO%%yt08cBQ= zNuDxTIFC>2H%Wc3lM+~$N(z@er$-sDilt@p>DSAKPq78>WYmaFt;|Yc#$&zAjs3Dm z9cY!>7@rHL@%_3Bi~fzV>UC?ifGwVzc}UdT(H%TA<7JNO$(*)H-VkI5xm(3fA>%1P zAj7mL+{u3Lu! z=Do*;-fIDZ>i4;}?)D^H%%)zfZ&3DT@{P20CXL>gT2K<={r!n4J#lr(w|{|4zi%~@ zG*kAH2+kE%wwTm-&ygh9hR{}m0pwwKuUyjCsLA_*|UC!C(i?gK@BttA3r>bKo#`>O8^#n0`-%X0IB*WjGu~oicOOZ!a+H4>%-Kf@ zCki%BqJgi-07Ok0?C>> z<}a}h6!BGgyV6YeY4=1m*ag#9U8ccxnZp$v*Ub)zV8&TN z@CLsiV({9-o^DNYJ@i7rhR9li};^(0AS|1}KWh6O`PO9mv>}5L>&O%yEo!gcanzjCZ#(7?1t=ja9XJ({%X$7v}FT zvT*<{b6***-sP-QrEbR#I58Sy`YOWs_%Z3g#DON@3!BUzs7gwSmT`Ll8=i69cYeX~ zSm&x1^kQ&;YMY?+IUt?!@(Kck-tYJ4R7drgEUIyZAJ}b-EAKFFpO)xV*dI%ig2AlW znFm^c316y{C76M3iTy5f5Vx;M`!Mcwj2=+Yh6XzS0TRmSwpZVEW-n<&tuN*L}}b<2DC??6Z1_pyQ@70aVv zVMn}<0f4C=GxN$A$fIFg;_6z*uEO?-o@9_t#uf8QpC6J782i0fx-9VhKiE#%APFzh z0iMga?$1RcK&Cv`+eG>p--N}u!o&Y5D}L*TF(^Y;Qv?HK%G6Ey5O(9I-$kR)B%$6BS!!`l#3Y z%*DvQ>o9XrCnpNaMGR8M#Ez^q+F9I;s2#nyP8eM0C=UuEUzvVKB8JKnx0HO!JujU{ z`3CKHu^4#=?yR8$C}*BE@Nc__$V0^Fanv#^``kB!a3%Y*Asyzh$DJV<@P@Z?4bAsi zt4mD?+2{JFGM(iTCs1A!bs-i_&i8zn*!!c#;DP@C;I*> zL~IlBziRu|uq3nZ?V6^Xrqs&Riqwv3r7||=lA)%_OsXCdp&EfXWi@G z`w%ittCY$%wbgi&XON(|&E0i?)&4$hunBUEZFCBMxZm_?Fk+Lsi?tph0nMcV6@`Dp zhRfJZ@>|!~I~}9vC=s(e&(w4RH){L(3iJZ=e>n#nXq?&$nBHQx`TT{q61ud$FOgLy z&kjCIq4u&&}?qIVMUrqD+j&bmZ zI?!(GL++2pF6-%H$5kswe@|>BsViE~E2eU!52S|8FH%6zHP5T=QO<+FtSyoqKjoRJ z|9;F|O>N~`jHBJg9l7S+3F9rRj}AwJbX7%Z^5ajd9H-i;f+7T;$GFAB-T$Y3mXFeD zMPJQXZ2GnojKqrQFcIsLnNJDEAD!DTwjRO6cU7h$7 zC)ABTZMk38Lk$2kV(gB-gI>bKWY~lPOopa5G}vrf;@8JV$-TI z?<8}diHQy)oo9JhcC_V4A|;4b`}mNm3;scRdfo^}=)%cq-Z5Eg(oJRl;g8w+dk zuXr%DHhR``l2Jj3m$WeJTJ;#T*}2>b|G}EWi1*wQ-lD-dGdj`%kPk`_J#YfEMrvl2 zk;TJmUvWQ2^6ZW_fY2D?Qc9eiYp{2Oca7YvUtcbIxcUl^WzMClazCb_D+nr$ zViP48ml1we6zb;^7!l@lZ-}G_S*Mc$hqF zz}qJGXttH7aEsf{-W43ky*G~M^-(^T%Ez&t$6-5GM0)dUa8Iqr-N!hGu3^<|K&=eO2i`332EiE!&x<%r6F=Y#>_MY%piF0ZJkUg~CQnRBJUbb3?a}zQN zAor6SPfnGYv-_pVwd&X-?tZr>w?8G*nw^idO7r)7p5T+;Q%}nQBD+$n7sSV%bOaEjfxTC_RhYa4w#B9rlPETvm#rM zT(!=g0Nnx0!ZRfE&9>(47tFMVSfh)S^JXKFR=1UKVKAYdBWRZxgfMB&66o5aCsmzP zrg*g)QODv(KSa6y$aR{>0s^snQ1T~lke^JK82him8u5i!mua`BCDSM_A2tDVRj_?y zg{Ey&U)D$j*tk?fCnscQb9m3PE0~30*5zsO`~pKN6-y6VPnHdb)z#Hj!l5*;;WDH1 zYN-_xO<5A*dX&#VxiQeKc?>R`x}IqVPDBv6)dRY63r*o(UC}~^drM4^PL~?eOK4hB z=hp~Il$|6qCt1PmQ23JGQe`cOoviL#DLu~;*RuwwuhB>6u#1*soKCBi ztNvoifYTEUa zD}OcN-d2NXxlMd?sNT@AmgScQVUY_7{%=z}uA@8qcu(f6bhecGe)y3GD0l<*W(rMt zTq7fmUbt*D)ob!@`fD&fsan@P2eb+wYyQmr#Yp2Mfz5LEG#PaD(GP&7z^RL}U4(}v zj$D&a251fsTT)Vg-43eD&_1sBTbEc2f58d(zexmiC!?h8I(Azx1nL@NHXGTkkoq+Op^z+jLIkd zuO8sOEh4+!)@c@h=u<%?NdRQxk$RW6;%<(92zKBy;$Z&ihN%7XjioDszF+8cq^1Zl zi%LyN@zke8bj`Zm?8%t#X0qZici-a9jj+0BmrT{0 z&(M4UMb95wi}G1Nif*SwYt}Ph`-!W5JyZKKx5_4ickay){f9a_kVbdscFlpk{|?or zcc$4<7TY=n`}#SE12@t1FKH?Sj`Qy{*N z&q&hKtFHh4rZ-||+%lTZd^9uGKHcWpw-m4Dy7oCpkYrRk;Gt%N9LLgD|olKrkGzeiEOhy(EM_E?qB4 zMfT7Wu<%Glbe!2KS@F4qhGj3*Iy@CV;y7(YUcs}gPG5!Q%Y)!AbT;w`7jsT`TJD*% zvBkYUu;J8;Qk_DecRmzO79)_6b7o{XUwrkQ$j()il%9&NAn6YkVd@%DuLCFsIy~HDdD}-o^8`!x!;dY`wetjl6QGt_j&2@i;&|`8b zGH4L;Cd5x)U}orM<)S#NwcG)l&yV$y@{$T#I zj1~!sd|;2#QLM7teebldU0v1UBn(n>Hsq~4+Q{y>+7EPH+=zpTY@^kr^ zcLVs4*zV}@-)L|+u~3^l!)4#*%WKb7$P*C`E7QnyB$1f3rZ+IK4#>V-<7IK(+C5wL zabh*VKR3+x{I?27!yJvK$uj*hM%`xGv)ely5v|LM22j5! zg)LlIbpq&i*31$Y(q1*RF( zY5Eh@AL%t~`J&1Iw9UBLR`dsENQx*v2Ygr|wj};U4@U142KWBow!*t2JX=s)qx)3c zofg68NklZA9nje5;gA`K9VyD1UPg0PSTHO(ZE&D~W&wtGiaVlklpr#gBWJjFHcCer zhkdp3Dp_5T+XPYm73DuS(^#B*jKIG$TtrSpOoa=gqC?qG5mLiP-7Bg}=aQxU=?47T z{w<7F-qgWY=g4o-embX^8?RhDLEYk?S^~l|IK7Zw!Y!w#XfqJ7Zf#Nc64X(Hn3j2j z5{DB))?bFE3dj}YI>bo`ognM^Q^~4sD1pwM*$_-B4`EcyO~SH{nlStDgao$Fd%?kl zLBI_T%tX&It8&I9T(3 zeD2Gk5GF01q$yua8*7CA0|F z8YOGo{5DIEPV(x3z`DC<#O3ergg3QR33bxk7W-xJClc!e>>J>xiv{`A@!DyE(mkYT ziSCt54V2HeXWENsU%f-L1-O}8Q_H5;e_B|;92qb(>|Q(2ofbxz!)vBkP+4n%(?;8OUt^dx1DU4KHs>Fbqx9R zxw;nPKOMIQT~bC6c);7mSASLLAzGW)s$EsLe@N9`U*%~`z;F|%u|J#>#m+(}9EUf^ z%HW}NYnr$M$#4I*GY@AW4H_-H(wq~JsQYjfIN-TM;94rCBg3lZG}-#VnAtQtHOStxKP zgFDNMEma&v5iS7c>b?MY5h3b%7}AJ;nFM$%tQbrMY<@b+A6bky$%t`(i0*ZA$m`Gu?nYK2^%Uk#&oriDgy{sNC zvK0qYrdxQjSxOMHbZYv=%~+O`rOJW?!V?9E?60wU)W_HVXjftTM6Mr5L#32MR)%{z zlHt1_lfA!(5li>+@ykU^!6t(YE8;^%Zw6}lrZRJ_Ece5BoiR~(Ruw}!(K3xfR1(39 z0{85*aB+tJn<;(GIerZlYA<*_%ab69b^<$iNpGQ&={Cfng)%m1PBBP{RHiD>^i9mS z8*UZ?=ojaeIb-O(Fts3=OT4g;iwnAPC3NYEqB$woGi}{}ua}~Rt+@EcXW)x`BakPj zqP2c((JNkcJdr;&`ZMqlwIue^U4xpLqt<~{=|**dneVjIC^l1Vf(+wDeips39}?La z?LT^Mfvzb#!Ox`kBH13);L71Vnoo5pK#x5i#Vi0god?|(6zuS>*?v|D8i8c4{};BZ-3TJ>~3jReu3!{Uvja0sIUvQ?YU zM!m!WLl%2x$};EChj49tp|5Lb&Ml^H0IvWuGrHInDq=LAWOVe7^YoI_oX?bbn5l&; zfA=b4Ld{{=Lin>c%b9C#pmw`@KHg_AoL9v1&7kSvaofiaqDA)Ql5lL&s zsTmmMeDNmMKXMMAcM)6e(K}icqjoc8+K@qMGBxt0ZWr`@u?U2Z5@KuTv(Y7^qFZ28 zmPbCSuTMJL++NDZyz}3qsBt@o(G1rTK*%}craJ|I`BOmQG6OXu$R#J!h8D!dR&#u* z04Cd8IQu$~S0!MD&hTmAGOIxj+ixk@D1hOs`nm&Q1$#agmAAQ$5wjj1Tnt5KhexQHydQQF8+&~lJJ%cpMC@O_eH6s4VB_>sAwD1^6X6<` z_7sl_9+h)Mm>AzEK_udo-t=k=qBb@_=Fk+L$fZOxuQt@=R~=7Jh54CSXiee)f;~D9 zX~2>G0FiBEtXAM)nQ;XSs*g-JQ|m+}ckHIddgloIv>Q;)5>5IkckIr=eS@=vh!~wF z_#V;*KC3P*^8B6#Q)kKbwS8F-dpdA-HNKf=^UrKI<>*262w?KsLE70<#L^wyOV!gB zr-Ym{^RZ3gdtf^p^t$Noj~zTXii69H|C8kCNwj@4f5)fHIMo$A>-LmW<-HuG;n#xN z&mhTSdle`q}OA=?lDfk?fk*p+Px0n zgxg(+x*s=vgTE(_JZU;9TKVJ15ai3u-r;-o!qG@v7e7PySLAuXnny_b8w5dcC_mVc zf3KDh;Pv;4>_QB=tNCE$G4pMozc_-4s|Pom@AT`u_jAbit>;jagkDxd$~w_k)SEB_@dWpU8A-k!ycjwOu!!~xZm|3A5R zl4I!lnaO@Ql~ss2@J%}GVXOSO%EzoRP7bmj;fIy0E;duJZ5lv!knX3&T0ZKV)JIjq zOjP4}#RA1l`;g^kC6KB-w&E*OSSDd>95bia*KyZFczyzhp?|;0KZo8a1ETR+GDo$4 zTtlyuEuBxDc#`Ey}I#g!1=5dCc9KX7dfaAKYx40rh>LMp}z|djm;7=-zBul2cZD>VTNhN2h|Y+b0u1G zWpKeV$D*MeE;6y&^w%Kj&b4pt{s~wReK}o3tik2>f5UQij>^p5T}ubx<7_bb$1TQ| z_kJRT#qe5~#`}Nz*5Vi4ftdg5nc2VZ9TYJ7{J8l*1oHhULz9NX)WY|8pTBIb+_itt z`VMdepcf;}sYFXw>#~3Q{2ui=p`jQGI7%Ma<-T2QWm{s*5a+n%v7DoK*xx6kt~#pT zG3b3RY(g=YntJj0H+Aw&8b*dj?l{cGtYc^Ik$h8x7v%+7Y63>!;zmHa>wrbh`#H$Di*EcZ@~XdQbk?+ z3y}5|(i=Iq^II<*GFY^N-t1lWTu(*Gty*`HuBUxR;lwZ7q>nO>eukhQ2pJO_Dx0C^ Ve;%rMd-7{D2Rj$rvP-w({s$2~V;%qi literal 0 HcmV?d00001 diff --git a/docs/SoC-Skoda-settings-2.PNG b/docs/SoC-Skoda-settings-2.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6805cea3324fe6c41304ea828f24564c430ea99e GIT binary patch literal 25032 zcmbrm2UybW|2AyfGPUkXtu!;+4zyAe2dSAYGaKecqjI8|xJAlYIm=RW;Yu_2K%CH> zDVYN|Dk?c~0VxU!0xwwi{r~-s=Y5~|868Io-|sa(*ZN%N=RB{=SGR8&iS9bIOGrpa z^xDP&l?!j@KiS5}Dd7LwYI5~i~X+5YNP`AfpvFVv>D8klg z_+`Uh!oskE_POTmkS|}=Hzn+mC~qj7Qj>zTJ&*QT+|p`iF@1eABnr9^_CD&#h4OEX zTlIyn$scd08!&I0FttI>{bI-zQoP?eEO}Bx_H(U&?O8gfQQ?Bk2fwA%_`&-my6OED zaq9-g>$+zv$#t-`*IJ0NEUf@)y_FWcIn7#gii>6+c~)Z01MjP2{P2<-Y6zVAT<1Ic zY>+W~-eF{J$O-+-xi9Tv&3HnBN%h2yR_egw37_SG#qKxRIDW&0gubM9yZBo4v<}(! zua@^Gs$mN`C$_{XUOKm_W&3gYxWY^4wzP;9-pq`Abl|PURqwq+2{+{{>+JO+#I$mt z3JVuewbOX{_|m}foKx|eH%gQ+FvtHX4y`XHb0u{6%a8rFZHAq4uc9jvW8)u}q#Zia z>ExX+BI3RoeXrTHVMd_H{Ay`&q8g@f<#D^LzHrOtcWUQPEo1`@J_^ii2a`srha^EFCD^ZD%$OoZ0v$l(bdvx3N;YgT*!;sYq>=+4x|m zBFvx3{C{8U_P{niJpcRXpKKv|of};zv^^wa%f`Fi+yBRcJf&a@&(ICjh!K~7foJ%! zrRoTpa4c!OO7=Q&*n9`ArfHQx4D~LXw2l+g)ITC8Hi&N91<4WxbNl%HXDdE`;Vi6zHd=l6X z4y8kSF^A8Cje*Ub`9a!v5Z^@eM_Xm)?nUYKhPi7Z?<#E-75&@b zv5W}wszFlL%8~fKgf0_^D`YpYFyLlE`_7GqsYq_$2XhS{RNO8MJV`)Z8lB0=Hd;OU zpI|~_J}6$- z=x~+TvQ;LYI2mx~QXwv0L{azCKUE|wq$w3G-JjckC~jyHLDzWj@JitG%2H^)_{oF7@a+B;qOlW0X= zUvS;2a-lB)mC@(v&{3iqoCftv=K;DC2 zhv&zhS17eu*>_9{F7hEU>Ek3)dL%rXWU7V2pm@5yw1So(#87(395#h-%a^BZ zKAl~koyv+tR&$D;WoF8jFlb_u<4rFb5LcAyKQ~?PIcB}`(f-Z3x{PdKuG3x@KwEd% zXCT^g_&%o+cVTwLvA>rKB$2sfi&uI>waQvVE%zD&YP#_Py)uB63SLdU=(tO}b3KA9H?f zL=g`mfs6^ld`qR??%gJ{$gTZpBI#!vyDh#i{L_$=t*cMiW^dxW)%SVbA+`pfJd4xn zS-|NDv#$A`!u7vUL3Yih_ZYyiQsWFu))T1942?BIpX4ZzN08Y=N1(0uBFA^)_Ua@I z-!*17T5py4Fq5uvVU%uxZNvrF!_v<|;p%8L9`!Oi4}Klhro^{HW&gxZ;M;A_YfAfh zJJcU4-a9v9f4Rc>jXkrhj*`$6V|j6q-d%~H2M&^{K1H9oCcZj{)KKRe*FIdHfM+*% zKjmhqb7MGGA2@H;Ueb&a>CZ^60e&5U$}r>)wpX{u%|MgKmJ$;h7iLa3(IvVd&b8wH z?eS0AZp=#&rw0b|E(v_n^UP#gMs}HFNy@d(H`=y61zNzWJ4a2<^!>{#0{w6lsJ1goTb#+;Fz26%r~zj&L3#FSW0xXa7|gzy*2FJ zs6P|FO{Sr^4_IuJlb?35!z7XliQ=M^?@<;Xke$-w1Cfsk_wh#$V%r58$ur$1qce1# zMtC&pPSm7u+}duqAc0g3_|4W~!Tx5T*ZIcD@^ZrR<||6)l80t&gIJ!E7(|h!peTRJdp5!ioiPmba1xl$as!kwQTZ#_I z6mfoP^MLh!6QCG7Rnto*rMam1KEz5V_*KhH(7_j%{yO%K?$-8T0zT63;Az!p{gExn zw3_g1cM-sy-K~K;WNdnm5o}A)`PCcVs>qnPG6b-ts?04L+Z&f7H>!|vK*|mUF>9Bw(pPVqfOCMLUxcJ`9!|Dgl z%eSi%p=^88STG;a=PTA`&yD#7_p^`5L=tqdnsA+=3yN8XgSQ2YrKh+`$H!#ixuP76 z=|JEdk<4-Slki1PkdZsW5^|nRKCE!79V>H<*wd5MYXO4ZZ+wTqh za50#|nxvR{4tNVZtDQ%?edmj{90_x?YW`QgcauIkbLlt>OJ(sa=Xn)6^E{W0?f*;E z9@x?TWi!ql9PZpk%7M#UWmG=l*Ifc137RFOLjJ#n1^?m;J_@PkBgc(uR8-8it0;IL zfSHF#cK?PXe5j&^Ej<_P)Nb6Fx#QC*y$;chq~UrcYhDs|kGJ&+p z$e87JPKED~k$c}o`9ZwLN2)(NQgw`e!?Q0}uWmnT@%+)|F?mJcOw!4Pf!+-HK#d5u z#NVjxxkudYqoT);E0}-JFc|8UwR*GF!HaU?50aCY6Bzh*=T-F#5T^XLo{{7Etujjz z?|U3NYVQ`f{29im&__aVw_QDXbnuHJCJ(~zZKv~rpdEk8n0UKgrk@EjAJk8&5eDj~ zDCC*#{EaH5cD4u|7ZJ|<2Y{NJhlm4njQ^m}X1#MlGS_z>o4HCn{u^`JNq1++i=Oyr z@_qe(gQ5`20Gf|R!;F@k`I+yz#ar-A)oAJ`ng(`h z&9AS@rn$;%s7$GD+NY+D*^h>j%!c=!2ho(#YQye>UNt6Qq~B5I%R`SG5@j6By})f}9zvLfd8`XbjnXElpgNdQvps#k`;_9*s( zJr6|JBnSQ6$6Me!)IyoqYT4CxS0cF>7wf*NWQfpeYZi1sg z7&&Z-jtrDVxD|ls4>W%~2uZ!HD_h$h=vD|vJso!8Qj=>&6j(%E2OXGgO6|;$dhK)& zXP^JVIC!Ll*2A78RC=Y}J=4+9F~38|Tyd*RkF(`3$ym}rDFhM{!$+ot_M4Ib=hUZ@ zhgS`m$JL(}Yjow}{aiYqZmCR1BoPA7UkKax*wK>Mugy%;!-c{eGi8~ZUVFglviq>*@;+E)U6~&4O1N}z#zeFH%+Z-@xi&H(FbGX89sT$g z^hAfy2AnCC{Ia%rNwki>&AGnlaaXOW9`5EOB>6Fx$V%Q}NO^Hn7&>~CeJIjvkeiI> zwAPY)i7zu6cN}6!P1MNoXHc7%9P$1jx4~{7K+DYxya!zTs@wIo z@N|Ogr;fXfhHQ6keP)r0Ti>nT1cw8YEf2o~Qp;6YJatt3HI_t&OMAe_iw`ck5jl%B zB7M9Q2U?EZV!e-Eqn4WV9MiB6@(z=3C)|eF;2v2Y{17q2GH3TSlO|ZJ@Ny5GJ;f$! z&Ay}OrfTTk1<*AeYZ|yAx*Cv)xE9c|5*bz#N!AfNT|2Fzc?-&9z9>U}FL7FDTOtZa zvHfflo9wE9TvT*Iz(r^Uhj}J`n5;TKnm@FgfCzwfd@hPk2f+hTSk>ub^2|GA{O4E< zXi(JyyF}@N#E;@I;B&eVE11nVWkqv0)}#q?jaNVq<=nj@^i#5FsbdEu#os4vU0NwT zx(SB)DIRO__qE%KmKBs23l7Rv*}9XshB4ZbCysW!^Q4U{nf-usO=8*R@4u<@X*A$Y zJ)A?^mrKp>qdm}G=`omBSqOWi3h1#`zh=Cu*EX5Qyl7|{YnY51iru#2@_twz$tJIp zPtSZJ8EuslQV~qdT_Qmh=KW2NeQ!#BGVGF(IC8}bqhcl!Uw^w;vdAyx{$6lZ7p$W) zjxuL;Qt=)Pob4U>cOe_8eC~3W+v%l9h$Swm$bRdavB8=A&w`w(>! zeh`hxx>R>H$uxQaD-j^&rL~h3&TZI5?uu>lOi#~t68nK%ftyM93|?8Rz(^dEH6g+s zRXV`eO8kbI&$!(I$J@x^c<>Om6hV|e4zFBQ|ExO+zXRP5wTwlzquh;<4?6CyOY@Wm zu~wOsz)=I<(%L6f0l73{NNeAtz1Hig%HCFpTM|F~zGs5Yc1yq}yD>RrdQ^tIZmd$M zGKz6W?)Yty$#rwoCymBeEt4VvPK`;DvmwjpKlIqb6&U%!!Dpq;5I^X3nlcR0F0Vsk z0*4n>Q2`&-!^eRI+;@bE)X0ajGOoQtn}igB!7)zx$EuWV_w??1;8hR!){|9t(qg$+ zDlG7%t~(OGtJZxGZ>lH7A;`VqM;GrEO$M$VZEJQtO=2xRo=ES0xp2*=A0DZ00DICEWxyCs&bA zj*JF@8A)I|@Br>TCFd=drA~dO@fi1xGBe*z=H4I!)6lxmBu{8N)L-^cpp^v?dWZ_B zpnP&atb<@Ap;ia<8~v`=nj9ouxo~S9Gg}*b-mK1^R|RdMT~ecoGTpn)04-Srz|qEs zWl*c!Gc(^ME1XtKU+6{H4p*I)#9bdkR*Gx7l%;Rowyzs%PM^cS4;6>Ao=_*qH>15I z^de5jyqc~oLO6l$9iH`iIeoEqT5r@%(;brrL$*-Gg}$uI&m@^a_NGeqIsI(|DEBh8 zl!j0f7q>YzZXSvh89u5!S_Ee;D_MM!h>WuYXJu)+Yow=S4Y-{iXmGrBnAKml`jTb& z8b4C~sRuq+C;W>Y<|3*4S66S9s(Y6&%NobR`?t+jS)XIVi(_kI6WjrkHB*{B-s4;p z9$x2qFoKwsKF-AVGM{$e%AaKd9f2J#SfAw; zK;QyXT;1;pjibJJ5__PcdfnlJWes1SgNH?+=MB0leTd@Yn7<(x+Z>hy*gkW zLy>P6qB=o&#J5dP%k1Foryjkq1Xo+)^12OcYX;PVV_JBz@UpAQVTK6$S97M4WvIzD z^j`ESxI(jJm=K)RuOjP|q*9P!(1JVa`JsRnhxe)mIK$5yL`BWTjwB}R$-Y>%AkN1P zd@2c7IV3S`x)&*#Jr%5CdZ5d2Q(@J6sEhvczSj$SCVOKdm6I zr!Tn6%iNu>cPMPSvgTWP`*ZU_1y--m-k;3797WaxtM3<3lF@m1uR>m7Nvsa{5tVH% z>xs;QA4IC)KUD2w0%{X4fI!BPTBhzA{SMSxW(zHv<>kEKlTln!NoFg@u0fXQ;fs^x z?;TFN$hW4CCL`0K=FC(?8}N!0meA#4Ozd&l#~ewmtJLWuh<*UgSLa~6)ITz$_kYrD z#V#z|ncq|fKH4l|hkcn4mPL{P zT+QC*SX14`dwW$!I4Li~V3880o=l8nJ=wNx-;az!^uH)!wtcjBC@Q za|l>*?$gZJW6w_$@puY(AVTKar>+Z$pFy+mz3-U0Jk3<}Pg%eb`57SrmzQKl4Awe3 zPCr pq>8Ycb}&<|vna5>lq5lw$h}NO&=X%9wBCyc;MN9w2(>eY+txqLN2@X#=OTY>J_`V1mSmc zX!Hsme9T>bSY!<|3-3Df0X%&Fy z#H)^;Ja$^hl3A=SUC7_$WQrvbN_8-dC$V{ss}^wzWG@m2GYq0TS5FG^x;P|ueN zJ@eSdCl>029UWVlHlR@z_pXOI1pc%f9;A&A_gOQlb(`>}h<430F$(Ov*t|K8OHxU> z=)`a|)0xN^FUxT+qPy7URm$AxVpKt?xsRkZ9*Zz%2EsL8PM=8&Sx(Z0niC4fbT9B@ z{*J^O{5>o{yI8-dK{Oo{f~8{cPP~v9^|zWg5P()(VL-We@#QW?|HA5WPA?OZ8@lK9c})$>P&yhR+2qOQ1b*ErG%;3iKo zxCQ8^;}$r*RTr*a&4IKM*@R~vH0F)zN)u8We0{YiL;geTBjn6bKWwXT6X#v5(ucZA zK#Np}V^ASif)cKq#8DbL$)A#Af+^V(EVrLrdr^te7i(x(W?GFibS^{Odp8%X`MZ;u zvkO)yEidh78*x@R1W!}IXFAtG-`?%O;B2b;`f&ZvzjQimbaOk9=>@c7U#h)p)^$ID z4N&eL9nSnk&v&JZn@gk+n*HMr6WSEdM~X|%ocp-<6F2dsPLK{7#l~6M^%IS!;Q2%C zh=_9BTTf;(so&dmDhS0c982{V>*i0^Efc!)uGwQpI@tNdbLka#qM0>UON@t^t1QVV zhPoR!YVjjmV}uALXnz2^Ab935yf+C~Q;$E#aK33n9A|4G@hs{05Z7u@?SaTEkiQ43 zlq{|LB?_A!R$B*7Q+kOdy%Mvck>jsIVmc)i0$)l@9fWwe?vQM%KOQ+B_+nJ|)?TGa zL)HFI4X|76<)VSSyvtvC1s$N{va$wb80k0L7E*sq-^G<{Ite)0-#T4E;;q z5wvy1gh~g5fw}khM`Kh>atv!~uokNYl;*4~YvHYr%Z42`InXA&mX$0&t->oXYv}k0 zNa42I_oGBK-R~{)eLz?2C*N2B3QjXokk~g?4P~Z8ija`yl*xLyq?CI6A1v>03cwIlZLkm41`(xBSOjE z^g}L6dY5vm_9#{Ekon-Ks%_Z$X6N5}%JWDNK1a_;*{#5iOX50@(NK!-TDSSMq&MDE zno-%j$y3nd7nK+2;aC@py5N5~CAe){=UmW^M-D?z_& z(&u|<$wETYvU)otZdj&f?@+iXi1DA>ADj7-dX^jeWEpXl%_Vg%mUG3cR}kI;4#Dn| zFi-_I=dN*TawY-u5g8{?S-vFw9||%5GA{o_&;K(;2NI`;3yVzrlT<46!c1J0-EBSG zkNo?8g7Yi-0&Zfv3`{#gbZNGaKK)y!dTt+w!afoT*$ou15Vw$;^S6qMYaDJB*6JuY zv|SUlSxBg{)M2WSO?`DA`I|HNxCDrfQH(RPu8uu$(tCM?v)>$~Ke z(9#Kk#yL4tcJ7yO=5H1&$=zP&Pi|1CbU8zuHtom?iU|`2hKd1~aSw8!Yu2|8c}s&W z8S!%YCP(-E?#$_U+s4*p+kS@Ace#U?gyo+sa-<*f&6n}MN;DggbGLs(|9)H%d2zNS zXT#3tO?&$3K6$#9ld#(sNaLw&rzlRfo+%AJXgqi?9I&zt`0Uy=J6C(vmd zwKSg`c=pm*1bEqMVDIS2Uj5iTfqx9)r?O82xNxI|HNWu*HQ++$3nO72{a=}3Ex+k8 z`Y9V+#0uP;7V%tu>mHzd>A6shy4uVh|D(@}FZ>ezdihU5*@RX55!q)#X`<$Xe;-%Z znGs&j_Z6nHuWbd>7?Q@Au zQL^_P{@PNxy`;aW&&Vp{{d#L+ zzlfwx`<;pil{U*zDcM+N%`Tb++hWr$KJKrI=kr?6U%ZViQ2e2#xmmE_o-fwcvJdi9 zB?_w21Z3InccI(Ol2q`&a~jX-{qubP2InRr%Kw$I_kZj2bO&`x&4)}S4~7%R z(9&6B?kLAGQZGV}Y_WrE^p;)M9+;h*b-nipe8!68142Xv>e z#08C%wT`MkO9a0AM%#umAe?)8GMbqSo@POdpNK}%ba*BDDNdG zk>s9OXN>06YK=6aaQorhehdfT^DEt|97io;oh15w~B3_kc&Slj&Ak^Sa#SR%$%He!(CXfC((DLI&#P z_nmw`nDn?)&OZFWGvN(a-S(q8Sd8b3xoyHM(tkakCiytdbiI)1axqtP<`bh#|HYRG z3E=*j1*?lEHGPCH_6GU=%4l|LGxg{0F?$D<#F@v=(g_{+m-D=Jc3ELCr*;9p#RjhJ zWhMgx8+lfk*on!H3RF7aNInINr?ZWRl3hmU3051@} z(Ue~{zFb`mw0GOF-|XeyFi}zVh3zs|eszwZ_uM)@7A-FE*2mNw=T&JVq8OF&QC%4# z{Z;zLSOlh0eixt;v}zj^f{9bI4O&*Hb0(hHbB(w~MXO>ra0nB%JZ2M{g1fnK7{P9s zzcb}TQXYK88UEPj!}UCL``h1!ygd*WpM!i*S!6;wv^nxu*8z_CMCv`-uANyK@=ipM z#LD?2i3jV23rxZgOu~wg`~Zd-S4aDp9gTQq7GwOUs?bmw8oQ75)ZKr+h}!NRkcc>i zQ{;`m-XNo;&~LW!WzrEbB~tvG8dJV3aC&*3$R}6E$(`v4?Lh~=0nQpvcF2I}zwVm) zs=h)$!R2nutsOENn*|H@OZ8sZBlgElhOT^mR$J<-?vwGAS>m!@#KGb=#hKDA(I>I= zdvs*Ndu&y?0tC8GmMw;OC^2xA-w6y^;52vq1@;-Tfr)vk?fvWfTfcw%pO0tEUlPX* zgbyxF~?p=0<*n}bxfdINNEI%wWW=;x==et z@HHB`t2qIjL~s8kn)stwil)bq_VzE$=I)&ytyZH>;Rm5X3^EB))hGGSR1gw6RHQKz z=i^{*eA~wtk!;YkTg$QnXqq`7V4rQ}Jhy>iwwjU_^v1R>v_)keE)B`Q4A6 zf}L<_ltXRe7zK*D^!?uLHpNTs@^gp?j55>@^lH&dJjM8b3gmO!mpn_*!)g^{-O)8hh=y;V7wu_lf zt0l@!RSZ;5FQjtdr=6X~`( zv~(RfzjC1@tvdzR+s~p3dP~P-jsO=*oqwL>n^F3+FwP*o;>3YO$=&l!0<@Q;5n$F) zORhL%<~+C(D(I`yFUk5bB9eeLw9wKm0cKI$m2dMg{Jo{Oj=exyQ!M49a`IecqB5K8 zvR0qHvRQzA+7$BbKPJ3SHjCINXzpLY$+&M?q0&p%#v3Oi4CU2ag-_*F8I$_#`@Wsw zFC_EZa((^yo+WjxCmB$E-T}X{g6+nxiYlNAdn-3o!><#&3nL$%tOus>BrXd6Y)$QxeuOV;%evH1XC;gGq2w%Yc1 zo6MM||5WeU-v}qBxAabHcw8`lFs_6O)`F0MfxxDI3D|ef-%6%LbZ;pzM}VvLUJHp? z)=b_=7ReWbx;OG|kNS^%zm3Y|OGeN4^6TSIxK4phFQKhYZzlbE*$!P7E!G@IuCb$--u^pH;{Vp z7lq9(iyH=<*WoD^$Ggi@=}GCt7ls>ct@6{0FnvlLNH`EvVWYCzWL9*$L(*&Qp*0Az z_X@us)D+196}VBK#dK1v6?(-g3GdMP=GFA*Pf4f4hYz*E?9Wcrz*C6HBL*7NKB$9w z5e;yi!YU4A?nv6~8X+L9a3b<2q=IH5q|To+Qe`!}fag{WA1xINH5k8cTOilLJzOQf zd3U~%>@@Y5Gf0@z_V-9dzezJ%F_5)VXl@O@NUf8eAf65C|4g5Xq(8G7ayvH~D9tp& zT^8YcdjK(Vx}jQ3t5-KD$V>L^eD67vHB>j7DFR$lUAW`<9z8}zT{M9e4K?5EXjo5; z&7NOh3kDwcffY*NjrPBkBILMMXy zkVkFhkS+oHW@u|WFsH9{HZW2jRTM`zd zR-FVDc+AngqtDFU1tQfo!9m53Fb(z5%ZI*^Z^SsIj%r{yRQ$`JktwRR`PgPzSuh1nYu(jRL(MjT>#@qXZBDl!Z3nB>}5 zuMv%M261XufFB{09^^yaWmM6P62z!mHD+<8i~(SrX`ba3vG?1AjI;Wuk2i#~0>4ym zEYEhE&P?>RH)aajeTCFSx$>XmaZq8@@=>JnPL|h4Ufh$@D@wZ18k`Y zD9Fb~JHb~vtKZg7)W73yFYn>vp7ww^#jD+(pD7gPjdQt<>nn?WIrR7Gb^VD|^jF1x z&hvK(RV?Z6!>Dk@WPZS-bSOd+iS@SmYh_{rvdnN^{~VnIvK;bm0Ibx!j&Z71_Amad z>qR2=twp=+Pjl%GI_N18U9l?j`iP6_HSk~kRiEAC+f2(wq+Aqf$ROLRL~$sKcLjdF z*Z>w?SA5roIJ`ATwvPlu=*XTxjIQ&wILMj>z9K4f6g-G^idtb5`E70%>_b;v(?en` zO)bvGP!w|x@4`DS#z_WG`@Np1eT`#mCfOdHWPgigdoQouXnNQw<<71Nc@#BeKJM}I z^GoF;3L!C6zt1wn3yjOq-BX&4y3eTTkk`#%S@7o|#5Y`T3H%DPM4dE3-#9mCWV!-Q zgXBr!ye6A+Wq4wP^wig6v;)5b{Bo1Cp9T^el9t%v>IL(KxIVIOB8L-{Mz{ttOUrh0 zQ#2zjL&^egPOr}g5SdmyqO(G334W!Bwk1k%E;njmc$>`UP8WqA1#qIs?C0AiRqvY~ z`^XL4*RGOW?SBXY1&wcuRvl${lF9kICSZyyWj6v$u|Evq1nsGbC|u=UJtJIF)MD!z zN4Lr280~JKnQ@mbXUGA@b0*}tr0++FPm^!Z!iLl*hKS47Cjt*Q9Xoq|5uR3B#a55R z!PquX!`^|recF9w%|j+oOl?R^m5UjJR4?Y=af#SNJ@eie9Z?V$5J@0lD5LCA#u+!KF{nE~i^#`6-?LG`& z^7={*W`rOrb#B80`-uRv^oI2V8t2q&oILi#&#%N7lBl z{ho=CLPjj!6cGHe!BP2SQr=Km^MDdcwU<>yO)OS5^z1dAykXVPnUZ}#zQJ(PMb4z> z-6MLHRCz_6P@%r@6%26!i6zz00FBDerL%CY>T~qHY1w17y$mLK*2kU9K19fzNOz$n z@*Tkd9b-X%gH)*Lk6%7v4=rG+w@4p|JqS@!<4_;Nv``4NHLp5WhLAt+my`Q={qqPz zJsC}&Xv1EvoU2f~QiNZjIB${qcb{3_+yi>Df35bCn?jBV=2s6c;9A$v)6vp~0^eog z{Vr-`e|bZFQ7#@R$qGOZpi}vw(-*0`ogUC^hBM&=vUP}SA6P_COpmN4tp&}}0@U37KtyVyV>*X1 ztr`8%^LAGbH)QX;JF+ z%y7R$74e#;G zKf|&)Y*3Wut80bZAC1+;+)>3Fi#!iZ9kXpEw%{$+HF2GwiR62@2b{w8_#v~y0Xsq-)5yZGgrA3``!owr*!-J_aps+c1R!R0KdH`G7T;n@>w}~@J zECCZ|0#WmCWvz(dXaLBk4+L-TuIZ#F@OIjI6+5O5nlE##=Mz=&tRttVFVrF`XM)~% z)BReQv?Tc(tt&X#;4?6)@5hfkGh-kr%oh*Q!OdCL+GY} z*%D;^7kmDHVaVU;S`eQJ%Hk(ixv;V0Zy;u=LxQEP%KyPX!7AYJqNFwuZg}|rtoR!t zf-vEC!I>ZRB6_O@CH@8dFS=e)S`ZYR5a8LlUBDx)w#WG)iaX{xzZxO>73T-}_WT2R zb~%L^6T@pF)a4Hq)ZV$|fa!0ai51V91NRC7nd$cQ_Z8GSi{DYf9pYaCKs=K%!9R3K z9sk&B+Uc?U?R7w)Upv%InUlh$|y zOIzvM>IQ)y@b^Xkg=($9$xm9a)t6enGo@Njf5j0IKkb}IT)~KZ3GYJX^=93PzYaaC z_PO`-or>#}vudt_*hN7Nz&C4msu=Jq9k!Yt^Q%~F?O5I(Z)q*eWQ??q+4duH%ls0A z+}_=exNdwFmEZeGZZd92tv)4tmaQom#hT8*jKd zJ#J3}!riDXB5483XZX3=p7)xWwX3=3@SAsEFk|yXFAAt}siS`O7x_#CSfx-h{ts25 z75NuzD?3(k&Bwd&)Z`w)YPgNZ20Gf0w(RBG#xJZRi|X}v*+Q4mpij-(>av_XFU{>JFkiqpx zfxs!PgF3*8e+RgEl(bhhFa+1F9ue)nuhIq{ySG{gP1Uj&ODHk=(dzE2L;JW=1xiAV z>a7_QG%QyXJ(Yk?Iuhuc_z#`<#39-LR|$7oc9_S`Mc0CP?XlGcxP(?5GRV7x-3a0q zvfqPw#>^gf-Zo|~dUP&mmQgoc1zO*S=5>~Rie8a=&{;F02cV$nku~IcRfLa@Em2+c_efZn=&UZiXyig#<$hMvZXhPaq&01c?z5Ah>&SF4 z%sr4!90t@g9}Lq;V6Z4KCL>#6@-&0V?q#%wgeWl=eryRXHCgUTEQWj65X+J1VbAIT z?3gME&({!IbRP$FRgFU~`V3p8(PKbPBj9@4qC@sC{q@Go$IRg&3+7@T*c%x*{aDsN zaB&KP;!KKM_p%ZScQ@0+>F`LBoJb-vhzf8favudSq*I}FWDK?tn(|v1Stvvw(e{|D zWQUj_nh60u9I|)~O_vAf#fYuVyk3nK=M=N4o`6+2mfl9E%3xPpsZcZqN6~^+_M3ql z#ew=SyY@c2_rhQ?=H8fZ{0NHh6H#mLBH5ifCneq}Icl;M^v86sJmeIcMd2`{E$txYK3*T^n+bM=^DPC@0e7cG24Uel zVycmTY(OoEJ3up{7(X1~cZs3-QC`0+#5B-1E=bks;;1ZS$Bsph5%y%}rNFBU+#K-{WgJueFU zDl&L{bd5^tLt-ys zh?IWj@LrvMLTO0MvodfX!LU*5c;L~8Nlu-a%s1RM938i=Wi*+sC`0)Oy|oXvjzoZY zZ@VzuMo?gn>%K2f`NVcWL%{3vB?+;{5BawiPx5Yw zaD4Bcj@xmRr3HM#m))_SpII?{XAF**4d0n}rU#(9>8Q% zm(+eH^7u}8p9!4c&iBFVwnYt?D+gfjU-9XZP~V^R%Pv9BXs^EcSj54bp%{s` zw;RQP=f)sF|EM1|1trzId0EWCZ3m%TS_#FQTmk9!7TV1}6eG~RiqJ;_$ELP$>(nPr zzH^(lAf|W=g(zek;8ep}-stq`;tlkgkH0y4S(Puvq$%6hQFYfTl81R;U;D0b#A{-; zSF(O@m48Ole_qdHW~Q9iZJJLHqT0~BklZ$84SqBL$;=0_Xxy?AoytZUyZsb)Vsxp) zhYdIdpDa7&3|-OdRDD*LNLa_+8D0GuPh9~I&9-1caXMAxZ%%Y6p^36l$tpDOYk-T~ z70GMAivFFx7F%WhzKhqUaaSu4R37U`%BX(<^9GtUrORJ3&thq7-Zdny0d9E5YmlA- ziI_>85#WkW#|D6_IMufoq1?0Ug5r|lJ(BOFYDE!d17QfXYuxe4IxX%pUYF)_%_ZP4 zf3PlPxBJ^$iU0N{7QOzRpP{D&@MX(yt;TJuKtNDA&Ho`JG;yU<@G3-KK((v<@4i|R z9DeBjk-WC~-0yP=PaL{}#RNhG%9$uQLhJS`%;G-3bcNqrG_wy}2dcQx^|4#!>0`|Xs-D)K? z6()eGNg4wA1Q;1-AgD>$I$&xeWI`7n^0pMT@KS$dL>yTF>FjLH)e1_ThXuW6XSXa8 z8npKf`I+Fw=uGl`Lb+KHQ6QS^4h=FD2J9be5(F+M{B#8;L)24>O$|m4&6(Q}Em7`* z?v)ba=x1z+sYq0=le~rWA!&OI>6F(C0DYTG{(Z2y7TJYx8&`a4L4f5qT{RH9)`YjN zpY(PB9OA1u)p-x_bq*$AesZDdn190!-w6KSEGRrwE#RyaFDgvNkzKDfO>JxRSN*T} zYOU};^_|?fiLXsJxJu9K;{{<%q`m<6eoU}284wJph@vQPvWCSEzPM|ax-`dz=Yk69 z*PYGtAAij48xtVAYDmi@D z*hYvYBvksTJEM`6E>@u4FBo1u3q2#>7+X!$b-|Q={#!kg`>pN>?)m(q*YGvvCFy^) zszR|dg3OosSIFQo{G+D)WLbPI=)y+6P5-I${5I-;jL}o}@&A^A6)CVF;C{OemDoh~69f3S$)?p!qS?7S7>{G8Jh5Iz9t#v^T}0soYbXF)mtXI8 zpE!+y@8`6$on5h}Ssr~*QbM~Eu^#X_#-k*~9n9mNV75l#L6P&5#7?*^jka6pXZVJ2 zMXb+gG;doqq;6L#&w)Z-@ULBoNPEM!$&_Cuc6Qq3iuAy#RANOKO}$23eJyzpY=B5; zbS8pwsa*$=hz3FnP*+@6LhI|_SYAC_>UtULfu@M;&Ql-cbs{hTV`h^&`u>pO zbe=c$rNJkzyn%$%YCLjLE0j@ByT1;8%lM9M`!!J>?61MYNs)W zv;r-IKtCoL8LPmZI%XwfNAQEpu}93Bg@WlB2ELa0nErvGg*94Go#V?Z%MF!Y5eq44 zv`ui6Z}=NRDrN%LA<$Sto8kezk%l)=8K)&|nzByYTvSuasn^xR0Xqm)?bhFpv!{u4 z=qzRmZ^-*hY4Wp_aOtmc5vlBy%;vlUpH6^_NB(@OG{NZ6Y>_N#k5ti-K%_si4yvPK z-Aig?*Hux-KHn@l8BTyicg&Kr#xSS(!47HYV!Yo~_WRiE=sk6Zg%k{WBWFukmy zPP1IjFnszdCv~(W|KaEljKy-jMigHSS*w`id>Rxe2mq3PPm7MCF z3cJ?7;Vwr$-K%u5k!zST&B>@Lpj#0PkuKvwebb6Z97buAWfFYW_lau{JjE{ND`Uuf z9|kd~dBcgF*)Fq0s&?=9>LgUp1N%_z#&NW4wlu9|+Cb(Mxt&Ij(M&-0X|q*xI4f+8 z=(16ariqY>{>D&6?uAV@1;RaYWZj)heo9{c9R1c-ct=la$z!YX+i$kTDTc0(U#>bp zNr^n?Cb=1$dNV=}N^6Myu{Is{zdF|XGCOdrX+-r zFTk*>-Udy*F8fqwNif8-zj3u7Myr>h!YUono2+TQcMVx?B04K6nx zYQbK+5%~YKbLHVsuYG^Y zAtmFeES2F%r=3A6yMv@fBxM_0hmvJ-?Aut5LfNLYP=rv*GDrwxIEf)yv(8wKeH~&l zV$95Q|9<0a?{%K%eXr-b-v5rj=9;<3egE#cm+$wpeDg!pQ)>Kzhdnz)?-b7*WQt9+ z(VdNhUK64)Dt0ZUWTF-9ljf-3c|M!I@sxYS#OGox(lXd7ShZsYfJCT#Ov1`o7$3!DqF@x1c*KCeZm%|i1X z4^F1gDdsD`Ksw9E5E#4vop(456U&6X`u#Urs!U&Kw-F zjId|5bP~r#ZcLdewgd-`B-gd8@J~#u>A&*heCY#BAK|!ZUSjDRbeqDNq?NDlG#=oe zFDz#)sgBvdV#$fKUhQC>q(63|K@`9f&h**>?zw$bzH#adW^fat?oJxS^U=fN((Cb) zwK~n{uy;NO2Qu`YTORRSNI$%XymWw_rDbboJ1rm{Xe{0mJ1}TzK8|l7EtPHehzr>L zj5RB+fi`9GY3MJc>g1uz(^m{i#-{1pjOl~L^*K$Bv?q;hfEifndnPuArEdllm4rYlcl33EZD*!VKt|E6TO5Y@I9m*6^6Ek-{$$;U&(?Z_+0UTZ8dpBeK*O zrb&exg<#?_m#5ZVf(;1vSaP{PXVTUq?BuRJA);6^mktg9PDBgEz5^QsA; z&`OH}{V%{%BEG;sAAr$aU#xQO&Ew2~N@fz?O3-_XoCS#`V_qE)BzDC@lkyMt{tx#4 z5BC0_uy?mP4d$>m-5mtJTe}Amgvv4+50iWZy>kiJJW46VHf=TV(|((%;Z{{JUqB7< zYmm*$Ei=h#Y0~h2=84w?T#y$M)m@z~Jv*veH2wzL01Jrw=2=YipXm zt}7koT;(3m5=bL~22f%H=mt^kXWh?=p;pWnSWpe2dxh_;4$onOL`Uv-_bsJeUVk6X zClS#Q{ROjgAPM@vy|(9}{U_jE-Uv>vA)Vy$>ObM`Jr1}>u&ncGmdzs55{sx-2ca^Q zDeSarUHFXBnzdoV=o`bw9SFFygOL3*C6g>xi^Xo=*=*E)9P`BB400Fs{TPszN^vN2 z2Rg3PBl0zd@P(9(p-o&;Q19ql0QbK18(Wq0O>z~wqG!^2uMF#`{GwgN3-?zLyrB1m zpsHlw!b@iOGCPXu(TI7m7+r5W5n;b= z9=A-pMzw~grw$tmKrO+e57shd4^@M?x>2#8GyfY4q65b?+)z+sm)fEB#DHPb zXDQYP0>x+Z$#w?9t_XmS=I9)n9Cytf?@5#SMzpiyMAZD;Y2BzUkQw2&?tAk|)N8Gx zim*xi7e-}dUl1CFUAtf8;YtCWmjLB0F$2oWPf&gWcF`SYGdG-S74dSwsnu$N&Rf*i zF72=}7|!c0bb{u1eZc`;`XoL(QR=F&Mi`RyR0_15SMOZQx;mDhH~Yby`kqXxNloOH z59!+Nm7CD%BL+QVsQYf8R%4vi)kckqC{A67cI^!4C<Jt4QAaCx%t4Q#Ai!_s){T6=1*=S z7hcWNqIF_4HCcTdie$mB22YE*5~#=Wgwx@AB`i(G)kQ#+K=kNmk@K~L0fIMb)c!%i z6tlj3*aGviHBnBLfO$DeomV5f+TQRU=vF$G9=<*_hM z-d{054?bEwInqC0(Fof+ep@2DwaI*xO5*rjA1gU<@i)4p4_@)T;%T$v+2pjDCqe7qlK(v@vnv3 zvp!OMwt;z-zmPz%@MaB)x~B6XeoL&W?#vIFhCxMN0>N6{TpjX8mn|xC0B;-ISg)o6niL$!1Q6n%gwyS5x+ytOHKo-2F?w8=06%CX zpPvb^HB)Cxlp*xErzh{~Zkzwsqu%ov^eZADfv|M)UCQwwv&hTavmm5MJNyzx%czmM z_UD31>uA%(PZ9V6Q2ucpiaWarHB_PUh5m=w*y#spuHxYbzx0HO>#RbNGu39@fCYgb z=?}bX8lMl1rn=!i&MyuL2+!JiuKZ#bcT~SaXf&n4PcZ5Sx>}S#ky0V(7+i;k1XXkA zrwW|yp(t4Nfr!_wiOGj1Io$0F3$Sq?+GM%XrKZwyv(CP;xdF_e+YFLrZhL;BzAv~cuwMNX zrlrggn_9tSY+il69PDe7c#h@fGAKpt`=uF;lnxc3glR%vmP3{HWxF_WZ=djKbEIm7e*! zt;EffT2uJ?c5`#ca>5^MGXZD``-d7(aAOzY}`2=&ti@_^`a;k0d|74RR`Cabq9 zzQ{BRRgo0db-1kbk|Gx|d=`t{Mm|Nq z=$SE}m)}LnMvdscb50^ntiAn$d*0p4u`2fW{i3GaKm;pK3x2q9Sx|Gt`ijEkBmAs% zSzG1laChtmu_?Nhc7Je*CJW+NAUh=?zKt_aHol7)v{Ly zi&N>IX3^boWvv3e*Nv%;1`EkkKDuebvHBDjWlgD&)uQQAg*t{pE!8BLX~dUKDA-$X>P+wpY&UhEYb|XU=S76BaI<7zZT&a0wzpQoi;s*u3*-`f zUh`sNEx&yc!)qrZw>O&Tps-UkCxBCM{i9S7^HB7Ks(J&loO_uxLRo7NraO^R6G~8d zDO=!Te<>)%!Jti3JIJ+70Yx(^32kZd9+}HCSI@5tN?4$YFyHW@>WE)i(d2zeKtD1L zf$}ju>EED&e-V*AbW@Y|hAvQH8F*jrR381+@iq3U`olHO3o(vamERGYw{_9I&7jBV zRh6m4VLcCQ+stxzG*c1`{yJgYHC^E$`S3VnGX*`qFF5y1C!w-ZF)8qHZ@#GLl;;6( z-wvh>Ji4>0*!HI%MQ-x8XiI09ybW%t%_B~yjQ^NeVrN+mn-*<6gUgP6RO^y$DG|wv zP0PT_eJSfY_QLO;m0JE_NQAcjpu*CCrZ)3?l=87k-J0^Q!rlQ4?_({`7!)Osg>Zb;q7&^x{4XJ8#)jv9uY>n7Zh-?8?vPm6bmJfvf$J zV};h9&zL0yh5ol`_UQ^1)V!5WvVk{8$r{}`PdR?Y)po+0pZs9(xpDjQonRs;3)GQ+ zrZ8mD{5jSJ5GJt6J>C04< z{k8zZ*W}Vh#&B|RP!M%>*Qmg3E877WQB-2|m1170lMGvix?I^K_Lda%&3kDyE2k>K zB4X)UOSxOQwKtJs$|q){UXrI4&(^1jh1ir3Z(!*cp*xh$MYKG_;tD4VzvCN^5jy{5 zKm&|C3?4Zs=kxIE$i{RAlHYEiLo@1Kg%UV|p!%XRr&<+sr}Ve-s}vw8nu@$2JAh0_ zf3b@EI|>0lbN@S;-@juh+S4GGhpk;NeMSUv}8KF=sqQrEr>&o2+09Ik7|iiyVPv|Ay&r zh91zJ{2oxcalPF44lfCf!eL2U8#-E?>)QuB)H=0ix(Ri0abio>!2I)$ji?s(ZH-UnyP7Z zZd>s1XcXpyjeMM>+8k0!Wg3an97Y0@6;eWdlp&>TPATPpu_Fquc+n0PzwF4om~=Mw zP;qugE+qYesjIV1ka>wG3xve#C{mUTF-MnFfAAKH9EUEG(Z@f8OzY6&@BY5y8CO2& z%&(H^BMAHVVHdYD6wl|^dluse3W92JXj!J%_A^a9{y#&c-ti{{?dSU*Qj`Q$G9v_s z-YX5ZNJDqQ&roDxhPe_+}Dwu)Q{Vi0;$Db&`jpZPHVnq2R=Sz_2Qt;Wyuk~kFZ@>e*^P)h8Sfr~t zD85nXOhdRj9ts;EWgl?y10`MeXTvxrUD#g7(<%xa|Gsc_a?qyw+GcO3mnP4zJ=Lp+ zM~!$V5QS6Yrb^@})PZ#ndfQ^4!d0jEr-v(@U;XkBQe5#h=NAHFI zSNk#Nml$1CjteG-frT7PJQ=n--U;aW{Y@x}m=fbZUv(RN50TWAZ4rKXgaO60GIo`4 z{c`%d^&l5&z{ib)K!ox)#mC`I0phLyvWj`kov`1PwO|pHJ>)~hU;VO0)zmN#aGua# zXxSc%a0gKnUKEGz#%Xw3wHnnc4}Ve}yLV@}g9xbWJJD?WQ&vl|ca~{)pFTrd2 zuPY?kL?C`WwVHIZajrDguh~&F-uiSzo4A)1$)nSn=%unnmE`;NEp>5ef1erh2pyKj zg0*~b@BO>3I*MBw4grSLBEn(U)mC@liZpJO9yASD-&G-HwHqNMr@iCZRIcCgdXqjay_DAusY&;q>@-y*C}cjn_N9PrCP(!;bjNx8^7KLo7`d z1sjg1r|HHEu{2Oa)~H2ZDd|~dA6x@UB_MaJy3QoDKR|3GAnkT{R$Gz3VGpQnx>L{Z zMC1}nqBu=A1g94-yh1$3t%|Y#p3?Vsw?iuzfyj+)-nBRI?$$lPcA3LJKYqF@HDjx4 zJ0P^demZtibCXOg8mfuW0Jlb5J2;Ar*FG&O{rHxMN9O!ERBd*d!?Tf;aiF>|8cJOd NXY>qD5_Igs{srN)79jut literal 0 HcmV?d00001 diff --git a/docs/SoC-Skoda.md b/docs/SoC-Skoda.md new file mode 100644 index 0000000000..a994abf327 --- /dev/null +++ b/docs/SoC-Skoda.md @@ -0,0 +1,82 @@ +# SoC-Modul Skoda + +Das SoC-Modul Skoda gibt es in openWB 2.x. + +## Konfiguration + +Die Konfiguration erfolgt im Bereich Einstellungen - Konfiguration - Fahrzeuge: + +![Allgemeine Konfiguration](SoC-Skoda-settings-1.PNG) + +![Spezielle Konfiguration](SoC-Skoda-settings-2.PNG) + +## Hinweise + +Für nicht-Skoda Fahrzeuge (Audi, VW, etc.) funktioniert das Modul nicht. + +Erfolgreich getestet u.a. für folgende Fahrzeuge: Enyaq. + +**Wichtig für alle Fahrzeuge:** +Es muss ein aktives Konto im Skoda ID Portal vorhanden sein und die "MySkoda App" muss eingerichtet sein. + +**WICHTIG:** +Skoda ändert gelegentlich die Bedingungen für die Nutzung der Online-Services. + +Diese müssen bestätigt werden. Wenn der SOC-Abruf plötzlich nicht mehr funktioniert, VOR dem Posten bitte Schritt 1 ausführen. + +Bei Problemen zunächst bitte diese Schritte durchführen: + +1. sicherstellen, dass auf dieser Skoda-Seite alle Einverständnisse gegeben wurden. + + + + In einigen Fällen wurden die Einverständnisse gegeben und trotzdem funktionierte die Abfrage nicht. + Hier hat folgendes Vorgehen geholfen: Im Skoda Konto das Land temporär umstellen, d.h. + - auf ein anderes Land als das eigene ändern + - sichern + - zurück auf das eigene Land ändern + - sichern. + +2. Nach einem manuellen SOC-Abruf (Kreispfeil hinter dem SOC klicken) auf der Status - Seite EV-SOC Log und Debug log auf Fehler kontrollieren + +3. Falls im Ev-Soc Log Fehler 303 (unknown redirect) gemeldet wird: + - Ursache 1: Bestimmte Sonderzeichen im Passwort funktionieren nicht mit dem Modul. Bitte das Passwort auf eines ohne Sonderzeichen ändern und testen. + - Ursache 2: Falsche Email, Passwort oder VIN eingegeben. Alle 3 löschen, speichern, neu eingeben, speichern und testen. + +4. Falls eine Firewall im Spiel ist: Es gab einzelne Probleme beim Internet-Zugriff der openWB auf Python Archive und Fahrzeug-Server wenn IPV6 aktiv ist. + +5. Nach Neustart bzw. Änderung der LP-Konfiguration werden im EV-Soc-Log Fehler ausgegeben (permission oder fehlende Datei). + + Diese Fehler sind normal und können ignoriert werden. Leider wird im Debug Mode 0 keine Positiv-Meldung ausgegeben. + Empfehlung: + - In Einstellungen - System - Fehlersuche dies einstellen: Debug Level/Details + - dann einen manuellen SOC-Abruf durchführen (im Dashboard auf Kreispfeil klicken). + - danach sollte im EV-SOC-Log eine Zeile ähnlich dieser kommen: + + `2023-02-12 11:57:14 INFO:soc_skoda:Lp1 SOC: 61%@2023-02-12T11:53:20` + + Diese Zeile zeigt folgende Information: + + `2023-02-12 11:57:14` *- Timestamp des SOC-Abrufs* + + `INFO` *- Debug Level INFO* + + `soc_skoda` *- SOC-Modul* + + `Lp1` *- Ladepunkt* + + `SOC: 61%` *- SOC Stand* + + `@2025-02-12T11:53:20` *- Timestamp des Updates vom EV zum VW Cloud-Server* + +6. Falls diese Schritte nicht zum Erfolg führen, das Problem im [Support Thema](https://forum.openwb.de/viewforum.php?f=12) mit Angabe relevanter Daten posten + - oWB SW Version + - oWB gekauft oder selbst installiert + - wenn selbst installiert: welches OS(Stretch/Buster) + - welches Fahrzeug + - falls vorhanden Angaben über Firewall, VPN, etc., also Appliances, die den Internetzugang limitieren könnten + - relevante Abschnitte der Logs, vor allem Fehlermeldungen, als CODE-blocks (). + +Das SoC-Log mit evtl. Fehlermeldungen kann wie folgt eingesehen werden: + +- Einstellungen - System - Fehlersuche