The AbU Language: IoT Distributed Programming Made Easy

Event-driven programming based on Event-Condition-Action (ECA) rules allows users to define complex automation routines in a simple, declarative way; for this reason, in recent years ECA rules have been adopted by the majority of companies in the Internet of Things (IoT) industry as a promising paradigm for implementing ubiquitous and pervasive systems. Unfortunately, programming simplicity comes to a price: most implementations of ECA rules are bound to a strongly centralized communication infrastructure, that poses serious limitations on the application scenarios for the IoT, due to scalability, security and availability issues. To mitigate these issues, recent works introduced abstractions for communication and coordination of ensembles of IoT devices in a decentralized setting, effectively moving the computation towards the edge of the network without sacrificing the programming simplicity prerogative of ECA rules. In particular, Attribute-based memory Updates is a communication model transparently enhancing ECA rules-based systems with an interaction mechanism where communication is similar to broadcast but actual receivers are selected on the spot, by means of predicates (i.e., properties) over devices attributes. In this paper, we introduce AbU-dsl, a Domain Specific Language for the IoT that compiles on top of an implementation of Attribute-based memory Updates. In this way, AbU-dsl provides a practical development interface, based on ECA rules, to effectively program IoT devices in a fully decentralized setting, by exploiting a full-fledged attribute-based interaction model. Thus, programmers can specify interactions between devices in a declarative way, abstracting from details such as devices identity, number, or even their existence, without the need for a central controlling service.


I. INTRODUCTION
The Internet of Things (IoT) is nowadays an integral part of our daily life, and it is composed by many computational objects communicating with each other. These objects, usually called smart devices, can interact with the physical environment (by means of sensors and actuators), with users and with other computational systems. Indeed, the peculiarity of smart devices is their computational power: they are not just data collectors, but they can elaborate the collected data and send it to external entities. change or a signal from a sensor) by executing one or more actions, that can update the internal state of the device or act on the environment via an actuator. Of course, the effect caused by the rule action can trigger other rules, and so on.
However, in most current models and implementations of this paradigm, the ECA rules are stored on, and executed by, a central computing entity, possibly hosted on some server on the cloud (as in IFTTT, Samsung SmartThings, Microsoft Power Automate, Zapier and Google Home) and accessible via the Internet. Thus, the components of the IoT system cannot directly communicate: the coordination between devices is demanded to the central node/service.
Although simple, such a centralized architecture inherently affects crucial aspects like scalability, availability, privacy and security. Indeed, the increase of IoT smart devices (think of smart cities, smart farming, etc..) is going to produce a massive amount of data [3], and transferring, storing and processing this data in the cloud will overload servers and network channels. As a consequence, cloud servers will not be able to guarantee acceptable transfer rates and response times; moreover, sending all this data back and forth on the network is a big waste of energy. The dependence on Internet connections and on a central node/service hinders availability, which is also a critical requirement for many IoT and other pervasive and autonomic applications; e.g., a smart door lock may be stuck because the server is not reachable. Smart devices often deal with sensible and personal data (like health sensors, surveillance cameras, etc.) that the user would prefer to do not share with some untrusted server on the cloud; in fact, the need for a service in the cloud raises concerns about data sovereignty. Finally, the dependence on external services increases the attack surface of an IoT system; e.g., an attacker can open a house front door, taking advantage of some vulnerability on the server communicating with the door, and unknown to the user.
In this context, a natural evolution of the IoT would bring the underlying infrastructure closer to the so-called edge computing paradigm [3], [4], which aims to move the computation away from cloud data centers towards the ''edge'' of the network, i.e., the smart devices which are the sources of data. This approach allows to mitigate the previously mentioned issues, as it reduces data transfers between the edge and the center of the network -in fact, there can be no center at all.
At the same time, placing the application logic on many devices in a truly distributed and decentralized setting introduces new issues and challenges. In particular, it requires suitable mechanisms and abstractions for communication and coordination of (possibly large) ensembles of distributed components. In this respect, Attribute-based memory Updates (AbU) [5] is a time-coupled, space-uncoupled interaction model recently introduced for coordinating large numbers of components which are not supposed to have a global knowledge of the system, in the spirit of Attribute-based Communication (AbC) [6], [7]. The latter is a loosely coupled message-oriented interaction model specifically designed for coordinating large numbers of components. The key aspect of Attribute-based Communication is that the actual receivers are selected ''on the spot'' by means of predicates over node attributes; dually, a node can ''filter'' incoming messages by means of predicates. For instance, in AbC we have primitives to express ''send (the value of) expression e to all nodes satisfying predicate '' and, dually, ''receive the message x when it satisfies the predicate ''. Many interaction models, such as channels, agents, pub/sub, broadcast and multicast, can be readily implemented by using Attributebased Communication [7], [8].
In [5], the authors introduced the AbU calculus, a formal model aiming to integrate ECA programming with Attribute-based Communication. In this calculus, interactions are reduced to events of the same kind ECA programs already deal with, i.e., memory updates. More precisely, an AbU system is composed by a set of agents, called nodes, each endowed with a local memory (representing the local variables, sensors and actuators) and a set of ECA rules. When a rule of the form x 1 , . . . , x n : act is triggered on a given node due to a change in some local variable x i , the action act can update the memory of that node (like in normal ECA programming), but it can also update the memory of other nodes, selected upon their memories by the predicate . For instance, an AbU rule like the following: accessTime (@ role = logger) : log = log :: accessTime means ''when (my local) variable accessTime changes, append its value to the variable log of all nodes whose variable role has value logger''. Clearly, the update of log may trigger other rules on these (remote) nodes, and so on.
In this paper, we introduce AbU-dsl, a Domain Specific Language (DSL) based on the AbU calculus of [5]. This results in a new programming language merging the simplicity of ECA rules with distributed (decentralized) coordination and communication mechanisms, in the spirit of Attributebased Communication. Hence, AbU-dsl can serve as a reference language for programming IoT smart devices, in an easy yet powerful way.
For instance, the AbU rule introduced above can be encoded in AbU-dsl as follows: rule LogAccess on accessTime for all (ext.role == "logger") do ext.log = ext.log :: this.accessTime that is very concise and easy to understand. Paper Structure: In Section II we introduce AbU-dsl, a new ECA-inspired DSL extended with Attribute-based memory Updates. Its syntax and operational semantics are presented in Subsections II-A and II-B, respectively. In Section III we use AbU-dsl in some practical IoT scenarios. In Section IV we discuss how the DSL is compiled to a target language and deployed on a target architecture. Finally, in Section V we draw some conclusions, discuss related work and outline some future directions.

