Skip to content

Commit 7d9da99

Browse files
authoredDec 8, 2024
Device Tracker sync across gateways (#265)
Device Tracker sync across gateways - OpenMQTTGateway & Theengs Gateway
1 parent 9188b12 commit 7d9da99

File tree

4 files changed

+106
-27
lines changed

4 files changed

+106
-27
lines changed
 

‎TheengsGateway/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"""
2020

2121
import sys
22+
import uuid
2223
from pathlib import Path
2324

2425
from .ble_gateway import run
@@ -48,6 +49,12 @@ def main() -> None:
4849
if configuration["discovery_topic"].endswith("/sensor"):
4950
configuration["discovery_topic"] = configuration["discovery_topic"][:-7]
5051

52+
# Get the MAC address of the gateway.
53+
mac_address = uuid.UUID(int=uuid.getnode()).hex[-12:]
54+
configuration["gateway_id"] = ":".join(
55+
[mac_address[i : i + 2] for i in range(0, 12, 2)]
56+
).upper()
57+
5158
if not configuration["host"]:
5259
sys.exit("MQTT host is not specified")
5360

‎TheengsGateway/ble_gateway.py

+70-25
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ def on_connect(
152152
retain=True,
153153
)
154154
self.subscribe(self.configuration["subscribe_topic"])
155+
if self.configuration["enable_multi_gtw_sync"]:
156+
self.subscribe(self.configuration["trackersync_topic"])
155157
else:
156158
logger.error(
157159
"Failed to connect to MQTT broker %s:%d reason code: %s",
@@ -227,28 +229,52 @@ def subscribe(self, sub_topic: str) -> None:
227229
"""Subscribe to MQTT topic <sub_topic>."""
228230

229231
def on_message(client, userdata, msg) -> None: # noqa: ANN001,ARG001
230-
logger.info(
231-
"Received `%s` from `%s` topic",
232-
msg.payload.decode(),
233-
msg.topic,
234-
)
235-
try:
236-
msg_json = json.loads(msg.payload.decode())
237-
except (json.JSONDecodeError, UnicodeDecodeError) as exception:
238-
logger.warning(
239-
"Invalid JSON message %s: %s", msg.payload.decode(), exception
240-
)
241-
return
232+
# Evaluate trackersync messages
233+
if (
234+
msg.topic == self.configuration["trackersync_topic"]
235+
and self.configuration["enable_multi_gtw_sync"]
236+
):
237+
msg_json = json.loads(msg.payload)
238+
logger.debug("trackersync message: %s", msg_json)
242239

243-
try:
244-
msg_json["id"] = self.rpa2id(msg_json["id"])
245-
except KeyError:
246-
logger.warning(
247-
"JSON message %s doesn't contain id", msg.payload.decode()
240+
if (
241+
msg_json["gatewayid"] != self.configuration["gateway_id"]
242+
and msg_json["trackerid"] in self.discovered_trackers
243+
and self.discovered_trackers[msg_json["trackerid"]].time != 0
244+
):
245+
self.discovered_trackers[msg_json["trackerid"]].time = 0
246+
logger.debug(
247+
"Tracker %s disassociated by gateway %s",
248+
msg_json["trackerid"],
249+
msg_json["gatewayid"],
250+
)
251+
252+
logger.debug(
253+
"[DIS] Discovered Trackers: %s", self.discovered_trackers
254+
)
255+
else:
256+
logger.info(
257+
"Received `%s` from `%s` topic",
258+
msg.payload.decode(),
259+
msg.topic,
248260
)
249-
return
261+
try:
262+
msg_json = json.loads(msg.payload.decode())
263+
except (json.JSONDecodeError, UnicodeDecodeError) as exception:
264+
logger.warning(
265+
"Invalid JSON message %s: %s", msg.payload.decode(), exception
266+
)
267+
return
268+
269+
try:
270+
msg_json["id"] = self.rpa2id(msg_json["id"])
271+
except KeyError:
272+
logger.warning(
273+
"JSON message %s doesn't contain id", msg.payload.decode()
274+
)
275+
return
250276

251-
self.decode_advertisement(msg_json)
277+
self.decode_advertisement(msg_json)
252278

253279
self.client.subscribe(sub_topic)
254280
self.client.on_message = on_message
@@ -370,14 +396,11 @@ def check_tracker_timeout(self) -> None:
370396
if (
371397
round(time()) - time_model.time >= self.configuration["tracker_timeout"]
372398
and time_model.time != 0
373-
and (
374-
self.configuration["discovery"]
375-
or self.configuration["general_presence"]
376-
)
377399
):
378400
if (
379401
time_model.model_id in ("APPLEWATCH", "APPLEDEVICE")
380402
and not self.configuration["discovery"]
403+
and self.configuration["general_presence"]
381404
):
382405
message = json.dumps(
383406
{"id": address, "presence": "absent", "unlocked": False}
@@ -391,9 +414,12 @@ def check_tracker_timeout(self) -> None:
391414
+ "/"
392415
+ address.replace(":", ""),
393416
)
417+
394418
time_model.time = 0
395419
self.discovered_trackers[address] = time_model
396-
logger.debug("Discovered Trackers: %s", self.discovered_trackers)
420+
421+
logger.info("Tracker %s timed out", address)
422+
logger.debug("[TO] Discovered Trackers: %s", self.discovered_trackers)
397423

398424
async def ble_scan_loop(self) -> None:
399425
"""Scan for BLE devices."""
@@ -440,6 +466,10 @@ async def ble_scan_loop(self) -> None:
440466
"Sent %s messages to MQTT",
441467
self.published_messages,
442468
)
469+
470+
# Check tracker timeouts
471+
self.check_tracker_timeout()
472+
443473
await asyncio.sleep(
444474
self.configuration["ble_time_between_scans"],
445475
)
@@ -611,11 +641,26 @@ def publish_json(
611641
+ "/"
612642
+ get_address(data_json).replace(":", ""),
613643
)
644+
645+
# Update tracker last received time
614646
self.discovered_trackers[str(data_json["id"])] = TnM(
615647
round(time()),
616648
str(data_json["model_id"]),
617649
)
618-
logger.debug("Discovered Trackers: %s", self.discovered_trackers)
650+
# Publish trackersync message
651+
if self.configuration["enable_multi_gtw_sync"]:
652+
message = json.dumps(
653+
{
654+
"gatewayid": self.configuration["gateway_id"],
655+
"trackerid": data_json["id"],
656+
}
657+
)
658+
self.publish(
659+
message,
660+
self.configuration["trackersync_topic"],
661+
)
662+
663+
logger.debug("[GP] Discovered Trackers: %s", self.discovered_trackers)
619664

620665
# Remove "track" if PUBLISH_ADVDATA is 0
621666
if not self.configuration["publish_advdata"] and "track" in data_json:

‎TheengsGateway/config.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"port": 1883,
2121
"user": "",
2222
"pass": "",
23-
"ble_scan_time": 5,
23+
"ble_scan_time": 7,
2424
"ble_time_between_scans": 5,
2525
"publish_topic": "home/TheengsGateway/BTtoMQTT",
2626
"lwt_topic": "home/TheengsGateway/LWT",
@@ -51,6 +51,8 @@
5151
"ble": 1,
5252
"whitelist": [],
5353
"blacklist": [],
54+
"enable_multi_gtw_sync": 1,
55+
"trackersync_topic": "home/internal/trackersync",
5456
}
5557

5658

@@ -268,6 +270,18 @@ def parse_args() -> argparse.Namespace:
268270
type=int,
269271
help="Enable (1) or disable (0) WebSocket (default: 0)",
270272
)
273+
parser.add_argument(
274+
"-gs",
275+
"--enable_multi_gtw_sync",
276+
type=int,
277+
help="Disable (0) or enable (1) to use tracker and closest control devices sync across Theengs Gateway gateways and OpenMQTTGateway (default: 1)", # noqa: E501
278+
)
279+
parser.add_argument(
280+
"-tt",
281+
"--trackersync_topic",
282+
type=str,
283+
help="Internal trackersync publish topic",
284+
)
271285
return parser.parse_args()
272286

273287

‎TheengsGateway/discovery.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,21 @@ def copy_pub_device(self, device: dict) -> dict:
273273
self.discovered_trackers[device["id"]] = TnM(
274274
round(time()), device["model_id"]
275275
)
276-
logger.debug("Discovered Trackers: %s", self.discovered_trackers)
277276

277+
# Publish trackersync message
278+
if self.configuration["enable_multi_gtw_sync"]:
279+
message = json.dumps(
280+
{
281+
"gatewayid": self.configuration["gateway_id"],
282+
"trackerid": device["id"],
283+
}
284+
)
285+
self.publish(
286+
message,
287+
self.configuration["trackersync_topic"],
288+
)
289+
290+
logger.debug(" Discovered Trackers: %s", self.discovered_trackers)
278291
pub_device_copy = device.copy()
279292
# Remove "track" if PUBLISH_ADVDATA is 0
280293
if not self.configuration["publish_advdata"] and "track" in pub_device_copy:

0 commit comments

Comments
 (0)
Please sign in to comment.