From 06c8c8787e28f5de20bc893c1b5e7101ab223e08 Mon Sep 17 00:00:00 2001 From: James Freiwirth Date: Sat, 10 May 2025 21:07:55 +0100 Subject: [PATCH 01/48] Quick and dirty script to publish the locations of each device onto an MQTT topic discoverable by home assistant --- .../LocateTracker/decrypt_locations.py | 57 +++++---- .../LocateTracker/location_request.py | 3 +- README.md | 40 +----- publish_mqtt.py | 118 ++++++++++++++++++ requirements.txt | 3 +- 5 files changed, 162 insertions(+), 59 deletions(-) create mode 100644 publish_mqtt.py diff --git a/NovaApi/ExecuteAction/LocateTracker/decrypt_locations.py b/NovaApi/ExecuteAction/LocateTracker/decrypt_locations.py index c0e7033..2a278ab 100644 --- a/NovaApi/ExecuteAction/LocateTracker/decrypt_locations.py +++ b/NovaApi/ExecuteAction/LocateTracker/decrypt_locations.py @@ -129,32 +129,47 @@ def decrypt_location_response_locations(device_update_protobuf): if not location_time_array: print("No locations found.") - return - - for loc in location_time_array: - - if loc.status == Common_pb2.Status.SEMANTIC: - print(f"Semantic Location: {loc.name}") - - else: - proto_loc = DeviceUpdate_pb2.Location() - proto_loc.ParseFromString(loc.decrypted_location) - - latitude = proto_loc.latitude / 1e7 - longitude = proto_loc.longitude / 1e7 - altitude = proto_loc.altitude - - print(f"Latitude: {latitude}") - print(f"Longitude: {longitude}") - print(f"Altitude: {altitude}") - print(f"Google Maps Link: {create_google_maps_link(latitude, longitude)}") - + return None + + # Return data from the most recent location + loc = location_time_array[0] + + if loc.status == Common_pb2.Status.SEMANTIC: + print(f"Semantic Location: {loc.name}") + location_data = { + 'semantic_location': loc.name, + 'timestamp': datetime.datetime.fromtimestamp(loc.time).strftime('%Y-%m-%d %H:%M:%S'), + 'status': loc.status, + 'is_own_report': loc.is_own_report + } + else: + proto_loc = DeviceUpdate_pb2.Location() + proto_loc.ParseFromString(loc.decrypted_location) + + latitude = proto_loc.latitude / 1e7 + longitude = proto_loc.longitude / 1e7 + altitude = proto_loc.altitude + + print(f"Latitude: {latitude}") + print(f"Longitude: {longitude}") + print(f"Altitude: {altitude}") + print(f"Google Maps Link: {create_google_maps_link(latitude, longitude)}") print(f"Time: {datetime.datetime.fromtimestamp(loc.time).strftime('%Y-%m-%d %H:%M:%S')}") print(f"Status: {loc.status}") print(f"Is Own Report: {loc.is_own_report}") print("-" * 40) - pass + location_data = { + 'latitude': latitude, + 'longitude': longitude, + 'altitude': altitude, + 'accuracy': loc.accuracy, + 'timestamp': datetime.datetime.fromtimestamp(loc.time).strftime('%Y-%m-%d %H:%M:%S'), + 'status': loc.status, + 'is_own_report': loc.is_own_report + } + + return location_data if __name__ == '__main__': diff --git a/NovaApi/ExecuteAction/LocateTracker/location_request.py b/NovaApi/ExecuteAction/LocateTracker/location_request.py index 9dec8a5..01cce3f 100644 --- a/NovaApi/ExecuteAction/LocateTracker/location_request.py +++ b/NovaApi/ExecuteAction/LocateTracker/location_request.py @@ -53,7 +53,8 @@ def handle_location_response(response): while result is None: asyncio.get_event_loop().run_until_complete(asyncio.sleep(0.1)) - decrypt_location_response_locations(result) + locations = decrypt_location_response_locations(result) + return locations if __name__ == '__main__': get_location_data_for_device(get_example_data("sample_canonic_device_id"), "Test") \ No newline at end of file diff --git a/README.md b/README.md index 488eab1..ce6caa3 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,8 @@ -# GoogleFindMyTools +# GoogleFindMyTools Home Assistant -This repository includes some useful tools that reimplement parts of Google's Find My Device Network. Note that the code of this repo is still very experimental. +This is a fork of https://github.com/leonboe1/GoogleFindMyTools -### What's possible? -Currently, it is possible to query Find My Device trackers and Android devices, read out their E2EE keys, and decrypt encrypted locations sent from the Find My Device network. You can also send register your own ESP32- or Zephyr-based trackers, as described below. +It includes a new script, publish_mqtt.py that will publish the location of all your devices to an MQTT broker. These devices are then discoverable by home assistant and you can display them on a map, make automations etc. -### How to use -- Clone this repository: `git clone` or download the ZIP file -- Change into the directory: `cd GoogleFindMyTools` -- Optional: Create venv: `python -m venv venv` -- Optional: Activate venv: `venv\Scripts\activate` (Windows) or `source venv/bin/activate` (Linux & macOS) -- Install all required packages: `pip install -r requirements.txt` -- Install the latest version of Google Chrome: https://www.google.com/chrome/ -- Start the program by running [main.py](main.py): `python main.py` or `python3 main.py` +Just run this script on a cronjob every so often to keep things up to date. -### Authentication - -On the first run, an authentication sequence is executed, which requires a computer with access to Google Chrome. - -The authentication results are stored in `Auth/secrets.json`. If you intend to run this tool on a headless machine, you can just copy this file to avoid having to use Chrome. - -### Known Issues -- "Your encryption data is locked on your device" is shown if you have never set up Find My Device on an Android device. Solution: Login with your Google Account on an Android device, go to Settings > Google > All Services > Find My Device > Find your offline devices > enable "With network in all areas" or "With network in high-traffic areas only". If "Find your offline devices" is not shown in Settings, you will need to download the Find My Device app from Google's Play Store, and pair a real Find My Device tracker with your device to force-enable the Find My Device network. -- No support for trackers using the P-256 curve and 32-Byte advertisements. Regular trackers don't seem to use this curve at all - I can only confirm that it is used with Sony's WH1000XM5 headphones. -- No support for the authentication process on ARM Linux -- Please also consider the issues listed in the [README in the ESP32Firmware folder](ESP32Firmware/README.md) if you want to register custom trackers. - -### Firmware for custom ESP32-based trackers -If you want to use an ESP32 as a custom Find My Device tracker, you can find the firmware in the folder ESP32Firmware. To register a new tracker, run main.py and press 'r' if you are asked to. Afterward, follow the instructions on-screen. - -For more information, check the [README in the ESP32Firmware folder](ESP32Firmware/README.md). - -### Firmware for custom Zephyr-based trackers -If you want to use a Zephyr-supported BLE device (e.g. nRF51/52) as a custom Find My Device tracker, you can find the firmware in the folder ZephyrFirmware. To register a new tracker, run main.py and press 'r' if you are asked to. Afterward, follow the instructions on-screen. - -For more information, check the [README in the ZephyrFirmware folder](ZephyrFirmware/README.md). - -### iOS App -You can also use my [iOS App](https://testflight.apple.com/join/rGqa2mTe) to access your Find My Device trackers on the go. diff --git a/publish_mqtt.py b/publish_mqtt.py new file mode 100644 index 0000000..9c97347 --- /dev/null +++ b/publish_mqtt.py @@ -0,0 +1,118 @@ +import json +import time +from typing import Dict, Any + +import paho.mqtt.client as mqtt +from NovaApi.ListDevices.nbe_list_devices import request_device_list +from NovaApi.ExecuteAction.LocateTracker.location_request import get_location_data_for_device +from ProtoDecoders.decoder import parse_device_list_protobuf, get_canonic_ids + +# MQTT Configuration +MQTT_BROKER = "192.168.1.10" # Change this to your MQTT broker address +MQTT_PORT = 1883 +MQTT_USERNAME = None # Set your MQTT username if required +MQTT_PASSWORD = None # Set your MQTT password if required +MQTT_CLIENT_ID = "google_find_my_publisher" + +# Home Assistant MQTT Discovery +DISCOVERY_PREFIX = "homeassistant" +DEVICE_PREFIX = "google_find_my" + +def on_connect(client: mqtt.Client, userdata: Any, flags: Dict, rc: int) -> None: + """Callback when connected to MQTT broker""" + print(f"Connected to MQTT broker with result code {rc}") + +def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str) -> None: + """Publish Home Assistant MQTT discovery configuration for a device""" + base_topic = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" + + # Device configuration for Home Assistant + config = { + "name": device_name, + "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", + "state_topic": f"{base_topic}/state", + "json_attributes_topic": f"{base_topic}/attributes", + "payload_home": "home", + "payload_not_home": "not_home", + "source_type": "gps", + "device": { + "identifiers": [f"{DEVICE_PREFIX}_{canonic_id}"], + "name": device_name, + "model": "Google Find My Device", + "manufacturer": "Google" + } + } + + # Publish discovery config + client.publish(f"{base_topic}/config", json.dumps(config), retain=True) + +def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, location_data: Dict) -> None: + """Publish device state and attributes to MQTT""" + base_topic = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" + + # Extract location data + lat = location_data.get('latitude') + lon = location_data.get('longitude') + accuracy = location_data.get('accuracy') + altitude = location_data.get('altitude') + timestamp = location_data.get('timestamp', time.time()) + + # Publish state (home/not_home/unknown) + state = "not_home" if lat and lon else "unknown" + client.publish(f"{base_topic}/state", state) + + # Publish attributes + attributes = { + "latitude": lat, + "longitude": lon, + "altitude": altitude, + "gps_accuracy": accuracy, + "source_type": "gps", + "last_updated": timestamp + } + client.publish(f"{base_topic}/attributes", json.dumps(attributes)) + +def main(): + # Initialize MQTT client + client = mqtt.Client(client_id=MQTT_CLIENT_ID) + client.on_connect = on_connect + + if MQTT_USERNAME and MQTT_PASSWORD: + client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + try: + client.connect(MQTT_BROKER, MQTT_PORT) + client.loop_start() + + print("Loading devices...") + result_hex = request_device_list() + device_list = parse_device_list_protobuf(result_hex) + canonic_ids = get_canonic_ids(device_list) + + print(f"Found {len(canonic_ids)} devices") + + # Publish discovery config and state for each device + for device_name, canonic_id in canonic_ids: + print(f"Processing device: {device_name}") + + # Publish discovery configuration + publish_device_config(client, device_name, canonic_id) + + # Get and publish location data + location_data = get_location_data_for_device(canonic_id, device_name) + publish_device_state(client, device_name, canonic_id, location_data) + + print(f"Published data for {device_name}") + + print("\nAll devices have been published to MQTT") + print("Devices will now be discoverable in Home Assistant") + print("You may need to restart Home Assistant or trigger device discovery") + + except Exception as e: + print(f"Error: {e}") + finally: + client.loop_stop() + client.disconnect() + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt index 8ddccc4..f08a576 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,5 @@ httpx>=0.28.0 h2>=4.1.0 setuptools>=75.6.0 aiohttp>=3.11.8 -http_ece>=1.1.0 \ No newline at end of file +http_ece>=1.1.0 +paho_mqtt From fec839593936d16d47cab1157dc9d00ee2c2832c Mon Sep 17 00:00:00 2001 From: James Freiwirth Date: Sun, 11 May 2025 08:00:38 +0100 Subject: [PATCH 02/48] Make script a bit more reliable --- publish_mqtt.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index 9c97347..96af721 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -18,9 +18,9 @@ DISCOVERY_PREFIX = "homeassistant" DEVICE_PREFIX = "google_find_my" -def on_connect(client: mqtt.Client, userdata: Any, flags: Dict, rc: int) -> None: +def on_connect(client, userdata, flags, result_code, properties): """Callback when connected to MQTT broker""" - print(f"Connected to MQTT broker with result code {rc}") + print(f"Connected to MQTT broker with result code {result_code}") def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str) -> None: """Publish Home Assistant MQTT discovery configuration for a device""" @@ -28,7 +28,7 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str # Device configuration for Home Assistant config = { - "name": device_name, + #"name": device_name, "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", "state_topic": f"{base_topic}/state", "json_attributes_topic": f"{base_topic}/attributes", @@ -42,9 +42,10 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str "manufacturer": "Google" } } - + print(f"{base_topic}/config") # Publish discovery config - client.publish(f"{base_topic}/config", json.dumps(config), retain=True) + r = client.publish(f"{base_topic}/config", json.dumps(config), retain=True) + return r def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, location_data: Dict) -> None: """Publish device state and attributes to MQTT""" @@ -58,7 +59,7 @@ def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, timestamp = location_data.get('timestamp', time.time()) # Publish state (home/not_home/unknown) - state = "not_home" if lat and lon else "unknown" + state = "unknown" client.publish(f"{base_topic}/state", state) # Publish attributes @@ -70,13 +71,14 @@ def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, "source_type": "gps", "last_updated": timestamp } - client.publish(f"{base_topic}/attributes", json.dumps(attributes)) + r = client.publish(f"{base_topic}/attributes", json.dumps(attributes)) + return r def main(): # Initialize MQTT client - client = mqtt.Client(client_id=MQTT_CLIENT_ID) + client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, MQTT_CLIENT_ID) client.on_connect = on_connect - + if MQTT_USERNAME and MQTT_PASSWORD: client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) @@ -96,12 +98,14 @@ def main(): print(f"Processing device: {device_name}") # Publish discovery configuration - publish_device_config(client, device_name, canonic_id) + msg_info = publish_device_config(client, device_name, canonic_id) + msg_info.wait_for_publish() # Get and publish location data location_data = get_location_data_for_device(canonic_id, device_name) - publish_device_state(client, device_name, canonic_id, location_data) - + msg_info = publish_device_state(client, device_name, canonic_id, location_data) + msg_info.wait_for_publish() + print(f"Published data for {device_name}") print("\nAll devices have been published to MQTT") From 817c390ce87c71992db0273bad22252298a7f55f Mon Sep 17 00:00:00 2001 From: James Freiwirth Date: Sun, 11 May 2025 08:00:58 +0100 Subject: [PATCH 03/48] Remove duplicate name --- publish_mqtt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index 96af721..8a98d5e 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -28,7 +28,6 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str # Device configuration for Home Assistant config = { - #"name": device_name, "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", "state_topic": f"{base_topic}/state", "json_attributes_topic": f"{base_topic}/attributes", From 8127ed5ec482dbc7f35cc204334c1e935dea773b Mon Sep 17 00:00:00 2001 From: James Freiwirth Date: Sun, 11 May 2025 08:06:23 +0100 Subject: [PATCH 04/48] Remove payload home/nothome --- publish_mqtt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index 8a98d5e..2a555a2 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -31,8 +31,6 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", "state_topic": f"{base_topic}/state", "json_attributes_topic": f"{base_topic}/attributes", - "payload_home": "home", - "payload_not_home": "not_home", "source_type": "gps", "device": { "identifiers": [f"{DEVICE_PREFIX}_{canonic_id}"], From 87bc772f1b25d4be585a36a0423484bf0abc44c0 Mon Sep 17 00:00:00 2001 From: James Freiwirth Date: Sun, 11 May 2025 14:58:20 +0100 Subject: [PATCH 05/48] Add timeout --- Auth/fcm_receiver.py | 101 +++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/Auth/fcm_receiver.py b/Auth/fcm_receiver.py index c49f9ef..1f2a103 100644 --- a/Auth/fcm_receiver.py +++ b/Auth/fcm_receiver.py @@ -1,31 +1,28 @@ import asyncio import base64 import binascii - from Auth.firebase_messaging import FcmRegisterConfig, FcmPushClient from Auth.token_cache import set_cached_value, get_cached_value class FcmReceiver: - _instance = None _listening = False - + def __new__(cls, *args, **kwargs): if cls._instance is None: - cls._instance = super(FcmReceiver, cls).__new__(cls, *args, **kwargs) + cls._instance = super(FcmReceiver, cls).__new__(cls) return cls._instance - + def __init__(self): if hasattr(self, '_initialized') and self._initialized: return self._initialized = True - + # Define Firebase project configuration project_id = "google.com:api-project-289722593072" app_id = "1:289722593072:android:3cfcf5bc359f0308" api_key = "AIzaSyD_gko3P392v6how2H7UpdeXQ0v2HLettc" message_sender_id = "289722593072" - fcm_config = FcmRegisterConfig( project_id=project_id, app_id=app_id, @@ -33,84 +30,102 @@ def __init__(self): messaging_sender_id=message_sender_id, bundle_id="com.google.android.apps.adm", ) - self.credentials = get_cached_value('fcm_credentials') self.location_update_callbacks = [] self.pc = FcmPushClient(self._on_notification, fcm_config, self.credentials, self._on_credentials_updated) - - - def register_for_location_updates(self, callback): - + self.listen_task = None + self.timeout_task = None + + def register_for_location_updates(self, callback, timeout_seconds=60): if not self._listening: - asyncio.get_event_loop().run_until_complete(self._register_for_fcm_and_listen()) - + asyncio.get_event_loop().run_until_complete( + self._register_for_fcm_and_listen(timeout_seconds) + ) self.location_update_callbacks.append(callback) - return self.credentials['fcm']['registration']['token'] - - + def stop_listening(self): + if self.timeout_task and not self.timeout_task.done(): + self.timeout_task.cancel() + if self.listen_task and not self.listen_task.done(): + self.listen_task.cancel() asyncio.get_event_loop().run_until_complete(self.pc.stop()) self._listening = False - - + def get_android_id(self): - if self.credentials is None: - return asyncio.get_event_loop().run_until_complete(self._register_for_fcm_and_listen()) - + return asyncio.get_event_loop().run_until_complete( + self._register_for_fcm_and_listen() + ) return self.credentials['gcm']['android_id'] - - + # Define a callback function for handling notifications def _on_notification(self, obj, notification, data_message): - + # Reset the timeout timer when we receive a notification + if self.timeout_task and not self.timeout_task.done(): + self.timeout_task.cancel() + # Check if the payload is present if 'data' in obj and 'com.google.android.apps.adm.FCM_PAYLOAD' in obj['data']: - # Decode the base64 string base64_string = obj['data']['com.google.android.apps.adm.FCM_PAYLOAD'] decoded_bytes = base64.b64decode(base64_string) - - # print("[FCMReceiver] Decoded FMDN Message:", decoded_bytes.hex()) - # Convert to hex string hex_string = binascii.hexlify(decoded_bytes).decode('utf-8') - for callback in self.location_update_callbacks: callback(hex_string) else: print("[FCMReceiver] Payload not found in the notification.") - - + def _on_credentials_updated(self, creds): self.credentials = creds - # Also store to disk set_cached_value('fcm_credentials', self.credentials) print("[FCMReceiver] Credentials updated.") - - + + async def _timeout_handler(self, timeout_seconds): + try: + await asyncio.sleep(timeout_seconds) + print(f"[FCMReceiver] Timed out after {timeout_seconds} seconds") + if self._listening: + await self.pc.stop() + self._listening = False + except asyncio.CancelledError: + # This is normal when a notification is received and the timeout is canceled + pass + async def _register_for_fcm(self): fcm_token = None - # Register or check in with FCM and get the FCM token while fcm_token is None: try: fcm_token = await self.pc.checkin_or_register() except Exception as e: await self.pc.stop() - print("[FCMReceiver] Failed to register with FCM. Retrying...") + print(f"[FCMReceiver] Failed to register with FCM: {str(e)}. Retrying...") await asyncio.sleep(5) - - - async def _register_for_fcm_and_listen(self): + + async def _register_for_fcm_and_listen(self, timeout_seconds=60): await self._register_for_fcm() - await self.pc.start() + + self.listen_task = asyncio.create_task(self.pc.start()) self._listening = True print("[FCMReceiver] Listening for notifications. This can take a few seconds...") - + + # Set up the timeout + if timeout_seconds > 0: + self.timeout_task = asyncio.create_task(self._timeout_handler(timeout_seconds)) if __name__ == "__main__": receiver = FcmReceiver() - print(receiver.get_android_id()) \ No newline at end of file + try: + # Example usage with a 30-second timeout + def on_location_update(hex_data): + print(f"Received location update: {hex_data[:20]}...") + + receiver.register_for_location_updates(on_location_update, timeout_seconds=30) + # Keep the main thread running + asyncio.get_event_loop().run_forever() + except KeyboardInterrupt: + print("Stopping...") + receiver.stop_listening() \ No newline at end of file From 572508842947af5362379fed5abc01284bb0aa64 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 17:47:30 +0200 Subject: [PATCH 06/48] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ce6caa3..7aca635 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# GoogleFindMyTools Home Assistant +# GoogleFindMyTools (Raspberry) Home Assistant -This is a fork of https://github.com/leonboe1/GoogleFindMyTools +Dies ist ein fork of https://github.com/endeavour/GoogleFindMyTools-homeassistant -It includes a new script, publish_mqtt.py that will publish the location of all your devices to an MQTT broker. These devices are then discoverable by home assistant and you can display them on a map, make automations etc. - -Just run this script on a cronjob every so often to keep things up to date. +Dieser Fork von GoogleFindMyTools ist für den Betrieb auf Raspberry OS gedacht, da es dort ansonsten Probleme mit Chromeium und dem login in Chrome gibt. Die Kommunikation findet über Mqqt zu Home Assistant (Mqqt Brocker) statt. +Da Google Find my Device entweder einen Standort in Koordinatenform oder einen String "home" bzw. "zuhause", wurde die publish_mqqt.py angepasst. Falls google nun den string zuhause sendet, ersetzt der Raspbbery diesen durch Koordinaten für die Home Zone. +Der Aufruf zum aktualisieren des Standortes erfolgt über Home Assisant via mqtt. In diesem sind die Kooardinaten für die Homezone (Koordinaten + Radius) enthalten. From 0f72d742e4cf4b5d24ac7286e8103485c9a50729 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 17:48:30 +0200 Subject: [PATCH 07/48] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7aca635..05917b3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Dies ist ein fork of https://github.com/endeavour/GoogleFindMyTools-homeassistant -Dieser Fork von GoogleFindMyTools ist für den Betrieb auf Raspberry OS gedacht, da es dort ansonsten Probleme mit Chromeium und dem login in Chrome gibt. Die Kommunikation findet über Mqqt zu Home Assistant (Mqqt Brocker) statt. +Dieser Fork von GoogleFindMyTools ist für den Betrieb auf Raspberry OS gedacht, da es dort ansonsten Probleme mit Chromeium und dem login in Chrome gibt. Die Kommunikation findet über Mqqt zu Home Assistant (Mqqt Brocker) statt, welches auf einem anderen Gerät läuft. Da Google Find my Device entweder einen Standort in Koordinatenform oder einen String "home" bzw. "zuhause", wurde die publish_mqqt.py angepasst. Falls google nun den string zuhause sendet, ersetzt der Raspbbery diesen durch Koordinaten für die Home Zone. Der Aufruf zum aktualisieren des Standortes erfolgt über Home Assisant via mqtt. In diesem sind die Kooardinaten für die Homezone (Koordinaten + Radius) enthalten. From 45ec362001d0a342573e7863edc41d4c88836279 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 18:42:23 +0200 Subject: [PATCH 08/48] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 05917b3..d66750d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Dies ist ein fork of https://github.com/endeavour/GoogleFindMyTools-homeassistant Dieser Fork von GoogleFindMyTools ist für den Betrieb auf Raspberry OS gedacht, da es dort ansonsten Probleme mit Chromeium und dem login in Chrome gibt. Die Kommunikation findet über Mqqt zu Home Assistant (Mqqt Brocker) statt, welches auf einem anderen Gerät läuft. + Da Google Find my Device entweder einen Standort in Koordinatenform oder einen String "home" bzw. "zuhause", wurde die publish_mqqt.py angepasst. Falls google nun den string zuhause sendet, ersetzt der Raspbbery diesen durch Koordinaten für die Home Zone. Der Aufruf zum aktualisieren des Standortes erfolgt über Home Assisant via mqtt. In diesem sind die Kooardinaten für die Homezone (Koordinaten + Radius) enthalten. +Da der Chrome Browser auf dem Raspberry beim "requstest url was not found on this server" meldet, kann man sich dort nicht einloggen. Man muss daher GoogleFindMyTools auf einem Windows PC installieren, sich einloggen und die secrets.json mit den Zugangsdaten von dem PC auf den Raspberry kopieren. Daher ist die Anleitung in drei Schritte aufgeteilt: Installation auf dem Windows PC, installation auf dem Raspberry OS und anschließend die Mqqt Verbindung zu Home Assistant. + From db287ce033387d3320dfef3f16623a634ade572f Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:11:45 +0200 Subject: [PATCH 09/48] Update README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index d66750d..acbf6de 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,47 @@ Der Aufruf zum aktualisieren des Standortes erfolgt über Home Assisant via mqtt Da der Chrome Browser auf dem Raspberry beim "requstest url was not found on this server" meldet, kann man sich dort nicht einloggen. Man muss daher GoogleFindMyTools auf einem Windows PC installieren, sich einloggen und die secrets.json mit den Zugangsdaten von dem PC auf den Raspberry kopieren. Daher ist die Anleitung in drei Schritte aufgeteilt: Installation auf dem Windows PC, installation auf dem Raspberry OS und anschließend die Mqqt Verbindung zu Home Assistant. +## Installation Windows PC: + +git installieren: https://git-scm.com/download/win
+Python installieren: https://www.python.org/downloads/windows/ + + +PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren +``` +git clone https://github.com/leonboe1/GoogleFindMyTools +``` + + +``` +python -m venv venv +``` +falls dass nicht geht ```& "C:\Users\[USER]\AppData\Local\Programs\Python\Python313\python.exe" -m venv venv``` +hier bei muss [USER] durch den PC User ersetzt werden bzw. wo auch immer wurde
+