II. THE AbU LANGUAGE
AbU-dsl is a Domain Specific Language for the Internet of Things, particularly suitable for autonomic systems. It follows the Event Condition Action programming paradigm and, at the same time, it can be deployed in a truly distributed and decentralized network. Devices interaction is implemented by means of Attribute-based memory Updates [5], which is the memory-based counterpart of Attribute-based Communication [6]. In particular, AbU-dsl builds upon the AbU calculus [5], the archetypal calculus with Attribute-based memory Updates.
In the rest of the section, in order to present more concisely the DSL syntax and semantics, we adopt the following notation. The expression (x) * means zero or more repetitions of x, while (x) + means one or more repetitions of x. The expression [x] represents an optional occurrence of x, while x | y stands for either x or y. The symbol stands for ''is defined as''.

A. SYNTAX
An AbU-dsl program consists in a non-empty list of IoT devices, preceded by a (possibly empty) list of type declarations and followed by a (possibly empty) list of ECA rules. The syntax of AbU-dsl programs is given in Figure 1. We suppose to have a denumerable set Identifiers of identifiers (i.e., alphanumeric non-quoted strings), ranged over by Id, DevId and RuleId.
Each device Dev is equipped with some ECA rules that act on the device. These ECA rules are specified by means of rule references Refs (i.e., a non-empty list (RuleId) + of rule identifiers) written after the optional keyword has. The latter is optional since a node may have no specific rule acting on it (e.g., an actuator that can only be changed by external devices). Furthermore, a device has a unique identifier DevId, a description Descr (i.e., a quoted string describing the device functionalities), a resources declaration ResDecl (i.e., a non-empty list (PhysRes | LogRes | CompRes) + of internal resources) and an invariant Bexp (that is, a boolean expression), written after the optional keyword where, that resources have to fulfill (if present).
As an example, the following AbU-dsl code: watering : "A simple device managing a water pump" { # Resources declaration. physical input decimal moisture ... where # Device invariant. moistMinLevel <= moistMaxLevel } has openValve closeValve defines a device called watering that manages a water valve, in order to maintain the soil moisture level within an given interval (moistMinLevel and moistMaxLevel). The latter can be modified at run-time by the user, so the invariant moistMinLevel <= moistMaxLevel assures that only valid intervals can be specified. Furthermore, the device is equipped with two ECA rules openValve and closeValve, that open and close the water valve, respectively.
A resource can be physical, logical or compound. A physical resource PhysRes can be used either as input (input), modeling a sensor; or as output (output), modeling an actuator. An input resource is supposed to be read-only, an output resource is supposed to be write-only and a logical resource LogRes has no constraints. Logical resources can be used as local variables. Logical and physical output resources have to be declared with an initialization value Val, while physical input resources do not. All physical and logical resources have a name Id and a primitive type PType, that can be either: boolean, for a boolean resource, e.g., true or false; integer, for an integer resource, e.g., 42 or -42; decimal, for a decimal resource, e.g., 3.14 or -3.14; or string, for a string resource, e.g., "sTr1nG". Elements Val ∈ Values belong to primitive types.
A compound resource CompRes is a structured object, whose schema (i.e., the description of the object fields) is defined in a type declaration. A type declaration TypeDecl reserves an identifier CType, written after the keyword define, to the new compound type, and its schema is enclosed between curly brackets, after the keyword as. In particular, a schema FieldDecl is a non-empty list of field declarations, that can be: physical input PType, for physical input fields; physical output PType, for physical output fields; and logical PType, for logical fields. 1 When compound resources are declared (in a device), their fields that require initialization must be provided with a value (at declaration time). In particular, compound resources are initialized by a constructor Const, that is a list of comma-separated field initializations, of the form Id = Val(, Id = Val) * . A constructor may be empty, when no field requires initialization (i.e., physical input fields). Compound resource fields are accessed à la Python, namely by using square brackets.
As an example, the following AbU-dsl code: define PresenceSensor { mode : physical output string movement : physical input boolean } defines a compound resource having two fields, mode and movement. The first requires an initialization value, while the second does not, hence a resource prSen of type PresenceSensor can be initialized with the constructor PresenceSensor prSens = (mode = "idle"), inside a node resource declaration. The field movement can be accessed by writing prSens [movement].
Each ECA rule Rule has a unique identifier RuleId, that can be referred by multiple devices. Rules are guarded by an event Evt, which is a non-empty list of simple resources or compound resource fields. Note that, Evt represents a list of resources/fields that the rule continuously check for changes. When one of the resources/fields is updated with a new value, then one (or more) task, among those in the tasks list specified after the rule event, may be executed, depending on a given condition contained in the task.
As an example, the following AbU-dsl code: defines a rule named nightAlarm that is activated when either the resource light or the field movement of the (compound) resource prSens is modified. In a task Task, an action is performed when the condition Cnd is true. The optional modifier all means that the task may act on remote (external) devices. In this case, the condition and the action in the task may reference resources on external devices, by prefixing them with the ext. keyword. When the modifier all is omitted, the condition and the action are considered on the local (current) device only. With task conditions, the all modifier and local/remote resource access we can express Attribute-based memory Updates [5].
An action Act is a non-empty comma-separated list of assignments on different resources/fields. An assignment Assign can be on the local device this.Id = Exp or on a remote device ext.Id = Exp (this applies also for compound resources, with local this.Id[Id] = Exp and remote ext.Id[Id] = Exp field assignments). Notice that, on each node the assignments of an action are executed simultaneously as a single step, not sequentially.
As an example, the following AbU-dsl code: rule nightAlarm on light prSens[movement] for all (ext.node == "alarmRing") do ext.ring = true, this.status = "alarm" defines the behavior of the rule nightAlarm previously introduced. In particular, all devices tagged alarmRing are forced to ring the alarm (via a remote update ext.ring = true), while the current device changes status (via the local update this.status = "alarm").
An access to the resource Id on the local device can be made by writing this.Id (or just Id). Similarly, an access on the field of a local compound resource can be made by writing this.Id[Id] (or just Id[Id]). Conditions Cnd are boolean expressions that can access a resource Id on remote devices, by writing ext.Id, or by writing ext.Id [Id] in the case of compound resource fields access.
AbU-dsl is provided with foreign functions, allowing code written in the compilation target language (see Section IV for details) to be used inside AbU-dsl expressions. The syntax for foreign functions is foreign(FuncName, ParamLst), where FuncName is a quoted string containing the name of the function to call and ParamLst is a, possibly empty, comma-separated list of AbU-dsl identifiers or values. Note that, foreign functions are assumed to be pure w.r.t. AbU-dsl, so that they cannot introduce side-effects in the caller AbU-dsl program. The return value of foreign functions should be compatible with an AbU-dsl primitive type.
As an example, the following AbU-dsl code: rule computeVolume on radius, height for (true) do volume = foreign("math.Pow", radius, 2) * height * foreign("math.Pi") defines a rule that computes the volume of a cylinder when either its radius or its height change. The code make use of the Golang math library to retrieve the value of π and to compute exponentiation. Note that, we could had called a user-defined Golang function that directly computes the volume of the cylinder, using a single foreign instance. Finally, comments can be inserted inline, after the keyword #, while multi-line comments are enclosed within the delimiters \@ ... @\.
Detailed examples of AbU-dsl programs will be provided in Section III, while a complete presentation of the syntax can be found on the DSL GitHub repository. 2

1) SYNTACTIC SUGAR
To ease code writing, we can define the following macros on top of the previously defined syntactic constructs. With: we denote a rule that has default code Act to execute when the event happens, independently from tasks condition. This is a shorthand for: . . . ;Id n := Exp n in is dropped. Note that, the pre-processing does not perform any evaluation of the expressions, it is just a mere syntactic substitution.

