Create custom rules in EARL

Instead using only the Rule creator GUI, you can create the custom rule to fit your needs.

Example use case: Water tank level

User has a tank depth sensors that transmit the tank water level in millimetres.
She wants to calculate relative level of fullness, and store that value in a virtual asset, set to a percentage value - which is the measured depth divided by the tank max depth (which is a known constant).
So it’s simple maths, but how can she do this?

To start creating the custom rule, hold SHIFT while clicking on the + NEW RULE button in the RULES window. 


Our platform comes with a powerful, integrated rules engine which lets you automate work by defining actions that need to be taken in response to a set of triggers or events. In your case, the event is receiving new state of the tank depth.
While in ground where you device resides
  1. Choose RULES in main menu
  2. Hold the SHIFT key while choosing +NEW RULE
  • This hidden feature will open the editor that allows you to enter code that executes the rule
  1. Paste the code similar to this:
tank = allthingstalk.device.DEVICE_ID_GOES_HERE
op = allthingstalk.math.basic.op

tank.level -> op.a
100 -> op.b
op.a/b -> tank.fullness
  • level is the name of the sensor asset that measures the current depth of the water
  • 100 is the max depth of the tank 
  • fullness is the virtual asset, holding percentage value of how full is the tank

Below is the reference that will help you explore further our rules engine code.
Earl Standard Library Reference
Earl Standard Library implements components that are thought of as being useful for implementing rules in IoT scenarios. This document describes all components in detail. This document describes all components in detail. For components that are already implemented externally, info can be found at the bottom.


  • Components are divided into sections according to their namespace
  • Description of each component starts with listing its inputs and outputs
  • Input names are prefixed with “req” ***if they are required and opt”* if they are optional
  • Both input and output names are followed by type in parentheses, e.g. “(urn)”
  • Types might be specified informally, e.g. “(comparable)”
  • Informal type “truthy” denotes everything with value other than  null, "", false, 0, [], and {}.
  • Input/Output (port) description follows the type
  • If no inputs or outputs are available, this will be indicated as None
  • Components in general let Earl save the values on inputs for later even when “called”. However, some of them instruct Earl to erase certain inputs. These inputs’ types are prefixed with “◐”.
  • Variable number of inputs will be indicated with “…”


Notify - allthingstalk.messaging.notify

  • req recipient (urn): The message recipient, either a user or a ground
  • req message (string): The message that will be sent
  • opt sendPush (◐ ****truthy): Send push notification
  • opt sendWeb (◐ ****truthy): Send web notification
  • opt sendEmail (◐ ****truthy): Send email
  • None
Notify is used for sending messages to a single user or all users in a ground. It can be used to send push notifications, web notifications and emails.
# Send email when data is received on an asset
device = allthingstalk.device.ExampleId1
notify = allthingstalk.messaging.notify
urn:allthingstalk:user:ExampleId2 -> notify.recipient
"Your device just sent something!" -> notify.message
device.asset -> notify.sendEmail


Compare -

  • req a (comparable): One of the values that’s being compared
  • req b (comparable): The other value that’s being compared
  • a>b (bool): True if a is greater than b
  • a=b (bool): True if a equals b
  • a<b (bool): True if a is less than b
  • a<>b (bool): true if a doesn’t equal b
Compare compares values either numerically or lexicographically. End results of comparing values of different types is not defined.
# Turn on led if temperature is greater than 20. Turn off otherwise.
device = allthingstalk.device.ExampleId1
compare =
device.temperature -> compare.a
20 -> compare.b
compare.a>b -> device.led
BasicOps - allthingstalk.math.basic.op
  • req a (number): Number a
  • req b (number): Number b
  • a+b (number): a + b
  • a-b (number): a - b
  • a*b (number): a * b
  • a/b (number): a / b. If b is zero, the output is not sent.
  • a^b (number): a to the power of b
BasicOps deals with basic mathematical operations on two arguments.
# Display the difference between two temperatures
display = allthingstalk.device.ExampleId1
livingRoom = allthingstalk.device.ExampleId2
bathroom = allthingstalk.device.ExampleId3
op = allthingstalk.math.basic.op
livingRoom.temperature -> op.a
bathroom.temperature -> op.b
op.a-b -> display.text
BasicLogic - allthingstalk.math.basic.logic
  • opt ****a (any): First argument
  • opt ****b (any): Second argument
  • and (bool): a and b
  • or (bool): a or b
  • nand (bool): not (a and b)
  • nor (bool): not (a or b)
  • nota (bool): not a
  • notb (bool): not b
