From bd331a477c1abe5812f6ed6c811fe1c762244f80 Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Fri, 11 Jun 2021 10:15:14 -0400 Subject: [PATCH] Support conversions to imperial. --- src/diagnostic.js | 30 +++++++++++++++++++ src/index.js | 2 +- src/measurement.js | 65 ++++++++++++++++++++++++++++++++++------- src/mqtt.js | 9 ++++++ test/diagnostic.spec.js | 14 +++++---- test/mqtt.spec.js | 23 ++++++++++++++- 6 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/diagnostic.js b/src/diagnostic.js index 9a8bbd3..ff309d6 100644 --- a/src/diagnostic.js +++ b/src/diagnostic.js @@ -10,6 +10,9 @@ class Diagnostic { d => _.has(d, 'value') && _.has(d, 'unit') ); this.diagnosticElements = _.map(validEle, e => new DiagnosticElement(e)); + const converted = _.map(_.filter(this.diagnosticElements, e => e.isConvertible), + e => DiagnosticElement.convert(e)); + this.diagnosticElements.push(... converted); } hasElements() { @@ -24,6 +27,29 @@ class Diagnostic { } class DiagnosticElement { + /** + * + * @param {DiagnosticElement} element + */ + static convert(element) { + const {name, unit, value} = element; + const convertedUnit = Measurement.convertUnit(unit); + return new DiagnosticElement({ + name: DiagnosticElement.convertName(name, convertedUnit), + unit: convertedUnit, + value: Measurement.convertValue(value, unit) + }) + } + + static convertName(name, unit) { + return `${name} ${_.replace(_.toUpper(unit), /\W/g, '')}`; + } + + /** + * @param {string} ele.name + * @param {string|number} ele.value + * @param {string} ele.unit + */ constructor(ele) { this._name = ele.name; this.measurement = new Measurement(ele.value, ele.unit); @@ -41,6 +67,10 @@ class DiagnosticElement { return this.measurement.unit; } + get isConvertible() { + return this.measurement.isConvertible; + } + toString() { return `${this.name}: ${this.measurement.toString()}`; } diff --git a/src/index.js b/src/index.js index e2cf401..26120a7 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const _ = require('lodash'); const Vehicle = require('./vehicle'); const {Diagnostic} = require('./diagnostic'); const MQTT = require('./mqtt'); -const {Commands} = require('./commands'); +const Commands = require('./commands'); const logger = require('./logger'); diff --git a/src/measurement.js b/src/measurement.js index 5aecc05..08e3358 100644 --- a/src/measurement.js +++ b/src/measurement.js @@ -1,9 +1,18 @@ -// const convert = require('convert-units'); +const _ = require('lodash'); +const convert = require('convert-units'); class Measurement { + static CONVERTABLE_UNITS = [ + '°C', + 'km', + 'kPa', + 'km/l(e)' + ]; + constructor(value, unit) { this.value = value; this.unit = Measurement.correctUnitName(unit); + this.isConvertible = _.includes(Measurement.CONVERTABLE_UNITS, this.unit); } /** @@ -22,7 +31,7 @@ class Measurement { case 'KPa': return 'kPa'; case 'kmple': - return 'km/l(e)'; // TODO check on this + return 'km/l(e)'; case 'volts': case 'Volts': return 'V'; @@ -36,16 +45,50 @@ class Measurement { } } - // 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); + /** + * + * @param {string|number} value + * @param {string} unit + * @returns {string|number} + */ + static convertValue(value, unit) { + switch (unit) { + case '°C': + value = convert(value).from('C').to('F'); + break; + case 'km': + value = convert(value).from('km').to('mi'); + break; + case 'kPa': + value = convert(value).from('kPa').to('psi'); + break; + case 'km/l(e)': + // km/L = (1.609344 / 3.785411784) * MPG + value = value / (1.609344 / 3.785411784); + break; } - }*/ + return value; + } + + /** + * + * @param {string} unit + * @returns {string} + */ + static convertUnit(unit) { + switch (unit) { + case '°C': + return '°F'; + case 'km': + return 'mi'; + case 'kPa': + return 'psi'; + case 'km/l(e)': + return 'mpg(e)'; + default: + return unit; + } + } toString() { return `${this.value}${this.unit}`; diff --git a/src/mqtt.js b/src/mqtt.js index 2d34064..5c86be4 100644 --- a/src/mqtt.js +++ b/src/mqtt.js @@ -191,17 +191,26 @@ class MQTT { return this.mapSensorConfigPayload(diag, diagEl, 'voltage'); case 'HYBRID BATTERY MINIMUM TEMPERATURE': case 'AMBIENT AIR TEMPERATURE': + case 'AMBIENT AIR TEMPERATURE F': return this.mapSensorConfigPayload(diag, diagEl, 'temperature'); case 'EV BATTERY LEVEL': return this.mapSensorConfigPayload(diag, diagEl, 'battery'); case 'TIRE PRESSURE LF': return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`); + case 'TIRE PRESSURE LF PSI': + return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front PSI', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`); case 'TIRE PRESSURE LR': return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`); + case 'TIRE PRESSURE LR PSI': + return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear PSI', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`); case 'TIRE PRESSURE RF': return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`); + case 'TIRE PRESSURE RF PSI': + return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front PSI', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`); case 'TIRE PRESSURE RR': return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`); + case 'TIRE PRESSURE RR PSI': + return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear PSI', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`); // binary sensor case 'EV PLUG STATE': // unplugged/plugged return this.mapBinarySensorConfigPayload(diag, diagEl, 'plug'); diff --git a/test/diagnostic.spec.js b/test/diagnostic.spec.js index edcabff..6f312e1 100644 --- a/test/diagnostic.spec.js +++ b/test/diagnostic.spec.js @@ -1,7 +1,7 @@ const assert = require('assert'); const _ = require('lodash'); -const { Diagnostic } = require('../src/diagnostic'); +const { Diagnostic, DiagnosticElement } = require('../src/diagnostic'); const apiResponse = require('./diagnostic.sample.json'); describe('Diagnostics', () => { @@ -12,13 +12,13 @@ describe('Diagnostics', () => { it('should parse a diagnostic response', () => { assert.strictEqual(d.name, 'AMBIENT AIR TEMPERATURE'); - assert.strictEqual(d.diagnosticElements.length, 1); + assert.strictEqual(d.diagnosticElements.length, 2); }); 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.length, 3); assert.strictEqual(lines[0], 'AMBIENT AIR TEMPERATURE:'); }); }); @@ -28,15 +28,19 @@ describe('Diagnostics', () => { it('should parse a diagnostic element', () => { assert.strictEqual(d.name, 'TIRE PRESSURE'); assert.ok(_.isArray(d.diagnosticElements)); - assert.strictEqual(d.diagnosticElements.length, 6); + assert.strictEqual(d.diagnosticElements.length, 12); }); 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.length, 13); assert.strictEqual(lines[0], 'TIRE PRESSURE:'); assert.strictEqual(lines[1], ' TIRE PRESSURE LF: 240.0kPa'); }); + + it('should strip non-alpha chars', () => { + assert.strictEqual(DiagnosticElement.convertName('TEMP', '°F'), 'TEMP F'); + }); }); }); diff --git a/test/mqtt.spec.js b/test/mqtt.spec.js index c61258d..78ae3a6 100644 --- a/test/mqtt.spec.js +++ b/test/mqtt.spec.js @@ -86,10 +86,31 @@ describe('MQTT', () => { unit_of_measurement: '°C', value_template: '{{ value_json.ambient_air_temperature }}' }); + assert.deepStrictEqual(mqtt.getConfigPayload(d, d.diagnosticElements[1]), { + availability_topic: 'homeassistant/XXX/available', + device: { + identifiers: [ + 'XXX' + ], + manufacturer: 'foo', + model: 2020, + name: '2020 foo bar' + }, + device_class: 'temperature', + json_attributes_template: undefined, + name: 'Ambient Air Temperature F', + payload_available: 'true', + payload_not_available: 'false', + state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state', + json_attributes_topic: undefined, + unit_of_measurement: '°F', + value_template: '{{ value_json.ambient_air_temperature_f }}' + }); }); it('should generate state payloads', () => { assert.deepStrictEqual(mqtt.getStatePayload(d), { - ambient_air_temperature: 15 + ambient_air_temperature: 15, + ambient_air_temperature_f: 59 }); }); });