B. SEMANTICS
In order to present the semantics of AbU-dsl, we need some auxiliary functions and definitions. The set of semantic values comprises integers (Z), decimals (Q), booleans (tt, ff), character strings (S) and an undefined value (⊥), namely the value In the following, we denote by ruleOf(RuleId) the ECA rule (code) that has RuleId as rule name in an intended 2 Available at https://github.com/abu-lang/abudsl. AbU-dsl program. Given a device Dev, we denote with inv(Dev) its invariant, if present. Similarly, given an ECA rule Rule, we denote with task(Rule) the set of its tasks, and with event(Rule) the set of resources in its event.

1) OPERATIONAL (SMALL STEP) SEMANTICS
Following [5], the semantics of AbU-dsl builds upon execution states and pools. A device state σ ∈ Identifiers − → V, is a map from resource (names) to values, while a device pool θ ⊆ n∈N U n is a set of updates. An update upd is a finite list of pairs (Id, v) ∈ U, meaning that the resource Id will take the value v after the commitment of the update.
Let Act = [this.]Id 1 = Exp 1 , . . . ,[this.]Id n = Exp n be a (local) task action, its evaluation Act in the device state σ returns an update. Formally, Act σ The evaluation semantics for value expressions Exp is standard. As we will see in a moment, the semantic function · is applied only to local task actions, that do not contain instances of external resources ext.Id.
Given a list Refs = RuleId 1 . . . RuleId k of ECA rules (names) and a set X of resources that have been modified, we define the set of active rules as Active(Refs, X ) namely the rules in Refs that listen on resources in X and, hence, that may be fired.
The local updates are the updates originated from the tasks of the active rules in Refs that act only locally (i.e., the modifier all is not present in the tasks condition) and that satisfy the task condition, namely, LocUpds(Refs, X , σ ) The satisfiability relation is defined as usual: σ | Bexp Bexp σ = tt (the evaluation semantics for boolean expressions Bexp is standard as well). Note that, the default updates, namely the updates originated by actions under the scope of default are local updates, by definition. When we have a task containing the modifier all, a remote device is needed to evaluate the task condition. In the AbU-dsl semantics, when a device needs to evaluate a task involving remote devices, it partially evaluates the task (with its own state) and then it sends the partially evaluated task to all other devices. The latter, receive the task and complete the evaluation, potentially adding updates to their pool. In particular, the partial evaluation of tasks works as follows. With {|Task| }σ we denote the task obtained from Task with each occurrence of resources [this.]Id in the task condition and the right-hand side of the assignments in the task action replaced with the value σ (Id). After that, each instance of ext.Id in the task condition and action is replaced with this.Id. Finally, the modifier all is dropped. For instance, consider the task Task for all(this.x < ext.x) do ext.y = x + ext.y Then, considering a device state σ [x → 1 y → 0], assigning 1 to x and 0 to y, we have that {|Task| }σ is: for (1 < this.x) do this.y = 1 + this.y Note that, once the task is partially evaluated and sent to other devices, it becomes ''syntactically local'' for the receiving devices and, hence, its action can be evaluated with the semantic function · .
Finally, the remote tasks are the pre-evaluated tasks of active rules in Refs whose condition contains all (i.e., tasks that require a remote device to be evaluated), namely, RemTasks(Refs, X , σ )

