Compare commits
No commits in common. "main" and "v1.1.2" have entirely different histories.
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@ -4,8 +4,8 @@ updates:
|
|||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "daily"
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "daily"
|
||||||
|
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@ -16,25 +16,25 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.x]
|
node-version: [12.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2.3.4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: michaelwoods/onstar2mqtt
|
images: michaelwoods/onstar2mqtt
|
||||||
flavor: |
|
|
||||||
latest=true
|
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
|
||||||
type=schedule,pattern=weekly
|
type=schedule,pattern=weekly
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v2.1.5
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
@ -43,19 +43,19 @@ jobs:
|
|||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.1.0
|
uses: docker/setup-qemu-action@v1.2.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Push to DockerHub
|
- name: Push to DockerHub
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
71
.github/workflows/codeql-analysis.yml
vendored
71
.github/workflows/codeql-analysis.yml
vendored
@ -1,71 +0,0 @@
|
|||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ main ]
|
|
||||||
schedule:
|
|
||||||
- cron: '28 1 * * 0'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
|
||||||
# Learn more:
|
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@ -9,32 +9,27 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v2.3.4
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: michaelwoods/onstar2mqtt
|
images: michaelwoods/onstar2mqtt
|
||||||
flavor: |
|
|
||||||
latest=true
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.1.0
|
uses: docker/setup-qemu-action@v1.2.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1.9.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Push to DockerHub
|
- name: Push to DockerHub
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@
|
|||||||
.nyc_output/
|
.nyc_output/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
onstar2mqtt.env
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM node:18-alpine
|
FROM node:12
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ["package.json", "/app/"]
|
COPY ["package.json", "/app/"]
|
||||||
COPY ["package-lock.json", "/app/"]
|
COPY ["package-lock.json", "/app/"]
|
||||||
RUN npm ci --omit=dev --no-fund
|
RUN npm install --no-fund
|
||||||
|
|
||||||
COPY ["src", "/app/src"]
|
COPY ["src", "/app/src"]
|
||||||
|
|
||||||
|
92
HA-MQTT.md
92
HA-MQTT.md
@ -1,8 +1,11 @@
|
|||||||
Sample configs for MQTT Home Assistant integration.
|
Sample configs for MQTT Home Assistant integration.
|
||||||
|
|
||||||
### Commands
|
### Lovelace Dashboard
|
||||||
|
Create a new dashboard, or use the cards in your own view. The `mdi:car-electric` icon works well here.
|
||||||
|
|
||||||
#### example script yaml:
|

