Controlling Yoctopuce modules with a Flic Hub LR

Controlling Yoctopuce modules with a Flic Hub LR

Do you remember the Flics buttons we talked about a few years ago? Well, we still use them internally. In the meantime, Shortcut Labs has released a version 2 of these buttons and their Hub. It turns out that the new model, called Flic Hub LR, can run JavaScript code written by the user and thus allows you to implement behaviors a little more complex than On/Off. Of course, we wondered if we could run the JavaScript version of the Yoctopuce API on these new Hubs.

Flic Hub LR and Flic 2 button
Flic Hub LR and Flic 2 button


We'll kill the suspense right away, the Yoctopuce API doesn't work on the Flic Hub LR because of the many limitations imposed by these hubs.

  • All the code must fit in a single file.
  • The standard network APIs have been replaced with specific calls.
  • The Hub uses a pre-2015 version of JavaScript: no let, class, async, await primitives...

In short, trying to use the standard Yoctopuce API looks like a dead end. On the other hand, nothing prevents us from using the REST interface of the Yoctopuce modules to write a small library that imitates the basic behavior of the official libraries. It's just that we'll have to get out of the box and dust off the old JavaScript programming techniques we used 10 years ago.

General functions

Our library needs two essential functions.

getFunctionURL

The first function to implement is a function that can retrieve the URL of a Yoctopuce function based on the IP address of the hub that is supposed to host it, its type, and its hardware or logical name. This function uses network features, so it must be asynchronous. But as the async and await primitives are not available, we have fallen back on the old callback system. If the call went well, the callback success is called with the URL as parameter, in case of failure the callback failed is called with the error message as parameter.

function getFunctionURL(addr, functionType, functionName, success, failed)
  { ... }


We'll spare you the implementation details, but the principle of operation is as follows:

If we consider that the hub address given as a parameter is W.X.Y.Z:abcd, the function first tries to retrieve the http://W.X.Y.Z:abcd/api/services.json page. This is a JSON structure that describes all the modules and functions available on a hub. To illustrate this, here is a simplified example of a VirtualHub to which a Yocto-Light-V3 and a Yocto-Temperature are connected.

{"whitePages":
  [ {"serialNumber":"VIRTHUB0-3880db7f12","logicalName":"",...},
    {"serialNumber":"LIGHTMK3-1222A6","logicalName":"",...},
    {"serialNumber":"TMPSENS1-1C0FA2","logicalName":"",...}
  ],
 "yellowPages":
 { "Network":
     [{...,"hardwareId":"VIRTHUB0-3880db7f12.network",
           "logicalName":"VIRTHUB0-1c75d4",...}],
   "DataLogger":
     [{...,"hardwareId":"LIGHTMK3-1222A6.dataLogger","logicalName":"",...},
      {...,"hardwareId":"TMPSENS1-1C0FA2.dataLogger","logicalName":"",...}
     ],
   "LightSensor":
     [{...,"hardwareId":"LIGHTMK3-1222A6.lightSensor","logicalName":"",...}],
   "Temperature":
     [{...,"hardwareId":"TMPSENS1-1C0FA2.temperature",
           "logicalName":"myTemp",...}]
 }
}



This data structure is divided into two lists:

  • whitePages: the list of connected modules with, among other things, their serial number and logical name
  • yellowPages: the list of all connected functions grouped by type. Each entry consists of a set of properties including the hardware name and the logical name.


Therefore, if the serial number or the logical name of the module is specified in functionName, getFunctionURL scans the whitePages first to find the corresponding module.

It then scans the yellowPages to find the entry for which

  • The type corresponds to the functionType parameter
  • The hardware id or logical name matches the function name specified in the functionName
  • If applicable, the serial number of the module corresponds to the functionName parameter


If the functionName parameter is empty, the getFunctionURL returns the first function of the right type found in the yellowPages.

The result is a URL that has the following form:
http://adresse/bySerial/serial number/api/Function hardware name

For example, in the case of a Temperature function.

http://192.168.0.32:4444/bySerial/METEOMK2-20783D/api/temperature


getData

This is the second essential generic function that is used to retrieve the complete state of a Yoctopuce function. Its prototype is:

function getData(success, failed) {...}



This getData function performs an HTTP request with the URL computed by getFunctionURL which allows it to obtain a JSON structure describing the state of the function corresponding to the URL, for example in the case of a "Temperature" function:

{"logicalName":"myTemp",
 "advertisedValue":"25.1",
 "unit":"'C",
 "currentValue":1644953,
 "lowestValue":1618739,
 "highestValue":1656225,
 ...
}