{|Task| }σ
Rule ∈ Active(Refs, X ) ∧ Task ∈ task(Rule) .n] we have that θ i is a device pool for Dev i .
The small-step operational semantics of a program Prg is modeled as a Labeled Transition System (LTS). In particular,

Prg
σ, θ α − → σ , θ means that the program Prg evolves, producing the label α. Here, labels are given by: where T is a finite (possibly empty) list of tasks and upd an update. The semantics is distributed, in the sense that each device does not have a global knowledge about the system. The semantics is depicted in Figure 2, where rules (Exec), (Exec-Fail), (Input) and (Propagate) model the evolution of single devices, while the rule (Comm) models the evolution of an ensemble of devices (i.e., of a program). To simplify the presentation of the transition rules, in Figure 2 we omit the list of ECA rules (code) that devices refer to.
The rule (Exec) executes an update picked from the pool; while a rule (Input) models an external modification of some resources. The execution of an update, or the modification of resources in general, may trigger some other ECA rules. Hence, after updating its state, a device launches a discovery phase, to find new updates to add to the local pool (or some pools of remote devices), given by the activation of some ECA rules. The discovery phase is composed by two parts, the local and the remote one. A device performs a local discovery by means of the function LocUpds, that adds to the local pool all updates originated by the activation of some local rules. Then, by means of the function RemTasks, the device computes a list of tasks that may update remote devices and sends it to all devices of the program. This is modeled with the labels upd T , produced by the rule (Exec), and upd T , produced by the rule (Input). On the other side, when a device receives a list of tasks, executing the rule (Propagate) with a label T , it evaluates them and adds to its pool the actions generated by the tasks whose condition is satisfied. Note that, not necessarily all devices have to modify their pool (indeed, a task condition may not hold in a remote device). The rule (Comm) synchronizes the whole discovery phase, originated by a change in the state of a device of the program. When a device executes an action originating only local updates, the rule (Comm) is applied with = , producing the label upd or the label upd (i.e., with an empty tasks list ). The latter, is matched by a label T = , that all devices can generate by applying the rule (Propagate). Attentive readers may notice that in the rule (Exec) we start the discovery on the resources that have been modified only (the set X performs such check), while in the rule (Input) we start the discovery on all resources (no checks on X ). This is due to the assumption that inputs from the environment reflect an actual change in some external components. 3 Finally, the semantics also checks the fulfillment of invariants, at run-time. Indeed, when an update would break an invariant, the rule (Exec) is not applicable; instead, the rule (Exec-Fail) is performed, that ignores the update (i.e., it is removed from the pool without commitment). This fact is observable with labels of the form upd .