+ + +``` +venv\Scripts\activate +``` +alternativ +``` +.\venv\Scripts\Activate.ps1 +``` +
+ + +``` +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process +``` + + + + +venv\Scripts\activate + + + +``` + +``` From 5f0e4cda9ef484ea0dcc481e816d873ee53ea57e Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:25:01 +0200 Subject: [PATCH 10/48] Update README.md --- README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index acbf6de..3306145 100644 --- a/README.md +++ b/README.md @@ -14,42 +14,38 @@ Da der Chrome Browser auf dem Raspberry beim "requstest url was not found on thi git installieren: https://git-scm.com/download/win
Python installieren: https://www.python.org/downloads/windows/ +Im Chromebrowser mit dem Nutzkonto einloggen. Wichtig: hiermit ist nicht die website von google.com gemeint, sondern das Chrome Desktop Programm! PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren ``` git clone https://github.com/leonboe1/GoogleFindMyTools ``` - - ``` python -m venv venv ``` falls dass nicht geht ```& "C:\Users\[USER]\AppData\Local\Programs\Python\Python313\python.exe" -m venv venv``` -hier bei muss [USER] durch den PC User ersetzt werden bzw. wo auch immer wurde
-


- - +hier bei muss [USER] durch den PC User ersetzt werden bzw. wo auch immer wurde ``` venv\Scripts\activate ``` -alternativ +alternativ ```.\venv\Scripts\Activate.ps1``` ``` -.\venv\Scripts\Activate.ps1 +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process ``` -
- - ``` -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process +cd GoogleFindMyTools +``` +``` +pip install -r requirements.txt +``` +``` +python main.py``` ``` +Es könnte nun der Fehler "undetected_chromedriver" kommen. In diesem Fall muss der Chromedriver separat installiert werden +Zuerst muss man die Chrome Version herausfinden: Öffne Chrome und gib chrome://settings/help in die Adresszeile ein. Notiere die Version, z.B. 114.0.5735.199 +Nun muss man den passenden ChromeDriver herunterladen: https://googlechromelabs.github.io/chrome-for-testing/. Wenn die erste Zahl z.B. 144 der Versionnummer übereinstimmt, reicht das. -venv\Scripts\activate - - - -``` -``` From 57dc1ab07976265a084b0dbd3bbd78032f50a487 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:37:21 +0200 Subject: [PATCH 11/48] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3306145..895f612 100644 --- a/README.md +++ b/README.md @@ -47,5 +47,6 @@ Es könnte nun der Fehler "undetected_chromedriver" kommen. In diesem Fall muss Zuerst muss man die Chrome Version herausfinden: Öffne Chrome und gib chrome://settings/help in die Adresszeile ein. Notiere die Version, z.B. 114.0.5735.199 Nun muss man den passenden ChromeDriver herunterladen: https://googlechromelabs.github.io/chrome-for-testing/. Wenn die erste Zahl z.B. 144 der Versionnummer übereinstimmt, reicht das. - +Entpacke die datei chromedriver.exe nach C:\Tools\chromedriver\
+Nun müssen wir noch den dateipfad anpassen, damit GoogleFindMyTools weiß wo dieser liegt. From b2379af6de6b41fb06a7677cd4636e38101cda8f Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:43:00 +0200 Subject: [PATCH 12/48] Update README.md --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/README.md b/README.md index 895f612..0295b06 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,80 @@ Nun muss man den passenden ChromeDriver herunterladen: https://googlechromelabs. Entpacke die datei chromedriver.exe nach C:\Tools\chromedriver\
Nun müssen wir noch den dateipfad anpassen, damit GoogleFindMyTools weiß wo dieser liegt. +Gehe unter C:\WINDOWS\system32\GoogleFindMyTools\chrome_driver.py und öffne die Datei als Admin + + +wir müssen folgenden Block +``` +def create_driver(): + """Create a Chrome WebDriver with undetected_chromedriver.""" + + try: + chrome_options = get_options() + driver = uc.Chrome(options=chrome_options) + print("[ChromeDriver] Installed and browser started.") + return driver + except Exception: + print("[ChromeDriver] Default ChromeDriver creation failed. Trying alternative paths...") + + chrome_path = find_chrome() + if chrome_path: + chrome_options = get_options() + chrome_options.binary_location = chrome_path + try: + driver = uc.Chrome(options=chrome_options) + print(f"[ChromeDriver] ChromeDriver started using {chrome_path}") + return driver + except Exception as e: + print(f"[ChromeDriver] ChromeDriver failed using path {chrome_path}: {e}") + else: + print("[ChromeDriver] No Chrome executable found in known paths.") + + raise Exception( + "[ChromeDriver] Failed to install ChromeDriver. A current version of Chrome was not detected on your system.\n" + "If you know that Chrome is installed, update Chrome to the latest version. If the script is still not working, " + "set the path to your Chrome executable manually inside the script." + ) +``` + +durch diesen ersetzten + +``` +def create_driver(): + """Create a Chrome WebDriver with undetected_chromedriver.""" + + try: + chrome_options = get_options() + driver = uc.Chrome(options=chrome_options) + print("[ChromeDriver] Installed and browser started.") + return driver + except Exception: + print("[ChromeDriver] Default ChromeDriver creation failed. Trying alternative paths...") + + chrome_path = find_chrome() + if chrome_path: + chrome_options = get_options() + #chrome_options.binary_location = chrome_path + chrome_options.debugger_address = "127.0.0.1:9222" + try: + #driver = uc.Chrome(options=chrome_options) + driver = uc.Chrome( + options=chrome_options, + driver_executable_path="C:\\Tools\\chromedriver\\chromedriver.exe", + use_subprocess=False + ) + print(f"[ChromeDriver] ChromeDriver started using {chrome_path}") + return driver + except Exception as e: + print(f"[ChromeDriver] ChromeDriver failed using path {chrome_path}: {e}") + else: + print("[ChromeDriver] No Chrome executable found in known paths.") + + raise Exception( + "[ChromeDriver] Failed to install ChromeDriver. A current version of Chrome was not detected on your system.\n" + "If you know that Chrome is installed, update Chrome to the latest version. If the script is still not working, " + "set the path to your Chrome executable manually inside the script." + ) +``` + +Hierdurch wird unser neuer Chromedriver verwendet und Chrome in Debug modus geöffnet. Melde dich dort im Chorme mit deinem Google Profil noch einmal an (wichtig: nicht die Website sondern im Chrome). Schließe alle Browser Fenster From b9de5cdec9a693040a967e49e267546eb43d1fd9 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:45:19 +0200 Subject: [PATCH 13/48] Update README.md --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0295b06..fb5cb1c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,10 @@ pip install -r requirements.txt ``` python main.py``` ``` - +
+
+
+
Es könnte nun der Fehler "undetected_chromedriver" kommen. In diesem Fall muss der Chromedriver separat installiert werden Zuerst muss man die Chrome Version herausfinden: Öffne Chrome und gib chrome://settings/help in die Adresszeile ein. Notiere die Version, z.B. 114.0.5735.199 @@ -127,3 +130,17 @@ def create_driver(): ``` Hierdurch wird unser neuer Chromedriver verwendet und Chrome in Debug modus geöffnet. Melde dich dort im Chorme mit deinem Google Profil noch einmal an (wichtig: nicht die Website sondern im Chrome). Schließe alle Browser Fenster + +gebe nun +``` +& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="C:\ChromeDebugTemp" +``` +in powershell ein und führe +``` +python main.py``` +``` +noch einmal aus. Nun sollte es funktionieren + + + + From 9ed979b1849bf762c1fcd0a56d5eab4973a99f76 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 19:57:12 +0200 Subject: [PATCH 14/48] Update README.md --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb5cb1c..5b630fe 100644 --- a/README.md +++ b/README.md @@ -137,10 +137,47 @@ gebe nun ``` in powershell ein und führe ``` -python main.py``` +python main.py ``` noch einmal aus. Nun sollte es funktionieren +## Installation Raspberry: +Für einen Aufbau habe ich einen Raspberry 2 b verwendet und dort Raspberry OS Bookworm verwendet. Die Verbindung kann man mit Putty (Port 22) von Windows zum Raspberry herstellen. Anschließend mit User und Passwort anmelden. + +``` +sudo apt install chromium-browser +``` +``` +sudo apt install chromium-browser +``` +Es es klappt kann man sich über die UI im Chromebrowser anmelden. Falls nicht "requstest url was not found on this server", muss man die secrets.json von windows später zum Raspberry kopieren + + +Installieren wir nun GoogleFindMyTools +``` +git clone https://github.com/xHecktor/GoogleFindMyTools-homeassistant.git ~/GoogleFindMyTools +``` +``` +cd ~/GoogleFindMyTools +``` +``` +python3 -m venv venv +``` +``` +source venv/bin/activate +``` +``` +``` +``` +v +``` + + + + + + + From 01a45327a92196f0d432f05174b3a433774e6909 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 20:07:12 +0200 Subject: [PATCH 15/48] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b630fe..e9d1401 100644 --- a/README.md +++ b/README.md @@ -169,10 +169,12 @@ python3 -m venv venv source venv/bin/activate ``` ``` +pip install -r requirements.txt ``` ``` -v +python3 main.py ``` +oder ```python main.py``` From 7c18645d9ef120c6f754f74203f4c8ecb9758c7f Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 20:16:31 +0200 Subject: [PATCH 16/48] Update README.md --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9d1401..0e0f220 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,10 @@ in powershell ein und führe ``` python main.py ``` -noch einmal aus. Nun sollte es funktionieren +noch einmal aus. Nun sollte es funktionieren. + + +PowerShell lassen wir geöffnet und richten nun den Raspberry ein. @@ -177,6 +180,14 @@ python3 main.py oder ```python main.py``` +hier wird nun ein Fehler wegen der Zugangsdaten von Chrome Browser kommen. Daher gehen wir wieder in Powershell und übertragen nun die Zugangsdaten von Windows zum Raspberry. +In PowerShell folgenes eintrippen: +``` +scp Auth\secrets.json admin@raspberrypi.local:~/GoogleFindMyTools/Auth/ +``` +ggf. muss hier admin durch den User und raspberry durch den Gerätenamen im oben link ersetzt werden. + + From fb290a31448e9b761ac99245fd373af5a276ba69 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 20:28:22 +0200 Subject: [PATCH 17/48] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 0e0f220..55fc01a 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,18 @@ scp Auth\secrets.json admin@raspberrypi.local:~/GoogleFindMyTools/Auth/ ggf. muss hier admin durch den User und raspberry durch den Gerätenamen im oben link ersetzt werden. +

