Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Scriptable iOS widget that displays the status of your Renault ZOE on your iPhone and iPad.
// 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 2021-02-10
// 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"
// should we use apple-maps or google maps?
let mapProvider = "apple" // "apple" or "google"
// optional:
// enter your VIN / FIN if you have more than 1 vehicle in your account
// or if you get any login-errors
// leave it blank to auto-select it
let VIN = "" // starts with VF1... enter like this: "VF1XXXXXXXXX"
// do not edit
let kamareonURL = "https://api-wired-prod-1-euw1.wrd-aws.com"
let kamareonAPI = "Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2"
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()
//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.png", 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 = "⚫ Entkoppelt"
} else {
plugIcon = await getImage("zoe-plug-on.png", "")
plugStateLabel = "🟢 Gekoppelt"
}
if(data.batteryStatus.attributes.chargingStatus == "1.0"){
plugStateLabel = "⚡ Wird geladen …"
}
if(data.batteryStatus.attributes.plugStatus == 1 && data.batteryStatus.attributes.chargingStatus == "0"){
plugStateLabel = "➤ 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"){
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
}
chargingInstantaneousPower = Math.round(chargingInstantaneousPower).toLocaleString()
let chargingRemainingTime = time_convert(data.batteryStatus.attributes.chargingRemainingTime)
chargingRemainingTimeString = " | " + chargingRemainingTime + " h"
chargeStateLabel = +chargingInstantaneousPower +" 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("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("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("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("Verf. Energie")
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("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("Position")
LocationLabel.font = Font.mediumSystemFont(12)
const LocationVal = LocationStack.addText("➤ Karte öffnen")
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=Mein+Auto&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("Vortemp.")
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("➤ Klima stoppen")
ac_url = `scriptable:///run?scriptName=${scriptName}&action=stop_ac`;
} else {
AcVal = AcStack.addText("➤ Klima 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()
apiResult = JSON.parse(apiResult)
console.log("4.: " + 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[0].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)
// set carPicture
carPicture = await apiResult.vehicleLinks[0].vehicleDetails.assets[0].renditions[0].url
Keychain.set('carPicture', carPicture)
console.log('carPicture (new): ' + carPicture)
// set VIN
VIN = apiResult.vehicleLinks[0].vin
Keychain.set('VIN', VIN)
console.log('VIN (new generated): ' + VIN)
}
// 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)
}
// 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
}
// 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)
} else {
// download once
let imageUrl
switch (image) {
case 'my-renault-car.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
@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Oct 26, 2020

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

  • iOS 14
  • Scriptable version 1.5 (oder neuer)
  • My RENAULT - Account Zugangsdaten (Email + Passwort)
  • Optional: Fahrgestellnummer des Fahrzeugs (VIN/FIN) bei mehreren Fahrzeugen im Account
  • Derzeit nur mit einer ZOE Phase 2 (2020) getestet, sollte mit Abstrichen aber auch mit Phase 1 laufen.

Installation

  • Kopiere den gesamten Sourcecode von oben (klick vorher auf "raw" oben rechts)
  • Öffne die Scriptable App
  • Klick auf das "+" Symbol oben rechts und füge das kopierte Skript ein
  • Ändere nun im Script ganz oben "your_email" und "your_pass" mit den Zugangsdaten Deines My-RENAULT Accounts (E-Mailadresse und zugehöriges Passwort)
  • Trage bei ZOE_Phase eine 1 oder 2 für Dein Fahrzeug ein
  • Gebe optional die VIN / FIN Deines Fahrzeugs in die entsprechende Stelle ein. (Dies wird nur benötigt, wenn Du mehrere Fahrzeuge in Deinem Account hast und das Widget auf ein bestimmtes Fahrzeug zeigen lassen willst oder wenn es zu Problemen beim Datenabruf kommt)
  • Klick auf den Titel des Skripts ganz oben und vergebe einen Namen (z.B. ZOE)
  • Speichere das Skript durch Klick auf "Done" oben links
  • Gehe auf deinen iOS Homescreen und drücke irgendwo lang, um in den "Wiggle Mode" zu kommen (mit dem man auch die App Symbole anordnen kann)
  • Drücke das "+" Symbol oben links, blättere dann nach unten zu "Scriptable" (Liste ist alphabetisch), wähle die zweite Widget Größe (medium / breites Format) und drücke unten auf "Widget hinzufügen"
  • Drücke auf das Widget, um seine Einstellungen zu bearbeiten (optional lang drücken, wenn der Wiggle Modus schon beendet wurde)
    Wähle unter "Script" das oben erstellte aus (ZOE)
  • Warte einen Moment (ca. 15 Sek.), bis das Widget die Daten vom Server geladen hat.

Bekannte Fehler

  • Es kann derzeit noch zu Timeouts / Quota-Limits kommen, wenn das Widget zu oft versucht die Daten vom Server zu laden. Nach 15 Minuten sollte es aber gehen.
  • Beim Drücken des "Klima starten" oder "Klima stoppen" Links wird Scriptable geöffnet und das Widget erneut geöffnet. Dies ist eine Vorgabe von Apple und derzeit leider nicht anders zu implementieren.

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

10.02.2021 10:24 Uhr

  • Bugfix URL für Ladestart – @dehsgr
  • Version: 2021-02-10

09.02.2021 02:10 Uhr

  • Bugfix nach erneuten Änderungen von Renault – mit großem Dank erneut an @dehsgr
  • Version: 2021-02-09

02.02.2021 01:30 Uhr

  • Neuer kamareonAPI Key. Danke an @dehsgr
  • Quickfix: einfach den kamareonAPI Key (Zeile 23) ändern auf: "Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2"

31.10.2020 12:00 Uhr

  • Danke an Marcus für die Hilfe beim Testen
  • Div. Bugfixes (und sicher div. neue Bugs eingebaut)
  • Anpassungen für Kompatibilität mit Phase 1
  • Anzeige der Laderestdauer
  • Anzeige der aktuellen kW beim Laden (Es gibt aber die Vermutung, dass es sich hierbei nur um die derzeit maximal mögliche Kapazität handelt - daher hat evtl. selbst Renault in seiner App derzeit keine richtige kW Anzeige mehr eingebaut!)
  • Erste Gehversuche einen "Klima starten" und "Klima stoppen" Knopf einzubauen - hier wird derzeit noch dran gearbeitet! (Ich kann es nicht testen, da mein ZOE Ph2 2020 derzeit keine Vorklimatisierung per App / API zulässt)
  • Erste Versuche einen Button für "Laden starten" einzubauen, wenn Fahrzeug gekoppelt und keine Ladeaktivität
  • Bei Phase 2 wird die Batteriekapazität, bei Phase 2 die Akkutemperatur angezeigt (Phase 1 und 2 können nur jw. diese Info anzeigen)
  • In Planung: Push Message wenn Akku voll geladen wurde

26.10.2020 16:58 Uhr

  • " Lädt (44 kWh)". Nun wird die Ladeleistung angezeigt. Muss aber noch von div. Usern bitte getestet werden. Angeblich stimmt die Angabe der Ladeleistung nicht immer. (Vielleicht wird sie deshalb auch nicht im Auto angezeigt :-)

26.10.2020 16:22 Uhr

  • Nun kompatibel mit Phase 1 Modellen (es wird derzeit hier nur der Ladestand und die Restreichweite angezeigt) (Danke Alexander)
  • Kleinere kosmetische Fixes, keine Einbindung mehr von Icons, sondern nativen Emojis (Spart Ladezeit und Verbindungen zu externen Servern)
  • " Lädt ..." wird nun bei aktiver Ladung angezeigt

26.10.2020 10:48 Uhr

  • Kleiner Fix für die Passwortübermittlung (Danke an BowlingX)

26.10.2020 9:35 Uhr

  • Erstes Release (getestet bislang nur auf iPhone 7) - Das Script ist noch in Entwicklung!

Roadmap

  • Status "Lädt grade" einfügen
  • Stecker-Icon und Text richtig zentrieren
  • Auf anderen iOS-Geräten testen und Größen / Abstände anpassen
  • Loading Indicator hinzufügen (Opacity etc.)
  • Kompatibel mit älteren ZOEs machen
  • Verständliche Fehlermeldungen einbauen
  • Ladeleistung anzeigen ( zb. so: Lädt mit 43 kWh )
  • Vorklimatisierungsbutton einfügen (frühe Beta!)
  • Push Notifications z.b. wenn Akku 100% geladen
  • Besserer Umgang mit Quota Limit (wer kann helfen?)
@BowlingX

This comment has been minimized.

Copy link

@BowlingX BowlingX commented Oct 26, 2020

Thank you!! Really cool.
I had a problem with a password that contains % signs.
There is a problem in the following line:

https://gist.github.com/mountbatt/772e4512089802a2aa2622058dd1ded7?fbclid=IwAR0HurkUSr4r1dJZZhQY68o_PgQWVf8e5bfGZnuORxqKxILDlGDwi4c2QWk#file-zoe-widget-js-L129

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
@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Oct 26, 2020

Thank you!! Really cool.
I had a problem with a password that contains % signs.
There is a problem in the following line:

https://gist.github.com/mountbatt/772e4512089802a2aa2622058dd1ded7?fbclid=IwAR0HurkUSr4r1dJZZhQY68o_PgQWVf8e5bfGZnuORxqKxILDlGDwi4c2QWk#file-zoe-widget-js-L129

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

Danke! Ist eingefügt

@jrathert

This comment has been minimized.

Copy link

@jrathert jrathert commented Oct 26, 2020

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 :-)

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Oct 26, 2020

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.

@tobwil

This comment has been minimized.

Copy link

@tobwil tobwil commented Oct 27, 2020

A377EE61-2208-42B9-A3D4-3B34BE9054C5

Ladeleistung wird leider falsch angegeben. Hier wird real gerade dreiphasig mit 9,6kWh geladen. Zoe Intens R135 Phase 2.
Identische, aber auch falsche Werte, werden auch via Zeddy App ausgegeben.

@druelie

This comment has been minimized.

Copy link

@druelie druelie commented Oct 27, 2020

kWh ist eine Einheit für die Batteriekapazität (Energiemenge), nicht die Ladeleistung. Leistung wird in Watt bzw. kW angegeben.

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Oct 28, 2020

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?

@tobwil

This comment has been minimized.

Copy link

@tobwil tobwil commented Oct 28, 2020

In der Renault App wird das ja gar nicht mehr angezeigt. Vermutlich auch aus diesem Grund. :-)

