Forum: PC-Programmierung Apple AirTag RSSI Wert erfassen


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Armin G. (armin_graewe)


Lesenswert?

Zunächst möchte ich mich dafür entschuldigen, dass diese Anfrage ein 
wenig off-Topic ist, aber ich gerife mittleweile nach jedem Strohhalm.
In anderen diversen (Programmierer)Foren kam bisher nichts brauchbares 
heraus. Offenbar auch weil viele Programmierer nichts von Hochfequenz 
und Protokollen verstehen. Auch ChatGPT hat nichts funktionierendes 
hervorgebracht.

Nun zu meinem eigentlichen Anliegen :
Ich entwickle derzeit für mich (und später auch für andere) eine 
Android-App mit Android-Studio und Kotlin, die sportliche Leistungen 
aufzeichnet. Diese App läuft bisher excellent ist aber noch nicht 
vollständig.
Ich muss es noch schaffen, zu erkennen, ob sich ein Apple AirTag in 
meiner Nähe befindet. Leider muss es ein AirTag sein, weil nur das 
zuverlässig wasserfest und klein und leicht genug ist.

Ich habe mittlerweile mehr als 40 Stunden mit Programmierexperimenten 
und Internetrecherchen verbracht und bin kurz vor der Aufgabe des 
Projets.
Hiiiiiiiillllllffffffeeeeee !!!!

Ich habe bisher mit der "altbeacon Android Beacon Libary" getestet.
Im Referenz Demoprogramm sehe ich den AirTag, allerdings ohne RSSI-Wert.
Das Demoprogramm ist dermassen aufgebläht, dass es trotz einiger 
Kommentierungen nur schwer nachzuvollziehen ist.
Ausserdem bin ich Kotlin Anfänger, kenne mich aber gut in anderen 
Programmiersprachen, Bluetooth und Elektronik aus.

Gibt es nicht einen einfacheren Weg den RSSI-Wert eines empfangenen 
Bluetooth-Pakets zu ermitteln.
Mein Wunsch wäre es, ca. jede Sekunde nachzuschauen ob sich das AirTag 
im Bereich von ca. 3 Metern befindet.
Also sowas wie empfangene Bluetooth Pakete nach der der AirTag UUID 
durchsuchen und den dazugehörigen RSSI-Wert übergeben.

Im Voraus Danke für Eure Antworten und nochmals sorry für offtopic.
Auch Vorschläge für andere kompetente Foren sind willommen.

Gruß Armin
------------------------------------------------------------------
Ich weis noch auf welcher Seite man den Lötkolben anfasst :-)

von Frank E. (Firma: Q3) (qualidat)


Lesenswert?

Ich kann auch nur vermuten:

Die funktechnische "Anwesenheit" (und damit der RSSI-Wert) ist doch 
nicht das Gleiche, wie eine Kommunikation (OSI lässt grüßen). Das Eine 
setzt natürlich das Andere voraus ...

Worauf ich hinaus will: Kann man den RSSI-Wert nicht aus "weiter unten" 
liegenden Schichten des Betriebssystems auslesen? Per Syscall oder über 
eine Systemvariable ... oder wie immer das bei Android gelöst ist. Also 
näher an die Hardware.

Android ist doch Java? Spontan gefunden:

https://stackoverflow.com/questions/15312858/get-bluetooth-signal-strength

: Bearbeitet durch User
von Harald A. (embedded)


Lesenswert?

Liegt da ein Linux drunter? Dann kann man doch einfach mit btmon 
scannen. Überhaupt gibt es unter Linux viele Möglichkeiten. Ansonsten 
einen Raspberry Pi aufsetzen und die Sachen per beliebiger Schnittstelle 
an das Zielsystem.

von Armin G. (armin_graewe)


Lesenswert?

Danke @Frank E.
Der Hinweis auf den Stackflow Artikel ist sehr interessant.
Das war ja genau mein Gedanke, einfach "weiter unten" ab zu greifen.
Ich brauche keine echte Kommunikation, sondern nur den Empfang.
Leider fehlt mir heute die Zeit die Vorschläge des Artikels zu testen.
Ich werde das aber baldmöglichst testen und berichten.

Android ist nicht Java, aber die Sprache Kotlin in der ich schreibe, ist 
eine Weiterentwicklung von Java. Android ist ein Betriebssystem und im 
weitesten Sinne Linux ähnlich.

