Git Product home page Git Product logo

hap-python's Introduction

PyPI version Build Status codecov Python Versions Documentation Status Downloads

HAP-python

HomeKit Accessory Protocol implementation in python 3. With this project, you can integrate your own smart devices and add them to your iOS Home app. Since Siri is integrated with the Home app, you can start voice-control your accessories right away.

Main features:

  • Camera - HAP-python supports the camera accessory from version 2.3.0!
  • asyncio support - You can run various tasks or accessories in the event loop.
  • Out of the box support for Apple-defined services - see them in the resources folder.
  • Secure pairing by just scanning the QR code.
  • Integrated with the home automation framework Home Assistant.

The project was developed for a Raspberry Pi, but it should work on other platforms. To kick-start things, you can open main.py or busy_home.py, where you will find some fake accessories. Just run one of them, for example python3 busy_home.py, and you can add it in the Home app (be sure to be in the same network). Stop it by hitting Ctrl+C.

There are example accessories as well as integrations with real products in the accessories folder. See how to configure your camera in camera_main.py.

Table of Contents

  1. API
  2. Installation
  3. Setting up a camera
  4. Run at boot (and a Switch to shutdown your device)
  5. Notice

Installation

As of version 3.5.1, HAP-python no longer supports python older than 3.6, because we are moving to asyncio. If your platform does not have a compatible python out of the box, you can install it manually or just use an older version of HAP-python.

As a prerequisite, you will need Avahi/Bonjour installed (due to zeroconf package). On a Raspberry Pi, you can get it with:

$ sudo apt-get install libavahi-compat-libdnssd-dev

avahi-utils may also fit the bill. Then, you can install with pip3 (you will need sudo or --user for the install):

$ pip3 install HAP-python[QRCode]

This will install HAP-python in your python packages, so that you can import it as pyhap. To uninstall, just do:

$ pip3 uninstall HAP-python

API

A typical flow for using HAP-python starts with implementing an Accessory. This is done by subclassing Accessory and putting in place a few details (see below). After that, you give your accessory to an AccessoryDriver to manage. This will take care of advertising it on the local network, setting a HAP server and running the Accessory. Take a look at main.py for a quick start on that.

from pyhap.accessory import Accessory, Category
import pyhap.loader as loader

class TemperatureSensor(Accessory):
    """Implementation of a mock temperature sensor accessory."""

    category = Category.SENSOR  # This is for the icon in the iOS Home app.

    def __init__(self, *args, **kwargs):
        """Here, we just store a reference to the current temperature characteristic and
        add a method that will be executed every time its value changes.
        """
        # If overriding this method, be sure to call the super's implementation first.
        super().__init__(*args, **kwargs)

        # Add the services that this Accessory will support with add_preload_service here
        temp_service = self.add_preload_service('TemperatureSensor')
        self.temp_char = temp_service.get_characteristic('CurrentTemperature')

        # Having a callback is optional, but you can use it to add functionality.
        self.temp_char.setter_callback = self.temperature_changed

    def temperature_changed(self, value):
        """This will be called every time the value of the CurrentTemperature
        is changed. Use setter_callbacks to react to user actions, e.g. setting the
        lights On could fire some GPIO code to turn on a LED (see pyhap/accessories/LightBulb.py).
        """
        print('Temperature changed to: ', value)

    @Accessory.run_at_interval(3)  # Run this method every 3 seconds
    # The `run` method can be `async` as well
    def run(self):
        """We override this method to implement what the accessory will do when it is
        started.

        We set the current temperature to a random number. The decorator runs this method
        every 3 seconds.
        """
        self.temp_char.set_value(random.randint(18, 26))

    # The `stop` method can be `async` as well
    def stop(self):
        """We override this method to clean up any resources or perform final actions, as
        this is called by the AccessoryDriver when the Accessory is being stopped.
        """
        print('Stopping accessory.')

Service Callbacks

When you are working with tightly coupled characteristics such as "On" and "Brightness," you may need to use a service callback to receive all changes in a single request.

With characteristic callbacks, you do now know that a "Brightness" characteristic is about to be processed right after an "On" and may end up setting a LightBulb to 100% and then dim it back down to the expected level.

