Initial version that queries vehicles and its diagnostics then prints to console.
This commit is contained in:
commit
f39961eae3
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM node:12
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ["package.json", "/app/"]
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY ["src", "/app/src"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["npm", "run", "start"]
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2020 Michael Woods
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "onstar2mqtt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "OnStarJS wrapper for MQTT",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/index.js",
|
||||||
|
"test": "mocha"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/michaelwoods/onstar2mqtt.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"onstar",
|
||||||
|
"mqtt",
|
||||||
|
"gm",
|
||||||
|
"chevrolet"
|
||||||
|
],
|
||||||
|
"author": "Michael Woods",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/michaelwoods/onstar2mqtt/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/michaelwoods/onstar2mqtt#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"async-mqtt": "^2.6.1",
|
||||||
|
"convert-units": "^2.3.4",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"onstarjs": "^2.0.10",
|
||||||
|
"uuid": "^8.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "^8.2.1"
|
||||||
|
}
|
||||||
|
}
|
40
src/diagnostic.js
Normal file
40
src/diagnostic.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const Measurement = require('./measurement');
|
||||||
|
|
||||||
|
class Diagnostic {
|
||||||
|
constructor(diagResponse) {
|
||||||
|
this.name = diagResponse.name;
|
||||||
|
const validEle = _.filter(
|
||||||
|
diagResponse.diagnosticElement,
|
||||||
|
d => _.has(d, 'value') && _.has(d, 'unit')
|
||||||
|
);
|
||||||
|
this.diagnosticElements = _.map(validEle, e => new DiagnosticElement(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
elementsToString(diag) {
|
||||||
|
const validEle = _.filter(diag, d => _.has(d, 'value') && _.has(d, 'unit'));
|
||||||
|
let output = '';
|
||||||
|
_.forEach(validEle, e => output += ` ${e.name} ${e.value}${e.unit}\n`);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
let elements = '';
|
||||||
|
_.forEach(this.diagnosticElements, e => elements += ` ${e.toString()}\n`)
|
||||||
|
return `${this.name}:\n` + elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiagnosticElement {
|
||||||
|
constructor(ele) {
|
||||||
|
this.name = ele.name;
|
||||||
|
this.measurement = new Measurement(ele.value, ele.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.name}: ${this.measurement.toString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { Diagnostic, DiagnosticElement };
|
70
src/index.js
Normal file
70
src/index.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
const OnStar = require('onstarjs');
|
||||||
|
const mqtt = require('async-mqtt');
|
||||||
|
const uuidv4 = require('uuid').v4;
|
||||||
|
const _ = require('lodash');
|
||||||
|
const Vehicle = require('./vehicle');
|
||||||
|
const { Diagnostic } = require('./diagnostic');
|
||||||
|
|
||||||
|
const onstarConfig = {
|
||||||
|
deviceId: process.env.ONSTAR_DEVICEID || uuidv4(),
|
||||||
|
vin: process.env.ONSTAR_VIN,
|
||||||
|
username: process.env.ONSTAR_USERNAME,
|
||||||
|
password: process.env.ONSTAR_PASSWORD,
|
||||||
|
onStarPin: process.env.ONSTAR_PIN,
|
||||||
|
checkRequestStatus: process.env.ONSTAR_SYNC == "true",
|
||||||
|
refreshInterval: 30*60*1000 // 30min TODO: configurable
|
||||||
|
};
|
||||||
|
|
||||||
|
const mqttConfig = {
|
||||||
|
host: process.env.MQTT_HOST,
|
||||||
|
username: process.env.MQTT_USERNAME,
|
||||||
|
password: process.env.MQTT_PASSWORD,
|
||||||
|
port: process.env.MQTT_PORT,
|
||||||
|
tls: process.env.MQTT_TLS,
|
||||||
|
prefix: process.env.MQTT_PREFIX,
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectionHandler = async client => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageHandler = async client => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const onStar = OnStar.create(onstarConfig);
|
||||||
|
// const client = await mqtt.connectAsync(`${mqttConfig ? 'mqtt' : 'mqtts'}://${mqttConfig.host}:${mqttConfig.port}`, {
|
||||||
|
// username, password
|
||||||
|
// } = mqttConfig);
|
||||||
|
|
||||||
|
// connectionHandler();
|
||||||
|
console.log('Requesting vehicles.');
|
||||||
|
const vehiclesRes = await onStar.getAccountVehicles().catch(err => console.error(err));
|
||||||
|
console.log(_.get(vehiclesRes, 'status'));
|
||||||
|
const vehicles = _.map(
|
||||||
|
_.get(vehiclesRes, 'response.data.vehicles.vehicle'),
|
||||||
|
v => new Vehicle(v)
|
||||||
|
);
|
||||||
|
console.log('Vehicles returned:');
|
||||||
|
|
||||||
|
// Note: the library is set to use only the configured VIN, but using multiple for future proofing.
|
||||||
|
for (const v of vehicles) {
|
||||||
|
console.log(v.toString());
|
||||||
|
|
||||||
|
console.log('Requesting diagnostics:')
|
||||||
|
const statsRes = await onStar.diagnostics({
|
||||||
|
diagnosticItem: v.getSupported()
|
||||||
|
}).catch(err => console.error(err));
|
||||||
|
console.log(_.get(statsRes, 'status'));
|
||||||
|
const stats = _.map(
|
||||||
|
_.get(statsRes, 'response.data.commandResponse.body.diagnosticResponse'),
|
||||||
|
d => new Diagnostic(d)
|
||||||
|
);
|
||||||
|
_.forEach(stats, s => console.log(s.toString()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run()
|
||||||
|
.then(() => console.log('Done, exiting.'))
|
||||||
|
.catch(e => console.error(e));
|
40
src/measurement.js
Normal file
40
src/measurement.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const convert = require('convert-units');
|
||||||
|
|
||||||
|
class Measurement {
|
||||||
|
constructor(value, unit) {
|
||||||
|
this.value = value;
|
||||||
|
this.unit = Measurement.correctUnitName(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static correctUnitName(unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'Cel': return 'C';
|
||||||
|
case 'kwh': return 'kWh';
|
||||||
|
case 'KM': return 'km';
|
||||||
|
case 'KPa': return 'kPa';
|
||||||
|
case 'kmple': return 'km/l e'; // TODO check on this
|
||||||
|
case 'volts': return 'V';
|
||||||
|
case 'Stat':
|
||||||
|
case 'N/A':
|
||||||
|
return '';
|
||||||
|
default: return unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this may not be required. Check consuming application.
|
||||||
|
static convertToImperial(value, unit) {
|
||||||
|
switch(unit) {
|
||||||
|
case 'Cel':
|
||||||
|
const val = convert(value).from('C').to('F');
|
||||||
|
return new Measurement(val, 'F');
|
||||||
|
default:
|
||||||
|
return new Measurement(value, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.value}${this.unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Measurement;
|
4
src/mqtt.js
Normal file
4
src/mqtt.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
class MQTT {
|
||||||
|
|
||||||
|
}
|
34
src/vehicle.js
Normal file
34
src/vehicle.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
class Vehicle {
|
||||||
|
constructor(vehicle) {
|
||||||
|
this.make = vehicle.make;
|
||||||
|
this.model = vehicle.model;
|
||||||
|
this.vin = vehicle.vin;
|
||||||
|
this.year = vehicle.year;
|
||||||
|
|
||||||
|
const diagCmd = _.find(
|
||||||
|
_.get(vehicle, 'commands.command'),
|
||||||
|
cmd => cmd.name === 'diagnostics'
|
||||||
|
);
|
||||||
|
this.supportedDiagnostics = _.get(diagCmd,
|
||||||
|
'commandData.supportedDiagnostics.supportedDiagnostic');
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported(diag) {
|
||||||
|
return _.includes(this.supportedDiagnostics, diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSupported(diags = []) {
|
||||||
|
if (diags.length === 0) {
|
||||||
|
return this.supportedDiagnostics;
|
||||||
|
}
|
||||||
|
return _.intersection(this.supportedDiagnostics, diags);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.year} ${this.make} ${this.model} ${this.vin}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Vehicle;
|
275
test/diagnostic.sample.json
Normal file
275
test/diagnostic.sample.json
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
{
|
||||||
|
"commandResponse": {
|
||||||
|
"requestTime": "2020-11-30T12:00:00.000Z",
|
||||||
|
"completionTime": "2020-11-30T00:00:00.000Z",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/requests/fizzbuzzREQ",
|
||||||
|
"status": "success",
|
||||||
|
"type": "diagnostics",
|
||||||
|
"body": {
|
||||||
|
"diagnosticResponse": [
|
||||||
|
{
|
||||||
|
"name": "AMBIENT AIR TEMPERATURE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "AMBIENT AIR TEMPERATURE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "15",
|
||||||
|
"unit": "Cel"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CHARGER POWER LEVEL",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "CHARGER POWER LEVEL",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "NO_REDUCTION",
|
||||||
|
"unit": "N/A"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ENERGY EFFICIENCY",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "CO2 AVOIDED",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ELECTRIC ECONOMY",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "21.85",
|
||||||
|
"unit": "kwh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FUEL AVOIDED",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GAS MILES",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LIFETIME EFFICIENCY",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "21.85",
|
||||||
|
"unit": "kwh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LIFETIME EV ODO",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LIFETIME FUEL ECON",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LIFETIME MPGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "40.73",
|
||||||
|
"unit": "kmple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ODOMETER",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "6013.8",
|
||||||
|
"unit": "KM"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV CHARGE STATE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "EV CHARGE STATE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "charging_complete",
|
||||||
|
"value": "charging_complete",
|
||||||
|
"unit": "N/A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIORITY CHARGE INDICATOR",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "FALSE",
|
||||||
|
"unit": "N/A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIORITY CHARGE STATUS",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "NOT_ACTIVE",
|
||||||
|
"unit": "N/A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PRIORITY_CHARGE_STATUS",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV PLUG STATE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "EV PLUG STATE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "plugged",
|
||||||
|
"value": "plugged",
|
||||||
|
"unit": "Stat"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV SCHEDULED CHARGE START",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "EV SCHEDULED CHARGE START 120V DAY",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "Monday"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV SCHEDULED CHARGE START 240V DAY",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "Monday"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SCHED CHG START 120V",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "11:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SCHED CHG START 240V",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "11:30"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GET CHARGE MODE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "CHARGE MODE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "DEPARTURE_BASED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RATE TYPE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "PEAK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ODOMETER",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "ODOMETER",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "6013.8",
|
||||||
|
"unit": "KM"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE LF",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "YELLOW",
|
||||||
|
"value": "240.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE LR",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "YELLOW",
|
||||||
|
"value": "236.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE PLACARD FRONT",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "262.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE PLACARD REAR",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "262.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE RF",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "YELLOW",
|
||||||
|
"value": "236.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TIRE PRESSURE RR",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "YELLOW",
|
||||||
|
"value": "228.0",
|
||||||
|
"unit": "KPa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VEHICLE RANGE",
|
||||||
|
"diagnosticElement": [
|
||||||
|
{
|
||||||
|
"name": "EV MAX RANGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV MIN RANGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "EV RANGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na",
|
||||||
|
"value": "341.0",
|
||||||
|
"unit": "KM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GAS RANGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TOTAL RANGE",
|
||||||
|
"status": "NA",
|
||||||
|
"message": "na"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
test/diagnostic.spec.js
Normal file
42
test/diagnostic.spec.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const { Diagnostic } = require('../src/diagnostic');
|
||||||
|
const apiResponse = require('./diagnostic.sample.json');
|
||||||
|
|
||||||
|
describe('Diagnostics', () => {
|
||||||
|
let d;
|
||||||
|
|
||||||
|
describe('Diagnostic', () => {
|
||||||
|
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[0]')));
|
||||||
|
|
||||||
|
it('should parse a diagnostic response', () => {
|
||||||
|
assert.strictEqual(d.name, 'AMBIENT AIR TEMPERATURE');
|
||||||
|
assert.strictEqual(d.diagnosticElements.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toString() correctly', () => {
|
||||||
|
const output = d.toString().trimEnd();
|
||||||
|
const lines = output.split(/\r\n|\r|\n/);
|
||||||
|
assert.strictEqual(lines.length, 2);
|
||||||
|
assert.strictEqual(lines[0], 'AMBIENT AIR TEMPERATURE:');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DiagnosticElement', () => {
|
||||||
|
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[8]')));
|
||||||
|
it('should parse a diagnostic element', () => {
|
||||||
|
assert.strictEqual(d.name, 'TIRE PRESSURE');
|
||||||
|
assert.ok(_.isArray(d.diagnosticElements));
|
||||||
|
assert.strictEqual(d.diagnosticElements.length, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toString() correctly', () => {
|
||||||
|
const output = d.toString().trimEnd();
|
||||||
|
const lines = output.split(/\r\n|\r|\n/);
|
||||||
|
assert.strictEqual(lines.length, 7);
|
||||||
|
assert.strictEqual(lines[0], 'TIRE PRESSURE:');
|
||||||
|
assert.strictEqual(lines[1], ' TIRE PRESSURE LF: 240.0kPa');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
41
test/vehicle.spec.js
Normal file
41
test/vehicle.spec.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const Vehicle = require('../src/vehicle');
|
||||||
|
const apiResponse = require('./vehicles.sample.json');
|
||||||
|
|
||||||
|
describe('Vehicle', () => {
|
||||||
|
let v;
|
||||||
|
beforeEach(() => v = new Vehicle(_.get(apiResponse, 'vehicles.vehicle[0]')));
|
||||||
|
|
||||||
|
it('should parse a vehicle response', () => {
|
||||||
|
assert.notStrictEqual(v.year, 2020);
|
||||||
|
assert.strictEqual(v.make, 'Chevrolet');
|
||||||
|
assert.strictEqual(v.model, 'Bolt EV');
|
||||||
|
assert.strictEqual(v.vin, 'foobarVIN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the list of supported diagnostics', () => {
|
||||||
|
const supported = v.getSupported();
|
||||||
|
assert.ok(_.isArray(supported));
|
||||||
|
assert.strictEqual(supported.length, 22);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return common supported and requested diagnostics', () => {
|
||||||
|
let supported = v.getSupported(['ODOMETER']);
|
||||||
|
assert.ok(_.isArray(supported));
|
||||||
|
assert.strictEqual(supported.length, 1);
|
||||||
|
|
||||||
|
supported = v.getSupported(['ODOMETER', 'foo', 'bar']);
|
||||||
|
assert.ok(_.isArray(supported));
|
||||||
|
assert.strictEqual(supported.length, 1);
|
||||||
|
|
||||||
|
supported = v.getSupported(['foo', 'bar']);
|
||||||
|
assert.ok(_.isArray(supported));
|
||||||
|
assert.strictEqual(supported.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toString() correctly', () => {
|
||||||
|
assert.strictEqual(v.toString(), '2020 Chevrolet Bolt EV foobarVIN')
|
||||||
|
});
|
||||||
|
});
|
237
test/vehicles.sample.json
Normal file
237
test/vehicles.sample.json
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
{
|
||||||
|
"vehicles": {
|
||||||
|
"size": "1",
|
||||||
|
"vehicle": [
|
||||||
|
{
|
||||||
|
"vin": "foobarVIN",
|
||||||
|
"make": "Chevrolet",
|
||||||
|
"model": "Bolt EV",
|
||||||
|
"year": "2020",
|
||||||
|
"manufacturer": "General Motors",
|
||||||
|
"bodyStyle": "CAR",
|
||||||
|
"phone": "+5558675309",
|
||||||
|
"unitType": "EMBEDDED",
|
||||||
|
"onstarStatus": "ACTIVE",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN",
|
||||||
|
"isInPreActivation": "false",
|
||||||
|
"commands": {
|
||||||
|
"command": [
|
||||||
|
{
|
||||||
|
"name": "cancelAlert",
|
||||||
|
"description": "Cancel a vehicle alert (honk horns/flash lights).",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/cancelAlert",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getHotspotInfo",
|
||||||
|
"description": "Retrives the WiFi Hotspot info",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/hotspot/commands/getInfo",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lockDoor",
|
||||||
|
"description": "Locks the doors.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/lockDoor",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unlockDoor",
|
||||||
|
"description": "Unlocks the doors.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/unlockDoor",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "alert",
|
||||||
|
"description": "Triggers a vehicle alert (honk horns/flash lights).",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/alert",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "start",
|
||||||
|
"description": "Remotely starts the vehicle.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/start",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cancelStart",
|
||||||
|
"description": "Cancels previous remote start command.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/cancelStart",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "diagnostics",
|
||||||
|
"description": "Retrieves diagnostic vehicle data.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/diagnostics",
|
||||||
|
"isPrivSessionRequired": "false",
|
||||||
|
"commandData": {
|
||||||
|
"supportedDiagnostics": {
|
||||||
|
"supportedDiagnostic": [
|
||||||
|
"LAST TRIP FUEL ECONOMY",
|
||||||
|
"ENERGY EFFICIENCY",
|
||||||
|
"HYBRID BATTERY MINIMUM TEMPERATURE",
|
||||||
|
"EV ESTIMATED CHARGE END",
|
||||||
|
"LIFETIME ENERGY USED",
|
||||||
|
"EV BATTERY LEVEL",
|
||||||
|
"EV PLUG VOLTAGE",
|
||||||
|
"HOTSPOT CONFIG",
|
||||||
|
"ODOMETER",
|
||||||
|
"HOTSPOT STATUS",
|
||||||
|
"CHARGER POWER LEVEL",
|
||||||
|
"LIFETIME EV ODOMETER",
|
||||||
|
"EV PLUG STATE",
|
||||||
|
"EV CHARGE STATE",
|
||||||
|
"TIRE PRESSURE",
|
||||||
|
"AMBIENT AIR TEMPERATURE",
|
||||||
|
"LAST TRIP DISTANCE",
|
||||||
|
"INTERM VOLT BATT VOLT",
|
||||||
|
"GET COMMUTE SCHEDULE",
|
||||||
|
"GET CHARGE MODE",
|
||||||
|
"EV SCHEDULED CHARGE START",
|
||||||
|
"VEHICLE RANGE"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "location",
|
||||||
|
"description": "Retrieves the vehicle's current location.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/location",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chargeOverride",
|
||||||
|
"description": "Sends Charge Override",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/chargeOverride",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getChargingProfile",
|
||||||
|
"description": "Gets the Charge Mode",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getChargingProfile",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getCommuteSchedule",
|
||||||
|
"description": "Gets the commuting schedule",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getCommuteSchedule",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "connect",
|
||||||
|
"description": "Initiates a connection to the vehicle",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/connect",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setChargingProfile",
|
||||||
|
"description": "Sets the charging profile",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/setChargingProfile",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setCommuteSchedule",
|
||||||
|
"description": "Sets the commuting schedule",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/setCommuteSchedule",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stopFastCharge",
|
||||||
|
"description": "Stops the charge",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/stopFastCharge",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "createTripPlan",
|
||||||
|
"description": "Create Trip Plan",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/createTripPlan",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getTripPlan",
|
||||||
|
"description": "Provides the ability to retrieve an existing trip plan for an electric vehicle",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getTripPlan",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getHotspotStatus",
|
||||||
|
"description": "Retrive WiFi status",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/hotspot/commands/getStatus",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setHotspotInfo",
|
||||||
|
"description": "update the WiFi SSID and passPhrase",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/hotspot/commands/setInfo",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "disableHotspot",
|
||||||
|
"description": "Disable WiFi Hotspot",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/hotspot/commands/disable",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "enableHotspot",
|
||||||
|
"description": "Enable WiFi Hotspot",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/hotspot/commands/enable",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getRateSchedule",
|
||||||
|
"description": "Get EV Rate Schedule",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getRateSchedule",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setRateSchedule",
|
||||||
|
"description": "Set EV Rate Schedule.",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/setRateSchedule",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getChargerPowerLevel",
|
||||||
|
"description": " Get the charger level",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getChargerPowerLevel",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setChargerPowerLevel",
|
||||||
|
"description": " Set the charger level",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/setChargerPowerLevel",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setPriorityCharging",
|
||||||
|
"description": "Set priority charging",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/setPriorityCharging",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getPriorityCharging",
|
||||||
|
"description": "Get priority charging",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/getPriorityCharging",
|
||||||
|
"isPrivSessionRequired": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stopCharge",
|
||||||
|
"description": "Sets the Stop Charge",
|
||||||
|
"url": "https://api.gm.com/api/v1/account/vehicles/foobarVIN/commands/stopCharge",
|
||||||
|
"isPrivSessionRequired": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"module": [
|
||||||
|
{
|
||||||
|
"moduleType": "BYOM2",
|
||||||
|
"moduleCapability": "SF3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"propulsionType": "BEV",
|
||||||
|
"isSharedVehicle": "false",
|
||||||
|
"ownerAccount": "999999999"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user