Danke auch @Harald A.
Wie oben gesagt ich entwickle für ein Android Samrtphone, und das 
Betriebssystem ist Android, und Linux nur im weitesten Sinne ähnlich.
Danke auch für den Tip mit externer Hardware.
So bin ich mit meinem Projekt angefangen, nämlich mit einem auf ESP32 
basierendem Bluetooth Interface. Da war das Auslesen der UUID und des 
RSSI Wertes ganz einfach. Ich hatte sogar schon ein Frontend mit großen 
7-Segment Ziffern dazu gebaut. Als ich mein Projekt dann bei 
befreundeten Sportlern ansprach ergab sich so großes Interesse daran, 
dass ich beschlossen habe, das Rad nicht ein zweites mal zu erfinden 
sondern auf Massenware, nämlich ein Smartphone zu setzen.
Große oder stromfressende zusatz-Hardware scheidet aus.
Das ganze muss klein und praktikabel zum Mitnehmen sein.
Ich hatte auch schon den Ansatz eine Tastatur zu schlachten und die 
üblicherweise kleine Platine per USB an das Smartphone zu koppeln oder 
sofort eine Bluetooth Tastatur zu schlachten und dann damit die App zu 
steuern. Aber das sind dann halt Individuallösungen, die nie für die 
Allgemeinheit brauchbar sind.

Noch mal herzlichen Dank an Euch.
Gruß Armin
------------------------------------------------------------------
Ich weis noch auf welcher Seite man den Lötkolben anfasst :-)

von Armin G. (armin_graewe)


Lesenswert?

@Frank E.

Heureka ! Es funktioniert !!!

Das Programm in dem Link den Du gepostet hattest funktionierte zwar auch 
nicht, aber es hat mich auf den richtigen Weg gebracht.
Ich habe jetzt ein neues Programm selbst geschrieben.
Der Trick besteht darin, das man den "BluetoothLeScanner" nutzen muss.
Sobald ich meinen Test etwas geordnet habe werde ich ihn hier 
veröffentlichen. Vielleicht helfe ich damit ja auch anderen, denn über 
die Zusammenarbeit von AirTag und Android gibt es nur wenig im Netz.

Noch mal herzlichen Dank an Frank E.
Gruß Armin
------------------------------------------------------------------
Ich weis noch auf welcher Seite man den Lötkolben anfasst :-)

von Armin G. (armin_graewe)


Lesenswert?

Wie versprochen hier nun die Lösung wie man den RSSI-Wert eines Apple 
AirTag als Beacon ausliest.
Es funktioniert für nahezu alle BLE Beacons.
Der RSSI Wert des Beacon wird bei jedem gesendeten Paket angezeigt, ohne 
sich per Bluetooth mit dem Beacon zu verbinden.
Ein Apple AirTag Beacon sendet im unkonfigurierten Zustand ca. 1x pro 
Sekunde. Somit sind auch schnelle Erfassungen möglich.

Als 'Bonbon' speichert die App auch noch die angezeigten Werte ab wenn 
man auf die Stop Taste drückt.
Die Datei befindet sich in dem Ordner Dokumente/Beacontest/

Es ist ein Smartphone mit mindestens API34 extension 7 erforderlich.
Das ist mit Android 14 erfüllt.

Beim Debug unter AndroidStudio ist es relativ einfach die angeforderten 
Rechte frei zu geben. Dazu einfach in Debug Kopfzeile auf "app" clicken,
dann Edit Configuations und im Feld "Install Flags" "-g" eintragen.

Nun zum Code.
Zuerst die activity_main.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="-XX dBm"
        android:textAlignment="center"
        android:textColor="@color/purple_700"
        android:textSize="60sp"
        android:textStyle="bold" />

    <ScrollView
        android:id="@+id/TimelineScrollbar"
        android:layout_width="361dp"
        android:layout_height="545dp"
        android:layout_marginLeft="10dp"
        android:scrollIndicators="right"
        android:scrollbarAlwaysDrawVerticalTrack="true"
        android:scrollbarSize="6dp"
        android:scrollbarStyle="insideOverlay"
        android:scrollbarThumbHorizontal="@color/purple_700"
        android:scrollbarThumbVertical="@color/purple_700"
        android:scrollbars="vertical"
        android:textAlignment="gravity">

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="BeaconList"
            android:textColor="@color/black"
            android:textSize="24sp" />

    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button_start"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="10dp"
            android:background="#009688"
            android:gravity="center|center_horizontal|center_vertical"
            android:text="Start"
            android:textSize="24sp" />

        <Button
            android:id="@+id/button_stop"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:background="#E91E63"
            android:gravity="center|center_horizontal|center_vertical"
            android:text="Stop"
            android:textSize="24sp" />

    </LinearLayout>

</LinearLayout>

Dann die MainActivity.kt :

package com.example.beacontest6

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat

/* ----------------------------------------------------------
   - Programm : Beacontest6                                 -
   - Usage : Recognize Bluetooth BLE Becaon and get Infos   -
   -          like Devicename, Deviceadress and RSSI Value  -
   - Copyright : 2024 Armin Graewe, Muenster, Germany       -
   ----------------------------------------------------------*/

class MainActivity : Activity() {

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val bluetoothLeScanner = 
(getSystemService(Context.BLUETOOTH_SERVICE) as 
BluetoothManager).adapter.bluetoothLeScanner