BasicLogic deals with basic boolean logic on one and two arguments.
# Notify me if any of my windows is open
frontWindow = allthingstalk.device.ExampleId1
backWindow = allthingstalk.device.ExampleId2
notify = allthingstalk.messaging.notify
logic = allthingstalk.math.basic.logic
"Open window!" -> notify.message
urn:allthingstalk:user:ExampleId3 -> notify.recipient -> logic.a -> logic.b
logic.or -> notify.sendPush
BasicFunction - allthingstalk.math.basic.function
  • req in (number): Function argument
  • abs (number): Argument’s absolute value
  • sqrt (number): Argument’s square root
  • log (number): Argument’s natural logarithm
  • log2 (number): Argument’s logarithm with base 2
  • log10 (number): Argument’s logarithm with base 10
BasicFunction calculates values of basic math functions.

Trigonometry - allthingstalk.math.trig

  • req in (number): Function argument
  • sin (number): Sine of argument
  • cos (number): Cosine of argument
  • tan (number): Tangent of argument
  • asin (number): Arcsine of argument
  • acos (number): Arccosine of argument
  • atan (number): Arctangent of argument
Trigonometry calculates values of trigonometric functions.

RandomInteger - allthingstalk.math.randint

  • req from (integer): Lower bound for generated random integer
  • req to (integer): Upper bound for generated random integer
  • req generate (◐ any): Triggers random number generation
  • out (integer): Generated number number
RandomInteger generates random integers when generate input is triggered.
# Display a random number when the button is pushed
device = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
random = allthingstalk.math.randint
0 -> random.from
10 ->
device.button -> random.generate
random.out -> display.text

Average - allthingstalk.math.average

  • Variable number of inputs, custom names (… number): Numbers that are being averaged
  • avg (number): The average
Average averages any number of custom named numerical inputs.
# Display average temperature in the apartment
livingRoom = allthingstalk.device.ExampleId1
kitchen = allthingstalk.device.ExampleId2
bathroom = allthingstalk.device.ExampleId3
display = allthingstalk.device.ExampleId4
average = allthingstalk.math.average
livingRoom.temperature -> average.a
kitchen.temperature -> average.b
bathroom.temperature -> average.c
average.avg -> display.text
Rounding - allthingstalk.math.rounding
  • req in (number): Rounding function argument
  • floor (number): Floor of the argument
  • ceil (number): Ceiling of the argument
  • trunc (number): The argument truncated (decimals removed)
  • fractional (number): The decimal part of the number
Rounding calculates rounding functions on the input argument.

Convert - allthningstalk.math.convert

  • req in (number): A value / quantity of a unit
  • radians (number): Treats input as degrees and outputs radians
  • degrees (number): Treats input as radians and outputs degrees
  • celsius (number): Treats input as degrees Fahrenheit and outputs degrees Celsius
  • fahrenheit (number): Treats input as degrees Celsius and outputs degrees Fahrenheit
  • meters (number): Treats inputs as feet and outputs meters
  • feet (number): Treats inputs as meters and outputs feet
Convert performs basic conversions between mathematical and physical units.
# Convert temperature in Celsius for display in Fahrenheit
weatherStation = allthingstalk.device.ExampleId1
converter = allthingstalk.math.convert
fahrenheitDisplay = allthingstalk.device.ExampleId2
weatherStation.temperature ->
converter.fahrenheit -> fahrenheitDisplay.fahrenheit
Clamp - allthingstalk.math.clamp
  • req in (number): Input value
  • req min (number): Clamping lower bound
  • req max (number): Clamping upper bound
  • out (number): Clamped value
Clamp performs clamping on the input value. It restricts the output to the range between min and max, returning the min if value is less than it, max if it’s bigger than max, and the unchanged value otherwise.
# We have a light dimmer that can only be actuated with values
# between 0 and 255. However, the user could theoretically try
# setting any value.
input = allthingstalk.device.ExampleId1
dimmer = allthingstalk.device.ExampleId2
clamp = allthingstalk.math.clamp
0 -> clamp.min
255 -> clamp.max
input.value ->
clamp.out -> dimmer.value
Map -
  • req in (number): Input value
  • req from_lower (number): Lower input bound
  • req from_upper (number): Upper input bound
  • req to_lower (number): Lower output bound
  • req to_upper (number): Upper output bound
  • out (number): Mapped value
Map is used for mapping the input value from range bounded by from_lower and from_upper, to the range bounded by to_lower and to_upper.
# Map joystick position to crane position
joystick = allthingstalk.device.ExampleId1
crane = allthingstalk.device.ExampleId2
mapX =
mapY =
0 -> mapX.from_lower
1 -> mapX.from_upper
-100 -> mapX.to_lower
200 -> mapX.to_upper
joystick.x ->
mapX.out -> crane.x
0 -> mapY.from_lower
1 -> mapY.from_upper
0 -> mapY.to_lower
50 -> mapY.to_upper
joystick.y ->
mapY.out -> crane.y


Counter - allthingstalk.state.counter

  • req increment (◐ ****truthy): Increment the counter
  • req reset (◐ ****truthy): Reset the counter
  • value (number): Current counter value
