Skip to content

Commit fdc2c55

Browse files
authored
v2.8.1
2 parents 4973c12 + 2b2e4f3 commit fdc2c55

5 files changed

Lines changed: 83 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Sections
1616
### Developers
1717
-->
1818

19+
## [2.8.1] - 2020-04-06
20+
21+
### Fixed
22+
- Fix an issue where reading just one byte at the beginning of a block can crash the connection. [#235](https://github.com/ikalchev/HAP-python/pull/235)
23+
- Improve camera accessory integration. [#231](https://github.com/ikalchev/HAP-python/pull/231)
24+
1925
## [2.8.0] - 2020-04-02
2026

2127
### Added

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,68 @@ class TemperatureSensor(Accessory):
109109
print('Stopping accessory.')
110110
```
111111

112+
## Service Callbacks
113+
114+
When you are working with tightly coupled characteristics such as "On" and "Brightness,"
115+
you may need to use a service callback to receive all changes in a single request.
116+
117+
With characteristic callbacks, you do now know that a "Brightness" characteristic is
118+
about to be processed right after an "On" and may end up setting a LightBulb to 100%
119+
and then dim it back down to the expected level.
120+
121+
```python
122+
from pyhap.accessory import Accessory
123+
from pyhap.const import Category
124+
import pyhap.loader as loader
125+
126+
class Light(Accessory):
127+
"""Implementation of a mock light accessory."""
128+
129+
category = Category.CATEGORY_LIGHTBULB # This is for the icon in the iOS Home app.
130+
131+
def __init__(self, *args, **kwargs):
132+
"""Here, we just store a reference to the on and brightness characteristics and
133+
add a method that will be executed every time their value changes.
134+
"""
135+
# If overriding this method, be sure to call the super's implementation first.
136+
super().__init__(*args, **kwargs)
137+
138+
# Add the services that this Accessory will support with add_preload_service here
139+
serv_light = self.add_preload_service('Lightbulb')
140+
self.char_on = serv_light.configure_char('On', value=self._state)
141+
self.char_brightness = serv_light.configure_char('Brightness', value=100)
142+
143+
serv_light.setter_callback = self._set_chars
144+
145+
def _set_chars(self, char_values):
146+
"""This will be called every time the value of the on of the
147+
characteristics on the service changes.
148+
"""
149+
if "On" in char_values:
150+
print('On changed to: ', char_values["On"])
151+
if "Brightness" in char_values:
152+
print('Brightness changed to: ', char_values["Brightness"])
153+
154+
@Acessory.run_at_interval(3) # Run this method every 3 seconds
155+
# The `run` method can be `async` as well
156+
def run(self):
157+
"""We override this method to implement what the accessory will do when it is
158+
started.
159+
160+
We set the current temperature to a random number. The decorator runs this method
161+
every 3 seconds.
162+
"""
163+
self.char_on.set_value(random.randint(0, 1))
164+
self.char_brightness.set_value(random.randint(1, 100))
165+
166+
# The `stop` method can be `async` as well
167+
def stop(self):
168+
"""We override this method to clean up any resources or perform final actions, as
169+
this is called by the AccessoryDriver when the Accessory is being stopped.
170+
"""
171+
print('Stopping accessory.')
172+
```
173+
112174
## Setting up a camera <a name="Camera"></a>
113175

114176
The [Camera accessory](pyhap/camera.py) implements the HomeKit Protocol for negotiating stream settings,

pyhap/camera.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def __init__(self, options, *args, **kwargs):
425425
self.add_preload_service('Microphone')
426426
management = self.add_preload_service('CameraRTPStreamManagement')
427427
management.configure_char('StreamingStatus',
428-
getter_callback=self._get_streaimg_status)
428+
getter_callback=self._get_streaming_status)
429429
management.configure_char('SupportedRTPConfiguration',
430430
value=self.get_supported_rtp_config(
431431
options.get('srtp', False)))
@@ -484,7 +484,7 @@ async def _start_stream(self, objs, reconfigure): # pylint: disable=unused-argu
484484
if video_rtp_param:
485485
video_rtp_param_objs = tlv.decode(video_rtp_param)
486486
# TODO: Optionals, handle the case where they are missing
487-
opts['v_ssrc'] = 1 or struct.unpack('<I',
487+
opts['v_ssrc'] = struct.unpack('<I',
488488
video_rtp_param_objs.get(
489489
RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE']))[0]
490490
opts['v_payload_type'] = \
@@ -539,7 +539,7 @@ async def _start_stream(self, objs, reconfigure): # pylint: disable=unused-argu
539539
del self.sessions[session_id]
540540
self.streaming_status = STREAMING_STATUS['AVAILABLE']
541541

542-
def _get_streaimg_status(self):
542+
def _get_streaming_status(self):
543543
"""Get the streaming status in TLV format.
544544
545545
Called when iOS reads the StreaminStatus ``Characteristic``.
@@ -667,9 +667,8 @@ def set_endpoints(self, value):
667667
video_srtp_tlv = NO_SRTP
668668
audio_srtp_tlv = NO_SRTP
669669

670-
# TODO: Use os.urandom(4) but within the allowed value bounds
671-
video_ssrc = b'\x01'
672-
audio_ssrc = b'\x01'
670+
video_ssrc = int.from_bytes(os.urandom(3), byteorder="big")
671+
audio_ssrc = int.from_bytes(os.urandom(3), byteorder="big")
673672

674673
res_address_tlv = tlv.encode(
675674
SETUP_ADDR_INFO['ADDRESS_VER'], self.stream_address_isv6,
@@ -692,9 +691,9 @@ def set_endpoints(self, value):
692691
'address': address,
693692
'v_port': target_video_port,
694693
'v_srtp_key': to_base64_str(video_master_key + video_master_salt),
695-
# TODO: 'v_ssrc': video_ssrc,
694+
'v_ssrc': video_ssrc,
696695
'a_port': target_audio_port,
697-
'audio_srtp_key': to_base64_str(audio_master_key + audio_master_salt),
696+
'a_srtp_key': to_base64_str(audio_master_key + audio_master_salt),
698697
'a_ssrc': audio_ssrc
699698
}
700699

pyhap/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""This module contains constants used by other modules."""
22
MAJOR_VERSION = 2
33
MINOR_VERSION = 8
4-
PATCH_VERSION = 0
4+
PATCH_VERSION = 1
55
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
66
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
77
REQUIRED_PYTHON_VER = (3, 5)

pyhap/hap_server.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -709,11 +709,10 @@ def _wrapper(self, *args, **kwargs):
709709
return func(self, *args, **kwargs)
710710
return _wrapper
711711

712-
def recv_into(self, buffer, nbytes=1042, flags=0):
712+
def recv_into(self, buffer, nbytes=None, flags=0):
713713
"""Receive and decrypt up to nbytes in the given buffer."""
714-
data = self.recv(nbytes, flags)
715-
for i, b in enumerate(data):
716-
buffer[i] = b
714+
data = self.recv(nbytes or len(buffer), flags)
715+
buffer[:len(data)] = data
717716
return len(data)
718717

719718
def recv(self, buflen=1042, flags=0):
@@ -734,11 +733,12 @@ def recv(self, buflen=1042, flags=0):
734733
# It may be that we already read some data and we have
735734
# 1 byte left, return whatever we have.
736735
return result
737-
block_length_bytes = self.socket.recv(self.LENGTH_LENGTH)
736+
# Always wait for a full block to arrive
737+
block_length_bytes = self.socket.recv(
738+
self.LENGTH_LENGTH, socket.MSG_WAITALL
739+
)
738740
if not block_length_bytes:
739741
return result
740-
# TODO: handle this
741-
assert len(block_length_bytes) == self.LENGTH_LENGTH
742742
# Init. info about the block we just started.
743743
# Note we are setting the total length to block_length + mac length
744744
self.curr_in_total = \

0 commit comments

Comments
 (0)