npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mangar2/automation

v2.0.0

Published

Provides rule based automation

Downloads

3

Readme

Automation

This is one of the core modules of yaha, the automation. Use it to automate your processes. The automation is "event-driven". There are many included features to make it simple to define rules - still it is not always easy to understand why and what for. Please - this time - read the manual from the beginning

Content

Installation

Do not install it directly, instead install it with npm @mangar2/runservices

Configuration

Use the runservices service to use the automation module and write the configuraiton in the configuration files used by runservices. The configuration must be provided in JSON with the follow Schema

{
    "title": "Motion configuration",
    "type": "object",
    "properties": {
        "motionTopics": {
            "description": "Subscription list to receive all relevant events/movements",
            "type": "array",
            "items": { "type": "string" }
        },
        "subscribeQoS": { "enum": [0, 1, 2] },
        "presenceTopic": {
            "description": "Topic holding the presence state",
            "type": "string"
        },
        "rules": {
            "description": "Filename(s) to a rule file (type string: single, type array:0..n)",
            "type": ["string", "array"],
            "items": { "type": "string" }
        },
        "intervalInSeconds": {
            "description": "Iinterval between checks for events based on rules. Additionally all rules will be checked on every receive Message",
            "type": "integer"

        },
        "longitude": { "type": "number" },
        "latitude": { "type": "number" },
        "additionalProperties": false
    },
    "required": ["motionTopics", "longitude", "latitude"]
}

The default configuration is:

const defaultConfiguration = {
    motionTopics: ['+/+/+/motion sensor/detection state', '$SYS/presence/set'],
    presenceTopic: '$SYS/presence',
    rules: ['rules.json'],
    intervalInSeconds: 60,
    subscribeQoS: 1
}
  • motionTopis: An array of topics containing events (usually motion events)
  • presenceTopic: the topic you use to signal the presence state
  • rules: the list of rule files
  • intervalInSeconds: the amount of seconds between checking the rules for new actions. Note: rules are checked additionally on any received message
  • subscribeQoS: Qualiy of service used to subscribe for any topic used in the automation rules

What you have to provide

The only element you have to provide additionally is longitude, latitude: the geo-coordinates of your location to calculate sunset/sunrise in decimal degrees (DD). As example the London Tower Bridge has the coorinates 51.505153, -0.075664

I recommend to provide additionally a list of rule files - use more than one to structure your rules if you will have a larger set of them. You may keep everything else as default. A configuration example is:

{
    "production": {
        "automation": {
            "longitude": -0.075664,
            "latitude": 51.505554,
            "rules": [
                "C:\\yourpath\\motionrules.json",
                "C:\\yourpath\\automationrules.json",
                "C:\\yourpath\\alertrules.json"
            ]
        }
    }
}

Rule processing

The automation is based on "rules" set in a JSON file. There is no easy user-interface to define them (maybe it will come later).

Rule tree

You are free to put the rules in a "tree" on any level in JSON-Objects. The parser will scan for the property name "rules" and will take everything that follows as rule. The "rules" property must be the last organizational level in the rule tree. Examples:

{
    "rules": {
        "myRuleName1": {},
        "myRuleName2": {}
    }
}

or

{
    "ground": {
        "rules": {
            "myRuleName": {}
        }
    },
    "first": {
        "myRuleName": {}
    }
}

or

{
    "ground": {
        "livingroom": {
            "multimedia": {
                "rules": {
                    "myRuleName": {}
                }
            }
        }
    }
}

rule format

A rule is a JSON object with the following members, mandatory is only "topic".

{
    "rules": {
        "myRuleName": {
            "time": "",
            "duration": "",
            "check": "",
            "value": "",
            "topic": "",
            "cooldownInSeconds": 60,
            "delayInSeconds": 0,
            "qos": "",
            "allOf": "",
            "anyOf": "",
            "noneOf": "",
            "allow": "",
            "durationWithoutMovementInMinutes": 5
        }
    }
}

The simplest possible move is (both following rules are doing the same, it is an alternative way to write it):

{
    "rules": {
        "simpleRule1": {
            "value": "on",
            "topic": "ground/livingroom/light/set"
        },
        "simpleRule2": {
            "topic": {"ground/livingroom/light/set": "on"}
        }
    }
}

What does this rule do? It will send a message with topic "ground/livingroom/light" and value on once and never again. We need to add something to make it sensful.

topic

The topic is mandatory. It is a unique id for the device or the action that is triggered by the rule. The topic will be send to the central message broker and then forewarded to the devices which subscribed to the topic.

