g3pylib
Glasses3 Python Library
This library is still in alpha. It's not yet feature complete and there are no guarantees that the API will be stable.
A Python client for Glasses3. It handles all the low level details of communication with the Glasses3 websocket API and exposes a Python API which includes all the endpoints in the websocket API of the Glasses3 as well as some extra convenience methods. It also implements streaming with RTSP and service discovery with Zeroconf.
Note
This library should be platform independent in theory but the development has been done mainly in Windows environments and it's only briefly tested on other platforms.
Installation
For the moment we only support Python 3.10.
To install this package, clone it and use either
flit install
or
pip install .
To run examples or tests you need some extra dependencies which are installed by default with the flit
command. If you are using pip
the extra dependencies can be installed alongside the library with
pip install '.[test, examples, example-app]'
on windows:
pip install ".[test, examples, example-app]"
Documentation
The library documentation can be found here and there is also a developer guide for the glasses API in PDF format which can be downloaded here.
Environment
The tests and examples load the glasses hostname, which by default is the serial number, from the .env
file in the project root folder.
See example content below:
G3_HOSTNAME=tg03b-080200045321
You can also specify this variable directly in your environment.
Examples
The example folder contains a few smaller examples showcasing different use cases of the library as well as a larger controller application with a simple GUI.
Run the example app with python examples/g3pycontroller
.
Note, the examples use OpenCV, and there is a known issue with OpenCV and PyAv. If you experience freezes when displaying video frames in the samples, please check out the workarounds in the OpenCV repo. Thanks @edavalosanaya for mentioning this in https://github.com/tobiipro/g3pylib/issues/83.
Contributing
More information on how to contribute to this project can be found in CONTRIBUTING.md.
It contains developer guidelines as well as information on how to run tests and enable logging.
g3pylib is a python wrapper around the Glasses3 web API which lets you control Glasses3 devices.
API Endpoints
All endpoints in the g3pylib
module corresponding to an endpoint in the Glasses3 web API are undocumented and placed first in each module.
The following naming convention is used to translate web API endpoint names to g3pylib
API endpoint names:
- Properties: example_property -> get_example_property/set_example_property
- Actions: example_action -> example_action
- Signals: example_signal -> subscribe_to_example_signal
The web API endpoints can be browsed in the Glasses3 Example web client accessed via http://your-g3-address.
Useful information
In any code examples, g3
will be a connected instance of Glasses3
.
The default hostname of a Glasses3 device is its serial number.
1""" 2.. include:: ../../README.md 3 4g3pylib is a python wrapper around the Glasses3 web API which lets you control Glasses3 devices. 5 6## API Endpoints 7All endpoints in the `g3pylib` module corresponding to an endpoint in the Glasses3 web API are undocumented and placed first in each module. 8The following naming convention is used to translate web API endpoint names to `g3pylib 9` API endpoint names: 10 - Properties: example_property -> get_example_property/set_example_property 11 - Actions: example_action -> example_action 12 - Signals: example_signal -> subscribe_to_example_signal 13 14The web API endpoints can be browsed in the Glasses3 Example web client accessed via http://*your-g3-address*. 15 16## Useful information 17In any code examples, `g3` will be a connected instance of `Glasses3`. 18 19The default hostname of a Glasses3 device is its serial number. 20""" 21from __future__ import annotations 22 23import logging 24from contextlib import asynccontextmanager 25from types import TracebackType 26from typing import Any, AsyncIterator, Coroutine, Generator, Optional, Tuple, Type, cast 27 28 29import g3pylib.websocket 30from g3pylib._utils import APIComponent 31from g3pylib.calibrate import Calibrate 32from g3pylib.exceptions import FeatureNotAvailableError 33from g3pylib.g3typing import URI, LoggerLike 34from g3pylib.recorder import Recorder 35from g3pylib.recordings import Recordings 36from g3pylib.rudimentary import Rudimentary 37from g3pylib.settings import Settings 38from g3pylib.streams import Streams 39from g3pylib.system import System 40from g3pylib.websocket import G3WebSocketClientProtocol 41from g3pylib.zeroconf import DEFAULT_WEBSOCKET_PATH, G3Service, G3ServiceDiscovery 42 43__version__ = "0.3.1-alpha" 44 45DEFAULT_RTSP_LIVE_PATH = "/live/all" 46DEFAULT_RTSP_PORT = 8554 47DEFAULT_HTTP_PORT = 80 48 49_logger = logging.getLogger(__name__) 50 51 52class Glasses3(APIComponent): 53 """ 54 Represents a Glasses3 device. 55 56 Holds the API components and a WebSocket connection to a Glasses3 device. 57 The `stream_rtsp` context can be used for live stream functionality. 58 59 For the recommended way to create a connected instance of Glasses3, see `connect_to_glasses`. 60 """ 61 62 def __init__( 63 self, 64 connection: G3WebSocketClientProtocol, 65 rtsp_url: Optional[str], 66 http_url: Optional[str], 67 logger: Optional[LoggerLike] = None, 68 ) -> None: 69 self.logger: LoggerLike = ( 70 logging.getLogger(__name__) if logger is None else logger 71 ) 72 self._rtsp_url = rtsp_url 73 self._http_url = http_url 74 self._connection: G3WebSocketClientProtocol = connection 75 self._recorder: Optional[Recorder] = None 76 self._recordings: Optional[Recordings] = None 77 self._rudimentary: Optional[Rudimentary] = None 78 self._system: Optional[System] = None 79 self._calibrate: Optional[Calibrate] = None 80 self._settings: Optional[Settings] = None 81 82 @property 83 def calibrate(self) -> Calibrate: 84 if self._calibrate is None: 85 self._calibrate = Calibrate(self._connection, URI("/calibrate")) 86 return self._calibrate 87 88 @property 89 def recorder(self) -> Recorder: 90 if self._recorder is None: 91 self._recorder = Recorder(self._connection, URI("/recorder")) 92 return self._recorder 93 94 @property 95 def recordings(self) -> Recordings: 96 if self._recordings is None: 97 self._recordings = Recordings( 98 self._connection, URI("/recordings"), self._http_url 99 ) 100 return self._recordings 101 102 @property 103 def rudimentary(self) -> Rudimentary: 104 if self._rudimentary is None: 105 self._rudimentary = Rudimentary(self._connection, URI("/rudimentary")) 106 return self._rudimentary 107 108 @property 109 def system(self) -> System: 110 if self._system is None: 111 self._system = System(self._connection, URI("/system")) 112 return self._system 113 114 @property 115 def settings(self) -> Settings: 116 if self._settings is None: 117 self._settings = Settings(self._connection, URI("/settings")) 118 return self._settings 119 120 @property 121 def rtsp_url(self) -> Optional[str]: 122 """The RTSP URL used for live stream.""" 123 return self._rtsp_url 124 125 @asynccontextmanager 126 async def stream_rtsp( 127 self, 128 scene_camera: bool = True, 129 audio: bool = False, 130 eye_cameras: bool = False, 131 gaze: bool = False, 132 sync: bool = False, 133 imu: bool = False, 134 events: bool = False, 135 ) -> AsyncIterator[Streams]: 136 """Set up an RTSP connection in the form of a Streams object with the Stream properties indicated by the arguments. 137 138 The Stream objects can be used to demux/decode their stream. For example, `stream_rtsp()` can be used as follows: 139 ```python 140 async with connect_to_glasses(g3_hostname) as g3: 141 async with g3.stream_rtsp() as streams: 142 async with streams.scene_camera.decode() as decoded_stream: 143 for _ in range(500): 144 frame, _timestamp = await decoded_stream.get() 145 image = frame.to_ndarray(format="bgr24") 146 cv2.imshow("Video", image) 147 cv2.waitKey(1) 148 ``` 149 150 *Alpha version note:* Only the scene_camera, eye_camera and gaze attributes are implemented so far. 151 """ 152 if self.rtsp_url is None: 153 raise FeatureNotAvailableError( 154 "This Glasses3 object was initialized without a proper RTSP url." 155 ) 156 async with Streams.connect( 157 self.rtsp_url, 158 scene_camera=scene_camera, 159 audio=audio, 160 eye_cameras=eye_cameras, 161 gaze=gaze, 162 sync=sync, 163 imu=imu, 164 events=events, 165 ) as streams: 166 await streams.play() 167 yield streams 168 169 async def close(self) -> None: 170 """Close down the underlying websocket connection to the Glasses3 device.""" 171 await self._connection.close() 172 173 174class connect_to_glasses: 175 """This class contains a set of classmethods which are used to connect to a pair of glasses. 176 177 The preferred way to use this class is as an async context manager like this: 178 179 ```python 180 async with connect_to_glasses.with_hostname(glasses_serial_number) as g3: 181 # Here you can call the glasses 182 await g3.get_name() 183 # Here the connection is closed 184 ``` 185 186 It does however also support usage without a `with`-block like this: 187 188 ```python 189 g3 = await connect_to_glasses.with_hostname(glasses_serial_number) 190 # Here you can call the glasses 191 await g3.get_name() 192 # You have to remember to close the connection like this: 193 g3.close() 194 # And here g3 still exists but it is unusable 195 ``` 196 """ 197 198 def __init__( 199 self, 200 url_generator: Coroutine[Any, Any, Tuple[str, Optional[str], Optional[str]]], 201 ) -> None: 202 """You should probably not use this constructor unless you need to generate the URLs to your glasses in a very specific way. 203 The regular use cases are covered in the alternative constructors below: `with_url`, `with_zeroconf`, `with_hostname` and `with_service`. 204 205 If you want to use this constructor you need to supply a couroutine which returns a tuple that contains two URLs. 206 The first URL should point to the websocket and the second URL should point to the RTSP endpoint. 207 """ 208 self.url_generator = url_generator 209 210 @staticmethod 211 async def _urls_from_zeroconf( 212 using_ip: bool, timeout: float 213 ) -> Tuple[str, Optional[str], Optional[str]]: 214 async with G3ServiceDiscovery.listen() as service_discovery: 215 service = await service_discovery.wait_for_single_service( 216 service_discovery.events, 217 timeout, 218 ) 219 return await connect_to_glasses._urls_from_service(service, using_ip) 220 221 @staticmethod 222 async def _urls_from_service( 223 service: G3Service, using_ip: bool 224 ) -> Tuple[str, Optional[str], Optional[str]]: 225 return ( 226 service.ws_url(using_ip), 227 service.rtsp_url(using_ip), 228 service.http_url(using_ip), 229 ) 230 231 @staticmethod 232 async def _urls_from_hostname( 233 hostname: str, using_zeroconf: bool, using_ip: bool 234 ) -> Tuple[str, Optional[str], Optional[str]]: 235 if not using_zeroconf: 236 return ( 237 f"ws://{hostname}{DEFAULT_WEBSOCKET_PATH}", 238 f"rtsp://{hostname}:{DEFAULT_RTSP_PORT}{DEFAULT_RTSP_LIVE_PATH}", 239 f"http://{hostname}:{DEFAULT_HTTP_PORT}", 240 ) 241 else: 242 service = await G3ServiceDiscovery.request_service(hostname) 243 return await connect_to_glasses._urls_from_service(service, using_ip) 244 245 @classmethod 246 def with_zeroconf( 247 cls, using_ip: bool = True, timeout: float = 3000 248 ) -> connect_to_glasses: 249 """Connects by listening for available glasses on the network using zeroconf. 250 Connects to the first pair of glasses that answers so if there are multiple glasses on the 251 network the behavior is undefined. 252 253 If `using_ip` is set to True (default) we will generate the the URL used for connection with the ip. 254 If it's set to False we will use the hostname, which will depend on DNS working as it should. 255 256 `timeout` defines the time in milliseconds before `asyncio.TimeoutError` is raised. 257 """ 258 return cls(cls._urls_from_zeroconf(using_ip, timeout)) 259 260 @classmethod 261 def with_hostname( 262 cls, hostname: str, using_zeroconf: bool = False, using_ip: bool = True 263 ) -> connect_to_glasses: 264 """Connects to the pair of glasses with the given hostname. 265 266 If `using_zeroconf` is set to False (default) we will not depend on zeroconf 267 for fetching details on how to generate the URL and instead use detault values for the URL components specified 268 in the [developer guide](https://www.tobiipro.com/product-listing/tobii-pro-glasses3-api/#ResourcesSpecifications). 269 If it's set to True, all URL components are fetched with zeroconf. 270 271 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting when zeroconf is used. 272 If the hostname is used, it depends on DNS working as it should.""" 273 return cls(cls._urls_from_hostname(hostname, using_zeroconf, using_ip)) 274 275 @classmethod 276 def with_service( 277 cls, service: G3Service, using_ip: bool = True 278 ) -> connect_to_glasses: 279 """Connects to the pair of glasses referred to by the given service. 280 281 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting. 282 If the hostname is used, it depends on DNS working as it should. 283 """ 284 return cls(cls._urls_from_service(service, using_ip)) 285 286 @classmethod 287 def with_url( 288 cls, ws_url: str, rtsp_url: Optional[str] = None, http_url: Optional[str] = None 289 ): 290 """Connects to the pair of glasses at the specified URL. `ws_url` should 291 be a websocket URL (starting with `ws://`) and `rtsp_url` should be an RTSP 292 url (starting with `rtsp://` or `rtspt://`).""" 293 294 async def urls(): 295 return (ws_url, rtsp_url, http_url) 296 297 return cls(urls()) 298 299 def __await__(self) -> Generator[Any, None, Glasses3]: 300 return self.__await_impl__().__await__() 301 302 async def __await_impl__(self) -> Glasses3: 303 ws_url, rtsp_url, http_url = await self.url_generator 304 _logger.info( 305 f"Attempting connection to websocket {ws_url}, RTSP {rtsp_url} and HTTP {http_url}" 306 ) 307 connection = cast( 308 G3WebSocketClientProtocol, await g3pylib.websocket.connect(ws_url) 309 ) 310 connection.start_receiver_task() 311 self.connection = connection 312 return Glasses3(connection, rtsp_url, http_url) 313 314 async def __aenter__(self) -> Glasses3: 315 return await self 316 317 async def __aexit__( 318 self, 319 exception_type: Optional[Type[BaseException]], 320 exception_value: Optional[BaseException], 321 traceback: Optional[TracebackType], 322 ) -> None: 323 await self.connection.close()
53class Glasses3(APIComponent): 54 """ 55 Represents a Glasses3 device. 56 57 Holds the API components and a WebSocket connection to a Glasses3 device. 58 The `stream_rtsp` context can be used for live stream functionality. 59 60 For the recommended way to create a connected instance of Glasses3, see `connect_to_glasses`. 61 """ 62 63 def __init__( 64 self, 65 connection: G3WebSocketClientProtocol, 66 rtsp_url: Optional[str], 67 http_url: Optional[str], 68 logger: Optional[LoggerLike] = None, 69 ) -> None: 70 self.logger: LoggerLike = ( 71 logging.getLogger(__name__) if logger is None else logger 72 ) 73 self._rtsp_url = rtsp_url 74 self._http_url = http_url 75 self._connection: G3WebSocketClientProtocol = connection 76 self._recorder: Optional[Recorder] = None 77 self._recordings: Optional[Recordings] = None 78 self._rudimentary: Optional[Rudimentary] = None 79 self._system: Optional[System] = None 80 self._calibrate: Optional[Calibrate] = None 81 self._settings: Optional[Settings] = None 82 83 @property 84 def calibrate(self) -> Calibrate: 85 if self._calibrate is None: 86 self._calibrate = Calibrate(self._connection, URI("/calibrate")) 87 return self._calibrate 88 89 @property 90 def recorder(self) -> Recorder: 91 if self._recorder is None: 92 self._recorder = Recorder(self._connection, URI("/recorder")) 93 return self._recorder 94 95 @property 96 def recordings(self) -> Recordings: 97 if self._recordings is None: 98 self._recordings = Recordings( 99 self._connection, URI("/recordings"), self._http_url 100 ) 101 return self._recordings 102 103 @property 104 def rudimentary(self) -> Rudimentary: 105 if self._rudimentary is None: 106 self._rudimentary = Rudimentary(self._connection, URI("/rudimentary")) 107 return self._rudimentary 108 109 @property 110 def system(self) -> System: 111 if self._system is None: 112 self._system = System(self._connection, URI("/system")) 113 return self._system 114 115 @property 116 def settings(self) -> Settings: 117 if self._settings is None: 118 self._settings = Settings(self._connection, URI("/settings")) 119 return self._settings 120 121 @property 122 def rtsp_url(self) -> Optional[str]: 123 """The RTSP URL used for live stream.""" 124 return self._rtsp_url 125 126 @asynccontextmanager 127 async def stream_rtsp( 128 self, 129 scene_camera: bool = True, 130 audio: bool = False, 131 eye_cameras: bool = False, 132 gaze: bool = False, 133 sync: bool = False, 134 imu: bool = False, 135 events: bool = False, 136 ) -> AsyncIterator[Streams]: 137 """Set up an RTSP connection in the form of a Streams object with the Stream properties indicated by the arguments. 138 139 The Stream objects can be used to demux/decode their stream. For example, `stream_rtsp()` can be used as follows: 140 ```python 141 async with connect_to_glasses(g3_hostname) as g3: 142 async with g3.stream_rtsp() as streams: 143 async with streams.scene_camera.decode() as decoded_stream: 144 for _ in range(500): 145 frame, _timestamp = await decoded_stream.get() 146 image = frame.to_ndarray(format="bgr24") 147 cv2.imshow("Video", image) 148 cv2.waitKey(1) 149 ``` 150 151 *Alpha version note:* Only the scene_camera, eye_camera and gaze attributes are implemented so far. 152 """ 153 if self.rtsp_url is None: 154 raise FeatureNotAvailableError( 155 "This Glasses3 object was initialized without a proper RTSP url." 156 ) 157 async with Streams.connect( 158 self.rtsp_url, 159 scene_camera=scene_camera, 160 audio=audio, 161 eye_cameras=eye_cameras, 162 gaze=gaze, 163 sync=sync, 164 imu=imu, 165 events=events, 166 ) as streams: 167 await streams.play() 168 yield streams 169 170 async def close(self) -> None: 171 """Close down the underlying websocket connection to the Glasses3 device.""" 172 await self._connection.close()
Represents a Glasses3 device.
Holds the API components and a WebSocket connection to a Glasses3 device.
The stream_rtsp
context can be used for live stream functionality.
For the recommended way to create a connected instance of Glasses3, see connect_to_glasses
.
63 def __init__( 64 self, 65 connection: G3WebSocketClientProtocol, 66 rtsp_url: Optional[str], 67 http_url: Optional[str], 68 logger: Optional[LoggerLike] = None, 69 ) -> None: 70 self.logger: LoggerLike = ( 71 logging.getLogger(__name__) if logger is None else logger 72 ) 73 self._rtsp_url = rtsp_url 74 self._http_url = http_url 75 self._connection: G3WebSocketClientProtocol = connection 76 self._recorder: Optional[Recorder] = None 77 self._recordings: Optional[Recordings] = None 78 self._rudimentary: Optional[Rudimentary] = None 79 self._system: Optional[System] = None 80 self._calibrate: Optional[Calibrate] = None 81 self._settings: Optional[Settings] = None
126 @asynccontextmanager 127 async def stream_rtsp( 128 self, 129 scene_camera: bool = True, 130 audio: bool = False, 131 eye_cameras: bool = False, 132 gaze: bool = False, 133 sync: bool = False, 134 imu: bool = False, 135 events: bool = False, 136 ) -> AsyncIterator[Streams]: 137 """Set up an RTSP connection in the form of a Streams object with the Stream properties indicated by the arguments. 138 139 The Stream objects can be used to demux/decode their stream. For example, `stream_rtsp()` can be used as follows: 140 ```python 141 async with connect_to_glasses(g3_hostname) as g3: 142 async with g3.stream_rtsp() as streams: 143 async with streams.scene_camera.decode() as decoded_stream: 144 for _ in range(500): 145 frame, _timestamp = await decoded_stream.get() 146 image = frame.to_ndarray(format="bgr24") 147 cv2.imshow("Video", image) 148 cv2.waitKey(1) 149 ``` 150 151 *Alpha version note:* Only the scene_camera, eye_camera and gaze attributes are implemented so far. 152 """ 153 if self.rtsp_url is None: 154 raise FeatureNotAvailableError( 155 "This Glasses3 object was initialized without a proper RTSP url." 156 ) 157 async with Streams.connect( 158 self.rtsp_url, 159 scene_camera=scene_camera, 160 audio=audio, 161 eye_cameras=eye_cameras, 162 gaze=gaze, 163 sync=sync, 164 imu=imu, 165 events=events, 166 ) as streams: 167 await streams.play() 168 yield streams
Set up an RTSP connection in the form of a Streams object with the Stream properties indicated by the arguments.
The Stream objects can be used to demux/decode their stream. For example, stream_rtsp()
can be used as follows:
async with connect_to_glasses(g3_hostname) as g3:
async with g3.stream_rtsp() as streams:
async with streams.scene_camera.decode() as decoded_stream:
for _ in range(500):
frame, _timestamp = await decoded_stream.get()
image = frame.to_ndarray(format="bgr24")
cv2.imshow("Video", image)
cv2.waitKey(1)
Alpha version note: Only the scene_camera, eye_camera and gaze attributes are implemented so far.
170 async def close(self) -> None: 171 """Close down the underlying websocket connection to the Glasses3 device.""" 172 await self._connection.close()
Close down the underlying websocket connection to the Glasses3 device.
Inherited Members
175class connect_to_glasses: 176 """This class contains a set of classmethods which are used to connect to a pair of glasses. 177 178 The preferred way to use this class is as an async context manager like this: 179 180 ```python 181 async with connect_to_glasses.with_hostname(glasses_serial_number) as g3: 182 # Here you can call the glasses 183 await g3.get_name() 184 # Here the connection is closed 185 ``` 186 187 It does however also support usage without a `with`-block like this: 188 189 ```python 190 g3 = await connect_to_glasses.with_hostname(glasses_serial_number) 191 # Here you can call the glasses 192 await g3.get_name() 193 # You have to remember to close the connection like this: 194 g3.close() 195 # And here g3 still exists but it is unusable 196 ``` 197 """ 198 199 def __init__( 200 self, 201 url_generator: Coroutine[Any, Any, Tuple[str, Optional[str], Optional[str]]], 202 ) -> None: 203 """You should probably not use this constructor unless you need to generate the URLs to your glasses in a very specific way. 204 The regular use cases are covered in the alternative constructors below: `with_url`, `with_zeroconf`, `with_hostname` and `with_service`. 205 206 If you want to use this constructor you need to supply a couroutine which returns a tuple that contains two URLs. 207 The first URL should point to the websocket and the second URL should point to the RTSP endpoint. 208 """ 209 self.url_generator = url_generator 210 211 @staticmethod 212 async def _urls_from_zeroconf( 213 using_ip: bool, timeout: float 214 ) -> Tuple[str, Optional[str], Optional[str]]: 215 async with G3ServiceDiscovery.listen() as service_discovery: 216 service = await service_discovery.wait_for_single_service( 217 service_discovery.events, 218 timeout, 219 ) 220 return await connect_to_glasses._urls_from_service(service, using_ip) 221 222 @staticmethod 223 async def _urls_from_service( 224 service: G3Service, using_ip: bool 225 ) -> Tuple[str, Optional[str], Optional[str]]: 226 return ( 227 service.ws_url(using_ip), 228 service.rtsp_url(using_ip), 229 service.http_url(using_ip), 230 ) 231 232 @staticmethod 233 async def _urls_from_hostname( 234 hostname: str, using_zeroconf: bool, using_ip: bool 235 ) -> Tuple[str, Optional[str], Optional[str]]: 236 if not using_zeroconf: 237 return ( 238 f"ws://{hostname}{DEFAULT_WEBSOCKET_PATH}", 239 f"rtsp://{hostname}:{DEFAULT_RTSP_PORT}{DEFAULT_RTSP_LIVE_PATH}", 240 f"http://{hostname}:{DEFAULT_HTTP_PORT}", 241 ) 242 else: 243 service = await G3ServiceDiscovery.request_service(hostname) 244 return await connect_to_glasses._urls_from_service(service, using_ip) 245 246 @classmethod 247 def with_zeroconf( 248 cls, using_ip: bool = True, timeout: float = 3000 249 ) -> connect_to_glasses: 250 """Connects by listening for available glasses on the network using zeroconf. 251 Connects to the first pair of glasses that answers so if there are multiple glasses on the 252 network the behavior is undefined. 253 254 If `using_ip` is set to True (default) we will generate the the URL used for connection with the ip. 255 If it's set to False we will use the hostname, which will depend on DNS working as it should. 256 257 `timeout` defines the time in milliseconds before `asyncio.TimeoutError` is raised. 258 """ 259 return cls(cls._urls_from_zeroconf(using_ip, timeout)) 260 261 @classmethod 262 def with_hostname( 263 cls, hostname: str, using_zeroconf: bool = False, using_ip: bool = True 264 ) -> connect_to_glasses: 265 """Connects to the pair of glasses with the given hostname. 266 267 If `using_zeroconf` is set to False (default) we will not depend on zeroconf 268 for fetching details on how to generate the URL and instead use detault values for the URL components specified 269 in the [developer guide](https://www.tobiipro.com/product-listing/tobii-pro-glasses3-api/#ResourcesSpecifications). 270 If it's set to True, all URL components are fetched with zeroconf. 271 272 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting when zeroconf is used. 273 If the hostname is used, it depends on DNS working as it should.""" 274 return cls(cls._urls_from_hostname(hostname, using_zeroconf, using_ip)) 275 276 @classmethod 277 def with_service( 278 cls, service: G3Service, using_ip: bool = True 279 ) -> connect_to_glasses: 280 """Connects to the pair of glasses referred to by the given service. 281 282 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting. 283 If the hostname is used, it depends on DNS working as it should. 284 """ 285 return cls(cls._urls_from_service(service, using_ip)) 286 287 @classmethod 288 def with_url( 289 cls, ws_url: str, rtsp_url: Optional[str] = None, http_url: Optional[str] = None 290 ): 291 """Connects to the pair of glasses at the specified URL. `ws_url` should 292 be a websocket URL (starting with `ws://`) and `rtsp_url` should be an RTSP 293 url (starting with `rtsp://` or `rtspt://`).""" 294 295 async def urls(): 296 return (ws_url, rtsp_url, http_url) 297 298 return cls(urls()) 299 300 def __await__(self) -> Generator[Any, None, Glasses3]: 301 return self.__await_impl__().__await__() 302 303 async def __await_impl__(self) -> Glasses3: 304 ws_url, rtsp_url, http_url = await self.url_generator 305 _logger.info( 306 f"Attempting connection to websocket {ws_url}, RTSP {rtsp_url} and HTTP {http_url}" 307 ) 308 connection = cast( 309 G3WebSocketClientProtocol, await g3pylib.websocket.connect(ws_url) 310 ) 311 connection.start_receiver_task() 312 self.connection = connection 313 return Glasses3(connection, rtsp_url, http_url) 314 315 async def __aenter__(self) -> Glasses3: 316 return await self 317 318 async def __aexit__( 319 self, 320 exception_type: Optional[Type[BaseException]], 321 exception_value: Optional[BaseException], 322 traceback: Optional[TracebackType], 323 ) -> None: 324 await self.connection.close()
This class contains a set of classmethods which are used to connect to a pair of glasses.
The preferred way to use this class is as an async context manager like this:
async with connect_to_glasses.with_hostname(glasses_serial_number) as g3:
# Here you can call the glasses
await g3.get_name()
# Here the connection is closed
It does however also support usage without a with
-block like this:
g3 = await connect_to_glasses.with_hostname(glasses_serial_number)
# Here you can call the glasses
await g3.get_name()
# You have to remember to close the connection like this:
g3.close()
# And here g3 still exists but it is unusable
199 def __init__( 200 self, 201 url_generator: Coroutine[Any, Any, Tuple[str, Optional[str], Optional[str]]], 202 ) -> None: 203 """You should probably not use this constructor unless you need to generate the URLs to your glasses in a very specific way. 204 The regular use cases are covered in the alternative constructors below: `with_url`, `with_zeroconf`, `with_hostname` and `with_service`. 205 206 If you want to use this constructor you need to supply a couroutine which returns a tuple that contains two URLs. 207 The first URL should point to the websocket and the second URL should point to the RTSP endpoint. 208 """ 209 self.url_generator = url_generator
You should probably not use this constructor unless you need to generate the URLs to your glasses in a very specific way.
The regular use cases are covered in the alternative constructors below: with_url
, with_zeroconf
, with_hostname
and with_service
.
If you want to use this constructor you need to supply a couroutine which returns a tuple that contains two URLs. The first URL should point to the websocket and the second URL should point to the RTSP endpoint.
246 @classmethod 247 def with_zeroconf( 248 cls, using_ip: bool = True, timeout: float = 3000 249 ) -> connect_to_glasses: 250 """Connects by listening for available glasses on the network using zeroconf. 251 Connects to the first pair of glasses that answers so if there are multiple glasses on the 252 network the behavior is undefined. 253 254 If `using_ip` is set to True (default) we will generate the the URL used for connection with the ip. 255 If it's set to False we will use the hostname, which will depend on DNS working as it should. 256 257 `timeout` defines the time in milliseconds before `asyncio.TimeoutError` is raised. 258 """ 259 return cls(cls._urls_from_zeroconf(using_ip, timeout))
Connects by listening for available glasses on the network using zeroconf. Connects to the first pair of glasses that answers so if there are multiple glasses on the network the behavior is undefined.
If using_ip
is set to True (default) we will generate the the URL used for connection with the ip.
If it's set to False we will use the hostname, which will depend on DNS working as it should.
timeout
defines the time in milliseconds before asyncio.TimeoutError
is raised.
261 @classmethod 262 def with_hostname( 263 cls, hostname: str, using_zeroconf: bool = False, using_ip: bool = True 264 ) -> connect_to_glasses: 265 """Connects to the pair of glasses with the given hostname. 266 267 If `using_zeroconf` is set to False (default) we will not depend on zeroconf 268 for fetching details on how to generate the URL and instead use detault values for the URL components specified 269 in the [developer guide](https://www.tobiipro.com/product-listing/tobii-pro-glasses3-api/#ResourcesSpecifications). 270 If it's set to True, all URL components are fetched with zeroconf. 271 272 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting when zeroconf is used. 273 If the hostname is used, it depends on DNS working as it should.""" 274 return cls(cls._urls_from_hostname(hostname, using_zeroconf, using_ip))
Connects to the pair of glasses with the given hostname.
If using_zeroconf
is set to False (default) we will not depend on zeroconf
for fetching details on how to generate the URL and instead use detault values for the URL components specified
in the developer guide.
If it's set to True, all URL components are fetched with zeroconf.
using_ip
specifies if the ip or the hostname should be used in the URL used for connecting when zeroconf is used.
If the hostname is used, it depends on DNS working as it should.
276 @classmethod 277 def with_service( 278 cls, service: G3Service, using_ip: bool = True 279 ) -> connect_to_glasses: 280 """Connects to the pair of glasses referred to by the given service. 281 282 `using_ip` specifies if the ip or the hostname should be used in the URL used for connecting. 283 If the hostname is used, it depends on DNS working as it should. 284 """ 285 return cls(cls._urls_from_service(service, using_ip))
Connects to the pair of glasses referred to by the given service.
using_ip
specifies if the ip or the hostname should be used in the URL used for connecting.
If the hostname is used, it depends on DNS working as it should.
287 @classmethod 288 def with_url( 289 cls, ws_url: str, rtsp_url: Optional[str] = None, http_url: Optional[str] = None 290 ): 291 """Connects to the pair of glasses at the specified URL. `ws_url` should 292 be a websocket URL (starting with `ws://`) and `rtsp_url` should be an RTSP 293 url (starting with `rtsp://` or `rtspt://`).""" 294 295 async def urls(): 296 return (ws_url, rtsp_url, http_url) 297 298 return cls(urls())
Connects to the pair of glasses at the specified URL. ws_url
should
be a websocket URL (starting with ws://
) and rtsp_url
should be an RTSP
url (starting with rtsp://
or rtspt://
).