If all goes well, getData tries to parse this structure and pass the result to the success function. If a problem occurs then the corresponding error message is passed to failed.

At this stage, the hardest part is done, we just have to code the classes that manage the behavior of the Yoctopuce functions.

Reading a sensor: YTemperature

To implement the control of the YTemperature function, we create a JavaScript function that mimics the behavior of a class. Remember that the "Class" primitive is not available on the Flic Hub LR.

function YTemperature(addr, name)
  { this.type     = "Temperature"
    this.get_data = generic_getData;
    this.get_url  = generic_getUrl
    this.addr     = addr;
    this.name     = name;
    this.url      = "";
    this.get_currentValue = YTemperature_getCurrentValue;
    this.get_unit = YTemperature_getUnit;
  }


YTemperature.get_currentValue

To retrieve the value of our temperature sensor, we simply retrieve the JSON data that corresponds to its URL. The value of a sensor is found in the integer field currentValue. To get its floating point value, we need to divide currentValue by 65536.0. This is valid for all the values of all the Yoctopuce sensors. If everything goes well, the result is passed to the callback success, otherwise the error message describing the problem is passed to the callback failed.

function YTemperature_getCurrentValue(success, failed)
  { this.get_data(
    function (data) { if (success) success(data.currentValue / 65536.0);},
    function (err) { if (failed) failed(err) }
    );
  }


Controlling an actuator: YRelay

Relays are one of the simplest Yoctopuce actuators, their control is implemented using a function/class very similar to YTemperature

function YRelay(addr, name)
  { this.type = "Relay"
    this.get_data  = generic_getData;
    this.get_url   = generic_getUrl
    this.addr      = addr;
    this.name      = name;
    this.url       = "";
    this.pulse     = YRelay_pulse;
    this.set_state = YRelay_set_state;
    this.toggle    = YRelay_toggle;
  }



YRelay.set_state

To change the state of a relay, you have to call its URL with the parameter "state=" zero for state A, one for state B, or X to toggle the state. To do this, set_state first calls its get_url method which allows it to retrieve and store its URL(this.url) and then makes a request on this URL with the state parameter set to 0 or 1.

function YRelay_set_state(state, success, failed)
  { var self = this;
    if (self.url == "")
      return self.get_url(
          function () {self.set_state(state, success, failed)},
          failed
          );
    var cmd = self.url + ".json?state=" + (state ? "1" : "0");
    AsyncRequest(cmd, success, function (err) {if (failed) failed(self.errDecode(err));});
  }



YRelay.toggle

The toggle function is even easier to implement

function YRelay_toggle(success, failed)
  { var self = this;
    if (self.url == "")
      return self.get_url(
         function (){self.toggle(success, failed) },
         failed
         );
    var cmd = self.url + ".json?state=X";
    AsyncRequest(cmd,
         success,
         function (err){ if (failed) failed(self.errDecode(err));}
         );
  }




Note that the success() callback is called with the data structure corresponding to the relay, so you can know what state the relay is in after the call without having to make a second request.

The list of commands available for the URL of each function is not officially published, but you can easily find all these commands by consulting the source code of any of the Yoctopuce libraries.

Usage

We end up with a library that is easy to use and that offers possibilities quite similar to the basic functions of the official API. To reference a function, you can use almost any combination of hardware and logical names:

// references the first available relay at
// address 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "");

// references the first relay called "myRelay"
// available at address 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "myRelay");

// references relay number 1 available on the
// Yocto-MaxiPowerRelay MXPWRRLY-12345 at
// address 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "MXPWRRLY-12345.relay1");

// references the relay named "myRelay" available
// on the module named "myModule" at address 192.168.0.32:4444
var r = new YRelay("192.168.0.32:4444", "myModule.myRelay");



To change the state of a relay, you just have to call its Set_state method

r.set_state(1, null, function(err){console.log(err);});



To read a sensor, the code is quite similar, except that the result is not directly returned by the get_state method but passed as a parameter to a callback.

// references the first temperature sensor
// available at address
var t = new YTemperature("192.168.0.32:4444", "");

// retrieves the value of the sensor
t.get_currentValue(
   function(value) {console.log("value="+value);},
   function(err) {console.log("Error: "+value);}
   );




Limitations

Obviously, this simplified library is not 100% equivalent to the official Yoctopuce libraries since it only allows you to do polling. But it is quite sufficient for Yoctopuce applications that are limited to occasional Yoctopuce calls. Module absence is handled gracefully by calling Fail() callbacks, but this system lacks the power of the original API arrival/removal callbacks.

Moreover, this library can only interface modules that are accessible via a YoctoHub or a VirtualHub, in other words it only emulates the network mode of the classic Yoctopuce API. The USB mode is clearly out of reach.