2) WAVE SEMANTICS
On top of the operational semantics described above, we can define a big-step semantics, dubbed wave semantics, that hides internal computation steps. This semantics represents only state modifications resulting from inputs from the surrounding environment. An AbU-dsl configuration , is a pair consisting of an execution state and an execution pool , for a given AbU-dsl program. A configuration is said stable when no more execution steps can be performed, namely when all device pools in are empty. The wave semantics transforms stable configurations in stable configurations. Let − → * be the transitive closure of − →, without occurrences of labels of the form upd T , namely − → * denotes a finite sequence of internal execution steps (with the corresponding discovery phases), without interleaving input steps. The wave semantics for a program Prg is: The idea is that a stable system, in the state , reacts to an external stimulus upd by executing a series of tasks that propagate across the devices like a ''wave'', until it becomes stable again, in the state , waiting for the next stimulus. Note that, in the wave semantics inputs do not interleave with internal steps: the system needs to reach stability before processing the next input. If we allow arbitrary input steps during the computation, a system may never reach stability since the execution pools could be never emptied. This assumption has a practical explanation: in the IoT context, usually, external changes (in sensors) take much more time than internal computation steps [9].

C. OPTIMIZATION
The semantics described so far follows precisely the semantics of the AbU calculus [5], but it could be optimized for a more efficient implementation. Indeed, when a device of a program performs a local update (either an execution or an input), i.e., when the committed update does not trigger ECA rules on remote devices, the semantics in Figure 2 forces all devices to synchronize, by means of an empty discovery. In other words, when a device performs an update upd that does not trigger ECA rules on remote devices, i.e., when it emits a label upd (or upd ), then all other devices must perform a (Propagate) step matching . To improve the performance of AbU-dsl, we can slightly modify the language semantics, allowing devices to asynchronously execute local updates. This translates in applying the transition rule (Comm) only when T = , and to apply the following transition rule otherwise, i.e., when α ∈ {upd , upd }: Note that, the added rule does not change the semantics of AbU-dsl in Figure 2, but it improves performance since less device communications are performed.

III. IoT PROGRAMMING EXAMPLES
In this section, we provide some IoT application scenarios for AbU-dsl. In particular, we model in AbU-dsl IoT devices that need to autonomously interact with each other, without the need of a central controlling node.

A. SWARM OF ROBOTS
Consider a scenario where a swarm of robot drones is in charge of taking specific measurements, randomly picked in a large uninhabited area. Each drone is equipped with a battery that periodically needs to be recharged by returning to a docking station. It may happen that a drone runs out of energy before returning to the charging spot. In this case, the low-battery drone asks for help from its neighbors. If a drone has some energy to share and it is close enough to the requester, it will enter the ''rescue'' mode. A drone in ''rescue'' mode will reach the drone in distress, sharing with it some energy (this phase is not modeled in the example for space reasons). We can model this scenario (supposing to have four drones) in AbU-dsl as follows.
The position of drones is given by a user-defined type Coords, that has two fields latitude and longitude. For each drone we have an AbU-dsl device with a resource battery, indicating the battery level of the drone; a (compound) resource pos, indicating where is located the drone; a resource mode, indicating in which operative state is the drone; a resource help, indicating the position of a drone LISTING 1. A program modeling a swarm of robots. that needs help; and a resource threshold, indicating when another drone is considered close to the current. Formally, the AbU-dsl program modeling the drone-swarm scenario is depicted in Listing 1. Now suppose that the battery levels of the drones are the following: battery = 4 for droneA; battery = 81 for droneB; battery = 97 for droneC; and battery = 65 for droneD. We assume that drones have an embedded battery sensor that updates the drone resource battery.
The ECA rule batteryCheck says that when the current drone battery level is low (i.e., when battery < 5), then the current drone has to send to all neighbors (using all) that have some energy to share (i.e., that have ext.battery > 80) its position, performing a remote update (composed by the action at lines 53 -54).
In the example, droneA can fire the rule, since its battery level is low. Then, it pre-evaluates the task condition, yielding 4 < 5~and battery > 80, which is sent to the other drones, together with the pre-evaluation of the task action, namely help[latitude] = 2 and help[longitude] = 2. Among all possible receivers, only droneB and droneC are interested in the communication, since they are the only drones with battery level greater than 80. So, they both add to their pool the update (help[latitude], 2)(help[longitude], 2). This ends the discovery phase originated by droneA.
The rule setRescue, instead, is fired when a drone receives a help request (i.e., when its resource help changes) and basically checks if the current drone position is close to the requester drone position (by checking that the difference between latitude and longitude values is less than threshold). If it is the case, the current drone enters the rescue mode performing the local update mode = "rescue".
In the example, when droneB and droneC execute the update (help[latitude], 2)(help[longitude], 2) the task of the rule setRescue may be executed. For droneB this does not happen, since absint (12 -2) < 6 at line 60 is not satisfied (the drone is too far from droneA). Instead, the conditions absint (5 -2) < 7 and absint (2 -2) < 7 at lines 60 -61 are both satisfied for droneC, hence the latter can execute the rule task, adding to its pool the update (mode, "rescue").

