Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1.1.1 #53

Merged
merged 27 commits into from Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -7,7 +7,8 @@ python:
sudo: required
dist: xenial
install: pip install -U tox-travis coveralls
script: tox
script:
- tox
after_success:
- coveralls
deploy:
Expand Down
4 changes: 3 additions & 1 deletion .vscode/launch.json
Expand Up @@ -9,7 +9,9 @@
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "C:\\Users\\saxon\\GitHub\\mattsaxon\\pysonofflan\\"}
}
]
}
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Expand Up @@ -121,7 +121,7 @@ A reminder for the maintainers on how to deploy.
Make sure all your changes are committed (including an entry in HISTORY.rst).
Then run::

$ bumpversion patch # possible: major / minor / patch
$ bump2version patch # possible: major / minor / patch
$ git push
$ git push --tags

Expand Down
6 changes: 6 additions & 0 deletions HISTORY.rst
@@ -1,6 +1,12 @@
History
=======

1.1.1 (2020-01-02)
------------------

* Optimisations to deal with later zeroconf versions which have some different behaviour
* Improved error handling of unexpected errors

1.1.0 (2020-01-10)
------------------

Expand Down
2 changes: 1 addition & 1 deletion pysonofflanr3/__init__.py
Expand Up @@ -50,7 +50,7 @@ async def state_callback(device):

__author__ = "Matt Saxon"
__email__ = "saxonmatt@hotmail.com"
__version__ = "1.1.0"
__version__ = '1.1.1'
__url__ = "https://github.com/mattsaxon/pysonofflan"

# flake8: noqa
Expand Down
105 changes: 59 additions & 46 deletions pysonofflanr3/client.py
Expand Up @@ -18,7 +18,7 @@ class SonoffLANModeClient:

Uses protocol as was documented documented by Itead

This documented has since been unpublished
This document has since been unpublished
"""

"""
Expand All @@ -33,6 +33,7 @@ class SonoffLANModeClient:
DEFAULT_PING_INTERVAL = 5
SERVICE_TYPE = "_ewelink._tcp.local."

# only a single zeroconf instance for all instances of this class
zeroconf = Zeroconf()

def __init__(
Expand Down Expand Up @@ -63,7 +64,7 @@ def __init__(
self.last_request = None
self.encrypted = False
self.type = None

self._info_cache = None
self._last_params = {"switch": "off"}

if self.logger is None:
Expand Down Expand Up @@ -102,9 +103,6 @@ def add_service(self, zeroconf, type, name):
self.logger.debug("Service %s added (again)" % name)
self.my_service_name = None

# else:
# self.logger.debug("Service %s added (not our switch)" % name)

if self.my_service_name is None:

info = zeroconf.get_service_info(type, name)
Expand Down Expand Up @@ -140,6 +138,7 @@ def add_service(self, zeroconf, type, name):
)

# listen for updates to the specific device
# (needed for zerconf 0.23.0, 0.24.0, fixed in later versions)
self.service_browser = \
ServiceBrowser(zeroconf, name, listener=self)

Expand All @@ -160,6 +159,7 @@ def add_service(self, zeroconf, type, name):
)

# self.http_session.headers.update(headers)
# needed to keep headers in same order
self.http_session.headers = headers

# find socket for end-point
Expand Down Expand Up @@ -187,58 +187,71 @@ def add_service(self, zeroconf, type, name):

def update_service(self, zeroconf, type, name):

# This is only needed for zderoconfg 0.24.1 to 0.24.4
if self.my_service_name == name:
# This is needed for zeroconfg 0.24.1
# onwards as updates come to the parent node
if self.my_service_name != name:
return

try:
info = zeroconf.get_service_info(type, name)
self.logger.debug("properties: %s", info.properties)
try:
info = zeroconf.get_service_info(type, name)

