Simple Beacon for Android SmartLock workaround.

Posted on
of 2
/ 2
  • Hi! I just discovered puck.js and pixl.js and I think it might be a solution to an issue I've been having and I wanted to see what people familiar with the devices thought about it.

    So Android has a "SmartLock" feature that lets me define criteria for circumstances for which the phone should not automatically lock (requiring the PIN to be re-entered) as long at the criteria are true. One criterion is GPS location, so basically you can say that the phone should not lock as long as it is at your home. Another criterion is a BT pairing with a trusted device, for instance, your phone should not lock as long as it is paired with your car's BT.

    The GPS criterion does not work particularly well for my home because I live in an apartment and the device frequently loses track of where it is (it works just fine when I'm at my parent's house though).

    So what I would like to be able to do is have some sort of BT device that sits in my apartment that I can have my phone stay paired with, thus invoking the SmartLock "keep unlocked" function based on the trusted device pairing. I have a BT speaker, but that's not an option because it then hijacks either the phone calls or the media output, and disabling those also breaks the pairing. So I need something that will stay paired, but also not hijack any features from the phone. And also I'd preferably like to keep it plugged into a power supply, so the pixl seems like a better choice for this than the puck, but I can go with either.

    Am I correct in thinking that either of these devices would do exactly what I need here?

    Also, since I don't actually need the device to do anything, can I just take it out of the box, power it up, and just pair my phone with it, or is there still some manual programming that needs to be done?

  • Hi!

    Yes, I think either device would be fine for that. I'll have a quick check tomorrow to be absolutely sure though.

    By the look of it you may have to pair with the device first before it'll appear in the Smart Lock list, which may mean you have to turn it into something like a media control (play/stop/pause button) - but even so that's just a few lines of code.

    The act of just broadcasting its existence shouldn't be anything that draws a bunch of power so a Puck would work fine for you for around a year's battery life.

    So... you may be able to just power on the device and make it work as you say, it may require some tweaking. However I'd seriously recommend uploading some code to the device (­) because potentially as it comes out of the box somebody in range in a different flat could just reprogram it to do something else :)

    I'll find out for sure tomorrow though

  • Cool, thanks!

    Yeah, I figured that there were some basic initial tasks that would need to be done regardless of the eventual implementation. I probably will want to add a pin for the pairing and code to toggle discoverability with the button press. It looks like there's enough documentation around to figure out how to do all that. :)

  • Ok, just checked and yes it's easy. Power up the Puck/Pixl, pair it in the Android Bluetooth menu and it pops up a notification asking if you want to use it for Smart Lock. Tap it, follow through the menus and you're sorted.

    I'd still recommend:

    • Updating the firmware on the device to 2v01 - there were some changes recently that really help to improve battery life if the device is connected all the time
    • Upload the command NRF.setServices(undefined, { uart : false }); - while this will allow other connections, it'll stop anyone actually modifying the device in any way.

    Unfortunately there's no way to set a pairing pin at the moment, but there is an example of toggling discoverability with a button press here:­#disable-bluetooth

  • That's great! Thanks again for your help.

  • The puck arrived today; it looks pretty cool. Only recently however did I catch the part that my Windows 7 laptop is not compatible as-is with the Web IDE tool to communicate with the Puck, so I have to wait until tomorrow for the BLE adapter to be delivered before I can proceed. :-) But I managed to update the firmware using nRF Connect from my phone at least.

    So I figure I'll run the whitelisting code (NRF.on('connect',function(addr) {...) to prevent any device besides my phone and my laptop to connect to the puck, and I'll run the code to toggle the BT with the button press.

    So, if I understand correctly, once I get the IDE tool connected and running, all I have to do is put those two bits of code into the editor on the right side, then click the Send button and the puck will be all set for what I want. And then, once I confirm that everything is good, run save() to save the code to flash for when the battery dies in a year and I'm all set. I can also be extra safe and run the NRF.setServices() code to disable the console and prevent any further changes (although I think that's redundant with the whitelist, or am I overlooking something?) unless the puck is reset by removing the battery.

    That should cover it, I think. Yes?

  • Yep... you got that right... in a nutshell... That's how @Gordon intended it to be...

    The battery thing is heavily dependent on what you do, though. Like using the phone as flashlight... works, but bring your battery down... almost like a always lit screen... or apps in the background that keep communicating. Your app though needs the puck to advertise only, and the phone knows what to do with it. And with that the battery life should make it.

  • So it works and it also doesn't work. The SmartLock part of it works fine, as long as I stay paired.

    But if the connection is broken for any reason (I turn off BT on my phone, I go far away, I turn off BT on the puck, etc...), it does not automatically get re-established. Now, from looking around a bit, I think that's probably a bug with Android and/or my Pixel 3. However, even when I try to manually repair by going into "previously connected devices" and clicking "Connect" next to the Puck entry, nothing happens. I have to forget the connection completely and re-pair by picking "pair new device". The Puck doesn't even show up in the list of devices for my phone to connect to until I forget it first, but it's still advertising, as I can see it in the list of devices when I scan from my PC, so obviously the phone is filtering out discovered devices that it hasn't forgotten yet.

    So I'm not sure if the inability to manually reconnect without forgetting first is a bug on the phone side or on the puck side. My next step is to test similar conditions with a BT speaker that I have, so I can see if it's an issue with the phone.

  • That's a bit strange about the reconnect, but maybe it just doesn't reconnect if there's no service provided that it 'wants'.

    It's be worth trying without the whitelisting first just in case that is causing you issues.

    Maybe try making the Puck a HID device:­#multimedia-keys

    So literally just adding the lines:

    var controls = require("ble_hid_controls");
    NRF.setServices(undefined, { hid : });

    That'll make it a 'multimedia control' (for play/stop/pause/etc) that the phone will probably want to stay connected to.

  • I think the issue was on the phone side. I added a Tasker profile to automatically connect when the puck is detected, and so far it's looking good.

  • Also, I think the issue is only if I turn BT off and on again on my phone. If instead I disable BT on the puck then turn it back on, or take the puck far away from the phone then come back, the connection reestablishes automatically.

  • I'm almost hesitant to post this, because I recognize that the code is sloppy (my javascript fluency is cursory at best) and there are probably several much better ways of doing what I did (particularly the LED stuff), but just in case anyone might find this useful, here's the code I'm running on the puck:

    // toggle LED states, based on bits in ldn
    // 1: Red, 2: Green, 4: Blue
    function toggle(ldn) {
        if(ldn&1) digitalWrite(LED1,digitalRead(LED1)?0:1)­;
        if(ldn&2) digitalWrite(LED2,digitalRead(LED2)?0:1)­;
        if(ldn&4) digitalWrite(LED3,digitalRead(LED3)?0:1)­;
    // Flash LEDs based on bits in z
    // one time for i milliseconds
    function wink(z,i) {
        setTimeout("digitalWrite([LED1,LED2,LED3­], 0);", i);
        // one more LED turnoff, just to be sure
        setTimeout("digitalWrite([LED3,LED2,LED1­], 0);", (i*2)+1000);
    // flash each LED in sequence for i milliseconds 
    // repeat lp times
    function lightshow(i,lp) {
        for(x=0; x<lp; x++) {
            setTimeout("digitalWrite([LED3,LED2,LED1­], 4);", (i*((3*x)+0)));
            setTimeout("digitalWrite([LED3,LED2,LED1­], 2);", (i*((3*x)+1)));
            setTimeout("digitalWrite([LED3,LED2,LED1­], 1);", (i*((3*x)+2)));
        setTimeout("digitalWrite([LED3,LED2,LED1­], 0);", (i*((3*lp)+0)));
        // one more LED turnoff, just to be sure
        setTimeout("digitalWrite([LED3,LED2,LED1­], 0);", (i*(3*lp))+1000);
    // flash LED based on bits in l, i times, for d milliseconds each time
    function blink(l,i,d) {
        var tf1 = "toggle("+l+");";
        inid = setInterval(tf1,i);
        var tf2 = "clearInterval("+inid+");";
        // one more LED turnoff, just to be sure
        setTimeout("digitalWrite([LED3,LED2,LED1­], 0);", (d*i*2)+1000);
    // periodically tell the LEDs to turn off in case an LED 
    // is left on because I wrote the above functions poorly
    // every 15 minutes
    var autoLEDOffIntID = setInterval("digitalWrite([LED3,LED2,LED­1],0);",1000*60*15); 
    // Code to whitelist devices for connections
    // Only allow:
    //      11:22:33:44:55:66 (my phone)
    //      aa:bb:cc:dd:ee:ff (BLE adapter on my laptop)
    var curConnect = "x";
    NRF.on('connect',function(addr) {
      if ((addr!="11:22:33:44:55:66 public") &&
          (addr!="aa:bb:cc:dd:ee:ff public"))
            blink(1,100,20);                    // OUTPUT - 20 quick red flashes when rejecting a non-whitelisted connection 
      curConnect = addr;
      if (curConnect == "11:22:33:44:55:66 public") 
        lightshow(100,10);                      // OUTPUT - celebration lights when connecting to my phone
      else wink(4,1500);                        // OUTPUT - 1 long blue when connecting to anything else
    NRF.on('disconnect',function() {
        curConnect = "x";
        blink(5,500,5);                         // OUTPUT - 5 medium red&blue flashes when disconnecting
    // Code to toggle Bluetooth on long button press
    var locked = false;
    setWatch(function() {
      locked = !locked;
      wink(locked?1:2,300);                     // OUTPUT - 1 quick red or green for inactive/active
      if (locked) 
      else NRF.wake();
    }, BTN, {repeat:true, edge:"rising", debounce:1000});
    // Code to report connection status quick button press
    setWatch(function() {
      if (locked) wink(1,2000);                 // OUTPUT - very long red if BT off
      else {
          if (curConnect == "x") wink(4,2000);  // OUTPUT - very long blue if BT on but not connected
          else wink(2,2000);                    // OUTPUT - very long green if BT on and connected
    }, BTN, {repeat:true, edge:"rising", debounce:50});
  • Oh, one thing that I know is definitely not correct in my code (but not fatally) is the fact that even the long-presses of the button set off the short-press function. I figure that, as written, the short-press watch is watching for presses of at least 50ms rather than just around 50ms, so the long-press sets off both watches.

    What's a better set of arguments for the watch to detect, say, a press of between 50ms-100ms?

  • Thanks for posting it up - it looks good!

    The only thing I'd say it generally using Strings as arguments to setInterval/setTimeout is considered bad form in JS (and usually you'd just use functions like setTimeout(function() { ... }, 1000)), but honestly there's no good reason why it's bad on Espruino (Strings are actually faster) other than that you don't get the command syntax-highlighted/linted in the IDE.

    What's a better set of arguments for the watch to detect, say, a press of between 50ms-100ms?

    Ahh - yes, there's a trick to it :) If you detect the falling edge instead of the rising edge, then you're called when the button is released and you can compare how long it was held down for:

    setWatch(function(e) {
      var d = e.time - e.lastTime;
      if (d>0.5/*seconds*/) console.log("Long press");
      else console.log("Short press");
    }, BTN, {repeat:true, edge:"falling", debounce:20});
  • Thanks for the feedback! And I'll definitely try that watch code out at some point.

  • Like your celebration light show.

    Btw, obvious code and code w/ comments / (inline) documentation is practically always the better code than what many would call 'better' in what ever aspect.

    For short / long button presses and combinations thereof and related actions / functions take a look at Software Buttons - Many buttons from just one hardware button. This almost-module alows you to configure timings.

  • btw, I tried the code you suggested to make the puck an HID device, and I got an error:

    >var controls = require("ble_hid_controls");
    Uncaught Error: Module ble_hid_controls not found
     at line 1 col 42
    var controls = require("ble_hid_controls");

    I don't know if I need to do something first to make that module available.

    I'm using the Windows executable version of the Espruino IDE, if that makes a difference.

  • Wed 2019.01.30

    @FuzzyBumble are you 'send' 'ing the code using the right hand editor pane of the IDE rather than the console left hand pane?

    Beneath heading
    Working with Modules
    Espruino Web IDE

  • Ah, I see. No, I just put it in the command prompt on the left side, because I wanted to validate it on it's own before adding it to the rest of my code. So I guess that's not a thing I can do. :)

    I did it on the right side now and it appears to have gone through successfully.

  • Hmm. So I added the NRF.setServices() call to my code and uploaded it with the IDE. The problem is that now, for some reason, when my phone connects, it's MAC address is being reported as something other than what it had been reporting before. And on top of that, it appears that the address changes each time I toggle BT off and on again on my phone.

    The problem with that being that my whitelist function is completely useless now.

    I tried performing a hard reset to undo the changes and start over, but it's still exhibiting the same behavior that started after the NRF.setServices() call.

    How can I undo what I just did?!?!

  • Wow, that's really strange. Not sure why that would be other than maybe it's something to do with it using a secure connection?

    Can your 'forget' the bluetooth device on your phone? I seem to recall that on Android that once it thought the device was a HID device it kept treating it as that even when the HID was gone, until you told the phone to forget the device.

  • So near as I can figure, this is what seems to be the case:

    Apparently, on android phones, there is a security feature where they randomize their MAC address each time the transmitter is restarted for BLE advertising.

    The phone's BT does have a fixed MAC address. And from the beginning, the Puck would see that fixed address in "addr" in "NRF.on('connect',function(addr){...})".­

    Ever since I made the "NRF.setServices(undefined, { hid : });" call, the "addr" variable in the NRF.on event has been resolving to the random MAC instead.

    Forgetting the device on my phone did not change anything, the Puck still showed up in the discovered devices with the HID icon instead of the BT icon next to it. But I was able to change the advertised name on the Puck to something new, and now it shows up with the new name and the default BT icon, so that much appears to be resolved, but the random MAC is still ending up in "addr".

    So the question is how to revert the NRF.on('connect') behavior back to how it was before, where the addr argument resolves to the real fixed address instead of the advertised random one.

  • So the question is how to revert the NRF.on('connect') behavior back to how it was before, where the addr argument resolves to the real fixed address instead of the advertised random one.

    Honestly, I'd have thought that it was something the phone started doing. I don't know of any setting on the Puck that would be responsible for the phone reporting a random MAC address.

    I'd just started looking at bonding with a pin code for this thread, so I just had a bit of a fiddle with what I'd done and realised I could add passkey pairing really easily.


    Now, when you pair you'll have to type 123456 into your phone. Since nobody else would be able to guess it, nobody else can connect - problem solved? :)

  • I came up with an imperfect hacky workaround... I set up the whitelisting code to capture the MAC address of the most recently rejected connection attempt, and then I set a watch for a medium-length button press that whitelists that captured MAC. So whenever my phone ends up with a different MAC, and the connection is rejected, I can tell the puck to accept it and then try again.

    But obviously, if there's any way I can revert to how the puck was behaving a couple of days ago, that would be infinitely more preferable.

  • Oh, I just saw your reply.... yeah, that should be a workable solution. I'll check it out.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview

Simple Beacon for Android SmartLock workaround.

Posted by Avatar for FuzzyBumble @FuzzyBumble