2020-12-01 03:52:38 +00:00
const _ = require ( 'lodash' ) ;
2020-11-30 21:26:14 +00:00
2020-12-01 03:52:38 +00:00
/ * *
* Supports Home Assistant MQTT Discovery ( https : //www.home-assistant.io/docs/mqtt/discovery/)
*
* Supplies sensor configuration data and initialize sensors in HA .
*
* Topic format : prefix / type / instance / name
* Examples :
* - homeassistant / sensor / VIN / TIRE _PRESSURE / state -- Diagnostic
* - payload : {
* TIRE _PRESSURE _LF : 244.0 ,
* TIRE _PRESSURE _LR : 240.0 ,
* TIRE _PRESSURE _PLACARD _FRONT : 262.0 ,
* TIRE _PRESSURE _PLACARD _REAR : 262.0 ,
* TIRE _PRESSURE _RF : 240.0 ,
* TIRE _PRESSURE _RR : 236.0 ,
* }
* - homeassistant / sensor / VIN / TIRE _PRESSURE _LF / config -- Diagnostic Element
* - payload : {
* device _class : "pressure" ,
* name : "Tire Pressure: Left Front" ,
* state _topic : "homeassistant/sensor/VIN/TIRE_PRESSURE/state" ,
* unit _of _measurement : "kPa" ,
* value _template : "{{ value_json.TIRE_PRESSURE_LF }}" ,
* json _attributes _template : "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_FRONT} | tojson }}"
* }
* - homeassistant / sensor / VIN / TIRE _PRESSURE _RR / config -- Diagnostic Element
* - payload : {
* device _class : "pressure" ,
* name : "Tire Pressure: Right Rear" ,
* state _topic : "homeassistant/sensor/VIN/TIRE_PRESSURE/state" ,
* unit _of _measurement : "kPa" ,
* value _template : "{{ value_json.TIRE_PRESSURE_RR }}" ,
* json _attributes _template : "{{ {'recommendation': value_json.TIRE_PRESSURE_PLACARD_REAR} | tojson }}"
* }
* /
2020-11-30 21:26:14 +00:00
class MQTT {
2022-01-09 01:17:35 +00:00
constructor ( vehicle , prefix = 'homeassistant' , namePrefix ) {
2020-12-01 03:52:38 +00:00
this . prefix = prefix ;
2020-12-28 02:55:49 +00:00
this . vehicle = vehicle ;
this . instance = vehicle . vin ;
2022-01-09 01:17:35 +00:00
this . namePrefix = namePrefix
2020-12-01 03:52:38 +00:00
}
static convertName ( name ) {
return _ . toLower ( _ . replace ( name , / /g , '_' ) ) ;
}
static convertFriendlyName ( name ) {
return _ . startCase ( _ . lowerCase ( name ) ) ;
}
2020-12-02 19:11:50 +00:00
static determineSensorType ( name ) {
switch ( name ) {
case 'EV CHARGE STATE' :
case 'EV PLUG STATE' :
case 'PRIORITY CHARGE INDICATOR' :
case 'PRIORITY CHARGE STATUS' :
return 'binary_sensor' ;
2021-11-16 17:01:29 +00:00
case 'getLocation' :
return 'device_tracker' ;
2020-12-02 19:11:50 +00:00
default :
return 'sensor' ;
}
}
2022-01-09 01:17:35 +00:00
/ * *
* @ param { string } name
* @ returns { string }
* /
addNamePrefix ( name ) {
if ( ! this . namePrefix ) return name
return ` ${ this . namePrefix } ${ name } `
}
2020-12-01 03:52:38 +00:00
/ * *
2021-11-16 17:01:29 +00:00
* @ param { 'sensor' | 'binary_sensor' | 'device_tracker' } type
2020-12-01 03:52:38 +00:00
* @ returns { string }
* /
getBaseTopic ( type = 'sensor' ) {
return ` ${ this . prefix } / ${ type } / ${ this . instance } ` ;
}
2020-12-28 02:21:13 +00:00
getAvailabilityTopic ( ) {
return ` ${ this . prefix } / ${ this . instance } /available ` ;
}
2021-06-10 17:29:46 +00:00
getCommandTopic ( ) {
return ` ${ this . prefix } / ${ this . instance } /command ` ;
}
2020-12-01 03:52:38 +00:00
/ * *
*
2020-12-02 19:11:50 +00:00
* @ param { DiagnosticElement } diag
2020-12-01 03:52:38 +00:00
* /
2020-12-02 19:11:50 +00:00
getConfigTopic ( diag ) {
let sensorType = MQTT . determineSensorType ( diag . name ) ;
return ` ${ this . getBaseTopic ( sensorType ) } / ${ MQTT . convertName ( diag . name ) } /config ` ;
2020-12-01 03:52:38 +00:00
}
/ * *
*
2020-12-02 19:11:50 +00:00
* @ param { Diagnostic } diag
2020-12-01 03:52:38 +00:00
* /
2020-12-02 19:11:50 +00:00
getStateTopic ( diag ) {
let sensorType = MQTT . determineSensorType ( diag . name ) ;
return ` ${ this . getBaseTopic ( sensorType ) } / ${ MQTT . convertName ( diag . name ) } /state ` ;
2020-12-01 03:52:38 +00:00
}
/ * *
*
2020-12-02 19:11:50 +00:00
* @ param { Diagnostic } diag
* @ param { DiagnosticElement } diagEl
2020-12-01 03:52:38 +00:00
* /
2020-12-02 19:11:50 +00:00
getConfigPayload ( diag , diagEl ) {
return this . getConfigMapping ( diag , diagEl ) ;
2020-12-01 03:52:38 +00:00
}
/ * *
* Return the state payload for this diagnostic
2020-12-02 19:11:50 +00:00
* @ param { Diagnostic } diag
2020-12-01 03:52:38 +00:00
* /
2020-12-02 19:11:50 +00:00
getStatePayload ( diag ) {
2020-12-01 03:52:38 +00:00
const state = { } ;
2020-12-02 19:11:50 +00:00
_ . forEach ( diag . diagnosticElements , e => {
2020-12-01 03:52:38 +00:00
// massage the binary_sensor values
let value ;
2020-12-02 19:11:50 +00:00
switch ( e . name ) {
2020-12-01 03:52:38 +00:00
case 'EV PLUG STATE' : // unplugged/plugged
value = e . value === 'plugged' ;
break ;
case 'EV CHARGE STATE' : // not_charging/charging
value = e . value === 'charging' ;
break ;
case 'PRIORITY CHARGE INDICATOR' : // FALSE/TRUE
value = e . value === 'TRUE' ;
break ;
case 'PRIORITY CHARGE STATUS' : // NOT_ACTIVE/ACTIVE
value = e . value === 'ACTIVE' ;
break ;
default :
// coerce to number if possible, API uses strings :eyeroll:
2021-06-11 03:44:31 +00:00
// eslint-disable-next-line no-case-declarations
2020-12-01 03:52:38 +00:00
const num = _ . toNumber ( e . value ) ;
value = _ . isNaN ( num ) ? e . value : num ;
break ;
}
state [ MQTT . convertName ( e . name ) ] = value ;
} ) ;
return state ;
}
2020-12-28 19:35:38 +00:00
mapBaseConfigPayload ( diag , diagEl , device _class , name , attr ) {
2020-12-01 03:52:38 +00:00
name = name || MQTT . convertFriendlyName ( diagEl . name ) ;
2022-01-09 01:17:35 +00:00
name = this . addNamePrefix ( name ) ;
2022-05-20 12:51:51 +00:00
// Generate the unique id from the vin and name
let unique _id = ` ${ this . vehicle . vin } - ${ diagEl . name } `
unique _id = unique _id . replace ( /\s+/g , '-' ) . toLowerCase ( ) ;
2020-12-01 03:52:38 +00:00
return {
device _class ,
name ,
2020-12-28 02:55:49 +00:00
device : {
identifiers : [ this . vehicle . vin ] ,
manufacturer : this . vehicle . make ,
model : this . vehicle . year ,
name : this . vehicle . toString ( )
} ,
2020-12-28 02:21:13 +00:00
availability _topic : this . getAvailabilityTopic ( ) ,
payload _available : 'true' ,
payload _not _available : 'false' ,
2020-12-28 19:35:38 +00:00
state _topic : this . getStateTopic ( diag ) ,
value _template : ` {{ value_json. ${ MQTT . convertName ( diagEl . name ) } }} ` ,
2020-12-28 19:51:34 +00:00
json _attributes _topic : _ . isUndefined ( attr ) ? undefined : this . getStateTopic ( diag ) ,
2022-05-20 10:10:18 +00:00
json _attributes _template : attr ,
2022-05-20 12:51:51 +00:00
unique _id : unique _id
2020-12-28 19:35:38 +00:00
} ;
}
mapSensorConfigPayload ( diag , diagEl , device _class , name , attr ) {
name = name || MQTT . convertFriendlyName ( diagEl . name ) ;
return _ . extend (
this . mapBaseConfigPayload ( diag , diagEl , device _class , name , attr ) ,
{ unit _of _measurement : diagEl . unit } ) ;
2020-12-01 03:52:38 +00:00
}
2020-12-28 19:35:38 +00:00
mapBinarySensorConfigPayload ( diag , diagEl , device _class , name , attr ) {
name = name || MQTT . convertFriendlyName ( diagEl . name ) ;
return _ . extend (
this . mapBaseConfigPayload ( diag , diagEl , device _class , name , attr ) ,
{ payload _on : true , payload _off : false } ) ;
}
2020-12-01 03:52:38 +00:00
/ * *
*
* @ param { Diagnostic } diag
* @ param { DiagnosticElement } diagEl
* /
getConfigMapping ( diag , diagEl ) {
// TODO: this sucks, find a better way to map these diagnostics and their elements for discovery.
switch ( diagEl . name ) {
case 'LIFETIME ENERGY USED' :
case 'LIFETIME EFFICIENCY' :
case 'ELECTRIC ECONOMY' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'energy' ) ;
2020-12-01 03:52:38 +00:00
case 'INTERM VOLT BATT VOLT' :
case 'EV PLUG VOLTAGE' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'voltage' ) ;
2020-12-01 03:52:38 +00:00
case 'HYBRID BATTERY MINIMUM TEMPERATURE' :
case 'AMBIENT AIR TEMPERATURE' :
2021-06-11 14:15:14 +00:00
case 'AMBIENT AIR TEMPERATURE F' :
2022-01-10 17:00:11 +00:00
case 'ENGINE COOLANT TEMP' :
case 'ENGINE COOLANT TEMP F' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'temperature' ) ;
2020-12-01 03:52:38 +00:00
case 'EV BATTERY LEVEL' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'battery' ) ;
2020-12-01 03:52:38 +00:00
case 'TIRE PRESSURE LF' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Left Front' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_FRONT' ) } } | tojson }} ` ) ;
2021-06-11 14:15:14 +00:00
case 'TIRE PRESSURE LF PSI' :
2022-01-10 17:15:35 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Left Front PSI' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_FRONT_PSI' ) } } | tojson }} ` ) ;
2020-12-01 03:52:38 +00:00
case 'TIRE PRESSURE LR' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Left Rear' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_REAR' ) } } | tojson }} ` ) ;
2021-06-11 14:15:14 +00:00
case 'TIRE PRESSURE LR PSI' :
2022-01-10 17:15:35 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Left Rear PSI' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_REAR_PSI' ) } } | tojson }} ` ) ;
2020-12-01 03:52:38 +00:00
case 'TIRE PRESSURE RF' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Right Front' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_FRONT' ) } } | tojson }} ` ) ;
2021-06-11 14:15:14 +00:00
case 'TIRE PRESSURE RF PSI' :
2022-01-10 17:15:35 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Right Front PSI' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_FRONT_PSI' ) } } | tojson }} ` ) ;
2020-12-01 03:52:38 +00:00
case 'TIRE PRESSURE RR' :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Right Rear' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_REAR' ) } } | tojson }} ` ) ;
2021-06-11 14:15:14 +00:00
case 'TIRE PRESSURE RR PSI' :
2022-01-10 17:15:35 +00:00
return this . mapSensorConfigPayload ( diag , diagEl , 'pressure' , 'Tire Pressure: Right Rear PSI' , ` {{ {'recommendation': value_json. ${ MQTT . convertName ( 'TIRE_PRESSURE_PLACARD_REAR_PSI' ) } } | tojson }} ` ) ;
2020-12-01 03:52:38 +00:00
// binary sensor
case 'EV PLUG STATE' : // unplugged/plugged
2020-12-28 19:35:38 +00:00
return this . mapBinarySensorConfigPayload ( diag , diagEl , 'plug' ) ;
2020-12-01 03:52:38 +00:00
case 'EV CHARGE STATE' : // not_charging/charging
2020-12-28 19:35:38 +00:00
return this . mapBinarySensorConfigPayload ( diag , diagEl , 'battery_charging' ) ;
2020-12-02 19:11:50 +00:00
// binary_sensor, but no applicable device_class
2020-12-01 03:52:38 +00:00
case 'PRIORITY CHARGE INDICATOR' : // FALSE/TRUE
case 'PRIORITY CHARGE STATUS' : // NOT_ACTIVE/ACTIVE
2020-12-28 19:35:38 +00:00
return this . mapBinarySensorConfigPayload ( diag , diagEl ) ;
2020-12-01 03:52:38 +00:00
// no device class, camel case name
case 'EV RANGE' :
case 'ODOMETER' :
case 'LAST TRIP TOTAL DISTANCE' :
case 'LAST TRIP ELECTRIC ECON' :
case 'LIFETIME MPGE' :
case 'CHARGER POWER LEVEL' :
default :
2020-12-28 19:35:38 +00:00
return this . mapSensorConfigPayload ( diag , diagEl ) ;
2020-12-01 03:52:38 +00:00
}
}
2020-11-30 21:26:14 +00:00
}
2020-12-01 03:52:38 +00:00
module . exports = MQTT ;