Skip to content

Add suppport for MQTT message matching via RegExp #1371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,14 @@ Other users have been sharing configurations that work for them on our GitHub si
### Camera MQTT Parameters

- `motionTopic`: The MQTT topic to watch for motion alerts.
- `motionMessage`: The message to watch for to trigger motion alerts. Will use the name of the camera if blank.
- `motionMessage`: The message to watch for to trigger motion alerts. Will use the name of the camera if both `motionMessage` and `motionMessageRegExp` are blank.
- `motionMessageRegExp`: A RegExp string to match messages on the motion mqtt topic and trigger motion alerts. Ex: '".*"' will match any message.
- `motionResetTopic`: The MQTT topic to watch for motion resets.
- `motionResetMessage`: The message to watch for to trigger motion resets. Will use the name of the camera if blank.
- `motionResetMessage`: The message to watch for to trigger motion resets. Will use the name of the camera if both `motionResetMessage` and `motionResetMessageRegExp` are blank.
- `motionResetMessageRegExp`: A RegExp string to match messages on the motionReset mqtt topic and trigger motion alerts. Ex: '"/.*/"' will match any message.
- `doorbellTopic`: The MQTT topic to watch for doorbell alerts.
- `doorbellMessage`: The message to watch for to trigger doorbell alerts. Will use the name of the camera if blank.
- `doorbellMessage`: The message to watch for to trigger doorbell alerts. Will use the name of the camera if both `doorbellMessage` and `doorbellMessageRegExp` are blank.
- `doorbellMessageRegExp`: A RegExp string to match messages on the doorbell mqtt topic and trigger motion alerts. Ex: '".*"' will match any message.

#### Camera MQTT Example