+nun müssen wir die Daten vom Mqtt Brocker eintragen. Daher wieder in Putty die publish_mqtt.py auf dem Raspberry öffenen +``` +nano publish_mqtt.py +``` +und folgende Felder anpassten: + +>MQTT_BROKER = "192.168.1.100" # Ändere die IP zu der von Home Assistant +MQTT_PORT = 1883 +MQTT_USERNAME = "mqttuser" # Ändere einen Usernamen +MQTT_PASSWORD = "password" # Ändere dein Passwort From f26e46388b84816b7d44c4e199501be0c96d3322 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 20:43:42 +0200 Subject: [PATCH 18/48] Update README.md --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55fc01a..1aa270c 100644 --- a/README.md +++ b/README.md @@ -195,13 +195,117 @@ nano publish_mqtt.py ``` und folgende Felder anpassten: - ->MQTT_BROKER = "192.168.1.100" # Ändere die IP zu der von Home Assistant +``` +MQTT_BROKER = "192.168.1.100" # Ändere die IP zu der von Home Assistant MQTT_PORT = 1883 MQTT_USERNAME = "mqttuser" # Ändere einen Usernamen MQTT_PASSWORD = "password" # Ändere dein Passwort +``` +Drücke STG+X, dann Y und Enter + +Nun richten wir einen Autstart ein +``` +cd /home/admin +``` +``` +nano update_location.sh +``` +und dort folgendes einfügen: +``` +#!/bin/bash +cd /home/admin/GoogleFindMyTools +source venv/bin/activate +python3 publish_mqtt.py +``` +Drücke STG+X, dann Y und Enter +``` +chmod +x update_location.sh +``` +Ausführung testen: +``` +./update_location.sh +``` + +