For example the topic ground/livingroom/light could be send to a device switching the light in the livingroom on or off. The job for the automation is just sending a message. Acting must be done by the actors. This makes yaha highly flexible. We can add any actor we like without having to change the automation module

There are three ways to format topics. One rule can trigger many messages with different topics and values:

{
    "rules": {
        "switchLivingroomLightOn": {
            "value": "on",
            "topic": "ground/livingroom/light/set"
        },
        "switchLivingroomLightAndFloorLightOn": {
            "value": "on",
            "topic": ["ground/livingroom/light/set", "ground/floor/light/set"]
        },
        "switchLivingroomLightOnAndFloorLightOff": {
            "topic": { "ground/livingroom/light/set": "on", "ground/floor/light/set": "off" }
        }
    }
}

value

The value is the state of the device to set. I recommend to use "set" topics to change a state and the same topic without set to signal a state. You can provide any value you like - it must be interpreted by the actors. (Note the topic with and without "set" is only a recommendation. You are completely free to define your own topics or use the topics your actor requires). Using the "set" principle, the automation will usually use "set" topics while the devices will send "status" topics without set to inform about successful state changes.

{
    "rules": {
        "switchLivingroomLightOn": {
            "topic": "ground/livingroom/light/set",
            "value": "on"
        },
        "signalThatLivingroomLightIsOn": {
            "topic": { "ground/livingroom/light": "on"}
        }
    }
}

Why have "value", if you can just place the value as property value in the topic? Sometimes it is more clear to bring it separately. For example for many topics with the same value or if the value is calculated by a complex term. But value is not needed, it is always possible to place any value in the topic property

time

The time property defines the start time of the rule

Time requires a "time of day" string formatted as HH:MM:SS or HH:MM. Example 9:00 or 09:00 or 09:00:00 are all setting the rule start time to 9 am.

The following example will switch a light on at 20:00 in the evening and switch it off at 6:00 in the morning.

{
    "rules": {
        "switchGardenLightOn": {
            "time": "20:00",
            "topic": { "outdoor/light/set": "on"}
        },
        "switchGardenLightOn": {
            "time": "6:00",
            "topic": { "outdoor/light/set": "off"}
        }
    }
}

Usecase example, switch off at fixed time

A nightly rule can switch off anything you forgot to switch of yourself

{
    "rules": {
        "switchOfNigh": {
            "time": "3:00",
            "value": "off",
            "topic": ["garden/light", "livingroom/multimedia", "floor/heating"]
        }
    }
}

calculations

Rule values supports a calculation tree to calculate values. The decision tree can be used in many places in the rule definition - for example to calculate a value

variables

When calculating values we need variables. A string with a "/" in it is a variable. The automation module can subscribe to receive topics and will then automatically store them as variables. This allows for example to act on temperature change.

The following default variables are automatically set, you need to set longitude and latitude of your location in the configuration file to get proper values for sun related times:

  • /time current time of day
  • /weekday current day in the week (0 for sunday, 1 for monday, ...)
  • /sunrise time of sunrise (today)
  • /sunset time of sunset
  • /civildawn, /civildusk (please google)
  • /nauticaldawn, /nauticaldusk (please goolgle)
  • /astronomicaldawn, /astronomicaldusk (please goolge)

calculation tree format

The calculation tree has a near lisp syntax. Lisp is a programming language. Suported are

  • ["and", term1, term2, ...]
  • ["or", term1, term2, ...]
  • [comperator, term1, term2] with comperator =, !=, <>, >, <, >=, <=
  • [operator, term1, term2] with operator = + or -
  • ["if", condition, trueValue, falseValue]
  • ["switch", term, { value1: term1, value2: term2, ..., default: termDefault }]

every term can be calulated as well with the same structure.

Example: The following example will switch the light on between 20:00 and 6:00 and switch it off between 6:00 and 20:00. It does the same as in the example of the "time" section. But I think the example in the time secition is easier to read - this one here could be prefered by programmers.

{
    "rules": {
        "switchGardenLightOn": {
            "time": "20:00",
            "value": [
                "if",
                ["or", [">=", "/time", "20:00"], ["<=", "/time", "6:00"]],
                "on",
                "off"
            ],
            "topic": "outdoor/light/set"
        }
    }
}

check

The check property adds an additional check to the a rule that must evaluate to true to trigger the rule. The "calculation" section how the check decision can be defined.