B. SMART HVAC SYSTEM
We provide an AbU-dsl implementation of a Heating, Ventilation and Air Conditioning (HVAC) system. In this scenario we have three devices connected through a network: the HVAC control system, a temperature sensor, and a humidity sensor. To distinguish the devices, a logical resource node is used. In particular, node takes the values "system", "tempSens" and "humSens" on the HVAC control system, the temperature sensor and the humidity sensor, respectively.
The AbU-dsl code modeling the scenario is depicted in Listing 2. The rule notifyTemp on the temperature sensor device is simply responsible of signaling changes to the resource temperature to the HVAC control system, by selecting all devices that have node equals to "system". The rule notifyHum do the same for the resource humidity on the humidity sensor device.
All other rules in Listing 2 are related to the HVAC control system. The latter activates heating and air conditioning according to the values of temperature and humidity, received by the sensors. In particular, when the temperature is lower than 18 • C (this.temperature < 18) the rule cool activates the heating (this.heating = true). Instead, when the temperature is greater than 27 • C (this.temperature > 27), then the rule warm deactivates the heating (this.heating = false). The air LISTING 2. A program modeling an HVAC system. conditioning is turned on (this.conditioning = true), by means of the ECA rule dry, when the humidity exceeds the upper bound of the Givoni's comfort zone [10], i.e., when the condition at lines 28 -29 is satisfied. The HVAC control system is also bestowed with a physical button for manually stopping the air conditioning. Indeed, the rule stopAir stops the air conditioning (this.conditioning = false) when the button is pressed (this.airButton is true). Finally, by means of the invariant not (conditioning and heating) we specify that no update can result in the activation of both heating and air conditioning simultaneously.
Note that, the same problem can be modeled by means of a single device, embedding the two sensors and the control system. We can model this scenario in AbU-dsl with a single device comprising all resources introduced in Listing 2 and transforming remote rules into local ones. This highlights the flexibility of AbU-dsl, that is able to model both distributed and centralized ensembles of devices.

C. PROGRAMMING A RASPBERRY Pi
In this last example, we show how AbU-dsl allows to easily program a Raspberry Pi equipped with physical sensors and actuators. We actually tested the example on real devices, compiling the AbU-dsl program of Listing 3 with the AbU compiler, that we will present in Section IV. In particular, the code has been deployed on two Raspberry Pi 3b, one equipped with a brushed DC motor with L293 driver; and the other equipped with two LEDs and two GPIO buttons.
The wheel device (i.e., one of the two Raspberry Pi), that is equipped with the brushed DC motor, contains a (compound) resource motor initialized with the actual physical PIN numbers connected to the Raspberry (fields forwardPin and backwardPin) and the initial electric tension (expressed by an integer between 0 and 255) applied to the PINs (fields forwardPace and backwardPace). These tensions are used to set the rotation speed of the motor, as described in the L293 motor datasheet.
When motor[forwardPace] is greater than 0 then the motor spins clockwise, with the given pace, while when motor[backwardPace] is greater than 0 then the motor spins counterclockwise, with the given pace. The specification of the L293 driver does not describe what happens when both PINs are simultaneously powered, so, to prevent motor damages, we enforce at programming level (i.e., by means of AbU-dsl code) that only one PIN at a time can be powered, by using the invariant at lines 20 -21. The device is controlled by two ECA rules drive and brake: the first continuously increases the speed of the motor on a given spinning direction; while the second stops the motor rotation, when the maximum speed is reached.
To change the motor rotation direction we need to press a combination of buttons on the device controls (i.e., the other Raspberry Pi), that is equipped with two GPIO buttons and two one-color LEDs. Similarly to the previous case, we have to initialize buttons and LEDs resources with the physical PIN numbers connected to the Raspberry (setting the pin field of the LED and GPIOButton resources). Then, the boolean field status of LED resources can be set to true and false indicating that the LED is on and off, respectively. Similarly, the boolean field status of GPIOButton resources can be set to true and false indicating that the button is pressed and released, respectively.
Buttons are used to control the rotation direction of the motor and to turn on and off the LEDs. In particular, the ECA rule changeDir sets the motor spin counterclockwise, by performing the actions ext.motor[forwardPace] = 0 and ext.motor[backwardPace] = 1, when the first button is pressed and the second one is not pressed; while the rule sets the motor spin clockwise, by performing the actions ext.motor[forwardPace] = 1 and ext.motor[backwardPace] = 0, when the second button is pressed and the first one is not pressed. Note that, the rule potentially affects all devices in the network that have a brushed DC motor, by selecting the devices such that ext.node == "DCmotor". Finally, the ECA rule toggleLed toggles the status of the two LEDs (lines 46 -47), when both buttons are pressed together (buttonA[status] and buttonB[status]).