+Nun erstellen wir noch einen Listener, um die Trigger von Home Assistant zu empfangen: +``` +nano mqtt_listener.py +``` +hier müssen auch wieder die Zugangsdaten des Mqtt Brockers angepasst werden + +``` +import paho.mqtt.client as mqtt +import subprocess +import datetime +import json + +MQTT_BROKER = "192.168.1100" +MQTT_PORT = 1883 +MQTT_TOPIC = "googlefindmytools/trigger/update" +MQTT_USER = "mqttuser" +MQTT_PASS = "password" +def on_connect(client, userdata, flags, rc, properties=None): + print("Connected with result code " + str(rc)) + client.subscribe(MQTT_TOPIC) +def on_message(client, userdata, msg): + print(f"Received trigger: {msg.payload.decode()}") + + try: + # JSON laden + payload = json.loads(msg.payload.decode()) + + # Optional: Nur ausführen, wenn bestimmte Keys vorhanden sind + if "lat_home" in payload and "lon_home" in payload and "home_radius" in payload: + print("→ Konfiguration aus Trigger empfangen, sende retained an googlefindmytools/config") + + # An config-Topic weiterleiten (retain!) + client.publish("googlefindmytools/config", json.dumps(payload), retain=True) + + else: + print("→ Kein gültiger Konfigurations-Payload – wird ignoriert.") + + except json.JSONDecodeError: + print("→ Kein JSON – evtl. nur 'start' ohne Konfiguration") + + # Skript immer starten, egal ob mit oder ohne Konfiguration + print("Running update_location.sh...") + + try: + result = subprocess.run( + ["/home/admin/update_location.sh"], + capture_output=True, + text=True, + timeout=120 + ) + print(" STDOUT:\n", result.stdout) + print(" STDERR:\n", result.stderr) + + now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if result.returncode == 0: + status_msg = f" Standortdaten erfolgreich aktualisiert um {now}." + else: + status_msg = f" Fehler bei der Standortaktualisierung ({result.returncode}) um {now}.\n{result.stderr}" + except Exception as e: + import traceback + status_msg = f" Ausnahmefehler: {e}\n{traceback.format_exc()}" + + client.publish("googlefindmytools/status", status_msg) + + + +client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) +client.username_pw_set(MQTT_USER, MQTT_PASS) +client.on_connect = on_connect +client.on_message = on_message + + +client.connect(MQTT_BROKER, MQTT_PORT, 60) +client.loop_forever() + +``` +Drücke STG+X, dann Y und Enter From 2276c9d62c9509e1f3defdf25ba0256de9eb068b Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:00:31 +0200 Subject: [PATCH 19/48] Update README.md --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1aa270c..8c46e44 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,13 @@ Python installieren: https://www.python.org/downloads/windows/ Im Chromebrowser mit dem Nutzkonto einloggen. Wichtig: hiermit ist nicht die website von google.com gemeint, sondern das Chrome Desktop Programm! PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren +``` +sudo apt install systemd-networkd-wait-online +``` +``` +sudo systemctl enable systemd-networkd-wait-online.service +``` + ``` git clone https://github.com/leonboe1/GoogleFindMyTools ``` @@ -239,7 +246,7 @@ import subprocess import datetime import json -MQTT_BROKER = "192.168.1100" +MQTT_BROKER = "192.168.100" MQTT_PORT = 1883 MQTT_TOPIC = "googlefindmytools/trigger/update" MQTT_USER = "mqttuser" @@ -308,4 +315,68 @@ client.loop_forever() Drücke STG+X, dann Y und Enter +``` +chmod +x mqtt_listener.py +``` +hiermit kann man den listener testen +``` +python3 ~/mqtt_listener.py +``` + +


+Abschließend müssen wir noch den Listener Service erstellen +``` +sudo nano /etc/systemd/system/mqtt_listener.service +``` +hier muss folgendes hineinkopiert werden: +``` + +[Unit] +Description=MQTT Listener for Google Find My Tools +Wants=network-online.target +After=network.target +#After=network-online.target + + +[Service] +ExecStart=/home/admin/GoogleFindMyTools/venv/bin/python /home/admin/mqtt_listener.py +WorkingDirectory=/home/admin +StandardOutput=journal +StandardError=journal +Restart=always +User=admin +Environment="PATH=/home/admin/GoogleFindMyTools/venv/bin" + +[Install] +WantedBy=multi-user.target +``` +Drücke STG+X, dann Y und Enter +``` +sudo systemctl daemon-reexec +``` +``` +sudo systemctl daemon-reload +``` +``` +sudo systemctl enable mqtt_listener.service +``` +``` +sudo systemctl start mqtt_listener.service +``` +listener Service testen: +``` +sudo systemctl status mqtt_listener.service +``` +``` +journalctl -u mqtt_listener.service -f +``` +



+wenn man sich später die kommunikation mit HA anschauen möchte: +``` +cd /home/admin +source ~/GoogleFindMyTools/venv/bin/activate +python3 ~/mqtt_listener.py +``` +Str + c zum beenden + From c335981008477c2bc022143d0e9cafd13ab22f59 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:19:25 +0200 Subject: [PATCH 20/48] Update README.md --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 8c46e44..73ead97 100644 --- a/README.md +++ b/README.md @@ -380,3 +380,32 @@ python3 ~/mqtt_listener.py Str + c zum beenden +# Home Assistant +Abschnließend müssen wir noch den Broker in Home Assistant installieren + +In der configuration.yaml +``` +mqtt: +``` +hinzufügen + +Als nächstes müssen wir einen neuen User zu HA hinzufügen. +Einstellungen/Personen/Benutzer hinzufügen +Dieser Username und Passwort muss mit dem übereinstimmen, was wir oben im Raspberry bereits hinterlegt haben. + + +Nun installieren wir den Mqqt Broker: +In Home Assistant Einstellungen/ Geräte&Dienste Integration hinzufügen drücken und nach Mqtt suchen und installieren und das offizielle Mqqt auswählen (nicht das manuelle mit den Benutzerdetails) + +Nun richten wir den Broker ein: +Einstellungen/ Geräte&Dienste / MQTT + +Dort gibt es nun einen Integrationseintrag: "Mosquitto Mqtt Broker". Dort gehen wir auf die drei Punkte und wählen "Neu konfigueren" aus. +Folgendes geben wir ein: +Server: core-mosquitto +Port: 1883 +Benutzername: Dein User den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py +Passwort: Dein Passwort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py + + + From cc431b0931e13669d2cfa5438a082b4cf84a09d0 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:20:21 +0200 Subject: [PATCH 21/48] Update README.md --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 73ead97..0c64a78 100644 --- a/README.md +++ b/README.md @@ -389,23 +389,23 @@ mqtt: ``` hinzufügen -Als nächstes müssen wir einen neuen User zu HA hinzufügen. -Einstellungen/Personen/Benutzer hinzufügen -Dieser Username und Passwort muss mit dem übereinstimmen, was wir oben im Raspberry bereits hinterlegt haben. +Als nächstes müssen wir einen neuen User zu HA hinzufügen.
+Einstellungen/Personen/Benutzer hinzufügen
+Dieser Username und Passwort muss mit dem übereinstimmen, was wir oben im Raspberry bereits hinterlegt haben.
-Nun installieren wir den Mqqt Broker: -In Home Assistant Einstellungen/ Geräte&Dienste Integration hinzufügen drücken und nach Mqtt suchen und installieren und das offizielle Mqqt auswählen (nicht das manuelle mit den Benutzerdetails) +Nun installieren wir den Mqqt Broker:
+In Home Assistant Einstellungen/ Geräte&Dienste Integration hinzufügen drücken und nach Mqtt suchen und installieren und das offizielle Mqqt auswählen (nicht das manuelle mit den Benutzerdetails)
-Nun richten wir den Broker ein: -Einstellungen/ Geräte&Dienste / MQTT +Nun richten wir den Broker ein:
+Einstellungen/ Geräte&Dienste / MQTT

-Dort gibt es nun einen Integrationseintrag: "Mosquitto Mqtt Broker". Dort gehen wir auf die drei Punkte und wählen "Neu konfigueren" aus. -Folgendes geben wir ein: -Server: core-mosquitto -Port: 1883 -Benutzername: Dein User den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py -Passwort: Dein Passwort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py +Dort gibt es nun einen Integrationseintrag: "Mosquitto Mqtt Broker". Dort gehen wir auf die drei Punkte und wählen "Neu konfigueren" aus.
+Folgendes geben wir ein:
+Server: core-mosquitto
+Port: 1883
+Benutzername: Dein User den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
+Passwort: Dein Passwort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
From 6229540c485eb72131e4aaa723edf6a4748383d5 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:28:38 +0200 Subject: [PATCH 22/48] Update README.md --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 0c64a78..a2d86b8 100644 --- a/README.md +++ b/README.md @@ -407,5 +407,45 @@ Port: 1883
Benutzername: Dein User den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
Passwort: Dein Passwort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
+



+Nun erstellen wir den Aufruf zum Orten der Google Tags +Einstellungen/Automatisierungen&Szenen/Skripte/ Neues Skript hinzufügen + + +Hier kannst du die Koordinaten und den Umkreis von deinem Zuhause definieren. Wenn nun der Google Tag anstatt von Koordinaten nur "Zuhause" meldet, wird dieses in Koordinaten umgewandelt, damit Home Assisant den Tracker anzeigen kann. +``` +alias: Google Tracker aktualisieren +sequence: + - data: + topic: googlefindmytools/trigger/update + payload: "{ \"lat_home\": 31.8909528, \"lon_home\": 7.1904316, \"home_radius\": 500 }" + action: mqtt.publish +``` +speichern und ausführen. Die Google Tags sollten nun in Home Assistan angezeigt werden. + +Zuletzt legen wir noch eine Automatisierung ab, um den Standort alle 15 min zu aktualisieren: +Einstellungen/Automatisierungen&Szenen/ Automatisierung erstellen + +``` +alias: Google_Airtag +description: "" +triggers: + - trigger: time_pattern + minutes: /15 +conditions: [] +actions: + - action: script.update_google_locations + metadata: {} + data: {} +mode: single +``` +speichern + + + +So fertig sind wir + + + From 70dd7790a5d3671b806d3a505ec805a98d9efb15 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:31:35 +0200 Subject: [PATCH 23/48] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a2d86b8..0911332 100644 --- a/README.md +++ b/README.md @@ -249,8 +249,8 @@ import json MQTT_BROKER = "192.168.100" MQTT_PORT = 1883 MQTT_TOPIC = "googlefindmytools/trigger/update" -MQTT_USER = "mqttuser" -MQTT_PASS = "password" +MQTT_USER = "DeinMqqtUser" +MQTT_PASS = "DeinMqttPasswort" def on_connect(client, userdata, flags, rc, properties=None): print("Connected with result code " + str(rc)) @@ -391,7 +391,7 @@ hinzufügen Als nächstes müssen wir einen neuen User zu HA hinzufügen.
Einstellungen/Personen/Benutzer hinzufügen
-Dieser Username und Passwort muss mit dem übereinstimmen, was wir oben im Raspberry bereits hinterlegt haben.
+Dieser DeinMqqtUser und DeinMqqtPasswort muss mit dem übereinstimmen, was wir oben im Raspberry bereits hinterlegt haben.
Nun installieren wir den Mqqt Broker:
@@ -404,8 +404,8 @@ Dort gibt es nun einen Integrationseintrag: "Mosquitto Mqtt Broker". Dort gehen Folgendes geben wir ein:
Server: core-mosquitto
Port: 1883
-Benutzername: Dein User den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
-Passwort: Dein Passwort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
+Benutzername: DeinMqqtUser den du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py
+Passwort: DeinMqttPasswort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py




