-
-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Some may wish to drive their own automations through tools like NodeRED, where it is useful to get more detailed MQTT messages containing the whole entity state. Closes #434
- Loading branch information
Showing
10 changed files
with
519 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# MQTT | ||
|
||
**Integration Key:** `mqtt` | ||
|
||
::: tip | ||
|
||
If you are looking to integrate with [Home Assistant Core](https://www.home-assistant.io) via MQTT take a look at the [Home Assistant integration](./home-assistant.md) instead. | ||
|
||
::: | ||
|
||
The MQTT integration will send messages with room-assistant entity update information to an [MQTT broker](https://mqtt.org). | ||
|
||
## Message Format | ||
|
||
Entity updates are sent into unique topics for each entity, grouped by instance name and entity type. The topic format is `baseTopic/instanceName/entityType/entityId`. | ||
|
||
Each message will have the following properties: | ||
|
||
- **entity** - This includes the whole current entity state, like you would see it in the [API](../guide/api.md). | ||
- **hasAuthority** - This boolean value shows whether this update message comes from an entity that has authority over the entity. It will be `false` for distributed entity updates that are emitted from non-leader instances. You may use this to respect the room-assistant leader in your own automations, but you can of course also just pick a single instance to work off. | ||
|
||
Optionally the message will also include the following properties: | ||
|
||
- **diff** - This is an array of changes to the previous entity state. Each array item includes a `path` to the changed property based off the entity JSON root, the `oldValue` and the `newValue`. Could be used to monitor for specific changes only. This property will not be included when instances emit an entity refresh (e.g. after re-connecting to the MQTT broker). | ||
|
||
::: details Example Message | ||
|
||
This message could have been posted to `room-assistant/entity/living-room/bluetooth-low-energy-presence-sensor/ble-some-id`: | ||
|
||
```json | ||
{ | ||
"entity": { | ||
"attributes": { | ||
"distance": 3.6, | ||
"lastUpdatedAt": "2021-02-28T14:17:33.141Z" | ||
}, | ||
"id": "ble-some-id", | ||
"name": "Something Room Presence", | ||
"distributed": true, | ||
"stateLocked": true, | ||
"distances": { | ||
"bedroom": { | ||
"lastUpdatedAt": "2021-02-28T14:17:32.605Z", | ||
"distance": 9.4, | ||
"outOfRange": false | ||
}, | ||
"living-room": { | ||
"lastUpdatedAt": "2021-02-28T14:17:33.141Z", | ||
"distance": 3.6, | ||
"outOfRange": false | ||
} | ||
}, | ||
"timeout": 180, | ||
"measuredValues": { | ||
"bedroom": { | ||
"rssi": -79.81192947697268, | ||
"measuredPower": -59 | ||
}, | ||
"living-room": { | ||
"rssi": -70.40174705168248, | ||
"measuredPower": -59 | ||
} | ||
}, | ||
"state": "living-room" | ||
}, | ||
"diff": [ | ||
{ | ||
"path": "/measuredValues/living-room", | ||
"oldValue": { | ||
"rssi": -73.37709762753302, | ||
"measuredPower": -59 | ||
}, | ||
"newValue": { | ||
"rssi": -70.40174705168248, | ||
"measuredPower": -59 | ||
} | ||
}, | ||
{ | ||
"path": "/distances/living-room", | ||
"oldValue": { | ||
"lastUpdatedAt": "2021-02-28T14:17:32.308Z", | ||
"distance": 3.8, | ||
"outOfRange": false | ||
}, | ||
"newValue": { | ||
"lastUpdatedAt": "2021-02-28T14:17:33.141Z", | ||
"distance": 3.6, | ||
"outOfRange": false | ||
} | ||
} | ||
], | ||
"hasAuthority": true | ||
} | ||
``` | ||
|
||
::: | ||
|
||
To get started with your automations based on these topics it is recommended to just explore the data provided in the topics using e.g. a GUI MQTT tool. | ||
|
||
## Settings | ||
|
||
| Name | Type | Default | Description | | ||
| ------------- | ----------------------------- | ----------------------- | ------------------------------------------------------------ | | ||
| `mqttUrl` | String | `mqtt://localhost:1883` | Connection string for your MQTT broker. | | ||
| `mqttOptions` | [MQTT Options](#mqtt-options) | | Additional options for the MQTT connection. | | ||
| `baseTopic` | String | `room-assistant/entity` | Base for the entity update topics. | | ||
| `qos` | Number | `0` | Quality of Service level that the messages will be sent with. | | ||
| `retain` | Boolean | `false` | Whether to mark the messages as to retain or not. | | ||
|
||
### MQTT Options | ||
|
||
| Name | Type | Default | Description | | ||
| -------------------- | ------- | ------- | ------------------------------------------------------------ | | ||
| `username` | String | | Username for authentication | | ||
| `password` | String | | Password for authentication | | ||
| `rejectUnauthorized` | Boolean | `true` | Whether MQTTS connections should fail for invalid certificates or not. Set this to `false` if you are using a self-signed certificate and connect via TLS. | | ||
|
||
::: details Example Config | ||
|
||
```yaml | ||
global: | ||
integrations: | ||
- mqtt | ||
mqtt: | ||
mqttUrl: mqtt://localhost:1883 | ||
mqttOptions: | ||
username: youruser | ||
password: yourpass | ||
retain: false | ||
``` | ||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { IClientOptions } from "async-mqtt"; | ||
|
||
export class MqttConfig { | ||
mqttUrl = 'mqtt://localhost:1883'; | ||
mqttOptions: IClientOptions = {}; | ||
baseTopic = 'room-assistant/entity'; | ||
qos: 0 | 1 | 2 = 0; | ||
retain = false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { mocked } from "ts-jest/utils"; | ||
import { MqttService } from "./mqtt.service"; | ||
import { MqttHealthIndicator } from "./mqtt.health"; | ||
import { HealthCheckError } from "@nestjs/terminus"; | ||
|
||
jest.mock('./mqtt.service') | ||
|
||
describe('MqttHealthIndicator', () => { | ||
const serviceMock = mocked(new MqttService(undefined, undefined, undefined)); | ||
const healthIndicator = new MqttHealthIndicator(serviceMock); | ||
|
||
it('should report healthy if connection is established', () => { | ||
serviceMock.isConnected.mockReturnValue(true); | ||
|
||
const result = healthIndicator.connectionCheck(); | ||
expect(result['mqtt_connected'].status).toEqual('up'); | ||
}); | ||
|
||
it('should report unhealthy if connection not established yet', () => { | ||
serviceMock.isConnected.mockReturnValue(undefined); | ||
|
||
expect(() => healthIndicator.connectionCheck()).toThrow(HealthCheckError); | ||
}); | ||
|
||
it('should report unhealthy if connection lost', () => { | ||
serviceMock.isConnected.mockReturnValue(false); | ||
|
||
expect(() => healthIndicator.connectionCheck()).toThrow(HealthCheckError); | ||
}); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from "@nestjs/terminus"; | ||
import { Injectable, Optional } from "@nestjs/common"; | ||
import { MqttService } from "./mqtt.service"; | ||
import { HealthIndicatorService } from "../../status/health-indicator.service"; | ||
|
||
@Injectable() | ||
export class MqttHealthIndicator extends HealthIndicator { | ||
constructor(private readonly mqttService: MqttService, @Optional() healthIndicatorService?: HealthIndicatorService) { | ||
super(); | ||
healthIndicatorService?.registerHealthIndicator(async () => | ||
this.connectionCheck() | ||
); | ||
} | ||
|
||
connectionCheck(): HealthIndicatorResult { | ||
const isHealthy = this.mqttService.isConnected(); | ||
const result = this.getStatus('mqtt_connected', isHealthy); | ||
|
||
if (isHealthy) { | ||
return result; | ||
} | ||
|
||
throw new HealthCheckError('No connection to MQTT broker', result); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { DynamicModule, Module } from "@nestjs/common"; | ||
import { ConfigModule } from "../../config/config.module"; | ||
import { EntitiesModule } from "../../entities/entities.module"; | ||
import { StatusModule } from "../../status/status.module"; | ||
import { MqttService } from "./mqtt.service"; | ||
|
||
@Module({}) | ||
export default class MqttModule { | ||
static forRoot(): DynamicModule { | ||
return { | ||
module: MqttModule, | ||
imports: [ConfigModule, EntitiesModule, StatusModule], | ||
providers: [MqttService] | ||
} | ||
} | ||
} |
Oops, something went wrong.