Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 67
- Registriert: Sa 10. Nov 2018, 15:50
- Hat sich bedankt: 7 Mal
- Danke erhalten: 10 Mal
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 8
- Registriert: Sa 2. Mai 2020, 21:46
- Hat sich bedankt: 1 Mal
- Danke erhalten: 15 Mal
Das Script kann nichts mehr als die BMW App Eben nur der Vorteil, dass es direkt auf dem Homescreen angezeigt wird ohne das man die App öffnen muss. Also für den einen oder anderen nur ein unnützes Gimmick. Oder eben für mich ein doch nützliches GimmickAbRiNgOi hat geschrieben: ↑ Dieses Aktivierung benötigt die ein Paket aus dem Store https://www.bmw-connecteddrive.at/app/i ... kage_Store um 69 € pro Jahr oder funktioniert die app auch bei abgelaufenen Paketen oder ganz ohne ?
Und: Was kann die App dann mehr als die BMW App außer das diese am Start Bildschirm ist?
Es müssen meiner Meinung nach mind. die Remote Services gebucht sein. Da ich aber ein i3 aus 2015 habe sind dort eh einige Pakete dauerhaft dabei.
https://www.bmw-connecteddrive.at/app/i ... emoteOffer
Wichtig ist die Installationsanleitung, Schritt für Schritt einzuhalten. also Scriptable App installieren, GitHub Webseite mit Script mit Safari Browser öffnen, auf RAW oben rechts klicken, alles markierend in Zwischenablage kopieren, in Scriptable App auf Plus-Zeichen klicken und Script einfügen. Dann unten rechts auf den Play Button klicken. Dann Zugangsdaten für ConnectedDrive (email Adresse und Kennwort) eingeben.
Anschließend oben auf „Untitled Script“ klicken und einen Namen vergeben. Dann auf Done klicken.
Rest bzgl. Widget hinzufügen wie in der Anleitung angegeben, vorgehen.
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 720
- Registriert: Mo 26. Jun 2017, 07:07
- Wohnort: Erding
- Hat sich bedankt: 41 Mal
- Danke erhalten: 97 Mal
das Format ist dd.mm.yyyy ... inzwsichen kommt die meldung sogar schon wenn ich den code ins skript kopiere und play drücke ...
10/22 i4 40e, Brooklyn Grey
04/91 520iA E34, Islandgrün Metallic
04/91 520iA E34, Islandgrün Metallic
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 142
- Registriert: Mi 6. Nov 2019, 15:46
- Wohnort: Bayern
- Hat sich bedankt: 2 Mal
- Danke erhalten: 30 Mal
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 445
- Registriert: Do 27. Dez 2018, 17:50
- Hat sich bedankt: 28 Mal
- Danke erhalten: 69 Mal
strings = {}
strings['de'] = {
'What to do ?':'Was ist zu tun ?',
'Start cabin heating':'Starte die Heizung',
'Flash lights':'Lichthupe',
'Make some noise':'Mach Lärm !',
'Lock the doors':'Türen verriegeln',
'Nothing, thank you.':'Nix, danke.',
'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 Man !',
'Noooooooo !':'Neiiiiiiin',
'Its done':'Erledigt',
'Your request to ':'Deine Anforderung für ',
' was sent.':' wurde gesendet.',
'Thank you !':'Dankeschön ;o)',
'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(14)
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(14)
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(14)
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(14)
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(14)
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(14)
}
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.de/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.de/remot ... /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.de/api/v ... ynamic/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.de/api/v ... gation/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.de/api/m ... e&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 ... sions?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/ ... patch.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)
}
})
}
strings['de'] = {
'What to do ?':'Was ist zu tun ?',
'Start cabin heating':'Starte die Heizung',
'Flash lights':'Lichthupe',
'Make some noise':'Mach Lärm !',
'Lock the doors':'Türen verriegeln',
'Nothing, thank you.':'Nix, danke.',
'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 Man !',
'Noooooooo !':'Neiiiiiiin',
'Its done':'Erledigt',
'Your request to ':'Deine Anforderung für ',
' was sent.':' wurde gesendet.',
'Thank you !':'Dankeschön ;o)',
'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(14)
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(14)
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(14)
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(14)
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(14)
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(14)
}
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.de/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.de/remot ... /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.de/api/v ... ynamic/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.de/api/v ... gation/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.de/api/m ... e&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 ... sions?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/ ... patch.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)
}
})
}
Re: Geniales iOS14 Homescreen Widget Script für SoC, Reichweite, usw.
- Profil
- Beiträge: 445
- Registriert: Do 27. Dez 2018, 17:50
- Hat sich bedankt: 28 Mal
- Danke erhalten: 69 Mal
Anzeige
-
- Vergleichbare Themen
-
-
Suche nach OCCP Server mit Strommanagement von exterm Script
von wlanrouter » Do 7. Sep 2023, 23:41 » in Private Lade-Infrastruktur -
Letzter Beitrag von koaschten
Di 19. Sep 2023, 17:10
-
Suche nach OCCP Server mit Strommanagement von exterm Script
-
-
SOC über OBD per WLAN auslesen um Wallbox zu steuern
von Ladidaaa » Fr 28. Apr 2023, 17:16 » in Private Lade-Infrastruktur -
Letzter Beitrag von TSG
Di 2. Apr 2024, 11:49
-
SOC über OBD per WLAN auslesen um Wallbox zu steuern
-
-
SoH zurücksetzen für lineareren SOC?
von geka » Sa 29. Apr 2023, 22:52 » in ZOE - Allgemeine Themen -
Letzter Beitrag von Sonnenfahrer
So 30. Apr 2023, 10:54
-
SoH zurücksetzen für lineareren SOC?
-
-
Bis zu welchem SoC ladet ihr auf der Langstrecke?
von Manisch » Di 9. Mai 2023, 16:40 » in Mégane E-TECH Electric - Batterie, Reichweite -
Letzter Beitrag von Manisch
Mi 10. Mai 2023, 08:24
-
Bis zu welchem SoC ladet ihr auf der Langstrecke?
-
-
SoC bei Ankunft
von pmd » So 21. Mai 2023, 10:16 » in i3X - Infotainment -
Letzter Beitrag von elekart
Mi 24. Mai 2023, 18:02
-
SoC bei Ankunft