def __decrypt_payload_using_key(self, payload): if self.enc_key is None or self.enc_key == "": raise MazdaException("Missing encryption key") buf = base64.b64decode(payload) decrypted = decrypt_aes128cbc_buffer_to_str(buf, self.enc_key, IV) return json.loads(decrypted)
async def set_hvac_setting(self, internal_vin, temperature, temperature_unit, front_defroster, rear_defroster): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin, "hvacsettings": { "FrontDefroster": 1 if front_defroster else 0, "RearDefogger": 1 if rear_defroster else 0, "Temperature": temperature, "TemperatureType": 1 if temperature_unit.lower() == "c" else 2 } } response = await self.connection.api_request( "POST", "remoteServices/updateHVACSetting/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to set HVAC setting") return response
async def send_poi(self, internal_vin, latitude, longitude, name): # Calculate a POI ID that is unique to the name and location poi_id = hashlib.sha256((str(name) + str(latitude) + str(longitude)).encode()).hexdigest()[0:10] post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin, "placemarkinfos": [{ "Altitude": 0, "Latitude": abs(latitude), "LatitudeFlag": 0 if (latitude >= 0) else 1, "Longitude": abs(longitude), "LongitudeFlag": 0 if (longitude < 0) else 1, "Name": name, "OtherInformation": "{}", "PoiId": poi_id, "source": "google" }] } response = await self.connection.api_request( "POST", "remoteServices/sendPOI/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to send POI")
async def get_nickname(self, vin): if len(vin) != 17: raise MazdaException("Invalid VIN") post_body = {"internaluserid": "__INTERNAL_ID__", "vin": vin} response = await self.connection.api_request( "POST", "remoteServices/getNickName/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to get vehicle nickname") return response["carlineDesc"]
def __encrypt_payload_using_key(self, payload): if self.enc_key is None or self.enc_key == "": raise MazdaException("Missing encryption key") if payload is None or payload == "": return "" return encrypt_aes128cbc_buffer_to_base64_str(payload.encode("utf-8"), self.enc_key, IV)
def __get_sign_from_payload_and_timestamp(self, payload, timestamp): if timestamp is None or timestamp == "": return "" if self.sign_key is None or self.sign_key == "": raise MazdaException("Missing sign key") return self.__get_payload_sign( self.__encrypt_payload_using_key(payload) + timestamp + timestamp[6:] + timestamp[3:], self.sign_key)
async def __api_request_retry(self, method, uri, query_dict={}, body_dict={}, needs_keys=True, needs_auth=False, num_retries=0): if num_retries > MAX_RETRIES: raise MazdaException("Request exceeded max number of retries") if needs_keys: await self.__ensure_keys_present() if needs_auth: await self.__ensure_token_is_valid() retry_message = (" - attempt #" + str(num_retries + 1)) if (num_retries > 0) else "" self.logger.debug(f"Sending {method} request to {uri}{retry_message}") try: return await self.__send_api_request(method, uri, query_dict, body_dict, needs_keys, needs_auth) except (MazdaAPIEncryptionException): self.logger.debug( "Server reports request was not encrypted properly. Retrieving new encryption keys." ) await self.__retrieve_keys() return await self.__api_request_retry(method, uri, query_dict, body_dict, needs_keys, needs_auth, num_retries + 1) except (MazdaTokenExpiredException): self.logger.debug( "Server reports access token was expired. Retrieving new access token." ) await self.login() return await self.__api_request_retry(method, uri, query_dict, body_dict, needs_keys, needs_auth, num_retries + 1) except (MazdaLoginFailedException): self.logger.debug( "Login failed for an unknown reason. Trying again.") await self.login() return await self.__api_request_retry(method, uri, query_dict, body_dict, needs_keys, needs_auth, num_retries + 1) except (MazdaRequestInProgressException): self.logger.debug( "Request failed because another request was already in progress. Waiting 30 seconds and trying again." ) await asyncio.sleep(30) return await self.__api_request_retry(method, uri, query_dict, body_dict, needs_keys, needs_auth, num_retries + 1)
async def update_nickname(self, vin, new_nickname): if len(vin) != 17: raise MazdaException("Invalid VIN") if len(new_nickname) > 20: raise MazdaException("Nickname is too long") post_body = { "internaluserid": "__INTERNAL_ID__", "vin": vin, "vtitle": new_nickname } response = await self.connection.api_request( "POST", "remoteServices/updateNickName/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to update vehicle nickname")
async def door_unlock(self, internal_vin): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin } response = await self.connection.api_request( "POST", "remoteServices/doorUnlock/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to unlock door") return response
async def refresh_vehicle_status(self, internal_vin): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin } response = await self.connection.api_request( "POST", "remoteServices/activeRealTimeVehicleStatus/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to refresh vehicle status") return response
async def get_hvac_setting(self, internal_vin): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin } response = await self.connection.api_request( "POST", "remoteServices/getHVACSetting/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to get HVAC setting") return response
async def get_health_report(self, internal_vin): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin, "limit": 1, "offset": 0 } response = await self.connection.api_request( "POST", "remoteServices/getHealthReport/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to get health report") return response
async def get_ev_vehicle_status(self, internal_vin): post_body = { "internaluserid": "__INTERNAL_ID__", "internalvin": internal_vin, "limit": 1, "offset": 0, "vecinfotype": "0" } response = await self.connection.api_request( "POST", "remoteServices/getEVVehicleStatus/v4", body_dict=post_body, needs_keys=True, needs_auth=True) if response["resultCode"] != "200S00": raise MazdaException("Failed to get EV vehicle status") return response
async def __send_api_request(self, method, uri, query_dict={}, body_dict={}, needs_keys=True, needs_auth=False): timestamp = self.__get_timestamp_str_ms() original_query_str = "" encrypted_query_dict = {} if query_dict: original_query_str = urlencode(query_dict) encrypted_query_dict["params"] = self.__encrypt_payload_using_key( original_query_str) original_body_str = "" encrypted_body_Str = "" if body_dict: original_body_str = json.dumps(body_dict) encrypted_body_Str = self.__encrypt_payload_using_key( original_body_str) headers = { "device-id": self.base_api_device_id, "app-code": self.app_code, "app-os": APP_OS, "user-agent": USER_AGENT_BASE_API, "app-version": APP_VERSION, "app-unique-id": APP_PACKAGE_ID, "access-token": (self.access_token if needs_auth else ""), "X-acf-sensor-data": "", "req-id": "req_" + timestamp, "timestamp": timestamp } if "checkVersion" in uri: headers["sign"] = self.__get_sign_from_timestamp(timestamp) elif method == "GET": headers["sign"] = self.__get_sign_from_payload_and_timestamp( original_query_str, timestamp) elif method == "POST": headers["sign"] = self.__get_sign_from_payload_and_timestamp( original_body_str, timestamp) response = await self._session.request(method, self.base_url + uri, headers=headers, data=encrypted_body_Str) response_json = await response.json() if response_json["state"] == "S": if "checkVersion" in uri: return self.__decrypt_payload_using_app_code( response_json["payload"]) else: return self.__decrypt_payload_using_key( response_json["payload"]) elif response_json["errorCode"] == 600001: raise MazdaAPIEncryptionException( "Server rejected encrypted request") elif response_json["errorCode"] == 600002: raise MazdaTokenExpiredException("Token expired") else: raise MazdaException("Request failed for an unknown reason")
async def __send_api_request(self, method, uri, query_dict={}, body_dict={}, needs_keys=True, needs_auth=False): timestamp = self.__get_timestamp_str_ms() original_query_str = "" encrypted_query_dict = {} if query_dict: original_query_str = urlencode(query_dict) encrypted_query_dict["params"] = self.__encrypt_payload_using_key( original_query_str) original_body_str = "" encrypted_body_Str = "" if body_dict: original_body_str = json.dumps(body_dict) encrypted_body_Str = self.__encrypt_payload_using_key( original_body_str) headers = { "device-id": self.base_api_device_id, "app-code": self.app_code, "app-os": APP_OS, "user-agent": USER_AGENT_BASE_API, "app-version": APP_VERSION, "app-unique-id": APP_PACKAGE_ID, "access-token": (self.access_token if needs_auth else ""), "X-acf-sensor-data": self.sensor_data_builder.generate_sensor_data(), "req-id": "req_" + timestamp, "timestamp": timestamp } if "checkVersion" in uri: headers["sign"] = self.__get_sign_from_timestamp(timestamp) elif method == "GET": headers["sign"] = self.__get_sign_from_payload_and_timestamp( original_query_str, timestamp) elif method == "POST": headers["sign"] = self.__get_sign_from_payload_and_timestamp( original_body_str, timestamp) response = await self._session.request(method, self.base_url + uri, headers=headers, data=encrypted_body_Str) response_json = await response.json() if response_json.get("state") == "S": if "checkVersion" in uri: return self.__decrypt_payload_using_app_code( response_json["payload"]) else: decrypted_payload = self.__decrypt_payload_using_key( response_json["payload"]) self.logger.debug("Response payload: %s", decrypted_payload) return decrypted_payload elif response_json.get("errorCode") == 600001: raise MazdaAPIEncryptionException( "Server rejected encrypted request") elif response_json.get("errorCode") == 600002: raise MazdaTokenExpiredException("Token expired") elif response_json.get("errorCode") == 920000 and response_json.get( "extraCode") == "400S01": raise MazdaRequestInProgressException( "Request already in progress, please wait and try again") elif response_json.get("errorCode") == 920000 and response_json.get( "extraCode") == "400S11": raise MazdaException( "The engine can only be remotely started 2 consecutive times. Please drive the vehicle to reset the counter." ) elif "error" in response_json: raise MazdaException("Request failed: " + response_json["error"]) else: raise MazdaException("Request failed for an unknown reason")