A Domain Specific Language (DSL) for the IoT.
abudsl
is a distributed language merging the programming simplicity prerogative of Event Condition Action (ECA) rules with a powerful (decentralized) communication and coordination mechanism based on attributes. The language exploits Attribute-based memory Updates and it has its roots in the AbU calculus1.
In abudsl
, IoT devices (comprising sensors, actuators and internal variables) are programmed by means of rules following the Event Condition Action programming style:
on event if condition do action
This reads as: when event occurs, if condition is verified, then execute action. The event can be a change in a sensor or the modification of an actuator, while an action is a list of updates of variables and actuators.
The peculiarity of abudsl
is that such rules are distributed, in the sense that they can act on a distributed network of IoT devices. Another key point is decentralization. Indeed, in abudsl
, devices do not have a global knowledge about the network: communication and coordination is performed by means of an attribute-based interaction. In this respect, abudsl
exploits Attribute-based memory Updates: an attributed-based interaction mechanism which is decentralized and that fits neatly within the ECA paradigm. Indeed, in abudsl
a device can perform an action on itself (the current device) or on an external (remote) device. But, from the programmer point of view, both types of action are just memory updates.
In the following, we gently present the grammar for the (basic) syntax of the DSL. Have a look at the abudsl-grammar for the complete syntax of abudsl
. In the examples folder it is possible to find some abudsl
coding examples.
File extension: the official extension for
abudsl
program source files is.abu
.
An abudsl
program consists in a non-empty list of (IoT) devices, equipped with sensors, actuators and internal variables, followed by a list of ECA rules acting on these devices.
A device is of the form:
DeviceId
:
Description{
ResourceDeclaration
[where
BooleanExpression ]
}
where DeviceId is the name of the device (an alphanumeric string); Description is a quoted string describing the device functionality; and ResourceDeclaration is a non-empty list of resource (sensors, actuators and internal variables) declarations. A resource can be physical or logical.
A physical resource declaration can be of the forms:
Input
physical input
PrimitiveType ResourceId
Outputphysical output
PrimitiveType ResourceId=
Value
Input physical resources are used to model sensors; while Output pyshical resources are used to model actuators. The first, are supposed to be read-only; while the latter are supposed to be write-only.
A logical resource declaration is of the form:
logical
PrimitiveType ResourceId=
Value
and it is used to model internal device variables. This kind of resource does not have read/write constraints. Note that, logical and physical output resources have to be declared with an initialization Value, while physical input resources do not.
Each resource is declared with a name ResourceId (an alphanumeric string) and a (primitive) type PrimitiveType. In the basic syntax of abudsl
, we have the following primitive types:
boolean
, for boolean resources liketrue
orfalse
integer
, for integer resources like42
or-42
decimal
, for decimal resources like3.14
or-3.14
string
, for (quoted) string resources like"sTr1nG"
Elements in Value belong to primitive types, hence we can have: a BooleanValue, with type boolean
; a IntegerValue, with type integer
; a DecimalValue, with type decimal
; and a StringValue, with type string
.
Strings format: strings can contain spaces and special characters, like
_
(underscore),\
(backslash),#
(octothorpe) or'
(single quote); but they cannot contain the double quote symbol"
.
Finally, a device can be equipped with an (optional) invariant, introduced after the keyword where
. The invariant is a boolean expression that the device have to fulfill during execution (no updates violating the invariant are allowed).
Here is a device full example (self-explanatory):
hvac : "An HVAC control system" {
# Resources declaration.
physical output boolean heating = false
physical output boolean conditioning = false
logical integer temperature = 0
logical integer humidity = 0
physical input boolean airButton
logical string node = "hvac"
where
# Device invariant.
not (conditioning and heating)
}
where the lines after the keyword #
are comments.
In abudsl
it is also possible to define custom types, in order to use compound resources (something like objects). They are defined by means of a (possibly empty) list of type declarations:
define
CompoundTypeas {
FieldDeclaration
}
where CompoundType is an an alphanumeric string (the new type identifier) and FieldDeclaration is a non-empty list of field declarations of the form:
ResourceId
:
(physical input
PrimitiveType |physical output
PrimitiveType |logical
PrimitiveType )
Here is an example of type declaration (self-explanatory):
define GPIOButton as {
pin : physical output integer
status : logical boolean
}
Compound types are instantiated and initialized when declared in a device. This is done by using an anonymous constructor taking as parameter a possibly empty list of fields that need to be initialized. Hence, a constructor is of the form:
(
[ ResourceId=
Value (,
ResourceId=
Value )* ])
For instance, to instantiate the compound type GPIOButton
we can insert the following resource declaration:
GPIOButton button = (pin = 38, status = false)
To access a compound resource field in expressions, we use a double brackets notation à la Python. For instance, to access the field pin
of the compound resource button
having type GPIOButton
we can use:
button[pin]
As said at the beginning, devices are programmed by means of ECA rules acting on them. A rule can act on multiple devices and a device can be influenced by multiple rules. Hence, each device is suffixed by a list of RuleId, namely rule names, after the (optional) keyword has
. For instance:
hvac : "An HVAC control system" {
# Resources declaration.
...
logical string node = "hvac"
where
# Device invariant.
not (conditioning and heating)
} has cool warm dry stopAir
means that the device hvac
can be affected by the ECA rules named cool
, warm
, dry
and stopAir
. Note that, the list of rules is optional since a device may not have specific rules acting on it (for instance, when an actuator can only be changed by external devices but its does not impact any other device).
An ECA rule is of the form:
rule
RuleId
on
Event
( Task )+
where RuleId is the name of the rule; Event is a non-empty space-separated list of resources on which the rule is waiting for changes; and ( Task )+ is a non-empty list of tasks that may be activated when a resource in Event changes. Rule names and resources are alphanumeric strings. For instance:
rule dry
on humidity temperature
is a rule named dry
that is waiting for changes in the resources humidity
and temperature
.
An ECA rule task is of the form:
for
[all
] Condition
do
Action
where Condition is a boolean expression and Action is a list of comma-separated list of resource assignments. When Condition is satisfied, then the assignments in Action are performed. For instance:
for (2 + 0.5 * temperature < humidity and 38 - temperature < humidity)
do conditioning = true
is a task that turns on the conditioning system (doing conditionig = true
) when the humidity is above a given threshold (namely when the condition after for
is true).
The full code of the dry
rule is then the following:
rule dry
on humidity temperature
for (2 + 0.5 * temperature < humidity and 38 - temperature < humidity)
do conditioning = true
An Action may perform multiple assignments, atomically executed. For instance:
do x = 4, y = 1
yields a list of updates that, when committed, will simultaneously modify both resources x
and y
.
ECA rule tasks may act on external devices, by using the (optional) modifier all
. In this case, the condition and the action in the task may reference resources on external devices, by prefixing them with the ext.
keyword. For instance:
rule notifyTemp
on temperature
for all (ext.node == "hvac")
do ext.temperature = this.temperature
is a rule that updates the temperature of external devices with the temperature value of the current device (doing ext.temperature = this.temperature
). External devices may be filtered. Indeed, only the devices with node == "hvac"
are affected by the update. The use of the keyword this.
to indicate a resource on the current device is optional.
To easy the programming of ECA rules, abudsl
provides the following rule abstractions.
Default Rule
rule
RuleId
on
Eventdefault
Action
( Task )*
IfElse Rule
rule
RuleId
on
Event
for
[all
] Condition
do
Action
owise
Action
Let Rule
rule
RuleId
on
Event
let
LetDeclarationin
( Task )+
In a Default rule the assignments in Action are always executed when Event happens, independently from tasks condition. In a IfElse rule the action after do
is performed when Condition is true, while the action after owise
is performed when Condition is false. Finally, in a Let rule the substitutions in LetDeclaration are applied inside the non-empty list of tasks ( Task )+. In particular, LetDeclaration is a semicolon-separated list of substitutions from expressions to resources. For instance, the rule
rule stupidCalculatorLet
on x y
let sum := (x + y); diff := (x - y) in
for (sum > 0)
do result = sum * diff
is equivalent to the following:
rule stupidCalculator
on x y
for ((x + y) > 0)
do result = (x + y) * (x - y)
In the basic syntax of abudsl
, an Expression can be a BooleanExpression, a NumericExpression or a StringExpression. The definition of expressions is standard and it comprises: boolean operators, like not
(negation), and
(conjunction), or
(disjunction); arithmetic and string operators, like absint
(absolute value for integers), absdec
(absolute value for decimals), +
(addition), -
(subtraction), *
(multiplication), /
(division), %
(modulo), ::
(concatenation); and comparison operators, like ==
(equal), !=
(not equal), <
(less than), <=
(less than or equal), >
(greater than), >=
(greater than or equal). The standard operators composition priority can be overridden by using left (
and right )
round brackets.
The detailed grammar of expressions can be found here.
Inline comments start with a #
:
# This is an inline comment.
while multi-line comments are enclosed between \@
and @\
:
\@
This is a multi-
line comment.
@\
Footnotes
-
Marino Miculan and Michele Pasqua. "A Calculus for Attribute-Based Memory Updates". In Antonio Cerone and Peter Ölveczky, editors, Proceedings of the 18th international colloquium on theoretical aspects of computing, ICTAC 2021, volume 12819 of Lecture Notes in Computer Science. Springer, 2021. ↩