UNPKG

iobroker.bluelink

Version:

Adapter to control Hyundai or Kia vehicle

991 lines (843 loc) 40.8 kB
'use strict'; const utils = require('@iobroker/adapter-core'); const { BlueLinky } = require('bluelinky'); const Json2iob = require('./lib/json2iob'); const tools = require('./lib/tools'); const Create_tools = require('./lib/create_tools').Create_tools; const adapterIntervals = {}; //halten von allen Intervallen let request_count = 48; // halbstündig sollte als Standardeinstellung reichen (zu häufige Abfragen entleeren die Batterie spürbar) const max_request = 400; let blueLinkyClient; let slow_charging = 100; let fast_charging = 100; const todayDriveEmpty = '{"period":0,"rawDate":"20230608","date":"2023-06-07T00:00:01.000Z","consumption":{"total":0,"engine":0,"climate":0,"devices":0,"battery":0},"regen":0,"distance":0}'; const POSSIBLE_CHARGE_LIMIT_VALUES = [50, 60, 70, 80, 90, 100]; let create_tools; /* seat states from bluelinky 1: Unknown 2: Off 3: Low Cool 4: Medium Cool 5: Full Cool 6: Low Heat 7: Medium Heat 8: High Heat */ /* Checks for errors in the API response. If an error is found, an exception is raised. retCode known values: - S: success - F: failure resCode / resMsg known values: - 0000: no error - 4002: "Invalid request body - invalid deviceId", relogin will resolve but a bandaid. - 4004: "wrong credentials or server error" - 4004: "Duplicate request" - 4081: "Request timeout" - 5031: "Unavailable remote control - Service Temporary Unavailable" - 5091: "Exceeds number of requests" - 5921: "No Data Found v2 - No Data Found v2" - 9999: "Undefined Error - Response timeout" :param response: the API's JSON response */ class Bluelink extends utils.Adapter { constructor(options) { super({ ...options, name: 'bluelink', }); this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); this.on('unload', this.onUnload.bind(this)); this.vehiclesDict = {}; this.batteryState12V = {}; this.vehicles = []; this.json2iob = new Json2iob(this); adapterIntervals.evHistoryInterval = null; this.countError = 0; } async onReady() { //first check account settings this.setState('info.connection', false, true); let loginGo = true; if (this.config.motor == 'GAS') { this.config.evHistory = false; this.config.request = 1; } if (this.config.request < 1 || this.config.request > max_request) { this.log.warn('Request is invalid got to default ' + request_count); } else { request_count = this.config.request; } if (this.config.username == '') { this.log.error('Check Settings. No Username set'); loginGo = false; } if (this.config.motor == '') { this.log.error('Check Settings. Enginetype is not defined '); loginGo = false; } if (loginGo) { await this.login(); } } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * @param {() => void} callback */ onUnload(callback) { try { clearTimeout(adapterIntervals.readAllStates); clearInterval(adapterIntervals.evHistoryInterval); this.log.info('Adapter bluelink cleaned up everything...'); callback(); } catch (e) { callback(); } } async onStateChange(id, state) { if (state) { if (id.indexOf('.control.') === -1) { return; } this.log.debug('New Event for state: ' + JSON.stringify(state)); this.log.debug('ID: ' + JSON.stringify(id)); let response = ''; const vin = id.split('.')[2]; const vehicle = this.vehiclesDict[vin]; let tmpControl = id.split('.')[5]; if (tmpControl == undefined) { // wenn undefined nimm ebene höher tmpControl = id.split('.')[4]; } const force_update_obj = await this.getStateAsync(`${vin}.control.force_update`); try { switch (tmpControl) { case 'force_checkDriveInfo': this.log.info('force checkDriveInfo '); await this.readStatusVin(vehicle, force_update_obj.val); this.driveHistory(vehicle); break; case 'lock': this.log.info('Starting lock for vehicle'); response = await vehicle.lock(); this.log.debug(JSON.stringify(response)); break; case 'unlock': this.log.info('Starting unlock for vehicle'); response = await vehicle.unlock(); this.log.debug(JSON.stringify(response)); break; case 'start': this.log.info('Starting clima for vehicle'); const airTempC = await this.getStateAsync(`${vin}.control.clima.set.airTemp`); const defrost = await this.getStateAsync(`${vin}.control.clima.set.defrost`); const heating = await this.getStateAsync(`${vin}.control.clima.set.heating`); try { response = await vehicle.start({ airCtrl: true, hvacType: 0, igniOnDuration: 10, temperature: airTempC.val, defrost: defrost.val, heating1: heating.val, username: this.config.username, vin: vin, }); this.log.debug(JSON.stringify(response)); } catch (err) { this.log.error(err); } break; case 'stop': this.log.info('Stop clima for vehicle'); response = await vehicle.stop(); this.log.debug(JSON.stringify(response)); break; case 'force_refresh_from_server': this.log.info('Forcing refresh from Server'); await this.readStatusVin(vehicle,false); break; case 'force_refresh_from_car': this.log.info('Forcing refresh from Car'); await this.readStatusVin(vehicle,true); break; case 'force_refresh': this.log.info('Forcing refresh'); await this.readStatusVin(vehicle, force_update_obj.val); break; case 'force_update': if (force_update_obj.val) { this.log.info('Update method for ' + vin + ' changed to "directly from the car"'); } else { this.log.info('Update method for ' + vin + ' changed to "from the server"'); } break; case 'force_login': clearTimeout(adapterIntervals.readAllStates); clearInterval(adapterIntervals.evHistoryInterval); this.login(); break; case 'charge': this.log.info('Start charging'); response = await vehicle.startCharge(); this.log.debug(JSON.stringify(response)); break;case 'charge_stop': this.log.info('Stop charging'); response = await vehicle.stopCharge(); this.log.debug(JSON.stringify(response)); break; case 'charge_limit_fast': case 'charge_limit_slow': if (!state.ack) { if (!POSSIBLE_CHARGE_LIMIT_VALUES.includes(state.val)) { this.log.error(`Charge target values are limited to ${POSSIBLE_CHARGE_LIMIT_VALUES.join(', ')}`); } else { const charge_option = { fast: fast_charging, slow: slow_charging }; if (tmpControl == 'charge_limit_fast') { this.log.info('Set new charging options charge_limit_fast'); //set fast charging const charge_limit_slow = await this.getStateAsync(`${vin}.control.charge_limit_slow`); charge_option.fast = state.val; charge_option.slow = charge_limit_slow.val; } if (tmpControl == 'charge_limit_slow') { this.log.info('Set new charging options charge_limit_slow'); //set slow charging const charge_limit_fast = await this.getStateAsync(`${vin}.control.charge_limit_fast`); charge_option.slow = state.val; charge_option.fast = charge_limit_fast.val; } response = await vehicle.setChargeTargets(charge_option); this.log.debug(JSON.stringify(response)); } } break; default: this.log.error('No command for Control found'); } } catch(err) { this.log.error('Error onStateChange ' + err); } } } /** * Funktion to login in bluelink / UVO */ async login() { try { this.log.info('Login to api'); const loginOptions = { username: this.config.username, password: this.config.client_secret, pin: this.config.client_secret_pin, brand: this.config.brand, region: 'EU', language: this.config.language, }; blueLinkyClient = new BlueLinky(loginOptions); create_tools = new Create_tools(this); blueLinkyClient.on('ready', async (vehicles) => { this.setState('info.connection', true, true); // wir haben eine Verbindung und haben Autos this.log.info(vehicles.length + ' Vehicles found'); this.log.debug(JSON.stringify(vehicles, this.getCircularReplacer())); this.vehicles = vehicles; for (const vehicle of vehicles) { const vin = vehicle.vehicleConfig.vin; this.vehiclesDict[vin] = vehicle; await this.setObjectNotExistsAsync(vin, { type: 'device', common: { name: vehicle.vehicleConfig.nickname, }, native: {}, }); await create_tools.setControlObjects(vin); await create_tools.setStatusObjects(vin); await this.json2iob.parse(`${vin}.general`, vehicle.vehicleConfig); //read all infos await this.readStatus(); if (this.config.evHistory && this.config.motor != 'GAS') { try { await this.driveHistory(vehicle); adapterIntervals.evHistoryInterval = setInterval(() => { this.receiveEVInformation(vehicle, false); }, 60 * 60 * 1000); // check einmal die stunde nur intern } catch (error) { this.log.error('Error in receiveEVInformation'); } } await this.setStateAsync(`${vin}.error_counter`, 0, true); // tools.cleanObjects(vin, this); } this.countError = 0; }); blueLinkyClient.on('error', async (err) => { // something went wrong with login this.log.error(err); this.log.error('Server is not available or login credentials are wrong'); this.log.error('next auto login attempt in 1 hour or restart adapter manual'); const requestTimeout = setTimeout(async () => { this.login(); }, 1000 * 60 * 60); // warte 1 stunde }); } catch (error) { this.log.error('Error in login/on function'); if (typeof error === 'string') { this.log.error(error); } else if (error instanceof Error) { this.log.error(error.message); } } } //read new sates from vehicle async readStatus(force = false) { //read new verhicle status for (const vehicle of this.vehicles) { const vin = vehicle.vehicleConfig.vin; const force_update_obj = await this.getStateAsync(`${vin}.control.force_update`); this.log.debug('Read new status from api for ' + vin); const batteryControlState12V = await this.getStateAsync(`${vin}.control.batteryControlState12V`); if (this.batteryState12V[vin] && this.batteryState12V[vin] < batteryControlState12V.val && force_update_obj.val) { this.log.warn('Vin' + vin + ' 12V Battery state is low: ' + vin + ' ' + this.batteryState12V[vin] + '%. Recharge to prevent damage!'); if (this.config.protectAgainstDeepDischarge && !force) { this.log.warn('Auto Refresh is disabled, only use force refresh to reenable refresh if you are willing to risk your battery'); continue; } } await this.readStatusVin(vehicle,force_update_obj.val); } //set ne cycle if (force) { clearTimeout(adapterIntervals.readAllStates); } adapterIntervals.readAllStates = setTimeout(this.readStatus.bind(this), ((24 * 60) / request_count) * 60000); } // force_update = true Daten vom Auto // force_update = false vom server async readStatusVin(vehicle, force_update) { const vin = vehicle.vehicleConfig.vin; try { let newStatus; if(force_update) { this.log.info('Read new update for ' + vin + ' directly from the car'); } else { this.log.info('Read new update for ' + vin + ' from the server'); } try { newStatus = await vehicle.fullStatus({ refresh: force_update, parsed: true, }); //set all values this.log.debug('Set fullStatus for ' + vin); this.log.debug('RAW ' + JSON.stringify(newStatus)); // raw data await this.json2iob.parse(vin + '.vehicleStatusRaw', newStatus); if (newStatus.hasOwnProperty('vehicleStatus')) { await this.setVehicleStatusObjects(vin); await this.setNewFullStatus(newStatus, vin); } else { // neue struktur ohne auflösung await tools.cleanNotAvailableObjects(this); // location sonderlocke if (newStatus.hasOwnProperty('Location')) { // beniziner haben anscheinenden keine location await tools.setLocation(this, vin, newStatus.Location.GeoCoord.Latitude, newStatus.Location.GeoCoord.Longitude, newStatus.Location.Speed.Value); } //Odometer this.checkOdometer(vin, newStatus); } } catch (error) { if (typeof error === 'string') { this.log.error('Error on API-Request fullStatus'); this.log.error(error); } else { //if(error.source.statusCode == 503) { this.log.info('Error on fullStatus - new try with Status Request'); newStatus = await vehicle.status({ refresh: force_update, parsed: true, }); this.log.debug('Set Status for ' + vin); this.log.debug(JSON.stringify(newStatus)); await this.json2iob.parse(vin + '.vehicleStatusRaw', newStatus); if (newStatus.hasOwnProperty('vehicleStatus')) { await this.setVehicleStatusObjects(vin); await this.setShortStatus(newStatus, vin); } else { // neue struktur ohne auflösung await tools.cleanNotAvailableObjects(this); // location sonderlocke if (newStatus.hasOwnProperty('Location')) { // beniziner haben anscheinenden keine location await tools.setLocation(this, vin, newStatus.Location.GeoCoord.Latitude, newStatus.Location.GeoCoord.Longitude, newStatus.Location.Speed.Value); } //Odometer this.checkOdometer(vin, newStatus); } } } //Abfrage war erfolgreich, lösche ErrorCounter this.countError = 0; await this.setStateAsync(`${vin}.error_counter`, this.countError, true); this.log.info('Update for ' + vin + ' successfull'); // last update await this.setStateAsync(`${vin}.lastInfoUpdate`, Number(Date.now()), true); } catch (error) { this.countError += 1; // add 1 this.log.error('Error on API-Request Status, ErrorCount:' + this.countError); if (typeof error === 'string') { this.log.error(error); } else if (error instanceof Error) { this.log.error(error.message); } } await this.setStateAsync(`${vin}.error_counter`, this.countError, true); if (this.countError > this.config.errorCounter) { //Error counter over x erros, restart Adapter to fix te API Token this.restart(); } } async checkOdometer(vin, newStatus){ let odometer = 0; if (newStatus.hasOwnProperty('Drivetrain')) { odometer = newStatus.Drivetrain.Odometer; } if (newStatus.hasOwnProperty('odometer')) { odometer = newStatus.odometer; } if (newStatus.hasOwnProperty('ccs2Status')) { if (newStatus.ccs2Status.state.Vehicle.hasOwnProperty('Drivetrain')) { odometer = newStatus.ccs2Status.state.Vehicle.Drivetrain.Odometer; } } if (odometer > 0) { await this.setStateAsync(`${vin}.odometer.value`, {val: odometer, ack: true}); } } async receiveEVInformation(vehicle, manu) { const tickHour = new Date().getHours(); // um 23 uhr daten festschreiben if (tickHour == 23 && !manu) { this.log.info('DriveHistory Update for ' + vehicle.vehicleConfig.vin); this.driveHistory(vehicle); } } async driveHistory(vehicle) { try { const vin = vehicle.vehicleConfig.vin; const driveHistory = await vehicle.driveHistory(); this.log.debug('driveHistory ' + JSON.stringify(driveHistory)); if (driveHistory.hasOwnProperty('history')) { if (driveHistory.history.length > 0) { this.log.debug('driveHistory-Data: ' + JSON.stringify(driveHistory)); await this.setObjectNotExistsAsync(vin + '.driveHistory', { type: 'channel', common: { name: 'drive history', }, native: {}, }); await this.json2iob.parse(vin + '.driveHistory', driveHistory, {preferedArrayName: 'rawDate'}); this.todayOnly(vin, driveHistory); } } const monthlyReport = await vehicle.monthlyReport(); // die funktion liefert immer 1 monat ab heuitgen datum zurück, warum auch immer this.log.debug('monthlyReport ' + JSON.stringify(monthlyReport)); if (monthlyReport.hasOwnProperty('start')) { if (monthlyReport.start != undefined) { await this.setObjectNotExistsAsync(vin + '.monthlyReport', { type: 'channel', common: { name: 'monthly report', }, native: {}, }); await this.json2iob.parse(vin + '.monthlyReport', monthlyReport); } } if (this.config.motor == 'EV') { let tripInfo = await vehicle.tripInfo({ year: new Date().getFullYear(), month: new Date().getMonth(), day: new Date().getDay() }); this.log.debug('tripInfo ' + JSON.stringify(tripInfo)); tripInfo = tripInfo.map(entry => { const {trips, ...rest} = entry; // Destrukturieren, um den `trips`-Knoten zu entfernen return { time: 'today', // Neues Element einfügen an erster stelle ...rest // Restliche Eigenschaften hinzufügen }; }); if (tripInfo.length > 0) { await this.setObjectNotExistsAsync(vin + '.tripInfo', { type: 'channel', common: { name: 'trip information', }, native: {}, }); await this.json2iob.parse(vin + '.tripInfo', tripInfo); } } } catch (error) { this.log.error('EV History fetching failed'); if (typeof error === 'string') { this.log.error(error); } else if (error instanceof Error) { this.log.error(error.message); } } } async todayOnly(vin, driveHistory) { const onlyHistory = driveHistory.history; const today = this.getToday(); for (const lpEntry in onlyHistory) { const res = onlyHistory[lpEntry]; this.log.debug('check Today ' + today + ' ' + res.rawDate); if (today == res.rawDate) { // suche heutiges Datum await this.setObjectNotExistsAsync(vin + '.driveHistory.today', { type: 'channel', common: { name: 'today report', }, native: {}, }); this.log.debug('write Today ' + res); await this.json2iob.parse(vin + '.driveHistory.today', res); break; } } } getToday() { const today = new Date(); const yyyy = today.getFullYear(); let mm = today.getMonth() + 1; let dd = today.getDate(); if (dd < 10) dd = '0' + dd; if (mm < 10) mm = '0' + mm; return yyyy + '' + mm + '' + dd; } //short status async setShortStatus(newStatus, vin) { try { //chassis await this.setStateAsync(vin + '.vehicleStatus.doorLock', {val: newStatus.chassis.locked, ack: true}); await this.setStateAsync(vin + '.vehicleStatus.trunkOpen', {val: newStatus.chassis.trunkOpen, ack: true}); await this.setStateAsync(vin + '.vehicleStatus.hoodOpen', {val: newStatus.chassis.hoodOpen, ack: true}); this.checkDoor(vin, newStatus.chassis.openDoors); //climate await this.setStateAsync(vin + '.vehicleStatus.airCtrlOn', {val: newStatus.climate.active, ack: true}); await this.setStateAsync(vin + '.vehicleStatus.airTemp', { val: newStatus.climate.temperatureSetpoint, ack: true }); let steerWheelHeat = newStatus.climate.steeringwheelHeat; if (typeof steerWheelHeat == 'number') { steerWheelHeat = steerWheelHeat == 0 ? false : true; } await this.setStateAsync(vin + '.vehicleStatus.steerWheelHeat', {val: steerWheelHeat, ack: true}); //Engine if (newStatus.engine.hasOwnProperty('batteryChargeHV')) { await this.setStateAsync(vin + '.vehicleStatus.battery.soc', { val: newStatus.engine.batteryChargeHV, ack: true }); } if (newStatus.engine.hasOwnProperty('charging')) { await this.setStateAsync(vin + '.vehicleStatus.battery.charge', { val: newStatus.engine.charging, ack: true }); } if (newStatus.engine.hasOwnProperty('batteryCharge12v')) { await this.setStateAsync(vin + '.vehicleStatus.battery.soc-12V', { val: newStatus.engine.batteryCharge12v, ack: true }); this.batteryState12V[vin] = newStatus.engine.batteryCharge12v; } } catch (err) { this.log.error(err.stack); } } //full status async setNewFullStatus(newStatus, vin) { let lastUpdate = 0; try { await this.setStateAsync(vin + '.vehicleStatus.airCtrlOn', { val: newStatus.vehicleStatus.airCtrlOn, ack: true }); if (newStatus.vehicleStatus.hasOwnProperty('airTemp')) { await this.setStateAsync(vin + '.vehicleStatus.airTemp', { val: this.getCelsiusFromTempcode(newStatus.vehicleStatus.airTemp.value), ack: true }); } if (newStatus.hasOwnProperty('vehicleLocation')) { if (newStatus.vehicleLocation != undefined) { lastUpdate = newStatus.vehicleLocation.time; tools.setLocation(this, vin, newStatus.vehicleLocation.coord.lat, newStatus.vehicleLocation.coord.lon, newStatus.vehicleLocation.speed.value); } } //Charge if (this.config.motor == 'GAS') { await this.setStateAsync(vin + '.vehicleStatus.dte', { val: newStatus.vehicleStatus.dte.value, ack: true }); } else { if (newStatus.vehicleStatus.hasOwnProperty('evStatus')) { if (newStatus.vehicleStatus.evStatus.reservChargeInfos.hasOwnProperty('targetSOClist[0]')) { if (newStatus.vehicleStatus.evStatus.reservChargeInfos.targetSOClist[0].plugType == 1) { //Slow = 1 -> Index 0 ist slow slow_charging = newStatus.vehicleStatus.evStatus.reservChargeInfos.targetSOClist[0].targetSOClevel; await this.setStateAsync(vin + '.control.charge_limit_slow', { val: slow_charging, ack: true, }); fast_charging = newStatus.vehicleStatus.evStatus.reservChargeInfos.targetSOClist[1].targetSOClevel; await this.setStateAsync(vin + '.control.charge_limit_fast', { val: fast_charging, ack: true, }); } else { //fast = 0 -> Index 0 ist fast slow_charging = newStatus.vehicleStatus.evStatus.reservChargeInfos.targetSOClist[1].targetSOClevel; await this.setStateAsync(vin + '.control.charge_limit_slow', { val: slow_charging, ack: true, }); fast_charging = newStatus.vehicleStatus.evStatus.reservChargeInfos.targetSOClist[0].targetSOClevel; await this.setStateAsync(vin + '.control.charge_limit_fast', { val: fast_charging, ack: true, }); } } //Nur für Elektro Fahrzeuge - Battery await this.setStateAsync(vin + '.vehicleStatus.dte', { val: newStatus.vehicleStatus.evStatus.drvDistance[0].rangeByFuel.totalAvailableRange.value, ack: true, }); let evRange = newStatus.vehicleStatus.evStatus.drvDistance[0].rangeByFuel.evModeRange.value; if (evRange < 1 && this.config.batteryRange > 0) { evRange = Math.round(((newStatus.vehicleStatus.evStatus.batteryStatus / 100) * this.config.batteryRange)*100)/100; } await this.setStateAsync(vin + '.vehicleStatus.evModeRange', { val: evRange, ack: true, }); if (newStatus.vehicleStatus.evStatus.drvDistance[0].rangeByFuel.hasOwnProperty('gasModeRange')) { //Only for PHEV await this.setStateAsync(vin + '.vehicleStatus.gasModeRange', { val: newStatus.vehicleStatus.evStatus.drvDistance[0].rangeByFuel.gasModeRange.value, ack: true, }); } await this.setStateAsync(vin + '.vehicleStatus.battery.soc', { val: newStatus.vehicleStatus.evStatus.batteryStatus, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.battery.charge', { val: newStatus.vehicleStatus.evStatus.batteryCharge, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.battery.plugin', { val: newStatus.vehicleStatus.evStatus.batteryPlugin, ack: true }); //Ladezeit anzeigen, da noch nicht klar welche Werte await this.setStateAsync(vin + '.vehicleStatus.battery.minutes_to_charged', { val: newStatus.vehicleStatus.evStatus.remainTime2.atc.value, ack: true, }); this.log.debug('Folgende Ladezeitenmöglichkeiten wurden gefunden:'); this.log.debug(JSON.stringify(newStatus.vehicleStatus.evStatus.remainTime2)); } } if (newStatus.hasOwnProperty('ccs2Status')) { this.log.debug('ccs2Status: ' + JSON.stringify(newStatus.ccs2Status)); if (newStatus.ccs2Status.state.Vehicle.hasOwnProperty('Location')) { const ts = newStatus.ccs2Status.state.Vehicle.Location.TimeStamp; const lastUpdate_ccs2 = ts.Year + String(ts.Mon).padStart(2, '0') + String(ts.Day).padStart(2, '0') + String(ts.Hour).padStart(2, '0') + String(ts.Min).padStart(2, '0') + String(ts.Sec).padStart(2, '0'); if (lastUpdate_ccs2 > lastUpdate) { tools.setLocation(this, vin, newStatus.ccs2Status.state.Vehicle.Location.GeoCoord.Latitude, newStatus.ccs2Status.state.Vehicle.Location.GeoCoord.Longitude, newStatus.ccs2Status.state.Vehicle.Location.Speed.Value); } } // Battery await this.setStateAsync(vin + '.vehicleStatus.battery.soc-12V', { val: newStatus.ccs2Status.state.Vehicle.Electronics.Battery.Level, ack: true }); if (newStatus.ccs2Status.state.Vehicle.Green.hasOwnProperty('BatteryManagement')) { if (newStatus.ccs2Status.state.Vehicle.Green.BatteryManagement.hasOwnProperty('BatteryRemain')) { await this.setStateAsync(vin + '.vehicleStatus.battery.soc', { val: newStatus.ccs2Status.state.Vehicle.Green.BatteryManagement.BatteryRemain.Ratio, ack: true }); } } if (newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.hasOwnProperty('ConnectorFastening')) { await this.setStateAsync(vin + '.vehicleStatus.battery.charge', { val: newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.ConnectorFastening.State == 1 ? true : false, ack: true }); } if (newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.hasOwnProperty('Charging')) { await this.setStateAsync(vin + '.vehicleStatus.battery.minutes_to_charged', { val: newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.Charging.RemainTime, ack: true, }); } if (newStatus.ccs2Status.state.Vehicle.Green.hasOwnProperty('BatteryManagement')) { if (newStatus.ccs2Status.state.Vehicle.Green.BatteryManagement.hasOwnProperty('SoH')) { await this.setStateAsync(vin + '.vehicleStatus.battery.soh', { val: newStatus.ccs2Status.state.Vehicle.Green.BatteryManagement.SoH.Ratio, ack: true, }); } } //Odometer await this.checkOdometer(newStatus); //fast = 0 -> Index 0 ist fast if (newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.hasOwnProperty('TargetSoC')) { slow_charging = newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.TargetSoC.Standard; await this.setStateAsync(vin + '.control.charge_limit_slow', { val: slow_charging, ack: true, }); fast_charging = newStatus.ccs2Status.state.Vehicle.Green.ChargingInformation.TargetSoC.Quick; await this.setStateAsync(vin + '.control.charge_limit_fast', { val: fast_charging, ack: true, }); } } else { if (newStatus.vehicleStatus.hasOwnProperty('battery')) { await this.setStateAsync(vin + '.vehicleStatus.battery.soc-12V', { val: newStatus.vehicleStatus.battery.batSoc, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.battery.state-12V', { val: newStatus.vehicleStatus.battery.batState, ack: true }); } //Odometer await this.checkOdometer(newStatus); } //door await this.setStateAsync(vin + '.vehicleStatus.doorLock', { val: newStatus.vehicleStatus.doorLock, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.trunkOpen', { val: newStatus.vehicleStatus.trunkOpen, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.hoodOpen', { val: newStatus.vehicleStatus.hoodOpen, ack: true }); this.checkDoor(vin, newStatus.vehicleStatus.doorOpen); //status parameter await this.setStateAsync(vin + '.vehicleStatus.airCtrlOn', { val: newStatus.vehicleStatus.airCtrlOn, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.smartKeyBatteryWarning', { val: newStatus.vehicleStatus.smartKeyBatteryWarning, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.washerFluidStatus', { val: newStatus.vehicleStatus.washerFluidStatus, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.breakOilStatus', { val: newStatus.vehicleStatus.breakOilStatus, ack: true }); let steerWheelHeat = newStatus.vehicleStatus.steerWheelHeat; if (typeof steerWheelHeat == 'number') { steerWheelHeat = steerWheelHeat == 0 ? false : true; } await this.setStateAsync(vin + '.vehicleStatus.steerWheelHeat', {val: steerWheelHeat, ack: true}); await this.setStateAsync(vin + '.vehicleStatus.sideBackWindowHeat', { val: newStatus.vehicleStatus.sideBackWindowHeat, ack: true }); // hier 12V merken if (newStatus.vehicleStatus. hasOwnProperty('battery')) { this.log.debug('Set ' + newStatus.vehicleStatus.battery.batSoc + ' battery state for ' + vin); await this.setStateAsync(vin + '.vehicleStatus.battery.soc-12V', { val: newStatus.vehicleStatus.battery.batSoc, ack: true }); this.batteryState12V[vin] = newStatus.vehicleStatus.battery.batSoc; } } catch (err) { this.log.error(err.message); this.log.error(err.stack); } } async checkDoor(vin, doors) { if (doors != undefined) { let frontLeft = doors.frontLeft; let frontRight = doors.frontRight; let backLeft = doors.backLeft; let backRight = doors.backRight; // HEV hyundai send 0 but we need boolean if (typeof frontLeft == 'number') { frontLeft = frontLeft == 0 ? false : true; } if (typeof frontRight == 'number') { frontRight = frontRight == 0 ? false : true; } if (typeof backLeft == 'number') { backLeft = backLeft == 0 ? false : true; } if (typeof backRight == 'number') { backRight = backRight == 0 ? false : true; } await this.setStateAsync(vin + '.vehicleStatus.doorOpen.frontLeft', { val: frontLeft, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.doorOpen.frontRight', { val: frontRight, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.doorOpen.backLeft', { val: backLeft, ack: true }); await this.setStateAsync(vin + '.vehicleStatus.doorOpen.backRight', { val: backRight, ack: true }); } } getCircularReplacer() { const seen = new WeakSet(); return (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; } getCelsiusFromTempcode(tempCode) { const tempRange = []; //Range for EU for (let i = 14; i <= 30; i += 0.5) { tempRange.push(i); } const tempIndex = parseInt(tempCode, 16); return tempRange[tempIndex]; } } if (require.main !== module) { module.exports = (options) => new Bluelink(options); } else { new Bluelink(); }