PyChromecast 14.0.0__tar.gz → 14.0.2__tar.gz
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.
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PKG-INFO +7 -1
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/PKG-INFO +7 -1
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/README.rst +6 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/const.py +3 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/homeassistant.py +29 -1
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/plex.py +2 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/youtube.py +1 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/response_handler.py +4 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/socket_client.py +36 -37
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pyproject.toml +6 -1
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/requirements.txt +1 -1
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/LICENSE +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/MANIFEST.in +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/SOURCES.txt +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/dependency_links.txt +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/not-zip-safe +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/requires.txt +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/PyChromecast.egg-info/top_level.txt +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/__init__.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/config.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/__init__.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/bbciplayer.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/bbcsounds.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/bubbleupnp.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/dashcast.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/homeassistant_media.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/media.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/multizone.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/nrkradio.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/nrktv.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/receiver.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/shaka.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/supla.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/controllers/yleareena.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/dial.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/discovery.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/error.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/authority_keys_pb2.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/authority_keys_pb2.pyi +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/cast_channel_pb2.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/cast_channel_pb2.pyi +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/logging_pb2.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/logging_pb2.pyi +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/generated/readme.txt +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/models.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/pychromecast/quick_play.py +0 -0
- {PyChromecast-14.0.0 → PyChromecast-14.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyChromecast
|
|
3
|
-
Version: 14.0.
|
|
3
|
+
Version: 14.0.2
|
|
4
4
|
Summary: Python module to talk to Google Chromecast.
|
|
5
5
|
Author-email: Paulus Schoutsen <paulus@paulusschoutsen.nl>
|
|
6
6
|
Project-URL: Homepage, https://github.com/home-assistant-libs/pychromecast
|
|
@@ -192,3 +192,9 @@ work. Without him it would not have been possible!
|
|
|
192
192
|
|
|
193
193
|
.. _Linux style wildcard: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
|
194
194
|
.. _Fred Clift: https://github.com/minektur
|
|
195
|
+
|
|
196
|
+
|ohf-logo|
|
|
197
|
+
|
|
198
|
+
.. |ohf-logo| image:: https://www.openhomefoundation.org/badges/pychromecast.png
|
|
199
|
+
:alt: PyChromecast - A library from the Open Home Foundation
|
|
200
|
+
:target: https://www.openhomefoundation.org/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyChromecast
|
|
3
|
-
Version: 14.0.
|
|
3
|
+
Version: 14.0.2
|
|
4
4
|
Summary: Python module to talk to Google Chromecast.
|
|
5
5
|
Author-email: Paulus Schoutsen <paulus@paulusschoutsen.nl>
|
|
6
6
|
Project-URL: Homepage, https://github.com/home-assistant-libs/pychromecast
|
|
@@ -192,3 +192,9 @@ work. Without him it would not have been possible!
|
|
|
192
192
|
|
|
193
193
|
.. _Linux style wildcard: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
|
194
194
|
.. _Fred Clift: https://github.com/minektur
|
|
195
|
+
|
|
196
|
+
|ohf-logo|
|
|
197
|
+
|
|
198
|
+
.. |ohf-logo| image:: https://www.openhomefoundation.org/badges/pychromecast.png
|
|
199
|
+
:alt: PyChromecast - A library from the Open Home Foundation
|
|
200
|
+
:target: https://www.openhomefoundation.org/
|
|
@@ -175,3 +175,9 @@ work. Without him it would not have been possible!
|
|
|
175
175
|
|
|
176
176
|
.. _Linux style wildcard: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
|
177
177
|
.. _Fred Clift: https://github.com/minektur
|
|
178
|
+
|
|
179
|
+
|ohf-logo|
|
|
180
|
+
|
|
181
|
+
.. |ohf-logo| image:: https://www.openhomefoundation.org/badges/pychromecast.png
|
|
182
|
+
:alt: PyChromecast - A library from the Open Home Foundation
|
|
183
|
+
:target: https://www.openhomefoundation.org/
|
|
@@ -14,6 +14,7 @@ REQUEST_TIMEOUT = 10.0
|
|
|
14
14
|
|
|
15
15
|
MF_CANTON = "Canton Elektronik GmbH + Co. KG"
|
|
16
16
|
MF_GOOGLE = "Google Inc."
|
|
17
|
+
MF_HARMAN = "HARMAN International Industries"
|
|
17
18
|
MF_JBL = "JBL"
|
|
18
19
|
MF_LENOVO = "LENOVO"
|
|
19
20
|
MF_LG = "LG"
|
|
@@ -42,11 +43,13 @@ CAST_TYPES = {
|
|
|
42
43
|
"nest audio": (CAST_TYPE_AUDIO, MF_GOOGLE),
|
|
43
44
|
"nest wifi point": (CAST_TYPE_AUDIO, MF_GOOGLE),
|
|
44
45
|
"bravia 4k vh2": (CAST_TYPE_CHROMECAST, MF_SONY),
|
|
46
|
+
"bravia 4k vh21": (CAST_TYPE_CHROMECAST, MF_SONY),
|
|
45
47
|
"C4A": (CAST_TYPE_AUDIO, MF_SONY),
|
|
46
48
|
"JBL Link 10": (CAST_TYPE_AUDIO, MF_JBL),
|
|
47
49
|
"JBL Link 20": (CAST_TYPE_AUDIO, MF_JBL),
|
|
48
50
|
"JBL Link 300": (CAST_TYPE_AUDIO, MF_JBL),
|
|
49
51
|
"JBL Link 500": (CAST_TYPE_AUDIO, MF_JBL),
|
|
52
|
+
"JBL Link Portable": (CAST_TYPE_AUDIO, MF_HARMAN),
|
|
50
53
|
"lenovocd-24502f": (CAST_TYPE_AUDIO, MF_LENOVO),
|
|
51
54
|
"Lenovo Smart Display 7": (CAST_TYPE_CHROMECAST, MF_LENOVO),
|
|
52
55
|
"LG WK7 ThinQ Speaker": (CAST_TYPE_AUDIO, MF_LG),
|
|
@@ -4,6 +4,7 @@ Controller to interface with Home Assistant
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from functools import partial
|
|
7
|
+
import logging
|
|
7
8
|
import threading
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
@@ -29,6 +30,8 @@ ERR_WRONG_INSTANCE = 20
|
|
|
29
30
|
ERR_NOT_CONNECTED = 21
|
|
30
31
|
ERR_FETCH_CONFIG_FAILED = 22
|
|
31
32
|
|
|
33
|
+
_LOGGER = logging.getLogger(__name__)
|
|
34
|
+
|
|
32
35
|
|
|
33
36
|
class HomeAssistantController(BaseController):
|
|
34
37
|
"""Controller to interact with Home Assistant."""
|
|
@@ -45,6 +48,7 @@ class HomeAssistantController(BaseController):
|
|
|
45
48
|
app_id: str = APP_HOMEASSISTANT_LOVELACE,
|
|
46
49
|
hass_connect_timeout: float = DEFAULT_HASS_CONNECT_TIMEOUT,
|
|
47
50
|
) -> None:
|
|
51
|
+
_LOGGER.debug("HomeAssistantController.__init__")
|
|
48
52
|
super().__init__(app_namespace, app_id)
|
|
49
53
|
self.hass_url = hass_url
|
|
50
54
|
self.hass_uuid = hass_uuid
|
|
@@ -77,16 +81,19 @@ class HomeAssistantController(BaseController):
|
|
|
77
81
|
def channel_connected(self) -> None:
|
|
78
82
|
"""Called when a channel has been openend that supports the
|
|
79
83
|
namespace of this controller."""
|
|
84
|
+
_LOGGER.debug("HomeAssistantController.channel_connected")
|
|
80
85
|
self.get_status()
|
|
81
86
|
|
|
82
87
|
def channel_disconnected(self) -> None:
|
|
83
88
|
"""Called when a channel is disconnected."""
|
|
89
|
+
_LOGGER.debug("HomeAssistantController.channel_disconnected")
|
|
84
90
|
self.status = None
|
|
85
91
|
self._hass_connecting_event.set()
|
|
86
92
|
|
|
87
93
|
def receive_message(self, _message: CastMessage, data: dict) -> bool:
|
|
88
94
|
"""Called when a message is received."""
|
|
89
95
|
if data.get("type") == "receiver_status":
|
|
96
|
+
_LOGGER.debug("HomeAssistantController.receive_message %s", data)
|
|
90
97
|
if data["hassUrl"] != self.hass_url or data["hassUUID"] != self.hass_uuid:
|
|
91
98
|
self.logger.info("Received status for another instance")
|
|
92
99
|
self.unregister()
|
|
@@ -96,6 +103,9 @@ class HomeAssistantController(BaseController):
|
|
|
96
103
|
self.status = data
|
|
97
104
|
|
|
98
105
|
if was_connected or not self.hass_connected:
|
|
106
|
+
_LOGGER.debug(
|
|
107
|
+
"HomeAssistantController.receive_message already connected"
|
|
108
|
+
)
|
|
99
109
|
return True
|
|
100
110
|
|
|
101
111
|
# We just got connected, call the callbacks.
|
|
@@ -104,6 +114,7 @@ class HomeAssistantController(BaseController):
|
|
|
104
114
|
return True
|
|
105
115
|
|
|
106
116
|
if data.get("type") == "receiver_error":
|
|
117
|
+
_LOGGER.debug("HomeAssistantController.receive_message %s", data)
|
|
107
118
|
if data.get("error_code") == ERR_WRONG_INSTANCE:
|
|
108
119
|
self.logger.info("Received ERR_WRONG_INSTANCE")
|
|
109
120
|
self.unregister()
|
|
@@ -113,14 +124,19 @@ class HomeAssistantController(BaseController):
|
|
|
113
124
|
|
|
114
125
|
def _call_on_connect_callbacks(self, msg_sent: bool) -> None:
|
|
115
126
|
"""Call on connect callbacks."""
|
|
127
|
+
_LOGGER.debug("HomeAssistantController._call_on_connect_callbacks %s", msg_sent)
|
|
116
128
|
while self._on_connect:
|
|
117
129
|
self._on_connect.pop()(msg_sent, None)
|
|
118
130
|
|
|
119
131
|
def _connect_hass(self, callback_function: CallbackType) -> None:
|
|
120
132
|
"""Connect to Home Assistant and call the provided callback."""
|
|
133
|
+
_LOGGER.debug("HomeAssistantController._connect_hass")
|
|
121
134
|
self._on_connect.append(callback_function)
|
|
122
135
|
|
|
123
136
|
if not self._hass_connecting_event.is_set():
|
|
137
|
+
_LOGGER.debug(
|
|
138
|
+
"HomeAssistantController._connect_hass _hass_connecting_event not set"
|
|
139
|
+
)
|
|
124
140
|
return
|
|
125
141
|
|
|
126
142
|
self._hass_connecting_event.clear()
|
|
@@ -135,6 +151,9 @@ class HomeAssistantController(BaseController):
|
|
|
135
151
|
}
|
|
136
152
|
)
|
|
137
153
|
except Exception: # pylint: disable=broad-except
|
|
154
|
+
_LOGGER.debug(
|
|
155
|
+
"HomeAssistantController._connect_hass failed to send connect message"
|
|
156
|
+
)
|
|
138
157
|
self._hass_connecting_event.set()
|
|
139
158
|
self._call_on_connect_callbacks(False)
|
|
140
159
|
raise
|
|
@@ -143,10 +162,10 @@ class HomeAssistantController(BaseController):
|
|
|
143
162
|
try:
|
|
144
163
|
if not self._hass_connecting_event.is_set():
|
|
145
164
|
self.logger.warning("_connect_hass failed for %s", self.hass_url)
|
|
165
|
+
self._call_on_connect_callbacks(False)
|
|
146
166
|
raise PyChromecastError() # pylint: disable=broad-exception-raised
|
|
147
167
|
finally:
|
|
148
168
|
self._hass_connecting_event.set()
|
|
149
|
-
self._call_on_connect_callbacks(False)
|
|
150
169
|
|
|
151
170
|
def show_demo(self) -> None:
|
|
152
171
|
"""Show the demo."""
|
|
@@ -154,6 +173,7 @@ class HomeAssistantController(BaseController):
|
|
|
154
173
|
|
|
155
174
|
def get_status(self, *, callback_function: CallbackType | None = None) -> None:
|
|
156
175
|
"""Get status of Home Assistant Cast."""
|
|
176
|
+
_LOGGER.debug("HomeAssistantController.get_status")
|
|
157
177
|
self._send_connected_message(
|
|
158
178
|
{
|
|
159
179
|
"type": "get_status",
|
|
@@ -171,6 +191,7 @@ class HomeAssistantController(BaseController):
|
|
|
171
191
|
callback_function: CallbackType | None = None,
|
|
172
192
|
) -> None:
|
|
173
193
|
"""Show a Lovelace UI."""
|
|
194
|
+
_LOGGER.debug("HomeAssistantController.show_lovelace_view")
|
|
174
195
|
self._send_connected_message(
|
|
175
196
|
{
|
|
176
197
|
"type": "show_lovelace_view",
|
|
@@ -186,10 +207,17 @@ class HomeAssistantController(BaseController):
|
|
|
186
207
|
self, data: dict[str, Any], callback_function: CallbackType | None
|
|
187
208
|
) -> None:
|
|
188
209
|
"""Send a message to a connected Home Assistant Cast"""
|
|
210
|
+
_LOGGER.debug("HomeAssistantController._send_connected_message %s", data)
|
|
189
211
|
if self.hass_connected:
|
|
212
|
+
_LOGGER.debug(
|
|
213
|
+
"HomeAssistantController._send_connected_message already connected"
|
|
214
|
+
)
|
|
190
215
|
self.send_message_nocheck(data, callback_function=callback_function)
|
|
191
216
|
return
|
|
192
217
|
|
|
218
|
+
_LOGGER.debug(
|
|
219
|
+
"HomeAssistantController._send_connected_message not yet connected"
|
|
220
|
+
)
|
|
193
221
|
self._connect_hass(
|
|
194
222
|
chain_on_success(
|
|
195
223
|
partial(self.send_message_nocheck, data), callback_function
|
|
@@ -108,6 +108,8 @@ def media_to_chromecast_command( # pylint: disable=invalid-name, too-many-local
|
|
|
108
108
|
dict: Returs a dict formatted correctly to start playback on a Chromecast.
|
|
109
109
|
"""
|
|
110
110
|
|
|
111
|
+
machineIdentifier = None
|
|
112
|
+
token = None
|
|
111
113
|
if media is not None:
|
|
112
114
|
# Lets set some params for the user if they use plexapi.
|
|
113
115
|
server: PlexServer = (
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
+
import logging
|
|
6
7
|
import threading
|
|
7
8
|
from typing import Protocol
|
|
8
9
|
|
|
9
10
|
from .error import RequestFailed, RequestTimeout
|
|
10
11
|
|
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
11
14
|
CallbackType = Callable[[bool, dict | None], None]
|
|
12
15
|
"""Signature of optional callback functions supported by methods sending messages.
|
|
13
16
|
|
|
@@ -61,6 +64,7 @@ def chain_on_success(
|
|
|
61
64
|
|
|
62
65
|
def _callback(msg_sent: bool, response: dict | None) -> None:
|
|
63
66
|
if not msg_sent:
|
|
67
|
+
_LOGGER.debug("Not calling on_success %s", on_success)
|
|
64
68
|
if callback_function:
|
|
65
69
|
callback_function(msg_sent, response)
|
|
66
70
|
return
|
|
@@ -10,31 +10,31 @@ Without him this would not have been possible.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import abc
|
|
13
|
-
from dataclasses import dataclass
|
|
14
13
|
import errno
|
|
15
14
|
import json
|
|
16
15
|
import logging
|
|
17
|
-
import
|
|
16
|
+
import selectors
|
|
18
17
|
import socket
|
|
19
18
|
import ssl
|
|
20
19
|
import threading
|
|
21
20
|
import time
|
|
22
21
|
from collections import defaultdict
|
|
22
|
+
from dataclasses import dataclass
|
|
23
23
|
from struct import pack, unpack
|
|
24
24
|
|
|
25
25
|
import zeroconf
|
|
26
26
|
|
|
27
|
-
from .
|
|
27
|
+
from .const import MESSAGE_TYPE, REQUEST_ID, SESSION_ID
|
|
28
|
+
from .controllers import BaseController, CallbackType
|
|
28
29
|
from .controllers.media import MediaController
|
|
29
30
|
from .controllers.receiver import CastStatus, CastStatusListener, ReceiverController
|
|
30
|
-
from .const import MESSAGE_TYPE, REQUEST_ID, SESSION_ID
|
|
31
31
|
from .dial import get_host_from_service
|
|
32
32
|
from .error import (
|
|
33
33
|
ChromecastConnectionError,
|
|
34
34
|
ControllerNotRegistered,
|
|
35
|
-
UnsupportedNamespace,
|
|
36
35
|
NotConnected,
|
|
37
36
|
PyChromecastStopped,
|
|
37
|
+
UnsupportedNamespace,
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
# pylint: disable-next=no-name-in-module
|
|
@@ -64,13 +64,9 @@ CONNECTION_STATUS_FAILED = "FAILED"
|
|
|
64
64
|
CONNECTION_STATUS_FAILED_RESOLVE = "FAILED_RESOLVE"
|
|
65
65
|
# The socket connection was lost and needs to be retried
|
|
66
66
|
CONNECTION_STATUS_LOST = "LOST"
|
|
67
|
-
# Check for select poll method
|
|
68
|
-
SELECT_HAS_POLL = hasattr(select, "poll")
|
|
69
67
|
|
|
70
68
|
HB_PING_TIME = 10
|
|
71
69
|
HB_PONG_TIME = 10
|
|
72
|
-
POLL_TIME_BLOCKING = 5.0
|
|
73
|
-
POLL_TIME_NON_BLOCKING = 0.01
|
|
74
70
|
TIMEOUT_TIME = 30.0
|
|
75
71
|
RETRY_TIME = 5.0
|
|
76
72
|
|
|
@@ -215,6 +211,11 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
215
211
|
self.connecting = True
|
|
216
212
|
self.first_connection = True
|
|
217
213
|
self.socket: socket.socket | ssl.SSLSocket | None = None
|
|
214
|
+
self.selector = selectors.DefaultSelector()
|
|
215
|
+
self.wakeup_selector_key = self.selector.register(
|
|
216
|
+
self.socketpair[0], selectors.EVENT_READ
|
|
217
|
+
)
|
|
218
|
+
self.remote_selector_key: selectors.SelectorKey | None = None
|
|
218
219
|
|
|
219
220
|
# dict mapping namespace on Controller objects
|
|
220
221
|
self._handlers: dict[str, set[BaseController]] = defaultdict(set)
|
|
@@ -238,8 +239,10 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
238
239
|
tries = self.tries
|
|
239
240
|
|
|
240
241
|
if self.socket is not None:
|
|
242
|
+
self.selector.unregister(self.socket)
|
|
241
243
|
self.socket.close()
|
|
242
244
|
self.socket = None
|
|
245
|
+
self.remote_selector_key = None
|
|
243
246
|
|
|
244
247
|
# Make sure nobody is blocking.
|
|
245
248
|
for callback_function in self._request_callbacks.values():
|
|
@@ -288,10 +291,15 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
288
291
|
try:
|
|
289
292
|
if self.socket is not None:
|
|
290
293
|
# If we retry connecting, we need to clean up the socket again
|
|
291
|
-
self.
|
|
294
|
+
self.selector.unregister(self.socket) # type: ignore[unreachable]
|
|
295
|
+
self.socket.close()
|
|
292
296
|
self.socket = None
|
|
297
|
+
self.remote_selector_key = None
|
|
293
298
|
|
|
294
299
|
self.socket = new_socket()
|
|
300
|
+
self.remote_selector_key = self.selector.register(
|
|
301
|
+
self.socket, selectors.EVENT_READ
|
|
302
|
+
)
|
|
295
303
|
self.socket.settimeout(self.timeout)
|
|
296
304
|
self._report_connection_status(
|
|
297
305
|
ConnectionStatus(
|
|
@@ -530,7 +538,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
530
538
|
self.logger.debug("Thread started...")
|
|
531
539
|
while not self.stop.is_set():
|
|
532
540
|
try:
|
|
533
|
-
if self._run_once(
|
|
541
|
+
if self._run_once() == 1:
|
|
534
542
|
break
|
|
535
543
|
except Exception: # pylint: disable=broad-except
|
|
536
544
|
self._force_recon = True
|
|
@@ -545,7 +553,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
545
553
|
# Clean up
|
|
546
554
|
self._cleanup()
|
|
547
555
|
|
|
548
|
-
def _run_once(self
|
|
556
|
+
def _run_once(self) -> int:
|
|
549
557
|
"""Receive from the socket and handle data."""
|
|
550
558
|
# pylint: disable=too-many-branches, too-many-statements, too-many-return-statements
|
|
551
559
|
|
|
@@ -559,20 +567,8 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
559
567
|
assert self.socket is not None
|
|
560
568
|
|
|
561
569
|
# poll the socket, as well as the socketpair to allow us to be interrupted
|
|
562
|
-
rlist = [self.socket, self.socketpair[0]]
|
|
563
570
|
try:
|
|
564
|
-
|
|
565
|
-
# Map file descriptors to socket objects because select.select does not support fd > 1024
|
|
566
|
-
# https://stackoverflow.com/questions/14250751/how-to-increase-filedescriptors-range-in-python-select
|
|
567
|
-
fd_to_socket = {rlist_item.fileno(): rlist_item for rlist_item in rlist}
|
|
568
|
-
|
|
569
|
-
poll_obj = select.poll()
|
|
570
|
-
for poll_fd in rlist:
|
|
571
|
-
poll_obj.register(poll_fd, select.POLLIN)
|
|
572
|
-
poll_result = poll_obj.poll(timeout * 1000) # timeout in milliseconds
|
|
573
|
-
can_read = [fd_to_socket[fd] for fd, _status in poll_result]
|
|
574
|
-
else:
|
|
575
|
-
can_read, _, _ = select.select(rlist, [], [], timeout)
|
|
571
|
+
ready = self.selector.select()
|
|
576
572
|
except (ValueError, OSError) as exc:
|
|
577
573
|
self.logger.error(
|
|
578
574
|
"[%s(%s):%s] Error in select call: %s",
|
|
@@ -584,9 +580,10 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
584
580
|
self._force_recon = True
|
|
585
581
|
return 0
|
|
586
582
|
|
|
583
|
+
can_read = {key for key, _ in ready}
|
|
587
584
|
# read message from chromecast
|
|
588
585
|
message = None
|
|
589
|
-
if self.
|
|
586
|
+
if self.remote_selector_key in can_read and not self._force_recon:
|
|
590
587
|
try:
|
|
591
588
|
message = self._read_message()
|
|
592
589
|
except InterruptLoop as exc:
|
|
@@ -611,18 +608,17 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
611
608
|
if self.stop.is_set():
|
|
612
609
|
return 1
|
|
613
610
|
raise
|
|
614
|
-
except socket.error:
|
|
611
|
+
except socket.error as exc:
|
|
615
612
|
self._force_recon = True
|
|
616
613
|
self.logger.error(
|
|
617
|
-
"[%s(%s):%s] Error reading from socket
|
|
614
|
+
"[%s(%s):%s] Error reading from socket: %s",
|
|
618
615
|
self.fn or "",
|
|
619
616
|
self.host,
|
|
620
617
|
self.port,
|
|
618
|
+
exc,
|
|
621
619
|
)
|
|
622
|
-
else:
|
|
623
|
-
data = _dict_from_message_payload(message)
|
|
624
620
|
|
|
625
|
-
if self.
|
|
621
|
+
if self.wakeup_selector_key in can_read:
|
|
626
622
|
# Clear the socket's buffer
|
|
627
623
|
self.socketpair[0].recv(128)
|
|
628
624
|
|
|
@@ -635,6 +631,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
635
631
|
return 0
|
|
636
632
|
|
|
637
633
|
# See if any handlers will accept this message
|
|
634
|
+
data = _dict_from_message_payload(message)
|
|
638
635
|
self._route_message(message, data)
|
|
639
636
|
|
|
640
637
|
if REQUEST_ID in data and data[REQUEST_ID] in self._request_callbacks:
|
|
@@ -652,8 +649,8 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
652
649
|
# check if connection is expired
|
|
653
650
|
reset = False
|
|
654
651
|
if self._force_recon:
|
|
655
|
-
self.logger.
|
|
656
|
-
"[%s(%s):%s]
|
|
652
|
+
self.logger.debug(
|
|
653
|
+
"[%s(%s):%s] Forced reconnection",
|
|
657
654
|
self.fn or "",
|
|
658
655
|
self.host,
|
|
659
656
|
self.port,
|
|
@@ -661,7 +658,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
661
658
|
reset = True
|
|
662
659
|
|
|
663
660
|
elif self.heartbeat_controller.is_expired():
|
|
664
|
-
self.logger.
|
|
661
|
+
self.logger.info(
|
|
665
662
|
"[%s(%s):%s] Heartbeat timeout, resetting connection",
|
|
666
663
|
self.fn or "",
|
|
667
664
|
self.host,
|
|
@@ -767,6 +764,7 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
767
764
|
|
|
768
765
|
self.socketpair[0].close()
|
|
769
766
|
self.socketpair[1].close()
|
|
767
|
+
self.selector.close()
|
|
770
768
|
|
|
771
769
|
self.connecting = True
|
|
772
770
|
|
|
@@ -897,17 +895,18 @@ class SocketClient(threading.Thread, CastStatusListener):
|
|
|
897
895
|
else:
|
|
898
896
|
callback_function(True, None)
|
|
899
897
|
self.socket.sendall(be_size + msg.SerializeToString())
|
|
900
|
-
except socket.error:
|
|
898
|
+
except socket.error as exc:
|
|
901
899
|
if callback_function:
|
|
902
900
|
callback_function(False, None)
|
|
903
901
|
if not no_add_request_id:
|
|
904
902
|
self._request_callbacks.pop(request_id, None)
|
|
905
903
|
self._force_recon = True
|
|
906
|
-
self.logger.
|
|
907
|
-
"[%s(%s):%s] Error writing to socket
|
|
904
|
+
self.logger.warning(
|
|
905
|
+
"[%s(%s):%s] Error writing to socket: %s",
|
|
908
906
|
self.fn or "",
|
|
909
907
|
self.host,
|
|
910
908
|
self.port,
|
|
909
|
+
exc,
|
|
911
910
|
)
|
|
912
911
|
else:
|
|
913
912
|
if callback_function:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "PyChromecast"
|
|
7
|
-
version = "14.0.
|
|
7
|
+
version = "14.0.2"
|
|
8
8
|
description = "Python module to talk to Google Chromecast."
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
authors = [
|
|
@@ -31,6 +31,11 @@ classifiers=[
|
|
|
31
31
|
[tool.black]
|
|
32
32
|
exclude = 'pb2'
|
|
33
33
|
|
|
34
|
+
[tool.rstcheck]
|
|
35
|
+
# README.rst has embedded python examples which show the interactive interpreter output
|
|
36
|
+
# that's not valid Python, so we disable the check
|
|
37
|
+
ignore_languages = ["python"]
|
|
38
|
+
|
|
34
39
|
[tool.setuptools]
|
|
35
40
|
platforms = ["any"]
|
|
36
41
|
zip-safe = false
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|