Virtual Devices

Fibaro Home Center controllers use a virtual device(VD) to represent equipment external to the home center, typically equipment that can be managed over an IP network using the web’s HTTP protocol. The VD user interface is defined by creating rows containing either labels, one or more buttons (per row) or a slidebar. Fibaro provide a good description of how to create the virtual device button and label layout here. Additional output options allow updates to the current icon or to use a log function that briefly displays a message over the icon on the devices listing.

Note: Some external equipment may be managed via a “plug-in”, this is not the same as a virtual device, a plug-in is not restricted to the virtual device shell and functions provided by Fibaro.

Sample Virtual Device

Using the "Download Sample VD" button below, you can download the virtual device used in the examples on this page. Once downloaded install the VD in your HC2 as follows:

  1. go to "Devices", select "Add or remove device"
  2. In the section "To add the Virtual Device" click on browse to find the downloaded .vfib file. Once this has been selected the virtual device will be installed.
  3. Configure the device for your environment, change the ip addrress and port numbers to that of your target server and application
Download Sample VD

Have fun experimenting.

Virtual Device ID

All Fibaro devices have an id number associated with them, virtual devices are no exception. A virtual device can obtain it’s id via a VD specific API function getSelfId():

FunctionDescription
fibaro:getSelfId(); Returns the device Id of the virtual device calling this function

For a scene to interface with the VD it will need to know this device id. One technique is to set a global variable in the VD’s mainline, as in the following example (note the variable, here "vdSampId", must have been declared to the Fibaro system via the global variables panel).

   local devId = fibaro:getSelfId();
   fibaro:setGlobal("vdSampId", devId);
   fibaro:sleep(24*60*60*1000);

Note: the sleep prevents execution of the mainline every second (the default action).

The label

As the Fibaro tutorial instructs, create a label by ticking the label box in the "choose set of buttons" section of the virtual device's Advanced tab. Click the "Add set" button to create a label row on the VD ready for your configuration.

Label added and configured

In the UI the label row will appear as two elements. A fixed prefix, the “Label:” field in the VD label configuration and then variable text. This variable text is set programmatically by assigning a value to a variation on the labels configured "ID:" i.e. variable ui.configured_id.value e.g. when ID: in the config is set to "outputLabel" by using:

   local devId = fibaro:getSelfId();
   fibaro:call(devId, "setProperty", "ui.outputLabel.value", "Hello World");
Label appearance on the mobile UI

The result as displayed on the mobile based Home Center app is shown right.

 

 

Note also that by selecting “Main” in the label's configuration (see above), the label prefix and variable text will also be displayed with the VD’s icon in the devices listing.

While the label is an important feature of the UI, it’s contents are also available to scenes (provided they know the VD’s id) e.g.

   local devId = fibaro:getGlobalValue("vdSampId");
   local data = fibaro:getValue(devId, "ui.outputLabel.value");

For clarity, getValue() retrieves the variable text portion of the VD’s label only. With the steps taken so far, data now contains “Hello World” (i.e. not “Sample Hello World”). Finally a scene can also use fibaro:call( ,“setProperty”,) but exercise caution using this technique to ensure the VD does not lose the external device state, a better choice might be to let the scene press a button (see below) which then updates the label.

Buttons

Buttons are created in a similar manner to labels, with the option of having multiple buttons per row. Using a button on the virtual device UI can cause requests to be sent to the external equipment over a TCPIP connection, the IP address and TCP port number for accessing the external device are part of the VD’s configuration. A number of options are available when a button is pressed:

  1. A fixed data string associated with that button, through the buttons configuration, is sent to the configured IP address and TCP port. This string must exactly follow the protocol requirements of the external device, typically HTTP statements.
  2. Lua code can be invoked to: Note the Lua code can choose to use either hardcoded or configured values for the target application.
Thumbnails Icons

