TechSomething

retrieve data from FindMy network and send it somewhere else

note: this is still a work in progress

why #

we want to programatically retrieve findmy items data and send it somewhere else

the goal is to ingest that data in traccar

notes #

phantom item: #

i've shared a tag, then removed and re-shared before I could accept it, on macos I found 2 invites for the same tag and accepted both. At this point I could only remove the "alive" one since the first one, that have been unshared) cannot be managed or deleted.

phantom notifications #

on the iphone I see the notifications for the invites but don't see the invites inside the section "items" in "findmy"

partial data before accepting shared item #

I've shared a second tag and even before I could accept the invite, I've seen the item data in Items.data

the data is without sensitive data until you accept the tag sharing (see more in "how to read the data in the csv")

airtags #

there's a limit of 16 AirTags per Apple ID

show me the data #

the items (airtags and compatible devices) data is in:
~/Library/Caches/com.apple.findmy.fmipcore/Items.data

converting data to csv #

for our tests we'll use airtag alex script: https://github.com/icepick3000/AirtagAlex
it converts the items data to csv

how to read the data in the csv #

example of the yet to be accepted shared tag:

2023-11-12  16:00:00,"Tag01","Not Available","b777",77777,77,3,"2.0.0",0,null,null,null,null,0,0,0,0,"null","null","null","","","","","","","","","",""

example of the accepted tag (so all data is there)
:

2023-11-12  16:01:00,"Tag01","283KHVFAHSF832","b777",77777,77,3,"2.0.0",4,"crowdsourced",44.113763000000000,12.575151000000000,1699801200000,-1,42.000000000000000,0,-1,"false","true","true","Via Fasulla 0","0","IT","","Lombardia","Via Fasulla","Bergamo","Italy","",""

(the data has been altered for privacy)

use case #

a headless vm with macos Sonoma (which supports items=airtag sharing),
that receives shared airtags and sends the data somewhere else

this way we can leverage the power of FindMy network but still using the tools we like,
we can see this as a "compatibility layer"

what is working/thoughts #

important #

it seems that for having the items update their position you need to have macos logged in and findmy open,
the screen can be locked.

traccar script #

this is a modified version of AirtagAlex's script,
this writes data to the csv and also sends it to traccar

#!/bin/bash

ITEMS_FILE="./Items.data"
CSV_FILE="./Airtags.csv"
CSV_HEADER="datetime,name,serialnumber,producttype,productindentifier,vendoridentifier,antennapower,systemversion,batterystatus,locationpositiontype,locationlatitude,locationlongitude,locationtimestamp,locationverticalaccuracy,locationhorizontalaccuracy,locationfloorlevel,locationaltitude,locationisinaccurate,locationisold,locationfinished,addresslabel,addressstreetaddress,addresscountrycode,addressstatecode,addressadministrativearea,addressstreetname,addresslocality,addresscountry,addressareaofinteresta,addressareaofinterestb"

### VARS for traccar:
traccar_url="http://your.traccar.url:5055"

copy_items_data() {
	echo "Creating a copy of Items.data to prevent potential file corruption"
	if ! cp -p ~/Library/Caches/com.apple.findmy.fmipcore/Items.data "$ITEMS_FILE"; then
	    echo "Failed to copy Items.data file. Please ensure Terminal has 'Full Disk Access' in the 'Privacy & Security' section in macOS Preferences" >&2
	    exit 1
	fi
}

create_csv_file() {
	echo "Checking if $CSV_FILE exists"
	if [ ! -f "$CSV_FILE" ]; then
	    echo "$CSV_FILE does not exist, creating one"
	    if ! echo "$CSV_HEADER" >> "$CSV_FILE"; then
	        echo "Failed to create $CSV_FILE. Please ensure the destination directory is writable." >&2
	        exit 1
	    fi
	fi
}

