phue2 0.0.1.dev279__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.
- phue2/__init__.py +37 -0
- phue2/__main__.py +408 -0
- phue2/_internal/__init__.py +1 -0
- phue2/_internal/console.py +175 -0
- phue2/bridge.py +1246 -0
- phue2/exceptions.py +22 -0
- phue2/group.py +118 -0
- phue2/light.py +301 -0
- phue2/scene.py +58 -0
- phue2/sensor.py +307 -0
- phue2-0.0.1.dev279.dist-info/METADATA +188 -0
- phue2-0.0.1.dev279.dist-info/RECORD +15 -0
- phue2-0.0.1.dev279.dist-info/WHEEL +4 -0
- phue2-0.0.1.dev279.dist-info/entry_points.txt +2 -0
- phue2-0.0.1.dev279.dist-info/licenses/LICENSE +21 -0
phue2/sensor.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""Sensor classes for interacting with Philips Hue sensors."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections import UserDict
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Any, TypeAlias
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from phue2 import Bridge
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("phue_modern")
|
|
12
|
+
|
|
13
|
+
SensorData: TypeAlias = dict[str, Any]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _SensorDataWrapper(UserDict[str, Any]):
|
|
17
|
+
"""
|
|
18
|
+
Base class for Sensor State/Config wrappers using UserDict.
|
|
19
|
+
Updates the bridge when an item is set.
|
|
20
|
+
Holds a cached view of the state/config.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# UserDict stores data in the 'data' attribute (which is a dict)
|
|
24
|
+
data: SensorData
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, bridge: "Bridge", sensor_id: int, initial_data: SensorData | None = None
|
|
28
|
+
):
|
|
29
|
+
super().__init__(initial_data or {})
|
|
30
|
+
self._bridge = bridge
|
|
31
|
+
self._sensor_id = sensor_id
|
|
32
|
+
# Store the method responsible for updating the bridge (state or config)
|
|
33
|
+
self._update_bridge_method: Callable[[int, SensorData], Any] = (
|
|
34
|
+
self._get_bridge_update_method()
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def _get_bridge_update_method(self) -> Callable[[int, SensorData], Any]:
|
|
38
|
+
# This method should be implemented by subclasses
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
|
|
41
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
42
|
+
"""Sets an item locally and updates the bridge."""
|
|
43
|
+
# Optional: Check if the value is actually changing to avoid unnecessary API calls
|
|
44
|
+
if key in self.data and self.data[key] == value:
|
|
45
|
+
logger.debug(
|
|
46
|
+
f"Skipping bridge update for sensor {self._sensor_id}: key '{key}' value unchanged."
|
|
47
|
+
)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
# Update local cache *before* calling the bridge
|
|
51
|
+
# If the bridge call fails, the local cache reflects the intended state.
|
|
52
|
+
# Alternatively, update after bridge call succeeds if you want cache
|
|
53
|
+
# to always reflect confirmed bridge state. Let's update before for responsiveness.
|
|
54
|
+
super().__setitem__(key, value)
|
|
55
|
+
|
|
56
|
+
# Update the bridge with *only the changed key/value pair*
|
|
57
|
+
update_payload = {key: value}
|
|
58
|
+
try:
|
|
59
|
+
logger.debug(
|
|
60
|
+
f"Updating bridge for sensor {self._sensor_id}: {update_payload}"
|
|
61
|
+
)
|
|
62
|
+
self._update_bridge_method(self._sensor_id, update_payload)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
# Handle potential API errors (e.g., network issues, invalid key/value)
|
|
65
|
+
logger.error(
|
|
66
|
+
f"Failed to update bridge for sensor {self._sensor_id} with {update_payload}: {e}"
|
|
67
|
+
)
|
|
68
|
+
# Optional: Revert local change if bridge update fails?
|
|
69
|
+
# Depends on desired behavior. For now, keep local change.
|
|
70
|
+
# Consider raising the exception or providing feedback.
|
|
71
|
+
# For simplicity here, we just log the error.
|
|
72
|
+
|
|
73
|
+
def update(self, *args: Any, **kwargs: Any) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Updates the dictionary with multiple items.
|
|
76
|
+
Calls the bridge update method for each changed item individually.
|
|
77
|
+
Note: This may result in multiple API calls. If the bridge API supports
|
|
78
|
+
updating multiple keys in one call, this could be optimized.
|
|
79
|
+
"""
|
|
80
|
+
# UserDict.update handles merging args and kwargs into self.data
|
|
81
|
+
# We need to intercept this to call the bridge for each change.
|
|
82
|
+
temp_dict = dict(*args, **kwargs)
|
|
83
|
+
for key, value in temp_dict.items():
|
|
84
|
+
# This will call our overridden __setitem__, which handles bridge update
|
|
85
|
+
self[key] = value
|
|
86
|
+
|
|
87
|
+
def sync_from_bridge_data(self, data: SensorData) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Updates the local cache directly from data fetched from the bridge,
|
|
90
|
+
bypassing the bridge update mechanism (__setitem__).
|
|
91
|
+
"""
|
|
92
|
+
# Use super().update to modify self.data directly without triggering __setitem__
|
|
93
|
+
self.data.clear()
|
|
94
|
+
self.data.update(data)
|
|
95
|
+
# Alternatively: super().update(data) if you want to merge instead of replace.
|
|
96
|
+
# Clearing first ensures the cache exactly matches the fetched data.
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class SensorState(_SensorDataWrapper):
|
|
100
|
+
"""
|
|
101
|
+
State of a Hue sensor. Updates bridge state on item set.
|
|
102
|
+
Provides a dictionary-like interface to a cached view of the sensor state.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def _get_bridge_update_method(self):
|
|
106
|
+
return self._bridge.set_sensor_state
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class SensorConfig(_SensorDataWrapper):
|
|
110
|
+
"""
|
|
111
|
+
Configuration of a Hue sensor. Updates bridge config on item set.
|
|
112
|
+
Provides a dictionary-like interface to a cached view of the sensor config.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def _get_bridge_update_method(self):
|
|
116
|
+
return self._bridge.set_sensor_config
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Sensor:
|
|
120
|
+
"""
|
|
121
|
+
Hue Sensor object.
|
|
122
|
+
|
|
123
|
+
Provides access to sensor properties, state, and configuration.
|
|
124
|
+
State and config are cached locally. Modifying `sensor.state['key'] = value`
|
|
125
|
+
or `sensor.config['key'] = value` will update the bridge.
|
|
126
|
+
Call `sensor.refresh()` to update the local cache from the bridge.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
# Type hint for the bridge reference
|
|
130
|
+
bridge: "Bridge"
|
|
131
|
+
sensor_id: int
|
|
132
|
+
|
|
133
|
+
# Private attributes for caching
|
|
134
|
+
# Use Optional[T] and initialize to None to indicate they haven't been fetched yet.
|
|
135
|
+
_raw_data: SensorData | None = None
|
|
136
|
+
_state: SensorState
|
|
137
|
+
_config: SensorConfig
|
|
138
|
+
# Cache basic attributes to avoid repeated parsing/dict access
|
|
139
|
+
_name: str | None = None
|
|
140
|
+
_modelid: str | None = None
|
|
141
|
+
_swversion: str | None = None
|
|
142
|
+
_type: str | None = None
|
|
143
|
+
_uniqueid: str | None = None
|
|
144
|
+
_manufacturername: str | None = None
|
|
145
|
+
_recycle: bool | None = None
|
|
146
|
+
|
|
147
|
+
def __init__(self, bridge: "Bridge", sensor_id: int):
|
|
148
|
+
self.bridge = bridge
|
|
149
|
+
self.sensor_id = sensor_id
|
|
150
|
+
|
|
151
|
+
# Initialize state and config wrappers without initial data.
|
|
152
|
+
# Data will be populated by refresh() or on first access.
|
|
153
|
+
self._state = SensorState(bridge, sensor_id)
|
|
154
|
+
self._config = SensorConfig(bridge, sensor_id) # Use SensorConfig
|
|
155
|
+
|
|
156
|
+
def __repr__(self) -> str:
|
|
157
|
+
# Fetch name for repr if not already cached. Handle potential errors.
|
|
158
|
+
try:
|
|
159
|
+
name = self.name # Use the property to ensure data is loaded
|
|
160
|
+
except Exception:
|
|
161
|
+
name = f"ID {self.sensor_id}" # Fallback if name fetch fails
|
|
162
|
+
return f'<{self.__class__.__module__}.{self.__class__.__name__} object "{name}" at {hex(id(self))}>'
|
|
163
|
+
|
|
164
|
+
def refresh(self) -> None:
|
|
165
|
+
"""Fetches the latest data from the bridge and updates the local cache."""
|
|
166
|
+
logger.debug(f"Refreshing data for sensor {self.sensor_id}")
|
|
167
|
+
try:
|
|
168
|
+
# Assume bridge.get_sensor(id) returns the full sensor dictionary
|
|
169
|
+
self._raw_data = self.bridge.get_sensor(self.sensor_id)
|
|
170
|
+
if not isinstance(self._raw_data, dict):
|
|
171
|
+
raise TypeError(
|
|
172
|
+
f"Expected dict from bridge.get_sensor, got {type(self._raw_data)}"
|
|
173
|
+
)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Failed to refresh data for sensor {self.sensor_id}: {e}")
|
|
176
|
+
# Keep stale data or clear it? Let's keep stale data and log error.
|
|
177
|
+
# self._raw_data = None # Option: clear cache on failure
|
|
178
|
+
raise # Re-raise the exception so the caller knows refresh failed
|
|
179
|
+
|
|
180
|
+
# Update cached attributes directly from raw data
|
|
181
|
+
self._name = self._raw_data.get("name")
|
|
182
|
+
self._modelid = self._raw_data.get("modelid")
|
|
183
|
+
self._swversion = self._raw_data.get("swversion")
|
|
184
|
+
self._type = self._raw_data.get("type")
|
|
185
|
+
self._uniqueid = self._raw_data.get("uniqueid")
|
|
186
|
+
self._manufacturername = self._raw_data.get("manufacturername")
|
|
187
|
+
self._recycle = self._raw_data.get("recycle")
|
|
188
|
+
|
|
189
|
+
# Update state and config caches using the sync method (avoids bridge writes)
|
|
190
|
+
self._state.sync_from_bridge_data(self._raw_data.get("state", {}))
|
|
191
|
+
self._config.sync_from_bridge_data(self._raw_data.get("config", {}))
|
|
192
|
+
logger.debug(f"Sensor {self.sensor_id} data refreshed successfully.")
|
|
193
|
+
|
|
194
|
+
def _ensure_data(self) -> None:
|
|
195
|
+
"""Ensures sensor data has been fetched at least once, calling refresh() if needed."""
|
|
196
|
+
if self._raw_data is None:
|
|
197
|
+
logger.debug(f"Initial data fetch for sensor {self.sensor_id}")
|
|
198
|
+
self.refresh() # This will fetch and populate everything
|
|
199
|
+
|
|
200
|
+
# --- Properties ---
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def name(self) -> str:
|
|
204
|
+
"""Get or set the name of the sensor [string]. Fetches data on first access."""
|
|
205
|
+
self._ensure_data()
|
|
206
|
+
# Provide a default/fallback if name is somehow missing after refresh
|
|
207
|
+
return self._name or f"Sensor {self.sensor_id}"
|
|
208
|
+
|
|
209
|
+
@name.setter
|
|
210
|
+
def name(self, value: str) -> None:
|
|
211
|
+
if not isinstance(value, str): # type: ignore[unnecessary-isinstance]
|
|
212
|
+
raise TypeError("Sensor name must be a string")
|
|
213
|
+
|
|
214
|
+
# Get the current name (ensures data is loaded if it wasn't already)
|
|
215
|
+
old_name = self.name
|
|
216
|
+
if old_name == value:
|
|
217
|
+
return # No change needed
|
|
218
|
+
|
|
219
|
+
logger.debug(
|
|
220
|
+
f"Attempting to rename sensor {self.sensor_id} from '{old_name}' to '{value}'"
|
|
221
|
+
)
|
|
222
|
+
try:
|
|
223
|
+
# Use bridge.set_sensor for top-level attributes like name
|
|
224
|
+
self.bridge.set_sensor(self.sensor_id, {"name": value})
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.error(
|
|
227
|
+
f"Failed to set name for sensor {self.sensor_id} via bridge: {e}"
|
|
228
|
+
)
|
|
229
|
+
raise # Re-raise exception
|
|
230
|
+
|
|
231
|
+
# Update local cache only after successful bridge call
|
|
232
|
+
self._name = value
|
|
233
|
+
if self._raw_data: # Update raw data cache if it exists
|
|
234
|
+
self._raw_data["name"] = value
|
|
235
|
+
|
|
236
|
+
# Update bridge's name lookup cache (if it exists and is used)
|
|
237
|
+
if hasattr(self.bridge, "sensors_by_name"):
|
|
238
|
+
# Be careful modifying another object's state directly
|
|
239
|
+
# Ensure this is the intended interaction pattern with the Bridge class
|
|
240
|
+
if old_name in self.bridge.sensors_by_name:
|
|
241
|
+
del self.bridge.sensors_by_name[old_name]
|
|
242
|
+
self.bridge.sensors_by_name[value] = self
|
|
243
|
+
logger.info(f"Sensor {self.sensor_id} renamed to '{value}'")
|
|
244
|
+
|
|
245
|
+
# Read-only properties using the cache
|
|
246
|
+
@property
|
|
247
|
+
def modelid(self) -> str | None:
|
|
248
|
+
"""Get hardware model ID [string, read-only]. Fetches data on first access."""
|
|
249
|
+
self._ensure_data()
|
|
250
|
+
return self._modelid
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def swversion(self) -> str | None:
|
|
254
|
+
"""Get firmware version [string, read-only]. Fetches data on first access."""
|
|
255
|
+
self._ensure_data()
|
|
256
|
+
return self._swversion
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def type(self) -> str | None:
|
|
260
|
+
"""Get sensor type [string, read-only]. Fetches data on first access."""
|
|
261
|
+
self._ensure_data()
|
|
262
|
+
return self._type
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def uniqueid(self) -> str | None:
|
|
266
|
+
"""Get unique device ID [string, read-only]. Fetches data on first access."""
|
|
267
|
+
self._ensure_data()
|
|
268
|
+
return self._uniqueid
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def manufacturername(self) -> str | None:
|
|
272
|
+
"""Get manufacturer name [string, read-only]. Fetches data on first access."""
|
|
273
|
+
self._ensure_data()
|
|
274
|
+
return self._manufacturername
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def recycle(self) -> bool | None:
|
|
278
|
+
"""Check if resource should be auto-removed [bool, read-only]. Fetches data on first access."""
|
|
279
|
+
self._ensure_data()
|
|
280
|
+
return self._recycle
|
|
281
|
+
|
|
282
|
+
# State and Config properties return the cached wrapper objects
|
|
283
|
+
@property
|
|
284
|
+
def state(self) -> SensorState:
|
|
285
|
+
"""
|
|
286
|
+
Get the cached state of the sensor [SensorState].
|
|
287
|
+
Modifying items (e.g., `sensor.state['on'] = True`) updates the bridge.
|
|
288
|
+
Call `sensor.refresh()` to sync cache from the bridge.
|
|
289
|
+
Fetches data on first access.
|
|
290
|
+
"""
|
|
291
|
+
self._ensure_data()
|
|
292
|
+
return self._state
|
|
293
|
+
|
|
294
|
+
# Removed state.setter - modifications happen via the returned object
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def config(self) -> SensorConfig:
|
|
298
|
+
"""
|
|
299
|
+
Get the cached config of the sensor [SensorConfig].
|
|
300
|
+
Modifying items (e.g., `sensor.config['on'] = True`) updates the bridge.
|
|
301
|
+
Call `sensor.refresh()` to sync cache from the bridge.
|
|
302
|
+
Fetches data on first access.
|
|
303
|
+
"""
|
|
304
|
+
self._ensure_data()
|
|
305
|
+
return self._config
|
|
306
|
+
|
|
307
|
+
# Removed config.setter - modifications happen via the returned object
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phue2
|
|
3
|
+
Version: 0.0.1.dev279
|
|
4
|
+
Summary: A Philips Hue Python library
|
|
5
|
+
Author: Nathanaël Lécaudé, Nathan Nowack
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# phue2
|
|
13
|
+
|
|
14
|
+
modern Python library to control the Philips Hue lighting system
|
|
15
|
+
|
|
16
|
+
This is a fork of the original [phue library](https://github.com/studioimaginaire/phue) by Nathanaël Lécaudé, modernized with type annotations, improved error handling, and a fully-featured CLI. The library remains MIT licensed.
|
|
17
|
+
|
|
18
|
+
> [!IMPORTANT]
|
|
19
|
+
|
|
20
|
+
> I appreciate the original authors work and if there's any interest in merging these (substantial) changes back into the original library, I'm happy to do so.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Using uv
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv add phue2
|
|
28
|
+
|
|
29
|
+
uv pip install phue2
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Using pip
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install phue2
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Repository Structure
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
.
|
|
42
|
+
├── LICENSE
|
|
43
|
+
├── README.md
|
|
44
|
+
├── examples/
|
|
45
|
+
├── src/
|
|
46
|
+
│ └── phue/
|
|
47
|
+
│ ├── __init__.py
|
|
48
|
+
│ ├── __main__.py # CLI interface
|
|
49
|
+
│ ├── bridge.py # Bridge connection handling
|
|
50
|
+
│ ├── exceptions.py # Custom exceptions
|
|
51
|
+
│ ├── group.py # Group controls
|
|
52
|
+
│ ├── light.py # Light controls
|
|
53
|
+
│ ├── scene.py # Scene handling
|
|
54
|
+
│ └── sensor.py # Sensor controls
|
|
55
|
+
└── tests/ # Tests
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Python 3.10 or higher (not compatible with Python 2.x)
|
|
61
|
+
- httpx (for network requests)
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
- Fully typed with Python type annotations
|
|
66
|
+
- Robust error handling with custom exceptions
|
|
67
|
+
- Comprehensive test suite
|
|
68
|
+
- Colorful, user-friendly command-line interface
|
|
69
|
+
- Support for Lights, Groups, Scenes, and Sensors
|
|
70
|
+
- Auto-discovery of Hue bridges on the network
|
|
71
|
+
- Simple and intuitive API for controlling Hue devices
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Command Line Usage
|
|
76
|
+
|
|
77
|
+
The library includes a command-line interface for controlling your Hue lights:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# List all lights
|
|
81
|
+
phue ls
|
|
82
|
+
|
|
83
|
+
# Get details about a specific light
|
|
84
|
+
phue get light "Living Room"
|
|
85
|
+
|
|
86
|
+
# Turn on a light and set brightness
|
|
87
|
+
phue set light "Kitchen" --on --bri 200
|
|
88
|
+
|
|
89
|
+
# List all groups
|
|
90
|
+
phue ls groups
|
|
91
|
+
|
|
92
|
+
# Turn off lights in a group
|
|
93
|
+
phue set group "Downstairs" --off
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Basic Usage
|
|
97
|
+
|
|
98
|
+
Using the set_light and get_light methods you can control pretty much all the parameters:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from phue2 import Bridge
|
|
102
|
+
|
|
103
|
+
# Connect to the bridge
|
|
104
|
+
b = Bridge('192.168.1.100')
|
|
105
|
+
|
|
106
|
+
# If the app is not registered and the button is not pressed, press the button and call connect()
|
|
107
|
+
# This only needs to be run a single time
|
|
108
|
+
b.connect()
|
|
109
|
+
|
|
110
|
+
# Get the bridge state (This returns the full dictionary that you can explore)
|
|
111
|
+
b.get_api()
|
|
112
|
+
|
|
113
|
+
# Prints if light 1 is on or not
|
|
114
|
+
b.get_light(1, 'on')
|
|
115
|
+
|
|
116
|
+
# Set brightness of lamp 1 to max
|
|
117
|
+
b.set_light(1, 'bri', 254)
|
|
118
|
+
|
|
119
|
+
# Turn lamp 2 on
|
|
120
|
+
b.set_light(2, 'on', True)
|
|
121
|
+
|
|
122
|
+
# You can also control multiple lamps by sending a list as lamp_id
|
|
123
|
+
b.set_light([1, 2], 'on', True)
|
|
124
|
+
|
|
125
|
+
# You can also use light names instead of the id
|
|
126
|
+
b.get_light('Kitchen')
|
|
127
|
+
b.set_light('Kitchen', 'bri', 254)
|
|
128
|
+
|
|
129
|
+
# Also works with lists
|
|
130
|
+
b.set_light(['Bathroom', 'Garage'], 'on', False)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Light Objects
|
|
134
|
+
|
|
135
|
+
If you want to work in a more object-oriented way, you can get Light objects:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# Get a flat list of light objects
|
|
139
|
+
lights = b.lights
|
|
140
|
+
|
|
141
|
+
# Print light names
|
|
142
|
+
for light in lights:
|
|
143
|
+
print(light.name)
|
|
144
|
+
|
|
145
|
+
# Set brightness of each light to 127
|
|
146
|
+
for light in lights:
|
|
147
|
+
light.brightness = 127
|
|
148
|
+
|
|
149
|
+
# Get a dictionary with the light name as the key
|
|
150
|
+
light_names = b.get_light_objects('name')
|
|
151
|
+
|
|
152
|
+
# Set lights using name as key
|
|
153
|
+
for light_name in ['Kitchen', 'Bedroom', 'Garage']:
|
|
154
|
+
light = light_names.get(light_name)
|
|
155
|
+
if light:
|
|
156
|
+
light.on = True
|
|
157
|
+
light.hue = 15000
|
|
158
|
+
light.saturation = 120
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Error Handling
|
|
162
|
+
|
|
163
|
+
The library provides custom exceptions for better error handling:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from phue2 import Bridge, PhueRegistrationException, PhueRequestTimeout
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
b = Bridge('192.168.1.100')
|
|
170
|
+
b.connect()
|
|
171
|
+
except PhueRegistrationException:
|
|
172
|
+
print("Press the button on the bridge and try again")
|
|
173
|
+
except PhueRequestTimeout:
|
|
174
|
+
print("Could not connect to the bridge - check your network")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Acknowledgments
|
|
178
|
+
|
|
179
|
+
This project is a fork of the original [phue](https://github.com/studioimaginaire/phue) library created by Nathanaël Lécaudé.
|
|
180
|
+
|
|
181
|
+
The modernized version was created by [zzstoatzz](https://github.com/zzstoatzz) to add type annotations and a more opinionated CLI.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT - http://opensource.org/licenses/MIT
|
|
186
|
+
|
|
187
|
+
"Hue Personal Wireless Lighting" is a trademark owned by Koninklijke Philips Electronics N.V., see www.meethue.com for more information.
|
|
188
|
+
I am in no way affiliated with the Philips organization.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
phue2/__init__.py,sha256=Uu9QwkzgpJtpWpYEO7MWuAVRPrHclQ7NkbrzKIIyFeU,873
|
|
2
|
+
phue2/__main__.py,sha256=22DGc0YJGxtF5eOQlnYhH29hPLkvh56svKONyqnrTEQ,12987
|
|
3
|
+
phue2/bridge.py,sha256=iFv0pq19bJ3r8El3ZwOzrfac-pls9d8y1PMbxv7cbBg,43862
|
|
4
|
+
phue2/exceptions.py,sha256=tFN3uEkHx2k-oU-wnmg1oKeoZJRrtZSS7L1iep5NHJ8,540
|
|
5
|
+
phue2/group.py,sha256=0ttIZrN-wJPfyXCv9CFxdsJuiYqhiy44gjojeIUI-5A,3995
|
|
6
|
+
phue2/light.py,sha256=BfvsiXUszEHh-1s1n3Nn-cTjiIPpBQ-JNiM-hAqpd5s,10793
|
|
7
|
+
phue2/scene.py,sha256=SB2BTVVS01ytPPvIp8njeXFYCKYi0cZLmxxBdplYKZo,1827
|
|
8
|
+
phue2/sensor.py,sha256=iSe0goDK-xX2AVUJh5Gi60MhKVaylLyMW16l7oEmEpY,12431
|
|
9
|
+
phue2/_internal/__init__.py,sha256=E6FcITKZNPECEK0c0umWMezKBVY6OCDqJpbOkbBfVNw,38
|
|
10
|
+
phue2/_internal/console.py,sha256=nUHNNLUhCAaaBLi82c-gJJXjSFScpFPgZWinxx3JYcs,4853
|
|
11
|
+
phue2-0.0.1.dev279.dist-info/METADATA,sha256=S_QMhD3uo0QqTomtr0NTD21OCIW3cQFhcGiAlireibs,4749
|
|
12
|
+
phue2-0.0.1.dev279.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
+
phue2-0.0.1.dev279.dist-info/entry_points.txt,sha256=izaWBWq5BqZKkEzL_Cha5PflzTST2vAmGd9NFm-3rk4,45
|
|
14
|
+
phue2-0.0.1.dev279.dist-info/licenses/LICENSE,sha256=53NTqbgURXQ5wulynQxi47i8YBuEcfThsunG4LSJs4s,1080
|
|
15
|
+
phue2-0.0.1.dev279.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nathan Nowack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|