self.type = info.properties.get(b"type")
self.logger.debug("type: %s", self.type)
# This is useful optimsation for 0.24.1 onwards
# as multiple updates that are the same are received
if info.properties == self._info_cache:
self.logger.debug("same update received for device: %s", name)
return
else:
self._info_cache = info.properties

data1 = info.properties.get(b"data1")
data2 = info.properties.get(b"data2")
self.logger.debug("properties: %s", info.properties)

if data2 is not None:
data1 += data2
data3 = info.properties.get(b"data3")
self.type = info.properties.get(b"type")
self.logger.debug("type: %s", self.type)

if data3 is not None:
data1 += data3
data4 = info.properties.get(b"data4")
data1 = info.properties.get(b"data1")
data2 = info.properties.get(b"data2")

if data4 is not None:
data1 += data4
if data2 is not None:
data1 += data2
data3 = info.properties.get(b"data3")

if info.properties.get(b"encrypt"):
self.encrypted = True
# decrypt the message
iv = info.properties.get(b"iv")
data = sonoffcrypto.decrypt(data1, iv, self.api_key)
self.logger.debug("decrypted data: %s", data)
if data3 is not None:
data1 += data3
data4 = info.properties.get(b"data4")

else:
self.encrypted = False
data = data1
if data4 is not None:
data1 += data4

self.properties = info.properties
if info.properties.get(b"encrypt"):
self.encrypted = True
# decrypt the message
iv = info.properties.get(b"iv")
data = sonoffcrypto.decrypt(data1, iv, self.api_key)
self.logger.debug("decrypted data: %s", data)

# process the events on an event loop
# this method is on a background thread called from zeroconf
asyncio.run_coroutine_threadsafe(
self.event_handler(data), self.loop)
else:
self.encrypted = False
data = data1

except Exception as ex:
self.logger.error(
"Error updating service for device %s: %s,"
" probably wrong API key: %s",
self.device_id,
format(ex), name
)
self.properties = info.properties

else:
self.logger.debug("Service %s updated (not our switch)" % name)
# process the events on an event loop
# this method is on a background thread called from zeroconf
asyncio.run_coroutine_threadsafe(
self.event_handler(data), self.loop)

except ValueError as ex:
self.logger.error(
"Error updating service for device %s: %s"
" Probably wrong API key.",
self.device_id, format(ex)
)

except Exception as ex:
self.logger.error(
"Error updating service for device %s: %s, %s",
self.device_id, format(ex), traceback.format_exc()
)

def retry_connection(self):

Expand Down Expand Up @@ -267,7 +280,7 @@ def retry_connection(self):
"Retry_connection() Unexpected error for device %s: %s %s",
self.device_id,
format(ex),
traceback.format_exc,
traceback.format_exc(),
)
break

Expand Down
1 change: 0 additions & 1 deletion pysonofflanr3/sonoffcrypto.py
Expand Up @@ -34,7 +34,6 @@ def format_encryption_msg(payload, api_key, data):
payload["encrypt"] = True

if data is None:
# data["data"] = encrypt("{ }", iv)
payload["data"] = ""
else:
payload["data"] = \
Expand Down
6 changes: 3 additions & 3 deletions pysonofflanr3/sonoffdevice.py
Expand Up @@ -250,7 +250,7 @@ async def send_updated_params_loop(self):
"Unexpected error for device %s: %s %s",
self.device_id,
format(ex),
traceback.format_exc,
traceback.format_exc(),
)
break

Expand All @@ -263,7 +263,7 @@ async def send_updated_params_loop(self):
"Unexpected error for device %s: %s %s",
self.device_id,
format(ex),
traceback.format_exc,
traceback.format_exc(),
)

finally:
Expand Down Expand Up @@ -366,7 +366,7 @@ async def handle_message(self, message):
"Unexpected error in handle_message() for device %s: %s %s",
self.device_id,
format(ex),
traceback.format_exc,
traceback.format_exc(),
)

