Complete on{X} script to control the Where Clock

After a lot of head scratching I’ve finished the on{X} script to control the Where Clock: making it turn  around when I enter or leave places. Here it is:

// add your locations to this list, use as many decimal places as possible, these are fake  :-) 
var locations = [
    {name: "home", lat: 50.9, lon: -1.3, rad: 1},
    {name: "pilates", lat: 51.9, lon: -1.3, rad: 1},
    {name: "pub", lat: 52.9, lon: -1.3, rad: 1}
    ];
var hostname = "your.hostname.here";
var port = "1234";  // your port here

function move(place) {
    console.log('Entered region: ' + place);
    device.ajax(
    {
      url: "http://" + hostname + ":" + port + "/move?l=" + place,
      type: 'GET',
      headers: {}
    },
    function onSuccess(body, textStatus, response) {
        var msg = device.notifications.createNotification('Clock is set to: ' + place);
        msg.show();
        console.log('Success: ' + textStatus);
    },
    function onError(textStatus, response) {
        var msg = device.notifications.createNotification('Failed to set clock to ' + place + '\n' + textStatus);
        msg.show();
        console.log('Error: ' + textStatus);
    });
}

function build_callback(place) {
    console.log("Building callback for " + place);
    return function() {
        move(place);
    };
}

for (i = 0; i < locations.length; i++) {
    var location = locations[i];
    var region = device.regions.createRegion({
        latitude: location.lat,
        longitude: location.lon,
        name: location.name,
        radius: location.rad
    });
    region.on('enter', build_callback(region.name));
    region.on('exit', build_callback("travelling"));
    device.regions.startMonitoring(region);
}
console.log("Script executed");

// test code follows
/*
var regions = device.regions.getAll();
console.log(regions.length);
for (i = 0; i < regions.length; i++) {
    r = regions[i];
    console.log(r.name + ":" + r.latitude + ':' + r.longitude + ':' + r.radius);
}
var locationSignal = { latitude: 50.919675, longitude: -1.377101 };
regions[0].emit('enter', locationSignal);
regions[0].emit('exit', locationSignal);
regions[1].emit('enter', locationSignal);
regions[1].emit('exit', locationSignal);
regions[2].emit('enter', locationSignal);
regions[2].emit('exit', locationSignal);
*/

The hardest part was the callback (line 45). I’m not that good at Javascript and I’m never sure if things are being passed by value or reference and when they are evaluated. You have to provide a function that will be called when a region is entered or exited. The API for regions shows a callback inline with a “signal” parameter passed to it. The tricky thing is, how to do that in a loop? I want to loop through all the locations defined at the top of the script and set all the entry and exit callbacks for them.

You can’t just do

region.on('enter', move(region.name))

because the “move(region.name)” gets evaluated (called) as the callback is defined so it sends instructions to move to all the places at the beginning (and probably not later). So we need some delay to the evaluation.

You also can’t just do

region.on('enter', function () { move(region.name); });

because all those anonymous callback functions get bound to the same instance of “region”: Javascript (unnecessarily) reuses the object each time it goes round the loop. When “region.name” is evaluated it ends up being whatever the last place you defined was. In this example I would always be at the pub…

(To be fair, the surprisingly good Javascript editor in the on{X} web page does warn you against creating functions in loops, presumably for this very reason.)

The solution is in the code above: set the callback to be the result of the “build_callback” function which returns a function that calls the “move” function with the place already evaluated and fixed.

You might have noticed that in the code above, the regions are defined to have a 1m radius.  I originally set them to be 10m which seems to make more sense. However, although this code seems to be correct now it doesn’t actually work very well.  I went to the shops today and it was only once I was about 3 miles away from home that the phone sent the “travelling” signal (because it realised I was outside the home region) and then it did send the “home” signal at some point when I got back but I missed it.

So I think it’s all technically working now but that on{X} just isn’t up to the job.  I might try with tasker instead but I never got on with tasker’s UI.