Skip to content

Commit

Permalink
Adds a cooldownTime option to Telegram handlers (#87)
Browse files Browse the repository at this point in the history
* 1.1.3

* Initial implementation of cooldownTime

* Revert incorrectly added files

This reverts commit b49d311.

* Initial implementation of cooldownTime

* Revert incorrectly added files

This reverts commit b49d311.

* Fix cooldown logic

* WTF version number

* Doc updates

* 1.1.5
  • Loading branch information
neilenns authored May 28, 2020
1 parent f1d5970 commit ccb9488
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## Version 1.1.5

- Add a cooldownTime option to the Telegram handler. This makes it easier
to have a trigger that fires frequently without spamming Telegram chats with
pictures.

## Version 1.1.4

- Fixed a bug where MQTT log messages contained "Trigger" as the message tag.
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ real-time schema validation (such as Visual Studio Code).
| handlers | Required. A list of handlers that get called when the trigger fires. Currently [webRequest](#defining-webrequest-handlers), [mqtt](#defining-mqtt-handlers), and [Telegram](#defining-telegram-handlers) handlers are supported. |
| enabled | Optional. Default `true`. When set to `false` the trigger will be ignored. | `false` |
| threshold | Optional. A minimum and maximum threshold that must be satisifed for the trigger to fire. See [defining trigger thresholds](#defining-trigger-thresholds) for more information. | |
| cooldownTime | Optional. Default 0. Specifies the length of time in seconds that have to pass between detected images for the trigger to fire again. | `60` |

### Defining trigger thresholds

Expand Down Expand Up @@ -111,9 +112,10 @@ Here is an example of the data sent in the message:
A Telegram handler sends message with the photo that triggered the event. See [Configuring Telegram](#configuring-telegram)
below for details on how to obtain chatIds.

| Property | Description | Example |
| -------- | ---------------------------------------------------------------- | ------------------ |
| chatIds | Required. An array of chatIds to message when the trigger fires. | `[123123, 227352]` |
| Property | Description | Example |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| chatIds | Required. An array of chatIds to message when the trigger fires. | `[123123, 227352]` |
| cooldownTime | Optional. Default 0. Specifies the length of time in seconds that have to pass between detected images for the chat messages to get sent again. This is independent from the cooldownTime specified for the overall trigger, and allows the trigger to fire more often overall than the Telegram messages. | `60` |

## Configuring MQTT

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-deepstackai-trigger",
"version": "1.1.4",
"version": "1.1.5",
"description": "Detects motion using DeepStack AI and calls registered triggers based on trigger rules.",
"main": "dist/src/main.js",
"files": [
Expand Down
6 changes: 4 additions & 2 deletions sampleConfiguration/triggers.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"topic": "aimotion/triggers/cat"
},
"telegram": {
"chatIds": [1, 2, 3]
"chatIds": [1, 2, 3],
"cooldownTime": 60
}
},
"watchObjects": ["cat"]
Expand All @@ -38,7 +39,8 @@
"topic": "aimotion/triggers/dog"
},
"telegram": {
"chatIds": [1, 2, 3]
"chatIds": [1, 2, 3],
"cooldownTime": 60
}
},
"watchObjects": ["dog"]
Expand Down
9 changes: 5 additions & 4 deletions src/Trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default class Trigger {
private _processExisting: boolean;
private _watcher: chokidar.FSWatcher;

public receivedDate: Date;
public name: string;
public watchPattern: string;
public watchObjects?: string[];
Expand Down Expand Up @@ -122,9 +123,9 @@ export default class Trigger {
// Copying files in Windows preserves the lastModified and createdDate fields
// from the original. Using lastAccessTime ensures all these checks perform
// correctly even during development.
const receivedDate = new Date(stats.atimeMs);
this.receivedDate = new Date(stats.atimeMs);

if (receivedDate < this._initalizedTime && !this._processExisting) {
if (this.receivedDate < this._initalizedTime && !this._processExisting) {
log.info(`Trigger ${this.name}`, `${fileName}: Skipping as it was created before the service started.`);
return false;
}
Expand All @@ -133,12 +134,12 @@ export default class Trigger {
if (!this.cooldownTime) return true;

// getTime() returns milliseconds so divide by 1000 to get seconds
const secondsSinceLastTrigger = (receivedDate.getTime() - this._lastTriggerTime.getTime()) / 1000;
const secondsSinceLastTrigger = (this.receivedDate.getTime() - this._lastTriggerTime.getTime()) / 1000;

// Only check cooldown on images that have timestamps after startup.
// This eases testing since this code only gets to this point for images
// that arrived prior to startup when _processExisting is true.
if (secondsSinceLastTrigger < this.cooldownTime && receivedDate > this._initalizedTime) {
if (secondsSinceLastTrigger < this.cooldownTime && this.receivedDate > this._initalizedTime) {
log.info(
`Trigger ${this.name}`,
`${fileName}: Skipping as it was received before the cooldown period of ${this.cooldownTime} seconds expired.`,
Expand Down
1 change: 1 addition & 0 deletions src/handlers/telegramManager/ITelegramConfigJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
*--------------------------------------------------------------------------------------------*/
export default interface ITelegramConfigJson {
chatIds: number[];
cooldownTime: number;
}
1 change: 1 addition & 0 deletions src/handlers/telegramManager/TelegramConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
export default class TelegramConfig {
public chatIds: number[];
public enabled: boolean;
public cooldownTime: number;

constructor(init?: Partial<TelegramConfig>) {
Object.assign(this, init);
Expand Down
38 changes: 38 additions & 0 deletions src/handlers/telegramManager/TelegramManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import ITelegramManagerConfigJson from "./ITelegramManagerConfigJson";

let isEnabled = false;
let telegramBot: TelegramBot;
// Tracks the last time each trigger fired, for use when calculating cooldown time windows
const cooldowns = new Map<Trigger, Date>();

/**
* Takes a path to a configuration file and loads all of the triggers from it.
Expand Down Expand Up @@ -62,6 +64,14 @@ export async function processTrigger(
return [];
}

// Don't send if within the cooldown time
if (!passesCooldownTime(fileName, trigger)) {
return [];
}

// Save the trigger's last fire time
cooldowns.set(trigger, new Date());

// Send all the messages
return Promise.all(trigger.telegramConfig.chatIds.map(chatId => sendTelegramMessage(trigger.name, fileName, chatId)));
}
Expand All @@ -85,6 +95,34 @@ async function sendTelegramMessage(
return message;
}

/**
* Checks to see if a trigger fired within the cooldownw window
* specified for the Telegram handler.
* @param fileName The filename of the image that fired the trigger
* @param trigger The trigger
* @returns true if the trigger happened outside of the cooldown window
*/
function passesCooldownTime(fileName: string, trigger: Trigger): boolean {
const lastTriggerTime = cooldowns.get(trigger);

// If this was never triggered then no cooldown applies.
if (!lastTriggerTime) {
return true;
}

// getTime() returns milliseconds so divide by 1000 to get seconds
const secondsSinceLastTrigger = (trigger.receivedDate.getTime() - lastTriggerTime.getTime()) / 1000;

if (secondsSinceLastTrigger < trigger.telegramConfig.cooldownTime) {
log.info(
`Telegram manager`,
`${fileName}: Skipping sending message as the cooldown period of ${trigger.telegramConfig.cooldownTime} seconds hasn't expired.`,
);
return false;
}

return true;
}
/**
* Loads a trigger configuration file
* @param configFilePath The path to the configuration file
Expand Down
8 changes: 8 additions & 0 deletions src/schemas/telegramHandlerConfiguration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
"minItems": 1,
"uniqueItems": true
},
"cooldownTime": {
"description": "Number of seconds required between sending notifications to the listed chats.",
"type": "number",
"default": 0,
"minimum": 0,
"maximum": 600,
"examples": [5]
},
"enabled": {
"description": "Enables the telegram handler on this trigger. Default is true.",
"type": "boolean",
Expand Down

0 comments on commit ccb9488

Please sign in to comment.