lghorizon 0.9.0__py3-none-any.whl → 0.9.0b0__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 +2 -67
- lghorizon/exceptions.py +3 -3
- lghorizon/lghorizon_api.py +11 -41
- lghorizon/lghorizon_device.py +28 -101
- lghorizon/lghorizon_device_state_processor.py +26 -90
- lghorizon/lghorizon_message_factory.py +1 -0
- lghorizon/lghorizon_models.py +69 -250
- lghorizon/lghorizon_mqtt_client.py +66 -278
- lghorizon/lghorizon_recording_factory.py +2 -16
- lghorizon-0.9.0b0.dist-info/METADATA +41 -0
- lghorizon-0.9.0b0.dist-info/RECORD +17 -0
- lghorizon-0.9.0.dist-info/METADATA +0 -191
- lghorizon-0.9.0.dist-info/RECORD +0 -17
- {lghorizon-0.9.0.dist-info → lghorizon-0.9.0b0.dist-info}/WHEEL +0 -0
- {lghorizon-0.9.0.dist-info → lghorizon-0.9.0b0.dist-info}/licenses/LICENSE +0 -0
- {lghorizon-0.9.0.dist-info → lghorizon-0.9.0b0.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any, Callable, Coroutine
|
|
5
4
|
|
|
6
5
|
import paho.mqtt.client as mqtt
|
|
7
|
-
|
|
6
|
+
from typing import Any, Callable, Coroutine
|
|
8
7
|
from .helpers import make_id
|
|
9
8
|
from .lghorizon_models import LGHorizonAuth
|
|
10
9
|
|
|
@@ -12,46 +11,32 @@ _logger = logging.getLogger(__name__)
|
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class LGHorizonMqttClient:
|
|
15
|
-
"""
|
|
14
|
+
"""LGHorizon MQTT client."""
|
|
15
|
+
|
|
16
|
+
_mqtt_broker_url: str = ""
|
|
17
|
+
_mqtt_client: mqtt.Client
|
|
18
|
+
_auth: LGHorizonAuth
|
|
19
|
+
_mqtt_token: str = ""
|
|
20
|
+
client_id: str = ""
|
|
21
|
+
_on_connected_callback: Callable[[], Coroutine[Any, Any, Any]]
|
|
22
|
+
_on_message_callback: Callable[[dict, str], Coroutine[Any, Any, Any]]
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def is_connected(self):
|
|
26
|
+
"""Is client connected."""
|
|
27
|
+
return self._mqtt_client.is_connected
|
|
16
28
|
|
|
17
29
|
def __init__(
|
|
18
30
|
self,
|
|
19
31
|
auth: LGHorizonAuth,
|
|
20
32
|
on_connected_callback: Callable[[], Coroutine[Any, Any, Any]],
|
|
21
33
|
on_message_callback: Callable[[dict, str], Coroutine[Any, Any, Any]],
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
):
|
|
35
|
+
"""Initialize the MQTT client."""
|
|
24
36
|
self._auth = auth
|
|
25
|
-
"""Initialize the LGHorizonMqttClient.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
auth: The authentication object for obtaining MQTT tokens.
|
|
29
|
-
on_connected_callback: An async callback function for MQTT connection events.
|
|
30
|
-
on_message_callback: An async callback function for MQTT message events.
|
|
31
|
-
loop: The asyncio event loop.
|
|
32
|
-
"""
|
|
33
37
|
self._on_connected_callback = on_connected_callback
|
|
34
38
|
self._on_message_callback = on_message_callback
|
|
35
|
-
self._loop =
|
|
36
|
-
|
|
37
|
-
self._mqtt_client: mqtt.Client | None = None
|
|
38
|
-
self._mqtt_broker_url: str = ""
|
|
39
|
-
self._mqtt_token: str = ""
|
|
40
|
-
self.client_id: str = ""
|
|
41
|
-
self._reconnect_task: asyncio.Task | None = None
|
|
42
|
-
self._disconnect_requested: bool = False
|
|
43
|
-
|
|
44
|
-
# FIFO queues
|
|
45
|
-
self._message_queue: asyncio.Queue = asyncio.Queue()
|
|
46
|
-
self._publish_queue: asyncio.Queue = asyncio.Queue()
|
|
47
|
-
|
|
48
|
-
# Worker tasks
|
|
49
|
-
self._message_worker_task: asyncio.Task | None = None
|
|
50
|
-
self._publish_worker_task: asyncio.Task | None = None
|
|
51
|
-
|
|
52
|
-
@property
|
|
53
|
-
def is_connected(self) -> bool:
|
|
54
|
-
return self._mqtt_client is not None and self._mqtt_client.is_connected()
|
|
39
|
+
self._loop = asyncio.get_event_loop()
|
|
55
40
|
|
|
56
41
|
@classmethod
|
|
57
42
|
async def create(
|
|
@@ -59,277 +44,80 @@ class LGHorizonMqttClient:
|
|
|
59
44
|
auth: LGHorizonAuth,
|
|
60
45
|
on_connected_callback: Callable[[], Coroutine[Any, Any, Any]],
|
|
61
46
|
on_message_callback: Callable[[dict, str], Coroutine[Any, Any, Any]],
|
|
62
|
-
)
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
loop = asyncio.get_running_loop()
|
|
66
|
-
instance = cls(auth, on_connected_callback, on_message_callback, loop)
|
|
67
|
-
|
|
68
|
-
# Service config ophalen
|
|
47
|
+
):
|
|
48
|
+
"""Create the MQTT client."""
|
|
49
|
+
instance = cls(auth, on_connected_callback, on_message_callback)
|
|
69
50
|
service_config = await auth.get_service_config()
|
|
70
51
|
mqtt_broker_url = await service_config.get_service_url("mqttBroker")
|
|
71
52
|
instance._mqtt_broker_url = mqtt_broker_url.replace("wss://", "").replace(
|
|
72
53
|
":443/mqtt", ""
|
|
73
54
|
)
|
|
74
|
-
|
|
75
55
|
instance.client_id = await make_id()
|
|
76
|
-
|
|
77
|
-
# Paho client
|
|
78
56
|
instance._mqtt_client = mqtt.Client(
|
|
79
57
|
client_id=instance.client_id,
|
|
80
58
|
transport="websockets",
|
|
81
59
|
)
|
|
60
|
+
|
|
82
61
|
instance._mqtt_client.ws_set_options(
|
|
83
62
|
headers={"Sec-WebSocket-Protocol": "mqtt, mqttv3.1, mqttv3.11"}
|
|
84
63
|
)
|
|
85
|
-
|
|
86
|
-
# Token ophalen
|
|
87
64
|
instance._mqtt_token = await auth.get_mqtt_token()
|
|
88
|
-
instance._mqtt_client.username_pw_set(
|
|
89
|
-
|
|
90
|
-
instance._mqtt_token,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# TLS instellen (blocking → executor)
|
|
94
|
-
await loop.run_in_executor(None, instance._mqtt_client.tls_set)
|
|
95
|
-
|
|
65
|
+
instance._mqtt_client.username_pw_set(auth.household_id, instance._mqtt_token)
|
|
66
|
+
instance._mqtt_client.tls_set()
|
|
96
67
|
instance._mqtt_client.enable_logger(_logger)
|
|
97
68
|
instance._mqtt_client.on_connect = instance._on_connect
|
|
98
|
-
instance.
|
|
99
|
-
instance.
|
|
100
|
-
|
|
69
|
+
instance._on_connected_callback = on_connected_callback
|
|
70
|
+
instance._on_message_callback = on_message_callback
|
|
101
71
|
return instance
|
|
102
72
|
|
|
103
|
-
|
|
104
|
-
"""Connect the MQTT client to the broker asynchronously."""
|
|
105
|
-
if not self._mqtt_client:
|
|
106
|
-
raise RuntimeError("MQTT client not initialized")
|
|
107
|
-
|
|
108
|
-
if self.is_connected:
|
|
109
|
-
_logger.debug("MQTT client is already connected.")
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
self._disconnect_requested = False # Reset flag for new connection attempt
|
|
113
|
-
|
|
114
|
-
# Cancel any ongoing reconnect task if connect() is called manually
|
|
115
|
-
if self._reconnect_task and not self._reconnect_task.done():
|
|
116
|
-
_logger.debug("Cancelling existing reconnect task before manual connect.")
|
|
117
|
-
self._reconnect_task.cancel()
|
|
118
|
-
self._reconnect_task = None
|
|
119
|
-
|
|
120
|
-
_logger.info("Attempting initial MQTT connection...")
|
|
121
|
-
# Blocking connect → executor
|
|
122
|
-
await self._loop.run_in_executor(
|
|
123
|
-
None,
|
|
124
|
-
self._mqtt_client.connect,
|
|
125
|
-
self._mqtt_broker_url,
|
|
126
|
-
443,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
# Start Paho thread
|
|
130
|
-
self._mqtt_client.loop_start()
|
|
131
|
-
|
|
132
|
-
# Start workers
|
|
133
|
-
self._message_worker_task = asyncio.create_task(self._message_worker())
|
|
134
|
-
self._publish_worker_task = asyncio.create_task(self._publish_worker())
|
|
135
|
-
|
|
136
|
-
async def disconnect(self) -> None:
|
|
137
|
-
"""Disconnect the MQTT client from the broker asynchronously."""
|
|
138
|
-
if not self._mqtt_client:
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
# Stop workers
|
|
142
|
-
if self._message_worker_task:
|
|
143
|
-
self._message_worker_task.cancel()
|
|
144
|
-
self._message_worker_task = None
|
|
145
|
-
|
|
146
|
-
if self._publish_worker_task:
|
|
147
|
-
self._publish_worker_task.cancel()
|
|
148
|
-
self._publish_worker_task = None
|
|
149
|
-
|
|
150
|
-
# Blocking disconnect → executor
|
|
151
|
-
await self._loop.run_in_executor(None, self._mqtt_client.disconnect)
|
|
152
|
-
self._mqtt_client.loop_stop()
|
|
153
|
-
|
|
154
|
-
async def subscribe(self, topic: str) -> None:
|
|
155
|
-
"""Subscribe to an MQTT topic.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
topic: The MQTT topic to subscribe to.
|
|
159
|
-
"""
|
|
160
|
-
if not self._mqtt_client:
|
|
161
|
-
raise RuntimeError("MQTT client not initialized")
|
|
162
|
-
|
|
163
|
-
self._mqtt_client.subscribe(topic)
|
|
164
|
-
|
|
165
|
-
async def publish_message(self, topic: str, json_payload: str) -> None:
|
|
166
|
-
"""Queue an MQTT message for publishing.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
topic: The MQTT topic to publish to.
|
|
170
|
-
json_payload: The JSON payload as a string.
|
|
171
|
-
"""
|
|
172
|
-
await self._publish_queue.put((topic, json_payload))
|
|
173
|
-
|
|
174
|
-
# -------------------------
|
|
175
|
-
# INTERNAL CALLBACKS
|
|
176
|
-
# -------------------------
|
|
177
|
-
|
|
178
|
-
def _on_connect(self, client, userdata, flags, result_code):
|
|
179
|
-
"""Callback for when the MQTT client connects to the broker.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
client: The Paho MQTT client instance.
|
|
183
|
-
userdata: User data passed to the client.
|
|
184
|
-
flags: Response flags from the broker.
|
|
185
|
-
result_code: The connection result code.
|
|
186
|
-
"""
|
|
73
|
+
def _on_connect(self, client, userdata, flags, result_code): # pylint: disable=unused-argument
|
|
187
74
|
if result_code == 0:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
self._reconnect_task = None # Clear the reference
|
|
194
|
-
asyncio.run_coroutine_threadsafe(
|
|
195
|
-
self._on_connected_callback(),
|
|
196
|
-
self._loop,
|
|
197
|
-
)
|
|
75
|
+
self._mqtt_client.on_message = self._on_message
|
|
76
|
+
if self._on_connected_callback:
|
|
77
|
+
asyncio.run_coroutine_threadsafe(
|
|
78
|
+
self._on_connected_callback(), self._loop
|
|
79
|
+
)
|
|
198
80
|
elif result_code == 5:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
202
|
-
# Schedule the token refresh and reconnect in the main event loop
|
|
203
|
-
asyncio.run_coroutine_threadsafe(
|
|
204
|
-
self._handle_token_refresh_and_reconnect(), self._loop
|
|
205
|
-
)
|
|
81
|
+
self._mqtt_client.username_pw_set(self._auth.household_id, self._mqtt_token)
|
|
82
|
+
asyncio.run_coroutine_threadsafe(self.connect(), self._loop)
|
|
206
83
|
else:
|
|
207
|
-
_logger.error(
|
|
208
|
-
|
|
209
|
-
# which will then trigger the general reconnect loop.
|
|
210
|
-
|
|
211
|
-
async def _handle_token_refresh_and_reconnect(self):
|
|
212
|
-
"""Refreshes the MQTT token and attempts to reconnect the client."""
|
|
213
|
-
try:
|
|
214
|
-
# Get new token
|
|
215
|
-
self._mqtt_token = await self._auth.get_mqtt_token()
|
|
216
|
-
self._mqtt_client.username_pw_set(
|
|
217
|
-
self._auth.household_id,
|
|
218
|
-
self._mqtt_token,
|
|
84
|
+
_logger.error(
|
|
85
|
+
"Cannot connect to MQTT server with resultCode: %s", result_code
|
|
219
86
|
)
|
|
220
|
-
_logger.info("MQTT token refreshed. Attempting to reconnect.")
|
|
221
|
-
# Call connect. If it fails, _on_disconnect will be triggered,
|
|
222
|
-
# and the _reconnect_loop will take over.
|
|
223
|
-
await self.connect()
|
|
224
|
-
except Exception as e:
|
|
225
|
-
_logger.error("Failed to refresh MQTT token or initiate reconnect: %s", e)
|
|
226
|
-
# If token refresh itself fails, or connect() raises an exception
|
|
227
|
-
# before _on_disconnect can be called, ensure reconnect loop starts.
|
|
228
|
-
if not self._disconnect_requested and (
|
|
229
|
-
not self._reconnect_task or self._reconnect_task.done()
|
|
230
|
-
):
|
|
231
|
-
_logger.info(
|
|
232
|
-
"Scheduling MQTT reconnect after token refresh/connect failure."
|
|
233
|
-
)
|
|
234
|
-
self._reconnect_task = asyncio.create_task(self._reconnect_loop())
|
|
235
|
-
|
|
236
|
-
def _on_message(self, client, userdata, message):
|
|
237
|
-
"""Callback for when an MQTT message is received.
|
|
238
87
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
userdata: User data passed to the client.
|
|
242
|
-
message: The MQTTMessage object containing topic and payload.
|
|
243
|
-
"""
|
|
88
|
+
def _on_message(self, client, userdata, message): # pylint: disable=unused-argument
|
|
89
|
+
"""Wrapper for handling MQTT messages in a thread-safe manner."""
|
|
244
90
|
asyncio.run_coroutine_threadsafe(
|
|
245
|
-
self.
|
|
246
|
-
self._loop,
|
|
91
|
+
self._on_client_message(client, userdata, message), self._loop
|
|
247
92
|
)
|
|
248
93
|
|
|
249
|
-
def
|
|
250
|
-
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
client: The Paho MQTT client instance.
|
|
254
|
-
userdata: User data passed to the client.
|
|
255
|
-
result_code: The disconnection result code.
|
|
256
|
-
"""
|
|
257
|
-
_logger.warning("MQTT disconnected with result code: %s", result_code)
|
|
258
|
-
if not self._disconnect_requested:
|
|
259
|
-
_logger.info("Unexpected MQTT disconnection. Initiating reconnect loop.")
|
|
260
|
-
if not self._reconnect_task or self._reconnect_task.done():
|
|
261
|
-
self._reconnect_task = asyncio.run_coroutine_threadsafe(
|
|
262
|
-
self._reconnect_loop(), self._loop
|
|
263
|
-
)
|
|
264
|
-
else:
|
|
265
|
-
_logger.debug("Reconnect loop already active.")
|
|
266
|
-
else:
|
|
267
|
-
_logger.info("MQTT disconnected as requested.")
|
|
268
|
-
|
|
269
|
-
async def _reconnect_loop(self):
|
|
270
|
-
"""Manages the MQTT reconnection process with exponential backoff."""
|
|
271
|
-
retries = 0
|
|
272
|
-
while not self._disconnect_requested:
|
|
273
|
-
if self.is_connected:
|
|
274
|
-
_logger.debug(
|
|
275
|
-
"MQTT client reconnected within loop, stopping reconnect attempts."
|
|
276
|
-
)
|
|
277
|
-
break # Already connected, stop trying
|
|
278
|
-
|
|
279
|
-
delay = min(2**retries, 60) # Exponential backoff, max 60 seconds
|
|
280
|
-
_logger.debug(
|
|
281
|
-
"Waiting %s seconds before MQTT reconnect attempt %s",
|
|
282
|
-
delay,
|
|
283
|
-
retries + 1,
|
|
284
|
-
)
|
|
285
|
-
await asyncio.sleep(delay)
|
|
286
|
-
|
|
287
|
-
try:
|
|
288
|
-
_logger.info("Attempting MQTT reconnect...")
|
|
289
|
-
await self.connect()
|
|
290
|
-
# If connect() succeeds, _on_connect will be called, which will cancel this task.
|
|
291
|
-
# If connect() fails, _on_disconnect will be called again, and this loop continues.
|
|
292
|
-
break # If connect() doesn't raise, assume it's handled by _on_connect
|
|
293
|
-
except Exception as e:
|
|
294
|
-
_logger.error("MQTT reconnect attempt failed: %s", e)
|
|
295
|
-
retries += 1
|
|
296
|
-
self._reconnect_task = None # Clear task when loop finishes or is cancelled.
|
|
297
|
-
|
|
298
|
-
# -------------------------
|
|
299
|
-
# MESSAGE WORKER (FIFO)
|
|
300
|
-
# -------------------------
|
|
301
|
-
|
|
302
|
-
async def _message_worker(self):
|
|
303
|
-
"""Worker task to process incoming MQTT messages from the queue."""
|
|
304
|
-
while True:
|
|
305
|
-
topic, payload = await self._message_queue.get()
|
|
306
|
-
|
|
307
|
-
try:
|
|
308
|
-
json_payload = json.loads(payload)
|
|
309
|
-
await self._on_message_callback(json_payload, topic)
|
|
310
|
-
except Exception:
|
|
311
|
-
_logger.exception("Error processing MQTT message")
|
|
312
|
-
|
|
313
|
-
self._message_queue.task_done()
|
|
314
|
-
|
|
315
|
-
# -------------------------
|
|
316
|
-
# PUBLISH WORKER (FIFO)
|
|
317
|
-
# -------------------------
|
|
318
|
-
|
|
319
|
-
async def _publish_worker(self):
|
|
320
|
-
"""Worker task to process outgoing MQTT publish commands from the queue."""
|
|
321
|
-
while True:
|
|
322
|
-
topic, payload = await self._publish_queue.get()
|
|
323
|
-
|
|
324
|
-
try:
|
|
325
|
-
# Wacht tot MQTT echt connected is
|
|
326
|
-
while not self.is_connected:
|
|
327
|
-
await asyncio.sleep(0.1)
|
|
94
|
+
async def connect(self) -> None:
|
|
95
|
+
"""Connect the client."""
|
|
96
|
+
self._mqtt_client.connect(self._mqtt_broker_url, 443)
|
|
97
|
+
self._mqtt_client.loop_start()
|
|
328
98
|
|
|
329
|
-
|
|
330
|
-
|
|
99
|
+
async def subscribe(self, topic: str) -> None:
|
|
100
|
+
"""Subscribe to a MQTT topic."""
|
|
101
|
+
self._mqtt_client.subscribe(topic)
|
|
331
102
|
|
|
332
|
-
|
|
333
|
-
|
|
103
|
+
async def publish_message(self, topic: str, json_payload: str) -> None:
|
|
104
|
+
"""Publish a MQTT message."""
|
|
105
|
+
self._mqtt_client.publish(topic, json_payload, qos=2)
|
|
334
106
|
|
|
335
|
-
|
|
107
|
+
async def disconnect(self) -> None:
|
|
108
|
+
"""Disconnect the client."""
|
|
109
|
+
if self._mqtt_client.is_connected():
|
|
110
|
+
self._mqtt_client.disconnect()
|
|
111
|
+
|
|
112
|
+
async def _on_client_message(self, client, userdata, message): # pylint: disable=unused-argument
|
|
113
|
+
"""Handle messages received by mqtt client."""
|
|
114
|
+
json_payload = await self._loop.run_in_executor(
|
|
115
|
+
None, json.loads, message.payload
|
|
116
|
+
)
|
|
117
|
+
_logger.debug(
|
|
118
|
+
"Received MQTT message \n\ntopic: %s\npayload:\n\n%s\n",
|
|
119
|
+
message.topic,
|
|
120
|
+
json.dumps(json_payload, indent=2),
|
|
121
|
+
)
|
|
122
|
+
if self._on_message_callback:
|
|
123
|
+
await self._on_message_callback(json_payload, message.topic)
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
1
|
from .lghorizon_models import (
|
|
3
2
|
LGHorizonRecordingList,
|
|
4
3
|
LGHorizonRecordingSingle,
|
|
5
4
|
LGHorizonRecordingSeason,
|
|
6
5
|
LGHorizonRecordingShow,
|
|
7
6
|
LGHorizonRecordingType,
|
|
8
|
-
LGHorizonShowRecordingList,
|
|
9
7
|
)
|
|
10
8
|
|
|
11
9
|
|
|
@@ -34,22 +32,10 @@ class LGHorizonRecordingFactory:
|
|
|
34
32
|
|
|
35
33
|
return LGHorizonRecordingList(recording_list)
|
|
36
34
|
|
|
37
|
-
async def create_episodes(self, episode_json: dict) ->
|
|
35
|
+
async def create_episodes(self, episode_json: dict) -> LGHorizonRecordingList:
|
|
38
36
|
"""Create a LGHorizonRecording list based for episodes."""
|
|
39
37
|
recording_list = []
|
|
40
|
-
show_title: Optional[str] = None
|
|
41
|
-
if "images" in episode_json:
|
|
42
|
-
images = episode_json["images"]
|
|
43
|
-
show_image = next(
|
|
44
|
-
(img["url"] for img in images if img.get("type") == "titleTreatment"),
|
|
45
|
-
images[0]["url"] if images else None,
|
|
46
|
-
)
|
|
47
|
-
else:
|
|
48
|
-
show_image = None
|
|
49
|
-
|
|
50
38
|
for recording in episode_json["data"]:
|
|
51
39
|
recording_single = LGHorizonRecordingSingle(recording)
|
|
52
|
-
if show_title is None:
|
|
53
|
-
show_title = recording_single.show_title or recording_single.title
|
|
54
40
|
recording_list.append(recording_single)
|
|
55
|
-
return
|
|
41
|
+
return LGHorizonRecordingList(recording_list)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lghorizon
|
|
3
|
+
Version: 0.9.0b0
|
|
4
|
+
Summary: Python client for Liberty Global Horizon settop boxes
|
|
5
|
+
Home-page: https://github.com/sholofly/LGHorizon-python
|
|
6
|
+
Author: Rudolf Offereins
|
|
7
|
+
Author-email: r.offereins@gmail.com
|
|
8
|
+
License: MIT license
|
|
9
|
+
Keywords: LG,Horizon,API,Settop box
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: paho-mqtt
|
|
24
|
+
Requires-Dist: requests>=2.22.0
|
|
25
|
+
Requires-Dist: backoff>=1.9.0
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
# LG Horizon Api
|
|
40
|
+
|
|
41
|
+
Python library to control multiple LG Horizon boxes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
lghorizon/__init__.py,sha256=kUPbxGDOnoMwR3TdAeuzt8-vDB2bwRQCX_6yPSH1Rf8,181
|
|
2
|
+
lghorizon/const.py,sha256=HINlbyevEN9ZRnfIBbSGNc6i9J8WkIgpqkLZrwyqpGQ,5307
|
|
3
|
+
lghorizon/exceptions.py,sha256=-6v55KDTogBldGAg1wV9Mrxm5L5BsaVguhBgVMOeJHk,404
|
|
4
|
+
lghorizon/helpers.py,sha256=SGlEN6V0kh2vqw1qCKmM1KhfeO-UvPyyQmnThgFLFhs,272
|
|
5
|
+
lghorizon/lghorizon_api.py,sha256=r3Pc6ju4uHdSZOPZLtu3wxPFDMaoCGFt_3gZ4RNCg-Q,11781
|
|
6
|
+
lghorizon/lghorizon_device.py,sha256=jaxEjTjIx1zI9rBsv9gUwScRsVVNZrJMeM4ygxllBIo,12198
|
|
7
|
+
lghorizon/lghorizon_device_state_processor.py,sha256=R-aLtmS50nqJ3CZ7R5Qdg9w4lvZ3xIWiMskh4ls2sTA,12062
|
|
8
|
+
lghorizon/lghorizon_message_factory.py,sha256=1ZqgoGCERI-fhFh9ralemeHjPcpaPJS2AAkoGIbp9YI,1496
|
|
9
|
+
lghorizon/lghorizon_models.py,sha256=bncfG_gfJaG604xceg-svg8_EUDsplRXSSknqgOqh6k,41141
|
|
10
|
+
lghorizon/lghorizon_mqtt_client.py,sha256=bUWhTM8BJ1V5CbgP8ueJrm-pmopxBB7v_hLHKSwLxUU,4704
|
|
11
|
+
lghorizon/lghorizon_recording_factory.py,sha256=qIVfnfIvXliUv-Gy1LJXYmE6rFg90WhOQn_3P4ji2T0,1755
|
|
12
|
+
lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
lghorizon-0.9.0b0.dist-info/licenses/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
|
|
14
|
+
lghorizon-0.9.0b0.dist-info/METADATA,sha256=aGK1F_OuZ-TZJkBMSO58RPWpKKnFylwIscKOorPSOf8,1284
|
|
15
|
+
lghorizon-0.9.0b0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
16
|
+
lghorizon-0.9.0b0.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
|
|
17
|
+
lghorizon-0.9.0b0.dist-info/RECORD,,
|