Small fixes and looping.
This commit is contained in:
parent
ed2c38f53f
commit
d095287158
@ -4,3 +4,7 @@ A service that utilizes the [OnStarJS](https://github.com/samrum/OnStarJS) libra
|
|||||||
There is no official relationship with GM, Chevrolet nor OnStar. In fact, it would be nice if they'd even respond to development requests, so we wouldn't have to reverse engineer their API.
|
There is no official relationship with GM, Chevrolet nor OnStar. In fact, it would be nice if they'd even respond to development requests, so we wouldn't have to reverse engineer their API.
|
||||||
### Home Assistant configuration templates
|
### Home Assistant configuration templates
|
||||||
Auto discovery is enabled, for further integrations see [HA-MQTT.md](HA-MQTT.md).
|
Auto discovery is enabled, for further integrations see [HA-MQTT.md](HA-MQTT.md).
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
1. Add travis docker build w/ push to docker hub
|
||||||
|
1. Logging library
|
@ -2,9 +2,6 @@ const _ = require('lodash');
|
|||||||
|
|
||||||
const Measurement = require('./measurement');
|
const Measurement = require('./measurement');
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class Diagnostic {
|
class Diagnostic {
|
||||||
constructor(diagResponse) {
|
constructor(diagResponse) {
|
||||||
this.name = diagResponse.name;
|
this.name = diagResponse.name;
|
||||||
|
72
src/index.js
72
src/index.js
@ -4,6 +4,7 @@ const uuidv4 = require('uuid').v4;
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Vehicle = require('./vehicle');
|
const Vehicle = require('./vehicle');
|
||||||
const {Diagnostic} = require('./diagnostic');
|
const {Diagnostic} = require('./diagnostic');
|
||||||
|
const MQTT = require('./mqtt');
|
||||||
|
|
||||||
const onstarConfig = {
|
const onstarConfig = {
|
||||||
deviceId: process.env.ONSTAR_DEVICEID || uuidv4(),
|
deviceId: process.env.ONSTAR_DEVICEID || uuidv4(),
|
||||||
@ -11,60 +12,77 @@ const onstarConfig = {
|
|||||||
username: process.env.ONSTAR_USERNAME,
|
username: process.env.ONSTAR_USERNAME,
|
||||||
password: process.env.ONSTAR_PASSWORD,
|
password: process.env.ONSTAR_PASSWORD,
|
||||||
onStarPin: process.env.ONSTAR_PIN,
|
onStarPin: process.env.ONSTAR_PIN,
|
||||||
checkRequestStatus: process.env.ONSTAR_SYNC == "true",
|
checkRequestStatus: process.env.ONSTAR_SYNC === "true",
|
||||||
refreshInterval: 30*60*1000 // 30min TODO: configurable
|
refreshInterval: parseInt(process.env.ONSTAR_REFRESH) || (30 * 60 * 1000) // 30 min
|
||||||
};
|
};
|
||||||
|
|
||||||
const mqttConfig = {
|
const mqttConfig = {
|
||||||
host: process.env.MQTT_HOST,
|
host: process.env.MQTT_HOST || 'localhost',
|
||||||
username: process.env.MQTT_USERNAME,
|
username: process.env.MQTT_USERNAME,
|
||||||
password: process.env.MQTT_PASSWORD,
|
password: process.env.MQTT_PASSWORD,
|
||||||
port: process.env.MQTT_PORT,
|
port: parseInt(process.env.MQTT_PORT) || 1883,
|
||||||
tls: process.env.MQTT_TLS,
|
tls: process.env.MQTT_TLS || false,
|
||||||
prefix: process.env.MQTT_PREFIX,
|
prefix: process.env.MQTT_PREFIX || 'homeassistant',
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectionHandler = async client => {
|
let loop;
|
||||||
|
(async () => {
|
||||||
};
|
try {
|
||||||
|
|
||||||
const messageHandler = async client => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const run = async () => {
|
|
||||||
const onStar = OnStar.create(onstarConfig);
|
const onStar = OnStar.create(onstarConfig);
|
||||||
// const client = await mqtt.connectAsync(`${mqttConfig ? 'mqtt' : 'mqtts'}://${mqttConfig.host}:${mqttConfig.port}`, {
|
const client = await mqtt.connectAsync(`${mqttConfig.tls
|
||||||
// username, password
|
? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`,
|
||||||
// } = mqttConfig);
|
{ username: mqttConfig.username, password: mqttConfig.password });
|
||||||
|
|
||||||
// connectionHandler();
|
|
||||||
console.log('Requesting vehicles.');
|
console.log('Requesting vehicles.');
|
||||||
const vehiclesRes = await onStar.getAccountVehicles().catch(err => console.error(err));
|
const vehiclesRes = await onStar.getAccountVehicles();
|
||||||
console.log(_.get(vehiclesRes, 'status'));
|
console.log(_.get(vehiclesRes, 'status'));
|
||||||
const vehicles = _.map(
|
const vehicles = _.map(
|
||||||
_.get(vehiclesRes, 'response.data.vehicles.vehicle'),
|
_.get(vehiclesRes, 'response.data.vehicles.vehicle'),
|
||||||
v => new Vehicle(v)
|
v => new Vehicle(v)
|
||||||
);
|
);
|
||||||
console.log('Vehicles returned:');
|
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) {
|
for (const v of vehicles) {
|
||||||
console.log(v.toString());
|
console.log(v.toString());
|
||||||
|
}
|
||||||
|
const mqttHA = new MQTT('homeassistant', vehicles[0].vin);
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
// Note: the library is set to use only the configured VIN, but using multiple for future proofing.
|
||||||
|
for (const v of vehicles) {
|
||||||
console.log('Requesting diagnostics:')
|
console.log('Requesting diagnostics:')
|
||||||
const statsRes = await onStar.diagnostics({
|
const statsRes = await onStar.diagnostics({
|
||||||
diagnosticItem: v.getSupported()
|
diagnosticItem: v.getSupported()
|
||||||
}).catch(err => console.error(err));
|
});
|
||||||
console.log(_.get(statsRes, 'status'));
|
console.log(_.get(statsRes, 'status'));
|
||||||
const stats = _.map(
|
const stats = _.map(
|
||||||
_.get(statsRes, 'response.data.commandResponse.body.diagnosticResponse'),
|
_.get(statsRes, 'response.data.commandResponse.body.diagnosticResponse'),
|
||||||
d => new Diagnostic(d)
|
d => new Diagnostic(d)
|
||||||
);
|
);
|
||||||
_.forEach(stats, s => console.log(s.toString()));
|
|
||||||
|
for (const s of stats) {
|
||||||
|
if (!s.hasElements()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// configure, then set state
|
||||||
|
for (const d of s.diagnosticElements) {
|
||||||
|
console.log(mqttHA.getConfigTopic(d));
|
||||||
|
console.log(JSON.stringify(mqttHA.getConfigPayload(s, d)));
|
||||||
|
await client.publish(mqttHA.getConfigTopic(d), JSON.stringify(mqttHA.getConfigPayload(s, d)));
|
||||||
|
}
|
||||||
|
console.log(mqttHA.getStateTopic(s));
|
||||||
|
console.log(JSON.stringify(mqttHA.getStatePayload(s)));
|
||||||
|
await client.publish(mqttHA.getStateTopic(s), JSON.stringify(mqttHA.getStatePayload(s)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
run()
|
const main = () => run()
|
||||||
.then(() => console.log('Done, exiting.'))
|
.then(() => console.log('Done, sleeping.'))
|
||||||
.catch(e => console.error(e));
|
.catch(e => console.error(e))
|
||||||
|
|
||||||
|
main();
|
||||||
|
loop = setInterval(main, onstarConfig.refreshInterval);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
@ -13,11 +13,16 @@ class Measurement {
|
|||||||
*/
|
*/
|
||||||
static correctUnitName(unit) {
|
static correctUnitName(unit) {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'Cel': return '°C';
|
case 'Cel':
|
||||||
case 'kwh': return 'kWh';
|
return '°C';
|
||||||
case 'KM': return 'km';
|
case 'kwh':
|
||||||
case 'KPa': return 'kPa';
|
return 'kWh';
|
||||||
case 'kmple': return 'km/l(e)'; // TODO check on this
|
case 'KM':
|
||||||
|
return 'km';
|
||||||
|
case 'KPa':
|
||||||
|
return 'kPa';
|
||||||
|
case 'kmple':
|
||||||
|
return 'km/l(e)'; // TODO check on this
|
||||||
case 'volts':
|
case 'volts':
|
||||||
case 'Volts':
|
case 'Volts':
|
||||||
return 'V';
|
return 'V';
|
||||||
@ -26,7 +31,8 @@ class Measurement {
|
|||||||
case 'N/A':
|
case 'N/A':
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
default: return unit;
|
default:
|
||||||
|
return unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
src/mqtt.js
69
src/mqtt.js
@ -49,6 +49,18 @@ class MQTT {
|
|||||||
return _.startCase(_.lowerCase(name));
|
return _.startCase(_.lowerCase(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static determineSensorType(name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'EV CHARGE STATE':
|
||||||
|
case 'EV PLUG STATE':
|
||||||
|
case 'PRIORITY CHARGE INDICATOR':
|
||||||
|
case 'PRIORITY CHARGE STATUS':
|
||||||
|
return 'binary_sensor';
|
||||||
|
default:
|
||||||
|
return 'sensor';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {'sensor'|'binary_sensor'} type
|
* @param {'sensor'|'binary_sensor'} type
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
@ -59,37 +71,38 @@ class MQTT {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DiagnosticElement} diagnostic
|
* @param {DiagnosticElement} diag
|
||||||
*/
|
*/
|
||||||
getConfigTopic(diagnostic) {
|
getConfigTopic(diag) {
|
||||||
return `${this.getBaseTopic()}/${MQTT.convertName(diagnostic.name)}/config`;
|
let sensorType = MQTT.determineSensorType(diag.name);
|
||||||
|
return `${this.getBaseTopic(sensorType)}/${MQTT.convertName(diag.name)}/config`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
* @param {Diagnostic} diag
|
||||||
|
*/
|
||||||
|
getStateTopic(diag) {
|
||||||
|
let sensorType = MQTT.determineSensorType(diag.name);
|
||||||
|
return `${this.getBaseTopic(sensorType)}/${MQTT.convertName(diag.name)}/state`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Diagnostic} diag
|
||||||
* @param {DiagnosticElement} diagEl
|
* @param {DiagnosticElement} diagEl
|
||||||
* @param {'sensor'|'binary_sensor'} sensorType
|
|
||||||
*/
|
*/
|
||||||
getStateTopic(diagEl, sensorType = 'sensor') {
|
getConfigPayload(diag, diagEl) {
|
||||||
return `${this.getBaseTopic(sensorType)}/${MQTT.convertName(diagEl.name)}/state`;
|
return this.getConfigMapping(diag, diagEl);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Diagnostic} diagnostic
|
|
||||||
* @param {DiagnosticElement} diagnosticElement
|
|
||||||
*/
|
|
||||||
getConfigPayload(diagnostic, diagnosticElement) {
|
|
||||||
return this.getConfigMapping(diagnostic, diagnosticElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the state payload for this diagnostic
|
* Return the state payload for this diagnostic
|
||||||
* @param {Diagnostic} diagnostic
|
* @param {Diagnostic} diag
|
||||||
*/
|
*/
|
||||||
getStatePayload(diagnostic) {
|
getStatePayload(diag) {
|
||||||
const state = {};
|
const state = {};
|
||||||
_.forEach(diagnostic.diagnosticElements, e => {
|
_.forEach(diag.diagnosticElements, e => {
|
||||||
// massage the binary_sensor values
|
// massage the binary_sensor values
|
||||||
let value;
|
let value;
|
||||||
switch (e.name) {
|
switch (e.name) {
|
||||||
@ -116,15 +129,15 @@ class MQTT {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapConfigPayload(diag, diagEl, device_class, name, sensorType = 'sensor', attr) {
|
mapConfigPayload(diag, diagEl, device_class, name, attr) {
|
||||||
name = name || MQTT.convertFriendlyName(diagEl.name);
|
name = name || MQTT.convertFriendlyName(diagEl.name);
|
||||||
// TODO availability
|
// TODO availability
|
||||||
return {
|
return {
|
||||||
device_class,
|
device_class,
|
||||||
name,
|
name,
|
||||||
state_topic: this.getStateTopic(diag, sensorType),
|
state_topic: this.getStateTopic(diag),
|
||||||
unit_of_measurement: diagEl.unit,
|
unit_of_measurement: diagEl.unit,
|
||||||
value_template: `{{ value_json.${MQTT.convertName(diagEl.name)} }`,
|
value_template: `{{ value_json.${MQTT.convertName(diagEl.name)} }}`,
|
||||||
json_attributes_template: attr
|
json_attributes_template: attr
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -150,21 +163,21 @@ class MQTT {
|
|||||||
case 'EV BATTERY LEVEL':
|
case 'EV BATTERY LEVEL':
|
||||||
return this.mapConfigPayload(diag, diagEl, 'battery');
|
return this.mapConfigPayload(diag, diagEl, 'battery');
|
||||||
case 'TIRE PRESSURE LF':
|
case 'TIRE PRESSURE LF':
|
||||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front', 'sensor', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_FRONT} | tojson }}");
|
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_FRONT} | tojson }}");
|
||||||
case 'TIRE PRESSURE LR':
|
case 'TIRE PRESSURE LR':
|
||||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', 'sensor', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_FRONT} | tojson }}");
|
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_FRONT} | tojson }}");
|
||||||
case 'TIRE PRESSURE RF':
|
case 'TIRE PRESSURE RF':
|
||||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', 'sensor', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_REAR} | tojson }}");
|
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_REAR} | tojson }}");
|
||||||
case 'TIRE PRESSURE RR':
|
case 'TIRE PRESSURE RR':
|
||||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', 'sensor', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_REAR} | tojson }}");
|
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_REAR} | tojson }}");
|
||||||
// binary sensor
|
// binary sensor
|
||||||
case 'EV PLUG STATE': // unplugged/plugged
|
case 'EV PLUG STATE': // unplugged/plugged
|
||||||
return this.mapConfigPayload(diag, diagEl, 'plug', undefined, 'binary_sensor');
|
return this.mapConfigPayload(diag, diagEl, 'plug');
|
||||||
case 'EV CHARGE STATE': // not_charging/charging
|
case 'EV CHARGE STATE': // not_charging/charging
|
||||||
return this.mapConfigPayload(diag, diagEl, 'battery_charging', undefined, 'binary_sensor');
|
return this.mapConfigPayload(diag, diagEl, 'battery_charging');
|
||||||
|
// binary_sensor, but no applicable device_class
|
||||||
case 'PRIORITY CHARGE INDICATOR': // FALSE/TRUE
|
case 'PRIORITY CHARGE INDICATOR': // FALSE/TRUE
|
||||||
case 'PRIORITY CHARGE STATUS': // NOT_ACTIVE/ACTIVE
|
case 'PRIORITY CHARGE STATUS': // NOT_ACTIVE/ACTIVE
|
||||||
return this.mapConfigPayload(diag, diagEl, undefined, undefined, 'binary_sensor');
|
|
||||||
// no device class, camel case name
|
// no device class, camel case name
|
||||||
case 'EV RANGE':
|
case 'EV RANGE':
|
||||||
case 'ODOMETER':
|
case 'ODOMETER':
|
||||||
|
@ -35,17 +35,17 @@ describe('MQTT', () => {
|
|||||||
assert.strictEqual(mqtt.getConfigTopic(d), 'homeassistant/sensor/XXX/ambient_air_temperature/config');
|
assert.strictEqual(mqtt.getConfigTopic(d), 'homeassistant/sensor/XXX/ambient_air_temperature/config');
|
||||||
});
|
});
|
||||||
it('should generate state topics', () => {
|
it('should generate state topics', () => {
|
||||||
assert.strictEqual(mqtt.getStateTopic(d), 'homeassistant/sensor/XXX/ambient_air_temperature/state');
|
assert.strictEqual(mqtt.getStateTopic(d, d.diagnosticElements[0]), 'homeassistant/sensor/XXX/ambient_air_temperature/state');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('binary_sensor', () => {
|
describe('binary_sensor', () => {
|
||||||
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[3]')));
|
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[3]')));
|
||||||
it('should generate config topics', () => {
|
it('should generate config topics', () => {
|
||||||
assert.strictEqual(mqtt.getConfigTopic(d), 'homeassistant/sensor/XXX/ev_charge_state/config');
|
assert.strictEqual(mqtt.getConfigTopic(d), 'homeassistant/binary_sensor/XXX/ev_charge_state/config');
|
||||||
});
|
});
|
||||||
it('should generate state topics', () => {
|
it('should generate state topics', () => {
|
||||||
assert.strictEqual(mqtt.getStateTopic(d.diagnosticElements[1]), 'homeassistant/sensor/XXX/priority_charge_indicator/state');
|
assert.strictEqual(mqtt.getStateTopic(d.diagnosticElements[1]), 'homeassistant/binary_sensor/XXX/priority_charge_indicator/state');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -61,7 +61,7 @@ describe('MQTT', () => {
|
|||||||
name: 'Ambient Air Temperature',
|
name: 'Ambient Air Temperature',
|
||||||
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
||||||
unit_of_measurement: '°C',
|
unit_of_measurement: '°C',
|
||||||
value_template: '{{ value_json.ambient_air_temperature }'
|
value_template: '{{ value_json.ambient_air_temperature }}'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should generate state payloads', () => {
|
it('should generate state payloads', () => {
|
||||||
@ -80,7 +80,7 @@ describe('MQTT', () => {
|
|||||||
name: 'Priority Charge Indicator',
|
name: 'Priority Charge Indicator',
|
||||||
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
||||||
unit_of_measurement: undefined,
|
unit_of_measurement: undefined,
|
||||||
value_template: '{{ value_json.priority_charge_indicator }'
|
value_template: '{{ value_json.priority_charge_indicator }}'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should generate state payloads', () => {
|
it('should generate state payloads', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user