Nun erstellen wir den Aufruf zum Orten der Google Tags From 303d22a39a6ed664ef1671771ef9ef887d8aaecf Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:32:31 +0200 Subject: [PATCH 24/48] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0911332..431725f 100644 --- a/README.md +++ b/README.md @@ -205,8 +205,8 @@ und folgende Felder anpassten: ``` MQTT_BROKER = "192.168.1.100" # Ändere die IP zu der von Home Assistant MQTT_PORT = 1883 -MQTT_USERNAME = "mqttuser" # Ändere einen Usernamen -MQTT_PASSWORD = "password" # Ändere dein Passwort +MQTT_USERNAME = "DeinMqttUser" # Ändere einen Usernamen +MQTT_PASSWORD = "DeinMqttPasswort" # Ändere dein Passwort ``` Drücke STG+X, dann Y und Enter From f0d77331e806bba28dfc0c26170571ca299159df Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:34:39 +0200 Subject: [PATCH 25/48] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 431725f..bd9d3d1 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Für einen Aufbau habe ich einen Raspberry 2 b verwendet und dort Raspberry OS B sudo apt install chromium-browser ``` ``` -sudo apt install chromium-browser +sudo apt install chromium-chromedriver ``` Es es klappt kann man sich über die UI im Chromebrowser anmelden. Falls nicht "requstest url was not found on this server", muss man die secrets.json von windows später zum Raspberry kopieren From c1848ee65423935fc148e78d308dc452113bb942 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:35:55 +0200 Subject: [PATCH 26/48] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd9d3d1..f78787f 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,7 @@ Python installieren: https://www.python.org/downloads/windows/ Im Chromebrowser mit dem Nutzkonto einloggen. Wichtig: hiermit ist nicht die website von google.com gemeint, sondern das Chrome Desktop Programm! PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren -``` -sudo apt install systemd-networkd-wait-online -``` -``` -sudo systemctl enable systemd-networkd-wait-online.service -``` + ``` git clone https://github.com/leonboe1/GoogleFindMyTools @@ -164,6 +159,12 @@ sudo apt install chromium-chromedriver ``` Es es klappt kann man sich über die UI im Chromebrowser anmelden. Falls nicht "requstest url was not found on this server", muss man die secrets.json von windows später zum Raspberry kopieren +``` +sudo apt install systemd-networkd-wait-online +``` +``` +sudo systemctl enable systemd-networkd-wait-online.service +``` Installieren wir nun GoogleFindMyTools ``` From 12d78cc377e200e49956f7ca2782bf1aaa136aba Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:45:07 +0200 Subject: [PATCH 27/48] Update requirements.txt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chromedriver kann nicht direkt in raspberry verwendet werden. Daher wird die secrets.json von einem anderen Gerät kopiert --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f08a576..5adb8e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -undetected-chromedriver>=3.5.5 +#undetected-chromedriver>=3.5.5 selenium>=4.27.1 gpsoauth>=1.1.1 requests>=2.32.3 From b8aec88b91199f741e825a5b63d688ef38dabbaf Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:49:21 +0200 Subject: [PATCH 28/48] Update publish_mqtt.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home zone wird festgelegt in Koordinaten falls Google Tag nur semantisch "zuhause" ausgibt Home Zone wird in der Mqtt Nachricht von Home Assistant an Raspberry bestimmt Trigger zum ausführen kommt von Home Assistent --- publish_mqtt.py | 226 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 199 insertions(+), 27 deletions(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index 2a555a2..a866ed4 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -1,31 +1,94 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jul 15 16:08:02 2025 + +@author: Jan +""" + import json import time from typing import Dict, Any +from math import radians, cos, sin, asin, sqrt import paho.mqtt.client as mqtt from NovaApi.ListDevices.nbe_list_devices import request_device_list from NovaApi.ExecuteAction.LocateTracker.location_request import get_location_data_for_device from ProtoDecoders.decoder import parse_device_list_protobuf, get_canonic_ids # MQTT Configuration -MQTT_BROKER = "192.168.1.10" # Change this to your MQTT broker address +MQTT_BROKER = "192.168.3.65" # Change this to your MQTT broker address MQTT_PORT = 1883 -MQTT_USERNAME = None # Set your MQTT username if required -MQTT_PASSWORD = None # Set your MQTT password if required +MQTT_USERNAME = "mqttuser" # Set your MQTT username if required +MQTT_PASSWORD = "Coconuts1990" # Set your MQTT password if required +lat_home = 0 #48.8909528 # DEIN Zuhause-Breitengrad +lon_home = 0 #9.1904316 # DEIN Zuhause-Längengrad +home_cycle = 0 #200 # Umkreis der Homezone in [m] +config_received = False + MQTT_CLIENT_ID = "google_find_my_publisher" +current_home_config = { + "lat_home": lat_home, + "lon_home": lon_home, + "home_radius": home_cycle +} + # Home Assistant MQTT Discovery DISCOVERY_PREFIX = "homeassistant" DEVICE_PREFIX = "google_find_my" +def on_any_message(client, userdata, msg): + print(f"ANY MSG: {msg.topic} → {msg.payload.decode()}") + + + def on_connect(client, userdata, flags, result_code, properties): """Callback when connected to MQTT broker""" + print("===> on_config_message aufgerufen!") print(f"Connected to MQTT broker with result code {result_code}") + client.subscribe("googlefindmytools/config") + print(current_home_config) + + + +def calculate_distance(lat1, lon1, lat2, lon2): + """Berechnet Entfernung zwischen zwei GPS-Koordinaten in Metern.""" + lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 + c = 2 * asin(sqrt(a)) + return 6371000 * c # Erdradius in Metern + + +def on_config_message(client, userdata, msg): + global current_home_config, config_received + print(f"[MQTT] Nachricht empfangen auf Topic: {msg.topic}") + print(f"[MQTT] Payload: {msg.payload.decode()}") + try: + payload = json.loads(msg.payload.decode()) + lat = float(payload.get("lat_home", current_home_config["lat_home"])) + lon = float(payload.get("lon_home", current_home_config["lon_home"])) + radius = int(payload.get("home_radius", current_home_config["home_radius"])) + + current_home_config["lat_home"] = lat + current_home_config["lon_home"] = lon + current_home_config["home_radius"] = radius + + config_received = True # Markiere, dass Konfiguration eingetroffen ist + + print(f"[Config] Neue Home-Zone: lat={lat}, lon={lon}, radius={radius} m") + except Exception as e: + print(f"[Config] Fehler beim Verarbeiten der Konfiguration: {e}") + + + + def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str) -> None: """Publish Home Assistant MQTT discovery configuration for a device""" base_topic = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" - + # Device configuration for Home Assistant config = { "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", @@ -47,18 +110,51 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, location_data: Dict) -> None: """Publish device state and attributes to MQTT""" base_topic = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" - + # Extract location data lat = location_data.get('latitude') lon = location_data.get('longitude') accuracy = location_data.get('accuracy') altitude = location_data.get('altitude') timestamp = location_data.get('timestamp', time.time()) - + + if (lat is None or lon is None) and location_data.get("semantic_location") == "Zuhause": + #lat = lat_home + #lon = lon_home + lat = current_home_config["lat_home"] + lon = current_home_config["lon_home"] + + print(f"[Fallback] Semantische Position erkannt: '{location_data.get('semantic_location', 'Unbekannt')}' → Fallback-Koordinaten werden verwendet.") + print(f"[Fallback] Semantische Position erkannt: 'Zuhause' → Fallback-Koordinaten werden verwendet.") + + # Publish state (home/not_home/unknown) state = "unknown" + + if lat is not None and lon is not None: + + lat_home_cfg = current_home_config["lat_home"] + lon_home_cfg = current_home_config["lon_home"] + home_radius_cfg = current_home_config["home_radius"] + + dist = calculate_distance(lat_home_cfg, lon_home_cfg, lat, lon) + if dist < home_radius_cfg: + state = "home" + else: + state = "not_home" + elif location_data.get("semantic_location") == "Zuhause": + lat = lat_home_cfg + lon = lon_home_cfg + state = "home" + print(f"[Fallback] Semantische Position erkannt: '{location_data.get('semantic_location')}' → Fallback-Koordinaten werden verwendet.") + else: + state = "unkown" + + + + client.publish(f"{base_topic}/state", state) - + # Publish attributes attributes = { "latitude": lat, @@ -66,7 +162,9 @@ def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, "altitude": altitude, "gps_accuracy": accuracy, "source_type": "gps", - "last_updated": timestamp + "last_updated": timestamp, + "semantic_location": location_data.get("semantic_location") + } r = client.publish(f"{base_topic}/attributes", json.dumps(attributes)) return r @@ -74,46 +172,120 @@ def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, def main(): # Initialize MQTT client client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, MQTT_CLIENT_ID) + #client.subscribe("googlefindmytools/config") client.on_connect = on_connect + client.on_message = on_any_message + + client.message_callback_add("googlefindmytools/config", on_config_message) + #client.subscribe("googlefindmytools/config") + if MQTT_USERNAME and MQTT_PASSWORD: client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) - + try: + print("Starte Verbindung zu MQTT...") client.connect(MQTT_BROKER, MQTT_PORT) client.loop_start() - + print("→ MQTT Verbindung gestartet") + print("Warte auf MQTT-Konfiguration... (max 5 Sekunden)") + timeout = 5 + waited = 0 + while not config_received and waited < timeout: + time.sleep(0.1) + waited += 0.1 + if int(waited * 10) % 10 == 0: # jede volle Sekunde + print(f" ...warte seit {int(waited)}s") + if config_received: + print("Konfiguration empfangen.") + else: + print("Keine Konfiguration empfangen – verwende Default-Werte.") + + print("Loading devices...") result_hex = request_device_list() device_list = parse_device_list_protobuf(result_hex) canonic_ids = get_canonic_ids(device_list) - + print(f"Found {len(canonic_ids)} devices") - + # Publish discovery config and state for each device for device_name, canonic_id in canonic_ids: + print("\n" + "=" * 60) print(f"Processing device: {device_name}") - - # Publish discovery configuration - msg_info = publish_device_config(client, device_name, canonic_id) - msg_info.wait_for_publish() - + print("=" * 60) + + + # Publish discovery config (optional – funktioniert nicht zwingend jedes Mal) + try: + if client.is_connected(): + msg_info = publish_device_config(client, device_name, canonic_id) + msg_info.wait_for_publish() + else: + print(f"[Discovery] MQTT nicht verbunden – Discovery für {device_name} übersprungen.") + except Exception as e: + print(f"[Discovery] Error publishing config for {device_name}: {e}") + # Get and publish location data - location_data = get_location_data_for_device(canonic_id, device_name) - msg_info = publish_device_state(client, device_name, canonic_id, location_data) - msg_info.wait_for_publish() + try: + location_data = get_location_data_for_device(canonic_id, device_name) + if location_data: + if client.is_connected(): + msg_info = publish_device_state(client, device_name, canonic_id, location_data) + msg_info.wait_for_publish() + print(f"Published data for {device_name}") + else: + raise Exception("MQTT client is not connected during state publish") + else: + raise Exception("Keine Standortdaten vorhanden") + except Exception as e: + print(f"[Error] Fehler beim Verarbeiten von {device_name}: {e}") + + # Fallback auf unknown, wenn keine Daten gepublisht werden konnten + try: + if client.is_connected(): + fallback_info = client.publish( + f"homeassistant/device_tracker/{DEVICE_PREFIX}_{canonic_id}/state", + payload="unknown", + retain=True + ) + fallback_info.wait_for_publish() + print(f"[Fallback] Unknown-Status für {device_name} veröffentlicht.") + else: + print(f"[Fallback] MQTT nicht verbunden – konnte 'unknown' für {device_name} nicht setzen.") + except Exception as retry_e: + print(f"[Fallback] Fehler beim Setzen von 'unknown' für {device_name}: {retry_e}") + + print("-" * 60) + - print(f"Published data for {device_name}") - print("\nAll devices have been published to MQTT") print("Devices will now be discoverable in Home Assistant") print("You may need to restart Home Assistant or trigger device discovery") - - except Exception as e: - print(f"Error: {e}") + + + finally: - client.loop_stop() - client.disconnect() + print("→ MQTT Loop stoppen...") + client.loop_stop() # Stoppt die MQTT-Loop + print("→ MQTT Verbindung trennen...") + client.disconnect() # Trennt die Verbindung zum Broker + + # Falls ein FCM Listener läuft, stoppen (nur wenn du darauf Zugriff hast) + try: + if 'fcm_client' in locals(): # Überprüfen, ob fcm_client existiert + fcm_client.stop() # Falls du eine Instanz gespeichert hast + print("→ FCM Listener gestoppt.") + else: + print("→ Kein FCM Listener gefunden.") + except AttributeError as e: + print(f"[Error] Fehler beim Stoppen des FCM Listeners: {e}") + except Exception as e: + print(f"[Unexpected Error] Ein unerwarteter Fehler ist aufgetreten: {e}") + + + + if __name__ == '__main__': main() From 024ec5edc2dff28bc7d5513f7816c19a36c880f3 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 21:51:28 +0200 Subject: [PATCH 29/48] Update chrome_driver.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Raspberry kann den chromedriver nicht benutzen. Daher kommt selenium zum einsatz und die secrets.json muss nachträglich von einem anderen Gerät kopiert werden --- chrome_driver.py | 103 +++++++++-------------------------------------- 1 file changed, 19 insertions(+), 84 deletions(-) diff --git a/chrome_driver.py b/chrome_driver.py index 903198e..88a9f9d 100644 --- a/chrome_driver.py +++ b/chrome_driver.py @@ -1,86 +1,21 @@ -# -# GoogleFindMyTools - A set of tools to interact with the Google Find My API -# Copyright © 2024 Leon Böttger. All rights reserved. -# - -import undetected_chromedriver as uc -import os -import shutil -import platform - -def find_chrome(): - """Find Chrome executable using known paths and system commands.""" - possiblePaths = [ - r"C:\Program Files\Google\Chrome\Application\chrome.exe", - r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", - r"C:\ProgramData\chocolatey\bin\chrome.exe", - r"C:\Users\%USERNAME%\AppData\Local\Google\Chrome\Application\chrome.exe", - "/usr/bin/google-chrome", - "/usr/local/bin/google-chrome", - "/opt/google/chrome/chrome", - "/snap/bin/chromium", - "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" - ] - - # Check predefined paths - for path in possiblePaths: - if os.path.exists(path): - return path - - # Use system command to find Chrome - try: - if platform.system() == "Windows": - chrome_path = shutil.which("chrome") - else: - chrome_path = shutil.which("google-chrome") or shutil.which("chromium") - if chrome_path: - return chrome_path - except Exception as e: - print(f"[ChromeDriver] Error while searching system paths: {e}") - - return None - - -def get_options(): - chrome_options = uc.ChromeOptions() - chrome_options.add_argument("--start-maximized") - chrome_options.add_argument("--disable-extensions") - chrome_options.add_argument("--disable-gpu") - chrome_options.add_argument("--no-sandbox") - - return chrome_options - +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service def create_driver(): - """Create a Chrome WebDriver with undetected_chromedriver.""" - - try: - chrome_options = get_options() - driver = uc.Chrome(options=chrome_options) - print("[ChromeDriver] Installed and browser started.") - return driver - except Exception: - print("[ChromeDriver] Default ChromeDriver creation failed. Trying alternative paths...") - - chrome_path = find_chrome() - if chrome_path: - chrome_options = get_options() - chrome_options.binary_location = chrome_path - try: - driver = uc.Chrome(options=chrome_options) - print(f"[ChromeDriver] ChromeDriver started using {chrome_path}") - return driver - except Exception as e: - print(f"[ChromeDriver] ChromeDriver failed using path {chrome_path}: {e}") - else: - print("[ChromeDriver] No Chrome executable found in known paths.") - - raise Exception( - "[ChromeDriver] Failed to install ChromeDriver. A current version of Chrome was not detected on your system.\n" - "If you know that Chrome is installed, update Chrome to the latest version. If the script is still not working, " - "set the path to your Chrome executable manually inside the script." - ) - - -if __name__ == '__main__': - create_driver() \ No newline at end of file + chrome_options = Options() + chrome_options.add_argument("--no-sandbox") + chrome_options.add_argument("--disable-dev-shm-usage") + chrome_options.add_argument("--headless=new") + chrome_options.add_argument("--disable-gpu") + chrome_options.add_argument("--window-size=1920,1080") + + # Manueller Pfad zu chromedriver (überprüft mit `which chromedriver`) + service = Service(executable_path="/usr/bin/chromedriver") + + try: + driver = webdriver.Chrome(service=service, options=chrome_options) + print("[ChromeDriver] ChromeDriver gestartet.") + return driver + except Exception as e: + raise Exception(f"[ChromeDriver] Fehler beim Start von ChromeDriver: {e}") From fd1f004cfb0f6e6c4139b4f53abd6766841a0a1a Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 22:16:53 +0200 Subject: [PATCH 30/48] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f78787f..67d0b9b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Dieser Fork von GoogleFindMyTools ist für den Betrieb auf Raspberry OS gedacht, Da Google Find my Device entweder einen Standort in Koordinatenform oder einen String "home" bzw. "zuhause", wurde die publish_mqqt.py angepasst. Falls google nun den string zuhause sendet, ersetzt der Raspbbery diesen durch Koordinaten für die Home Zone. Der Aufruf zum aktualisieren des Standortes erfolgt über Home Assisant via mqtt. In diesem sind die Kooardinaten für die Homezone (Koordinaten + Radius) enthalten. +Die Home Zone (Koordinaten + Umkreis) sind nötig, da Home Assistant immer einen Status zur Übermittlung der Attribute (Koordinaten) benötigt. Vorher hat die publisch_mqtt.py immer unkown als Status gesendet und die Koordinaten als Attribute. Die folge war, das home assistent den tracker bei jeder Standort aktualisierung auf unkown gesetzt hat und dann die Attribute ausliest, um dann wieder "home" als status zu setzten. Mit dieser Änderung wird direkt der richtige status an home Assistent gesendet. + Da der Chrome Browser auf dem Raspberry beim "requstest url was not found on this server" meldet, kann man sich dort nicht einloggen. Man muss daher GoogleFindMyTools auf einem Windows PC installieren, sich einloggen und die secrets.json mit den Zugangsdaten von dem PC auf den Raspberry kopieren. Daher ist die Anleitung in drei Schritte aufgeteilt: Installation auf dem Windows PC, installation auf dem Raspberry OS und anschließend die Mqqt Verbindung zu Home Assistant. ## Installation Windows PC: From 85a647693047800f2fe54255959ed468f4216d37 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Wed, 16 Jul 2025 22:22:08 +0200 Subject: [PATCH 31/48] Update publish_mqtt.py --- publish_mqtt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index a866ed4..2291e02 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -16,10 +16,10 @@ from ProtoDecoders.decoder import parse_device_list_protobuf, get_canonic_ids # MQTT Configuration -MQTT_BROKER = "192.168.3.65" # Change this to your MQTT broker address +MQTT_BROKER = "192.168.1.100" # Change this to your MQTT broker address MQTT_PORT = 1883 -MQTT_USERNAME = "mqttuser" # Set your MQTT username if required -MQTT_PASSWORD = "Coconuts1990" # Set your MQTT password if required +MQTT_USERNAME = "DeinMqttUser" # Set your MQTT username if required +MQTT_PASSWORD = "DeinMqttPassword" # Set your MQTT password if required lat_home = 0 #48.8909528 # DEIN Zuhause-Breitengrad lon_home = 0 #9.1904316 # DEIN Zuhause-Längengrad home_cycle = 0 #200 # Umkreis der Homezone in [m] From 6e0eb94b7b2ff070abc9cb2e1cc4241656290760 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Thu, 17 Jul 2025 12:01:24 +0200 Subject: [PATCH 32/48] Update README.md Mqqt Service restart --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 67d0b9b..36ee077 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,8 @@ ExecStart=/home/admin/GoogleFindMyTools/venv/bin/python /home/admin/mqtt_listene WorkingDirectory=/home/admin StandardOutput=journal StandardError=journal -Restart=always +#Restart=always +Restart=on-failure User=admin Environment="PATH=/home/admin/GoogleFindMyTools/venv/bin" From 409fee59aca33434043d8fe3c80b3c00d47d70de Mon Sep 17 00:00:00 2001 From: xHecktor Date: Thu, 17 Jul 2025 12:04:07 +0200 Subject: [PATCH 33/48] Update nbe_list_devices.py Eingabe aufforderung verursacht fehler bei der automatisierten Mqtt abfragee. Daher einfach alle abfragen --- NovaApi/ListDevices/nbe_list_devices.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/NovaApi/ListDevices/nbe_list_devices.py b/NovaApi/ListDevices/nbe_list_devices.py index 7590f31..478ce0f 100644 --- a/NovaApi/ListDevices/nbe_list_devices.py +++ b/NovaApi/ListDevices/nbe_list_devices.py @@ -1,8 +1,3 @@ -# -# GoogleFindMyTools - A set of tools to interact with the Google Find My API -# Copyright © 2024 Leon Böttger. All rights reserved. -# - import binascii from NovaApi.ExecuteAction.LocateTracker.location_request import get_location_data_for_device from NovaApi.nova_request import nova_request @@ -15,10 +10,8 @@ def request_device_list(): - hex_payload = create_device_list_request() result = nova_request(NOVA_LIST_DEVICS_API_SCOPE, hex_payload) - return result @@ -56,20 +49,12 @@ def list_devices(): print("") print("The following trackers are available:") + # Anstatt Eingabeaufforderung, jedes Gerät automatisch durchlaufen for idx, (device_name, canonic_id) in enumerate(canonic_ids, start=1): print(f"{idx}. {device_name}: {canonic_id}") - selected_value = input("\nIf you want to see locations of a tracker, type the number of the tracker and press 'Enter'.\nIf you want to register a new ESP32- or Zephyr-based tracker, type 'r' and press 'Enter': ") - - if selected_value == 'r': - print("Loading...") - register_esp32() - else: - selected_idx = int(selected_value) - 1 - selected_device_name = canonic_ids[selected_idx][0] - selected_canonic_id = canonic_ids[selected_idx][1] - - get_location_data_for_device(selected_canonic_id, selected_device_name) + # Automatisch den Standort jedes Geräts abfragen + get_location_data_for_device(canonic_id, device_name) if __name__ == '__main__': From 5f283494a5a82cdd259129b89eeae500179c83e8 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Thu, 17 Jul 2025 12:05:33 +0200 Subject: [PATCH 34/48] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36ee077..9f91beb 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ Abschließend müssen wir noch den Listener Service erstellen ``` sudo nano /etc/systemd/system/mqtt_listener.service ``` -hier muss folgendes hineinkopiert werden: +hier muss folgendes hineinkopiert werden (Achtung User Verzeichnis anpassen, hier admin): ``` [Unit] From fb61e6855009085a5eae5ae9f058a3e0e19535a9 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 09:52:21 +0200 Subject: [PATCH 35/48] Update README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9f91beb..7df89f1 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren git clone https://github.com/leonboe1/GoogleFindMyTools ``` ``` +cd GoogleFindMyTools +``` +``` +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process +``` +``` python -m venv venv ``` falls dass nicht geht ```& "C:\Users\[USER]\AppData\Local\Programs\Python\Python313\python.exe" -m venv venv``` @@ -33,9 +39,7 @@ hier bei muss [USER] durch den PC User ersetzt werden bzw. wo auch immer wurde venv\Scripts\activate ``` alternativ ```.\venv\Scripts\Activate.ps1``` -``` -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -``` + ``` cd GoogleFindMyTools ``` @@ -43,7 +47,7 @@ cd GoogleFindMyTools pip install -r requirements.txt ``` ``` -python main.py``` +python main.py ```

