openhaystack icon indicating copy to clipboard operation
openhaystack copied to clipboard

PuckJS Firmware

Open bettse opened this issue 4 years ago • 15 comments

I ported the firmware to the PuckJS (https://www.puck-js.com/)


function AirTag(pk) {
  var ad = [
    0x1e, /* Length (30) */
    0xff, /* Manufacturer Specific Data (type 0xff) */
    0x4c, 0x00, /* Company ID (Apple) */
    0x12, 0x19, /* Offline Finding type and length */
    0x00, /* State */
    pk[6], pk[7], pk[8], pk[9], pk[10], pk[11], pk[12], pk[13], 
    pk[14], pk[15], pk[16], pk[17], pk[18], pk[19], pk[20], pk[21], 
    pk[22], pk[23], pk[24], pk[25], pk[26], pk[27],
    0x00, /* First two bits */
    0x00, /* Hint (0x00) */
  ];
  ad[29] = pk[0] >> 6;

  const addr = [
    pk[0], pk[1], pk[2], pk[3], pk[4], pk[5]
  ];

  addr[0] |= 0xC0;

  const s = addr
    .map(x => x.toString(0x10).padStart(2, '0'))
    .join(':')
  
  return {
    Ad: [].slice.call(ad),
    Address: s + " random",
  };
};

const public_key = [
  0xbf, 0x44, 0x1f, 0xa2, 0x44, 0x40, 0x70, 0x65, 0x24, 0xe4, 0x0f, 0x5b,
  0x4b, 0xc0, 0xd1, 0x8e, 0x40, 0x98, 0xe4, 0x6f, 0xa6, 0xd5, 0xd4, 0x14,
  0x0a, 0xfe, 0xa8, 0x05
];

const airtag = AirTag(public_key);

NRF.setAddress(airtag.Address);

NRF.setAdvertising([
  airtag.Ad,
  {} // this will add a 'normal' advertising packet showing name/etc  
], {interval:100});

bettse avatar May 11 '21 04:05 bettse

This is great! Just to add that it's possible to craft a link of the form https://espruino.com/ide?upload&code=... which will automatically upload the firmware to a Puck, so if there's interest we could add this to OpenHaystack really easily?

gfwilliams avatar May 11 '21 08:05 gfwilliams

This is great! Just to add that it's possible to craft a link of the form https://espruino.com/ide?upload&code=... which will automatically upload the firmware to a Puck, so if there's interest we could add this to OpenHaystack really easily?

Did this ever happen? My Puck.js is coming in a few days.

LowEnergyGenerator avatar Nov 03 '22 18:11 LowEnergyGenerator

As far as I know nothing got added past the code here (but uploading this code is itself pretty easy).

Even if this didn't go into openhaystack, it could be added to https://espruino.github.io/EspruinoApps/ or https://banglejs.com/apps/ pretty easily so that uploading could be just a matter of copying/pasting the public key in.

I don't have an iPhone or even a remotely recent Mac, but @LowEnergyGenerator if you're in a position to test then I can add something to https://espruino.github.io/EspruinoApps/ that you can try out.

gfwilliams avatar Nov 04 '22 08:11 gfwilliams

~~As a report, I successfully used the code above (deployed using the Chrome App interface) with a puck.js.~~

Apologies, I misspoke. Some time had passed so I checked more carefully and it seems that I actually used this modification which the author states was based on the code above.

/*
OPENHAYSTACK
A framework for tracking personal Bluetooth devices via Apple's massive Find My network
2021-07-15 SK
*/
print("OpenHaystack Beacon\n===================\n");

const key_b64 = "Fy5SNl1ehMqUe49PJ4sxgrvTJLj/hlNjVYy5qg=="; // replace with your public (advertisement) key

function b64ToArray(str) {
    let bstr = atob(str);
    let arr = [];
    for (let i = 0; i < bstr.length; i++) {
        arr[i] = bstr.charCodeAt(i);
    }
    return arr;
}

const key = b64ToArray(key_b64); // public key

print("Public key:");
print("- base64:\n" + key_b64);
print("- hexadecimal:\n" + key.map(x => x.toString(16).padStart(2, '0')).join(' '));

const mac = [ key[0] | 0b11000000, key[1], key[2], key[3], key[4], key[5] ].map(x => x.toString(16).padStart(2, '0')).join(':'); // mac address

print("\nMAC address:\n" + mac);

const adv = [ 0x1e, 0xff, 0x4c, 0x00, 0x12, 0x19, 0x00, key[6], key[7], key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15], key[16], key[17], key[18], key[19], key[20], key[21], key[22], key[23], key[24], key[25], key[26], key[27], key[0] >> 6, 0x00 ]; // advertising packet

print("\nAdvertising packet:\n" + adv.map(x => x.toString(16).padStart(2, '0')).join(' ') + "\n");

NRF.setAddress(mac);
NRF.setAdvertising(adv, {interval:5000});

print("\nTo start beacon click on IDE upper-left icon to disconnect board. BLE stack will restart");
print("Then reset (if code saved in RAM) / hard-reset (if code in Flash) board to reconnect IDE");
print("To hard-reset hold BTN1 for >5s while repowering the board. Type reset(1) to erase Flash");

