python-linkplay 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
linkplay/__main__.py CHANGED
@@ -1,14 +1,19 @@
1
1
  import asyncio
2
2
  import aiohttp
3
3
 
4
- from linkplay.discovery import discover_linkplay_bridges, discover_multirooms
4
+ from linkplay.controller import LinkPlayController
5
5
 
6
6
 
7
7
  async def main():
8
8
  async with aiohttp.ClientSession() as session:
9
- bridges = await discover_linkplay_bridges(session)
10
- multirooms = await discover_multirooms(bridges)
11
- return bridges, multirooms
9
+ controller = LinkPlayController(session)
10
+ await controller.discover()
11
+
12
+ for bridge in controller.bridges:
13
+ print(bridge)
14
+
15
+ for multiroom in controller.multirooms:
16
+ print(multiroom.followers)
12
17
 
13
18
  if __name__ == "__main__":
14
19
  asyncio.run(main())
linkplay/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.0.1'
1
+ __version__ = '0.0.3'
linkplay/bridge.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
-
2
+ from typing import Any
3
3
  from aiohttp import ClientSession
4
4
 
5
5
  from linkplay.consts import (
@@ -15,8 +15,10 @@ from linkplay.consts import (
15
15
  InputMode,
16
16
  SpeakerType,
17
17
  PlayingMode,
18
- INPUT_MODE_MAP
18
+ INPUT_MODE_MAP,
19
+ MultiroomAttribute
19
20
  )
21
+
20
22
  from linkplay.utils import session_call_api_json, session_call_api_ok, decode_hexstr
21
23
 
22
24
 
@@ -243,9 +245,21 @@ class LinkPlayMultiroom():
243
245
  leader: LinkPlayBridge
244
246
  followers: list[LinkPlayBridge]
245
247
 
246
- def __init__(self, leader: LinkPlayBridge, followers: list[LinkPlayBridge]):
248
+ def __init__(self, leader: LinkPlayBridge):
247
249
  self.leader = leader
248
- self.followers = followers
250
+ self.followers = []
251
+
252
+ async def update_status(self, bridges: list[LinkPlayBridge]) -> None:
253
+ """Updates the multiroom status."""
254
+ properties: dict[Any, Any] = await self.leader.json_request(LinkPlayCommand.MULTIROOM_LIST)
255
+
256
+ self.followers = []
257
+ if int(properties[MultiroomAttribute.NUM_FOLLOWERS]) == 0:
258
+ return
259
+
260
+ follower_uuids = [follower[MultiroomAttribute.UUID] for follower in properties[MultiroomAttribute.FOLLOWER_LIST]]
261
+ new_followers = [bridge for bridge in bridges if bridge.device.uuid in follower_uuids]
262
+ self.followers.extend(new_followers)
249
263
 
250
264
  async def ungroup(self) -> None:
251
265
  """Ungroups the multiroom group."""
linkplay/consts.py CHANGED
@@ -285,3 +285,4 @@ class MultiroomAttribute(StrEnum):
285
285
  NUM_FOLLOWERS = "slaves"
286
286
  FOLLOWER_LIST = "slave_list"
287
287
  UUID = "uuid"
288
+ IP = "ip"
linkplay/controller.py ADDED
@@ -0,0 +1,34 @@
1
+ from aiohttp import ClientSession
2
+
3
+ from linkplay.bridge import LinkPlayBridge, LinkPlayMultiroom
4
+ from linkplay.discovery import discover_linkplay_bridges
5
+
6
+
7
+ class LinkPlayController():
8
+ """Represents a LinkPlay controller to manage the devices and multirooms."""
9
+
10
+ session: ClientSession
11
+ bridges: list[LinkPlayBridge]
12
+ multirooms: list[LinkPlayMultiroom]
13
+
14
+ def __init__(self, session: ClientSession):
15
+ self.session = session
16
+ self.bridges = []
17
+ self.multirooms = []
18
+
19
+ async def discover(self) -> None:
20
+ """Attempts to discover LinkPlay devices on the local network."""
21
+
22
+ # Discover new bridges
23
+ discovered_bridges = await discover_linkplay_bridges(self.session)
24
+ current_bridges = [bridge.device.uuid for bridge in self.bridges]
25
+ new_bridges = [discovered_bridge for discovered_bridge in discovered_bridges if discovered_bridge.device.uuid not in current_bridges]
26
+ self.bridges.extend(new_bridges)
27
+
28
+ # Create new multirooms
29
+ for new_bridge in new_bridges:
30
+ self.multirooms.append(LinkPlayMultiroom(new_bridge))
31
+
32
+ # Update multirooms
33
+ for multiroom in self.multirooms:
34
+ await multiroom.update_status(self.bridges)
linkplay/discovery.py CHANGED
@@ -5,7 +5,7 @@ from async_upnp_client.search import async_search
5
5
  from async_upnp_client.utils import CaseInsensitiveDict
6
6
 
7
7
  from linkplay.consts import UPNP_DEVICE_TYPE, LinkPlayCommand, MultiroomAttribute
8
- from linkplay.bridge import LinkPlayBridge, LinkPlayMultiroom
8
+ from linkplay.bridge import LinkPlayBridge
9
9
  from linkplay.exceptions import LinkPlayRequestException
10
10
 
11
11
 
@@ -21,9 +21,9 @@ async def linkplay_factory_bridge(ip_address: str, session: ClientSession) -> Li
21
21
  return bridge
22
22
 
23
23
 
24
- async def discover_linkplay_bridges(session: ClientSession) -> list[LinkPlayBridge]:
24
+ async def discover_linkplay_bridges(session: ClientSession, discovery_through_multiroom: bool = True) -> list[LinkPlayBridge]:
25
25
  """Attempts to discover LinkPlay devices on the local network."""
26
- devices: list[LinkPlayBridge] = []
26
+ bridges: dict[str, LinkPlayBridge] = {}
27
27
 
28
28
  async def add_linkplay_device_to_list(upnp_device: CaseInsensitiveDict):
29
29
  ip_address: str | None = upnp_device.get('_host')
@@ -32,32 +32,36 @@ async def discover_linkplay_bridges(session: ClientSession) -> list[LinkPlayBrid
32
32
  return
33
33
 
34
34
  if bridge := await linkplay_factory_bridge(ip_address, session):
35
- devices.append(bridge)
35
+ bridges[bridge.device.uuid] = bridge
36
36
 
37
37
  await async_search(
38
38
  search_target=UPNP_DEVICE_TYPE,
39
39
  async_callback=add_linkplay_device_to_list
40
40
  )
41
41
 
42
- return devices
42
+ # Discover additional bridges through grouped multirooms
43
+ if discovery_through_multiroom:
44
+ multiroom_discovered_bridges: dict[str, LinkPlayBridge] = {}
45
+ for bridge in bridges.values():
46
+ for new_bridge in await discover_bridges_through_multiroom(bridge, session):
47
+ multiroom_discovered_bridges[new_bridge.device.uuid] = new_bridge
43
48
 
49
+ bridges = bridges | multiroom_discovered_bridges
44
50
 
45
- async def discover_multirooms(bridges: list[LinkPlayBridge]) -> list[LinkPlayMultiroom]:
46
- """Discovers multirooms through the list of provided bridges."""
47
- multirooms: list[LinkPlayMultiroom] = []
51
+ return list(bridges.values())
48
52
 
49
- for bridge in bridges:
50
- properties: dict[Any, Any] = await bridge.json_request(LinkPlayCommand.MULTIROOM_LIST)
51
53
 
52
- if int(properties[MultiroomAttribute.NUM_FOLLOWERS]) == 0:
53
- continue
54
+ async def discover_bridges_through_multiroom(bridge: LinkPlayBridge,
55
+ session: ClientSession) -> list[LinkPlayBridge]:
56
+ """Discovers bridges through the multiroom of the provided bridge."""
57
+ properties: dict[Any, Any] = await bridge.json_request(LinkPlayCommand.MULTIROOM_LIST)
54
58
 
55
- followers: list[LinkPlayBridge] = []
56
- for follower in properties[MultiroomAttribute.FOLLOWER_LIST]:
57
- follower_uuid = follower[MultiroomAttribute.UUID]
58
- if follower_bridge := next((b for b in bridges if b.device.uuid == follower_uuid), None):
59
- followers.append(follower_bridge)
59
+ if int(properties[MultiroomAttribute.NUM_FOLLOWERS]) == 0:
60
+ return []
60
61
 
61
- multirooms.append(LinkPlayMultiroom(bridge, followers))
62
+ followers: list[LinkPlayBridge] = []
63
+ for follower in properties[MultiroomAttribute.FOLLOWER_LIST]:
64
+ if new_bridge := await linkplay_factory_bridge(follower[MultiroomAttribute.IP], session):
65
+ followers.append(new_bridge)
62
66
 
63
- return multirooms
67
+ return followers
linkplay/utils.py CHANGED
@@ -56,4 +56,7 @@ async def session_call_api_ok(endpoint: str, session: ClientSession, command: st
56
56
 
57
57
  def decode_hexstr(hexstr: str) -> str:
58
58
  """Decode a hex string."""
59
- return bytes.fromhex(hexstr).decode("utf-8")
59
+ try:
60
+ return bytes.fromhex(hexstr).decode("utf-8")
61
+ except ValueError:
62
+ return hexstr
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python_linkplay
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: A Python Library for Seamless LinkPlay Device Control
5
5
  Author: Velleman Group nv
6
6
  License: MIT
@@ -0,0 +1,14 @@
1
+ linkplay/__init__.py,sha256=y9ZehEq-KhS3cwn-PUpwVSJGfDUx7e5wf_G6guODcTk,56
2
+ linkplay/__main__.py,sha256=hVlIhEeSfIRlTjNsDXVMSAtOatbUqqIMdXPxdwBBE1c,447
3
+ linkplay/__version__.py,sha256=yyLtGsBhynC-sSHdax-rRGvHNjH-gdWxIY68WnPVfDw,22
4
+ linkplay/bridge.py,sha256=NX02ZBcbaz7yZv1EeFvQ9CAUh5jVcuSABtpKIY4aj74,10948
5
+ linkplay/consts.py,sha256=KIBGrRQqxd1B4kRO0Vl0e5-UzbMLzJGC_ECohzkSRwQ,7750
6
+ linkplay/controller.py,sha256=Rp92CQdpwreH-I132DrxogiZ_I8NngdmCeXzfQYAFMQ,1244
7
+ linkplay/discovery.py,sha256=NIUC3FVPH-FcHvMmojJw0s0w2Q-WCuulGrQA-PDLcsw,2642
8
+ linkplay/exceptions.py,sha256=tWJWHsKVkUEq3Yet1Z739IxcaQT8YamDeSp0tqHde9c,107
9
+ linkplay/utils.py,sha256=E_SjIyeK76ishhwuU24m28y1FDAMEj1QbRNt-aHIMdA,2137
10
+ python_linkplay-0.0.3.dist-info/LICENSE,sha256=bgEtxMyjEHX_4uwaAY3GCFTm234D4AOZ5dM15sk26ms,1073
11
+ python_linkplay-0.0.3.dist-info/METADATA,sha256=ifK_uu-ZBe81Wcqo8YoIObMiuPQ6ub1QUio7Mnm5JYE,2859
12
+ python_linkplay-0.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
13
+ python_linkplay-0.0.3.dist-info/top_level.txt,sha256=CpSaOVPTzJf5TVIL7MrotSCR34gcIOQy-11l4zGmxxM,9
14
+ python_linkplay-0.0.3.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- linkplay/__init__.py,sha256=y9ZehEq-KhS3cwn-PUpwVSJGfDUx7e5wf_G6guODcTk,56
2
- linkplay/__main__.py,sha256=AiwpQKJIV9HrSqj0qU-a8MKE-AUjbfTrQ6st45U78rE,382
3
- linkplay/__version__.py,sha256=DsAHdxLC16H2VjdFOU5tBx2xT9VnNQ-XbTS24fRCa_w,22
4
- linkplay/bridge.py,sha256=l5Vvtp2SHF2FYMa67Yf-daRGsErMRXB1GMPzHhwwG6k,10344
5
- linkplay/consts.py,sha256=XpRJaPmz39EKFAZrgZEJRloL5lEI3p8jLPgrEJHgbjk,7736
6
- linkplay/discovery.py,sha256=wmF7fsCd4htr7zHfDRh9z0J2cu1OjK50ZQaBfg3WlaU,2303
7
- linkplay/exceptions.py,sha256=tWJWHsKVkUEq3Yet1Z739IxcaQT8YamDeSp0tqHde9c,107
8
- linkplay/utils.py,sha256=L8P7n1JtI0Q62fHV2TC5K03TuPQXesSikXLVje6Av-E,2079
9
- python_linkplay-0.0.1.dist-info/LICENSE,sha256=bgEtxMyjEHX_4uwaAY3GCFTm234D4AOZ5dM15sk26ms,1073
10
- python_linkplay-0.0.1.dist-info/METADATA,sha256=-JpSNww6wpyDmpWHdQ-fAZXYBP4cCbPPUpSQCxh6sg0,2859
11
- python_linkplay-0.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
12
- python_linkplay-0.0.1.dist-info/top_level.txt,sha256=CpSaOVPTzJf5TVIL7MrotSCR34gcIOQy-11l4zGmxxM,9
13
- python_linkplay-0.0.1.dist-info/RECORD,,