Example: This example will switch on a light in the morning at 6:00 if the sun has not been rised. (current time is less than sunrise)

{
    "rules": {
        "switchLightOnMorning": {
            "time": "6:00",
            "check": ["<", "/time", "/sunrise"],
            "topic": { "outdoor/light/set": "on" }
        }
    }
}

use-case switch outdoor lights

The already described possibilities are sufficient to create the first real use-case. Handling light outdoor.

{
    "rules": {
        "switchOnMorning": {
            "time": "6:00",
            "check": ["<", "/time", ["-", "/sunrise", 10]],
            "topic": { "outdoor/light/set": "on" }
        },
        "switchOffSunrise": {
            "check": [">=", "/time", "/sunrise"],
            "topic": { "outdoor/light/set": "off" }
        },
        "switchOnSunset": {
            "check": ["and" [">=", "/time", "/sunset"] ["<=", "/time", "22:50"]],
            "topic": { "outdoor/light/set": "on" }
        },
        "switchOffNight": {
            "time": "23:00",
            "topic": { "outdoor/light/set": "off" }
        }
    }
}

Explained:

switchOnMorning switches the light on, if it is 6:00 or later AND current time is at least 10 minutes before sunrise. Please not that subtracting numbers from times is interpreted as subtracting minutes. This is the typical usecase for automation

switchOffSunrise switches the light off on sunrise. Please note that we have a 10 minutes differens between the first rule - the light will not switch on for less than 10 minutes.

switchOnSunset switches the light on again on sunset - if it is not later than 22:50

switchOffNight switches the light off at 23:00

Please note that you may switch several lights by adding topics or by having multiple actors subscribing to "outdoor/light/set" - it is up to you to design this. I prefer to have one topic per light

cooldownInSeconds, how rules trigger

The rule triggering has some intelligence built in. It will only trigger on changes.

Example:

{
    "rules": {
        "switchOnMorning": {
            "time": "6:00",
            "check": ["<", "/time", ["-", "/sunrise", 10]],
            "topic": { "outdoor/light/set": "on" }
        }
    }
}

switchOnMorning will only trigger once after time is later than 6:00 and the sun rised. Rules only trigger once with the same value. It will trigger again, if the value changes, example:

{
    "rules": {
        "switchOnMorning": {
            "time": "6:00",
            "value": ["if", ["<", "/time", "/sunrise"], "on", "off"],
            "topic": "outdoor/light/set"
        }
    }
}

This rule will trigger after 6:00 before sunrise to set it on and after sunrise to set it off

This behaviour can be changed by adding the "cooldownInSeconds". The following rule will trigger every 10 seconds starting at 6:00 as long as the current time is before sunrise. Do not set "cooldownInSeconds" to values lower than 60 seconds. The automation is checking rules only every minute or whenever a message is received (the timing can be changed in the configuration file)

{
    "rules": {
        "switchOnMorning": {
            "time": "6:00",
            "cooldownInSeconds": 60,
            "check": ["<", "/time", "/sunrise"],
            "topic": { "outdoor/light/set": "on" }
        }
    }
}

usecase switch on for one hour

This example shows how to create rules switching devices on for one hour in the morning and in the evening

Four rule solution, very simple (simple is good).

{
    "rules": {
        "switchOnMorning": {
            "time": "6:00",
            "topic": { "anything/set": "on" }
        },
        "switchOffMorning": {
            "time": "7:00",
            "topic": { "anything/set": "off" }
        },
        "switchOnEvening": {
            "time": "20:00",
            "topic": { "anything/set": "on" }
        },
        "switchOffEvening": {
            "time": "21:00",
            "topic": { "anything/set": "off" }
        }
    }
}

The following works too:

{
    "rules": {
        "switchOn": {
            "value": ["if",
                ["and", [">=", "/time", "6:00"], ["<=", "/time", "7:00"]],
                "on", "off"],
            "topic": "anything/set"
        },
        "switchOn": {
            "value": ["if",
                ["and", [">=", "/time", "20:00"], ["<=", "/time", "21:00"]],
                "on", "off"],
            "topic": "anything/set"
        }
    }
}

Or all in one, the "if" version can be combined:

{
    "rules": {
        "switch": {
            "value": ["if",
                ["or"
                    ["and", [">=", "/time", "6:00"], ["<=", "/time", "7:00"]],
                    ["and", [">=", "/time", "20:00"], ["<=", "/time", "21:00"]]
                ]
                "on", "off"],
            "topic": "anything/set"
        }
    }
}

