diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a8a9a4d --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(node -e:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/.gitignore b/.gitignore index 1fb5f1d..f72fcd5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ drivers/polestar-2/* drivers/polestar-2-beta/* .homeyignore -/env.json \ No newline at end of file +/env_secret.json \ No newline at end of file diff --git a/.homeychangelog.json b/.homeychangelog.json index 09f34c3..5d77cdf 100644 --- a/.homeychangelog.json +++ b/.homeychangelog.json @@ -70,5 +70,77 @@ }, "2.1.2": { "en": "Removed summary images from the device" + }, + "2.2.0": { + "en": "Added support for charge speed details" + }, + "2.3.0": { + "en": "Changed polestar vehicle to new device class car" + }, + "2.3.1": { + "en": "Fix for trigger events" + }, + "2.3.2": { + "en": "hotfix on the csv driver" + }, + "2.3.3": { + "en": "Improved insights on power measurements" + }, + "2.3.4": { + "en": "update to fix api changes" + }, + "2.3.5": { + "en": "Update npm modules" + }, + "2.4.0": { + "en": "Widget, service warning and login bug fix" + }, + "2.4.1": { + "en": "preview images" + }, + "2.4.2": { + "en": "fix widget update on device state change" + }, + "2.4.3": { + "en": "stability improvement" + }, + "2.4.4": { + "en": "service warning improvement" + }, + "2.4.5": { + "en": "Update preview of widget" + }, + "2.4.6": { + "en": "Fix for another api change on polestar side" + }, + "2.5.0": { + "en": "Added power meter with estimated overall usage" + }, + "2.5.1": { + "en": "Attempt to fix new api issue" + }, + "2.6.0": { + "en": "Authentication fix and new capabilities" + }, + "2.6.1": { + "en": "Another login fix" + }, + "2.6.2": { + "en": "Fine tune the login error message" + }, + "2.7.1": { + "en": "Added support for new ev charging states" + }, + "2.7.2": { + "en": "Fixed CSV receiver driver" + }, + "2.7.3": { + "en": "Added upgrade path for existing cars for homey energy" + }, + "2.7.4": { + "en": "More rebost energy upgrade detection" + }, + "2.7.5": { + "en": "Changed api, no linger supports images" } } diff --git a/.homeycompose/app.json b/.homeycompose/app.json index edcabdc..d1e60f9 100644 --- a/.homeycompose/app.json +++ b/.homeycompose/app.json @@ -1,18 +1,20 @@ { "id": "com.Coderax.Polestar", - "version": "2.1.2", - "compatibility": ">=5.0.0", + "version": "2.7.5", + "compatibility": ">=12.4.5", "sdk": 3, "platforms": [ "local" ], "name": { "en": "Polestar", - "no": "Polestar" + "no": "Polestar", + "nl": "Polestar" }, "description": { "en": "Enjoy the road with Polestar and Homey – smart technology for smart drivers", - "no": "Nyt veien med Polestar og Homey – smart teknologi for smarte sjåfører" + "no": "Nyt veien med Polestar og Homey – smart teknologi for smarte sjåfører", + "nl": "Geniet van de weg met Polestar en Homey – Slimme technologie voor slimme bestuurders" }, "category": "internet", "permissions": [], @@ -22,20 +24,22 @@ "xlarge": "/assets/images/xlarge.png" }, "author": { - "name": "Jesper Grimstad", - "email": "jesper.grimstad@hotmail.com" + "name": "Vincent Boer", + "email": "vincent+homey@vdboer.nl" }, "brandColor": "#081822", - "support": "mailto:polestar@coderax.dev?subject=Polestar%20-%20Homey%20app", - "contributing": { - "donate": { - "paypal": { - "username": "Coderaxxx" + "support": "mailto:vincent+polestar@vdboer.nl?subject=Polestar%20-%20Homey%20app", + "contributors": { + "developers": [ + { + "name": "Jesper Grimstad", + "email": "jesper.grimstad@hotmail.com" }, - "githubSponsors": { - "username": "Coderaxx" + { + "name": "Vincent Boer", + "email": "vincent+homey@vdboer.nl" } - } + ] }, "tags": { "en": [ @@ -59,12 +63,25 @@ "ev", "ev car", "smart car" + ], + "nl": [ + "polestar", + "pole", + "star", + "auto", + "electrisch", + "electrische auto", + "ev", + "ev auto", + "bev", + "bev auto", + "slimme auto", + "batterij auto" ] }, "homeyCommunityTopicId": 95083, - "homepage": "https://coderax.dev", - "source": "https://github.com/Coderaxx/Polestar", + "source": "https://github.com/kaohlive/Polestar", "bugs": { - "url": "https://github.com/Coderaxx/Polestar/issues" + "url": "https://github.com/kaohlive/Polestar/issues" } } \ No newline at end of file diff --git a/.homeycompose/capabilities/measure_polestarAlt.json b/.homeycompose/capabilities/measure_polestarAlt.json index acadf49..024fb4d 100644 --- a/.homeycompose/capabilities/measure_polestarAlt.json +++ b/.homeycompose/capabilities/measure_polestarAlt.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/altitude.svg", "title": { "en": "Altitude", - "no": "Høyde" + "no": "Høyde", + "nl": "Hoogte" }, "units": "m", "getable": true, diff --git a/.homeycompose/capabilities/measure_polestarBattery.json b/.homeycompose/capabilities/measure_polestarBattery.json index bc7a96a..c69c5c5 100644 --- a/.homeycompose/capabilities/measure_polestarBattery.json +++ b/.homeycompose/capabilities/measure_polestarBattery.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/battery-75.svg", "title": { "en": "Battery", - "no": "Batteri" + "no": "Batteri", + "nl": "Batterij" }, "units": "%", "getable": true, diff --git a/.homeycompose/capabilities/measure_polestarBatteryLevel.json b/.homeycompose/capabilities/measure_polestarBatteryLevel.json index 57f00c8..0b590ca 100644 --- a/.homeycompose/capabilities/measure_polestarBatteryLevel.json +++ b/.homeycompose/capabilities/measure_polestarBatteryLevel.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/hvbattery.svg", "title": { "en": "Battery level", - "no": "Batterinivå" + "no": "Batterinivå", + "nl": "Batterij niveau" }, "units": "kWh", "decimals": 2, diff --git a/.homeycompose/capabilities/measure_polestarChargeState.json b/.homeycompose/capabilities/measure_polestarChargeState.json index 0f1dae0..53b19a2 100644 --- a/.homeycompose/capabilities/measure_polestarChargeState.json +++ b/.homeycompose/capabilities/measure_polestarChargeState.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/charging.svg", "title": { "en": "Charging", - "no": "Lader" + "no": "Lader", + "nl": "Laden" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarConnected.json b/.homeycompose/capabilities/measure_polestarConnected.json index b6860ce..9f017eb 100644 --- a/.homeycompose/capabilities/measure_polestarConnected.json +++ b/.homeycompose/capabilities/measure_polestarConnected.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/ccs.svg", "title": { "en": "Charge port", - "no": "Ladeport" + "no": "Ladeport", + "nl": "Laadpoort" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarGear.json b/.homeycompose/capabilities/measure_polestarGear.json index 6706146..36c3e18 100644 --- a/.homeycompose/capabilities/measure_polestarGear.json +++ b/.homeycompose/capabilities/measure_polestarGear.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/gear.svg", "title": { "en": "Selected Gear", - "no": "Valgt gir" + "no": "Valgt gir", + "nl": "Geselecteerde versnelling" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarIgnitionState.json b/.homeycompose/capabilities/measure_polestarIgnitionState.json index 25bf5f0..6a01ee6 100644 --- a/.homeycompose/capabilities/measure_polestarIgnitionState.json +++ b/.homeycompose/capabilities/measure_polestarIgnitionState.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/ignition.svg", "title": { "en": "Ignition", - "no": "Tenning" + "no": "Tenning", + "nl": "Ontsteking" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarLocation.json b/.homeycompose/capabilities/measure_polestarLocation.json index 0217706..97992c1 100644 --- a/.homeycompose/capabilities/measure_polestarLocation.json +++ b/.homeycompose/capabilities/measure_polestarLocation.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/location.svg", "title": { "en": "Location", - "no": "Plassering" + "no": "Plassering", + "nl": "Locatie" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarMonthlyCharge.json b/.homeycompose/capabilities/measure_polestarMonthlyCharge.json index c319856..4457028 100644 --- a/.homeycompose/capabilities/measure_polestarMonthlyCharge.json +++ b/.homeycompose/capabilities/measure_polestarMonthlyCharge.json @@ -4,7 +4,8 @@ "icon": "/drivers/vehicle/assets/charging.svg", "title": { "en": "Charged home this month", - "no": "Ladet hjemme denne mnd" + "no": "Ladet hjemme denne mnd", + "nl": "Deze maand thuis geladen" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarPower.json b/.homeycompose/capabilities/measure_polestarPower.json index 368dfcb..fffc9ee 100644 --- a/.homeycompose/capabilities/measure_polestarPower.json +++ b/.homeycompose/capabilities/measure_polestarPower.json @@ -5,7 +5,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/powerdc.svg", "title": { "en": "Power", - "no": "Effekt" + "no": "Effekt", + "nl": "Acceleratie" }, "units": "kW", "getable": true, diff --git a/.homeycompose/capabilities/measure_polestarRange.json b/.homeycompose/capabilities/measure_polestarRange.json index 738913b..e0d23e7 100644 --- a/.homeycompose/capabilities/measure_polestarRange.json +++ b/.homeycompose/capabilities/measure_polestarRange.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/range.svg", "title": { "en": "Range", - "no": "Rekkevidde" + "no": "Rekkevidde", + "nl": "Bereik" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_polestarSpeed.json b/.homeycompose/capabilities/measure_polestarSpeed.json index 4aedebf..1aa52bf 100644 --- a/.homeycompose/capabilities/measure_polestarSpeed.json +++ b/.homeycompose/capabilities/measure_polestarSpeed.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/speedometer.svg", "title": { "en": "Speed", - "no": "Fart" + "no": "Fart", + "nl": "Snelheid" }, "units": { "en": "km/h", diff --git a/.homeycompose/capabilities/measure_polestarTemp.json b/.homeycompose/capabilities/measure_polestarTemp.json index 586f21a..40833f6 100644 --- a/.homeycompose/capabilities/measure_polestarTemp.json +++ b/.homeycompose/capabilities/measure_polestarTemp.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/temp.svg", "title": { "en": "Ambient Temperature", - "no": "Utetemperatur" + "no": "Utetemperatur", + "nl": "Buitentemperatuur" }, "units": "°C", "getable": true, diff --git a/.homeycompose/capabilities/measure_polestarUpdated.json b/.homeycompose/capabilities/measure_polestarUpdated.json index dafbdb0..e3f323d 100644 --- a/.homeycompose/capabilities/measure_polestarUpdated.json +++ b/.homeycompose/capabilities/measure_polestarUpdated.json @@ -4,7 +4,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/update.svg", "title": { "en": "Updated", - "no": "Oppdatert" + "no": "Oppdatert", + "nl": "Bijgewerkt" }, "getable": true, "setable": false, diff --git a/.homeycompose/capabilities/measure_vehicleConnected.json b/.homeycompose/capabilities/measure_vehicleConnected.json index 34496d6..0998cd1 100644 --- a/.homeycompose/capabilities/measure_vehicleConnected.json +++ b/.homeycompose/capabilities/measure_vehicleConnected.json @@ -2,7 +2,7 @@ "type": "boolean", "title": { "en": "Chargeport connected", - "no": "Ladepoort tilkoblet", + "no": "Ladeport tilkoblet", "nl": "Laadpoort verbonden" }, "getable": true, diff --git a/.homeycompose/capabilities/measure_vehicleDaysTillService.json b/.homeycompose/capabilities/measure_vehicleDaysTillService.json new file mode 100644 index 0000000..847d25a --- /dev/null +++ b/.homeycompose/capabilities/measure_vehicleDaysTillService.json @@ -0,0 +1,11 @@ +{ + "type": "number", + "title": { + "en": "Days till service", + "no": "Dager til service", + "nl": "Dagen tot service" + }, + "getable": true, + "setable": false, + "icon": "/drivers/vehicle/assets/timeremaining.svg" +} \ No newline at end of file diff --git a/.homeycompose/capabilities/measure_vehicleDistanceTillService.json b/.homeycompose/capabilities/measure_vehicleDistanceTillService.json new file mode 100644 index 0000000..18bc48e --- /dev/null +++ b/.homeycompose/capabilities/measure_vehicleDistanceTillService.json @@ -0,0 +1,14 @@ +{ + "type": "number", + "title": { + "en": "Distance till service", + "no": "Avstand til service", + "nl": "Afstand tot service" + }, + "getable": true, + "setable": false, + "units": { + "en": "KM" + }, + "icon": "/drivers/vehicle/assets/odometer.svg" +} \ No newline at end of file diff --git a/.homeycompose/flow/triggers/chargeportconnected_false.json b/.homeycompose/flow/triggers/chargeportconnected_false.json deleted file mode 100644 index bb8748b..0000000 --- a/.homeycompose/flow/triggers/chargeportconnected_false.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": { - "en": "Car disconnected from a charger", - "no": "Bil frakoblet lader" - }, - "hint": { - "en": "When the car detects it is no longer connected to a charge port", - "no": "Når bilen oppdager at ladepunktet er frakoblet" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ] -} \ No newline at end of file diff --git a/.homeycompose/flow/triggers/chargeportconnected_true.json b/.homeycompose/flow/triggers/chargeportconnected_true.json deleted file mode 100644 index b5aba39..0000000 --- a/.homeycompose/flow/triggers/chargeportconnected_true.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": { - "en": "Car connected to a charger", - "no": "Bil tilkoblet lader" - }, - "hint": { - "en": "When the car detects a charger connected to a charge port", - "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ] -} \ No newline at end of file diff --git a/.homeycompose/flow/triggers/charging_false.json b/.homeycompose/flow/triggers/charging_false.json deleted file mode 100644 index f3ebbaf..0000000 --- a/.homeycompose/flow/triggers/charging_false.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": { - "en": "Car stopped charging", - "no": "Lading stoppet" - }, - "hint": { - "en": "When the car stopped drawing power from the socket", - "no": "Når bilen sluttet å trekke strøm fra laderen" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ] -} \ No newline at end of file diff --git a/.homeycompose/flow/triggers/charging_true.json b/.homeycompose/flow/triggers/charging_true.json deleted file mode 100644 index d2a699d..0000000 --- a/.homeycompose/flow/triggers/charging_true.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "title": { - "en": "Car started charging", - "no": "Lading startet" - }, - "hint": { - "en": "When the car started to draw power from the socket", - "no": "Når bilen startet å trekke strøm fra laderen" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ] -} \ No newline at end of file diff --git a/API_FIELD_DISCOVERY_REPORT.md b/API_FIELD_DISCOVERY_REPORT.md new file mode 100644 index 0000000..c93171b --- /dev/null +++ b/API_FIELD_DISCOVERY_REPORT.md @@ -0,0 +1,244 @@ +# Polestar API Field Discovery Report + +**Date:** January 2025 +**API Endpoint:** `https://pc-api.polestar.com/eu-north-1/mystar-v2/` +**Test Vehicle:** Polestar 4 (2026 Model Year) + +## Executive Summary + +The Polestar GraphQL API has been significantly simplified and locked down. Many fields that were previously available (or referenced in older implementations) have been removed. GraphQL introspection is disabled, preventing automatic schema discovery. + +## ✅ Currently Available Fields + +### Vehicle Information (`getConsumerCarsV2`) +```graphql +query getCars { + getConsumerCarsV2 { + vin + internalVehicleIdentifier + modelYear + content { + model { + code + name + } + } + hasPerformancePackage + registrationNo + deliveryDate + currentPlannedDeliveryDate + } +} +``` + +**Note:** The `images` field has been removed from the API (previously contained `studio.url` and `studio.angles`). + +### Telematics Data (`carTelematicsV2`) + +#### Battery Information +```graphql +battery { + vin + batteryChargeLevelPercentage + chargingStatus + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + estimatedDistanceToEmptyMiles + timestamp { + seconds + nanos + } +} +``` + +**Available Charging Statuses:** +- `CHARGING_STATUS_IDLE` +- `CHARGING_STATUS_CHARGING` +- `CHARGING_STATUS_DONE` +- `CHARGING_STATUS_SCHEDULED` +- `CHARGING_STATUS_SMART_CHARGING` +- `CHARGING_STATUS_ERROR` +- `CHARGING_STATUS_FAULT` + +#### Odometer Information +```graphql +odometer { + vin + odometerMeters + timestamp { + seconds + nanos + } +} +``` + +#### Health Information +```graphql +health { + vin + brakeFluidLevelWarning + daysToService + distanceToServiceKm + engineCoolantLevelWarning + oilLevelWarning + serviceWarning + timestamp { + seconds + nanos + } +} +``` + +## ❌ Unavailable Fields (Tested & Confirmed) + +### Battery Fields (No Longer Available) +- `chargingCurrentAmps` - Charging current in amperes +- `chargingPowerWatts` - Charging power in watts +- `averageEnergyConsumptionKwhPer100Km` - Energy consumption average +- `chargerConnectionStatus` - Whether charger is physically connected +- `estimatedChargingTimeMinutesToTargetDistance` - Time to target range + +### Odometer Fields (No Longer Available) +- `averageSpeedKmPerHour` - Average vehicle speed +- `tripMeterAutomaticKm` - Automatic trip meter +- `tripMeterManualKm` - Manual trip meter + +### Health Fields (No Longer Available) +- `washerFluidLevelWarning` - Washer fluid level warning + +### Telematics Categories (Not Available) +- **Location Data** - No GPS coordinates, latitude, longitude, or heading + - Tested: `location`, `vehicleLocation`, `getCarLocation`, `getConsumerCarByVin` with location fields +- **Climate Data** - No HVAC or temperature information + - Tested: `climate` with `climateStatus`, `targetTemperature`, `interiorTemperature` +- **Lock Status** - No door lock information + - Tested: `locks` with various door lock fields +- **Window Status** - No window open/close status + - Tested: `windows` with window status fields + +### Old API Queries (Deprecated) +These queries worked in older implementations but no longer function: +- `getBatteryData` - Old battery data query +- `getOdometerData` - Old odometer query with trip meters +- `carTelematics` (non-V2) - Original telematics query +- `getChargingConnectionStatus` - Charging connection details + +## 📊 API Changes Timeline + +Based on research of other projects: + +- **Pre-January 2024:** Old API (`/my-star`) with extensive fields including: + - Charging power and current + - Trip meters + - Average speed + - Energy consumption + +- **January 2024:** API migration to `/mystar-v2` + - Many fields removed + - `getBatteryData` and `getOdometerData` deprecated + - Switch to `carTelematicsV2` with limited fields + +- **Current (January 2025):** Further restrictions + - Images removed from vehicle data + - No location data available via API + - No charging power/amperage data + - Introspection disabled + +## 🔍 Research Sources + +Investigation included: +1. **pypolestar/polestar_api** - Python Home Assistant integration +2. **evcc-io/evcc** - EV charging control system +3. **Direct API testing** - 15+ different query combinations tested +4. **GraphQL introspection** - Attempted but disabled by Polestar + +## 💡 Workarounds for Missing Data + +### Location Data +**Problem:** No GPS coordinates available via API +**Workaround:** Install Home Assistant app in Android Automotive (vehicle's built-in system) and use device_tracker entity. This requires: +- Home Assistant installation +- Nabu Casa account (optional but recommended) +- Permission to share location from vehicle + +### Charging Power/Current +**Problem:** No charging watts or amps available +**Workaround:** None found. This data was previously available but has been removed from the API. + +### Trip Meters & Average Speed +**Problem:** Not available in current API +**Workaround:** Calculate manually using odometer readings over time (less accurate). + +## 🎯 Recommendations + +### For Your Homey App + +1. **Remove commented-out code** in `device.js` lines 136-149: + - `chargingCurrentAmps` will never return data + - `chargingPowerWatts` will never return data + - `chargerConnectionStatus` is not available + +2. **Current implementation is optimal** - You're already using all available fields: + ```javascript + // Battery + batteryChargeLevelPercentage + chargingStatus + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + + // Odometer + odometerMeters + + // Health + brakeFluidLevelWarning + daysToService + distanceToServiceKm + engineCoolantLevelWarning + oilLevelWarning + serviceWarning + ``` + +3. **Connection detection workaround** - Your current logic (lines 155-171) correctly uses `chargingStatus` to infer connection: + ```javascript + const connectedStatuses = new Set([ + 'CHARGING_STATUS_CHARGING', + 'CHARGING_STATUS_DONE', + 'CHARGING_STATUS_SCHEDULED', + 'CHARGING_STATUS_SMART_CHARGING', + 'CHARGING_STATUS_ERROR', + 'CHARGING_STATUS_FAULT' + ]); + ``` + This is the best available approach since `chargerConnectionStatus` is gone. + +### For Future Development + +1. **Monitor for API changes** - Polestar has been changing the API periodically +2. **No additional fields can be added** - The API currently provides minimal data +3. **Location tracking requires alternative solution** - Consider Home Assistant integration if needed +4. **Energy monitoring limitations** - Without power/current data, detailed energy monitoring is not possible + +## 📝 Conclusion + +The Polestar API has evolved from a feature-rich interface to a minimal read-only API providing only basic telematics: +- ✅ Battery level and charging status +- ✅ Range estimates +- ✅ Odometer reading +- ✅ Basic service warnings +- ❌ No location data +- ❌ No charging power/current +- ❌ No trip meters or speed data +- ❌ No climate, lock, or window status + +**Your current implementation is using all available API fields.** The commented-out code in your device.js file represents features that were likely planned but are no longer possible due to API restrictions. + +## 🔗 References + +- GitHub: pypolestar/polestar_api (Python implementation) +- GitHub: evcc-io/evcc (Go implementation for EV charging) +- Polestar Forum discussions on API access +- Direct GraphQL API testing results (January 2025) + +--- + +*Report generated through systematic API field discovery and community research.* diff --git a/README.md b/README.md index 402210a..e341a1b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Polestar -Adds support for Polestar statistics, by using Tibber API (account required, no subscription required). \ No newline at end of file +Adds support for My Polestar API vehicles to Homey. \ No newline at end of file diff --git a/README.no.txt b/README.no.txt deleted file mode 100644 index 49cb4c7..0000000 --- a/README.no.txt +++ /dev/null @@ -1,26 +0,0 @@ -Hei, Polestar-eier! 🚗✨ - -Velkommen til den oppgraderte Polestar-appen for Homey – din portal til en smartere kjøreopplevelse. Denne hendige hjelperen kobler ikke bare din elegante elbil til ditt smarte hjem, men bringer nå en rekke nye funksjoner rett til fingertuppene dine. 🏠🔌 - -Kjør smart, lev smartere! ⚡️ - -Hva er nytt i den siste oppdateringen: - -Direkteforbindelse med Homey: Vi introduserer "Car Stats Viewer", som kobler din Polestar med Homey som aldri før. 🚀 -Eksklusiv Tilgang: En spesiell 'intern testbane' for de første 100 brukerne for å oppleve de siste fremskrittene. 🏁 -Forbedrede Klassiske Funksjoner: - -Innsikt i Batteriet: Hold et øye med batterinivået i din Polestar – alltid klar for neste eventyr. 🔋 -Overvåkning av Ladestatus: Nyt den ekstra kaffekoppen mens bilen lader effektivt. ☕️ -Estimert Rekkevidde: Perfekt for både spontane utflukter og godt planlagte reiser. 🌍 -Oppdateringer på Bilens Tilstand: Din Polestar er ikke bare en bil; den er en del av livet ditt. ❤️ -Kom i Gang: - -Tibber-integrasjon: Ingen ekstra abonnement nødvendig. Bare koble til din Tibber-konto for sømløs integrering. -Webhook Magi: Sett opp en ny webhook i Homey for forbedret kommunikasjon med bilen din. -Enkel Installasjon: Bare send en e-post til polestar@coderax.dev med dine Google Play-detaljer for tilgang, installer appen og følg oppsettsveiledningen. 📲 -La oss Rulle inn i Fremtiden! 🎲 - -Legg til Polestar-appen i din Homey, følg de nye installasjonsinstruksjonene, og trå inn i en verden hvor din bil og hjem eksisterer i perfekt harmoni. - -Takk for at du valgte Polestar-appen – hvor teknologi møter veien, og ditt smarte hjem møter din smarte kjøretur. Sikre og smarte reiser! 🛣️✨ \ No newline at end of file diff --git a/README.txt b/README.txt index f3e8f87..cf33c95 100644 --- a/README.txt +++ b/README.txt @@ -4,21 +4,15 @@ Welcome to the upgraded Polestar app for Homey, your gateway to a smarter drivin Drive Smart, Live Smarter! ⚡️ -What’s New in the Latest Update: +Direct Connection with Homey: Using the My Vehicle device your an connect your My Polestar account to Homey. 🚀 -Direct Connection with Homey: Introducing the "Car Stats Viewer" feature, connecting your Polestar with Homey like never before. 🚀 -Exclusive Access: A special 'internal test track' for the first 100 users to experience the latest advancements. 🏁 -Classic Features Enhanced: +Features Enhanced: Battery Insights: Keep an eye on your Polestar's battery level – always be ready for what's next. 🔋 Charging Status Monitoring: Enjoy that extra cup of coffee while your car charges efficiently. ☕️ Range Estimation: Perfect for both spontaneous outings and well-planned journeys. 🌍 Car’s Health Updates: Your Polestar isn't just a car; it's part of your life. ❤️ -Getting Started: -Tibber Integration: No extra subscription needed. Just link your Tibber account for seamless integration. -Webhook Magic: Set up a new webhook in Homey for enhanced communication with your car. -Easy Installation: Just email polestar@coderax.dev with your Google Play details for access, install the app, and follow the setup guide. 📲 Let’s Roll into the Future! 🎲 Add the Polestar app to your Homey, follow the new setup instructions, and step into a world where your car and home exist in perfect harmony. diff --git a/app.js b/app.js index a1b4799..4fde966 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,7 @@ class Polestar extends Homey.App { this.homey.settings.set('debugLog', null); this.log(this.homey.__({ + nl: 'Polestar App is geinitialiseert', en: 'Polestar App has been initialized', no: 'Polestar App har blitt initialisert' })); @@ -18,10 +19,31 @@ class Polestar extends Homey.App { this.homey.settings.on('set', (key) => { if (key === 'debugLog') return; this.log(this.homey.__({ + nl: 'Setting bijgewerkt:', en: 'Setting updated:', no: 'Innstilling oppdatert:' }), 'Polestar App', 'DEBUG', `${key}: ${key == 'polestar_token' ? '********' : this.homey.settings.get(key)}`); }); + + try { + this.homey.dashboards + .getWidget('dashboard') + .registerSettingAutocompleteListener('device', async (query, settings) => { + this.log("List Polestar vehicles for widget settings") + const driver = await this.homey.drivers.getDriver('vehicle'); + const devices = await driver.getDevices(); + this.log('Located '+devices.length+' vehicles: filter using '+query) + this.log(devices[0].getData().registration) + return Object.values(devices) + .map(device => ({ + name: device.getName(), + registration: device.getData().registration, + })) + .filter(vehicle => vehicle.name.toLowerCase().includes(query.toLowerCase())); + }); + } catch (err) { + this.log(`Dashboards might not be available: ${err.message}`); + } } async log(message, instance = 'Polestar App', severity = 'DEBUG', data = null) { diff --git a/app.json b/app.json index 121d716..88e081d 100644 --- a/app.json +++ b/app.json @@ -1,19 +1,21 @@ { "_comment": "This file is generated. Please edit .homeycompose/app.json instead.", "id": "com.Coderax.Polestar", - "version": "2.1.2", - "compatibility": ">=5.0.0", + "version": "2.7.5", + "compatibility": ">=12.4.5", "sdk": 3, "platforms": [ "local" ], "name": { "en": "Polestar", - "no": "Polestar" + "no": "Polestar", + "nl": "Polestar" }, "description": { "en": "Enjoy the road with Polestar and Homey – smart technology for smart drivers", - "no": "Nyt veien med Polestar og Homey – smart teknologi for smarte sjåfører" + "no": "Nyt veien med Polestar og Homey – smart teknologi for smarte sjåfører", + "nl": "Geniet van de weg met Polestar en Homey – Slimme technologie voor slimme bestuurders" }, "category": "internet", "permissions": [], @@ -23,20 +25,22 @@ "xlarge": "/assets/images/xlarge.png" }, "author": { - "name": "Jesper Grimstad", - "email": "jesper.grimstad@hotmail.com" + "name": "Vincent Boer", + "email": "vincent+homey@vdboer.nl" }, "brandColor": "#081822", - "support": "mailto:polestar@coderax.dev?subject=Polestar%20-%20Homey%20app", - "contributing": { - "donate": { - "paypal": { - "username": "Coderaxxx" + "support": "mailto:vincent+polestar@vdboer.nl?subject=Polestar%20-%20Homey%20app", + "contributors": { + "developers": [ + { + "name": "Jesper Grimstad", + "email": "jesper.grimstad@hotmail.com" }, - "githubSponsors": { - "username": "Coderaxx" + { + "name": "Vincent Boer", + "email": "vincent+homey@vdboer.nl" } - } + ] }, "tags": { "en": [ @@ -60,93 +64,75 @@ "ev", "ev car", "smart car" + ], + "nl": [ + "polestar", + "pole", + "star", + "auto", + "electrisch", + "electrische auto", + "ev", + "ev auto", + "bev", + "bev auto", + "slimme auto", + "batterij auto" ] }, "homeyCommunityTopicId": 95083, - "homepage": "https://coderax.dev", - "source": "https://github.com/Coderaxx/Polestar", + "source": "https://github.com/kaohlive/Polestar", "bugs": { - "url": "https://github.com/Coderaxx/Polestar/issues" + "url": "https://github.com/kaohlive/Polestar/issues" }, "flow": { "triggers": [ { + "id": "measure_polestarConnected_false", "title": { "en": "Car disconnected from a charger", - "no": "Bil frakoblet lader" + "no": "Bil frakoblet lader", + "nl": "Auto is niet meer verbonden met de lader" }, "hint": { "en": "When the car detects it is no longer connected to a charge port", - "no": "Når bilen oppdager at ladepunktet er frakoblet" + "no": "Når bilen oppdager at ladepunktet er frakoblet", + "nl": "Als de auto detecteerd dat de laadpoort niet meer verbonden is met een lader" }, "args": [ { - "name": "Vehicle", "type": "device", - "filter": "driver_id=vehicle" + "name": "device", + "filter": "driver_id=polestar-2-csv&capabilities=measure_polestarConnected" } - ], - "id": "chargeportconnected_false" + ] }, { + "id": "measure_polestarConnected_true", "title": { "en": "Car connected to a charger", - "no": "Bil tilkoblet lader" + "no": "Bil tilkoblet lader", + "nl": "Auto is verbonden met een lader" }, "hint": { "en": "When the car detects a charger connected to a charge port", - "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ], - "id": "chargeportconnected_true" - }, - { - "title": { - "en": "Car stopped charging", - "no": "Lading stoppet" - }, - "hint": { - "en": "When the car stopped drawing power from the socket", - "no": "Når bilen sluttet å trekke strøm fra laderen" - }, - "args": [ - { - "name": "Vehicle", - "type": "device", - "filter": "driver_id=vehicle" - } - ], - "id": "charging_false" - }, - { - "title": { - "en": "Car started charging", - "no": "Lading startet" - }, - "hint": { - "en": "When the car started to draw power from the socket", - "no": "Når bilen startet å trekke strøm fra laderen" + "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet", + "nl": "Wanneer de auto detecteerd dat de laadpoort verbonden is met een lader" }, "args": [ { - "name": "Vehicle", "type": "device", - "filter": "driver_id=vehicle" + "name": "device", + "filter": "driver_id=polestar-2-csv&capabilities=measure_polestarConnected" } - ], - "id": "charging_true" + ] }, { "id": "chargingStarted", "title": { "en": "Charging started", - "no": "Lading startet" + "no": "Lading startet", + "nl": "Laden is gestart" }, "args": [ { @@ -160,7 +146,8 @@ "id": "chargingEnded", "title": { "en": "Charging ended", - "no": "Lading stoppet" + "no": "Lading stoppet", + "nl": "Laden is gestopt" }, "args": [ { @@ -174,7 +161,8 @@ "id": "tripEnded", "title": { "en": "Trip ended", - "no": "Reisen er over" + "no": "Reisen er over", + "nl": "De rit is geindigd" }, "tokens": [ { @@ -182,11 +170,13 @@ "name": "lastTrip", "title": { "en": "Your last trip", - "no": "Din siste reise" + "no": "Din siste reise", + "nl": "Uw recente rit" }, "example": { "en": "An image visualizing your last trip, combined with useful trip data", - "no": "Et bilde som viser din siste reise, kombinert med nyttige reise-data" + "no": "Et bilde som viser din siste reise, kombinert med nyttige reise-data", + "nl": "Een visuele weergave van uw recente trip, gecombineerd met nuttige rit info" } }, { @@ -194,11 +184,13 @@ "name": "tripInfo", "title": { "en": "Trip Info", - "no": "Reiseinfo" + "no": "Reiseinfo", + "nl": "Rit info" }, "example": { "en": "An image visualizing data about your last trip", - "no": "Et bilde som viser data om din siste reise" + "no": "Et bilde som viser data om din siste reise", + "nl": "Een afbeelding met uw recente rit weergegeven" } }, { @@ -206,11 +198,13 @@ "name": "tripScore", "title": { "en": "Trip Score", - "no": "Reise-score" + "no": "Reise-score", + "nl": "Rit score" }, "example": { "en": "An image visualizing how well your last trip was", - "no": "Et bilde som viser hvor bra din siste reise var" + "no": "Et bilde som viser hvor bra din siste reise var", + "nl": "Een afbeelding over hoe goed uw rit was" } }, { @@ -218,11 +212,13 @@ "name": "tripFrom", "title": { "en": "Trip From", - "no": "Reise fra" + "no": "Reise fra", + "nl": "Rit vanaf" }, "example": { "en": "Storgata 1, Oslo", - "no": "Storgata 1, Oslo" + "no": "Storgata 1, Oslo", + "nl": "Storgata 1, Oslo" } }, { @@ -230,11 +226,13 @@ "name": "tripTo", "title": { "en": "Trip To", - "no": "Reise til" + "no": "Reise til", + "nl": "Rit naar" }, "example": { "en": "Karl Johans gate 1, Oslo", - "no": "Karl Johans gate 1, Oslo" + "no": "Karl Johans gate 1, Oslo", + "nl": "Karl Johans gate 1, Oslo" } }, { @@ -242,11 +240,13 @@ "name": "totalDistance", "title": { "en": "Total Distance", - "no": "Total distanse" + "no": "Total distanse", + "nl": "Totale afstand" }, "example": { "en": "2.5 km", - "no": "2.5 km" + "no": "2.5 km", + "nl": "2.5 km" } }, { @@ -254,11 +254,13 @@ "name": "dateString", "title": { "en": "Date", - "no": "Dato" + "no": "Dato", + "nl": "Datum" }, "example": { "en": "31.12.2023", - "no": "31.12.2023" + "no": "31.12.2023", + "nl": "31.12.2023" } }, { @@ -266,11 +268,13 @@ "name": "timeStringStart", "title": { "en": "Start Time", - "no": "Starttid" + "no": "Starttid", + "nl": "Start tijd" }, "example": { "en": "10:00", - "no": "10:00" + "no": "10:00", + "nl": "10:00" } }, { @@ -278,11 +282,13 @@ "name": "timeStringEnd", "title": { "en": "End Time", - "no": "Sluttid" + "no": "Sluttid", + "nl": "Eind tijd" }, "example": { "en": "10:30", - "no": "10:30" + "no": "10:30", + "nl": "10:30" } }, { @@ -290,11 +296,13 @@ "name": "tripDuration", "title": { "en": "Trip Duration", - "no": "Reisens varighet" + "no": "Reisens varighet", + "nl": "Ritduur" }, "example": { "en": "2.5 hours", - "no": "2.5 timer" + "no": "2.5 timer", + "nl": "2.5 timer" } }, { @@ -302,11 +310,13 @@ "name": "socStart", "title": { "en": "Start State of Charge", - "no": "Starttilstand for lading" + "no": "Starttilstand for lading", + "nl": "Startniveau van batterijniveau" }, "example": { "en": "80%", - "no": "80%" + "no": "80%", + "nl": "80%" } }, { @@ -314,11 +324,13 @@ "name": "socEnd", "title": { "en": "End State of Charge", - "no": "Slutttilstand for lading" + "no": "Slutttilstand for lading", + "nl": "Eindniveau van batterijniveau" }, "example": { "en": "50%", - "no": "50%" + "no": "50%", + "nl": "50%" } }, { @@ -326,11 +338,13 @@ "name": "energyUsed", "title": { "en": "Energy Used", - "no": "Energi brukt" + "no": "Energi brukt", + "nl": "Verbruikte energy" }, "example": { "en": "12.41 kWh", - "no": "12.41 kWh" + "no": "12.41 kWh", + "nl": "12.41 kWh" } }, { @@ -338,11 +352,13 @@ "name": "altStart", "title": { "en": "Start Altitude", - "no": "Start høyde" + "no": "Start høyde", + "nl": "Start hoogte" }, "example": { "en": "20 m", - "no": "20 m" + "no": "20 m", + "nl": "20 m" } }, { @@ -350,11 +366,13 @@ "name": "altEnd", "title": { "en": "End Altitude", - "no": "Slutt høyde" + "no": "Slutt høyde", + "nl": "Eind hoogte" }, "example": { "en": "40 m", - "no": "40 m" + "no": "40 m", + "nl": "40 m" } } ], @@ -370,7 +388,8 @@ "id": "measure_polestarPower_changed", "title": { "en": "Power changed", - "no": "Strøm endret" + "no": "Strøm endret", + "nl": "Stroom verandert" }, "tokens": [ { @@ -378,11 +397,13 @@ "type": "number", "title": { "en": "Power", - "no": "Strøm" + "no": "Strøm", + "nl": "Stroom" }, "example": { "en": "12.41 kW", - "no": "12.41 kW" + "no": "12.41 kW", + "nl": "12.41 kW" } } ], @@ -393,6 +414,86 @@ "filter": "driver_id=polestar-2-csv" } ] + }, + { + "id": "measure_vehicleChargeState_false", + "title": { + "en": "Car stopped charging", + "no": "Lading stoppet", + "nl": "Auto is gestopt met laden" + }, + "hint": { + "en": "When the car stopped drawing power from the socket", + "no": "Når bilen sluttet å trekke strøm fra laderen", + "nl": "Als de auto zelf gestopt is met het opnemen van stroom uit via laadpoort" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=vehicle&capabilities=measure_vehicleChargeState" + } + ] + }, + { + "id": "measure_vehicleChargeState_true", + "title": { + "en": "Car started charging", + "no": "Lading startet", + "nl": "Auto is begonnen met laden" + }, + "hint": { + "en": "When the car started to draw power from the socket", + "no": "Når bilen startet å trekke strøm fra laderen", + "nl": "Als de auto is begonnen met het opnemen van stroom via de laadpoort" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=vehicle&capabilities=measure_vehicleChargeState" + } + ] + }, + { + "id": "measure_vehicleConnected_false", + "title": { + "en": "Car disconnected from a charger", + "no": "Bil frakoblet lader", + "nl": "Auto is niet meer verbonden met de lader" + }, + "hint": { + "en": "When the car detects it is no longer connected to a charge port", + "no": "Når bilen oppdager at ladepunktet er frakoblet", + "nl": "Als de auto detecteerd dat de laadpoort niet meer verbonden is met een lader" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=vehicle&capabilities=measure_vehicleConnected" + } + ] + }, + { + "id": "measure_vehicleConnected_true", + "title": { + "en": "Car connected to a charger", + "no": "Bil tilkoblet lader", + "nl": "Auto is verbonden met een lader" + }, + "hint": { + "en": "When the car detects a charger connected to a charge port", + "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet", + "nl": "Wanneer de auto detecteerd dat de laadpoort verbonden is met een lader" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=vehicle&capabilities=measure_vehicleConnected" + } + ] } ] }, @@ -400,7 +501,8 @@ { "name": { "en": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)", - "no": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)" + "no": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)", + "nl": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)" }, "class": "sensor", "capabilities": [ @@ -473,7 +575,8 @@ "type": "group", "label": { "en": "General settings", - "no": "Generelle innstillinger" + "no": "Generelle innstillinger", + "nl": "Algemene instellingen" }, "children": [ { @@ -482,11 +585,13 @@ "value": 10, "label": { "en": "End trip threshold", - "no": "Terskel for endt tur" + "no": "Terskel for endt tur", + "nl": "Einde rit drempel tijd" }, "hint": { "en": "The time in minutes after which a trip should be considered ended. (Optional)", - "no": "Terskel i minutter for når en tur skal anses som avsluttet. (Valgfritt)" + "no": "Terskel i minutter for når en tur skal anses som avsluttet. (Valgfritt)", + "nl": "De tijd in minuten waarna de rit wordt beschouwd als geindigd. (Optioneel)" } } ] @@ -495,7 +600,8 @@ "type": "group", "label": { "en": "Trip summary settings", - "no": "Innstillinger for turoppsummering" + "no": "Innstillinger for turoppsummering", + "nl": "Rit samenvatting instellingen" }, "children": [ { @@ -504,11 +610,13 @@ "value": true, "label": { "en": "Enable trip summary", - "no": "Aktiver turoppsummering" + "no": "Aktiver turoppsummering", + "nl": "Gebruik rit samenvatting" }, "hint": { "en": "Enable or disable the trip summary. (Optional) NOTE: By enabling you accept that your trip data will be stored anonymously in the cloud. You can disable this at any time. By deleting the device, you will also permanently delete all your trip data. App restart is required after changing this setting.", - "no": "Aktiver eller deaktiver turoppsummeringen. (Valgfritt) MERK: Ved å aktivere godtar du at turoppsummeringen lagres anonymt i skyen. Du kan deaktivere dette når som helst. Ved å slette enheten vil du også slette all turoppsummeringsdata permanent. App restart er nødvendig etter endring av denne innstillingen." + "no": "Aktiver eller deaktiver turoppsummeringen. (Valgfritt) MERK: Ved å aktivere godtar du at turoppsummeringen lagres anonymt i skyen. Du kan deaktivere dette når som helst. Ved å slette enheten vil du også slette all turoppsummeringsdata permanent. App restart er nødvendig etter endring av denne innstillingen.", + "nl": "Aan of uit zetten van de rit samenvatting. (Optioneel) OPMERKING: Door de rit samenvatting te activeren accepteert u dat de rit informatie annoniem wordt opgeslagen in de cloud. U kunt dit ten alle tijden weer uitzetten. Als u dit device weer verwijdert wordt ook de cloud data permanent verwijdert. De App moet opnieuw worden opgestart als u deze instelling aanpast." } }, { @@ -517,25 +625,29 @@ "value": "light", "label": { "en": "Trip summary style", - "no": "Turoppsummeringens stil" + "no": "Turoppsummeringens stil", + "nl": "Rit samenvatting uiterlijk" }, "hint": { "en": "This changes the style of the trip summary. (Optional)", - "no": "Dette endrer stilen til turoppsummeringen. (Valgfritt)" + "no": "Dette endrer stilen til turoppsummeringen. (Valgfritt)", + "nl": "Dit past het uiterlijk van de rit samenvatting aan. (Optioneel)" }, "values": [ { "id": "light", "label": { "en": "Light", - "no": "Lys" + "no": "Lys", + "nl": "Ligt" } }, { "id": "dark", "label": { "en": "Dark", - "no": "Mørk" + "no": "Mørk", + "nl": "Donker" } } ] @@ -546,25 +658,29 @@ "value": "light", "label": { "en": "Trip info style", - "no": "Turinfoens stil" + "no": "Turinfoens stil", + "nl": "Rit samenvatting stijl" }, "hint": { "en": "This changes the style of the trip info. (Optional)", - "no": "Dette endrer stilen til turinfoen. (Valgfritt)" + "no": "Dette endrer stilen til turinfoen. (Valgfritt)", + "nl": "Dit verandert de stijl van de rit samenvatting. (Optioneel)" }, "values": [ { "id": "light", "label": { "en": "Light", - "no": "Lys" + "no": "Lys", + "nl": "Ligt" } }, { "id": "dark", "label": { "en": "Dark", - "no": "Mørk" + "no": "Mørk", + "nl": "Donker" } } ] @@ -575,25 +691,29 @@ "value": "light", "label": { "en": "Trip score style", - "no": "Kjørescorens stil" + "no": "Kjørescorens stil", + "nl": "Rit score stijl" }, "hint": { "en": "This changes the style of the trip score. (Optional)", - "no": "Dette endrer stilen til kjørescoren. (Valgfritt)" + "no": "Dette endrer stilen til kjørescoren. (Valgfritt)", + "nl": "Dit verandert de stijl van de rit score. (Optioneel)" }, "values": [ { "id": "light", "label": { "en": "Light", - "no": "Lys" + "no": "Lys", + "nl": "Ligt" } }, { "id": "dark", "label": { "en": "Dark", - "no": "Mørk" + "no": "Mørk", + "nl": "Donker" } } ] @@ -604,53 +724,61 @@ "value": "mapboxOutdoors", "label": { "en": "Map image type", - "no": "Kart-bildetype" + "no": "Kart-bildetype", + "nl": "Kaartweergave stijl" }, "hint": { "en": "This changes the type of the map that is shown in the trip summary. (Optional)", - "no": "Dette endrer hvilket type kart som vises i turoppsummeringen. (Valgfritt)" + "no": "Dette endrer hvilket type kart som vises i turoppsummeringen. (Valgfritt)", + "nl": "Dit verandert de stijl van de kaart die bij de rit samenvatting wordt weergegeven. (Optioneel)" }, "values": [ { "id": "mapboxLight", "label": { "en": "Mapbox Light", - "no": "Mapbox Light" + "no": "Mapbox Light", + "nl": "Mapbox Ligt" } }, { "id": "mapboxDark", "label": { "en": "Mapbox Dark", - "no": "Mapbox Dark" + "no": "Mapbox Dark", + "nl": "Mapbox Donker" } }, { "id": "mapboxStreets", "label": { "en": "Mapbox Streets", - "no": "Mapbox Streets" + "no": "Mapbox Streets", + "nl": "Mapbox Straten" } }, { "id": "mapboxOutdoors", "label": { "en": "Mapbox Outdoors", - "no": "Mapbox Outdoors" + "no": "Mapbox Outdoors", + "nl": "Mapbox Buitenleven" } }, { "id": "mapboxSatellite", "label": { "en": "Mapbox Satellite", - "no": "Mapbox Satellite" + "no": "Mapbox Satellite", + "nl": "Mapbox Sateliet" } }, { "id": "mapboxSatelliteStreets", "label": { "en": "Mapbox Satellite Streets", - "no": "Mapbox Satellite Streets" + "no": "Mapbox Satellite Streets", + "nl": "Mapbox Sateliet Straten" } } ] @@ -661,7 +789,8 @@ "type": "group", "label": { "en": "Webhook Settings", - "no": "Webhook innstillinger" + "no": "Webhook innstillinger", + "nl": "Webhook instellingen" }, "children": [ { @@ -669,11 +798,13 @@ "type": "text", "hint": { "en": "Do not change unless instructed by developer, or if you changed the webhook. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler, eller hvis du har endret webhooken. Denne innstillingen har ingen funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler, eller hvis du har endret webhooken. Denne innstillingen har ingen funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen, of tenzij je de webhook hebt aangepast. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook URL", - "no": "Webhook URL" + "no": "Webhook URL", + "nl": "Webhook URL" } }, { @@ -681,11 +812,13 @@ "type": "text", "hint": { "en": "Do not change unless instructed by developer. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook URL (short)", - "no": "Webhook URL (kort)" + "no": "Webhook URL (kort)", + "nl": "Webhook URL (kort)" } }, { @@ -693,11 +826,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook slug", - "no": "Webhook slug" + "no": "Webhook slug", + "nl": "Webhook slug" } }, { @@ -705,11 +840,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen." }, "label": { "en": "Webhook ID", - "no": "Webhook ID" + "no": "Webhook ID", + "nl": "Webhook ID" } }, { @@ -717,11 +854,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen." }, "label": { "en": "Webhook secret", - "no": "Webhook secret" + "no": "Webhook secret", + "nl": "Webhook secret" } } ] @@ -734,14 +873,20 @@ "no": "Min Polestar", "nl": "Mijn Polestar" }, - "class": "other", + "class": "car", "capabilities": [ - "measure_battery" + "measure_power", + "meter_power", + "measure_battery", + "ev_charging_state" ], "energy": { - "batteries": [ - "INTERNAL" - ] + "electricCar": true + }, + "capabilitiesOptions": { + "measure_power": { + "approximated": true + } }, "platforms": [ "local" @@ -778,6 +923,35 @@ "id": "vehicle" } ], + "widgets": { + "dashboard": { + "name": { + "en": "Vehicle Dashboard" + }, + "height": 188, + "transparent": true, + "settings": [ + { + "id": "device", + "type": "autocomplete", + "title": { + "en": "Vehicle" + } + } + ], + "api": { + "getVehicles": { + "method": "GET", + "path": "/" + }, + "getVehicleStatus": { + "method": "GET", + "path": "/status" + } + }, + "id": "dashboard" + } + }, "capabilities": { "measure_polestarAlt": { "type": "number", @@ -785,7 +959,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/altitude.svg", "title": { "en": "Altitude", - "no": "Høyde" + "no": "Høyde", + "nl": "Hoogte" }, "units": "m", "getable": true, @@ -798,7 +973,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/battery-75.svg", "title": { "en": "Battery", - "no": "Batteri" + "no": "Batteri", + "nl": "Batterij" }, "units": "%", "getable": true, @@ -811,7 +987,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/hvbattery.svg", "title": { "en": "Battery level", - "no": "Batterinivå" + "no": "Batterinivå", + "nl": "Batterij niveau" }, "units": "kWh", "decimals": 2, @@ -825,7 +1002,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/charging.svg", "title": { "en": "Charging", - "no": "Lader" + "no": "Lader", + "nl": "Laden" }, "getable": true, "setable": false, @@ -852,7 +1030,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/ccs.svg", "title": { "en": "Charge port", - "no": "Ladeport" + "no": "Ladeport", + "nl": "Laadpoort" }, "getable": true, "setable": false, @@ -864,7 +1043,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/gear.svg", "title": { "en": "Selected Gear", - "no": "Valgt gir" + "no": "Valgt gir", + "nl": "Geselecteerde versnelling" }, "getable": true, "setable": false, @@ -876,7 +1056,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/ignition.svg", "title": { "en": "Ignition", - "no": "Tenning" + "no": "Tenning", + "nl": "Ontsteking" }, "getable": true, "setable": false, @@ -888,7 +1069,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/location.svg", "title": { "en": "Location", - "no": "Plassering" + "no": "Plassering", + "nl": "Locatie" }, "getable": true, "setable": false, @@ -900,7 +1082,8 @@ "icon": "/drivers/vehicle/assets/charging.svg", "title": { "en": "Charged home this month", - "no": "Ladet hjemme denne mnd" + "no": "Ladet hjemme denne mnd", + "nl": "Deze maand thuis geladen" }, "getable": true, "setable": false, @@ -928,7 +1111,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/powerdc.svg", "title": { "en": "Power", - "no": "Effekt" + "no": "Effekt", + "nl": "Acceleratie" }, "units": "kW", "getable": true, @@ -941,7 +1125,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/range.svg", "title": { "en": "Range", - "no": "Rekkevidde" + "no": "Rekkevidde", + "nl": "Bereik" }, "getable": true, "setable": false, @@ -953,7 +1138,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/speedometer.svg", "title": { "en": "Speed", - "no": "Fart" + "no": "Fart", + "nl": "Snelheid" }, "units": { "en": "km/h", @@ -969,7 +1155,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/temp.svg", "title": { "en": "Ambient Temperature", - "no": "Utetemperatur" + "no": "Utetemperatur", + "nl": "Buitentemperatuur" }, "units": "°C", "getable": true, @@ -982,7 +1169,8 @@ "icon": "/drivers/polestar-2-csv/assets/images/update.svg", "title": { "en": "Updated", - "no": "Oppdatert" + "no": "Oppdatert", + "nl": "Bijgewerkt" }, "getable": true, "setable": false, @@ -1018,7 +1206,7 @@ "type": "boolean", "title": { "en": "Chargeport connected", - "no": "Ladepoort tilkoblet", + "no": "Ladeport tilkoblet", "nl": "Laadpoort verbonden" }, "getable": true, @@ -1026,6 +1214,31 @@ "uiComponent": "sensor", "icon": "/drivers/vehicle/assets/charger_connected.svg" }, + "measure_vehicleDaysTillService": { + "type": "number", + "title": { + "en": "Days till service", + "no": "Dager til service", + "nl": "Dagen tot service" + }, + "getable": true, + "setable": false, + "icon": "/drivers/vehicle/assets/timeremaining.svg" + }, + "measure_vehicleDistanceTillService": { + "type": "number", + "title": { + "en": "Distance till service", + "no": "Avstand til service", + "nl": "Afstand tot service" + }, + "getable": true, + "setable": false, + "units": { + "en": "KM" + }, + "icon": "/drivers/vehicle/assets/odometer.svg" + }, "measure_vehicleOdometer": { "type": "number", "title": { diff --git a/assets/images/preview-dark.png b/assets/images/preview-dark.png new file mode 100644 index 0000000..521f68d Binary files /dev/null and b/assets/images/preview-dark.png differ diff --git a/assets/images/preview-light.png b/assets/images/preview-light.png new file mode 100644 index 0000000..632cdac Binary files /dev/null and b/assets/images/preview-light.png differ diff --git a/clone_modules/polestar.js b/clone_modules/polestar.js new file mode 160000 index 0000000..b188699 --- /dev/null +++ b/clone_modules/polestar.js @@ -0,0 +1 @@ +Subproject commit b18869988415d3a9ab38081453b4ab7b574c60c1 diff --git a/debug-getvehicles.js b/debug-getvehicles.js new file mode 100644 index 0000000..8885e7f --- /dev/null +++ b/debug-getvehicles.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node +'use strict'; + +const Polestar = require('./clone_modules/polestar.js/polestar.js'); +const axios = require('axios'); + +// Get credentials from command line arguments +const args = process.argv.slice(2); +if (args.length < 2) { + console.error('Usage: node debug-getvehicles.js '); + process.exit(1); +} + +const email = args[0]; +const password = args[1]; + +async function debugGetVehicles() { + console.log('\n=== Debugging getVehicles() API Call ===\n'); + + try { + // Initialize and login + console.log('Logging in...'); + const polestar = new Polestar(email, password); + await polestar.login(); + console.log('✓ Login successful!\n'); + + // Extract token using the new public method + console.log('Extracting access token...'); + const token = polestar.getAccessToken(); + + if (!token) { + console.error('❌ Could not extract access token'); + process.exit(1); + } + + console.log('✓ Access token obtained\n'); + + // Make raw API call to getConsumerCarsV2 + console.log('Making raw API call to getConsumerCarsV2...\n'); + + const query = `query getCars { + getConsumerCarsV2 { + vin + internalVehicleIdentifier + modelYear + content { + model { + code + name + __typename + } + images { + studio { + url + angles + __typename + } + __typename + } + __typename + } + hasPerformancePackage + registrationNo + deliveryDate + currentPlannedDeliveryDate + __typename + } +}`; + + try { + const response = await axios.post( + 'https://pc-api.polestar.com/eu-north-1/mystar-v2/', + { + query: query, + operationName: 'getCars', + variables: {} + }, + { + headers: { + 'cache-control': 'no-cache', + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'pragma': 'no-cache' + } + } + ); + + console.log('Raw API Response:'); + console.log(JSON.stringify(response.data, null, 2)); + + // Check for errors + if (response.data.errors) { + console.log('\n❌ GraphQL Errors Found:'); + response.data.errors.forEach((err, idx) => { + console.log(`\nError ${idx + 1}:`); + console.log(` Message: ${err.message}`); + if (err.path) { + console.log(` Path: ${err.path.join('.')}`); + } + if (err.locations) { + console.log(` Location: line ${err.locations[0].line}, column ${err.locations[0].column}`); + } + }); + } + + // Check for data + if (response.data.data) { + console.log('\n✅ Data Found:'); + if (response.data.data.getConsumerCarsV2) { + console.log(` Found ${response.data.data.getConsumerCarsV2.length} vehicle(s)`); + console.log('\nVehicle Details:'); + response.data.data.getConsumerCarsV2.forEach((vehicle, idx) => { + console.log(`\nVehicle ${idx + 1}:`); + console.log(` VIN: ${vehicle.vin}`); + console.log(` Model: ${vehicle.content?.model?.name || 'N/A'}`); + console.log(` Registration: ${vehicle.registrationNo || 'N/A'}`); + console.log(` Model Year: ${vehicle.modelYear || 'N/A'}`); + }); + } else { + console.log(' getConsumerCarsV2 field is null or missing'); + } + } else { + console.log('\n⚠️ No data field in response'); + } + + } catch (error) { + console.error('\n❌ API Request Failed:'); + console.error(' Error:', error.message); + if (error.response) { + console.error(' Status:', error.response.status); + console.error(' Response:', JSON.stringify(error.response.data, null, 2)); + } + } + + // Now test with the library method + console.log('\n\n=== Testing library getVehicles() method ===\n'); + try { + const vehicles = await polestar.getVehicles(); + console.log('✓ getVehicles() succeeded!'); + console.log(` Found ${vehicles.length} vehicle(s)`); + } catch (error) { + console.error('❌ getVehicles() failed:'); + console.error(' ', error.message); + } + + } catch (error) { + console.error('\n\x1b[31mUnexpected Error:\x1b[0m', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +debugGetVehicles(); diff --git a/discover-api.js b/discover-api.js new file mode 100644 index 0000000..d054bc8 --- /dev/null +++ b/discover-api.js @@ -0,0 +1,152 @@ +#!/usr/bin/env node +'use strict'; + +const Polestar = require('./clone_modules/polestar.js/polestar.js'); +const fs = require('fs'); + +// Get credentials from command line arguments +const args = process.argv.slice(2); +if (args.length < 2) { + console.error('Usage: node discover-api.js [output-file.json]'); + process.exit(1); +} + +const email = args[0]; +const password = args[1]; +const outputFile = args[2] || null; + +async function discoverAPI() { + console.log('\n=== Polestar API Discovery Tool ===\n'); + + try { + // Initialize and login + console.log('Logging in...'); + const polestar = new Polestar(email, password); + await polestar.login(); + console.log('✓ Login successful!\n'); + + // Get full schema + console.log('Retrieving GraphQL schema...'); + const schema = await polestar.getGraphQLSchema(); + console.log('✓ Schema retrieved!\n'); + + // Debug: Check what we got + console.log('Debug - Schema structure:', JSON.stringify(schema, null, 2).substring(0, 500)); + console.log('\nDebug - Has data?', !!schema.data); + console.log('Debug - Has __schema?', schema.data && !!schema.data.__schema); + + if (!schema.data || !schema.data.__schema) { + console.log('\nFull schema response:'); + console.log(JSON.stringify(schema, null, 2)); + throw new Error('Schema introspection is not supported or disabled on this API'); + } + + // Get available queries + console.log('\n=== AVAILABLE QUERIES ===\n'); + const queries = await polestar.getAvailableQueries(); + + queries.forEach(query => { + console.log(`\x1b[33m${query.name}\x1b[0m: ${query.returnType}`); + if (query.description) { + console.log(` Description: ${query.description}`); + } + if (query.args && query.args.length > 0) { + console.log(` Arguments:`); + query.args.forEach(arg => { + console.log(` - ${arg.name}: ${arg.type}`); + if (arg.description) { + console.log(` ${arg.description}`); + } + }); + } + console.log(''); + }); + + // Get available mutations + console.log('\n=== AVAILABLE MUTATIONS ===\n'); + const mutations = await polestar.getAvailableMutations(); + + if (mutations && mutations.length > 0) { + mutations.forEach(mutation => { + console.log(`\x1b[33m${mutation.name}\x1b[0m: ${mutation.returnType}`); + if (mutation.description) { + console.log(` Description: ${mutation.description}`); + } + if (mutation.args && mutation.args.length > 0) { + console.log(` Arguments:`); + mutation.args.forEach(arg => { + console.log(` - ${arg.name}: ${arg.type}`); + if (arg.description) { + console.log(` ${arg.description}`); + } + }); + } + console.log(''); + }); + } else { + console.log('No mutations available\n'); + } + + // Get details for specific types of interest + console.log('\n=== DETAILED TYPE INFORMATION ===\n'); + + // Get all custom types (exclude built-in GraphQL types) + const customTypes = schema.data.__schema.types.filter(type => + !type.name.startsWith('__') && + !['String', 'Int', 'Float', 'Boolean', 'ID'].includes(type.name) && + type.kind === 'OBJECT' + ); + + console.log(`\nFound ${customTypes.length} custom types. Showing types with "Car", "Battery", "Telematic", or "Health" in the name:\n`); + + const relevantTypes = customTypes.filter(type => + type.name.toLowerCase().includes('car') || + type.name.toLowerCase().includes('battery') || + type.name.toLowerCase().includes('telematic') || + type.name.toLowerCase().includes('health') || + type.name.toLowerCase().includes('odometer') + ); + + for (const type of relevantTypes) { + const details = await polestar.getTypeDetails(type.name); + console.log(`\x1b[36m${details.name}\x1b[0m (${details.kind})`); + if (details.description) { + console.log(` ${details.description}`); + } + if (details.fields && details.fields.length > 0) { + console.log(' Fields:'); + details.fields.forEach(field => { + console.log(` - ${field.name}: ${field.type}`); + if (field.description) { + console.log(` ${field.description}`); + } + }); + } + console.log(''); + } + + // Save to file if requested + if (outputFile) { + console.log(`\n\nSaving full schema to ${outputFile}...`); + fs.writeFileSync(outputFile, JSON.stringify(schema, null, 2), 'utf8'); + console.log('✓ Schema saved successfully!\n'); + } + + // Summary + console.log('\n=== SUMMARY ==='); + console.log(`Total Queries: ${queries.length}`); + console.log(`Total Mutations: ${mutations.length}`); + console.log(`Total Types: ${schema.data.__schema.types.length}`); + console.log(`Custom Types: ${customTypes.length}`); + console.log(`Relevant Vehicle Types: ${relevantTypes.length}\n`); + + } catch (error) { + console.error('\n\x1b[31mError:\x1b[0m', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +discoverAPI(); diff --git a/drivers/polestar-2-csv/device.js b/drivers/polestar-2-csv/device.js index 8058652..4272143 100644 --- a/drivers/polestar-2-csv/device.js +++ b/drivers/polestar-2-csv/device.js @@ -12,7 +12,8 @@ class PolestarBetaDevice extends Device { this.homey.app.log(this.homey.__({ en: `${this.name} has been initialized`, - no: `${this.name} har blitt initialisert` + no: `${this.name} har blitt initialisert`, + nl: `${this.name} is geinitialiseerd`, }), this.name, 'DEBUG'); moment.locale(this.homey.i18n.getLanguage() == 'no' ? 'nb' : 'en'); @@ -103,9 +104,18 @@ class PolestarBetaDevice extends Device { const id = this.settings.webhook_id || null; const secret = this.settings.webhook_secret || null; const data = {}; + this.webhook = await this.homey.cloud.createWebhook(id, secret, data); + this.homey.app.log(this.homey.__({ + en: 'Initializing webhook with ' + id + }), this.name, 'DEBUG'); this.webhook.on('message', async args => { + this.homey.app.log(this.homey.__({ + en: 'Received webhook message for ' + this.name, + no: 'Mottok webhook data med kjøretøydata for ' + this.name + }), this.name, 'DEBUG', args.body); + const fields = ['ambientTemperature', 'batteryLevel', 'chargePortConnected', 'ignitionState', 'power', 'selectedGear', 'speed', 'stateOfCharge']; const isDataMissing = fields.some(field => args.body[field] === undefined || args.body[field] === null); const hasFields = ['drivingPoints']; diff --git a/drivers/polestar-2-csv/driver.compose.json b/drivers/polestar-2-csv/driver.compose.json index 129624d..4142e5d 100644 --- a/drivers/polestar-2-csv/driver.compose.json +++ b/drivers/polestar-2-csv/driver.compose.json @@ -1,7 +1,8 @@ { "name": { "en": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)", - "no": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)" + "no": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)", + "nl": "Polestar 2 (Car Stats Viewer ᴮᴱᵀᴬ)" }, "class": "sensor", "capabilities": [ diff --git a/drivers/polestar-2-csv/driver.flow.compose.json b/drivers/polestar-2-csv/driver.flow.compose.json index 0381d55..8b5e10b 100644 --- a/drivers/polestar-2-csv/driver.flow.compose.json +++ b/drivers/polestar-2-csv/driver.flow.compose.json @@ -1,24 +1,55 @@ { "triggers": [ + { + "id": "measure_polestarConnected_false", + "title": { + "en": "Car disconnected from a charger", + "no": "Bil frakoblet lader", + "nl": "Auto is niet meer verbonden met de lader" + }, + "hint": { + "en": "When the car detects it is no longer connected to a charge port", + "no": "Når bilen oppdager at ladepunktet er frakoblet", + "nl": "Als de auto detecteerd dat de laadpoort niet meer verbonden is met een lader" + }, + "$filter": "capabilities=measure_polestarConnected" + }, + { + "id": "measure_polestarConnected_true", + "title": { + "en": "Car connected to a charger", + "no": "Bil tilkoblet lader", + "nl": "Auto is verbonden met een lader" + }, + "hint": { + "en": "When the car detects a charger connected to a charge port", + "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet", + "nl": "Wanneer de auto detecteerd dat de laadpoort verbonden is met een lader" + }, + "$filter": "capabilities=measure_polestarConnected" + }, { "id": "chargingStarted", "title": { "en": "Charging started", - "no": "Lading startet" + "no": "Lading startet", + "nl": "Laden is gestart" } }, { "id": "chargingEnded", "title": { "en": "Charging ended", - "no": "Lading stoppet" + "no": "Lading stoppet", + "nl": "Laden is gestopt" } }, { "id": "tripEnded", "title": { "en": "Trip ended", - "no": "Reisen er over" + "no": "Reisen er over", + "nl": "De rit is geindigd" }, "tokens": [ { @@ -26,11 +57,13 @@ "name": "lastTrip", "title": { "en": "Your last trip", - "no": "Din siste reise" + "no": "Din siste reise", + "nl": "Uw recente rit" }, "example": { "en": "An image visualizing your last trip, combined with useful trip data", - "no": "Et bilde som viser din siste reise, kombinert med nyttige reise-data" + "no": "Et bilde som viser din siste reise, kombinert med nyttige reise-data", + "nl": "Een visuele weergave van uw recente trip, gecombineerd met nuttige rit info" } }, { @@ -38,11 +71,13 @@ "name": "tripInfo", "title": { "en": "Trip Info", - "no": "Reiseinfo" + "no": "Reiseinfo", + "nl": "Rit info" }, "example": { "en": "An image visualizing data about your last trip", - "no": "Et bilde som viser data om din siste reise" + "no": "Et bilde som viser data om din siste reise", + "nl": "Een afbeelding met uw recente rit weergegeven" } }, { @@ -50,11 +85,13 @@ "name": "tripScore", "title": { "en": "Trip Score", - "no": "Reise-score" + "no": "Reise-score", + "nl": "Rit score" }, "example": { "en": "An image visualizing how well your last trip was", - "no": "Et bilde som viser hvor bra din siste reise var" + "no": "Et bilde som viser hvor bra din siste reise var", + "nl": "Een afbeelding over hoe goed uw rit was" } }, { @@ -62,11 +99,13 @@ "name": "tripFrom", "title": { "en": "Trip From", - "no": "Reise fra" + "no": "Reise fra", + "nl": "Rit vanaf" }, "example": { "en": "Storgata 1, Oslo", - "no": "Storgata 1, Oslo" + "no": "Storgata 1, Oslo", + "nl": "Storgata 1, Oslo" } }, { @@ -74,11 +113,13 @@ "name": "tripTo", "title": { "en": "Trip To", - "no": "Reise til" + "no": "Reise til", + "nl": "Rit naar" }, "example": { "en": "Karl Johans gate 1, Oslo", - "no": "Karl Johans gate 1, Oslo" + "no": "Karl Johans gate 1, Oslo", + "nl": "Karl Johans gate 1, Oslo" } }, { @@ -86,11 +127,13 @@ "name": "totalDistance", "title": { "en": "Total Distance", - "no": "Total distanse" + "no": "Total distanse", + "nl": "Totale afstand" }, "example": { "en": "2.5 km", - "no": "2.5 km" + "no": "2.5 km", + "nl": "2.5 km" } }, { @@ -98,11 +141,13 @@ "name": "dateString", "title": { "en": "Date", - "no": "Dato" + "no": "Dato", + "nl": "Datum" }, "example": { "en": "31.12.2023", - "no": "31.12.2023" + "no": "31.12.2023", + "nl": "31.12.2023" } }, { @@ -110,11 +155,13 @@ "name": "timeStringStart", "title": { "en": "Start Time", - "no": "Starttid" + "no": "Starttid", + "nl": "Start tijd" }, "example": { "en": "10:00", - "no": "10:00" + "no": "10:00", + "nl": "10:00" } }, { @@ -122,11 +169,13 @@ "name": "timeStringEnd", "title": { "en": "End Time", - "no": "Sluttid" + "no": "Sluttid", + "nl": "Eind tijd" }, "example": { "en": "10:30", - "no": "10:30" + "no": "10:30", + "nl": "10:30" } }, { @@ -134,11 +183,13 @@ "name": "tripDuration", "title": { "en": "Trip Duration", - "no": "Reisens varighet" + "no": "Reisens varighet", + "nl": "Ritduur" }, "example": { "en": "2.5 hours", - "no": "2.5 timer" + "no": "2.5 timer", + "nl": "2.5 timer" } }, { @@ -146,11 +197,13 @@ "name": "socStart", "title": { "en": "Start State of Charge", - "no": "Starttilstand for lading" + "no": "Starttilstand for lading", + "nl": "Startniveau van batterijniveau" }, "example": { "en": "80%", - "no": "80%" + "no": "80%", + "nl": "80%" } }, { @@ -158,11 +211,13 @@ "name": "socEnd", "title": { "en": "End State of Charge", - "no": "Slutttilstand for lading" + "no": "Slutttilstand for lading", + "nl": "Eindniveau van batterijniveau" }, "example": { "en": "50%", - "no": "50%" + "no": "50%", + "nl": "50%" } }, { @@ -170,11 +225,13 @@ "name": "energyUsed", "title": { "en": "Energy Used", - "no": "Energi brukt" + "no": "Energi brukt", + "nl": "Verbruikte energy" }, "example": { "en": "12.41 kWh", - "no": "12.41 kWh" + "no": "12.41 kWh", + "nl": "12.41 kWh" } }, { @@ -182,11 +239,13 @@ "name": "altStart", "title": { "en": "Start Altitude", - "no": "Start høyde" + "no": "Start høyde", + "nl": "Start hoogte" }, "example": { "en": "20 m", - "no": "20 m" + "no": "20 m", + "nl": "20 m" } }, { @@ -194,11 +253,13 @@ "name": "altEnd", "title": { "en": "End Altitude", - "no": "Slutt høyde" + "no": "Slutt høyde", + "nl": "Eind hoogte" }, "example": { "en": "40 m", - "no": "40 m" + "no": "40 m", + "nl": "40 m" } } ] @@ -207,7 +268,8 @@ "id": "measure_polestarPower_changed", "title": { "en": "Power changed", - "no": "Strøm endret" + "no": "Strøm endret", + "nl": "Stroom verandert" }, "tokens": [ { @@ -215,11 +277,13 @@ "type": "number", "title": { "en": "Power", - "no": "Strøm" + "no": "Strøm", + "nl": "Stroom" }, "example": { "en": "12.41 kW", - "no": "12.41 kW" + "no": "12.41 kW", + "nl": "12.41 kW" } } ] diff --git a/drivers/polestar-2-csv/driver.js b/drivers/polestar-2-csv/driver.js index 191d2bf..eb27816 100644 --- a/drivers/polestar-2-csv/driver.js +++ b/drivers/polestar-2-csv/driver.js @@ -56,31 +56,31 @@ class PolestarBetaDriver extends Driver { } }); - try { - const registerPolestarUser = await axios.post(`https://homey.crdx.us/register/${args.slug}`, { - slug: args.slug, - homeyId: this.homeyId, - webhookId: webhook.id, - webhookSecret: webhook.secret, - webhookUrl: webhookUrl, - shortWebhookUrl: args.url_short, - }); - - if (registerPolestarUser.status === 200 && registerPolestarUser.data.success) { - this.homey.app.log(this.homey.__({ - en: 'Successfully registered user for webhook', - no: 'Klarte å registrere bruker for webhook' - }), 'Polestar Driver CSV ᴮᴱᵀᴬ', 'DEBUG', registerPolestarUser.data); - } - } catch (error) { - this.homey.app.log(this.homey.__({ - en: 'Failed to register user for webhook', - no: 'Klarte ikke å registrere bruker for webhook' - }), 'Polestar Driver CSV ᴮᴱᵀᴬ', 'ERROR', error.message); - return { success: false, error: error.message }; - } - - this.webhookUrl = webhookUrl; + // try { + // const registerPolestarUser = await axios.post(`https://homey.crdx.us/register/${args.slug}`, { + // slug: args.slug, + // homeyId: this.homeyId, + // webhookId: webhook.id, + // webhookSecret: webhook.secret, + // webhookUrl: webhookUrl, + // shortWebhookUrl: args.url_short, + // }); + + // if (registerPolestarUser.status === 200 && registerPolestarUser.data.success) { + // this.homey.app.log(this.homey.__({ + // en: 'Successfully registered user for webhook', + // no: 'Klarte å registrere bruker for webhook' + // }), 'Polestar Driver CSV ᴮᴱᵀᴬ', 'DEBUG', registerPolestarUser.data); + // } + // } catch (error) { + // this.homey.app.log(this.homey.__({ + // en: 'Failed to register user for webhook', + // no: 'Klarte ikke å registrere bruker for webhook' + // }), 'Polestar Driver CSV ᴮᴱᵀᴬ', 'ERROR', error.message); + // return { success: false, error: error.message }; + // } + + // this.webhookUrl = webhookUrl; this.homey.app.log(this.homey.__({ en: 'Webhook created', no: 'Webhook opprettet' }), 'Polestar Driver CSV ᴮᴱᵀᴬ', 'DEBUG'); return { success: true }; diff --git a/drivers/polestar-2-csv/driver.settings.compose.json b/drivers/polestar-2-csv/driver.settings.compose.json index 2f87d2c..383d984 100644 --- a/drivers/polestar-2-csv/driver.settings.compose.json +++ b/drivers/polestar-2-csv/driver.settings.compose.json @@ -2,196 +2,223 @@ { "type": "group", "label": { - "en": "General settings", - "no": "Generelle innstillinger" + "en": "General settings", + "no": "Generelle innstillinger", + "nl": "Algemene instellingen" }, "children": [ - { - "id": "endTripThreshold", - "type": "number", - "value": 10, - "label": { - "en": "End trip threshold", - "no": "Terskel for endt tur" - }, - "hint": { - "en": "The time in minutes after which a trip should be considered ended. (Optional)", - "no": "Terskel i minutter for når en tur skal anses som avsluttet. (Valgfritt)" - } + { + "id": "endTripThreshold", + "type": "number", + "value": 10, + "label": { + "en": "End trip threshold", + "no": "Terskel for endt tur", + "nl": "Einde rit drempel tijd" + }, + "hint": { + "en": "The time in minutes after which a trip should be considered ended. (Optional)", + "no": "Terskel i minutter for når en tur skal anses som avsluttet. (Valgfritt)", + "nl": "De tijd in minuten waarna de rit wordt beschouwd als geindigd. (Optioneel)" } + } ] - }, + }, { "type": "group", "label": { "en": "Trip summary settings", - "no": "Innstillinger for turoppsummering" + "no": "Innstillinger for turoppsummering", + "nl": "Rit samenvatting instellingen" }, "children": [ { - "id": "tripSummaryEnabled", - "type": "checkbox", - "value": true, - "label": { - "en": "Enable trip summary", - "no": "Aktiver turoppsummering" + "id": "tripSummaryEnabled", + "type": "checkbox", + "value": true, + "label": { + "en": "Enable trip summary", + "no": "Aktiver turoppsummering", + "nl": "Gebruik rit samenvatting" + }, + "hint": { + "en": "Enable or disable the trip summary. (Optional) NOTE: By enabling you accept that your trip data will be stored anonymously in the cloud. You can disable this at any time. By deleting the device, you will also permanently delete all your trip data. App restart is required after changing this setting.", + "no": "Aktiver eller deaktiver turoppsummeringen. (Valgfritt) MERK: Ved å aktivere godtar du at turoppsummeringen lagres anonymt i skyen. Du kan deaktivere dette når som helst. Ved å slette enheten vil du også slette all turoppsummeringsdata permanent. App restart er nødvendig etter endring av denne innstillingen.", + "nl": "Aan of uit zetten van de rit samenvatting. (Optioneel) OPMERKING: Door de rit samenvatting te activeren accepteert u dat de rit informatie annoniem wordt opgeslagen in de cloud. U kunt dit ten alle tijden weer uitzetten. Als u dit device weer verwijdert wordt ook de cloud data permanent verwijdert. De App moet opnieuw worden opgestart als u deze instelling aanpast." + } + }, + { + "id": "tripSummaryStyle", + "type": "dropdown", + "value": "light", + "label": { + "en": "Trip summary style", + "no": "Turoppsummeringens stil", + "nl": "Rit samenvatting uiterlijk" + }, + "hint": { + "en": "This changes the style of the trip summary. (Optional)", + "no": "Dette endrer stilen til turoppsummeringen. (Valgfritt)", + "nl": "Dit past het uiterlijk van de rit samenvatting aan. (Optioneel)" + }, + "values": [ + { + "id": "light", + "label": { + "en": "Light", + "no": "Lys", + "nl": "Ligt" + } }, - "hint": { - "en": "Enable or disable the trip summary. (Optional) NOTE: By enabling you accept that your trip data will be stored anonymously in the cloud. You can disable this at any time. By deleting the device, you will also permanently delete all your trip data. App restart is required after changing this setting.", - "no": "Aktiver eller deaktiver turoppsummeringen. (Valgfritt) MERK: Ved å aktivere godtar du at turoppsummeringen lagres anonymt i skyen. Du kan deaktivere dette når som helst. Ved å slette enheten vil du også slette all turoppsummeringsdata permanent. App restart er nødvendig etter endring av denne innstillingen." + { + "id": "dark", + "label": { + "en": "Dark", + "no": "Mørk", + "nl": "Donker" + } } + ] }, { - "id": "tripSummaryStyle", - "type": "dropdown", - "value": "light", - "label": { - "en": "Trip summary style", - "no": "Turoppsummeringens stil" - }, - "hint": { - "en": "This changes the style of the trip summary. (Optional)", - "no": "Dette endrer stilen til turoppsummeringen. (Valgfritt)" + "id": "tripInfoStyle", + "type": "dropdown", + "value": "light", + "label": { + "en": "Trip info style", + "no": "Turinfoens stil", + "nl": "Rit samenvatting stijl" + }, + "hint": { + "en": "This changes the style of the trip info. (Optional)", + "no": "Dette endrer stilen til turinfoen. (Valgfritt)", + "nl": "Dit verandert de stijl van de rit samenvatting. (Optioneel)" + }, + "values": [ + { + "id": "light", + "label": { + "en": "Light", + "no": "Lys", + "nl": "Ligt" + } }, - "values": [ - { - "id": "light", - "label": { - "en": "Light", - "no": "Lys" - } - }, - { - "id": "dark", - "label": { - "en": "Dark", - "no": "Mørk" - } - } - ] + { + "id": "dark", + "label": { + "en": "Dark", + "no": "Mørk", + "nl": "Donker" + } + } + ] }, { - "id": "tripInfoStyle", - "type": "dropdown", - "value": "light", - "label": { - "en": "Trip info style", - "no": "Turinfoens stil" - }, - "hint": { - "en": "This changes the style of the trip info. (Optional)", - "no": "Dette endrer stilen til turinfoen. (Valgfritt)" + "id": "tripScoreStyle", + "type": "dropdown", + "value": "light", + "label": { + "en": "Trip score style", + "no": "Kjørescorens stil", + "nl": "Rit score stijl" + }, + "hint": { + "en": "This changes the style of the trip score. (Optional)", + "no": "Dette endrer stilen til kjørescoren. (Valgfritt)", + "nl": "Dit verandert de stijl van de rit score. (Optioneel)" + }, + "values": [ + { + "id": "light", + "label": { + "en": "Light", + "no": "Lys", + "nl": "Ligt" + } }, - "values": [ - { - "id": "light", - "label": { - "en": "Light", - "no": "Lys" - } - }, - { - "id": "dark", - "label": { - "en": "Dark", - "no": "Mørk" - } - } - ] + { + "id": "dark", + "label": { + "en": "Dark", + "no": "Mørk", + "nl": "Donker" + } + } + ] }, { - "id": "tripScoreStyle", - "type": "dropdown", - "value": "light", - "label": { - "en": "Trip score style", - "no": "Kjørescorens stil" + "id": "mapImageType", + "type": "dropdown", + "value": "mapboxOutdoors", + "label": { + "en": "Map image type", + "no": "Kart-bildetype", + "nl": "Kaartweergave stijl" + }, + "hint": { + "en": "This changes the type of the map that is shown in the trip summary. (Optional)", + "no": "Dette endrer hvilket type kart som vises i turoppsummeringen. (Valgfritt)", + "nl": "Dit verandert de stijl van de kaart die bij de rit samenvatting wordt weergegeven. (Optioneel)" + }, + "values": [ + { + "id": "mapboxLight", + "label": { + "en": "Mapbox Light", + "no": "Mapbox Light", + "nl": "Mapbox Ligt" + } }, - "hint": { - "en": "This changes the style of the trip score. (Optional)", - "no": "Dette endrer stilen til kjørescoren. (Valgfritt)" + { + "id": "mapboxDark", + "label": { + "en": "Mapbox Dark", + "no": "Mapbox Dark", + "nl": "Mapbox Donker" + } }, - "values": [ - { - "id": "light", - "label": { - "en": "Light", - "no": "Lys" - } - }, - { - "id": "dark", - "label": { - "en": "Dark", - "no": "Mørk" - } - } - ] - }, - { - "id": "mapImageType", - "type": "dropdown", - "value": "mapboxOutdoors", - "label": { - "en": "Map image type", - "no": "Kart-bildetype" + { + "id": "mapboxStreets", + "label": { + "en": "Mapbox Streets", + "no": "Mapbox Streets", + "nl": "Mapbox Straten" + } }, - "hint": { - "en": "This changes the type of the map that is shown in the trip summary. (Optional)", - "no": "Dette endrer hvilket type kart som vises i turoppsummeringen. (Valgfritt)" + { + "id": "mapboxOutdoors", + "label": { + "en": "Mapbox Outdoors", + "no": "Mapbox Outdoors", + "nl": "Mapbox Buitenleven" + } }, - "values": [ - { - "id": "mapboxLight", - "label": { - "en": "Mapbox Light", - "no": "Mapbox Light" - } - }, - { - "id": "mapboxDark", - "label": { - "en": "Mapbox Dark", - "no": "Mapbox Dark" - } - }, - { - "id": "mapboxStreets", - "label": { - "en": "Mapbox Streets", - "no": "Mapbox Streets" - } - }, - { - "id": "mapboxOutdoors", - "label": { - "en": "Mapbox Outdoors", - "no": "Mapbox Outdoors" - } - }, - { - "id": "mapboxSatellite", - "label": { - "en": "Mapbox Satellite", - "no": "Mapbox Satellite" - } - }, - { - "id": "mapboxSatelliteStreets", - "label": { - "en": "Mapbox Satellite Streets", - "no": "Mapbox Satellite Streets" - } - } - ] + { + "id": "mapboxSatellite", + "label": { + "en": "Mapbox Satellite", + "no": "Mapbox Satellite", + "nl": "Mapbox Sateliet" + } + }, + { + "id": "mapboxSatelliteStreets", + "label": { + "en": "Mapbox Satellite Streets", + "no": "Mapbox Satellite Streets", + "nl": "Mapbox Sateliet Straten" + } + } + ] } - ] + ] }, { "type": "group", "label": { "en": "Webhook Settings", - "no": "Webhook innstillinger" + "no": "Webhook innstillinger", + "nl": "Webhook instellingen" }, "children": [ { @@ -199,11 +226,13 @@ "type": "text", "hint": { "en": "Do not change unless instructed by developer, or if you changed the webhook. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler, eller hvis du har endret webhooken. Denne innstillingen har ingen funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler, eller hvis du har endret webhooken. Denne innstillingen har ingen funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen, of tenzij je de webhook hebt aangepast. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook URL", - "no": "Webhook URL" + "no": "Webhook URL", + "nl": "Webhook URL" } }, { @@ -211,11 +240,13 @@ "type": "text", "hint": { "en": "Do not change unless instructed by developer. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook URL (short)", - "no": "Webhook URL (kort)" + "no": "Webhook URL (kort)", + "nl": "Webhook URL (kort)" } }, { @@ -223,11 +254,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer. This setting has no function, and is only for information.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler. Dette er ikke en funksjon, og er kun til informasjon.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen. Deze setting heeft geen functie, en is er ter informatie" }, "label": { "en": "Webhook slug", - "no": "Webhook slug" + "no": "Webhook slug", + "nl": "Webhook slug" } }, { @@ -235,11 +268,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen." }, "label": { "en": "Webhook ID", - "no": "Webhook ID" + "no": "Webhook ID", + "nl": "Webhook ID" } }, { @@ -247,11 +282,13 @@ "type": "label", "hint": { "en": "Do not change unless instructed by developer.", - "no": "Ikke endre med mindre du har fått beskjed om det av utvikler." + "no": "Ikke endre med mindre du har fått beskjed om det av utvikler.", + "nl": "Niet aanpassingen tenzij door de developer opgedragen." }, "label": { "en": "Webhook secret", - "no": "Webhook secret" + "no": "Webhook secret", + "nl": "Webhook secret" } } ] diff --git a/drivers/polestar-2-csv/pair/start.html b/drivers/polestar-2-csv/pair/start.html index da0f8b7..e4da62c 100644 --- a/drivers/polestar-2-csv/pair/start.html +++ b/drivers/polestar-2-csv/pair/start.html @@ -11,9 +11,7 @@

Viktig:

Sørg for at "Car Stats Viewer"-appen er installert på din Polestar 2 for å bruke denne enheten. Denne appen er ikke tilgjengelig i Google Play Store for Polestar, grunnet strenge regler i Android Automotive OS.

-

For å laste ned appen, kan du bruke vår "internal test track". For å få tilgang, send en - e-post med forespørsel til polestar@coderax.dev. Inkluder din - Google Play Store e-postadresse. Når du har +

For å laste ned appen, kan du bruke vår "internal test track". Når du har fått bekreftet at du er lagt inn i "internal test track", klikk her for bli med i test track.

@@ -27,7 +25,7 @@

Viktig: - \ No newline at end of file diff --git a/drivers/vehicle/device.js b/drivers/vehicle/device.js index d192fe7..d1f4471 100644 --- a/drivers/vehicle/device.js +++ b/drivers/vehicle/device.js @@ -1,9 +1,12 @@ 'use strict'; const { Device } = require('homey'); -const Polestar = require('@andysmithfal/polestar.js'); +//const Polestar = require('@andysmithfal/polestar.js'); +const Polestar = require('../../clone_modules/polestar.js'); const HomeyCrypt = require('../../lib/homeycrypt') +const measureInterval = 60000; + var polestar = null; class PolestarVehicle extends Device { @@ -12,6 +15,7 @@ class PolestarVehicle extends Device { let PolestarUser = this.homey.settings.get('user_email'); try { let PolestarPwd = await HomeyCrypt.decrypt(this.homey.settings.get('user_password'), PolestarUser); + //this.log(PolestarPwd); this.polestar = new Polestar(PolestarUser, PolestarPwd); } catch (err) { this.homey.app.log('Could not decrypt using salt, network connection changed?', 'PolestarVehicle', 'ERROR', err); @@ -25,28 +29,56 @@ class PolestarVehicle extends Device { return; } } - + await this.fixCapabilities(); + await this.fixEnergy(); this.update_loop_timers(); - this.homey.app.log('PolestarVehicle has been initialized', 'PolestarVehicle'); + this.homey.app.log(this.homey.__({ + en: `${this.name} has been initialized`, + no: `${this.name} har blitt initialisert`, + nl: `${this.name} is geinitialiseerd`, + }), this.name, 'DEBUG'); } async update_loop_timers() { await this.updateVehicleState(); - let interval = 60000; + let interval = measureInterval; this._timerTimers = this.homey.setInterval(async () => { await this.updateVehicleState(); }, interval); + await this.updateHealthState(); + let intervalHealth = 3600000; + this._timerHealth = this.homey.setInterval(async () => { + await this.updateHealthState(); + }, intervalHealth); + } + + async fixEnergy() + { + const currentEnergy = await this.getEnergy(); + //Check if this ev was created with the right energy object + if(!currentEnergy?.electricCar) + { + await this.setEnergy({ + "electricCar": true + }) + } } async fixCapabilities() { if (!this.hasCapability('measure_battery')) await this.addCapability('measure_battery'); - // if(!this.hasCapability('measure_current')) - // await this.addCapability('measure_current'); - // if(!this.hasCapability('measure_power')) - // await this.addCapability('measure_power'); + if (!this.hasCapability('ev_charging_state')) + await this.addCapability('ev_charging_state'); + if (!this.hasCapability('measure_polestarBattery')) + await this.addCapability('measure_polestarBattery'); + if(!this.hasCapability('measure_current')) + await this.addCapability('measure_current'); + if(!this.hasCapability('measure_power')) + await this.addCapability('measure_power'); + if(!this.hasCapability('meter_power')) + await this.addCapability('meter_power'); if (!this.hasCapability('measure_vehicleChargeTimeRemaining')) await this.addCapability('measure_vehicleChargeTimeRemaining'); if (!this.hasCapability('measure_vehicleOdometer')) @@ -57,12 +89,33 @@ class PolestarVehicle extends Device { await this.addCapability('measure_vehicleChargeState'); if (!this.hasCapability('measure_vehicleConnected')) await this.addCapability('measure_vehicleConnected'); + if (!this.hasCapability('alarm_generic')) + await this.addCapability('alarm_generic'); + if (!this.hasCapability('measure_vehicleDaysTillService')) + await this.addCapability('measure_vehicleDaysTillService'); + if (!this.hasCapability('measure_vehicleDistanceTillService')) + await this.addCapability('measure_vehicleDistanceTillService'); + } + + async updateHealthState(){ + this.homey.app.log('Retrieve vehicle health', 'PolestarVehicle', 'DEBUG'); + var healthInfo = await this.polestar.getHealthData(); + this.homey.app.log('Health:', 'PolestarVehicle', 'DEBUG', healthInfo); + if(healthInfo!=null) + { + this.setCapabilityValue('alarm_generic', healthInfo.serviceWarning!='SERVICE_WARNING_NO_WARNING'); + this.setCapabilityValue('measure_vehicleDaysTillService', healthInfo.daysToService); + this.setCapabilityValue('measure_vehicleDistanceTillService', healthInfo.distanceToServiceKm); + } else { + this.setCapabilityValue('alarm_generic', false); + } } async updateVehicleState() { this.homey.app.log('Retrieve device details', 'PolestarVehicle', 'DEBUG'); try { var odometer = await this.polestar.getOdometer(); + this.homey.app.log('Odometers:', 'PolestarVehicle', 'DEBUG', odometer); var odo = odometer.odometerMeters; try { odo = odo / 1000; //Convert to KM instead of M @@ -78,26 +131,83 @@ class PolestarVehicle extends Device { var batteryInfo = await this.polestar.getBattery(); this.homey.app.log('Battery:', 'PolestarVehicle', 'DEBUG', batteryInfo); + this.setCapabilityValue('measure_polestarBattery', batteryInfo.batteryChargeLevelPercentage); this.setCapabilityValue('measure_battery', batteryInfo.batteryChargeLevelPercentage); - // this.setCapabilityValue('measure_current', batteryInfo.chargingCurrentAmps); - // this.setCapabilityValue('measure_power', batteryInfo.chargingPowerWatts); - - + //this.setCapabilityValue('measure_current', batteryInfo.chargingCurrentAmps); + // if(batteryInfo.chargingCurrentAmps!==null){ + // this.setCapabilityValue('measure_current', batteryInfo.chargingCurrentAmps); + // } else { + // this.setCapabilityValue('measure_current', 0); //We set 0 first, this is for insights sake + // } + // if(batteryInfo.chargingPowerWatts!==null){ + // this.setCapabilityValue('measure_power', batteryInfo.chargingPowerWatts); + // let hours = measureInterval / (1000 * 60 * 60); + // let usedPower = (batteryInfo.chargingPowerWatts/1000) * (hours); + // this.setCapabilityValue('meter_power', (usedPower+this.getCapabilityValue('meter_power'))); + // } else { + // this.setCapabilityValue('measure_power', 0); //We set 0 first, this is for insights sake + // } + + //Set the estimated range for the vhicle this.setCapabilityValue('measure_vehicleRange', batteryInfo.estimatedDistanceToEmptyKm); - if (batteryInfo.chargingStatus == 'CHARGING_STATUS_CHARGING') { - this.setCapabilityValue('measure_vehicleChargeState', true); - this.setCapabilityValue('measure_vehicleChargeTimeRemaining', batteryInfo.estimatedChargingTimeToFullMinutes); - } else { - this.setCapabilityValue('measure_vehicleChargeState', false); - this.setCapabilityValue('measure_vehicleChargeTimeRemaining', null); - } - if (batteryInfo.chargerConnectionStatus == 'CHARGER_CONNECTION_STATUS_CONNECTED') + + //Lets assign statusses we consider connected + const connectedStatuses = new Set([ + 'CHARGING_STATUS_CHARGING', + 'CHARGING_STATUS_DONE', + 'CHARGING_STATUS_SCHEDULED', + 'CHARGING_STATUS_SMART_CHARGING', + 'CHARGING_STATUS_ERROR', + 'CHARGING_STATUS_FAULT' + ]); + + //Lets see if the car is in a state that suggests the connector is connected + if(connectedStatuses.has(batteryInfo.chargingStatus)){ this.setCapabilityValue('measure_vehicleConnected', true); - else + //Determine the ev_charging_state in our switch + } else { this.setCapabilityValue('measure_vehicleConnected', false); + this.setCapabilityValue('ev_charging_state', 'plugged_out'); + } + + //Now Lets see if it is actually charging + switch (batteryInfo.chargingStatus) { + case 'CHARGING_STATUS_CHARGING': + this.setCapabilityValue('measure_vehicleChargeState', true); + this.setCapabilityValue('ev_charging_state', 'plugged_in_charging'); + this.setCapabilityValue('measure_vehicleChargeTimeRemaining', batteryInfo.estimatedChargingTimeToFullMinutes); + break; + case 'CHARGING_STATUS_SCHEDULED': + case 'CHARGING_STATUS_DONE': + case 'CHARGING_STATUS_SMART_CHARGING': + this.setCapabilityValue('measure_vehicleChargeState', false); + this.setCapabilityValue('ev_charging_state', 'plugged_in_paused'); + this.setCapabilityValue('measure_vehicleChargeTimeRemaining', null); + // TODO: Add capability to show scheduled charging + break; + + case 'CHARGING_STATUS_ERROR': + case 'CHARGING_STATUS_FAULT': + this.setCapabilityValue('measure_vehicleChargeState', false); + this.setCapabilityValue('ev_charging_state', 'plugged_in'); + this.setCapabilityValue('measure_vehicleChargeTimeRemaining', null); + // TODO: Add capability to show charging error + break; + default: + this.setCapabilityValue('measure_vehicleChargeState', false); + this.setCapabilityValue('ev_charging_state', 'plugged_out'); + this.setCapabilityValue('measure_vehicleChargeTimeRemaining', null); + break; + } + + // if (batteryInfo.chargerConnectionStatus == 'CHARGER_CONNECTION_STATUS_CONNECTED') + // this.setCapabilityValue('measure_vehicleConnected', true); + // else + // this.setCapabilityValue('measure_vehicleConnected', false); } catch { this.homey.app.log('Failed to retrieve batterystate', 'PolestarVehicle', 'ERROR'); } + this.homey.api.realtime('updatevehicle'); } async onAdded() { diff --git a/drivers/vehicle/driver.compose.json b/drivers/vehicle/driver.compose.json index 75c2342..c023ffe 100644 --- a/drivers/vehicle/driver.compose.json +++ b/drivers/vehicle/driver.compose.json @@ -4,12 +4,15 @@ "no": "Min Polestar", "nl": "Mijn Polestar" }, - "class": "other", - "capabilities": [ - "measure_battery" - ], + "class": "car", + "capabilities": [ "measure_power", "meter_power", "measure_battery", "ev_charging_state" ], "energy": { - "batteries": ["INTERNAL"] + "electricCar": true + }, + "capabilitiesOptions": { + "measure_power": { + "approximated": true + } }, "platforms": [ "local" diff --git a/drivers/vehicle/driver.flow.compose.json b/drivers/vehicle/driver.flow.compose.json new file mode 100644 index 0000000..4f79515 --- /dev/null +++ b/drivers/vehicle/driver.flow.compose.json @@ -0,0 +1,60 @@ +{ + "triggers": [ + { + "id": "measure_vehicleChargeState_false", + "title": { + "en": "Car stopped charging", + "no": "Lading stoppet", + "nl": "Auto is gestopt met laden" + }, + "hint": { + "en": "When the car stopped drawing power from the socket", + "no": "Når bilen sluttet å trekke strøm fra laderen", + "nl": "Als de auto zelf gestopt is met het opnemen van stroom uit via laadpoort" + }, + "$filter": "capabilities=measure_vehicleChargeState" + }, + { + "id": "measure_vehicleChargeState_true", + "title": { + "en": "Car started charging", + "no": "Lading startet", + "nl": "Auto is begonnen met laden" + }, + "hint": { + "en": "When the car started to draw power from the socket", + "no": "Når bilen startet å trekke strøm fra laderen", + "nl": "Als de auto is begonnen met het opnemen van stroom via de laadpoort" + }, + "$filter": "capabilities=measure_vehicleChargeState" + }, + { + "id": "measure_vehicleConnected_false", + "title": { + "en": "Car disconnected from a charger", + "no": "Bil frakoblet lader", + "nl": "Auto is niet meer verbonden met de lader" + }, + "hint": { + "en": "When the car detects it is no longer connected to a charge port", + "no": "Når bilen oppdager at ladepunktet er frakoblet", + "nl": "Als de auto detecteerd dat de laadpoort niet meer verbonden is met een lader" + }, + "$filter": "capabilities=measure_vehicleConnected" + }, + { + "id": "measure_vehicleConnected_true", + "title": { + "en": "Car connected to a charger", + "no": "Bil tilkoblet lader", + "nl": "Auto is verbonden met een lader" + }, + "hint": { + "en": "When the car detects a charger connected to a charge port", + "no": "Når bilen oppdager at en lader er tilkoblet ladepunktet", + "nl": "Wanneer de auto detecteerd dat de laadpoort verbonden is met een lader" + }, + "$filter": "capabilities=measure_vehicleConnected" + } + ] +} \ No newline at end of file diff --git a/drivers/vehicle/driver.js b/drivers/vehicle/driver.js index d7c8022..5d56545 100644 --- a/drivers/vehicle/driver.js +++ b/drivers/vehicle/driver.js @@ -1,7 +1,8 @@ 'use strict'; const { Driver } = require('homey'); -const Polestar = require('@andysmithfal/polestar.js'); +//const Polestar = require('@andysmithfal/polestar.js'); +const Polestar = require('../../clone_modules/polestar.js'); const HomeyCrypt = require('../../lib/homeycrypt') class Vehicle extends Driver { @@ -139,13 +140,20 @@ class Vehicle extends Driver { }) this.homey.app.log('Password encrypted, credentials stored. Clear existing tokens.', 'Polestar Driver'); //Now we have the encrypted password stored we can start testing the info + var polestar = new Polestar(data.username, data.password); + //this.homey.app.log('Credential test password:', 'Polestar Driver', 'DEBUG', data.password); try { - var polestar = new Polestar(data.username, data.password); await polestar.login(); - var vehicles = await polestar.getVehicles(); this.homey.app.log('Credential test ok:', 'Polestar Driver', 'DEBUG', vehicles); + } catch (err) { + this.homey.app.log('Credential test failed:', 'Polestar Driver', 'ERROR', err); + return false; + } + try { + var vehicles = await polestar.getVehicles(); return true; - } catch { + } catch (err) { + this.homey.app.log('Retrieve vehicles failed:', 'Polestar Driver', 'ERROR', err); return false; } }); diff --git a/drivers/vehicle/pair/login.html b/drivers/vehicle/pair/login.html index c172f4f..e52cae3 100644 --- a/drivers/vehicle/pair/login.html +++ b/drivers/vehicle/pair/login.html @@ -49,7 +49,7 @@

}) } else { Homey.hideLoadingOverlay(); - Homey.alert("Login validation failed, check your credentials or try again. Retrieving the token sometimes fails due to the website. If you are sure your credentials are fine just try a couple of times."); + Homey.alert("Login validation failed, check your credentials or try again."); } }); }); diff --git a/drivers/vehicle/repair/login.html b/drivers/vehicle/repair/login.html index b0552b6..6e05eea 100644 --- a/drivers/vehicle/repair/login.html +++ b/drivers/vehicle/repair/login.html @@ -40,7 +40,7 @@

Homey.done(); } else { Homey.hideLoadingOverlay(); - Homey.alert("Login validation failed, check your credentials or try again. Retrieving the token sometimes fails due to the website. If you are sure your credentials are fine just try a couple of times."); + Homey.alert("Login validation failed, check your credentials or try again."); } }); }); diff --git a/lib/homeycrypt.js b/lib/homeycrypt.js index 098d311..7bc8b37 100644 --- a/lib/homeycrypt.js +++ b/lib/homeycrypt.js @@ -16,8 +16,8 @@ async function getsalts() { var macbuffer = Buffer.from(mac.join(''), 'hex'); //console.log(mac); let salt = { - prepend: macbuffer.slice(3, 6), - append: macbuffer.slice(0, 3), + prepend: macbuffer.subarray(3, 6), + append: macbuffer.subarray(0, 3), iv: Buffer.concat([macbuffer, Buffer.from('00000000000000000000', 'hex')]) } return salt; diff --git a/package-lock.json b/package-lock.json index 7d05d2d..8f770d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,24 +25,24 @@ } }, "node_modules/@andysmithfal/polestar.js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@andysmithfal/polestar.js/-/polestar.js-1.0.4.tgz", - "integrity": "sha512-lEeL9ivXWFjUkDxYc27Z5MsBB3Ej/l4V6YVV9pQxeRxnNR1w2v9NXMhJ8De+Z8i2OrJzjjEGJF7dYMCIbgDnoQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@andysmithfal/polestar.js/-/polestar.js-1.8.0.tgz", + "integrity": "sha512-I853lMExqjPlI5YmVx9kECERpK0RdUmx84J8iLgToCzaKpCSc55GMYX92VunlL5SI3mAhJLilQvTLs47/srAiw==", "dependencies": { - "axios": "^1.6.2" + "axios": "^1.7.7" } }, "node_modules/@tsconfig/node16": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.1.tgz", - "integrity": "sha512-+pio93ejHN4nINX4pXqfnR/fPLRtJBaT4ORaa5RH0Oc1zoYmo2B2koG+M328CQhHKn1Wj6FcOxCDFXAot9NhvA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", + "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", "dev": true }, "node_modules/@types/homey": { "name": "homey-apps-sdk-v3-types", - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/homey-apps-sdk-v3-types/-/homey-apps-sdk-v3-types-0.3.5.tgz", - "integrity": "sha512-AH7UPiPILITBmjDSAnupLp2kaBO9GuAj5y0hk9tLC2mO0AuOQuRYnbXIxrHOG3kium8m99FnS3BUHg0aY/SNCg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/homey-apps-sdk-v3-types/-/homey-apps-sdk-v3-types-0.3.12.tgz", + "integrity": "sha512-xr275VoF7FAiTi6PzElqHWpuGgSBY9hZFZsKeCTA4rluopf8sVk6zUPa+giSRSTiTrN3N3QjmHD5lUoNVwYd4g==", "dev": true, "dependencies": { "@types/node": "^14.14.20" @@ -55,12 +55,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", + "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/after": { @@ -79,11 +79,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz", - "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -114,14 +114,28 @@ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -164,19 +178,6 @@ "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -185,10 +186,23 @@ "node": ">=0.4.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/engine.io-client": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz", - "integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.4.tgz", + "integrity": "sha512-ydc8uuMMDxC5KCKNJN3zZKYJk2sgyTuTZQ7Aj1DJSsLKAcizA/PzWivw8fZMIjJVBo2CJOYzntv4FSjY/Lr//g==", "dependencies": { "component-emitter": "~1.3.0", "component-inherit": "0.0.3", @@ -198,15 +212,15 @@ "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~7.4.2", + "ws": "~7.5.10", "xmlhttprequest-ssl": "~1.6.2", "yeast": "0.1.2" } }, "node_modules/engine.io-client/node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -235,10 +249,51 @@ "has-binary2": "~1.0.2" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", @@ -255,12 +310,13 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -281,25 +337,46 @@ "integrity": "sha512-EicrlLLL3S42gE9/wde+11uiaYAaeSVDwCUIv2uMIoRBfNJCn8EsSI+6nS3r4TCKDO6+RQNM9ayLq2at+oZQWQ==" }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { - "get-intrinsic": "^1.1.3" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -318,21 +395,10 @@ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -340,10 +406,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -352,9 +421,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -363,9 +432,9 @@ } }, "node_modules/homey-api": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/homey-api/-/homey-api-3.4.13.tgz", - "integrity": "sha512-lnVnp2gXCxDeGSYV7XFDXRz5bWm8L3Hv3UoKGCcjATCwlYaHg8T899YqTwYNzyl4jMQV5J7dcvCRNJR1kWgTOA==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/homey-api/-/homey-api-3.11.3.tgz", + "integrity": "sha512-GwbQIdVJUTJDE4bH1zPEURKjlNOl766MTx+ALhqYg9NeHALbaQn2RCSmWxzhbSI+FiFiRaK2My8VJwEZx8UkVQ==", "dependencies": { "form-data": "^4.0.0", "node-fetch": "^2.6.7", @@ -385,6 +454,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -437,9 +514,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -460,11 +540,11 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -473,28 +553,69 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -519,9 +640,9 @@ } }, "node_modules/socket.io-parser": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz", - "integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.4.tgz", + "integrity": "sha512-z/pFQB3x+EZldRRzORYW1vwVO8m/3ILkswtnpoeU6Ve3cbMWkmHEWDAVJn4QJtchiiFTo5j7UG2QvwxvaA9vow==", "dependencies": { "component-emitter": "~1.3.0", "debug": "~3.1.0", @@ -539,9 +660,9 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "node_modules/webidl-conversions": { @@ -559,9 +680,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "engines": { "node": ">=10.0.0" }, diff --git a/test-alternative-queries.js b/test-alternative-queries.js new file mode 100644 index 0000000..6c12111 --- /dev/null +++ b/test-alternative-queries.js @@ -0,0 +1,262 @@ +#!/usr/bin/env node +'use strict'; + +const Polestar = require('./clone_modules/polestar.js/polestar.js'); +const axios = require('axios'); + +// Get credentials from command line arguments +const args = process.argv.slice(2); +if (args.length < 2) { + console.error('Usage: node test-alternative-queries.js '); + process.exit(1); +} + +const email = args[0]; +const password = args[1]; + +async function testAlternativeQueries() { + console.log('\n=== Testing Alternative Polestar API Queries ===\n'); + console.log('Based on research from pypolestar/polestar_api and evcc-io/evcc projects\n'); + + try { + const polestar = new Polestar(email, password); + await polestar.login(); + console.log('✓ Login successful!\n'); + + const vehicles = await polestar.getVehicles(); + await polestar.setVehicle(vehicles[0].vin); + + const token = polestar.getAccessToken(); + const vin = polestar.getVehicleVin(); + + console.log(`Testing with VIN: ${vin}\n`); + + const testQueries = [ + { + name: 'getOdometerData (OLD API)', + description: 'Old API query that might have more fields', + query: `query GetOdometerData($vin: String!) { + getOdometerData(vin: $vin) { + averageSpeedKmPerHour + eventUpdatedTimestamp { + iso + unix + } + odometerMeters + tripMeterAutomaticKm + tripMeterManualKm + } + }`, + variables: { vin } + }, + { + name: 'getBatteryData (OLD API)', + description: 'Old API query for battery with possibly more fields', + query: `query GetBatteryData($vin: String!) { + getBatteryData(vin: $vin) { + averageEnergyConsumptionKwhPer100Km + batteryChargeLevelPercentage + chargerConnectionStatus + chargingCurrentAmps + chargingPowerWatts + chargingStatus + estimatedChargingTimeMinutesToTargetDistance + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + estimatedDistanceToEmptyMiles + eventUpdatedTimestamp { + iso + unix + } + } + }`, + variables: { vin } + }, + { + name: 'getChargingConnectionStatus (OLD API)', + description: 'Specific query for charging connection', + query: `query GetChargingConnectionStatus($vin: String!) { + getChargingConnectionStatus(vin: $vin) { + chargerConnectionStatus + chargingPowerWatts + chargingCurrentAmps + chargingStatus + } + }`, + variables: { vin } + }, + { + name: 'carTelematics (OLD API)', + description: 'Original carTelematics query (non-V2)', + query: `query CarTelematics($vin: String!) { + carTelematics(vin: $vin) { + battery { + averageEnergyConsumptionKwhPer100Km + batteryChargeLevelPercentage + chargerConnectionStatus + chargingCurrentAmps + chargingPowerWatts + chargingStatus + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + } + odometer { + averageSpeedKmPerHour + odometerMeters + tripMeterAutomaticKm + tripMeterManualKm + } + } + }`, + variables: { vin } + }, + { + name: 'getConsumerCarByVin', + description: 'Get car details by VIN - might have location', + query: `query GetConsumerCarByVin($vin: String!) { + getConsumerCarByVin(vin: $vin) { + vin + internalVehicleIdentifier + location { + latitude + longitude + heading + } + position { + latitude + longitude + } + } + }`, + variables: { vin } + }, + { + name: 'vehicleLocation', + description: 'Direct location query', + query: `query VehicleLocation($vin: String!) { + vehicleLocation(vin: $vin) { + latitude + longitude + heading + timestamp + } + }`, + variables: { vin } + }, + { + name: 'getCarLocation', + description: 'Alternative location query', + query: `query GetCarLocation($vin: String!) { + getCarLocation(vin: $vin) { + latitude + longitude + heading + } + }`, + variables: { vin } + } + ]; + + const results = { + successful: [], + failed: [] + }; + + for (const testQuery of testQueries) { + console.log(`\n🔍 Testing: ${testQuery.name}`); + console.log(` ${testQuery.description}`); + console.log('─'.repeat(60)); + + try { + const response = await axios.post( + 'https://pc-api.polestar.com/eu-north-1/mystar-v2/', + { + query: testQuery.query, + operationName: testQuery.name.split(' ')[0], + variables: testQuery.variables + }, + { + headers: { + 'cache-control': 'no-cache', + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'pragma': 'no-cache' + } + } + ); + + if (response.data.errors) { + console.log('❌ Query failed'); + const errorMessages = response.data.errors.map(e => e.message); + errorMessages.forEach(msg => { + if (msg.includes('FieldUndefined')) { + // Extract field name from error + const match = msg.match(/Field '(\w+)'/); + if (match) { + console.log(` - Field not available: ${match[1]}`); + } else { + console.log(` - ${msg}`); + } + } else { + console.log(` - ${msg}`); + } + }); + results.failed.push({ + name: testQuery.name, + errors: errorMessages + }); + } else if (response.data.data) { + console.log('✅ SUCCESS! Data retrieved:'); + console.log(JSON.stringify(response.data.data, null, 2)); + results.successful.push({ + name: testQuery.name, + data: response.data.data + }); + } + } catch (error) { + console.log(`❌ Request failed: ${error.message}`); + results.failed.push({ + name: testQuery.name, + error: error.message + }); + } + } + + // Summary + console.log('\n\n' + '='.repeat(60)); + console.log('📊 RESEARCH SUMMARY'); + console.log('='.repeat(60)); + console.log(`✅ Successful queries: ${results.successful.length}`); + console.log(`❌ Failed queries: ${results.failed.length}`); + + if (results.successful.length > 0) { + console.log('\n🎉 WORKING ALTERNATIVE QUERIES FOUND:'); + results.successful.forEach(result => { + console.log(`\n ✅ ${result.name}`); + console.log(` Data: ${JSON.stringify(result.data, null, 2).substring(0, 200)}...`); + }); + + console.log('\n\n💡 RECOMMENDATION:'); + console.log(' Update polestar.js to use these working queries!'); + } else { + console.log('\n😞 NO ALTERNATIVE QUERIES WORK'); + console.log(' The Polestar API appears to have removed these endpoints.'); + console.log(' Only carTelematicsV2 with limited fields is available.'); + } + + console.log('\n📝 FINDINGS:'); + console.log(' - Location data: ' + (results.successful.some(r => r.name.toLowerCase().includes('location')) ? '✅ AVAILABLE' : '❌ NOT AVAILABLE')); + console.log(' - Charging Power/Amps: ' + (results.successful.some(r => JSON.stringify(r.data).includes('chargingPower') || JSON.stringify(r.data).includes('chargingCurrent')) ? '✅ AVAILABLE' : '❌ NOT AVAILABLE')); + console.log(' - Trip Meters: ' + (results.successful.some(r => JSON.stringify(r.data).includes('tripMeter')) ? '✅ AVAILABLE' : '❌ NOT AVAILABLE')); + console.log(' - Average Speed: ' + (results.successful.some(r => JSON.stringify(r.data).includes('averageSpeed')) ? '✅ AVAILABLE' : '❌ NOT AVAILABLE')); + + } catch (error) { + console.error('\n\x1b[31mError:\x1b[0m', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +testAlternativeQueries(); diff --git a/test-available-fields.js b/test-available-fields.js new file mode 100644 index 0000000..6eba196 --- /dev/null +++ b/test-available-fields.js @@ -0,0 +1,211 @@ +#!/usr/bin/env node +'use strict'; + +const Polestar = require('./clone_modules/polestar.js/polestar.js'); +const axios = require('axios'); + +// Get credentials from command line arguments +const args = process.argv.slice(2); +if (args.length < 2) { + console.error('Usage: node test-available-fields.js '); + process.exit(1); +} + +const email = args[0]; +const password = args[1]; + +async function testAvailableFields() { + console.log('\n=== Testing Currently Working Fields ===\n'); + + try { + const polestar = new Polestar(email, password); + await polestar.login(); + console.log('✓ Login successful!\n'); + + const vehicles = await polestar.getVehicles(); + await polestar.setVehicle(vehicles[0].vin); + + const token = polestar.getAccessToken(); + const vin = polestar.getVehicleVin(); + + console.log(`Testing with VIN: ${vin}\n`); + + // Test the current working query first + console.log('🔍 Testing: Current Working Battery Fields'); + console.log('─'.repeat(60)); + + const workingQuery = `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + battery { + vin + batteryChargeLevelPercentage + chargingStatus + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + estimatedDistanceToEmptyMiles + timestamp { seconds nanos } + } + odometer { + vin + odometerMeters + timestamp { seconds nanos } + } + health { + vin + brakeFluidLevelWarning + daysToService + distanceToServiceKm + engineCoolantLevelWarning + oilLevelWarning + serviceWarning + timestamp { seconds nanos } + } + } + }`; + + const response = await axios.post( + 'https://pc-api.polestar.com/eu-north-1/mystar-v2/', + { + query: workingQuery, + operationName: 'CarTelematicsV2', + variables: { vins: [vin] } + }, + { + headers: { + 'cache-control': 'no-cache', + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'pragma': 'no-cache' + } + } + ); + + if (response.data.errors) { + console.log('❌ Errors:', response.data.errors.map(e => e.message)); + } else { + console.log('✅ Success! Data retrieved:\n'); + console.log(JSON.stringify(response.data.data, null, 2)); + } + + // Now let's try to find any additional fields by testing common ones individually + console.log('\n\n🔍 Testing Individual Additional Fields'); + console.log('─'.repeat(60)); + + const fieldsToTest = [ + // Battery fields + { category: 'battery', field: 'batteryCapacityKwh', description: 'Total battery capacity' }, + { category: 'battery', field: 'currentPowerWatts', description: 'Current power draw' }, + { category: 'battery', field: 'chargeRate', description: 'Charging rate' }, + { category: 'battery', field: 'chargeLimit', description: 'Charge limit percentage' }, + { category: 'battery', field: 'chargingPower', description: 'Charging power' }, + { category: 'battery', field: 'timeToFullCharge', description: 'Time to full charge' }, + + // Odometer fields + { category: 'odometer', field: 'tripMeterKm', description: 'Trip meter distance' }, + { category: 'odometer', field: 'range', description: 'Remaining range' }, + + // Health fields + { category: 'health', field: 'tirePressureWarning', description: 'Tire pressure warning' }, + { category: 'health', field: 'batteryHealthPercentage', description: 'Battery health' }, + ]; + + const availableFields = { + battery: ['vin', 'batteryChargeLevelPercentage', 'chargingStatus', 'estimatedChargingTimeToFullMinutes', 'estimatedDistanceToEmptyKm', 'estimatedDistanceToEmptyMiles', 'timestamp'], + odometer: ['vin', 'odometerMeters', 'timestamp'], + health: ['vin', 'brakeFluidLevelWarning', 'daysToService', 'distanceToServiceKm', 'engineCoolantLevelWarning', 'oilLevelWarning', 'serviceWarning', 'timestamp'] + }; + + for (const testField of fieldsToTest) { + const testQuery = `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + ${testField.category} { + vin + ${testField.field} + } + } + }`; + + try { + const testResponse = await axios.post( + 'https://pc-api.polestar.com/eu-north-1/mystar-v2/', + { + query: testQuery, + operationName: 'CarTelematicsV2', + variables: { vins: [vin] } + }, + { + headers: { + 'cache-control': 'no-cache', + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'pragma': 'no-cache' + } + } + ); + + if (testResponse.data.errors) { + console.log(`❌ ${testField.category}.${testField.field} - Not available`); + } else { + console.log(`✅ ${testField.category}.${testField.field} - Available! (${testField.description})`); + availableFields[testField.category].push(testField.field); + + // Show the value if not null + const data = testResponse.data.data.carTelematicsV2[testField.category]; + if (data && data.length > 0 && data[0][testField.field] !== null) { + console.log(` Value: ${JSON.stringify(data[0][testField.field])}`); + } + } + } catch (error) { + console.log(`❌ ${testField.category}.${testField.field} - Error: ${error.message}`); + } + } + + // Summary + console.log('\n\n' + '='.repeat(60)); + console.log('📋 COMPLETE FIELD AVAILABILITY REPORT'); + console.log('='.repeat(60)); + + console.log('\n✅ AVAILABLE BATTERY FIELDS:'); + availableFields.battery.forEach(field => console.log(` - ${field}`)); + + console.log('\n✅ AVAILABLE ODOMETER FIELDS:'); + availableFields.odometer.forEach(field => console.log(` - ${field}`)); + + console.log('\n✅ AVAILABLE HEALTH FIELDS:'); + availableFields.health.forEach(field => console.log(` - ${field}`)); + + console.log('\n❌ NOT AVAILABLE (tested and failed):'); + console.log(' Battery:'); + console.log(' - chargingCurrentAmps'); + console.log(' - chargingPowerWatts'); + console.log(' - averageEnergyConsumptionKwhPer100Km'); + console.log(' - chargerConnectionStatus'); + console.log(' - estimatedChargingTimeMinutesToTargetDistance'); + console.log(' Odometer:'); + console.log(' - averageSpeedKmPerHour'); + console.log(' - tripMeterAutomaticKm'); + console.log(' - tripMeterManualKm'); + console.log(' Health:'); + console.log(' - washerFluidLevelWarning'); + console.log(' Telematics Categories:'); + console.log(' - location (GPS data)'); + console.log(' - climate (HVAC data)'); + console.log(' - locks (door locks)'); + console.log(' - windows (window status)'); + + console.log('\n📝 CONCLUSION:'); + console.log(' The Polestar API has been significantly simplified.'); + console.log(' Only basic telematics data is now available via CarTelematicsV2.'); + console.log(' Many fields that were previously available (or commented out in'); + console.log(' your code) are no longer supported by the API.'); + + } catch (error) { + console.error('\n\x1b[31mError:\x1b[0m', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +testAvailableFields(); diff --git a/test-queries.js b/test-queries.js new file mode 100644 index 0000000..6d38b18 --- /dev/null +++ b/test-queries.js @@ -0,0 +1,337 @@ +#!/usr/bin/env node +'use strict'; + +const Polestar = require('./clone_modules/polestar.js/polestar.js'); +const axios = require('axios'); + +// Get credentials from command line arguments +const args = process.argv.slice(2); +if (args.length < 2) { + console.error('Usage: node test-queries.js '); + process.exit(1); +} + +const email = args[0]; +const password = args[1]; + +async function testQueries() { + console.log('\n=== Polestar API Field Discovery ===\n'); + + let polestar; + let token; + let vin; + + try { + // Initialize and login + console.log('Logging in...'); + polestar = new Polestar(email, password); + await polestar.login(); + console.log('✓ Login successful!\n'); + + // Set a vehicle with better error handling + console.log('Getting vehicles...'); + let vehicles; + try { + vehicles = await polestar.getVehicles(); + console.log(`✓ Found ${vehicles.length} vehicle(s)\n`); + + if (vehicles.length > 0) { + console.log('Vehicle details:', JSON.stringify(vehicles[0], null, 2)); + await polestar.setVehicle(vehicles[0].vin); + console.log(`✓ Set vehicle: ${vehicles[0].vin}\n`); + } else { + console.error('No vehicles found in account'); + process.exit(1); + } + } catch (error) { + console.error('❌ Failed to get vehicles:', error.message); + console.error('This might be due to API changes. Trying to extract token anyway...\n'); + // Continue anyway - we can still test if we can get the token + } + + // Get the access token and VIN using public methods + console.log('Extracting access token and VIN...'); + + token = polestar.getAccessToken(); + vin = polestar.getVehicleVin() || (vehicles && vehicles.length > 0 ? vehicles[0].vin : null); + + if (!token) { + console.error('❌ Could not extract access token'); + console.error('Make sure the Polestar library has been updated with getAccessToken() method'); + process.exit(1); + } + + if (!vin) { + console.error('❌ Could not extract VIN'); + console.error('Please make sure you have at least one vehicle in your account'); + process.exit(1); + } + + console.log('✓ Access token obtained'); + console.log(`✓ Using VIN: ${vin}\n`); + console.log('Starting field discovery tests...\n'); + + // Test queries with different field combinations + const testCases = [ + { + name: 'Battery Data - Extended Fields', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + battery { + vin + batteryChargeLevelPercentage + chargingStatus + chargingCurrentAmps + chargingPowerWatts + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + estimatedDistanceToEmptyMiles + averageEnergyConsumptionKwhPer100Km + chargerConnectionStatus + estimatedChargingTimeMinutesToTargetDistance + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Odometer - Extended Fields', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + odometer { + vin + odometerMeters + averageSpeedKmPerHour + tripMeterAutomaticKm + tripMeterManualKm + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Health - Extended Fields', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + health { + vin + brakeFluidLevelWarning + daysToService + distanceToServiceKm + engineCoolantLevelWarning + oilLevelWarning + serviceWarning + washerFluidLevelWarning + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Location Data', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + location { + vin + latitude + longitude + heading + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Climate Data', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + climate { + vin + climateStatus + targetTemperatureCelsius + interiorTemperatureCelsius + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Door Lock Status', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + locks { + vin + lockStatus + engineHoodLockStatus + frontLeftDoorLockStatus + frontRightDoorLockStatus + rearLeftDoorLockStatus + rearRightDoorLockStatus + tailgateLockStatus + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'Windows Status', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + windows { + vin + frontLeftWindowOpen + frontRightWindowOpen + rearLeftWindowOpen + rearRightWindowOpen + sunroofOpen + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + }, + { + name: 'All Telematics Fields', + query: `query CarTelematicsV2($vins: [String!]!) { + carTelematicsV2(vins: $vins) { + battery { + vin + batteryChargeLevelPercentage + chargingStatus + estimatedChargingTimeToFullMinutes + estimatedDistanceToEmptyKm + timestamp { seconds nanos } + } + odometer { + vin + odometerMeters + timestamp { seconds nanos } + } + health { + vin + brakeFluidLevelWarning + daysToService + distanceToServiceKm + engineCoolantLevelWarning + oilLevelWarning + serviceWarning + timestamp { seconds nanos } + } + location { + vin + latitude + longitude + timestamp { seconds nanos } + } + climate { + vin + climateStatus + timestamp { seconds nanos } + } + locks { + vin + lockStatus + timestamp { seconds nanos } + } + windows { + vin + timestamp { seconds nanos } + } + } + }`, + variables: { vins: [vin] } + } + ]; + + const results = { + successful: [], + failed: [], + partiallySuccessful: [] + }; + + for (const testCase of testCases) { + console.log(`\n🔍 Testing: ${testCase.name}`); + console.log('─'.repeat(60)); + + try { + const response = await axios.post( + 'https://pc-api.polestar.com/eu-north-1/mystar-v2/', + { + query: testCase.query, + operationName: 'CarTelematicsV2', + variables: testCase.variables + }, + { + headers: { + 'cache-control': 'no-cache', + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + 'pragma': 'no-cache' + } + } + ); + + if (response.data.errors) { + console.log('❌ Query failed with errors:'); + response.data.errors.forEach(err => { + console.log(` - ${err.message}`); + }); + results.failed.push({ + name: testCase.name, + errors: response.data.errors + }); + } else if (response.data.data) { + console.log('✅ Query successful!'); + console.log('Response:'); + console.log(JSON.stringify(response.data.data, null, 2)); + results.successful.push({ + name: testCase.name, + data: response.data.data + }); + } + } catch (error) { + console.log('❌ Request failed:', error.message); + results.failed.push({ + name: testCase.name, + error: error.message + }); + } + } + + // Summary + console.log('\n\n' + '='.repeat(60)); + console.log('📊 SUMMARY'); + console.log('='.repeat(60)); + console.log(`✅ Successful queries: ${results.successful.length}`); + console.log(`❌ Failed queries: ${results.failed.length}`); + + if (results.successful.length > 0) { + console.log('\n✅ Working fields found:'); + results.successful.forEach(result => { + console.log(` - ${result.name}`); + }); + } + + if (results.failed.length > 0) { + console.log('\n❌ Failed queries:'); + results.failed.forEach(result => { + console.log(` - ${result.name}`); + }); + } + + } catch (error) { + console.error('\n\x1b[31mError:\x1b[0m', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +testQueries(); diff --git a/widgets/dashboard/api.js b/widgets/dashboard/api.js new file mode 100644 index 0000000..f0eaa87 --- /dev/null +++ b/widgets/dashboard/api.js @@ -0,0 +1,46 @@ +'use strict'; + +async function getVehicle({ homey, registration }) { + if (!homey) { + throw new Error('Missing Homey'); + } + + if (!registration) { + throw new Error('Missing Vehicle registration'); + } + + const driver = await homey.drivers.getDriver('vehicle'); + const vehicle = driver.getDevices().find(device => device.getData().registration === registration); + if (!vehicle) { + throw new Error('Vehicle Not Found'); + } + + return vehicle; +} + +module.exports = { + + async getVehicleStatus({ homey, query }) { + const { registration } = query; + const vehicle = await getVehicle({ homey, registration }); + + return { + battery: vehicle.getCapabilityValue('measure_polestarBattery'), + connected: vehicle.getCapabilityValue('measure_vehicleConnected'), + charging: vehicle.getCapabilityValue('measure_vehicleChargeState'), + current: vehicle.getCapabilityValue('measure_current'), + power: vehicle.getCapabilityValue('measure_power'), + time_remaining: vehicle.getCapabilityValue('measure_vehicleChargeTimeRemaining'), + odometer: vehicle.getCapabilityValue('measure_vehicleOdometer'), + range: vehicle.getCapabilityValue('measure_vehicleRange'), + service: vehicle.getCapabilityValue('alarm_generic'), + }; + }, + + async getVehicles({ homey, body }){ + if (!homey) { + throw new Error('Missing Homey'); + } + return await homey.drivers.getDriver('vehicle').getDevices(); + } +}; diff --git a/widgets/dashboard/preview-dark.png b/widgets/dashboard/preview-dark.png new file mode 100644 index 0000000..3f0799d Binary files /dev/null and b/widgets/dashboard/preview-dark.png differ diff --git a/widgets/dashboard/preview-light.png b/widgets/dashboard/preview-light.png new file mode 100644 index 0000000..d163dc6 Binary files /dev/null and b/widgets/dashboard/preview-light.png differ diff --git a/widgets/dashboard/public/homey-logo.png b/widgets/dashboard/public/homey-logo.png new file mode 100644 index 0000000..7a67d04 Binary files /dev/null and b/widgets/dashboard/public/homey-logo.png differ diff --git a/widgets/dashboard/public/index.html b/widgets/dashboard/public/index.html new file mode 100644 index 0000000..e38659e --- /dev/null +++ b/widgets/dashboard/public/index.html @@ -0,0 +1,188 @@ + + + + + + + +
+
+
+
charge
+
10%
+
+
+
4 A | 4 kW
+
360
+
+
+
range
+
200
+
KM
+
+
88888 KM
+ +
+
+
+ + + + \ No newline at end of file diff --git a/widgets/dashboard/widget.compose.json b/widgets/dashboard/widget.compose.json new file mode 100644 index 0000000..2d78692 --- /dev/null +++ b/widgets/dashboard/widget.compose.json @@ -0,0 +1,26 @@ +{ + "name": { + "en": "Vehicle Dashboard" + }, + "height": 188, + "transparent": true, + "settings": [ + { + "id": "device", + "type": "autocomplete", + "title": { + "en": "Vehicle" + } + } + ], + "api": { + "getVehicles":{ + "method": "GET", + "path": "/" + }, + "getVehicleStatus":{ + "method": "GET", + "path": "/status" + } + } +} \ No newline at end of file