|
||||||
|
|
||||||
|
script yaml:
|
||||||
```yaml
|
```yaml
|
||||||
alias: Car - Start Vehicle
|
alias: Car - Start Vehicle
|
||||||
sequence:
|
sequence:
|
||||||
@ -14,87 +17,7 @@ mode: single
|
|||||||
icon: 'mdi:car-electric'
|
icon: 'mdi:car-electric'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Triger precondition via calendar
|
dashboard yaml:
|
||||||
````yaml
|
|
||||||
alias: Car Precondition
|
|
||||||
description: Precondition if group.family is home (ie, at least one person).
|
|
||||||
trigger:
|
|
||||||
- platform: state
|
|
||||||
entity_id: calendar.YOUR_CAL_NAME
|
|
||||||
from: 'off'
|
|
||||||
to: 'on'
|
|
||||||
condition:
|
|
||||||
- condition: state
|
|
||||||
entity_id: group.family
|
|
||||||
state: home
|
|
||||||
- condition: state
|
|
||||||
entity_id: calendar.YOUR_CAL_NAME
|
|
||||||
state: Bolt Start
|
|
||||||
attribute: message
|
|
||||||
action:
|
|
||||||
- service: script.car_start_vehicle
|
|
||||||
data: {}
|
|
||||||
mode: single
|
|
||||||
````
|
|
||||||
|
|
||||||
### Location
|
|
||||||
Unfortunately, the MQTT Device tracker uses a home/not_home state and the MQTT Json device tracker does not support
|
|
||||||
the discovery schema so a manual entity configuration is required.
|
|
||||||
|
|
||||||
device tracker yaml:
|
|
||||||
```yaml
|
|
||||||
device_tracker:
|
|
||||||
- platform: mqtt_json
|
|
||||||
devices:
|
|
||||||
your_car_name: homeassistant/device_tracker/YOUR_CAR_VIN/getlocation/state
|
|
||||||
```
|
|
||||||
|
|
||||||
#### script yaml:
|
|
||||||
```yaml
|
|
||||||
alias: Car - Location
|
|
||||||
sequence:
|
|
||||||
- service: mqtt.publish
|
|
||||||
data:
|
|
||||||
topic: homeassistant/YOUR_CAR_VIN/command
|
|
||||||
payload: '{"command": "getLocation"}'
|
|
||||||
mode: single
|
|
||||||
icon: 'mdi:map-marker'
|
|
||||||
```
|
|
||||||
### Automation:
|
|
||||||
Create an automation to update the location whenever the odometer changes, instead of on a time interval.
|
|
||||||
```alias: Update EV Location
|
|
||||||
description: ""
|
|
||||||
trigger:
|
|
||||||
- platform: state
|
|
||||||
entity_id:
|
|
||||||
- sensor.odometer_mi
|
|
||||||
condition: []
|
|
||||||
action:
|
|
||||||
- service: script.locate_bolt_ev
|
|
||||||
data: {}
|
|
||||||
mode: single
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Commands:
|
|
||||||
[OnStarJS Command Docs](https://github.com/samrum/OnStarJS#commands)
|
|
||||||
1. `getAccountVehicles`
|
|
||||||
2. `startVehicle`
|
|
||||||
3. `cancelStartVehicle`
|
|
||||||
4. `alert`
|
|
||||||
5. `cancelAlert`
|
|
||||||
6. `lockDoor`
|
|
||||||
7. `unlockDoor`
|
|
||||||
8. `chargeOverride`
|
|
||||||
9. `cancelChargeOverride`
|
|
||||||
10. `getLocation`
|
|
||||||
|
|
||||||
|
|
||||||
### Lovelace Dashboard
|
|
||||||
Create a new dashboard, or use the cards in your own view. The `mdi:car-electric` icon works well here.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### dashboard yaml:
|
|
||||||
```yaml
|
```yaml
|
||||||
views:
|
views:
|
||||||
- badges: []
|
- badges: []
|
||||||
@ -224,3 +147,6 @@ views:
|
|||||||
columns: 2
|
columns: 2
|
||||||
title: Bolt EV
|
title: Bolt EV
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TODO
|
||||||
|
- Utility meter that resets for monthly LIFETIME ENERGY USED. This seems to only be updated after a full charge, along with other data points.
|
@ -13,7 +13,7 @@ Collect the following information:
|
|||||||
1. MQTT server information: hostname, username, password
|
1. MQTT server information: hostname, username, password
|
||||||
1. If using TLS, define `MQTT_PORT` and `MQTT_TLS=true`
|
1. If using TLS, define `MQTT_PORT` and `MQTT_TLS=true`
|
||||||
|
|
||||||
Supply these values to the ENV vars below. The default data refresh interval is 30 minutes and can be overridden with ONSTAR_REFRESH with values in milliseconds.
|
Supply these values to the ENV vars below.
|
||||||
### [Docker](https://hub.docker.com/r/michaelwoods/onstar2mqtt)
|
### [Docker](https://hub.docker.com/r/michaelwoods/onstar2mqtt)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -51,7 +51,7 @@ MQTT_PASSWORD=
|
|||||||
```
|
```
|
||||||
### Node.js
|
### Node.js
|
||||||
It's a typical node.js application, define the same environment values as described in the docker sections and run with:
|
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, this is only tested with Node.js 18.x.
|
`npm run start`. Currently, this is only tested with Node.js 12.x.
|
||||||
|
|
||||||
### Home Assistant configuration templates
|
### Home Assistant configuration templates
|
||||||
MQTT auto discovery is enabled. For further integrations and screenshots see [HA-MQTT.md](HA-MQTT.md).
|
MQTT auto discovery is enabled. For further integrations and screenshots see [HA-MQTT.md](HA-MQTT.md).
|
||||||
@ -67,3 +67,6 @@ MQTT auto discovery is enabled. For further integrations and screenshots see [HA
|
|||||||
`npm version [major|minor|patch] -m "Version %s" && git push --follow-tags`
|
`npm version [major|minor|patch] -m "Version %s" && git push --follow-tags`
|
||||||
|
|
||||||
Publish the release on GitHub to trigger a release build (ie, update 'latest' docker tag).
|
Publish the release on GitHub to trigger a release build (ie, update 'latest' docker tag).
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
1. Figure out metric->imperial unit handling
|
||||||
|
7748
package-lock.json
generated
7748
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "onstar2mqtt",
|
"name": "onstar2mqtt",
|
||||||
"version": "1.5.4",
|
"version": "1.1.2",
|
||||||
"description": "OnStarJS wrapper for MQTT",
|
"description": "OnStarJS wrapper for MQTT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -29,23 +29,23 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/michaelwoods/onstar2mqtt#readme",
|
"homepage": "https://github.com/michaelwoods/onstar2mqtt#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-mqtt": "^2.6.3",
|
"async-mqtt": "^2.6.1",
|
||||||
"convert-units": "^2.3.4",
|
"convert-units": "^2.3.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"onstarjs": "^2.3.16",
|
"onstarjs": "^2.2.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^8.3.2",
|
||||||
"winston": "^3.8.2"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@babel/eslint-parser": "^7.14.5",
|
||||||
"@babel/eslint-plugin": "^7.19.1",
|
"@babel/eslint-plugin": "^7.14.5",
|
||||||
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.14.5",
|
||||||
"eslint": "^8.35.0",
|
"eslint": "^7.28.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.23.4",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-promise": "^5.1.0",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^9.0.0",
|
||||||
"nyc": "^15.1.0"
|
"nyc": "^15.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,6 @@ class Commands {
|
|||||||
return this.onstar.setChargingProfile();
|
return this.onstar.setChargingProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
return this.onstar.location();
|
|
||||||
}
|
|
||||||
|
|
||||||
async diagnostics({diagnosticItem = [
|
async diagnostics({diagnosticItem = [
|
||||||
Commands.CONSTANTS.DIAGNOSTICS.ODOMETER,
|
Commands.CONSTANTS.DIAGNOSTICS.ODOMETER,
|
||||||
Commands.CONSTANTS.DIAGNOSTICS.TIRE_PRESSURE,
|
Commands.CONSTANTS.DIAGNOSTICS.TIRE_PRESSURE,
|
||||||
|
@ -10,9 +10,6 @@ class Diagnostic {
|
|||||||
d => _.has(d, 'value') && _.has(d, 'unit')
|
d => _.has(d, 'value') && _.has(d, 'unit')
|
||||||
);
|
);
|
||||||
this.diagnosticElements = _.map(validEle, e => new DiagnosticElement(e));
|
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() {
|
hasElements() {
|
||||||
@ -27,29 +24,6 @@ class Diagnostic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DiagnosticElement {
|
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) {
|
constructor(ele) {
|
||||||
this._name = ele.name;
|
this._name = ele.name;
|
||||||
this.measurement = new Measurement(ele.value, ele.unit);
|
this.measurement = new Measurement(ele.value, ele.unit);
|
||||||
@ -67,10 +41,6 @@ class DiagnosticElement {
|
|||||||
return this.measurement.unit;
|
return this.measurement.unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isConvertible() {
|
|
||||||
return this.measurement.isConvertible;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.name}: ${this.measurement.toString()}`;
|
return `${this.name}: ${this.measurement.toString()}`;
|
||||||
}
|
}
|
||||||
|
65
src/index.js
65
src/index.js
@ -5,7 +5,7 @@ const _ = require('lodash');
|
|||||||
const Vehicle = require('./vehicle');
|
const Vehicle = require('./vehicle');
|
||||||
const {Diagnostic} = require('./diagnostic');
|
const {Diagnostic} = require('./diagnostic');
|
||||||
const MQTT = require('./mqtt');
|
const MQTT = require('./mqtt');
|
||||||
const Commands = require('./commands');
|
const {Commands} = require('./commands');
|
||||||
const logger = require('./logger');
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
|
||||||
@ -15,11 +15,9 @@ 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: _.get(process.env, 'ONSTAR_SYNC', 'true') === 'true',
|
checkRequestStatus: process.env.ONSTAR_SYNC === "true" || true,
|
||||||
refreshInterval: parseInt(process.env.ONSTAR_REFRESH) || (30 * 60 * 1000), // 30 min
|
refreshInterval: parseInt(process.env.ONSTAR_REFRESH) || (30 * 60 * 1000), // 30 min
|
||||||
requestPollingIntervalSeconds: parseInt(process.env.ONSTAR_POLL_INTERVAL) || 6, // 6 sec default
|
allowCommands: _.toLower(_.get(process, 'env.ONSTAR_ALLOW_COMMANDS', 'true')) === 'true'
|
||||||
requestPollingTimeoutSeconds: parseInt(process.env.ONSTAR_POLL_TIMEOUT) || 60, // 60 sec default
|
|
||||||
allowCommands: _.get(process.env, 'ONSTAR_ALLOW_COMMANDS', 'true') === 'true'
|
|
||||||
};
|
};
|
||||||
logger.info('OnStar Config', {onstarConfig});
|
logger.info('OnStar Config', {onstarConfig});
|
||||||
|
|
||||||
@ -30,7 +28,6 @@ const mqttConfig = {
|
|||||||
port: parseInt(process.env.MQTT_PORT) || 1883,
|
port: parseInt(process.env.MQTT_PORT) || 1883,
|
||||||
tls: process.env.MQTT_TLS || false,
|
tls: process.env.MQTT_TLS || false,
|
||||||
prefix: process.env.MQTT_PREFIX || 'homeassistant',
|
prefix: process.env.MQTT_PREFIX || 'homeassistant',
|
||||||
namePrefix: process.env.MQTT_NAME_PREFIX || '',
|
|
||||||
};
|
};
|
||||||
logger.info('MQTT Config', {mqttConfig});
|
logger.info('MQTT Config', {mqttConfig});
|
||||||
|
|
||||||
@ -48,15 +45,6 @@ const getVehicles = async commands => {
|
|||||||
return vehicles;
|
return vehicles;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCurrentVehicle = async commands => {
|
|
||||||
const vehicles = await getVehicles(commands);
|
|
||||||
const currentVeh = _.find(vehicles, v => v.vin.toLowerCase() === onstarConfig.vin.toLowerCase());
|
|
||||||
if (!currentVeh) {
|
|
||||||
throw new Error(`Configured vehicle VIN ${onstarConfig.vin} not available in account vehicles`);
|
|
||||||
}
|
|
||||||
return currentVeh;
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectMQTT = async availabilityTopic => {
|
const connectMQTT = async availabilityTopic => {
|
||||||
const url = `${mqttConfig.tls ? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`;
|
const url = `${mqttConfig.tls ? 'mqtts' : 'mqtt'}://${mqttConfig.host}:${mqttConfig.port}`;
|
||||||
const config = {
|
const config = {
|
||||||
@ -77,31 +65,10 @@ const configureMQTT = async (commands, client, mqttHA) => {
|
|||||||
client.on('message', (topic, message) => {
|
client.on('message', (topic, message) => {
|
||||||
logger.debug('Subscription message', {topic, message});
|
logger.debug('Subscription message', {topic, message});
|
||||||
const {command, options} = JSON.parse(message);
|
const {command, options} = JSON.parse(message);
|
||||||
const cmd = commands[command];
|
const commandFn = commands[command].bind(commands);
|
||||||
if (!cmd) {
|
logger.info('Command sent', {command});
|
||||||
logger.error('Command not found', {command});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const commandFn = cmd.bind(commands);
|
|
||||||
logger.info('Command sent', { command });
|
|
||||||
commandFn(options || {})
|
commandFn(options || {})
|
||||||
.then(data => {
|
.then(() => logger.info('Command completed', {command}))
|
||||||
// TODO refactor the response handling for commands
|
|
||||||
logger.info('Command completed', { command });
|
|
||||||
const responseData = _.get(data, 'response.data');
|
|
||||||
if (responseData) {
|
|
||||||
logger.info('Command response data', { responseData });
|
|
||||||
const location = _.get(data, 'response.data.commandResponse.body.location');
|
|
||||||
if (location) {
|
|
||||||
const topic = mqttHA.getStateTopic({ name: command });
|
|
||||||
// TODO create device_tracker entity. MQTT device tracker doesn't support lat/lon and mqtt_json
|
|
||||||
// doesn't have discovery
|
|
||||||
client.publish(topic,
|
|
||||||
JSON.stringify({ latitude: location.lat, longitude: location.long }), { retain: true })
|
|
||||||
.then(() => logger.info('Published location to topic.', { topic }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err=> logger.error('Command error', {command, err}));
|
.catch(err=> logger.error('Command error', {command, err}));
|
||||||
});
|
});
|
||||||
const topic = mqttHA.getCommandTopic();
|
const topic = mqttHA.getCommandTopic();
|
||||||
@ -112,19 +79,17 @@ const configureMQTT = async (commands, client, mqttHA) => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const commands = init();
|
const commands = init();
|
||||||
const vehicle = await getCurrentVehicle(commands);
|
const vehicles = await getVehicles(commands);
|
||||||
|
|
||||||
const mqttHA = new MQTT(vehicle, mqttConfig.prefix, mqttConfig.namePrefix);
|
const mqttHA = new MQTT(vehicles[0], 'homeassistant');
|
||||||
const availTopic = mqttHA.getAvailabilityTopic();
|
const availTopic = mqttHA.getAvailabilityTopic();
|
||||||
const client = await connectMQTT(availTopic);
|
const client = await connectMQTT(availTopic);
|
||||||
client.publish(availTopic, 'true', {retain: true})
|
|
||||||
.then(() => logger.debug('Published availability'));
|
|
||||||
await configureMQTT(commands, client, mqttHA);
|
await configureMQTT(commands, client, mqttHA);
|
||||||
|
|
||||||
const configurations = new Map();
|
const configurations = new Map();
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const states = new Map();
|
const states = new Map();
|
||||||
const v = vehicle;
|
const v = vehicles[0];
|
||||||
logger.info('Requesting diagnostics');
|
logger.info('Requesting diagnostics');
|
||||||
const statsRes = await commands.diagnostics({diagnosticItem: v.getSupported()});
|
const statsRes = await commands.diagnostics({diagnosticItem: v.getSupported()});
|
||||||
logger.info('Diagnostic request status', {status: _.get(statsRes, 'status')});
|
logger.info('Diagnostic request status', {status: _.get(statsRes, 'status')});
|
||||||
@ -174,17 +139,7 @@ const configureMQTT = async (commands, client, mqttHA) => {
|
|||||||
|
|
||||||
const main = async () => run()
|
const main = async () => run()
|
||||||
.then(() => logger.info('Updates complete, sleeping.'))
|
.then(() => logger.info('Updates complete, sleeping.'))
|
||||||
.catch(e => {
|
.catch(e => logger.error('Error', {error: e}))
|
||||||
if (e instanceof Error) {
|
|
||||||
logger.error('Error', {error: _.pick(e, [
|
|
||||||
'message', 'stack',
|
|
||||||
'response.status', 'response.statusText', 'response.headers', 'response.data',
|
|
||||||
'request.method', 'request.body', 'request.contentType', 'request.headers', 'request.url'
|
|
||||||
])});
|
|
||||||
} else {
|
|
||||||
logger.error('Error', {error: e});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await main();
|
await main();
|
||||||
setInterval(main, onstarConfig.refreshInterval);
|
setInterval(main, onstarConfig.refreshInterval);
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
const _ = require('lodash');
|
// const convert = require('convert-units');
|
||||||
const convert = require('convert-units');
|
|
||||||
|
|
||||||
class Measurement {
|
class Measurement {
|
||||||
static CONVERTABLE_UNITS = [
|
|
||||||
'°C',
|
|
||||||
'km',
|
|
||||||
'kPa',
|
|
||||||
'km/l(e)',
|
|
||||||
// Helps with conversion to Gallons.
|
|
||||||
'lit'
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(value, unit) {
|
constructor(value, unit) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.unit = Measurement.correctUnitName(unit);
|
this.unit = Measurement.correctUnitName(unit);
|
||||||
this.isConvertible = _.includes(Measurement.CONVERTABLE_UNITS, this.unit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,12 +22,10 @@ class Measurement {
|
|||||||
case 'KPa':
|
case 'KPa':
|
||||||
return 'kPa';
|
return 'kPa';
|
||||||
case 'kmple':
|
case 'kmple':
|
||||||
return 'km/l(e)';
|
return 'km/l(e)'; // TODO check on this
|
||||||
case 'volts':
|
case 'volts':
|
||||||
case 'Volts':
|
case 'Volts':
|
||||||
return 'V';
|
return 'V';
|
||||||
case 'l':
|
|
||||||
return 'lit';
|
|
||||||
// these are states
|
// these are states
|
||||||
case 'Stat':
|
case 'Stat':
|
||||||
case 'N/A':
|
case 'N/A':
|
||||||
@ -49,59 +36,20 @@ class Measurement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// TODO this may not be required. Check consuming application.
|
||||||
*
|
/*static convertToImperial(value, unit) {
|
||||||
* @param {string|number} value
|
switch(unit) {
|
||||||
* @param {string} unit
|
case 'Cel':
|
||||||
* @returns {string|number}
|
const val = convert(value).from('C').to('F');
|
||||||
*/
|
return new Measurement(val, 'F');
|
||||||
static convertValue(value, unit) {
|
|
||||||
switch (unit) {
|
|
||||||
case '°C':
|
|
||||||
value = _.round(convert(value).from('C').to('F'));
|
|
||||||
break;
|
|
||||||
case 'km':
|
|
||||||
value = _.round(convert(value).from('km').to('mi'), 1);
|
|
||||||
break;
|
|
||||||
case 'kPa':
|
|
||||||
value = _.round(convert(value).from('kPa').to('psi'), 1);
|
|
||||||
break;
|
|
||||||
case 'km/l(e)':
|
|
||||||
// km/L = (1.609344 / 3.785411784) * MPG
|
|
||||||
value = _.round(value / (1.609344 / 3.785411784), 1);
|
|
||||||
break;
|
|
||||||
case 'lit':
|
|
||||||
value = _.round(value / 3.785411784, 1);
|
|
||||||
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)';
|
|
||||||
case 'lit':
|
|
||||||
return 'gal';
|
|
||||||
default:
|
default:
|
||||||
return unit;
|
return new Measurement(value, unit);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.value}${this.unit}`;
|
return `${this.value}${this.unit}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Measurement;
|
module.exports = Measurement;
|
34
src/mqtt.js
34
src/mqtt.js
@ -36,11 +36,10 @@ const _ = require('lodash');
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
class MQTT {
|
class MQTT {
|
||||||
constructor(vehicle, prefix = 'homeassistant', namePrefix) {
|
constructor(vehicle, prefix = 'homeassistant') {
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
this.vehicle = vehicle;
|
this.vehicle = vehicle;
|
||||||
this.instance = vehicle.vin;
|
this.instance = vehicle.vin;
|
||||||
this.namePrefix = namePrefix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static convertName(name) {
|
static convertName(name) {
|
||||||
@ -58,24 +57,13 @@ class MQTT {
|
|||||||
case 'PRIORITY CHARGE INDICATOR':
|
case 'PRIORITY CHARGE INDICATOR':
|
||||||
case 'PRIORITY CHARGE STATUS':
|
case 'PRIORITY CHARGE STATUS':
|
||||||
return 'binary_sensor';
|
return 'binary_sensor';
|
||||||
case 'getLocation':
|
|
||||||
return 'device_tracker';
|
|
||||||
default:
|
default:
|
||||||
return 'sensor';
|
return 'sensor';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {'sensor'|'binary_sensor'} type
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
addNamePrefix(name) {
|
|
||||||
if (!this.namePrefix) return name
|
|
||||||
return `${this.namePrefix} ${name}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {'sensor'|'binary_sensor'|'device_tracker'} type
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getBaseTopic(type = 'sensor') {
|
getBaseTopic(type = 'sensor') {
|
||||||
@ -153,10 +141,6 @@ class MQTT {
|
|||||||
|
|
||||||
mapBaseConfigPayload(diag, diagEl, device_class, name, attr) {
|
mapBaseConfigPayload(diag, diagEl, device_class, name, attr) {
|
||||||
name = name || MQTT.convertFriendlyName(diagEl.name);
|
name = name || MQTT.convertFriendlyName(diagEl.name);
|
||||||
name = this.addNamePrefix(name);
|
|
||||||
// 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();
|
|
||||||
return {
|
return {
|
||||||
device_class,
|
device_class,
|
||||||
name,
|
name,
|
||||||
@ -172,8 +156,7 @@ class MQTT {
|
|||||||
state_topic: this.getStateTopic(diag),
|
state_topic: this.getStateTopic(diag),
|
||||||
value_template: `{{ value_json.${MQTT.convertName(diagEl.name)} }}`,
|
value_template: `{{ value_json.${MQTT.convertName(diagEl.name)} }}`,
|
||||||
json_attributes_topic: _.isUndefined(attr) ? undefined : this.getStateTopic(diag),
|
json_attributes_topic: _.isUndefined(attr) ? undefined : this.getStateTopic(diag),
|
||||||
json_attributes_template: attr,
|
json_attributes_template: attr
|
||||||
unique_id: unique_id
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,28 +191,17 @@ class MQTT {
|
|||||||
return this.mapSensorConfigPayload(diag, diagEl, 'voltage');
|
return this.mapSensorConfigPayload(diag, diagEl, 'voltage');
|
||||||
case 'HYBRID BATTERY MINIMUM TEMPERATURE':
|
case 'HYBRID BATTERY MINIMUM TEMPERATURE':
|
||||||
case 'AMBIENT AIR TEMPERATURE':
|
case 'AMBIENT AIR TEMPERATURE':
|
||||||
case 'AMBIENT AIR TEMPERATURE F':
|
|
||||||
case 'ENGINE COOLANT TEMP':
|
|
||||||
case 'ENGINE COOLANT TEMP F':
|
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'temperature');
|
return this.mapSensorConfigPayload(diag, diagEl, 'temperature');
|
||||||
case 'EV BATTERY LEVEL':
|
case 'EV BATTERY LEVEL':
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'battery');
|
return this.mapSensorConfigPayload(diag, diagEl, 'battery');
|
||||||
case 'TIRE PRESSURE LF':
|
case 'TIRE PRESSURE LF':
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
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_PSI')}} | tojson }}`);
|
|
||||||
case 'TIRE PRESSURE LR':
|
case 'TIRE PRESSURE LR':
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Left Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
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_PSI')}} | tojson }}`);
|
|
||||||
case 'TIRE PRESSURE RF':
|
case 'TIRE PRESSURE RF':
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Front', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_FRONT')}} | tojson }}`);
|
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_PSI')}} | tojson }}`);
|
|
||||||
case 'TIRE PRESSURE RR':
|
case 'TIRE PRESSURE RR':
|
||||||
return this.mapSensorConfigPayload(diag, diagEl, 'pressure', 'Tire Pressure: Right Rear', `{{ {'recommendation': value_json.${MQTT.convertName('TIRE_PRESSURE_PLACARD_REAR')}} | tojson }}`);
|
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_PSI')}} | tojson }}`);
|
|
||||||
// binary sensor
|
// binary sensor
|
||||||
case 'EV PLUG STATE': // unplugged/plugged
|
case 'EV PLUG STATE': // unplugged/plugged
|
||||||
return this.mapBinarySensorConfigPayload(diag, diagEl, 'plug');
|
return this.mapBinarySensorConfigPayload(diag, diagEl, 'plug');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const { Diagnostic, DiagnosticElement } = require('../src/diagnostic');
|
const { Diagnostic } = require('../src/diagnostic');
|
||||||
const apiResponse = require('./diagnostic.sample.json');
|
const apiResponse = require('./diagnostic.sample.json');
|
||||||
|
|
||||||
describe('Diagnostics', () => {
|
describe('Diagnostics', () => {
|
||||||
@ -12,13 +12,13 @@ describe('Diagnostics', () => {
|
|||||||
|
|
||||||
it('should parse a diagnostic response', () => {
|
it('should parse a diagnostic response', () => {
|
||||||
assert.strictEqual(d.name, 'AMBIENT AIR TEMPERATURE');
|
assert.strictEqual(d.name, 'AMBIENT AIR TEMPERATURE');
|
||||||
assert.strictEqual(d.diagnosticElements.length, 2);
|
assert.strictEqual(d.diagnosticElements.length, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should toString() correctly', () => {
|
it('should toString() correctly', () => {
|
||||||
const output = d.toString().trimEnd();
|
const output = d.toString().trimEnd();
|
||||||
const lines = output.split(/\r\n|\r|\n/);
|
const lines = output.split(/\r\n|\r|\n/);
|
||||||
assert.strictEqual(lines.length, 3);
|
assert.strictEqual(lines.length, 2);
|
||||||
assert.strictEqual(lines[0], 'AMBIENT AIR TEMPERATURE:');
|
assert.strictEqual(lines[0], 'AMBIENT AIR TEMPERATURE:');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -28,19 +28,15 @@ describe('Diagnostics', () => {
|
|||||||
it('should parse a diagnostic element', () => {
|
it('should parse a diagnostic element', () => {
|
||||||
assert.strictEqual(d.name, 'TIRE PRESSURE');
|
assert.strictEqual(d.name, 'TIRE PRESSURE');
|
||||||
assert.ok(_.isArray(d.diagnosticElements));
|
assert.ok(_.isArray(d.diagnosticElements));
|
||||||
assert.strictEqual(d.diagnosticElements.length, 12);
|
assert.strictEqual(d.diagnosticElements.length, 6);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should toString() correctly', () => {
|
it('should toString() correctly', () => {
|
||||||
const output = d.toString().trimEnd();
|
const output = d.toString().trimEnd();
|
||||||
const lines = output.split(/\r\n|\r|\n/);
|
const lines = output.split(/\r\n|\r|\n/);
|
||||||
assert.strictEqual(lines.length, 13);
|
assert.strictEqual(lines.length, 7);
|
||||||
assert.strictEqual(lines[0], 'TIRE PRESSURE:');
|
assert.strictEqual(lines[0], 'TIRE PRESSURE:');
|
||||||
assert.strictEqual(lines[1], ' TIRE PRESSURE LF: 240.0kPa');
|
assert.strictEqual(lines[1], ' TIRE PRESSURE LF: 240.0kPa');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should strip non-alpha chars', () => {
|
|
||||||
assert.strictEqual(DiagnosticElement.convertName('TEMP', '°F'), 'TEMP F');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,16 +28,6 @@ describe('MQTT', () => {
|
|||||||
assert.strictEqual(MQTT.convertFriendlyName('FOO BAR'), 'Foo Bar');
|
assert.strictEqual(MQTT.convertFriendlyName('FOO BAR'), 'Foo Bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should determine sensor types', () => {
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('EV CHARGE STATE'), 'binary_sensor');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('EV PLUG STATE'), 'binary_sensor');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('PRIORITY CHARGE INDICATOR'), 'binary_sensor');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('PRIORITY CHARGE STATUS'), 'binary_sensor');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('getLocation'), 'device_tracker');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType('foo'), 'sensor');
|
|
||||||
assert.strictEqual(MQTT.determineSensorType(''), 'sensor');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('topics', () => {
|
describe('topics', () => {
|
||||||
let d;
|
let d;
|
||||||
|
|
||||||
@ -92,37 +82,14 @@ describe('MQTT', () => {
|
|||||||
payload_available: 'true',
|
payload_available: 'true',
|
||||||
payload_not_available: 'false',
|
payload_not_available: 'false',
|
||||||
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
state_topic: 'homeassistant/sensor/XXX/ambient_air_temperature/state',
|
||||||
unique_id: 'xxx-ambient-air-temperature',
|
|
||||||
json_attributes_topic: undefined,
|
json_attributes_topic: undefined,
|
||||||
unit_of_measurement: '°C',
|
unit_of_measurement: '°C',
|
||||||
value_template: '{{ value_json.ambient_air_temperature }}'
|
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',
|
|
||||||
unique_id: 'xxx-ambient-air-temperature-f',
|
|
||||||
json_attributes_topic: undefined,
|
|
||||||
unit_of_measurement: '°F',
|
|
||||||
value_template: '{{ value_json.ambient_air_temperature_f }}'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('should generate state payloads', () => {
|
it('should generate state payloads', () => {
|
||||||
assert.deepStrictEqual(mqtt.getStatePayload(d), {
|
assert.deepStrictEqual(mqtt.getStatePayload(d), {
|
||||||
ambient_air_temperature: 15,
|
ambient_air_temperature: 15
|
||||||
ambient_air_temperature_f: 59
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -148,7 +115,6 @@ describe('MQTT', () => {
|
|||||||
payload_off: false,
|
payload_off: false,
|
||||||
payload_on: true,
|
payload_on: true,
|
||||||
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
state_topic: 'homeassistant/binary_sensor/XXX/ev_charge_state/state',
|
||||||
unique_id: 'xxx-priority-charge-indicator',
|
|
||||||
json_attributes_topic: undefined,
|
json_attributes_topic: undefined,
|
||||||
value_template: '{{ value_json.priority_charge_indicator }}'
|
value_template: '{{ value_json.priority_charge_indicator }}'
|
||||||
});
|
});
|
||||||
@ -181,7 +147,6 @@ describe('MQTT', () => {
|
|||||||
payload_available: 'true',
|
payload_available: 'true',
|
||||||
payload_not_available: 'false',
|
payload_not_available: 'false',
|
||||||
state_topic: 'homeassistant/sensor/XXX/tire_pressure/state',
|
state_topic: 'homeassistant/sensor/XXX/tire_pressure/state',
|
||||||
unique_id: 'xxx-tire-pressure-lf',
|
|
||||||
json_attributes_topic: 'homeassistant/sensor/XXX/tire_pressure/state',
|
json_attributes_topic: 'homeassistant/sensor/XXX/tire_pressure/state',
|
||||||
unit_of_measurement: 'kPa',
|
unit_of_measurement: 'kPa',
|
||||||
value_template: '{{ value_json.tire_pressure_lf }}'
|
value_template: '{{ value_json.tire_pressure_lf }}'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user