retain messages, configure only at start
This commit is contained in:
parent
4dd146b6b1
commit
87f2f858a0
10
README.md
10
README.md
@ -1,7 +1,9 @@
|
||||
# onstar2mqtt
|
||||
A service that utilizes the [OnStarJS](https://github.com/samrum/OnStarJS) library to expose OnStar data to MQTT topics. Mostly focused around EVs, however happy to accept PRs for other vehicle types.
|
||||
A service that utilizes the [OnStarJS](https://github.com/samrum/OnStarJS) library to expose OnStar data to MQTT topics.
|
||||
|
||||
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.
|
||||
The functionality is mostly focused around EVs (specifically the Bolt EV), however PRs for other vehicle types are certainly welcome.
|
||||
|
||||
There is no affiliation with this project and 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.
|
||||
|
||||
## Running
|
||||
Collect the following information:
|
||||
@ -49,10 +51,10 @@ MQTT_PASSWORD=
|
||||
```
|
||||
### Node.js
|
||||
It's a typical node.js application, define the same environment values as described in the docker sections and run with:
|
||||
`npm run start`. Currently, only tested with Node.js 12.x.
|
||||
`npm run start`. Currently, this is only tested with Node.js 12.x.
|
||||
|
||||
### Home Assistant configuration templates
|
||||
MQTT auto discovery is enabled. For further integrations see [HA-MQTT.md](HA-MQTT.md).
|
||||
MQTT auto discovery is enabled. For further integrations and screenshots see [HA-MQTT.md](HA-MQTT.md).
|
||||
|
||||
## Development
|
||||
### Running
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "onstar2mqtt",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.4",
|
||||
"description": "OnStarJS wrapper for MQTT",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
@ -16,7 +16,10 @@
|
||||
"onstar",
|
||||
"mqtt",
|
||||
"gm",
|
||||
"chevrolet"
|
||||
"chevrolet",
|
||||
"homeassistant",
|
||||
"home-assistant",
|
||||
"home assistant"
|
||||
],
|
||||
"author": "Michael Woods",
|
||||
"license": "MIT",
|
||||
|
49
src/index.js
49
src/index.js
@ -29,10 +29,6 @@ let loop;
|
||||
(async () => {
|
||||
try {
|
||||
const onStar = OnStar.create(onstarConfig);
|
||||
const client = await mqtt.connectAsync(`${mqttConfig.tls
|
||||
? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`,
|
||||
{ username: mqttConfig.username, password: mqttConfig.password });
|
||||
|
||||
console.log('Requesting vehicles.');
|
||||
const vehiclesRes = await onStar.getAccountVehicles();
|
||||
console.log(_.get(vehiclesRes, 'status'));
|
||||
@ -44,9 +40,21 @@ let loop;
|
||||
for (const v of vehicles) {
|
||||
console.log(v.toString());
|
||||
}
|
||||
const mqttHA = new MQTT('homeassistant', vehicles[0].vin);
|
||||
|
||||
const mqttHA = new MQTT('homeassistant', vehicles[0].vin);
|
||||
const availTopic = mqttHA.getAvailabilityTopic();
|
||||
const client = await mqtt.connectAsync(`${mqttConfig.tls
|
||||
? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`, {
|
||||
username: mqttConfig.username,
|
||||
password: mqttConfig.password,
|
||||
will: {topic: availTopic, payload: 'false', retain: true}
|
||||
});
|
||||
|
||||
await client.publish(availTopic, 'true', {retain: true});
|
||||
|
||||
const configurations = new Map();
|
||||
const run = async () => {
|
||||
const states = new Map();
|
||||
// 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:')
|
||||
@ -63,24 +71,39 @@ let loop;
|
||||
if (!s.hasElements()) {
|
||||
continue;
|
||||
}
|
||||
// configure, then set state
|
||||
// configure once, then set or update states
|
||||
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)));
|
||||
const topic = mqttHA.getConfigTopic(d)
|
||||
const payload = mqttHA.getConfigPayload(s, d);
|
||||
configurations.set(topic, {configured: false, payload});
|
||||
}
|
||||
console.log(mqttHA.getStateTopic(s));
|
||||
console.log(JSON.stringify(mqttHA.getStatePayload(s)));
|
||||
await client.publish(mqttHA.getStateTopic(s), JSON.stringify(mqttHA.getStatePayload(s)));
|
||||
|
||||
const topic = mqttHA.getStateTopic(s);
|
||||
const payload = mqttHA.getStatePayload(s);
|
||||
states.set(topic, payload);
|
||||
}
|
||||
}
|
||||
for (let [topic, config] of configurations) {
|
||||
// configure once
|
||||
if (!config.configured) {
|
||||
config.configured = true;
|
||||
const {payload} = config;
|
||||
console.log(`${topic} ${JSON.stringify(payload)}`);
|
||||
await client.publish(topic, JSON.stringify(payload), {retain: true});
|
||||
}
|
||||
}
|
||||
// update states
|
||||
for (let [topic, state] of states) {
|
||||
console.log(`${topic} ${JSON.stringify(state)}`);
|
||||
await client.publish(topic, JSON.stringify(state), {retain: true});
|
||||
}
|
||||
};
|
||||
|
||||
const main = () => run()
|
||||
.then(() => console.log('Done, sleeping.'))
|
||||
.catch(e => console.error(e))
|
||||
|
||||
main();
|
||||
await main();
|
||||
loop = setInterval(main, onstarConfig.refreshInterval);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
15
src/mqtt.js
15
src/mqtt.js
@ -69,6 +69,10 @@ class MQTT {
|
||||
return `${this.prefix}/${type}/${this.instance}`;
|
||||
}
|
||||
|
||||
getAvailabilityTopic() {
|
||||
return `${this.prefix}/${this.instance}/available`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DiagnosticElement} diag
|
||||
@ -135,6 +139,9 @@ class MQTT {
|
||||
return {
|
||||
device_class,
|
||||
name,
|
||||
availability_topic: this.getAvailabilityTopic(),
|
||||
payload_available: 'true',
|
||||
payload_not_available: 'false',
|
||||
state_topic: this.getStateTopic(diag),
|
||||
unit_of_measurement: diagEl.unit,
|
||||
value_template: `{{ value_json.${MQTT.convertName(diagEl.name)} }}`,
|
||||
@ -163,13 +170,13 @@ 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', `{{ {"recommendation": value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
||||
case 'TIRE PRESSURE LR':
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', `{{ {"recommendation": value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
||||
case 'TIRE PRESSURE RF':
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', `{{ {"recommendation": value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
||||
case 'TIRE PRESSURE RR':
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', `{{ {"recommendation": value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
||||
return this.mapConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
||||
// binary sensor
|
||||
case 'EV PLUG STATE': // unplugged/plugged
|
||||
return this.mapConfigPayload(diag, diagEl, 'plug');
|
||||
|
@ -56,9 +56,12 @@ describe('MQTT', () => {
|
||||
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[0]')));
|
||||
it('should generate config payloads', () => {
|
||||
assert.deepStrictEqual(mqtt.getConfigPayload(d, d.diagnosticElements[0]), {
|
||||
availability_topic: 'homeassistant/XXX/available',
|
||||
device_class: 'temperature',
|
||||
json_attributes_template: undefined,
|
||||
name: 'Ambient Air Temperature',
|
||||
payload_available: 'true',
|
||||
payload_not_available: 'false',
|
||||
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
||||
unit_of_measurement: '°C',
|
||||
value_template: '{{ value_json.ambient_air_temperature }}'
|
||||
@ -75,19 +78,22 @@ describe('MQTT', () => {
|
||||
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[3]')));
|
||||
it('should generate config payloads', () => {
|
||||
assert.deepStrictEqual(mqtt.getConfigPayload(d, d.diagnosticElements[1]), {
|
||||
device_class: undefined,
|
||||
json_attributes_template: undefined,
|
||||
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 }}'
|
||||
availability_topic: 'homeassistant/XXX/available',
|
||||
device_class: undefined,
|
||||
json_attributes_template: undefined,
|
||||
name: 'Priority Charge Indicator',
|
||||
payload_available: 'true',
|
||||
payload_not_available: 'false',
|
||||
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
||||
unit_of_measurement: undefined,
|
||||
value_template: '{{ value_json.priority_charge_indicator }}'
|
||||
});
|
||||
});
|
||||
it('should generate state payloads', () => {
|
||||
assert.deepStrictEqual(mqtt.getStatePayload(d), {
|
||||
ev_charge_state: false,
|
||||
priority_charge_indicator: false,
|
||||
priority_charge_status: false
|
||||
ev_charge_state: false,
|
||||
priority_charge_indicator: false,
|
||||
priority_charge_status: false
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -96,9 +102,12 @@ describe('MQTT', () => {
|
||||
beforeEach(() => d = new Diagnostic(_.get(apiResponse, 'commandResponse.body.diagnosticResponse[8]')));
|
||||
it('should generate payloads with an attribute', () => {
|
||||
assert.deepStrictEqual(mqtt.getConfigPayload(d, d.diagnosticElements[0]), {
|
||||
availability_topic: 'homeassistant/XXX/available',
|
||||
device_class: 'pressure',
|
||||
json_attributes_template: '{{ {"recommendation": value_json.tire_pressure_placard_front} | tojson }}',
|
||||
json_attributes_template: "{{ {'recommendation': value_json.tire_pressure_placard_front} | tojson }}",
|
||||
name: 'Tire Pressure: Left Front',
|
||||
payload_available: 'true',
|
||||
payload_not_available: 'false',
|
||||
state_topic: 'homeassistant/sensor/XXX/tire_pressure/state',
|
||||
unit_of_measurement: 'kPa',
|
||||
value_template: '{{ value_json.tire_pressure_lf }}'
|
||||
|
Loading…
Reference in New Issue
Block a user