Initial version that queries vehicles and its diagnostics then prints to console.

This commit is contained in:
Michael Woods 2020-11-30 16:26:14 -05:00
commit f39961eae3
13 changed files with 850 additions and 0 deletions

11
Dockerfile Normal file
View 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
View 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.

1
README.md Normal file
View File

@ -0,0 +1 @@

36
package.json Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
class MQTT {
}

34
src/vehicle.js Normal file
View 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
View 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
View 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
View 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
View 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"
}
]
}
}