IV. A DISTRIBUTED IMPLEMENTATION
AbU-dsl is a (decentralized) distributed language, hence any implementation has to deal with the intrinsic issues of distributed systems. In particular, by the CAP theorem [11] we cannot have, at the same time, consistency, availability and partition-tolerance. Hence, some compromises have to be taken, depending on the application context. For instance, in a scenario with low network traffic we can aim for correctness, implementing a robust, but slow, communication protocol. Vice versa, when devices exchange data at a high rate (or when the network is not stable), communication should take very short time, hence we may prefer to renounce to consistency in favor of eventual consistency. For these reasons, a flexible and modular implementation is mandatory, where modules can be implemented in different ways, depending on the application context. Hence, we designed a modular architecture suitable to implement AbU-dsl devices, as depicted in Figure 3. An AbU-dsl device consists in a device state (mapping resources to values), a device pool (a set of updates to execute) and a list of ECA rules (modeling the device behavior). An ECA Rules Engine module is in charge of executing the updates in the pool and to discover new rules to trigger, potentially on remote devices (distributed discovery). This module also implements Attribute-based memory Updates and deals with IoT inputs (from sensors) and outputs (to actuators), which are accessed by means of a dedicated interface. A separate IoT Drivers module translates low-level IoT devices primitives to high-level signals for the rule engine and vice versa. The Distribution module is in charge of joining a cluster of AbU-dsl devices and exchanging messages with them. It embodies all distributed infrastructure-related aspects, that can be tuned to meet the desired context-related requirements. Moreover, it provides the communication APIs needed by the rule engine to implement the (distributed) discovery phase (and, in turn, Attributed-based memory Updates). For instance, the labels upd T and upd T of the AbU-dsl semantics may generate a broadcast communication.
We opted for an exogenous language design, meaning that the DSL provides an abstraction layer for an existing full-fledged general-purpose programming language. In this way, we can reuse existing code (or fast-developing new code), demanding to AbU-dsl only distributed communication and coordination aspects. Local computations on devices are implemented using the underlying general-purpose language.
As a first prototype, we have developed GoAbU, an implementation of AbU-dsl in Golang, together with several open-source support tools for AbU-dsl: abuc, an AbU-dsl compiler; and abusim, a simulator for AbU-dsl devices. The extension of AbU-dsl source code files is .abu. The code of GoAbU and these tools is publicly accessible on GitHub. 4

A. GOLANG-BASED PROTOTYPE
GoAbU 5 implements the architecture described in Figure 3. In particular, the ECA Rules Engine module is built on top of the Hyperjump's Grule [12] library, a stable ECA rules engine providing an incremental evaluation strategy. Incremental evaluation enhances efficiency in rule evaluation by avoiding the repetition of previous computations and by exploiting the structural similarity between rules.
The IoT Drivers module is built on top of GOBOT [13], a Go library for the IoT ecosystem, with a great availability of device drivers. The library provides data abstractions for programming IoT devices in a simple way. Devices behavior is specified by implementing callback functions, that can be compiled to all supported platforms (e.g., arm64).
Finally, the Distribution module is based on HashiCorp's Memberlist [14], a popular Go library for cluster membership and failures detection that uses a SWIM-like gossip protocol [15]. In such protocol, to achieve a lightweight and scalable cluster membership solution, every membership update message (carrying information about joins, leaves or failures) is distributed by piggybacking the update on the messages used to implement the failure detection protocol. This allows for solutions relying on cluster membership, with eventual consistency, that are able to scale on larger systems.
GoAbU provides a correct implementation of the semantics depicted in Figure 2, where the whole discovery phase is executed in a single atomic step. This implies that the partially evaluated tasks must be delivered to other devices by means of an atomic (''all or none'') operation, as when reliable broadcast is performed. In the GoAbU implementation, we achieve reliable broadcast by using a customized transactional two-phase commit protocol [16].

B. COMPILATION AND DEPLOYMENT
In this section, we describe the compilation and deployment pipeline for AbU-dsl programs. A graphical representation of the whole compilation process is reported in Figure 4.
Before compilation, we need a little pre-processing. First, we have a de-sugaring phase, that removes the syntactic constructs added to simplify coding, as explained in Subsection II-A. Then, we have a splitting phase, that separates the devices of the program, together with the corresponding rules code. In particular, a program consisting in n devices will result in n ''partial'' AbU-dsl programs consisting in one device only. Finally, the code of the rules referenced by a device is copied in the corresponding singledevice program.
The resulting ''partial'' AbU-dsl programs are compiled to the target language. The compiler abuc 6 supports different targets, that may yield standalone or intermediate compilations. In the first case, the AbU-dsl program is translated to the target machine code and the IoT run-time is added, obtaining a standalone executable. In the second case, the AbU-dsl program is translated to the target programming language code, so that it can be extended or linked into other projects.
At the time of writing, abuc supports the following targets: go, namely the output of the compilation is a ready-to-use 6 Available at https://github.com/abu-lang/abuc. GoAbU code, that can be imported to other Golang project and manually customized; amd64, namely the output of the compilation is an amd64 executable; and arm64, namely the output of the compilation is an arm64 executable.
As a matter of example, by using the following command on the AbU-dsl program in Listing 2: $ abuc -o hvacGo -t go hvac.abu we obtain three Golang source files hvacGo-system.go, hvacGo-tempSens.go and hvacGo-humSens.go. The first contains (the GoAbU code of) the system device and the rules cool, warm, dry and stopAir; the second contains the tempsSens device and the rule notifyTemp; while the third contains the humSens device and the rule notifyHum.
The compiler accepts also an optional parameter -c (or -config), that is used to specify a configuration files for linking IoT libraries, as explained next.

