home..

DIY Airtags

Using Macless Haystack, anyone can use the Apple Findmy network with a BLE beacon and Linux server. No Apple device needed! Lets get started. I developed this program for the Nicenano v1(NRF52840) using the Zephyr 2.5.0 SDK:

main.cpp:

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/controller.h>

static uint8_t pubKey[29] = {
<SET THIS TO THE HEXDUMP OF THE RAW PUBLIC KEY, NOT BASE64 ENCODED!!!>
};

static uint8_t adv_data[29] = {
	0x4c, 0x00, /* Company ID (Apple) */
	0x12, 0x19, /* Offline Finding type and length */
	0x00, /* State */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, /* First two bits */
	0x00, /* Hint (0x00) */
};

void set_payload_from_key(uint8_t *payload, uint8_t *public_key) {
    /* copy last 22 bytes */
	memcpy(&payload[5], &public_key[7], 22);
	/* append two bits of public key */
	payload[27] = public_key[1] >> 6;
}

static const struct bt_data ad[] = {
	/* STEP 4.1.2 - Set the advertising flags */
        //BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
	/* STEP 4.1.3 - Set the advertising packet data  */
        //BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME)),
};

static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_MANUFACTURER_DATA, adv_data,sizeof(adv_data)),
};

void set_addr_from_key(bt_addr_t *addr, uint8_t *public_key) {
	addr->val[0] = public_key[6];
	addr->val[1] = public_key[5];
	addr->val[2] = public_key[4];
	addr->val[3] = public_key[3];
	addr->val[4] = public_key[2];
	addr->val[5] = public_key[1] | 0b11000000;
}

static bt_addr_le_t addr;
static bt_addr_t bt_addr;

int main(void) {
        set_addr_from_key(&bt_addr,pubKey);
        addr.a=bt_addr;
        addr.type=BT_ADDR_LE_RANDOM;
	//bt_addr_le_from_str("FF:AA:AA:AA:AA:FF", "random", &addr);
        // bt_addr_le_from_str("FF:AA:AA:AA:AA:FF", "random", &addr2);
	
	bt_id_create(&addr, NULL);
        // bt_id_create(&addr2, NULL);

        struct bt_le_adv_param adv_param = {
                .id = 0,
                .sid = 0,
                .secondary_max_skip = 0,
                .options = BT_LE_ADV_OPT_USE_IDENTITY,
                .interval_min = 3200,
                .interval_max = 3300,
                .peer = NULL,
        };
        
        bt_enable(NULL);
        set_payload_from_key(adv_data,pubKey);
		bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));

		while(1) {
			k_sleep(K_FOREVER);
		}
}

prj.conf:

CONFIG_BT=y
CONFIG_BT_ID_MAX=2
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y

CONFIG_LOG=n
CONFIG_GPIO=n
CONFIG_SERIAL=n
CONFIG_CONSOLE=n
CONFIG_PINCTRL=n
CONFIG_SPI=n
CONFIG_FLASH=n
CONFIG_USB_CDC_ACM_LOG_LEVEL_OFF=n
CONFIG_USB_DEVICE_STACK=n

CONFIG_MPU_ALLOW_FLASH_WRITE=n
CONFIG_NVS=n
CONFIG_SETTINGS_NVS=n
CONFIG_FLASH_PAGE_LAYOUT=n
CONFIG_FLASH_MAP=n

CONFIG_PM_DEVICE=y

The prj.conf file is very important!! It disabled all unessecary SPI, external SPI flash, GPIO, etc to minimize power consumption. I measureed with my multimeter and its roughtly 30micro A!!! Very good. A single CR2032 will last around 250 days. Lets try with a Xiao BLE to see if I can get even better low power usage. First I need to unbrick it because I accidently flashed wrong firmware onto it. DFU does not work at all, so I need to flash the original bootloader back.

© 2025 Wayne Zeng   •  Theme  Moonwalk