@druelie

This comment has been minimized.

Copy link

@druelie druelie commented Oct 28, 2020

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 :)

@fawick

This comment has been minimized.

Copy link

@fawick fawick commented Oct 29, 2020

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.

@bloemi

This comment has been minimized.

Copy link

@bloemi bloemi commented Oct 31, 2020

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

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Oct 31, 2020

@bloemi gute Idee!

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Nov 1, 2020

@bloemi Ist nun eingebaut …

@MacSchierer

This comment has been minimized.

Copy link

@MacSchierer MacSchierer commented Nov 1, 2020

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
}
@t-stricker

This comment has been minimized.

Copy link

@t-stricker t-stricker commented Nov 8, 2020

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.

@t-stricker

This comment has been minimized.

Copy link

@t-stricker t-stricker commented Nov 8, 2020

Scriptable App neu installieren hats gelöst...

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Nov 8, 2020

@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.

@cristianmargescu

This comment has been minimized.

Copy link

@cristianmargescu cristianmargescu commented Nov 11, 2020

Falls ihr noch Hilfe braucht Entwicklung/Testing gerne melden.

@RYOSAEBAXYZ

This comment has been minimized.

Copy link

@RYOSAEBAXYZ RYOSAEBAXYZ commented Nov 13, 2020

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 !

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Nov 14, 2020