        val rssi_msg = findViewById<View>(R.id.textView1) as TextView
        val rssi_list = findViewById<View>(R.id.textView2) as TextView
        val scroll = findViewById<View>(R.id.TimelineScrollbar) as 
ScrollView

        val BuStart = findViewById<View>(R.id.button_start) as Button
        val BuStop = findViewById<View>(R.id.button_stop) as Button

        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        val filters = mutableListOf<ScanFilter>()   // Leere Liste von 
Filtern sieht alle devices
        //filters.add(ScanFilter.Builder().setDeviceName("AirTag").build())
        filters.add(ScanFilter.Builder().setDeviceAddress("D6:49:48:05:D3:51").b 
uild())
        // Replace MAC Address with your own value or rem the above line 
out to see all Beacon

        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: 
ScanResult) {
                //Toast.makeText(getApplicationContext(), " Callback 
triggered ", Toast.LENGTH_SHORT).show()
                //rssi_msg.text = "Callback Triggered"
                //if (result.device.name != null && result.device.name 
== "AirTag") {
                //if (result.device.name != null) {
                //val name = result.device.name //"" 
//result.device.name
                val deviceAddress = result.device.address
                val rssi = result.rssi
                // Hier können Sie den RSSI-Wert des AirTag-Beacons 
verwenden
                Log.d("RSSI", "Device: $deviceAddress" + " " + "RSSI: 
$rssi")
                rssi_list.text = rssi_list.text.toString() + 
deviceAddress + " => " + rssi + "dBm\n"
                scroll.fullScroll(View.FOCUS_DOWN)
                rssi_msg.text = String.format("%02d  dBm", rssi)
                //}
            }
        }

        BuStart.setOnClickListener {
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.BLUETOOTH_SCAN
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then 
overriding
                //   public void onRequestPermissionsResult(int 
requestCode, String[] permissions,
                //                                          int[] 
grantResults)
                // to handle the case where the user grants the 
permission. See the documentation
                // for ActivityCompat#requestPermissions for more 
details.
                Toast.makeText(getApplicationContext(), " No Scan 
Permission ", Toast.LENGTH_SHORT)
                    .show()
                rssi_list.text = " No Scan Permission \n"
                return@setOnClickListener

            } else {
                // Hier wird der Scanner gestartet
                //bluetoothLeScanner?.startScan(filters, settings, 
scanCallback)
                bluetoothLeScanner?.startScan(filters, settings, 
scanCallback)
                Toast.makeText(getApplicationContext(), " Scanner 
Started ", Toast.LENGTH_SHORT)
                    .show()
                rssi_list.text = " Scanner Started \n"
            }
        }

        BuStop.setOnClickListener {
            // Hier wird der Scanner gestopped
            bluetoothLeScanner?.stopScan(scanCallback)
            Toast.makeText(getApplicationContext(), " Scanner Stopped ", 
Toast.LENGTH_SHORT)
                .show()
            rssi_list.text = rssi_list.text.toString() + " Scanner 
Stopped \n"
            scroll.fullScroll(View.FOCUS_DOWN)

            // Hier wird die Liste respeichert (for API 34 ext7 only)
            // Zu finden unter : 
/storage/emulated/0/Documents/YourCustomDirectory/RSSIwerte.txt
            val content = rssi_list.text as String
            val fileName = "RSSIwerte.txt"
            saveStringToMediaStore(this, fileName, content)
        }
    }

        // allgemeine File-Speicherfunktion (for API 34 ext7 only)
        // Files werden in : Documents/BeaconTest/ abgelegt
    fun saveStringToMediaStore(context: Context, fileName: String, 
content: String) {
        val values = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
            put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
            put(MediaStore.MediaColumns.RELATIVE_PATH, 
Environment.DIRECTORY_DOCUMENTS + "/BeaconTest")
        }

        val uri = 
context.contentResolver.insert(MediaStore.Files.getContentUri("external" 
),  values)

        uri?.let { outputStream ->
            context.contentResolver.openOutputStream(outputStream).use { 
it?.write(content.toByteArray()) }
        }
    }

    @SuppressLint("MissingPermission")
    override fun onDestroy() {
        super.onDestroy()
        //bluetoothLeScanner?.stopScan(scanCallback)
    }
}

Und zuletzt die AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android";
    xmlns:tools="http://schemas.android.com/tools">;

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
    <uses-permission 
android:name="android.permission.BLUETOOTH_CONNECT"/>
    <uses-permission 
android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission 
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission 
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission 
android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BeaconTest6"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.BeaconTest6">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category 
android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Ich hoffe jemandem damit geholfen zu haben :-)
Kommentare sind willkommen.
Gruß Armin
------------------------------------------------------------------
Ich weis noch auf welcher Seite man den Lötkolben anfasst :-)

von Armin G. (armin_graewe)


Angehängte Dateien:

Lesenswert?

Und hier den Code auch noch mal als Dateien :

Gruß Armin

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.