diff --git a/.gitignore b/.gitignore index 6f1e990..8515217 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,5 @@ sec_*.txt /custom_components/mbapi2020/messages/m* /custom_components/mbapi2020/messages/c* /custom_components/mbapi2020/messages/rc* -/custom_components/mbapi2020/messages/ssu* \ No newline at end of file +/custom_components/mbapi2020/messages/ssu* +/custom_components/mbapi2020/resources* \ No newline at end of file diff --git a/custom_components/mbapi2020/client.py b/custom_components/mbapi2020/client.py index 1f32bdd..45667d5 100644 --- a/custom_components/mbapi2020/client.py +++ b/custom_components/mbapi2020/client.py @@ -49,6 +49,7 @@ CONF_PIN, DEFAULT_CACHE_PATH, DEFAULT_COUNTRY_CODE, + DEFAULT_DOWNLOAD_PATH, DEFAULT_LOCALE, DEFAULT_SOCKET_MIN_RETRY, ) @@ -720,6 +721,22 @@ async def doors_lock(self, vin: str): await self.websocket.call(message.SerializeToString()) LOGGER.info("End Doors_lock for vin %s", loghelper.Mask_VIN(vin)) + async def download_images(self, vin: str): + """Download the car related images.""" + LOGGER.info("Start download_images for vin %s", loghelper.Mask_VIN(vin)) + + download_path = self._hass.config.path(DEFAULT_DOWNLOAD_PATH) + target_file_name = Path(download_path, f"{vin}.zip") + zip_file = await self.webapi.download_images(vin) + if zip_file: + Path(download_path).mkdir(parents=True, exist_ok=True) + + with open(target_file_name, mode="wb") as zf: + zf.write(zip_file) + zf.close() + + LOGGER.info("End download_images for vin %s", loghelper.Mask_VIN(vin)) + async def auxheat_configure(self, vin: str, time_selection: int, time_1: int, time_2: int, time_3: int): """Send the auxheat configure command to the car.""" LOGGER.info("Start auxheat_configure for vin %s", loghelper.Mask_VIN(vin)) diff --git a/custom_components/mbapi2020/const.py b/custom_components/mbapi2020/const.py index b012151..69ec8ef 100644 --- a/custom_components/mbapi2020/const.py +++ b/custom_components/mbapi2020/const.py @@ -57,6 +57,7 @@ UPDATE_INTERVAL = timedelta(seconds=300) DEFAULT_CACHE_PATH = "custom_components/mbapi2020/messages" +DEFAULT_DOWNLOAD_PATH = "custom_components/mbapi2020/resources" DEFAULT_LOCALE = "en-GB" DEFAULT_COUNTRY_CODE = "EN" @@ -148,6 +149,7 @@ SERVICE_WINDOWS_OPEN = "windows_open" SERVICE_WINDOWS_CLOSE = "windows_close" SERVICE_WINDOWS_MOVE = "windows_move" +SERVICE_DOWNLOAD_IMAGES = "download_images" SERVICE_AUXHEAT_CONFIGURE_SCHEMA = vol.Schema( { diff --git a/custom_components/mbapi2020/services.py b/custom_components/mbapi2020/services.py index 8a96dae..abc0d71 100644 --- a/custom_components/mbapi2020/services.py +++ b/custom_components/mbapi2020/services.py @@ -1,4 +1,5 @@ """Services for the Blink integration.""" + from __future__ import annotations from homeassistant.core import HomeAssistant @@ -20,6 +21,7 @@ SERVICE_CHARGE_PROGRAM_CONFIGURE, SERVICE_DOORS_LOCK_URL, SERVICE_DOORS_UNLOCK_URL, + SERVICE_DOWNLOAD_IMAGES, SERVICE_ENGINE_START, SERVICE_ENGINE_STOP, SERVICE_PREHEAT_START, @@ -158,6 +160,9 @@ async def battery_max_soc_configure(call) -> None: call.data.get(CONF_VIN), call.data.get("max_soc") ) + async def download_images(call) -> None: + await domain[_get_config_entryid(call.data.get(CONF_VIN))].client.download_images(call.data.get(CONF_VIN)) + # Register all the above services service_mapping = [ ( @@ -175,6 +180,7 @@ async def battery_max_soc_configure(call) -> None: (SERVICE_CHARGE_PROGRAM_CONFIGURE, charge_program_configure, SERVICE_VIN_CHARGE_PROGRAM_SCHEMA), (SERVICE_DOORS_LOCK_URL, doors_lock, SERVICE_VIN_SCHEMA), (SERVICE_DOORS_UNLOCK_URL, doors_unlock, SERVICE_VIN_PIN_SCHEMA), + (SERVICE_DOWNLOAD_IMAGES, download_images, SERVICE_VIN_SCHEMA), (SERVICE_ENGINE_START, engine_start, SERVICE_VIN_SCHEMA), (SERVICE_ENGINE_STOP, engine_stop, SERVICE_VIN_SCHEMA), (SERVICE_PREHEAT_START, preheat_start, SERVICE_PREHEAT_START_SCHEMA), @@ -212,6 +218,7 @@ def remove_services(hass: HomeAssistant) -> None: hass.services.async_remove(DOMAIN, SERVICE_BATTERY_MAX_SOC_CONFIGURE) hass.services.async_remove(DOMAIN, SERVICE_DOORS_LOCK_URL) hass.services.async_remove(DOMAIN, SERVICE_DOORS_UNLOCK_URL) + hass.services.async_remove(DOMAIN, SERVICE_DOWNLOAD_IMAGES) hass.services.async_remove(DOMAIN, SERVICE_ENGINE_START) hass.services.async_remove(DOMAIN, SERVICE_ENGINE_STOP) hass.services.async_remove(DOMAIN, SERVICE_PREHEAT_START) diff --git a/custom_components/mbapi2020/services.yaml b/custom_components/mbapi2020/services.yaml index 9b60fb6..a53c18a 100644 --- a/custom_components/mbapi2020/services.yaml +++ b/custom_components/mbapi2020/services.yaml @@ -348,7 +348,6 @@ windows_move: - "90" - "100" - send_route: description: "Sends a route to the car. (Single location only)" fields: @@ -394,3 +393,13 @@ send_route: required: True selector: text: + +download_images: + description: "Download the images and save it to the component folder." + fields: + vin: + description: "vin of the car" + example: "Wxxxxxxxxxxxxxx" + required: True + selector: + text: diff --git a/custom_components/mbapi2020/translations/en.json b/custom_components/mbapi2020/translations/en.json index 71a2aa6..2209b4e 100755 --- a/custom_components/mbapi2020/translations/en.json +++ b/custom_components/mbapi2020/translations/en.json @@ -165,7 +165,17 @@ }, "doors_lock": { "name": "Doors lock", - "description": "Lock a car defined by a vin. PIN setup required. See options dialog of the integration.", + "description": "Lock a car defined by a vin", + "fields": { + "vin": { + "name": "Vin", + "description": "Vin/Fin of the car" + } + } + }, + "download_images": { + "name": "Download images", + "description": "Downloads the app images to the components resource folder for a car defined by a vin.", "fields": { "vin": { "name": "Vin", diff --git a/custom_components/mbapi2020/webapi.py b/custom_components/mbapi2020/webapi.py index 782112c..38401af 100644 --- a/custom_components/mbapi2020/webapi.py +++ b/custom_components/mbapi2020/webapi.py @@ -56,6 +56,7 @@ async def _request( endpoint: str, rcp_headers: bool = False, ignore_errors: bool = False, + return_as_json: bool = True, **kwargs, ): """Make a request against the API.""" @@ -94,7 +95,10 @@ async def _request( if "url" in kwargs: async with self._session.request(method, **kwargs) as resp: # resp.raise_for_status() - return await resp.json(content_type=None) + if return_as_json: + return await resp.json(content_type=None) + else: + return await resp.read() else: async with self._session.request(method, url, **kwargs) as resp: if 400 <= resp.status < 500: @@ -112,7 +116,10 @@ async def _request( else: resp.raise_for_status() - return await resp.json(content_type=None) + if return_as_json: + return await resp.json(content_type=None) + else: + return await resp.read() except ClientError as err: LOGGER.debug(traceback.format_exc()) @@ -202,3 +209,8 @@ async def is_car_rcp_supported(self, vin: str, **kwargs): resp_status = resp.status await resp.text() return bool(resp_status == 200) + + async def download_images(self, vin: str): + """Download the car images and store it to the components ressource folder.""" + url = f"/v1/vehicle/{vin}/topviewimage" + return await self._request("get", url, rcp_headers=False, ignore_errors=False, return_as_json=False)