Skip to content

Commit

Permalink
Fix pairing url generator
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Jan 30, 2021
1 parent 96fb1aa commit 666bd97
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 79 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
- push
- pull_request

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8]

steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Test with tox
run: tox
- name: Lint with tox
run: TOXENV=lint tox
- name: pylint with tox
run: TOXENV=pylint tox
- name: Docs with tox
run: TOXENV=docs tox
124 changes: 75 additions & 49 deletions pyhap/accessory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
"""Module for the Accessory classes."""
import itertools
import logging
import struct

from pyhap import util, SUPPORT_QR_CODE
from pyhap.const import (
STANDALONE_AID, HAP_REPR_AID, HAP_REPR_IID, HAP_REPR_SERVICES,
HAP_REPR_VALUE, CATEGORY_OTHER, CATEGORY_BRIDGE)
STANDALONE_AID,
HAP_REPR_AID,
HAP_REPR_IID,
HAP_REPR_SERVICES,
HAP_REPR_VALUE,
CATEGORY_OTHER,
CATEGORY_BRIDGE,
)
from pyhap.iid_manager import IIDManager

if SUPPORT_QR_CODE:
Expand Down Expand Up @@ -51,13 +56,14 @@ def __init__(self, driver, display_name, aid=None):
def __repr__(self):
"""Return the representation of the accessory."""
services = [s.display_name for s in self.services]
return "<accessory display_name='{}' services={}>" \
.format(self.display_name, services)
return "<accessory display_name='{}' services={}>".format(
self.display_name, services
)

def __getstate__(self):
state = self.__dict__.copy()
state['driver'] = None
state['run_sentinel'] = None
state["driver"] = None
state["run_sentinel"] = None
return state

def _set_services(self):
Expand Down Expand Up @@ -85,29 +91,31 @@ def add_info_service(self):
Called in `__init__` to be sure that it is the first service added.
May be overridden.
"""
serv_info = self.driver.loader.get_service('AccessoryInformation')
serv_info.configure_char('Name', value=self.display_name)
serv_info.configure_char('SerialNumber', value='default')
serv_info = self.driver.loader.get_service("AccessoryInformation")
serv_info.configure_char("Name", value=self.display_name)
serv_info.configure_char("SerialNumber", value="default")
self.add_service(serv_info)

def set_info_service(self, firmware_revision=None, manufacturer=None,
model=None, serial_number=None):
def set_info_service(
self, firmware_revision=None, manufacturer=None, model=None, serial_number=None
):
"""Quick assign basic accessory information."""
serv_info = self.get_service('AccessoryInformation')
serv_info = self.get_service("AccessoryInformation")
if firmware_revision:
serv_info.configure_char(
'FirmwareRevision', value=firmware_revision)
serv_info.configure_char("FirmwareRevision", value=firmware_revision)
if manufacturer:
serv_info.configure_char('Manufacturer', value=manufacturer)
serv_info.configure_char("Manufacturer", value=manufacturer)
if model:
serv_info.configure_char('Model', value=model)
serv_info.configure_char("Model", value=model)
if serial_number:
if len(serial_number) >= 1:
serv_info.configure_char('SerialNumber', value=serial_number)
serv_info.configure_char("SerialNumber", value=serial_number)
else:
logger.warning(
"Couldn't add SerialNumber for %s. The SerialNumber must "
"be at least one character long.", self.display_name)
"be at least one character long.",
self.display_name,
)

def add_preload_service(self, service, chars=None):
"""Create a service with the given name and add it to this acc."""
Expand All @@ -123,8 +131,7 @@ def add_preload_service(self, service, chars=None):
def set_primary_service(self, primary_service):
"""Set the primary service of the acc."""
for service in self.services:
service.is_primary_service = service.type_id == \
primary_service.type_id
service.is_primary_service = service.type_id == primary_service.type_id

def config_changed(self):
"""Notify the accessory about configuration changes.
Expand All @@ -142,7 +149,8 @@ def config_changed(self):
Deprecated. Use `driver.config_changed()` instead.
"""
logger.warning(
'This method is now deprecated. Use \'driver.config_changed\' instead.')
"This method is now deprecated. Use 'driver.config_changed' instead."
)
self.driver.config_changed()

def add_service(self, *servs):
Expand Down Expand Up @@ -184,24 +192,27 @@ def xhm_uri(self):
:rtype: str
"""
buffer = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00')
payload = 0
payload |= 0 & 0x7 # version