Counter is a stateful component that counts when triggered to increment. It can also be reset.
# Count and display the number of passengers that pass through a gate
gate = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
counter = allthingstalk.state.counter
gate.sense -> counter.increment
counter.value -> display.text
Bit - allthingstalk.state.bit
  • opt ****store (◐ any): Set bit’s state to True
  • opt ****toggle (◐ any): Toggle’s bit’s state
  • opt ****clear (◐ any): Set bit’s state to False
  • value (bool): Bit state
Bit is a stateful component that keeps one bit (a boolean value) of state. Initially, it is set to False.
# Alternatevely play and pause the stereo each time the button
# is pressed.
stereo = allthingstalk.device.ExampleId1
controller = allthingstalk.device.ExampleId2
bit = allthingstalk.state.bit
controller.button -> bit.toggle
bit.value ->
Change - allthingstalk.state.change
  • req in (any): Input value
  • out (any): The value, only propagated if it changed
  • old (any): The old value that was replaced, only propagated if the value changed
Change is a stateful component that keeps track of value changes and ignores the values that are repeated. Useful in debouncing scenarios.
# Call the fire department once the fire starts spreading
# and don't repeat the calls if the sensors continues registering
# smoke.
firedep = allthingstalk.device.ExampleId1
detector = allthingstalk.device.ExampleId2
change = allthingstalk.state.change
detector.smoke ->
change.out -> firedep.notify


Format - allthingstalk.string.format

  • req format (format string): A formatting string. Earl is using the same formatting strings as Python.
  • req values (any, or list of any): Input value or values that are used to fill in the formatting string
  • out (string): Formatted string
Format inserts values into formatting string, producing a formatted string.
# Humanize the value output by saying "The value is " and value,
# instead of just showing the value. So, "The value is 6" instead
# of just "6".
device = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
format = allthingstalk.string.format
"The value is %s" -> format.format
device.value -> format.values  # The template has only one place for a value,
                               # so we're sending only one value to "values"
format.out -> display.text

Selector - allthingstalk.util.selector

  • req s_0 (any): First selector bit
  • req s_0 (any): Second selector bit
  • req in_0 (any): First input
  • req in_1 (any): Second input
  • req in_2 (any): Third input
  • req in_3 (any): Fourth input
  • out (any): Selected output
Selector implements a two-bit multiplexer. Selector bits are coerced into booleans and the selection is made over provided inputs. Selected input is forwarded to "out" unchanged.
# Figure out if the bike is being stolen or not
bike = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
selector = allthingstalk.util.selector
bike.moving -> selector.s_0
bike.locked -> selector.s_1
"be careful, bike left unlocked" -> selector.in_0
"enjoy your ride" -> selector.in_1
"bike locked, hopefully this will deter theft" -> selector.in_2
"theft in progress! your bike is moving while locked" -> selector.in_3
Gate - allthingstalk.util.gate
  • req in (any): Input data
  • opt lock (any): Locks the gate so that inputs are not forwarded anymore. Data forwarding stopped immediately.
  • opt lock_delayed (any): Locks the gate so that inputs are not forwarded anymore. Data forwarded one last time.
  • opt unlock (any): Unlocks the gate so that data is forwarded again.
  • opt locked (bool): Locks or unlocks the gate, depending on the value of the argument.
  • out (any): Forwarded "in" data.
Gate implements a lockable data forwarder. While unlocked, data is simply passed through, from "in" to "out". Once locked, data stops coming through until the next unlock.
# Lock the warning until its explicitly reset
package = allthingstalk.device.ExampleId1
compare =
gate = allthingstlak.util.gate
package.acceleration -> compare.a
9.81 -> compare.b
compare.a>b ->
compare.a>b ? true -> gate.lock
package.reset ? true -> gate.unlock
gate.out -> package.warning
Condition - allthingstalk.util.condition
  • req if (any): If predicate, truthy values are treated as true
  • opt then (any): Resulting value in case that “if” value is truthy. No output if not set.
  • opt else (any): Resulting value in case that “if” value is falsey. No output if not set.
  • out (any): The value provided in “then” or “else” depending on “if”
Condition implements the classic if/then/else programming construct. The value provided in “if” is tested for truthfulness. If truthy, “then” value is propagated if present. If not, “else” value is propagated if present. It is recommended to replace it with syntactic sugar that ARL offers.
# Display coin toss result. For the sake of the example,
# coin's "toss" asset is boolean.
coin = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
condition = allthingstalk.util.condition
coin.toss -> condition.if
"heads" -> condition.then
"tails" -> condition.else
condition.out -> display.text
Using the ternary conditional operator, we can do this in a much simpler way.
coin = allthingstalk.device.ExampleId1
display = allthingstalk.device.ExampleId2
coin.toss ? "heads" : "tails" -> display.text

External Component
Timer Component:
Timer Component