@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
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.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)
}
})
}
Alles anzeigen