delayInSeconds

Most rules shall trigger immediately. But in some cases, a situation should be stable for some time, before the message is sent. DelayInSeconds is the amount of seconds required for a rule to create an identical message (topic/value) before the message is sent

Example:

{
    "rules": {
        "standby": {
            "check": ["<", "/power", 10],
            "delayInSeconds": 60,
            "topic": { "powerswitch/set": "off" }
        }
    }
}

The power must be below 10 for at least 60 seconds, before power is switched off.

duration, limit time

The timespan of the rule can be limited with the duration property. Duration needs a time property to be relevant. The duration value must be formatted as HH:MM or H:MM. The duration has a default value of six hours if not specified

Example:

{
    "rules": {
        "switchOnEvening": {
            "time": "18:00",
            "duration": "4:00",
            "check": [">", "/time", "/sunset"],
            "topic": { "outdoor/light/set": "on" }
        }
    }
}

This rule now switches the the light on starting at 18:00 after sunset but only if sunset was before 18:00 + 4:00 = 22:00

qos

qos or quality of service defines the quality of service whith which the message is delivered

  • 0, the message is just sent once - there is no acknowledge that the message has been delivered. This is the fastest way to send messages, but messages can get lost
  • 1, the message is sent at least once - the message is send again and again until an acknowledge is received
  • 2, the message is sent exactly once - checked by a two step handshake. This takes the longes time and produces a large amount of communication

Default is qos = 1 - leave it in most cases, if you are in a local network. Use qos = 0, if the message is sent very often and it is not important to get all of them. Use qos = 2, if the message is really importend like a water leakage alarm.

oneOf, allOf

These properties (together with noneOf and allow) are used for event driven automation rules like movements.

oneOf requires one of the topics in the array, allOf requires all topics in the array.

This functionality works completely different than the rules shown before. Here the rule triggers every time it detects an action (use cooldownInSeconds to limit it)

The following rule will send a presence message if a movement in the floor occured:

{
    "rules": {
        "switchOn": {
            "oneOf": ["floor/movement"],
            "topic": { "$SYS/presence": "awake" }
        }
    }
}

The following rule triggers if a movement in the floor and in the kitchen occured at (nearly) the same time. "Nearly same time" is in 5 seconds (configurabe in the configuration file).

The following rule will trigger if someone walks from kitchen to floor or from floor to kitchen

{
    "rules": {
        "switchOn": {
            "allOf": ["floor/movement", "kitchen/movement"],
            "topic": { "do/something": "left or entered kitchen" }
        }
    }
}

usecase presence status

Having a presence status will bring you to an advanced level of automation. Many rules profits from the knowlege of presence. Use for example the values "absent", "present", "sleeping".

The following example will set the status to presence, if there is a motion detected on the ground-floor.

{
    "rules": {
        "wakeup": {
            "check": ["=", "system/presence", "sleeping"],
            "allOf": ["floor/motion"],
            "topic": { "system/presence": "present" }
        },
        "comingHome": {
            "check": ["=", "system/presence", "absent"],
            "allOf": ["floor/motion"],
            "topic": { "system/presence": "present" }
        }
    }
}

usecase safe energy

Many electronic devices only needs to run if somebody is present ant not sleeping - for example a smart speaker. With a presence status it can be controlled easily.

{
    "rules": {
        "onIfPresent": {
            "value": [
                "switch",
                "system/presence",
                { "present": "on", "default": "off" }
            ],
            "topic": [
                "livingroom/multimedia/set",
                "study/smartspeaker/set"
            ]
        }
    }
}

noneOf

Use noneOf to prevent rule triggering, if an event occured. A wakeup rule can be hindered to trigger on movement in the bathroom.

This may prevent setting your home to "present" if you are only going to the bathroom at night.

{
    "rules": {
        "wakeup": {
            "check": ["=", "system/presence", "sleeping"],
            "anyOf": ["ground/floor/motion"],
            "noneOf": ["first/bathroom/motion", "first/floor/motion"],
            "topic": { "system/presence": "present" }
        }
    }
}

allow

Here you may add allowed events - all non allowed events are then denied. Use it rarely (better use "noneOf" and list all rules that are not allowed), I try to get rid of it - it will depricate.

durationWithoutMovementInMinutes

Use this to detect absence or sleeping states - no movement for quite some time with the last movement at the front door or at the floor of the sleeping room.

