HAP-python 1.0__tar.gz → 1.0.6__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 → HAP-python-1.0.6}/HAP_python.egg-info/PKG-INFO +1 -1
- {HAP-python-1.0 → HAP-python-1.0.6}/PKG-INFO +1 -1
- {HAP-python-1.0 → HAP-python-1.0.6}/README.md +41 -25
- HAP-python-1.0.6/pyhap/accessories/Http.py +207 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessory.py +25 -4
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessory_driver.py +16 -1
- {HAP-python-1.0 → HAP-python-1.0.6}/setup.py +1 -1
- HAP-python-1.0/pyhap/accessories/Http.py +0 -127
- {HAP-python-1.0 → HAP-python-1.0.6}/HAP_python.egg-info/SOURCES.txt +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/HAP_python.egg-info/dependency_links.txt +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/HAP_python.egg-info/requires.txt +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/HAP_python.egg-info/top_level.txt +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/LICENSE +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/MANIFEST.in +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/__init__.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/AM2302.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/BMP180.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/FakeFan.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/LightBulb.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/MotionSensor.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/ShutdownSwitch.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/TSL2591.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/TemperatureSensor.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/accessories/__init__.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/characteristic.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/hap_server.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/hsrp.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/loader.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/params.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/resources/characteristics.json +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/resources/services.json +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/service.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/tlv.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/pyhap/util.py +0 -0
- {HAP-python-1.0 → HAP-python-1.0.6}/setup.cfg +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
[](https://badge.fury.io/py/HAP-python)
|
|
1
2
|
# HAP-python
|
|
2
3
|
|
|
3
4
|
HomeKit Accessory Protocol implementation in python 3 (tested with 3.4, 3.5 and 3.6).
|
|
@@ -28,23 +29,12 @@ On a Raspberry Pi, you can get it with:
|
|
|
28
29
|
```
|
|
29
30
|
sudo apt-get install libavahi-compat-libdnssd-dev
|
|
30
31
|
```
|
|
31
|
-
`avahi-utils` may also fit the bill. Then,
|
|
32
|
-
Just git clone this project and install it like (you will need `sudo` or `--user` for the install):
|
|
33
|
-
|
|
32
|
+
`avahi-utils` may also fit the bill. Then, you can install with `pip3` (you will need `sudo` or `--user` for the install):
|
|
34
33
|
```sh
|
|
35
|
-
|
|
36
|
-
python3 setup.py install
|
|
37
|
-
```
|
|
38
|
-
or
|
|
39
|
-
```
|
|
40
|
-
pip3 install .
|
|
34
|
+
pip3 install HAP-python
|
|
41
35
|
```
|
|
42
36
|
|
|
43
|
-
This will install HAP-python in your python packages, so that you can import it as `pyhap`.
|
|
44
|
-
Alternatively, you can install only the dependencies and run with the root directory of this project in your `PYTHONPATH`.
|
|
45
|
-
|
|
46
|
-
To uninstall, just do:
|
|
47
|
-
|
|
37
|
+
This will install HAP-python in your python packages, so that you can import it as `pyhap`. To uninstall, just do:
|
|
48
38
|
```
|
|
49
39
|
pip3 uninstall HAP-python
|
|
50
40
|
```
|
|
@@ -125,30 +115,56 @@ class TemperatureSensor(Accessory):
|
|
|
125
115
|
|
|
126
116
|
## Integrating non-compatible devices <a name="HttpAcc"></a>
|
|
127
117
|
HAP-python may not be available for many IoT devices. For them, HAP-python allows devices
|
|
128
|
-
to be bridged by means of communicating with an HTTP server - the [
|
|
118
|
+
to be bridged by means of communicating with an HTTP server - the [HttpBridge](pyhap/accessories/Http.py). You can add as many remote accessories as you like.
|
|
129
119
|
|
|
130
120
|
For example, the bellow snippet creates an Http Accessory that listens on port 51800
|
|
131
121
|
for updates on the TemperatureSensor service:
|
|
132
122
|
```python
|
|
133
123
|
import pyhap.util as util
|
|
134
|
-
|
|
135
|
-
from pyhap.
|
|
124
|
+
import pyhap.loader as loader
|
|
125
|
+
from pyhap.accessories.Http import HttpBridge
|
|
126
|
+
from pyhap.accessory import Accessory
|
|
136
127
|
from pyhap.accessory_driver import AccessoryDriver
|
|
137
128
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
129
|
+
# get loaders
|
|
130
|
+
service_loader = loader.get_serv_loader()
|
|
131
|
+
char_loader = loader.get_char_loader()
|
|
132
|
+
|
|
133
|
+
# Create an accessory with the temperature sensor service.
|
|
134
|
+
# Also, add an optional characteristic StatusLowBattery to that service.
|
|
135
|
+
remote_accessory = Accessory("foo", aid=2) # aids must be unique for each accessory.
|
|
136
|
+
tservice = service_loader.get("TemperatureSensor")
|
|
137
|
+
tservice.add_opt_characteristic(
|
|
138
|
+
char_loader.get("StatusLowBattery"))
|
|
139
|
+
remote_accessory.add_service(tservice)
|
|
140
|
+
|
|
141
|
+
# Create the HTTP Bridge and add the accessory to it.
|
|
142
|
+
address = ("", 51111)
|
|
143
|
+
http_bridge = HttpBridge(address=address,
|
|
144
|
+
display_name="HTTP Bridge",
|
|
145
|
+
pincode=b"203-23-999")
|
|
146
|
+
http_bridge.add_accessory(remote_accessory)
|
|
147
|
+
|
|
148
|
+
# Add to driver and run.
|
|
149
|
+
driver = AccessoryDriver(http_bridge, 51826)
|
|
141
150
|
driver.start()
|
|
142
151
|
```
|
|
143
152
|
Now, remote accessories can do an HTTP POST to the address of the device where the
|
|
144
|
-
accessory is running (port
|
|
153
|
+
accessory is running (port 51111) with the following content:
|
|
145
154
|
```json
|
|
146
|
-
{
|
|
147
|
-
|
|
155
|
+
{
|
|
156
|
+
"aid": 2,
|
|
157
|
+
"services": {
|
|
158
|
+
"TemperatureSensor": {
|
|
159
|
+
"CurrentTemperature" : 20,
|
|
160
|
+
"StatusLowBattery": true,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
148
163
|
}
|
|
149
164
|
```
|
|
150
|
-
This will update the value of the characteristic "CurrentTemperature" to 20 degrees C
|
|
151
|
-
|
|
165
|
+
This will update the value of the characteristic "CurrentTemperature" to 20 degrees C
|
|
166
|
+
and "StatusLowBattery" to `true`.
|
|
167
|
+
Needless to say the communication to the Http Bridge poses a security risk, so
|
|
152
168
|
keep that in mind.
|
|
153
169
|
|
|
154
170
|
## Run at boot <a name="AtBoot"></a>
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""This module provides HttpAccessory - an accessory that
|
|
2
|
+
allows remote devices to provide HAP services by sending POST
|
|
3
|
+
requests.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import threading
|
|
7
|
+
import logging
|
|
8
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
9
|
+
|
|
10
|
+
from pyhap.accessory import Bridge, Category
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HttpBridgeHandler(BaseHTTPRequestHandler):
|
|
16
|
+
"""Handles requests and passes value updates to an HttpAccessory.
|
|
17
|
+
|
|
18
|
+
The POST request should contain json data with the format:
|
|
19
|
+
{ "aid": <aid>
|
|
20
|
+
"services": {
|
|
21
|
+
<service>: {
|
|
22
|
+
<characteristic>: value,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
{ "aid": 2
|
|
29
|
+
"services": {
|
|
30
|
+
TemperatureSensor" : {
|
|
31
|
+
"CurrentTemperature": 20
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, http_accessory, sock, client_addr, server):
|
|
38
|
+
"""Create a handler that passes updates to the given HttpAccessory.
|
|
39
|
+
"""
|
|
40
|
+
self.http_accessory = http_accessory
|
|
41
|
+
super(HttpBridgeHandler, self).__init__(sock, client_addr, server)
|
|
42
|
+
|
|
43
|
+
def respond_ok(self):
|
|
44
|
+
"""Reply with code 200 (OK) and close the connection.
|
|
45
|
+
"""
|
|
46
|
+
self.send_response(200)
|
|
47
|
+
self.send_header("Content-Type", "text/html")
|
|
48
|
+
self.send_header("Content-Length", 0)
|
|
49
|
+
self.end_headers()
|
|
50
|
+
self.close_connection = 1
|
|
51
|
+
|
|
52
|
+
def respond_err(self):
|
|
53
|
+
"""Reply with code 400 and close the connection.
|
|
54
|
+
"""
|
|
55
|
+
self.send_response(400)
|
|
56
|
+
self.send_header("Content-Type", "text/html")
|
|
57
|
+
self.send_header("Content-Length", 0)
|
|
58
|
+
self.end_headers()
|
|
59
|
+
self.close_connection = 1
|
|
60
|
+
|
|
61
|
+
def do_POST(self):
|
|
62
|
+
"""Read the payload as json and update the state of the accessory.
|
|
63
|
+
"""
|
|
64
|
+
length = int(self.headers["Content-Length"])
|
|
65
|
+
try:
|
|
66
|
+
# The below decode is necessary only for python <3.6, because loads prior 3.6
|
|
67
|
+
# doesn't know bytes/bytearray.
|
|
68
|
+
content = self.rfile.read(length).decode("utf-8")
|
|
69
|
+
data = json.loads(content)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error("Bad POST request; Error was: %s", str(e))
|
|
72
|
+
self.respond_err()
|
|
73
|
+
else:
|
|
74
|
+
self.http_accessory.update_state(data)
|
|
75
|
+
self.respond_ok()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class HttpBridge(Bridge):
|
|
79
|
+
"""An accessory that listens to HTTP requests containing characteristic updates.
|
|
80
|
+
|
|
81
|
+
Simple devices/implementations can just HTTP POST data as:
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
<aid>: int,
|
|
85
|
+
"services": {
|
|
86
|
+
<service1>: {
|
|
87
|
+
<characteristic1>: value
|
|
88
|
+
...
|
|
89
|
+
}
|
|
90
|
+
...
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Then this accessory takes care of communicating this update to any HAP clients.
|
|
95
|
+
|
|
96
|
+
The way you configure a HttpBridge is by adding Accessory objects. In the
|
|
97
|
+
example below, we add three accessories to a HTTP Bridge:
|
|
98
|
+
>>> # get loaders
|
|
99
|
+
>>> service_loader = loader.get_serv_loader()
|
|
100
|
+
>>> char_loader = loader.get_char_loader()
|
|
101
|
+
>>>
|
|
102
|
+
>>> # Create an accessory with the temperature sensor service.
|
|
103
|
+
>>> temperature_acc_1 = Accessory("temp1", 2)
|
|
104
|
+
>>> temperature_acc_1.add_service(service_loader.get("TemperatureSensor"))
|
|
105
|
+
>>>
|
|
106
|
+
>>> # Create an accessory with the temperature sensor service.
|
|
107
|
+
>>> # Also, add an optional characteristic Name to the service.
|
|
108
|
+
>>> temperature_acc_2 = Accessory("temp2", 3)
|
|
109
|
+
>>> temp_service = service_loader.get("TemperatureSensor")
|
|
110
|
+
>>> temp_service.add_opt_characteristic(char_loader.get("StatusLowBattery"))
|
|
111
|
+
>>> temperature_acc_2.add_service(temp_service)
|
|
112
|
+
>>>
|
|
113
|
+
>>> # Create a lightbulb accessory.
|
|
114
|
+
>>> light_bulb_acc = Accessory("bulb", 4)
|
|
115
|
+
>>> light_bulb_acc.add_service(service_loader.get("Lightbulb"))
|
|
116
|
+
>>>
|
|
117
|
+
>>> # Finally, create the HTTP Bridge and add all accessories to it.
|
|
118
|
+
>>> http_bridge = HttpBridge("HTTP Bridge", address=("", 51111))
|
|
119
|
+
>>> for accessory in (temperature_acc_1, temperature_acc_2, light_bulb_acc):
|
|
120
|
+
... http_bridge.add_accessory(accessory)
|
|
121
|
+
>>>
|
|
122
|
+
>>> # add to an accessory driver and start as usual
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
category = Category.OTHER
|
|
126
|
+
|
|
127
|
+
def __init__(self, address, *args, **kwargs):
|
|
128
|
+
"""Initialise and add the given services.
|
|
129
|
+
|
|
130
|
+
@param address: The address-port on which to listen for requests.
|
|
131
|
+
@type address: tuple(str, int)
|
|
132
|
+
|
|
133
|
+
@param accessories:
|
|
134
|
+
"""
|
|
135
|
+
super(HttpBridge, self).__init__(*args, **kwargs)
|
|
136
|
+
|
|
137
|
+
# For exclusive access to updates. Slight overkill...
|
|
138
|
+
self.update_lock = None
|
|
139
|
+
self.server_thread = None
|
|
140
|
+
self._set_server(address)
|
|
141
|
+
|
|
142
|
+
def _set_server(self, address):
|
|
143
|
+
"""Set up a HTTPServer to listen on the given address.
|
|
144
|
+
"""
|
|
145
|
+
self.server = HTTPServer(address, lambda *a: HttpBridgeHandler(self, *a))
|
|
146
|
+
self.server_thread = threading.Thread(target=self.server.serve_forever)
|
|
147
|
+
self.update_lock = threading.Lock()
|
|
148
|
+
|
|
149
|
+
def __getstate__(self):
|
|
150
|
+
"""Return the state of this instance, less the server and server thread.
|
|
151
|
+
|
|
152
|
+
Also add the server address. All this is because we cannot pickle such
|
|
153
|
+
objects and to allow to recover the server using the address.
|
|
154
|
+
"""
|
|
155
|
+
state = super(HttpBridge, self).__getstate__()
|
|
156
|
+
state["server"] = None
|
|
157
|
+
state["server_thread"] = None
|
|
158
|
+
state["update_lock"] = None
|
|
159
|
+
state["address"] = self.server.server_address
|
|
160
|
+
return state
|
|
161
|
+
|
|
162
|
+
def __setstate__(self, state):
|
|
163
|
+
"""Load the state and set up the server with the address in the state.
|
|
164
|
+
"""
|
|
165
|
+
self.__dict__.update(state)
|
|
166
|
+
self._set_server(state["address"])
|
|
167
|
+
|
|
168
|
+
def update_state(self, data):
|
|
169
|
+
"""Update the characteristics from the received data.
|
|
170
|
+
|
|
171
|
+
Expected to be called from HapHttpHandler. Updates are thread-safe.
|
|
172
|
+
|
|
173
|
+
@param data: A dict of values that should be set, e.g.:
|
|
174
|
+
{
|
|
175
|
+
<aid>: int,
|
|
176
|
+
<service1 name> : {
|
|
177
|
+
<characteristic1 name>: value
|
|
178
|
+
...
|
|
179
|
+
}
|
|
180
|
+
...
|
|
181
|
+
}
|
|
182
|
+
@type data: dict
|
|
183
|
+
"""
|
|
184
|
+
aid = data["aid"]
|
|
185
|
+
logger.debug("Got update from accessory with aid: %d", aid)
|
|
186
|
+
accessory = self.accessories[aid]
|
|
187
|
+
service_data = data["services"]
|
|
188
|
+
for service, char_data in service_data.items():
|
|
189
|
+
service_obj = accessory.get_service(service)
|
|
190
|
+
for char, value in char_data.items():
|
|
191
|
+
char_obj = service_obj.get_characteristic(char)
|
|
192
|
+
with self.update_lock:
|
|
193
|
+
char_obj.set_value(value)
|
|
194
|
+
|
|
195
|
+
def stop(self):
|
|
196
|
+
"""Stop the server.
|
|
197
|
+
"""
|
|
198
|
+
super(HttpBridge, self).stop()
|
|
199
|
+
logger.debug("Stopping HTTP bridge server.")
|
|
200
|
+
self.server.shutdown()
|
|
201
|
+
self.server.server_close()
|
|
202
|
+
|
|
203
|
+
def run(self):
|
|
204
|
+
"""Start the server - can listen for requests.
|
|
205
|
+
"""
|
|
206
|
+
logger.debug("Starting HTTP bridge server.")
|
|
207
|
+
self.server_thread.start()
|
|
@@ -112,7 +112,8 @@ class Accessory(object):
|
|
|
112
112
|
mac = util.generate_mac()
|
|
113
113
|
return cls(display_name, aid=aid, mac=mac, pincode=pincode)
|
|
114
114
|
|
|
115
|
-
def __init__(self, display_name, aid=
|
|
115
|
+
def __init__(self, display_name, aid=STANDALONE_AID, mac=None, pincode=None,
|
|
116
|
+
iid_manager=None):
|
|
116
117
|
|
|
117
118
|
self.display_name = display_name
|
|
118
119
|
self.aid = aid
|
|
@@ -145,7 +146,6 @@ class Accessory(object):
|
|
|
145
146
|
The default implementation adds only the AccessoryInformation services
|
|
146
147
|
and sets its Name characteristic to the Accessory's display name.
|
|
147
148
|
"""
|
|
148
|
-
# Info service
|
|
149
149
|
info_service = get_serv_loader().get("AccessoryInformation")
|
|
150
150
|
info_service.get_characteristic("Name")\
|
|
151
151
|
.set_value(self.display_name, False)
|
|
@@ -171,6 +171,22 @@ class Accessory(object):
|
|
|
171
171
|
"""
|
|
172
172
|
self.run_sentinel = run_sentinel
|
|
173
173
|
|
|
174
|
+
def config_changed(self):
|
|
175
|
+
"""Notify the accessory about configuration changes.
|
|
176
|
+
|
|
177
|
+
These include new services or updated characteristic values, e.g.
|
|
178
|
+
the Name of a service changed.
|
|
179
|
+
|
|
180
|
+
This method also notifies the broker about the change, so that it can
|
|
181
|
+
publish the changes to the world.
|
|
182
|
+
|
|
183
|
+
@note: If you are changing the configuration of a bridged accessory
|
|
184
|
+
(i.e. an Accessory that is contained in a Bridge),
|
|
185
|
+
you should call the `config_changed` method on the Bridge.
|
|
186
|
+
"""
|
|
187
|
+
self.config_version += 1
|
|
188
|
+
self.broker.config_changed()
|
|
189
|
+
|
|
174
190
|
def add_service(self, *servs):
|
|
175
191
|
"""Add the given services to this Accessory.
|
|
176
192
|
|
|
@@ -298,8 +314,13 @@ class Bridge(Accessory):
|
|
|
298
314
|
|
|
299
315
|
category = Category.BRIDGE
|
|
300
316
|
|
|
301
|
-
def __init__(self, display_name,
|
|
302
|
-
|
|
317
|
+
def __init__(self, display_name, mac=None, pincode=None, iid_manager=None):
|
|
318
|
+
aid = STANDALONE_AID
|
|
319
|
+
# A Bridge cannot be Bridge, hence talks directly to HAP clients.
|
|
320
|
+
# Thus, we need a mac.
|
|
321
|
+
mac = mac or util.generate_mac()
|
|
322
|
+
super(Bridge, self).__init__(display_name, aid=aid, mac=mac,
|
|
323
|
+
pincode=pincode, iid_manager=iid_manager)
|
|
303
324
|
self.accessories = {} # aid: acc
|
|
304
325
|
|
|
305
326
|
def _set_services(self):
|
|
@@ -12,7 +12,8 @@ AccessoryDriver (all this happens through the publish() interface). The Accessor
|
|
|
12
12
|
will then check if there is a client that subscribed for events from this exact
|
|
13
13
|
Characteristic from this exact Accessory (remember, it could be a Bridge with more than
|
|
14
14
|
one Accessory in it). If so, the event is put in a FIFO queue - the event queue. This
|
|
15
|
-
terminates the call chain and concludes the publishing process from the Characteristic
|
|
15
|
+
terminates the call chain and concludes the publishing process from the Characteristic,
|
|
16
|
+
the Characteristic does not block waiting for the actual send to happen.
|
|
16
17
|
|
|
17
18
|
When the AccessoryDriver is started, it spawns an event dispatch thread. The purpose of
|
|
18
19
|
this thread is to get events from the event queue and send them to subscribed clients.
|
|
@@ -217,6 +218,16 @@ class AccessoryDriver(object):
|
|
|
217
218
|
self.sent_events = 0
|
|
218
219
|
self.accumulated_qsize = 0
|
|
219
220
|
|
|
221
|
+
def config_changed(self):
|
|
222
|
+
"""Notify the driver that the accessory's configuration has changed.
|
|
223
|
+
|
|
224
|
+
Updates the mDNS advertisment, so that iOS clients know they need to new data.
|
|
225
|
+
Also, persists the accessory, so that the new configuration is available on
|
|
226
|
+
restart.
|
|
227
|
+
"""
|
|
228
|
+
self.update_advertisment()
|
|
229
|
+
self.persist()
|
|
230
|
+
|
|
220
231
|
def update_advertisment(self):
|
|
221
232
|
"""Updates the mDNS service info for the accessory."""
|
|
222
233
|
self.advertiser.unregister_service(self.mdns_service_info)
|
|
@@ -434,6 +445,10 @@ class AccessoryDriver(object):
|
|
|
434
445
|
self.http_server.shutdown()
|
|
435
446
|
self.http_server.server_close()
|
|
436
447
|
self.http_server_thread.join()
|
|
448
|
+
|
|
449
|
+
logger.debug("Persisting accessory state")
|
|
450
|
+
self.persist()
|
|
451
|
+
|
|
437
452
|
logger.debug("AccessoryDriver stopped successfully")
|
|
438
453
|
|
|
439
454
|
def signal_handler(self, _signal, _frame):
|
|
@@ -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.6",
|
|
8
8
|
url="https://github.com/ikalchev/HAP-python.git",
|
|
9
9
|
classifiers=[
|
|
10
10
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
"""An accessory that listens to HTTP requests containing characteristic updates.
|
|
2
|
-
|
|
3
|
-
A more simple device/implementation can just HTTP POST data as:
|
|
4
|
-
{ <service_name>: {
|
|
5
|
-
<characteristic_name>: value
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
Then this accessory takes care of communicating this update to any HAP clients.
|
|
9
|
-
"""
|
|
10
|
-
import json
|
|
11
|
-
import threading
|
|
12
|
-
import logging
|
|
13
|
-
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
14
|
-
|
|
15
|
-
from pyhap.accessory import Accessory, Category
|
|
16
|
-
import pyhap.loader as loader
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class HapHttpHandler(BaseHTTPRequestHandler):
|
|
22
|
-
"""
|
|
23
|
-
Handles POST requests and passes characteristic value updates to an HttpAccessory.
|
|
24
|
-
|
|
25
|
-
The POST request should contain json data with the format:
|
|
26
|
-
{ <service_name>: {
|
|
27
|
-
<characteristic_name>: value,
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
{ "TemperatureSensor" : {
|
|
33
|
-
"CurrentTemperature": 20
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, httpAccessory, sock, client_addr, server):
|
|
39
|
-
"""
|
|
40
|
-
Creates a handler that passes updates to the given HttpAccessory.
|
|
41
|
-
"""
|
|
42
|
-
self.httpAccessory = httpAccessory
|
|
43
|
-
super(HapHttpHandler, self).__init__(sock, client_addr, server)
|
|
44
|
-
|
|
45
|
-
def respond_ok(self):
|
|
46
|
-
self.send_response(200)
|
|
47
|
-
self.send_header("Content-Type", "text/html")
|
|
48
|
-
self.send_header("Content-Length", 0)
|
|
49
|
-
self.end_headers()
|
|
50
|
-
self.close_connection = 1
|
|
51
|
-
|
|
52
|
-
def respond_err(self):
|
|
53
|
-
self.send_response(400)
|
|
54
|
-
self.send_header("Content-Type", "text/html")
|
|
55
|
-
self.send_header("Content-Length", 0)
|
|
56
|
-
self.end_headers()
|
|
57
|
-
self.close_connection = 1
|
|
58
|
-
|
|
59
|
-
def do_POST(self):
|
|
60
|
-
"""
|
|
61
|
-
Read the payload as json and update the state of the httpAccessory.
|
|
62
|
-
"""
|
|
63
|
-
length = int(self.headers["Content-Length"])
|
|
64
|
-
try:
|
|
65
|
-
# The below decode is necessary only for python <3.6, because loads prior 3.6
|
|
66
|
-
# doesn't know bytes/bytearray.
|
|
67
|
-
content = self.rfile.read(length).decode("utf-8")
|
|
68
|
-
data = json.loads(content)
|
|
69
|
-
except Exception as e:
|
|
70
|
-
logger.error("Bad POST request; Error was: %s", str(e))
|
|
71
|
-
self.respond_err()
|
|
72
|
-
else:
|
|
73
|
-
self.httpAccessory.update_state(data)
|
|
74
|
-
self.respond_ok()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
'''TODO: should make it possible to init with {"aid" : [services]}
|
|
78
|
-
or {"addr": [services]} etc., so that this accessory can bridge several
|
|
79
|
-
other. In this way remote devices can use a simple interface and this module will do
|
|
80
|
-
the hap magic.
|
|
81
|
-
'''
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class Http(Accessory):
|
|
85
|
-
|
|
86
|
-
category = Category.OTHER
|
|
87
|
-
|
|
88
|
-
def __init__(self, address, hapServices, *args, **kwargs):
|
|
89
|
-
self.hapServices = hapServices
|
|
90
|
-
super(Http, self).__init__(*args, **kwargs)
|
|
91
|
-
self._set_server(address)
|
|
92
|
-
|
|
93
|
-
def _set_server(self, address):
|
|
94
|
-
self.server = HTTPServer(address, lambda *a: HapHttpHandler(self, *a))
|
|
95
|
-
self.serverThread = threading.Thread(target=self.server.serve_forever)
|
|
96
|
-
|
|
97
|
-
def _set_services(self):
|
|
98
|
-
super(Http, self)._set_services()
|
|
99
|
-
ldr = loader.get_serv_loader()
|
|
100
|
-
for s in self.hapServices:
|
|
101
|
-
self.add_service(ldr.get(s))
|
|
102
|
-
|
|
103
|
-
def __getstate__(self):
|
|
104
|
-
state = super(Http, self).__getstate__()
|
|
105
|
-
state["server"] = None
|
|
106
|
-
state["serverThread"] = None
|
|
107
|
-
state["address"] = self.server.server_address
|
|
108
|
-
return state
|
|
109
|
-
|
|
110
|
-
def __setstate__(self, state):
|
|
111
|
-
self.__dict__.update(state)
|
|
112
|
-
self._set_server(state["address"])
|
|
113
|
-
|
|
114
|
-
def update_state(self, data):
|
|
115
|
-
for service, charData in data.items():
|
|
116
|
-
serviceObj = self.get_service(service)
|
|
117
|
-
for char, value in charData.items():
|
|
118
|
-
charObj = serviceObj.get_characteristic(char)
|
|
119
|
-
charObj.set_value(value)
|
|
120
|
-
|
|
121
|
-
def stop(self):
|
|
122
|
-
super(Http, self).stop()
|
|
123
|
-
self.server.shutdown()
|
|
124
|
-
self.server.server_close()
|
|
125
|
-
|
|
126
|
-
def run(self):
|
|
127
|
-
self.serverThread.start()
|
|
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
|