Expand All @@ -165,6 +168,30 @@ Other users have been sharing configurations that work for them on our GitHub si
}
```

#### Camera MQTT Example with RegExp Message Matching

```json
{
"platform": "Camera-ffmpeg",
"cameras": [
{
"name": "Camera Name",
"videoConfig": {
"source": "-i rtsp://myfancy_rtsp_stream"
},
"mqtt": {
"motionTopic": "home/camera",
"motionMessageRegExp": "ON", // will match any message that contains "ON"
"motionResetTopic": "home/camera",
"motionResetMessageRegExp": "OFF", // will match any message that contains "OFF"
"doorbellTopic": "home/doobell",
"doorbellMessageRegExp": ".*" // will match all messages
}
}
]
}
```

### Automation Parameters

- `mqtt`: Defines the hostname or IP of the MQTT broker to connect to for MQTT-based automation. If not set, MQTT support is not started. See the project site for [more information on using MQTT](https://sunoo.github.io/homebridge-camera-ffmpeg/automation/mqtt.html).
Expand Down
3 changes: 3 additions & 0 deletions src/configTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ export type VideoConfig = {
export type MqttCameraConfig = {
motionTopic?: string;
motionMessage?: string;
motionMessageRegExp?: string;
motionResetTopic?: string;
motionResetMessage?: string;
motionResetMessageRegExp?: string;
doorbellTopic?: string;
doorbellMessage?: string;
doorbellMessageRegExp?: string;
};
80 changes: 62 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
PlatformAccessoryEvent,
PlatformConfig
} from 'homebridge';
import http from 'http';
import mqtt from 'mqtt';
import { AutomationReturn } from './autoTypes';
import { CameraConfig, FfmpegPlatformConfig } from './configTypes';

import { AutomationReturn } from './autoTypes';
import { Logger } from './logger';
import { StreamingDelegate } from './streamingDelegate';
import http from 'http';
import mqtt from 'mqtt';

const version = require('../package.json').version; // eslint-disable-line @typescript-eslint/no-var-requires

let hap: HAP;
Expand All @@ -40,7 +42,10 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
private readonly accessories: Array<PlatformAccessory> = [];
private readonly motionTimers: Map<string, NodeJS.Timeout> = new Map();
private readonly doorbellTimers: Map<string, NodeJS.Timeout> = new Map();
private readonly mqttActions: Map<string, Map<string, Array<MqttAction>>> = new Map();
private readonly mqttActions: Map<string, {
messageMap: Map<string, Array<MqttAction>> | undefined,
reMap: Map<RegExp, Array<MqttAction>> | undefined,
}> = new Map();

constructor(log: Logging, config: PlatformConfig, api: API) {
this.log = new Logger(log);
Expand Down Expand Up @@ -92,12 +97,22 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
api.on(APIEvent.DID_FINISH_LAUNCHING, this.didFinishLaunching.bind(this));
}

addMqttAction(topic: string, message: string, details: MqttAction): void {
const messageMap = this.mqttActions.get(topic) || new Map();
const actionArray = messageMap.get(message) || [];
actionArray.push(details);
messageMap.set(message, actionArray);
this.mqttActions.set(topic, messageMap);
addMqttAction(topic: string, message: string | RegExp, details: MqttAction): void {
let { messageMap, reMap } = this.mqttActions.get(topic) || { messageMap: undefined, reMap: undefined };
let actionArray

if (typeof message === 'string') {
actionArray = messageMap?.get(message) || []
messageMap = messageMap || new Map()
actionArray.push(details);
messageMap.set(message, actionArray);
} else { // RegExp
actionArray = reMap?.get(message) || []
reMap = reMap || new Map()
actionArray.push(details);
reMap.set(message, actionArray)
}
this.mqttActions.set(topic, {messageMap, reMap});
}

setupAccessory(accessory: PlatformAccessory, cameraConfig: CameraConfig): void {
Expand Down Expand Up @@ -171,16 +186,32 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
if (this.config.mqtt) {
if (cameraConfig.mqtt) {
if (cameraConfig.mqtt.motionTopic) {
this.addMqttAction(cameraConfig.mqtt.motionTopic, cameraConfig.mqtt.motionMessage || cameraConfig.name!,
{accessory: accessory, active: true, doorbell: false});
if (cameraConfig.mqtt.motionMessageRegExp) {
this.addMqttAction(cameraConfig.mqtt.motionTopic, new RegExp(cameraConfig.mqtt.motionMessageRegExp),
{accessory: accessory, active: true, doorbell: false});
} else {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be backwards compatible to default to camera name

this.addMqttAction(cameraConfig.mqtt.motionTopic, cameraConfig.mqtt.motionMessage || cameraConfig.name!,
{accessory: accessory, active: true, doorbell: false});
}
}
if (cameraConfig.mqtt.motionResetTopic) {
this.addMqttAction(cameraConfig.mqtt.motionResetTopic, cameraConfig.mqtt.motionResetMessage || cameraConfig.name!,
{accessory: accessory, active: false, doorbell: false});
if (cameraConfig.mqtt.motionResetMessageRegExp) {
this.addMqttAction(cameraConfig.mqtt.motionResetTopic, new RegExp(cameraConfig.mqtt.motionResetMessageRegExp),
{accessory: accessory, active: false, doorbell: false});
} else {
this.addMqttAction(cameraConfig.mqtt.motionResetTopic, cameraConfig.mqtt.motionResetMessage || cameraConfig.name!,
{accessory: accessory, active: false, doorbell: false});
}

}
if (cameraConfig.mqtt.doorbellTopic) {
this.addMqttAction(cameraConfig.mqtt.doorbellTopic, cameraConfig.mqtt.doorbellMessage || cameraConfig.name!,
{accessory: accessory, active: true, doorbell: true});
if (cameraConfig.mqtt.doorbellMessageRegExp) {
this.addMqttAction(cameraConfig.mqtt.doorbellTopic, new RegExp(cameraConfig.mqtt.doorbellMessageRegExp),
{accessory: accessory, active: true, doorbell: true});
} else {
this.addMqttAction(cameraConfig.mqtt.doorbellTopic, cameraConfig.mqtt.doorbellMessage || cameraConfig.name!,
{accessory: accessory, active: true, doorbell: true});
}
}
}
}
Expand Down Expand Up @@ -367,7 +398,7 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
}
});
client.on('message', (topic: string, message: Buffer) => {
const messageMap = this.mqttActions.get(topic);
const { messageMap, reMap } = this.mqttActions.get(topic) || { messageMap: undefined, reMap: undefined };
if (messageMap) {
const actionArray = messageMap.get(message.toString());
if (actionArray) {
Expand All @@ -380,6 +411,19 @@ class FfmpegPlatform implements DynamicPlatformPlugin {
}
}
}
if (reMap) {
for (const [re, actionArray] of reMap) {
if (re.test(message.toString())) {
for (const action of actionArray) {
if (action.doorbell) {
this.doorbellHandler(action.accessory, action.active);
} else {
this.motionHandler(action.accessory, action.active);
}
}
}
}
}
});
}
if (this.config.porthttp) {
Expand Down Expand Up @@ -421,4 +465,4 @@ export = (api: API): void => {
Accessory = api.platformAccessory;

api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, FfmpegPlatform);
};
};