Skip to main content

hivescript

Hivescript is a scripting language that is built into Hivekit. It is designed to be a simple and efficient way to run logic when things change in the hivekit system. Hivescript is very capable, but it is not intended to be a full replacement for programming languages like python or javascript. If you find that hivescript doesn't suit your use case, it may be easier to write some code using the client library to do what you need to do.

Concepts

hivescript instructions are loaded into the realm. They can consist of up to four parts, the every, the when, the then and the until. In an instruction, when present, they always follow that order, but I'll talk about when, and then first because they are the most commonly used parts, and until and every afterwards.

Here's an example of a simple hivescript instruction:

when
object(troublemaker="true")
then
delete()

This instruction will delete any object that has the attribute troublemaker set to true. The when part of the instruction is a filter that selects the objects that the instruction is interested in. The then part of the instruction is a list of actions that will be performed on the objects that match the when filter.

Instructions don't just take effect when they are added, they remain active in the realm, so in this case if an object gets a troublemaker attribute set to true in the future, it will be deleted at that point.

When working with hivescript, you should think in terms of groups of things. We select a group of things out of the realm (when), and then we can perform actions on objects as they come into the group (then) or leave the group (until).

The when, then and until parts consist of function calls. Functions are defined in the realm and either filter the current group of things, or take actions on the current group. In this example, object is a function that selects just objects that match a criteria out of the realm, and delete is a function that deletes all the objects in the current group.

Installing an instruction into a realm

You can load instructions into your realm through the hivekit web interface, or by using the client library.

Adding an instruction using the web interface.

To add an instruction using the client library, you can use the realm.instruction.create method. Here's an example:

import HivekitClient from "@hivekit/client-js";

const client = new HivekitClient();
await client.connect('wss://hivekit.io/ws');
await client.authenticate('my_api_token');

const realm = await client.realm.get('realmId');
await realm.instruction.create('operations/ins1', {
label: 'delete troublemakers',
instructionString: `
when
object(troublemaker="true")
then
delete()
`
})

You can read more about using the client library in the client library documentation.

When

Hivekit instructions operate on groups of things. The when section of the instruction decides which things are part of the initial group. When the membership of the group changes, the then and until parts of the instruction will run depending on what has changed.

Think of the when part of the instruction as a filter that selects the things that the instruction is interested in out of everything in the realm. Most commonly, you'll be writing instructions that operate on objects in the realm. In our example above, we are selecting objects that have the attribute troublemaker set to true, but the object() function can select a group of objects in other ways too.

Function calls can be chained too.

when
object(type="bike").near(10, type="danger")
then
sendToUrl("https://webhook.site/my-webhook-id")

In this case, the when part of the instruction selects all objects that are of type bike. These are then filtered based on which of them are within 10 meters of an object of type danger.

It's important to think about what is in the group here. an object(type="bike") call will return all the objects in the realm that are of type bike, so the result of that function call is a group of objects. The near(10, type="danger") call doesn't just filter the object, it returns pairs of objects that are within 10 meters of each other. So the result of the near function call is a group of pairs of objects. The first object in each pair is the bike and the second object is the danger that the bike is close to. When the group of pairs changes, then the when part of the instruction will operate on any new pairs. This means that if a new danger comes close to a bike, even if that bike has already triggered for some other danger, the new danger will cause a new triggering for the pair (bike, danger).

Then

When things come into the group defined by the when clause, we can perform actions on them. The then part of the instruction is a list of actions that will be performed on objects that newly match the when filter.

In the troublemaker example, we perform the delete action on all objects as soon as they match the when filter. The delete action will delete the object from the realm. Just as before, the then section is a list of function calls, and we can filter or process the group of objects we operate on before taking action.

You can include more than one action in the then section. Actions are split by a semicolon. Here's an example:

when
object(type="bike")
then
debug("bikes");
sendToUrl("https://webhook.site/bikes")

Until

Until is just like then, but instead of operating on things that are coming into the active group, they operate on things that are leaving the active group.

Here's an example:

when
object(type="sensor", value<100)
then
set("status", "low")
until
set("status", "normal")

In this example, we are selecting all objects that are of type sensor and have a value less than 100. When we first select these objects, we set the status attribute to low. When the value of the sensor goes back above 100, the sensor will leave the active group and the until part of the instruction will run, setting the status attribute to normal.

Every

Most instructions should be written so that they are triggered by changes occurring in the realm. Instructions then only need to run when the realm is changing. However, sometimes you want your active group to depend on the time, and time is not something that changes in the realm. For these cases, you can use the every part of an instruction.

every 30 seconds
when
object(type="sensor", $lastUpdate<now().minus(minutes=1))
then
set("status","faulty")
until
set("status","ok")

In this example, we find sensors that haven't reported in the last minute. To do this, we needed to use a special attribute $lastUpdate which is set to the last time that hivekit processed an update for the object. You can also see that some of the functions understand time periods too, so we can use minutes=1 to specify a time period of 1 minute instead of using the number of milliseconds.

now() returns the current time, but if the whole instruction ran every time the output of now() changed, it would need to be evaluated continually. To keep the load on the hivekit server low, we evaluate the now() function in accordance with the every part of the instruction.

In this case, we'll check every 30 seconds to see if any objects newly match the when filter.

The parameter to every is a time period. It understands a range of type periods, including weeks, days, hours, minutes and seconds, and abbreviations of those measures, such as every 3 mins or every 2s.

Since every forces the instruction to re-evaluate, for efficiency reasons, you should use it only when you need to (such as when you need to check the time) and you should set the period to as long as possible.

Recursion

Instructions trigger on changes, but they can also cause changes. This means that instructions can trigger themselves or other instructions. In order to avoid infinite loops, after a certain number of recursive calls, the instructions will stop triggering.