X10 Home Automation using a Scala internal DSL
Scala has been designed to enable the implementation of Domain Specific Languages. This is surely one of its major strength (see DSLs – A powerfull Scala feature for more information).
This article is an attempt to develop a very simple internal DSL dealing with the X10 home automation protocol (as done here on the top of Oslo).
Here is the syntax (not sure that it reflects exactly the X10 protocol, but it's enough for our example ...):
First step: we need a trait that offers all the X10 commands. Any class or trait that needs some X10 capabilities will have to extend (i.e.mix) this X10DSL trait:
To achieve this we will only need to parameterize our X10Command class as follows: The X10Command class is now an abstract class because the playCommand function is not defined. It's a case class because we need to extend it as follows: The X10DSL trait must be changed: These changes ensure that the behaviors of the on and off command will remain unchanged because the Unit type has been used to parameterize the X10Command[T].
In order to implement the level parameter of the bright and dim commands, we will parameterize the X10Command class with a new Level case class: When invoked, the level function forward the percentage parameter to a given function f (passed when constructing the Level instance).
Let's now modify the dim and bright objects so that they extend X10Command[Level] : The playCommand function must be overridden: it has to return a Level instance that is responsible for capturing the level parameter.
Before doing this, let's define the following private function (done for the dim command only, but it's the same for the bright command): The playCommandDim function wraps the invokation of the X10Driver. It can easily be curried and that's exactly what we are going to do now.
The idea behind all this is to give the playCommandDim function to the Level instance in a curried form.
The goal is to ensure that the capture of the level parameter (through the invokation of the Level.level(parameter:Int) function) will forward the parameter value to the X10Driver through the invokation of the playCommandDim function.
We need only one additional line of code: This way, the Level instance exposes the level function (i.e. the feature of our internal DSL) and plugs it with the playCommandDim intermediate function that is responsible for invoking the underlying X10 driver.
Do the same for the bright command and you'll be able to compile and run the following code: Here is the complete source code: That's the end of the exercise :))
Hope you enjoyed it !!!
This article is an attempt to develop a very simple internal DSL dealing with the X10 home automation protocol (as done here on the top of Oslo).
Here is the syntax (not sure that it reflects exactly the X10 protocol, but it's enough for our example ...):
- on [house_code] [unit_code]
where [house_code] is a letter between A and P (16 values) and [unit_code] an integer between 1 and 16 or All (for any devices). - Idem for the off command
- It's the same for bright and dim commands except that an additional level parameter is required:
bright A 12 level 80
Here, the expected level value must between 0 and 100 (it's a percentage)
Preparation steps
Consider the following (very simple) X10 driver: Then our goal is to implement a Scala internal DSL that allows defining a list of commands (as specified above) that will invoke the X10Driver.sendCommand methods in the end.First step: we need a trait that offers all the X10 commands. Any class or trait that needs some X10 capabilities will have to extend (i.e.mix) this X10DSL trait:
Handling the X10 house and unit codes
Now, by changing the implementation of the X10Command class, we will give to all those commands the ability to:- take a house code and a unit code as parameter
- wrap a string based identifier
Adding the level parameter for both the bright and dim commands
To achieve this we will only need to parameterize our X10Command class as follows: The X10Command class is now an abstract class because the playCommand function is not defined. It's a case class because we need to extend it as follows: The X10DSL trait must be changed: These changes ensure that the behaviors of the on and off command will remain unchanged because the Unit type has been used to parameterize the X10Command[T].
In order to implement the level parameter of the bright and dim commands, we will parameterize the X10Command class with a new Level case class: When invoked, the level function forward the percentage parameter to a given function f (passed when constructing the Level instance).
Let's now modify the dim and bright objects so that they extend X10Command[Level] : The playCommand function must be overridden: it has to return a Level instance that is responsible for capturing the level parameter.
Before doing this, let's define the following private function (done for the dim command only, but it's the same for the bright command): The playCommandDim function wraps the invokation of the X10Driver. It can easily be curried and that's exactly what we are going to do now.
The idea behind all this is to give the playCommandDim function to the Level instance in a curried form.
The goal is to ensure that the capture of the level parameter (through the invokation of the Level.level(parameter:Int) function) will forward the parameter value to the X10Driver through the invokation of the playCommandDim function.
We need only one additional line of code: This way, the Level instance exposes the level function (i.e. the feature of our internal DSL) and plugs it with the playCommandDim intermediate function that is responsible for invoking the underlying X10 driver.
Do the same for the bright command and you'll be able to compile and run the following code: Here is the complete source code: That's the end of the exercise :))
Hope you enjoyed it !!!
- Tags:
