Da bei mir da fast nichts ändert, würde ich fast behaupten, dass sich das auf alle Fahrten bezieht. Seit der letzten Ladung kommt bei mir eigtl. nicht hin.
Beiträge von Benutzername
-
-
@Clubbi Habe gerade gesehen, dass es bei dem Quellcode auf Seite 2 ein paar Smileys reingehauen hat, weil ich es nicht als Code eingegeben habe. Vielleicht lag es daran. Jetzt also nochmal richtig:
Code
Alles anzeigenstrings = {} strings['de'] = { 'What to do ?':'Aktion wählen', 'Start cabin heating':'Klimatisierung starten', 'Flash lights':'Lichthupe', 'Make some noise':'Hupe', 'Lock the doors':'Türen verriegeln', 'Nothing, thank you.':'Keine Aktion', 'head the cabin':'den Innenraum heizen', 'flash the lights':'die Lichter anmachen', 'blow the horn':'die Hupe betätigen', 'lock the doors':'die Türen verriegeln', 'You want to ':'Du möchtest ', 'Just to make it clear':'Nur um sicherzugehen', 'Yes dude !':'Ja', 'Noooooooo !':'Nein', 'Its done':'Erledigt', 'Your request to ':'Deine Anforderung für ', ' was sent.':' wurde gesendet.', 'Thank you !':'Dankeschön', 'Unable to login':'Login nicht möglich', 'You want to enter your credentials again ?':'Möchtest Du Deine Zugangsdaten noch mal eintragen?', 'Yes':'Ja', 'No, they are fine':'Nein, die sind ok', 'Save':'Speichern' } const userKey = Script.name()+'_cd_user' const passKey = Script.name()+'_cd_pass' const vinKey = Script.name()+'_vin' if ((Keychain.contains(userKey)) && Keychain.contains(passKey)) { let carData = await accumulateData() const mainWdgData = await createWidget(carData) // build the normal widget login get the token vin and data const widget = mainWdgData const token = carData.token // we need the token and vin for later actions const vin = carData.vin if (config.runsInWidget) { Script.setWidget(widget) } else { widget.presentMedium() let useralert = new Alert() useralert.title = localize('What to do ?') useralert.addAction(localize('Start cabin heating')) useralert.addAction(localize('Flash lights')) useralert.addAction(localize('Make some noise')) useralert.addAction(localize('Lock the doors')) useralert.addCancelAction(localize('Nothing, thank you.')) let action = await useralert.present() switch (action) { case 0: proceedAction('head the cabin','RCN',token,vin) break case 1: proceedAction('flash the lights','RLF',token,vin) break case 2: proceedAction('blow the horn','RHB',token,vin) break case 3: proceedAction('lock the doors','RDL',token,vin) break } } Script.complete() } else { await askForUsername() await askForPassword() } function getPostBody(input) { let result = '' Object.keys(input).map((key)=>{ result = result + '&' + key + '=' + encodeURIComponent(input[key]) }) return result } function localize(text) { let lng = Device.language() if ((strings[lng]) && (strings[lng][text])) { return strings[lng][text] } else { return text } } async function accumulateData() { let result = {} let vin let token = await getLoginToken() if (token) { result.token = token vin = getVinbyParameterOrKeyChain() if (vin === false) { vin = await getVin(token) } if (vin) { result.vin = vin result.carImage = await fetchImage(vin) let data = await getVehicleStatus(token,vin) let dataSOC = await getVehicleStatusSOC(token,vin) if (data) { result.charging_status = data.charging_status result.chargingSystemStatus = data.chargingSystemStatus result.chargingTimeRemaining = data.chargingTimeRemaining result.chargingLevelHv = data.chargingLevelHv result.soc_hv_percent = data.soc_hv_percent result.socmax = dataSOC.socmax result.soc = dataSOC.soc result.beRemainingRangeElectricKm = data.beRemainingRangeElectricKm result.door_lock_state = data.door_lock_state result.updateTime = data.updateTime result.lat = data.gps_lat result.lon = data.gps_lng let nowDate = new Date() let chg = await getlastCharge(token,vin,nowDate) if (chg.latest === undefined) { // maybe we have a new month and the uses hasnt chargd yet so go one month back nowDate.setDate(1) let month = nowDate.getMonth() if (month === 0) { // Special in january go also one year back nowDate.setMonth(11) // nowDate.setFullYear(nowDate.getFullYear()-1) } else { nowDate.setMonth(nowDate.getMonth() - 1) } chg = await getlastCharge(token,vin,nowDate) } result.lastCharge = chg.latest } else { result.error = 'unable to fetch data' } } else { result.error = 'unable to get your vin' } } else { result.loginError = true result.error = 'unable to login' } return result } async function createWidget(data) { let bgColor = '#FFFFFF' let fgColor = '#000000' // if(Device.isUsingDarkAppearance()) { bgColor = '#000000' fgColor = '#FFFFFF' // } let chrglblCol = new Color(fgColor) let widget = new ListWidget() let canvas widget.backgroundColor = new Color(bgColor, 1.0) if (data.error === undefined) { let carIconCell let wideMode = false if ((config.widgetFamily === 'medium') || (config.widgetFamily === 'large') || (config.runsInWidget === false)) { let row = widget.addStack() row.layoutHorizontally() canvas = row.addStack() canvas.layoutVertically() row.addSpacer(8) carIconCell = row.addStack() wideMode = true } else { canvas = widget } // Battery stack let batteryStack = canvas.addStack() // Battery icon let battIcon = SFSymbol.named('bolt.fill.batteryblock'); let battIconElement = batteryStack.addImage(battIcon.image) battIconElement.imageSize = new Size(15, 15) batteryStack.addSpacer(8) // Set color based on charging state if (data.charging_status == 'NOCHARGING') { battIconElement.tintColor = new Color(fgColor) } else { chrglblCol = Color.blue() battIconElement.tintColor = chrglblCol } let batteryText = batteryStack.addText(Math.floor(data.chargingLevelHv) + '% - ' + (Math.floor(data.soc*100)/100) + ' kWh') batteryText.textColor = chrglblCol batteryText.font = Font.systemFont(12) canvas.addSpacer() // Range stack let rangeStack = canvas.addStack() let rangeIcon = SFSymbol.named('gauge'); let rangeIconElement = rangeStack.addImage(rangeIcon.image) rangeIconElement.imageSize = new Size(15, 15) rangeIconElement.tintColor = new Color(fgColor) rangeStack.addSpacer(8) let rangeText = rangeStack.addText(Math.floor(data.beRemainingRangeElectricKm) +'km') rangeText.textColor = new Color(fgColor) rangeText.font = Font.systemFont(12) canvas.addSpacer() // MAXSOC stack let maxsocStack = canvas.addStack() let maxsocIcon = SFSymbol.named('bolt.fill.batteryblock'); let maxsocIconElement = maxsocStack.addImage(maxsocIcon.image) maxsocIconElement.imageSize = new Size(15, 15) maxsocIconElement.tintColor = new Color(fgColor) maxsocStack.addSpacer(8) let maxsocText = maxsocStack.addText('max ' + (Math.floor(data.socmax *100)/100) +' kWh') maxsocText.textColor = new Color(fgColor) maxsocText.font = Font.systemFont(12) canvas.addSpacer() // in wide Mode we will add the iamge on the right side let carIconStack let imgSize let paddingBottom = -25 let paddingTrailing = 0 if (wideMode===true) { imgSize = new Size(150, 150) paddingBottom = -20 paddingTrailing = -20 carIconStack = carIconCell.addStack() } else { imgSize = new Size(100, 100) carIconStack = canvas.addStack() } const carImageStack = carIconStack.addStack() carIconStack.layoutHorizontally() carImageStack.backgroundColor = new Color(bgColor, 1.0) carImageStack.cornerRadius = 8 const wimg = carIconStack.addImage(data.carImage) wimg.imageSize = imgSize wimg.rightAlignImage() wimg.url = 'https://maps.apple.com/?q=SE&ll='+data.lat+','+data.lon carIconStack.setPadding(-40,0,paddingBottom,paddingTrailing) canvas.addSpacer() // Lock State Stack let lockStack = canvas.addStack() let lockIcon if (data.door_lock_state === 'SECURED') { lockIcon = SFSymbol.named('lock.circle'); } else { lockIcon = SFSymbol.named('lock.open') } let lockIconElement = lockStack.addImage(lockIcon.image) lockIconElement.imageSize = new Size(15, 15) lockIconElement.tintColor = new Color(fgColor) lockStack.addSpacer(8) let lockText = lockStack.addText(data.door_lock_state) lockText.textColor = new Color(fgColor) lockText.font = Font.systemFont(12) canvas.addSpacer() // add the charging data if we are running in a wider mode if (wideMode === true) { if (data.lastCharge !== undefined) { let chargeStack = widget.addStack() let lchargeIcon = SFSymbol.named('bolt.car'); let lchargeIconElement = chargeStack.addImage(lchargeIcon.image) lchargeIconElement.imageSize = new Size(15, 15) lchargeIconElement.tintColor = new Color(fgColor) chargeStack.addSpacer(8) let chargeText = chargeStack.addText(data.lastCharge) chargeText.font = Font.systemFont(12) chargeText.textColor = new Color(fgColor) widget.addSpacer() } } // update stack let updateStack = widget.addStack() let chargingActive = (data.chargingSystemStatus === 'CHARGINGACTIVE') let timeIcon = SFSymbol.named((chargingActive === true) ? 'timer' : 'clock'); let timeIconElement = updateStack.addImage(timeIcon.image) timeIconElement.imageSize = new Size(15, 15) timeIconElement.tintColor = chrglblCol updateStack.addSpacer(8) // Use the utc and convert to local time let df = new DateFormatter() let date if (chargingActive === true) { let now = new Date() // calculate the charging end time based on the remaining minutes date = new Date(now.getTime()+parseInt(data.chargingTimeRemaining)*60000) } else { df.dateFormat = 'dd.MM.yyyy HH:mm:ss Z' date = df.date(data.updateTime) } df.useShortDateStyle() df.useShortTimeStyle() let updateText = updateStack.addText(df.string(date)) updateText.textColor = chrglblCol // make it blue when the the car is charging if (wideMode===false) { // in smallmode make the text even smaller updateText.font = Font.systemFont(11) } else { updateText.font = Font.systemFont(12) } widget.addSpacer() } else { canvas = widget canvas.addText(data.error) if ((data.loginError === true) && (config.runsInWidget === false)) { showReloginAlert() } } return widget } async function proceedAction(question,actionType,token,vin) { let proceedAlert = new Alert() proceedAlert.title = localize('Just to make it clear') proceedAlert.message = localize('You want to ') + localize(question) + '?' proceedAlert.addAction(localize('Yes dude !')) proceedAlert.addCancelAction(localize('Noooooooo !')) let action = await proceedAlert.present() if (action === 0) { await performRemoteAction(actionType,token,vin) proceedAlert = new Alert() proceedAlert.title = localize('Its done') proceedAlert.message = localize('Your request to ') + localize(question) + localize(' was sent.') proceedAlert.addAction(localize('Thank you !')) await proceedAlert.present() } } async function showReloginAlert() { let useralert = new Alert() useralert.title = localize('Unable to login') useralert.message = localize('You want to enter your credentials again ?') useralert.addAction(localize('Yes')) useralert.addCancelAction(localize('No, they are fine')) let action = await useralert.present() if (action === 0) { await askForUsername() await askForPassword() } } async function askForUsername() { let useralert = new Alert() useralert.title = 'Connected Drive' let cduser = useralert.addTextField('Username') useralert.addAction(localize('Save')) await useralert.present() Keychain.set(userKey,useralert.textFieldValue(0)) } async function askForPassword() { let useralert = new Alert() useralert.title = 'Connected Drive' let cduser = useralert.addSecureTextField('Password') useralert.addAction(localize('Save')) await useralert.present() Keychain.set(passKey,useralert.textFieldValue(0)) } function getVehicleImage(vin) { return new Promise(async (resolve,reject)=>{ let vehicleImageListUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/image/v1/' + vin + '?startAngle=0&stepAngle=10&width=780' let vehicleImageListRequest = new Request(vehicleImageListUrl) vehicleImageListRequest.method = 'get' vehicleImageListRequest.headers = {'Content-Type': 'application/json'} let data = await vehicleImageListRequest.loadJSON() if ((data) && (data.angleUrls)) { resolve(data.angleUrls[5].url) } else { resolve(false) } }) } function performRemoteAction(action,token,vin) { return new Promise(async(resolve,reject)=>{ let remoteCommandUrl = 'https://www.bmw-connecteddrive.com/remoteservices/rsapi/v1/' + vin + '/' + action let remoteRequest = new Request(remoteCommandUrl) remoteRequest.body = '{}' remoteRequest.method = 'POST' remoteRequest.headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } remoteRequest.loadJSON().then((data)=>{ resolve()}) .catch((e)=>{ console.error(e) resolve()}) }) } function getVehicleStatus(token,vin) { return new Promise(async(resolve,reject)=>{ let vehicleDataUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/dynamic/v1/' + vin + '?offset=-60' let vehicleDataRequest = new Request(vehicleDataUrl) vehicleDataRequest.method = 'get' vehicleDataRequest.headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } let data = await vehicleDataRequest.loadJSON() if (data) { resolve(data.attributesMap) } else { resolve(false) } }) } function getVehicleStatusSOC(token,vin) { return new Promise(async(resolve,reject)=>{ let vehicleDataUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/navigation/v1/' + vin + '' let vehicleDataRequest = new Request(vehicleDataUrl) vehicleDataRequest.method = 'get' vehicleDataRequest.headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } let data = await vehicleDataRequest.loadJSON() if (data) { resolve(data) } else { resolve(false) } }) } function getVinbyParameterOrKeyChain() { let parameters = args.widgetParameter if (parameters != null && parameters.length > 0) { console.log('using vin ' + parameters + ' from widget parameters') return parameters } else { // try the keychain if (!Keychain.contains(vinKey)) { console.log('there is no stored vin') return false } else { let vin = Keychain.get(vinKey) console.log('using vin ' + vin +' from keychain') return vin } } } function getVin(token) { return new Promise(async(resolve,reject)=>{ console.log('using vin from cd') let vehicleListUrl = 'https://www.bmw-connecteddrive.com/api/me/vehicles/v2?all=true&brand=BM' let vehicleListRequest = new Request(vehicleListUrl) vehicleListRequest.method = 'get' vehicleListRequest.headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } let list = await vehicleListRequest.loadJSON() if (list.length > 0) { let vin = list[0].vin console.log(list[0]) Keychain.set(vinKey,vin) resolve(vin) } else { console.log(list[0]) resolve(false) } }) } function getlastCharge(token,vin,date) { let result = {} result.latest = undefined return new Promise( async(resolve,reject)=>{ let df = new DateFormatter() df.dateFormat = 'yyyy-MM' let thisMonth = df.string(date) + '-01T00:00:00.000' let chargeSessionUrl = 'https://cocoapi.bmwgroup.com/eadrax-chs/v1/charging-sessions?vin=' + vin + '&date=' + thisMonth let vehicleChargeRequest = new Request(chargeSessionUrl) vehicleChargeRequest.method = 'get' vehicleChargeRequest.headers = { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } let list = await vehicleChargeRequest.loadJSON() if (list.chargingSessions !== undefined) { result.nrj = list.chargingSessions.total if ((list.chargingSessions.sessions) && (list.chargingSessions.sessions.length > 0)) { let latest = list.chargingSessions.sessions[0] result.latest = latest.title + '|' + latest.energyCharged } } resolve(result) }) } async function getLoginToken() { let user let pwd try { if (!Keychain.contains(userKey)) { console.error('missing parameters') } else { user = Keychain.get(userKey) pwd = Keychain.get(passKey) } let url = 'https://customer.bmwgroup.com/gcdm/oauth/authenticate' let post_data = { 'state': 'eyJtYXJrZXQiOiJkZSIsImxhbmd1YWdlIjoiZGUiLCJkZXN0aW5hdGlvbiI6ImxhbmRpbmdQYWdlIn0', 'username': user, 'client_id': 'dbf0a542-ebd1-4ff0-a9a7-55172fbfce35', 'password': pwd, 'redirect_uri': 'https://www.bmw-connecteddrive.com/app/default/static/external-dispatch.html', 'response_type': 'token', 'scope': 'authenticate_user fupo', 'locale': 'DE-de' } let lRequest = new Request(url) lRequest.method = 'POST' lRequest.body = getPostBody(post_data) let result = await lRequest.load() let tokenUrl = lRequest.response.url if (tokenUrl) { let match = tokenUrl.match(/&access_token=([a-zA-z0-9]{0,})/) if (match != null) { let token = match[1] return token } } } catch(e) { console.error(e) } return null } function fetchImage(vin) { return new Promise (async(resolve,reject)=>{ let fm = FileManager.local() let dir = fm.documentsDirectory() let path = fm.joinPath(dir, vin + '.png') if (fm.fileExists(path)) { resolve(fm.readImage(path)) } else { let carImageUrl = await getVehicleImage(vin) let carImageRequest = new Request(carImageUrl) let carImage = await carImageRequest.loadImage() fm.writeImage(path, carImage) resolve(carImage) } }) } -
Ja
Einfach das i3 in SE (oder was anderes) in der Zeile ersetzen:
wimg.url = 'https://maps.apple.com/?q=i3&ll='+data.lat+','+data.lon -
So, hab's tatsächlich zum laufen gebracht, hätt' ich mir gar ned zugetraut, aber:
Ist es so richtig/gewollt, daß beim antippen des Widgets die App "Scriptable" aufgeht, mir kurz die Scripts angezeigt werden, dann das Widget nochmal in einem eigenen Fenster, welches sogleich von einem Auswahlmenü "Aktion wählen" überlagert wird, aufgeht

Diese Auswahlmenü muß ich dann "mit "keine Aktion" wegtippen, anschl. das Widget über den "close"-Button schließen und zum Schluß noch die "Scriptable"-App wegwischen - seeeehr umständlich das ganze

Denke schon, zumindest ist es bei mir auch so. Außer du klickst auf das Fahrzeug, dann springt er zu Scriptable und dann zu Karten und zeigt dir den Standort vom Fahrzeug an.
Aber wieso umständlich? Das Widget ist hauptsächlich dazu da, schnell mit einem Wisch auf dem Homescreen die wichtigsten Daten zu sehen. Wieso also überhaupt drauf drücken?
Bei mir IOS wird beim antippen des Widged Scriptable geöffnet und der wartekringel dreht sich Stunden lang, nichts passiert. Bekomme es nicht zum laufen.
Also das Skript von oben, den richtigen Connected-Zugangsdaten, als Widget hinzugefügt und dort unter den Parametern die vollständige VIN (nicht nur die letzten 7 Ziffern) eingegeben und es geht noch immer nicht? Dann bin ich mit meinem Latein um ehrlich zu sein auch am Ende

-
Alles anzeigen
Ich denke, dass man sich entscheiden muss.
Wenn das EV möglichst effektiv durch den Wind soll, dann sehen die Dinger eben alle aus wie ein Prius - mehr oder weniger.
Man kann sich ja mal den Verbrauch eines EV6 und eines Ioniq5 bei 130km/h ansehen - da liegen sicherlich Welten dazwischen.
Ich habe mich seither immer SUVs verweigert, weil die alle imo Energieverschwender sind (Schrankwand im Wind).
!al sehen wie der Nissan Arya dann wirklich aussieht - bei dem könnte ich schwach werden
Von der Grundform sicherlich, aber Gestaltungsmöglichkeiten gibt es da sicherlich auch noch. Das Heck vom EV6 gefällt mir z.B. gar nicht, was sicherlich größtenteils an den Heckleuchten liegt. Das hätte man sicherlich auch anders gestalten können, ohne größere Beeinträchtigung der Effizienz. Aber letztlich ist der Geschmack eben unterschiedlich.
Der Ariya wird wohl so kommen. Zumindest gibt es auf Youtube Videos von welchen, die zur Vorabpräsentation zu den Händlern rollten. Ich kann mir nicht vorstellen, dass sie da mehrere Studienmodelle in verschiedenen Farben gebaut haben, um sie bei den Händlern zu zeigen und dann doch noch vom Design abweichen. Das werden sicherlich Vorserienmodelle gewesen sein, die auch so kommen. Da ich mir Mitsubishis und Nissans aufgewachsen bin, beobachte ich den Ariya auch. Aber wenn der Basispreis von rund 45 t€ stimmt, wäre mir das zu viel.
Der sieht doch nicht schlimm aus. Erinnert mich irgendwie an Citroen.
Das kann sein, mit deren Design konnte ich auch nie was anfangen. Aber als Landei habe ich natürlich keine Ahnung von Haute Couture

-
Ich bin sehr angetan ... anbei auch ein Video von der Homepage
https://www.hyundai.de/modelle/der-neue-ioniq5/Ich glaube, das Auto könnte der Branche richtig gut tun
Insgeheim hatte ich etwas auf den KIA gehofft, da ich den e-Niro z.B. auch deutlich ansprechender finde als den Kona. Aber mit dem EV6 haben die für mich den Vogel echt abgeschossen, da ist der Ioniq 5 richtig ansehnlich dagegen
https://ecomento.de/2021/03/15…-mit-neuer-designsprache/ -
Ich hab jetzt das Kopiert und bei mir nachdem ich alles gelöscht habe eingesetzt. Widged funktioniert nicht und bei Script kommt der Fehler no Vin/ Vin from Cd?
gruss
Also wenn du das Script in Scriptable direkt ausführst ist der Fehler normal, weil er da die VIN noch gar nicht hat. Die holt er sich erst, wenn du sie beim Widget als Parameter eingegeben hast.
-
Ja
strings = {}
strings['de'] = {
'What to do ?':'Aktion wählen',
'Start cabin heating':'Klimatisierung starten',
'Flash lights':'Lichthupe',
'Make some noise':'Hupe',
'Lock the doors':'Türen verriegeln',
'Nothing, thank you.':'Keine Aktion',
'head the cabin':'den Innenraum heizen',
'flash the lights':'die Lichter anmachen',
'blow the horn':'die Hupe betätigen',
'lock the doors':'die Türen verriegeln',
'You want to ':'Du möchtest ',
'Just to make it clear':'Nur um sicherzugehen',
'Yes dude !':'Ja',
'Noooooooo !':'Nein',
'Its done':'Erledigt',
'Your request to ':'Deine Anforderung für ',
' was sent.':' wurde gesendet.',
'Thank you !':'Dankeschön',
'Unable to login':'Login nicht möglich',
'You want to enter your credentials again ?':'Möchtest Du Deine Zugangsdaten noch mal eintragen?',
'Yes':'Ja',
'No, they are fine':'Nein, die sind ok',
'Save':'Speichern'
}const userKey = Script.name()+'_cd_user'
const passKey = Script.name()+'_cd_pass'
const vinKey = Script.name()+'_vin'if ((Keychain.contains(userKey)) && Keychain.contains(passKey)) {
let carData = await accumulateData()
const mainWdgData = await createWidget(carData) // build the normal widget login get the token vin and data
const widget = mainWdgData
const token = carData.token // we need the token and vin for later actions
const vin = carData.vinif (config.runsInWidget) {
Script.setWidget(widget)
} else {
widget.presentMedium()
let useralert = new Alert()
useralert.title = localize('What to do ?')
useralert.addAction(localize('Start cabin heating'))
useralert.addAction(localize('Flash lights'))
useralert.addAction(localize('Make some noise'))
useralert.addAction(localize('Lock the doors'))
useralert.addCancelAction(localize('Nothing, thank you.'))
let action = await useralert.present()
switch (action) {
case 0:
proceedAction('head the cabin','RCN',token,vin)
break
case 1:
proceedAction('flash the lights','RLF',token,vin)
break
case 2:
proceedAction('blow the horn','RHB',token,vin)
break
case 3:
proceedAction('lock the doors','RDL',token,vin)
break
}
}
Script.complete()
} else {
await askForUsername()
await askForPassword()
}function getPostBody(input) {
let result = ''
Object.keys(input).map((key)=>{
result = result + '&' + key + '=' + encodeURIComponent(input[key])
})
return result
}function localize(text) {
let lng = Device.language()
if ((strings[lng]) && (strings[lng][text])) {
return strings[lng][text]
} else {
return text
}
}async function accumulateData() {
let result = {}
let vin
let token = await getLoginToken()
if (token) {
result.token = token
vin = getVinbyParameterOrKeyChain()
if (vin === false) {
vin = await getVin(token)
}
if (vin) {
result.vin = vin
result.carImage = await fetchImage(vin)
let data = await getVehicleStatus(token,vin)
let dataSOC = await getVehicleStatusSOC(token,vin)
if (data) {
result.charging_status = data.charging_status
result.chargingSystemStatus = data.chargingSystemStatus
result.chargingTimeRemaining = data.chargingTimeRemaining
result.chargingLevelHv = data.chargingLevelHv
result.soc_hv_percent = data.soc_hv_percent
result.socmax = dataSOC.socmax
result.soc = dataSOC.soc
result.beRemainingRangeElectricKm = data.beRemainingRangeElectricKm
result.door_lock_state = data.door_lock_state
result.updateTime = data.updateTime
result.lat = data.gps_lat
result.lon = data.gps_lng
let nowDate = new Date()
let chg = await getlastCharge(token,vin,nowDate)
if (chg.latest === undefined) {
// maybe we have a new month and the uses hasnt chargd yet so go one month back
nowDate.setDate(1)
let month = nowDate.getMonth()
if (month === 0) { // Special in january go also one year back
nowDate.setMonth(11) //
nowDate.setFullYear(nowDate.getFullYear()-1)
} else {
nowDate.setMonth(nowDate.getMonth() - 1)
}
chg = await getlastCharge(token,vin,nowDate)
}
result.lastCharge = chg.latest} else {
result.error = 'unable to fetch data'
}
} else {
result.error = 'unable to get your vin'
}
} else {
result.loginError = true
result.error = 'unable to login'
}
return result
}async function createWidget(data) {
let bgColor = '#FFFFFF'
let fgColor = '#000000'// if(Device.isUsingDarkAppearance()) {
bgColor = '#000000'
fgColor = '#FFFFFF'
// }
let chrglblCol = new Color(fgColor)
let widget = new ListWidget()
let canvas
widget.backgroundColor = new Color(bgColor, 1.0)
if (data.error === undefined) {
let carIconCell
let wideMode = false
if ((config.widgetFamily === 'medium')
(config.widgetFamily === 'large')
(config.runsInWidget === false)) {
let row = widget.addStack()
row.layoutHorizontally()
canvas = row.addStack()
canvas.layoutVertically()
row.addSpacer(8)
carIconCell = row.addStack()
wideMode = true
} else {
canvas = widget
}
// Battery stack
let batteryStack = canvas.addStack()
// Battery icon
let battIcon = SFSymbol.named('bolt.fill.batteryblock');
let battIconElement = batteryStack.addImage(battIcon.image)
battIconElement.imageSize = new Size(15, 15)
batteryStack.addSpacer(8)// Set color based on charging state
if (data.charging_status == 'NOCHARGING') {
battIconElement.tintColor = new Color(fgColor)
} else {
chrglblCol = Color.blue()
battIconElement.tintColor = chrglblCol
}
let batteryText = batteryStack.addText(Math.floor(data.chargingLevelHv) + '% - ' + (Math.floor(data.soc*100)/100) + ' kWh')
batteryText.textColor = chrglblCol
batteryText.font = Font.systemFont(12)
canvas.addSpacer()// Range stack
let rangeStack = canvas.addStack()
let rangeIcon = SFSymbol.named('gauge');
let rangeIconElement = rangeStack.addImage(rangeIcon.image)
rangeIconElement.imageSize = new Size(15, 15)
rangeIconElement.tintColor = new Color(fgColor)
rangeStack.addSpacer(8)
let rangeText = rangeStack.addText(Math.floor(data.beRemainingRangeElectricKm) +'km')
rangeText.textColor = new Color(fgColor)
rangeText.font = Font.systemFont(12)
canvas.addSpacer()// MAXSOC stack
let maxsocStack = canvas.addStack()
let maxsocIcon = SFSymbol.named('bolt.fill.batteryblock');
let maxsocIconElement = maxsocStack.addImage(maxsocIcon.image)
maxsocIconElement.imageSize = new Size(15, 15)
maxsocIconElement.tintColor = new Color(fgColor)
maxsocStack.addSpacer(8)
let maxsocText = maxsocStack.addText('max ' + (Math.floor(data.socmax *100)/100) +' kWh')
maxsocText.textColor = new Color(fgColor)
maxsocText.font = Font.systemFont(12)
canvas.addSpacer()// in wide Mode we will add the iamge on the right side
let carIconStack
let imgSize
let paddingBottom = -25
let paddingTrailing = 0
if (wideMode===true) {
imgSize = new Size(150, 150)
paddingBottom = -20
paddingTrailing = -20
carIconStack = carIconCell.addStack()
} else {
imgSize = new Size(100, 100)
carIconStack = canvas.addStack()
}const carImageStack = carIconStack.addStack()
carIconStack.layoutHorizontally()
carImageStack.backgroundColor = new Color(bgColor, 1.0)
carImageStack.cornerRadius = 8
const wimg = carIconStack.addImage(data.carImage)
wimg.imageSize = imgSize
wimg.rightAlignImage()
wimg.url = 'https://maps.apple.com/?q=i3&ll='+data.lat+','+data.lon
carIconStack.setPadding(-40,0,paddingBottom,paddingTrailing)
canvas.addSpacer()// Lock State Stack
let lockStack = canvas.addStack()
let lockIcon
if (data.door_lock_state === 'SECURED') {
lockIcon = SFSymbol.named('lock.circle');
} else {
lockIcon = SFSymbol.named('lock.open')
}
let lockIconElement = lockStack.addImage(lockIcon.image)
lockIconElement.imageSize = new Size(15, 15)
lockIconElement.tintColor = new Color(fgColor)
lockStack.addSpacer(8)
let lockText = lockStack.addText(data.door_lock_state)
lockText.textColor = new Color(fgColor)
lockText.font = Font.systemFont(12)
canvas.addSpacer()// add the charging data if we are running in a wider mode
if (wideMode === true) {
if (data.lastCharge !== undefined) {
let chargeStack = widget.addStack()
let lchargeIcon = SFSymbol.named('bolt.car');
let lchargeIconElement = chargeStack.addImage(lchargeIcon.image)
lchargeIconElement.imageSize = new Size(15, 15)
lchargeIconElement.tintColor = new Color(fgColor)
chargeStack.addSpacer(8)let chargeText = chargeStack.addText(data.lastCharge)
chargeText.font = Font.systemFont(12)
chargeText.textColor = new Color(fgColor)
widget.addSpacer()
}
}// update stack
let updateStack = widget.addStack()
let chargingActive = (data.chargingSystemStatus === 'CHARGINGACTIVE')
let timeIcon = SFSymbol.named((chargingActive === true) ? 'timer' : 'clock');
let timeIconElement = updateStack.addImage(timeIcon.image)
timeIconElement.imageSize = new Size(15, 15)
timeIconElement.tintColor = chrglblCol
updateStack.addSpacer(8)// Use the utc and convert to local time
let df = new DateFormatter()
let dateif (chargingActive === true) {
let now = new Date() // calculate the charging end time based on the remaining minutes
date = new Date(now.getTime()+parseInt(data.chargingTimeRemaining)*60000)
} else {
df.dateFormat = 'dd.MM.yyyy HH:mm:ss Z'
date = df.date(data.updateTime)
}df.useShortDateStyle()
df.useShortTimeStyle()let updateText = updateStack.addText(df.string(date))
updateText.textColor = chrglblCol // make it blue when the the car is charging
if (wideMode===false) { // in smallmode make the text even smaller
updateText.font = Font.systemFont(11)
} else {
updateText.font = Font.systemFont(12)
}
widget.addSpacer()} else {
canvas = widget
canvas.addText(data.error)
if ((data.loginError === true) && (config.runsInWidget === false)) {
showReloginAlert()
}
}
return widget
}async function proceedAction(question,actionType,token,vin) {
let proceedAlert = new Alert()
proceedAlert.title = localize('Just to make it clear')
proceedAlert.message = localize('You want to ') + localize(question) + '?'
proceedAlert.addAction(localize('Yes dude !'))
proceedAlert.addCancelAction(localize('Noooooooo !'))
let action = await proceedAlert.present()
if (action === 0) {
await performRemoteAction(actionType,token,vin)
proceedAlert = new Alert()
proceedAlert.title = localize('Its done')
proceedAlert.message = localize('Your request to ') + localize(question) + localize(' was sent.')
proceedAlert.addAction(localize('Thank you !'))
await proceedAlert.present()
}
}async function showReloginAlert() {
let useralert = new Alert()
useralert.title = localize('Unable to login')
useralert.message = localize('You want to enter your credentials again ?')
useralert.addAction(localize('Yes'))
useralert.addCancelAction(localize('No, they are fine'))
let action = await useralert.present()
if (action === 0) {
await askForUsername()
await askForPassword()
}
}async function askForUsername() {
let useralert = new Alert()
useralert.title = 'Connected Drive'
let cduser = useralert.addTextField('Username')
useralert.addAction(localize('Save'))
await useralert.present()
Keychain.set(userKey,useralert.textFieldValue(0))
}async function askForPassword() {
let useralert = new Alert()
useralert.title = 'Connected Drive'
let cduser = useralert.addSecureTextField('Password')
useralert.addAction(localize('Save'))
await useralert.present()
Keychain.set(passKey,useralert.textFieldValue(0))
}function getVehicleImage(vin) {
return new Promise(async (resolve,reject)=>{
let vehicleImageListUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/image/v1/' + vin + '?startAngle=0&stepAngle=10&width=780'
let vehicleImageListRequest = new Request(vehicleImageListUrl)
vehicleImageListRequest.method = 'get'
vehicleImageListRequest.headers = {'Content-Type': 'application/json'}
let data = await vehicleImageListRequest.loadJSON()
if ((data) && (data.angleUrls)) {
resolve(data.angleUrls[5].url)
} else {
resolve(false)
}
})
}function performRemoteAction(action,token,vin) {
return new Promise(async(resolve,reject)=>{
let remoteCommandUrl = 'https://www.bmw-connecteddrive.com/remoteservices/rsapi/v1/' + vin + '/' + actionlet remoteRequest = new Request(remoteCommandUrl)
remoteRequest.body = '{}'
remoteRequest.method = 'POST'
remoteRequest.headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
remoteRequest.loadJSON().then((data)=>{
resolve()})
.catch((e)=>{
console.error(e)
resolve()})})
}function getVehicleStatus(token,vin) {
return new Promise(async(resolve,reject)=>{
let vehicleDataUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/dynamic/v1/' + vin + '?offset=-60'
let vehicleDataRequest = new Request(vehicleDataUrl)
vehicleDataRequest.method = 'get'
vehicleDataRequest.headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
let data = await vehicleDataRequest.loadJSON()
if (data) {
resolve(data.attributesMap)
} else {
resolve(false)
}
})
}function getVehicleStatusSOC(token,vin) {
return new Promise(async(resolve,reject)=>{
let vehicleDataUrl = 'https://www.bmw-connecteddrive.com/api/vehicle/navigation/v1/' + vin + ''
let vehicleDataRequest = new Request(vehicleDataUrl)
vehicleDataRequest.method = 'get'
vehicleDataRequest.headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
let data = await vehicleDataRequest.loadJSON()
if (data) {
resolve(data)
} else {
resolve(false)
}
})
}function getVinbyParameterOrKeyChain() {
let parameters = args.widgetParameter
if (parameters != null && parameters.length > 0) {
console.log('using vin ' + parameters + ' from widget parameters')
return parameters
} else {
// try the keychain
if (!Keychain.contains(vinKey)) {
console.log('there is no stored vin')
return false
} else {
let vin = Keychain.get(vinKey)
console.log('using vin ' + vin +' from keychain')
return vin
}
}
}function getVin(token) {
return new Promise(async(resolve,reject)=>{
console.log('using vin from cd')
let vehicleListUrl = 'https://www.bmw-connecteddrive.com/api/me/vehicles/v2?all=true&brand=BM'
let vehicleListRequest = new Request(vehicleListUrl)
vehicleListRequest.method = 'get'
vehicleListRequest.headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
let list = await vehicleListRequest.loadJSON()
if (list.length > 0) {
let vin = list[0].vin
console.log(list[0])
Keychain.set(vinKey,vin)
resolve(vin)
} else {
console.log(list[0])
resolve(false)
}
})
}function getlastCharge(token,vin,date) {
let result = {}
result.latest = undefined
return new Promise( async(resolve,reject)=>{
let df = new DateFormatter()
df.dateFormat = 'yyyy-MM'
let thisMonth = df.string(date) + '-01T00:00:00.000'
let chargeSessionUrl = 'https://cocoapi.bmwgroup.com/eadrax-chs/v1/charging-sessions?vin=' + vin + '&date=' + thisMonth
let vehicleChargeRequest = new Request(chargeSessionUrl)
vehicleChargeRequest.method = 'get'
vehicleChargeRequest.headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
}
let list = await vehicleChargeRequest.loadJSON()
if (list.chargingSessions !== undefined) {
result.nrj = list.chargingSessions.totalif ((list.chargingSessions.sessions) && (list.chargingSessions.sessions.length > 0)) {
let latest = list.chargingSessions.sessions[0]
result.latest = latest.title + '|' + latest.energyCharged
}
}
resolve(result)
})
}async function getLoginToken() {
let user
let pwd
try {
if (!Keychain.contains(userKey)) {
console.error('missing parameters')
} else {
user = Keychain.get(userKey)
pwd = Keychain.get(passKey)
}
let url = 'https://customer.bmwgroup.com/gcdm/oauth/authenticate'
let post_data = {
'state': 'eyJtYXJrZXQiOiJkZSIsImxhbmd1YWdlIjoiZGUiLCJkZXN0aW5hdGlvbiI6ImxhbmRpbmdQYWdlIn0',
'username': user,
'client_id': 'dbf0a542-ebd1-4ff0-a9a7-55172fbfce35',
'password': pwd,
'redirect_uri': 'https://www.bmw-connecteddrive.com/app/default/static/external-dispatch.html',
'response_type': 'token',
'scope': 'authenticate_user fupo',
'locale': 'DE-de'
}
let lRequest = new Request(url)
lRequest.method = 'POST'
lRequest.body = getPostBody(post_data)
let result = await lRequest.load()
let tokenUrl = lRequest.response.url
if (tokenUrl) {
let match = tokenUrl.match(/&access_token=([a-zA-z0-9]{0,})/)
if (match != null) {
let token = match[1]
return token
}
}} catch(e) {
console.error(e)
}
return null
}function fetchImage(vin) {
return new Promise (async(resolve,reject)=>{
let fm = FileManager.local()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, vin + '.png')
if (fm.fileExists(path)) {
resolve(fm.readImage(path))
} else {
let carImageUrl = await getVehicleImage(vin)
let carImageRequest = new Request(carImageUrl)
let carImage = await carImageRequest.loadImage()
fm.writeImage(path, carImage)
resolve(carImage)
}
})}
-
Also ich finde nichts. Hab alles de auf com geändert aber tut sich nichts. Hab alles neu installiert und trotzdem nicht. Schade
Komisch, eigtl. sollte es dann wieder gehen. Nachdem du das Widget rausgeschmissen und wieder hinzugefügt hast, hast du dann unter "Widget bearbeiten" auch wieder die vollständige VIN als Parameter mitgegeben?
Mensch, 1000 Dank für den Tip!!!
IMG_5271.jpgGerne

-
Also ich habe es jetzt bei mir auch ausprobiert und alle Connecteddrive-Links auf com geändert und jetzt geht es wieder
