Skip to content

Commit c67c856

Browse files
authored
feat: simplify examples (#99)
1 parent 7dc290c commit c67c856

File tree

6 files changed

+128
-156
lines changed

6 files changed

+128
-156
lines changed

docs/usage.md

+21-59
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,12 @@ from __future__ import annotations
1111

1212
import asyncio
1313
import logging
14-
from collections.abc import Callable
15-
from typing import TypedDict
1614

1715
import habluetooth
18-
from aioesphomeapi import APIClient, ReconnectLogic
1916

20-
import bleak_esphome
21-
22-
23-
class ESPHomeDeviceConfig(TypedDict):
24-
"""Configuration for an ESPHome device."""
25-
26-
address: str
27-
noise_psk: str | None
17+
from bleak_esphome import APIConnectionManager, ESPHomeDeviceConfig
2818

19+
CONNECTION_TIMEOUT = 5
2920

3021
# An unlimited number of devices can be added here
3122
ESPHOME_DEVICES: list[ESPHomeDeviceConfig] = [
@@ -40,65 +31,36 @@ ESPHOME_DEVICES: list[ESPHomeDeviceConfig] = [
4031
]
4132

4233

43-
async def setup_api_connection(
44-
address: str, noise_psk: str | None = None
45-
) -> tuple[ReconnectLogic, APIClient]:
46-
"""Setup the API connection."""
47-
cli = APIClient(address=address, port=6053, password=None, noise_psk=noise_psk)
48-
unregister_scanner: Callable[[], None] | None = None
49-
50-
async def on_disconnect(expected_disconnect: bool) -> None:
51-
nonlocal unregister_scanner
52-
if unregister_scanner is not None:
53-
unregister_scanner()
54-
unregister_scanner = None
55-
56-
async def on_connect() -> None:
57-
nonlocal unregister_scanner
58-
device_info = await cli.device_info()
59-
client_data = bleak_esphome.connect_scanner(cli, device_info, True)
60-
scanner = client_data.scanner
61-
assert scanner is not None # noqa: S101
62-
scanner.async_setup()
63-
unregister_scanner = habluetooth.get_manager().async_register_scanner(scanner)
64-
65-
reconnect_logic = ReconnectLogic(
66-
client=cli,
67-
on_disconnect=on_disconnect,
68-
on_connect=on_connect,
69-
)
70-
await reconnect_logic.start()
71-
72-
return reconnect_logic, cli
73-
34+
async def example_app() -> None:
35+
"""Example application here."""
36+
import bleak
7437

75-
async def run_application(cli: APIClient) -> None:
76-
"""Test application here."""
77-
import bleak # noqa
38+
await asyncio.sleep(5) # Give time for advertisements to be received
7839

7940
# Use bleak normally here
41+
devices = await bleak.BleakScanner.discover(return_adv=True)
42+
for d, a in devices.values():
43+
print()
44+
print(d)
45+
print("-" * len(str(d)))
46+
print(a)
8047

8148
# Wait forever
82-
event = asyncio.Event()
83-
await event.wait()
49+
await asyncio.Event().wait()
8450

8551

8652
async def run() -> None:
8753
"""Run the main application."""
88-
esphome_connections: list[tuple[ReconnectLogic, APIClient]] = []
89-
reconnect_logic: ReconnectLogic | None = None
90-
cli: APIClient | None = None
54+
connections = [APIConnectionManager(device) for device in ESPHOME_DEVICES]
55+
await habluetooth.BluetoothManager().async_setup()
9156
try:
92-
await habluetooth.BluetoothManager().async_setup()
93-
for device in ESPHOME_DEVICES:
94-
esphome_connections.append(
95-
await setup_api_connection(device["address"], device["noise_psk"])
96-
)
97-
await run_application(cli)
57+
await asyncio.wait(
58+
(asyncio.create_task(conn.start()) for conn in connections),
59+
timeout=CONNECTION_TIMEOUT,
60+
)
61+
await example_app()
9862
finally:
99-
for reconnect_logic, cli in esphome_connections:
100-
await reconnect_logic.stop()
101-
await cli.disconnect()
63+
await asyncio.gather(*(conn.stop() for conn in connections))
10264

10365

10466
logging.basicConfig(level=logging.DEBUG)

examples/setup_scanner.py

+21-59
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,12 @@
22

33
import asyncio
44
import logging
5-
from collections.abc import Callable
6-
from typing import TypedDict
75

86
import habluetooth
9-
from aioesphomeapi import APIClient, ReconnectLogic
107

11-
import bleak_esphome
12-
13-
14-
class ESPHomeDeviceConfig(TypedDict):
15-
"""Configuration for an ESPHome device."""
16-
17-
address: str
18-
noise_psk: str | None
8+
from bleak_esphome import APIConnectionManager, ESPHomeDeviceConfig
199

10+
CONNECTION_TIMEOUT = 5
2011

2112
# An unlimited number of devices can be added here
2213
ESPHOME_DEVICES: list[ESPHomeDeviceConfig] = [
@@ -31,65 +22,36 @@ class ESPHomeDeviceConfig(TypedDict):
3122
]
3223

3324

34-
async def setup_api_connection(
35-
address: str, noise_psk: str | None = None
36-
) -> tuple[ReconnectLogic, APIClient]:
37-
"""Setup the API connection."""
38-
cli = APIClient(address=address, port=6053, password=None, noise_psk=noise_psk)
39-
unregister_scanner: Callable[[], None] | None = None
40-
41-
async def on_disconnect(expected_disconnect: bool) -> None:
42-
nonlocal unregister_scanner
43-
if unregister_scanner is not None:
44-
unregister_scanner()
45-
unregister_scanner = None
46-
47-
async def on_connect() -> None:
48-
nonlocal unregister_scanner
49-
device_info = await cli.device_info()
50-
client_data = bleak_esphome.connect_scanner(cli, device_info, True)
51-
scanner = client_data.scanner
52-
assert scanner is not None # noqa: S101
53-
scanner.async_setup()
54-
unregister_scanner = habluetooth.get_manager().async_register_scanner(scanner)
55-
56-
reconnect_logic = ReconnectLogic(
57-
client=cli,
58-
on_disconnect=on_disconnect,
59-
on_connect=on_connect,
60-
)
61-
await reconnect_logic.start()
62-
63-
return reconnect_logic, cli
64-
25+
async def example_app() -> None:
26+
"""Example application here."""
27+
import bleak
6528

66-
async def run_application(cli: APIClient) -> None:
67-
"""Test application here."""
68-
import bleak # noqa
29+
await asyncio.sleep(5) # Give time for the scanner to find devices
6930

7031
# Use bleak normally here
32+
devices = await bleak.BleakScanner.discover(return_adv=True)
33+
for d, a in devices.values():
34+
print()
35+
print(d)
36+
print("-" * len(str(d)))
37+
print(a)
7138

7239
# Wait forever
73-
event = asyncio.Event()
74-
await event.wait()
40+
await asyncio.Event().wait()
7541

7642

7743
async def run() -> None:
7844
"""Run the main application."""
79-
esphome_connections: list[tuple[ReconnectLogic, APIClient]] = []
80-
reconnect_logic: ReconnectLogic | None = None
81-
cli: APIClient | None = None
45+
connections = [APIConnectionManager(device) for device in ESPHOME_DEVICES]
46+
await habluetooth.BluetoothManager().async_setup()
8247
try:
83-
await habluetooth.BluetoothManager().async_setup()
84-
for device in ESPHOME_DEVICES:
85-
esphome_connections.append(
86-
await setup_api_connection(device["address"], device["noise_psk"])
87-
)
88-
await run_application(cli)
48+
await asyncio.wait(
49+
(asyncio.create_task(conn.start()) for conn in connections),
50+
timeout=CONNECTION_TIMEOUT,
51+
)
52+
await example_app()
8953
finally:
90-
for reconnect_logic, cli in esphome_connections:
91-
await reconnect_logic.stop()
92-
await cli.disconnect()
54+
await asyncio.gather(*(conn.stop() for conn in connections))
9355

9456

9557
logging.basicConfig(level=logging.DEBUG)

poetry.lock

+10-37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ python = ">=3.11,<3.14"
3131
aioesphomeapi = ">=29"
3232
bleak = ">=0.21.1"
3333
bluetooth-data-tools = ">=1.18.0"
34-
habluetooth = ">=3.17.0"
34+
habluetooth = ">=3.24.1"
3535
lru-dict = ">=1.2.0"
3636
bleak-retry-connector = ">=3.8.0"
3737

src/bleak_esphome/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from .connect import connect_scanner
2+
from .connection_manager import APIConnectionManager, ESPHomeDeviceConfig
23

34
__all__ = [
5+
"APIConnectionManager",
6+
"ESPHomeDeviceConfig",
47
"connect_scanner",
58
]
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
from collections.abc import Callable
5+
from typing import TypedDict
6+
7+
import habluetooth
8+
from aioesphomeapi import APIClient, ReconnectLogic
9+
10+
import bleak_esphome
11+
12+
13+
class ESPHomeDeviceConfig(TypedDict):
14+
"""Configuration for an ESPHome device."""
15+
16+
address: str
17+
noise_psk: str | None
18+
19+
20+
class APIConnectionManager:
21+
"""Manager for the API connection to an ESPHome device."""
22+
23+
def __init__(self, config: ESPHomeDeviceConfig) -> None:
24+
"""Initialize the API connection manager."""
25+
self._address = config["address"]
26+
self._noise_psk = config["noise_psk"]
27+
self._cli: APIClient = APIClient(
28+
address=self._address, port=6053, password=None, noise_psk=self._noise_psk
29+
)
30+
self._reconnect_logic = ReconnectLogic(
31+
client=self._cli,
32+
on_disconnect=self._on_disconnect,
33+
on_connect=self._on_connect,
34+
)
35+
self._unregister_scanner: Callable[[], None] | None = None
36+
self._start_future: asyncio.Future[None] = (
37+
asyncio.get_running_loop().create_future()
38+
)
39+
40+
async def _on_disconnect(self, expected_disconnect: bool) -> None:
41+
"""Handle the disconnection of the API client."""
42+
if self._unregister_scanner is not None:
43+
self._unregister_scanner()
44+
self._unregister_scanner = None
45+
46+
async def _on_connect(self) -> None:
47+
"""Handle the connection of the API client."""
48+
device_info = await self._cli.device_info()
49+
client_data = bleak_esphome.connect_scanner(self._cli, device_info, True)
50+
scanner = client_data.scanner
51+
assert scanner is not None # noqa: S101
52+
scanner.async_setup()
53+
self._unregister_scanner = habluetooth.get_manager().async_register_scanner(
54+
scanner
55+
)
56+
if not self._start_future.done():
57+
self._start_future.set_result(None)
58+
59+
async def start(self) -> None:
60+
"""Start the API connection."""
61+
await self._reconnect_logic.start()
62+
await self._start_future
63+
64+
async def stop(self) -> None:
65+
"""Stop the API connection."""
66+
await self._reconnect_logic.stop()
67+
await self._cli.disconnect()
68+
if not self._start_future.done():
69+
self._start_future.cancel()
70+
if self._unregister_scanner is not None:
71+
self._unregister_scanner()
72+
self._unregister_scanner = None

0 commit comments

Comments
 (0)