1) LINKING IoT LIBRARIES
In order to map (low-level) IoT resources with (high-level) AbU-dsl resources, we have to link devices driver libraries. In particular, for each custom type defined in AbU-dsl (e.g., the DCMotor type in Listing 3) we have to link a corresponding driver written in the target language. This information is provided to the compiler by using a .json configuration file. In Listing 4 we show an example of such configuration file, for the AbU-dsl program in Listing 3. The first line of the file specifies the necessary libraries (in the example, the GOBOT driver for the Raspberry Pi), while the second line imports the target language file containing the interfaces to which AbU-dsl types should be mapped to (in the example, we have a Golang implementation of the GOBOT-based drivers). The third line VOLUME 10, 2022 LISTING 4. An configuration file example for the AbU-dsl compiler.
is an initialization string that is required by the specific target GoAbU. The rest of the file comprises the mappings. For each custom AbU-dsl type we specify the corresponding target language interface (in the example, for instance, we associate the AbU-dsl type DCMotor with the Golang struct L293Motor) and the mappings for the AbU-dsl type fields (in the example, for instance, we associate the field forwardPin with the Golang resource fPin of L293Motor). Finally, Args specifies the order of the parameters required by the target language type constructor.
As a matter of example, by using the following command on the AbU-dsl program in Listing 3: $ abuc -t arm64 -c config.json rasp-pi.abu where config.json is the configuration file in Listing 4, we obtain two arm64 executables 7 wheel and controls. The first can be deployed on the Raspberry Pi 3b equipped with the brushed DC motor, while the second can be deployed the other Raspberry Pi equipped with the two LEDs and the two GPIO buttons.

C. SIMULATION ENVIRONMENT
In order to test AbU-dsl implementations, we have developed abusim, 8 a simulator that automatically deploys and executes AbU-dsl devices. The simulator is configured by means of a dedicated .yaml file, and can be used with different 7 File names are taken from the devices in the source code if the option -o is not present. 8 Available at https://github.com/abu-lang/abusim.
implementations of AbU-dsl. Indeed, it is sufficient to provide the simulator with Docker images containing the devices code (e.g., GoAbU compiled code).

V. CONCLUSION
In this paper we have introduced AbU-dsl, a new Domain Specific Language for the IoT merging the simplicity of ECA programming with Attribute-based memory Updates [5]. The latter is a new time-coupled, space-uncoupled interaction mechanism where nodes communication is performed without a global knowledge of network participants, and it fits neatly within the ECA programming paradigm. We have shown how practical IoT systems of smart devices can be easily programmed in AbU-dsl, even in the case of complex scenarios. Then, we have described the compilation and deployment process of an AbU-dsl program, considering a particular target (implementation) language, i.e., Golang. A prototype implementation of AbU-dsl in Golang (i.e., GoAbU), together with a compiler and a simulation environment, is publicly available in the language GitHub repository: https://github.com/abu-lang

A. RELATED WORK
To the best of our knowledge, the only work aiming at merging the ECA programming paradigm with attribute-based interaction is [5], which is indeed the theoretical model at the basis of the DSL presented in this paper. In turn, the model of [5] has its root in the AbC calculus, introduced and studied in [6], [7], and [8] as a core calculus for SCEL [17]. Various extensions of AbC has been proposed [18], [19], as well as correct implementations in Erlang and Golang [20], [21], [22]. Concerning ECA programming for IoT systems, a notably example is IRON [23], whose formal semantics is defined in [9] and [24]. Most works dealing with ECA rules try to assess properties such as termination, confluence, absence of redundant or contradicting rules. To this aim, [23], [25], and [26] implement verification mechanisms to check these properties on IRON programs. Other works propose approaches to verify systems based on ECA rules by using Petri Nets [27] and Binary Decision Diagrams [28]. In [1] and [29], the authors present a tool-supported method for verifying and controlling the correct interactions of ECA rules. A formalization of an ECA rule-based system is provided in order to transform the translation into a Heptagon/BZR program.
Finally, recent work [30], [31] on the Reactive Data model adopts a declarative attribute-based interaction similar to AbU-dsl's. In this model, ECA rules are given by declarative Response Relations, while attribute-based interaction is obtained by using dynamic end-points of the relations, defined by graph query languages. However, these approaches are not intended to be used for distributed programming.

B. FUTURE WORK
First, we plan to develop a type system for AbU-dsl, with the aim of performing devices and ECA rules well-formedness checks, together with expressions type checks. Furthermore, we plan to extend the AbU-dsl compiler, targeting more languages and architectures. Note that, AbU-dsl can be considered as a target language for the implementation of other distributed languages, e.g., agent based, hence we may think of developing compilers for other known DSL, e.g., JADEL [32], to AbU-dsl. In order to guarantee termination of AbU-dsl programs, we plan to implement a static verification mechanism to ensure stabilization, as defined in [5] for the AbU calculus. Similarly, we may implement for AbU-dsl verification mechanisms for safety and security IoT requirements, defined, for instance, in terms of behavioral equivalences (e.g., bisimulations) between AbU-dsl devices, as done in [33]. Along this line, in IoT systems it is often important to guarantee that inputs are processed within precise time bounds; to this end, following [34], we can think of adding quantitative aspects to the semantics of AbU-dsl, in order to provide precise estimations of stabilization times.
The smooth integration of an attribute-based interaction mechanism within the ECA paradigm should simplify the porting to the distributed setting of many known results and techniques from the ECA literature. In particular, we are interested in porting to AbU-dsl the verification techniques developed for ECA languages, such as IRON [25], [26], [27].
Another interesting issue is distributed runtime verification, in order to detect violations at runtime of given correctness properties, e.g., expressed in temporal logics like CTL or the µ-calculus [35]. Finally, a new generalization of Attribute-based Communication has been presented in [36]; it could be interesting to investigate how to apply that interaction model to the ECA programming.