From e379d516b6980dd0a9a9844e2be53420f61b74c5 Mon Sep 17 00:00:00 2001 From: bj00rn Date: Wed, 25 Jun 2025 20:15:36 +0000 Subject: [PATCH 1/2] Rework help output --- src/configuration/argparse_extensions.py | 36 +++ src/configuration/parser.py | 294 +++++++++++++---------- 2 files changed, 197 insertions(+), 133 deletions(-) diff --git a/src/configuration/argparse_extensions.py b/src/configuration/argparse_extensions.py index eba9d94..dcc280b 100644 --- a/src/configuration/argparse_extensions.py +++ b/src/configuration/argparse_extensions.py @@ -2,6 +2,7 @@ import argparse from argparse import ArgumentParser, Namespace +from gettext import gettext as _ import os from typing import TYPE_CHECKING, Any, override @@ -9,6 +10,40 @@ from collections.abc import Callable, Sequence +class ArgumentHelpFormatter(argparse.RawTextHelpFormatter): + """Custom argument formatter. + + Appends environment variable and default value to help. + """ + + def _get_help_string(self, action: argparse.Action) -> str | None: + _help = action.help + if _help is None: + _help = "" + + if isinstance(action, EnvDefault): + # append type + t = action.type + if t is not None: + if ( + hasattr(t, "__annotations__") + and t.__annotations__.get("return", None) is not None + ): + _help += f"\n(type: {t.__annotations__.get('return', None)})" + elif hasattr(t, "__name__"): + _help += f"\n(type: {t.__name__})" + + if "%(default)" not in _help and action.default is not argparse.SUPPRESS: + defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + # append default value + _help += _("\n(default: %(default)s)") + # append environment variable + _help += f"\n(environment variable: {action.envvar})" + # whitespace from each line + return "\n".join([m.lstrip() for m in _help.split("\n")]) + + class EnvDefault(argparse.Action): def __init__( self, @@ -17,6 +52,7 @@ def __init__( default: str | None = None, **kwargs: dict[str, Any], ) -> None: + self.envvar = envvar if os.environ.get(envvar): default = os.environ[envvar] if required and default: diff --git a/src/configuration/parser.py b/src/configuration/parser.py index dd7ee7c..97c07cd 100644 --- a/src/configuration/parser.py +++ b/src/configuration/parser.py @@ -9,6 +9,7 @@ from configuration import Configuration, TransportProtocol from configuration.argparse_extensions import ( + ArgumentHelpFormatter, EnvDefault, cfg_value_to_dict, check_bool, @@ -159,66 +160,74 @@ def __setup_osmand(args: Namespace, config: Configuration) -> None: def setup_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(prog="MQTT Gateway", add_help=True) - parser.add_argument( + parser = argparse.ArgumentParser( + prog="MQTT Gateway", formatter_class=ArgumentHelpFormatter + ) + + mqtt = parser.add_argument_group("MQTT Broker Configuration") + mqtt.add_argument( "-m", "--mqtt-uri", - help="The URI to the MQTT Server. Environment Variable: MQTT_URI," - "TCP: tcp://mqtt.eclipseprojects.io:1883 " - "WebSocket: ws://mqtt.eclipseprojects.io:9001" - "TLS: tls://mqtt.eclipseprojects.io:8883", + help="""The URI to the MQTT Server. + TCP: tcp://mqtt.eclipseprojects.io:1883 + WebSocket: ws://mqtt.eclipseprojects.io:9001 + TLS: tls://mqtt.eclipseprojects.io:8883""", dest="mqtt_uri", required=False, action=EnvDefault, envvar="MQTT_URI", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-server-cert", help="Path to the server certificate authority file in PEM format for TLS.", dest="tls_server_cert_path", required=False, action=EnvDefault, envvar="MQTT_SERVER_CERT", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-user", - help="The MQTT user name. Environment Variable: MQTT_USER", + help="The MQTT user name.", dest="mqtt_user", required=False, action=EnvDefault, envvar="MQTT_USER", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-password", - help="The MQTT password. Environment Variable: MQTT_PASSWORD", + help="The MQTT password.", dest="mqtt_password", required=False, action=EnvDefault, envvar="MQTT_PASSWORD", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-client-id", - help="The MQTT Client Identifier. Environment Variable: " - "MQTT_CLIENT_ID " - "Default is saic-python-mqtt-gateway", + help="The MQTT Client Identifier.", default="saic-python-mqtt-gateway", dest="mqtt_client_id", required=False, action=EnvDefault, envvar="MQTT_CLIENT_ID", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-topic-prefix", - help="MQTT topic prefix. Environment Variable: MQTT_TOPIC Default is saic", + help="MQTT topic prefix.", default="saic", dest="mqtt_topic", required=False, action=EnvDefault, envvar="MQTT_TOPIC", + type=str, ) - parser.add_argument( + mqtt.add_argument( "--mqtt-allow-dots-in-topic", - help="Allow dots in MQTT topics. Environment Variable: MQTT_ALLOW_DOTS_IN_TOPIC Default is True", + help="Allow dots in MQTT topics.", dest="mqtt_allow_dots_in_topic", required=False, action=EnvDefault, @@ -226,104 +235,166 @@ def setup_parser() -> argparse.ArgumentParser: type=check_bool, envvar="MQTT_ALLOW_DOTS_IN_TOPIC", ) - parser.add_argument( + mqtt.add_argument( + "--mqtt-server-cert-check-hostname", + help="""\ + Check TLS certificate hostname when using custom certificate. + Set to (False) when using self-signed certificate without a matching hostname. + This option might be insecure.""", + dest="tls_server_cert_check_hostname", + required=False, + action=EnvDefault, + envvar="MQTT_SERVER_CERT_CHECK_HOSTNAME", + default=True, + type=check_bool, + ) + + saic_api = parser.add_argument_group( + "SAIC API Configuration", + "Configuration for the SAIC API connection.", + ) + saic_api.add_argument( "-s", "--saic-rest-uri", - help="The SAIC uri. Environment Variable: SAIC_REST_URI Default is the European " - "Production Endpoint: https://tap-eu.soimt.com", + help="The SAIC uri. Default is European Production Endpoint", default="https://gateway-mg-eu.soimt.com/api.app/v1/", dest="saic_rest_uri", required=False, action=EnvDefault, + type=str, envvar="SAIC_REST_URI", ) - parser.add_argument( + saic_api.add_argument( "-u", "--saic-user", - help="The SAIC user name. Environment Variable: SAIC_USER", + help="The SAIC user name.", dest="saic_user", required=True, action=EnvDefault, envvar="SAIC_USER", + type=str, ) - parser.add_argument( + saic_api.add_argument( "-p", "--saic-password", - help="The SAIC password. Environment Variable: SAIC_PASSWORD", + help="The SAIC password.", dest="saic_password", required=True, action=EnvDefault, envvar="SAIC_PASSWORD", + type=str, ) - parser.add_argument( + saic_api.add_argument( "--saic-phone-country-code", - help="The SAIC phone country code. Environment Variable: SAIC_PHONE_COUNTRY_CODE", + help="The SAIC phone country code.", dest="saic_phone_country_code", required=False, action=EnvDefault, envvar="SAIC_PHONE_COUNTRY_CODE", + type=str, ) - parser.add_argument( + saic_api.add_argument( "--saic-region", "--saic-region", - help="The SAIC API region. Environment Variable: SAIC_REGION", + help="The SAIC API region.", default="eu", dest="saic_region", required=False, action=EnvDefault, envvar="SAIC_REGION", + type=str, ) - parser.add_argument( + saic_api.add_argument( "--saic-tenant-id", - help="The SAIC API tenant id. Environment Variable: SAIC_TENANT_ID", + help="The SAIC API tenant id.", default="459771", dest="saic_tenant_id", required=False, action=EnvDefault, envvar="SAIC_TENANT_ID", + type=str, ) - parser.add_argument( + saic_api.add_argument( "--battery-capacity-mapping", - help="The mapping of VIN to full batteryc" - " apacity. Multiple mappings can be provided separated" - " by , Example: LSJXXXX=54.0,LSJYYYY=64.0," - " Environment Variable: BATTERY_CAPACITY_MAPPING", + help="""\ + The mapping of VIN to full battery capacity. + Multiple mappings can be provided separated by comma. + Example: LSJXXXX=54.0,LSJYYYY=64.0,""", dest="battery_capacity_mapping", required=False, action=EnvDefault, envvar="BATTERY_CAPACITY_MAPPING", + type=str, ) - parser.add_argument( - "--charging-stations-json", - help="Custom charging stations configuration file name", - dest="charging_stations_file", - required=False, - action=EnvDefault, - envvar="CHARGING_STATIONS_JSON", - ) - parser.add_argument( + saic_api.add_argument( "--saic-relogin-delay", - help="How long to wait before attempting another login to the SAIC API. Environment " - "Variable: SAIC_RELOGIN_DELAY", + help="How long to wait before attempting another login to the SAIC API.", dest="saic_relogin_delay", required=False, action=EnvDefault, envvar="SAIC_RELOGIN_DELAY", type=check_positive, ) - parser.add_argument( + saic_api.add_argument( "--saic-read-timeout", - help="HTTP Read timeout for the SAIC API. Environment " - "Variable: SAIC_READ_TIMEOUT", + help="HTTP Read timeout for the SAIC API.", dest="saic_read_timeout", required=False, action=EnvDefault, envvar="SAIC_READ_TIMEOUT", type=check_positive_float, ) - parser.add_argument( + saic_api.add_argument( + "--messages-request-interval", + help="The interval for retrieving messages in seconds.", + dest="messages_request_interval", + required=False, + action=EnvDefault, + envvar="MESSAGES_REQUEST_INTERVAL", + default=60, + type=check_positive, + ) + saic_api.add_argument( + "--charge-min-percentage", + help="How many percentage points we should try to refresh the charge state.", + dest="charge_dynamic_polling_min_percentage", + required=False, + action=EnvDefault, + envvar="CHARGE_MIN_PERCENTAGE", + default="1.0", + type=check_positive_float, + ) + saic_api.add_argument( + "--publish-raw-api-data", + help="Publish raw SAIC API request/response to MQTT.", + dest="publish_raw_api_data", + required=False, + action=EnvDefault, + envvar="PUBLISH_RAW_API_DATA_ENABLED", + default=False, + type=check_bool, + ) + + openwb_integration = parser.add_argument_group( + "OpenWB Integration", "Configuration for the OpenWB integration." + ) + openwb_integration.add_argument( + "--charging-stations-json", + help="Custom charging stations configuration file name", + dest="charging_stations_file", + required=False, + action=EnvDefault, + envvar="CHARGING_STATIONS_JSON", + type=str, + ) + + homeassistant_integration = parser.add_argument_group( + "Home Assistant Integration", + "Configuration for the Home Assistant integration.", + ) + homeassistant_integration.add_argument( "--ha-discovery", - help="Enable Home Assistant Discovery. Environment Variable: HA_DISCOVERY_ENABLED", + help="Enable Home Assistant Discovery.", dest="ha_discovery_enabled", required=False, action=EnvDefault, @@ -331,19 +402,18 @@ def setup_parser() -> argparse.ArgumentParser: default=True, type=check_bool, ) - parser.add_argument( + homeassistant_integration.add_argument( "--ha-discovery-prefix", - help="Home Assistant Discovery Prefix. Environment Variable: HA_DISCOVERY_PREFIX", + help="Home Assistant Discovery Prefix.", dest="ha_discovery_prefix", required=False, action=EnvDefault, envvar="HA_DISCOVERY_PREFIX", default="homeassistant", ) - parser.add_argument( + homeassistant_integration.add_argument( "--ha-show-unavailable", - help="Show entities as Unavailable in Home Assistant when car polling fails. " - "Environment Variable: HA_SHOW_UNAVAILABLE", + help="Show entities as Unavailable in Home Assistant when car polling fails.", dest="ha_show_unavailable", required=False, action=EnvDefault, @@ -351,66 +421,36 @@ def setup_parser() -> argparse.ArgumentParser: default=True, type=check_bool, ) - parser.add_argument( - "--messages-request-interval", - help="The interval for retrieving messages in seconds. Environment Variable: " - "MESSAGES_REQUEST_INTERVAL", - dest="messages_request_interval", - required=False, - action=EnvDefault, - envvar="MESSAGES_REQUEST_INTERVAL", - default=60, - ) - parser.add_argument( - "--charge-min-percentage", - help="How many percentage points we should try to refresh the charge state. Environment Variable: " - "CHARGE_MIN_PERCENTAGE", - dest="charge_dynamic_polling_min_percentage", - required=False, - action=EnvDefault, - envvar="CHARGE_MIN_PERCENTAGE", - default="1.0", - type=check_positive_float, - ) - parser.add_argument( - "--publish-raw-api-data", - help="Publish raw SAIC API request/response to MQTT. Environment Variable: " - "PUBLISH_RAW_API_DATA_ENABLED", - dest="publish_raw_api_data", - required=False, - action=EnvDefault, - envvar="PUBLISH_RAW_API_DATA_ENABLED", - default=False, - type=check_bool, + + abrp_integration = parser.add_argument_group( + "A Better Route Planner (ABRP) Integration", + "Configuration for the A Better Route Planner integration.", ) # ABRP Integration - parser.add_argument( + abrp_integration.add_argument( "--abrp-api-key", - help="The API key for the A Better Route Planer telemetry API." - " Default is the open source telemetry" - " API key 8cfc314b-03cd-4efe-ab7d-4431cd8f2e2d." - " Environment Variable: ABRP_API_KEY", + help="The API key for the A Better Route Planer telemetry API.", default="8cfc314b-03cd-4efe-ab7d-4431cd8f2e2d", dest="abrp_api_key", required=False, action=EnvDefault, envvar="ABRP_API_KEY", + type=str, ) - parser.add_argument( + abrp_integration.add_argument( "--abrp-user-token", - help="The mapping of VIN to ABRP User Token." - " Multiple mappings can be provided seperated by ," - " Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl," - " Environment Variable: ABRP_USER_TOKEN", + help="""The mapping of VIN to ABRP User Token. + Multiple mappings can be provided seperated by , + Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl,""", dest="abrp_user_token", required=False, action=EnvDefault, envvar="ABRP_USER_TOKEN", + type=str, ) - parser.add_argument( + abrp_integration.add_argument( "--publish-raw-abrp-data", - help="Publish raw ABRP API request/response to MQTT. Environment Variable: " - "PUBLISH_RAW_ABRP_DATA_ENABLED", + help="Publish raw ABRP API request/response to MQTT.", dest="publish_raw_abrp_data", required=False, action=EnvDefault, @@ -418,35 +458,37 @@ def setup_parser() -> argparse.ArgumentParser: default=False, type=check_bool, ) + # OsmAnd Integration - parser.add_argument( + osmand_integration = parser.add_argument_group( + "OsmAnd Integration", + "Configuration for the OsmAnd integration.", + ) + osmand_integration.add_argument( "--osmand-server-uri", - help="The URL of your OsmAnd Server." - " Default unset" - " Environment Variable: OSMAND_SERVER_URI", + help="The URL of your OsmAnd Server.", default=None, dest="osmand_server_uri", required=False, action=EnvDefault, envvar="OSMAND_SERVER_URI", + type=str, ) - parser.add_argument( + osmand_integration.add_argument( "--osmand-device-id", - help="The mapping of VIN to OsmAnd Device ID." - " Multiple mappings can be provided seperated by ," - " Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl," - " Default is to use the car VIN as Device ID, " - " Environment Variable: OSMAND_DEVICE_ID", + help="""The mapping of VIN to OsmAnd Device ID. + Multiple mappings can be provided seperated by , + Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl, + Uses the car VIN as Device ID if not set""", dest="osmand_device_id", required=False, action=EnvDefault, envvar="OSMAND_DEVICE_ID", + type=str, ) - parser.add_argument( + osmand_integration.add_argument( "--osmand-use-knots", - help="Whether to use knots of kph as a speed unit in OsmAnd messages. " - "Enabled (True) by default to ensure compatibilty with Traccar. " - "Environment Variable: OSMAND_USE_KNOTS", + help="Whether to use knots of kph as a speed unit in OsmAnd messages to ensure compatibilty with Traccar.", dest="osmand_use_knots", required=False, action=EnvDefault, @@ -454,10 +496,9 @@ def setup_parser() -> argparse.ArgumentParser: default=True, type=check_bool, ) - parser.add_argument( + osmand_integration.add_argument( "--publish-raw-osmand-data", - help="Publish raw ABRP OsmAnd request/response to MQTT. Environment Variable: " - "PUBLISH_RAW_OSMAND_DATA_ENABLED", + help="Publish raw ABRP OsmAnd request/response to MQTT.", dest="publish_raw_osmand_data", required=False, action=EnvDefault, @@ -465,20 +506,7 @@ def setup_parser() -> argparse.ArgumentParser: default=False, type=check_bool, ) - parser.add_argument( - "--mqtt-server-cert-check-hostname", - help="Enable or disable TLS certificate hostname checking when using custom certificate." - "Enabled (True) by default" - "Set to (False) when using self-signed certificate without a matching hostname." - "This option might be insecure." - "Environment Variable: MQTT_SERVER_CERT_CHECK_HOSTNAME", - dest="tls_server_cert_check_hostname", - required=False, - action=EnvDefault, - envvar="MQTT_SERVER_CERT_CHECK_HOSTNAME", - default=True, - type=check_bool, - ) + return parser From 564955eaea638bbdee42ca4f3ddee38c645cec5b Mon Sep 17 00:00:00 2001 From: bj00rn Date: Thu, 26 Jun 2025 07:43:17 +0000 Subject: [PATCH 2/2] add functions to generate agrument groups --- src/configuration/parser.py | 128 ++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/src/configuration/parser.py b/src/configuration/parser.py index 97c07cd..6ceffba 100644 --- a/src/configuration/parser.py +++ b/src/configuration/parser.py @@ -161,17 +161,30 @@ def __setup_osmand(args: Namespace, config: Configuration) -> None: def setup_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - prog="MQTT Gateway", formatter_class=ArgumentHelpFormatter + prog="SAIC MQTT Gateway", formatter_class=ArgumentHelpFormatter ) + __add_mqtt_argument_group(parser) + __add_saic_api_argument_group(parser) + __add_openwb_argument_group(parser) + __add_homeassistant_argument_group(parser) + __add_abrp_argument_group(parser) + add_osmand_argument_group(parser) + + return parser + + +def __add_mqtt_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: mqtt = parser.add_argument_group("MQTT Broker Configuration") mqtt.add_argument( "-m", "--mqtt-uri", help="""The URI to the MQTT Server. - TCP: tcp://mqtt.eclipseprojects.io:1883 - WebSocket: ws://mqtt.eclipseprojects.io:9001 - TLS: tls://mqtt.eclipseprojects.io:8883""", + TCP: tcp://mqtt.eclipseprojects.io:1883 + WebSocket: ws://mqtt.eclipseprojects.io:9001 + TLS: tls://mqtt.eclipseprojects.io:8883""", dest="mqtt_uri", required=False, action=EnvDefault, @@ -180,7 +193,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-server-cert", - help="Path to the server certificate authority file in PEM format for TLS.", + help="""Path to the server certificate authority file in PEM format for TLS.""", dest="tls_server_cert_path", required=False, action=EnvDefault, @@ -189,7 +202,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-user", - help="The MQTT user name.", + help="""The MQTT user name.""", dest="mqtt_user", required=False, action=EnvDefault, @@ -198,7 +211,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-password", - help="The MQTT password.", + help="""The MQTT password.""", dest="mqtt_password", required=False, action=EnvDefault, @@ -207,7 +220,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-client-id", - help="The MQTT Client Identifier.", + help="""The MQTT Client Identifier.""", default="saic-python-mqtt-gateway", dest="mqtt_client_id", required=False, @@ -217,7 +230,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-topic-prefix", - help="MQTT topic prefix.", + help="""MQTT topic prefix.""", default="saic", dest="mqtt_topic", required=False, @@ -227,7 +240,7 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-allow-dots-in-topic", - help="Allow dots in MQTT topics.", + help="""Allow dots in MQTT topics.""", dest="mqtt_allow_dots_in_topic", required=False, action=EnvDefault, @@ -237,10 +250,9 @@ def setup_parser() -> argparse.ArgumentParser: ) mqtt.add_argument( "--mqtt-server-cert-check-hostname", - help="""\ - Check TLS certificate hostname when using custom certificate. - Set to (False) when using self-signed certificate without a matching hostname. - This option might be insecure.""", + help="""Check TLS certificate hostname when using custom certificate. + Set to (False) when using self-signed certificate without a matching hostname. + This option might be insecure.""", dest="tls_server_cert_check_hostname", required=False, action=EnvDefault, @@ -248,7 +260,12 @@ def setup_parser() -> argparse.ArgumentParser: default=True, type=check_bool, ) + return mqtt + +def __add_saic_api_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: saic_api = parser.add_argument_group( "SAIC API Configuration", "Configuration for the SAIC API connection.", @@ -256,7 +273,7 @@ def setup_parser() -> argparse.ArgumentParser: saic_api.add_argument( "-s", "--saic-rest-uri", - help="The SAIC uri. Default is European Production Endpoint", + help="""The SAIC uri. Default is European Production Endpoint""", default="https://gateway-mg-eu.soimt.com/api.app/v1/", dest="saic_rest_uri", required=False, @@ -267,7 +284,7 @@ def setup_parser() -> argparse.ArgumentParser: saic_api.add_argument( "-u", "--saic-user", - help="The SAIC user name.", + help="""The SAIC user name.""", dest="saic_user", required=True, action=EnvDefault, @@ -277,7 +294,7 @@ def setup_parser() -> argparse.ArgumentParser: saic_api.add_argument( "-p", "--saic-password", - help="The SAIC password.", + help="""The SAIC password.""", dest="saic_password", required=True, action=EnvDefault, @@ -286,7 +303,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--saic-phone-country-code", - help="The SAIC phone country code.", + help="""The SAIC phone country code.""", dest="saic_phone_country_code", required=False, action=EnvDefault, @@ -295,8 +312,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--saic-region", - "--saic-region", - help="The SAIC API region.", + help="""The SAIC API region.""", default="eu", dest="saic_region", required=False, @@ -306,7 +322,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--saic-tenant-id", - help="The SAIC API tenant id.", + help="""The SAIC API tenant id.""", default="459771", dest="saic_tenant_id", required=False, @@ -316,10 +332,9 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--battery-capacity-mapping", - help="""\ - The mapping of VIN to full battery capacity. - Multiple mappings can be provided separated by comma. - Example: LSJXXXX=54.0,LSJYYYY=64.0,""", + help="""The mapping of VIN to full battery capacity. + Multiple mappings can be provided separated by comma. + Example: LSJXXXX=54.0,LSJYYYY=64.0,""", dest="battery_capacity_mapping", required=False, action=EnvDefault, @@ -328,7 +343,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--saic-relogin-delay", - help="How long to wait before attempting another login to the SAIC API.", + help="""How long to wait before attempting another login to the SAIC API.""", dest="saic_relogin_delay", required=False, action=EnvDefault, @@ -337,7 +352,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--saic-read-timeout", - help="HTTP Read timeout for the SAIC API.", + help="""HTTP Read timeout for the SAIC API.""", dest="saic_read_timeout", required=False, action=EnvDefault, @@ -346,7 +361,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--messages-request-interval", - help="The interval for retrieving messages in seconds.", + help="""The interval for retrieving messages in seconds.""", dest="messages_request_interval", required=False, action=EnvDefault, @@ -356,7 +371,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--charge-min-percentage", - help="How many percentage points we should try to refresh the charge state.", + help="""How many percentage points we should try to refresh the charge state.""", dest="charge_dynamic_polling_min_percentage", required=False, action=EnvDefault, @@ -366,7 +381,7 @@ def setup_parser() -> argparse.ArgumentParser: ) saic_api.add_argument( "--publish-raw-api-data", - help="Publish raw SAIC API request/response to MQTT.", + help="""Publish raw SAIC API request/response to MQTT.""", dest="publish_raw_api_data", required=False, action=EnvDefault, @@ -374,27 +389,37 @@ def setup_parser() -> argparse.ArgumentParser: default=False, type=check_bool, ) + return saic_api + +def __add_openwb_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: openwb_integration = parser.add_argument_group( "OpenWB Integration", "Configuration for the OpenWB integration." ) openwb_integration.add_argument( "--charging-stations-json", - help="Custom charging stations configuration file name", + help="""Custom charging stations configuration file name""", dest="charging_stations_file", required=False, action=EnvDefault, envvar="CHARGING_STATIONS_JSON", type=str, ) + return openwb_integration + +def __add_homeassistant_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: homeassistant_integration = parser.add_argument_group( "Home Assistant Integration", "Configuration for the Home Assistant integration.", ) homeassistant_integration.add_argument( "--ha-discovery", - help="Enable Home Assistant Discovery.", + help="""Enable Home Assistant Discovery.""", dest="ha_discovery_enabled", required=False, action=EnvDefault, @@ -404,7 +429,7 @@ def setup_parser() -> argparse.ArgumentParser: ) homeassistant_integration.add_argument( "--ha-discovery-prefix", - help="Home Assistant Discovery Prefix.", + help="""Home Assistant Discovery Prefix.""", dest="ha_discovery_prefix", required=False, action=EnvDefault, @@ -413,7 +438,7 @@ def setup_parser() -> argparse.ArgumentParser: ) homeassistant_integration.add_argument( "--ha-show-unavailable", - help="Show entities as Unavailable in Home Assistant when car polling fails.", + help="""Show entities as Unavailable in Home Assistant when car polling fails.""", dest="ha_show_unavailable", required=False, action=EnvDefault, @@ -421,15 +446,19 @@ def setup_parser() -> argparse.ArgumentParser: default=True, type=check_bool, ) + return homeassistant_integration + +def __add_abrp_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: abrp_integration = parser.add_argument_group( "A Better Route Planner (ABRP) Integration", "Configuration for the A Better Route Planner integration.", ) - # ABRP Integration abrp_integration.add_argument( "--abrp-api-key", - help="The API key for the A Better Route Planer telemetry API.", + help="""The API key for the A Better Route Planer telemetry API.""", default="8cfc314b-03cd-4efe-ab7d-4431cd8f2e2d", dest="abrp_api_key", required=False, @@ -440,8 +469,8 @@ def setup_parser() -> argparse.ArgumentParser: abrp_integration.add_argument( "--abrp-user-token", help="""The mapping of VIN to ABRP User Token. - Multiple mappings can be provided seperated by , - Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl,""", + Multiple mappings can be provided seperated by , + Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl,""", dest="abrp_user_token", required=False, action=EnvDefault, @@ -450,7 +479,7 @@ def setup_parser() -> argparse.ArgumentParser: ) abrp_integration.add_argument( "--publish-raw-abrp-data", - help="Publish raw ABRP API request/response to MQTT.", + help="""Publish raw ABRP API request/response to MQTT.""", dest="publish_raw_abrp_data", required=False, action=EnvDefault, @@ -458,15 +487,19 @@ def setup_parser() -> argparse.ArgumentParser: default=False, type=check_bool, ) + return abrp_integration - # OsmAnd Integration + +def add_osmand_argument_group( + parser: argparse.ArgumentParser, +) -> argparse._ArgumentGroup: osmand_integration = parser.add_argument_group( "OsmAnd Integration", "Configuration for the OsmAnd integration.", ) osmand_integration.add_argument( "--osmand-server-uri", - help="The URL of your OsmAnd Server.", + help="""The URL of your OsmAnd Server.""", default=None, dest="osmand_server_uri", required=False, @@ -477,9 +510,9 @@ def setup_parser() -> argparse.ArgumentParser: osmand_integration.add_argument( "--osmand-device-id", help="""The mapping of VIN to OsmAnd Device ID. - Multiple mappings can be provided seperated by , - Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl, - Uses the car VIN as Device ID if not set""", + Multiple mappings can be provided seperated by , + Example: LSJXXXX=12345-abcdef,LSJYYYY=67890-ghijkl, + Uses the car VIN as Device ID if not set""", dest="osmand_device_id", required=False, action=EnvDefault, @@ -488,7 +521,7 @@ def setup_parser() -> argparse.ArgumentParser: ) osmand_integration.add_argument( "--osmand-use-knots", - help="Whether to use knots of kph as a speed unit in OsmAnd messages to ensure compatibilty with Traccar.", + help="""Whether to use knots of kph as a speed unit in OsmAnd messages to ensure compatibilty with Traccar.""", dest="osmand_use_knots", required=False, action=EnvDefault, @@ -498,7 +531,7 @@ def setup_parser() -> argparse.ArgumentParser: ) osmand_integration.add_argument( "--publish-raw-osmand-data", - help="Publish raw ABRP OsmAnd request/response to MQTT.", + help="""Publish raw ABRP OsmAnd request/response to MQTT.""", dest="publish_raw_osmand_data", required=False, action=EnvDefault, @@ -506,8 +539,7 @@ def setup_parser() -> argparse.ArgumentParser: default=False, type=check_bool, ) - - return parser + return osmand_integration def __process_charging_stations_file(config: Configuration, json_file: str) -> None: