HAP-python 1.0.6__tar.gz → 1.0.7__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.
Potentially problematic release.
This version of HAP-python might be problematic. Click here for more details.
- {HAP-python-1.0.6 → HAP-python-1.0.7}/HAP_python.egg-info/PKG-INFO +1 -1
- {HAP-python-1.0.6 → HAP-python-1.0.7}/HAP_python.egg-info/SOURCES.txt +1 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/HAP_python.egg-info/requires.txt +4 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/PKG-INFO +1 -1
- HAP-python-1.0.7/pyhap/accessories/SDS011.py +152 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/characteristic.py +72 -43
- {HAP-python-1.0.6 → HAP-python-1.0.7}/setup.py +7 -1
- {HAP-python-1.0.6 → HAP-python-1.0.7}/HAP_python.egg-info/dependency_links.txt +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/HAP_python.egg-info/top_level.txt +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/LICENSE +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/MANIFEST.in +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/README.md +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/__init__.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/AM2302.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/BMP180.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/FakeFan.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/Http.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/LightBulb.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/MotionSensor.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/ShutdownSwitch.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/TSL2591.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/TemperatureSensor.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessories/__init__.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessory.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/accessory_driver.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/hap_server.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/hsrp.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/loader.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/params.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/resources/characteristics.json +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/resources/services.json +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/service.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/tlv.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/pyhap/util.py +0 -0
- {HAP-python-1.0.6 → HAP-python-1.0.7}/setup.cfg +0 -0
|
@@ -25,6 +25,7 @@ pyhap/accessories/FakeFan.py
|
|
|
25
25
|
pyhap/accessories/Http.py
|
|
26
26
|
pyhap/accessories/LightBulb.py
|
|
27
27
|
pyhap/accessories/MotionSensor.py
|
|
28
|
+
pyhap/accessories/SDS011.py
|
|
28
29
|
pyhap/accessories/ShutdownSwitch.py
|
|
29
30
|
pyhap/accessories/TSL2591.py
|
|
30
31
|
pyhap/accessories/TemperatureSensor.py
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
An Accessory wrapper for the SDS011 air particulate density sensor.
|
|
3
|
+
|
|
4
|
+
The density sensor implementation can be found here:
|
|
5
|
+
https://github.com/ikalchev/py-sds011
|
|
6
|
+
Place the file under a package named sensors in your python path,
|
|
7
|
+
or change the import alltogether.
|
|
8
|
+
"""
|
|
9
|
+
import time
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from sensors.SDS011 import SDS011 as AirSensor
|
|
13
|
+
from pyhap.accessory import Accessory, Category
|
|
14
|
+
import pyhap.loader as loader
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SDS011(Accessory):
|
|
20
|
+
"""Accessory wrapper for SDS011.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
category = Category.SENSOR
|
|
24
|
+
|
|
25
|
+
SORTED_PM_QUALITY_MAP = ((200, 5), (150, 4), (100, 3), (50, 2), (0, 1))
|
|
26
|
+
"""
|
|
27
|
+
Threshold-to-state tuples. These show the state for which the threshold is
|
|
28
|
+
lower boundry. Uses something like Air Quality Index (AQI).
|
|
29
|
+
|
|
30
|
+
The UI shows:
|
|
31
|
+
1 - Excellent
|
|
32
|
+
2 - Good
|
|
33
|
+
3 - Fair
|
|
34
|
+
4 - Inferior
|
|
35
|
+
5 - Poor
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, serial_port, *args, sleep_duration_s=15*60, calib_duration_s=15,
|
|
39
|
+
**kwargs):
|
|
40
|
+
"""Initialise and start SDS011 on the given port.
|
|
41
|
+
|
|
42
|
+
@param serial_port: The SDS011 port, e.g. /dev/ttyUSB0.
|
|
43
|
+
@type serial_port: str
|
|
44
|
+
|
|
45
|
+
@param sleep_duration_s: The amount of time in seconds the sensor sleeps before
|
|
46
|
+
waking it up for reading. Defaults to 15 minutes.
|
|
47
|
+
@type sleep_duration_s: float
|
|
48
|
+
|
|
49
|
+
@param calib_duration_s: The amount of time in seconds to wait after the sensor
|
|
50
|
+
has been waken up before reading its data. Defaults to 15 seconds.
|
|
51
|
+
@type calib_duration_s: float
|
|
52
|
+
"""
|
|
53
|
+
self.pm25_quality = None
|
|
54
|
+
self.pm25_density = None
|
|
55
|
+
self.pm10_quality = None
|
|
56
|
+
self.pm10_density = None
|
|
57
|
+
super(SDS011, self).__init__(*args, **kwargs)
|
|
58
|
+
self.sleep_duration_s = sleep_duration_s
|
|
59
|
+
self.calib_duration_s = calib_duration_s
|
|
60
|
+
self.serial_port = serial_port
|
|
61
|
+
self.sensor = AirSensor(serial_port)
|
|
62
|
+
self.sensor.sleep(sleep=False)
|
|
63
|
+
|
|
64
|
+
def _set_services(self):
|
|
65
|
+
"""Add the AirQualitySensor services (one for PM2.5 and one for PM10).
|
|
66
|
+
|
|
67
|
+
Also adds and configures optional characteristics, such as Name,
|
|
68
|
+
AirParticulateSize, AirParticulateDensity.
|
|
69
|
+
"""
|
|
70
|
+
super(SDS011, self)._set_services()
|
|
71
|
+
char_loader = loader.get_char_loader()
|
|
72
|
+
|
|
73
|
+
# PM2.5
|
|
74
|
+
air_quality_pm25 = loader.get_serv_loader().get("AirQualitySensor")
|
|
75
|
+
pm25_size = char_loader.get("AirParticulateSize")
|
|
76
|
+
pm25_size.set_value(0, should_notify=False)
|
|
77
|
+
self.pm25_density = char_loader.get("AirParticulateDensity")
|
|
78
|
+
pm25_name = char_loader.get("Name")
|
|
79
|
+
pm25_name.set_value("PM2.5", should_notify=False)
|
|
80
|
+
self.pm25_quality = air_quality_pm25.get_characteristic("AirQuality")
|
|
81
|
+
air_quality_pm25.add_opt_characteristic(pm25_name, pm25_size, self.pm25_density)
|
|
82
|
+
|
|
83
|
+
# PM10
|
|
84
|
+
air_quality_pm10 = loader.get_serv_loader().get("AirQualitySensor")
|
|
85
|
+
pm10_size = char_loader.get("AirParticulateSize")
|
|
86
|
+
pm10_size.set_value(1, should_notify=False)
|
|
87
|
+
self.pm10_density = char_loader.get("AirParticulateDensity")
|
|
88
|
+
pm10_name = char_loader.get("Name")
|
|
89
|
+
pm10_name.set_value("PM10", should_notify=False)
|
|
90
|
+
self.pm10_quality = air_quality_pm10.get_characteristic("AirQuality")
|
|
91
|
+
air_quality_pm10.add_opt_characteristic(pm10_name, pm10_size, self.pm10_density)
|
|
92
|
+
|
|
93
|
+
self.add_service(air_quality_pm25)
|
|
94
|
+
self.add_service(air_quality_pm10)
|
|
95
|
+
|
|
96
|
+
def __getstate__(self):
|
|
97
|
+
"""Get the state, less the sensor.
|
|
98
|
+
"""
|
|
99
|
+
state = super(SDS011, self).__getstate__()
|
|
100
|
+
state["sensor"] = None
|
|
101
|
+
return state
|
|
102
|
+
|
|
103
|
+
def __setstate__(self, state):
|
|
104
|
+
"""Set the state of this Accessory and initialise the sensor
|
|
105
|
+
with the serial port in the state.
|
|
106
|
+
"""
|
|
107
|
+
self.__dict__.update(state)
|
|
108
|
+
self.sensor = AirSensor(self.serial_port)
|
|
109
|
+
|
|
110
|
+
def get_quality_classification(self, pm, is_pm25=False):
|
|
111
|
+
"""Get the air quality clasification based on the PM density.
|
|
112
|
+
|
|
113
|
+
Uses Air Quality Index (AQI), without averaging for an hour.
|
|
114
|
+
|
|
115
|
+
@see: SDS011.SORTED_PM_QUALITY_MAP
|
|
116
|
+
|
|
117
|
+
@rtype: int
|
|
118
|
+
"""
|
|
119
|
+
assert pm >= 0
|
|
120
|
+
return next(state for threshold, state in self.SORTED_PM_QUALITY_MAP
|
|
121
|
+
if threshold <= pm)
|
|
122
|
+
|
|
123
|
+
def run(self):
|
|
124
|
+
"""Start updating the air quality readings.
|
|
125
|
+
|
|
126
|
+
Initially, we read from the sensor and update the values. Then we put
|
|
127
|
+
it in sleep mode and while the sentinel is not set:
|
|
128
|
+
- Sleep for `self.sleep_duration_s`.
|
|
129
|
+
- Wake up and wait `self.calib_duration_s` seconds.
|
|
130
|
+
- Get the sensor's readings and update.
|
|
131
|
+
"""
|
|
132
|
+
pm25, pm10 = self.sensor.query()
|
|
133
|
+
self.pm25_density.set_value(pm25)
|
|
134
|
+
self.pm25_quality.set_value(
|
|
135
|
+
self.get_quality_classification(pm25, is_pm25=True))
|
|
136
|
+
self.pm10_density.set_value(pm10)
|
|
137
|
+
self.pm10_quality.set_value(
|
|
138
|
+
self.get_quality_classification(pm10, is_pm25=False))
|
|
139
|
+
self.sensor.sleep()
|
|
140
|
+
while not self.run_sentinel.wait(self.sleep_duration_s):
|
|
141
|
+
logger.debug("Waking up sensor.")
|
|
142
|
+
self.sensor.sleep(sleep=False)
|
|
143
|
+
time.sleep(self.calib_duration_s)
|
|
144
|
+
pm25, pm10 = self.sensor.query()
|
|
145
|
+
self.pm25_density.set_value(pm25)
|
|
146
|
+
self.pm25_quality.set_value(
|
|
147
|
+
self.get_quality_classification(pm25, is_pm25=True))
|
|
148
|
+
self.pm10_density.set_value(pm10)
|
|
149
|
+
self.pm10_quality.set_value(
|
|
150
|
+
self.get_quality_classification(pm10, is_pm25=False))
|
|
151
|
+
self.sensor.sleep()
|
|
152
|
+
logger.debug("Read cycle done. Sleeping.")
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
All things for a HAP characteristic.
|
|
2
3
|
|
|
4
|
+
A Characteristic is the smallest unit of the smart home, e.g.
|
|
5
|
+
a temperature measuring or a device status.
|
|
6
|
+
"""
|
|
3
7
|
|
|
4
8
|
class HAP_FORMAT:
|
|
5
9
|
BOOL = 'bool'
|
|
@@ -52,39 +56,59 @@ class CharacteristicError(Exception):
|
|
|
52
56
|
pass
|
|
53
57
|
|
|
54
58
|
|
|
59
|
+
_HAP_NUMERIC_FIELDS = {"maxValue", "minValue", "minStep", "unit"}
|
|
55
60
|
"""Fields that should be included in the HAP representation of the characteristic.
|
|
56
61
|
|
|
57
62
|
That is, if they are present in the specification of a numeric-value characteristic.
|
|
58
63
|
"""
|
|
59
|
-
_HAP_NUMERIC_FIELDS = {"maxValue", "minValue", "minStep", "unit"}
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
class Characteristic(object):
|
|
67
|
+
"""Represents a HAP characteristic, the smallest unit of the smart home.
|
|
68
|
+
|
|
69
|
+
A HAP characteristic is some measurement or state, like battery status or
|
|
70
|
+
the current temperature. Characteristics are contained in services.
|
|
71
|
+
Each charactaristic has a unique type UUID and a set of properties,
|
|
72
|
+
like format, min and max values, valid values and others.
|
|
73
|
+
"""
|
|
63
74
|
|
|
64
75
|
def __init__(self, display_name, type_id, properties, value=None, broker=None):
|
|
76
|
+
assert "Format" in properties and "Permissions" in properties
|
|
65
77
|
self.display_name = display_name
|
|
66
78
|
self.type_id = type_id
|
|
67
|
-
assert "Format" in properties and "Permissions" in properties
|
|
68
79
|
self.properties = properties
|
|
69
|
-
self.
|
|
80
|
+
self.has_valid_values = "ValidValues" in self.properties
|
|
70
81
|
self.value = value or HAP_FORMAT.DEFAULT[properties["Format"]]
|
|
71
82
|
self.broker = broker
|
|
72
83
|
self.setter_callback = None
|
|
73
|
-
self.
|
|
84
|
+
self.hap_template = self._create_hap_template()
|
|
85
|
+
|
|
86
|
+
def _create_hap_template(self):
|
|
87
|
+
"""Create a HAP template for describing this Characteristic.
|
|
88
|
+
|
|
89
|
+
Contains properties that do not change or change rarely, e.g. the type.
|
|
90
|
+
"""
|
|
91
|
+
template = dict()
|
|
92
|
+
if self.properties["Format"] in HAP_FORMAT.NUMERIC:
|
|
93
|
+
template = {k: self.properties[k]
|
|
94
|
+
for k in self.properties.keys() & _HAP_NUMERIC_FIELDS}
|
|
95
|
+
return template
|
|
74
96
|
|
|
75
97
|
def set_value(self, value, should_notify=True):
|
|
76
|
-
"""Set the given value.
|
|
98
|
+
"""Set the given raw value. It is checked if it is a valid value.
|
|
77
99
|
|
|
78
100
|
@param value: The value to assign as this Characteristic's value.
|
|
79
101
|
@type value: Depends on properties["Format"]
|
|
80
102
|
|
|
81
103
|
@param should_notify: Whether a the change should be sent to subscribed clients.
|
|
104
|
+
The notification is called _after_ the setter callback.
|
|
82
105
|
@type should_notify: bool
|
|
83
106
|
|
|
84
|
-
@raise ValueError: When the value being assigned is not one of the
|
|
107
|
+
@raise ValueError: When the value being assigned is not one of the valid values
|
|
85
108
|
for this Characteristic.
|
|
86
109
|
"""
|
|
87
|
-
if self.
|
|
110
|
+
if (self.has_valid_values
|
|
111
|
+
and value not in self.properties["ValidValues"].values()):
|
|
88
112
|
raise ValueError
|
|
89
113
|
self.value = value
|
|
90
114
|
if self.setter_callback is not None:
|
|
@@ -93,53 +117,49 @@ class Characteristic(object):
|
|
|
93
117
|
self.notify()
|
|
94
118
|
|
|
95
119
|
def get_value(self):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def notify(self):
|
|
99
|
-
data = {
|
|
100
|
-
"type_id": self.type_id,
|
|
101
|
-
"value": self.value,
|
|
102
|
-
}
|
|
103
|
-
self.broker.publish(data, self)
|
|
120
|
+
"""Get the raw value of this Characteristic.
|
|
104
121
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
template = {k: self.properties[k]
|
|
109
|
-
for k in self.properties.keys() & _HAP_NUMERIC_FIELDS}
|
|
110
|
-
return template
|
|
111
|
-
|
|
112
|
-
def _value_to_HAP(self):
|
|
113
|
-
hap_rep = self.hap_value_template.copy()
|
|
122
|
+
@deprecated: Use self.value instead.
|
|
123
|
+
"""
|
|
124
|
+
return self.value
|
|
114
125
|
|
|
126
|
+
def get_hap_value(self):
|
|
127
|
+
"""Get the value of the characteristic, constrained with the HAP properties.
|
|
128
|
+
"""
|
|
129
|
+
val = self.value
|
|
115
130
|
if self.properties["Format"] == HAP_FORMAT.STRING:
|
|
116
|
-
val =
|
|
117
|
-
if len(self.value) > 64:
|
|
118
|
-
hap_rep["maxLen"] = min(len(self.value), 256)
|
|
131
|
+
val = val[:256]
|
|
119
132
|
elif self.properties["Format"] in HAP_FORMAT.NUMERIC:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
else:
|
|
126
|
-
val = self.value
|
|
133
|
+
if "maxValue" in self.properties:
|
|
134
|
+
val = min(self.properties["maxValue"], val)
|
|
135
|
+
if "minValue" in self.properties:
|
|
136
|
+
val = max(self.properties["minValue"], val)
|
|
137
|
+
return val
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
def notify(self):
|
|
140
|
+
"""Notify clients about a value change.
|
|
130
141
|
|
|
131
|
-
|
|
142
|
+
@note: Non-blocking, i.e. does not wait for the update to be sent.
|
|
143
|
+
@note: Uses the `get_hap_value`, i.e. sends the HAP value.
|
|
144
|
+
@see: accessory_driver.publish
|
|
145
|
+
"""
|
|
146
|
+
data = {
|
|
147
|
+
"type_id": self.type_id,
|
|
148
|
+
"value": self.get_hap_value(),
|
|
149
|
+
}
|
|
150
|
+
self.broker.publish(data, self)
|
|
132
151
|
|
|
133
|
-
def to_HAP(self, iid_manager
|
|
152
|
+
def to_HAP(self, iid_manager):
|
|
134
153
|
"""Create a HAP representation of this Characteristic.
|
|
135
154
|
|
|
136
|
-
@
|
|
137
|
-
|
|
155
|
+
@note: Uses the `get_hap_value`, i.e. sends the HAP value.
|
|
156
|
+
|
|
157
|
+
@param iid_manager: IID manager to query for this object's IID.
|
|
158
|
+
@type iid_manager: IIDManager
|
|
138
159
|
|
|
139
160
|
@return: A HAP representation.
|
|
140
161
|
@rtype: dict
|
|
141
162
|
"""
|
|
142
|
-
assert iid_manager is not None
|
|
143
163
|
hap_rep = {
|
|
144
164
|
"iid": iid_manager.get_iid(self),
|
|
145
165
|
"type": str(self.type_id).upper(),
|
|
@@ -147,5 +167,14 @@ class Characteristic(object):
|
|
|
147
167
|
"perms": self.properties["Permissions"],
|
|
148
168
|
"format": self.properties["Format"],
|
|
149
169
|
}
|
|
150
|
-
|
|
170
|
+
|
|
171
|
+
value_info = self.hap_template.copy()
|
|
172
|
+
val = self.get_hap_value()
|
|
173
|
+
if self.properties["Format"] == HAP_FORMAT.STRING:
|
|
174
|
+
if len(val) > 64:
|
|
175
|
+
value_info["maxLen"] = min(len(val), 256)
|
|
176
|
+
if HAP_PERMISSIONS.READ in self.properties["Permissions"]:
|
|
177
|
+
value_info["value"] = val
|
|
178
|
+
|
|
179
|
+
hap_rep.update(value_info)
|
|
151
180
|
return hap_rep
|
|
@@ -4,7 +4,7 @@ setup(
|
|
|
4
4
|
name="HAP-python",
|
|
5
5
|
description="HomeKit Accessory Protocol implementation in python3",
|
|
6
6
|
author="Ivan Kalchev",
|
|
7
|
-
version="1.0.
|
|
7
|
+
version="1.0.7",
|
|
8
8
|
url="https://github.com/ikalchev/HAP-python.git",
|
|
9
9
|
classifiers=[
|
|
10
10
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -27,6 +27,12 @@ setup(
|
|
|
27
27
|
"zeroconf",
|
|
28
28
|
"curve25519-donna"
|
|
29
29
|
],
|
|
30
|
+
extras_require={
|
|
31
|
+
"dev": [
|
|
32
|
+
"pytest",
|
|
33
|
+
"tox",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
30
36
|
package_data={
|
|
31
37
|
"pyhap": ["resources/*"],
|
|
32
38
|
}
|
|
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
|