value_low = int(self.driver.state.pincode.replace(b'-', b''), 10)
value_low |= 1 << 28
struct.pack_into('>L', buffer, 4, value_low)
payload <<= 4
payload |= 0 & 0xF # reserved bits

if self.category == CATEGORY_OTHER:
buffer[4] = buffer[4] | 1 << 7
payload <<= 8
payload |= self.category & 0xFF # category

value_high = self.category >> 1
struct.pack_into('>L', buffer, 0, value_high)
payload <<= 4
payload |= 2 & 0xF # flags

encoded_payload = base36.dumps(
struct.unpack_from('>L', buffer, 4)[0] +
(struct.unpack_from('>L', buffer, 0)[0] * (1 << 32))).upper()
encoded_payload = encoded_payload.rjust(9, '0')
payload <<= 27
payload |= (
int(self.driver.state.pincode.replace(b"-", b""), 10) & 0x7FFFFFFF
) # pincode

return 'X-HM://' + encoded_payload + self.driver.state.setup_id
encoded_payload = base36.dumps(payload).upper()
encoded_payload = encoded_payload.rjust(9, "0")

return "X-HM://" + encoded_payload + self.driver.state.setup_id

def get_characteristic(self, aid, iid):
"""Get the characteristic for the given IID.
Expand Down Expand Up @@ -244,17 +255,27 @@ def setup_message(self):
pincode = self.driver.state.pincode.decode()
if SUPPORT_QR_CODE:
xhm_uri = self.xhm_uri()
print('Setup payload: {}'.format(xhm_uri), flush=True)
print('Scan this code with your HomeKit app on your iOS device:',
flush=True)
print("Setup payload: {}".format(xhm_uri), flush=True)
print(
"Scan this code with your HomeKit app on your iOS device:", flush=True
)
print(QRCode(xhm_uri).terminal(quiet_zone=2), flush=True)
print('Or enter this code in your HomeKit app on your iOS device: '
'{}'.format(pincode), flush=True)
print(
"Or enter this code in your HomeKit app on your iOS device: "
"{}".format(pincode),
flush=True,
)
else:
print('To use the QR Code feature, use \'pip install '
'HAP-python[QRCode]\'', flush=True)
print('Enter this code in your HomeKit app on your iOS device: {}'
.format(pincode), flush=True)
print(
"To use the QR Code feature, use 'pip install " "HAP-python[QRCode]'",
flush=True,
)
print(
"Enter this code in your HomeKit app on your iOS device: {}".format(
pincode
),
flush=True,
)

@staticmethod
def run_at_interval(seconds):
Expand All @@ -272,14 +293,16 @@ def run(self):
Determines the interval on which the decorated method will be called.
:type seconds: float
"""

def _repeat(func):
async def _wrapper(self, *args):
while True:
await self.driver.async_add_job(func, self, *args)
if await util.event_wait(
self.driver.aio_stop_event, seconds):
if await util.event_wait(self.driver.aio_stop_event, seconds):
break

return _wrapper

return _repeat

async def run(self):
Expand Down Expand Up @@ -351,8 +374,11 @@ def add_accessory(self, acc):

