Skip to content

Commit 62dc9c9

Browse files
authored
Add async_apple_scanner example (#719)
1 parent 135983c commit 62dc9c9

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

examples/async_apple_scanner.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
3+
""" Scan for apple devices. """
4+
5+
import argparse
6+
import asyncio
7+
import logging
8+
from typing import Any, Optional, cast
9+
10+
from zeroconf import DNSQuestionType, IPVersion, ServiceStateChange, Zeroconf
11+
from zeroconf.aio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf
12+
13+
HOMESHARING_SERVICE: str = "_appletv-v2._tcp.local."
14+
DEVICE_SERVICE: str = "_touch-able._tcp.local."
15+
MEDIAREMOTE_SERVICE: str = "_mediaremotetv._tcp.local."
16+
AIRPLAY_SERVICE: str = "_airplay._tcp.local."
17+
COMPANION_SERVICE: str = "_companion-link._tcp.local."
18+
RAOP_SERVICE: str = "_raop._tcp.local."
19+
AIRPORT_ADMIN_SERVICE: str = "_airport._tcp.local."
20+
DEVICE_INFO_SERVICE: str = "_device-info._tcp.local."
21+
22+
ALL_SERVICES = [
23+
HOMESHARING_SERVICE,
24+
DEVICE_SERVICE,
25+
MEDIAREMOTE_SERVICE,
26+
AIRPLAY_SERVICE,
27+
COMPANION_SERVICE,
28+
RAOP_SERVICE,
29+
AIRPORT_ADMIN_SERVICE,
30+
DEVICE_INFO_SERVICE,
31+
]
32+
33+
log = logging.getLogger(__name__)
34+
35+
36+
def async_on_service_state_change(
37+
zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange
38+
) -> None:
39+
print("Service %s of type %s state changed: %s" % (name, service_type, state_change))
40+
if state_change is not ServiceStateChange.Added:
41+
return
42+
base_name = name[: -len(service_type) - 1]
43+
device_name = f"{base_name}.{DEVICE_INFO_SERVICE}"
44+
asyncio.ensure_future(_async_show_service_info(zeroconf, service_type, name))
45+
# Also probe for device info
46+
asyncio.ensure_future(_async_show_service_info(zeroconf, DEVICE_INFO_SERVICE, device_name))
47+
48+
49+
async def _async_show_service_info(zeroconf: Zeroconf, service_type: str, name: str) -> None:
50+
info = AsyncServiceInfo(service_type, name)
51+
await info.async_request(zeroconf, 3000, question_type=DNSQuestionType.QU)
52+
print("Info from zeroconf.get_service_info: %r" % (info))
53+
if info:
54+
addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_addresses()]
55+
print(" Name: %s" % name)
56+
print(" Addresses: %s" % ", ".join(addresses))
57+
print(" Weight: %d, priority: %d" % (info.weight, info.priority))
58+
print(" Server: %s" % (info.server,))
59+
if info.properties:
60+
print(" Properties are:")
61+
for key, value in info.properties.items():
62+
print(" %s: %s" % (key, value))
63+
else:
64+
print(" No properties")
65+
else:
66+
print(" No info")
67+
print('\n')
68+
69+
70+
class AsyncAppleScanner:
71+
def __init__(self, args: Any) -> None:
72+
self.args = args
73+
self.aiobrowser: Optional[AsyncServiceBrowser] = None
74+
self.aiozc: Optional[AsyncZeroconf] = None
75+
76+
async def async_run(self) -> None:
77+
self.aiozc = AsyncZeroconf(ip_version=ip_version)
78+
await self.aiozc.zeroconf.async_wait_for_start()
79+
print("\nBrowsing %s service(s), press Ctrl-C to exit...\n" % ALL_SERVICES)
80+
kwargs = {'handlers': [async_on_service_state_change], 'question_type': DNSQuestionType.QU}
81+
if self.args.target:
82+
kwargs["addr"] = self.args.target
83+
self.aiobrowser = AsyncServiceBrowser(self.aiozc.zeroconf, ALL_SERVICES, **kwargs) # type: ignore
84+
while True:
85+
await asyncio.sleep(1)
86+
87+
async def async_close(self) -> None:
88+
assert self.aiozc is not None
89+
assert self.aiobrowser is not None
90+
await self.aiobrowser.async_cancel()
91+
await self.aiozc.async_close()
92+
93+
94+
if __name__ == '__main__':
95+
logging.basicConfig(level=logging.DEBUG)
96+
97+
parser = argparse.ArgumentParser()
98+
parser.add_argument('--debug', action='store_true')
99+
version_group = parser.add_mutually_exclusive_group()
100+
version_group.add_argument('--target', help='Unicast target')
101+
version_group.add_argument('--v6', action='store_true')
102+
version_group.add_argument('--v6-only', action='store_true')
103+
args = parser.parse_args()
104+
105+
if args.debug:
106+
logging.getLogger('zeroconf').setLevel(logging.DEBUG)
107+
if args.v6:
108+
ip_version = IPVersion.All
109+
elif args.v6_only:
110+
ip_version = IPVersion.V6Only
111+
else:
112+
ip_version = IPVersion.V4Only
113+
114+
loop = asyncio.get_event_loop()
115+
runner = AsyncAppleScanner(args)
116+
try:
117+
loop.run_until_complete(runner.async_run())
118+
except KeyboardInterrupt:
119+
loop.run_until_complete(runner.async_close())

0 commit comments

Comments
 (0)