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.
|
||||
### Home Assistant configuration templates
|
||||
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');
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Diagnostic {
|
||||
constructor(diagResponse) {
|
||||
this.name = diagResponse.name;
|
||||
@ -49,4 +46,4 @@ class DiagnosticElement {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Diagnostic, DiagnosticElement };
|
||||
module.exports = {Diagnostic, DiagnosticElement};
|
78
src/index.js
78
src/index.js
@ -3,7 +3,8 @@ const mqtt = require('async-mqtt');
|
||||
const uuidv4 = require('uuid').v4;
|
||||
const _ = require('lodash');
|
||||
const Vehicle = require('./vehicle');
|
||||
const { Diagnostic } = require('./diagnostic');
|
||||
const {Diagnostic} = require('./diagnostic');
|
||||
const MQTT = require('./mqtt');
|
||||
|
||||
const onstarConfig = {
|
||||
deviceId: process.env.ONSTAR_DEVICEID || uuidv4(),
|
||||
@ -11,60 +12,77 @@ const onstarConfig = {
|
||||
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
|
||||
checkRequestStatus: process.env.ONSTAR_SYNC === "true",
|
||||
refreshInterval: parseInt(process.env.ONSTAR_REFRESH) || (30 * 60 * 1000) // 30 min
|
||||
};
|
||||
|
||||
const mqttConfig = {
|
||||
host: process.env.MQTT_HOST,
|
||||
host: process.env.MQTT_HOST || 'localhost',
|
||||
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,
|
||||
port: parseInt(process.env.MQTT_PORT) || 1883,
|
||||
tls: process.env.MQTT_TLS || false,
|
||||
prefix: process.env.MQTT_PREFIX || 'homeassistant',
|
||||
};
|
||||
|
||||
const connectionHandler = async client => {
|
||||
|
||||
};
|
||||
|
||||
const messageHandler = async client => {
|
||||
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
let loop;
|
||||
(async () => {
|
||||
try {
|
||||
const onStar = OnStar.create(onstarConfig);
|
||||
// const client = await mqtt.connectAsync(`${mqttConfig ? 'mqtt' : 'mqtts'}://${mqttConfig.host}:${mqttConfig.port}`, {
|
||||
// username, password
|
||||
// } = mqttConfig);
|
||||
const client = await mqtt.connectAsync(`${mqttConfig.tls
|
||||
? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`,
|
||||
{ username: mqttConfig.username, password: mqttConfig.password });
|
||||
|
||||
// connectionHandler();
|
||||
console.log('Requesting vehicles.');
|
||||
const vehiclesRes = await onStar.getAccountVehicles().catch(err => console.error(err));
|
||||
const vehiclesRes = await onStar.getAccountVehicles();
|
||||
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());
|
||||
}
|
||||
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:')
|
||||
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));
|
||||
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)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const main = () => run()
|
||||
.then(() => console.log('Done, sleeping.'))
|
||||
.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) {
|
||||
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 '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':
|
||||
case 'Volts':
|
||||
return 'V';
|
||||
@ -26,7 +31,8 @@ class Measurement {
|
||||
case 'N/A':
|
||||
return undefined;
|
||||
|
||||
default: return unit;
|
||||
default:
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
|
||||
|
71
src/mqtt.js
71
src/mqtt.js
@ -49,6 +49,18 @@ class MQTT {
|
||||
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
|
||||
* @returns {string}
|
||||
@ -59,40 +71,41 @@ class MQTT {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DiagnosticElement} diagnostic
|
||||
* @param {DiagnosticElement} diag
|
||||
*/
|
||||
getConfigTopic(diagnostic) {
|
||||
return `${this.getBaseTopic()}/${MQTT.convertName(diagnostic.name)}/config`;
|
||||
getConfigTopic(diag) {
|
||||
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 {'sensor'|'binary_sensor'} sensorType
|
||||
*/
|
||||
getStateTopic(diagEl, sensorType = 'sensor') {
|
||||
return `${this.getBaseTopic(sensorType)}/${MQTT.convertName(diagEl.name)}/state`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Diagnostic} diagnostic
|
||||
* @param {DiagnosticElement} diagnosticElement
|
||||
*/
|
||||
getConfigPayload(diagnostic, diagnosticElement) {
|
||||
return this.getConfigMapping(diagnostic, diagnosticElement);
|
||||
getConfigPayload(diag, diagEl) {
|
||||
return this.getConfigMapping(diag, diagEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the state payload for this diagnostic
|
||||
* @param {Diagnostic} diagnostic
|
||||
* @param {Diagnostic} diag
|
||||
*/
|
||||
getStatePayload(diagnostic) {
|
||||
getStatePayload(diag) {
|
||||
const state = {};
|
||||
_.forEach(diagnostic.diagnosticElements, e => {
|
||||
_.forEach(diag.diagnosticElements, e => {
|
||||
// massage the binary_sensor values
|
||||
let value;
|
||||
switch(e.name) {
|
||||
switch (e.name) {
|
||||
case 'EV PLUG STATE': // unplugged/plugged
|
||||
value = e.value === 'plugged';
|
||||
break;
|
||||
@ -116,15 +129,15 @@ class MQTT {
|
||||
return state;
|
||||
}
|
||||
|
||||
mapConfigPayload(diag, diagEl, device_class, name, sensorType = 'sensor', attr) {
|
||||
mapConfigPayload(diag, diagEl, device_class, name, attr) {
|
||||
name = name || MQTT.convertFriendlyName(diagEl.name);
|
||||
// TODO availability
|
||||
return {
|
||||
device_class,
|
||||
name,
|
||||
state_topic: this.getStateTopic(diag, sensorType),
|
||||
state_topic: this.getStateTopic(diag),
|
||||
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
|
||||
};
|
||||
}
|
||||
@ -150,21 +163,21 @@ class MQTT {
|
||||
case 'EV BATTERY LEVEL':
|
||||
return this.mapConfigPayload(diag, diagEl, 'battery');
|
||||
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':
|
||||
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':
|
||||
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':
|
||||
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
|
||||
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
|
||||
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 STATUS': // NOT_ACTIVE/ACTIVE
|
||||
return this.mapConfigPayload(diag, diagEl, undefined, undefined, 'binary_sensor');
|
||||
// no device class, camel case name
|
||||
case 'EV RANGE':
|
||||
case 'ODOMETER':
|
||||
|
@ -35,17 +35,17 @@ describe('MQTT', () => {
|
||||
assert.strictEqual(mqtt.getConfigTopic(d), 'homeassistant/sensor/XXX/ambient_air_temperature/config');
|
||||
});
|
||||
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', () => {
|
||||
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[3]')));
|
||||
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', () => {
|
||||
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',
|
||||
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
||||
unit_of_measurement: '°C',
|
||||
value_template: '{{ value_json.ambient_air_temperature }'
|
||||
value_template: '{{ value_json.ambient_air_temperature }}'
|
||||
});
|
||||
});
|
||||
it('should generate state payloads', () => {
|
||||
@ -80,7 +80,7 @@ describe('MQTT', () => {
|
||||
name: 'Priority Charge Indicator',
|
||||
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
||||
unit_of_measurement: undefined,
|
||||
value_template: '{{ value_json.priority_charge_indicator }'
|
||||
value_template: '{{ value_json.priority_charge_indicator }}'
|
||||
});
|
||||
});
|
||||
it('should generate state payloads', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user