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. 

Solution

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
Where
  • 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.

Conventions

  • 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 “…”

Messaging

Notify - allthingstalk.messaging.notify

Inputs
  • 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
Outputs
  • 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.
Example
# 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

Math

Compare - allthingstalk.math.compare

Inputs
  • req a (comparable): One of the values that’s being compared
  • req b (comparable): The other value that’s being compared
Outputs
  • 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.
Example
# Turn on led if temperature is greater than 20. Turn off otherwise.
 
device = allthingstalk.device.ExampleId1
compare = allthingstalk.math.compare
 
device.temperature -> compare.a
20 -> compare.b
 
compare.a>b -> device.led
 
BasicOps - allthingstalk.math.basic.op
Inputs
  • req a (number): Number a
  • req b (number): Number b
Outputs
  • 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.
Example
# 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
Inputs
  • opt ****a (any): First argument
  • opt ****b (any): Second argument
Outputs
  • 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.
Example
# 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
 
frontWindow.open -> logic.a
backWindow.open -> logic.b
logic.or -> notify.sendPush
 
BasicFunction - allthingstalk.math.basic.function
Inputs
  • req in (number): Function argument
Outputs
  • 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

Inputs
  • req in (number): Function argument
Outputs
  • 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

Inputs
  • 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
Outputs
  • out (integer): Generated number number
RandomInteger generates random integers when generate input is triggered.
Example
# 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 -> random.to
device.button -> random.generate
random.out -> display.text

Average - allthingstalk.math.average

Inputs
  • Variable number of inputs, custom names (… number): Numbers that are being averaged
Outputs
  • avg (number): The average
Average averages any number of custom named numerical inputs.
Example
# 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
Inputs
  • req in (number): Rounding function argument
Outputs
  • 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

Inputs
  • req in (number): A value / quantity of a unit
Outputs
  • 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.
Example
# Convert temperature in Celsius for display in Fahrenheit
 
weatherStation = allthingstalk.device.ExampleId1
converter = allthingstalk.math.convert
fahrenheitDisplay = allthingstalk.device.ExampleId2
 
weatherStation.temperature -> converter.in
converter.fahrenheit -> fahrenheitDisplay.fahrenheit
 
Clamp - allthingstalk.math.clamp
Inputs
  • req in (number): Input value
  • req min (number): Clamping lower bound
  • req max (number): Clamping upper bound
Outputs
  • 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.
Example
# 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.in
clamp.out -> dimmer.value
 
Map - allthingstalk.math.map
Inputs
  • 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
Outputs
  • 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.
Example
# Map joystick position to crane position
 
joystick = allthingstalk.device.ExampleId1
crane = allthingstalk.device.ExampleId2
mapX = allthingstalk.math.map
mapY = allthingstalk.math.map
 
0 -> mapX.from_lower
1 -> mapX.from_upper
-100 -> mapX.to_lower
200 -> mapX.to_upper
joystick.x -> mapX.in
mapX.out -> crane.x
 
0 -> mapY.from_lower
1 -> mapY.from_upper
0 -> mapY.to_lower
50 -> mapY.to_upper
joystick.y -> mapY.in
mapY.out -> crane.y
 

States

Counter - allthingstalk.state.counter

Inputs
  • req increment (◐ ****truthy): Increment the counter
  • req reset (◐ ****truthy): Reset the counter
Outputs
  • value (number): Current counter value
Counter is a stateful component that counts when triggered to increment. It can also be reset.
Example
# 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
Input
  • 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
Outputs
  • value (bool): Bit state
Bit is a stateful component that keeps one bit (a boolean value) of state. Initially, it is set to False.
Example
# 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 -> stereo.play
 
Change - allthingstalk.state.change
Inputs
  • req in (any): Input value
Outputs
  • 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.
Example
# 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.in
change.out -> firedep.notify
 

Strings

Format - allthingstalk.string.format

Inputs
  • 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
Outputs
  • out (string): Formatted string
Format inserts values into formatting string, producing a formatted string.
Example
# 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
 
Utils

Selector - allthingstalk.util.selector

Inputs
  • 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
Outputs
  • 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.
Example
# 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
Inputs
  • 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.
Outputs
  • 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.
Example
# Lock the warning until its explicitly reset
 
package = allthingstalk.device.ExampleId1
compare = allthingstalk.math.compare
gate = allthingstlak.util.gate
 
package.acceleration -> compare.a
9.81 -> compare.b
 
compare.a>b -> gate.in
compare.a>b ? true -> gate.lock
package.reset ? true -> gate.unlock
gate.out -> package.warning
 
Condition - allthingstalk.util.condition
Inputs
  • 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.
Outputs
  • 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.
Example
# 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