if acc.aid is None:
# For some reason AID=7 gets unsupported. See issue #61
acc.aid = next(aid for aid in itertools.count(2)
if aid != 7 and aid not in self.accessories)
acc.aid = next(
aid
for aid in itertools.count(2)
if aid != 7 and aid not in self.accessories
)
elif acc.aid == self.aid or acc.aid in self.accessories:
raise ValueError("Duplicate AID found when attempting to add accessory")

Expand Down Expand Up @@ -389,4 +415,4 @@ async def stop(self):


def get_topic(aid, iid):
return str(aid) + '.' + str(iid)
return str(aid) + "." + str(iid)
1 change: 0 additions & 1 deletion pyhap/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@


FFMPEG_CMD = (
# pylint: disable=bad-continuation
'ffmpeg -re -f avfoundation -framerate {fps} -i 0:0 -threads 0 '
'-vcodec libx264 -an -pix_fmt yuv420p -r {fps} -f rawvideo -tune zerolatency '
'-vf scale={width}:{height} -b:v {v_max_bitrate}k -bufsize {v_max_bitrate}k '
Expand Down
113 changes: 113 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
[tool.black]
target-version = ["py37", "py38"]
exclude = 'generated'

[tool.isort]
# https://github.com/PyCQA/isort/wiki/isort-Settings
profile = "black"
# will group `import x` and `from x import` of the same module.
force_sort_within_sections = true
known_first_party = [
"pyhap",
]
combine_as_imports = true

[tool.pylint.MASTER]
ignore = [
"tests",
]
# Use a conservative default here; 2 should speed up most setups and not hurt
# any too bad. Override on command line as appropriate.
# Disabled for now: https://github.com/PyCQA/pylint/issues/3584
#jobs = 2
load-plugins = [
"pylint_strict_informational",
]
persistent = false
extension-pkg-whitelist = [
"ciso8601",
"cv2",
]

[tool.pylint.BASIC]
good-names = [
"_",
"ev",
"ex",
"fp",
"i",
"id",
"j",
"k",
"Run",
"T",
]

[tool.pylint."MESSAGES CONTROL"]
# Reasons disabled:
# format - handled by black
# locally-disabled - it spams too much
# duplicate-code - unavoidable
# cyclic-import - doesn't test if both import on load
# abstract-class-little-used - prevents from setting right foundation
# unused-argument - generic callbacks and setup methods create a lot of warnings
# too-many-* - are not enforced for the sake of readability
# too-few-* - same as too-many-*
# abstract-method - with intro of async there are always methods missing
# inconsistent-return-statements - doesn't handle raise
# too-many-ancestors - it's too strict.
# wrong-import-order - isort guards this
disable = [
"format",
"abstract-class-little-used",
"abstract-method",
"cyclic-import",
"duplicate-code",
"inconsistent-return-statements",
"locally-disabled",
"not-context-manager",
"too-few-public-methods",
"too-many-ancestors",
"too-many-arguments",
"too-many-branches",
"too-many-instance-attributes",
"too-many-lines",
"too-many-locals",
"too-many-public-methods",
"too-many-return-statements",
"too-many-statements",
"too-many-boolean-expressions",
"unused-argument",
"wrong-import-order",
]
enable = [
#"useless-suppression", # temporarily every now and then to clean them up
"use-symbolic-message-instead",
]

[tool.pylint.REPORTS]
score = false

[tool.pylint.TYPECHECK]
ignored-classes = [
"_CountingAttr", # for attrs
]

[tool.pylint.FORMAT]
expected-line-ending-format = "LF"

[tool.pylint.EXCEPTIONS]
overgeneral-exceptions = [
"BaseException",
"Exception",
"HomeAssistantError",
]

[tool.pytest.ini_options]
testpaths = [
"tests",
]
norecursedirs = [
".git",
"testing_config",
]
2 changes: 2 additions & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
base36
flake8
flake8-docstrings
pydocstyle
pylint
pytest
pytest-cov
pytest-timeout>=1.2.1
pyqrcode
tox
Loading

0 comments on commit 666bd97

Please sign in to comment.