{
    "rules": {
        "sleeping": {
            "check": ["=", "system/presence", "present"],
            "anyOf": ["first/floor/motion"],
            "durationWithoutMovementInMinutes": 15,
            "topic": { "system/presence": "sleeping" }
        },
        "absence": {
            "check": ["=", "system/presence", "present"],
            "allOf": ["ground/floor/motion"],
            "durationWithoutMovementInMinutes": 30,
            "topic": { "system/presence": "absent" }
        }
    }
}

usecase alarm on movement if absent

Having presence/absence detected you may add the ability to send an alarm to your smartphone. Yaha supports the "pushover" app for this task

{
    "rules": {
        "alarm": {
            "check": ["=", "system/presence", "absent"],
            "anyOf": ["+/+/motion"],
            "topic": { "system/alarm": "motion" }
        }
    }
}

usecase livingroom roller shutter

This is a real world example. It shows many features of the automation module. You need to have a presence topic to use it.

Note that you do not need to subscribe for the topics needed (like indoor and outdoor temperature). The system detects that you are using these topic and thus will automatically subscribe for them (see mqttbroker documentation if you did not understand this note).

roller up in the morning

Imagine to have two rollers in a livingroom (south and east). When shall the roller move up? Surely not too early while sleeping, but when you are awake - and sun rised.

{
    "rules": {
        "UpMorning": {
            "time": ["switch", "system/presence", {"absent": "8:00", "awake": "6:00", "sleeping": "10:00"}],
            "check": [">=", "/time", ["-", "/sunrise", 10]],
            "topic": {
                "shutter/east/set": 0,
                "shutter/west/set": 0
            }
        }
    }
}

This rule solves many cases in a short simple rule.

  • Does not move up too early to not wake neighours ("awake": "6:00")
  • Does not move up while you are sleeping, only if you are sleeping too long ("sleeping": "10:00")
  • Does move up at "normal" time while not at home (for example in holidays)
  • Does not move up if the sun did not rise
  • Moves two shutter roller (west and east)
  • Reacts at the right time (if you awake at 7:33, it will move up at 7:33 and not forget to move up - because 6:00 already passed)
  • Moves up only once, even if you wake up and then get back sleeping

roller down for sun protection

{
    "rules": {
        "DownCooling": {
            "time": ["switch", "system/presence", {"absent": "9:30", "awake": "12:00", "sleeping": "9:30"}],
            "check": [
                "and",
                [">", "outdoor/temperature", 22],
                [">", "indoor/temperature",
                    ["switch", "system/presence", {"absent": 23, "awake": 25, "sleeping": 24}]
                ]
            ],
            "topic": {
                "shutter/east/set": 100,
                "shutter/west/set": 100
            }
        }
    }
}

This rule will move down the roller at day to protect against too much sun in summer:

  • moves only down, it indoor and outdoor temperature are high
  • request higher indoor temperature if you are present and not sleeping - closed rollers are not nice if you are there
  • move the rollers down later if you are present, again to protect against dark rooms
  • moves down only once - if you like to overrule manually it will not bug you

roller move up again at afternoon

If the roller moved down for sun protection, it needs to move up again

{
    "rules": {
        "UpAfternoon": {
            "time": ["switch", "system/presence", {"absent": "17:00", "awake": "16:00", "sleeping": "17:00"}],
            "check": ["<=", "/time", ["-", "/sunset", 30]],
            "topic": {
                "shutter/east/set": 0,
                "shutter/west/set": 0
            }
        }
    }
}
  • moves the roller up again, a little earlier, if you are at home
  • but never, if sunset is near
  • if you come home for example at 16:30, the roller will automatically move up on arrival

roller moves down at sunset

{
    "rules": {
        "DownSunsetse": {
            "check": [">=", "/time", "/sunset"],
            "topic": { "shutter/east/set": "100" }
        },
        "DownSunsetsw": {
            "check": [">=", "/time", ["-", "/sunset", 20]],
            "topic": { "shutter/west/set": "100" }
        },
        "DownEvening": {
            "time": "23:00",
            "topic": {
                "shutter/east/set": 100,
                "shutter/west/set": 100
            }
        }
    }
}

The shutter are moving down at different times:

  • Moving down on sunset
  • Move one earlier than the other, if you have a terrace door. The first roller will warn you that the terrace door will be blocket in 20 minutes.
  • It not down already, it fill (finally) move down at 23:00. If for example to stoped the roller to move down due to a long warm night, it will finally move down later.

If you have a motion detection in the terace you might also hinder the rollers to move down, if you are at the terace