From f92be61fcab4f0c439b108ce9652dcd41d13dec2 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 19:48:15 +0200 Subject: [PATCH 36/48] Create mqtt_listener.py --- mqtt_listener.py | 228 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 mqtt_listener.py diff --git a/mqtt_listener.py b/mqtt_listener.py new file mode 100644 index 0000000..b94ae34 --- /dev/null +++ b/mqtt_listener.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jul 15 16:08:02 2025 + +@author: Jan +""" + +from systemd.daemon import notify +import threading +import time +import sys + + +import json +import time +from math import radians, cos, sin, asin, sqrt +import paho.mqtt.client as mqtt +from NovaApi.ListDevices.nbe_list_devices import request_device_list +from NovaApi.ExecuteAction.LocateTracker.location_request import get_location_data_for_device +from ProtoDecoders.decoder import parse_device_list_protobuf, get_canonic_ids + +# MQTT Configuration +MQTT_BROKER = "192.168.3.65" +MQTT_PORT = 1883 +MQTT_USERNAME = "mqttuser" +MQTT_PASSWORD = "Coconuts1990" + +# Home zone defaults +lat_home = 0 # placeholder until config arrives +lon_home = 0 +home_radius = 0 +config_received = False +last_full_update = 0.0 + +MQTT_CLIENT_ID = "google_find_my_publisher" + +# Home Assistant MQTT Discovery prefixes +DISCOVERY_PREFIX = "homeassistant" +DEVICE_PREFIX = "google_find_my" + +current_home_config = { + "lat_home": lat_home, + "lon_home": lon_home, + "home_radius": home_radius +} + +# MQTT Callbacks and helpers + +def _thread_excepthook(args): + """Beende den Prozess, sobald irgendein Thread eine unbehandelte Exception wirft.""" + print(f"⚠️ Uncaught thread exception: {args.exc_value!r} – exiting.") + sys.exit(1) +threading.excepthook = _thread_excepthook + + +def calculate_distance(lat1, lon1, lat2, lon2): + """Berechnet Entfernung zwischen zwei GPS-Koordinaten in Metern.""" + lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 + c = 2 * asin(sqrt(a)) + return 6371000 * c # Erdradius + + +def on_any_message(client, userdata, msg): + print(f"ANY MSG: {msg.topic} → {msg.payload.decode()}") + + +def on_connect(client, userdata, flags, result_code, properties=None): + print("🔌 Connected to MQTT, rc=", result_code) + client.subscribe([ + ("googlefindmytools/config", 0), + ("googlefindmytools/trigger/update", 0), + ]) + print("Current home config:", current_home_config) + print("✅ Subscribed to config & trigger/update") + +def on_disconnect(*args, **kwargs): + print("⚠️ MQTT disconnected—will reconnect…") + +def on_config_message(client, userdata, msg): + global config_received + print(f"[Config] Topic: {msg.topic}, Payload: {msg.payload.decode()}") + try: + payload = json.loads(msg.payload.decode()) + current_home_config.update({ + "lat_home": float(payload.get("lat_home", current_home_config["lat_home"])), + "lon_home": float(payload.get("lon_home", current_home_config["lon_home"])), + "home_radius": int(payload.get("home_radius", current_home_config["home_radius"])) + }) + config_received = True + print("✅ Updated home zone:", current_home_config) + except Exception as e: + print("❌ Error parsing config:", e) + + +def publish_device_config(client, device_name, canonic_id): + base = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" + cfg = { + "unique_id": f"{DEVICE_PREFIX}_{canonic_id}", + "state_topic": f"{base}/state", + "json_attributes_topic": f"{base}/attributes", + "source_type": "gps", + "device": {"identifiers": [f"{DEVICE_PREFIX}_{canonic_id}"], + "name": device_name, + "model": "Google Find My Device", + "manufacturer": "Google"} + } + client.publish(f"{base}/config", json.dumps(cfg), retain=True) + + +def publish_device_state(client, name, cid, loc): + lat = loc.get("latitude") + lon = loc.get("longitude") + sem = loc.get("semantic_location") + if lat is not None and lon is not None: + dist = calculate_distance(current_home_config["lat_home"], + current_home_config["lon_home"], lat, lon) + state = "home" if dist < current_home_config["home_radius"] else "not_home" + elif sem: + state = "home" if sem.lower() == "zuhause" else sem + lat, lon = current_home_config["lat_home"], current_home_config["lon_home"] + else: + state = "unknown" + + attrs = { + "latitude": lat, + "longitude": lon, + "altitude": loc.get("altitude"), + "gps_accuracy": loc.get("accuracy"), + "source_type": "gps" if lat is not None else "semantic", + "last_updated": loc.get("timestamp"), + "semantic_location": sem + } + + topic_s = f"homeassistant/device_tracker/google_find_my_{cid}/state" + topic_a = f"homeassistant/device_tracker/google_find_my_{cid}/attributes" + client.publish(topic_s, state, retain=True) + client.publish(topic_a, json.dumps(attrs), retain=True) + + +def run_full_update(client): + """Ruft Liste ab und published Config+State für alle Geräte.""" + global last_full_update + print("▶️ Triggered run_full_update at", time.strftime("%Y-%m-%d %H:%M:%S")) + try: + hexdata = request_device_list() + devices = parse_device_list_protobuf(hexdata) + for name, cid in get_canonic_ids(devices): + publish_device_config(client, name, cid) + loc = get_location_data_for_device(cid, name) + publish_device_state(client, name, cid, loc or {}) + print("✅ Full update complete") + last_full_update = time.time() + except Exception as e: + print("❌ run_full_update error:", e) + + +def main(): + global last_full_update + + client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, MQTT_CLIENT_ID) + client.on_connect = on_connect + client.on_message = on_any_message + client.message_callback_add("googlefindmytools/config", on_config_message) + #client.message_callback_add("googlefindmytools/trigger/update", lambda c,u,m: run_full_update(c)) + client.message_callback_add( + "googlefindmytools/trigger/update", + lambda c, u, m: ( + print("📨 Received trigger/update payload:", m.payload.decode()), + run_full_update(c) + ) + ) + + + client.reconnect_delay_set(min_delay=1, max_delay=60) + client.on_disconnect = on_disconnect + + # Auth + client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + print("🔌 Connecting to MQTT…") + client.connect(MQTT_BROKER, MQTT_PORT) + + # Start loop + client.loop_start() + + notify("READY=1") + + last_full_update = time.time() + + # Watchdog-Ping alle 10 Sekunden + def _wd_pinger(): + INTERVAL = 30 # Sekunde(n) zwischen Pings + THRESHOLD = 400 # wenn seit 60 s kein Full‑Update lief, kein Ping mehr + while True: + now = time.time() + if now - last_full_update < THRESHOLD: + notify("WATCHDOG=1") + else: + print("⚠️ Dienst scheint nicht mehr gesund (kein Full‑Update seit", + int(now - last_full_update), "s). Watchdog darf eingreifen.") + break + time.sleep(INTERVAL) + + + threading.Thread(target=_wd_pinger, daemon=True).start() + + + # wait for config if needed + + t0 = time.time() + while not config_received and time.time() - t0 < 5: + time.sleep(0.1) + if not config_received: + print("⚠️ No config received, using defaults.") + + # initial update + run_full_update(client) + + # stay in loop for triggers + client.loop_forever() + + +if __name__ == '__main__': + main() + From 3a917d07abf5fdbd0f05c1a1019bb32e3116c7ad Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 19:52:46 +0200 Subject: [PATCH 37/48] Update publish_mqtt.py --- publish_mqtt.py | 91 +++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/publish_mqtt.py b/publish_mqtt.py index 2291e02..4b72997 100644 --- a/publish_mqtt.py +++ b/publish_mqtt.py @@ -9,6 +9,7 @@ import time from typing import Dict, Any +#from publish_mqtt import current_home_config from math import radians, cos, sin, asin, sqrt import paho.mqtt.client as mqtt from NovaApi.ListDevices.nbe_list_devices import request_device_list @@ -107,67 +108,52 @@ def publish_device_config(client: mqtt.Client, device_name: str, canonic_id: str r = client.publish(f"{base_topic}/config", json.dumps(config), retain=True) return r -def publish_device_state(client: mqtt.Client, device_name: str, canonic_id: str, location_data: Dict) -> None: - """Publish device state and attributes to MQTT""" - base_topic = f"{DISCOVERY_PREFIX}/device_tracker/{DEVICE_PREFIX}_{canonic_id}" - - # Extract location data - lat = location_data.get('latitude') - lon = location_data.get('longitude') - accuracy = location_data.get('accuracy') - altitude = location_data.get('altitude') - timestamp = location_data.get('timestamp', time.time()) - - if (lat is None or lon is None) and location_data.get("semantic_location") == "Zuhause": - #lat = lat_home - #lon = lon_home - lat = current_home_config["lat_home"] - lon = current_home_config["lon_home"] - - print(f"[Fallback] Semantische Position erkannt: '{location_data.get('semantic_location', 'Unbekannt')}' → Fallback-Koordinaten werden verwendet.") - print(f"[Fallback] Semantische Position erkannt: 'Zuhause' → Fallback-Koordinaten werden verwendet.") +def publish_device_state(client, name, cid, location_data): + # 1) Hole Home‑Zone + lat_home = current_home_config["lat_home"] + lon_home = current_home_config["lon_home"] + home_radius = current_home_config["home_radius"] + # 2) Versuche GPS‑Koordinaten + lat = location_data.get("latitude") + lon = location_data.get("longitude") + sem = location_data.get("semantic_location") - # Publish state (home/not_home/unknown) - state = "unknown" - + # 3) State‑Ermittlung if lat is not None and lon is not None: + dist = calculate_distance(lat_home, lon_home, lat, lon) + state = "home" if dist < home_radius else "not_home" - lat_home_cfg = current_home_config["lat_home"] - lon_home_cfg = current_home_config["lon_home"] - home_radius_cfg = current_home_config["home_radius"] - - dist = calculate_distance(lat_home_cfg, lon_home_cfg, lat, lon) - if dist < home_radius_cfg: + elif sem: + # Fallback: semantischer Raum → immer Home‑Koordinaten + lat, lon = lat_home, lon_home + if sem.lower() == "zuhause": state = "home" else: - state = "not_home" - elif location_data.get("semantic_location") == "Zuhause": - lat = lat_home_cfg - lon = lon_home_cfg - state = "home" - print(f"[Fallback] Semantische Position erkannt: '{location_data.get('semantic_location')}' → Fallback-Koordinaten werden verwendet.") - else: - state = "unkown" - + state = sem # z.B. "Arbeitszimmer" + print(f"[Fallback] Semantische Position erkannt: '{sem}' → Fallback-Koordinaten werden verwendet.") + else: + # keinerlei Info + state = "unknown" + + # 4) Attribute‑Payload + attrs = { + "latitude": lat, + "longitude": lon, + "altitude": location_data.get("altitude"), + "gps_accuracy": location_data.get("accuracy"), + "source_type": "gps" if location_data.get("latitude") is not None else "semantic", + "last_updated": location_data.get("timestamp"), + "semantic_location": sem + } + # 5) Publish + topic_state = f"homeassistant/device_tracker/google_find_my_{cid}/state" + topic_attr = f"homeassistant/device_tracker/google_find_my_{cid}/attributes" - client.publish(f"{base_topic}/state", state) - - # Publish attributes - attributes = { - "latitude": lat, - "longitude": lon, - "altitude": altitude, - "gps_accuracy": accuracy, - "source_type": "gps", - "last_updated": timestamp, - "semantic_location": location_data.get("semantic_location") - - } - r = client.publish(f"{base_topic}/attributes", json.dumps(attributes)) - return r + client.publish(topic_state, state, retain=True) + client.publish(topic_attr, json.dumps(attrs), retain=True) def main(): # Initialize MQTT client @@ -289,3 +275,4 @@ def main(): if __name__ == '__main__': main() + From e141e1c60c9a3d86b3896824bb13fd5db69a5d6c Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 19:53:23 +0200 Subject: [PATCH 38/48] Update mqtt_listener.py --- mqtt_listener.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mqtt_listener.py b/mqtt_listener.py index b94ae34..c816a7e 100644 --- a/mqtt_listener.py +++ b/mqtt_listener.py @@ -20,10 +20,10 @@ from ProtoDecoders.decoder import parse_device_list_protobuf, get_canonic_ids # MQTT Configuration -MQTT_BROKER = "192.168.3.65" +MQTT_BROKER = "192.168.1.100" # Change this to your MQTT broker address MQTT_PORT = 1883 -MQTT_USERNAME = "mqttuser" -MQTT_PASSWORD = "Coconuts1990" +MQTT_USERNAME = "DeinMqttUser" # Set your MQTT username if required +MQTT_PASSWORD = "DeinMqttPassword" # Set your MQTT password if required # Home zone defaults lat_home = 0 # placeholder until config arrives From 25e90c7b8a23f83d8687ca911704ff60aca36897 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:02:50 +0200 Subject: [PATCH 39/48] Update README.md --- README.md | 135 ++++++++++++++---------------------------------------- 1 file changed, 35 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 7df89f1..7c5354e 100644 --- a/README.md +++ b/README.md @@ -217,107 +217,20 @@ MQTT_PASSWORD = "DeinMqttPasswort" # Ändere dein Passwort ``` Drücke STG+X, dann Y und Enter -Nun richten wir einen Autstart ein -``` -cd /home/admin -``` -``` -nano update_location.sh -``` -und dort folgendes einfügen: -``` -#!/bin/bash -cd /home/admin/GoogleFindMyTools -source venv/bin/activate -python3 publish_mqtt.py -``` -Drücke STG+X, dann Y und Enter -``` -chmod +x update_location.sh -``` -Ausführung testen: -``` -./update_location.sh -``` -


-Nun erstellen wir noch einen Listener, um die Trigger von Home Assistant zu empfangen: +Auch die Zugangsdaten vom LIstener anpassen, um die Trigger von Home Assistant zu empfangen: ``` nano mqtt_listener.py ``` hier müssen auch wieder die Zugangsdaten des Mqtt Brockers angepasst werden ``` -import paho.mqtt.client as mqtt -import subprocess -import datetime -import json - MQTT_BROKER = "192.168.100" MQTT_PORT = 1883 MQTT_TOPIC = "googlefindmytools/trigger/update" MQTT_USER = "DeinMqqtUser" MQTT_PASS = "DeinMqttPasswort" -def on_connect(client, userdata, flags, rc, properties=None): - print("Connected with result code " + str(rc)) - client.subscribe(MQTT_TOPIC) - -def on_message(client, userdata, msg): - print(f"Received trigger: {msg.payload.decode()}") - - try: - # JSON laden - payload = json.loads(msg.payload.decode()) - - # Optional: Nur ausführen, wenn bestimmte Keys vorhanden sind - if "lat_home" in payload and "lon_home" in payload and "home_radius" in payload: - print("→ Konfiguration aus Trigger empfangen, sende retained an googlefindmytools/config") - - # An config-Topic weiterleiten (retain!) - client.publish("googlefindmytools/config", json.dumps(payload), retain=True) - - else: - print("→ Kein gültiger Konfigurations-Payload – wird ignoriert.") - - except json.JSONDecodeError: - print("→ Kein JSON – evtl. nur 'start' ohne Konfiguration") - - # Skript immer starten, egal ob mit oder ohne Konfiguration - print("Running update_location.sh...") - - try: - result = subprocess.run( - ["/home/admin/update_location.sh"], - capture_output=True, - text=True, - timeout=120 - ) - print(" STDOUT:\n", result.stdout) - print(" STDERR:\n", result.stderr) - - now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if result.returncode == 0: - status_msg = f" Standortdaten erfolgreich aktualisiert um {now}." - else: - status_msg = f" Fehler bei der Standortaktualisierung ({result.returncode}) um {now}.\n{result.stderr}" - except Exception as e: - import traceback - status_msg = f" Ausnahmefehler: {e}\n{traceback.format_exc()}" - - client.publish("googlefindmytools/status", status_msg) - - - -client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) -client.username_pw_set(MQTT_USER, MQTT_PASS) -client.on_connect = on_connect -client.on_message = on_message - - -client.connect(MQTT_BROKER, MQTT_PORT, 60) -client.loop_forever() - ``` Drücke STG+X, dann Y und Enter @@ -325,10 +238,7 @@ Drücke STG+X, dann Y und Enter ``` chmod +x mqtt_listener.py ``` -hiermit kann man den listener testen -``` -python3 ~/mqtt_listener.py -``` +


Abschließend müssen wir noch den Listener Service erstellen @@ -340,20 +250,37 @@ hier muss folgendes hineinkopiert werden (Achtung User Verzeichnis anpassen, hie [Unit] Description=MQTT Listener for Google Find My Tools +After=network-online.target Wants=network-online.target -After=network.target -#After=network-online.target +StartLimitIntervalSec=60 +StartLimitBurst=5 [Service] -ExecStart=/home/admin/GoogleFindMyTools/venv/bin/python /home/admin/mqtt_listener.py -WorkingDirectory=/home/admin +#Type=simple +User=admin +WorkingDirectory=/home/admin/GoogleFindMyTools +Environment="PATH=/home/admin/GoogleFindMyTools/venv/bin" +#ExecStart=/home/admin/GoogleFindMyTools/venv/bin/python mqtt_listener.py +ExecStart=/home/admin/GoogleFindMyTools/venv/bin/python /home/admin/GoogleFindMyTools/mqtt_listener.py + +# stderr ins Nichts leiten, stdout bleibt im Journal +#StandardError=null StandardOutput=journal StandardError=journal -#Restart=always + +# 🧹 Alte Subprozesse (z. B. nbe_list_devices.py) aufräumen +ExecStopPost=/usr/bin/pkill -f nbe_list_devices.py + +# → Watchdog einschalten +Type=notify +WatchdogSec=30 +NotifyAccess=all + +# Fallback‑Restart, falls das Skript wirklich abstürzt Restart=on-failure -User=admin -Environment="PATH=/home/admin/GoogleFindMyTools/venv/bin" +RestartSec=5 + [Install] WantedBy=multi-user.target @@ -383,7 +310,15 @@ wenn man sich später die kommunikation mit HA anschauen möchte: ``` cd /home/admin source ~/GoogleFindMyTools/venv/bin/activate -python3 ~/mqtt_listener.py +journalctl -u mqtt_listener.service -f + +``` +Str + c zum beenden + +wenn man später die kommunikation mit HA sehen möchte, einfach ein zweites Puttyfenster aufmachen und folgendes eingeben: +user ip und password müssen natürlich die von deinem Broker (Home Assistant) sein +``` +mosquitto_sub -h 192.168.1.100 -u DeinMqttUser -P DeinMqttPassword -v -t "homeassistant/#" ``` Str + c zum beenden From df53dd137c5e526f94ba8447aaea7e68e5d0f13d Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:16:01 +0200 Subject: [PATCH 40/48] Update README.md --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c5354e..27321bd 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,32 @@ sudo systemctl enable mqtt_listener.service ``` sudo systemctl start mqtt_listener.service ``` +Nun müssen wir noch einen Watchdog erstellen. Dies ist leider nur eine behälfsmäßige Lösung. Ich habe die Erfahrung gemacht, dass sich der update Service gerne aufhängt. Der Watchdoog schaut, ob der Updateprozess innnerhalb von 400 Sekunden fertig gemeldet hat. Ist es nicht der Fall, killt alles und startet es neu. 400s habe ich deshalb eingestellt, da Home Assistant alle 5 min einen Trigger schickt. Wenn Home Assistant seltener die Trigger sendet, sollte auch der Watchdog angepasst werden. + +Um den Watchdog zu starten +``` +sudo apt install watchdog +``` +``` +sudo systemctl enable watchdog +``` +``` +sudo systemctl start watchdog +``` +``` +sudo nano /etc/watchdog.conf +``` +Aktiviere folgende Zeilen, indem du die Raute davor entfernst. Falls nicht vorhanden füge diese einfach hinzu. +``` +watchdog-device = /dev/watchdog +max-load-1 = 24 +temperature-device = /sys/class/thermal/thermal_zone0/temp +max-temperature = 75000 +``` +``` + + + listener Service testen: ``` sudo systemctl status mqtt_listener.service @@ -361,7 +387,7 @@ alias: Google Tracker aktualisieren sequence: - data: topic: googlefindmytools/trigger/update - payload: "{ \"lat_home\": 31.8909528, \"lon_home\": 7.1904316, \"home_radius\": 500 }" + payload: "{ \"lat_home\": 31.8909428, \"lon_home\": 7.1704316, \"home_radius\": 500 }" action: mqtt.publish ``` speichern und ausführen. Die Google Tags sollten nun in Home Assistan angezeigt werden. From 2cba636d3ba3a7b131a3e8291534d32a94ecfcb5 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:17:03 +0200 Subject: [PATCH 41/48] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27321bd..0b819d1 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ watchdog-device = /dev/watchdog max-load-1 = 24 temperature-device = /sys/class/thermal/thermal_zone0/temp max-temperature = 75000 -``` + ``` From 86c67dc5a8320b0536f700c288becc855a434b92 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:19:30 +0200 Subject: [PATCH 42/48] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 5adb8e5..3e4a088 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ setuptools>=75.6.0 aiohttp>=3.11.8 http_ece>=1.1.0 paho_mqtt +watchdog From e1fdb6a87ed53046d1c9b9569b3eec310ac2f450 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:22:56 +0200 Subject: [PATCH 43/48] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e4a088..5adb8e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,3 @@ setuptools>=75.6.0 aiohttp>=3.11.8 http_ece>=1.1.0 paho_mqtt -watchdog From e7eee9ed6b18ea2811c13dc0f16b39be177f46f4 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Mon, 21 Jul 2025 20:30:56 +0200 Subject: [PATCH 44/48] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b819d1..6c93ac9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Python installieren: https://www.python.org/downloads/windows/ Im Chromebrowser mit dem Nutzkonto einloggen. Wichtig: hiermit ist nicht die website von google.com gemeint, sondern das Chrome Desktop Programm! -PowerShell als Admin ausführen und GoogleFindMyTools von leonboe1 installieren +PowerShell als Admin ausführen und GoogleFindMyTools ** von leonboe1 ** installieren ``` From 9088d0a3c699bdfe32e5e019816f6e29a796b749 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Tue, 22 Jul 2025 13:05:52 +0200 Subject: [PATCH 45/48] Update README.md --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6c93ac9..79978eb 100644 --- a/README.md +++ b/README.md @@ -377,11 +377,14 @@ Benutzername: DeinMqqtUser den du beim Raspberry verwendet hast in der mqtt_list Passwort: DeinMqttPasswort das du beim Raspberry verwendet hast in der mqtt_listener.py und in der publish_mqtt.py




-Nun erstellen wir den Aufruf zum Orten der Google Tags -Einstellungen/Automatisierungen&Szenen/Skripte/ Neues Skript hinzufügen +Nun erstellen wir den Aufruf zum Orten der Google Tags
+Einstellungen/Automatisierungen&Szenen/Skripte/ hier auf den Button "+Skript erstellen" klicken und "Neues Skipt erstellen im Dialog auswählen
+nun sind wir im Editor der GUI geführt ist. Um es uns leichter zu machen, klick oben rechts aud ie drei Punkt und wähle "in YAML bearbeiten" aus
-Hier kannst du die Koordinaten und den Umkreis von deinem Zuhause definieren. Wenn nun der Google Tag anstatt von Koordinaten nur "Zuhause" meldet, wird dieses in Koordinaten umgewandelt, damit Home Assisant den Tracker anzeigen kann. +Hier kannst du die Koordinaten und den Umkreis von deinem Zuhause definieren. Wenn nun der Google Tag anstatt von Koordinaten nur "Zuhause" meldet, wird dieses in Koordinaten umgewandelt, damit Home Assisant den Tracker anzeigen kann.
+Kopiere das Skript in den Editor + ``` alias: Google Tracker aktualisieren sequence: @@ -390,17 +393,18 @@ sequence: payload: "{ \"lat_home\": 31.8909428, \"lon_home\": 7.1704316, \"home_radius\": 500 }" action: mqtt.publish ``` -speichern und ausführen. Die Google Tags sollten nun in Home Assistan angezeigt werden. - -Zuletzt legen wir noch eine Automatisierung ab, um den Standort alle 15 min zu aktualisieren: -Einstellungen/Automatisierungen&Szenen/ Automatisierung erstellen +speichern und ausführen. Die Google Tags sollten nun in Home Assistan angezeigt werden (Einstellungen/Geräte und Dienste/MQTT). +Zuletzt legen wir noch eine Automatisierung ab, um den Standort alle 5 min zu aktualisieren:
+Einstellungen/Automatisierungen&Szenen/ auf den Button "+Automatisierung erstellen" klicken und "Neue Automation erstellen" auswählen
+nun sind wir im Editor der GUI geführt ist. Um es uns leichter zu machen, klick oben rechts aud ie drei Punkt und wähle "in YAML bearbeiten" aus
+Kopiere das Skript in den Editor
``` alias: Google_Airtag description: "" triggers: - trigger: time_pattern - minutes: /15 + minutes: /5 conditions: [] actions: - action: script.update_google_locations From ca752df53ff6dd571bf0d66234318d5796391d1b Mon Sep 17 00:00:00 2001 From: xHecktor Date: Tue, 22 Jul 2025 17:33:53 +0200 Subject: [PATCH 46/48] Update README.md --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79978eb..c3a749a 100644 --- a/README.md +++ b/README.md @@ -416,9 +416,75 @@ speichern -So fertig sind wir +**So fertig sind wir** +**kleines Extra:** +Wer es noch brauchen kann hier sind drei Karten für das dashboard
+Einfach in das Dashboard gehen, eine irgendeine neue Karte hinzufügen und auf "im Code Editor anzeigen" gehen.
+Anschleißend folgenden code hinein kopieren und die Trackernamen (Airtag_1) anpassen
+Hier eine Karte +``` +type: map +entities: + - device_tracker.Airtag_1 + - device_tracker.Airtag_2 + - device_tracker.Airtag_3 +default_zoom: 16 +hours_to_show: 1 +theme_mode: auto +``` + +Hier die Personen +``` +type: tile +features_position: bottom +vertical: true +entity: device_tracker.Airtag_1 +state_content: + - state + - last_changed + - semantic_location +grid_options: + rows: 2 + columns: 6 +``` + +Hier als Markdown +``` +type: markdown +title: Google Airtag Status +content: > + **Status:** {{ states('device_tracker.Airtag_1') }} + + **Letzter Wechsel:** {% if states.device_tracker.naomi.last_changed %}{{ + as_timestamp(states.device_tracker.naomi.last_changed)| + timestamp_custom('%Y-%m-%d %H:%M:%S') }}{% else %}– + + {% endif %} + + **Semantic Location:** {{ state_attr('device_tracker.naomi', + 'semantic_location') or '–' }} + + **Letztes GPS‑Update:** {{ state_attr('device_tracker.naomi', + 'last_updated') or '–' }} +grid_options: + columns: 9 + rows: auto +``` +```show_name: true +show_icon: true +type: button +entity: automation.google_airtag +tap_action: + action: perform-action + perform_action: automation.trigger + target: + entity_id: automation.google_airtag + data: + skip_condition: true +``` +Viel Spaß damit From 6d132b8f0a4d1637fd5113e7a413163a41556743 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Tue, 22 Jul 2025 17:34:43 +0200 Subject: [PATCH 47/48] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c3a749a..6b1e339 100644 --- a/README.md +++ b/README.md @@ -473,6 +473,7 @@ grid_options: columns: 9 rows: auto ``` +Ein Button zum manuellen aktualisieren der Tracker ```show_name: true show_icon: true type: button From b4c443b5e7197aaba080369152cc49e11a48b8d2 Mon Sep 17 00:00:00 2001 From: xHecktor Date: Tue, 22 Jul 2025 17:36:45 +0200 Subject: [PATCH 48/48] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6b1e339..addbf45 100644 --- a/README.md +++ b/README.md @@ -458,16 +458,16 @@ title: Google Airtag Status content: > **Status:** {{ states('device_tracker.Airtag_1') }} - **Letzter Wechsel:** {% if states.device_tracker.naomi.last_changed %}{{ - as_timestamp(states.device_tracker.naomi.last_changed)| + **Letzter Wechsel:** {% if states.device_tracker.Airtag_1.last_changed %}{{ + as_timestamp(states.device_tracker.Airtag_1.last_changed)| timestamp_custom('%Y-%m-%d %H:%M:%S') }}{% else %}– {% endif %} - **Semantic Location:** {{ state_attr('device_tracker.naomi', + **Semantic Location:** {{ state_attr('device_tracker.Airtag_1', 'semantic_location') or '–' }} - **Letztes GPS‑Update:** {{ state_attr('device_tracker.naomi', + **Letztes GPS‑Update:** {{ state_attr('device_tracker.Airtag_1', 'last_updated') or '–' }} grid_options: columns: 9