Skip to content

Commit

Permalink
V1.1.1 (#53)
Browse files Browse the repository at this point in the history
* Minor changes to optimise different zeroconf versions

* Update HISTORY.rst

* Update setup.py

* Update client.py

* flake8 updates

* Update .travis.yml

* Update tox.ini

* Update tox.ini

* Update tox.ini

* Update tox.ini

* Revert "Update tox.ini"

This reverts commit 31070f6.

* Update tox.ini

* Update tox.ini

* Update tox.ini

* Update tox.ini

* Update tox.ini

* flake

* Update .travis.yml

* verison update

* A couple of code tidies

* Update setup.py

* Update mock_listener.py

* Minor changes

* Fix error handling. And bump zeroconf version

* flake 8

* Update HISTORY.rst
  • Loading branch information
mattsaxon committed Feb 1, 2020
1 parent 459f5f3 commit 3d285a3
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 84 deletions.
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

0 comments on commit 3d285a3

Please sign in to comment.