Application

To test our library, we built a small system that displays the temperature on a panel of LEDs and turns on a fan when the temperature goes above a certain threshold, unless the fan is manually triggered by pressing a Flic button (override).

var ts = 26; // temperature threshold
var r = new YRelay("192.168.0.20", "");
var t = new YTemperature("192.168.0.21", "myTemp");
var c = new YColorLedCluster("192.168.0.22", "");

function run()
 {var delta=0.25  // Schmitt trigger

  if (todo == ACTION_DONOTHING)
    t.get_currentValue(
      function (res)
       { console.log("temp= "+res.toFixed(1))
         if ((res >= ts + delta) && (lastTemp < ts + delta))
            {override=false; todo = ACTION_ON;}
         if ((res <= ts - delta) && (lastTemp > ts - delta))
            {override=false; todo = ACTION_OFF;}
     if (lastTemp!=res) refreshNeeded=true;
         lastTemp = res;
       },
      function (err) { console.log(err); });

  switch (todo)
    { case ACTION_ON:
        console.log("ON");
        r.set_state(1, null, logError)
        currentState = ACTION_ON;
        todo = ACTION_DONOTHING;
        refreshNeeded =true;
        break;
      case ACTION_OFF:
        console.log("OFF");
        refreshNeeded =true;
        r.set_state(0, null, logError)
        currentState = ACTION_OFF;
        todo = ACTION_DONOTHING;
        break;
    }
   if (refreshNeeded)
   { var buffer = new Uint32Array(512);
     var color = 0x101000;
     if (lastTemp<ts-delta) color=  0x002000;
     if (lastTemp>ts+delta) color=  0x200000;
     DrawTextinBuffer(buffer, lastTemp.toFixed(1)+"°C" , 0, FontSmall, color)
     var state =  (currentState==ACTION_ON?"ON":"OFF")
     DrawTextinBuffer(buffer, state , 30, FontSmall, 0x101010);
     if (override) DrawTextinBuffer(buffer, "OVRD" , 45, FontSmall, 0x04040A)
     c.rgbColorArray(0,buffer,null,logError );
   }
  }
// flic button control
buttonManager.on("buttonSingleOrDoubleClickOrHold",
 function (obj)
  { var button = buttonManager.getButton(obj.bdaddr);
    if (obj.isSingleClick)
      { console.log("Click on " + obj.bdaddr)
        switch (currentState)
        { case ACTION_ON:  todo = ACTION_OFF; override =true; run(); break;
          case ACTION_OFF: todo = ACTION_ON;  override =true; run(); break;
        }
      }
  });

console.log("hello");
setInterval( run, 1000);



And to prove that it works, we made a little video:

  



And of course, you can download the full code of this application. The code only deals with the Temperature, Relay and ColorLedCluster functions, but it would be quite easy to add support for other functions based on the existing code.

What we think of the Flic Hub SDK

Honestly, although we didn't expect miracles, we are overall a bit disappointed by the Flic Hub SDK:

The fact that Flic Hub SDK is based on an old version of JavaScript is quite disabling. As all functions that do input/output have to be asynchronous, it forces us to adopt a programming style entirely based on callbacks, which makes the code very quickly heavy and unreadable, not to mention that it requires good programming skills to write code that works as expected.

The IDE used to code for the Flic Hub SDK is cloud-based, which poses several problems:

  • The proposed editor is quite minimalist: it offers syntax coloring and... that's it. No refactoring, no code reformatting, not even search and replace. If you are used to coding with a modern editor configured to your own programming habits, the SDK editor will quickly annoy you.
  • If Shortcut Labs disappears, the ability to code for the Flic Hub LR will disappear with it. And that should be written in red on the package.


We are not sure if it is possible to run several scripts in parallel on the Flic Hub LR and apparently there is no debugger. Moreover, version 3.0.12 of the firmware we had available has a nasty tendency to crash because of what looks like an internal stack problem. This bug will, apparently, be fixed in a future version.

In short, if the possibility to run your own code on a Flic Hub LR is welcomed, you shouldn't expect too much from it and make sure you limit yourself to very simple applications, otherwise you'll be exposed to cruel disappointments.

Conclusion

Although this post is based on a very specific hardware, namely the Flic Hub LR, it can serve as an inspiration to write, if the need arose, your own Yoctopuce library in any programming language as long as this language is capable of making HTTP requests.

While we were at it, we also updated the Yocto-Flic application to work with the new generation of Flic 2 buttons, as the old multicolored ones seem to have disappeared from the landscape.

Add a comment No comment yet Back to blog












Yoctopuce, get your stuff connected.