lghorizon 0.9.0.dev3__py3-none-any.whl → 0.9.1__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.
- lghorizon/__init__.py +71 -2
- lghorizon/const.py +12 -77
- lghorizon/exceptions.py +3 -3
- lghorizon/lghorizon_api.py +42 -12
- lghorizon/lghorizon_device.py +101 -28
- lghorizon/lghorizon_device_state_processor.py +90 -26
- lghorizon/lghorizon_message_factory.py +0 -1
- lghorizon/lghorizon_models.py +250 -69
- lghorizon/lghorizon_mqtt_client.py +278 -66
- lghorizon/lghorizon_recording_factory.py +16 -2
- lghorizon-0.9.1.dist-info/METADATA +189 -0
- lghorizon-0.9.1.dist-info/RECORD +17 -0
- lghorizon-0.9.0.dev3.dist-info/METADATA +0 -41
- lghorizon-0.9.0.dev3.dist-info/RECORD +0 -17
- {lghorizon-0.9.0.dev3.dist-info → lghorizon-0.9.1.dist-info}/WHEEL +0 -0
- {lghorizon-0.9.0.dev3.dist-info → lghorizon-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {lghorizon-0.9.0.dev3.dist-info → lghorizon-0.9.1.dist-info}/top_level.txt +0 -0
lghorizon/__init__.py
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
"""Python client for LG Horizon."""
|
|
2
2
|
|
|
3
3
|
from .lghorizon_api import LGHorizonApi
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
4
|
+
from .lghorizon_device import LGHorizonDevice
|
|
5
|
+
from .lghorizon_models import (
|
|
6
|
+
LGHorizonAuth,
|
|
7
|
+
LGHorizonChannel,
|
|
8
|
+
LGHorizonCustomer,
|
|
9
|
+
LGHorizonDeviceState,
|
|
10
|
+
LGHorizonProfile,
|
|
11
|
+
LGHorizonRecording,
|
|
12
|
+
LGHorizonRecordingList,
|
|
13
|
+
LGHorizonShowRecordingList,
|
|
14
|
+
LGHorizonRecordingSeason,
|
|
15
|
+
LGHorizonRecordingSingle,
|
|
16
|
+
LGHorizonRecordingShow,
|
|
17
|
+
LGHorizonRecordingQuota,
|
|
18
|
+
LGHorizonRecordingType,
|
|
19
|
+
LGHorizonUIStateType,
|
|
20
|
+
LGHorizonMessageType,
|
|
21
|
+
LGHorizonRunningState,
|
|
22
|
+
LGHorizonRecordingSource,
|
|
23
|
+
LGHorizonRecordingState,
|
|
24
|
+
LGHorizonSourceType,
|
|
25
|
+
LGHorizonPlayerState,
|
|
26
|
+
LGHorizonAppsState,
|
|
27
|
+
LGHorizonUIState,
|
|
28
|
+
LGHorizonProfileOptions,
|
|
29
|
+
LGHorizonServicesConfig,
|
|
30
|
+
)
|
|
31
|
+
from .exceptions import (
|
|
32
|
+
LGHorizonApiError,
|
|
33
|
+
LGHorizonApiConnectionError,
|
|
34
|
+
LGHorizonApiUnauthorizedError,
|
|
35
|
+
LGHorizonApiLockedError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .const import COUNTRY_SETTINGS
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"LGHorizonApi",
|
|
42
|
+
"LGHorizonDevice",
|
|
43
|
+
"LGHorizonAuth",
|
|
44
|
+
"LGHorizonChannel",
|
|
45
|
+
"LGHorizonCustomer",
|
|
46
|
+
"LGHorizonDeviceState",
|
|
47
|
+
"LGHorizonProfile",
|
|
48
|
+
"LGHorizonApiError",
|
|
49
|
+
"LGHorizonApiConnectionError",
|
|
50
|
+
"LGHorizonApiUnauthorizedError",
|
|
51
|
+
"LGHorizonApiLockedError",
|
|
52
|
+
"LGHorizonRecordingList",
|
|
53
|
+
"LGHorizonRecordingSeason",
|
|
54
|
+
"LGHorizonRecordingSingle",
|
|
55
|
+
"LGHorizonRecordingShow",
|
|
56
|
+
"LGHorizonRecordingQuota",
|
|
57
|
+
"LGHorizonRecordingType",
|
|
58
|
+
"LGHorizonUIStateType",
|
|
59
|
+
"LGHorizonMessageType",
|
|
60
|
+
"LGHorizonRunningState",
|
|
61
|
+
"LGHorizonRecordingSource",
|
|
62
|
+
"LGHorizonRecordingState",
|
|
63
|
+
"LGHorizonSourceType",
|
|
64
|
+
"LGHorizonPlayerState",
|
|
65
|
+
"LGHorizonAppsState",
|
|
66
|
+
"LGHorizonUIState",
|
|
67
|
+
"LGHorizonProfileOptions",
|
|
68
|
+
"LGHorizonProfile",
|
|
69
|
+
"LGHorizonAuth",
|
|
70
|
+
"LGHorizonServicesConfig",
|
|
71
|
+
"LGHorizonRecording",
|
|
72
|
+
"LGHorizonShowRecordingList",
|
|
73
|
+
"COUNTRY_SETTINGS",
|
|
74
|
+
]
|
lghorizon/const.py
CHANGED
|
@@ -50,106 +50,41 @@ COUNTRY_SETTINGS = {
|
|
|
50
50
|
"api_url": "https://spark-prod-nl.gnp.cloud.ziggogo.tv",
|
|
51
51
|
"mqtt_url": "obomsg.prod.nl.horizon.tv",
|
|
52
52
|
"use_refreshtoken": False,
|
|
53
|
-
"
|
|
54
|
-
{
|
|
55
|
-
"channelId": "NL_000073_019506",
|
|
56
|
-
"channelName": "Netflix",
|
|
57
|
-
"channelNumber": "150",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"channelId": "NL_000074_019507",
|
|
61
|
-
"channelName": "Videoland",
|
|
62
|
-
"channelNumber": "151",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"channelId": "NL_000194_019352",
|
|
66
|
-
"channelName": "NPO",
|
|
67
|
-
"channelNumber": "152",
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"channelId": "NL_000199_019356",
|
|
71
|
-
"channelName": "Prime Video",
|
|
72
|
-
"channelNumber": "153",
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
"platform_types": {
|
|
76
|
-
"EOS": {"manufacturer": "Arris", "model": "DCX960"},
|
|
77
|
-
"APOLLO": {"manufacturer": "Arris", "model": "VIP5002W"},
|
|
78
|
-
},
|
|
79
|
-
"language": "nl",
|
|
53
|
+
"name": "Ziggo",
|
|
80
54
|
},
|
|
81
55
|
"ch": {
|
|
82
56
|
"api_url": "https://spark-prod-ch.gnp.cloud.sunrisetv.ch",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
57
|
+
"use_refreshtoken": True,
|
|
58
|
+
"name": "UPC Switzerland",
|
|
85
59
|
},
|
|
86
60
|
"be-basetv": {
|
|
87
61
|
"api_url": "https://spark-prod-be.gnp.cloud.base.tv",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"platform_types": {
|
|
91
|
-
"EOS": {"manufacturer": "Arris", "model": "DCX960"},
|
|
92
|
-
"HORIZON": {"manufacturer": "Arris", "model": "VIP5002W"},
|
|
93
|
-
},
|
|
62
|
+
"use_refreshtoken": True,
|
|
63
|
+
"name": "BASE TV (BE)",
|
|
94
64
|
},
|
|
95
65
|
"be-nl": {
|
|
96
66
|
"api_url": "https://spark-prod-be.gnp.cloud.telenet.tv",
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"oauth_add_accept_header": False,
|
|
100
|
-
"oauth_url": "https://login.prd.telenet.be/openid/login.do",
|
|
101
|
-
"oauth_quote_login": False,
|
|
102
|
-
"oauth_redirect_header": "Location",
|
|
103
|
-
"channels": [
|
|
104
|
-
{"channelId": "netflix", "channelName": "Netflix", "channelNumber": "600"},
|
|
105
|
-
{"channelId": "youtube", "channelName": "Youtube", "channelNumber": "-1"},
|
|
106
|
-
],
|
|
107
|
-
"platform_types": {
|
|
108
|
-
"EOS": {"manufacturer": "Arris", "model": "DCX960"},
|
|
109
|
-
"HORIZON": {"manufacturer": "Arris", "model": "DCX960"},
|
|
110
|
-
"EOS2": {"manufacturer": "HUMAX", "model": "2008C-STB-TN"},
|
|
111
|
-
},
|
|
112
|
-
"language": "nl",
|
|
67
|
+
"use_refreshtoken": True,
|
|
68
|
+
"name": "Telenet (BE)",
|
|
113
69
|
},
|
|
114
70
|
"be-nl-preprod": {
|
|
115
71
|
"api_url": "https://spark-preprod-be.gnp.cloud.telenet.tv",
|
|
116
72
|
"use_refreshtoken": True,
|
|
117
|
-
"
|
|
118
|
-
"oauth_password_fieldname": "j_password",
|
|
119
|
-
"oauth_add_accept_header": False,
|
|
120
|
-
"oauth_url": "https://login.prd.telenet.be/openid/login.do",
|
|
121
|
-
"oauth_quote_login": False,
|
|
122
|
-
"oauth_redirect_header": "Location",
|
|
123
|
-
"channels": [
|
|
124
|
-
{"channelId": "netflix", "channelName": "Netflix", "channelNumber": "600"},
|
|
125
|
-
{"channelId": "youtube", "channelName": "Youtube", "channelNumber": "-1"},
|
|
126
|
-
],
|
|
127
|
-
"platform_types": {
|
|
128
|
-
"EOS": {"manufacturer": "Arris", "model": "DCX960"},
|
|
129
|
-
"HORIZON": {"manufacturer": "Arris", "model": "DCX960"},
|
|
130
|
-
"EOS2": {"manufacturer": "HUMAX", "model": "2008C-STB-TN"},
|
|
131
|
-
},
|
|
132
|
-
"language": "nl",
|
|
73
|
+
"name": "Telenet (BE, PREPROD)",
|
|
133
74
|
},
|
|
134
75
|
"gb": {
|
|
135
76
|
"api_url": "https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com",
|
|
136
|
-
"
|
|
137
|
-
"
|
|
77
|
+
"use_refreshtoken": True,
|
|
78
|
+
"name": "Virgin Media (GB)",
|
|
138
79
|
},
|
|
139
80
|
"ie": {
|
|
140
81
|
"api_url": "https://spark-prod-ie.gnp.cloud.virginmediatv.ie",
|
|
141
82
|
"use_refreshtoken": False,
|
|
142
|
-
"
|
|
143
|
-
"language": "en",
|
|
83
|
+
"name": "Virgin Media (IE)",
|
|
144
84
|
},
|
|
145
85
|
"pl": {
|
|
146
86
|
"api_url": "https://spark-prod-pl.gnp.cloud.upctv.pl",
|
|
147
87
|
"use_refreshtoken": False,
|
|
148
|
-
"
|
|
149
|
-
"language": "pl",
|
|
150
|
-
"platform_types": {
|
|
151
|
-
"EOS": {"manufacturer": "Arris", "model": "DCX960"},
|
|
152
|
-
"APOLLO": {"manufacturer": "Arris", "model": "VIP5002W"},
|
|
153
|
-
},
|
|
88
|
+
"name": "UPC (PL)",
|
|
154
89
|
},
|
|
155
90
|
}
|
lghorizon/exceptions.py
CHANGED
|
@@ -6,12 +6,12 @@ class LGHorizonApiError(Exception):
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class LGHorizonApiConnectionError(LGHorizonApiError):
|
|
9
|
-
"""
|
|
9
|
+
"""Exception for connection-related errors with the LG Horizon API."""
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class LGHorizonApiUnauthorizedError(Exception):
|
|
13
|
-
"""
|
|
13
|
+
"""Exception for unauthorized access to the LG Horizon API."""
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class LGHorizonApiLockedError(LGHorizonApiUnauthorizedError):
|
|
17
|
-
"""
|
|
17
|
+
"""Exception for locked account errors with the LG Horizon API."""
|
lghorizon/lghorizon_api.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""LG Horizon API client."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any, Dict, cast
|
|
4
|
+
from typing import Any, Dict, cast, Callable, Optional
|
|
5
5
|
|
|
6
6
|
from .lghorizon_device import LGHorizonDevice
|
|
7
7
|
from .lghorizon_models import LGHorizonChannel
|
|
@@ -15,7 +15,11 @@ from .lghorizon_models import LGHorizonMessageType
|
|
|
15
15
|
from .lghorizon_message_factory import LGHorizonMessageFactory
|
|
16
16
|
from .lghorizon_models import LGHorizonStatusMessage, LGHorizonUIStatusMessage
|
|
17
17
|
from .lghorizon_models import LGHorizonRunningState
|
|
18
|
-
from .lghorizon_models import
|
|
18
|
+
from .lghorizon_models import (
|
|
19
|
+
LGHorizonRecordingList,
|
|
20
|
+
LGHorizonRecordingQuota,
|
|
21
|
+
LGHorizonShowRecordingList,
|
|
22
|
+
)
|
|
19
23
|
from .lghorizon_recording_factory import LGHorizonRecordingFactory
|
|
20
24
|
from .lghorizon_device_state_processor import LGHorizonDeviceStateProcessor
|
|
21
25
|
|
|
@@ -26,7 +30,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
26
30
|
class LGHorizonApi:
|
|
27
31
|
"""LG Horizon API client."""
|
|
28
32
|
|
|
29
|
-
_mqtt_client: LGHorizonMqttClient
|
|
33
|
+
_mqtt_client: LGHorizonMqttClient | None
|
|
30
34
|
auth: LGHorizonAuth
|
|
31
35
|
_service_config: LGHorizonServicesConfig
|
|
32
36
|
_customer: LGHorizonCustomer
|
|
@@ -41,10 +45,18 @@ class LGHorizonApi:
|
|
|
41
45
|
|
|
42
46
|
def __init__(self, auth: LGHorizonAuth, profile_id: str = "") -> None:
|
|
43
47
|
"""Initialize LG Horizon API client."""
|
|
48
|
+
"""Initialize LG Horizon API client.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
auth: The authentication object for API requests.
|
|
52
|
+
profile_id: The ID of the user profile to use (optional).
|
|
53
|
+
"""
|
|
44
54
|
self.auth = auth
|
|
45
55
|
self._profile_id = profile_id
|
|
46
56
|
self._channels = {}
|
|
47
57
|
self._device_state_processor = None
|
|
58
|
+
self._mqtt_client = None
|
|
59
|
+
self._initialized = False
|
|
48
60
|
|
|
49
61
|
async def initialize(self) -> None:
|
|
50
62
|
"""Initialize the API client."""
|
|
@@ -62,14 +74,20 @@ class LGHorizonApi:
|
|
|
62
74
|
)
|
|
63
75
|
self._initialized = True
|
|
64
76
|
|
|
65
|
-
async def
|
|
77
|
+
async def set_token_refresh_callback(
|
|
78
|
+
self, token_refresh_callback: Callable[str, None]
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Set the token refresh callback."""
|
|
81
|
+
self.auth.token_refresh_callback = token_refresh_callback
|
|
82
|
+
|
|
83
|
+
async def get_devices(self) -> dict[str, LGHorizonDevice]:
|
|
66
84
|
"""Get devices."""
|
|
67
85
|
if not self._initialized:
|
|
68
86
|
raise RuntimeError("LGHorizonApi not initialized")
|
|
69
87
|
|
|
70
88
|
return self._devices
|
|
71
89
|
|
|
72
|
-
async def get_profiles(self) ->
|
|
90
|
+
async def get_profiles(self) -> dict[str, LGHorizonProfile]:
|
|
73
91
|
"""Get profile IDs."""
|
|
74
92
|
if not self._initialized:
|
|
75
93
|
raise RuntimeError("LGHorizonApi not initialized")
|
|
@@ -77,14 +95,16 @@ class LGHorizonApi:
|
|
|
77
95
|
return self._customer.profiles
|
|
78
96
|
|
|
79
97
|
async def get_profile_channels(
|
|
80
|
-
self, profile_id: str
|
|
98
|
+
self, profile_id: Optional[str] = None
|
|
81
99
|
) -> Dict[str, LGHorizonChannel]:
|
|
82
100
|
"""Returns channels to display baed on profile."""
|
|
83
101
|
# Attempt to retrieve the profile by the given profile_id
|
|
102
|
+
if not profile_id:
|
|
103
|
+
profile_id = self._profile_id
|
|
84
104
|
profile = self._customer.profiles.get(profile_id)
|
|
85
105
|
|
|
86
106
|
# If the specified profile is not found, and there are other profiles available,
|
|
87
|
-
# default to the first profile in the customer's list.
|
|
107
|
+
# default to the first profile in the customer's list if available.
|
|
88
108
|
if not profile and self._customer.profiles:
|
|
89
109
|
_LOGGER.debug(
|
|
90
110
|
"Profile with ID '%s' not found. Defaulting to first available profile.",
|
|
@@ -135,6 +155,10 @@ class LGHorizonApi:
|
|
|
135
155
|
self._initialized = False
|
|
136
156
|
|
|
137
157
|
async def _create_mqtt_client(self) -> LGHorizonMqttClient:
|
|
158
|
+
"""Create and configure the MQTT client.
|
|
159
|
+
|
|
160
|
+
Returns: An initialized LGHorizonMqttClient instance.
|
|
161
|
+
"""
|
|
138
162
|
mqtt_client = await LGHorizonMqttClient.create(
|
|
139
163
|
self.auth,
|
|
140
164
|
self._on_mqtt_connected,
|
|
@@ -144,8 +168,10 @@ class LGHorizonApi:
|
|
|
144
168
|
|
|
145
169
|
async def _on_mqtt_connected(self):
|
|
146
170
|
"""MQTT connected callback."""
|
|
171
|
+
await self._mqtt_client.subscribe("#")
|
|
147
172
|
await self._mqtt_client.subscribe(self.auth.household_id)
|
|
148
173
|
# await self._mqtt_client.subscribe(self.auth.household_id + "/#")
|
|
174
|
+
# await self._mqtt_client.subscribe(self.auth.household_id + "/+/#")
|
|
149
175
|
await self._mqtt_client.subscribe(
|
|
150
176
|
self.auth.household_id + "/" + self._mqtt_client.client_id
|
|
151
177
|
)
|
|
@@ -177,12 +203,16 @@ class LGHorizonApi:
|
|
|
177
203
|
case LGHorizonMessageType.STATUS:
|
|
178
204
|
message.__class__ = LGHorizonStatusMessage
|
|
179
205
|
status_message = cast(LGHorizonStatusMessage, message)
|
|
180
|
-
device = self._devices
|
|
206
|
+
device = self._devices.get(status_message.source, None)
|
|
207
|
+
if not device:
|
|
208
|
+
return
|
|
181
209
|
await device.handle_status_message(status_message)
|
|
182
210
|
case LGHorizonMessageType.UI_STATUS:
|
|
183
211
|
message.__class__ = LGHorizonUIStatusMessage
|
|
184
212
|
ui_status_message = cast(LGHorizonUIStatusMessage, message)
|
|
185
|
-
device = self._devices
|
|
213
|
+
device = self._devices.get(ui_status_message.source, None)
|
|
214
|
+
if not device:
|
|
215
|
+
return
|
|
186
216
|
if (
|
|
187
217
|
not device.device_state.state
|
|
188
218
|
== LGHorizonRunningState.ONLINE_RUNNING
|
|
@@ -190,7 +220,7 @@ class LGHorizonApi:
|
|
|
190
220
|
return
|
|
191
221
|
await device.handle_ui_status_message(ui_status_message)
|
|
192
222
|
|
|
193
|
-
async def _get_customer_info(self) ->
|
|
223
|
+
async def _get_customer_info(self) -> LGHorizonCustomer:
|
|
194
224
|
service_url = await self._service_config.get_service_url(
|
|
195
225
|
"personalizationService"
|
|
196
226
|
)
|
|
@@ -244,14 +274,14 @@ class LGHorizonApi:
|
|
|
244
274
|
|
|
245
275
|
async def get_show_recordings(
|
|
246
276
|
self, show_id: str, channel_id: str
|
|
247
|
-
) ->
|
|
277
|
+
) -> LGHorizonShowRecordingList: # type: ignore[valid-type]
|
|
248
278
|
"""Retrieve all recordings."""
|
|
249
279
|
_LOGGER.debug("Retrieving recordings fro show...")
|
|
250
280
|
service_url = await self._service_config.get_service_url("recordingService")
|
|
251
281
|
lang = await self._customer.get_profile_lang(self._profile_id)
|
|
252
282
|
episodes_json = await self.auth.request(
|
|
253
283
|
service_url,
|
|
254
|
-
f"/customers/
|
|
284
|
+
f"/customers/{self.auth.household_id}/episodes/shows/{show_id}?source=recording&isAdult=false&offset=0&limit=100&profileId={self._profile_id}&language={lang}&channelId={channel_id}&sort=time&sortOrder=asc",
|
|
255
285
|
)
|
|
256
286
|
recordings = await self._recording_factory.create_episodes(episodes_json)
|
|
257
287
|
return recordings
|
lghorizon/lghorizon_device.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""LG Horizon Device."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
4
|
+
import asyncio
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Any, Callable, Coroutine, Dict, Optional
|
|
@@ -146,8 +146,6 @@ class LGHorizonDevice:
|
|
|
146
146
|
|
|
147
147
|
async def register_mqtt(self) -> None:
|
|
148
148
|
"""Register the mqtt connection."""
|
|
149
|
-
if not self._mqtt_client.is_connected:
|
|
150
|
-
raise LGHorizonApiConnectionError("MQTT client not connected.")
|
|
151
149
|
topic = f"{self._auth.household_id}/{self._mqtt_client.client_id}/status"
|
|
152
150
|
payload = {
|
|
153
151
|
"source": self._mqtt_client.client_id,
|
|
@@ -159,14 +157,23 @@ class LGHorizonDevice:
|
|
|
159
157
|
async def set_callback(
|
|
160
158
|
self, change_callback: Callable[[str], Coroutine[Any, Any, Any]]
|
|
161
159
|
) -> None:
|
|
162
|
-
"""Set a callback function.
|
|
160
|
+
"""Set a callback function to be called when the device state changes.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
change_callback: An asynchronous callable that takes the device ID
|
|
164
|
+
as an argument.
|
|
165
|
+
"""
|
|
163
166
|
self._change_callback = change_callback
|
|
164
167
|
await self.register_mqtt() # type: ignore [assignment] # Callback can be None
|
|
165
168
|
|
|
166
169
|
async def handle_status_message(
|
|
167
170
|
self, status_message: LGHorizonStatusMessage
|
|
168
171
|
) -> None:
|
|
169
|
-
"""
|
|
172
|
+
"""Handle an incoming status message from the set-top box.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
status_message: The status message received from the device.
|
|
176
|
+
"""
|
|
170
177
|
old_running_state = self.device_state.state
|
|
171
178
|
new_running_state = status_message.running_state
|
|
172
179
|
if (
|
|
@@ -200,7 +207,11 @@ class LGHorizonDevice:
|
|
|
200
207
|
self.recording_capacity = payload["used"] # Use the setter
|
|
201
208
|
|
|
202
209
|
async def _trigger_callback(self):
|
|
203
|
-
|
|
210
|
+
"""Trigger the registered callback function.
|
|
211
|
+
|
|
212
|
+
This method is called when the device's state changes and a callback is set.
|
|
213
|
+
"""
|
|
214
|
+
if self._change_callback is not None:
|
|
204
215
|
_LOGGER.debug("Callback called from box %s", self.device_id)
|
|
205
216
|
await self._change_callback(self.device_id)
|
|
206
217
|
|
|
@@ -267,38 +278,100 @@ class LGHorizonDevice:
|
|
|
267
278
|
if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
|
|
268
279
|
await self.send_key_to_box(MEDIA_KEY_RECORD)
|
|
269
280
|
|
|
281
|
+
async def set_player_position(self, position: int) -> None:
|
|
282
|
+
"""Set the player position on the settop box."""
|
|
283
|
+
payload = {
|
|
284
|
+
"source": self.device_id,
|
|
285
|
+
"type": "CPE.setPlayerPosition",
|
|
286
|
+
"runtimeType": "setPlayerposition",
|
|
287
|
+
"id": await make_id(),
|
|
288
|
+
"version": "1.3.11",
|
|
289
|
+
"status": {"relativePosition": position},
|
|
290
|
+
}
|
|
291
|
+
payload_str = json.dumps(payload)
|
|
292
|
+
await self._mqtt_client.publish_message(
|
|
293
|
+
f"{self._auth.household_id}/{self.device_id}", payload_str
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
async def display_message(self, sourceType: str, message: str) -> None:
|
|
297
|
+
"""Display a message on the set-top box and repeat it for longer visibility.
|
|
298
|
+
|
|
299
|
+
# We sturen de payload 3 keer met een kortere tussentijd
|
|
300
|
+
|
|
301
|
+
This method sends the message payload multiple times to ensure it stays
|
|
302
|
+
visible on the screen for a longer duration, as the display time for
|
|
303
|
+
such messages is typically short.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
sourceType: The type of source for the message (e.g., "linear").
|
|
307
|
+
message: The message string to display.
|
|
308
|
+
"""
|
|
309
|
+
for i in range(3):
|
|
310
|
+
payload = {
|
|
311
|
+
"id": await make_id(8),
|
|
312
|
+
"type": "CPE.pushToTV",
|
|
313
|
+
"source": {
|
|
314
|
+
"clientId": self._mqtt_client.client_id,
|
|
315
|
+
"friendlyDeviceName": f"\n\n{message}",
|
|
316
|
+
},
|
|
317
|
+
"status": {
|
|
318
|
+
"sourceType": sourceType,
|
|
319
|
+
"source": {"channelId": "1234"},
|
|
320
|
+
"title": "Nieuwe melding",
|
|
321
|
+
"relativePosition": 0,
|
|
322
|
+
"speed": 1,
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
await self._mqtt_client.publish_message(
|
|
327
|
+
f"{self._auth.household_id}/{self.device_id}", json.dumps(payload)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Omdat de melding 3 seconden blijft staan, wachten we 3 seconden
|
|
331
|
+
# voor de volgende 'refresh'.
|
|
332
|
+
if i < 2:
|
|
333
|
+
await asyncio.sleep(3)
|
|
334
|
+
|
|
270
335
|
async def set_channel(self, source: str) -> None:
|
|
271
336
|
"""Change te channel from the settopbox."""
|
|
272
337
|
channel = [src for src in self._channels.values() if src.title == source][0]
|
|
273
|
-
payload =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
338
|
+
payload = {
|
|
339
|
+
"id": await make_id(8),
|
|
340
|
+
"type": "CPE.pushToTV",
|
|
341
|
+
"source": {
|
|
342
|
+
"clientId": self._mqtt_client.client_id,
|
|
343
|
+
"friendlyDeviceName": "Home Assistant",
|
|
344
|
+
},
|
|
345
|
+
"status": {
|
|
346
|
+
"sourceType": "linear",
|
|
347
|
+
"source": {"channelId": channel.id},
|
|
348
|
+
"relativePosition": 0,
|
|
349
|
+
"speed": 1,
|
|
350
|
+
},
|
|
351
|
+
}
|
|
283
352
|
|
|
284
353
|
await self._mqtt_client.publish_message(
|
|
285
|
-
f"{self._auth.household_id}/{self.device_id}", payload
|
|
354
|
+
f"{self._auth.household_id}/{self.device_id}", json.dumps(payload)
|
|
286
355
|
)
|
|
287
356
|
|
|
288
357
|
async def play_recording(self, recording_id):
|
|
289
358
|
"""Play recording."""
|
|
290
|
-
payload =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
359
|
+
payload = {
|
|
360
|
+
"id": await make_id(8),
|
|
361
|
+
"type": "CPE.pushToTV",
|
|
362
|
+
"source": {
|
|
363
|
+
"clientId": self._mqtt_client.client_id,
|
|
364
|
+
"friendlyDeviceName": "Home Assistant",
|
|
365
|
+
},
|
|
366
|
+
"status": {
|
|
367
|
+
"sourceType": "nDVR",
|
|
368
|
+
"source": {"recordingId": recording_id},
|
|
369
|
+
"relativePosition": 0,
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
|
|
300
373
|
await self._mqtt_client.publish_message(
|
|
301
|
-
f"{self._auth.household_id}/{self.device_id}", payload
|
|
374
|
+
f"{self._auth.household_id}/{self.device_id}", json.dumps(payload)
|
|
302
375
|
)
|
|
303
376
|
|
|
304
377
|
async def send_key_to_box(self, key: str) -> None:
|