while true; do
	copy_items_data
	create_csv_file

	echo "Checking number of Airtags to process"
	airtagsnumber=$(jq ".[].serialNumber" "$ITEMS_FILE" | wc -l)
	echo "Number of Airtags to process: $airtagsnumber"
	airtagsnumber=$((airtagsnumber-1))

	for j in $(seq 0 "$airtagsnumber"); do
	echo "Processing airtag number $j"

	datetime=$(date +"%Y-%m-%d  %T")

	serialnumber=$(jq ".[$j].serialNumber" "$ITEMS_FILE")
	name=$(jq ".[$j].name" "$ITEMS_FILE")
	producttype=$(jq ".[$j].productType.type" "$ITEMS_FILE")
	productindentifier=$(jq ".[$j].productType.productInformation.productIdentifier" "$ITEMS_FILE")
	vendoridentifier=$(jq ".[$j].productType.productInformation.vendorIdentifier" "$ITEMS_FILE")
	antennapower=$(jq ".[$j].productType.productInformation.antennaPower" "$ITEMS_FILE")
	systemversion=$(jq ".[$j].systemVersion" "$ITEMS_FILE")
	batterystatus=$(jq ".[$j].batteryStatus" "$ITEMS_FILE")
	locationpositiontype=$(jq ".[$j].location.positionType" "$ITEMS_FILE")
	locationlatitude=$(jq ".[$j].location.latitude" "$ITEMS_FILE")
	locationlongitude=$(jq ".[$j].location.longitude" "$ITEMS_FILE")
	locationtimestamp=$(jq ".[$j].location.timeStamp" "$ITEMS_FILE")
	locationverticalaccuracy=$(jq ".[$j].location.verticalAccuracy // 0" "$ITEMS_FILE")
	locationhorizontalaccuracy=$(jq ".[$j].location.horizontalAccuracy // 0" "$ITEMS_FILE")
	locationfloorlevel=$(jq ".[$j].location.floorlevel // 0" "$ITEMS_FILE")
	locationaltitude=$(jq ".[$j].location.altitude // 0" "$ITEMS_FILE")
	locationisinaccurate=$(jq ".[$j].location.isInaccurate" "$ITEMS_FILE" | awk '{ print "\""$0"\"" }')
	locationisold=$(jq ".[$j].location.isOld" "$ITEMS_FILE" | awk '{ print "\""$0"\"" }' )
	locationfinished=$(jq ".[$j].location.locationFinished" "$ITEMS_FILE" | awk '{ print "\""$0"\"" }' )
	addresslabel=$(jq ".[$j].address.label // \"\"" "$ITEMS_FILE")
	addressstreetaddress=$(jq ".[$j].address.streetAddress // \"\"" "$ITEMS_FILE")
	addresscountrycode=$(jq ".[$j].address.countryCode // \"\"" "$ITEMS_FILE")
	addressstatecode=$(jq ".[$j].address.stateCode // \"\"" "$ITEMS_FILE")
	addressadministrativearea=$(jq ".[$j].address.administrativeArea // \"\"" "$ITEMS_FILE")
	addressstreetname=$(jq ".[$j].address.streetName // \"\"" "$ITEMS_FILE")
	addresslocality=$(jq ".[$j].address.locality // \"\"" "$ITEMS_FILE")
	addresscountry=$(jq ".[$j].address.country // \"\"" "$ITEMS_FILE")
	addressareaofinteresta=$(jq ".[$j].address.areaOfInterest[0] // \"\"" "$ITEMS_FILE")
	addressareaofinterestb=$(jq ".[$j].address.areaOfInterest[1] // \"\"" "$ITEMS_FILE")

	echo "Writing data to $CSV_FILE"
	echo "$datetime","$name","$serialnumber","$producttype","$productindentifier","$vendoridentifier","$antennapower","$systemversion","$batterystatus","$locationpositiontype","$locationlatitude","$locationlongitude","$locationtimestamp","$locationverticalaccuracy","$locationhorizontalaccuracy","$locationfloorlevel","$locationaltitude","$locationisinaccurate","$locationisold","$locationfinished","$addresslabel","$addressstreetaddress","$addresscountrycode","$addressstatecode","$addressadministrativearea","$addressstreetname","$addresslocality","$addresscountry","$addressareaofinteresta","$addressareaofinterestb" >> "$CSV_FILE"


        ### block to write data into traccar:
        #
        serialnumber=`echo $serialnumber | sed 's/\"//g'`
        ### removed due to updated code: tracname=`cat ~/Desktop/Airtags/Items.data | jq .[$j].name | sed 's!"!!g'`
        ### batterystatus=`cat ~/Desktop/Airtags/Items.data | jq .[$j].batteryStatus`
        akku=$((batterystatus * 100))
        # send data to traccar:
	wget --spider $traccar_url/?id=$serialnumber\&lat=$locationlatitude\&lon=$locationlongitude\&speed=0\&user=Airtag\&batteryLevel=$akku\&accuracy=$locationhorizontalaccuracy\&timestamp=$locationtimestamp\&serialnumber=$serialnumber\&lastupdate=$locationtimestamp
        #
        echo "Sleep for 1 second between Airtags"
        sleep 1
        #
        ### END block to write data into traccar

	done
	echo -e "Checking again in 1 minute...\n"
	sleep 60

done

to check: #

sources: #