When pressed the button icon will replace the VD icon, so it is worthwhile, if not making unique icons then at least making subtle differences to the icons used. Note Fibaro calls them icons but they are in fact simple 128x128 pixel images (I have only seen .png files, .jpg and others may be accepted but there is no forced requirement for .ico format images). You can easily make fixed dimension images yourself using GIMP (the free OpenSource graphics software), see our article on Thumbnails and Icons.

Using Character String Based Requests

This technique is a fire and forget approach, you define a fixed format request that is to be sent to the configured target application e.g. if you are sending data to a web server you will need to write the HTTP header and any body-payload required. This is the simplest technique for sending requests and there are lots of examples in the forum that you can use for inspiration.

A word of warning: the fibaro example referenced above gives the impression that encoding a password using base64 is secure, this is not the case! Base 64 is easily decrypted. With no HTTPS support you should only send userids and passwords on the local LAN, never across the Internet.

Button with Text String

In the example shown, pressing the button labelled "String Based Request" will cause the configured string "GET / HTTP/1.0" to be sent to the server asis, nothing more nor less.

The Fibaro tutorial article also illustrates how you can use your browser to capture the text sent to a server, thus data sent for any browser action can be cut-and-paste into the Fibaro VD button's string field. Transmitted data can also be viewed through standard "developer tools" on all of the major browsers without the need for add-ons.

Using Net.FHttp

The configured IP address and Port number for the target application can be extracted using:

   local devId = fibaro:getSelfId();
   local ipAddr = fibaro:getValue( devId, "IPAddress");
   local tcpPort = fibaro:getValue( devId, "TCPPort");

Note: ipAddr can also be replaced by a website domain name (exclude any "http://" prefix).

A connection to the target application is established and a reference to a C storage object is returned by calling Net.FHttp( ipAddr <, tcpPort >). tcpPort is optional and defaults to 80. e.g.

   local vdConn = Net.FHttp(ipAddr, tcpPort);

Note that for Lua the vdConn local variable is of type userdata, a storage pointer, and has significance only to the underlying C functions supporting this instance of Net.FHttp(). This vdConn variable (or whatever variable name you choose) is the representation of the connection. The underlying and hidden connection storage is automatically freed once the connection is disconnected or when button processing terminates. Note that Net.FHttp() always returns userdata even when a connection cannot be established, however a subsequent request will fail with error code 2.

API requests are made as functions of the userdata variable returned on the Net.FHttp call e.g.

   local response, status, returnCode = vdConn:request(location <, data >);

The three returned values are as follows:

ResponseProvided staus is good, this is the HTTP body/content returned by the server. Note I tried testing this on an html page from the lua editor writing the output to debug log but the received html caused the entire request to hang, so be careful what you test with.
StatusIf the request completed, then status contains the HTTP status code. A successful request is typically status 200
Return CodeIdentifies whether the request could be processed by Fibaro. A return code of 0 indicates successful completion by the Fibaro API (see Status for what the Server thinks). Return code 2 indicates that the connection was not established.

The possible HTTP based function requests are:

FunctionDescription
GET(location ); This is an HTTP page GET request. The server is presented with:
GET location HTTP/1.1
Connection: Close
Content-Length: 0
Host: ipAddr:tcpPort

My testing showed that although connection close was reuested and given by the test server it was not necessary to re-issue the Net.FHttp() request, the userdata remained valid and subsequent requests succeeded.

PUT(location, data ); This is an HTTP PUT request. The server is presented with:
PUT location HTTP/1.1
Connection: Close
Content-Length: length of supplied data
Host: ipAddr:tcpPort

the supplied data

Typically the supplied data will be a JSON type structure converted to a string.

A simple GET request from the configured server follows:

   local devId = fibaro:getSelfId();
   local ipAddr = fibaro:getValue( devId, "IPAddress");
   local tcpPort = fibaro:getValue( devId, "TCPPort");
   local vdConn = Net.FHttp(ipAddr,tcpPort);
   local resp, stat, retc = vdConn:GET("/");

Using Net.FTcpSocket

