ikalchev / hap-python Goto Github PK
View Code? Open in Web Editor NEWA python implementation of the HomeKit Accessory Protocol (HAP)
License: Other
A python implementation of the HomeKit Accessory Protocol (HAP)
License: Other
Characteristic value updates are not persisted, thus, when HAP-python starts again, the last values are not seen in iOS. This should be done when the accessory driver stops (doing it on every update would be an overkill).
I'm still hunting down exactly what's going on, but it seems that for some reason I can't successfully pair any HAP-python accessories any longer.
I'm not sure this is something that has changed with our code, as I un-paired and then attempted to re-pair with one that I know worked a couple of weeks ago, but without any luck.
If you check out the repo to master
, and then run tox -e temperature
, it should start a server, and show the QR code. In my case, I'm not able to finish the pairing process.
I've turned logging up to DEBUG, and I don't see the pairing being initiated at all.
The issue regarding home-assistant/core#12488 (comment)
As I mentioned above, I personally don't like the way Hap-python
handles the __init__
calls for Accessories. At the moment calls look something like this:
acc = TemperatureSensor(display_name='Temperatur')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Parameter are just being forwarded until Accessory. If you at a wrapper class in between, another level that just forwards the information is introduced.
In python their should be a better way to do this, although that might require some more remodeling. Unfortunately I woun't have time until the next weekend to take a deeper look at it.
is possible?
The notify method should check if the broker is None and attempt to publish only if it is not None.
Hi @gerswin,
I needed to amend the author info about all my past commits to HAP-python (no code changes). This may have affected your fork. If so, please accept my apologies and if you need any help in removing issues, please let me know.
Best,
Ivan
bme280 support air pressure.
Currently, Characteristic only respects min/max value constraints when sending its state through the to_HAP
method. However, when a value change is published to iOS clients (notify
), the sent value could be outside of min/max range. There should be a distinction between actual char value and it's "hap" representation, .e.g:
char = Characteristic(...)
char.value # raw value
char.get_value() # hapified value
char.set_value(value, ...)
# ^ should be equivalent to
if value not in self.allowed_values: raise ....
self.value = value
Hi,
I'm trying to set up a door (LockMechanism
service) and I'm having this problem: the hap_server responds with timed out.
The door is initially closed and then I tap to open it within the apple's home app.
Here is what I have:
main.py
"""An example of how to setup and start an Accessory.
This is:
1. Create the Accessory object you want.
2. Add it to an AccessoryDriver, which will advertise it on the local network,
setup a server to answer client queries, etc.
"""
import logging
import signal
from pyhap.accessories.ElectronicLock import ElectronicLock
from pyhap.accessory import Bridge
from pyhap.accessory_driver import AccessoryDriver
logging.basicConfig(level=logging.DEBUG)
def get_accessory():
"""Call this method to get a standalone Accessory."""
acc = ElectronicLock("Door")
return acc
acc = get_accessory()
# Start the accessory on port 51826
driver = AccessoryDriver(acc, port=51826)
# We want KeyboardInterrupts and SIGTERM (kill) to be handled by the driver itself,
# so that it can gracefully stop the accessory, server and advertising.
signal.signal(signal.SIGINT, driver.signal_handler)
signal.signal(signal.SIGTERM, driver.signal_handler)
# Start it!
driver.start()
ElectronicLock.py
from pyhap.accessory import Accessory, Category
import pyhap.loader as loader
class LOCK_STATE:
OPEN = 0
CLOSED = 1
class ElectronicLock(Accessory):
category = Category.DOOR
def __init__(self, *args, **kwargs):
super(ElectronicLock, self).__init__(*args, **kwargs)
def set_state(self, value):
self.get_service("LockMechanism").get_characteristic("LockCurrentState").set_value(value)
self.get_service("LockMechanism").get_characteristic("LockTargetState").set_value(value, should_callback=False)
def lock_target_callback(self, value):
if value == LOCK_STATE.OPEN:
print("OPEN")
else:
print("CLOSED")
self.set_state(value)
def _set_services(self):
super(ElectronicLock, self)._set_services()
lm_service = loader.get_serv_loader().get("LockMechanism")
self.add_service(lm_service)
lm_service.get_characteristic("LockTargetState").setter_callback = self.lock_target_callback
terminal log:
DEBUG:pyhap.hap_server:Request PUT from address '('192.168.1.100', 54902)' for path '/characteristics'.
INFO:pyhap.hap_server:192.168.1.100 - "PUT /characteristics HTTP/1.1" 207 -
DEBUG:pyhap.hap_server:Request GET from address '('192.168.1.100', 54902)' for path '/characteristics?id=1.8,1.9'.
INFO:pyhap.hap_server:192.168.1.100 - "GET /characteristics?id=1.8,1.9 HTTP/1.1" 207 -
DEBUG:pyhap.hap_server:Request PUT from address '('192.168.1.100', 54902)' for path '/characteristics'.
OPEN
INFO:pyhap.hap_server:192.168.1.100 - "PUT /characteristics HTTP/1.1" 207 -
INFO:pyhap.hap_server:192.168.1.100 - Request timed out: timeout('timed out',)
The thing is that after the time out occurs, the server seems to stop listening new requests. I took a look at HAPServerHandler -> BaseHTTPRequestHandler
but I couldn't find anything.
Any ideas why this is happening?
Apple has pushed new Services and Characteristics, which need to be parsed and added to HAP-python using gen_hap_types.py
.
Also, Apple specifies some non-intuitive default characteristic constraints - for example, by default the CurrentTemperature has a min value of 0, i.e. you cannot report negative values. Thus, there should be a way to override Apple's default values at the end of gen_hap_types.py
. See pull request #28
Currently, it's really not possible to extend the encoder: you need to copy-paste the method body, and then add your extra stuff you want to store in the state before you dump it to json.
I think each operation should be split into two, so it's possible to add/change the data that will be persisted (and handle that extra data on the way in), but also more easily switch the encoding method (for instance, to be able to use a non-JSON encoder).
class Encoder(object):
def prepare_state(self, accessory):
return { ... }
def persist(self, fp, accessory):
json.dump(self.prepare_state(accessory), fp)
def restore_state(self, accessory, state):
...
def load_into(self, fp, accessory):
self.restore_state(accessory, json.load(fp))
I'm not sure if those are the best names, but hopefully get the idea.
A nice feature about Homebridge/hap-nodejs is that you can scan a QR code in a terminal window.
I've had a quick look at the code for generating it, and if I can port it to Python I'll create a PR.
this is my code, can u explain me how to get the status of device.
import logging
from pyhap.accessory import Accessory, Category
import pyhap.loader as loader
import paho.mqtt.subscribe as subscribe
import paho.mqtt.publish as publish
auth = {
'username':"",
'password':""
}
class LightBulb(Accessory):
category = Category.LIGHTBULB
def __init__(self, *args,**kwargs):
super(LightBulb, self).__init__(*args, **kwargs)
def __setstate__(self, state):
self.__dict__.update(state)
def set_bulb(self, value):
print(value)
publish.single("inTopic",
payload=value,
hostname="localhost",
client_id="screen",
auth=auth,
retain=False,
port=1883)
def _set_services(self):
super(LightBulb, self)._set_services()
bulb_service = loader.get_serv_loader().get("Lightbulb")
self.add_service(bulb_service)
bulb_service.get_characteristic("On").setter_callback = self.set_bulb
def stop(self):
super(LightBulb, self).stop()
Trying to get this working, and it works for the HomeKit requests, but not when the door state changes outside of HomeKit (i.e. when I open the garage with my car remote, instead of HomeKit).
Would like some help troubleshooting this. I am getting the opposite transition state than you would expect, only when the door state changes outside of a HomeKit request from an iOS device.
You can easily simulate this on a RPi, by shorting out a GPIO pin to ground for a CLOSED
state, and removing from ground for an OPEN
state. On the iOS, device you can request open, then simulate open, and so forth.
To repro the issue I am having, simply simulate the opposite state of the door, without requesting it from HAP-python.
Also, would like some clarification on how to properly set the initial state of CurrentDoorState
, as I was seeing some weird things there too, like starting out as Opening
, even though it was clearly set to CLOSED
Here is a simple driver, using pin 17 (BCM)/ 11(BOARD) for the switch, using the new QR stuff of course ;)
from pyhap.accessories.GarageDoor import GarageDoor
from pyhap.accessory_driver import AccessoryDriver
import signal
garage = GarageDoor(2, 17, 'GarageDoorTest', pincode=b'920-54-727')
print(garage.display_name)
print(garage.pincode.decode())
garage.print_qr()
driver = AccessoryDriver(garage, port=51826)
signal.signal(signal.SIGINT, driver.signal_handler)
signal.signal(signal.SIGTERM, driver.signal_handler)
driver.start()
Please see PR
in last update bridge mode is not working.
tested with main.py
Currently HAP-python
uses threads which, at least on a larger scale, does impact performance quite a bit. Are there plans to change to an async framework, like asyncio
in the future?
Unfortunately this would also require a more recent min python version, to take full advantage of the new async syntax introduced with py3.5.
I think it might be useful to implement something like a global_setter
for services. Those should get called when one or more char values of this service are about to be changed to provide an access point for end users.
The concrete case is an issue / bug with the HomeKit lightbulb service. When setting the light to a specific brightness in the Home app from the off state, HomeKit calls to char client_update_values
, one for the state (on / off) and one for the brightness char.
Reacting to both with the currently used setter_callbacks
could lead to flickering if say the light is first set to on (100%) and only then to the wanted brightness.
Links:
Update
I've looked at the HAP doc, it might be better to implement it in the accessory class.
It should be possible to configure the properties of a characteristic to align with the functionality of the device and the iOS UI. For example, say your window covering can go up only to 50% open and that it is either closed or open. HAP specifies the default values for the coverings target position as maxValue=100 and minStep=0.1. This means iOS clients can slide at 0.1 step all the way to 100%, which you must map to all-up or all-down in your code.
Instead, you can do:
service = loader.get_serv_loader().get("WindowCovering")
pos = service.get_characteristic("TargetPosition")
pos.properties["maxValue"] = 50
pos.properties["minStep"] = 50
pos.hap_template = pos._create_hap_template()
Now iOS correctly allows you to only fully go up or down and the max display value is 50%. The hacky thing about the above is the last line and this is the topic of this issue - overriding properties should be more "native".
i have 10 lightbulbs devices but for some reason one or two of them randomly gets not supported.
Currently, you can use the Http accessory to expose non-hap-compatible devices by allowing them to submit characteristic updates in the form of http post requests.
However, you can only add services with their mandatory characteristics, whereas it should be possible to also add optional chars, such as Name.
This issue tracks the effort for the above.
This could then raise a KeyError
, rather than an AssertionError
when a non-existent characteristic is accessed.
Congratulations for your great work.
I add all accessory to homekit and I work perfectly but when I restart process, my iPhone with iOS 11.2.1 not reconnect accesories. The accessories have simbol (!). What can I do?
Thank you very much
The current version removes deprecated Apple-defined characteristics and services. These should be available somewhere for people using an older iOS.
Putting this issue here to track progress on the Sphinx Documentation.
Todo:
I don't really like the amount of work that I have to do to define an accessory, and use it - specifically: creating a driver, and how much super()...
stuff is required.
Taking a leaf out of django's book, I've been playing around with using metaclasses, and a more declarative approach to declaring the accessory, and her services.
As a result, I have a syntax for definition that looks like:
class TemperatureSensor(Accessory):
category = 'SENSOR'
class AccessoryInformation:
Manufacturer = 'Matthew Schinckel'
Model = 'DS18B20'
SerialNumber = '28-0516c0fc92ff'
temperature = RemoteDS18B20(server='garagepi.local', sensor_id='28-0516c0fc92ff')
I'm not totally finished with the concept yet, as I think that maybe the Name
field could be defined on the class like this too.
To use it, you can then do:
TemperatureSensor('Garage', port=XXX, persist_file=YYY).start()
There's lots more moving parts, but I think it's a cleaner syntax, but it moves lots of the logic from the accessory level down to the service level. I understand this may not be the way the whole project wants to go, and as such it's a wrapper around classes from HAP-python
.
A link to the code is coming soon.
Consider the following Apple-defined service in json as an example:
"LockMechanism": {
"IncludedServices": [
"00000044-0000-1000-8000-0026BB765291"
],
"OptionalCharacteristics": [
"Name"
],
"RequiredCharacteristics": [
"LockCurrentState",
"LockTargetState"
],
"UUID": "00000045-0000-1000-8000-0026BB765291"
}
The IncludedServices
field is not yet supported in HAP-python.
I'm not exactly sure how to override the AccessoryService
fields: I've tried a bit in my GarageDoor._set_services()
, but it doesn't seem to work.
I wonder if there should be a way to have this information either pulled from the class, or some other mechanism.
I have a large framework for a home automation system which in many ways is a bridge of bridges, acting as an overall compatibility and abstraction layer across multiple systems.
However with both of these components in place, my code sets the value of the light, which triggers the callback, which calls my code, which updates the value of the light, which triggers the callback (repeat forever).
Is there a way for me to differentiate so that the callback is only triggered when a user makes a change and not when the device is updated from my code?
do you have an example for use as bridge?
Reading through the Apple HAP documentation, it's pretty clear that "if a device can dynamically generate pin codes, it should."
To that end, I started playing around with stuff, and a dynamically generated pin code has no drawbacks (other than having to see what value was generated this time around).
I believe the same is true of setup id (which is used for the URL generation in #31 and #30). To that end, I believe that these values should not be stored in the pickle.
The other thing I discovered while developing accessories is that pickle is not a great way of storing stuff, especially during development. It means that if you change a class, or an import, then things can stop working, and you'd need to delete the pickle file and then re-add the accessory. There are also possible security implications of pickling data, but I'm less concerned about that because we aren't unpickling user supplied data.
Hello Ivan,
I did try to use optional characteristics for example "RotationSpeed" for "Fan" but this thing is not implemented, so I had to use https://github.com/KhaosT/HAP-NodeJS
But I prefer using Python instead of JS, would be nice to implement it and I can test it for you with my home-made Fan :-)
You can ask me for help :-)
Thanks,
Miroslav
it's me again, can u tell me how to use name characteristic??
self.get_service("Lightbulb").get_characteristic("Name").set_value("Bed")
it's not working
The new format should allow new HAP-python versions to understand state from an older version, removing the need to rm accessory.pickle
after updating HAP-python and potentially requiring less space.
Hi,
I am writing an adapter for HomeAssistant and I have been able to add a bridge, with a temperature and humidity sensors. Pairing goes fine but If I get an updated value from homeassistant I get an Error:
Traceback (most recent call last): File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run result = self.fn(*self.args, **self.kwargs) File "/home/homeassistant/.homeassistant/custom_components/HomeKit.py", line 105, in update_value self.hum_char.set_value(float(new_state.state)) File "/opt/homeassistant/lib/python3.5/site-packages/pyhap/characteristic.py", line 141, in set_value self.notify() File "/opt/homeassistant/lib/python3.5/site-packages/pyhap/characteristic.py", line 174, in notify self.broker.publish(data, self) File "/opt/homeassistant/lib/python3.5/site-packages/pyhap/accessory.py", line 328, in publish self.broker.publish(acc_data) AttributeError: 'NoneType' object has no attribute 'publish'
update_value is a class method of (in this case HumiditySensor, same for TemperatureSensor) which is called by HomeAssistant with the updated value.
my code for Humidity Sensor:
class HumiditySensor(Accessory):
category = Category.SENSOR
def __init__(self, *args, **kwargs):
super(HumiditySensor, self).__init__(*args, **kwargs)
self.hum_char = self.get_service("HumiditySensor").get_characteristic("CurrentRelativeHumidity")
# self.hum_char.setter_callback = self.humidity_changed
# def humidity_changed(self, value):
# _LOGGER.error("Humidity Changed : " +str(value))
def _set_services(self):
super(HumiditySensor, self)._set_services()
hum_sensor_service = loader.get_serv_loader().get("HumiditySensor")
self.add_service(hum_sensor_service)
def update_value(self,entity_id, old_state, new_state):
self.hum_char.set_value(float(new_state.state))
bridge = False # points to the bridge object after init
def setup(hass,config):
global bridge #Not sure If I need this...
T = TemperatureSensor("Temperatuur ")
H = HumiditySensor("Relatieve Luchtvochtigheid")
_LOGGER.info("HomeKit bridge started ...")
_LOGGER.info("display_name: "+config[DOMAIN]["display_name"])
bridge = Bridge(display_name = config[DOMAIN]["display_name"],pincode=b"123-45-678")
bridge.add_accessory(T)
bridge.add_accessory(H)
if os.path.exists("accessory.pickle"):
with open("accessory.pickle","rb") as f:
acc=pickle.load(f)
else:
acc=bridge
driver = AccessoryDriver(acc, 51826, address="10.3.3.40")
driver.start()
def handle_homeassistant_stop(event):
driver.stop()
hass.bus.listen('homeasststant_stop', handle_homeassistant_stop)
track_state_change(hass, "sensor.temperatuur_woonkamer_bmp", T.update_value)
track_state_change(hass, "sensor.relatieve_luchtvochtigheid", H.update_value)
setup is called by HomeAssistant at startup
I have to set the tcpip address as I have a dual homed server. and driver.stop() is called when HomeAssistant is about to exit/stop and the last two lines are to track the update value events.
I'm puzzled where this error comes from, I must have forgotten something.
Any help on how to find out what is wrong would be highly appreciated!
Kind regards,
Martijn
@ikalchev do you have any docs covering thread safety? I know elaborated about how threads are used in #74, but it looks like characteristics set_value()
is not really thread safe, while being called from multiple threads.
Also do you offer guidance anywhere for how an accessories run()
loop should interact with requests from py-HAP in a thread safe manner?
Thanks for sharing this project, I find python a little easier to using NodeJS. I can get the TempSensor, LightBulb to work on my Pi Zero W, but my attempt to mash code from multiple existing accessories to create a Garage Door Opener fails (it pairs and then unpairs right away). Can someone can point to the issues with this code? Not sure getstate is needed at all, my suspicion was that the required characteristic state is not getting set correctly.
class GarageDoorOpener(Accessory):
category = Category.GARAGE_DOOR_OPENER
@classmethod
def _gpio_setup(_cls, pulse_pin, sense_pin):
GPIO.setmode(GPIO.BCM)
GPIO.setup(pulse_pin, GPIO.OUT)
GPIO.setup(sense_pin, GPIO.IN)
GPIO.add_event_detect(sense_pin, GPIO.BOTH, callback=self._detected)
def __init__(self, *args, pulse_pin=26, sense_pin=22, **kwargs):
super(GarageDoorOpener, self).__init__(*args, **kwargs)
self.CurrentDoorState = self.get_service("GarageDoorOpener").get_characteristic("CurrentDoorState")
self.ObstructionDetected = self.get_service("GarageDoorOpener").get_characteristic("ObstructionDetected")
self.pulse_pin = pulse_pin
self.sense_pin = sense_pin
self._gpio_setup(pulse_pin,sense_pin)
def __getstate__(self):
state = super(GarageDoorOpener, self).__getstate__()
return state
def __setstate__(self, state):
self.__dict__.update(state)
self._gpio_setup(self.pulse_pin, self.sense_pin)
def _pulse_door(self, value):
GPIO.output(self.pulse_pin, GPIO.HIGH)
time.sleep(2)
GPIO.output(self.pulse_pin, GPIO.LOW)
time.sleep(2)
def _set_services(self):
super(GarageDoorOpener, self)._set_services()
garage_door_opener_service = loader.get_serv_loader().get("GarageDoorOpener")
self.add_service(garage_door_opener_service)
garage_door_opener_service.get_characteristic("TargetDoorState").setter_callback = self._pulse_door
def _detected(self, _pin):
if(GPIO.input(self.sense_pin)):
self.CurrentDoorState.set_value(1)
else:
self.CurrentDoorState.set_value(0)
def stop(self):
super(GarageDoorOpener, self).stop()
GPIO.cleanup()
~
The characteristic value is set to some default value on init. However, if this is passed to the iOS client, which expects a pre-defined set of valid values, it will not be happy. Therefore, when setting the default, valid values should be considered.
I can't find color temperature anywhere in the characteristics.json file.
The HAP-NodeJS implementation has a color temperature characteristic in HomeKitTypes.js
I think you need this if you want to enable white spectrum lights that supports setting color temperature, but not full color spectrum?
Edit: I think you also need color temperature as an OptionalCharacteristic of the Lightbulb service? HAP-NodeJS seems to have added this manually.
The Accessory module calculates AID like this:
acc.aid = len(self.accessories) + 2
However if you have previously deleted and created a new AID, the max number of that AID is higher, so it should be:
if len(self.accessories)>0:
acc.aid = max(self.accessories) + 1
else:
acc.aid = 2
I was thinking if it would make sense to add a new pypi project: HAP-python-min
. This would include all content from pyhap
except for the accessories
folder.
I'm currently working on a component for Home Assistant. Some of the examples form the accessories folder may work, but to port of all functions is basically more difficult than to write a new class which extends accessory
. Because the initial code base for Home Assistant is already big their is a focus to keep additional imports as small as possible to improve performance. A min
version would help accomplish that.
I found a small issue with removing accessory.
When I try to remove it I takes very long time sometimes iPhone get stuck and in console there is this error and accessory.pickle is not removed
I found that there is # TODO: can we have more than one connection?
in my home I have AppleTV as HUB and another iPhone with I'm sharing my home.
Dont worry to ask me with some help :-)
Maybe it could be a good idea to create a dev
branch for this project.
All PR's would be against the dev branch and later merged with one PR to master.
This would allow for easier maintenance of other projects using HAP-Python
with the added benefit of a rough change log for each new version in the master branch.
do you have an example for identify?
Is possible ?
This should propagate in AccessoryDriver.set-characteristics
. FYI @thomaspurchas @cdce8p
Wanted to open an issue to discuss this PR.
We should get some Sphinx (or some other auto-generated documentation) in place now that HA is starting to pick up interest. This will help a lot with new people.
Some basic stuff has been added, wanted to get feedback before I went too deep down the rabbit hole.
It's setup on the ReadTheDocs theme.
I noticed that in all the example accessories there is always some sort of polling loop updating a char value
.
I think it would be good to add a getter_callback
(in addition to the setter_callback
) that is called when an iOS client requests a char value
. Then the request can trigger a sensor poll, returning the most recent data, updating the local value
, and sending any notifications.
This would also remove the need for an explicit polling loop in most cases.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.