diff --git a/main.js b/main.js
index 3604d6d..a70d6ba 100644
--- a/main.js
+++ b/main.js
@@ -141,14 +141,17 @@ function createWindow() {
// get tesla Data
let authToken
- const getTeslaData = async () => {
+ const getTeslaData = async (vehicleIdx) => {
authToken = store.get('authToken')
+ if (typeof vehicleIdx !== 'number') vehicleIdx = store.get('vehicleIdx') || 0
if (mainWindow.isVisible() && authToken) {
try {
let vehicle
try {
- vehicle = await tesla.vehicle(authToken)
+ const vehicles = await tesla.vehicles(authToken)
+ vehicleIdx = (vehicleIdx % vehicles.length) || 0
+ vehicle = vehicles[vehicleIdx]
} catch (error) {
log.error(error)
store.delete('authToken')
@@ -159,8 +162,10 @@ function createWindow() {
}
store.set('vehicleId', vehicle.vehicleID)
+ store.set('vehicleIdx', vehicleIdx)
if (vehicle.state !== 'online') {
mainWindow.webContents.send('tesla-data', {
+ vehicleIdx,
...vehicle
})
await tesla.wakeUp(authToken, vehicle.vehicleID)
@@ -172,6 +177,7 @@ function createWindow() {
)
mainWindow.webContents.send('tesla-data', {
model: vehicle.model,
+ vehicleIdx,
...vehicleData
})
mainWindow.webContents.send('tesla-data-error', false)
@@ -208,6 +214,10 @@ function createWindow() {
startLogin(false, loginEmailPw)
})
+ ipcMain.on('switch-vehicle', async (event, vehicleIdx) => {
+ getTeslaData(vehicleIdx)
+ })
+
// wait helper function
async function wait(ms) {
return new Promise(resolve => {
@@ -219,7 +229,7 @@ function createWindow() {
mainWindow.webContents.send('action-loading', action)
try {
if (action === 'door-unlock') {
- await tesla.unLockDoor(store.get('authToken'), store.get('vehicleId'))
+ await tesla.unlockDoor(store.get('authToken'), store.get('vehicleId'))
} else {
await tesla.lockDoor(store.get('authToken'), store.get('vehicleId'))
}
@@ -359,12 +369,16 @@ function createWindow() {
}
let firstshow = true
+ let loading = false // lock to ensure we don't load more than we ought to
mainWindow.on('show', async () => {
if (firstshow) {
firstshow = false
return
}
- getTeslaData()
+ if (loading) return
+ loading = true
+ await getTeslaData()
+ loading = false
})
})
diff --git a/package.json b/package.json
index 595a1e7..bca5da7 100644
--- a/package.json
+++ b/package.json
@@ -13,11 +13,13 @@
"directories": {
"output": "packaged"
},
- "publish": [{
- "provider": "github",
- "owner": "geraldoramos",
- "repo": "nikola"
- }],
+ "publish": [
+ {
+ "provider": "github",
+ "owner": "geraldoramos",
+ "repo": "nikola"
+ }
+ ],
"appId": "com.github.geraldoramos.nikola",
"mac": {
"category": "public.app-category.utilities",
@@ -94,6 +96,6 @@
"electron-updater": "^4.0.6",
"google-maps-react": "^2.0.2",
"react-tooltip": "^3.10.0",
- "teslajs": "^4.3.9"
+ "teslajs": "^4.7.4"
}
-}
\ No newline at end of file
+}
diff --git a/src/assets/css/App.css b/src/assets/css/App.css
index 16ad1e3..000b745 100644
--- a/src/assets/css/App.css
+++ b/src/assets/css/App.css
@@ -42,6 +42,27 @@ body {
border-top-right-radius: var(--window-border-radius);
}
+.car-picker > svg {
+ position: absolute;
+ top: 5px;
+}
+
+.car-picker > svg:hover {
+ color: #bbbbff;
+}
+
+.car-picker > svg:active {
+ color: #ffffbb;
+}
+
+.car-picker .car-left {
+ left: 2px;
+}
+
+.car-picker .car-right {
+ right: 2px;
+}
+
.window-content {
background-color: var(--background-color);
}
diff --git a/src/components/Home.js b/src/components/Home.js
index b739cf9..592fd6e 100644
--- a/src/components/Home.js
+++ b/src/components/Home.js
@@ -4,7 +4,11 @@ import React, { Component } from 'react'
const { ipcRenderer, remote } = window.require('electron')
import batteryLevelIcon from './helpers/battery-level-icon'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faWifi } from '@fortawesome/free-solid-svg-icons'
+import {
+ faWifi,
+ faChevronCircleLeft,
+ faChevronCircleRight
+} from '@fortawesome/free-solid-svg-icons'
import App from './App'
import Login from './Login'
@@ -21,6 +25,8 @@ class Home extends React.Component {
online: true
}
this.alertOnlineStatus = this.alertOnlineStatus.bind(this)
+ this.handleVehicleIdxDec = this.handleVehicleIdxDec.bind(this)
+ this.handleVehicleIdxInc = this.handleVehicleIdxInc.bind(this)
}
alertOnlineStatus() {
@@ -106,6 +112,7 @@ class Home extends React.Component {
? store.gui_settings.gui_charge_rate_units
: null
},
+ vehicleIdx: store.vehicleIdx,
status: {
driverTempSetting: store.climate_state
? store.climate_state.driver_temp_setting
@@ -163,6 +170,14 @@ class Home extends React.Component {
window.removeEventListener('offline', this.alertOnlineStatus)
}
+ handleVehicleIdxDec() {
+ ipcRenderer.send('switch-vehicle', this.state.vehicleIdx - 1)
+ }
+
+ handleVehicleIdxInc() {
+ ipcRenderer.send('switch-vehicle', this.state.vehicleIdx + 1)
+ }
+
render() {
if (!this.state.online) {
return (
@@ -209,6 +224,22 @@ class Home extends React.Component {
diff --git a/src/components/Login.js b/src/components/Login.js
index 93668ee..8cf79df 100644
--- a/src/components/Login.js
+++ b/src/components/Login.js
@@ -65,6 +65,7 @@ class Login extends React.Component {
onChange={this.handleChange}
className="form-control"
placeholder="Enter email"
+ autofocus={true}
/>
diff --git a/src/components/helpers/battery-level-icon.js b/src/components/helpers/battery-level-icon.js
index d1f656e..fd44c9a 100644
--- a/src/components/helpers/battery-level-icon.js
+++ b/src/components/helpers/battery-level-icon.js
@@ -6,23 +6,18 @@ import {
faBatteryThreeQuarters
} from '@fortawesome/free-solid-svg-icons'
-export default level => {
- let battery
- switch (true) {
- case 0 <= level && level <= 9:
- battery = { type: faBatteryEmpty, color: '#cc0001' }
- break
- case 10 <= level && level <= 39:
- battery = { type: faBatteryQuarter, color: '#cc0001' }
- break
- case 40 <= level && level <= 69:
- battery = { type: faBatteryHalf, color: '#1BC47D' }
- break
- case 70 <= level && level <= 90:
- battery = { type: faBatteryThreeQuarters, color: '#1BC47D' }
- break
- default:
- battery = { type: faBatteryFull, color: '#1BC47D' }
+const levels = {
+ 10: { type: faBatteryEmpty, color: '#cc0001' },
+ 40: { type: faBatteryQuarter, color: '#cc0001' },
+ 70: { type: faBatteryHalf, color: '#1BC47D' },
+ 90: { type: faBatteryThreeQuarters, color: '#1BC47D' },
+ 100: { type: faBatteryFull, color: '#1BC47D' },
+}
+
+export default function batteryLevelIcon(thisLevel) {
+ for (const levelThresh of Object.keys(levels)) {
+ if (thisLevel <= Number(levelThresh)) return levels[levelThresh]
}
- return battery
+ // Over 100?
+ return levels[100]
}
diff --git a/tesla-api.js b/tesla-api.js
index a13399b..d59270d 100644
--- a/tesla-api.js
+++ b/tesla-api.js
@@ -2,156 +2,84 @@ const tjs = require('teslajs');
const log = require('electron-log');
module.exports = {
- login: ({ username, password}) => {
- return new Promise((resolve, reject) => {
- tjs.login(username, password, function (err, result) {
- if (err) {
- reject(err)
- return
- }
- resolve(result.authToken)
- })
- })
+ login({username, password}) {
+ log.debug('API: Logging in')
+ return tjs.loginAsync(username, password)
+ .then((result) => result.authToken)
},
- vehicle: (authToken) => {
- return new Promise((resolve, reject) => {
- const options = {
- authToken: authToken
- };
- log.info('Hitting Tesla API on ' + new Date())
- tjs.vehicle(options, function (err, vehicle) {
- if (err) {
- log.error(err)
- reject(err)
- return
- }
- resolve({
- authToken: authToken,
+ vehicle(authToken) {
+ const options = { authToken }
+ log.debug('API: Getting vehicle')
+ return tjs.vehicleAsync(options)
+ .then((vehicle) => {
+ return {
vehicleID: vehicle.id_s,
model: tjs.getModel(vehicle),
state: vehicle.state
- })
- });
- })},
- wakeUp: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.wakeUp(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
+ }
})
},
- vehicleData: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.vehicleData(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
+ vehicles(authToken) {
+ const options = { authToken }
+ log.debug('API: Getting all vehicles')
+ return tjs.vehiclesAsync(options)
+ .then((vehicles) => {
+ return vehicles.map((vehicle) => ({
+ vehicleID: vehicle.id_s,
+ model: tjs.getModel(vehicle),
+ state: vehicle.state
+ }))
})
},
- lockDoor: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.doorLock(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
- })
+ wakeUp(authToken, vehicleID) {
+ log.debug('API: Waking up')
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.wakeUpAsync(vehicleOptions)
},
- unLockDoor: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.doorUnlock(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
- })
+ vehicleData(authToken, vehicleID) {
+ log.debug(`API: Getting data for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.vehicleDataAsync(vehicleOptions)
},
- climateStart: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.climateStart(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
- })
+ lockDoor(authToken, vehicleID) {
+ log.debug(`API: Locking door for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.doorLockAsync(vehicleOptions)
},
- climateStop: (authToken, vehicleID) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.climateStop(vehicleOptions, function (err, data) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(data)
- });
- })
+ unlockDoor(authToken, vehicleID) {
+ log.debug(`API: Unlocking door for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.doorUnlockAsync(vehicleOptions)
},
- setSentryMode: (authToken, vehicleID, onoff) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.setSentryMode(vehicleOptions, onoff, function (err, done) {
- if (err) {
- log.error(err)
- reject(err)
- }
- resolve(done)
- });
- })
+ climateStart(authToken, vehicleID) {
+ log.debug(`API: Climate start for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.climateStartAsync(vehicleOptions)
},
- setTemps: (authToken, vehicleID, temp) => {
- return new Promise((resolve, reject) => {
- const vehicleOptions = {
- authToken,
- vehicleID
- };
- tjs.setTemps(vehicleOptions, temp, null, function (err, done) {
- if (done.result && !done.err) {
- resolve(done.result)
- return
- }
- log.error(done.reason, err)
- reject(done.reason)
- });
- })
+ climateStop(authToken, vehicleID) {
+ log.debug(`API: Climate stop for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.climateStopAsync(vehicleOptions)
+ },
+ setSentryMode(authToken, vehicleID, onoff) {
+ log.debug(`API: Turning Sentry mode ${onoff ? 'on' : 'off'} for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.setSentryModeAsync(vehicleOptions, onoff)
+ },
+ setTemps(authToken, vehicleID, temp) {
+ log.debug(`API: Setting temp to ${temp} for vehicle id ${vehicleID}`)
+ const vehicleOptions = { authToken, vehicleID }
+ return tjs.setTempsAsync(vehicleOptions, temp, null)
}
}
+
+// Add error logger. Log & rethrow.
+for (const methodName of Object.keys(module.exports)) {
+ const fn = module.exports[methodName];
+ module.exports[methodName] = function(...args) {
+ return fn(...args).catch((err) => {
+ log.error(`API: Error executing "${methodName}": ${err.message}`)
+ throw err;
+ });
+ }
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index fa0e9d3..a7bb28f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6958,10 +6958,10 @@ terser@^4.0.0:
source-map "~0.6.1"
source-map-support "~0.5.10"
-teslajs@^4.3.9:
- version "4.3.10"
- resolved "https://registry.yarnpkg.com/teslajs/-/teslajs-4.3.10.tgz#9e030f3a3558a02a1ad5cca2f7d4abb531950b25"
- integrity sha512-lPuuzOsdXF/9Y3AvujV2whfXkWPR3NeR5AkoYdgrX3VP+lsVqNh+LwtCoYhWtseXEEHpagSbU6ntXKq+gf6Bbw==
+teslajs@^4.7.4:
+ version "4.7.4"
+ resolved "https://registry.yarnpkg.com/teslajs/-/teslajs-4.7.4.tgz#49ab09f17808ddbfd218845b0d89954e338b532e"
+ integrity sha512-mkQ6YfsuotPilrXQ+/J2hSWeXdFP27q2cpWsyzk1Jfk6aYCDPWFpmu+aEJoK8ulsDN9BiMHZXPGxbqzrlaA0IA==
dependencies:
promise "^8.0.1"
request "^2.81.0"