Skip to content

Commit

Permalink
add zero conf examples
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-tcs committed Mar 7, 2024
1 parent 35f368a commit ce1127d
Show file tree
Hide file tree
Showing 9 changed files with 764 additions and 0 deletions.
119 changes: 119 additions & 0 deletions async_apple_scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env python3

""" Scan for apple devices. """

import argparse
import asyncio
import logging
from typing import Any, Optional, cast

from zeroconf import DNSQuestionType, IPVersion, ServiceStateChange, Zeroconf
from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf

HOMESHARING_SERVICE: str = "_appletv-v2._tcp.local."
DEVICE_SERVICE: str = "_touch-able._tcp.local."
MEDIAREMOTE_SERVICE: str = "_mediaremotetv._tcp.local."
AIRPLAY_SERVICE: str = "_airplay._tcp.local."
COMPANION_SERVICE: str = "_companion-link._tcp.local."
RAOP_SERVICE: str = "_raop._tcp.local."
AIRPORT_ADMIN_SERVICE: str = "_airport._tcp.local."
DEVICE_INFO_SERVICE: str = "_device-info._tcp.local."

ALL_SERVICES = [
HOMESHARING_SERVICE,
DEVICE_SERVICE,
MEDIAREMOTE_SERVICE,
AIRPLAY_SERVICE,
COMPANION_SERVICE,
RAOP_SERVICE,
AIRPORT_ADMIN_SERVICE,
DEVICE_INFO_SERVICE,
]

log = logging.getLogger(__name__)


def async_on_service_state_change(
zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange
) -> None:
print(f"Service {name} of type {service_type} state changed: {state_change}")
if state_change is not ServiceStateChange.Added:
return
base_name = name[: -len(service_type) - 1]
device_name = f"{base_name}.{DEVICE_INFO_SERVICE}"
asyncio.ensure_future(_async_show_service_info(zeroconf, service_type, name))
# Also probe for device info
asyncio.ensure_future(_async_show_service_info(zeroconf, DEVICE_INFO_SERVICE, device_name))


async def _async_show_service_info(zeroconf: Zeroconf, service_type: str, name: str) -> None:
info = AsyncServiceInfo(service_type, name)
await info.async_request(zeroconf, 3000, question_type=DNSQuestionType.QU)
print("Info from zeroconf.get_service_info: %r" % (info))
if info:
addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_addresses()]
print(" Name: %s" % name)
print(" Addresses: %s" % ", ".join(addresses))
print(" Weight: %d, priority: %d" % (info.weight, info.priority))
print(f" Server: {info.server}")
if info.properties:
print(" Properties are:")
for key, value in info.properties.items():
print(f" {key!r}: {value!r}")
else:
print(" No properties")
else:
print(" No info")
print('\n')


class AsyncAppleScanner:
def __init__(self, args: Any) -> None:
self.args = args
self.aiobrowser: Optional[AsyncServiceBrowser] = None
self.aiozc: Optional[AsyncZeroconf] = None

async def async_run(self) -> None:
self.aiozc = AsyncZeroconf(ip_version=ip_version)
await self.aiozc.zeroconf.async_wait_for_start()
print("\nBrowsing %s service(s), press Ctrl-C to exit...\n" % ALL_SERVICES)
kwargs = {'handlers': [async_on_service_state_change], 'question_type': DNSQuestionType.QU}
if self.args.target:
kwargs["addr"] = self.args.target
self.aiobrowser = AsyncServiceBrowser(self.aiozc.zeroconf, ALL_SERVICES, **kwargs) # type: ignore
while True:
await asyncio.sleep(1)

async def async_close(self) -> None:
assert self.aiozc is not None
assert self.aiobrowser is not None
await self.aiobrowser.async_cancel()
await self.aiozc.async_close()


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)

parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true')
version_group = parser.add_mutually_exclusive_group()
version_group.add_argument('--target', help='Unicast target')
version_group.add_argument('--v6', action='store_true')
version_group.add_argument('--v6-only', action='store_true')
args = parser.parse_args()

if args.debug:
logging.getLogger('zeroconf').setLevel(logging.DEBUG)
if args.v6:
ip_version = IPVersion.All
elif args.v6_only:
ip_version = IPVersion.V6Only
else:
ip_version = IPVersion.V4Only

loop = asyncio.get_event_loop()
runner = AsyncAppleScanner(args)
try:
loop.run_until_complete(runner.async_run())
except KeyboardInterrupt:
loop.run_until_complete(runner.async_close())
106 changes: 106 additions & 0 deletions async_browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3

""" Example of browsing for a service.
The default is HTTP and HAP; use --find to search for all available services in the network
"""

import argparse
import asyncio
import logging
from typing import Any, Optional, cast

from zeroconf import IPVersion, ServiceStateChange, Zeroconf
from zeroconf.asyncio import (
AsyncServiceBrowser,
AsyncServiceInfo,
AsyncZeroconf,
AsyncZeroconfServiceTypes,
)