@RYOSAEBAXYZ Are you sure, you copied the whole script? It looks like the script is not able to get all data from the server.

@RYOSAEBAXYZ

This comment has been minimized.

Copy link

@RYOSAEBAXYZ RYOSAEBAXYZ commented Nov 14, 2020

@RYOSAEBAXYZ

This comment has been minimized.

Copy link

@RYOSAEBAXYZ RYOSAEBAXYZ commented Nov 15, 2020

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')

@thkl

This comment has been minimized.

Copy link

@thkl thkl commented Nov 17, 2020

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 ?

@valentincosson

This comment has been minimized.

Copy link

@valentincosson valentincosson commented Dec 15, 2020

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)
@RYOSAEBAXYZ

This comment has been minimized.

Copy link

@RYOSAEBAXYZ RYOSAEBAXYZ commented Dec 21, 2020

let account = apiResult.accounts[index];

Thanks !! Where should your bit be added then ?

@RYOSAEBAXYZ

This comment has been minimized.

Copy link

@RYOSAEBAXYZ RYOSAEBAXYZ commented Dec 21, 2020

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 !

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 1, 2021

Since today the call is resulting in 403 making Kameron server calls from Germany. Is this reproducible to someone else?

@coolio2004

This comment has been minimized.

Copy link

@coolio2004 coolio2004 commented Feb 1, 2021

same error here from cologne

@lix-src

This comment has been minimized.

Copy link

@lix-src lix-src commented Feb 1, 2021

i hope this is only temporary

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 1, 2021