def shutdown_event_loop(self):
Expand Down
2 changes: 1 addition & 1 deletion requirements_dev.txt
Expand Up @@ -9,7 +9,7 @@ twine==3.1.1
click==7.0
coveralls==1.10.0
pycryptodome==3.9.4
zeroconf==0.23.0
zeroconf==0.24.4
flask==1.1.1
requests==2.22.0
setuptools==45.1.0
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
@@ -1,13 +1,13 @@
[bumpversion]
current_version = 0.3.0
current_version = 1.1.1
commit = True
tag = True

[bumpversion:file:setup.py]
search = version='{current_version}'
replace = version='{new_version}'

[bumpversion:file:pysonofflan/__init__.py]
[bumpversion:file:pysonofflanr3/__init__.py]
search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'

Expand Down
16 changes: 12 additions & 4 deletions setup.py
Expand Up @@ -11,10 +11,17 @@
with open('HISTORY.rst') as history_file:
history = history_file.read()

requirements = ['Click>=7.0', 'click_log', 'pycryptodome', 'requests', 'zeroconf>=0.23.0']
requirements = ['Click>=7.0', 'click_log', 'pycryptodome', 'requests', 'zeroconf>=0.24.4']
setup_requirements = []
test_requirements = ['pytest', 'tox', 'python-coveralls', 'flask', 'flake8']

PROJECT_URLS = {
"Home Assistant component": "https://github.com/mattsaxon/sonoff-lan-mode-homeassistant/",
"Bug Reports": "https://github.com/mattsaxon/pysonofflan/issues/",
"Component Docs": "https://pysonofflanr3.readthedocs.io/",
"Itead Dev Docs": "https://github.com/itead/Sonoff_Devices_DIY_Tools/tree/master/other/"
}

setup(
author="Matt Saxon",
author_email='saxonmatt@hotmail.com',
Expand All @@ -40,13 +47,14 @@
license="MIT license",
long_description=readme + '\n\n' + history,
include_package_data=True,
keywords='pysonofflanr3',
keywords='pysonofflanr3, homeassistant',
name='pysonofflanr3',
packages=find_packages(include=['pysonofflanr3']),
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/mattsaxon/pysonofflan',
version='1.1.0',
url='https://github.com/mattsaxon/pysonofflanr3',
project_urls=PROJECT_URLS,
version='1.1.1',
zip_safe=False,
)
32 changes: 27 additions & 5 deletions tests/mock_listener.py
@@ -1,33 +1,55 @@
import sys
from datetime import datetime
from zeroconf import ServiceBrowser, Zeroconf


class MyListener:

def remove_service(self, zeroconf, type, name):

if "Mock" in name:
global name_filter

if name_filter in name:
print("%s - Service %s removed" % (datetime.now(), name))
print(zeroconf.get_service_info(type, name))

def add_service(self, zeroconf, type, name):

if "Mock" in name:
global name_filter

if name_filter in name:
print("%s - Service %s added" % (datetime.now(), name))
print(zeroconf.get_service_info(type, name))

ServiceBrowser(zeroconf, name, listener)
# For zeroconf 0.23.0, this is needed as updates only
# come to the child entry
# self.browser = ServiceBrowser(zeroconf, name, listener)

def update_service(self, zeroconf, type, name):

if "Mock" in name:
global name_filter

if name_filter in name:
print("%s - Service %s updated" % (datetime.now(), name))
print(zeroconf.get_service_info(type, name))


if __name__ == "__main__":

global name_filter
name_filter = ""

try:
name_filter = sys.argv[1]

except IndexError:
pass

zeroconf = Zeroconf()
listener = MyListener()
browser = ServiceBrowser(zeroconf, "_ewelink._tcp.local.", listener)

listener.browser = \
ServiceBrowser(zeroconf, "_ewelink._tcp.local.", listener)

try:
input("Press enter to exit...\n\n")
Expand Down