// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: light-gray; icon-glyph: car; | |
// version 2023-04-18 | |
// latest changes: | |
// new kameron api key | |
// added language strings so you can translate it by yourself! | |
// add your my-renault account data: | |
let myRenaultUser = "user" // email | |
let myRenaultPass = "pass" // password | |
// set your ZOE Model (Phase 1 or 2) // bitte eingeben! | |
let ZOE_Phase = "2" // "1" or "2" | |
// set your battery size in kWh // bitte eingeben! | |
let ZOE_Battery = "52" // "52" or "41" or "22" or "21" | |
// should we use apple-maps or google maps? | |
let mapProvider = "apple" // "apple" or "google" | |
// optional: | |
// change that number to eg. 2 if you want to select an another car from your account. | |
// 1 is the first car, 2 will be the second. | |
let carNumber = 1; | |
// optional | |
// account number | |
// if you have problems with accessing the data, try to change this number from 0 to 1 | |
let accountNumber = 0; | |
// enter your VIN / FIN if you have any problems | |
// leave it empty if everything works // leer lassen, wenn alles läuft | |
let VIN = "" // starts with VF1... enter like this: "VF1XXXXXXXXX" | |
// Language Strings | |
// uncomment what you need. | |
// use /* to disable */ | |
// GERMAN / DEUTSCH | |
let text_ladestand = "Ladestand" | |
let text_reichweite = "Reichweite" | |
let text_restenergie = "Restenergie" | |
let text_kilometerstand = "Kilometerstand" | |
let text_position = "Position" | |
let text_klimatisierung = "Klimatisierung" | |
let text_karte_oeffnen = "➤ Karte öffnen" | |
let text_starten = "➤ Starten" | |
let text_stoppen = "➤ Stoppen" | |
let text_wird_geladen = "⚡ Wird geladen …" | |
let text_gekoppelt = "🟢 Gekoppelt" | |
let text_entkoppelt = "⚫ Entkoppelt" | |
let text_laden_starten = "➤ Laden starten" | |
let text_akkutemp = "Akkutemp." | |
/* | |
// ENGLISH | |
let text_ladestand = "Charge level" | |
let text_reichweite = "Range" | |
let text_restenergie = "Remaining energy" | |
let text_kilometerstand = "Mileage" | |
let text_position = "Position" | |
let text_klimatisierung = "Climate control" | |
let text_karte_oeffnen = "➤ Open map" | |
let text_starten = "➤ Start" | |
let text_stoppen = "➤ Stop" | |
let text_wird_geladen = "⚡ Charging..." | |
let text_gekoppelt = "🟢 Connected" | |
let text_entkoppelt = "⚫ Disconnected" | |
let text_laden_starten = "➤ Start charging" | |
let text_akkutemp = "Battery temp." | |
*/ | |
/* | |
// FRENCH: | |
let text_ladestand = "Niveau de charge" | |
let text_reichweite = "Autonomie" | |
let text_restenergie = "Énergie restante" | |
let text_kilometerstand = "Kilométrage" | |
let text_position = "Position" | |
let text_klimatisierung = "Climatisation" | |
let text_karte_oeffnen = "➤ Ouvrir la carte" | |
let text_starten = "➤ Démarrer" | |
let text_stoppen = "➤ Arrêter" | |
let text_wird_geladen = "⚡ En charge ..." | |
let text_gekoppelt = "🟢 Connecté" | |
let text_entkoppelt = "⚫ Déconnecté" | |
let text_laden_starten = "➤ Démarrer la charge" | |
let text_akkutemp = "Temp. de la batterie" | |
*/ | |
// do not edit | |
let kamareonURL = "https://api-wired-prod-1-euw1.wrd-aws.com" | |
let kamareonAPI = "YjkKtHmGfaceeuExUDKGxrLZGGvtVS0J" | |
let gigyaURL = "https://accounts.eu1.gigya.com" | |
let gigyaAPI = "3_7PLksOyBRkHv126x5WhHb-5pqC1qFR8pQjxSeLB6nhAnPERTUlwnYoznHSxwX668" // austria: "3__B4KghyeUb0GlpU62ZXKrjSfb7CPzwBS368wioftJUL5qXE0Z_sSy0rX69klXuHy" | |
const timenow = new Date().toJSON().slice(0,13).replace(/-/g,'').replace(/T/g,'-') //20201028-14 (14 = hour) | |
// clear everything from keychain if we are on an other day | |
if(Keychain.contains('lastJWTCall') && Keychain.get('lastJWTCall') != timenow){ | |
clearKeychain() | |
console.log("Keychain cleared") | |
} | |
// clear keychain, if script gets called with action parameters (to get new tokens) | |
if(args.queryParameters.action != ""){ | |
clearKeychain() | |
console.log("Keychain cleared cause of action parameters") | |
} | |
function clearKeychain() { | |
if(Keychain.contains('VIN')) { Keychain.remove('VIN') } | |
if(Keychain.contains('carPicture')) { Keychain.remove('carPicture') } // enable if picture is wrong | |
if(Keychain.contains('account_id')) { Keychain.remove('account_id') } | |
if(Keychain.contains('gigyaJWTToken')) { Keychain.remove('gigyaJWTToken') } | |
if(Keychain.contains('gigyaCookieValue')) { Keychain.remove('gigyaCookieValue') } | |
if(Keychain.contains('gigyaPersonID')) { Keychain.remove('gigyaPersonID') } | |
} | |
if(VIN && VIN != ""){ | |
Keychain.set('VIN', VIN) | |
} | |
const widget = new ListWidget() | |
await createWidget() | |
// used for debugging if script runs inside the app | |
if (!config.runsInWidget) { | |
await widget.presentMedium() | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
// build the widget | |
async function createWidget(items) { | |
// get all data in a single variable | |
const data = await getData() | |
console.log(data) | |
//widget.refreshAfterDate = new Date(Date.now() + 300) // dont know if this works | |
widget.setPadding(10, 0, 10, 20) | |
const wrap = widget.addStack() | |
wrap.layoutHorizontally() | |
wrap.topAlignContent() | |
wrap.spacing = 15 | |
const column0 = wrap.addStack() | |
column0.layoutVertically() | |
if(data.carPicture){ | |
const icon = await getImage("my-renault-car-"+VIN+".png", data.carPicture) | |
console.log("getting my-renault-car-"+VIN+".png") | |
console.log("current icon: " + data.carPicture) | |
let CarStack = column0.addStack() | |
let iconImg = CarStack.addImage(icon) | |
// simple hack if we have a phase 1 model (no location data & no hvac-status available) – resize car-image | |
// not the smartest solution - but i try to check if the results show only 1 column. | |
// if column2 is empty, we have to resizes the car-image for better styling | |
if( typeof(data.locationStatus) == 'undefined' && typeof(data.hvacStatus) == 'undefined' ){ | |
iconImg.imageSize = new Size(130, 73) | |
} | |
} | |
column0.addSpacer(8) | |
if(typeof(data.batteryStatus) != 'undefined'){ | |
let plugIcon | |
let plugStateLabel | |
let plugStateUrl | |
let scriptName = encodeURIComponent(Script.name()) | |
const PlugWrap = column0.addStack() | |
PlugWrap.layoutHorizontally() | |
//PlugWrap.setPadding(0,15,0,15) | |
if(data.batteryStatus.attributes.plugStatus == 0){ | |
//plugIcon = await getImage("zoe-plug-off.png", "") | |
plugStateLabel = text_entkoppelt | |
} else { | |
//plugIcon = await getImage("zoe-plug-on.png", "") | |
plugStateLabel = text_gekoppelt | |
} | |
if(data.batteryStatus.attributes.chargingStatus == "1.0"){ | |
plugStateLabel = text_wird_geladen | |
} | |
if(data.batteryStatus.attributes.plugStatus == 1 && data.batteryStatus.attributes.chargingStatus == "0"){ | |
plugStateLabel = text_laden_starten | |
plugStateUrl = `scriptable:///run?scriptName=${scriptName}&action=start_charge`; | |
} | |
const PlugText = PlugWrap.addStack() | |
PlugText.setPadding(0,10,0,0) | |
PlugText.layoutVertically() | |
plugStateLabel = PlugText.addText(plugStateLabel) | |
plugStateLabel.font = Font.regularSystemFont(10) | |
plugStateLabel.url = plugStateUrl | |
PlugText.addSpacer(6) | |
if(data.batteryStatus.attributes.chargingStatus == "1.0"){ // must be 1.0, debug = 0 | |
// (Akku minus verf. Energie) geteilt durch Restzeit = Ladegeschwindigkeit (by Marc) | |
let batteryAvailableEnergy = data.batteryStatus.attributes.batteryAvailableEnergy; | |
let chargingInstantaneousPower = data.batteryStatus.attributes.chargingInstantaneousPower | |
chargingInstantaneousPower = Math.round(chargingInstantaneousPower) | |
// check if the numbers are in Watt or kW | |
if(chargingInstantaneousPower > 150){ | |
// if over 200, we believe the value is in watt :-) | |
chargingInstantaneousPower = chargingInstantaneousPower / 1000 | |
} | |
console.log('chargingInstantaneousPower: ' + chargingInstantaneousPower) | |
chargingInstantaneousPower = Math.round(chargingInstantaneousPower).toLocaleString() | |
console.log('chargingInstantaneousPower rounded: ' + chargingInstantaneousPower) | |
let chargingPower = chargingInstantaneousPower | |
if(ZOE_Battery){ | |
chargingPower = (ZOE_Battery - batteryAvailableEnergy) / (data.batteryStatus.attributes.chargingRemainingTime / 60) | |
console.log("chargingPower calculated: " + chargingPower); | |
chargingPower = chargingPower.toFixed(1).toLocaleString() | |
} | |
console.log('chargingPower final result: ' + chargingPower) | |
let chargingRemainingTime = time_convert(data.batteryStatus.attributes.chargingRemainingTime) | |
chargingRemainingTimeString = " | " + chargingRemainingTime + " h" | |
chargeStateLabel = " " + chargingPower +" kW"+chargingRemainingTimeString | |
chargeStateLabel = PlugText.addText(chargeStateLabel) | |
chargeStateLabel.font = Font.regularSystemFont(10) | |
PlugText.addSpacer(2) | |
} | |
} | |
const column1 = wrap.addStack() | |
column1.layoutVertically() | |
//column1.addSpacer(3) | |
// simple quota-limit check: | |
// (battery status is the first request – if it reports nothing, we can be sure, that there will be no other data available at the moment) | |
if(!data.batteryStatus || typeof(data.batteryStatus) == "undefined"){ | |
if (config.runsInWidget) { // only in widget | |
throw new Error('Quota Limit! – Datenabruf zur Zeit nicht möglich. Später nochmals versuchen oder bei Renault beschweren.') | |
} else { | |
console.log('Quota Limit! – Datenabruf zur Zeit nicht möglich. Später nochmals versuchen oder bei Renault beschweren.') | |
} | |
} | |
if(typeof(data.batteryStatus) != 'undefined'){ | |
let BatteryStack = column1.addStack() | |
BatteryStack.layoutVertically() | |
const batteryStatusLabel = BatteryStack.addText(text_ladestand) | |
batteryStatusLabel.font = Font.mediumSystemFont(12) | |
const batteryStatusVal = BatteryStack.addText(data.batteryStatus.attributes.batteryLevel.toString()+" %") | |
batteryStatusVal.font = Font.boldSystemFont(16) | |
column1.addSpacer(10) | |
// push Message if maxSoC reachead | |
/* under development! */ | |
/* | |
let maxSoC = 62 | |
// if(batteryStatusVal == maxSoC && data.batteryStatus.attributes.chargingStatus != "-1.0"){ | |
const delaySeconds = 1; | |
let currentDate = new Date; | |
let newDate = new Date(currentDate.getTime() + (delaySeconds * 1000)); | |
chargeFull = new Notification() | |
chargeFull.identifier = "maxSoCReached" | |
chargeFull.title = "🔋 Geladen" | |
chargeFull.body = "Die Batterie Deines Fahrzeugs wurde zu " + maxSoC + " % geladen!" | |
chargeFull.sound = "complete" | |
chargeFull.setTriggerDate(newDate); | |
chargeFull.schedule() | |
// } */ | |
} | |
if(typeof(data.batteryStatus) != 'undefined'){ | |
let RangeStack = column1.addStack() | |
RangeStack.layoutVertically() | |
const RangeStatusLabel = RangeStack.addText(text_reichweite) | |
RangeStatusLabel.font = Font.mediumSystemFont(12) | |
const RangeStatusVal = RangeStack.addText(data.batteryStatus.attributes.batteryAutonomy.toString()+" km") | |
RangeStatusVal.font = Font.boldSystemFont(16) | |
column1.addSpacer(10) | |
} | |
if(ZOE_Phase == 1 && typeof(data.batteryStatus) != 'undefined'){ | |
if(typeof(data.batteryStatus.attributes.batteryTemperature) != 'undefined'){ | |
let TempStack = column1.addStack() | |
TempStack.layoutVertically() | |
const TempStatusLabel = TempStack.addText(text_akkutemp) | |
TempStatusLabel.font = Font.mediumSystemFont(12) | |
const TempStatusVal = TempStack.addText(data.batteryStatus.attributes.batteryTemperature.toString()+" °C") | |
TempStatusVal.font = Font.boldSystemFont(16) | |
} | |
} | |
if(ZOE_Phase == 2 && typeof(data.batteryStatus) != 'undefined'){ | |
if(typeof(data.batteryStatus.attributes.batteryAvailableEnergy) != 'undefined'){ | |
let AvEnergyStack = column1.addStack() | |
AvEnergyStack.layoutVertically() | |
const AvEnergyStatusLabel = AvEnergyStack.addText(text_restenergie) | |
AvEnergyStatusLabel.font = Font.mediumSystemFont(12) | |
const AvEnergyStatusVal = AvEnergyStack.addText(data.batteryStatus.attributes.batteryAvailableEnergy.toString()+" kWh") | |
AvEnergyStatusVal.font = Font.boldSystemFont(16) | |
} | |
} | |
const column2 = wrap.addStack() | |
column2.layoutVertically() | |
//column2.addSpacer(3) | |
if(typeof(data.cockpitStatus) != 'undefined'){ | |
let MileageStack = column2.addStack() | |
MileageStack.layoutVertically() | |
const MileageStatusLabel = MileageStack.addText(text_kilometerstand) | |
MileageStatusLabel.font = Font.mediumSystemFont(12) | |
let mileage = Math.round(data.cockpitStatus.attributes.totalMileage).toLocaleString() | |
const MileageStatusVal = MileageStack.addText(mileage.toString()+" km") | |
MileageStatusVal.font = Font.boldSystemFont(16) | |
column2.addSpacer(10) | |
} | |
if(typeof(data.locationStatus) != 'undefined'){ | |
let LocationStack = column2.addStack() | |
LocationStack.spacing = 2 | |
LocationStack.layoutVertically() | |
const LocationLabel = LocationStack.addText(text_position) | |
LocationLabel.font = Font.mediumSystemFont(12) | |
const LocationVal = LocationStack.addText(text_karte_oeffnen) | |
LocationVal.font = Font.boldSystemFont(12) | |
if(mapProvider == "google"){ | |
// https://www.google.com/maps/search/?api=1&query=58.698017,-152.522067 | |
LocationVal.url = "https://www.google.com/maps/search/?api=1&query="+data.locationStatus.attributes.gpsLatitude+","+data.locationStatus.attributes.gpsLongitude | |
} else { | |
// fallback to apple… | |
// http://maps.apple.com/?ll=50.894967,4.341626 | |
LocationVal.url = "http://maps.apple.com/?q=ZOE&ll="+data.locationStatus.attributes.gpsLatitude+","+data.locationStatus.attributes.gpsLongitude | |
} | |
//LocationStack.addSpacer(0.5) | |
column2.addSpacer(12) | |
} | |
//if(typeof(data.hvacStatus) != 'undefined'){ // we have to uncomment this later! | |
let AcStack = column2.addStack() | |
AcStack.spacing = 2 | |
AcStack.layoutVertically() | |
const AcLabel = AcStack.addText(text_klimatisierung) | |
AcLabel.font = Font.mediumSystemFont(12) | |
// create a self-opening url to run the start_ac function | |
// could be nicer, but seems to work at the moment. | |
let scriptName = encodeURIComponent(Script.name()) | |
let AcVal | |
let ac_url | |
if(args.queryParameters.action == 'start_ac'){ | |
AcVal = AcStack.addText(text_stoppen) | |
ac_url = `scriptable:///run?scriptName=${scriptName}&action=stop_ac`; | |
} else { | |
AcVal = AcStack.addText(text_starten) | |
ac_url = `scriptable:///run?scriptName=${scriptName}&action=start_ac`; | |
} | |
AcVal.font = Font.boldSystemFont(12) | |
AcVal.url = ac_url | |
//} // we have to uncomment this later! | |
} | |
// fetch all data | |
async function getData() { | |
// we are going now a long way through multiple servers to get access to our data | |
// 1. fetch session and user data from gigya | |
let gigyaCookieValue | |
let gigyaPersonID | |
if(Keychain.contains('gigyaCookieValue') && Keychain.get('gigyaCookieValue') != ""){ | |
gigyaCookieValue = Keychain.get('gigyaCookieValue') | |
} | |
//console.log('gigyaCookieValue (from keychain): ' + gigyaCookieValue) | |
if(Keychain.contains('gigyaPersonID') && Keychain.get('gigyaPersonID') != ""){ | |
gigyaPersonID = Keychain.get('gigyaPersonID') | |
} | |
console.log('gigyaPersonID (from keychain): ' + gigyaPersonID) | |
if(gigyaCookieValue == "" || gigyaPersonID == "" || | |
typeof(gigyaCookieValue) == "undefined" || typeof(gigyaPersonID) == "undefined") | |
{ | |
let url = gigyaURL + '/accounts.login?loginID=' + encodeURIComponent(myRenaultUser) + '&password=' + encodeURIComponent(myRenaultPass) + '&include=data&apiKey=' + gigyaAPI | |
let req = new Request(url) | |
let apiResult = await req.loadString() | |
apiResult = JSON.parse(apiResult) | |
console.log("1.: " + apiResult.statusCode) | |
if(apiResult.statusCode == "403"){ | |
let loginMessage = "Login nicht möglich. Zugangsdaten prüfen." | |
throw new Error(loginMessage); | |
} else { | |
gigyaCookieValue = apiResult.sessionInfo.cookieValue | |
gigyaPersonID = apiResult.data.personId | |
Keychain.set('gigyaCookieValue', gigyaCookieValue) | |
Keychain.set('gigyaPersonID', gigyaPersonID) | |
//console.log('gigyaCookieValue (new generated): ' + gigyaCookieValue) | |
//console.log('gigyaPersonID (new generated): ' + gigyaPersonID) | |
} | |
} | |
// 2. fetch JWT data from gigya | |
// renew gigyaJWTToken once a day | |
if(Keychain.contains('lastJWTCall') == false){ | |
Keychain.set('lastJWTCall', 'never') | |
} | |
let gigyaJWTToken | |
if(Keychain.contains('gigyaJWTToken')){ | |
gigyaJWTToken = Keychain.get('gigyaJWTToken') | |
} | |
//console.log('gigyaJWTToken (from keychain): ' + gigyaJWTToken) | |
if(gigyaJWTToken == "" || typeof(gigyaJWTToken) == "undefined"){ | |
let expiration = 87000 | |
url = gigyaURL + '/accounts.getJWT?oauth_token=' + gigyaCookieValue + '&login_token=' + gigyaCookieValue + '&expiration=' + expiration + '&fields=data.personId,data.gigyaDataCenter&ApiKey=' + gigyaAPI | |
req = new Request(url) | |
apiResult = await req.loadString() | |
apiResult = JSON.parse(apiResult) | |
console.log("3.: " + apiResult.statusCode) | |
gigyaJWTToken = apiResult.id_token | |
Keychain.set('gigyaJWTToken', gigyaJWTToken) | |
//console.log('gigyaJWTToken (new generated): ' + gigyaJWTToken) | |
const callDate = new Date().toJSON().slice(0,13).replace(/-/g,'').replace(/T/g,'-') | |
Keychain.set('lastJWTCall', callDate) | |
//console.log('lastJWTCall (new generated): ' + callDate) | |
} | |
// 3. fetch data from kamereon (person) | |
// if not in Keychain (we try to avoid quota limits here) | |
let account_id | |
if(Keychain.contains('account_id')){ | |
account_id = Keychain.get('account_id') | |
} | |
console.log('account_id (from keychain): ' + account_id) | |
if(account_id == "" || typeof(account_id) == "undefined"){ | |
url = kamareonURL + '/commerce/v1/persons/' + gigyaPersonID + '?country=DE' | |
req = new Request(url) | |
req.method = "GET" | |
req.headers = { "x-gigya-id_token": gigyaJWTToken, "apikey": kamareonAPI } | |
apiResult = await req.loadString() | |
console.log("4.: " + apiResult) | |
apiResult = JSON.parse(apiResult) | |
if(apiResult.type == "FUNCTIONAL"){ | |
let quotaMessage = apiResult.messages[0].message + " – Login derzeit nicht möglich. Später nochmal versuchen." | |
throw new Error(quotaMessage); | |
} else { | |
account_id = apiResult.accounts[accountNumber].accountId | |
Keychain.set('account_id', account_id) | |
console.log('account_id (new generated): ' + account_id) | |
} | |
} | |
// 4. fetch data from kamereon (all vehicles data) | |
// we need this only once to get the picture of the car and the VIN! | |
let carPicture | |
if(Keychain.contains('carPicture')){ | |
carPicture = Keychain.get('carPicture') | |
} | |
console.log('carPicture (from keychain): ' + carPicture) | |
if( Keychain.contains('VIN') && Keychain.get('VIN') != "" ){ | |
VIN = Keychain.get('VIN') | |
} | |
//console.log('VIN (from keychain): ' + VIN) | |
if(carPicture == "" || typeof(carPicture) == "undefined" || VIN == "" || typeof(VIN) == "undefined"){ | |
url = kamareonURL + '/commerce/v1/accounts/' + account_id + '/vehicles?country=DE' | |
req = new Request(url) | |
req.method = "GET" | |
req.headers = { "x-gigya-id_token": gigyaJWTToken, "apikey": kamareonAPI } | |
apiResult = await req.loadString() | |
apiResult = JSON.parse(apiResult) | |
if(carNumber == ""){ // fallback | |
carNumber = 0; | |
} | |
// set correct carNumber to array (starts with 0) | |
carNumber = carNumber - 1; | |
console.log("carNumber: " + carNumber) | |
// set carPicture | |
carPicture = await apiResult.vehicleLinks[carNumber].vehicleDetails.assets[0].renditions[0].url | |
Keychain.set('carPicture', carPicture) | |
console.log('carPicture (new): ' + carPicture) | |
// set VIN | |
if(VIN == "" || typeof(VIN) == "undefined"){ | |
VIN = apiResult.vehicleLinks[carNumber].vin | |
Keychain.set('VIN', VIN) | |
console.log('VIN (new generated): ' + VIN) | |
} | |
} | |
// log all vehicle data: | |
// console.log(apiResult.vehicleLinks) | |
// NOW WE CAN READ AND SET EVERYTHING INTO AN OBJECT: | |
const allResults = {}; | |
// real configurator picture of the vehicle | |
// old call: let carPicture = allVehicleData.vehicleLinks[0].vehicleDetails.assets[0].renditions[0].url // renditions[0] = large // renditions[1] = small image | |
allResults["carPicture"] = carPicture | |
// batteryStatus | |
// version: 2 | |
// batteryLevel = Num (percentage) | |
// plugStatus = bolean (0/1) | |
// chargeStatus = bolean (0/1) (?) | |
let batteryStatus = await getStatus('battery-status', 2, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
allResults["batteryStatus"] = batteryStatus | |
// cockpitStatus | |
// version: 2 | |
// totalMileage = Num (in Kilometres!) | |
let cockpitStatus = await getStatus('cockpit', 2, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
allResults["cockpitStatus"] = cockpitStatus | |
// locationStatus | |
// version: 1 | |
// gpsLatitude | |
// gpsLongitude | |
// LastUpdateTime | |
let locationStatus = await getStatus('location', 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
allResults["locationStatus"] = locationStatus | |
// chargeSchedule | |
// note: unused at the moment! | |
// version: 1 | |
let chargeSchedule = await getStatus('charging-settings', 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
allResults["chargeSchedule"] = chargeSchedule | |
// hvacStatus | |
// version: 1 | |
let hvacStatus = await getStatus('hvac-status', 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
allResults["hvacStatus"] = hvacStatus | |
console.log('hvacStatus: ' + hvacStatus) | |
// query parameter / args | |
// if query action = "start_ac" we start "vorklimatisierung" | |
// default temperature will be 21°C | |
let query_action = args.queryParameters.action | |
if( query_action == "start_ac" ){ | |
let attr_data = '{"data":{"type":"HvacStart","attributes":{"action":"start","targetTemperature":"21"}}}' | |
let action = await postStatus('hvac-start', attr_data.toString(), 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
console.log("start_ac_action: " + action) | |
//throw new Error(action) | |
} | |
if( query_action == "stop_ac" ){ | |
let attr_data = '{"data":{"type":"HvacStart","attributes":{"action":"cancel"}}}' | |
let action = await postStatus('hvac-start', attr_data.toString(), 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
console.log("stop_ac_action: " + action) | |
} | |
if( query_action == "start_charge" ){ | |
let attr_data = '{"data":{"type":"ChargingStart","attributes":{"action":"start"}}}' | |
let action = await postStatus('charging-start', attr_data.toString(), 1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) | |
console.log("start_charge_action: " + action) | |
} | |
console.log(allResults) | |
// return array | |
return allResults | |
} | |
// general function to get status-values from our vehicle | |
async function getStatus(endpoint, version=1, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI ){ | |
// fetch data from kamereon (single vehicle) | |
url = kamareonURL + '/commerce/v1/accounts/' + account_id + '/kamereon/kca/car-adapter/v' + version + '/cars/' + VIN + '/' + endpoint + '?country=DE' | |
req = new Request(url) | |
req.method = "GET" | |
req.headers = { "x-gigya-id_token": gigyaJWTToken, "apikey": kamareonAPI, "Content-type": "application/vnd.api+json" } | |
apiResult = await req.loadString() | |
if (req.response.statusCode == 200) { | |
apiResult = JSON.parse(apiResult) | |
} | |
return apiResult.data | |
console.log(api.Result.data) | |
} | |
// general function to POST status-values to our vehicle | |
async function postStatus(endpoint, jsondata, version, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI ){ | |
url = kamareonURL + '/commerce/v1/accounts/' + account_id + '/kamereon/kca/car-adapter/v' + version + '/cars/' + VIN + '/actions/' + endpoint + '?country=DE' | |
request = new Request(url) | |
request.method = "POST" | |
request.body = jsondata | |
request.headers = { "x-gigya-id_token": gigyaJWTToken, "apikey": kamareonAPI, "Content-type": "application/vnd.api+json" } | |
apiResult = await request.loadString() | |
console.log(apiResult) | |
//debug: | |
// throw new Error(url) | |
let pushBody | |
let sound | |
if (request.response.statusCode == 200) { | |
pushBody = "Die Übermittlung des Befehls war erfolgreich." | |
sound = "piano_success" | |
} else { | |
pushBody = "Es ist ein Fehler beim Senden des Befehls aufgetreten. Keine Verbindung. Code:" + request.response.statusCode | |
sound = "piano_error" | |
} | |
pushMessage = new Notification() | |
pushMessage.identifier = "zoePostStatus" | |
if(endpoint == "hvac-start"){ | |
pushMessage.title = "Kommando an Klimaanlage gesendet" | |
} | |
if(endpoint == "charge-start"){ | |
pushMessage.title = "Kommando an Ladeanlage gesendet" | |
} | |
//pushMessage.title = "Befehl gesendet" | |
pushMessage.body = pushBody | |
pushMessage.sound = sound | |
//pushMessage.setTriggerDate(newDate); | |
pushMessage.schedule() | |
return apiResult | |
} | |
function time_convert(num) | |
{ | |
var hours = Math.floor(num / 60); | |
var minutes = num % 60; | |
return hours + ":" + minutes; | |
} | |
// get images from local filestore or download them once | |
// this part is inspired by the dm-toilet-paper widget | |
// credits: https://gist.github.com/marco79cgn | |
async function getImage(image, imgUrl) { | |
let fm = FileManager.local() | |
let dir = fm.documentsDirectory() | |
let path = fm.joinPath(dir, image) | |
if (fm.fileExists(path)) { | |
return fm.readImage(path) | |
//fm.remove(path) | |
} else { | |
// download once | |
let imageUrl | |
switch (image) { | |
case 'my-renault-car-'+VIN+'.png': | |
imageUrl = imgUrl | |
break | |
default: | |
console.log(`Sorry, couldn't find ${image}.`); | |
} | |
if(imageUrl){ | |
let iconImage = await loadImage(imageUrl) | |
fm.writeImage(path, iconImage) | |
return iconImage | |
} | |
} | |
} | |
// helper function to download an image from a given url | |
async function loadImage(imgUrl) { | |
const req = new Request(imgUrl) | |
return await req.loadImage() | |
} | |
// end of script |
Thank you!! Really cool.
I had a problem with a password that contains %
signs.
There is a problem in the following line:
To support passwords with (% sign), they should be escaped with encodeURIComponent
.
It should be changed to
let url = gigyaURL + '/accounts.login?loginID=' + encodeURIComponent(myRenaultUser) + '&password=' + encodeURIComponent(myRenaultPass) + '&apiKey=' + gigyaAPI
Thank you!! Really cool.
I had a problem with a password that contains%
signs.
There is a problem in the following line:To support passwords with (% sign), they should be escaped with
encodeURIComponent
.
It should be changed tolet url = gigyaURL + '/accounts.login?loginID=' + encodeURIComponent(myRenaultUser) + '&password=' + encodeURIComponent(myRenaultPass) + '&apiKey=' + gigyaAPI
Danke! Ist eingefügt
Hi, ich habe eine ZOE von 2018. Die unterstützt leider nicht das Abfragen der location (bzw. das Abfragen schon, aber es kommt halt kein Ergebnis. Wenn man aber einfach den Code-Block in den Zeilen 112-122 in ein if (data.locationStatus) { ... }
packt, dann funktioniert es auch mit meiner ZOE :-)
Hi, ich habe eine ZOE von 2018. Die unterstützt leider nicht das Abfragen der location (bzw. das Abfragen schon, aber es kommt halt kein Ergebnis. Wenn man aber einfach den Code-Block in den Zeilen 112-122 in ein
if (data.locationStatus) { ... }
packt, dann funktioniert es auch mit meiner ZOE :-)
Danke @jrathert für den Hinweis. Ist nun bei allen Datenfeldern so eingebaut (Check if defined) und nun kann man es auch mit den älteren Modellen nutzen.
kWh ist eine Einheit für die Batteriekapazität (Energiemenge), nicht die Ladeleistung. Leistung wird in Watt bzw. kW angegeben.
Danke @druelie – habe es korrigiert. Die falschen Werte allerdings kann ich derzeit nicht in den Griff kriegen, das ist das, was das Auto leider per API übermittelt. Weißt Du ob das in der Renault App richtig angezeigt wird?
In der Renault App wird das ja gar nicht mehr angezeigt. Vermutlich auch aus diesem Grund. :-)
Vielleicht lässt sich auch noch einiges von folgender Android App abschauen:
https://github.com/robertaramar/MyZOE
Allerdings ist es ja kein Widget und der Entwickler macht keine Änderungen mehr, weil er keine Lust mehr hat, Renault ständig hinterher zu hechten, wenn wieder etwas nicht funktioniert.
Oder von diesem PHP-Script, was OS unabhängig funktioniert:
https://www.goingelectric.de/forum/viewtopic.php?f=57&t=58182
Falls das noch nicht bekannt ist :)
Es gibt einen (polnischen, aber dank Google Translate gut lesbaren) Artikel auf https://elektrowoz.pl/auta/renault-zoe-ze-50-teoretycznie-powinien-obslugiwac-moc-ladowania-70-kw-odkrycie-czytelnika/, in dem spekuliert wird, dass chargingInstantaneousPower der aktuell maximal moeglichen Ladeleistung entspricht.
vielleicht lässt sich noch ein Parameter zum Script hinzufügen der einen wählen lässt, ob Google Maps oder Apple Maps bei der Lokalisierung verwendet werden soll
@bloemi gute Idee!
@bloemi Ist nun eingebaut …
Vielen Dank für das geniale Skript. Etwas ähnliches hatte ich über ein paar Umwege realisiert.
Aber dein Skript wird das wohl jetzt ablösen! :-)
Vorschlag zum Layout bzw. Design: Schicke SFSymbols könnten gerne die Emoji-Symbole ablösen.
function addSymbol({
symbol = 'applelogo',
stack,
color = Color.white(),
size = 20,
imageOpacity = 1,
}) {
const _sym = SFSymbol.named(symbol)
const wImg = stack.addImage(_sym.image)
wImg.tintColor = color
wImg.imageSize = new Size(size, size)
wImg.containerRelativeShape = false
wImg.imageOpacity = imageOpacity
}
Danke für das Skript! Leider zeigts bei mir immer die falsche Wagenfarbe und die Phase 1 an statt 2. Hatte vorher einen Phase 1 in meinem Konto hinterlegt, nachdem löschen aus der Renault App zeigt das Widget die Farbe aber trotzdem an. Habe auch die 43 mal aus kommentiert, Bild ändert sich aber nicht.
Scriptable App neu installieren hats gelöst...
@t-stricker Ja, das Bild vom Auto wird in einem Keychain Element gesichert. Also Lösung hättest Du Zeile 43 aktivieren können und nach 1 Tag wäre das dann erschienen. Oder Du hättest noch einfach clearKeychain()
irgendwo nach Zeile 49 schreiben können, dann wäre der Keychain einmal komplett geleert worden. Neuinstallation von Scriptable scheint also auch den Keychain zu leeren. Danke für die Rückmeldung.
Falls ihr noch Hilfe braucht Entwicklung/Testing gerne melden.
Hi !! I always get the same error.
Error on line 425:45: TypeError: undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails')
Any idea ? (I’m from France ) thank you very much !
@RYOSAEBAXYZ Are you sure, you copied the whole script? It looks like the script is not able to get all data from the server.
Still the same msg no matter what. It seems credentials are not at fault. I can’t figure out what it is.
Zoe 1 (2015)
TypeError: undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails')
Maybe its a better approach to ask for the users credentials and store them in the device keychain instead to safe cleartext data at the top of the script ?
Should I be changing the country code for the API ? (France ?)
@RYOSAEBAXYZ I think i figured out why that's not working in france:
- gigyaAPI is different in france -->
"3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a"
let gigyaAPI = "3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a"
- country is "FR" instead of "DE"
you just need to replace all "DE" with "FR - And for my case, Renault API returns 3 accounts in step "4. Fetch the Data". So i updated the script to get the right one. I wrote this code for keychain set of account_id :
for (const index in apiResult.accounts) {
let account = apiResult.accounts[index];
if ("accountType" in account) {
if (account["accountType"] == "MYRENAULT") {
console.log(account);
account_id = account.accountId;
break;
}
}
}
Keychain.set('account_id', account_id)
console.log('account_id (new generated): ' + account_id)
let account = apiResult.accounts[index];
Thanks !! Where should your bit be added then ?
Should I be changing the country code for the API ? (France ?)
@RYOSAEBAXYZ I think i figured out why that's not working in france:
- gigyaAPI is different in france -->
"3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a"
let gigyaAPI = "3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a"
- country is "FR" instead of "DE"
you just need to replace all "DE" with "FR- And for my case, Renault API returns 3 accounts in step "4. Fetch the Data". So i updated the script to get the right one. I wrote this code for keychain set of account_id :
for (const index in apiResult.accounts) { let account = apiResult.accounts[index]; if ("accountType" in account) { if (account["accountType"] == "MYRENAULT") { console.log(account); account_id = account.accountId; break; } } } Keychain.set('account_id', account_id) console.log('account_id (new generated): ' + account_id)
You figured it out !! That was it !! The multiple accounts !! Thanks a lot !
Since today the call is resulting in 403 making Kameron server calls from Germany. Is this reproducible to someone else?
same error here from cologne
i hope this is only temporary
Seems that Renault changed the Kameron API key. Change it according to my revision and it should work again.
@dehsgr: Where did you extract the new API key?
@db-EV you might intercept communication of your MyRenault App and sniff that key.
@dehsgr: Then the app isn't getting the key from https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com/configuration/android/config_en_GB.json (or other localized .json) anymore. Maybe it's getting the API key from Firebase now?
@db-EV I really don‘t know where the key comes from. I sniffed it with traffic interception, following the request path of this gist. I did need a fast solution as I used same request collection directly from within node-red. If there is any possibility to get these keys dynamically, I would like you to tell me how. ;-)
Thanks @dehsgr! Great!
@dehsgr what did you use for the sniffing?
With Fiddler I am able to track Gigya requests but I am unable to track Kamereon requests.
I am trying to document the API on https://github.com/hacf-fr/renault-api/ (you can contact me on dev_at_zeflip_com
if you do not wish to publicise the method)
@epenet I used Fiddler and was able to track Kamareon requests too. :-D
I'm still unable to track Kamereon requests (the Android application complains about the certificates) but luckily both Gigya and Firebase requests are decrypted correctly and @db-EV was correct regarding Firebase.
I cleared all settings from my application, and on first launch it made a POST request to https://firebaseremoteconfig.googleapis.com/v1/projects/942374850736/namespaces/firebase:fetch
The resulting JSON contained a different key to yours: wRhd1ZSqXfOuF8oTNCkS029YU9qPGWa4
which I assume is linked to the country code (FR for me). Both your key and my key seem to work OK.
I have created a pull request on hacf-fr/renault-api#184 (branch https://github.com/hacf-fr/renault-api/tree/firebase) if anyone wants to help...
@epenet did you install & trust Fiddler root certificate on Android? This is pre-requisite.
@dehsgr yes I did, and that's how I'm able to decrypt Gigya and Firebase data.
If I add api-wired-prod-1-euw1.wrd-aws.com
to the skip decryption option, then the application work but I can't see the data.
If I remove api-wired-prod-1-euw1.wrd-aws.com
from the skip decryption option, then the application fail with a big red screen "security alert"
Maybe it's because I'm stuck on version 4.5.0 of the application
With the new API key, it worked exactly once. Now the data no longer updates (without an error message) even though I am on the road.
I am at a loss.
Maybe just "normal" problems with the Renault servers. Running here with 2 different accounts without problems so far.
No problem with the new Ae9... key neither. Still interested in finding the original source for the API keys.
@lix-src I am stuck on the same problem. It worked once but now the data does not change anymore - without any errors.
@lix-src But the MyRenault App is not updating too! So I think we have to wait …
@lix-srv & @mountbatt did you intercept app traffic and get the same key? Maybe you have to use another key. Or Renault is changing sth. in the backend again.
My last update was at 17:55, so for me the Ae9... key is still working
There seem to be some issues indeed. My Renault app hasn’t refreshed since 3pm. :-(
After a clean install, I can see the Renault app retrieving the apikey Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2 (among others) from https://firebaseremoteconfig.googleapis.com/v1/projects/renault-brand-ios/namespaces/firebase:fetch?key=...
but to do that, it uses yet another key, and I do not see where that one comes from. Any idea?
new error on line 119:85 ReferenceError: cannot access uninitialized variable New key?
@coolio2004 still working here...
Renault changed sth. on their API again. Just confusing. :-(
Do have problems getting data? For me is the API still working...
not for me in cologne
On fetching user data I get code 403 now:
Invalid namespace 'accounts' or method 'getAccountInfo' or you do not have the required permissions to call it.
This is caused on line 340 in above code. This results in "345:34: Type Error: undefined is not an object (evaluating 'apiResult.data.personId".
But there seems to be another issue now
@dehsgr Same problem here … will have a look. Hope we will find it :) – Zeddy is killed too …
It maybe is the "2. fetch user data from gigya" which causes the problem. My ZoePHP-Script goes straight from step 1 to step 3.
@mountbatt I'm a bit further. We can summarize first Lines of getData() function a bit:
// we are going now a long way through multiple servers to get access to our data
// 1. fetch session and user data from gigya
let gigyaCookieValue
let gigyaPersonID
let gigyaGigyaDataCenter
if(Keychain.contains('gigyaCookieValue') && Keychain.get('gigyaCookieValue') != ""){
gigyaCookieValue = Keychain.get('gigyaCookieValue')
}
console.log('gigyaCookieValue (from keychain): ' + gigyaCookieValue)
if(Keychain.contains('gigyaPersonID') && Keychain.get('gigyaPersonID') != ""){
gigyaPersonID = Keychain.get('gigyaPersonID')
}
console.log('gigyaPersonID (from keychain): ' + gigyaPersonID)
if(Keychain.contains('gigyaGigyaDataCenter') && Keychain.get('gigyaGigyaDataCenter') != ""){
gigyaGigyaDataCenter = Keychain.get('gigyaGigyaDataCenter')
}
console.log('gigyaGigyaDataCenter (from keychain): ' + gigyaGigyaDataCenter)
if(gigyaCookieValue == "" || gigyaPersonID == "" || gigyaGigyaDataCenter == "" ||
typeof(gigyaCookieValue) == "undefined" || typeof(gigyaPersonID) == "undefined" || typeof(gigyaGigyaDataCenter) == "undefined")
{
let url = gigyaURL + '/accounts.login?loginID=' + encodeURIComponent(myRenaultUser) + '&password=' + encodeURIComponent(myRenaultPass) + '&include=data&apiKey=' + gigyaAPI
let req = new Request(url)
let apiResult = await req.loadString()
apiResult = JSON.parse(apiResult)
console.log("1.: " + apiResult.statusCode)
if(apiResult.statusCode == "403"){
let loginMessage = "Login nicht möglich. Zugangsdaten prüfen."
throw new Error(loginMessage);
} else {
gigyaCookieValue = apiResult.sessionInfo.cookieValue
gigyaPersonID = apiResult.data.personId
gigyaGigyaDataCenter = apiResult.data.gigyaDataCenter
Keychain.set('gigyaCookieValue', gigyaCookieValue)
Keychain.set('gigyaPersonID', gigyaPersonID)
Keychain.set('gigyaGigyaDataCenter', gigyaGigyaDataCenter)
console.log('gigyaCookieValue (new generated): ' + gigyaCookieValue)
console.log('gigyaPersonID (new generated): ' + gigyaPersonID)
console.log('gigyaGigyaDataCenter (new generated): ' + gigyaGigyaDataCenter)
}
}
// 2. fetch JWT data from gigya
// renew gigyaJWTToken once a day
if(Keychain.contains('lastJWTCall') == false){
Keychain.set('lastJWTCall', 'never')
}
let gigyaJWTToken
if(Keychain.contains('gigyaJWTToken')){
gigyaJWTToken = Keychain.get('gigyaJWTToken')
}
console.log('gigyaJWTToken (from keychain): ' + gigyaJWTToken)
if(gigyaJWTToken == "" || typeof(gigyaJWTToken) == "undefined"){
let expiration = 87000
url = gigyaURL + '/accounts.getJWT?oauth_token=' + gigyaCookieValue + '&login_token=' + gigyaCookieValue + '&expiration=' + expiration + '&fields=data.personId,data.gigyaDataCenter&ApiKey=' + gigyaAPI
req = new Request(url)
apiResult = await req.loadString()
apiResult = JSON.parse(apiResult)
console.log("3.: " + apiResult.statusCode)
gigyaJWTToken = apiResult.id_token
Keychain.set('gigyaJWTToken', gigyaJWTToken)
console.log('gigyaJWTToken (new generated): ' + gigyaJWTToken)
const callDate = new Date().toJSON().slice(0,13).replace(/-/g,'').replace(/T/g,'-')
Keychain.set('lastJWTCall', callDate)
console.log('lastJWTCall (new generated): ' + callDate)
}
[...]
This is what I was doing to get my personal API usage working from within node-red again. I saw that @db-EV at https://github.com/db-EV/ZoePHP/blob/main/src/index.php. But I can't get your code running completely with these changes. As I can see this shouldn't be an issue of using GET instead of POST requests here. But I'm missing sth. other?!
Try this one on step 1:
// 1. fetch session from gigya
let gigyaCookieValue
let gigyaPersonID
if(Keychain.contains('gigyaCookieValue') && Keychain.get('gigyaCookieValue') != ""){
gigyaCookieValue = Keychain.get('gigyaCookieValue')
}
console.log('gigyaCookieValue (from keychain): ' + gigyaCookieValue)
if(gigyaCookieValue == "" || typeof(gigyaCookieValue) == "undefined"){
let url = gigyaURL + '/accounts.login?loginID=' + encodeURIComponent(myRenaultUser) + '&password=' + encodeURIComponent(myRenaultPass) + '&apiKey=' + gigyaAPI
let req = new Request(url)
let apiResult = await req.loadString()
apiResult = JSON.parse(apiResult)
console.log("1.: " + apiResult.statusCode)
if(apiResult.statusCode == "403"){
let loginMessage = "Login nicht möglich. Zugangsdaten prüfen."
throw new Error(loginMessage);
} else {
gigyaCookieValue = apiResult.sessionInfo.cookieValue
gigyaPersonID = apiResult.data.personId
Keychain.set('gigyaCookieValue', gigyaCookieValue)
Keychain.set('gigyaPersonID', gigyaPersonID)
console.log('gigyaCookieValue (new generated): ' + gigyaCookieValue)
console.log('gigyaPersonID (new generated): ' + gigyaPersonID)
}
}
@db-EV I think gigyaDataCenter is missing in your function. Or isn't it required anymore?
I have no value there in my script. Maybe it's not exactly working the same in JS.
But I see another problem in your solution:
url = gigyaURL + '/accounts.getJWT?oauth_token=' + gigyaCookieValue + '&login_token=' + gigyaCookieValue + '&expiration=' + expiration + '&fields=data.personId,data.gigyaDataCenter&ApiKey=' + gigyaAPI
should be
url = gigyaURL + '/accounts.getJWT?login_token=' + gigyaCookieValue + '&expiration=' + expiration + '&fields=data.personId,data.gigyaDataCenter&ApiKey=' + gigyaAPI
More exactly: you never need the gigyaDataCenter value, you just grab the gigyaPersonID to send it along with your request to the Renault Kamereon server to get the AccountID.
I just edited my fork. This isn't completely working for now, but we can fetch all required data.
Line 121 is producing some error:
plugStateLabel.url = 'scriptable:///run?scriptName=${scriptName}&action=start_charge';
@db-EV you're right. Thanks for that hint! I'll change that in my code too.
The ' sign seems to be wrong in the line 121. It's looks more like a french '. I hope you understand what I mean...
@db-EV the '-sign is correct there. I got the script fully functional now. @mountbatt you might take a look at my changes. Thanks for support.
Dear readers/users please update to latest version 2021-02-09. Give thanks to @dehsgr
thanx for your hard work
@mountbatt I just re-fixed charging link, if you would like to merge...
@dehsgr thanks. Merged!
@mountbatt there seem to be changes again to Renault API. Could you check for that?
Error occurs in line 411. The only thing I see in other scripts they are using POST requests here instead of GET. Might this be the issue?
Okay, just played a bit more.
carPicture = await apiResult.vehicleLinks[0].vehicleDetails.assets[0].renditions[0].url
is failing as Renault did some changes on their API again. The whole assets section seems to be missing in JSON data. So we‘ve to find another way for getting the car image...
@dehsgr Thanks for checking it. It looks like Renault changed the images on their server. (my car is now "looking" to the right) But i was able to get a new image without problems.
carPicture (new): https://3dv2.renault.com/ImageFromBookmark?configuration=X10%2FB10%2FEA3%2FDG%2FCAREG%2FVSTLAR%2FRET03%2FPROJAB%2FRALU17%2FDRAP13%2F3ATRPH%2FTEKPN%2FALEVA%2FVLCUI2%2FRETRCR%2FRETC%2FLVAREL%2FSGACHA%2FNA419%2FRNORM%2FTL09A%2FNBT022%2FDLIGM2%2FKITPOU%2FDANGMO%2FITPK4%2FVOLCHA%2FREACTI%2FAEBS07%2FPRAHL%2FRRCAM&databaseId=bfded4fa-7e22-4d4a-993d-8570c64d2eaa&bookmarkSet=CARPICKER&bookmark=EXT_34_RIGHT_FRONT&profile=HELIOS_OWNERSERVICES_LARGE
Before i added //
on line 400 to force the downloading of the carPicture (and not getting it from keychain)
@mountbatt but this is only a workaround, isn't it? I already commented out that line to get the image from keychain. But how we will get image url from API now?
Thanks for this script, but I have some issue with it. I'm always having on error "Error on line 412:48 : TypeError : undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails')
I'm from France, I've changed the Gigya Key in line 26 and the country to FR. Is there anything else to do?
Thanks for your help
Thanks for the great script @mountbatt, I've been playing with this and have created a fork which contains a rudimentary translation setup so that both English and German can be chosen as the interface language. It should be simple to add a new translation for another language too.
Hallo, seit ein paar Tagen aktualisiert scriptable das widget nicht mehr, weiß einer abhilfe?
auch hier alles ok
Hier läufts auch … @DJ-TMA ...
Thanks for this script, but I have some issue with it. I'm always having on error "Error on line 412:48 : TypeError : undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails')
I'm from France, I've changed the Gigya Key in line 26 and the country to FR. Is there anything else to do?
Thanks for your help
@jblaster do you fix it ? I'm from France too and the script didn't work at all for this error :
Maybe it's because @mountbatt is from DE ?
thnx for doing all this work - the script promises what i was looking for - except that error
First the CountryID
because of the Netherlands where i am - i changed 4 time's country=NL
and wondered if it could be a variable as let CountryID = "NL" // check your countries ID
to make the settings a bit more in line?
Now the error
i've carefully checked all lines but i still get in Scriptable:
Error on line 406:36: TypeError: undefined is not an object (evaluating 'apiResult.accounts[0]')
as already noticed the cause is probably in the lines above 406 - but which one?
BTW indefined
is 19 times in the script
any suggestion is welcome
Hallo, ist es möglich eine Ladebegrenzung zu setzen? z.B nur laden bis 80%
Leider habe ich keine Ahnung vom programmieren.
Vielen Dank.
Gruß
Stephan
Hallo, ist es möglich eine Ladebegrenzung zu setzen? z.B nur laden bis 80%
Leider habe ich keine Ahnung vom programmieren.
Vielen Dank.
Gruß
Stephan
Nein, da es sich nicht um eine App handelt. Das Widget wird nur ausgeführt, wenn man es aktiv als Nutzer öffnet. Und auch dann nur sporadisch. Da hat Apple seine Finger drauf. Daher eignet es sich nicht dafür um Benachrichtigungen oder gar lade Stopps auszuführen.
Thanks for this script, but I have some issue with it. I'm always having on error "Error on line 412:48 : TypeError : undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails')
I'm from France, I've changed the Gigya Key in line 26 and the country to FR. Is there anything else to do?
Thanks for your help@jblaster do you fix it ? I'm from France too and the script didn't work at all for this error :
Maybe it's because @mountbatt is from DE ?
Ich habe das gleiche Problem. Bei mir erscheint seit einiger Zeit die gleiche Fehlermeldung.
Das Script neu eingerichtet und sogar scriptable neu installiert brachte auch keine Lösung. Es scheint um das "CarPicture" zu gehen in Zeile 434.
Ob das mit einem iOS Update zusammenhängt, kann ich nicht sicher sagen. Derzeit habe ich iOS 15.1.
Würde mich über eine Lösung freuen.
Vielen Dank!
Hey, ist es möglich das man das Widget so anpasst, dass man mehrere Autos auf einmal sehen kann? Ich benötige eigentlich nur Den Autonamen/Kennzeigen und den Ladestatus. Es sollten dann 8 oder mehr Autos anzuzeigen sein.
Hello. Wonder if anyone could help me figure out how to change the widget to display in miles in the following code?
// cockpitStatus // version: 2 // totalMileage = Num (in Kilometres!) let cockpitStatus = await getStatus('cockpit', 2, kamareonURL, account_id, VIN, gigyaJWTToken, kamareonAPI) allResults["cockpitStatus"] = cockpitStatus
km / 1.609 = miles
I am also working to a javascript source to access Renault API; it's not a widget but a standalone page, it's experimental and it needs a PHP proxy to access Renault/Gigya/Kamereon servers, so it is more useful for developers than for final users, to find out what I discovered about the API:
https://github.com/jumpjack/myRenaultLC
For example, in the repo you find info about how to retrieve charge history.
Hi looks like we need new API keys ... I'm getting access errors
@plin2 nope. No new API Keys are required. Seems that Renault changed https://accounts.eu1.gigya.com/accounts.getJWT from GET to POST call.
No, I don't think so. My script ZoePHP is using POST for calling https://accounts.eu1.gigya.com/accounts.getJWT for a long time but now it stopped working. The script is getting stopped at the next step when calling https://api-wired-prod-1-euw1.wrd-aws.com. Therefore I think it's the Kamereon API key which has been changed.
@db-EV seems you're right. New Key should be VAX7XYKGfa92yMvXculCkEFyfZbuM7Ss. But after changing I run into another error on line 380:26 --> Unexpected EOF.
New Kamereon API key is working for my script. Thank you!
Maybe my PHP code will help you: https://github.com/db-EV/ZoePHP
Thanks, new key is working in renault-api: hacf-fr/renault-api#552
I'm online again too. @mountbatt should change line 27 to let kamareonAPI = "---entfernt---"
.
Key VAX7XYKGfa92yMvXculCkEFyfZbuM7Ss works in my solution as well
In case somebody uses FHEM for home automation: https://forum.fhem.de/index.php/topic,116273.0.html
Thanks, also my solution is working with new API key entfernt
No error message on my side. Last login update was this morning at 3.
Works also fine with Dacia Spring electric!
Works also fine with Dacia Spring electric!
@matzZz nice! Please give us a screenshot
Works also fine with Dacia Spring electric!
@matzZz nice! Please give us a screenshot
Try to add your VIN
Maybe sniffing traffic of this Nissan app can help reversing engineering the SRP protocol?
https://play.google.com/store/apps/details?id=com.ideeo.nsnremote
Moin,
ich feiere das Widget seit einem Jahr. Seit dem letzten Update bekomme ich aber angehängte Fehlermeldung (SyntaxError: Invalid character '\u201e'). Ins Script habe ich an entsprechenden Stellen meinen Login (Mailadresse & PW) und die VIN geschrieben. Die Fahrzeugangaben stimmten alle von vornherein (Zoe 2, Akkugröße, etc.).
Hast du eine Idee, woran es noch liegen könnte?
Danke im Voraus!
Tuedderbueddel
Moin,
ich feiere das Widget seit einem Jahr. Seit dem letzten Update bekomme ich aber angehängte Fehlermeldung (SyntaxError: Invalid character '\u201e'). Ins Script habe ich an entsprechenden Stellen meinen Login (Mailadresse & PW) und die VIN geschrieben. Die Fahrzeugangaben stimmten alle von vornherein (Zoe 2, Akkugröße, etc.).
Hast du eine Idee, woran es noch liegen könnte?
Danke im Voraus!
Tuedderbueddel
@Tuedderbueddel Deutet auf falsche Anzahl Anführungszeichen hin. Schau mal in Zeile 7 oder etwas davor oder danach ob du dich da nicht vertan hast…
Tatsächlich, daran lag es! Bei Benutzer und PW war die deutsche Version der Anführungszeichen. Nach dem Ändern auf die englische funzt es nun. Vielen Dank und weiter so!
Seit ein paar Tagen bekomme ich folgende Fehlermeldungen:
2022-04-30 17:52:15: Keychain cleared cause of action parameters
2022-04-30 17:52:15: gigyaPersonID (from keychain): undefined
2022-04-30 17:52:15: 1.: 200
2022-04-30 17:52:16: 3.: 200
2022-04-30 17:52:16: account_id (from keychain): undefined
2022-04-30 17:52:16: 4.: [object Object]
2022-04-30 17:52:16: Error on line 434:63: TypeError: undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails.assets')
Seit ein paar Tagen bekomme ich folgende Fehlermeldungen:
2022-04-30 17:52:15: Keychain cleared cause of action parameters 2022-04-30 17:52:15: gigyaPersonID (from keychain): undefined 2022-04-30 17:52:15: 1.: 200 2022-04-30 17:52:16: 3.: 200 2022-04-30 17:52:16: account_id (from keychain): undefined 2022-04-30 17:52:16: 4.: [object Object] 2022-04-30 17:52:16: Error on line 434:63: TypeError: undefined is not an object (evaluating 'apiResult.vehicleLinks[0].vehicleDetails.assets')
Das geht auch nur, wenn ich VIN hinzufüge.
Neues Update!
@druelie Vielleicht hast Du damit mehr Glück? Probiers mal …
Intro
Dieses Widget zeigt den aktuellen Status des Renault ZOE an. Dazu werden die Login-Daten für die My-Renault App benötigt. Diese müssen oben im Script eingegeben werden.
Anforderungen
Installation
Wähle unter "Script" das oben erstellte aus (ZOE)
Bekannte Fehler
Disclaimer
Bei diesem Widget / Script handelt es sich um ein reines Privatprojekt ohne kommerzielle Hintergründe etc. Ich stehe weder in Beziehung zu Renault, noch bekomme ich etwas dafür. Die API-Anbindung zu den Renault-Servern kann jederzeit von Renault geändert und der Zugriff des Widgets somit unmöglich werden. Beachte unbedingt, dass Du dein My-Renault-Passwort oben im Script nicht mit anderen teilst!
Hinweis
Beachte, dass zu viele Zugriffe auf die My-RENAULT API in kurzer Zeit zu einer temporären Abrufsperre führen. Das gilt aber für alle Zugriffe wie z.B. via Zeddy usw. Nach kurzer Zeit sollte der Abruf wieder gelingen.
Danke
Ein Dank geht an marco79cgn für sein dm-Widget – dies hat mich zu diesem Projekt inspiriert. Zudem gebührt Respekt muskat, der die API von Renault intensiv durchforstet hat.
Updates
03.05.2022 12:14 Uhr
02.05.2022 21:32 Uhr
16.03.2022 20:14 Uhr
20.08.2021 22:14 Uhr
(Batteriegröße - Verf. Energie) / Restladedauer
also z.b.(52 - 30) / 1.3 = 16.93 kW
10.02.2021 10:24 Uhr
09.02.2021 02:10 Uhr
02.02.2021 01:30 Uhr
31.10.2020 12:00 Uhr
26.10.2020 16:58 Uhr
26.10.2020 16:22 Uhr
26.10.2020 10:48 Uhr
26.10.2020 9:35 Uhr
Roadmap