Seems that Renault changed the Kameron API key. Change it according to my revision and it should work again.

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 1, 2021

@dehsgr: Where did you extract the new API key?

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 1, 2021

@db-EV you might intercept communication of your MyRenault App and sniff that key.

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 1, 2021

@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?

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 1, 2021

@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. ;-)

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 2, 2021

Thanks @dehsgr! Great!

@epenet

This comment has been minimized.

Copy link

@epenet epenet commented Feb 2, 2021

@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)

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 2, 2021

@epenet I used Fiddler and was able to track Kamareon requests too. :-D

@epenet

This comment has been minimized.

Copy link

@epenet epenet commented Feb 2, 2021

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...

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 2, 2021

@epenet did you install & trust Fiddler root certificate on Android? This is pre-requisite.

@epenet

This comment has been minimized.

Copy link

@epenet epenet commented Feb 2, 2021

@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

@lix-src

This comment has been minimized.

Copy link

@lix-src lix-src commented Feb 2, 2021

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.

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 2, 2021

Maybe just "normal" problems with the Renault servers. Running here with 2 different accounts without problems so far.

@plin2

This comment has been minimized.

Copy link

@plin2 plin2 commented Feb 2, 2021

No problem with the new Ae9... key neither. Still interested in finding the original source for the API keys.

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 2, 2021

@lix-src I am stuck on the same problem. It worked once but now the data does not change anymore - without any errors.

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 2, 2021

@lix-src But the MyRenault App is not updating too! So I think we have to wait …

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 2, 2021

@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.

@lix-src

This comment has been minimized.

Copy link

@lix-src lix-src commented Feb 2, 2021

@lix-src But the MyRenault App is not updating too! So I think we have to wait …

I think you're right. The app also works quite sluggishly.

@dehsgr: No, i can't intercept the traffic :/

@plin2

This comment has been minimized.

Copy link

@plin2 plin2 commented Feb 2, 2021

My last update was at 17:55, so for me the Ae9... key is still working

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 2, 2021

There seem to be some issues indeed. My Renault app hasn’t refreshed since 3pm. :-(

@g-mocken

This comment has been minimized.

Copy link

@g-mocken g-mocken commented Feb 3, 2021

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?

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 3, 2021

my car is offline too ...

0D7FCF5D-A1D3-42E8-8EDC-F6806C50B9D4
1A3B67C5-7D61-487B-836D-DBEA1D13886E

@coolio2004

This comment has been minimized.

Copy link

@coolio2004 coolio2004 commented Feb 4, 2021

new error on line 119:85 ReferenceError: cannot access uninitialized variable New key?

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 4, 2021

@coolio2004 still working here...

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

Renault changed sth. on their API again. Just confusing. :-(

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

Do have problems getting data? For me is the API still working...

@coolio2004

This comment has been minimized.

Copy link

@coolio2004 coolio2004 commented Feb 8, 2021

not for me in cologne

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

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

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 8, 2021

@dehsgr Same problem here … will have a look. Hope we will find it :) – Zeddy is killed too …

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

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.

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

@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?!

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

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)
			}
		}
@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

@db-EV I think gigyaDataCenter is missing in your function. Or isn't it required anymore?

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

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

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

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.

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

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';

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

@db-EV you're right. Thanks for that hint! I'll change that in my code too.

@db-EV

This comment has been minimized.

Copy link

@db-EV db-EV commented Feb 8, 2021

The ' sign seems to be wrong in the line 121. It's looks more like a french '. I hope you understand what I mean...

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 8, 2021

@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.

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 9, 2021

Dear readers/users please update to latest version 2021-02-09. Give thanks to @dehsgr

@coolio2004

This comment has been minimized.

Copy link

@coolio2004 coolio2004 commented Feb 9, 2021

thanx for your hard work👍

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Feb 10, 2021

@mountbatt I just re-fixed charging link, if you would like to merge...

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Feb 10, 2021

@dehsgr thanks. Merged!

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Mar 17, 2021

@mountbatt there seem to be changes again to Renault API. Could you check for that?

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Mar 18, 2021

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? 🤔

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Mar 18, 2021

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...

@mountbatt

This comment has been minimized.

Copy link
Owner Author

@mountbatt mountbatt commented Mar 18, 2021

@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)

ImageFromBookmark

@dehsgr

This comment has been minimized.

Copy link

@dehsgr dehsgr commented Mar 18, 2021

@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?

@jblaster

This comment has been minimized.

Copy link

@jblaster jblaster commented Apr 23, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment