API zum Auslesen der Lade- / Verbrauchsdaten

Also braucht man evtl ZWEI verschiedene Endpunkte.
Den alten um die VIN zu bekommen und die neue für Batterie und Odometer Daten.

Ist deaktiviert für die v2 API :confused:

Scheint so, so macht es die Polestar Webseite grade auch…

Danke dir. Das erklärt, warum der erste Teil meiner Abfragen vorhin immer noch ging, bis Battery… abgefragt wurde. Na, da weiß ich, was ich heute Nacht zu tun habe :slight_smile:

Wie ärgerlich :frowning: :frowning: :frowning: :frowning: :frowning:

Mit der API v2 scheint es jetzt weniger „lag“ zu geben und die Werte sind momentan immer ziemlich schnell aktuell.

Hab das Medium Widget endlich auch mal aktualisiert auf die offizielle API.

Nach dem OTA-Update auf P2.14.3 zeigt die Polestar-API für Home Assistant noch immer P2.13 an.
Hat noch jemand das Phänomen?

Alle sonstigen Einträge (SoC, Kilometer usw.) sind aktuell.

1 „Gefällt mir“

Ja, hier. Alles stimmt bis auf die Version und dessen Veröffentlichungsdatum. Aber solange alles andere stimmt, soll es mich nicht weiter stören. :smiley:

1 „Gefällt mir“

Hab zwar keinen HA, aber auch meine API Anbindung zeigt nach dem Update noch die 2.13.1 vom 9.12.23.

1 „Gefällt mir“

Seid ihr nach dem Update schon damit gefahren?
Ich noch nicht. Vielleicht liegt’s daran.

Ja, hat trotzdem nicht geholfen.

Heute Morgen ist der Eintrag aktuell. Allerdings wurde auch die Instanz meines Servers neu gestartet.
Keine Ahnung, ob es daran lag.

Passt vielleicht nicht 100%-ig in dieses Thema, aber ich weiß nicht, wohin damit: im Laufe des Abends ist mein Fahrzeug aus meinem Konto verschwunden. Gemerkt hab ich das durch ein Home-Assistant-Update, aber ich kann mir nicht vorstellen, dass das der Auslöser ist.

Geht’s noch jemandem so? Vielleicht nur ein temporärer Ausfall?

For what it’s worth: nachdem hier keine Selbstheilung eingetreten war, hab ich heute den Support angeschrieben. Gab gerade zwei E-Mails: eine, dass mein Polestar aus meinem Konto entfernt wurde, und eine zweite, dass er wieder hinzugefügt wurde. Der Support schrieb dann noch

Sollte diese Verbindung in Zukunft wieder getrennt werden, dann melden Sie sich schnellstmöglich wieder bei uns, damit wir uns das mal anschauen können.

Sollte das also noch bei jemand anderem passieren, schnell dem Support Bescheid geben - es scheint sich um einen Bug zu handeln. :slight_smile:

Hallo zusammen.

Kann hier mal jemand eine Rest-Api Abfrage teilen, wie ich sie direkt im Browser nutzen könnte, um Daten zu meinem Fahrzeug zu bekommen? Danke dafür.

Wahrscheinlich wird das irgendwie so aussehen:
https://api.polestar.com/bridge?auth=a4309eba1dc7903ed858392ed5c85067&user=xy@mail.de

So einfach ist das nicht. Du musst dich erst authentifizieren und dir einen AuthToken holen, bevor du dir per GraphQl query tatsächlich Daten holen kannst.

Die Diskussion könnte ein guter Start für dich sein: Polestar API Down: New API Endpoint "/mystar-v2" · Issue #96 · leeyuentuen/polestar_api · GitHub

Hat das Thema jemand schon in NodeRed gelöst? ich bin zu dumm dafür und habe es leider noch nicht geschafft.

So, hab es nun selbst zusammen gebracht - Für alle die auch gerne NodeRed verwenden wollen:

[{"id":"fd45e125eed8839a","type":"function","z":"8f1e61194be045a8","name":"getAllCarData","func":"// Code added here will be run once\n// whenever the node is started.\n\nclass Polestar {\n    #credentials = {\n        email: null,\n        password: null,\n    }\n\n    #token = {\n        access: null,\n        refresh: null,\n        expires: null,\n    }\n\n    #vehicle = {\n        vin: null,\n        id: null,\n    }\n\n    constructor(email, password) {\n        if (!email || !password) {\n            throw new Error(\"Email and password must be provided\")\n        }\n        this.#credentials.email = email\n        this.#credentials.password = password\n    }\n\n    async login() {\n        const { pathToken, cookie } = await this.#getLoginFlowTokens()\n        const tokenRequestCode = await this.#performLogin(pathToken, cookie)\n        const apiCreds = await this.#getApiToken(tokenRequestCode)\n        await this.#storeToken(apiCreds)\n        if (!await this.#checkAuthenticated()) {\n            throw new Error(\"Login failed, token is not valid\")\n        }\n    }\n\n    async #storeToken(token) {\n        const apiCreds = token\n\n        const tokenExpiryTime = new Date()\n        const secondsToAdd = apiCreds.expires_in\n        tokenExpiryTime.setSeconds(tokenExpiryTime.getSeconds() + (secondsToAdd - 120))\n\n        this.#token.access = apiCreds.access_token\n        this.#token.refresh = apiCreds.refresh_token\n        this.#token.expires = tokenExpiryTime\n    }\n\n    async #checkAuthenticated() {\n        if (!this.#token.access || !this.#token.refresh || !this.#token.expires) {\n            throw new Error(\"Not logged in\")\n        }\n        if (this.#token.expires < new Date()) {\n            const apiCreds = await this.#refreshToken()\n            this.#storeToken(apiCreds)\n        }\n\n        if (await this.#checkTokenValidity()) {\n            return true\n        } else {\n            throw new Error(\"Token is not valid or refresh token has expired\")\n        }\n\n    }\n\n    async #refreshToken() {\n        console.log(\"Refreshing token\")\n        const response = await axios.post(\n            \"https://pc-api.polestar.com/eu-north-1/auth\",\n            \"{\\\"query\\\":\\\"\\\\n  query refreshAuthToken($token: String!) {\\\\n    refreshAuthToken(token: $token) {\\\\n      access_token\\\\n      expires_in\\\\n      id_token\\\\n      refresh_token\\\\n    }\\\\n  }\\\\n\\\",\\\"variables\\\":{\\\"token\\\":\\\"\" + this.#token.refresh + \"\\\"}}\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    \"Authorization\": \"Bearer \" + this.#token.access,\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n                validateStatus: function (status) {\n                    return true\n                },\n            }\n        )\n        const data = await response.data\n        const apiCreds = data.data.refreshAuthToken\n        return {\n            access_token: apiCreds.access_token,\n            refresh_token: apiCreds.refresh_token,\n            expires_in: apiCreds.expires_in,\n        }\n    }\n\n    async #checkTokenValidity() {\n        const response = await axios.get(\n            \"https://pc-api.polestar.com/eu-north-1/my-star/?query=query%20introspectToken(%24token%3A%20String!)%20%7B%0A%20%20introspectToken(token%3A%20%24token)%20%7B%0A%20%20%20%20active%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=introspectToken&variables=%7B%22token%22%3A%22\" + this.#token.access + \"%22%7D\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    \"Authorization\": \"Bearer \" + this.#token.access,\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n                validateStatus: function (status) {\n                    return true\n                },\n            }\n        )\n        const data = await response.data.data\n        if (!data.introspectToken || !data.introspectToken.active) {\n            return false\n        } else {\n            return true\n        }\n    }\n\n    async #performLogin(pathToken, cookie) {\n        const response = await axios.post(\n            \"https://polestarid.eu.polestar.com/as/\" + pathToken + \"/resume/as/authorization.ping?client_id=polmystar\",\n            {\n                \"pf.username\": this.#credentials.email,\n                \"pf.pass\": this.#credentials.password,\n            },\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/x-www-form-urlencoded\",\n                    pragma: \"no-cache\",\n                    cookie: cookie,\n                },\n                maxRedirects: 0,\n                validateStatus: function (status) {\n                    return true\n                },\n            }\n        )\n\n        const redirectUrl = response.headers.location\n        const regex = /code=([^&]+)/\n        const match = redirectUrl.match(regex)\n        const tokenRequestCode = match ? match[1] : null\n\n        return tokenRequestCode\n    }\n\n    async #getLoginFlowTokens() {\n        const response = await axios.get(\n            \"https://polestarid.eu.polestar.com/as/authorization.oauth2?response_type=code&client_id=polmystar&redirect_uri=https%3A%2F%2Fwww.polestar.com%2Fsign-in-callback&scope=openid%20profile%20email%20customer:attributes%20customer:attributes:write\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    pragma: \"no-cache\",\n                },\n                referrerPolicy: \"strict-origin-when-cross-origin\",\n                body: null,\n                maxRedirects: 0,\n                method: \"GET\",\n                validateStatus: function (status) {\n                    return true\n                },\n            }\n        )\n        const data = await response\n        const redirectUrl = response.headers.location\n        const regex = /resumePath=(\\w+)/\n        const match = redirectUrl.match(regex)\n        const pathToken = match ? match[1] : null\n        const cookies = response.headers[\"set-cookie\"]\n        const cookie = cookies[0].split('; ')[0] + \";\"\n        return {\n            pathToken: pathToken,\n            cookie: cookie\n        }\n    }\n\n    async #getApiToken(tokenRequestCode) {\n        const response = await axios.get(\n            \"https://pc-api.polestar.com/eu-north-1/auth/?query=query%20getAuthToken(%24code%3A%20String!)%20%7B%0A%20%20getAuthToken(code%3A%20%24code)%20%7B%0A%20%20%20%20id_token%0A%20%20%20%20access_token%0A%20%20%20%20refresh_token%0A%20%20%20%20expires_in%0A%20%20%7D%0A%7D%0A&operationName=getAuthToken&variables=%7B%22code%22%3A%22\" + tokenRequestCode + \"%22%7D\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n                validateStatus: function (status) {\n                    return true\n                },\n            }\n        )\n        const data = await response.data\n        const apiCreds = data.data.getAuthToken\n        return {\n            access_token: apiCreds.access_token,\n            refresh_token: apiCreds.refresh_token,\n            expires_in: apiCreds.expires_in,\n        }\n    }\n\n    async getVehicles() {\n        if (!await this.#checkAuthenticated()) {\n            throw new Error(\"Not authenticated\")\n        }\n        const response = await axios.get(\n            \"https://pc-api.polestar.com/eu-north-1/my-star/?query=query%20getCars%20%7B%0A%20%20getConsumerCarsV2%20%7B%0A%20%20%20%20vin%0A%20%20%20%20internalVehicleIdentifier%0A%20%20%20%20modelYear%0A%20%20%20%20content%20%7B%0A%20%20%20%20%20%20model%20%7B%0A%20%20%20%20%20%20%20%20code%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20images%20%7B%0A%20%20%20%20%20%20%20%20studio%20%7B%0A%20%20%20%20%20%20%20%20%20%20url%0A%20%20%20%20%20%20%20%20%20%20angles%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20hasPerformancePackage%0A%20%20%20%20registrationNo%0A%20%20%20%20deliveryDate%0A%20%20%20%20currentPlannedDeliveryDate%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=getCars&variables=%7B%7D\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    \"Authorization\": \"Bearer \" + this.#token.access,\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n            }\n        )\n        if (!response.data.data.getConsumerCarsV2) {\n            throw new Error(\"No vehicles found\")\n        }\n        const vehicles = response.data.data.getConsumerCarsV2\n        return vehicles\n    }\n\n    async setVehicle(vin) {\n        if (!await this.#checkAuthenticated()) {\n            throw new Error(\"Not authenticated\")\n        }\n        const vehicles = await this.getVehicles()\n        let vehicle = null\n        if (vin) {\n            vehicle = vehicles.find((vehicle) => vehicle.vin === vin)\n        } else {\n            vehicle = vehicles[0]\n        }\n        if (!vehicle) {\n            throw new Error(\"Vehicle not found\")\n        }\n        this.#vehicle.vin = vehicle.vin\n        this.#vehicle.id = vehicle.internalVehicleIdentifier\n\n        return this.#vehicle\n    }\n\n    async getBattery() {\n        if (!await this.#checkAuthenticated()) {\n            throw new Error(\"Not authenticated\")\n        }\n\n        if (!this.#vehicle.vin) {\n            throw new Error(\"No vehicle selected\")\n        }\n\n        const response = await axios.get(\n            \"https://pc-api.polestar.com/eu-north-1/mystar-v2?query=query%20GetBatteryData(%24vin%3A%20String!)%20%7B%0A%20%20getBatteryData(vin%3A%20%24vin)%20%7B%0A%20%20%20%20averageEnergyConsumptionKwhPer100Km%0A%20%20%20%20batteryChargeLevelPercentage%0A%20%20%20%20chargerConnectionStatus%0A%20%20%20%20chargingCurrentAmps%0A%20%20%20%20chargingPowerWatts%0A%20%20%20%20chargingStatus%0A%20%20%20%20estimatedChargingTimeMinutesToTargetDistance%0A%20%20%20%20estimatedChargingTimeToFullMinutes%0A%20%20%20%20estimatedDistanceToEmptyKm%0A%20%20%20%20estimatedDistanceToEmptyMiles%0A%20%20%20%20eventUpdatedTimestamp%20%7B%0A%20%20%20%20%20%20iso%0A%20%20%20%20%20%20unix%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=GetBatteryData&variables=%7B%22vin%22%3A%22\" + this.#vehicle.vin + \"%22%7D\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    \"Authorization\": \"Bearer \" + this.#token.access,\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n            }\n        )\n        const data = await response.data.data.getBatteryData\n        return data\n    }\n\n    async getOdometer() {\n        if (!await this.#checkAuthenticated()) {\n            throw new Error(\"Not authenticated\")\n        }\n\n        if (!this.#vehicle.vin) {\n            throw new Error(\"No vehicle selected\")\n        }\n\n        const response = await axios.get(\n            \"https://pc-api.polestar.com/eu-north-1/mystar-v2?query=query%20GetOdometerData(%24vin%3A%20String!)%20%7B%0A%20%20getOdometerData(vin%3A%20%24vin)%20%7B%0A%20%20%20%20averageSpeedKmPerHour%0A%20%20%20%20eventUpdatedTimestamp%20%7B%0A%20%20%20%20%20%20iso%0A%20%20%20%20%20%20unix%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20odometerMeters%0A%20%20%20%20tripMeterAutomaticKm%0A%20%20%20%20tripMeterManualKm%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D&operationName=GetOdometerData&variables=%7B%22vin%22%3A%22\" + this.#vehicle.vin + \"%22%7D\",\n            {\n                headers: {\n                    \"cache-control\": \"no-cache\",\n                    \"content-type\": \"application/json\",\n                    \"Authorization\": \"Bearer \" + this.#token.access,\n                    pragma: \"no-cache\",\n                },\n                maxRedirects: 0,\n            }\n        )\n        const data = await response.data.data.getOdometerData\n        return data\n    }\n}\n\n\n\nconst polestar = new Polestar(\"DEINE EMAIL\", \"DEIN PASSWORD\");\nawait polestar.login();\nconst vehicles = await polestar.getVehicles();\nawait polestar.setVehicle(vehicles[0].vin);\nconst batt = await polestar.getBattery();\nconst odo = await polestar.getOdometer();\n\nmsg.odo = odo;\nmsg.vehicles = vehicles;\nmsg.bat = batt;\nmsg.payload = vehicles[0].content.model.name;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"axios","module":"axios"}],"x":700,"y":200,"wires":[["cd43d91816497c1e","5667b87be88d9f5d","34467ccf11dbacf2","a0aec7100a98d524","d5f544c8916f6ffd","9737340fef439758","377e31a58cc94d81","d59067120006bbd4"]]},{"id":"bf870353702a3547","type":"inject","z":"8f1e61194be045a8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":440,"y":200,"wires":[["fd45e125eed8839a"]]},{"id":"cd43d91816497c1e","type":"debug","z":"8f1e61194be045a8","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":980,"y":120,"wires":[]},{"id":"71042cdc04822d04","type":"debug","z":"8f1e61194be045a8","name":"Soc","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1350,"y":200,"wires":[]},{"id":"5667b87be88d9f5d","type":"function","z":"8f1e61194be045a8","name":"SoC","func":"msg.payload = msg.bat.batteryChargeLevelPercentage;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":200,"wires":[["71042cdc04822d04"]]},{"id":"a0aec7100a98d524","type":"function","z":"8f1e61194be045a8","name":"Verbrauch/100km","func":"msg.payload = msg.bat.averageEnergyConsumptionKwhPer100Km\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":300,"wires":[["00ba973a886207f7"]]},{"id":"d5f544c8916f6ffd","type":"function","z":"8f1e61194be045a8","name":"Steckerstatus","func":"msg.payload = msg.bat.chargerConnectionStatus\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":350,"wires":[["d4237b28ebf84db4"]]},{"id":"34467ccf11dbacf2","type":"function","z":"8f1e61194be045a8","name":"Distance","func":"msg.payload = msg.bat.estimatedDistanceToEmptyKm\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":960,"y":250,"wires":[["2ca76bfddd6b8b9e"]]},{"id":"9737340fef439758","type":"function","z":"8f1e61194be045a8","name":"Restladezeit","func":"msg.payload = msg.bat.estimatedChargingTimeToFullMinutes;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":970,"y":400,"wires":[["97cfe32175d2262d"]]},{"id":"377e31a58cc94d81","type":"function","z":"8f1e61194be045a8","name":"Kilometerstand","func":"msg.payload = msg.odo.odometerMeters / 1000;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":980,"y":450,"wires":[["4fc69f5975c3c82c"]]},{"id":"d59067120006bbd4","type":"function","z":"8f1e61194be045a8","name":"Durchschnitsgeschwindigkeit","func":"msg.payload = msg.odo.averageSpeedKmPerHour;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1020,"y":500,"wires":[["fa41b75b538a01b1"]]},{"id":"2ca76bfddd6b8b9e","type":"debug","z":"8f1e61194be045a8","name":"Reichweite","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1370,"y":250,"wires":[]},{"id":"00ba973a886207f7","type":"debug","z":"8f1e61194be045a8","name":"Durchschnitsverbrauch","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1400,"y":300,"wires":[]},{"id":"d4237b28ebf84db4","type":"debug","z":"8f1e61194be045a8","name":"Steckerstatus","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1380,"y":350,"wires":[]},{"id":"4fc69f5975c3c82c","type":"debug","z":"8f1e61194be045a8","name":"Kilometerstand","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1380,"y":450,"wires":[]},{"id":"fa41b75b538a01b1","type":"debug","z":"8f1e61194be045a8","name":"Durchschnitsgeschwindigkeit","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1420,"y":500,"wires":[]},{"id":"97cfe32175d2262d","type":"debug","z":"8f1e61194be045a8","name":"Restladezeit","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1370,"y":400,"wires":[]}]
1 „Gefällt mir“

Hey @bausi2k
Das funktioniert perfekt !!!
Vielen herzlichen Dank für das Publizieren :+1:t2::blush:
Andi

1 „Gefällt mir“