From aa4ca739f873ca0353a8e19eb75a73c23f994313 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sat, 23 Mar 2019 09:44:49 -0400 Subject: [PATCH 01/60] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3596826..fd93f1a 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,7 @@ while True: ``` ## Other Examples -Check **[more_examples][blynk-py-examples]** to get familiar with some of Blynk features. - -#### Examples List: +Examples can be found **[here][blynk-py-examples]** Check them all to get familiar with main Blynk API features. ##### Core operations: - 01_write_virtual_pin.py (how to write to Virtual Pin ) From c0f27a5d97533254fc021557810dd9a7a445c1d7 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 10:57:25 -0400 Subject: [PATCH 02/60] Update README.md --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index fd93f1a..a82e31b 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,46 @@ to load **blynklib** or any other library to hardware. Read [this document][esp8266-readme] to get more information. +## Documentation and other helpful links + +[Full Blynk Documentation](http://docs.blynk.cc/#blynk-firmware) - a complete guide on Blynk features + +[Community (Forum)](http://community.blynk.cc) - join a 500,000 Blynk community to ask questions and share ideas + +[Help Center](http://help.blynk.cc) - helpful articles on various Blynk aspects + +[Code Examples Browser](http://examples.blynk.cc) - browse examples to explore Blynk possibilities + +[Official Website](https://blynk.io) + +**Social Media:** + +[Facebook](https://www.fb.com/blynkapp) + +[Twitter](https://twitter.com/blynk_app) + +[Youtube](https://www.youtube.com/blynk) + +[Instagram](https://www.instagram.com/blynk.iot/) + +[LinkedIn](https://www.linkedin.com/company/b-l-y-n-k/) + + +## Blynk libraries for other platforms +* [C++](https://github.com/blynkkk/blynk-library) +* [Node.js, Espruino, Browsers](https://github.com/vshymanskyy/blynk-library-js) +* [Python (by Volodymyr Shymanskiy)](https://github.com/vshymanskyy/blynk-library-python) +* [Particle](https://github.com/vshymanskyy/blynk-library-spark) +* [Lua, OpenWrt, NodeMCU](https://github.com/vshymanskyy/blynk-library-lua) +* [OpenWrt packages](https://github.com/vshymanskyy/blynk-library-openwrt) +* [MBED](https://developer.mbed.org/users/vshymanskyy/code/Blynk/) +* [Node-RED](https://www.npmjs.com/package/node-red-contrib-blynk-ws) +* [LabVIEW](https://github.com/juncaofish/NI-LabVIEWInterfaceforBlynk) +* [C#](https://github.com/sverrefroy/BlynkLibrary) + +## Contributing +You are very welcome to contribute: stability bugfixes, new hardware support, or any other improvements. Please. + ### License This project is released under The MIT License (MIT) From 4c71af61ec2298fa0c0da05c0221d58361afa720 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:30:40 -0400 Subject: [PATCH 03/60] Update 01_write_virtual_pin.py --- examples/01_write_virtual_pin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01_write_virtual_pin.py b/examples/01_write_virtual_pin.py index c2cf513..04a688c 100644 --- a/examples/01_write_virtual_pin.py +++ b/examples/01_write_virtual_pin.py @@ -44,7 +44,7 @@ ==================================================================================================== Additional blynk info you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From 264bea59738752207cecf1b61bb12e79ebac2c40 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:31:04 -0400 Subject: [PATCH 04/60] Update 02_read_virtual_pin.py --- examples/02_read_virtual_pin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/02_read_virtual_pin.py b/examples/02_read_virtual_pin.py index 5019cd8..f45850b 100644 --- a/examples/02_read_virtual_pin.py +++ b/examples/02_read_virtual_pin.py @@ -47,7 +47,7 @@ ==================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From b3d3fae36e8acba6083757db0f8cc693dc538810 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:31:30 -0400 Subject: [PATCH 05/60] Update 03_connect_disconnect.py --- examples/03_connect_disconnect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/03_connect_disconnect.py b/examples/03_connect_disconnect.py index c4df093..e15c813 100644 --- a/examples/03_connect_disconnect.py +++ b/examples/03_connect_disconnect.py @@ -43,7 +43,7 @@ ==================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From f27f29e1e856f1376f6810c110af445b76350d2e Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:32:25 -0400 Subject: [PATCH 06/60] Update 04_email.py --- examples/04_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/04_email.py b/examples/04_email.py index 8be07d0..d3611d6 100644 --- a/examples/04_email.py +++ b/examples/04_email.py @@ -44,7 +44,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From 94c99e281b05a58dcca44cefcddcfdb574a63331 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:32:51 -0400 Subject: [PATCH 07/60] Update 05_set_property_notify.py --- examples/05_set_property_notify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/05_set_property_notify.py b/examples/05_set_property_notify.py index 5c13a11..b82139d 100644 --- a/examples/05_set_property_notify.py +++ b/examples/05_set_property_notify.py @@ -54,7 +54,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From 40bdcfcaa7571d2be432445db24dd2475dd69508 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:33:18 -0400 Subject: [PATCH 08/60] Update 06_terminal_widget.py --- examples/06_terminal_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/06_terminal_widget.py b/examples/06_terminal_widget.py index 2dccac1..1b2d804 100644 --- a/examples/06_terminal_widget.py +++ b/examples/06_terminal_widget.py @@ -48,7 +48,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From d2661604df5d0992456681cbcafd749c29cd0d3e Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:33:38 -0400 Subject: [PATCH 09/60] Update 07_tweet_and_logging.py --- examples/07_tweet_and_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/07_tweet_and_logging.py b/examples/07_tweet_and_logging.py index 094266d..bf09e4b 100644 --- a/examples/07_tweet_and_logging.py +++ b/examples/07_tweet_and_logging.py @@ -46,7 +46,7 @@ ===================================================================================================================== Additional blynk info you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From e367959ed0b4f0215e83a1334aacc8f2165bf4db Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:34:18 -0400 Subject: [PATCH 10/60] Update 01_touch_button.py --- examples/esp32/01_touch_button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/esp32/01_touch_button.py b/examples/esp32/01_touch_button.py index 15267ed..695dc79 100644 --- a/examples/esp32/01_touch_button.py +++ b/examples/esp32/01_touch_button.py @@ -27,7 +27,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From e47d69997189dee6c2f47432c7400fbce532129a Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:34:51 -0400 Subject: [PATCH 11/60] Update 02_terminal_cli.py --- examples/esp32/02_terminal_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/esp32/02_terminal_cli.py b/examples/esp32/02_terminal_cli.py index 6a30668..126f926 100644 --- a/examples/esp32/02_terminal_cli.py +++ b/examples/esp32/02_terminal_cli.py @@ -26,7 +26,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From 63ef71244fcd1b96ab314de00eb64009a96e8391 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:35:10 -0400 Subject: [PATCH 12/60] Update 03_temperature_humidity_dht22.py --- examples/esp32/03_temperature_humidity_dht22.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/esp32/03_temperature_humidity_dht22.py b/examples/esp32/03_temperature_humidity_dht22.py index a4ea3c9..de8cdb5 100644 --- a/examples/esp32/03_temperature_humidity_dht22.py +++ b/examples/esp32/03_temperature_humidity_dht22.py @@ -41,7 +41,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From 2549f4ebc4f97a04994054a7b9319a89ca215116 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:35:41 -0400 Subject: [PATCH 13/60] Update 01_weather_station_pi3b.py --- examples/raspberry/01_weather_station_pi3b.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/raspberry/01_weather_station_pi3b.py b/examples/raspberry/01_weather_station_pi3b.py index 8ceae01..d13d92d 100644 --- a/examples/raspberry/01_weather_station_pi3b.py +++ b/examples/raspberry/01_weather_station_pi3b.py @@ -106,7 +106,7 @@ ===================================================================================================================== Additional info about blynk you can find by examining such resources: - Downloads, docs, tutorials: http://www.blynk.cc + Downloads, docs, tutorials: https://blynk.io Sketch generator: http://examples.blynk.cc Blynk community: http://community.blynk.cc Social networks: http://www.fb.com/blynkapp From b6641e245b3190858de420aeb6a9d353c817610d Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:50:05 -0400 Subject: [PATCH 14/60] Update README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a82e31b..a6a5423 100644 --- a/README.md +++ b/README.md @@ -110,8 +110,7 @@ BLYNK_AUTH = '' #insert your Auth Token here import blynklib BLYNK_AUTH = '' #insert your Auth Token here -# base lib init -blynk = blynklib.Blynk(BLYNK_AUTH) +blynk = blynklib.Blynk(BLYNK_AUTH) # base library initialization # advanced options of lib init # from __future__ import print_function @@ -161,15 +160,15 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - 07_tweet_and_logging.py (how to post to Twitter and log events from your hardware) ##### Raspberry Pi (any): -- 01_weather_station_pi3b.py (connect DHT22; BMP180 sensors and send data to Blynk app) +- (01_weather_station_pi3b.py)[https://github.com/blynkkk/lib-python/tree/master/examples/raspberry] Connect DHT22; BMP180 sensors and send data to Blynk app ##### ESP32 -- 01_touch_button.py (connect TTP223B touch sensor to ESP32 and react to touch) -- 02_terminal_cli.py (communication between ESP32 hardware and app through Terminal widget) -- 03_temperature_humidity_dht22.py (connect DHT22 sensor and send data to Blynk app) +- [01_touch_button.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/01_touch_button.py) Connect TTP223B touch sensor to ESP32 and react to touch +- [02_terminal_cli.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/02_terminal_cli.py) Communication between ESP32 hardware and app through Terminal widget +- [03_temperature_humidity_dht22.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/03_temperature_humidity_dht22.py) Connect DHT22 sensor to ESP32 and send data to Blynk app ##### ESP8266 -- 01_potentiometer.py (connect slide potentiometer to ESP8266 and get resistance values) +- [01_potentiometer.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp8266/01_potentiometer.py) Cconnect potentiometer to ESP8266 and send resistance value to the app From 2c5db2f14eb2dc48036adfd966924689f066ccb8 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:50:42 -0400 Subject: [PATCH 15/60] Update README.md --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a6a5423..be2277a 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,8 @@ BLYNK_AUTH = '' #insert your Auth Token here import blynklib BLYNK_AUTH = '' #insert your Auth Token here -blynk = blynklib.Blynk(BLYNK_AUTH) # base library initialization +# base lib init +blynk = blynklib.Blynk(BLYNK_AUTH) # advanced options of lib init # from __future__ import print_function @@ -151,24 +152,24 @@ while True: Examples can be found **[here][blynk-py-examples]** Check them all to get familiar with main Blynk API features. ##### Core operations: -- 01_write_virtual_pin.py (how to write to Virtual Pin ) -- 02_read_virtual_pin.py (how to read Virtual Pin ) -- 03_connect_disconnect.py (connection management) -- 04_email.py(how to send send email and push notifications) -- 05_set_property_notify.py (how to change some of widget UI properties) -- 06_terminal_widget.py (communication between hardware and app through Terminal widget) -- 07_tweet_and_logging.py (how to post to Twitter and log events from your hardware) +- [01_write_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/01_write_virtual_pin.py): How to read incoming data from Blynk app to Virtual Pin and use it in your code +- [02_read_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/02_read_virtual_pin.py): How to update value on Virtual Pin +- [03_connect_disconnect.py](https://github.com/blynkkk/lib-python/blob/master/examples/03_connect_disconnect.py): Managing connection with Blynk Cloud +- [04_email.py](https://github.com/blynkkk/lib-python/blob/master/examples/04_email.py): How to send send email and push notifications from your hardware +- [05_set_property_notify.py](https://github.com/blynkkk/lib-python/blob/master/examples/05_set_property_notify.py): How to change some of widget UI properties like colors, labels, etc +- [06_terminal_widget.py](https://github.com/blynkkk/lib-python/blob/master/examples/06_terminal_widget.py): Communication between hardware and app through Terminal widget) +- [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware ##### Raspberry Pi (any): -- (01_weather_station_pi3b.py)[https://github.com/blynkkk/lib-python/tree/master/examples/raspberry] Connect DHT22; BMP180 sensors and send data to Blynk app +- 01_weather_station_pi3b.py (connect DHT22; BMP180 sensors and send data to Blynk app) ##### ESP32 -- [01_touch_button.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/01_touch_button.py) Connect TTP223B touch sensor to ESP32 and react to touch -- [02_terminal_cli.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/02_terminal_cli.py) Communication between ESP32 hardware and app through Terminal widget -- [03_temperature_humidity_dht22.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/03_temperature_humidity_dht22.py) Connect DHT22 sensor to ESP32 and send data to Blynk app +- 01_touch_button.py (connect TTP223B touch sensor to ESP32 and react to touch) +- 02_terminal_cli.py (communication between ESP32 hardware and app through Terminal widget) +- 03_temperature_humidity_dht22.py (connect DHT22 sensor and send data to Blynk app) ##### ESP8266 -- [01_potentiometer.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp8266/01_potentiometer.py) Cconnect potentiometer to ESP8266 and send resistance value to the app +- 01_potentiometer.py (connect slide potentiometer to ESP8266 and get resistance values) From 2b90be80373894e6507975154d287213746d8a3c Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Sun, 24 Mar 2019 11:56:49 -0400 Subject: [PATCH 16/60] Update README.md --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be2277a..e57a900 100644 --- a/README.md +++ b/README.md @@ -161,15 +161,19 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware ##### Raspberry Pi (any): -- 01_weather_station_pi3b.py (connect DHT22; BMP180 sensors and send data to Blynk app) +Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. + +- (01_weather_station_pi3b.py)[https://github.com/blynkkk/lib-python/tree/master/examples/raspberry] Connect DHT22; BMP180 sensors and send data to Blynk app ##### ESP32 -- 01_touch_button.py (connect TTP223B touch sensor to ESP32 and react to touch) -- 02_terminal_cli.py (communication between ESP32 hardware and app through Terminal widget) -- 03_temperature_humidity_dht22.py (connect DHT22 sensor and send data to Blynk app) +Read [ESP32 guide](https://github.com/blynkkk/lib-python/tree/master/examples/esp32) first. +- [01_touch_button.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/01_touch_button.py) Connect TTP223B touch sensor to ESP32 and react to touch +- [02_terminal_cli.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/02_terminal_cli.py) Communication between ESP32 hardware and app through Terminal widget +- [03_temperature_humidity_dht22.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp32/03_temperature_humidity_dht22.py) Connect DHT22 sensor to ESP32 and send data to Blynk app ##### ESP8266 -- 01_potentiometer.py (connect slide potentiometer to ESP8266 and get resistance values) +Read [ESP8266 guide](https://github.com/blynkkk/lib-python/tree/master/examples/esp8266) first. +- [01_potentiometer.py](https://github.com/blynkkk/lib-python/blob/master/examples/esp8266/01_potentiometer.py) Cconnect potentiometer to ESP8266 and send resistance value to the app From d6f8b4016e45ba41d43963bf6260de724b171701 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Mon, 25 Mar 2019 23:41:31 +0200 Subject: [PATCH 17/60] Added blynktimer module --- README.md | 19 ++++------ TIMERS.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++ blynktimer.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 TIMERS.md create mode 100644 blynktimer.py diff --git a/README.md b/README.md index e57a900..15b8711 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Full list of supported hardware can be found [here][blynk-hw]. To exclude compatibility issue preferable versions are Python 2.7.9 (or greater) or Python 3.4 (or greater) If python not present you can download and install it from [here][python-org]. - **NOTE:** To run python in "sandbox" you can try **virtualenv** module. Check [here][virtual-env] on how to do it. + **NOTE:** To run python in "sandbox" you can try **virtualenv** module. Check [this document][virtual-env] how to do it. - If you’re using preferable versions of python mentioned above, then **pip** comes installed with Python by default. Check pip availability: @@ -101,12 +101,12 @@ This library supports Python2, Python3, and Micropython. - You will get Auth Token delivered to your email account. - Put this Auth Token within your python script to authenticate your device on [public][blynk-server-public] or [local][blynk-server] -```py +```python BLYNK_AUTH = '' #insert your Auth Token here ``` #### Usage example -```py +```python import blynklib BLYNK_AUTH = '' #insert your Auth Token here @@ -163,7 +163,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. -- (01_weather_station_pi3b.py)[https://github.com/blynkkk/lib-python/tree/master/examples/raspberry] Connect DHT22; BMP180 sensors and send data to Blynk app +- [01_weather_station_pi3b.py](https://github.com/blynkkk/lib-python/blob/master/examples/raspberry/01_weather_station_pi3b.py) Connect DHT22; BMP180 sensors and send data to Blynk app ##### ESP32 Read [ESP32 guide](https://github.com/blynkkk/lib-python/tree/master/examples/esp32) first. @@ -197,15 +197,9 @@ Read [this document][esp8266-readme] to get more information. **Social Media:** -[Facebook](https://www.fb.com/blynkapp) +[Facebook](https://www.fb.com/blynkapp) [Twitter](https://twitter.com/blynk_app) [Youtube](https://www.youtube.com/blynk) -[Twitter](https://twitter.com/blynk_app) - -[Youtube](https://www.youtube.com/blynk) - -[Instagram](https://www.instagram.com/blynk.iot/) - -[LinkedIn](https://www.linkedin.com/company/b-l-y-n-k/) +[Instagram](https://www.instagram.com/blynk.iot/) [LinkedIn](https://www.linkedin.com/company/b-l-y-n-k/) ## Blynk libraries for other platforms @@ -250,3 +244,4 @@ This project is released under The MIT License (MIT) [micropython-pkg]: https://github.com/micropython/micropython/wiki/Getting-Started [virtual-env]: https://virtualenv.pypa.io/en/latest/installation/ [esp8266-readme]: https://github.com/blynkkk/lib-python/blob/master/examples/esp8266/README.md + [blynktimer-doc]: https://github.com/blynkkk/lib-python/blob/master/blynktimer.py diff --git a/TIMERS.md b/TIMERS.md new file mode 100644 index 0000000..211267c --- /dev/null +++ b/TIMERS.md @@ -0,0 +1,90 @@ +# Blynk Timers +There are two options of setting polling timers in **blynk** + + - create timers for your functions on hardware side + - create timers on Blynk App side. + +### Hardware timers +Existing core library solutions may be helpful for hardware timers creation. + + For example: + - micropython provides [machine.Timer][micropython-timer] + - for cPython [threading.Timer][threading-timer] can be used + - etc + + Unfortunately mentioned above solutions may be not so lightweight and clear as expected. + For Quickstart we provide separate [timer module][blynktimer] that allows execute functions periodically or run them once. + +##### Basic usage examples +```python +from blynktimer import Timer +blynk_timer = Timer() + +# run once timer that will fire after 1 sec +@blynk_timer.register(1, run_once=True) +def your_run_once_function(): + print('Hello, World!') + +# periodical timer that will fire each 5 sec +@blynk_timer.register(5) +def your_periodical_function(): + print('Hello, Blynkers!') + +while True: + blynk_timer.run() + +``` + +##### Advanced usage examples +```python +import time +from blynktimer import Timer + +# disable exception raise if all all timers were stopped +blynk_timer = Timer(no_timers_err=False) + + +# register two timers for single function with different function parameters +@blynk_timer.register(2, 'p1', 'p2', c=1, run_once=True) +@blynk_timer.register(3, 'fp1', 'fp2', run_once=False) +def function1(a, b, c=2): + time.sleep(c) + print('Function params: {} {} {}'.format(a, b, c)) + + +# simple function registration for further stop +@blynk_timer.register(4, run_once=False) +def function2(): + print('Function2') + + +# list available timers +print(blynk_timer.get_timers()) + +# switch timer state to stopped by timer id +# id = order_num + '_' + function_name +blynk_timer.stop('2_function2') + +while True: + intervals = blynk_timer.run() + # print real passed time for timer fired events + # maybe needed for debug + if any(intervals): + print(intervals) +``` + +To get more accuracy for timers intervals it is possible to decrease library WAIT_SEC parameter. Default value = 0.05 sec + +### Blynk App timers +Some Blynk app widgets have timer setting where yoy can define (1,2,5,10 etc) seconds intervals for reading +virtual pin values. + +Flow: + - each N seconds Blynk app widget will do Virtual Pin reading operation. + - Blynk Server for App read request will return current pin value + - Additionally Blynk server will fire read virtual pin event and send it to hardware + - If read pin event was registered on hardware certain handler will be executed + + [micropython-timer]: https://docs.micropython.org/en/latest/library/machine.Timer.html + [threading-timer]:https://docs.python.org/3/library/threading.html#threading.Timer + [blynktimer]: https://github.com/blynkkk/lib-python/blob/master/blynktimer.py \ No newline at end of file diff --git a/blynktimer.py b/blynktimer.py new file mode 100644 index 0000000..3df37af --- /dev/null +++ b/blynktimer.py @@ -0,0 +1,98 @@ +# Copyright (c) 2019 Anton Morozenko +""" +Polling timers for functions. +Registers timers and performs run once or periodical function execution after defined time intervals. +""" +try: + import utime as time + import uselect as select +except ImportError: + import time + import select + +WAIT_SEC = 0.05 +MAX_TIMERS = 16 + + +class TimerError(Exception): + pass + + +class Timer(object): + timers = {} + + def __init__(self, no_timers_err=True): + self.no_timers_err = no_timers_err + + def _get_func_name(self, obj): + if getattr(obj, '__name__', None) is None: + return self._get_func_name(obj.func) + return obj.__name__ + + def register(blynk, interval, *args, run_once=False, **kwargs): + class Deco(object): + def __init__(self, func): + self.func = func + if len(Timer.timers.keys()) >= MAX_TIMERS: + raise TimerError('Max allowed timers num={}'.format(MAX_TIMERS)) + _timer = _Timer(interval, func, run_once, *args, **kwargs) + Timer.timers['{}_{}'.format(len(Timer.timers.keys()), blynk._get_func_name(func))] = _timer + + def __call__(self, *f_args, **f_kwargs): + return self.func(*f_args, **f_kwargs) + + return Deco + + @staticmethod + def stop(t_id): + timer = Timer.timers.get(t_id, None) + if timer is None: + raise TimerError('Timer id={} not found'.format(t_id)) + Timer.timers[t_id].stopped = True + + @staticmethod + def is_stopped(t_id): + timer = Timer.timers.get(t_id, None) + if timer is None: + raise TimerError('Timer id={} not found'.format(t_id)) + return timer.stopped + + def get_timers(self): + states = {True: 'Stopped', False: 'Running'} + return {k: states[v.stopped] for k, v in self.timers.items()} + + def run(self): + # select call used cause time.sleep loads CPU up to 100% with small polling time + select.select([], [], [], WAIT_SEC) + timers_intervals = [curr_timer.run() for curr_timer in Timer.timers.values() if not curr_timer.stopped] + if not timers_intervals and self.no_timers_err: + raise TimerError('Running timers not found') + return timers_intervals + + +class _Timer(object): + def __init__(self, interval, deco, run_once, *args, **kwargs): + self.interval = interval + self.deco = deco + self.args = args + self.run_once = run_once + self.kwargs = kwargs + self.fire_time = None + self.fire_time_prev = None + self.stopped = False + + def run(self): + timer_real_interval = 0 + if self.fire_time is None: + self.fire_time = time.time() + self.interval + if self.fire_time_prev is None: + self.fire_time_prev = time.time() + curr_time = time.time() + if curr_time >= self.fire_time: + self.deco(*self.args, **self.kwargs) + if self.run_once: + self.stopped = True + timer_real_interval = curr_time - self.fire_time_prev + self.fire_time_prev = self.fire_time + self.fire_time = curr_time + self.interval + return timer_real_interval From 96b7dadf7778b3fe56b9cab7f5591e3bc818ba55 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Tue, 26 Mar 2019 16:13:56 +0200 Subject: [PATCH 18/60] Added blynktimer example --- README.md | 3 +- examples/08_blynk_timer.py | 79 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 examples/08_blynk_timer.py diff --git a/README.md b/README.md index 15b8711..8e66963 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [05_set_property_notify.py](https://github.com/blynkkk/lib-python/blob/master/examples/05_set_property_notify.py): How to change some of widget UI properties like colors, labels, etc - [06_terminal_widget.py](https://github.com/blynkkk/lib-python/blob/master/examples/06_terminal_widget.py): Communication between hardware and app through Terminal widget) - [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware +- [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. @@ -244,4 +245,4 @@ This project is released under The MIT License (MIT) [micropython-pkg]: https://github.com/micropython/micropython/wiki/Getting-Started [virtual-env]: https://virtualenv.pypa.io/en/latest/installation/ [esp8266-readme]: https://github.com/blynkkk/lib-python/blob/master/examples/esp8266/README.md - [blynktimer-doc]: https://github.com/blynkkk/lib-python/blob/master/blynktimer.py + [blynktimer-doc]: https://github.com/blynkkk/lib-python/blob/master/TIMERS.md diff --git a/examples/08_blynk_timer.py b/examples/08_blynk_timer.py new file mode 100644 index 0000000..c984a53 --- /dev/null +++ b/examples/08_blynk_timer.py @@ -0,0 +1,79 @@ +""" +[BLYNK TIMER EXAMPLE] ============================================================================================ + +Environment prepare: +In your Blynk App project: + - add "SuperChart" widget + - add two data streams for it ( stream #1 - Virtual Pin 8, stream #2 - Virtual Pin 9) + - set different colors for data stream (ex. red and orange) + - Run the App (green triangle in the upper right corner). + - define your auth token for current example and run it + + +This started program will register two timers that will update Virtual Pins data after defined intervals. +In app you can see on graph both pins data change. Additionally timers will print new pin values info to stdout. + +Schema: +================================================================================================================= + +-----------+ +--------------+ +--------------+ + | | | | | | + | blynk lib | | blynk server | | blynk app | + | | | virtual pin | | | + | | | | | | + +-----+-----+ +------+-------+ +-------+------+ + | | | + | | | + | | | + | | | ++------------+ | | +| +---------+ | | +| | | update virtual pin 8 value | | +| | | | notify app about vpin 8 update | +| +-------->------------------------------------->+ | +| | +----------------------------------->+ ++----------->------------------------------------->+ | + | +----------------------------------->+ + | update virtual pin 9 value | | + | | notify app about vpin 9 update | + | | | + | | | + | | | + + + + + +================================================================================================================ +Additional blynk info you can find by examining such resources: + + Downloads, docs, tutorials: https://blynk.io + Sketch generator: http://examples.blynk.cc + Blynk community: http://community.blynk.cc + Social networks: http://www.fb.com/blynkapp + http://twitter.com/blynk_app +==================================================================================================== +""" + +import blynklib +import blynktimer +import random + +BLYNK_AUTH = 'YourAuthToken' +blynk = blynklib.Blynk(BLYNK_AUTH) +timer = blynktimer.Timer() + +WRITE_EVENT_PRINT_MSG = "[WRITE_VIRTUAL_WRITE] Pin: V{} Value: '{}'" + + +# register two timers for different pins with different intervals +# first num is delay interval in seconds +# second num is function argument +# run_once flag that allows run timers once or periodically +@timer.register(4, 8, run_once=False) +@timer.register(10, 9, run_once=False) +def write_to_virtual_pin(pin_num): + value = random.randint(0, 20) + print(WRITE_EVENT_PRINT_MSG.format(pin_num, value)) + blynk.virtual_write(pin_num, value) + + +while True: + blynk.run() + timer.run() From 392f2d4e90acb80b1aa0a78591e7afbede1443a0 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Wed, 27 Mar 2019 16:04:53 -0400 Subject: [PATCH 19/60] Update 08_blynk_timer.py --- examples/08_blynk_timer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/08_blynk_timer.py b/examples/08_blynk_timer.py index c984a53..850aafc 100644 --- a/examples/08_blynk_timer.py +++ b/examples/08_blynk_timer.py @@ -55,14 +55,14 @@ import blynktimer import random -BLYNK_AUTH = 'YourAuthToken' +BLYNK_AUTH = 'YourAuthToken' # insert your Auth Token here blynk = blynklib.Blynk(BLYNK_AUTH) -timer = blynktimer.Timer() +timer = blynktimer.Timer() # create a new Timer instance WRITE_EVENT_PRINT_MSG = "[WRITE_VIRTUAL_WRITE] Pin: V{} Value: '{}'" -# register two timers for different pins with different intervals +# Code below: register two timers for different pins with different intervals # first num is delay interval in seconds # second num is function argument # run_once flag that allows run timers once or periodically From cf2ef6f3cac48e521bc550ba51965a047bde2590 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Thu, 28 Mar 2019 10:56:01 +0200 Subject: [PATCH 20/60] set "interval" as named parameter with default value --- TIMERS.md | 13 ++++++++----- blynktimer.py | 3 ++- examples/08_blynk_timer.py | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/TIMERS.md b/TIMERS.md index 211267c..42a3aa7 100644 --- a/TIMERS.md +++ b/TIMERS.md @@ -21,12 +21,13 @@ from blynktimer import Timer blynk_timer = Timer() # run once timer that will fire after 1 sec -@blynk_timer.register(1, run_once=True) +@blynk_timer.register(interval=1, run_once=True) def your_run_once_function(): print('Hello, World!') # periodical timer that will fire each 5 sec -@blynk_timer.register(5) +# run_once flag by default is False +@blynk_timer.register(interval=5) def your_periodical_function(): print('Hello, Blynkers!') @@ -45,15 +46,17 @@ blynk_timer = Timer(no_timers_err=False) # register two timers for single function with different function parameters -@blynk_timer.register(2, 'p1', 'p2', c=1, run_once=True) -@blynk_timer.register(3, 'fp1', 'fp2', run_once=False) +@blynk_timer.register('p1', 'p2', c=1, interval=2, run_once=True) +@blynk_timer.register('fp1', 'fp2', interval=3, run_once=False) def function1(a, b, c=2): time.sleep(c) print('Function params: {} {} {}'.format(a, b, c)) # simple function registration for further stop -@blynk_timer.register(4, run_once=False) +# interval default = 10 sec +# run_once default is False +@blynk_timer.register() def function2(): print('Function2') diff --git a/blynktimer.py b/blynktimer.py index 3df37af..00f3ba9 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -12,6 +12,7 @@ WAIT_SEC = 0.05 MAX_TIMERS = 16 +DEFAULT_INTERVAL = 10 class TimerError(Exception): @@ -29,7 +30,7 @@ def _get_func_name(self, obj): return self._get_func_name(obj.func) return obj.__name__ - def register(blynk, interval, *args, run_once=False, **kwargs): + def register(blynk, *args, interval=DEFAULT_INTERVAL, run_once=False, **kwargs): class Deco(object): def __init__(self, func): self.func = func diff --git a/examples/08_blynk_timer.py b/examples/08_blynk_timer.py index 850aafc..c434588 100644 --- a/examples/08_blynk_timer.py +++ b/examples/08_blynk_timer.py @@ -55,23 +55,23 @@ import blynktimer import random -BLYNK_AUTH = 'YourAuthToken' # insert your Auth Token here +BLYNK_AUTH = 'YourAuthToken' # insert your Auth Token here blynk = blynklib.Blynk(BLYNK_AUTH) -timer = blynktimer.Timer() # create a new Timer instance + +# create timers dispatcher instance +timer = blynktimer.Timer() WRITE_EVENT_PRINT_MSG = "[WRITE_VIRTUAL_WRITE] Pin: V{} Value: '{}'" # Code below: register two timers for different pins with different intervals -# first num is delay interval in seconds -# second num is function argument -# run_once flag that allows run timers once or periodically -@timer.register(4, 8, run_once=False) -@timer.register(10, 9, run_once=False) -def write_to_virtual_pin(pin_num): +# run_once flag allows to run timers once or periodically +@timer.register(vpin_num=8, interval=4, run_once=False) +@timer.register(vpin_num=9, interval=7, run_once=False) +def write_to_virtual_pin(vpin_num=1): value = random.randint(0, 20) - print(WRITE_EVENT_PRINT_MSG.format(pin_num, value)) - blynk.virtual_write(pin_num, value) + print(WRITE_EVENT_PRINT_MSG.format(vpin_num, value)) + blynk.virtual_write(vpin_num, value) while True: From b9822e75cc5cfcfaad4b9fef53922979aefd06ac Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 22 Apr 2019 11:32:51 +0300 Subject: [PATCH 21/60] dict_view types corrections for micropython --- blynktimer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blynktimer.py b/blynktimer.py index 00f3ba9..e045724 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -34,10 +34,10 @@ def register(blynk, *args, interval=DEFAULT_INTERVAL, run_once=False, **kwargs): class Deco(object): def __init__(self, func): self.func = func - if len(Timer.timers.keys()) >= MAX_TIMERS: + if len(list(Timer.timers.keys())) >= MAX_TIMERS: raise TimerError('Max allowed timers num={}'.format(MAX_TIMERS)) _timer = _Timer(interval, func, run_once, *args, **kwargs) - Timer.timers['{}_{}'.format(len(Timer.timers.keys()), blynk._get_func_name(func))] = _timer + Timer.timers['{}_{}'.format(len(list(Timer.timers.keys())), blynk._get_func_name(func))] = _timer def __call__(self, *f_args, **f_kwargs): return self.func(*f_args, **f_kwargs) From f92893468a49c5048878f80dca48dda2c107ac46 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Thu, 25 Apr 2019 11:46:44 +0300 Subject: [PATCH 22/60] added handling for cases when select.select not supported --- blynktimer.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/blynktimer.py b/blynktimer.py index e045724..32b85df 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -3,13 +3,31 @@ Polling timers for functions. Registers timers and performs run once or periodical function execution after defined time intervals. """ +# select.select call used as polling waiter where it is possible +# cause time.sleep sometimes may load CPU up to 100% with small polling wait interval try: - import utime as time - import uselect as select -except ImportError: + # cpython import time import select + polling_wait = lambda x: select.select([], [], [], x) + polling_wait(0.01) +except OSError: + # windows case where select.select call fails + polling_wait = lambda x: time.sleep(x) + +except ImportError: + # micropython + import utime as time + + try: + from uselect import select as s_select + + polling_wait = lambda x: s_select([], [], [], x) + except ImportError: + # case when micropython port does not support select.select + polling_wait = lambda x: time.sleep(x) + WAIT_SEC = 0.05 MAX_TIMERS = 16 DEFAULT_INTERVAL = 10 @@ -63,8 +81,7 @@ def get_timers(self): return {k: states[v.stopped] for k, v in self.timers.items()} def run(self): - # select call used cause time.sleep loads CPU up to 100% with small polling time - select.select([], [], [], WAIT_SEC) + polling_wait(WAIT_SEC) timers_intervals = [curr_timer.run() for curr_timer in Timer.timers.values() if not curr_timer.stopped] if not timers_intervals and self.no_timers_err: raise TimerError('Running timers not found') From 4bd673b382fbad50ae4d72874b6cdda5d1e62678 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Tue, 18 Jun 2019 16:59:17 +0300 Subject: [PATCH 23/60] Fix for handling virtual pin V0 events --- blynklib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blynklib.py b/blynklib.py index af20e1a..3c989c3 100644 --- a/blynklib.py +++ b/blynklib.py @@ -308,7 +308,7 @@ def __init__(self, func): # wildcard 'read V*' and 'write V*' events handling if str(event_name).lower() in (blynk._VPIN_READ_ALL, blynk._VPIN_WRITE_ALL): event_base_name = str(event_name).split(blynk._VPIN_WILDCARD)[0] - for i in range(1, blynk.VPIN_MAX_NUM + 1): + for i in range(blynk.VPIN_MAX_NUM + 1): blynk._events['{}{}'.format(event_base_name.lower(), i)] = func else: blynk._events[str(event_name).lower()] = func From 263196354ab70aebce35123d55aaad4204b7d5e9 Mon Sep 17 00:00:00 2001 From: Braden Mars Date: Thu, 20 Jun 2019 02:38:28 -0500 Subject: [PATCH 24/60] test(wildcard): Updated Wildcard Tests to include V0 (#7) * fix(wildcard): Wildcards allowing nonexistant VPin 33 Commit 4bd673b causes read/write wildcard tests to fail, as it allows a nonexistant virtual pin 33 t o be passed to a wildcard decorator. * fix: Reverted Code Change, Updated Tests Mistake on my part, not sure what I was thinking there. --- test/test_blynk_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index a092c0b..e2d6c74 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -94,7 +94,7 @@ def read_pin_handler(): pass assert 'read v10' in bl._events.keys() - assert len(bl._events.keys()) == bl.VPIN_MAX_NUM + assert len(bl._events.keys()) == bl.VPIN_MAX_NUM + 1 def test_write_wildcard_event(self, bl): bl._events = {} @@ -104,7 +104,7 @@ def write_pin_handler(): pass assert 'write v5' in bl._events.keys() - assert len(bl._events.keys()) == bl.VPIN_MAX_NUM + assert len(bl._events.keys()) == bl.VPIN_MAX_NUM + 1 def test_call_handler(self, bl): bl._events = {} From 37df41136113e684280061691c05ea4ab9829514 Mon Sep 17 00:00:00 2001 From: Braden Mars Date: Thu, 20 Jun 2019 14:36:15 -0500 Subject: [PATCH 25/60] fix(timer): BlynkTimer Broken on Low Memory Ports (#6) * fix(timer): BlynkTimer Broken on Low Memory Ports On low memory ports MICROPY_PY_FUNCTION_ATTRS is disabled, leading BlynkTimer to raise an AttributeError when trying to get a name for a new timer. This fixes it by naming each timer 'X_timer' (where X is the # of timer) if the __name__ attribute does not exist. Fixes #5 * docs(timer): Added note about Low Memory ports * feat(timer): Cleanup Code, handle all cases * docs(timer): Added some docstrings for clarification --- TIMERS.md | 3 +++ blynktimer.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TIMERS.md b/TIMERS.md index 42a3aa7..371f966 100644 --- a/TIMERS.md +++ b/TIMERS.md @@ -66,8 +66,11 @@ print(blynk_timer.get_timers()) # switch timer state to stopped by timer id # id = order_num + '_' + function_name +# OR: on ports with low memory (such as the esp8266) +# id = order_num + '_' + 'timer' blynk_timer.stop('2_function2') + while True: intervals = blynk_timer.run() # print real passed time for timer fired events diff --git a/blynktimer.py b/blynktimer.py index 32b85df..cbda5a5 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -44,9 +44,12 @@ def __init__(self, no_timers_err=True): self.no_timers_err = no_timers_err def _get_func_name(self, obj): - if getattr(obj, '__name__', None) is None: + """retrieves a suitable name for a function""" + if hasattr(obj, 'func'): + # handles nested decorators return self._get_func_name(obj.func) - return obj.__name__ + # simply returns 'timer' if on port without function attrs + return getattr(obj, '__name__', 'timer') def register(blynk, *args, interval=DEFAULT_INTERVAL, run_once=False, **kwargs): class Deco(object): From 201188970e0a38e8fcd5e09dec3036a8e24192a4 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 24 Jun 2019 14:46:44 +0300 Subject: [PATCH 26/60] connect handler for each server send needs closing forced reading --- README.md | 1 + blynklib.py | 17 +++-- examples/05_set_property_notify.py | 4 +- examples/09_sync_virtual_pin.py | 112 +++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 examples/09_sync_virtual_pin.py diff --git a/README.md b/README.md index 8e66963..4d1d57f 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [06_terminal_widget.py](https://github.com/blynkkk/lib-python/blob/master/examples/06_terminal_widget.py): Communication between hardware and app through Terminal widget) - [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** +- [09_sync_virtual_pin.py]((https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py)): How to sync virtual pin states and properties ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. diff --git a/blynklib.py b/blynklib.py index 3c989c3..906eb5c 100644 --- a/blynklib.py +++ b/blynklib.py @@ -275,13 +275,13 @@ def connect(self, timeout=_CONNECT_TIMEOUT): return False def disconnect(self, err_msg=None): + self.call_handler(self._DISCONNECT) if self._socket: self._socket.close() self._state = self.DISCONNECTED if err_msg: self.log('[ERROR]: {}\nConnection closed'.format(err_msg)) time.sleep(self.RECONNECT_SLEEP) - self.call_handler(self._DISCONNECT) def virtual_write(self, v_pin, *val): return self.send(self.virtual_write_msg(v_pin, *val)) @@ -336,16 +336,21 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif len(msg_args) == const(2) and msg_args[0] == 'vr': self.call_handler("{}{}".format(self._VPIN_READ, msg_args[1]), int(msg_args[1])) + def read_response(self, timeout=0.5): + end_time = time.time() + timeout + while time.time() <= end_time: + rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) + self._last_rcv_time = ticks_ms() + if rsp_data: + msg_type, msg_id, h_data, msg_args = self.parse_response(rsp_data, self.rcv_buffer) + self.process(msg_type, msg_id, h_data, msg_args) + def run(self): if not self.connected(): self.connect() else: try: - rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) - self._last_rcv_time = ticks_ms() - if rsp_data: - msg_type, msg_id, h_data, msg_args = self.parse_response(rsp_data, self.rcv_buffer) - self.process(msg_type, msg_id, h_data, msg_args) + self.read_response(timeout=self.SOCK_TIMEOUT) if not self.is_server_alive(): self.disconnect('Blynk server is offline') except KeyboardInterrupt: diff --git a/examples/05_set_property_notify.py b/examples/05_set_property_notify.py index b82139d..50f39d8 100644 --- a/examples/05_set_property_notify.py +++ b/examples/05_set_property_notify.py @@ -74,8 +74,8 @@ @blynk.handle_event('write V5') def write_handler(pin, value): - current_color = random.choice(colors.keys()) - blynk.set_property(pin, 'color', random.choice(colors.keys())) + current_color = random.choice(list(colors.keys())) + blynk.set_property(pin, 'color', current_color) blynk.notify(NOTIFY_MSG.format(colors[current_color])) print(NOTIFY_MSG.format(colors[current_color])) diff --git a/examples/09_sync_virtual_pin.py b/examples/09_sync_virtual_pin.py new file mode 100644 index 0000000..5e6769f --- /dev/null +++ b/examples/09_sync_virtual_pin.py @@ -0,0 +1,112 @@ +""" +[VIRTUAL PIN SYNC EXAMPLE] ========================================================================================== + +Environment prepare: +In your Blynk App project: + - add 3 "Button" widgets, + - bind then to Virtual Pins V0, V1, v2 + - set mode "SWITCH" for all of them + - Run the App (green triangle in the upper right corner). + - define your auth token for current example and run it + + +This started program will restore on connect write_virtual_pin states and colors. +Buttons states and colors can be modified during script run. +If script was interrupted (KeyboardInterrupt) buttons colors will be changed to red. +During next connect event ( script re-run) previous buttons states and colors will be restored. + +Schema: +===================================================================================================================== + +-----------+ +--------------+ +--------------+ + | | | | | | + | blynk lib | | blynk server | | blynk app | + | | | virtual pin | | | + | | | | | | + +-----+-----+ +------+-------+ +-------+------+ +connect handler | | | + +--------+ | | + | +------------------------------------>+ | + | | virtual pin sync | | + | +<------------------------------------+----------------------------------->+ + | | virtual pin write stored value | send pin value to app | + | +<------------------------------------+----------------------------------->+ + | | virtual pin apply stored properties | send pin properties to app | + +------->+ | | + write handler | | | + +--------+ +<-----------------------------------+ + | +<------------------------------------+ write event from button | + +>------>+ write event form server | | + | | | + | | | + disconnect | | | + handler | | | + +-------+ | | + | +------------------------------------>+ | + +------>+ set new virtual pin property | | + | | | + | | | + | | | + + + + + +===================================================================================================================== +Additional info about blynk you can find by examining such resources: + + Downloads, docs, tutorials: https://blynk.io + Sketch generator: http://examples.blynk.cc + Blynk community: http://community.blynk.cc + Social networks: http://www.fb.com/blynkapp + http://twitter.com/blynk_app +===================================================================================================================== +""" +import logging +import blynklib + +# tune console logging +_log = logging.getLogger('BlynkLog') +logFormatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") +consoleHandler = logging.StreamHandler() +consoleHandler.setFormatter(logFormatter) +_log.addHandler(consoleHandler) +_log.setLevel(logging.DEBUG) + +colors = {'1': '#FFC300', '0': '#CCCCCC', 'OFFLINE': '#FF0000'} + +BLYNK_AUTH = 'YourAuthToken' +blynk = blynklib.Blynk(BLYNK_AUTH, log=_log.info) + + +@blynk.handle_event("connect") +def connect_handler(): + _log.info('SCRIPT_START') + for pin in range(3): + _log.info('Syncing virtual pin {}'.format(pin)) + blynk.virtual_sync(pin) + + # within connect handler after each server send operation forced socket reading is required cause: + # - we are not in script listening state yet + # - without forced reading some portion of blynk server messages can be not delivered to HW + blynk.read_response(timeout=0.5) + + +@blynk.handle_event('write V*') +def write_handler(pin, value): + button_state = value[0] + blynk.set_property(pin, 'color', colors[button_state]) + + +@blynk.handle_event("disconnect") +def connect_handler(): + for pin in range(3): + _log.info("Set 'OFFLINE' color for pin {}".format(pin)) + blynk.set_property(pin, 'color', colors['OFFLINE']) + + +########################################################### +# infinite loop that waits for event +########################################################### +try: + while True: + blynk.run() +except KeyboardInterrupt: + blynk.disconnect() + _log.info('SCRIPT WAS INTERRUPTED') From bbe6f3ec719474072c4516be1f40876e3f082b08 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 24 Jun 2019 14:49:29 +0300 Subject: [PATCH 27/60] example path fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d1d57f..a87a5c9 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [06_terminal_widget.py](https://github.com/blynkkk/lib-python/blob/master/examples/06_terminal_widget.py): Communication between hardware and app through Terminal widget) - [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** -- [09_sync_virtual_pin.py]((https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py)): How to sync virtual pin states and properties +- [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. From 54087667a028da29ec8eca73f6cfaf4b9476c4c1 Mon Sep 17 00:00:00 2001 From: Vitor de Miranda Henrique Date: Wed, 26 Jun 2019 04:54:26 -0500 Subject: [PATCH 28/60] Add blynktimer to setup (#9) Looks good for me. Can be merged. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7de2cac..ffa7c20 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ author_email='antoha.ua@gmail.com', setup_requires=['pytest-runner', ], tests_require=['pytest', 'pytest-mock', ], - py_modules=['blynklib'], + py_modules=['blynklib', 'blynktimer'], classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", From 60744c50c746f76b0969e32e3bcb43a20a95076e Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 22 Jul 2019 11:47:36 +0300 Subject: [PATCH 29/60] Python2 compatibility fix. Named args changed to kwargs with defaults --- blynktimer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blynktimer.py b/blynktimer.py index cbda5a5..99dddd4 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -51,7 +51,11 @@ def _get_func_name(self, obj): # simply returns 'timer' if on port without function attrs return getattr(obj, '__name__', 'timer') - def register(blynk, *args, interval=DEFAULT_INTERVAL, run_once=False, **kwargs): + def register(blynk, *args, **kwargs): + # kwargs with defaults are used cause PEP 3102 no supported by Python2 + interval = kwargs.pop('interval', DEFAULT_INTERVAL) + run_once = kwargs.pop('run_once', False) + class Deco(object): def __init__(self, func): self.func = func From 5dca229afd8cad4494d10e62973e13530df63cd1 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Tue, 23 Jul 2019 16:40:02 +0300 Subject: [PATCH 30/60] Configurable init state for timers. add start function. --- blynktimer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/blynktimer.py b/blynktimer.py index 99dddd4..63b17a4 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -55,13 +55,14 @@ def register(blynk, *args, **kwargs): # kwargs with defaults are used cause PEP 3102 no supported by Python2 interval = kwargs.pop('interval', DEFAULT_INTERVAL) run_once = kwargs.pop('run_once', False) + stopped = kwargs.pop('stopped', False) class Deco(object): def __init__(self, func): self.func = func if len(list(Timer.timers.keys())) >= MAX_TIMERS: raise TimerError('Max allowed timers num={}'.format(MAX_TIMERS)) - _timer = _Timer(interval, func, run_once, *args, **kwargs) + _timer = _Timer(interval, func, run_once, stopped, *args, **kwargs) Timer.timers['{}_{}'.format(len(list(Timer.timers.keys())), blynk._get_func_name(func))] = _timer def __call__(self, *f_args, **f_kwargs): @@ -76,6 +77,13 @@ def stop(t_id): raise TimerError('Timer id={} not found'.format(t_id)) Timer.timers[t_id].stopped = True + @staticmethod + def start(t_id): + timer = Timer.timers.get(t_id, None) + if timer is None: + raise TimerError('Timer id={} not found'.format(t_id)) + Timer.timers[t_id].stopped = False + @staticmethod def is_stopped(t_id): timer = Timer.timers.get(t_id, None) @@ -96,7 +104,7 @@ def run(self): class _Timer(object): - def __init__(self, interval, deco, run_once, *args, **kwargs): + def __init__(self, interval, deco, run_once, stopped, *args, **kwargs): self.interval = interval self.deco = deco self.args = args @@ -104,7 +112,7 @@ def __init__(self, interval, deco, run_once, *args, **kwargs): self.kwargs = kwargs self.fire_time = None self.fire_time_prev = None - self.stopped = False + self.stopped = stopped def run(self): timer_real_interval = 0 From ff57196c0c939e8ae5c11c635c78f374d8f5efbc Mon Sep 17 00:00:00 2001 From: amorozenko Date: Wed, 24 Jul 2019 10:01:13 +0300 Subject: [PATCH 31/60] Fire time should be reinitialized to have successful next run --- blynktimer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blynktimer.py b/blynktimer.py index 63b17a4..e8c0d9d 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -83,6 +83,8 @@ def start(t_id): if timer is None: raise TimerError('Timer id={} not found'.format(t_id)) Timer.timers[t_id].stopped = False + Timer.timers[t_id].fire_time = None + Timer.timers[t_id].fire_time_prev = None @staticmethod def is_stopped(t_id): From f3d1b767da00be347dec9aeb0d05be6e379eba71 Mon Sep 17 00:00:00 2001 From: Blynk IoT platform Date: Fri, 16 Aug 2019 01:18:41 +0300 Subject: [PATCH 32/60] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a87a5c9..7515d83 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ Read [this document][esp8266-readme] to get more information. ## Blynk libraries for other platforms * [C++](https://github.com/blynkkk/blynk-library) * [Node.js, Espruino, Browsers](https://github.com/vshymanskyy/blynk-library-js) -* [Python (by Volodymyr Shymanskiy)](https://github.com/vshymanskyy/blynk-library-python) +* [Python](https://github.com/vshymanskyy/blynk-library-python) (by Volodymyr Shymanskyy) * [Particle](https://github.com/vshymanskyy/blynk-library-spark) * [Lua, OpenWrt, NodeMCU](https://github.com/vshymanskyy/blynk-library-lua) * [OpenWrt packages](https://github.com/vshymanskyy/blynk-library-openwrt) From 2ebcf31aa7f09de5cd2267dcfc1cc83d3d50ef11 Mon Sep 17 00:00:00 2001 From: Enrico Gueli Date: Mon, 26 Aug 2019 22:50:28 +0200 Subject: [PATCH 33/60] Add support for redirect command (#14) * Add support for redirect command --- blynklib.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/blynklib.py b/blynklib.py index 906eb5c..f9de30a 100644 --- a/blynklib.py +++ b/blynklib.py @@ -40,6 +40,11 @@ def stub_log(*args): class BlynkError(Exception): pass +class RedirectError(Exception): + def __init__(self, server, port): + self.server = server + self.port = port + class Protocol(object): MSG_RSP = const(0) @@ -53,9 +58,11 @@ class Protocol(object): MSG_INTERNAL = const(17) MSG_PROPERTY = const(19) MSG_HW = const(20) + MSG_REDIRECT = const(41) MSG_HEAD_LEN = const(5) STATUS_INVALID_TOKEN = const(9) + STATUS_NO_DATA = const(17) STATUS_OK = const(200) VPIN_MAX_NUM = const(32) @@ -83,7 +90,7 @@ def parse_response(self, rsp_data, msg_buffer): raise BlynkError('Command too long. Length = {}'.format(h_data)) elif msg_type in (self.MSG_RSP, self.MSG_PING, self.MSG_INTERNAL): pass - elif msg_type in (self.MSG_HW, self.MSG_BRIDGE): + elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_REDIRECT): msg_body = rsp_data[self.MSG_HEAD_LEN: self.MSG_HEAD_LEN + h_data] msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] else: @@ -214,10 +221,12 @@ def _authenticate(self): rsp_data = self.receive(self.rcv_buffer, self.SOCK_MAX_TIMEOUT) if not rsp_data: raise BlynkError('Auth stage timeout') - _, _, status, _ = self.parse_response(rsp_data, self.rcv_buffer) + msg_type, _, status, args = self.parse_response(rsp_data, self.rcv_buffer) if status != self.STATUS_OK: if status == self.STATUS_INVALID_TOKEN: raise BlynkError('Invalid Auth Token') + if msg_type == self.MSG_REDIRECT: + raise RedirectError(*args) raise BlynkError('Auth stage failed. Status={}'.format(status)) self._state = self.AUTHENTICATED self.log('Access granted') @@ -271,6 +280,11 @@ def connect(self, timeout=_CONNECT_TIMEOUT): except BlynkError as b_err: self.disconnect(b_err) sleep_ms(self.TASK_PERIOD_RES) + except RedirectError as r_err: + self.disconnect() + self.server = r_err.server + self.port = r_err.port + sleep_ms(self.TASK_PERIOD_RES) if time.time() >= end_time: return False From 4da4c27ea44953d51c50e5bd84a28555049feecb Mon Sep 17 00:00:00 2001 From: antohaUa Date: Thu, 29 Aug 2019 22:54:01 +0300 Subject: [PATCH 34/60] preparations for 0.2.5 version release --- LICENSE => LICENSE.txt | 0 blynklib.py | 3 ++- setup.py | 2 +- test/test_blynk_connection.py | 25 +++++++++++++++++-------- test/test_blynk_main.py | 15 ++++++++++++++- test/test_blynk_protocol.py | 2 +- 6 files changed, 35 insertions(+), 12 deletions(-) rename LICENSE => LICENSE.txt (100%) diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/blynklib.py b/blynklib.py index f9de30a..942a1c8 100644 --- a/blynklib.py +++ b/blynklib.py @@ -2,7 +2,7 @@ # Copyright (c) 2015-2019 Volodymyr Shymanskyy. # See the file LICENSE for copying permission. -__version__ = '0.2.4' +__version__ = '0.2.5' try: import usocket as socket @@ -40,6 +40,7 @@ def stub_log(*args): class BlynkError(Exception): pass + class RedirectError(Exception): def __init__(self, server, port): self.server = server diff --git a/setup.py b/setup.py index ffa7c20..0421be7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='blynklib', - version='0.2.4', + version='0.2.5', description='Blynk Python/Micropython library', long_description=long_description, long_description_content_type="text/markdown", diff --git a/test/test_blynk_connection.py b/test/test_blynk_connection.py index 02e2003..74e4325 100644 --- a/test/test_blynk_connection.py +++ b/test/test_blynk_connection.py @@ -3,7 +3,7 @@ import time import pytest import socket -from blynklib import Connection, BlynkError +from blynklib import Connection, BlynkError, RedirectError class TestBlynkConnection: @@ -96,7 +96,7 @@ def test_receive_raise_other_oserror(self, cb, mocker): with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 13]')): with pytest.raises(OSError) as os_err: cb.receive(10, 1) - assert '[Errno 13]' in str(os_err) + assert '[Errno 13]' in str(os_err.value) def test_is_server_alive_negative(self, cb): result = cb.is_server_alive() @@ -131,7 +131,7 @@ def test_get_socket_exception(self, cb, mocker): with mocker.patch('socket.getaddrinfo', side_effect=BlynkError('BE')): with pytest.raises(BlynkError) as b_err: cb._get_socket() - assert 'Connection with the Blynk server failed: BE' in str(b_err) + assert 'Connection with the Blynk server failed: BE' in str(b_err.value) def test_authenticate(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): @@ -144,35 +144,44 @@ def test_authenticate_invalid_auth_token(self, cb, mocker): with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x09'): with pytest.raises(BlynkError) as b_err: cb._authenticate() - assert 'Invalid Auth Token' in str(b_err) + assert 'Invalid Auth Token' in str(b_err.value) + + def test_authenticate_redirect_message(self, cb, mocker): + with mocker.patch.object(cb, 'send', return_value=None): + with mocker.patch.object(cb, 'receive', return_value=b'\x29\x00\x02\x00\x11127.0.0.1\x004444'): + with pytest.raises(RedirectError) as r_err: + cb._authenticate() + # pytest exception wraps real exception - so we need access value field first + assert '127.0.0.1' in r_err.value.server + assert '4444' in r_err.value.port def test_authenticate_not_ok_status(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x19'): with pytest.raises(BlynkError) as b_err: cb._authenticate() - assert 'Auth stage failed. Status=25' in str(b_err) + assert 'Auth stage failed. Status=25' in str(b_err.value) def test_authenticate_timeout(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): with mocker.patch.object(cb, 'receive', return_value=None): with pytest.raises(BlynkError) as b_err: cb._authenticate() - assert 'Auth stage timeout' in str(b_err) + assert 'Auth stage timeout' in str(b_err.value) def test_set_heartbeat_timeout(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): with mocker.patch.object(cb, 'receive', return_value=None): with pytest.raises(BlynkError) as b_err: cb._set_heartbeat() - assert 'Heartbeat stage timeout' in str(b_err) + assert 'Heartbeat stage timeout' in str(b_err.value) def test_set_heartbeat_error_status(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x0e'): with pytest.raises(BlynkError) as b_err: cb._set_heartbeat() - assert 'Set heartbeat returned code=14' in str(b_err) + assert 'Set heartbeat returned code=14' in str(b_err.value) def test_set_heartbeat_positive(self, cb, mocker): with mocker.patch.object(cb, 'send', return_value=None): diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index e2d6c74..7913105 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -2,7 +2,7 @@ from __future__ import print_function import socket import pytest -from blynklib import Blynk, BlynkError +from blynklib import Blynk, BlynkError, RedirectError class TestBlynk: @@ -31,6 +31,19 @@ def test_connect_exception(self, bl, mocker): assert result is False assert bl.disconnect.call_count > 1 + def test_connect_redirect_exception(self, bl, mocker): + with mocker.patch.object(bl, 'connected', return_value=False): + with mocker.patch.object(bl, '_get_socket', return_value=None): + with mocker.patch.object(bl, '_authenticate', side_effect=RedirectError('127.0.0.1', 4444)): + with mocker.patch.object(bl, 'disconnect', return_value=None): + with mocker.patch('time.sleep', return_value=None): + mocker.spy(bl, 'disconnect') + result = bl.connect(0.001) + assert result is False + assert bl.disconnect.call_count > 1 + assert bl.server == '127.0.0.1' + assert bl.port == 4444 + def test_connect_timeout(self, bl, mocker): bl._state = bl.CONNECTING with mocker.patch.object(bl, 'connected', return_value=False): diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index 7853a8a..ceee9ae 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -99,7 +99,7 @@ def test_parse_response_msg_hw_unicode(self, pb): def test_heartbeat_msg(self, pb): result = pb.heartbeat_msg(20, 2048) - assert result == b'\x11\x00\x02\x00+ver\x000.2.4\x00buff-in\x002048\x00h-beat\x0020\x00dev\x00python' + assert result == b'\x11\x00\x02\x00+ver\x000.2.5\x00buff-in\x002048\x00h-beat\x0020\x00dev\x00python' def test_login_msg(self, pb): result = pb.login_msg('1234') From f7ea75d06a4730e0f039186d13541f42f328daa8 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Sat, 31 Aug 2019 01:34:52 +0300 Subject: [PATCH 35/60] Added internal messages support.Added RTC example --- README.md | 3 +- blynklib.py | 14 +++++-- examples/10_rtc_sync.py | 77 +++++++++++++++++++++++++++++++++++++ test/test_blynk_main.py | 13 +++++-- test/test_blynk_protocol.py | 4 ++ 5 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 examples/10_rtc_sync.py diff --git a/README.md b/README.md index 7515d83..f296dd4 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,8 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [06_terminal_widget.py](https://github.com/blynkkk/lib-python/blob/master/examples/06_terminal_widget.py): Communication between hardware and app through Terminal widget) - [07_tweet_and_logging.py](https://github.com/blynkkk/lib-python/blob/master/examples/07_tweet_and_logging.py): How to post to Twitter and log events from your hardware - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** -- [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties +- [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties +- [10_rtc_sync.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_rtc_sync.py): How to perform RTC sync with blynk server ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. diff --git a/blynklib.py b/blynklib.py index 942a1c8..1797aa2 100644 --- a/blynklib.py +++ b/blynklib.py @@ -89,9 +89,9 @@ def parse_response(self, rsp_data, msg_buffer): raise BlynkError('invalid msg_id == 0') elif h_data >= msg_buffer: raise BlynkError('Command too long. Length = {}'.format(h_data)) - elif msg_type in (self.MSG_RSP, self.MSG_PING, self.MSG_INTERNAL): + elif msg_type in (self.MSG_RSP, self.MSG_PING): pass - elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_REDIRECT): + elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL, self.MSG_REDIRECT): msg_body = rsp_data[self.MSG_HEAD_LEN: self.MSG_HEAD_LEN + h_data] msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] else: @@ -129,6 +129,9 @@ def notify_msg(self, msg): def set_property_msg(self, pin, prop, *val): return self._pack_msg(self.MSG_PROPERTY, pin, prop, *val) + def internal_msg(self, *args): + return self._pack_msg(self.MSG_INTERNAL, *args) + class Connection(Protocol): SOCK_MAX_TIMEOUT = const(5) @@ -316,6 +319,9 @@ def notify(self, msg): def set_property(self, v_pin, property_name, *val): return self.send(self.set_property_msg(v_pin, property_name, *val)) + def internal(self, *args): + return self.send(self.internal_msg(*args)) + def handle_event(blynk, event_name): class Deco(object): def __init__(self, func): @@ -344,8 +350,8 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif msg_type == self.MSG_PING: self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): - if msg_type == self.MSG_INTERNAL and len(msg_args) >= const(3): - self.call_handler("{}{}".format(self._INTERNAL, msg_args[1]), msg_args[2:]) + if msg_type == self.MSG_INTERNAL and len(msg_args) >= const(2): + self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) elif len(msg_args) >= const(3) and msg_args[0] == 'vw': self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) elif len(msg_args) == const(2) and msg_args[0] == 'vr': diff --git a/examples/10_rtc_sync.py b/examples/10_rtc_sync.py new file mode 100644 index 0000000..f1a61bd --- /dev/null +++ b/examples/10_rtc_sync.py @@ -0,0 +1,77 @@ +""" +[RTC EXAMPLE] ======================================================================================================== + +Environment prepare: +In your Blynk App project: + - add "RTC" widget, + - set required TimeZone, + - Run the App (green triangle in the upper right corner). + - define your auth token for current example and run it + + +This started program on connect will send rtc_sync call to server. +RTC reply will be captured by "internal_rtc" handler +UTC time with required timezone correction will be printed + +Schema: +===================================================================================================================== + +-----------+ +--------------+ +--------------+ + | | | | | | + | blynk lib | | blynk server | | blynk app | + | | | | | | + | | | | | | + +-----+-----+ +------+-------+ +-------+------+ + | | | +connect handler | | | + +-------+ | | + | | | | + | | rtc sync | | + +------>------------------------------------->+ rtc widget present in app? | + | +----------------------------------->+ + | | | + | | yes rtc widget found | + | rtc with timezone correction +<-----------------------------------+ +internal_rtc | | | +handler +--------<------------------------------------+ | + | | | | + | | | | + +------>+ | | + | | | + | | | + + + + +===================================================================================================================== +Additional blynk info you can find by examining such resources: + + Downloads, docs, tutorials: https://blynk.io + Sketch generator: http://examples.blynk.cc + Blynk community: http://community.blynk.cc + Social networks: http://www.fb.com/blynkapp + http://twitter.com/blynk_app +===================================================================================================================== +""" + +import blynklib +from datetime import datetime + +BLYNK_AUTH = 'YourAuthToken' +blynk = blynklib.Blynk(BLYNK_AUTH) + + +@blynk.handle_event("connect") +def connect_handler(): + blynk.internal("rtc", "sync") + print("RTC sync request was sent") + + +@blynk.handle_event('internal_rtc') +def rtc_handler(rtc_data_list): + hr_rtc_value = datetime.utcfromtimestamp(int(rtc_data_list[0])).strftime('%Y-%m-%d %H:%M:%S') + print('Raw RTC value from server: {}'.format(rtc_data_list[0])) + print('Human readable RTC value: {}'.format(hr_rtc_value)) + + +########################################################### +# infinite loop that waits for event +########################################################### +while True: + blynk.run() diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index 7913105..6dbba96 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -85,6 +85,11 @@ def test_set_property(self, bl, mocker): result = bl.set_property(1, '2', '3') assert result == 60 + def test_internal(self, bl, mocker): + with mocker.patch.object(bl, 'send', return_value=70): + result = bl.internal('rtc', 'sync') + assert result == 70 + def test_hadle_event(self, bl): bl._events = {} @@ -143,13 +148,13 @@ def test_process_ping(self, bl, mocker): def test_process_internal(self, bl, mocker): bl._events = {} - @bl.handle_event('internal_5') + @bl.handle_event('internal_xyz') def internal_handler(*args): - bl._status = 'INTERNAL TEST{}'.format(*args) + bl._status = 'INTERNAL TEST {}'.format(*args) with mocker.patch.object(bl, 'send', return_value=None): - bl.process(bl.MSG_INTERNAL, 100, 200, ['int', 5, 1, 2]) - assert bl._status == 'INTERNAL TEST[1, 2]' + bl.process(bl.MSG_INTERNAL, 100, 20, ["xyz", "add", 2]) + assert bl._status == "INTERNAL TEST ['add', 2]" def test_process_write(self, bl, mocker): bl._events = {} diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index ceee9ae..c0cd6a7 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -136,3 +136,7 @@ def test_notify_msg(self, pb): def test_set_property_msg(self, pb): result = pb.set_property_msg(10, 'color', '#FF00EE') assert result == b'\x13\x00\x02\x00\x1010\x00color\x00#FF00EE' + + def test_internal_msg(self, pb): + result = pb.internal_msg('rtc', 'sync') + assert result == b'\x11\x00\x02\x00\x08rtc\x00sync' From 1db89d4d2765fc6780117baba3a3d94dab1290d1 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Wed, 18 Sep 2019 23:22:35 +0300 Subject: [PATCH 36/60] _last_rcv_time fixes for correct disconnect/connect handling --- blynklib.py | 3 ++- test/test_blynk_main.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/blynklib.py b/blynklib.py index 1797aa2..0341f2e 100644 --- a/blynklib.py +++ b/blynklib.py @@ -278,6 +278,7 @@ def connect(self, timeout=_CONNECT_TIMEOUT): self._get_socket() self._authenticate() self._set_heartbeat() + self._last_rcv_time = ticks_ms() self.log('Registered events: {}\n'.format(list(self._events.keys()))) self.call_handler(self._CONNECT) return True @@ -361,8 +362,8 @@ def read_response(self, timeout=0.5): end_time = time.time() + timeout while time.time() <= end_time: rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) - self._last_rcv_time = ticks_ms() if rsp_data: + self._last_rcv_time = ticks_ms() msg_type, msg_id, h_data, msg_args = self.parse_response(rsp_data, self.rcv_buffer) self.process(msg_type, msg_id, h_data, msg_args) diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index 6dbba96..8bd369c 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -2,13 +2,13 @@ from __future__ import print_function import socket import pytest -from blynklib import Blynk, BlynkError, RedirectError +import blynklib class TestBlynk: @pytest.fixture def bl(self): - blynk = Blynk('1234', log=print) + blynk = blynklib.Blynk('1234', log=print) yield blynk def test_connect(self, bl, mocker): @@ -17,13 +17,15 @@ def test_connect(self, bl, mocker): with mocker.patch.object(bl, '_authenticate', return_value=None): with mocker.patch.object(bl, '_set_heartbeat', return_value=None): with mocker.patch.object(bl, 'call_handler', return_value=None): - result = bl.connect() - assert result is True + with mocker.patch.object(blynklib, 'ticks_ms', return_value=42): + result = bl.connect() + assert result is True + assert bl._last_rcv_time == 42 def test_connect_exception(self, bl, mocker): with mocker.patch.object(bl, 'connected', return_value=False): with mocker.patch.object(bl, '_get_socket', return_value=None): - with mocker.patch.object(bl, '_authenticate', side_effect=BlynkError()): + with mocker.patch.object(bl, '_authenticate', side_effect=blynklib.BlynkError()): with mocker.patch.object(bl, 'disconnect', return_value=None): with mocker.patch('time.sleep', return_value=None): mocker.spy(bl, 'disconnect') @@ -34,7 +36,7 @@ def test_connect_exception(self, bl, mocker): def test_connect_redirect_exception(self, bl, mocker): with mocker.patch.object(bl, 'connected', return_value=False): with mocker.patch.object(bl, '_get_socket', return_value=None): - with mocker.patch.object(bl, '_authenticate', side_effect=RedirectError('127.0.0.1', 4444)): + with mocker.patch.object(bl, '_authenticate', side_effect=blynklib.RedirectError('127.0.0.1', 4444)): with mocker.patch.object(bl, 'disconnect', return_value=None): with mocker.patch('time.sleep', return_value=None): mocker.spy(bl, 'disconnect') From 626d19703494c0aaa3e16978717f91ee065c9e0e Mon Sep 17 00:00:00 2001 From: antohaUa Date: Wed, 18 Sep 2019 23:35:12 +0300 Subject: [PATCH 37/60] changed name for duplicate function --- examples/03_connect_disconnect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/03_connect_disconnect.py b/examples/03_connect_disconnect.py index e15c813..da3154b 100644 --- a/examples/03_connect_disconnect.py +++ b/examples/03_connect_disconnect.py @@ -71,7 +71,7 @@ def connect_handler(): @blynk.handle_event("disconnect") -def connect_handler(): +def disconnect_handler(): print(DISCONNECT_PRINT_MSG) print('Sleeping 4 sec in disconnect handler...') time.sleep(4) From 9c056f7feea3bb17d9d0823d61815014eaf298c3 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Tue, 15 Oct 2019 09:39:10 +0300 Subject: [PATCH 38/60] Preparing for cpython mPython versions splitting --- blynklib_cp.py | 368 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 blynklib_cp.py diff --git a/blynklib_cp.py b/blynklib_cp.py new file mode 100644 index 0000000..f8730aa --- /dev/null +++ b/blynklib_cp.py @@ -0,0 +1,368 @@ +# Copyright (c) 2019 Anton Morozenko +# Copyright (c) 2015-2019 Volodymyr Shymanskyy. +# See the file LICENSE for copying permission. + +__version__ = '0.2.5' + +import socket +import time +import struct + +LOGO = """ + ___ __ __ + / _ )/ /_ _____ / /__ + / _ / / // / _ \\/ '_/ + /____/_/\\_, /_//_/_/\\_\\ + /___/ for Python v{}\n""".format(__version__) + + +def stub_log(*args): + pass + + +def ticks_ms(): + return int(time.time() * 1000) + + +def sleep_ms(ms): + time.sleep(ms // 1000) + + +class BlynkError(Exception): + pass + + +class RedirectError(Exception): + def __init__(self, server, port): + self.server = server + self.port = port + + +class Protocol(object): + MSG_RSP = 0 + MSG_LOGIN = 2 + MSG_PING = 6 + MSG_TWEET = 12 + MSG_EMAIL = 13 + MSG_NOTIFY = 14 + MSG_BRIDGE = 15 + MSG_HW_SYNC = 16 + MSG_INTERNAL = 17 + MSG_PROPERTY = 19 + MSG_HW = 20 + MSG_REDIRECT = 41 + MSG_HEAD_LEN = 5 + + STATUS_INVALID_TOKEN = 9 + STATUS_NO_DATA = 17 + STATUS_OK = 200 + VPIN_MAX_NUM = 32 + + _msg_id = 0 + + def _get_msg_id(self, **kwargs): + if 'msg_id' in kwargs: + return kwargs['msg_id'] + self._msg_id += 1 + return self._msg_id if self._msg_id <= 0xFFFF else 0 + + def _pack_msg(self, msg_type, *args, **kwargs): + data = ('\0'.join([str(curr_arg) for curr_arg in args])).encode('utf-8') + return struct.pack('!BHH', msg_type, self._get_msg_id(**kwargs), len(data)) + data + + def parse_response(self, rsp_data, msg_buffer): + msg_args = [] + try: + msg_type, msg_id, h_data = struct.unpack('!BHH', rsp_data[:self.MSG_HEAD_LEN]) + except Exception as p_err: + raise BlynkError('Message parse error: {}'.format(p_err)) + if msg_id == 0: + raise BlynkError('invalid msg_id == 0') + elif h_data >= msg_buffer: + raise BlynkError('Command too long. Length = {}'.format(h_data)) + elif msg_type in (self.MSG_RSP, self.MSG_PING): + pass + elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL, self.MSG_REDIRECT): + msg_body = rsp_data[self.MSG_HEAD_LEN: self.MSG_HEAD_LEN + h_data] + msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] + else: + raise BlynkError("Unknown message type: '{}'".format(msg_type)) + return msg_type, msg_id, h_data, msg_args + + def heartbeat_msg(self, heartbeat, rcv_buffer): + return self._pack_msg(self.MSG_INTERNAL, 'ver', __version__, 'buff-in', rcv_buffer, 'h-beat', heartbeat, + 'dev', 'python') + + def login_msg(self, token): + return self._pack_msg(self.MSG_LOGIN, token) + + def ping_msg(self): + return self._pack_msg(self.MSG_PING) + + def response_msg(self, *args, **kwargs): + return self._pack_msg(self.MSG_RSP, *args, **kwargs) + + def virtual_write_msg(self, v_pin, *val): + return self._pack_msg(self.MSG_HW, 'vw', v_pin, *val) + + def virtual_sync_msg(self, *pins): + return self._pack_msg(self.MSG_HW_SYNC, 'vr', *pins) + + def email_msg(self, to, subject, body): + return self._pack_msg(self.MSG_EMAIL, to, subject, body) + + def tweet_msg(self, msg): + return self._pack_msg(self.MSG_TWEET, msg) + + def notify_msg(self, msg): + return self._pack_msg(self.MSG_NOTIFY, msg) + + def set_property_msg(self, pin, prop, *val): + return self._pack_msg(self.MSG_PROPERTY, pin, prop, *val) + + def internal_msg(self, *args): + return self._pack_msg(self.MSG_INTERNAL, *args) + + +class Connection(Protocol): + SOCK_MAX_TIMEOUT = 5 + SOCK_TIMEOUT = 0.05 + EAGAIN = 11 + ETIMEDOUT = 60 + RETRIES_TX_DELAY = 2 + RETRIES_TX_MAX_NUM = 3 + RECONNECT_SLEEP = 1 + TASK_PERIOD_RES = 50 + DISCONNECTED = 0 + CONNECTING = 1 + AUTHENTICATING = 2 + AUTHENTICATED = 3 + + _state = None + _socket = None + _last_rcv_time = 0 + _last_ping_time = 0 + _last_send_time = 0 + + def __init__(self, token, server='blynk-cloud.com', port=80, heartbeat=10, rcv_buffer=1024, log=stub_log): + self.token = token + self.server = server + self.port = port + self.heartbeat = heartbeat + self.rcv_buffer = rcv_buffer + self.log = log + + def send(self, data): + retries = self.RETRIES_TX_MAX_NUM + while retries > 0: + try: + retries -= 1 + self._last_send_time = ticks_ms() + return self._socket.send(data) + except (IOError, OSError): + sleep_ms(self.RETRIES_TX_DELAY) + + def receive(self, length, timeout): + d_buff = b'' + try: + self._socket.settimeout(timeout) + d_buff += self._socket.recv(length) + if len(d_buff) >= length: + d_buff = d_buff[:length] + return d_buff + except (IOError, OSError) as err: + if str(err) == 'timed out': + return b'' + if str(self.EAGAIN) in str(err) or str(self.ETIMEDOUT) in str(err): + return b'' + raise + + def is_server_alive(self): + now = ticks_ms() + h_beat_ms = self.heartbeat * 1000 + rcv_delta = now - self._last_rcv_time + ping_delta = now - self._last_ping_time + send_delta = now - self._last_send_time + if rcv_delta > h_beat_ms + (h_beat_ms // 2): + return False + if (ping_delta > h_beat_ms // 10) and (send_delta > h_beat_ms or rcv_delta > h_beat_ms): + self.send(self.ping_msg()) + self.log('Heartbeat time: {}'.format(now)) + self._last_ping_time = now + return True + + def _get_socket(self): + try: + self._state = self.CONNECTING + self._socket = socket.socket() + self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) + self._socket.settimeout(self.SOCK_TIMEOUT) + self.log('Connected to blynk server') + except Exception as g_exc: + raise BlynkError('Connection with the Blynk server failed: {}'.format(g_exc)) + + def _authenticate(self): + self.log('Authenticating device...') + self._state = self.AUTHENTICATING + self.send(self.login_msg(self.token)) + rsp_data = self.receive(self.rcv_buffer, self.SOCK_MAX_TIMEOUT) + if not rsp_data: + raise BlynkError('Auth stage timeout') + msg_type, _, status, args = self.parse_response(rsp_data, self.rcv_buffer) + if status != self.STATUS_OK: + if status == self.STATUS_INVALID_TOKEN: + raise BlynkError('Invalid Auth Token') + if msg_type == self.MSG_REDIRECT: + raise RedirectError(*args) + raise BlynkError('Auth stage failed. Status={}'.format(status)) + self._state = self.AUTHENTICATED + self.log('Access granted') + + def _set_heartbeat(self): + self.send(self.heartbeat_msg(self.heartbeat, self.rcv_buffer)) + rcv_data = self.receive(self.rcv_buffer, self.SOCK_MAX_TIMEOUT) + if not rcv_data: + raise BlynkError('Heartbeat stage timeout') + _, _, status, _ = self.parse_response(rcv_data, self.rcv_buffer) + if status != self.STATUS_OK: + raise BlynkError('Set heartbeat returned code={}'.format(status)) + self.log('Heartbeat = {} sec. MaxCmdBuffer = {} bytes'.format(self.heartbeat, self.rcv_buffer)) + + def connected(self): + return True if self._state == self.AUTHENTICATED else False + + +class Blynk(Connection): + _CONNECT_TIMEOUT = 30 # 30sec + _VPIN_WILDCARD = '*' + _VPIN_READ = 'read v' + _VPIN_WRITE = 'write v' + _INTERNAL = 'internal_' + _CONNECT = 'connect' + _DISCONNECT = 'disconnect' + _VPIN_READ_ALL = '{}{}'.format(_VPIN_READ, _VPIN_WILDCARD) + _VPIN_WRITE_ALL = '{}{}'.format(_VPIN_WRITE, _VPIN_WILDCARD) + _events = {} + + def __init__(self, token, **kwargs): + Connection.__init__(self, token, **kwargs) + self._start_time = ticks_ms() + self._last_rcv_time = ticks_ms() + self._last_send_time = ticks_ms() + self._last_ping_time = ticks_ms() + self._state = self.DISCONNECTED + print(LOGO) + + def connect(self, timeout=_CONNECT_TIMEOUT): + end_time = time.time() + timeout + while not self.connected(): + if self._state == self.DISCONNECTED: + try: + self._get_socket() + self._authenticate() + self._set_heartbeat() + self._last_rcv_time = ticks_ms() + self.log('Registered events: {}\n'.format(list(self._events.keys()))) + self.call_handler(self._CONNECT) + return True + except BlynkError as b_err: + self.disconnect(b_err) + sleep_ms(self.TASK_PERIOD_RES) + except RedirectError as r_err: + self.disconnect() + self.server = r_err.server + self.port = r_err.port + sleep_ms(self.TASK_PERIOD_RES) + if time.time() >= end_time: + return False + + def disconnect(self, err_msg=None): + self.call_handler(self._DISCONNECT) + if self._socket: + self._socket.close() + self._state = self.DISCONNECTED + if err_msg: + self.log('[ERROR]: {}\nConnection closed'.format(err_msg)) + self._msg_id = 0 + time.sleep(self.RECONNECT_SLEEP) + + def virtual_write(self, v_pin, *val): + return self.send(self.virtual_write_msg(v_pin, *val)) + + def virtual_sync(self, *v_pin): + return self.send(self.virtual_sync_msg(*v_pin)) + + def email(self, to, subject, body): + return self.send(self.email_msg(to, subject, body)) + + def tweet(self, msg): + return self.send(self.tweet_msg(msg)) + + def notify(self, msg): + return self.send(self.notify_msg(msg)) + + def set_property(self, v_pin, property_name, *val): + return self.send(self.set_property_msg(v_pin, property_name, *val)) + + def internal(self, *args): + return self.send(self.internal_msg(*args)) + + def handle_event(blynk, event_name): + class Deco(object): + def __init__(self, func): + self.func = func + # wildcard 'read V*' and 'write V*' events handling + if str(event_name).lower() in (blynk._VPIN_READ_ALL, blynk._VPIN_WRITE_ALL): + event_base_name = str(event_name).split(blynk._VPIN_WILDCARD)[0] + for i in range(blynk.VPIN_MAX_NUM + 1): + blynk._events['{}{}'.format(event_base_name.lower(), i)] = func + else: + blynk._events[str(event_name).lower()] = func + + def __call__(self): + return self.func() + + return Deco + + def call_handler(self, event, *args, **kwargs): + if event in self._events.keys(): + self.log("Event: ['{}'] -> {}".format(event, args)) + self._events[event](*args, **kwargs) + + def process(self, msg_type, msg_id, msg_len, msg_args): + if msg_type == self.MSG_RSP: + self.log('Response status: {}'.format(msg_len)) + elif msg_type == self.MSG_PING: + self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) + elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): + if msg_type == self.MSG_INTERNAL and len(msg_args) >= 2: + self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) + elif len(msg_args) >= 3 and msg_args[0] == 'vw': + self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) + elif len(msg_args) == 2 and msg_args[0] == 'vr': + self.call_handler("{}{}".format(self._VPIN_READ, msg_args[1]), int(msg_args[1])) + + def read_response(self, timeout=0.5): + end_time = time.time() + timeout + while time.time() <= end_time: + rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) + if rsp_data: + self._last_rcv_time = ticks_ms() + msg_type, msg_id, h_data, msg_args = self.parse_response(rsp_data, self.rcv_buffer) + self.process(msg_type, msg_id, h_data, msg_args) + + def run(self): + if not self.connected(): + self.connect() + else: + try: + self.read_response(timeout=self.SOCK_TIMEOUT) + if not self.is_server_alive(): + self.disconnect('Blynk server is offline') + except KeyboardInterrupt: + raise + except BlynkError as b_err: + self.log(b_err) + self.disconnect() + except Exception as g_exc: + self.log(g_exc) From 62fb5ee53b755541518d1228f54bfaaba9d77858 Mon Sep 17 00:00:00 2001 From: Antony Kurniawan Date: Mon, 21 Oct 2019 16:57:19 +0700 Subject: [PATCH 39/60] Implements SSL connection (#20) Added new feature "SSL socket connection" --- README.md | 10 +++++++++- blynklib.py | 4 ++-- blynklib_cp.py | 19 +++++++++++++++++-- certificate/_blynk-cloudcom.crt | 23 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 certificate/_blynk-cloudcom.crt diff --git a/README.md b/README.md index f296dd4..5593253 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,15 @@ blynk = blynklib.Blynk(BLYNK_AUTH) # advanced options of lib init # from __future__ import print_function -# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, heartbeat=10, rcv_buffer=1024, log=print) +# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, ssl=False, heartbeat=10, rcv_buffer=1024, log=print) + +# Lib init with hardware SSL connection example +# blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='default') +# the ssl_cert='default' is for default blynk server (blynk-cloud.com) or self hosted server with Let's Encrypt certificate. + +# if you have self hosted server with your own self signed certificate, then you can change ssl_cert with your +# own CA file. For example if you have CA file in /home/blynk/ca.crt, then the param will be +# blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_mode='/home/blynk/ca.crt') # register handler for Virtual Pin V22 reading by Blynk App. # when a widget in Blynk App asks Virtual Pin data from server within given configurable interval (1,2,5,10 sec etc) diff --git a/blynklib.py b/blynklib.py index 0341f2e..e5ab5fc 100644 --- a/blynklib.py +++ b/blynklib.py @@ -67,7 +67,7 @@ class Protocol(object): STATUS_OK = const(200) VPIN_MAX_NUM = const(32) - _msg_id = 1 + _msg_id = 0 def _get_msg_id(self, **kwargs): if 'msg_id' in kwargs: @@ -381,4 +381,4 @@ def run(self): self.log(b_err) self.disconnect() except Exception as g_exc: - self.log(g_exc) + self.log(g_exc) \ No newline at end of file diff --git a/blynklib_cp.py b/blynklib_cp.py index f8730aa..c7e25d9 100644 --- a/blynklib_cp.py +++ b/blynklib_cp.py @@ -4,9 +4,11 @@ __version__ = '0.2.5' +import os import socket -import time +import ssl import struct +import time LOGO = """ ___ __ __ @@ -127,6 +129,7 @@ def internal_msg(self, *args): class Connection(Protocol): SOCK_MAX_TIMEOUT = 5 SOCK_TIMEOUT = 0.05 + SOCK_SSL_TIMEOUT = 1 EAGAIN = 11 ETIMEDOUT = 60 RETRIES_TX_DELAY = 2 @@ -144,13 +147,14 @@ class Connection(Protocol): _last_ping_time = 0 _last_send_time = 0 - def __init__(self, token, server='blynk-cloud.com', port=80, heartbeat=10, rcv_buffer=1024, log=stub_log): + def __init__(self, token, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, log=stub_log): self.token = token self.server = server self.port = port self.heartbeat = heartbeat self.rcv_buffer = rcv_buffer self.log = log + self.ssl_cert = ssl_cert def send(self, data): retries = self.RETRIES_TX_MAX_NUM @@ -197,6 +201,17 @@ def _get_socket(self): self._socket = socket.socket() self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) self._socket.settimeout(self.SOCK_TIMEOUT) + if self.ssl_cert: + self.log('Using SSL socket...') + if (self.ssl_cert == "default"): + sslContext = ssl.create_default_context() + caFile = os.path.dirname(__file__) + "/certificate/_blynk-cloudcom.crt" + sslContext.load_verify_locations(cafile=caFile) + else: + sslContext = ssl.create_default_context(cafile=self.ssl_cert) + sslContext.verify_mode = ssl.CERT_REQUIRED + self._socket.settimeout(self.SOCK_SSL_TIMEOUT) + self._socket = sslContext.wrap_socket(sock=self._socket, server_hostname=self.server) self.log('Connected to blynk server') except Exception as g_exc: raise BlynkError('Connection with the Blynk server failed: {}'.format(g_exc)) diff --git a/certificate/_blynk-cloudcom.crt b/certificate/_blynk-cloudcom.crt new file mode 100644 index 0000000..a83f9d8 --- /dev/null +++ b/certificate/_blynk-cloudcom.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID5TCCAs2gAwIBAgIJAIHSnb+cv4ECMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD +VQQGEwJVQTENMAsGA1UECAwES3lpdjENMAsGA1UEBwwES3lpdjELMAkGA1UECgwC +SVQxEzARBgNVBAsMCkJseW5rIEluYy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNv +bTEfMB0GCSqGSIb3DQEJARYQZG1pdHJpeUBibHluay5jYzAeFw0xNjAzMTcxMTU4 +MDdaFw0yMTAzMTYxMTU4MDdaMIGIMQswCQYDVQQGEwJVQTENMAsGA1UECAwES3lp +djENMAsGA1UEBwwES3lpdjELMAkGA1UECgwCSVQxEzARBgNVBAsMCkJseW5rIElu +Yy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNvbTEfMB0GCSqGSIb3DQEJARYQZG1p +dHJpeUBibHluay5jYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALso +bhbXQuNlzYBFa9h9pd69n43yrGTL4Ba6k5Q1zDwY9HQbMdfC5ZfnCkqT7Zf+R5MO +RW0Q9nLsFNLJkwKnluRCYGyUES8NAmDLQBbZoVc8mv9K3mIgAQvGyY2LmKak5GSI +V0PC3x+iN03xU2774+Zi7DaQd7vTl/9RGk8McyHe/s5Ikbe14bzWcY9ZV4PKgCck +p1chbmLhSfGbT3v64sL8ZbIppQk57/JgsZMrVpjExvxQPZuJfWbtoypPfpYO+O8l +1szaMlTEPIZVMoYi9uE+DnOlhzJFn6Ac4FMrDzJXzMmCweSX3IxguvXALeKhUHQJ ++VP3G6Q3pkZRVKz+5XsCAwEAAaNQME4wHQYDVR0OBBYEFJtqtI62Io66cZgiTR5L +A5Tl5m+xMB8GA1UdIwQYMBaAFJtqtI62Io66cZgiTR5LA5Tl5m+xMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKphjtEOGs7oC3S87+AUgIw4gFNOuv+L +C98/l47OD6WtsqJKvCZ1lmKxY5aIro9FBPk8ktCOsbwEjE+nyr5wul+6CLFr+rnv +7OHYGwLpjoz+rZgYJiQ61E1m0AZ4y9Fyd+D90HW6247vrBXyEiUXOhN/oDDVfDQA +eqmNBx1OqWel81D3tA7zPMA7vUItyWcFIXNjOCP+POy7TMxZuhuPMh5bVu+/cthl +/Q9u/Z2lKl4CWV0Ivt2BtlN6iefva0e2AP/As+gfwjxrb0t11zSILLNJ+nxRIwg+ +k4MGb1zihKbIXUzsjslONK4FY5rlQUSwKJgEAVF0ClxB4g6dECm0ckc= +-----END CERTIFICATE----- From 5f44be85e818afda45bd9f90e3418804a53879f3 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 21 Oct 2019 13:56:21 +0300 Subject: [PATCH 40/60] cPython ssl socket feature --- README.md | 16 ++- blynklib.py | 4 +- blynklib_cp.py | 22 ++--- ...blynk-cloudcom.crt => blynk-cloud.com.crt} | 46 ++++----- examples/11_ssl_socket.py | 97 +++++++++++++++++++ setup.py | 4 +- test/test_blynk_connection.py | 58 ++++------- test/test_blynk_main.py | 2 +- test/test_blynk_protocol.py | 32 +++--- 9 files changed, 178 insertions(+), 103 deletions(-) rename certificate/{_blynk-cloudcom.crt => blynk-cloud.com.crt} (98%) create mode 100644 examples/11_ssl_socket.py diff --git a/README.md b/README.md index 5593253..ab2ed30 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ pip install --user -e . ``` #### Testing -You can run unit tests on cPython systems using the command: +You can run unit tests for cPython version of library (blynklib_cp.py) systems using the command: python setup.py test @@ -115,15 +115,12 @@ blynk = blynklib.Blynk(BLYNK_AUTH) # advanced options of lib init # from __future__ import print_function -# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, ssl=False, heartbeat=10, rcv_buffer=1024, log=print) +# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, log=print) -# Lib init with hardware SSL connection example -# blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='default') -# the ssl_cert='default' is for default blynk server (blynk-cloud.com) or self hosted server with Let's Encrypt certificate. - -# if you have self hosted server with your own self signed certificate, then you can change ssl_cert with your -# own CA file. For example if you have CA file in /home/blynk/ca.crt, then the param will be -# blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_mode='/home/blynk/ca.crt') +# Lib init with SSL socket connection +# blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='') +# current blynk-cloud.com certificate stored in project as +# https://github.com/blynkkk/lib-python/blob/master/certificate/blynk-cloud.com.crt # register handler for Virtual Pin V22 reading by Blynk App. # when a widget in Blynk App asks Virtual Pin data from server within given configurable interval (1,2,5,10 sec etc) @@ -170,6 +167,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** - [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties - [10_rtc_sync.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_rtc_sync.py): How to perform RTC sync with blynk server +- [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_ssl_socket.py): SSL server connection. Feature available only fo cPython. ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. diff --git a/blynklib.py b/blynklib.py index e5ab5fc..0341f2e 100644 --- a/blynklib.py +++ b/blynklib.py @@ -67,7 +67,7 @@ class Protocol(object): STATUS_OK = const(200) VPIN_MAX_NUM = const(32) - _msg_id = 0 + _msg_id = 1 def _get_msg_id(self, **kwargs): if 'msg_id' in kwargs: @@ -381,4 +381,4 @@ def run(self): self.log(b_err) self.disconnect() except Exception as g_exc: - self.log(g_exc) \ No newline at end of file + self.log(g_exc) diff --git a/blynklib_cp.py b/blynklib_cp.py index c7e25d9..6bc5d64 100644 --- a/blynklib_cp.py +++ b/blynklib_cp.py @@ -2,9 +2,8 @@ # Copyright (c) 2015-2019 Volodymyr Shymanskyy. # See the file LICENSE for copying permission. -__version__ = '0.2.5' +__version__ = '0.2.6' -import os import socket import ssl import struct @@ -147,7 +146,8 @@ class Connection(Protocol): _last_ping_time = 0 _last_send_time = 0 - def __init__(self, token, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, log=stub_log): + def __init__(self, token, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, + log=stub_log): self.token = token self.server = server self.port = port @@ -175,7 +175,7 @@ def receive(self, length, timeout): d_buff = d_buff[:length] return d_buff except (IOError, OSError) as err: - if str(err) == 'timed out': + if 'timed out' in str(err): return b'' if str(self.EAGAIN) in str(err) or str(self.ETIMEDOUT) in str(err): return b'' @@ -202,16 +202,14 @@ def _get_socket(self): self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) self._socket.settimeout(self.SOCK_TIMEOUT) if self.ssl_cert: + # system’s default CA certificates case + if self.ssl_cert == "default": + self.ssl_cert = None self.log('Using SSL socket...') - if (self.ssl_cert == "default"): - sslContext = ssl.create_default_context() - caFile = os.path.dirname(__file__) + "/certificate/_blynk-cloudcom.crt" - sslContext.load_verify_locations(cafile=caFile) - else: - sslContext = ssl.create_default_context(cafile=self.ssl_cert) - sslContext.verify_mode = ssl.CERT_REQUIRED + ssl_context = ssl.create_default_context(cafile=self.ssl_cert) + ssl_context.verify_mode = ssl.CERT_REQUIRED self._socket.settimeout(self.SOCK_SSL_TIMEOUT) - self._socket = sslContext.wrap_socket(sock=self._socket, server_hostname=self.server) + self._socket = ssl_context.wrap_socket(sock=self._socket, server_hostname=self.server) self.log('Connected to blynk server') except Exception as g_exc: raise BlynkError('Connection with the Blynk server failed: {}'.format(g_exc)) diff --git a/certificate/_blynk-cloudcom.crt b/certificate/blynk-cloud.com.crt similarity index 98% rename from certificate/_blynk-cloudcom.crt rename to certificate/blynk-cloud.com.crt index a83f9d8..040d61d 100644 --- a/certificate/_blynk-cloudcom.crt +++ b/certificate/blynk-cloud.com.crt @@ -1,23 +1,23 @@ ------BEGIN CERTIFICATE----- -MIID5TCCAs2gAwIBAgIJAIHSnb+cv4ECMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD -VQQGEwJVQTENMAsGA1UECAwES3lpdjENMAsGA1UEBwwES3lpdjELMAkGA1UECgwC -SVQxEzARBgNVBAsMCkJseW5rIEluYy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNv -bTEfMB0GCSqGSIb3DQEJARYQZG1pdHJpeUBibHluay5jYzAeFw0xNjAzMTcxMTU4 -MDdaFw0yMTAzMTYxMTU4MDdaMIGIMQswCQYDVQQGEwJVQTENMAsGA1UECAwES3lp -djENMAsGA1UEBwwES3lpdjELMAkGA1UECgwCSVQxEzARBgNVBAsMCkJseW5rIElu -Yy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNvbTEfMB0GCSqGSIb3DQEJARYQZG1p -dHJpeUBibHluay5jYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALso -bhbXQuNlzYBFa9h9pd69n43yrGTL4Ba6k5Q1zDwY9HQbMdfC5ZfnCkqT7Zf+R5MO -RW0Q9nLsFNLJkwKnluRCYGyUES8NAmDLQBbZoVc8mv9K3mIgAQvGyY2LmKak5GSI -V0PC3x+iN03xU2774+Zi7DaQd7vTl/9RGk8McyHe/s5Ikbe14bzWcY9ZV4PKgCck -p1chbmLhSfGbT3v64sL8ZbIppQk57/JgsZMrVpjExvxQPZuJfWbtoypPfpYO+O8l -1szaMlTEPIZVMoYi9uE+DnOlhzJFn6Ac4FMrDzJXzMmCweSX3IxguvXALeKhUHQJ -+VP3G6Q3pkZRVKz+5XsCAwEAAaNQME4wHQYDVR0OBBYEFJtqtI62Io66cZgiTR5L -A5Tl5m+xMB8GA1UdIwQYMBaAFJtqtI62Io66cZgiTR5LA5Tl5m+xMAwGA1UdEwQF -MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKphjtEOGs7oC3S87+AUgIw4gFNOuv+L -C98/l47OD6WtsqJKvCZ1lmKxY5aIro9FBPk8ktCOsbwEjE+nyr5wul+6CLFr+rnv -7OHYGwLpjoz+rZgYJiQ61E1m0AZ4y9Fyd+D90HW6247vrBXyEiUXOhN/oDDVfDQA -eqmNBx1OqWel81D3tA7zPMA7vUItyWcFIXNjOCP+POy7TMxZuhuPMh5bVu+/cthl -/Q9u/Z2lKl4CWV0Ivt2BtlN6iefva0e2AP/As+gfwjxrb0t11zSILLNJ+nxRIwg+ -k4MGb1zihKbIXUzsjslONK4FY5rlQUSwKJgEAVF0ClxB4g6dECm0ckc= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID5TCCAs2gAwIBAgIJAIHSnb+cv4ECMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD +VQQGEwJVQTENMAsGA1UECAwES3lpdjENMAsGA1UEBwwES3lpdjELMAkGA1UECgwC +SVQxEzARBgNVBAsMCkJseW5rIEluYy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNv +bTEfMB0GCSqGSIb3DQEJARYQZG1pdHJpeUBibHluay5jYzAeFw0xNjAzMTcxMTU4 +MDdaFw0yMTAzMTYxMTU4MDdaMIGIMQswCQYDVQQGEwJVQTENMAsGA1UECAwES3lp +djENMAsGA1UEBwwES3lpdjELMAkGA1UECgwCSVQxEzARBgNVBAsMCkJseW5rIElu +Yy4xGDAWBgNVBAMMD2JseW5rLWNsb3VkLmNvbTEfMB0GCSqGSIb3DQEJARYQZG1p +dHJpeUBibHluay5jYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALso +bhbXQuNlzYBFa9h9pd69n43yrGTL4Ba6k5Q1zDwY9HQbMdfC5ZfnCkqT7Zf+R5MO +RW0Q9nLsFNLJkwKnluRCYGyUES8NAmDLQBbZoVc8mv9K3mIgAQvGyY2LmKak5GSI +V0PC3x+iN03xU2774+Zi7DaQd7vTl/9RGk8McyHe/s5Ikbe14bzWcY9ZV4PKgCck +p1chbmLhSfGbT3v64sL8ZbIppQk57/JgsZMrVpjExvxQPZuJfWbtoypPfpYO+O8l +1szaMlTEPIZVMoYi9uE+DnOlhzJFn6Ac4FMrDzJXzMmCweSX3IxguvXALeKhUHQJ ++VP3G6Q3pkZRVKz+5XsCAwEAAaNQME4wHQYDVR0OBBYEFJtqtI62Io66cZgiTR5L +A5Tl5m+xMB8GA1UdIwQYMBaAFJtqtI62Io66cZgiTR5LA5Tl5m+xMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKphjtEOGs7oC3S87+AUgIw4gFNOuv+L +C98/l47OD6WtsqJKvCZ1lmKxY5aIro9FBPk8ktCOsbwEjE+nyr5wul+6CLFr+rnv +7OHYGwLpjoz+rZgYJiQ61E1m0AZ4y9Fyd+D90HW6247vrBXyEiUXOhN/oDDVfDQA +eqmNBx1OqWel81D3tA7zPMA7vUItyWcFIXNjOCP+POy7TMxZuhuPMh5bVu+/cthl +/Q9u/Z2lKl4CWV0Ivt2BtlN6iefva0e2AP/As+gfwjxrb0t11zSILLNJ+nxRIwg+ +k4MGb1zihKbIXUzsjslONK4FY5rlQUSwKJgEAVF0ClxB4g6dECm0ckc= +-----END CERTIFICATE----- diff --git a/examples/11_ssl_socket.py b/examples/11_ssl_socket.py new file mode 100644 index 0000000..5174b4b --- /dev/null +++ b/examples/11_ssl_socket.py @@ -0,0 +1,97 @@ +""" +[SSL CONNECT/DISCONNECT EVENTS EXAMPLE] ================================================================= +NOTE! + This example works correctly only fo cPython version of library (blynklib_cp.py) + For micropython present limitation that keyword arguments of wrap_socket may be not supported + + +Environment prepare: + - define your auth token for current example and run it + + +This started program after successful connect operation will call and execute "connect event handler" +Within handler after short sleep delay blynk disconnect call will be performed that will trigger +"disconnect event handler" execution. + +Schema: +===================================================================================================== + +-----------+ +--------------+ + | | | | + | blynk lib | | blynk server | + | | | virtual pin | + | | | | + +-----+-----+ +------+-------+ + | | + | connect/authenticate request | + +------------------------------------>+ + connect handler | | + (user function) | connected successfully | + +-----------<------------------------------------+ + | | | + | | disconnect request | + +--------->------------------------------------->+ + | | + | | +disconnect handler | | + (user function) | disconnected successfully | + +-----------<------------------------------------+ + | | | + | | | + +--------->+ | + | reconnect request | + | performed by lib automatically | + +------------------------------------>+ + + + + + +==================================================================================================== +Additional info about blynk you can find by examining such resources: + + Downloads, docs, tutorials: https://blynk.io + Sketch generator: http://examples.blynk.cc + Blynk community: http://community.blynk.cc + Social networks: http://www.fb.com/blynkapp + http://twitter.com/blynk_app +===================================================================================================== +""" + +import blynklib_cp as blynklib +import time +import logging + +# tune console logging +_log = logging.getLogger('BlynkLog') +logFormatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") +consoleHandler = logging.StreamHandler() +consoleHandler.setFormatter(logFormatter) +_log.addHandler(consoleHandler) +_log.setLevel(logging.DEBUG) + +BLYNK_AUTH = 'YourAuthToken' + +blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='../certificate/blynk-cloud.com.crt', log=_log.info) + +CONNECT_PRINT_MSG = '[CONNECT_EVENT]' +DISCONNECT_PRINT_MSG = '[DISCONNECT_EVENT]' + + +@blynk.handle_event("connect") +def connect_handler(): + print(CONNECT_PRINT_MSG) + print('Sleeping 4 sec in SSL connect handler...') + time.sleep(4) + blynk.disconnect() + + +@blynk.handle_event("disconnect") +def disconnect_handler(): + print(DISCONNECT_PRINT_MSG) + print('Sleeping 3 sec in SSL disconnect handler...') + time.sleep(5) + + +########################################################### +# infinite loop that waits for event +########################################################### +while True: + blynk.run() diff --git a/setup.py b/setup.py index 0421be7..01c0295 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='blynklib', - version='0.2.5', + version='0.2.6', description='Blynk Python/Micropython library', long_description=long_description, long_description_content_type="text/markdown", @@ -15,7 +15,7 @@ author_email='antoha.ua@gmail.com', setup_requires=['pytest-runner', ], tests_require=['pytest', 'pytest-mock', ], - py_modules=['blynklib', 'blynktimer'], + py_modules=['blynklib_cp', 'blynktimer'], classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", diff --git a/test/test_blynk_connection.py b/test/test_blynk_connection.py index 74e4325..7ba5418 100644 --- a/test/test_blynk_connection.py +++ b/test/test_blynk_connection.py @@ -3,7 +3,7 @@ import time import pytest import socket -from blynklib import Connection, BlynkError, RedirectError +from blynklib_cp import Connection, BlynkError, RedirectError class TestBlynkConnection: @@ -12,18 +12,6 @@ def cb(self): connection = Connection('1234', log=print) yield connection - def test_set_socket_timeout_positive(self, cb): - in_timeout = 10 - cb._socket = socket.socket() - cb._set_socket_timeout(in_timeout) - timeout = cb._socket.gettimeout() - assert timeout == in_timeout - - def test_set_socket_timeout_via_poll(self, cb): - in_timeout = 10 - cb._socket = 2222 - cb._set_socket_timeout(in_timeout) - def test_send(self, cb, mocker): cb._socket = socket.socket() with mocker.patch('socket.socket.send', return_value=5): @@ -57,46 +45,40 @@ def test_send_error_retry_count(self, cb, mocker): def test_receive(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', return_value=b'12345'): - result = cb.receive(10, 1) - assert result == b'12345' + with mocker.patch('socket.socket.recv', return_value=b'12345'): + result = cb.receive(10, 1) + assert result == b'12345' def test_receive_timeout(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', side_effect=OSError('timed out')): - result = cb.receive(10, 1) - assert result == b'' + with mocker.patch('socket.socket.recv', side_effect=OSError('timed out')): + result = cb.receive(10, 1) + assert result == b'' def test_receive_timeout_2(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', side_effect=socket.timeout('timed out')): - result = cb.receive(10, 1) - assert result == b'' + with mocker.patch('socket.socket.recv', side_effect=socket.timeout('timed out')): + result = cb.receive(10, 1) + assert result == b'' def test_receive_eagain(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', side_effect=IOError('[Errno 11]')): - result = cb.receive(10, 1) - assert result == b'' + with mocker.patch('socket.socket.recv', side_effect=IOError('[Errno 11]')): + result = cb.receive(10, 1) + assert result == b'' def test_receive_etimeout(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 60]')): - result = cb.receive(10, 1) - assert result == b'' + with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 60]')): + result = cb.receive(10, 1) + assert result == b'' def test_receive_raise_other_oserror(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch.object(cb, '_set_socket_timeout', return_value=None): - with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 13]')): - with pytest.raises(OSError) as os_err: - cb.receive(10, 1) - assert '[Errno 13]' in str(os_err.value) + with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 13]')): + with pytest.raises(OSError) as os_err: + cb.receive(10, 1) + assert '[Errno 13]' in str(os_err.value) def test_is_server_alive_negative(self, cb): result = cb.is_server_alive() diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index 8bd369c..1ce1586 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -2,7 +2,7 @@ from __future__ import print_function import socket import pytest -import blynklib +import blynklib_cp as blynklib class TestBlynk: diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index c0cd6a7..ed6fb2b 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import sys import pytest -from blynklib import Protocol, BlynkError +from blynklib_cp import Protocol, BlynkError class TestBlynkProtocol: @@ -23,7 +23,7 @@ def test_get_msg_id_before_loop(self, pb): def test_get_msg_id_after_loop(self, pb): pb._msg_id = 0xFFFF msg_id = pb._get_msg_id() - assert msg_id == 1 + assert msg_id == 0 def test_get_msg_id_defined(self, pb): pb._msg_id = 0xFFFF @@ -34,13 +34,13 @@ def test_pack_msg(self, pb): msg_type = 20 args = ["test", 1234, 745, 'abcde'] result = pb._pack_msg(msg_type, *args) - assert result == b'\x14\x00\x02\x00\x13test\x001234\x00745\x00abcde' + assert result == b'\x14\x00\x01\x00\x13test\x001234\x00745\x00abcde' def test_pack_msg_no_args(self, pb): msg_type = 15 args = [] result = pb._pack_msg(msg_type, *args) - assert result == b'\x0f\x00\x02\x00\x00' + assert result == b'\x0f\x00\x01\x00\x00' def test_pack_msg_unicode(self, pb): if sys.version_info[0] == 2: @@ -49,7 +49,7 @@ def test_pack_msg_unicode(self, pb): msg_type = 20 args = ["ёж"] result = pb._pack_msg(msg_type, *args) - assert result == b'\x14\x00\x02\x00\x04\xd1\x91\xd0\xb6' + assert result == b'\x14\x00\x01\x00\x04\xd1\x91\xd0\xb6' def test_parse_response_msg_hw(self, pb): data = b'\x14\x00\x02\x00\x13test\x001234\x00745\x00abcde' @@ -99,44 +99,44 @@ def test_parse_response_msg_hw_unicode(self, pb): def test_heartbeat_msg(self, pb): result = pb.heartbeat_msg(20, 2048) - assert result == b'\x11\x00\x02\x00+ver\x000.2.5\x00buff-in\x002048\x00h-beat\x0020\x00dev\x00python' + assert result == b'\x11\x00\x01\x00+ver\x000.2.6\x00buff-in\x002048\x00h-beat\x0020\x00dev\x00python' def test_login_msg(self, pb): result = pb.login_msg('1234') - assert result == b'\x02\x00\x02\x00\x041234' + assert result == b'\x02\x00\x01\x00\x041234' def test_ping_msg(self, pb): result = pb.ping_msg() - assert result == b'\x06\x00\x02\x00\x00' + assert result == b'\x06\x00\x01\x00\x00' def test_response_msg(self, pb): result = pb.response_msg(202) - assert result == b'\x00\x00\x02\x00\x03202' + assert result == b'\x00\x00\x01\x00\x03202' def test_virtual_write_msg(self, pb): result = pb.virtual_write_msg(127, 'abc', 123) - assert result == b'\x14\x00\x02\x00\x0evw\x00127\x00abc\x00123' + assert result == b'\x14\x00\x01\x00\x0evw\x00127\x00abc\x00123' def test_virtual_sync_msg(self, pb): result = pb.virtual_sync_msg(1, 24) - assert result == b'\x10\x00\x02\x00\x07vr\x001\x0024' + assert result == b'\x10\x00\x01\x00\x07vr\x001\x0024' def test_email_msg(self, pb): result = pb.email_msg('a@b.com', 'Test', 'MSG') - assert result == b'\r\x00\x02\x00\x10a@b.com\x00Test\x00MSG' + assert result == b'\r\x00\x01\x00\x10a@b.com\x00Test\x00MSG' def test_tweet_msg(self, pb): result = pb.tweet_msg('tweet_msg_test') - assert result == b'\x0c\x00\x02\x00\x0etweet_msg_test' + assert result == b'\x0c\x00\x01\x00\x0etweet_msg_test' def test_notify_msg(self, pb): result = pb.notify_msg('app_msg_test') - assert result == b'\x0e\x00\x02\x00\x0capp_msg_test' + assert result == b'\x0e\x00\x01\x00\x0capp_msg_test' def test_set_property_msg(self, pb): result = pb.set_property_msg(10, 'color', '#FF00EE') - assert result == b'\x13\x00\x02\x00\x1010\x00color\x00#FF00EE' + assert result == b'\x13\x00\x01\x00\x1010\x00color\x00#FF00EE' def test_internal_msg(self, pb): result = pb.internal_msg('rtc', 'sync') - assert result == b'\x11\x00\x02\x00\x08rtc\x00sync' + assert result == b'\x11\x00\x01\x00\x08rtc\x00sync' From 1e3bc4f3bb4ce7538f86a64e01c114e8e3a1bb8e Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 21 Oct 2019 14:01:48 +0300 Subject: [PATCH 41/60] ssl example path fix --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab2ed30..6b08641 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ pip install --user -e . ``` #### Testing -You can run unit tests for cPython version of library (blynklib_cp.py) systems using the command: +You can run unit tests for cPython version of library (blynklib_cp.py) using the command: python setup.py test @@ -80,7 +80,7 @@ This library supports Python2, Python3, and Micropython. - Tested to work with: Raspberry Pi (any), ESP32, ESP8266 ##### List of available operations: - - Subscribe to connect/disconnect events + - Subscribe to connect/disconnect events (ssl connection available only for cPython lib) - Subscribe to read/write events of [virtual pins][blynk-vpins] - [Virtual Pin][blynk-vpins] write - [Virtual Pin][blynk-vpins] sync @@ -115,7 +115,8 @@ blynk = blynklib.Blynk(BLYNK_AUTH) # advanced options of lib init # from __future__ import print_function -# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, log=print) +# blynk = blynklib.Blynk(BLYNK_AUTH, server='blynk-cloud.com', port=80, ssl_cert=None, +# heartbeat=10, rcv_buffer=1024, log=print) # Lib init with SSL socket connection # blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='') @@ -167,7 +168,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** - [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties - [10_rtc_sync.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_rtc_sync.py): How to perform RTC sync with blynk server -- [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_ssl_socket.py): SSL server connection. Feature available only fo cPython. +- [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/11_ssl_socket.py): SSL server connection. Feature available only fo cPython. ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. From 81dfc160a94c02ef03737cf00e426f69298dd148 Mon Sep 17 00:00:00 2001 From: amorozenko Date: Mon, 21 Oct 2019 14:53:40 +0300 Subject: [PATCH 42/60] micropython code extracted into separate library blynklib_mp.py --- README.md | 10 +- blynklib.py | 135 +++++++++-------- blynklib_cp.py => blynklib_mp.py | 137 ++++++++---------- examples/11_ssl_socket.py | 6 +- examples/esp32/01_touch_button.py | 2 +- examples/esp32/02_terminal_cli.py | 2 +- .../esp32/03_temperature_humidity_dht22.py | 2 +- examples/esp32/README.md | 8 +- examples/esp8266/01_potentiometer.py | 2 +- examples/esp8266/README.md | 14 +- setup.py | 2 +- test/test_blynk_connection.py | 2 +- test/test_blynk_main.py | 2 +- test/test_blynk_protocol.py | 2 +- 14 files changed, 157 insertions(+), 169 deletions(-) rename blynklib_cp.py => blynklib_mp.py (83%) diff --git a/README.md b/README.md index 6b08641..8a7042b 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ pip install --user -e . ``` #### Testing -You can run unit tests for cPython version of library (blynklib_cp.py) using the command: +You can run unit tests for cPython version of library (blynklib.py) using the command: python setup.py test @@ -73,14 +73,14 @@ and related installation docs can be found [here][micropython-pkg]. ## Features -This library supports Python2, Python3, and Micropython. +This library supports Python2, Python3 (blynklib.py) , and Micropython (blynklib_mp.py). - Communication with public or local [Blynk Server][blynk-server]. - Exchange any data between your hardware and app - Tested to work with: Raspberry Pi (any), ESP32, ESP8266 ##### List of available operations: - - Subscribe to connect/disconnect events (ssl connection available only for cPython lib) + - Subscribe to connect/disconnect events (ssl connection supported only by cPython lib) - Subscribe to read/write events of [virtual pins][blynk-vpins] - [Virtual Pin][blynk-vpins] write - [Virtual Pin][blynk-vpins] sync @@ -108,6 +108,7 @@ BLYNK_AUTH = '' #insert your Auth Token here #### Usage example ```python import blynklib +# import blynklib_mp as blynklib # micropython import BLYNK_AUTH = '' #insert your Auth Token here # base lib init @@ -122,6 +123,7 @@ blynk = blynklib.Blynk(BLYNK_AUTH) # blynk = blynklib.Blynk(BLYNK_AUTH, port=443, ssl_cert='') # current blynk-cloud.com certificate stored in project as # https://github.com/blynkkk/lib-python/blob/master/certificate/blynk-cloud.com.crt +# Note! ssl feature supported only by cPython # register handler for Virtual Pin V22 reading by Blynk App. # when a widget in Blynk App asks Virtual Pin data from server within given configurable interval (1,2,5,10 sec etc) @@ -168,7 +170,7 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [08_blynk_timer.py](https://github.com/blynkkk/lib-python/blob/master/examples/08_blynk_timer.py): How send data periodically from hardware by using **[Blynk Timer][blynktimer-doc]** - [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties - [10_rtc_sync.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_rtc_sync.py): How to perform RTC sync with blynk server -- [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/11_ssl_socket.py): SSL server connection. Feature available only fo cPython. +- [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/11_ssl_socket.py): SSL server connection. Feature supported only by cPython library. ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. diff --git a/blynklib.py b/blynklib.py index 0341f2e..6bc5d64 100644 --- a/blynklib.py +++ b/blynklib.py @@ -2,28 +2,12 @@ # Copyright (c) 2015-2019 Volodymyr Shymanskyy. # See the file LICENSE for copying permission. -__version__ = '0.2.5' +__version__ = '0.2.6' -try: - import usocket as socket - import utime as time - import ustruct as struct - import uselect as select - from micropython import const - - ticks_ms = time.ticks_ms - sleep_ms = time.sleep_ms - - IOError = OSError -except ImportError: - import socket - import time - import struct - import select - - const = lambda x: x - ticks_ms = lambda: int(time.time() * 1000) - sleep_ms = lambda x: time.sleep(x // 1000) +import socket +import ssl +import struct +import time LOGO = """ ___ __ __ @@ -37,6 +21,14 @@ def stub_log(*args): pass +def ticks_ms(): + return int(time.time() * 1000) + + +def sleep_ms(ms): + time.sleep(ms // 1000) + + class BlynkError(Exception): pass @@ -48,32 +40,32 @@ def __init__(self, server, port): class Protocol(object): - MSG_RSP = const(0) - MSG_LOGIN = const(2) - MSG_PING = const(6) - MSG_TWEET = const(12) - MSG_EMAIL = const(13) - MSG_NOTIFY = const(14) - MSG_BRIDGE = const(15) - MSG_HW_SYNC = const(16) - MSG_INTERNAL = const(17) - MSG_PROPERTY = const(19) - MSG_HW = const(20) - MSG_REDIRECT = const(41) - MSG_HEAD_LEN = const(5) - - STATUS_INVALID_TOKEN = const(9) - STATUS_NO_DATA = const(17) - STATUS_OK = const(200) - VPIN_MAX_NUM = const(32) - - _msg_id = 1 + MSG_RSP = 0 + MSG_LOGIN = 2 + MSG_PING = 6 + MSG_TWEET = 12 + MSG_EMAIL = 13 + MSG_NOTIFY = 14 + MSG_BRIDGE = 15 + MSG_HW_SYNC = 16 + MSG_INTERNAL = 17 + MSG_PROPERTY = 19 + MSG_HW = 20 + MSG_REDIRECT = 41 + MSG_HEAD_LEN = 5 + + STATUS_INVALID_TOKEN = 9 + STATUS_NO_DATA = 17 + STATUS_OK = 200 + VPIN_MAX_NUM = 32 + + _msg_id = 0 def _get_msg_id(self, **kwargs): if 'msg_id' in kwargs: return kwargs['msg_id'] self._msg_id += 1 - return self._msg_id if self._msg_id <= 0xFFFF else 1 + return self._msg_id if self._msg_id <= 0xFFFF else 0 def _pack_msg(self, msg_type, *args, **kwargs): data = ('\0'.join([str(curr_arg) for curr_arg in args])).encode('utf-8') @@ -134,18 +126,19 @@ def internal_msg(self, *args): class Connection(Protocol): - SOCK_MAX_TIMEOUT = const(5) + SOCK_MAX_TIMEOUT = 5 SOCK_TIMEOUT = 0.05 - EAGAIN = const(11) - ETIMEDOUT = const(60) - RETRIES_TX_DELAY = const(2) - RETRIES_TX_MAX_NUM = const(3) - RECONNECT_SLEEP = const(1) - TASK_PERIOD_RES = const(50) - DISCONNECTED = const(0) - CONNECTING = const(1) - AUTHENTICATING = const(2) - AUTHENTICATED = const(3) + SOCK_SSL_TIMEOUT = 1 + EAGAIN = 11 + ETIMEDOUT = 60 + RETRIES_TX_DELAY = 2 + RETRIES_TX_MAX_NUM = 3 + RECONNECT_SLEEP = 1 + TASK_PERIOD_RES = 50 + DISCONNECTED = 0 + CONNECTING = 1 + AUTHENTICATING = 2 + AUTHENTICATED = 3 _state = None _socket = None @@ -153,21 +146,15 @@ class Connection(Protocol): _last_ping_time = 0 _last_send_time = 0 - def __init__(self, token, server='blynk-cloud.com', port=80, heartbeat=10, rcv_buffer=1024, log=stub_log): + def __init__(self, token, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, + log=stub_log): self.token = token self.server = server self.port = port self.heartbeat = heartbeat self.rcv_buffer = rcv_buffer self.log = log - - def _set_socket_timeout(self, timeout): - if getattr(self._socket, 'settimeout', None): - self._socket.settimeout(timeout) - else: - p = select.poll() - p.register(self._socket) - p.poll(int(timeout * 1000)) + self.ssl_cert = ssl_cert def send(self, data): retries = self.RETRIES_TX_MAX_NUM @@ -182,13 +169,13 @@ def send(self, data): def receive(self, length, timeout): d_buff = b'' try: - self._set_socket_timeout(timeout) + self._socket.settimeout(timeout) d_buff += self._socket.recv(length) if len(d_buff) >= length: d_buff = d_buff[:length] return d_buff except (IOError, OSError) as err: - if str(err) == 'timed out': + if 'timed out' in str(err): return b'' if str(self.EAGAIN) in str(err) or str(self.ETIMEDOUT) in str(err): return b'' @@ -213,7 +200,16 @@ def _get_socket(self): self._state = self.CONNECTING self._socket = socket.socket() self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) - self._set_socket_timeout(self.SOCK_TIMEOUT) + self._socket.settimeout(self.SOCK_TIMEOUT) + if self.ssl_cert: + # system’s default CA certificates case + if self.ssl_cert == "default": + self.ssl_cert = None + self.log('Using SSL socket...') + ssl_context = ssl.create_default_context(cafile=self.ssl_cert) + ssl_context.verify_mode = ssl.CERT_REQUIRED + self._socket.settimeout(self.SOCK_SSL_TIMEOUT) + self._socket = ssl_context.wrap_socket(sock=self._socket, server_hostname=self.server) self.log('Connected to blynk server') except Exception as g_exc: raise BlynkError('Connection with the Blynk server failed: {}'.format(g_exc)) @@ -250,7 +246,7 @@ def connected(self): class Blynk(Connection): - _CONNECT_TIMEOUT = const(30) # 30sec + _CONNECT_TIMEOUT = 30 # 30sec _VPIN_WILDCARD = '*' _VPIN_READ = 'read v' _VPIN_WRITE = 'write v' @@ -300,6 +296,7 @@ def disconnect(self, err_msg=None): self._state = self.DISCONNECTED if err_msg: self.log('[ERROR]: {}\nConnection closed'.format(err_msg)) + self._msg_id = 0 time.sleep(self.RECONNECT_SLEEP) def virtual_write(self, v_pin, *val): @@ -351,11 +348,11 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif msg_type == self.MSG_PING: self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): - if msg_type == self.MSG_INTERNAL and len(msg_args) >= const(2): + if msg_type == self.MSG_INTERNAL and len(msg_args) >= 2: self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) - elif len(msg_args) >= const(3) and msg_args[0] == 'vw': + elif len(msg_args) >= 3 and msg_args[0] == 'vw': self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) - elif len(msg_args) == const(2) and msg_args[0] == 'vr': + elif len(msg_args) == 2 and msg_args[0] == 'vr': self.call_handler("{}{}".format(self._VPIN_READ, msg_args[1]), int(msg_args[1])) def read_response(self, timeout=0.5): diff --git a/blynklib_cp.py b/blynklib_mp.py similarity index 83% rename from blynklib_cp.py rename to blynklib_mp.py index 6bc5d64..b85b2f1 100644 --- a/blynklib_cp.py +++ b/blynklib_mp.py @@ -4,10 +4,16 @@ __version__ = '0.2.6' -import socket -import ssl -import struct -import time +import usocket as socket +import utime as time +import ustruct as struct +import uselect as select +from micropython import const + +ticks_ms = time.ticks_ms +sleep_ms = time.sleep_ms + +IOError = OSError LOGO = """ ___ __ __ @@ -21,14 +27,6 @@ def stub_log(*args): pass -def ticks_ms(): - return int(time.time() * 1000) - - -def sleep_ms(ms): - time.sleep(ms // 1000) - - class BlynkError(Exception): pass @@ -40,32 +38,31 @@ def __init__(self, server, port): class Protocol(object): - MSG_RSP = 0 - MSG_LOGIN = 2 - MSG_PING = 6 - MSG_TWEET = 12 - MSG_EMAIL = 13 - MSG_NOTIFY = 14 - MSG_BRIDGE = 15 - MSG_HW_SYNC = 16 - MSG_INTERNAL = 17 - MSG_PROPERTY = 19 - MSG_HW = 20 - MSG_REDIRECT = 41 - MSG_HEAD_LEN = 5 - - STATUS_INVALID_TOKEN = 9 - STATUS_NO_DATA = 17 - STATUS_OK = 200 - VPIN_MAX_NUM = 32 - - _msg_id = 0 + MSG_RSP = const(0) + MSG_LOGIN = const(2) + MSG_PING = const(6) + MSG_TWEET = const(12) + MSG_EMAIL = const(13) + MSG_NOTIFY = const(14) + MSG_BRIDGE = const(15) + MSG_HW_SYNC = const(16) + MSG_INTERNAL = const(17) + MSG_PROPERTY = const(19) + MSG_HW = const(20) + MSG_REDIRECT = const(41) + MSG_HEAD_LEN = const(5) + + STATUS_INVALID_TOKEN = const(9) + STATUS_OK = const(200) + VPIN_MAX_NUM = const(32) + + _msg_id = 1 def _get_msg_id(self, **kwargs): if 'msg_id' in kwargs: return kwargs['msg_id'] - self._msg_id += 1 - return self._msg_id if self._msg_id <= 0xFFFF else 0 + self._msg_id += const(1) + return self._msg_id if self._msg_id <= const(0xFFFF) else const(1) def _pack_msg(self, msg_type, *args, **kwargs): data = ('\0'.join([str(curr_arg) for curr_arg in args])).encode('utf-8') @@ -92,7 +89,7 @@ def parse_response(self, rsp_data, msg_buffer): def heartbeat_msg(self, heartbeat, rcv_buffer): return self._pack_msg(self.MSG_INTERNAL, 'ver', __version__, 'buff-in', rcv_buffer, 'h-beat', heartbeat, - 'dev', 'python') + 'dev', 'mpython') def login_msg(self, token): return self._pack_msg(self.MSG_LOGIN, token) @@ -126,19 +123,18 @@ def internal_msg(self, *args): class Connection(Protocol): - SOCK_MAX_TIMEOUT = 5 + SOCK_MAX_TIMEOUT = const(5) SOCK_TIMEOUT = 0.05 - SOCK_SSL_TIMEOUT = 1 - EAGAIN = 11 - ETIMEDOUT = 60 - RETRIES_TX_DELAY = 2 - RETRIES_TX_MAX_NUM = 3 - RECONNECT_SLEEP = 1 - TASK_PERIOD_RES = 50 - DISCONNECTED = 0 - CONNECTING = 1 - AUTHENTICATING = 2 - AUTHENTICATED = 3 + EAGAIN = const(11) + ETIMEDOUT = const(60) + RETRIES_TX_DELAY = const(2) + RETRIES_TX_MAX_NUM = const(3) + RECONNECT_SLEEP = const(1) + TASK_PERIOD_RES = const(50) + DISCONNECTED = const(0) + CONNECTING = const(1) + AUTHENTICATING = const(2) + AUTHENTICATED = const(3) _state = None _socket = None @@ -146,15 +142,18 @@ class Connection(Protocol): _last_ping_time = 0 _last_send_time = 0 - def __init__(self, token, server='blynk-cloud.com', port=80, ssl_cert=None, heartbeat=10, rcv_buffer=1024, - log=stub_log): + def __init__(self, token, server='blynk-cloud.com', port=80, heartbeat=10, rcv_buffer=1024, log=stub_log): self.token = token self.server = server self.port = port self.heartbeat = heartbeat self.rcv_buffer = rcv_buffer self.log = log - self.ssl_cert = ssl_cert + + def _set_socket_timeout(self, timeout): + p = select.poll() + p.register(self._socket) + p.poll(int(timeout * const(1000))) def send(self, data): retries = self.RETRIES_TX_MAX_NUM @@ -169,13 +168,13 @@ def send(self, data): def receive(self, length, timeout): d_buff = b'' try: - self._socket.settimeout(timeout) + self._set_socket_timeout(timeout) d_buff += self._socket.recv(length) if len(d_buff) >= length: d_buff = d_buff[:length] return d_buff except (IOError, OSError) as err: - if 'timed out' in str(err): + if str(err) == 'timed out': return b'' if str(self.EAGAIN) in str(err) or str(self.ETIMEDOUT) in str(err): return b'' @@ -183,13 +182,13 @@ def receive(self, length, timeout): def is_server_alive(self): now = ticks_ms() - h_beat_ms = self.heartbeat * 1000 + h_beat_ms = self.heartbeat * const(1000) rcv_delta = now - self._last_rcv_time ping_delta = now - self._last_ping_time send_delta = now - self._last_send_time - if rcv_delta > h_beat_ms + (h_beat_ms // 2): + if rcv_delta > h_beat_ms + (h_beat_ms // const(2)): return False - if (ping_delta > h_beat_ms // 10) and (send_delta > h_beat_ms or rcv_delta > h_beat_ms): + if (ping_delta > h_beat_ms // const(10)) and (send_delta > h_beat_ms or rcv_delta > h_beat_ms): self.send(self.ping_msg()) self.log('Heartbeat time: {}'.format(now)) self._last_ping_time = now @@ -199,20 +198,11 @@ def _get_socket(self): try: self._state = self.CONNECTING self._socket = socket.socket() - self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) - self._socket.settimeout(self.SOCK_TIMEOUT) - if self.ssl_cert: - # system’s default CA certificates case - if self.ssl_cert == "default": - self.ssl_cert = None - self.log('Using SSL socket...') - ssl_context = ssl.create_default_context(cafile=self.ssl_cert) - ssl_context.verify_mode = ssl.CERT_REQUIRED - self._socket.settimeout(self.SOCK_SSL_TIMEOUT) - self._socket = ssl_context.wrap_socket(sock=self._socket, server_hostname=self.server) - self.log('Connected to blynk server') + self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][-1]) + self._set_socket_timeout(self.SOCK_TIMEOUT) + self.log('Connected to server') except Exception as g_exc: - raise BlynkError('Connection with the Blynk server failed: {}'.format(g_exc)) + raise BlynkError('Server connection failed: {}'.format(g_exc)) def _authenticate(self): self.log('Authenticating device...') @@ -246,7 +236,7 @@ def connected(self): class Blynk(Connection): - _CONNECT_TIMEOUT = 30 # 30sec + _CONNECT_TIMEOUT = const(30) # 30sec _VPIN_WILDCARD = '*' _VPIN_READ = 'read v' _VPIN_WRITE = 'write v' @@ -296,7 +286,6 @@ def disconnect(self, err_msg=None): self._state = self.DISCONNECTED if err_msg: self.log('[ERROR]: {}\nConnection closed'.format(err_msg)) - self._msg_id = 0 time.sleep(self.RECONNECT_SLEEP) def virtual_write(self, v_pin, *val): @@ -348,11 +337,11 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif msg_type == self.MSG_PING: self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): - if msg_type == self.MSG_INTERNAL and len(msg_args) >= 2: + if msg_type == self.MSG_INTERNAL and len(msg_args) >= const(2): self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) - elif len(msg_args) >= 3 and msg_args[0] == 'vw': + elif len(msg_args) >= const(3) and msg_args[0] == 'vw': self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) - elif len(msg_args) == 2 and msg_args[0] == 'vr': + elif len(msg_args) == const(2) and msg_args[0] == 'vr': self.call_handler("{}{}".format(self._VPIN_READ, msg_args[1]), int(msg_args[1])) def read_response(self, timeout=0.5): @@ -371,7 +360,7 @@ def run(self): try: self.read_response(timeout=self.SOCK_TIMEOUT) if not self.is_server_alive(): - self.disconnect('Blynk server is offline') + self.disconnect('Server is offline') except KeyboardInterrupt: raise except BlynkError as b_err: diff --git a/examples/11_ssl_socket.py b/examples/11_ssl_socket.py index 5174b4b..273213b 100644 --- a/examples/11_ssl_socket.py +++ b/examples/11_ssl_socket.py @@ -1,8 +1,8 @@ """ [SSL CONNECT/DISCONNECT EVENTS EXAMPLE] ================================================================= NOTE! - This example works correctly only fo cPython version of library (blynklib_cp.py) - For micropython present limitation that keyword arguments of wrap_socket may be not supported + This example works correctly only fo cPython version of library (blynklib.py) + For micropython present limitation that keyword arguments of wrap_socket may be not supported by certain ports Environment prepare: @@ -55,7 +55,7 @@ ===================================================================================================== """ -import blynklib_cp as blynklib +import blynklib import time import logging diff --git a/examples/esp32/01_touch_button.py b/examples/esp32/01_touch_button.py index 695dc79..c831a70 100644 --- a/examples/esp32/01_touch_button.py +++ b/examples/esp32/01_touch_button.py @@ -34,7 +34,7 @@ http://twitter.com/blynk_app ===================================================================================================================== """ -import blynklib +import blynklib_mp as blynklib import network import utime as time from machine import Pin diff --git a/examples/esp32/02_terminal_cli.py b/examples/esp32/02_terminal_cli.py index 126f926..9fa33a8 100644 --- a/examples/esp32/02_terminal_cli.py +++ b/examples/esp32/02_terminal_cli.py @@ -33,7 +33,7 @@ http://twitter.com/blynk_app ===================================================================================================================== """ -import blynklib +import blynklib_mp as blynklib import network import uos import utime as time diff --git a/examples/esp32/03_temperature_humidity_dht22.py b/examples/esp32/03_temperature_humidity_dht22.py index de8cdb5..87c3c0d 100644 --- a/examples/esp32/03_temperature_humidity_dht22.py +++ b/examples/esp32/03_temperature_humidity_dht22.py @@ -48,7 +48,7 @@ http://twitter.com/blynk_app ===================================================================================================================== """ -import blynklib +import blynklib_mp as blynklib import network import utime as time from machine import Pin diff --git a/examples/esp32/README.md b/examples/esp32/README.md index 319ffa0..99433c0 100644 --- a/examples/esp32/README.md +++ b/examples/esp32/README.md @@ -59,7 +59,7 @@ ```bash export AMPY_PORT=/dev/ttyUSB0 ampy mkdir /lib - ampy put blynklib.py /lib/blynklib.py + ampy put blynklib_mp.py /lib/blynklib_mp.py ampy put test.py test.py ampy run test.py ``` @@ -87,17 +87,17 @@ For **.mpy** file compilation you need: ``` - compile source code and get .mpy file ```bash - ./mpy-cross -X heapsize=2812256 blynklib.py + ./mpy-cross -X heapsize=2812256 blynklib_mp.py ``` - .mpy files in the same manner can be placed to board libs with **[ampy][micropython-ampy]** as usual .py file ```bash - ampy put blynklib.mpy /lib/blynklib.mpy + ampy put blynklib_mp.mpy /lib/blynklib_mp.mpy ``` and then imported within user scripts as usual .py lib ```python # start of user script - import blynklib + import blynklib_mp ``` ## Wifi Connection diff --git a/examples/esp8266/01_potentiometer.py b/examples/esp8266/01_potentiometer.py index 612bbbb..48339f0 100644 --- a/examples/esp8266/01_potentiometer.py +++ b/examples/esp8266/01_potentiometer.py @@ -36,7 +36,7 @@ http://twitter.com/blynk_app ===================================================================================================================== """ -import blynklib +import blynklib_mp as blynklib import network import utime as time import machine diff --git a/examples/esp8266/README.md b/examples/esp8266/README.md index 97c3356..d462356 100644 --- a/examples/esp8266/README.md +++ b/examples/esp8266/README.md @@ -59,7 +59,7 @@ ```bash export AMPY_PORT=/dev/ttyUSB0 ampy mkdir /lib - ampy put blynklib.py /lib/blynklib.py + ampy put blynklib_mp.py /lib/blynklib_mp.py ampy put test.py test.py ampy run test.py ``` @@ -94,7 +94,7 @@ For custom esp8266 firmware build creation: ```text RUN apt-get update ... ... - COPY blynklib.py /micropython/ports/esp8266/modules/blynklib.py + COPY blynklib_mp.py /micropython/ports/esp8266/modules/blynklib_mp.py USER micropython ... ``` @@ -103,10 +103,10 @@ For custom esp8266 firmware build creation: Build process can take some time ~ 15-40 minutes. - after firmware created and copied locally - you can try to burn it with **esptool** to your ESP8266 board. - - connect to board CLI with **rshell** and test **blynklib** availability within **repl** + - connect to board CLI with **rshell** and test **blynklib_mp** availability within **repl** ```python - import blynklib - print(blynklib.LOGO) + import blynklib_mp + print(blynklib_mp.LOGO) ``` @@ -118,13 +118,13 @@ Examine [this document][blynk-esp32-readme] to get more details how to compile * After *.mpy files can be placed to **/lib** directory of esp8266 board with **ampy** tool. Libraries *.mpy can be simply imported in the same manner as standard *.py library ```python -import blynklib +import blynklib_mp ``` ***Note!!*** During custom firmware creation your libraries will be converted and adopted to esp8266 environment automatically. So you can create custom build and then just copy *.mpy files from docker system to local ```bash -docker cp micropython:/micropython/ports/esp8266/build/frozen_mpy/blynklib.mpy blynklib.mpy +docker cp micropython:/micropython/ports/esp8266/build/frozen_mpy/blynklib_mp.mpy blynklib_mp.mpy ``` diff --git a/setup.py b/setup.py index 01c0295..b478caf 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ author_email='antoha.ua@gmail.com', setup_requires=['pytest-runner', ], tests_require=['pytest', 'pytest-mock', ], - py_modules=['blynklib_cp', 'blynktimer'], + py_modules=['blynklib', 'blynktimer'], classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", diff --git a/test/test_blynk_connection.py b/test/test_blynk_connection.py index 7ba5418..ca57a2c 100644 --- a/test/test_blynk_connection.py +++ b/test/test_blynk_connection.py @@ -3,7 +3,7 @@ import time import pytest import socket -from blynklib_cp import Connection, BlynkError, RedirectError +from blynklib import Connection, BlynkError, RedirectError class TestBlynkConnection: diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index 1ce1586..8bd369c 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -2,7 +2,7 @@ from __future__ import print_function import socket import pytest -import blynklib_cp as blynklib +import blynklib class TestBlynk: diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index ed6fb2b..4ca4938 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import sys import pytest -from blynklib_cp import Protocol, BlynkError +from blynklib import Protocol, BlynkError class TestBlynkProtocol: From 059b0ecdff7512e788c898a46da5d80e9019576d Mon Sep 17 00:00:00 2001 From: amorozenko Date: Fri, 8 Nov 2019 10:56:55 +0200 Subject: [PATCH 43/60] Added handling for pytest-mocker>=1.11.2 context manager restriction --- README.md | 2 + blynklib.py | 2 +- setup.py | 2 +- test/test_blynk_connection.py | 84 ++++++++++---------- test/test_blynk_main.py | 142 +++++++++++++++++----------------- test/test_blynk_protocol.py | 4 +- 6 files changed, 119 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 8a7042b..64cd4a6 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ You can run unit tests for cPython version of library (blynklib.py) using the co python setup.py test +**NOTE** Blynklib version <0.2.6 should use pytest-mock<1.11.2. In version 1.11.2 were added restrictions for context manager usage + **NOTE:** Unit tests for Micropython ENV are not available yet. #### Micropython installation diff --git a/blynklib.py b/blynklib.py index 6bc5d64..e22210d 100644 --- a/blynklib.py +++ b/blynklib.py @@ -202,7 +202,7 @@ def _get_socket(self): self._socket.connect(socket.getaddrinfo(self.server, self.port)[0][4]) self._socket.settimeout(self.SOCK_TIMEOUT) if self.ssl_cert: - # system’s default CA certificates case + # system default CA certificates case if self.ssl_cert == "default": self.ssl_cert = None self.log('Using SSL socket...') diff --git a/setup.py b/setup.py index b478caf..cc089fd 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ author='Anton Morozenko', author_email='antoha.ua@gmail.com', setup_requires=['pytest-runner', ], - tests_require=['pytest', 'pytest-mock', ], + tests_require=['pytest', 'pytest-mock>=1.11.2', ], py_modules=['blynklib', 'blynktimer'], classifiers=[ "Programming Language :: Python :: 2.7", diff --git a/test/test_blynk_connection.py b/test/test_blynk_connection.py index ca57a2c..6ee71ab 100644 --- a/test/test_blynk_connection.py +++ b/test/test_blynk_connection.py @@ -86,9 +86,9 @@ def test_is_server_alive_negative(self, cb): def test_is_server_alive_positive_ping(self, cb, mocker): cb._last_rcv_time = int(time.time() * 1000) - with mocker.patch.object(cb, 'send', return_value=None): - result = cb.is_server_alive() - assert result is True + mocker.patch.object(cb, 'send', return_value=None) + result = cb.is_server_alive() + assert result is True def test_is_server_alive_positive_no_ping_1(self, cb): cb._last_rcv_time = int(time.time() * 1000) @@ -116,59 +116,59 @@ def test_get_socket_exception(self, cb, mocker): assert 'Connection with the Blynk server failed: BE' in str(b_err.value) def test_authenticate(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\xc8'): - cb._authenticate() - assert cb._state == cb.AUTHENTICATED + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\xc8') + cb._authenticate() + assert cb._state == cb.AUTHENTICATED def test_authenticate_invalid_auth_token(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x09'): - with pytest.raises(BlynkError) as b_err: - cb._authenticate() - assert 'Invalid Auth Token' in str(b_err.value) + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x09') + with pytest.raises(BlynkError) as b_err: + cb._authenticate() + assert 'Invalid Auth Token' in str(b_err.value) def test_authenticate_redirect_message(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x29\x00\x02\x00\x11127.0.0.1\x004444'): - with pytest.raises(RedirectError) as r_err: - cb._authenticate() - # pytest exception wraps real exception - so we need access value field first - assert '127.0.0.1' in r_err.value.server - assert '4444' in r_err.value.port + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x29\x00\x02\x00\x11127.0.0.1\x004444') + with pytest.raises(RedirectError) as r_err: + cb._authenticate() + # pytest exception wraps real exception - so we need access value field first + assert '127.0.0.1' in r_err.value.server + assert '4444' in r_err.value.port def test_authenticate_not_ok_status(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x19'): - with pytest.raises(BlynkError) as b_err: - cb._authenticate() - assert 'Auth stage failed. Status=25' in str(b_err.value) + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x19') + with pytest.raises(BlynkError) as b_err: + cb._authenticate() + assert 'Auth stage failed. Status=25' in str(b_err.value) def test_authenticate_timeout(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=None): - with pytest.raises(BlynkError) as b_err: - cb._authenticate() - assert 'Auth stage timeout' in str(b_err.value) + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=None) + with pytest.raises(BlynkError) as b_err: + cb._authenticate() + assert 'Auth stage timeout' in str(b_err.value) def test_set_heartbeat_timeout(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=None): - with pytest.raises(BlynkError) as b_err: - cb._set_heartbeat() - assert 'Heartbeat stage timeout' in str(b_err.value) + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=None) + with pytest.raises(BlynkError) as b_err: + cb._set_heartbeat() + assert 'Heartbeat stage timeout' in str(b_err.value) def test_set_heartbeat_error_status(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x0e'): - with pytest.raises(BlynkError) as b_err: - cb._set_heartbeat() - assert 'Set heartbeat returned code=14' in str(b_err.value) + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\x0e') + with pytest.raises(BlynkError) as b_err: + cb._set_heartbeat() + assert 'Set heartbeat returned code=14' in str(b_err.value) def test_set_heartbeat_positive(self, cb, mocker): - with mocker.patch.object(cb, 'send', return_value=None): - with mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\xc8'): - cb._set_heartbeat() + mocker.patch.object(cb, 'send', return_value=None) + mocker.patch.object(cb, 'receive', return_value=b'\x00\x00\x02\x00\xc8') + cb._set_heartbeat() def test_connected_false(self, cb): result = cb.connected() diff --git a/test/test_blynk_main.py b/test/test_blynk_main.py index 8bd369c..bff43bf 100644 --- a/test/test_blynk_main.py +++ b/test/test_blynk_main.py @@ -12,95 +12,95 @@ def bl(self): yield blynk def test_connect(self, bl, mocker): - with mocker.patch.object(bl, 'connected', return_value=False): - with mocker.patch.object(bl, '_get_socket', return_value=None): - with mocker.patch.object(bl, '_authenticate', return_value=None): - with mocker.patch.object(bl, '_set_heartbeat', return_value=None): - with mocker.patch.object(bl, 'call_handler', return_value=None): - with mocker.patch.object(blynklib, 'ticks_ms', return_value=42): - result = bl.connect() - assert result is True - assert bl._last_rcv_time == 42 + mocker.patch.object(bl, 'connected', return_value=False) + mocker.patch.object(bl, '_get_socket', return_value=None) + mocker.patch.object(bl, '_authenticate', return_value=None) + mocker.patch.object(bl, '_set_heartbeat', return_value=None) + mocker.patch.object(bl, 'call_handler', return_value=None) + mocker.patch.object(blynklib, 'ticks_ms', return_value=42) + result = bl.connect() + assert result is True + assert bl._last_rcv_time == 42 def test_connect_exception(self, bl, mocker): - with mocker.patch.object(bl, 'connected', return_value=False): - with mocker.patch.object(bl, '_get_socket', return_value=None): - with mocker.patch.object(bl, '_authenticate', side_effect=blynklib.BlynkError()): - with mocker.patch.object(bl, 'disconnect', return_value=None): - with mocker.patch('time.sleep', return_value=None): - mocker.spy(bl, 'disconnect') - result = bl.connect(0.001) - assert result is False - assert bl.disconnect.call_count > 1 + mocker.patch.object(bl, 'connected', return_value=False) + mocker.patch.object(bl, '_get_socket', return_value=None) + mocker.patch.object(bl, '_authenticate', side_effect=blynklib.BlynkError()) + mocker.patch.object(bl, 'disconnect', return_value=None) + mocker.patch('time.sleep', return_value=None) + mocker.spy(bl, 'disconnect') + result = bl.connect(0.001) + assert result is False + assert bl.disconnect.call_count > 1 def test_connect_redirect_exception(self, bl, mocker): - with mocker.patch.object(bl, 'connected', return_value=False): - with mocker.patch.object(bl, '_get_socket', return_value=None): - with mocker.patch.object(bl, '_authenticate', side_effect=blynklib.RedirectError('127.0.0.1', 4444)): - with mocker.patch.object(bl, 'disconnect', return_value=None): - with mocker.patch('time.sleep', return_value=None): - mocker.spy(bl, 'disconnect') - result = bl.connect(0.001) - assert result is False - assert bl.disconnect.call_count > 1 - assert bl.server == '127.0.0.1' - assert bl.port == 4444 + mocker.patch.object(bl, 'connected', return_value=False) + mocker.patch.object(bl, '_get_socket', return_value=None) + mocker.patch.object(bl, '_authenticate', side_effect=blynklib.RedirectError('127.0.0.1', 4444)) + mocker.patch.object(bl, 'disconnect', return_value=None) + mocker.patch('time.sleep', return_value=None) + mocker.spy(bl, 'disconnect') + result = bl.connect(0.001) + assert result is False + assert bl.disconnect.call_count > 1 + assert bl.server == '127.0.0.1' + assert bl.port == 4444 def test_connect_timeout(self, bl, mocker): bl._state = bl.CONNECTING - with mocker.patch.object(bl, 'connected', return_value=False): - result = bl.connect(0.001) - assert result is False + mocker.patch.object(bl, 'connected', return_value=False) + result = bl.connect(0.001) + assert result is False def test_disconnect(self, bl, mocker): bl._socket = socket.socket() - with mocker.patch('time.sleep', return_value=None): - bl.disconnect('123') + mocker.patch('time.sleep', return_value=None) + bl.disconnect('123') def test_virtual_write(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=10): - result = bl.virtual_write(20, 'va1', 'val2') - assert result == 10 + mocker.patch.object(bl, 'send', return_value=10) + result = bl.virtual_write(20, 'va1', 'val2') + assert result == 10 def test_virtual_sync(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=20): - result = bl.virtual_sync(20, 22) - assert result == 20 + mocker.patch.object(bl, 'send', return_value=20) + result = bl.virtual_sync(20, 22) + assert result == 20 def test_email(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=30): - result = bl.email('1', '2', '3') - assert result == 30 + mocker.patch.object(bl, 'send', return_value=30) + result = bl.email('1', '2', '3') + assert result == 30 def test_tweet(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=40): - result = bl.tweet('123') - assert result == 40 + mocker.patch.object(bl, 'send', return_value=40) + result = bl.tweet('123') + assert result == 40 def test_notify(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=50): - result = bl.notify('123') - assert result == 50 + mocker.patch.object(bl, 'send', return_value=50) + result = bl.notify('123') + assert result == 50 def test_set_property(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=60): - result = bl.set_property(1, '2', '3') - assert result == 60 + mocker.patch.object(bl, 'send', return_value=60) + result = bl.set_property(1, '2', '3') + assert result == 60 def test_internal(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=70): - result = bl.internal('rtc', 'sync') - assert result == 70 + mocker.patch.object(bl, 'send', return_value=70) + result = bl.internal('rtc', 'sync') + assert result == 70 def test_hadle_event(self, bl): bl._events = {} - @bl.handle_event("connect") + @bl.handle_event('connect') def connect_handler(): pass - @bl.handle_event("disconnect") - def connect_handler(): + @bl.handle_event('disconnect') + def disconnect_handler(): pass assert 'connect' in bl._events.keys() @@ -142,10 +142,10 @@ def test_process_rsp(self, bl, mocker): assert bl.log.call_count == 1 def test_process_ping(self, bl, mocker): - with mocker.patch.object(bl, 'send', return_value=None): - mocker.spy(bl, 'send') - bl.process(bl.MSG_PING, 100, 200, []) - assert bl.send.call_count == 1 + mocker.patch.object(bl, 'send', return_value=None) + mocker.spy(bl, 'send') + bl.process(bl.MSG_PING, 100, 200, []) + assert bl.send.call_count == 1 def test_process_internal(self, bl, mocker): bl._events = {} @@ -154,9 +154,9 @@ def test_process_internal(self, bl, mocker): def internal_handler(*args): bl._status = 'INTERNAL TEST {}'.format(*args) - with mocker.patch.object(bl, 'send', return_value=None): - bl.process(bl.MSG_INTERNAL, 100, 20, ["xyz", "add", 2]) - assert bl._status == "INTERNAL TEST ['add', 2]" + mocker.patch.object(bl, 'send', return_value=None) + bl.process(bl.MSG_INTERNAL, 100, 20, ['xyz', 'add', 2]) + assert bl._status == "INTERNAL TEST ['add', 2]" def test_process_write(self, bl, mocker): bl._events = {} @@ -165,9 +165,9 @@ def test_process_write(self, bl, mocker): def write_handler(pin, *values): bl._status = 'WRITE TEST{}'.format(*values) - with mocker.patch.object(bl, 'send', return_value=None): - bl.process(bl.MSG_HW, 100, 200, ['vw', 4, 1, 2]) - assert bl._status == 'WRITE TEST[1, 2]' + mocker.patch.object(bl, 'send', return_value=None) + bl.process(bl.MSG_HW, 100, 200, ['vw', 4, 1, 2]) + assert bl._status == 'WRITE TEST[1, 2]' def test_process_read(self, bl, mocker): bl._events = {} @@ -176,6 +176,6 @@ def test_process_read(self, bl, mocker): def read_handler(pin): bl._status = 'READ TEST{}'.format(pin) - with mocker.patch.object(bl, 'send', return_value=None): - bl.process(bl.MSG_HW, 100, 200, ['vr', 7]) - assert bl._status == 'READ TEST7' + mocker.patch.object(bl, 'send', return_value=None) + bl.process(bl.MSG_HW, 100, 200, ['vr', 7]) + assert bl._status == 'READ TEST7' diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index 4ca4938..6be589b 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -32,7 +32,7 @@ def test_get_msg_id_defined(self, pb): def test_pack_msg(self, pb): msg_type = 20 - args = ["test", 1234, 745, 'abcde'] + args = ['test', 1234, 745, 'abcde'] result = pb._pack_msg(msg_type, *args) assert result == b'\x14\x00\x01\x00\x13test\x001234\x00745\x00abcde' @@ -47,7 +47,7 @@ def test_pack_msg_unicode(self, pb): pytest.skip('Python2 unicode compatibility issue') msg_type = 20 - args = ["ёж"] + args = ['ёж'] result = pb._pack_msg(msg_type, *args) assert result == b'\x14\x00\x01\x00\x04\xd1\x91\xd0\xb6' From 3065d5dc7bf19933de80a2edaaf1dd81d12e0004 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Sun, 26 Jan 2020 12:04:14 +0200 Subject: [PATCH 44/60] Added app conn/disconn events. Updated tests to support latest pytest-mock version --- README.md | 2 + blynklib.py | 4 +- blynklib_mp.py | 4 +- examples/12_app_connect_disconnect.py | 74 ++++++++++++++++++++++ test/test_blynk_connection.py | 88 +++++++++++++-------------- 5 files changed, 124 insertions(+), 48 deletions(-) create mode 100644 examples/12_app_connect_disconnect.py diff --git a/README.md b/README.md index 64cd4a6..79d9af0 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,8 @@ Examples can be found **[here][blynk-py-examples]** Check them all to get famili - [09_sync_virtual_pin.py](https://github.com/blynkkk/lib-python/blob/master/examples/09_sync_virtual_pin.py): How to sync virtual pin states and properties - [10_rtc_sync.py](https://github.com/blynkkk/lib-python/blob/master/examples/10_rtc_sync.py): How to perform RTC sync with blynk server - [11_ssl_socket.py](https://github.com/blynkkk/lib-python/blob/master/examples/11_ssl_socket.py): SSL server connection. Feature supported only by cPython library. +- [12_app_connect_disconnect.py](https://github.com/blynkkk/lib-python/blob/master/examples/12_app_connect_disconnect.py): Managing APP connect/disconnect events with Blynk Cloud. + ##### Raspberry Pi (any): Read [Raspberry Pi guide](https://github.com/blynkkk/lib-python/tree/master/examples/raspberry) first. diff --git a/blynklib.py b/blynklib.py index e22210d..ab5f253 100644 --- a/blynklib.py +++ b/blynklib.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Anton Morozenko +# Copyright (c) 2019-2020 Anton Morozenko # Copyright (c) 2015-2019 Volodymyr Shymanskyy. # See the file LICENSE for copying permission. @@ -348,7 +348,7 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif msg_type == self.MSG_PING: self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): - if msg_type == self.MSG_INTERNAL and len(msg_args) >= 2: + if msg_type == self.MSG_INTERNAL: self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) elif len(msg_args) >= 3 and msg_args[0] == 'vw': self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) diff --git a/blynklib_mp.py b/blynklib_mp.py index b85b2f1..42677ca 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Anton Morozenko +# Copyright (c) 2019-2020 Anton Morozenko # Copyright (c) 2015-2019 Volodymyr Shymanskyy. # See the file LICENSE for copying permission. @@ -337,7 +337,7 @@ def process(self, msg_type, msg_id, msg_len, msg_args): elif msg_type == self.MSG_PING: self.send(self.response_msg(self.STATUS_OK, msg_id=msg_id)) elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL): - if msg_type == self.MSG_INTERNAL and len(msg_args) >= const(2): + if msg_type == self.MSG_INTERNAL: self.call_handler("{}{}".format(self._INTERNAL, msg_args[0]), msg_args[1:]) elif len(msg_args) >= const(3) and msg_args[0] == 'vw': self.call_handler("{}{}".format(self._VPIN_WRITE, msg_args[1]), int(msg_args[1]), msg_args[2:]) diff --git a/examples/12_app_connect_disconnect.py b/examples/12_app_connect_disconnect.py new file mode 100644 index 0000000..2159466 --- /dev/null +++ b/examples/12_app_connect_disconnect.py @@ -0,0 +1,74 @@ +""" +[APP CONNECT DISCONNECT EVENTS EXAMPLE] ============================================================ + +Environment prepare: +In your Blynk App project: + - in Project Settings enable flag "Notify devices when APP connected" + - define your auth token for current example and run it + - Run the App (green triangle in the upper right corner). + +This started program will call handlers and print messages for APP_CONNECT or APP_DISCONNECT events. + +Schema: +==================================================================================================== + +-----------+ +--------------+ +--------------+ + | | | | | | + | blynk lib | | blynk server | | blynk app | + | | | virtual pin | | | + | | | | | | + +-----+-----+ +------+-------+ +-------+------+ + | | app connected or disconnected | + | | from server | + | | + event handler | app connect/disconnect event +<-----------------------------------+ +(user function) | | | + +------------<-----------------------------------+ | + | | | | + | | | | + +--------->+ | | + | | | + | | | + | | | + | | | + | | | + | | | + | | | + | | | + + + + +==================================================================================================== +Additional blynk info you can find by examining such resources: + + Downloads, docs, tutorials: https://blynk.io + Sketch generator: http://examples.blynk.cc + Blynk community: http://community.blynk.cc + Social networks: http://www.fb.com/blynkapp + http://twitter.com/blynk_app +==================================================================================================== +""" + +import blynklib + +BLYNK_AUTH = 'YourAuthToken' + +# initialize Blynk +blynk = blynklib.Blynk(BLYNK_AUTH) + +APP_CONNECT_PRINT_MSG = '[APP_CONNECT_EVENT]' +APP_DISCONNECT_PRINT_MSG = '[APP_DISCONNECT_EVENT]' + + +@blynk.handle_event('internal_acon') +def app_connect_handler(*args): + print(APP_CONNECT_PRINT_MSG) + + +@blynk.handle_event('internal_adis') +def app_disconnect_handler(*args): + print(APP_DISCONNECT_PRINT_MSG) + + +########################################################### +# infinite loop that waits for event +########################################################### +while True: + blynk.run() diff --git a/test/test_blynk_connection.py b/test/test_blynk_connection.py index 6ee71ab..7c74430 100644 --- a/test/test_blynk_connection.py +++ b/test/test_blynk_connection.py @@ -14,71 +14,71 @@ def cb(self): def test_send(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.send', return_value=5): - result = cb.send('1234') - assert result == 5 + mocker.patch('socket.socket.send', return_value=5) + result = cb.send('1234') + assert result == 5 def test_send_ioerror(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.send', side_effect=IOError('IO')): - result = cb.send('1234') - assert result is None + mocker.patch('socket.socket.send', side_effect=IOError('IO')) + result = cb.send('1234') + assert result is None def test_send_oserror(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.send', side_effect=OSError('OS')): - result = cb.send('1234') - assert result is None + mocker.patch('socket.socket.send', side_effect=OSError('OS')) + result = cb.send('1234') + assert result is None def test_send_socket_timeout(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.send', side_effect=socket.timeout()): - result = cb.send('1234') - assert result is None + mocker.patch('socket.socket.send', side_effect=socket.timeout()) + result = cb.send('1234') + assert result is None def test_send_error_retry_count(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.send', side_effect=OSError('OS')): - mocker.spy(time, 'sleep') - cb.send('1234') - assert cb._socket.send.call_count == 3 + mocker.patch('socket.socket.send', side_effect=OSError('OS')) + mocker.spy(time, 'sleep') + cb.send('1234') + assert cb._socket.send.call_count == 3 def test_receive(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', return_value=b'12345'): - result = cb.receive(10, 1) - assert result == b'12345' + mocker.patch('socket.socket.recv', return_value=b'12345') + result = cb.receive(10, 1) + assert result == b'12345' def test_receive_timeout(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', side_effect=OSError('timed out')): - result = cb.receive(10, 1) - assert result == b'' + mocker.patch('socket.socket.recv', side_effect=OSError('timed out')) + result = cb.receive(10, 1) + assert result == b'' def test_receive_timeout_2(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', side_effect=socket.timeout('timed out')): - result = cb.receive(10, 1) - assert result == b'' + mocker.patch('socket.socket.recv', side_effect=socket.timeout('timed out')) + result = cb.receive(10, 1) + assert result == b'' def test_receive_eagain(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', side_effect=IOError('[Errno 11]')): - result = cb.receive(10, 1) - assert result == b'' + mocker.patch('socket.socket.recv', side_effect=IOError('[Errno 11]')) + result = cb.receive(10, 1) + assert result == b'' def test_receive_etimeout(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 60]')): - result = cb.receive(10, 1) - assert result == b'' + mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 60]')) + result = cb.receive(10, 1) + assert result == b'' def test_receive_raise_other_oserror(self, cb, mocker): cb._socket = socket.socket() - with mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 13]')): - with pytest.raises(OSError) as os_err: - cb.receive(10, 1) - assert '[Errno 13]' in str(os_err.value) + mocker.patch('socket.socket.recv', side_effect=OSError('[Errno 13]')) + with pytest.raises(OSError) as os_err: + cb.receive(10, 1) + assert '[Errno 13]' in str(os_err.value) def test_is_server_alive_negative(self, cb): result = cb.is_server_alive() @@ -103,17 +103,17 @@ def test_is_server_alive_positive_no_ping_2(self, cb): assert result is True def test_get_socket(self, cb, mocker): - with mocker.patch('socket.socket'): - with mocker.patch('socket.getaddrinfo'): - cb._get_socket() - assert cb._state == cb.CONNECTING + mocker.patch('socket.socket') + mocker.patch('socket.getaddrinfo') + cb._get_socket() + assert cb._state == cb.CONNECTING def test_get_socket_exception(self, cb, mocker): - with mocker.patch('socket.socket'): - with mocker.patch('socket.getaddrinfo', side_effect=BlynkError('BE')): - with pytest.raises(BlynkError) as b_err: - cb._get_socket() - assert 'Connection with the Blynk server failed: BE' in str(b_err.value) + mocker.patch('socket.socket') + mocker.patch('socket.getaddrinfo', side_effect=BlynkError('BE')) + with pytest.raises(BlynkError) as b_err: + cb._get_socket() + assert 'Connection with the Blynk server failed: BE' in str(b_err.value) def test_authenticate(self, cb, mocker): mocker.patch.object(cb, 'send', return_value=None) From 1f8f9e58441b88cc0b5aec1917623f611c0e3188 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Fri, 21 Feb 2020 22:13:01 +0200 Subject: [PATCH 45/60] Preparations for 0.2.6 release --- blynktimer.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/blynktimer.py b/blynktimer.py index e8c0d9d..704a391 100644 --- a/blynktimer.py +++ b/blynktimer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019 Anton Morozenko +# Copyright (c) 2019-2020 Anton Morozenko """ Polling timers for functions. Registers timers and performs run once or periodical function execution after defined time intervals. diff --git a/setup.py b/setup.py index cc089fd..144879d 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ author_email='antoha.ua@gmail.com', setup_requires=['pytest-runner', ], tests_require=['pytest', 'pytest-mock>=1.11.2', ], - py_modules=['blynklib', 'blynktimer'], + py_modules=['blynklib', 'blynktimer', 'blynklib_mp'], classifiers=[ "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", From a3c09d2e0a3f5113ba17140f1237c736094ceed3 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Fri, 21 Feb 2020 22:14:34 +0200 Subject: [PATCH 46/60] Preparations for 0.2.6 release --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index c1c093a..812fb5e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Anton Morozenko +Copyright (c) 2019-2020 Anton Morozenko Copyright (c) 2015-2019 Volodymyr Shymanskyy Permission is hereby granted, free of charge, to any person obtaining a copy From 6832b99f31dfa9c7c0dc98b01746b9069f9b1962 Mon Sep 17 00:00:00 2001 From: Francois Gervais Date: Sun, 31 May 2020 17:19:03 -0400 Subject: [PATCH 47/60] Ensure millisecond precision in read_response() (#35) The micropython documentation for utime.time() states that on some hardware, the function only have second precision. This is the case at least for the TinyPICO board. This means that we get an effective 1 second timeout for the read_response() function instead of the expected 50ms. In turn, it makes the run() function quite slow which delays the whole application. To get higher precision, the documentation recommends to use utime.ticks_ms() instead which is what have been implemented here. --- blynklib_mp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index 42677ca..29d8c36 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -345,8 +345,8 @@ def process(self, msg_type, msg_id, msg_len, msg_args): self.call_handler("{}{}".format(self._VPIN_READ, msg_args[1]), int(msg_args[1])) def read_response(self, timeout=0.5): - end_time = time.time() + timeout - while time.time() <= end_time: + end_time = time.ticks_ms() + int(timeout * const(1000)) + while time.ticks_diff(end_time, time.ticks_ms()) > 0: rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) if rsp_data: self._last_rcv_time = ticks_ms() From ef36f6e71d70b328266bc1ed7f1f27a9edf90059 Mon Sep 17 00:00:00 2001 From: Francois Gervais Date: Sun, 31 May 2020 17:19:34 -0400 Subject: [PATCH 48/60] Revert socket timeout implementation (#33) --- blynklib_mp.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index 29d8c36..a215be2 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -151,9 +151,12 @@ def __init__(self, token, server='blynk-cloud.com', port=80, heartbeat=10, rcv_b self.log = log def _set_socket_timeout(self, timeout): - p = select.poll() - p.register(self._socket) - p.poll(int(timeout * const(1000))) + if getattr(self._socket, 'settimeout', None): + self._socket.settimeout(timeout) + else: + p = select.poll() + p.register(self._socket) + p.poll(int(timeout * const(1000))) def send(self, data): retries = self.RETRIES_TX_MAX_NUM From ca5949b852765f17733d4d56f0f7718bd9a4ae53 Mon Sep 17 00:00:00 2001 From: Francois Gervais Date: Wed, 19 Aug 2020 17:08:42 -0400 Subject: [PATCH 49/60] Substract timestamps using ticks_diff() to account for wrap around (#36) From the utime documentation, ticks_diff() should be used to compare timestamps. They say the wrap around is implementation specific but on forums I see people saying it's about 298 hours/12 days which at least is the case for me on the tinypico. In this specific case, the wrap around means the deltas will be small negative numbers which in turn will make is_server_alive() return True for days without sending anymore pings to the server. We will soon after get disconnected and the application will pretty much stay forever in that state. --- blynklib_mp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index a215be2..6d30af3 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -186,9 +186,9 @@ def receive(self, length, timeout): def is_server_alive(self): now = ticks_ms() h_beat_ms = self.heartbeat * const(1000) - rcv_delta = now - self._last_rcv_time - ping_delta = now - self._last_ping_time - send_delta = now - self._last_send_time + rcv_delta = time.ticks_diff(now, self._last_rcv_time) + ping_delta = time.ticks_diff(now, self._last_ping_time) + send_delta = time.ticks_diff(now, self._last_send_time) if rcv_delta > h_beat_ms + (h_beat_ms // const(2)): return False if (ping_delta > h_beat_ms // const(10)) and (send_delta > h_beat_ms or rcv_delta > h_beat_ms): From b2cc4f56b4e4ea264d0a9eca3e14c787e57e5c2e Mon Sep 17 00:00:00 2001 From: Namik Date: Fri, 20 Nov 2020 23:50:19 +0100 Subject: [PATCH 50/60] Update blynklib.py (#39) --- blynklib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blynklib.py b/blynklib.py index ab5f253..1f15a30 100644 --- a/blynklib.py +++ b/blynklib.py @@ -65,7 +65,7 @@ def _get_msg_id(self, **kwargs): if 'msg_id' in kwargs: return kwargs['msg_id'] self._msg_id += 1 - return self._msg_id if self._msg_id <= 0xFFFF else 0 + return self._msg_id if self._msg_id <= 0xFFFF else 1 def _pack_msg(self, msg_type, *args, **kwargs): data = ('\0'.join([str(curr_arg) for curr_arg in args])).encode('utf-8') From f4a3caa0b5c62c7981455dc9b8f2ca98f980bfcd Mon Sep 17 00:00:00 2001 From: icetomcat Date: Tue, 12 Jan 2021 23:57:05 +0700 Subject: [PATCH 51/60] Process multiple messages from single response --- blynklib_mp.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index 6d30af3..9f44a86 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -70,6 +70,7 @@ def _pack_msg(self, msg_type, *args, **kwargs): def parse_response(self, rsp_data, msg_buffer): msg_args = [] + msg_tail = b'' try: msg_type, msg_id, h_data = struct.unpack('!BHH', rsp_data[:self.MSG_HEAD_LEN]) except Exception as p_err: @@ -83,9 +84,10 @@ def parse_response(self, rsp_data, msg_buffer): elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL, self.MSG_REDIRECT): msg_body = rsp_data[self.MSG_HEAD_LEN: self.MSG_HEAD_LEN + h_data] msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] + msg_tail = rsp_data[self.MSG_HEAD_LEN + h_data:] else: raise BlynkError("Unknown message type: '{}'".format(msg_type)) - return msg_type, msg_id, h_data, msg_args + return msg_type, msg_id, h_data, msg_args, msg_tail def heartbeat_msg(self, heartbeat, rcv_buffer): return self._pack_msg(self.MSG_INTERNAL, 'ver', __version__, 'buff-in', rcv_buffer, 'h-beat', heartbeat, @@ -214,7 +216,7 @@ def _authenticate(self): rsp_data = self.receive(self.rcv_buffer, self.SOCK_MAX_TIMEOUT) if not rsp_data: raise BlynkError('Auth stage timeout') - msg_type, _, status, args = self.parse_response(rsp_data, self.rcv_buffer) + msg_type, _, status, args, _ = self.parse_response(rsp_data, self.rcv_buffer) if status != self.STATUS_OK: if status == self.STATUS_INVALID_TOKEN: raise BlynkError('Invalid Auth Token') @@ -229,7 +231,7 @@ def _set_heartbeat(self): rcv_data = self.receive(self.rcv_buffer, self.SOCK_MAX_TIMEOUT) if not rcv_data: raise BlynkError('Heartbeat stage timeout') - _, _, status, _ = self.parse_response(rcv_data, self.rcv_buffer) + _, _, status, _, _ = self.parse_response(rcv_data, self.rcv_buffer) if status != self.STATUS_OK: raise BlynkError('Set heartbeat returned code={}'.format(status)) self.log('Heartbeat = {} sec. MaxCmdBuffer = {} bytes'.format(self.heartbeat, self.rcv_buffer)) @@ -353,8 +355,9 @@ def read_response(self, timeout=0.5): rsp_data = self.receive(self.rcv_buffer, self.SOCK_TIMEOUT) if rsp_data: self._last_rcv_time = ticks_ms() - msg_type, msg_id, h_data, msg_args = self.parse_response(rsp_data, self.rcv_buffer) - self.process(msg_type, msg_id, h_data, msg_args) + while rsp_data: + msg_type, msg_id, h_data, msg_args, rsp_data = self.parse_response(rsp_data, self.rcv_buffer) + self.process(msg_type, msg_id, h_data, msg_args) def run(self): if not self.connected(): From d42468b1742c98179d4b84c56dc20228586a8a8f Mon Sep 17 00:00:00 2001 From: icetomcat Date: Fri, 15 Jan 2021 23:34:30 +0700 Subject: [PATCH 52/60] Process multiple messages from single response --- blynklib_mp.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/blynklib_mp.py b/blynklib_mp.py index 9f44a86..46f8897 100644 --- a/blynklib_mp.py +++ b/blynklib_mp.py @@ -70,9 +70,10 @@ def _pack_msg(self, msg_type, *args, **kwargs): def parse_response(self, rsp_data, msg_buffer): msg_args = [] - msg_tail = b'' + msg_len = 0 try: msg_type, msg_id, h_data = struct.unpack('!BHH', rsp_data[:self.MSG_HEAD_LEN]) + msg_len = self.MSG_HEAD_LEN + h_data except Exception as p_err: raise BlynkError('Message parse error: {}'.format(p_err)) if msg_id == 0: @@ -82,12 +83,11 @@ def parse_response(self, rsp_data, msg_buffer): elif msg_type in (self.MSG_RSP, self.MSG_PING): pass elif msg_type in (self.MSG_HW, self.MSG_BRIDGE, self.MSG_INTERNAL, self.MSG_REDIRECT): - msg_body = rsp_data[self.MSG_HEAD_LEN: self.MSG_HEAD_LEN + h_data] + msg_body = rsp_data[self.MSG_HEAD_LEN: msg_len] msg_args = [itm.decode('utf-8') for itm in msg_body.split(b'\0')] - msg_tail = rsp_data[self.MSG_HEAD_LEN + h_data:] else: raise BlynkError("Unknown message type: '{}'".format(msg_type)) - return msg_type, msg_id, h_data, msg_args, msg_tail + return msg_type, msg_id, h_data, msg_args, msg_len def heartbeat_msg(self, heartbeat, rcv_buffer): return self._pack_msg(self.MSG_INTERNAL, 'ver', __version__, 'buff-in', rcv_buffer, 'h-beat', heartbeat, @@ -356,8 +356,9 @@ def read_response(self, timeout=0.5): if rsp_data: self._last_rcv_time = ticks_ms() while rsp_data: - msg_type, msg_id, h_data, msg_args, rsp_data = self.parse_response(rsp_data, self.rcv_buffer) + msg_type, msg_id, h_data, msg_args, msg_len = self.parse_response(rsp_data, self.rcv_buffer) self.process(msg_type, msg_id, h_data, msg_args) + rsp_data = rsp_data[msg_len:] def run(self): if not self.connected(): From 44e49df9e14f307f389ef9ccb6114aad43e91d74 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Mon, 26 Jul 2021 16:27:39 +0300 Subject: [PATCH 53/60] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79d9af0..0f0ca99 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,8 @@ Read [this document][esp8266-readme] to get more information. * [Lua, OpenWrt, NodeMCU](https://github.com/vshymanskyy/blynk-library-lua) * [OpenWrt packages](https://github.com/vshymanskyy/blynk-library-openwrt) * [MBED](https://developer.mbed.org/users/vshymanskyy/code/Blynk/) -* [Node-RED](https://www.npmjs.com/package/node-red-contrib-blynk-ws) +* [Node-RED for Blynk IoT](https://flows.nodered.org/node/node-red-contrib-blynk-iot) +* [Node-RED for old Blynk](https://www.npmjs.com/package/node-red-contrib-blynk-ws) * [LabVIEW](https://github.com/juncaofish/NI-LabVIEWInterfaceforBlynk) * [C#](https://github.com/sverrefroy/BlynkLibrary) From eb068b397a0aec9824bbcf8a1392b7dd7c7ef4fe Mon Sep 17 00:00:00 2001 From: Vova Date: Wed, 4 Aug 2021 15:12:28 +0300 Subject: [PATCH 54/60] Update docs --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 79d9af0..a5fddf3 100644 --- a/README.md +++ b/README.md @@ -201,13 +201,9 @@ Read [this document][esp8266-readme] to get more information. ## Documentation and other helpful links -[Full Blynk Documentation](http://docs.blynk.cc/#blynk-firmware) - a complete guide on Blynk features +[Full Blynk Documentation](https://docs.blynk.io) - a complete guide on Blynk features -[Community (Forum)](http://community.blynk.cc) - join a 500,000 Blynk community to ask questions and share ideas - -[Help Center](http://help.blynk.cc) - helpful articles on various Blynk aspects - -[Code Examples Browser](http://examples.blynk.cc) - browse examples to explore Blynk possibilities +[Community (Forum)](https://community.blynk.cc) - join a 1'000'000 Blynk community to ask questions and share ideas [Official Website](https://blynk.io) @@ -226,7 +222,7 @@ Read [this document][esp8266-readme] to get more information. * [Lua, OpenWrt, NodeMCU](https://github.com/vshymanskyy/blynk-library-lua) * [OpenWrt packages](https://github.com/vshymanskyy/blynk-library-openwrt) * [MBED](https://developer.mbed.org/users/vshymanskyy/code/Blynk/) -* [Node-RED](https://www.npmjs.com/package/node-red-contrib-blynk-ws) +* [Node-RED for Blynk IoT](https://flows.nodered.org/node/node-red-contrib-blynk-iot) * [LabVIEW](https://github.com/juncaofish/NI-LabVIEWInterfaceforBlynk) * [C#](https://github.com/sverrefroy/BlynkLibrary) From b84dd58380c53256a20cab6b5c1ccdbc51972dba Mon Sep 17 00:00:00 2001 From: Vova Date: Wed, 4 Aug 2021 15:18:05 +0300 Subject: [PATCH 55/60] Update links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5fddf3..e76525c 100644 --- a/README.md +++ b/README.md @@ -248,8 +248,8 @@ This project is released under The MIT License (MIT) [blynk-server-public]: http://blynk-cloud.com [blynk-docs]: https://docs.blynk.cc/ [blynk-py-examples]: https://github.com/blynkkk/lib-python/blob/master/examples - [blynk-app-android]: https://play.google.com/store/apps/details?id=cc.blynk - [blynk-app-ios]: https://itunes.apple.com/us/app/blynk-control-arduino-raspberry/id808760481?ls=1&mt=8 + [blynk-app-android]: https://play.google.com/store/apps/details?id=cloud.blynk + [blynk-app-ios]: https://apps.apple.com/us/app/blynk-iot/id1559317868 [blynk-vpins]: http://help.blynk.cc/getting-started-library-auth-token-code-examples/blynk-basics/what-is-virtual-pins [python-org]: https://www.python.org/downloads/ [micropython-org]: https://micropython.org/ From c43df249daaee198d208efcdad4dc8a0a2b0a596 Mon Sep 17 00:00:00 2001 From: antohaUa Date: Tue, 14 Sep 2021 22:01:25 +0300 Subject: [PATCH 56/60] unit test fix --- test/test_blynk_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_blynk_protocol.py b/test/test_blynk_protocol.py index 6be589b..a838e33 100644 --- a/test/test_blynk_protocol.py +++ b/test/test_blynk_protocol.py @@ -23,7 +23,7 @@ def test_get_msg_id_before_loop(self, pb): def test_get_msg_id_after_loop(self, pb): pb._msg_id = 0xFFFF msg_id = pb._get_msg_id() - assert msg_id == 0 + assert msg_id == 1 def test_get_msg_id_defined(self, pb): pb._msg_id = 0xFFFF From e4043f0d30028b3f8808d6cb78a644fb47d8c0e3 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Fri, 4 Mar 2022 00:27:08 +0200 Subject: [PATCH 57/60] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e76525c..834a452 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![StandWithUkraine banner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner.svg) + # Blynk Python Library This library provides API to connect IoT hardware that supports Micropython/Python to Blynk Cloud and communiate with Blynk apps (iOS and Android). You can send raw and processed sensor data and remotely control anything that is connected to your hardware (relays, motors, servos) from anywhere in the world. From 517326028854c1d1cc8c7606ef1d820606689945 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Fri, 4 Mar 2022 11:27:54 +0200 Subject: [PATCH 58/60] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 834a452..9d267a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![StandWithUkraine banner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner.svg) +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) # Blynk Python Library This library provides API to connect IoT hardware that supports Micropython/Python to Blynk Cloud and communiate with Blynk apps (iOS and Android). You can send raw and processed sensor data and remotely control anything that is connected to your hardware (relays, motors, servos) from anywhere in the world. From 064d9b132623653a2c17a7065016708e951cdb17 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Mon, 5 May 2025 16:01:29 +0300 Subject: [PATCH 59/60] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d267a2..94d2a43 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) +> [!IMPORTANT] +> **This project has been archived and is no longer maintained.** +> No further updates or support will be provided. +> We recommend switching to the **Blynk MQTT API** for future development. +> You can explore some [useful examples here](https://github.com/Blynk-Technologies/Blynk-MQTT-Samples). # Blynk Python Library This library provides API to connect IoT hardware that supports Micropython/Python to Blynk Cloud and communiate with Blynk apps (iOS and Android). You can send raw and processed sensor data and remotely control anything that is connected to your hardware (relays, motors, servos) from anywhere in the world. From 0329df63c414044fd51bb6f17ac9a993d853d507 Mon Sep 17 00:00:00 2001 From: Volodymyr Shymanskyy Date: Wed, 7 May 2025 20:11:22 +0300 Subject: [PATCH 60/60] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 94d2a43..b047946 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ > [!IMPORTANT] -> **This project has been archived and is no longer maintained.** -> No further updates or support will be provided. -> We recommend switching to the **Blynk MQTT API** for future development. -> You can explore some [useful examples here](https://github.com/Blynk-Technologies/Blynk-MQTT-Samples). +> **This project is still available for exploration, but is no longer actively maintained or updated.** +> We recommend switching to the Blynk MQTT API for a robust and future-proof experience. +> Support for this project will be phased out over time. +> You can explore some [useful MQTT examples here](https://github.com/Blynk-Technologies/Blynk-MQTT-Samples). # Blynk Python Library This library provides API to connect IoT hardware that supports Micropython/Python to Blynk Cloud and communiate with Blynk apps (iOS and Android). You can send raw and processed sensor data and remotely control anything that is connected to your hardware (relays, motors, servos) from anywhere in the world.