from pyhap.accessory import Accessory
from pyhap.const import Category
import pyhap.loader as loader

class Light(Accessory):
    """Implementation of a mock light accessory."""

    category = Category.CATEGORY_LIGHTBULB  # This is for the icon in the iOS Home app.

    def __init__(self, *args, **kwargs):
        """Here, we just store a reference to the on and brightness characteristics and
        add a method that will be executed every time their value changes.
        """
        # If overriding this method, be sure to call the super's implementation first.
        super().__init__(*args, **kwargs)

        # Add the services that this Accessory will support with add_preload_service here
        serv_light = self.add_preload_service('Lightbulb')
        self.char_on = serv_light.configure_char('On', value=self._state)
        self.char_brightness = serv_light.configure_char('Brightness', value=100)

        serv_light.setter_callback = self._set_chars

    def _set_chars(self, char_values):
        """This will be called every time the value of the on of the
        characteristics on the service changes.
        """
        if "On" in char_values:
            print('On changed to: ', char_values["On"])
        if "Brightness" in char_values:
            print('Brightness changed to: ', char_values["Brightness"])

    @Accessory.run_at_interval(3)  # Run this method every 3 seconds
    # The `run` method can be `async` as well
    def run(self):
        """We override this method to implement what the accessory will do when it is
        started.

        We set the current temperature to a random number. The decorator runs this method
        every 3 seconds.
        """
        self.char_on.set_value(random.randint(0, 1))
        self.char_brightness.set_value(random.randint(1, 100))

    # The `stop` method can be `async` as well
    def stop(self):
        """We override this method to clean up any resources or perform final actions, as
        this is called by the AccessoryDriver when the Accessory is being stopped.
        """
        print('Stopping accessory.')

Setting up a camera

The Camera accessory implements the HomeKit Protocol for negotiating stream settings, such as the picture width and height, number of audio channels and others. Starting a video and/or audio stream is very platform specific. Because of this, you need to figure out what video and audio settings your camera supports and set them in the options parameter that is passed to the Camera Accessory. Refer to the documentation for the Camera contructor for the settings you need to specify.

By default, HAP-python will execute the ffmpeg command with the negotiated parameters when the stream should be started and will terminate the started process when the stream should be stopped (see the default: Camera.FFMPEG_CMD). If the default command is not supported or correctly formatted for your platform, the streaming can fail.

For these cases, HAP-python has hooks so that you can insert your own command or implement the logic for starting or stopping the stream. There are two options:

  1. Pass your own command that will be executed when the stream should be started.

    You pass the command as a value to the key start_stream_cmd in the options parameter to the constuctor of the Camera Accessory. The command is formatted using the negotiated stream configuration parameters. For example, if the negotiated width is 640 and you pass foo start -width {width}, the command will be formatted as foo start -width 640.

    The full list of negotiated stream configuration parameters can be found in the documentation for the Camera.start method.

  2. Implement your own logic to start, stop and reconfigure the stream.

    If you need more flexibility in managing streams, you can directly implement the Camera methods start, stop and reconfigure. Each will be called when the stream should be respectively started, stopped or reconfigured. The start and reconfigure methods are given the negotiated stream configuration parameters.

    Have a look at the documentation of these methods for more information.

Finally, if you can take snapshots from the camera, you may want to implement the Camera.snapshot method. By default, this serves a stock photo.

Run at boot

This is a quick way to get HAP-python to run at boot on a Raspberry Pi. It is recommended to turn on "Wait for network" in raspi-config. If this turns to be unreliable, see this.

Copy the below in /etc/systemd/system/HAP-python.service (needs sudo).

[Unit]
Description = HAP-python daemon
Wants = pigpiod.service  # Remove this if you don't depend on pigpiod
After = local-fs.target network-online.target pigpiod.service

[Service]
User = lesserdaemon  # It's a good idea to use some unprivileged system user
# Script starting HAP-python, e.g. main.py
# Be careful to set any paths you use, e.g. for persisting the state.
ExecStart = /usr/bin/python3 /home/lesserdaemon/.hap-python/hap-python.py

[Install]
WantedBy = multi-user.target