In this variation, the "public (advertisement) key" string takes as input directly the string given by OpenHaystack. Note also the more typical advertising interval -- after a few months of use at this setting, I've experienced barely any battery drain.

ak2k avatar Nov 04 '22 10:11 ak2k

That's great! So what does the public_key string you can copy from OpenHaystack look like?

Is it literally just:

  0xbf, 0x44, 0x1f, 0xa2, 0x44, 0x40, 0x70, 0x65, 0x24, 0xe4, 0x0f, 0x5b,
  0x4b, 0xc0, 0xd1, 0x8e, 0x40, 0x98, 0xe4, 0x6f, 0xa6, 0xd5, 0xd4, 0x14,
  0x0a, 0xfe, 0xa8, 0x05

Or would there be a better format of data for the app to accept?

gfwilliams avatar Nov 04 '22 10:11 gfwilliams

I would be happy to test out whatever if someone wants to walk me through it, I'm new at this.

LowEnergyGenerator avatar Nov 06 '22 22:11 LowEnergyGenerator

Just added - if you go to https://espruino.com/apps you should be able to paste in the base64 code? If this works I'd love to get it added as a Bangle.js app too so users can find their Bangle with it :)

gfwilliams avatar Nov 07 '22 14:11 gfwilliams

I do not believe it worked. I added my public key in the web app and received this error on the web IDE:

 ____                 _ |  |_ ___ ___ _ ||___ ___ |  | -| . |  | | | |   | . | |||  || |||||_|          || espruino.com  2v15 (c) 2021 G.Williams

Uncaught SyntaxError: Got UNFINISHED STRING expected EOF  at line 6 col 59 in .boot0 const key_b64 = "XXMYPUBLICKEYXX";                                                           ^

OpenHaystack Beacon

Uncaught TypeError: Assignment to a constant  at line 8 col 17 const key_b64 = "XXXMYPUBLICKEYXXX";                 ^ Public key: - base64: XXXMYPUBLICKEYXXX - hexadecimal: XX XX...

MAC address: XX:XX....

Advertising packet: 1e ff .....

BLE Connected, queueing BLE restart for later

To start beacon click on IDE upper-left icon to disconnect board. BLE stack will restart Then reset (if code saved in RAM) / hard-reset (if code in Flash) board to reconnect IDE To hard-reset hold BTN1 for >5s while repowering the board. Type reset(1) to erase Flash

Screen Shot 2022-11-07 at 9 21 59 PM

And I'm not sure which code string to use, Im guessing I need to be using the Advertisement Key, Base64?

LowEnergyGenerator avatar Nov 08 '22 01:11 LowEnergyGenerator

Sorry - that was a stupid mistake of mine. Please can you try installing again? I think it should be fixed now.

The Uncaught TypeError: Assignment to a constant might have been because you'd already saved the OpenHaystack code in a different file. Sending require("Storage").eraseAll() in the IDE should clear out anything that's left in there which might get rid of that problem

gfwilliams avatar Nov 09 '22 08:11 gfwilliams

Sorry - that was a stupid mistake of mine. Please can you try installing again? I think it should be fixed now.

The Uncaught TypeError: Assignment to a constant might have been because you'd already saved the OpenHaystack code in a different file. Sending require("Storage").eraseAll() in the IDE should clear out anything that's left in there which might get rid of that problem

Ok, that may have worked. Waiting to see if it comes up on OpenHaystack.

LowEnergyGenerator avatar Nov 10 '22 16:11 LowEnergyGenerator

Success! One question, does your app load the code into the Flash or RAM? When the battery is removed, will the OpenHaystack code disappear or remain?

LowEnergyGenerator avatar Nov 10 '22 17:11 LowEnergyGenerator

That's great news! The code will remain in flash (it's as if 'save to flash' was selected in the IDE) so it'll remain when the battery is removed

gfwilliams avatar Nov 11 '22 08:11 gfwilliams

Just to say I've just added the functionality for Bangle.js smartwatches too, so hopefully folks will now be able to use OpenHaystack to find them if they get lost: https://espruino.github.io/BangleApps/?id=openhaystack

gfwilliams avatar Nov 11 '22 08:11 gfwilliams

Nice, @gfwilliams! The app indeed makes this super easy.

Out of curiosity, I notice that you've elected not to set a particular interval for advertisements. Are you expecting power usage to be just fine on the default interval?

ak2k avatar Apr 04 '23 15:04 ak2k

Hi, yes - the default interval is 375ms which seems to be a reasonable compromise for most cases giving ~1 year of battery life, and since the Puck is alternating advertisements so still advertising its name, that means that the Haystack beacon is only broadcast every 750ms.

Going any slower would make it hard to connect to the Puck reliably - however I guess there could be an option to lower the Puck's advertising interval, not advertise the Puck's name and also make it non-scannable and non-connectable, and together those could have a big impact on battery life (potentially doubling it)

gfwilliams avatar Apr 05 '23 07:04 gfwilliams