def async_on_service_state_change(
zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange
) -> None:
print(f"Service {name} of type {service_type} state changed: {state_change}")
if state_change is not ServiceStateChange.Added:
return
asyncio.ensure_future(async_display_service_info(zeroconf, service_type, name))


async def async_display_service_info(zeroconf: Zeroconf, service_type: str, name: str) -> None:
info = AsyncServiceInfo(service_type, name)
await info.async_request(zeroconf, 3000)
print("Info from zeroconf.get_service_info: %r" % (info))
if info:
addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_scoped_addresses()]
print(" Name: %s" % name)
print(" Addresses: %s" % ", ".join(addresses))
print(" Weight: %d, priority: %d" % (info.weight, info.priority))
print(f" Server: {info.server}")
if info.properties:
print(" Properties are:")
for key, value in info.properties.items():
print(f" {key!r}: {value!r}")
else:
print(" No properties")
else:
print(" No info")
print('\n')


class AsyncRunner:
def __init__(self, args: Any) -> None:
self.args = args
self.aiobrowser: Optional[AsyncServiceBrowser] = None
self.aiozc: Optional[AsyncZeroconf] = None

async def async_run(self) -> None:
self.aiozc = AsyncZeroconf(ip_version=ip_version)

services = ["_http._tcp.local.", "_hap._tcp.local."]
if self.args.find:
services = list(
await AsyncZeroconfServiceTypes.async_find(aiozc=self.aiozc, ip_version=ip_version)
)

print("\nBrowsing %s service(s), press Ctrl-C to exit...\n" % services)
self.aiobrowser = AsyncServiceBrowser(
self.aiozc.zeroconf, services, handlers=[async_on_service_state_change]
)
while True:
await asyncio.sleep(1)

async def async_close(self) -> None:
assert self.aiozc is not None
assert self.aiobrowser is not None
await self.aiobrowser.async_cancel()
await self.aiozc.async_close()


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)

parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true')
parser.add_argument('--find', action='store_true', help='Browse all available services')
version_group = parser.add_mutually_exclusive_group()
version_group.add_argument('--v6', action='store_true')
version_group.add_argument('--v6-only', action='store_true')
args = parser.parse_args()

if args.debug:
logging.getLogger('zeroconf').setLevel(logging.DEBUG)
if args.v6:
ip_version = IPVersion.All
elif args.v6_only:
ip_version = IPVersion.V6Only
else:
ip_version = IPVersion.V4Only

loop = asyncio.get_event_loop()
runner = AsyncRunner(args)
try:
loop.run_until_complete(runner.async_run())
except KeyboardInterrupt:
loop.run_until_complete(runner.async_close())
74 changes: 74 additions & 0 deletions async_registration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Example of announcing 250 services (in this case, a fake HTTP server)."""

import argparse
import asyncio
import logging
import socket
from typing import List, Optional

from zeroconf import IPVersion
from zeroconf.asyncio import AsyncServiceInfo, AsyncZeroconf


class AsyncRunner:
def __init__(self, ip_version: IPVersion) -> None:
self.ip_version = ip_version
self.aiozc: Optional[AsyncZeroconf] = None

async def register_services(self, infos: List[AsyncServiceInfo]) -> None:
self.aiozc = AsyncZeroconf(ip_version=self.ip_version)
tasks = [self.aiozc.async_register_service(info) for info in infos]
background_tasks = await asyncio.gather(*tasks)
await asyncio.gather(*background_tasks)
print("Finished registration, press Ctrl-C to exit...")
while True:
await asyncio.sleep(1)

async def unregister_services(self, infos: List[AsyncServiceInfo]) -> None:
assert self.aiozc is not None
tasks = [self.aiozc.async_unregister_service(info) for info in infos]
background_tasks = await asyncio.gather(*tasks)
await asyncio.gather(*background_tasks)
await self.aiozc.async_close()


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)

parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true')
version_group = parser.add_mutually_exclusive_group()
version_group.add_argument('--v6', action='store_true')
version_group.add_argument('--v6-only', action='store_true')
args = parser.parse_args()

if args.debug:
logging.getLogger('zeroconf').setLevel(logging.DEBUG)
if args.v6:
ip_version = IPVersion.All
elif args.v6_only:
ip_version = IPVersion.V6Only
else:
ip_version = IPVersion.V4Only

infos = []
for i in range(250):
infos.append(
AsyncServiceInfo(
"_http._tcp.local.",
f"Paul's Test Web Site {i}._http._tcp.local.",
addresses=[socket.inet_aton("127.0.0.1")],
port=80,
properties={'path': '/~paulsm/'},
server=f"zcdemohost-{i}.local.",
)
)

print("Registration of 250 services...")
loop = asyncio.get_event_loop()
runner = AsyncRunner(ip_version)
try:
loop.run_until_complete(runner.register_services(infos))
except KeyboardInterrupt:
loop.run_until_complete(runner.unregister_services(infos))
Loading

0 comments on commit ce1127d

Please sign in to comment.