Test that everything is fine by doing:

> sudo systemctl start HAP-python
> systemctl status HAP-python
> sudo journalctl -u HAP-python  # to see the output of the start up script.
> sudo systemctl stop HAP-python

To enable or disable at boot, do:

> sudo systemctl enable HAP-python
> sudo systemctl disable HAP-python

Shutdown switch

If you are running HAP-python on a Raspberry Pi, you may want to add a Shutdown Switch to your Home. This is a Switch Accessory, which, when triggered, executes sudo shutdown -h now, i.e. it shutdowns and halts the Pi. This allows you to safely unplug it.

For the above to work, you need to enable passwordless /sbin/shutdown to whichever user is running HAP-python. For example, do:

$ sudo visudo # and add the line: "<hap-user> ALL=NOPASSWD: /sbin/shutdown".

Notice

Some HAP know-how was taken from HAP-NodeJS by KhaosT.

I am not aware of any bugs, but I am more than confident that such exist. If you find any, please report and I will try to fix them.

Suggestions are always welcome.

Have fun!

hap-python's People

Contributors

adam-langley avatar adrum avatar akx avatar balloob avatar bdraco avatar bdsoha avatar bzzeke avatar cdce8p avatar ikalchev avatar iriz avatar jdsnape avatar jerr0328 avatar jerryc05 avatar jslay88 avatar kormax avatar lascabos avatar lmarlow avatar maximkulkin avatar mback2k avatar qqaatw avatar sanvd0000 avatar schinckel avatar spazm avatar thehappydinoa avatar vitkabele avatar whooo avatar xdissent avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hap-python's Issues

Accessory calculates AID incorrectly

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

Garage Door Opener

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

Implement optional characteristics

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

Implement Characteristic.set_value callback toggle.

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.

  • I can set the values of the lights for Homekit directly from my code using service.characteristic.set_value and this works no problem.
  • I need to be notified about lights change requests from a Homekit user so that I can reflect that on the actual device. Added a setter_callback which pushes the requested change to my code (which then applies it to the physical light) and this works no problem

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?

AccessoryService information.

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.

Remove accessory is not working properly

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

screenshots:
screen shot 2017-11-29 at 19 39 16

before remove:
img_0937

after remove:
img_0938

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 :-)

Sphinx Documentation

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.

Make AccessoryEncoder easier to subclass.

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.

Suggestion: HAP-python-min

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.

Persist char. values on accessory driver stop

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).

Amended commits

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

More Sphinx Work

Putting this issue here to track progress on the Sphinx Documentation.

Todo:

  • We will need to go through more of the project and switch over to sphinx docstrings. I only hit Accessory, Bridge, and AccessoryDriver.
  • Ubuntu LTS Installation Pre-Requisites.
  • More things, please comment for what you see missing.
  • intro/examples.rst
  • intro/tutorial.rst
  • intro/overview.rst

Dynamic pin code and setup IDs.

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.

Race condition when setting timeout for send event; Document Characteristic.setter_callback

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?

[Discussion] Alternative definition of accessories.

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.

Async support

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.

Possible problem in iOS 11

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

self.broker.publish -> self.broker == None

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

Suggestion: Dev branch for repo

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.

Global_setter for accessories

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.

i'm making LightBulb with mqtt and need to get state of device

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()
        

Support for HAP included services

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.

Accessory Object parameters

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:

main.py

acc = TemperatureSensor(display_name='Temperatur')

TemperatureSensor(Accessory)

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.

Pairing not initiating/completing.

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.

img_0283

I've turned logging up to DEBUG, and I don't see the pairing being initiated at all.

Pull latest Apple HAP types. Define overrides.

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

Add deprecated chars and services

The current version removes deprecated Apple-defined characteristics and services. These should be available somewhere for people using an older iOS.

Way to add optional chars. to services in Http Accessory

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.

Generate QR Code (and X-HM:// url)

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.

Enforce value constraints in Characteristic. Distinguish raw and HAP value.

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

[Feature Request] getter callback

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.

Allow characteristic properties like minValue to be overridden.

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".

Thread safety in set_value

@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?

Garage Door Opener

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()

~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.