Table of contents
Many integrations create multiple devices in the gateway. This section, on the basis of examples, will explain how to add multiple devices with different types and capabilities.
WARNING: this function is available only on Home Center 3, Home Center 3 Lite and Yubii Home with firmware 5.030 or higher.
Overview
When connecting to an IoT cloud service, other hubs, or gateways, you will usually receive a list of devices. They can be represented as a single family with the hub as a parent and each device as a child (also called ‘endpoint’). Each child must be assigned a proper type (available in the FIBARO System) to ensure it is compatible with scenes, panels, voice assistants, etc.
Sample model
In this example, we will use a sample response from an HTTP API of a hub that aggregates IoT devices. The response with a list of devices presents as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[ { "uid": "b59c67bf196a4758191e42f76670ceba", "name": "bedroom light", "type": "lightBulb", "state": 1 }, { "uid": "934b535800b1cba8f96a5d72f72f1611", "name": "living room", "type": "dimmableBulb", "state": 80 }, { "uid": "2be9bd7a3434f7038ca27d1918de58bd", "name": "bedroom", "type": "temperatureSensor", "state": 21.4 } ] |
This data can be represented in the FIBARO System in the following structure:
Structure explanation:
com.fibaro.deviceController
will represent the hub itself. Its job is to connect to the hub, parse the response data, and create child devices. Children will have types as below:com.fibaro.binarySwitch
will represent the device of alightBulb
type.state
field is mapped tovalue
property.com.fibaro.multilevelSwitch
will represent the device of adimmableBulb
type.state
field is mapped tovalue
property.com.fibaro.temperatureSensor
will represent the device of atemperatureSensor
type.state
field is mapped tovalue
property.
Defining class for a child device
In Quick App you can define a new class as shown below:
1 |
class '' () |
Classes in Quick Apps support inheritance. It means that your class can extend other existing classes.
When defining a new class you have to implement its constructor (by defining a special method __init()
).
To use Quick Apps built-in mechanism for handling child devices, it has to be derived from class QuickAppChild
.
The example below presents a definition of a class called MyBinarySwitch
, which derives from QuickAppChild
class:
1 2 3 4 5 6 7 8 9 10 11 |
-- Sample class for handling binary switch logic. You can create as many classes as you need. -- Each device type you create should have its own class which inherits from the QuickAppChild type. class 'MyBinarySwitch' (QuickAppChild) -- __init is a constructor for this class. All new classes must have it. function MyBinarySwitch:__init(device) -- You should not insert code before QuickAppChild.__init. QuickAppChild.__init(self, device) -- You must call a constructor from the parent class self:debug("MyBinarySwitch init") end |
Classes are defined to represent child devices. It is the best practice to create a class for each device type.
For example, if you want to handle children with types: com.fibaro.binarySwitch
, com.fibaro.multilevelSwitch
and com.fibaro.temperatureSensor
, you should define three classes, one for each of them.
Defining method for new classes
Defining new methods for your classes is done the same way as for the QuickApp
class.
An example of a class with all methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
-- Sample class for handling binary switch logic. You can create as many classes as you need. -- Each device type you create should have its own class which inherits from the QuickAppChild type. class 'MyBinarySwitch' (QuickAppChild) -- __init is a constructor for this class. All new classes must have it. function MyBinarySwitch:__init(device) -- You should not insert code before QuickAppChild.__init. QuickAppChild.__init(self, device) -- You must call a constructor from the parent class. self:debug("MyBinarySwitch init") end function MyBinarySwitch:turnOn() self:debug("child", self.id, "turned on") self:updateProperty("value", true) end function MyBinarySwitch:turnOff() self:debug("child", self.id, "turned off") self:updateProperty("value", false) end function MyBinarySwitch:hello() self:debug("hello from child device", self.id) end |
Because the MyBinarySwitch
was derived from QuickAppChild
class, you can use all QuickAppChild
methods (which are described further in this document).
Mapping actions to methods works the same as in a standard Quick App. So in this example, sending turnOn
action to a child device represented by MyBinarySwitch
class, will automatically call MyBinarySwitch:turnOff
method.
Creating a new child device
After defining all classes, you can now create child devices using QuickApp:createChildDevice
method like in the example below:
1 2 3 4 5 6 7 8 |
function QuickApp:createChild() local child = self:createChildDevice({ name = "myChild", type = "com.fibaro.binarySwitch", }, MyBinarySwitch) self:trace("Child device created: ", child.id) end |
This will create a new device in the system with the type com.fibaro.binarySwitch
and the name myChild
. In other words, it will create an instance of MyBinarySwitch
class. Every new child instance is automatically stored in QuickApp.childDevices
member table. So you can access them like this:
1 2 3 4 5 6 7 |
function QuickApp:printChildDevices() -- Print all child devices. self:debug("Child devices:") for id,device in pairs(self.childDevices) do self:debug("[", id, "]", device.name, ", type of: ", device.type) end end |
Initializing child devices on Quick App startup
When Quick App starts it needs to initialize its child devices. It must know how to map children types into proper classes. This initialization will fill QuickApp.childDevices
table.
Children initialization has to be done in QuickApp:onInit
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function QuickApp:onInit() self:debug("QuickApp:onInit") -- Setup classes for child devices. -- Here you can assign how child instances will be created. -- If type is not defined, QuickAppChild will be used. self:initChildDevices({ ["com.fibaro.binarySwitch"] = MyBinarySwitch, }) -- Print all child devices. self:debug("Child devices:") for id,device in pairs(self.childDevices) do self:debug("[", id, "]", device.name, ", type of: ", device.type) end end |
If you need to map multiple types into one class, it can be done like this:
1 2 3 4 |
self:initChildDevices({ ["com.fibaro.binarySwitch"] = MyBinarySwitch, ["com.fibaro.multilevelSitch"] = MyBinarySwitch, }) |
WARNING: if there is no class mapping for a type,
QuickAppChild
class will be used to create its instance.
Accessing parent member
WARNING:
self
inside of QuickApp methods is not the sameself
available in other classes.
It is a common mistake to treat self
as a global variable. self
refers to an instance of the current class (like for example this
in Java language). For example, if you have defined ‘login’ Quick App variable in the parent, you will not be able to access it using self:getVariable
inside your child’s class (yes, child devices can have their own variables).
Example of using self
in parent with defined ‘login’ variable and child without the variable:
1 2 3 4 5 6 7 8 9 10 |
function QuickApp:printLogin() local login = self:getVariable("login") self:debug(login) -- will print login variable end function MyBinarySwitch:printLogin() local login = self:getVariable("login") self:debug(login) -- will print nothing end |
You can access parent member inside a child class by using QuickAppChild.parent
. With this knowledge you can apply a simple fix to the previous example:
1 2 3 4 5 6 7 8 9 |
function QuickApp:printLogin() local login = self:getVariable("login") self:debug(login) -- will print login variable end function MyBinarySwitch:QuickApp() local login = self.parent:getVariable("login") self:debug(login) -- will print login variable from the parent end |
This way you can share parent resources with the children. For example, you can set up TCP client in the parent, and reuse it in your child. There is no need to set it up in each child separately.
Keeping your devices synchronized
Adding child devices is usually not a one-time action in the Quick App lifecycle. When we’re connected to a hub, its list of devices may change over time. Some devices may be added, some removed. The Quick App model should reflect the reality. It is necessary to distinguish which devices were already added to the gateway, which to add, and which need to be removed.
Let’s take a look at our sample model once again:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[ { "uid": "b59c67bf196a4758191e42f76670ceba", "name": "bedroom light", "type": "lightBulb", "state": 1 }, { "uid": "934b535800b1cba8f96a5d72f72f1611", "name": "living room", "type": "dimmableBulb", "state": 80 }, { "uid": "2be9bd7a3434f7038ca27d1918de58bd", "name": "bedroom", "type": "temperatureSensor", "state": 21.4 } ] |
When downloading the device model from a hub, you can store the original identifier for this device (assigned by the connected hub). Then you can create a mapping for our sample model, which may look like this: <hub device id>
-> <hc device id>
. This will enable you to check which device on the gateway represents a particular device on the hub. You have to do this on child device creation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function QuickApp:createChild(name, type, uid) local child = self:createChildDevice({ name = name, type = type, }, MyBinarySwitch) self:trace("Child device created: ", child.id) self:storeDevice(uid, child.id) end function QuickApp:storeDevice(uid, hcId) self.devicesMap[uid] = hcId -- Save devicesMap, so you can restore it after Quick App restart. -- Just put self.devicesMap = self:getVariable("devicesMap") in onInit method. self:setVariable("devicesMap", self.devicesMap) end |
Now by using devicesMap
you can properly synchronize your devices. It can help you with updating properties as well.