The configured IP address and Port number for the target application can be extracted using:

   local devId = fibaro:getSelfId();
   local ipAddr = fibaro:getValue( devId, "IPAddress");
   local tcpPort = fibaro:getValue( devId, "TCPPort");

A connection to the target application is established and a reference to a C storage object is returned by calling Net.FTcpSocket() e.g.

   local vdConn = Net.FTcpSocket(ipAddr, tcpPort);

Note that for Lua the vdConn local variable is of type userdata, a storage pointer, and has significance only to the underlying C functions supporting this instance of Net.FTcpSocket. This vdConn variable (or whatever variable name you choose) is the representation of the connection. The underlying and hidden connection storage is automatically freed once the connection is disconnected, either by a call to disconnect() or implicitly when button processing terminates. Note that Net.FTcpSocket always returns userdata even when a connection cannot be established, however subsequent requests will all fail with error code 2.

API requests are made as functions of the userdata returned on the Net.FTcpSocket call e.g. vdConn:request(). Requests are:

FunctionDescription
setReadTimout(timeout ); Sets the timeout value for reads. This is not a timeout value for the initial connect! Note: if you use a domain name instead of ipAddr then the timeout for DNS lookup is normally hardcoded at a fixed 23 seconds.
write(data ); Writes the passed data to the target application. Returns the number of bytes sent and a return code. Return code 2 seems to indicate a connection has not been established.
read(); Reads data from the target application. Returns the received data and a return code. Return code 1 seems to indicate that no data was received within the time allowed via setReadTimeout.
disconnect(); Issues a shutdown on the connection to the target application. Failure to issue disconnect may cause the target application to see the connection as having been aborted and result in unnecessary error log records.

Using this approach a button might be coded up as follows:

   local devId = fibaro:getSelfId();
   local ipAddr = fibaro:getValue( devId, "IPAddress");
   local tcpPort = fibaro:getValue( devId, "TCPPort");
   local timeOut = 5 * 1000;

   local vdConn = Net.FTcpSocket(ipAddr, tcpPort);
   local bytes, retc = vdConn:write("login z99u54");
   if retc > 0 then
        if retc == 2 then
             fibaro:debug( "Connection to ".. ipAddr ..":"
                  ..tcpPort .." not established");
          else
           fibaro:debug( "Write failed: "..retc);
          end;
       else
        fibaro:debug( "Write complete, sent: "..bytes.." bytes");
        vdConn:setReadTimeout(timeOut);
      local resp, retc = vdConn:read();
        if retc > 0 then
             if retc == 1 then
                  fibaro:debug( "Request timed out");
               else
                  fibaro:debug( "Read complete: "..retc.." - "..resp);
               end;
          else
         fibaro:debug( "Read complete, received: "..resp);
          end;
      end;
   vdConn:disconnect();

Aside: there is no reason why you cannot use Net.FTcpSocket to access a web server, but you then lose access to built in HTTP support functionality.

Interacting with Scenes

As was shown on the Devices page of this reference, from a scene you can display the properties and actions associated with a device and again, virtual devices are no exception. If you try this, you should see among the properties the variable associated with each label created on the VD. In the actions section you should see the standard pressButton(), setSlider() and setProperty().

Pressing a VD Button

As well as pressing buttons via the UI, it is possible to have a scene press a button. To do this we use the standard call function with action "pressButton" followed by the number of the button. This number is read from the UI processing left to right, top to bottom and includes labels (I call this the button grid number).

   local devId = fibaro:getGlobalValue("vdSampId");
   fibaro:call( devId, "pressButton", 2);

Specifying button grid number 2 means the first button is pressed, since the label occupies grid position 1. Note multiple buttons on one row each occupy one position on the grid.

Using button calls allows the VD to have full control of the external equipment and correctly maintain state. Having scenes use the HTTPClient class to access the external equipment circumvents this and should be avoided.

Questions or comments, please e-mail us at info@snys.nl