From b81f14b9448c76e3ddbb496a7a64ae18c41db22f Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 09:37:33 +0100 Subject: [PATCH 01/18] a bunch of changes --- .gitignore | 1 + poetry.lock | 120 ++++++++---- pyproject.toml | 30 +-- sudoisbot/apis/weather_pub.py | 175 +++++++++++++++++ sudoisbot/common.py | 21 +- sudoisbot/datatypes.py | 79 ++++++++ sudoisbot/listener.py | 6 +- sudoisbot/network/proxy.py | 77 ++++++-- sudoisbot/network/pub.py | 27 ++- sudoisbot/network/sub.py | 63 ++++-- sudoisbot/screen/basis33/LICENSE.txt | 22 +++ sudoisbot/screen/basis33/README.txt | 7 + sudoisbot/screen/basis33/basis33.ttf | Bin 0 -> 39488 bytes sudoisbot/screen/screen_pub.py | 203 ++++++++------------ sudoisbot/screen/screen_sub.py | 39 ++-- sudoisbot/sensors/arduino_rain.ino | 78 ++++++++ sudoisbot/sink/models.py | 8 + sudoisbot/sink/newdb.py | 4 +- sudoisbot/sink/simplestate.py | 24 ++- sudoisbot/sink/sink.py | 275 ++++++++++++++++++--------- sudoisbot/temps/dht.c | 92 +++++++++ sudoisbot/temps/rain_pub.py | 144 ++++++++++++++ sudoisbot/temps/sensors.py | 36 ++-- sudoisbot/temps/temp_pub.py | 12 +- sudoisbot/unifi.py | 41 ++++ sudoisbot/util/csv2db.py | 1 + sudoisbot/util/csv2influx.py | 145 ++++++++++++++ sudoisbot/util/suball.py | 7 +- sudoisbot/weather/weather_pub.py | 187 ------------------ 29 files changed, 1386 insertions(+), 538 deletions(-) create mode 100644 sudoisbot/apis/weather_pub.py create mode 100644 sudoisbot/datatypes.py create mode 100755 sudoisbot/screen/basis33/LICENSE.txt create mode 100755 sudoisbot/screen/basis33/README.txt create mode 100755 sudoisbot/screen/basis33/basis33.ttf create mode 100644 sudoisbot/sensors/arduino_rain.ino create mode 100644 sudoisbot/temps/dht.c create mode 100644 sudoisbot/temps/rain_pub.py create mode 100644 sudoisbot/util/csv2influx.py delete mode 100644 sudoisbot/weather/weather_pub.py diff --git a/.gitignore b/.gitignore index f8c3d0f..3178e54 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ sudoisbot.egg-info/ .#* \#* sudoisbot.yml +*-default.yml diff --git a/poetry.lock b/poetry.lock index bd4dba2..df77ac8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -104,20 +104,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.10" [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" -name = "importlib-metadata" +category = "main" +description = "InfluxDB client" +name = "influxdb" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" +python-versions = "*" +version = "5.3.0" [package.dependencies] -zipp = ">=0.5" +msgpack = "0.6.1" +python-dateutil = ">=2.6.0" +pytz = "*" +requests = ">=2.17.0" +six = ">=1.10.0" [package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +test = ["nose", "nose-cov", "mock", "requests-mock"] [[package]] category = "main" @@ -133,14 +135,14 @@ description = "Python logging made (stupidly) simple" name = "loguru" optional = false python-versions = ">=3.5" -version = "0.5.1" +version = "0.5.3" [package.dependencies] colorama = ">=0.3.4" win32-setctime = ">=1.0.0" [package.extras] -dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=4.3.20)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.3b0)"] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] [[package]] category = "main" @@ -165,6 +167,14 @@ optional = false python-versions = ">=3.5" version = "8.4.0" +[[package]] +category = "main" +description = "MessagePack (de)serializer." +name = "msgpack" +optional = false +python-versions = "*" +version = "0.6.1" + [[package]] category = "main" description = "NumPy is the fundamental package for array computing with Python." @@ -201,11 +211,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.13.1" -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] dev = ["pre-commit", "tox"] @@ -259,10 +264,6 @@ pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] checkqa-mypy = ["mypy (v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] @@ -296,6 +297,14 @@ tornado = ">=5.1" json = ["ujson"] socks = ["pysocks"] +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2020.1" + [[package]] category = "main" description = "YAML parser and emitter for Python" @@ -353,6 +362,7 @@ pyserial = "^3.4" reference = "8f0816fe3bc71014e2531e29253d9782621107b5" type = "git" url = "https://github.com/benediktkr/temper.git" + [[package]] category = "main" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." @@ -395,18 +405,24 @@ version = "1.0.1" dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" -name = "zipp" +category = "main" +description = "" +name = "zflux" optional = false -python-versions = ">=3.6" -version = "3.1.0" +python-versions = "^3.8" +version = "0.1.0" -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +[package.dependencies] +influxdb = "^5.3.0" +loguru = "^0.5.3" +pyyaml = "^5.3.1" +requests = "^2.24.0" +zmq = "^0.0.0" +[package.source] +reference = "6d394fc2fd8be15bfa4bc2691f9b96d1db973e4b" +type = "git" +url = "https://github.com/benediktkr/zflux.git" [[package]] category = "main" description = "You are probably looking for pyzmq." @@ -419,8 +435,9 @@ version = "0.0.0" pyzmq = "*" [metadata] -content-hash = "85f35b7b7505f6f5778b484d30c4f0ea5310e97c7428db0af881e8d6a8e8e779" -python-versions = "^3.7" +content-hash = "169f09eecc8c8ce84ab780a39b9ac42a75999d3021c325a9f5368a503eb76b38" +lock-version = "1.0" +python-versions = "^3.8" [metadata.files] atomicwrites = [ @@ -506,31 +523,34 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] -importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, +influxdb = [ + {file = "influxdb-5.3.0-py2.py3-none-any.whl", hash = "sha256:b4c034ee9c9ee888d43de547cf40c616bba35f0aa8f11e5a6f056bf5855970ac"}, + {file = "influxdb-5.3.0.tar.gz", hash = "sha256:9bcaafd57ac152b9824ab12ed19f204206ef5df8af68404770554c5b55b475f6"}, ] kiwisolver = [ {file = "kiwisolver-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74"}, {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8"}, {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2"}, + {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:be046da49fbc3aa9491cc7296db7e8d27bcf0c3d5d1a40259c10471b014e4e0c"}, {file = "kiwisolver-1.2.0-cp36-none-win32.whl", hash = "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b"}, {file = "kiwisolver-1.2.0-cp36-none-win_amd64.whl", hash = "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c"}, {file = "kiwisolver-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7"}, {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec"}, {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec"}, + {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:63f55f490b958b6299e4e5bdac66ac988c3d11b7fafa522800359075d4fa56d1"}, {file = "kiwisolver-1.2.0-cp37-none-win32.whl", hash = "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c"}, {file = "kiwisolver-1.2.0-cp37-none-win_amd64.whl", hash = "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1"}, {file = "kiwisolver-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113"}, {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e"}, {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657"}, + {file = "kiwisolver-1.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:38d05c9ecb24eee1246391820ed7137ac42a50209c203c908154782fced90e44"}, {file = "kiwisolver-1.2.0-cp38-none-win32.whl", hash = "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf"}, {file = "kiwisolver-1.2.0-cp38-none-win_amd64.whl", hash = "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd"}, {file = "kiwisolver-1.2.0.tar.gz", hash = "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f"}, ] loguru = [ - {file = "loguru-0.5.1-py3-none-any.whl", hash = "sha256:e5d362a43cd2fc2da63551d79a6830619c4d5b3a8b976515748026f92f351b61"}, - {file = "loguru-0.5.1.tar.gz", hash = "sha256:70201d5fce26da89b7a5f168caa2bb674e06b969829f56737db1d6472e53e7c3"}, + {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, + {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] matplotlib = [ {file = "matplotlib-3.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a47abc48c7b81fe6e636dde8a58e49b13d87d140e0f448213a4879f4a3f73345"}, @@ -554,6 +574,25 @@ more-itertools = [ {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, ] +msgpack = [ + {file = "msgpack-0.6.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3129c355342853007de4a2a86e75eab966119733eb15748819b6554363d4e85c"}, + {file = "msgpack-0.6.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:31f6d645ee5a97d59d3263fab9e6be76f69fa131cddc0d94091a3c8aca30d67a"}, + {file = "msgpack-0.6.1-cp27-cp27m-win32.whl", hash = "sha256:fd509d4aa95404ce8d86b4e32ce66d5d706fd6646c205e1c2a715d87078683a2"}, + {file = "msgpack-0.6.1-cp27-cp27m-win_amd64.whl", hash = "sha256:70cebfe08fb32f83051971264466eadf183101e335d8107b80002e632f425511"}, + {file = "msgpack-0.6.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:8e68c76c6aff4849089962d25346d6784d38e02baa23ffa513cf46be72e3a540"}, + {file = "msgpack-0.6.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:86b963a5de11336ec26bc4f839327673c9796b398b9f1fe6bb6150c2a5d00f0f"}, + {file = "msgpack-0.6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:72cb7cf85e9df5251abd7b61a1af1fb77add15f40fa7328e924a9c0b6bc7a533"}, + {file = "msgpack-0.6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8c73c9bcdfb526247c5e4f4f6cf581b9bb86b388df82cfcaffde0a6e7bf3b43a"}, + {file = "msgpack-0.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:26cb40116111c232bc235ce131cc3b4e76549088cb154e66a2eb8ff6fcc907ec"}, + {file = "msgpack-0.6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:62bd8e43d204580308d477a157b78d3fee2fb4c15d32578108dc5d89866036c8"}, + {file = "msgpack-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:a28e69fe5468c9f5251c7e4e7232286d71b7dfadc74f312006ebe984433e9746"}, + {file = "msgpack-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:97ac6b867a8f63debc64f44efdc695109d541ecc361ee2dce2c8884ab37360a1"}, + {file = "msgpack-0.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3ce7ef7ee2546c3903ca8c934d09250531b80c6127e6478781ae31ed835aac4c"}, + {file = "msgpack-0.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7c55649965c35eb32c499d17dadfb8f53358b961582846e1bc06f66b9bccc556"}, + {file = "msgpack-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:9d4f546af72aa001241d74a79caec278bcc007b4bcde4099994732e98012c858"}, + {file = "msgpack-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:300fd3f2c664a3bf473d6a952f843b4a71454f4c592ed7e74a36b205c1782d28"}, + {file = "msgpack-0.6.1.tar.gz", hash = "sha256:4008c72f5ef2b7936447dcb83db41d97e9791c83221be13d5e19db0796df1972"}, +] numpy = [ {file = "numpy-1.19.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:63d971bb211ad3ca37b2adecdd5365f40f3b741a455beecba70fd0dde8b2a4cb"}, {file = "numpy-1.19.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b6aaeadf1e4866ca0fdf7bb4eed25e521ae21a7947c59f78154b24fc7abbe1dd"}, @@ -621,6 +660,10 @@ python-telegram-bot = [ {file = "python-telegram-bot-12.8.tar.gz", hash = "sha256:327186c56469216207dcdf8706892e58e0a62e51ef46f5143268e387bbb4edc3"}, {file = "python_telegram_bot-12.8-py2.py3-none-any.whl", hash = "sha256:7eebed539ccacf77896cff9e41d1f68746b8ff3ca4da1e2e59285e9c749cb050"}, ] +pytz = [ + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, +] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, @@ -696,10 +739,7 @@ win32-setctime = [ {file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"}, {file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"}, ] -zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, -] +zflux = [] zmq = [ {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"}, {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"}, diff --git a/pyproject.toml b/pyproject.toml index 9b89434..7ec28dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Benedikt Kristinsson "] repository = "https://github.com/benediktkr/sudoisbot" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" python-telegram-bot = "*" PyYAML = "*" zmq = "^0.0.0" @@ -16,6 +16,8 @@ loguru = "^0.5.0" temper = {git = "https://github.com/benediktkr/temper.git"} requests = "^2.23.0" peewee = "^3.13.3" +influxdb = "^5.3.0" +zflux = {git = "https://github.com/benediktkr/zflux.git"} [tool.poetry.dev-dependencies] pytest = "^5.2" @@ -24,25 +26,25 @@ pytest = "^5.2" tglistener = "sudoisbot.tglistener:main" sendtelegram = "sudoisbot.sendtelegram:main" -proxy = "sudoisbot.network.proxy:pubsub_listener" -proxy_pubsub = "sudoisbot.network.proxy:pubsub_listener" -broker = "sudoisbot.network.broker:main" - +proxy = "sudoisbot.network.proxy:main" sink = "sudoisbot.sink.sink:main" -graphtemps = "sudoisbot.sink.graphtemps:main" -temper_sub = "sudoisbot.sink.sink:main" -rain_notify = "sudoisbot.sink.notifier:main" screen_pub = "sudoisbot.screen.screen_pub:main" - -unifi_clients = "sudoisbot.unifi_clients:show_clients" - -recenttemps = "sudoisbot.recenttemps:main" - -weather_pub = "sudoisbot.weather.weather_pub:main" +unifi_pub = "sudoisbot.unifi:pub" +weather_pub = "sudoisbot.apis.weather_pub:main" temper_pub = "sudoisbot.temps.temp_pub:main" +rain_pub = "sudoisbot.temps.rain_pub:main" +# these will pretty much only be run while developing so just use +# poetry run python unifi/clients.py +# or something like that +#recenttemps = "sudoisbot.recenttemps:main" +#unifi_clients = "sudoisbot.unifi_clients:show_clients" +#graphtemps = "sudoisbot.sink.graphtemps:main" +# not ready/might not be used +#broker = "sudoisbot.network.broker:main" + [build-system] requires = ["poetry>=0.12"] diff --git a/sudoisbot/apis/weather_pub.py b/sudoisbot/apis/weather_pub.py new file mode 100644 index 0000000..6b0bb7a --- /dev/null +++ b/sudoisbot/apis/weather_pub.py @@ -0,0 +1,175 @@ +#!/usr/bin/python3 + +# met.no: +# +# tuncate lat/long to 4 decimals +# +# Reponse headers (firefox): +# +# Date Thu, 25 Jun 2020 20:55:23 GMT +# Expires Thu, 25 Jun 2020 21:26:39 GMT +# +# Seems like 30 mins, but check "Expires" +# +# Use "If-Modified-Since" request header +# +# Depending on how i do this, add a random number of mins/secs to +# not do it on the hour/minute +# +# must support redirects and gzip compression (Accept-Encoding: gzip, deflate) +# + +# openweatherap: +# +# +# triggers: https://openweathermap.org/triggers +# - polling +# - may as well poll nowcast +# +# ratelimit: 60 calls/minute +# +# weather condition codes: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 +# +# maybe interesting project: https://github.com/aceisace/Inky-Calendar + +from datetime import datetime +from decimal import Decimal +import time +import json + +import requests +from loguru import logger +from requests.exceptions import RequestException + +from sudoisbot.network.pub import Publisher +from sudoisbot.config import read_config + + +# rain_conditions = [ +# 'rain', +# 'drizzle', +# 'thunderstorm' +# ] + +# # raining = 'rain' in main.lower() or 'rain' in desc.lower() +# # snowing = 'snow' in main.lower() or 'snow' in desc.lower() +# # drizzling = 'drizzle' in main.lower() or 'drizzle' in desc.lower() +# # thunderstorm = 'thunderstorm' in main.lower() or 'thunderstorm' in desc.lower() +# # any_percip = raining or snowing or drizzling or thunderstorm +# # if any_percip: +# # logger.bind(odd=True).trace(json.dumps(w)) +# # precipitation = { +# # 'raining': raining, +# # 'snowing': snowing, +# # 'drizzling': drizzling, +# # 'thunderstorm': thunderstorm, +# # 'any': any_percip +# # } + +def useragent(): + import pkg_resources + version = pkg_resources.get_distribution('sudoisbot').version + return f"sudoisbot/{version} github.com/benediktkr/sudoisbot" + + + +OWM_URL = "https://api.openweathermap.org/data/2.5/weather?lat={lat:.4f}&lon={lon:.4f}&appid={token}&sea_level={msl}&units=metric" + + +class NowcastPublisher(Publisher): + + def __init__(self, addr, locations, token): + super().__init__(addr, b"weather", None, 300) + + self.locations = [{ + 'name': a['name'], + 'lat': Decimal(a['lat']), + 'lon': Decimal(a['lon']), + 'msl': a['msl'] + } for a in locations] + + self.token = token + self.base_url = OWM_URL + + self.session = requests.Session() + self.session.headers.update({"User-Agent": useragent(), + "Accept": "application/json"}) + + def get_nowcast(self, location): + url = self.base_url.format(token=self.token, **location) + r = self.session.get(url) + r.raise_for_status() + if r.status_code == 203: + logger.warning("deprecation warning: http 203 returned") + + w = r.json() + + d = dict( + desc = ', '.join([a['description'] for a in w['weather']]), + main = ', '.join([a['main'] for a in w['weather']]), + + temp = w['main']['temp'], + feel_like = w['main']['feels_like'], + pressure = w['main']['pressure'], + humidity = w['main']['humidity'], + + wind_speed = w['wind']['speed'], + wind_deg = w['wind']['deg'], + + visibility = w['visibility'], + cloudiness = w['clouds']['all'], + + dt = w['dt'], + # misnomer on my behalf + # .fromtimestamp() -> converts to our tz (from UTC) + # .utcfromtimestamp() -> returns in UTC + weather_dt = datetime.fromtimestamp(w['dt']).isoformat() + ) + + + # only in the data when it's been raining/showing + if 'rain' in w: + rain_1h = w['rain']['1h'], + rain_3h = w['rain']['3h'], + if 'snow' in w: + snow_1h = w['snow']['1h'], + snow_3h = w['snow']['3h'], + + + return d + + def publish(self): + try: + for location in self.locations: + nowcast = self.get_nowcast(location) + self.send(location['name'], nowcast) + time.sleep(0.2) + except RequestException as e: + logger.error(e) + + def send(self, name, weather): + data = { + 'measurement': self.topic.decode(), + 'tags': { + 'name': name, + 'frequency': self.frequency, + 'type': 'weather', + }, + 'time': datetime.now().isoformat(), + 'fields': weather + } + bytedata = json.dumps(data).encode() + logger.debug(bytedata) + self.socket.send_multipart([self.topic, bytedata]) + + +def main(): + + config = read_config() + + addr = config['addr'] + locations = config['locations'] + token = config['owm_token'] + + with NowcastPublisher(addr, locations, token) as publisher: + publisher.loop() diff --git a/sudoisbot/common.py b/sudoisbot/common.py index be56c31..162c99f 100644 --- a/sudoisbot/common.py +++ b/sudoisbot/common.py @@ -2,17 +2,16 @@ import argparse import copy import os import sys +from itertools import islice from loguru import logger import yaml from sudoisbot.sendmsg import send_to_me - -def useragent(): - import pkg_resources - version = pkg_resources.get_distribution('sudoisbot').version - return f"sudoisbot/{version} github.com/benediktkr/sudoisbot" +def chunk(it, size=10): + it = iter(it) + return list(iter(lambda: list(islice(it, size)), [])) def catch22(): def actual_decorator(decorated_function): @@ -81,6 +80,11 @@ def read_configfile(name, section): _section.setdefault(s, _d) return _section except KeyError: + # throws an error sayign eg "section temper_pub not found" but the + # actual problem is that "logging" insnt found (deepcopy stuff raises + # the exception. + # + # really need to rewrite this crap..... logger.error("Section '{}' not found in '{}'", section, conffile) sys.exit(1) @@ -183,6 +187,13 @@ def init(name, argparser=None, fullconfig=False): else: raise + except PermissionError as e: + if args.verbose: + pass + else: + logger.error("try running with --verbose") + raise + # NOTE: used to disable deafult logger here # my defaults have backtrace/diagnose disabled diff --git a/sudoisbot/datatypes.py b/sudoisbot/datatypes.py new file mode 100644 index 0000000..52931ab --- /dev/null +++ b/sudoisbot/datatypes.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 + +from dataclasses import asdict, dataclass + + +@dataclass +class Typed: + def __post_init__(self): + for (name, field_type) in self.__annotations__.items(): + if not isinstance(self.__dict__[name], field_type): + current_type = type(self.__dict__[name]) + raise ValueError(f"The field '{name}' was '{current_type}' instead of '{field_type}'") + + def as_dict(self): + return asdict(self) + + def __getitem__(self, key): + from loguru import logger + logger.warning("not sure if i will keep this, prob use .as_dict()?") + return self.__dict__[key] + + +@dataclass +class Tags(Typed): + name: str + location: str + kind: str + +@dataclass +class Message(Typed): + time: str + measurement: str + fields: dict + tags: Tags + + @classmethod + def from_msg(cls, topic, msg): + + # thee type of the 'fields' type + fields_type = cls.__annotations__['fields'] + + return cls( + measurement=topic.decode(), + time=msg['time'], + tags=Tags(**msg['tags']), + fields=fields_type(**msg['fields']) + ) + + @classmethod + def from_topic(cls, topic, msg): + if topic == b'rain': + return RainMessage.from_msg(topic, msg) + else: + return Message.from_msg(topic, msg) + + + +@dataclass +class RainFields(Typed): + value: bool + value_int: int + +@dataclass +class RainMessage(Message): + time: str + measurement: str + fields: RainFields + tags: Tags + + def as_csv(self): + return f"{self.time},{self.tags.name},{self.fields.value_int}" + + +@dataclass +class InfluxDBDatapoint(Typed): + time: str + measurement: str + fields: dict + tags: dict diff --git a/sudoisbot/listener.py b/sudoisbot/listener.py index 6ecda41..b7593bb 100755 --- a/sudoisbot/listener.py +++ b/sudoisbot/listener.py @@ -59,10 +59,14 @@ class ConfiguredBotHandlers(object): def _get_temps(self): statefile = self.config['listener']['temp_state'] temps = simplestate.get_recent(statefile) + return temps def _temp_to_string(self, temps): - strs = [f"{k}: `{v['temp']}`C" for (k,v) in temps.items()] + sort = sorted(temps.items(), key=lambda v: v[1].get('type')) + + + strs = [f"{k}: `{v['temp']:.1f}`C" for (k,v) in temps.items()] return "\n".join(strs) def temp1m(self, update, context: CallbackContext): diff --git a/sudoisbot/network/proxy.py b/sudoisbot/network/proxy.py index 8b02830..fa19331 100644 --- a/sudoisbot/network/proxy.py +++ b/sudoisbot/network/proxy.py @@ -6,6 +6,7 @@ from loguru import logger import zmq from sudoisbot.common import init +from sudoisbot.config import read_config def dealer(dealer_addr, router_addr): print("dealer") @@ -26,8 +27,7 @@ def dealer(dealer_addr, router_addr): router.close() context.close() - -def pubsub(frontend_addr, backend_addr): +def proxy(frontend_addr, backend_addr): context = zmq.Context() # facing publishers @@ -37,6 +37,8 @@ def pubsub(frontend_addr, backend_addr): # facing services (sinks/subsribers) backend = context.socket(zmq.XPUB) backend.bind(backend_addr) + # infrom publishers of a new sink + #backend.setsockopt(ZMQ_XPUB_VERBOSE, 1) logger.info(f"zmq pubsub proxy: {frontend_addr} -> {backend_addr}") zmq.proxy(frontend, backend) @@ -46,18 +48,69 @@ def pubsub(frontend_addr, backend_addr): backend.close() context.close() -def pubsub_listener(): - config = init("proxy_pubsub") +def forwarder(frontend_addr, backend_addr, capture_addr=None): + context = zmq.Context() - frontend_addr = config['zmq_frontend'] - backend_addr = config['zmq_backend'] + # facing publishers + #frontend = context.socket(zmq.XSUB) - return pubsub(frontend_addr, backend_addr) + frontend = context.socket(zmq.SUB) + frontend.setsockopt(zmq.SUBSCRIBE, b'') + frontend.connect(frontend_addr) -def dealer_listener(): - config = init("proxy_dealer") + # facing services (sinks/subsribers) + backend = context.socket(zmq.XPUB) + backend.bind(backend_addr) + # infrom publishers of a new sink + #backend.setsockopt(ZMQ_XPUB_VERBOSE, 1) - dealer_addr = config['zmq_dealer'] - router_addr = config['zmq_router'] + logger.info(f"zmq pubsub proxy: {frontend_addr} -> {backend_addr}") + if capture_addr: + capture = context.socket(zmq.PUB) + capture.bind(capture_addr) + logger.info(f"capture: {capture_addr}") + else: + capture = None - return dealer(dealer_addr, router_addr) + zmq.proxy(frontend, backend, capture) + + # we never get here + frontend.close() + backend.close() + if capture: + capture.close() + context.close() + +def capture(): + context = zmq.Context() + socket = context.socket(zmq.SUB) + socket.setsockopt(zmq.SUBSCRIBE, b'') + addr = "tcp://127.0.0.1:5561" + socket.connect(addr) + print("connecting to " + addr) + + while True: + r = socket.recv_multipart() + print(r) + + print("====") + + + +def main_forwarder(): + # config = init("pubsub_forwarder") + # zmq_in_connect = config['zmq_in_connect'] + # zmq_frontend = config['zmq_frontend'] + # zmq_capture = config['zmq_capture'] + + zmq_in_connect = "tcp://192.168.1.2:5560" + zmq_backend = "tcp://*:5560" + zmq_capture = "tcp://127.0.0.1:5561" + + + return forwarder(zmq_in_connect, zmq_backend, zmq_capture) + + +def main(): + config = read_config() + return proxy(**config) diff --git a/sudoisbot/network/pub.py b/sudoisbot/network/pub.py index 70e2255..9193ab9 100644 --- a/sudoisbot/network/pub.py +++ b/sudoisbot/network/pub.py @@ -14,6 +14,7 @@ class Publisher(object): self.topic = topic self.name = name self.frequency = frequency + self.loop_sleep = self.frequency # TODO: decide if this is a good term or not self.type = self.topic.decode() @@ -25,6 +26,7 @@ class Publisher(object): self.context = zmq.Context() self.socket = self.context.socket(zmq.PUB) + logger.debug(f"topic: {self.topic}") def __enter__(self): self.socket.connect(self.addr) @@ -36,20 +38,27 @@ class Publisher(object): # print(exc_value) # print(traceback) - logger.debug("closing socket and destroyed context") self.socket.close() self.context.destroy() + logger.info("closed socket and destroyed context") def publish(self): raise NotImplementedError("base class cant do anything") - def loop(self): + def start(self): + raise NotImplementedError("base class cant do anything") + + def loop(self, sleeptime=None): + # old method of doing while True + # TODO: pass the freq here instead while True: try: self.publish() time.sleep(self.frequency) except KeyboardInterrupt: - logger.info("Caught C-c..") + logger.info("ok im leaving") + break + except StopIteration: break def message(self, msg={}): @@ -61,12 +70,16 @@ class Publisher(object): } return {**msg, **base} - - def send(self, temp): - data = self.message(temp) + def pub(self, data): jdata = json.dumps(data).encode() - logger.debug(jdata) + logger.trace(jdata) msg = [self.topic, jdata] self.socket.send_multipart(msg) return msg + + + def send(self, values): + # retire this method + data = self.message(values) + self.pub(data) diff --git a/sudoisbot/network/sub.py b/sudoisbot/network/sub.py index 733687e..6476638 100644 --- a/sudoisbot/network/sub.py +++ b/sudoisbot/network/sub.py @@ -1,32 +1,67 @@ #!/usr/bin/python3 import json +import time from loguru import logger import zmq +class SubscriberTimedOutError(Exception): pass + +def reconnect(delay=3.0): + + def wrapper(f): + while True: + try: + f() + except zmq.error.Again: + logger.info(f"reconnecting after {delay}sec") + time.sleep(delay) + continue + except KeyboardInterrupt: + logger.info("ok fine im leaving") + return + + return wrapper + + class Subscriber(object): - def __init__(self, addr, topic, timeout=2): + + def __init__(self, addr, topics, rcvtimeo=5*60): + if not isinstance(topics, list): + topics = [topics] + self.addr = addr - if isinstance(topic, bytes): - self.topic = topic - else: - self.topic = topic.encode("utf-8") - self.timeout = int(timeout) + self.topics = [t.encode() if isinstance(t, str) else t for t in topics] + self.rcvtimeo_secs = int(rcvtimeo) self.context = zmq.Context() - self.connect() - - def connect(self): self.socket = self.context.socket(zmq.SUB) - self.socket.setsockopt(zmq.SUBSCRIBE, self.topic) - self.socket.setsockopt(zmq.RCVTIMEO, self.timeout) - self.socket.connect(addr) + self.socket.setsockopt(zmq.RCVTIMEO, self.rcvtimeo_secs * 1000) + #logger.info(f"RCVTIMEO is {self.rcvtimeo_secs}s") + for topic in self.topics: + self.socket.setsockopt(zmq.SUBSCRIBE, topic) + def connect(self, addr=None): + self.socket.connect(self.addr) + logger.info(f"connected to: {self.addr}, topics: {self.topics}") + + def __enter__(self): + self.connect() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.socket.close() + self.context.destroy() + logger.debug("closed socket and destroyed context") def recv(self): try: - return self.socket.recv() + while True: + msg = self.socket.recv_multipart() + yield (msg[0], json.loads(msg[1])) + except zmq.error.Again: - finish_this_file() + logger.warning(f"no messages in {self.rcvtimeo_secs}s") + raise diff --git a/sudoisbot/screen/basis33/LICENSE.txt b/sudoisbot/screen/basis33/LICENSE.txt new file mode 100755 index 0000000..89fc54b --- /dev/null +++ b/sudoisbot/screen/basis33/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2004, 2005 Tristan Grimmer +Copyright (c) 2014 Manchson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sudoisbot/screen/basis33/README.txt b/sudoisbot/screen/basis33/README.txt new file mode 100755 index 0000000..c928e53 --- /dev/null +++ b/sudoisbot/screen/basis33/README.txt @@ -0,0 +1,7 @@ +basis33 is a fixed-width bitmap font for programming and text editing, which contains Latin, Cyrillic, Greek and Hebrew characters. + +It’s designed by Manchson basing on Latin-only font Proggy Clean by Tristan Grimmer. + +basis33 is free/libre software, you are welcome to redistribute and/or modify it under the terms of MIT/Expat license; see LICENSE for details. + + diff --git a/sudoisbot/screen/basis33/basis33.ttf b/sudoisbot/screen/basis33/basis33.ttf new file mode 100755 index 0000000000000000000000000000000000000000..b20255b2943f6109d529a83677f26ebd2e6ad753 GIT binary patch literal 39488 zcmdUY3v?aTdG5bQFWdMfjIc4rj9=J*Z0mJwLkSt%lCgvkW_%$}LO|M&mzfA5)dBt}H$h?P3oyKP(FuG>Gh|GOfeSc$7kuIcXC zCacZYL_Yp1&M(=sZ+*wuvH#mDVs60stp|_vAAR}Wb9RX|;I!_N!~GLSPn`lJ`S_1< zVaDN+yAECPJ1=h*IcG#<{L4dw{R8#q?yun9-(uS^gbVfe%KHHCJe;o_Ix=~DqpMh) zKMQ!~DMef1<<`<8SO-yds z_;9_*hyF!m=D!Jf!}h^%9sRpo=3VvA$vL=%^5Q2xa8q$S>5NMp&S&tN7W7HnNr%qZ zbWcd-Qct+%ap{8WSd#Khf5kZS5Cl`7=*?4)`y(25$~V}dai;HMWyZ%)`+9`1ZC_un zWRk8}m}I9iQev<-r+D93MU;vmWlEh5M<=MEA2-%5j{Z~B;uU|4J$NR0>)6JIE7f;+ z#=?E3IIPyWum1Qewb%W*VJJSPqoUngg*+tAuyE$&V|w`j{xZ5!W` zd)}$OTRw~Z+44*ufKZw&S~-Pic_}Yb`?6(*`{g^nxxZ@jd%k74T?UTgc~SFMM4?LF zV!Cs-Bxl6L!t?TnSGdJbZN2t9wu?vJFSm);Q0CdYrvmPMPD$9d*a*?ykG1?*6(5 z>VB#2g}QIn{Z-xnuKRI)Z~fcrZ)teB;m7IH^vd+9Wy_a0FJHWT>GI20EJREcz<6AH zPipV?qW6P!pRIc_^p^U*`kNbGZ1_LvqV$T|-lzV@sjr-R%gNm*-g;u!iEB^voajEW z^~9DFn@?PQV#SGNC(b|7a$>>1efHlz`FF?v?&k0PU|uFRA9(k$o70y$4w z`$}h{W$dmFp`89b; zeqDY;o|fN|-<02$&&xCN1^He1qWqpbD}NwgmgnRTA(06gltXeGYIn(JH4m8&%a?HFkK`+W@p1Dp6Yt+`9yX8Q&imwp@?LpReqJ7u56i>yA$deN{(MwE zEssgWFdm@3GaPw1KYZ3ez5|fN_skNr#_Tks=27#MdCq*@eB1maS)N>-j3%E-{wVoD z-K@Gbb=SdH9;*9X-Ph~BQ!n*v>JQbwzy68(m+F7gFuNgb*xPWZ;qHb98@}4`t%g?{ z)5gBW(Z zJDELycGv9Pvj_0GWA>A?pP&6tbJoncYR>qa_s;pkoNvvYKR2D*I(K01(YX)IeRA&e zb6=kO>b#ZnuAX=8yj$npG4J7dFU)V8-#!1n`A^UPo94#m4b3CXk2XKw{K~m&&b{{B z+s=LH+|Qr;(z*Y%V9|n23kDWEw%|MGEj{nRc~7194=pQOMp_(|7u;~cdoOtUg1=ro zWARms4=uiD@zaZ6Sp1_4&%3be!b2C{d*L$|{%FbKC9O-YUb1(|(2~2CJhJ4=OP*iy zgNx*%B^OF^`ajwox8Mk=~YW_S~|Y;v8B%}{n5qA#hWgE+r{@@{N%;%;vc7t zX_{_N-?k1qSh@??4I@}0}?UjD@LmzKY>V(E%&SG;S*CsurE z#S1I`W#y8USFgNv^=KwRhFstDai*!m3vMg5BS3j}(YpZ{_rg2TWrgzPKYd*E+%WGa-^OH*#U3%rEcVGIsOTT~Vzh1WZ zvQ3v=ciExK?z`-<%f55@?910(e&F&)FaP}Ie{=aOtt(qMwC-)ar}d%MXIsCwcG23l zwb!n_b?v*>KDzeVwJ)u!U)Q>>ciqu-kF0xk-Sg{yu)cBqw)KtAV`(YB`T zsDa+VOJd{LW3C zJ39|`+Ro2(ezo%lU5mTgx^C;bx9c-q&vd=e_5H4Ysm!gkRkl>Fs~oL7RC&7c?aIrQ zlN(w$?AxMAbojR!W~v+<#gpV|1##;GD=7H_(0)6JV6 z+Vu3MzrCXViX~TcU2!u!2(j{1hHTSfglR(7+`O#037fGe9WL9%oyfAf>{Ul^OEA(Q z?4pad?W}aRtZwdXb{UIB7t1!Nm@LVBn+0gYb`~v+bts}Z0mmtvi$zvdRSi2WLnfz3hI)q+gxB2UvY)llS!+KM(y zQ5GHHQvuPGOW_*fDzWHpgsLQzPVC-11`q5h$G?!Z2?&oB0_1bk*Oco@~A*aUb6n^ z2%u0B;94&Mt$=r3LCXDt@t+>O3^S!mX+?!sYrMgT42`vTiisyiW{96du%mSGhw-S; zBT;Dt;6>GNrX|>I^ht7}e9#{wY950rw4eN`C$BA3p2kG5a~Af2*-9l2wG)e`-&fE_F1pi-cG>kQ6;tI!Q12p~Z_zwdRrJ*qHQwvH8Z}`*GU8M0zY_NrhGU> zjWGu5f_;WUSy_!BCu?nL4t<5%BD6Y%1el-#WI-v^OMnxAAPxZt$_$mIQ(4BG#OK8- z*GL`o5841PP|NESwxxW4B)FrB20L#AWhY1!4}=DtVSQU=?0qxl@icqK9Z3Yi4!UK& zG#zc2A5dm7qhNh{3RQnoiG)N!<#cq`*T@4}DqD;CY6~<~ft^^ePXy|)tY#Tl@Z>|} zXO^vxq!D)19giF(`)LBu#^iNvF#=Na{=Yho%Z%mSIaF{VJC`~!{JrmluJ z$PiQn&E^;Z)3}qm0WP3b*=?LRRa*zBRvq9-j%XKI7Gq=ChC#cQl0Pt$^>Z=8L~q7x zKdRK>tRc_`fV5(4fD4eYK8ru68H}HN@bL<4W?FhV&r_O7pO0&W|Cec}QABet>|2Q@ z2^nhfXd#-@%LjCm{s=oBrsS9p_QRtM@yJPOQ`l;W>k zNHT0C&hbF~LLV(SSFkp5*$J1iO@sQLO?(bhI}Ze3i~!uI-SDAoN~H#mkLS&3;B1y; z*@;Y0fJZ=SQiX?cG0KZa;t853HdjJo0Ry(rIDs1GVJ(mXO+Mqi#v%)o#KYEEuEs5Z z&=zdteZp!E#?g;4`62^5gav|P)QH*9>Re_m(NnYmJ^%#T5!2{Nu+|pFag_@c!JbrZ zmSk$U$}jp<=X~UmdSrwp@3c?rs7Q|&*c6ImC60eKAz#3|5xrWxbg}o;dZgHA?t~MR zddRuJ%bA*bL%^!iUZ(Slsd&M&W$0m84*b#{G|G^2@T;o=7PLXRGzbgY(HhOMzJc|r zYJQ7#7tO|_H~QG;B|$w!>rTMiW270Erex08TUFLc!6O{0uCY=&fV+LFr;6z0Dv} zwL@>AY8X0bFEpkk6Z)IyPkos9GIp_vfT=q0WWyR6=e^2>&p_z$0Eg!I1K)_e2`a_h z$J-+1hdC!>0XgMXlY@8wBr?>LSyG)a2hzz(qVxg*C<46-P4fq63r)}yW}fE?Q3q<} z%ndY4QSV;>{6xfz!-7#7IOB&_NPR9#wOis+S#pd~Nn!-dd(_&Rw-sQZfAB+}D)dPz z;UF;r23)z#1iy+oa#-`zTtWL`w9Vj~7;TltazFaT0{SsWL+=+3k|8EKGpcY6=rZ>U zrIgf}U?r4`@&_V)4hTXm@!@I~;&YTMdxshAGSz0(tk*8HbgBhI7Av&{8&TbZ-flF&1=BjX9)Dv4o=oeO{U{u%dVv z*dv-+Im38Oc&8h$X<7I~@VBZ0ssY81*VVMe_@ELf(-$RLzQ!m3*(mHtJdUA&iC9&A z<@QnJy7`Jgtu)Y+oi*?{R1nLxP%x!<>LK}fN;&4<{H8D>XVjZF84nS#P*J@gj@YD# z3CQ9b0E4UYj3k&LYGlQL?HKTsndm7&im*d?1K^0l;dtKcGK}Q;_&OB_^X3RU;sURV z#Dcb|C}5ESA9seds-G}k#XOC55C;k!di#R*Fl5NvB^G^Fm0%QNl;L@XZV)^C)w0TBot`^? zN^p=JVS=t0WBhXq&{t4Kdnx+`M^j>ON}mN_HDr&6r&W_$u%JTz2zDGiiupEG@<`TgG}lq;FvfkT$Fgz$`bSW~k?Eo12=4)Jev14tikTg>IHQcZU$k`e;t_GH*3J{D}*kxtG z;))HDsg*4VOtKIwq*-(L1pVpr2A?F_S$IAX<-sEY54o|_9#v<7MiTHXAyzO~RjLbb z0Z4~-Z0ZkIG;hIba+YM^-g_D~3=E!IS`WQSu~4%VBTu+v0SHJr3or^h)R_l+liD1L zql9uG9)gFG_(%&n2qW+9G6+c9DIP&wclnsEnFnS8`h<`)a~!5-m>DRa@OixVP{Wh!=TfupJ!}qU|`xxbK)ff=bO}AVY8s zcr76s`J~pW0Rk`NB}vANhtgIZfpSHMd&CDq0ZeT{UZX7t`s-*7wY)~(E87$$NL`nY zq{xO16e(HQpRUk{Kz2UsUr^ycKd}h_TpTDX`iMI5a$~f%qy=Db^f4FBGW#o32&7 z@`=G(yh){)r*g{?klI##gR1Bkq>&5)9m>gYL`@?#u;vta?_*OI9$_PmDs!9A_M#V; zpQHggF>%w_;Q*M3d{H<6NkbR+Pc2+9y;*zz?IpgDX3L25ex%k$U$Dik^`4 zIssP9Nn9KO`6Po+ulbyb5lZbPK5udWm59=sCT77A|CXrmk)wtZ z4al{`s6ISDb~=6{q%6{+P^uX6{8-2&E6K#KwqbPStiw}cDPKqeN9K!=cP@pDrM&8Z}iL? zt0pki0)@3j`pUL8EyQYQ;IEoM@`|1tD(}8RZ@}nFgjd+U^US9JgGI|z|IaEie(*h>7ehzArYS>uEIWu(ncP)m~ z;HZ`fZ6NY+1{_terx8j{w=KcbcXy_zds@j@%H_1O!7ZUKQSx;Z|E`-_@UpV>HQq66 zkG^d%;8Pw%;jDjd#&bod6=pub0Nv)#9EwpSJafH8GUYm5aI)C$)HV1f0vwx-=ts5{RR7;pFbRvddMS^pPqfxMbJ+L))mSF=9 z$~2&Jy;jq$>xw@9Or<=IH6`jDpj&JxwMa^`p)I~9*$_hw^yy$jG=OL!8%Y?o_1Uk! zC!N2|?o?G7l#lGzDUjGFo32k0ajUx^&$hr*^ucO#fhkG^*er0^DtxNKKxdZF!fWVc z@6fY^RH|bIbAXtG>&Q>bT%a2>d_RsvuhXZwAjFYAxsCd#{MInm_?CS&N9lBMmeZ_%fVne;9l>3jm9S4zY6$a73g#r zh-n%u)8iqgC~xm$5zlG4sD?eAaG=9F=rAkgyc|D}1^DL;h=%&@3bg{O%wilaJr8Ww z=T910*$>|f(Hz9DkucJFo26#Z9h-GHpg3zSl;v2(1{=U0-l`!r#jyz6eeoZ5<+ewEtK*UUgD^hLz%)% zSckI7GQnsz{#0hkpnTzCiMp=U(0k^odZBkVfW_;4$Qp&}2D} zRM&EewFGK&+Sspp?sD2^`;kUlqD|%JK+6>Bq{yr76dYXr1f?-j;irO z@Od?@CBz6+LdgXJl<+Z>(prm;$LeFHXwgF1MC?FjWz4pb+pw0mp$#|^!G>1vD4cvAn?oYSXcXab=@ZQX z)18&JddZlvC7yo99_4{DiFT)uK}Xdf5puo6DZTmII@Nk22h<&PLyrb8mPwNqNYzoY zRVrLG8>o8u88GikA%DQD1R~orKC@QE#Irse>Xj6V*QM4X?Q{GF8c@Cz^ z=oR0W*N(nlmiJ>HP|w&$ueKRgp@9iiU6cfoC}*B0qG#^Sux*acF-AoBI5@5)` zp_Ioyy<~w_e+^~Y^`^6W5OGy|5A{=8)xuq%P}P+1G=x>(d@T|70p4`W-+Zp+jG$Cj zHJn*og_4Ur0Sy%^<=H4%%~-+NZ0Vi6=%LX<0o-Pgp~B!!vE_+U25|KX(?P}bFWRg6 zKI|foD%3{VQp;uvTO>`sKk`Rf^=tk<45$fx;WfJYj_;1Z5dc$PCNF4<5%=u0K|PT^ z@$fBpS_N~N z+9>KRqAG%Bp-eS~!XGgj6PA953bfEl@%<@``lyh8t8&GAvy5x`_f_(F6y{~(9D9KS zCtjcI6MZW@Eib-dKpvQd>$eMpSrP)5j$>BM(9>*H9I#mwjX6r^wBJL)ohmuUwe2&G z0uETKMu6$UKebSXjd>L_P)5uKebWGABkFNwGDD{Cm!JLMp=KJG0zQH|tKZi~<6Psw z!+UBhP%^+;?!CS_R*|2Kh~tq6%k*o+=aV&kPXtHO9p2}$+R<^+;Vk$d20a6eA|6za zf3!$)&u!Y#kK|}bfwcvaNg%eV2C1}yV$CD{{EzVS>?4kM=+t_t&X7)n6$a`YJ#4A3euW=P^!Y<6ttV>1=^et3_jZ3CwxB zvB^|CVZ4ZT$z$1OSP$4#r;whmQYhTqTJ+57o}Q~~jEXCtiE|7BCoRkc7XW623nc(9 z3iav|#16-Z=4ZXApa@ER7<)*6m@m?rS5v>L7t#nbv9uT#H5o!eL4R+9HmT)brCcjv zN}2S9RRT|+2dv9Ipg2HQ=#2sl`I>FcF`=lwYV>DaS#N|;qcf1JGPL=eBFwaCccjY% zInw{4EFnirBe0eZ$Umoe66-G~Vo?L30tv=7;2=r$=N3Yvh4a}Nx&i_}3XMO-Vaa+` zURkWk)k+ai3JvFLEnt)&YuUB9Qr1yEH!I>W;GNDst)u;5?`W3_N*UE!4wq}*Nntz8 zg|Wulpm1S9n!iSiBqqMc!uMimQI1jtBnJ^Y94&;xQ{AJ&Qj%$SAHCMz3p=kha`uJ& zc>40JT75<1mw(GUNYUfQ*C;W-V(4@fT;hF0<%Sqm_(kpy>I}3g;>qP(%m^TX+^$gf zRt1VJSku5_fr!QN9M%9c!)<`8FVWrinlBF4p9mkVgVNpfGhw3PU-#8 zTnh9bN|H2jq;Y7AS)&qyy-frVAqSeK)bf`@A#du5;Ry24nLRU-5sRQS>KcMgqOAna ztF{zch=0c^Mjc0B)`ApLg#!w35Hh8d%PE&V)(C22L+&SpQI6v54-E$0<@weMetRBP z$@OgIA9fU;?f5y6zAdlXgycASpfw1Do4^D~FjoxxoRzZ6p9}e@fVI8IwSUUZRZK=8 zkb<6YIu8oOTEHbF5)O7j2WSOhsBxZH;?(C~o>NB)ikxO3izUhV)OU5GEpsIv(+>tA zt9@d7kIUwAE#c~Hjy?k<5ReL~-hT%qZ;v@KGn$wSFJM+iYg3CuH?=lcDivAQ35F@& zr4*}V*A%~Xz*Wd#bA+jNea|j<1Mg)jLh$1Y#7HAzA(y)2I+V2{$itu?=nXacS&3?; zIH#BSBl>_i&oQ;aY#elkJU8dPD!!5)`UxcwrJ*ze8Wl}$;d#!J>h&G12Rg4m+xSda zJ|2NJ7L7>^VCbFuZbIv+n!(qO&AFGbBcKE}BcMiaKUHwT45ADv#`+wZvmP9*ICAB$ zycXK(z#Ah$faW}?NLdc&%=sl?oK6RkTADLa8VdT!yT$RYrqqbbage+rmKr)Efq@E`hi;^FBCLaG@TFgqnY)&P<5#yY+wO&W~Fd?rkwQ<2UAW?s`xbE`!!x*l!Pg1E? z0QxwZkVcF?=+1s1%GW?F42#!nkgJzvlgcvs;5o$`fl+TN2~dit1NZ z804L_xEn$jvxWxv{0&L(dKh5QW#@R zzDDBL8OSFkOpA%ff9LODSjZ1@_3JmqeAH&367s?kG=mb~uF#dYejSOZZ}RE;uv_fs zwG9;|k_9~a-D|dvzd-}r(Dq@S6piz7gf^^igp*H3eSXu%XIp9+B+Xif{wT0di*smL z&9~Y%kS!2KLn#7q6oq}j=sm0OCh8`E#iEkYd8vUiJ+O09L4HlJGL0n`&O$3AD>2H^ z#WTQh`I?9Ww5ZSgH=?2@sYPy@u>l7qHy{*`ezM| z>SJEtMLa)4B!|=L$477AaT$Cz7I;F$NuT(y_k1DA#~PjQH!!MjbjC;HKG{V{!gwQS zpW8mHl`*-Yf?t}#G1rG<9Cj8_(Ep}fO$_$Xxra1S0_+U4Wa==EeHe)V8LjdU<_ge) zmYgE?LJ9)9s0YvqjpSn(a;X-^WMngpYpOT+Gx~rn2S(T$hv3Wl9e&1C&7!m)ag!ub zDKHp-T=oUg><+x_k5fS6Z_`xu&U+U9*r(P9rp>YSn|#Ox8AqcZ#8|ImiUU05D&VMTSA`k(=TvsalR=7k~i|07*$(^tb3u znFkmc6?Bg1e^Zmr)0S~FC+iXavr0UOHk?O zw}D5M6^H_xpv-cQ+?514m2=F?8Eg6b=5I_sLxfsfID!u?sw{8kk%uCmQcfuAaz0aI z;}#5MF6RnB0|p$(R#|s^Gg)&G{_2GKJ8T>9nxT6%=SHH+KXjcxM8k=|7#0iq~8nE`26BwLngg*7R~x z69*uGDC|4rCX_3mD5zXvTbjeV8hfR#y_TsFd>F+-^zu>58@rELq_e(BXbVMZV1k<`{wOr3Lj0J<&qN;!_9fb3Y!D6jB5 z33gK&$Nd=Y00RE#SI8appH$m5H4=Ue1LFyV!p&K{%xcTF;%ze6Sj;CPT%;drzk!ak zAQioBJxfepDfDnxv+HjVj89V^5oK!G0$K^f{GPX*E_pr~g3Jp4z2;JrGE_^~kBENIc0Hy8vYkby$p zU~}B#O!=aylcQWcDj>&-bANq|#0iRrBMh1itMOWDzNym$JEKP)Ka_S@Jx3X?Tank2=5xh!##E(q z#A=QuiMP$Dr|^qv3jrl&-qFvaU&WYh!4-%LqYJB!TPUQJ*H{Dw0y*Qu^#}N6h^H&? zt$B1p!w?%WEXB2ZdUZWzBTV@9B{YP64$unv7aRt+wdk#yZ3Msb=%oT!FmiE8b^;q3dKpGNkyA*T zF;Znn5u!O7Ms@|IUiq~Nd(-~Hh(d4h8?7+@Hs`5>Y zUcNv^w3g4)$hgXo-Px@{akDSuQH;Z3tx8vZm3BX`0Y?7m100|1;+*zPkf;JAWC=NC z(HKz&g{{+Iul$3rY6(|N`L zj39lXKAoONxqG?!Xhe$#B~_4y5S&T$bmhG&u-x3GSo zlGF8k5(6u7lwBbNZ!5l^&MthvCXEH&=cuy0FHi?TtKLfX7^q(u^%kgSOa-$Jw7X2n zrIuhm78giu7JRaJhnDuLy%E{+I7c|--ooM(7J>MU6@a1*SJ8u&0Z^l}kZ8k{1PU!s zX>lkxM}JZO%Hu0c+zYnueZ^aB&GQ6W1S39I@<>HKOH)x;AdLkAr9!<8 zdpZjE7|}KM=7^1O(axeXQ zEMY8T{-7Jb}{!sd}W4Nd^>`)dS1hbq8{UU=8ufBR#OPp!*+?crNAa6cp zF*aM&IIeQ7l4G&1rJ{|+me>PNE1ShZ&N4d&FKjBdR)Ig)ElI3&f!qU{{x=$&QPBz7tHx$ zsLztG&ATPk=g7?YSBClmxpe-ckb$X3HZ|ixkmBn`=Fa}>P)|^QCDiL=&72vbUN3X! zTodZ}PsDR>3H3%?e@Cd#l#V&?3H4d#fjQp{^*M6x{7XW8f%MMr#{Ux@lcV@gzT+}1 zhh+%=y*I^wCO(K?(M<8*h}-b#!ha}^^$Kcz+I9jh`|Bd4h>$NzFBCk2k>&`QC{ z09y5@N|uz4Lsc@ozVa7QeMpih5J{YGsa1q}y438g)x_t`d5eLy+!Fembs2Z3cY z$L0009$3bKjTDTiv^)*NDuc3KDf3#{k*=s$&x^Mk&wsTQ75v@_ipRiTA(dLV(WVc9 z_HoecxuhjWnbBUnm5~O|<87ds`igC$#t)$_c^gwrd+Q~av`6a6>vsbG?b>@;U$8L# zKkzZR15gV~FZ7q9|CsisT+4EZK2XS_?5)+ks6ATg2p}9#IAt629DDnXt!nk-wMT1+ zF^3#^%RdSV$4eZZO`q#>32euIQK!E5$Tmc@>+!$Ud-ci=TuTv$dU5AE{D*b^xAksZ zS&kOG!~4tC9QPS?$zpXgm3(5)q z8uX%c*k&iBM^4#t3u<0wK{EgG%+?hE1k7>aQw*d#KiE} zXgWNR4h@bE9=Iz#Jl;P#IXKXo9vUAUOvetT2Z#E{4-dAclVfTB=w0d2!Ey9X#|}*P z504Iy9!~qygCK`3Cx-xLV(if5o&Dp3XgrYiPfUy*9PS6abYSe@u_J?{ll_y#cW8KI za3WnZIW(9q-xqo;zf_S74EB$t!=r#Un8sV_ox_tuW5*`b@xh77@!^An(TYX~M~)4U zhIo5q_{ea;!0sxe2~vJ+0vwRe*7V5O!0;g+2bHa(#}14PPYkuD0}$Q#@PT8KXgR@) z2M0&l3w*2}8&4+&M@9%?7<4PI1$EknSRfnV4MPbZlG^*7CWI1Z?j~_wCu%cm3wxo^;2) zbZ_sT>vnYabf?QV@5A}>*7W)vecSi!?@Q65ck}MP8`C}8(#^YXOy9C&cXw;rbHm== zo_+h$J-z9UU3+)#=)tueySMJ#-@Rk^HR%@g+r6hR-MM4ejy?eG+mjMu0JWnB{kEmM zdV05R2jtCLcI@2IcVlb1ZAag3!rTU+o726Ud;4~5-M@2lZ@PDX@7_K8dVszgV0Z7> zy{#8mdUo~f?pp_}xR&-@hm&;Q_RTwYDyq%rMB7;9hMOsJni9k6r@4&G_5ew`0$4vaxl~ z?!I1}wt~yvzP#)8JNEUorki_r?4v-o_3i<9iW5C3v)$~syT?PJsFmtmBWOZ_?FZ92 zE!{nvcLEmFv%9J>0?7en2%JGoNGCpWpnqa`q7#)~;yNO0P^iDr?)@*LJkE^Xh5PwP9!j*0yz|;WTZ|JfUKMubnJO zO*CxHV_z+nm}vF`3a1&z!Ttc)VNySU#A!&cGgX`nHKtofA(}CWiBl|2?BZ0QNQDOh zi9HC-r_naE+o-nP8YZ4h580JTN(gC^$Na-FSL*d<^5#ks~_ZU|g9T zJU*EYGA@oDZcXnT8a_D0K*=F#BHf802BX7P43Q&<;u;vY4;~mFyfZyKa@WzJ3Hbix z>WOquM#h_{#a{d*v6913<5*XeZ%)>9k+ik6Iq;>d>YDR_*VDCb_*dQBa zlU#xL@l$e@yh*N>H)9Qp+2K~rhqh^Ex%F$DzI3HB4}3ItlIHDKA5R zcSD!&l%J7z$+PlH@@~0Dto*F}gZxOoB)@NFnRCo+Gsny|^UQqHY|b?c%z388oNpGI zMdkvt*j#9qn2XF(bFoRyGPB&QFe}X}bBS4P)|gApW#)3zYSx-{X1!@M?WV(Ynl4i@ z8_Y(t$y{NsG(TmoGH)_hn>S6P#Gh~L%ZRQ5tdCB~x`IdY^z9?Tc zeXTz**|mQR`g-l(yq-5)xdj*SktwQbzykDJ@VvD+WFg!@}M z!}%3@?AW+LkDVL4!m$#Lo5QiEVRCGAY+}y9Fp_nQ)p|B-^T^Smeyz^zN5(feGC15n zyXWY{@CZU8+t27hQq0QC9b=(3d(V;KgX7`;?ER(2v-TVrJnWk{52JY%t_H9Z(XP^9N?Yv|De(b6j8?=A@0W@zoOy(K}21h3Qn*yx*yK$qAH<6)(H3DmRhq5-J zta&20Y)wXwCC7)G#=L~<#)rllG146AZ*R~;-6VJn`0I{>zk}F7PQw@lKbv49S@UYh zoE=~)xwFGfJ(;DQwd%>}ZfjIB+v|a%-Cx@lkKVqz+PqzMwfXB^ZQd@s+PqzMwZ(Sc z4!hdCeRj2NRCrx&o5IoCZ&#bQ)2_BH;riBa^mgCX))UUXUv#wx{OtjMd%)iw@V5v2 z?E!y#z~3J5w+H;~0e^eI-yZO{2mI{;e|x~+9`JVr{2c**N5J0^@OK3K9RYtwz~2$@ zcLe+$0e?rp-x2V41pFNVe@DRI5%6~g{G9=RXTaYX@OK9ModJJmz~340cLw~O0e@$} z-x=_C2K=1?e`mno8Sr-n{9OTmSHRyD@OK6LT>*brz~2?{cLn@i0e@G(-xct81^itB ze^wjZjZ0h?eSH*J-$k}$5-k0_$u8VU!~jQt8{yOm2S^xrQ7pa>Gt?5-2s13z~2+_ z_XPYs0e?@x-xKin1pGY#e^0>Q6Y%#0{M$nRZC!J2KZd|RK6nRKX9frAdyb8d(bYOS z+cz@VGpG3FL4(?CZ093i max_delay: logger.error(f"suicide snail: {delay.seconds} secs") - sys.exit(13) - -def msg2csv(msg): - short_timestamp = msg['timestamp'][:19] # no millisec - csv = f"{short_timestamp},{msg['name']},{msg['temp']}" - return csv - -def sink(addr, timeout, max_delay, state_file): - topic = b"temp" - context = zmq.Context() - socket = context.socket(zmq.SUB) - socket.setsockopt(zmq.SUBSCRIBE, topic) - socket.setsockopt(zmq.RCVTIMEO, timeout) - # Even though I'm the subscriber, I'm allowed to get this party - # started with `bind` - #socket.bind('tcp://*:5000') - - socket.connect(addr) - logger.info(f"Connected to: '{addr}'") - - while True: - try: - msg = socket.recv_multipart() - except zmq.error.Again: - secs = timeout // 1000 - logger.warning(f"no messages after {secs} seconds") - socket.close() - context.destroy() - raise + raise SystemExit("suicide snail") - j = json.loads(msg[1]) - csv = msg2csv(j) - logger.bind(csv=True).log("TEMPS", csv) - if state_file: - try: - update_state(j, state_file) - except PermissionError as e: - logger.error(e) - raise SystemExit +#from sudoisbot.sink import models +#from playhouse.db_url import connect +#from peewee import IntegrityError +# db = connect(dburl) +# models.db.initialize(db) +# try: +# if j['type'] == "weather": +# extra = json.dumps(j["weather"]) +# elif j["type"] == "temp": +# extra = json.dumps(j.get('metadata')) -@catch() -def main(): - #config = init(__name__) - config = init("temper_sub") - - addr = config['addr'] - state_file = config.get("state_file", "") - csv_file = config.get("csv_file", False) - timeout = config.get("timeout", 1000*60*5) # 5 minutes - max_delay = config.get('max_delay', 2) # seconds - - if state_file: - logger.info(f"Maintaining state file: {state_file}") - else: - logger.info("Not maintaining a state file") - - # adding a new log level. INFO is 20, temps should not be logged - # by an INFO logger - logger.level("TEMPS", no=19, color="", icon="🌡ï¸") - if csv_file: - # adding a logger to write the rotating csv files - # no logger timestamp since thats part of the csv data - try: - logger.add(csv_file, - level="TEMPS", - format="{message}", - rotation=config['csv_file_rotation'], - filter=lambda a: "csv" in a['extra']) - logger.info(f"Saving csv to: {csv_file}") - except PermissionError as e: - logger.error(e) - raise SystemExit - else: - logger.info("Not saving csv files") - - logger.info(f"max_delay: {max_delay} secs") - - while True: - # endless loop to handle reconnects - try: - sink(addr, timeout, max_delay, state_file) - except zmq.error.Again: - logger.info("reconnecting after 10 seconds") - sleep(10.0) - continue - -if __name__ == "__main__": - main() +# with models.db: +# models.Temps.create(timestamp=j['timestamp'], +# name=j['name'], +# temp=j['temp'], +# extra=extra) +# except IntegrityError as e: +# logger.error(e) diff --git a/sudoisbot/temps/dht.c b/sudoisbot/temps/dht.c new file mode 100644 index 0000000..3795bcc --- /dev/null +++ b/sudoisbot/temps/dht.c @@ -0,0 +1,92 @@ +/* + * dht.c: + * read temperature and humidity from DHT11 or DHT22 sensor + * + * depends on 'wiringpi' apt package + */ + +#include +#include +#include +#include + +#define MAX_TIMINGS 85 + +int data[5] = { 0, 0, 0, 0, 0 }; + +void print_json(int dht_pin) { + uint8_t laststate = HIGH; + uint8_t counter = 0; + uint8_t j = 0, i; + + data[0] = data[1] = data[2] = data[3] = data[4] = 0; + + /* pull pin down for 18 milliseconds */ + pinMode(dht_pin, OUTPUT); + digitalWrite(dht_pin, LOW); + delay(18); + + /* prepare to read the pin */ + pinMode(dht_pin, INPUT); + + /* detect change and read data */ + for (i = 0; i < MAX_TIMINGS; i++) { + counter = 0; + while ( digitalRead(dht_pin) == laststate ) { + counter++; + delayMicroseconds(1); + if (counter == 255) { + break; + } + } + laststate = digitalRead(dht_pin); + + if (counter == 255) { + break; + } + + /* ignore first 3 transitions */ + if ( (i >= 4) && (i % 2 == 0) ) { + /* shove each bit into the storage bytes */ + data[j / 8] <<= 1; + if (counter > 16) + data[j / 8] |= 1; + j++; + } + } + + /* + * check we read 40 bits (8bit x 5 ) + verify checksum in the last byte + * print it out if data is good + */ + if ((j >= 40) && (data[4] == ((data[0]+data[1]+data[2]+data[3]) & 0xFF))) { + float h = (float)((data[0] << 8) + data[1]) / 10; + if ( h > 100 ) { + h = data[0]; // for DHT11 + } + float c = (float)(((data[2] & 0x7F) << 8) + data[3]) / 10; + if ( c > 125 ) { + c = data[2]; // for DHT11 + } + if ( data[2] & 0x80 ) { + c = -c; + } + printf("{\"humidity\": %.1f, \"temp\": %.1f}\n", h, c); + } else { + printf("{\"error\": \"checksum\"}\n" ); + } +} + +int main(int argc, char *argv[]) { + if (wiringPiSetup() == -1) { + exit(1); + } + /* dht_pin 3 is GPIO-22 */ + int dht_pin; + if (argc > 0 && sscanf(argv[1], "%i", &dht_pin) != 1) { + exit(2); + } + + print_json(dht_pin); + return(0); +} diff --git a/sudoisbot/temps/rain_pub.py b/sudoisbot/temps/rain_pub.py new file mode 100644 index 0000000..9d9bc79 --- /dev/null +++ b/sudoisbot/temps/rain_pub.py @@ -0,0 +1,144 @@ +#!/usr/bin/python3 + +import json +from json.decoder import JSONDecodeError +from time import time, sleep +from datetime import datetime, timezone + +from loguru import logger +import serial + +from sudoisbot.network.pub import Publisher + +#device = "/dev/cu.usbserial-A800eGKH" +BAUDRATE = 9600 + + +class ArduinoSensor(serial.Serial): + def __init__(self, wait_timeout=1000, device="/dev/ttyUSB0"): + # wait_timeout = how often arduino should send a value in ms + # called 'timeout' in arduino code + # needs a better name + # especially since the next line also has a timeout variable + # but thats the serial read timeout + super().__init__(device, BAUDRATE, timeout=3600) + self.wait_timeout = wait_timeout + self.kind = "arduino" + + def start(self): + try: + data = self.readline() + if not json.loads(data)['ready']: + # 'true' is hardcoded.. + raise JSONDecodeError + except (KeyError, JSONDecodeError) as e: + # need to polish this when im able to reproduce + # maybe figure out why it happens + logger.error(f"invalid json: {data}") + raise SystemExit + + # \n is important ! + logger.info(f"setting {self.wait_timeout} as interval") + self.write(f"{self.wait_timeout}\r\n".encode()) + + + def __enter__(self): + # if i want to use this not as a context manager ill need + # self.started + super().__enter__() + self.start() + return self + + def iter_lines(self, f=json.loads): + while True: + line = self.readline() + if line == b"": + continue + if f is not None: + yield f(line) + else: + yield line + + +class ArduinoRainSensor(ArduinoSensor): + + def iter_lines(self, f=json.loads): + for jline in super().iter_lines(f): + rain = jline['digital'] == "LOW" + now = datetime.now(timezone.utc).isoformat() + yield { + 'digital': jline['digital'], + 'time': now, + 'rain': rain + } + + +class RainPublisher(Publisher): + def __init__(self, sensor, addr, name, loc, freq): + super().__init__(addr, b"rain", name, freq) + + self.sensor = sensor + self.freq = freq + self.pub_at = time() + + self.tags = { + 'name': name, + 'location': loc, + 'kind': self.sensor.kind + } + + + logger.info(f"emitting data as '{name}' every {freq}s") + + def publish(self, line): + data = { + 'measurement': 'rain', + 'time': line['time'], + 'tags': self.tags, + 'fields': { + 'value': line['rain'], + 'value_int': int(line['rain']) + } + } + + logger.info(json.dumps(data, indent=2)) + self.pub(data) + + + def start(self): + try: + return self._start() + except KeyboardInterrupt: + logger.info("ok im leaving") + + + def _start(self): + rain_state = False + for rainline in self.sensor.iter_lines(): + + #logger.debug(rainline) + + now = time() + if now >= self.pub_at or rainline['rain'] != rain_state: + + if rainline['rain'] != rain_state: + logger.warning("state change!") + + self.publish(rainline) + self.pub_at = now + self.freq + + rain_state = rainline['rain'] + +def main(): + addr = "tcp://broker.sudo.is:5559" + name = "balcony" + loc = "s21" + freq = 60 + + with ArduinoRainSensor(3000) as rain_sensor: + with RainPublisher(rain_sensor, addr, name, loc, freq) as pub: + pub.start() + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/sudoisbot/temps/sensors.py b/sudoisbot/temps/sensors.py index 49699a3..08d3960 100644 --- a/sudoisbot/temps/sensors.py +++ b/sudoisbot/temps/sensors.py @@ -15,6 +15,7 @@ class TempSensorBase(object): class TemperSensor(Temper, TempSensorBase): sensortype = "temper" + sample_interval = 30 @classmethod def get(cls): @@ -50,11 +51,12 @@ class TemperSensor(Temper, TempSensorBase): def read(self): data = self._read() - mapping = {'internal temperature': 'temp', - 'internal humidity': 'humidity', - 'external temperature': 'temp', - 'external humidity': 'humidity'} - + mapping = { + 'internal temperature': 'temp', + 'internal humidity': 'humidity', + 'external temperature': 'temp', + 'external humidity': 'humidity' + } results = [] for item in data: @@ -73,10 +75,17 @@ class TemperSensor(Temper, TempSensorBase): return results - - class Ds18b20Sensor(TempSensorBase): sensortype = "ds18b20" + sample_interval = 10 + + # study: 28-0300a279f70f + # outdoor: 28-0300a279bbc9 + + # File "/home/ben/sudoisbot/sudoisbot/temps/sensors.py", line 94, in _parse_data + # if not data[0].endswith("YES"): + # â”” [] + def __init__(self, sensor_ids): def w1path(sensor_id): @@ -91,6 +100,9 @@ class Ds18b20Sensor(TempSensorBase): raise SensorDisconnectedError(sensor) def _parse_data(self, data): + # YES = checksum matches + if len(data) == 0: + raise SensorDisconnectedError if not data[0].endswith("YES"): raise SensorDisconnectedError tempstr = data[1].rsplit(" ", 1)[1][2:] @@ -100,16 +112,16 @@ class Ds18b20Sensor(TempSensorBase): def read(self): # just expecting one sensor now + if not self.sensors: + raise SensorDisconnectedError(sensorid) + for sensorid, sensorpath in self.sensors: data = self._read_sensor(sensorpath) temp = self._parse_data(data) # figure out the rest and do checksums in the future - - yield {'temp': temp } - - else: - raise SensorDisconnectedError(sensorid) + yield {'temp': temp, + 'sensorid': sensorid } @classmethod def get(cls): diff --git a/sudoisbot/temps/temp_pub.py b/sudoisbot/temps/temp_pub.py index cfcaeca..f9ce0d5 100644 --- a/sudoisbot/temps/temp_pub.py +++ b/sudoisbot/temps/temp_pub.py @@ -18,6 +18,13 @@ from sudoisbot.temps.exceptions import * # use tmpfs on raspi for state # set up ntp on raspbi +# How to decide name: +# +# - check if the config tells us to expect a sensor +# and then also what its name is +# - autodetect sensors and load sensor classes +# - +# class TempPublisher(Publisher): def __init__(self, addr, name, freq, sensor=None): @@ -34,9 +41,10 @@ class TempPublisher(Publisher): for t in temp: data = { 'temp': t['temp'], 'metadata': { 'sensortype': self.sensortype, - 'firmware': t.get('firmware') } } + 'firmware': t.get('firmware'), + 'sensorid': t.get('sensorid')} } # adds name, timestamp, frequency, type - return self.send(data) + self.send(data) except KeyError as e: if self.sensortype == "temper" and e.args[0] == 'temp': diff --git a/sudoisbot/unifi.py b/sudoisbot/unifi.py index 5d6910b..78a8163 100755 --- a/sudoisbot/unifi.py +++ b/sudoisbot/unifi.py @@ -13,6 +13,32 @@ import requests from loguru import logger from sudoisbot.common import init +from sudoisbot.network.pub import Publisher + +class UnifiPublisher(Publisher): + def __init__(self, addr, name, freq, unifi_config, people): + super().__init__(addr, b"unifi", name, freq) + + self.unifi_config = unifi_config + self.people = people + + def publish(self): + home = set() + try: + # constructor logs in + api = UnifiApi(self.unifi_config) + wifi_clients = api.get_client_names() + except RequestException as e: + logger.error(e) + raise # ??? + + for person, devices in self.people.items(): + for device in devices: + if device in wifi_clients: + home.add(person) + + self.send({'home': list(home)}) + class UnifiApi(object): def __init__(self, unifi_config): @@ -80,5 +106,20 @@ class UnifiApi(object): logger.warning(f"weird client on unifi: {client}") return names +def pub(): + + config = init("unifi_pub", fullconfig=True) + + addr = config['screen_pub']['addr'] + name = 'unifi' + sleep = 240 + unifi_config = config['unifi'] + people = config['screen_pub']['people_home'] + + with UnifiPublisher(addr, name, sleep, unifi_config, people) as pub: + pub.loop() + + + if __name__ == "__main__": show_clients() diff --git a/sudoisbot/util/csv2db.py b/sudoisbot/util/csv2db.py index 04f28e8..2f3b549 100644 --- a/sudoisbot/util/csv2db.py +++ b/sudoisbot/util/csv2db.py @@ -112,5 +112,6 @@ if __name__ == "__main__": logger.error(e) logger.info(f"duplicates: {len(dups)}") logger.info(f"imported {len(imported)} rows from '{args.csv}'") + logger.info(f"database: '{args.db}'") logger.info(f"from: {imported[0].timestamp}") logger.info(f"to: {imported[-1].timestamp}") diff --git a/sudoisbot/util/csv2influx.py b/sudoisbot/util/csv2influx.py new file mode 100644 index 0000000..ec35b66 --- /dev/null +++ b/sudoisbot/util/csv2influx.py @@ -0,0 +1,145 @@ +#!/usr/bin/python3 +import sys +import argparse +from datetime import datetime +import os +import json +import dateutil.parser +from datetime import timezone +import fileinput + +from loguru import logger +from influxdb import InfluxDBClient +import requests.exceptions + +from sudoisbot.sink import models +from sudoisbot.common import init + + + +def mkbody(dt, name, temp): + dt = dateutil.parser.parse(dt).astimezone(timezone.utc).isoformat() + return { + "measurement": "temp", + "tags": { + "name": name + + }, + "time": dt, + "fields": { + "value": float(f"{float(temp):.2f}") # ugh...... + } + } + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--csv") + parser.add_argument("--last", type=int) + + config, args = init("csv2influx", parser, fullconfig=True) + + #print(os.environ['GRAFANAPASS']) + + logger.info("creating influxdb client") + client = InfluxDBClient( + host='ingest.sudo.is', + port=443, + username='sudoisbot', + password=os.environ['GRAFANAPASS'], + ssl=True, + verify_ssl=True, + database='sudoisbot' + ) + + if not args.csv: + logger.info("waiting for stdin data") + try: + for line in fileinput.input(): + text = line.strip() + dt, name, temp = text.split(",") + body = mkbody(dt, name, temp) + try: + client.write_points([body], time_precision='m') + print(json.dumps(body)) + # socket.gaierror: [Errno -2] Name or service not known + # urllib3.exceptions.NewConnectionError + # urllib3.exceptions.MaxRetryError: HTTPSConnectionPool + # requests.exceptions.ConnectionError: HTTPSConnectionPool(host='ingest.sudo.is', + # port=443): Max retries exceeded with url: /write?db=sudoisbot&precision=m (Cause + # d by NewConnectionError(': Failed to establish a new connection: [Errno -2] Name or service not known')) + + # 20.09.2020 + # influxdb.exceptions.InfluxDBServerError: b'\r\n504 Gateway Time-out\r\n\r\n

504 Gateway Time-out

\r\n
nginx/1.18.0 (Ubuntu)
\r\n\r\n\r\n' + except requests.exceptions.ConnectionError as e: + raise SystemExit(f"fatal error: {e}") + + + except KeyboardInterrupt: + logger.info("ok ok im leaving!") + raise SystemExit + + + + l = list() + name_input = "" + with open(args.csv, 'r') as f: + for line in f.readlines(): + d = dict() + items = line.strip().split(",") + if len(items) == 2: + # before i was smart enough to log the name + if not name_input: + name_input = input("enter name: ") + dt, d['temp'] = items + d['name'] = name_input + + else: + dt, name, temp = items + d['name'], d['temp'] = name, temp + + #d['timestamp'] = datetime.fromisoformat(dt) + d['timestamp'] = dt + body = mkbody(dt, name, temp) + # import json + # print(json.dumps(body, indent=2)) + # raise SystemExit + l.append(body) + logger.info("finished reading file") + + + # send to influx + logger.info("sending to influxdb") + if args.last: + sendthis = l[-args.last:] + logger.info(f"just sending last {args.last} measurements") + client.write_points(sendthis, batch_size=100, time_precision='m') + print(json.dumps(sendthis[0], indent=2)) + print(json.dumps(sendthis[-1], indent=2)) + #print(len([a for a in sendthis if a['tags']['name'] == 'bedroom'])) + + + else: + logger.info("sending all measurements from csv file") + client.write_points(l, batch_size=100, time_precision='m') + + + # try: + # record = models.Temps.create(**d) + # imported.append(record) + # except IntegrityError as e: + # if e.args[0].startswith("UNIQUE"): + # dups.append(line) + # if not args.ignore_dup: + # # still ignore them per say, put still print + # # a warning if we're not expecting them + # logger.warning(f"{e}: '{line}'") + # else: + # logger.error(e) + # logger.info(f"duplicates: {len(dups)}") + # logger.info(f"imported {len(imported)} rows from '{args.csv}'") + # logger.info(f"database: '{args.db}'") + # logger.info(f"from: {imported[0].timestamp}") + # logger.info(f"to: {imported[-1].timestamp}") diff --git a/sudoisbot/util/suball.py b/sudoisbot/util/suball.py index 26ce79a..c66a007 100644 --- a/sudoisbot/util/suball.py +++ b/sudoisbot/util/suball.py @@ -12,13 +12,14 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--topic", default="") # just get the config, so logger is just default config - config, args = init('suball', parser, fullconfig=True) + #config, args = init('suball', parser, fullconfig=True) context = zmq.Context() socket = context.socket(zmq.SUB) - socket.setsockopt(zmq.SUBSCRIBE, args.topic.encode()) + socket.setsockopt(zmq.SUBSCRIBE, b'') #args.topic.encode()) - addr = config['temper_sub']['addr'] + #addr = config['temper_sub']['addr'] + addr = "tcp://broker.s21.sudo.is:5560" socket.connect(addr) logger.info(f"connected to '{addr}'") diff --git a/sudoisbot/weather/weather_pub.py b/sudoisbot/weather/weather_pub.py deleted file mode 100644 index ac8d93e..0000000 --- a/sudoisbot/weather/weather_pub.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/python3 - -# S21 msl: 40 -# S21 lat long: (52.5167654, 13.4656278) -# - -# met.no: -# -# tuncate lat/long to 4 decimals -# -# Reponse headers (firefox): -# -# Date Thu, 25 Jun 2020 20:55:23 GMT -# Expires Thu, 25 Jun 2020 21:26:39 GMT -# -# Seems like 30 mins, but check "Expires" -# -# Use "If-Modified-Since" request header -# -# Depending on how i do this, add a random number of mins/secs to -# not do it on the hour/minute -# -# must support redirects and gzip compression (Accept-Encoding: gzip, deflate) -# - -# openweatherap: -# -# -# triggers: https://openweathermap.org/triggers -# - polling -# - may as well poll nowcast -# -# ratelimit: 60 calls/minute -# -# weather condition codes: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 -# -# maybe interesting project: https://github.com/aceisace/Inky-Calendar - -from datetime import datetime -from decimal import Decimal -import os -import time -import json - -import requests -from loguru import logger -from requests.exceptions import RequestException - -from sudoisbot.network.pub import Publisher -from sudoisbot.common import init, catch, useragent - - -#user_agent2 = f"{user_agent} schedule: 60m. this is a manual run for development, manually run by my author. hello to anyone reading, contact info on github" - -lat_lon = ('52.5167654', '13.4656278') -lat, lon = map(Decimal, lat_lon) -msl = 40 - - - -owm_url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat:.4f}&lon={lon:.4f}&appid={owm_token}&sea_level={msl}&units=metric" - -rain_conditions = [ - 'rain', - 'drizzle', - 'thunderstorm' -] - -class NowcastPublisher(Publisher): - - def __init__(self, addr, name, freq, location, msl, config): - topic = b"temp" - super().__init__(addr, topic, name, freq) - self.type = "weather" - self.lat, self.lon = map(Decimal, location) - #self.token = config['token'] - #self.url = config['url'] - self.token = owm_token - self.url = owm_url.format(lat=self.lat, lon=self.lon) - - logger.debug(self.url) - - # for debugging and understanding the data - logger.add("/tmp/owm_odd.json", - format="{message}", - filter=lambda x: 'odd' in x['extra'], level="TRACE") - - - self.session = requests.Session() - self.session.headers.update({"User-Agent": useragent(), - "Accept": "application/json"}) - - # def message(self, weather): - # super().message() - - def send(self, weather): - data = self.message() - data['weather'] = weather - data['temp'] = weather['temp'] - data['humidity'] = weather['humidity'] - bytedata = json.dumps(data).encode() - logger.debug(bytedata) - # parent class has debug logger - self.socket.send_multipart([self.topic, bytedata]) - - def query_api(self): - r = self.session.get(self.url) - r.raise_for_status() - if r.status_code == 203: - logger.warning("deprecation warning: http 203 returned") - return r.json() - - def get_nowcast(self): - w = self.query_api() - - - if len(w['weather']) > 1: - logger.warning(f"got {len(w['weather'])} conditions") - logger.warning(f"{w['weather']}") - logger.bind(odd=True).trace(json.dumps(w)) - - desc = ', '.join([a['description'] for a in w['weather']]) - main = ', '.join([a['main'] for a in w['weather']]) - - - raining = 'rain' in main.lower() or 'rain' in desc.lower() - snowing = 'snow' in main.lower() or 'snow' in desc.lower() - drizzling = 'drizzle' in main.lower() or 'drizzle' in desc.lower() - thunderstorm = 'thunderstorm' in main.lower() or 'thunderstorm' in desc.lower() - any_percip = raining or snowing or drizzling or thunderstorm - if any_percip: - logger.bind(odd=True).trace(json.dumps(w)) - precipitation = { - 'raining': raining, - 'snowing': snowing, - 'drizzling': drizzling, - 'thunderstorm': thunderstorm, - 'any': any_percip - } - - temp = w['main']['temp'] - humidity = w['main']['humidity'] - - pressure = w['main']['pressure'] - - wind = w.get('wind', {}) - # this is the rain/snow volume for the last 1h and 3h - rain = w.get('rain', {}) - snow = w.get('snow', {}) - - dt = w['dt'] - # misnomer on my behalf - # .fromtimestamp() -> converts to our tz (from UTC) - # .utcfromtimestamp() -> returns in UTC - weather_dt = datetime.fromtimestamp(dt).isoformat() - - return { - 'temp': temp, - 'desc': desc, - 'humidity': humidity, - 'wind': wind, - 'rain': rain, - 'main': main, - 'snow': snow, - 'pressure': pressure, - 'precipitation': precipitation - } - - def publish(self): - try: - nowcast = self.get_nowcast() - return self.send(nowcast) - except RequestException as e: - logger.error(e) - -def pub(addr): - freq = 60 * 5 # 5 mins - - with NowcastPublisher(addr, "fhain", freq, lat_lon, msl, {}) as publisher: - publisher.loop() - -@catch -def main(): - config = init("weather_pub", fullconfig=True) - addr = config['temper_pub']['addr'] - - pub(addr) -- 2.40.1 From f6453cc2dcf60a8083e1fbca1ef4053479f75f4f Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 09:47:24 +0100 Subject: [PATCH 02/18] this isnt a depdenency for python --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7ec28dd..f321770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ temper = {git = "https://github.com/benediktkr/temper.git"} requests = "^2.23.0" peewee = "^3.13.3" influxdb = "^5.3.0" -zflux = {git = "https://github.com/benediktkr/zflux.git"} [tool.poetry.dev-dependencies] pytest = "^5.2" -- 2.40.1 From a2f6423df16e37907b1b52e884fbef6fc9dcedc9 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 12:47:48 +0100 Subject: [PATCH 03/18] better config and stuff --- .gitignore | 1 - Dockerfile | 22 ++++++++++++---------- poetry.lock | 22 +--------------------- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 3178e54..f5ae1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -config.py *~ *.pyc dist/ diff --git a/Dockerfile b/Dockerfile index c94a573..794fb1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,16 @@ -FROM python:2.7 +FROM python:3.8 +MAINTAINER Benedikt Kristinsson -RUN mkdir /sudoisbot -WORKDIR /sudoisbot +ENV SUDOISBOT_VERSION "0.2.1" +COPY dist/sudoisbot-${SUDOISBOT_VERSION}.tar.gz /opt/sudoisbot.tar.gz -COPY setup.py /sudoisbot -COPY README.md /sudoisbot -COPY bin /sudoisbot/bin -COPY sudoisbot /sudoisbot/sudoisbot +RUN pip install /opt/sudoisbot.tar.gz -RUN python setup.py install +# idea is to override with bind mounts +# since config.py doesnt do env vars as-is +ENV SUDOISBOT_CONF "/etc/sudoisbot.yml" -COPY sudoisbot.yml /etc/sudoisbot.yml -ENTRYPOINT ["python", "/usr/local/bin/tglistener.py"] +ENV SUDOISBOT_LOGFILE "/var/log/sudoisbot/sudoisbot.log" + +EXPOSE 5559 +EXPOSE 5560 diff --git a/poetry.lock b/poetry.lock index df77ac8..2e78a64 100644 --- a/poetry.lock +++ b/poetry.lock @@ -404,25 +404,6 @@ version = "1.0.1" [package.extras] dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] -[[package]] -category = "main" -description = "" -name = "zflux" -optional = false -python-versions = "^3.8" -version = "0.1.0" - -[package.dependencies] -influxdb = "^5.3.0" -loguru = "^0.5.3" -pyyaml = "^5.3.1" -requests = "^2.24.0" -zmq = "^0.0.0" - -[package.source] -reference = "6d394fc2fd8be15bfa4bc2691f9b96d1db973e4b" -type = "git" -url = "https://github.com/benediktkr/zflux.git" [[package]] category = "main" description = "You are probably looking for pyzmq." @@ -435,7 +416,7 @@ version = "0.0.0" pyzmq = "*" [metadata] -content-hash = "169f09eecc8c8ce84ab780a39b9ac42a75999d3021c325a9f5368a503eb76b38" +content-hash = "ae1ef9bc650c44d1c520b06acb5fafed9a5baa9d61edb7585963e9528c1c663a" lock-version = "1.0" python-versions = "^3.8" @@ -739,7 +720,6 @@ win32-setctime = [ {file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"}, {file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"}, ] -zflux = [] zmq = [ {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"}, {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"}, -- 2.40.1 From f9c4c8dbe0bb64994e7cb8f64b80c0bd7b30b619 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 13:46:26 +0100 Subject: [PATCH 04/18] verison bumped to 0.3.0-dev --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f321770..91b94b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sudoisbot" -version = "0.2.1" +version = "0.3.0-dev" description = "" authors = ["Benedikt Kristinsson "] repository = "https://github.com/benediktkr/sudoisbot" -- 2.40.1 From cef9a417366984aa3dc1bab372f1f0039cfccb14 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 13:57:54 +0100 Subject: [PATCH 05/18] verison bumped to 0.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91b94b8..6b24ee6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sudoisbot" -version = "0.3.0-dev" +version = "0.3.0" description = "" authors = ["Benedikt Kristinsson "] repository = "https://github.com/benediktkr/sudoisbot" -- 2.40.1 From 7dbf34f4d9a02c432e73123c04af652f68b89105 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 15:37:15 +0100 Subject: [PATCH 06/18] fixes --- .gitignore | 1 + Dockerfile | 7 +++--- pyproject.toml | 3 ++- sudoisbot/config.py | 56 ++++++++++++++++++++++++++++++++++++++++++ sudoisbot/sink/sink.py | 4 +-- 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 sudoisbot/config.py diff --git a/.gitignore b/.gitignore index f5ae1ad..83cf893 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.pyc dist/ build/ +logs/ MANIFEST sudoisbot.egg-info/ .#* diff --git a/Dockerfile b/Dockerfile index 794fb1a..6a48733 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,15 @@ FROM python:3.8 MAINTAINER Benedikt Kristinsson -ENV SUDOISBOT_VERSION "0.2.1" -COPY dist/sudoisbot-${SUDOISBOT_VERSION}.tar.gz /opt/sudoisbot.tar.gz +COPY dist/sudoisbot-latest.tar.gz /opt/sudoisbot.tar.gz +# should build dependencies first RUN pip install /opt/sudoisbot.tar.gz # idea is to override with bind mounts # since config.py doesnt do env vars as-is ENV SUDOISBOT_CONF "/etc/sudoisbot.yml" - -ENV SUDOISBOT_LOGFILE "/var/log/sudoisbot/sudoisbot.log" +ENV SUDOISBOT_LOGFILE "/data/sudoisbot.log" EXPOSE 5559 EXPOSE 5560 diff --git a/pyproject.toml b/pyproject.toml index 6b24ee6..5444ef0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,9 +28,10 @@ sendtelegram = "sudoisbot.sendtelegram:main" proxy = "sudoisbot.network.proxy:main" sink = "sudoisbot.sink.sink:main" +weather = "sudoisbot.apis.weather_pub:main" + screen_pub = "sudoisbot.screen.screen_pub:main" unifi_pub = "sudoisbot.unifi:pub" -weather_pub = "sudoisbot.apis.weather_pub:main" temper_pub = "sudoisbot.temps.temp_pub:main" rain_pub = "sudoisbot.temps.rain_pub:main" diff --git a/sudoisbot/config.py b/sudoisbot/config.py new file mode 100644 index 0000000..df70115 --- /dev/null +++ b/sudoisbot/config.py @@ -0,0 +1,56 @@ +#!/usr/bin/python3 + +import sys +import os + +from loguru import logger +import yaml + +def setup_logger(): + if 'SUDOISBOT_LOGFILE' in os.environ: + logfile = os.environ["SUDOISBOT_LOGFILE"] + loglevel = os.environ.get("SUDOISBOT_LOGLEVEL", "DEBUG") + logger.remove() + logger.add(sys.stderr, level=loglevel) + logger.add(logfile, level=loglevel) + logger.debug("configured logger for env vars") + + +def read_config(name=None): + + # looks for config file, with the following order (default name): + # + # 1. file specified by environment var SUDOISBOT_CONF (name is ignored) + # 2. .${name}.yml in the users homedir + # 3. /etc/sudoisbot/${name}.yml + # 5. /usr/local/etc/sudoisbot/${name}.yml + # 6. .${name}.yml in current dir + homedir = os.path.expanduser("~") + if name is None: + ymlname = "sudoisbot.yml" + elif not name.endswith(".yml"): + ymlname = name + ".yml" + else: + ymlname = name + + locations = [ + os.environ.get("SUDOISBOT_CONF", ""), + os.path.join(homedir, "." + ymlname), + os.path.join('/etc/sudoisbot', ymlname), + os.path.join('/usr/local/etc/sudoisbot', ymlname), + os.path.join(os.curdir, f".{ymlname}") + ] + for conffile in locations: + try: + with open(conffile, 'r') as cf: + config = yaml.safe_load(cf) + logger.debug(f"using config file: {conffile} (new format)") + return config + except IOError as e: + + if e.errno == 2: continue + else: raise + else: + logger.error(f"No config file found") + logger.debug(f"serached: {', '.join(locations)}") + raise SystemExit("No config file found") diff --git a/sudoisbot/sink/sink.py b/sudoisbot/sink/sink.py index a29ec1c..872de33 100644 --- a/sudoisbot/sink/sink.py +++ b/sudoisbot/sink/sink.py @@ -61,7 +61,7 @@ class Sink(object): self.topics = topics self.setup_loggers(write_path) - self.state_dir = "/dev/shm" + self.state_dir = write_path self.handlers = { b'temp': self.handle_temp, b'weather': self.handle_weather @@ -147,7 +147,7 @@ class Sink(object): } def update_state(self, topic, newstate): - filename = os.path.join(self.state_dir, f"{topic.decode()}.json") + filename = os.path.join(self.state_dir, f"{topic.decode()}-state.json") simplestate.update_state(newstate, filename) def send_zflux(self, msg): -- 2.40.1 From 538900a68f94f550eb50a2df349a7824a61933dc Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sat, 7 Nov 2020 15:47:00 +0100 Subject: [PATCH 07/18] weird logging from env vars --- sudoisbot/config.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sudoisbot/config.py b/sudoisbot/config.py index df70115..8f2995f 100644 --- a/sudoisbot/config.py +++ b/sudoisbot/config.py @@ -6,7 +6,7 @@ import os from loguru import logger import yaml -def setup_logger(): +def read_config(name=None): if 'SUDOISBOT_LOGFILE' in os.environ: logfile = os.environ["SUDOISBOT_LOGFILE"] loglevel = os.environ.get("SUDOISBOT_LOGLEVEL", "DEBUG") @@ -16,8 +16,6 @@ def setup_logger(): logger.debug("configured logger for env vars") -def read_config(name=None): - # looks for config file, with the following order (default name): # # 1. file specified by environment var SUDOISBOT_CONF (name is ignored) -- 2.40.1 From f6c7e10542989af48b1263b408cb3c64c1eec9fb Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sun, 8 Nov 2020 17:33:20 +0100 Subject: [PATCH 08/18] Docerfile for build container --- Dockerfile | 4 ++++ Dockerfile.build | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 Dockerfile.build diff --git a/Dockerfile b/Dockerfile index 6a48733..4563c5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ FROM python:3.8 MAINTAINER Benedikt Kristinsson +RUN useradd -u 1210 -ms /bin/bash sudoisbot + COPY dist/sudoisbot-latest.tar.gz /opt/sudoisbot.tar.gz # should build dependencies first @@ -11,5 +13,7 @@ RUN pip install /opt/sudoisbot.tar.gz ENV SUDOISBOT_CONF "/etc/sudoisbot.yml" ENV SUDOISBOT_LOGFILE "/data/sudoisbot.log" +USER sudoisbot + EXPOSE 5559 EXPOSE 5560 diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 0000000..73630f2 --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,12 @@ +FROM benediktkr/poetry:latest +MAINTAINER Benedikt Kristinsson +RUN mkdir /builddir + +COPY sudoisbot/ /builddir/sudoisbot/ +COPY pyproject.toml /builddir/pyproject.toml +COPY poetry.lock /builddir/poetry.lock + +WORKDIR /builddir + +ENTRYPOINT ["poetry"] +CMD ["build", "-f", "sdist"] -- 2.40.1 From fb8f7e065a5332b730242f7a7b73e492ee8d7c22 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sun, 8 Nov 2020 19:04:11 +0100 Subject: [PATCH 09/18] correct package --- poetry.lock | 545 +++++++++++++++++++++++++++++++------------------ pyproject.toml | 2 +- 2 files changed, 344 insertions(+), 203 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e78a64..eb8e979 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,30 @@ +[[package]] +category = "main" +description = "In-process task scheduler with Cron-like capabilities" +name = "apscheduler" +optional = false +python-versions = "*" +version = "3.6.3" + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=1.2" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=2.8)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["pytest", "pytest-cov", "pytest-tornado5", "mock", "pytest-asyncio (<0.6)", "pytest-asyncio"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + [[package]] category = "dev" description = "Atomic file writes." @@ -13,13 +40,13 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" +version = "20.3.0" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] category = "main" @@ -35,7 +62,7 @@ description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = false python-versions = "*" -version = "1.14.0" +version = "1.14.3" [package.dependencies] pycparser = "*" @@ -55,7 +82,7 @@ marker = "sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" +version = "0.4.4" [[package]] category = "main" @@ -63,17 +90,17 @@ description = "cryptography is a package which provides cryptographic recipes an name = "cryptography" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "2.9.2" +version = "3.2.1" [package.dependencies] cffi = ">=1.8,<1.11.3 || >1.11.3" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -idna = ["idna (>=2.1)"] -pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +ssh = ["bcrypt (>=3.1.5)"] test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] [[package]] @@ -127,7 +154,7 @@ description = "A fast implementation of the Cassowary constraint solver" name = "kiwisolver" optional = false python-versions = ">=3.6" -version = "1.2.0" +version = "1.3.1" [[package]] category = "main" @@ -150,13 +177,15 @@ description = "Python plotting package" name = "matplotlib" optional = false python-versions = ">=3.6" -version = "3.2.2" +version = "3.3.2" [package.dependencies] +certifi = ">=2020.06.20" cycler = ">=0.10" kiwisolver = ">=1.0.1" -numpy = ">=1.11" -pyparsing = ">=2.0.1,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6" +numpy = ">=1.15" +pillow = ">=6.2.0" +pyparsing = ">=2.0.3,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6" python-dateutil = ">=2.1" [[package]] @@ -165,7 +194,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.4.0" +version = "8.6.0" [[package]] category = "main" @@ -181,7 +210,7 @@ description = "NumPy is the fundamental package for array computing with Python. name = "numpy" optional = false python-versions = ">=3.6" -version = "1.19.0" +version = "1.19.4" [[package]] category = "dev" @@ -201,7 +230,15 @@ description = "a little orm" name = "peewee" optional = false python-versions = "*" -version = "3.13.3" +version = "3.14.0" + +[[package]] +category = "main" +description = "Python Imaging Library (Fork)" +name = "pillow" +optional = false +python-versions = ">=3.6" +version = "8.0.1" [[package]] category = "dev" @@ -285,9 +322,10 @@ description = "We have made you a wrapper you can't refuse" name = "python-telegram-bot" optional = false python-versions = "*" -version = "12.8" +version = "13.0" [package.dependencies] +APScheduler = "3.6.3" certifi = "*" cryptography = "*" decorator = ">=4.4.0" @@ -303,7 +341,7 @@ description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" -version = "2020.1" +version = "2020.4" [[package]] category = "main" @@ -319,7 +357,7 @@ description = "Python bindings for 0MQ" name = "pyzmq" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" -version = "19.0.1" +version = "19.0.2" [[package]] category = "main" @@ -362,14 +400,24 @@ pyserial = "^3.4" reference = "8f0816fe3bc71014e2531e29253d9782621107b5" type = "git" url = "https://github.com/benediktkr/temper.git" - [[package]] category = "main" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." name = "tornado" optional = false python-versions = ">= 3.5" -version = "6.0.4" +version = "6.1" + +[[package]] +category = "main" +description = "tzinfo object for the local timezone" +name = "tzlocal" +optional = false +python-versions = "*" +version = "2.1" + +[package.dependencies] +pytz = "*" [[package]] category = "main" @@ -377,11 +425,11 @@ description = "HTTP library with thread-safe connection pooling, file post, and name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.9" +version = "1.25.11" [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] @@ -399,98 +447,102 @@ marker = "sys_platform == \"win32\"" name = "win32-setctime" optional = false python-versions = ">=3.5" -version = "1.0.1" +version = "1.0.3" [package.extras] dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] -[[package]] -category = "main" -description = "You are probably looking for pyzmq." -name = "zmq" -optional = false -python-versions = "*" -version = "0.0.0" - -[package.dependencies] -pyzmq = "*" - [metadata] -content-hash = "ae1ef9bc650c44d1c520b06acb5fafed9a5baa9d61edb7585963e9528c1c663a" +content-hash = "5e7eb18a52559d3684eaf243a0e8a765a41119d4ccc3ece0a8920b017130e880" lock-version = "1.0" python-versions = "^3.8" [metadata.files] +apscheduler = [ + {file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"}, + {file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] certifi = [ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, + {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, + {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, + {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, + {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, + {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, + {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, + {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, + {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, + {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, + {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, + {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, + {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, + {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, + {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, + {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, + {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, + {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, + {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, + {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] cryptography = [ - {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, - {file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, - {file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"}, - {file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"}, - {file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"}, - {file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"}, - {file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"}, - {file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"}, - {file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"}, - {file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"}, - {file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"}, - {file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"}, - {file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"}, - {file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"}, - {file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"}, - {file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"}, - {file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"}, - {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, - {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, + {file = "cryptography-3.2.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7"}, + {file = "cryptography-3.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d"}, + {file = "cryptography-3.2.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33"}, + {file = "cryptography-3.2.1-cp27-cp27m-win32.whl", hash = "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297"}, + {file = "cryptography-3.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4"}, + {file = "cryptography-3.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8"}, + {file = "cryptography-3.2.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e"}, + {file = "cryptography-3.2.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77"}, + {file = "cryptography-3.2.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7"}, + {file = "cryptography-3.2.1-cp35-cp35m-win32.whl", hash = "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b"}, + {file = "cryptography-3.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7"}, + {file = "cryptography-3.2.1-cp36-abi3-win32.whl", hash = "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f"}, + {file = "cryptography-3.2.1-cp36-abi3-win_amd64.whl", hash = "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538"}, + {file = "cryptography-3.2.1-cp36-cp36m-win32.whl", hash = "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b"}, + {file = "cryptography-3.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13"}, + {file = "cryptography-3.2.1-cp37-cp37m-win32.whl", hash = "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e"}, + {file = "cryptography-3.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb"}, + {file = "cryptography-3.2.1-cp38-cp38-win32.whl", hash = "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b"}, + {file = "cryptography-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851"}, + {file = "cryptography-3.2.1.tar.gz", hash = "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3"}, ] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, @@ -509,51 +561,66 @@ influxdb = [ {file = "influxdb-5.3.0.tar.gz", hash = "sha256:9bcaafd57ac152b9824ab12ed19f204206ef5df8af68404770554c5b55b475f6"}, ] kiwisolver = [ - {file = "kiwisolver-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74"}, - {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8"}, - {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2"}, - {file = "kiwisolver-1.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:be046da49fbc3aa9491cc7296db7e8d27bcf0c3d5d1a40259c10471b014e4e0c"}, - {file = "kiwisolver-1.2.0-cp36-none-win32.whl", hash = "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b"}, - {file = "kiwisolver-1.2.0-cp36-none-win_amd64.whl", hash = "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c"}, - {file = "kiwisolver-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7"}, - {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec"}, - {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec"}, - {file = "kiwisolver-1.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:63f55f490b958b6299e4e5bdac66ac988c3d11b7fafa522800359075d4fa56d1"}, - {file = "kiwisolver-1.2.0-cp37-none-win32.whl", hash = "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c"}, - {file = "kiwisolver-1.2.0-cp37-none-win_amd64.whl", hash = "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1"}, - {file = "kiwisolver-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113"}, - {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e"}, - {file = "kiwisolver-1.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657"}, - {file = "kiwisolver-1.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:38d05c9ecb24eee1246391820ed7137ac42a50209c203c908154782fced90e44"}, - {file = "kiwisolver-1.2.0-cp38-none-win32.whl", hash = "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf"}, - {file = "kiwisolver-1.2.0-cp38-none-win_amd64.whl", hash = "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd"}, - {file = "kiwisolver-1.2.0.tar.gz", hash = "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"}, + {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"}, + {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"}, + {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"}, ] loguru = [ {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] matplotlib = [ - {file = "matplotlib-3.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a47abc48c7b81fe6e636dde8a58e49b13d87d140e0f448213a4879f4a3f73345"}, - {file = "matplotlib-3.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:20bcd11efe194cd302bd0653cb025b8d16bcd80442359bfca8d49dc805f35ec8"}, - {file = "matplotlib-3.2.2-cp36-cp36m-win32.whl", hash = "sha256:2a6d64336b547e25730b6221e7aadfb01a391a065d43b5f51f0b9d7f673d2dd2"}, - {file = "matplotlib-3.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4416825ebc9c1f135027a30e8d8aea0edcf45078ce767c7f7386737413cfb98f"}, - {file = "matplotlib-3.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:465c752278d27895e23f1379d6fcfa3a2990643b803c25e3bc16a10641d2346a"}, - {file = "matplotlib-3.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:81de040403a33bf3c68e9d4a40e26c8d24da00f7e3fadd845003b7e106785da7"}, - {file = "matplotlib-3.2.2-cp37-cp37m-win32.whl", hash = "sha256:006413f08ba5db1f5b1e0d6fbdc2ac9058b062ccf552f57182563a78579c34b4"}, - {file = "matplotlib-3.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:da06fa530591a141ffbe1712bbeec784734c3436b40c942d21652f305199b5d9"}, - {file = "matplotlib-3.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:894dd47c0a6ce38dc19bc87d1f7e2b0608310b2a18d1572291157450b05ce874"}, - {file = "matplotlib-3.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1ab264770e7cf2cf4feb99f22c737066aef21ddf1ec402dc255450ac15eacb7b"}, - {file = "matplotlib-3.2.2-cp38-cp38-win32.whl", hash = "sha256:91c153f4318e3c67c035fd1185f5ea2613f15008b73b66985033033f6fe54bbd"}, - {file = "matplotlib-3.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:a68e42e22f7fd190a532e4215e142276970c2d54040a0c46842fcb3db8b6ec5b"}, - {file = "matplotlib-3.2.2-cp39-cp39-win32.whl", hash = "sha256:647cf232ccf6265d2ba1ac4103e8c8b6ac7b03a40da3421234ffb03dda217f59"}, - {file = "matplotlib-3.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:31d32c83bb2b617377c6156f75e88b9ec2ded289e47ad4ff0f263dc1019d88b1"}, - {file = "matplotlib-3.2.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:67065d938df34478451af62fbd0670d2b51c4d859fb66673064eb5de8660dd7c"}, - {file = "matplotlib-3.2.2.tar.gz", hash = "sha256:3d77a6630d093d74cbbfebaa0571d00790966be1ed204e4a8239f5cbd6835c5d"}, + {file = "matplotlib-3.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:27f9de4784ae6fb97679556c5542cf36c0751dccb4d6407f7c62517fa2078868"}, + {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:06866c138d81a593b535d037b2727bec9b0818cadfe6a81f6ec5715b8dd38a89"}, + {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5ccecb5f78b51b885f0028b646786889f49c54883e554fca41a2a05998063f23"}, + {file = "matplotlib-3.3.2-cp36-cp36m-win32.whl", hash = "sha256:69cf76d673682140f46c6cb5e073332c1f1b2853c748dc1cb04f7d00023567f7"}, + {file = "matplotlib-3.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:371518c769d84af8ec9b7dcb871ac44f7a67ef126dd3a15c88c25458e6b6d205"}, + {file = "matplotlib-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:793e061054662aa27acaff9201cdd510a698541c6e8659eeceb31d66c16facc6"}, + {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16b241c3d17be786966495229714de37de04472da472277869b8d5b456a8df00"}, + {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fb0409754b26f48045bacd6818e44e38ca9338089f8ba689e2f9344ff2847c7"}, + {file = "matplotlib-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:548cfe81476dbac44db96e9c0b074b6fb333b4d1f12b1ae68dbed47e45166384"}, + {file = "matplotlib-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f0268613073df055bcc6a490de733012f2cf4fe191c1adb74e41cec8add1a165"}, + {file = "matplotlib-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:57be9e21073fc367237b03ecac0d9e4b8ddbe38e86ec4a316857d8d93ac9286c"}, + {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:be2f0ec62e0939a9dcfd3638c140c5a74fc929ee3fd1f31408ab8633db6e1523"}, + {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c5d0c2ae3e3ed4e9f46b7c03b40d443601012ffe8eb8dfbb2bd6b2d00509f797"}, + {file = "matplotlib-3.3.2-cp38-cp38-win32.whl", hash = "sha256:a522de31e07ed7d6f954cda3fbd5ca4b8edbfc592a821a7b00291be6f843292e"}, + {file = "matplotlib-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8bc1d3284dee001f41ec98f59675f4d723683e1cc082830b440b5f081d8e0ade"}, + {file = "matplotlib-3.3.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:799c421bc245a0749c1515b6dea6dc02db0a8c1f42446a0f03b3b82a60a900dc"}, + {file = "matplotlib-3.3.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2f5eefc17dc2a71318d5a3496313be5c351c0731e8c4c6182c9ac3782cfc4076"}, + {file = "matplotlib-3.3.2.tar.gz", hash = "sha256:3d2edbf59367f03cd9daf42939ca06383a7d7803e3993eb5ff1bee8e8a3fbb6b"}, ] more-itertools = [ - {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, - {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, + {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, + {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, ] msgpack = [ {file = "msgpack-0.6.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3129c355342853007de4a2a86e75eab966119733eb15748819b6554363d4e85c"}, @@ -575,39 +642,77 @@ msgpack = [ {file = "msgpack-0.6.1.tar.gz", hash = "sha256:4008c72f5ef2b7936447dcb83db41d97e9791c83221be13d5e19db0796df1972"}, ] numpy = [ - {file = "numpy-1.19.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:63d971bb211ad3ca37b2adecdd5365f40f3b741a455beecba70fd0dde8b2a4cb"}, - {file = "numpy-1.19.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b6aaeadf1e4866ca0fdf7bb4eed25e521ae21a7947c59f78154b24fc7abbe1dd"}, - {file = "numpy-1.19.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:13af0184177469192d80db9bd02619f6fa8b922f9f327e077d6f2a6acb1ce1c0"}, - {file = "numpy-1.19.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:356f96c9fbec59974a592452ab6a036cd6f180822a60b529a975c9467fcd5f23"}, - {file = "numpy-1.19.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa1fe75b4a9e18b66ae7f0b122543c42debcf800aaafa0212aaff3ad273c2596"}, - {file = "numpy-1.19.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cbe326f6d364375a8e5a8ccb7e9cd73f4b2f6dc3b2ed205633a0db8243e2a96a"}, - {file = "numpy-1.19.0-cp36-cp36m-win32.whl", hash = "sha256:a2e3a39f43f0ce95204beb8fe0831199542ccab1e0c6e486a0b4947256215632"}, - {file = "numpy-1.19.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7b852817800eb02e109ae4a9cef2beda8dd50d98b76b6cfb7b5c0099d27b52d4"}, - {file = "numpy-1.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d97a86937cf9970453c3b62abb55a6475f173347b4cde7f8dcdb48c8e1b9952d"}, - {file = "numpy-1.19.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a86c962e211f37edd61d6e11bb4df7eddc4a519a38a856e20a6498c319efa6b0"}, - {file = "numpy-1.19.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d34fbb98ad0d6b563b95de852a284074514331e6b9da0a9fc894fb1cdae7a79e"}, - {file = "numpy-1.19.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:658624a11f6e1c252b2cd170d94bf28c8f9410acab9f2fd4369e11e1cd4e1aaf"}, - {file = "numpy-1.19.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:4d054f013a1983551254e2379385e359884e5af105e3efe00418977d02f634a7"}, - {file = "numpy-1.19.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:26a45798ca2a4e168d00de75d4a524abf5907949231512f372b217ede3429e98"}, - {file = "numpy-1.19.0-cp37-cp37m-win32.whl", hash = "sha256:3c40c827d36c6d1c3cf413694d7dc843d50997ebffbc7c87d888a203ed6403a7"}, - {file = "numpy-1.19.0-cp37-cp37m-win_amd64.whl", hash = "sha256:be62aeff8f2f054eff7725f502f6228298891fd648dc2630e03e44bf63e8cee0"}, - {file = "numpy-1.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dd53d7c4a69e766e4900f29db5872f5824a06827d594427cf1a4aa542818b796"}, - {file = "numpy-1.19.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:30a59fb41bb6b8c465ab50d60a1b298d1cd7b85274e71f38af5a75d6c475d2d2"}, - {file = "numpy-1.19.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:df1889701e2dfd8ba4dc9b1a010f0a60950077fb5242bb92c8b5c7f1a6f2668a"}, - {file = "numpy-1.19.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:33c623ef9ca5e19e05991f127c1be5aeb1ab5cdf30cb1c5cf3960752e58b599b"}, - {file = "numpy-1.19.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:26f509450db547e4dfa3ec739419b31edad646d21fb8d0ed0734188b35ff6b27"}, - {file = "numpy-1.19.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7b57f26e5e6ee2f14f960db46bd58ffdca25ca06dd997729b1b179fddd35f5a3"}, - {file = "numpy-1.19.0-cp38-cp38-win32.whl", hash = "sha256:a8705c5073fe3fcc297fb8e0b31aa794e05af6a329e81b7ca4ffecab7f2b95ef"}, - {file = "numpy-1.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:c2edbb783c841e36ca0fa159f0ae97a88ce8137fb3a6cd82eae77349ba4b607b"}, - {file = "numpy-1.19.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:8cde829f14bd38f6da7b2954be0f2837043e8b8d7a9110ec5e318ae6bf706610"}, - {file = "numpy-1.19.0.zip", hash = "sha256:76766cc80d6128750075378d3bb7812cf146415bd29b588616f72c943c00d598"}, + {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"}, + {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"}, + {file = "numpy-1.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad"}, + {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83"}, + {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764"}, + {file = "numpy-1.19.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6"}, + {file = "numpy-1.19.4-cp36-cp36m-win32.whl", hash = "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1"}, + {file = "numpy-1.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb"}, + {file = "numpy-1.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2"}, + {file = "numpy-1.19.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2"}, + {file = "numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9"}, + {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757"}, + {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15"}, + {file = "numpy-1.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387"}, + {file = "numpy-1.19.4-cp37-cp37m-win32.whl", hash = "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36"}, + {file = "numpy-1.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c"}, + {file = "numpy-1.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909"}, + {file = "numpy-1.19.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c"}, + {file = "numpy-1.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893"}, + {file = "numpy-1.19.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab"}, + {file = "numpy-1.19.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9"}, + {file = "numpy-1.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db"}, + {file = "numpy-1.19.4-cp38-cp38-win32.whl", hash = "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac"}, + {file = "numpy-1.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce"}, + {file = "numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63"}, + {file = "numpy-1.19.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37"}, + {file = "numpy-1.19.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414"}, + {file = "numpy-1.19.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc"}, + {file = "numpy-1.19.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3"}, + {file = "numpy-1.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753"}, + {file = "numpy-1.19.4-cp39-cp39-win32.whl", hash = "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f"}, + {file = "numpy-1.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b"}, + {file = "numpy-1.19.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08"}, + {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"}, ] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] peewee = [ - {file = "peewee-3.13.3.tar.gz", hash = "sha256:1269a9736865512bd4056298003aab190957afe07d2616cf22eaf56cb6398369"}, + {file = "peewee-3.14.0.tar.gz", hash = "sha256:59c5ef43877029b9133d87001dcc425525de231d1f983cece8828197fb4b84fa"}, +] +pillow = [ + {file = "Pillow-8.0.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11"}, + {file = "Pillow-8.0.1-cp36-cp36m-win32.whl", hash = "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e"}, + {file = "Pillow-8.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3"}, + {file = "Pillow-8.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8"}, + {file = "Pillow-8.0.1-cp37-cp37m-win32.whl", hash = "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0"}, + {file = "Pillow-8.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039"}, + {file = "Pillow-8.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015"}, + {file = "Pillow-8.0.1-cp38-cp38-win32.whl", hash = "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271"}, + {file = "Pillow-8.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7"}, + {file = "Pillow-8.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544"}, + {file = "Pillow-8.0.1-cp39-cp39-win32.whl", hash = "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140"}, + {file = "Pillow-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021"}, + {file = "Pillow-8.0.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6"}, + {file = "Pillow-8.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb"}, + {file = "Pillow-8.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8"}, + {file = "Pillow-8.0.1.tar.gz", hash = "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -638,12 +743,12 @@ python-dateutil = [ {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] python-telegram-bot = [ - {file = "python-telegram-bot-12.8.tar.gz", hash = "sha256:327186c56469216207dcdf8706892e58e0a62e51ef46f5143268e387bbb4edc3"}, - {file = "python_telegram_bot-12.8-py2.py3-none-any.whl", hash = "sha256:7eebed539ccacf77896cff9e41d1f68746b8ff3ca4da1e2e59285e9c749cb050"}, + {file = "python-telegram-bot-13.0.tar.gz", hash = "sha256:ca78a41626d728a8f51affa792270e210fa503ed298d395bed2bd1281842dca3"}, + {file = "python_telegram_bot-13.0-py2.py3-none-any.whl", hash = "sha256:c9fdd77f303fe168bdf669fb2e2129567fde882cc9382a6171c1571d0ac99df4"}, ] pytz = [ - {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, - {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, + {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, + {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, ] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, @@ -659,34 +764,38 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] pyzmq = [ - {file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"}, - {file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"}, - {file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"}, - {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"}, - {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"}, - {file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"}, - {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"}, - {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"}, - {file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"}, - {file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"}, - {file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"}, - {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"}, - {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"}, - {file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"}, - {file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"}, - {file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"}, - {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"}, - {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"}, - {file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"}, - {file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"}, - {file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"}, - {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"}, - {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"}, - {file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"}, - {file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"}, - {file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"}, - {file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"}, - {file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"}, + {file = "pyzmq-19.0.2-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:59f1e54627483dcf61c663941d94c4af9bf4163aec334171686cdaee67974fe5"}, + {file = "pyzmq-19.0.2-cp27-cp27m-win32.whl", hash = "sha256:c36ffe1e5aa35a1af6a96640d723d0d211c5f48841735c2aa8d034204e87eb87"}, + {file = "pyzmq-19.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:0a422fc290d03958899743db091f8154958410fc76ce7ee0ceb66150f72c2c97"}, + {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c20dd60b9428f532bc59f2ef6d3b1029a28fc790d408af82f871a7db03e722ff"}, + {file = "pyzmq-19.0.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d46fb17f5693244de83e434648b3dbb4f4b0fec88415d6cbab1c1452b6f2ae17"}, + {file = "pyzmq-19.0.2-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:f1a25a61495b6f7bb986accc5b597a3541d9bd3ef0016f50be16dbb32025b302"}, + {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ab0d01148d13854de716786ca73701012e07dff4dfbbd68c4e06d8888743526e"}, + {file = "pyzmq-19.0.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:720d2b6083498a9281eaee3f2927486e9fe02cd16d13a844f2e95217f243efea"}, + {file = "pyzmq-19.0.2-cp35-cp35m-win32.whl", hash = "sha256:29d51279060d0a70f551663bc592418bcad7f4be4eea7b324f6dd81de05cb4c1"}, + {file = "pyzmq-19.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:5120c64646e75f6db20cc16b9a94203926ead5d633de9feba4f137004241221d"}, + {file = "pyzmq-19.0.2-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:8a6ada5a3f719bf46a04ba38595073df8d6b067316c011180102ba2a1925f5b5"}, + {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fa411b1d8f371d3a49d31b0789eb6da2537dadbb2aef74a43aa99a78195c3f76"}, + {file = "pyzmq-19.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:00dca814469436455399660247d74045172955459c0bd49b54a540ce4d652185"}, + {file = "pyzmq-19.0.2-cp36-cp36m-win32.whl", hash = "sha256:046b92e860914e39612e84fa760fc3f16054d268c11e0e25dcb011fb1bc6a075"}, + {file = "pyzmq-19.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99cc0e339a731c6a34109e5c4072aaa06d8e32c0b93dc2c2d90345dd45fa196c"}, + {file = "pyzmq-19.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e36f12f503511d72d9bdfae11cadbadca22ff632ff67c1b5459f69756a029c19"}, + {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c40fbb2b9933369e994b837ee72193d6a4c35dfb9a7c573257ef7ff28961272c"}, + {file = "pyzmq-19.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5d9fc809aa8d636e757e4ced2302569d6e60e9b9c26114a83f0d9d6519c40493"}, + {file = "pyzmq-19.0.2-cp37-cp37m-win32.whl", hash = "sha256:3fa6debf4bf9412e59353defad1f8035a1e68b66095a94ead8f7a61ae90b2675"}, + {file = "pyzmq-19.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:73483a2caaa0264ac717af33d6fb3f143d8379e60a422730ee8d010526ce1913"}, + {file = "pyzmq-19.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36ab114021c0cab1a423fe6689355e8f813979f2c750968833b318c1fa10a0fd"}, + {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8b66b94fe6243d2d1d89bca336b2424399aac57932858b9a30309803ffc28112"}, + {file = "pyzmq-19.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:654d3e06a4edc566b416c10293064732516cf8871a4522e0a2ba00cc2a2e600c"}, + {file = "pyzmq-19.0.2-cp38-cp38-win32.whl", hash = "sha256:276ad604bffd70992a386a84bea34883e696a6b22e7378053e5d3227321d9702"}, + {file = "pyzmq-19.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:09d24a80ccb8cbda1af6ed8eb26b005b6743e58e9290566d2a6841f4e31fa8e0"}, + {file = "pyzmq-19.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:18189fc59ff5bf46b7ccf5a65c1963326dbfc85a2bc73e9f4a90a40322b992c8"}, + {file = "pyzmq-19.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b1dd4cf4c5e09cbeef0aee83f3b8af1e9986c086a8927b261c042655607571e8"}, + {file = "pyzmq-19.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c6d653bab76b3925c65d4ac2ddbdffe09710f3f41cc7f177299e8c4498adb04a"}, + {file = "pyzmq-19.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:949a219493a861c263b75a16588eadeeeab08f372e25ff4a15a00f73dfe341f4"}, + {file = "pyzmq-19.0.2-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:c1a31cd42905b405530e92bdb70a8a56f048c8a371728b8acf9d746ecd4482c0"}, + {file = "pyzmq-19.0.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a7e7f930039ee0c4c26e4dfee015f20bd6919cd8b97c9cd7afbde2923a5167b6"}, + {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"}, ] requests = [ {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, @@ -698,29 +807,61 @@ six = [ ] temper = [] tornado = [ - {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"}, - {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"}, - {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"}, - {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"}, - {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"}, - {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"}, - {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"}, - {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"}, - {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"}, + {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, + {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, + {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, + {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, + {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, + {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, + {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, + {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, + {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, + {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, + {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, + {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, + {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, + {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, + {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, + {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, + {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, + {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, +] +tzlocal = [ + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, - {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, + {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, + {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] win32-setctime = [ - {file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"}, - {file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"}, -] -zmq = [ - {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"}, - {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"}, + {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, + {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, ] diff --git a/pyproject.toml b/pyproject.toml index 5444ef0..16f3086 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ repository = "https://github.com/benediktkr/sudoisbot" python = "^3.8" python-telegram-bot = "*" PyYAML = "*" -zmq = "^0.0.0" numpy = "^1.18.4" matplotlib = "^3.2.1" loguru = "^0.5.0" @@ -17,6 +16,7 @@ temper = {git = "https://github.com/benediktkr/temper.git"} requests = "^2.23.0" peewee = "^3.13.3" influxdb = "^5.3.0" +pyzmq = "^19.0.2" [tool.poetry.dev-dependencies] pytest = "^5.2" -- 2.40.1 From 1dbabf64293a851d8a9b8fcb2b3a2a8820e8708b Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Wed, 11 Nov 2020 17:30:42 +0100 Subject: [PATCH 10/18] test jenkinsfile pipeile --- Jenkinsfile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..78cab52 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,5 @@ +stage('build') { + steps { + sh 'env' + } +} -- 2.40.1 From d8927c54bbdd9abc90fa272cd8c8a37bf587bd33 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Wed, 11 Nov 2020 23:45:11 +0100 Subject: [PATCH 11/18] correct yaml package figure out what the difference is --- poetry.lock | 72 ++++++------------------------------ pyproject.toml | 3 +- sudoisbot/util/csv2influx.py | 54 ++++++++++++++++++--------- 3 files changed, 49 insertions(+), 80 deletions(-) diff --git a/poetry.lock b/poetry.lock index eb8e979..1a877ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -54,7 +54,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2020.6.20" +version = "2020.11.8" [[package]] category = "main" @@ -130,24 +130,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.10" -[[package]] -category = "main" -description = "InfluxDB client" -name = "influxdb" -optional = false -python-versions = "*" -version = "5.3.0" - -[package.dependencies] -msgpack = "0.6.1" -python-dateutil = ">=2.6.0" -pytz = "*" -requests = ">=2.17.0" -six = ">=1.10.0" - -[package.extras] -test = ["nose", "nose-cov", "mock", "requests-mock"] - [[package]] category = "main" description = "A fast implementation of the Cassowary constraint solver" @@ -196,14 +178,6 @@ optional = false python-versions = ">=3.5" version = "8.6.0" -[[package]] -category = "main" -description = "MessagePack (de)serializer." -name = "msgpack" -optional = false -python-versions = "*" -version = "0.6.1" - [[package]] category = "main" description = "NumPy is the fundamental package for array computing with Python." @@ -365,13 +339,13 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" +version = "2.25.0" [package.dependencies] certifi = ">=2017.4.17" chardet = ">=3.0.2,<4" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] @@ -400,6 +374,7 @@ pyserial = "^3.4" reference = "8f0816fe3bc71014e2531e29253d9782621107b5" type = "git" url = "https://github.com/benediktkr/temper.git" + [[package]] category = "main" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." @@ -425,7 +400,7 @@ description = "HTTP library with thread-safe connection pooling, file post, and name = "urllib3" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.11" +version = "1.26.1" [package.extras] brotli = ["brotlipy (>=0.6.0)"] @@ -453,7 +428,7 @@ version = "1.0.3" dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [metadata] -content-hash = "5e7eb18a52559d3684eaf243a0e8a765a41119d4ccc3ece0a8920b017130e880" +content-hash = "277ff61e569227853a34e60db29e4be18562f26c36cfdc02d24534202379f413" lock-version = "1.0" python-versions = "^3.8" @@ -471,8 +446,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, + {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, + {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, ] cffi = [ {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, @@ -556,10 +531,6 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] -influxdb = [ - {file = "influxdb-5.3.0-py2.py3-none-any.whl", hash = "sha256:b4c034ee9c9ee888d43de547cf40c616bba35f0aa8f11e5a6f056bf5855970ac"}, - {file = "influxdb-5.3.0.tar.gz", hash = "sha256:9bcaafd57ac152b9824ab12ed19f204206ef5df8af68404770554c5b55b475f6"}, -] kiwisolver = [ {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, @@ -622,25 +593,6 @@ more-itertools = [ {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, ] -msgpack = [ - {file = "msgpack-0.6.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3129c355342853007de4a2a86e75eab966119733eb15748819b6554363d4e85c"}, - {file = "msgpack-0.6.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:31f6d645ee5a97d59d3263fab9e6be76f69fa131cddc0d94091a3c8aca30d67a"}, - {file = "msgpack-0.6.1-cp27-cp27m-win32.whl", hash = "sha256:fd509d4aa95404ce8d86b4e32ce66d5d706fd6646c205e1c2a715d87078683a2"}, - {file = "msgpack-0.6.1-cp27-cp27m-win_amd64.whl", hash = "sha256:70cebfe08fb32f83051971264466eadf183101e335d8107b80002e632f425511"}, - {file = "msgpack-0.6.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:8e68c76c6aff4849089962d25346d6784d38e02baa23ffa513cf46be72e3a540"}, - {file = "msgpack-0.6.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:86b963a5de11336ec26bc4f839327673c9796b398b9f1fe6bb6150c2a5d00f0f"}, - {file = "msgpack-0.6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:72cb7cf85e9df5251abd7b61a1af1fb77add15f40fa7328e924a9c0b6bc7a533"}, - {file = "msgpack-0.6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8c73c9bcdfb526247c5e4f4f6cf581b9bb86b388df82cfcaffde0a6e7bf3b43a"}, - {file = "msgpack-0.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:26cb40116111c232bc235ce131cc3b4e76549088cb154e66a2eb8ff6fcc907ec"}, - {file = "msgpack-0.6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:62bd8e43d204580308d477a157b78d3fee2fb4c15d32578108dc5d89866036c8"}, - {file = "msgpack-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:a28e69fe5468c9f5251c7e4e7232286d71b7dfadc74f312006ebe984433e9746"}, - {file = "msgpack-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:97ac6b867a8f63debc64f44efdc695109d541ecc361ee2dce2c8884ab37360a1"}, - {file = "msgpack-0.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3ce7ef7ee2546c3903ca8c934d09250531b80c6127e6478781ae31ed835aac4c"}, - {file = "msgpack-0.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7c55649965c35eb32c499d17dadfb8f53358b961582846e1bc06f66b9bccc556"}, - {file = "msgpack-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:9d4f546af72aa001241d74a79caec278bcc007b4bcde4099994732e98012c858"}, - {file = "msgpack-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:300fd3f2c664a3bf473d6a952f843b4a71454f4c592ed7e74a36b205c1782d28"}, - {file = "msgpack-0.6.1.tar.gz", hash = "sha256:4008c72f5ef2b7936447dcb83db41d97e9791c83221be13d5e19db0796df1972"}, -] numpy = [ {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"}, {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"}, @@ -798,8 +750,8 @@ pyzmq = [ {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"}, ] requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, + {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, + {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, @@ -854,8 +806,8 @@ tzlocal = [ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.25.11-py2.py3-none-any.whl", hash = "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"}, - {file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"}, + {file = "urllib3-1.26.1-py2.py3-none-any.whl", hash = "sha256:61ad24434555a42c0439770462df38b47d05d9e8e353d93ec3742900975e3e65"}, + {file = "urllib3-1.26.1.tar.gz", hash = "sha256:097116a6f16f13482d2a2e56792088b9b2920f4eb6b4f84a2c90555fb673db74"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/pyproject.toml b/pyproject.toml index 16f3086..9939ab8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,15 +8,14 @@ repository = "https://github.com/benediktkr/sudoisbot" [tool.poetry.dependencies] python = "^3.8" python-telegram-bot = "*" -PyYAML = "*" numpy = "^1.18.4" matplotlib = "^3.2.1" loguru = "^0.5.0" temper = {git = "https://github.com/benediktkr/temper.git"} requests = "^2.23.0" peewee = "^3.13.3" -influxdb = "^5.3.0" pyzmq = "^19.0.2" +pyyaml = "^5.3.1" [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/sudoisbot/util/csv2influx.py b/sudoisbot/util/csv2influx.py index ec35b66..c26e002 100644 --- a/sudoisbot/util/csv2influx.py +++ b/sudoisbot/util/csv2influx.py @@ -9,10 +9,12 @@ from datetime import timezone import fileinput from loguru import logger -from influxdb import InfluxDBClient +#from influxdb import InfluxDBClient import requests.exceptions -from sudoisbot.sink import models +#from sudoisbot.sink import models +from sudoisbot.sink.sink import ZFluxClient +from sudoisbot.config import read_config from sudoisbot.common import init @@ -34,24 +36,32 @@ def mkbody(dt, name, temp): if __name__ == "__main__": + config = read_config() + zflux = ZFluxClient(topic=config['zflux']['topic']) + zflux.connect(config['zflux']['addr']) + + parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--csv") parser.add_argument("--last", type=int) + args = parser.parse_args() - config, args = init("csv2influx", parser, fullconfig=True) + # -csv /srv/temps/temps.csv --last 9500 && + + #config, args = init("csv2influx", parser, fullconfig=True) #print(os.environ['GRAFANAPASS']) - logger.info("creating influxdb client") - client = InfluxDBClient( - host='ingest.sudo.is', - port=443, - username='sudoisbot', - password=os.environ['GRAFANAPASS'], - ssl=True, - verify_ssl=True, - database='sudoisbot' - ) + # logger.info("creating influxdb client") + # client = InfluxDBClient( + # host='ingest.sudo.is', + # port=443, + # username='sudoisbot', + # password=os.environ['GRAFANAPASS'], + # ssl=True, + # verify_ssl=True, + # database='sudoisbot' + # ) if not args.csv: logger.info("waiting for stdin data") @@ -61,7 +71,7 @@ if __name__ == "__main__": dt, name, temp = text.split(",") body = mkbody(dt, name, temp) try: - client.write_points([body], time_precision='m') + zflux.send(body) print(json.dumps(body)) # socket.gaierror: [Errno -2] Name or service not known # urllib3.exceptions.NewConnectionError @@ -83,8 +93,13 @@ if __name__ == "__main__": + import time + time.sleep(3.0) + logger.info('done sleeping') + l = list() name_input = "" + logger.info(f"reading {args.csv}...") with open(args.csv, 'r') as f: for line in f.readlines(): d = dict() @@ -111,19 +126,22 @@ if __name__ == "__main__": # send to influx - logger.info("sending to influxdb") + logger.info("sending to zflux") if args.last: sendthis = l[-args.last:] logger.info(f"just sending last {args.last} measurements") - client.write_points(sendthis, batch_size=100, time_precision='m') + #client.write_points(sendthis, batch_size=100, time_precision='m') + for item in sendthis: + zflux.send(item) print(json.dumps(sendthis[0], indent=2)) print(json.dumps(sendthis[-1], indent=2)) #print(len([a for a in sendthis if a['tags']['name'] == 'bedroom'])) else: - logger.info("sending all measurements from csv file") - client.write_points(l, batch_size=100, time_precision='m') + raise NotImplementedError + #logger.info("sending all measurements from csv file") + #client.write_points(l, batch_size=100, time_precision='m') # try: -- 2.40.1 From ce03f49dcadf027df4427ba1766d0b589f0ca9cd Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Thu, 12 Nov 2020 00:17:41 +0100 Subject: [PATCH 12/18] bla bla --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9939ab8..5ab55ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "sudoisbot" version = "0.3.0" -description = "" +description = "a home automation and monitoring system written to learn zmq" authors = ["Benedikt Kristinsson "] repository = "https://github.com/benediktkr/sudoisbot" -- 2.40.1 From 96849c6e7e679893c110d22a4e39402de9a3ab62 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Thu, 12 Nov 2020 00:20:38 +0100 Subject: [PATCH 13/18] weird order --- Dockerfile.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.build b/Dockerfile.build index 73630f2..58f1133 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -2,9 +2,10 @@ FROM benediktkr/poetry:latest MAINTAINER Benedikt Kristinsson RUN mkdir /builddir -COPY sudoisbot/ /builddir/sudoisbot/ COPY pyproject.toml /builddir/pyproject.toml COPY poetry.lock /builddir/poetry.lock +COPY sudoisbot/ /builddir/sudoisbot/ + WORKDIR /builddir -- 2.40.1 From 04f090de7e546528c21ad30c298f695865722475 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Fri, 13 Nov 2020 22:45:02 +0100 Subject: [PATCH 14/18] also build wheel --- Dockerfile.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.build b/Dockerfile.build index 58f1133..98d75fb 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -10,4 +10,4 @@ COPY sudoisbot/ /builddir/sudoisbot/ WORKDIR /builddir ENTRYPOINT ["poetry"] -CMD ["build", "-f", "sdist"] +CMD ["build"] -- 2.40.1 From df79483b73f22e9c08de9d7538b0b3c40f482fdd Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Sun, 15 Nov 2020 12:08:39 +0100 Subject: [PATCH 15/18] fixing bug --- sudoisbot/apis/weather_pub.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sudoisbot/apis/weather_pub.py b/sudoisbot/apis/weather_pub.py index 6b0bb7a..7b28022 100644 --- a/sudoisbot/apis/weather_pub.py +++ b/sudoisbot/apis/weather_pub.py @@ -129,11 +129,11 @@ class NowcastPublisher(Publisher): # only in the data when it's been raining/showing if 'rain' in w: - rain_1h = w['rain']['1h'], - rain_3h = w['rain']['3h'], + rain_1h = w['rain'].get('1h'), + rain_3h = w['rain'].get('3h'), if 'snow' in w: - snow_1h = w['snow']['1h'], - snow_3h = w['snow']['3h'], + snow_1h = w['snow'].get('1h'), + snow_3h = w['snow'].get('3h'), return d -- 2.40.1 From 48a1fc4d6456cc1778b33e19b3786f5d85e69cf8 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Wed, 18 Nov 2020 23:44:54 +0100 Subject: [PATCH 16/18] probably installed python-telegram-bot the wrong way somehow --- poetry.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1a877ab..2827983 100644 --- a/poetry.lock +++ b/poetry.lock @@ -428,7 +428,7 @@ version = "1.0.3" dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] [metadata] -content-hash = "277ff61e569227853a34e60db29e4be18562f26c36cfdc02d24534202379f413" +content-hash = "673eda69c1dc10a7e9ebb5f1d4fbc67c3f16b7e40703e3d1a5eaea8b7a9f1e78" lock-version = "1.0" python-versions = "^3.8" diff --git a/pyproject.toml b/pyproject.toml index 5ab55ce..5e6e7ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ repository = "https://github.com/benediktkr/sudoisbot" [tool.poetry.dependencies] python = "^3.8" -python-telegram-bot = "*" numpy = "^1.18.4" matplotlib = "^3.2.1" loguru = "^0.5.0" @@ -16,6 +15,7 @@ requests = "^2.23.0" peewee = "^3.13.3" pyzmq = "^19.0.2" pyyaml = "^5.3.1" +python-telegram-bot = "^13.0" [tool.poetry.dev-dependencies] pytest = "^5.2" -- 2.40.1 From 97a71ddb18e00732a048c0b03cc999b725176b51 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Wed, 18 Nov 2020 23:46:22 +0100 Subject: [PATCH 17/18] some sleeping for testing with zflux --- sudoisbot/util/csv2influx.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sudoisbot/util/csv2influx.py b/sudoisbot/util/csv2influx.py index c26e002..3b88861 100644 --- a/sudoisbot/util/csv2influx.py +++ b/sudoisbot/util/csv2influx.py @@ -64,7 +64,11 @@ if __name__ == "__main__": # ) if not args.csv: + logger.info("sleeping") + import time + time.sleep(3.0) logger.info("waiting for stdin data") + try: for line in fileinput.input(): text = line.strip() @@ -94,7 +98,7 @@ if __name__ == "__main__": import time - time.sleep(3.0) + time.sleep(10.0) logger.info('done sleeping') l = list() @@ -125,6 +129,8 @@ if __name__ == "__main__": logger.info("finished reading file") + + # send to influx logger.info("sending to zflux") if args.last: -- 2.40.1 From 6209dadb8379f271085b2f76eea26a6ef0e4e199 Mon Sep 17 00:00:00 2001 From: Benedikt Kristinsson Date: Fri, 18 Dec 2020 16:38:28 +0100 Subject: [PATCH 18/18] big refactor and an experimental buffering pun/sub proxy --- .gitignore | 1 + Dockerfile.old | 14 + Dockerfile.test | 30 ++ Makefile | 17 + build | 22 ++ build.sh | 26 ++ docker/proxy/Dockerfile | 18 + poetry.lock | 605 ++++++++++++++++------------- pyproject.toml | 47 +-- sudoisbot.yml | 12 - sudoisbot/__init__.py | 108 ++++- sudoisbot/{ => apis}/unifi.py | 51 ++- sudoisbot/apis/weather_pub.py | 62 ++- sudoisbot/config.py | 41 +- sudoisbot/network/proxy.py | 226 ++++++++++- sudoisbot/network/pub.py | 27 +- sudoisbot/network/sub.py | 9 +- sudoisbot/screen/screen_pub.py | 207 ++++++---- sudoisbot/screen/screen_sub.py | 26 +- sudoisbot/{temps => sensors}/dht.c | 0 sudoisbot/sensors/old.txt | 28 ++ sudoisbot/sensors/rain_pub.py | 115 ++++++ sudoisbot/sensors/sensors.py | 308 +++++++++++++++ sudoisbot/sensors/temp_pub.py | 79 ++++ sudoisbot/sink/models.py | 105 ++++- sudoisbot/sink/newdb.py | 30 +- sudoisbot/sink/simplestate.py | 30 +- sudoisbot/sink/sink.py | 133 +++---- sudoisbot/temps/exceptions.py | 6 - sudoisbot/temps/rain_pub.py | 144 ------- sudoisbot/temps/sensors.py | 154 -------- sudoisbot/temps/temp_pub.py | 111 ------ sudoisbot/unifi_clients.py | 12 - sudoisbot/util/csv2json.py | 79 ++++ sudoisbot/util/json2influx.py | 59 +++ sudoisbot/util/json2mariadb.py | 55 +++ sudoisbot/util/suball.py | 9 +- 37 files changed, 1978 insertions(+), 1028 deletions(-) create mode 100644 Dockerfile.old create mode 100644 Dockerfile.test create mode 100644 Makefile create mode 100755 build create mode 100755 build.sh create mode 100644 docker/proxy/Dockerfile delete mode 100644 sudoisbot.yml rename sudoisbot/{ => apis}/unifi.py (75%) rename sudoisbot/{temps => sensors}/dht.c (100%) create mode 100644 sudoisbot/sensors/old.txt create mode 100644 sudoisbot/sensors/rain_pub.py create mode 100644 sudoisbot/sensors/sensors.py create mode 100644 sudoisbot/sensors/temp_pub.py delete mode 100644 sudoisbot/temps/exceptions.py delete mode 100644 sudoisbot/temps/rain_pub.py delete mode 100644 sudoisbot/temps/sensors.py delete mode 100644 sudoisbot/temps/temp_pub.py delete mode 100644 sudoisbot/unifi_clients.py create mode 100644 sudoisbot/util/csv2json.py create mode 100644 sudoisbot/util/json2influx.py create mode 100644 sudoisbot/util/json2mariadb.py diff --git a/.gitignore b/.gitignore index 83cf893..b07aff6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ sudoisbot.egg-info/ \#* sudoisbot.yml *-default.yml +notes/ diff --git a/Dockerfile.old b/Dockerfile.old new file mode 100644 index 0000000..c94a573 --- /dev/null +++ b/Dockerfile.old @@ -0,0 +1,14 @@ +FROM python:2.7 + +RUN mkdir /sudoisbot +WORKDIR /sudoisbot + +COPY setup.py /sudoisbot +COPY README.md /sudoisbot +COPY bin /sudoisbot/bin +COPY sudoisbot /sudoisbot/sudoisbot + +RUN python setup.py install + +COPY sudoisbot.yml /etc/sudoisbot.yml +ENTRYPOINT ["python", "/usr/local/bin/tglistener.py"] diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..4357da9 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,30 @@ +FROM python:3.8 +MAINTAINER Benedikt Kristinsson + +RUN useradd -u 1210 -ms /bin/bash sudoisbot + +RUN mkdir /src && pip install poetry && poetry config virtualenvs.create false +WORKDIR /src + +COPY pyproject.toml /src/pyproject.toml +COPY poetry.lock /src/poetry.lock +RUN poetry install --no-root + + +COPY sudoisbot/ /src/sudoisbot/ +RUN poetry build + + +# should build dependencies first +#COPY dist/sudoisbot-latest.tar.gz /opt/sudoisbot.tar.gz +#RUN pip install /opt/sudoisbot.tar.gz + +# idea is to override with bind mounts +# since config.py doesnt do env vars as-is +ENV SUDOISBOT_CONF "/etc/sudoisbot.yml" +ENV SUDOISBOT_LOGFILE "/data/sudoisbot.log" + +USER sudoisbot + +EXPOSE 5559 +EXPOSE 5560 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..588ab00 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +SHELL=/bin/bash + +NAME=$(shell basename $(CURDIR)) +POETRY_VERSION=$(shell poetry version) +NAMESIZE=$(shell ${#NAME}) +VERSION=$(shell echo ${POETRY_VERSION:8} ) + +build: poetry-build docker-build docker-tag + +poetry-build: + poetry build ${VERSION} + +docker-build: + docker build -t sudoisbot . + +docker-tag: + docker tag sudoisbot benediktkr/sudoisbot:latest diff --git a/build b/build new file mode 100755 index 0000000..6cdf188 --- /dev/null +++ b/build @@ -0,0 +1,22 @@ +#!/bin/bash + +# set the version in the script and use it for a docker tag too +# make this a makefile + +arg=$1 + +version=$ grep "^version.*=" pyproject.toml | awk -F'"' '{print $2}') + +poetry build -f sdist + +sha1sum sudoisbot-${version}.tar.gz + +docker build -t sudoisbot . +docker tag sudoisbot benediktkr/sudoisbot:latest +docker tag sudoisbot benediktkr/sudoisbot:$version + + +if [ "$arg" = "push" ]; then + docker push benediktkr/sudoisbot:latest + docker push benediktkr/sudoisbot:$version +fi diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f90deca --- /dev/null +++ b/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +# set the version in the script and use it for a docker tag too +# make this a makefile + +if [ "$1" = "" ]; then + version=$(grep "^version.*=" pyproject.toml | awk -F'"' '{print $2}') +else + version=$1 + poetry version $version +fi + +poetry build -f sdist +cp dist/sudoisbot-${version}.tar.gz dist/sudoisbot-latest.tar.gz + +docker build -t sudoisbot . +docker tag sudoisbot benediktkr/sudoisbot:latest +docker tag sudoisbot benediktkr/sudoisbot:$version + +docker push benediktkr/sudoisbot:latest +docker push benediktkr/sudoisbot:$version + +#git add pyproject.toml +#git commit -m "verison bumped to $version" diff --git a/docker/proxy/Dockerfile b/docker/proxy/Dockerfile new file mode 100644 index 0000000..0fb28c1 --- /dev/null +++ b/docker/proxy/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.8 +MAINTAINER Benedikt Kristinsson + +#RUN mkdir /sudoisbot +#COPY sudoisbot/ /sudoisbot/sudoisbot/ +#COPY pyproject.toml /sudoisbot/pyproject.toml +#COPY poetry.lock /sudoisbot/poetry.lock +#RUN pip install /sudoisbot + +ENV SUDOISBOT_VERSION "0.2.1" +COPY dist/sudoisbot-${SUDOISBOT_VERSION}.tar.gz /opt/sudoisbot.tar.gz +RUN pip install /opt/sudoisbot.tar.gz && rm /opt/sudoisbot.tar.gz +# idea is to override with bind mounts +# since config.py doesnt do env vars as-is +ENV SUDOISBOT_CONF "/etc/sudoisbot.yml" + +EXPOSE 5559 +EXPOSE 5560 diff --git a/poetry.lock b/poetry.lock index 2827983..6e7f38c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,13 @@ [[package]] -category = "main" -description = "In-process task scheduler with Cron-like capabilities" name = "apscheduler" -optional = false -python-versions = "*" version = "3.6.3" +description = "In-process task scheduler with Cron-like capabilities" +category = "main" +optional = true +python-versions = "*" [package.dependencies] pytz = "*" -setuptools = ">=0.7" six = ">=1.4.0" tzlocal = ">=1.2" @@ -26,143 +25,156 @@ twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" -optional = false +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = true python-versions = "*" -version = "2020.11.8" [[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." name = "cffi" -optional = false +version = "1.14.4" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = true python-versions = "*" -version = "1.14.3" [package.dependencies] pycparser = "*" [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" -optional = false -python-versions = "*" -version = "3.0.4" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] -category = "main" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" [[package]] -category = "main" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." name = "cryptography" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "3.2.1" +version = "3.3.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = true +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" [package.dependencies] -cffi = ">=1.8,<1.11.3 || >1.11.3" +cffi = ">=1.12" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] -category = "main" -description = "Composable style cycles" name = "cycler" -optional = false -python-versions = "*" version = "0.10.0" +description = "Composable style cycles" +category = "main" +optional = true +python-versions = "*" [package.dependencies] six = "*" [[package]] -category = "main" -description = "Decorators for Humans" name = "decorator" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" version = "4.4.2" +description = "Decorators for Humans" +category = "main" +optional = true +python-versions = ">=2.6, !=3.0.*, !=3.1.*" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -category = "main" -description = "A fast implementation of the Cassowary constraint solver" -name = "kiwisolver" +name = "importlib-metadata" +version = "3.3.0" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.3.1" - -[[package]] -category = "main" -description = "Python logging made (stupidly) simple" -name = "loguru" -optional = false -python-versions = ">=3.5" -version = "0.5.3" [package.dependencies] -colorama = ">=0.3.4" -win32-setctime = ">=1.0.0" +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "kiwisolver" +version = "1.3.1" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = true +python-versions = ">=3.6" + +[[package]] +name = "loguru" +version = "0.5.3" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] [[package]] -category = "main" -description = "Python plotting package" name = "matplotlib" -optional = false +version = "3.3.3" +description = "Python plotting package" +category = "main" +optional = true python-versions = ">=3.6" -version = "3.3.2" [package.dependencies] -certifi = ">=2020.06.20" cycler = ">=0.10" kiwisolver = ">=1.0.1" numpy = ">=1.15" @@ -171,104 +183,122 @@ pyparsing = ">=2.0.3,<2.0.4 || >2.0.4,<2.1.2 || >2.1.2,<2.1.6 || >2.1.6" python-dateutil = ">=2.1" [[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" +version = "8.6.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" optional = false python-versions = ">=3.5" -version = "8.6.0" [[package]] -category = "main" -description = "NumPy is the fundamental package for array computing with Python." name = "numpy" -optional = false -python-versions = ">=3.6" version = "1.19.4" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = true +python-versions = ">=3.6" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "20.8" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] -category = "main" -description = "a little orm" name = "peewee" -optional = false -python-versions = "*" version = "3.14.0" - -[[package]] +description = "a little orm" category = "main" -description = "Python Imaging Library (Fork)" -name = "pillow" -optional = false -python-versions = ">=3.6" -version = "8.0.1" +optional = true +python-versions = "*" + +[[package]] +name = "pillow" +version = "8.0.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = true +python-versions = ">=3.6" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" [[package]] -category = "main" -description = "C parser in Python" name = "pycparser" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.20" +description = "C parser in Python" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] +name = "pymysql" +version = "0.10.1" +description = "Pure Python MySQL Driver" category = "main" -description = "Python parsing module" +optional = true +python-versions = "*" + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[[package]] name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "main" -description = "Python Serial Port Extension" name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +category = "main" optional = false python-versions = "*" -version = "3.4" + +[package.extras] +cp2110 = ["hidapi"] [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.4.3" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" @@ -276,33 +306,34 @@ py = ">=1.5.0" wcwidth = "*" [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "We have made you a wrapper you can't refuse" name = "python-telegram-bot" -optional = false +version = "13.1" +description = "We have made you a wrapper you can't refuse" +category = "main" +optional = true python-versions = "*" -version = "13.0" [package.dependencies] APScheduler = "3.6.3" certifi = "*" cryptography = "*" decorator = ">=4.4.0" +pytz = ">=2018.6" tornado = ">=5.1" [package.extras] @@ -310,127 +341,146 @@ json = ["ujson"] socks = ["pysocks"] [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" -optional = false -python-versions = "*" version = "2020.4" +description = "World timezone definitions, modern and historical" +category = "main" +optional = true +python-versions = "*" [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" [[package]] -category = "main" -description = "Python bindings for 0MQ" name = "pyzmq" +version = "19.0.2" +description = "Python bindings for 0MQ" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" -version = "19.0.2" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" -optional = false +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.25.0" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" +chardet = ">=3.0.2,<5" idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -category = "main" -description = "" -name = "temper" -optional = false -python-versions = "^3.7" +name = "sudoistemper" version = "0.1.0" +description = "" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" [package.dependencies] -pyserial = "^3.4" - -[package.source] -reference = "8f0816fe3bc71014e2531e29253d9782621107b5" -type = "git" -url = "https://github.com/benediktkr/temper.git" +pyserial = ">=3.4,<4.0" [[package]] -category = "main" -description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." name = "tornado" -optional = false -python-versions = ">= 3.5" version = "6.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "main" +optional = true +python-versions = ">= 3.5" [[package]] -category = "main" -description = "tzinfo object for the local timezone" -name = "tzlocal" +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" optional = false python-versions = "*" + +[[package]] +name = "tzlocal" version = "2.1" +description = "tzinfo object for the local timezone" +category = "main" +optional = true +python-versions = "*" [package.dependencies] pytz = "*" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" -optional = false +version = "1.26.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.26.1" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" -version = "0.2.5" [[package]] -category = "main" -description = "A small Python utility to set file creation time on Windows" -marker = "sys_platform == \"win32\"" name = "win32-setctime" +version = "1.0.3" +description = "A small Python utility to set file creation time on Windows" +category = "main" optional = false python-versions = ">=3.5" -version = "1.0.3" [package.extras] dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] +[[package]] +name = "zipp" +version = "3.4.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[extras] +graphs = ["numpy", "matplotlib"] +sink = ["peewee", "PyMySQL", "requests", "python-telegram-bot"] +utils = ["python-dateutil"] + [metadata] -content-hash = "673eda69c1dc10a7e9ebb5f1d4fbc67c3f16b7e40703e3d1a5eaea8b7a9f1e78" -lock-version = "1.0" -python-versions = "^3.8" +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "239556012f24717b99d505f3d97be52d27bdb6ed35db3724ad8b817fa5a06067" [metadata.files] apscheduler = [ @@ -446,78 +496,70 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] certifi = [ - {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, - {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cffi = [ - {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, - {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, - {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, - {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, - {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, - {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, - {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, - {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, - {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, - {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, - {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, - {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, - {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, - {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, - {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, - {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, - {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, - {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, - {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, - {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, - {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, - {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, - {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, - {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, - {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, - {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, - {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, - {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, - {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, - {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, + {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, + {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, + {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, + {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, + {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, + {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, + {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, + {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, + {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, + {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, + {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, + {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, + {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, + {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, + {file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"}, + {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, + {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, + {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, + {file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"}, + {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, + {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, + {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, + {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, + {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, + {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, + {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, ] chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] cryptography = [ - {file = "cryptography-3.2.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33"}, - {file = "cryptography-3.2.1-cp27-cp27m-win32.whl", hash = "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297"}, - {file = "cryptography-3.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e"}, - {file = "cryptography-3.2.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7"}, - {file = "cryptography-3.2.1-cp35-cp35m-win32.whl", hash = "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b"}, - {file = "cryptography-3.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7"}, - {file = "cryptography-3.2.1-cp36-abi3-win32.whl", hash = "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f"}, - {file = "cryptography-3.2.1-cp36-abi3-win_amd64.whl", hash = "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538"}, - {file = "cryptography-3.2.1-cp36-cp36m-win32.whl", hash = "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b"}, - {file = "cryptography-3.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13"}, - {file = "cryptography-3.2.1-cp37-cp37m-win32.whl", hash = "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e"}, - {file = "cryptography-3.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb"}, - {file = "cryptography-3.2.1-cp38-cp38-win32.whl", hash = "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b"}, - {file = "cryptography-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851"}, - {file = "cryptography-3.2.1.tar.gz", hash = "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3"}, + {file = "cryptography-3.3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030"}, + {file = "cryptography-3.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0"}, + {file = "cryptography-3.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812"}, + {file = "cryptography-3.3.1-cp27-cp27m-win32.whl", hash = "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e"}, + {file = "cryptography-3.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901"}, + {file = "cryptography-3.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d"}, + {file = "cryptography-3.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5"}, + {file = "cryptography-3.3.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"}, + {file = "cryptography-3.3.1-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244"}, + {file = "cryptography-3.3.1-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c"}, + {file = "cryptography-3.3.1-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c"}, + {file = "cryptography-3.3.1-cp36-abi3-win32.whl", hash = "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a"}, + {file = "cryptography-3.3.1-cp36-abi3-win_amd64.whl", hash = "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7"}, + {file = "cryptography-3.3.1.tar.gz", hash = "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6"}, ] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, @@ -531,6 +573,10 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +importlib-metadata = [ + {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, + {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, +] kiwisolver = [ {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, @@ -570,24 +616,31 @@ loguru = [ {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, ] matplotlib = [ - {file = "matplotlib-3.3.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:27f9de4784ae6fb97679556c5542cf36c0751dccb4d6407f7c62517fa2078868"}, - {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:06866c138d81a593b535d037b2727bec9b0818cadfe6a81f6ec5715b8dd38a89"}, - {file = "matplotlib-3.3.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5ccecb5f78b51b885f0028b646786889f49c54883e554fca41a2a05998063f23"}, - {file = "matplotlib-3.3.2-cp36-cp36m-win32.whl", hash = "sha256:69cf76d673682140f46c6cb5e073332c1f1b2853c748dc1cb04f7d00023567f7"}, - {file = "matplotlib-3.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:371518c769d84af8ec9b7dcb871ac44f7a67ef126dd3a15c88c25458e6b6d205"}, - {file = "matplotlib-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:793e061054662aa27acaff9201cdd510a698541c6e8659eeceb31d66c16facc6"}, - {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16b241c3d17be786966495229714de37de04472da472277869b8d5b456a8df00"}, - {file = "matplotlib-3.3.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fb0409754b26f48045bacd6818e44e38ca9338089f8ba689e2f9344ff2847c7"}, - {file = "matplotlib-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:548cfe81476dbac44db96e9c0b074b6fb333b4d1f12b1ae68dbed47e45166384"}, - {file = "matplotlib-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f0268613073df055bcc6a490de733012f2cf4fe191c1adb74e41cec8add1a165"}, - {file = "matplotlib-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:57be9e21073fc367237b03ecac0d9e4b8ddbe38e86ec4a316857d8d93ac9286c"}, - {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:be2f0ec62e0939a9dcfd3638c140c5a74fc929ee3fd1f31408ab8633db6e1523"}, - {file = "matplotlib-3.3.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c5d0c2ae3e3ed4e9f46b7c03b40d443601012ffe8eb8dfbb2bd6b2d00509f797"}, - {file = "matplotlib-3.3.2-cp38-cp38-win32.whl", hash = "sha256:a522de31e07ed7d6f954cda3fbd5ca4b8edbfc592a821a7b00291be6f843292e"}, - {file = "matplotlib-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8bc1d3284dee001f41ec98f59675f4d723683e1cc082830b440b5f081d8e0ade"}, - {file = "matplotlib-3.3.2-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:799c421bc245a0749c1515b6dea6dc02db0a8c1f42446a0f03b3b82a60a900dc"}, - {file = "matplotlib-3.3.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2f5eefc17dc2a71318d5a3496313be5c351c0731e8c4c6182c9ac3782cfc4076"}, - {file = "matplotlib-3.3.2.tar.gz", hash = "sha256:3d2edbf59367f03cd9daf42939ca06383a7d7803e3993eb5ff1bee8e8a3fbb6b"}, + {file = "matplotlib-3.3.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b2a5e1f637a92bb6f3526cc54cc8af0401112e81ce5cba6368a1b7908f9e18bc"}, + {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c586ac1d64432f92857c3cf4478cfb0ece1ae18b740593f8a39f2f0b27c7fda5"}, + {file = "matplotlib-3.3.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b03722c89a43a61d4d148acfc89ec5bb54cd0fd1539df25b10eb9c5fa6c393a"}, + {file = "matplotlib-3.3.3-cp36-cp36m-win32.whl", hash = "sha256:2c2c5041608cb75c39cbd0ed05256f8a563e144234a524c59d091abbfa7a868f"}, + {file = "matplotlib-3.3.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c092fc4673260b1446b8578015321081d5db73b94533fe4bf9b69f44e948d174"}, + {file = "matplotlib-3.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27c9393fada62bd0ad7c730562a0fecbd3d5aaa8d9ed80ba7d3ebb8abc4f0453"}, + {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b8ba2a1dbb4660cb469fe8e1febb5119506059e675180c51396e1723ff9b79d9"}, + {file = "matplotlib-3.3.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0caa687fce6174fef9b27d45f8cc57cbc572e04e98c81db8e628b12b563d59a2"}, + {file = "matplotlib-3.3.3-cp37-cp37m-win32.whl", hash = "sha256:b7b09c61a91b742cb5460b72efd1fe26ef83c1c704f666e0af0df156b046aada"}, + {file = "matplotlib-3.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6ffd2d80d76df2e5f9f0c0140b5af97e3b87dd29852dcdb103ec177d853ec06b"}, + {file = "matplotlib-3.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5111d6d47a0f5b8f3e10af7a79d5e7eb7e73a22825391834734274c4f312a8a0"}, + {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a4fe54eab2c7129add75154823e6543b10261f9b65b2abe692d68743a4999f8c"}, + {file = "matplotlib-3.3.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:83e6c895d93fdf93eeff1a21ee96778ba65ef258e5d284160f7c628fee40c38f"}, + {file = "matplotlib-3.3.3-cp38-cp38-win32.whl", hash = "sha256:b26c472847911f5a7eb49e1c888c31c77c4ddf8023c1545e0e8e0367ba74fb15"}, + {file = "matplotlib-3.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:09225edca87a79815822eb7d3be63a83ebd4d9d98d5aa3a15a94f4eee2435954"}, + {file = "matplotlib-3.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eb6b6700ea454bb88333d98601e74928e06f9669c1ea231b4c4c666c1d7701b4"}, + {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2d31aff0c8184b05006ad756b9a4dc2a0805e94d28f3abc3187e881b6673b302"}, + {file = "matplotlib-3.3.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d082f77b4ed876ae94a9373f0db96bf8768a7cca6c58fc3038f94e30ffde1880"}, + {file = "matplotlib-3.3.3-cp39-cp39-win32.whl", hash = "sha256:e71cdd402047e657c1662073e9361106c6981e9621ab8c249388dfc3ec1de07b"}, + {file = "matplotlib-3.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:756ee498b9ba35460e4cbbd73f09018e906daa8537fff61da5b5bf8d5e9de5c7"}, + {file = "matplotlib-3.3.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ad44f2c74c50567c694ee91c6fa16d67e7c8af6f22c656b80469ad927688457"}, + {file = "matplotlib-3.3.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:3a4c3e9be63adf8e9b305aa58fb3ec40ecc61fd0f8fd3328ce55bc30e7a2aeb0"}, + {file = "matplotlib-3.3.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:746897fbd72bd462b888c74ed35d812ca76006b04f717cd44698cdfc99aca70d"}, + {file = "matplotlib-3.3.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:5ed3d3342698c2b1f3651f8ea6c099b0f196d16ee00e33dc3a6fee8cb01d530a"}, + {file = "matplotlib-3.3.3.tar.gz", hash = "sha256:b1b60c6476c4cfe9e5cf8ab0d3127476fd3d5f05de0f343a452badaad0e4bdec"}, ] more-itertools = [ {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, @@ -630,8 +683,8 @@ numpy = [ {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"}, ] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, + {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, + {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, ] peewee = [ {file = "peewee-3.14.0.tar.gz", hash = "sha256:59c5ef43877029b9133d87001dcc425525de231d1f983cece8828197fb4b84fa"}, @@ -671,20 +724,24 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +pymysql = [ + {file = "PyMySQL-0.10.1-py2.py3-none-any.whl", hash = "sha256:44f47128dda8676e021c8d2dbb49a82be9e4ab158b9f03e897152a3a287c69ea"}, + {file = "PyMySQL-0.10.1.tar.gz", hash = "sha256:263040d2779a3b84930f7ac9da5132be0fefcd6f453a885756656103f8ee1fdd"}, +] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyserial = [ - {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"}, - {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"}, + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, ] pytest = [ {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, @@ -695,8 +752,8 @@ python-dateutil = [ {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] python-telegram-bot = [ - {file = "python-telegram-bot-13.0.tar.gz", hash = "sha256:ca78a41626d728a8f51affa792270e210fa503ed298d395bed2bd1281842dca3"}, - {file = "python_telegram_bot-13.0-py2.py3-none-any.whl", hash = "sha256:c9fdd77f303fe168bdf669fb2e2129567fde882cc9382a6171c1571d0ac99df4"}, + {file = "python-telegram-bot-13.1.tar.gz", hash = "sha256:5feebb08ed08d7b71ceb4c05372b9f6a21d83994b5018db111509765881c8282"}, + {file = "python_telegram_bot-13.1-py3-none-any.whl", hash = "sha256:9a887a5057d1eb4fede9d04f736d53365cdb1cda1035c0f4fecb645cfd59c456"}, ] pytz = [ {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, @@ -750,14 +807,17 @@ pyzmq = [ {file = "pyzmq-19.0.2.tar.gz", hash = "sha256:296540a065c8c21b26d63e3cea2d1d57902373b16e4256afe46422691903a438"}, ] requests = [ - {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, - {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] -temper = [] +sudoistemper = [ + {file = "sudoistemper-0.1.0-py3-none-any.whl", hash = "sha256:eb4bc28c0f7bf35ce12e7c1eb5255b571a293f2c360a2ba289292e0684aba0fd"}, + {file = "sudoistemper-0.1.0.tar.gz", hash = "sha256:083273089fcddbade3001812e1dee881231e2756081e424ef267818e937b1880"}, +] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, @@ -801,13 +861,18 @@ tornado = [ {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, ] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] tzlocal = [ {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, ] urllib3 = [ - {file = "urllib3-1.26.1-py2.py3-none-any.whl", hash = "sha256:61ad24434555a42c0439770462df38b47d05d9e8e353d93ec3742900975e3e65"}, - {file = "urllib3-1.26.1.tar.gz", hash = "sha256:097116a6f16f13482d2a2e56792088b9b2920f4eb6b4f84a2c90555fb673db74"}, + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -817,3 +882,7 @@ win32-setctime = [ {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, ] +zipp = [ + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, +] diff --git a/pyproject.toml b/pyproject.toml index 5e6e7ba..abb6071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,49 +1,34 @@ [tool.poetry] name = "sudoisbot" -version = "0.3.0" +version = "0.3.10.post4" description = "a home automation and monitoring system written to learn zmq" authors = ["Benedikt Kristinsson "] repository = "https://github.com/benediktkr/sudoisbot" [tool.poetry.dependencies] -python = "^3.8" -numpy = "^1.18.4" -matplotlib = "^3.2.1" +python = "^3.7" loguru = "^0.5.0" -temper = {git = "https://github.com/benediktkr/temper.git"} -requests = "^2.23.0" -peewee = "^3.13.3" pyzmq = "^19.0.2" pyyaml = "^5.3.1" -python-telegram-bot = "^13.0" +sudoistemper = "^0.1.0" +peewee = {version = "^3.14.0", optional = true} +python-telegram-bot = {version = "^13.1", optional = true} +matplotlib = {version = "^3.3.3", optional = true} +numpy = {version = "^1.19.4", optional = true} +requests = {version = "^2.25.0", optional = true} +PyMySQL = {version = "^0.10.1", optional = true} +python-dateutil = {version = "^2.8.1", optional = true} + +[tool.poetry.extras] +graphs = ["numpy", "matplotlib"] +sink = ["peewee", "PyMySql", "requests", "python-telegram-bot"] +utils = ["python-dateutil"] [tool.poetry.dev-dependencies] pytest = "^5.2" [tool.poetry.scripts] -tglistener = "sudoisbot.tglistener:main" -sendtelegram = "sudoisbot.sendtelegram:main" - -proxy = "sudoisbot.network.proxy:main" -sink = "sudoisbot.sink.sink:main" - -weather = "sudoisbot.apis.weather_pub:main" - -screen_pub = "sudoisbot.screen.screen_pub:main" -unifi_pub = "sudoisbot.unifi:pub" -temper_pub = "sudoisbot.temps.temp_pub:main" -rain_pub = "sudoisbot.temps.rain_pub:main" - - -# these will pretty much only be run while developing so just use -# poetry run python unifi/clients.py -# or something like that -#recenttemps = "sudoisbot.recenttemps:main" -#unifi_clients = "sudoisbot.unifi_clients:show_clients" -#graphtemps = "sudoisbot.sink.graphtemps:main" -# not ready/might not be used -#broker = "sudoisbot.network.broker:main" - +sudoisbot = "sudoisbot:main" [build-system] requires = ["poetry>=0.12"] diff --git a/sudoisbot.yml b/sudoisbot.yml deleted file mode 100644 index 6f82b62..0000000 --- a/sudoisbot.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- - -telegram: - api_key: "your-token" - -bot: - me: - username: your-username - id: your-id - authorized_users: - - your-id - diff --git a/sudoisbot/__init__.py b/sudoisbot/__init__.py index b794fd4..01feebd 100644 --- a/sudoisbot/__init__.py +++ b/sudoisbot/__init__.py @@ -1 +1,107 @@ -__version__ = '0.1.0' +#!/usr/bin/python3 -u + +__version__ = '0.3.0' + +import argparse +import os +import sys + +from sudoisbot.config import read_config + + +""" +tglistener = "sudoisbot.tglistener:main" +sendtelegram = "sudoisbot.sendtelegram:main" + +# these will pretty much only be run while developing so just use +# poetry run python unifi/clients.py +# or something like that +#recenttemps = "sudoisbot.recenttemps:main" +#unifi_clients = "sudoisbot.unifi_clients:show_clients" +#graphtemps = "sudoisbot.sink.graphtemps:main" + +""" + + +def run_temp_pub(args, config): + import sudoisbot.sensors.temp_pub + return sudoisbot.sensors.temp_pub.main(config) + +def run_sink(args, config): + import sudoisbot.sink.sink + return sudoisbot.sink.sink.main(args, config) + +def run_proxy(args, config): + from sudoisbot.network import proxy + return proxy.main_buffering(args, config) + +def run_weather_pub(args, config): + from sudoisbot.apis import weather_pub + return weather_pub.main(config) + +def run_screen_pub(args, config): + from sudoisbot.screen import screen_pub + return screen_pub.main(args, config) + +def run_unifi_pub(args, config): + + from sudoisbot.apis import unifi + if args.show_clients: + return unifi.show_clients(config) + else: + return unifi.main(config) + +def run_rain_pub(args, config): + from sudoisbot.sensors import rain_pub + return rain_pub.main(config) + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + # will default to env var for config path, and allow + # overriding with --config + env_confpath = os.environ.get("SUDOISBOT_CONF", None) + parser.add_argument("--config", default=env_confpath, + help="overrides default with $SUDOISBOT_CONF if set") + subparser = parser.add_subparsers(dest="cmd") + subparser.required = True + + parser_sink = subparser.add_parser('sink', help="start sink") + parser_sink.add_argument("--write-path") + parser_sink.set_defaults(func=run_sink) + + parser_proxy = subparser.add_parser('proxy', help="start proxy") + parser_proxy.add_argument('--forwarder', action='store_true') + parser_proxy.add_argument('--capture', action='store_true') + parser_proxy.set_defaults(func=run_proxy) + + parser_temp_pub = subparser.add_parser('temp_pub', help="start temp_publisher") + parser_temp_pub.set_defaults(func=run_temp_pub) + + parser_rain_pub = subparser.add_parser('rain_pub', help="start rain_pub") + parser_rain_pub.set_defaults(func=run_rain_pub) + + parser_screen_pub = subparser.add_parser('screen_pub', help="start screen_pub") + parser_screen_pub.add_argument("--no-loop", action="store_true") + parser_screen_pub.add_argument("--dry-run", action="store_true") + parser_screen_pub.add_argument("--rotation", type=int) + parser_screen_pub.add_argument("--statedir") + parser_screen_pub.set_defaults(func=run_screen_pub) + + parser_weather_pub = subparser.add_parser('weather_pub', help="start weather_pub") + parser_weather_pub.set_defaults(func=run_weather_pub) + + parser_unifi_pub = subparser.add_parser('unifi_pub', help="start unifi_pub") + parser_unifi_pub.add_argument("--show-clients", action="store_true") + parser_unifi_pub.set_defaults(func=run_unifi_pub) + + + args = parser.parse_args() + config = read_config(args.config) + + #if args.cmd not in config['allowed_cmds']: + # parser.error(f"config {config['file_path']} is not configured for '{cmd}'") + + rc = args.func(args, config) + sys.exit(rc) diff --git a/sudoisbot/unifi.py b/sudoisbot/apis/unifi.py similarity index 75% rename from sudoisbot/unifi.py rename to sudoisbot/apis/unifi.py index 78a8163..84eb7a3 100755 --- a/sudoisbot/unifi.py +++ b/sudoisbot/apis/unifi.py @@ -6,24 +6,27 @@ import json from urllib.parse import urljoin from itertools import groupby +from datetime import datetime, timezone import urllib3 urllib3.disable_warnings() import requests +from requests.exceptions import RequestException from loguru import logger from sudoisbot.common import init from sudoisbot.network.pub import Publisher class UnifiPublisher(Publisher): - def __init__(self, addr, name, freq, unifi_config, people): - super().__init__(addr, b"unifi", name, freq) + def __init__(self, addr, freq, unifi_config, people, location): + super().__init__(addr, b"unifi", "unifi", freq) self.unifi_config = unifi_config self.people = people + self.location = location def publish(self): - home = set() + try: # constructor logs in api = UnifiApi(self.unifi_config) @@ -32,12 +35,24 @@ class UnifiPublisher(Publisher): logger.error(e) raise # ??? - for person, devices in self.people.items(): - for device in devices: - if device in wifi_clients: - home.add(person) + home = dict() + for initials, devices in self.people.items(): + home[initials] = any(d in wifi_clients for d in devices) - self.send({'home': list(home)}) + + data = { + 'measurement': 'people', + 'time': datetime.now(timezone.utc).isoformat(), + 'tags': { + 'name': 'unifi', + 'frequency': self.frequency, + 'location': self.location + }, + 'fields': home + } + + self.pub(data) + #print(data) class UnifiApi(object): @@ -106,20 +121,22 @@ class UnifiApi(object): logger.warning(f"weird client on unifi: {client}") return names -def pub(): +def main(config): - config = init("unifi_pub", fullconfig=True) - - addr = config['screen_pub']['addr'] + addr = config['addr'] name = 'unifi' - sleep = 240 + sleep = 60 unifi_config = config['unifi'] - people = config['screen_pub']['people_home'] + people = config['people'] + location = config['location'] - with UnifiPublisher(addr, name, sleep, unifi_config, people) as pub: + with UnifiPublisher(addr, sleep, unifi_config, people, location) as pub: pub.loop() +def show_clients(config): + unifi_config = config['unifi'] -if __name__ == "__main__": - show_clients() + api = UnifiApi(unifi_config) + for client in api.get_clients_short(): + logger.info(client) diff --git a/sudoisbot/apis/weather_pub.py b/sudoisbot/apis/weather_pub.py index 7b28022..ee7d9aa 100644 --- a/sudoisbot/apis/weather_pub.py +++ b/sudoisbot/apis/weather_pub.py @@ -32,7 +32,7 @@ # # maybe interesting project: https://github.com/aceisace/Inky-Calendar -from datetime import datetime +from datetime import datetime, timezone from decimal import Decimal import time import json @@ -78,8 +78,8 @@ OWM_URL = "https://api.openweathermap.org/data/2.5/weather?lat={lat:.4f}&lon={lo class NowcastPublisher(Publisher): - def __init__(self, addr, locations, token): - super().__init__(addr, b"weather", None, 300) + def __init__(self, addr, locations, token, frequency): + super().__init__(addr, b"weather", None, frequency) self.locations = [{ 'name': a['name'], @@ -88,6 +88,7 @@ class NowcastPublisher(Publisher): 'msl': a['msl'] } for a in locations] + self.token = token self.base_url = OWM_URL @@ -108,13 +109,13 @@ class NowcastPublisher(Publisher): desc = ', '.join([a['description'] for a in w['weather']]), main = ', '.join([a['main'] for a in w['weather']]), - temp = w['main']['temp'], - feel_like = w['main']['feels_like'], - pressure = w['main']['pressure'], - humidity = w['main']['humidity'], + temp = float(w['main']['temp']), + feel_like = float(w['main']['feels_like']), + pressure = float(w['main']['pressure']), + humidity = float(w['main']['humidity']), - wind_speed = w['wind']['speed'], - wind_deg = w['wind']['deg'], + wind_speed = float(w['wind'].get('speed', 0.0)), + wind_deg = float(w['wind'].get('deg', 0.0)), visibility = w['visibility'], cloudiness = w['clouds']['all'], @@ -148,28 +149,47 @@ class NowcastPublisher(Publisher): logger.error(e) def send(self, name, weather): + now = datetime.now(timezone.utc).isoformat() + tags = { + 'name': name, + 'frequency': self.frequency, + 'type': 'weather', + 'kind': 'weather', + 'source': 'api', + 'environment': 'outside', + 'location': name, + } data = { 'measurement': self.topic.decode(), - 'tags': { - 'name': name, - 'frequency': self.frequency, - 'type': 'weather', - }, - 'time': datetime.now().isoformat(), + 'tags': tags, + 'time': now, 'fields': weather } - bytedata = json.dumps(data).encode() - logger.debug(bytedata) - self.socket.send_multipart([self.topic, bytedata]) + # for legacy and consistency reasons + for measurement in ['temp', 'humidity']: + data2 = { + 'measurement': measurement, + 'tags': tags, + 'time': now, + 'fields': {'value': weather[measurement] } + } + jdata = json.dumps(data2) + self.socket.send_multipart([b'temp', jdata.encode()]) -def main(): + #bytedata = json.dumps(data).encode() + #logger.debug(bytedata) + #self.socket.send_multipart([self.topic, bytedata]) + msg = self.pub(data) + logger.trace(msg) - config = read_config() + +def main(config): addr = config['addr'] locations = config['locations'] token = config['owm_token'] + freq = config['frequency'] - with NowcastPublisher(addr, locations, token) as publisher: + with NowcastPublisher(addr, locations, token, freq) as publisher: publisher.loop() diff --git a/sudoisbot/config.py b/sudoisbot/config.py index 8f2995f..8ed9487 100644 --- a/sudoisbot/config.py +++ b/sudoisbot/config.py @@ -6,7 +6,7 @@ import os from loguru import logger import yaml -def read_config(name=None): +def read_config(fullpath=None): if 'SUDOISBOT_LOGFILE' in os.environ: logfile = os.environ["SUDOISBOT_LOGFILE"] loglevel = os.environ.get("SUDOISBOT_LOGLEVEL", "DEBUG") @@ -16,39 +16,32 @@ def read_config(name=None): logger.debug("configured logger for env vars") - # looks for config file, with the following order (default name): - # - # 1. file specified by environment var SUDOISBOT_CONF (name is ignored) - # 2. .${name}.yml in the users homedir - # 3. /etc/sudoisbot/${name}.yml - # 5. /usr/local/etc/sudoisbot/${name}.yml - # 6. .${name}.yml in current dir - homedir = os.path.expanduser("~") - if name is None: - ymlname = "sudoisbot.yml" - elif not name.endswith(".yml"): - ymlname = name + ".yml" + if 'SUDOISBOT_CONF' in os.environ: + locations = [os.environ['SUDOISBOT_CONF']] + elif fullpath is not None: + fname = fullpath + locations = [fullpath] else: - ymlname = name + fname = "sudoisbot.yml" + locations = [ + os.path.join('/etc/', fname), + os.path.join('/usr/local/etc', fname), + os.path.join(os.curdir, fname), + os.path.join(os.path.expanduser("~"), "." + fname) - locations = [ - os.environ.get("SUDOISBOT_CONF", ""), - os.path.join(homedir, "." + ymlname), - os.path.join('/etc/sudoisbot', ymlname), - os.path.join('/usr/local/etc/sudoisbot', ymlname), - os.path.join(os.curdir, f".{ymlname}") - ] + ] for conffile in locations: try: with open(conffile, 'r') as cf: config = yaml.safe_load(cf) - logger.debug(f"using config file: {conffile} (new format)") + + config['file_path'] = conffile + logger.info(f"config file: {conffile}") return config except IOError as e: if e.errno == 2: continue else: raise else: - logger.error(f"No config file found") - logger.debug(f"serached: {', '.join(locations)}") + logger.error(f"config file not found: '{fname}', searched: {locations}") raise SystemExit("No config file found") diff --git a/sudoisbot/network/proxy.py b/sudoisbot/network/proxy.py index fa19331..8435647 100644 --- a/sudoisbot/network/proxy.py +++ b/sudoisbot/network/proxy.py @@ -1,6 +1,10 @@ #!/usr/bin/python3 -u +from collections import deque, defaultdict import os +import json +import time +import base64 from loguru import logger import zmq @@ -27,11 +31,50 @@ def dealer(dealer_addr, router_addr): router.close() context.close() -def proxy(frontend_addr, backend_addr): + + +def proxy_buffering(frontend_addr, backend_addr, capture_addr=None): context = zmq.Context() + disk_interval = 3 + disk_at = int(time.time()) + disk_interval + + def save_cache_to_disk(target_dir="/tmp/proxy_cache/"): + for topic in cache.keys(): + + filename = topic.decode() + ".cache" + + with open(os.path.join(target_dir, filename), 'wb') as f: + + for multipart_msg in list(cache[topic]): + parts64 = [base64.b64encode(a) for a in multipart_msg] + + #print(parts64) + f.write(b"|".join(parts64)) + f.write(b"\n") + + def load_cache_from_disk(target_dir="/tmp/proxy_cache"): + files = os.listdir(target_dir) + for filename in files: + fullpath = os.path.join(target_dir, filename) + with open(fullpath, 'rb') as f: + for line in f.readlines(): + parts64 = line.split(b"|") + yield [base64.b64decode(a) for a in parts64] + #os.remove(fullpath) + + def delete_cache_on_disk(topic, target_dir="/tmp/proxy_cache"): + filename = topic.decode() + ".cache" + fullpath = os.path.join(target_dir, filename) + try: + os.remove(fullpath) + except FileNotFoundError: + logger.warning(f"could not delete disk cache because {fullpath} does not exist") + + # facing publishers - frontend = context.socket(zmq.XSUB) + frontend = context.socket(zmq.SUB) + frontend.setsockopt(zmq.SUBSCRIBE, b'') frontend.bind(frontend_addr) # facing services (sinks/subsribers) @@ -41,14 +84,141 @@ def proxy(frontend_addr, backend_addr): #backend.setsockopt(ZMQ_XPUB_VERBOSE, 1) logger.info(f"zmq pubsub proxy: {frontend_addr} -> {backend_addr}") - zmq.proxy(frontend, backend) + if capture_addr: + capture = context.socket(zmq.PUB) + capture.bind(capture_addr) + logger.info(f"zmq capture: {capture_addr}") - # we never get here + + else: + capture = None + + + poller = zmq.Poller() + poller.register(frontend, zmq.POLLIN) + poller.register(backend, zmq.POLLIN) + if capture: + poller.register(backend, zmq.POLLIN) + + + # send \x01 to all publishers when they connect + + lvc = dict() + cache = defaultdict(deque) + cache_topics = set() + + for item in load_cache_from_disk(): + cache[item[0]].append(item) + + for topic in cache.keys(): + csize = len(cache[topic]) + if csize > 0: + logger.warning(f"{topic} - {csize} cached items loaded") + + while True: + try: + events = dict(poller.poll(1000)) + except KeyboardInterrupt: + logger.info("im leaving") + save_cache_to_disk() + logger.info("saved cache") + break + + + now = int(time.time()) + if now > disk_at: + save_cache_to_disk() + disk_at = now + disk_interval + + if capture: + stats = { + 'cache_size': { + k.decode(): len(v) for (k, v) in cache.items() + }, + 'topics': [a.decode() for a in lvc.keys()], + 'cache_topics': [a.decode() for a in cache_topics], + 'disk_at': disk_at + } + capture.send_multipart([b"meta:stats", json.dumps(stats).encode()]) + + if frontend in events: + msg = frontend.recv_multipart() + topic = msg[0] + + #frontend.send_multipart([b"\x00rain"]) + + if topic not in lvc: + logger.info(f"caching topic {topic} that hasnt seen a listener yet") + cache_topics.add(topic) + lvc[topic] = msg + + if topic in cache_topics: + #logger.debug(f"[o] cached {msg}") + cache[topic].append(msg) + else: + backend.send_multipart(msg) + + if capture: + capture.send_multipart(msg) + + + if backend in events: + + msg = backend.recv_multipart() + #logger.warning(f"[x] backend: {msg}") + if msg[0][0] == 0: + topic = msg[0][1:] + cache_topics.add(topic) + logger.info(f"[o] now caching {topic}") + + if msg[0][0] == 1: #'\x01' + topic = msg[0][1:] + if topic not in lvc: + # the keys of the topic dir are also a list of "known topics" + logger.success(f"registered {topic}") + lvc[topic] = None + + if topic in cache_topics: + csize = len(cache[topic]) + if csize > 0: + logger.info(f"draning {csize} messages for {topic}") + + while len(cache[topic]) > 0: + buffered = cache[topic].popleft() + backend.send_multipart(buffered) + + save_cache_to_disk() + + + logger.success(f"stopped caching {topic}") + cache_topics.discard(topic) + + + elif topic in lvc and lvc[topic] is not None: + cached = lvc[topic] + backend.send_multipart(cached + [b"cached"]) + logger.success(f"[>] lvc sent for {topic}") + + + #frontend.send(msg) + #logger.success(f"[>] backend: {msg}") + + + if capture in events: + logger.warning(f"capture: {capture.recv_mutlipart(msg)}") + + + #zmq.proxy(frontend, backend, capture) + #while True: + + + + # we never used to get here frontend.close() backend.close() context.close() -def forwarder(frontend_addr, backend_addr, capture_addr=None): +def proxy_forwarder(frontend_addr, backend_addr, capture_addr): context = zmq.Context() # facing publishers @@ -65,14 +235,17 @@ def forwarder(frontend_addr, backend_addr, capture_addr=None): #backend.setsockopt(ZMQ_XPUB_VERBOSE, 1) logger.info(f"zmq pubsub proxy: {frontend_addr} -> {backend_addr}") + + if capture_addr: capture = context.socket(zmq.PUB) capture.bind(capture_addr) - logger.info(f"capture: {capture_addr}") - else: - capture = None + logger.info(f"zmq capture: {capture_addr}") - zmq.proxy(frontend, backend, capture) + zmq.proxy(frontend, backend, capture) + + else: + zmq.proxy(frontend, backend) # we never get here frontend.close() @@ -81,23 +254,33 @@ def forwarder(frontend_addr, backend_addr, capture_addr=None): capture.close() context.close() -def capture(): +def capture(capture_addr): + capture_port = capture_addr.split(":")[-1] context = zmq.Context() socket = context.socket(zmq.SUB) socket.setsockopt(zmq.SUBSCRIBE, b'') - addr = "tcp://127.0.0.1:5561" + addr = f"tcp://127.0.0.1:{capture_port}" socket.connect(addr) - print("connecting to " + addr) + logger.info("connecting to " + addr) + import pprint + import sys while True: + r = socket.recv_multipart() - print(r) + #pprint.pprint(r[1].decode()) + #print(r) + jdata = json.loads(r[1].decode()) - print("====") + if "cache_size" in jdata: + print(r[1].decode(), end="\n") + sys.stdout.flush() + #print("") -def main_forwarder(): +def main_forwarder(config): + # config = init("pubsub_forwarder") # zmq_in_connect = config['zmq_in_connect'] # zmq_frontend = config['zmq_frontend'] @@ -108,9 +291,14 @@ def main_forwarder(): zmq_capture = "tcp://127.0.0.1:5561" - return forwarder(zmq_in_connect, zmq_backend, zmq_capture) + return forwarder( + config['frontend_addr'], config['backend_addr'], config['capture_addr']) -def main(): - config = read_config() - return proxy(**config) +def main_buffering(args, config): + capture_addr = config.get('capture_addr') + if args.capture: + return capture(capture_addr) + + return proxy_buffering( + config['frontend_addr'], config['backend_addr'], capture_addr) diff --git a/sudoisbot/network/pub.py b/sudoisbot/network/pub.py index 9193ab9..3d37b0a 100644 --- a/sudoisbot/network/pub.py +++ b/sudoisbot/network/pub.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from datetime import datetime +from datetime import datetime, timezone import json import time @@ -11,13 +11,9 @@ class Publisher(object): # have this class be a context manager with the loop? def __init__(self, addr, topic, name, frequency): self.addr = addr + self.name = None # this should be phased out self.topic = topic - self.name = name self.frequency = frequency - self.loop_sleep = self.frequency - - # TODO: decide if this is a good term or not - self.type = self.topic.decode() # And even though I'm the publisher, I can do the connecting rather @@ -26,7 +22,8 @@ class Publisher(object): self.context = zmq.Context() self.socket = self.context.socket(zmq.PUB) - logger.debug(f"topic: {self.topic}") + self.socket.set_hwm(256000) # 0 is supposdenly no limit + logger.info(f"emitting on {self.topic} every {self.frequency}s") def __enter__(self): self.socket.connect(self.addr) @@ -48,9 +45,7 @@ class Publisher(object): def start(self): raise NotImplementedError("base class cant do anything") - def loop(self, sleeptime=None): - # old method of doing while True - # TODO: pass the freq here instead + def loop(self): while True: try: self.publish() @@ -61,14 +56,13 @@ class Publisher(object): except StopIteration: break - def message(self, msg={}): + def message(self, data={}): base = { 'name': self.name, - 'timestamp': datetime.now().isoformat(), + 'timestamp': datetime.now(timezone.utc).isoformat(), 'frequency': self.frequency, - 'type': self.type, } - return {**msg, **base} + return {**data, **base} def pub(self, data): jdata = json.dumps(data).encode() @@ -81,5 +75,6 @@ class Publisher(object): def send(self, values): # retire this method - data = self.message(values) - self.pub(data) + raise NotImplementedError("use '.message()' for envelope and then '.pub()'") + #data = self.message(values) + #self.pub(data) diff --git a/sudoisbot/network/sub.py b/sudoisbot/network/sub.py index 6476638..e9cab63 100644 --- a/sudoisbot/network/sub.py +++ b/sudoisbot/network/sub.py @@ -36,11 +36,12 @@ class Subscriber(object): self.rcvtimeo_secs = int(rcvtimeo) self.context = zmq.Context() - self.socket = self.context.socket(zmq.SUB) + self.socket = self.context.socket(zmq.XSUB) self.socket.setsockopt(zmq.RCVTIMEO, self.rcvtimeo_secs * 1000) #logger.info(f"RCVTIMEO is {self.rcvtimeo_secs}s") for topic in self.topics: - self.socket.setsockopt(zmq.SUBSCRIBE, topic) + #self.socket.setsockopt(zmq.SUBSCRIBE, topic) + self.socket.send_multipart([b"\x01" + topic]) def connect(self, addr=None): @@ -60,7 +61,9 @@ class Subscriber(object): try: while True: msg = self.socket.recv_multipart() - yield (msg[0], json.loads(msg[1])) + cached = len(msg) > 2 and msg[2] == b"cached" + + yield (msg[0], json.loads(msg[1]), cached) except zmq.error.Again: logger.warning(f"no messages in {self.rcvtimeo_secs}s") diff --git a/sudoisbot/screen/screen_pub.py b/sudoisbot/screen/screen_pub.py index 0ea6e9e..c06839b 100644 --- a/sudoisbot/screen/screen_pub.py +++ b/sudoisbot/screen/screen_pub.py @@ -3,124 +3,199 @@ # ansible for now import argparse -from datetime import datetime +from datetime import datetime, timezone from os import path import sys +import random +import time +from dataclasses import dataclass, field, asdict from loguru import logger from sudoisbot.network.pub import Publisher from sudoisbot.sink import simplestate -from sudoisbot.common import init, catch, chunk +from sudoisbot.common import chunk def bark(): - import random numberofwoofs = random.randint(1,3) woofs = " " + ", ".join(["woof"] * numberofwoofs) return woofs - +@dataclass class ScreenPublisher(Publisher): - def __init__(self, - addr, freq=60, rot=0, statedir=None, - noloop=False, test=False): - super().__init__(addr, b"eink", "screen_pub", freq) + addr: str + weather_location: str - self.freq = freq - self.rotation = rot - self.noloop = noloop - self.test = test - if statedir is None: self.statedir = "/dev/shm" - else: self.statedir = statedir + freq: int = 60 + rotation: int = 0 + statedir: str = "/dev/shm" + msgs: list = field(default_factory=list) + + no_loop: bool = False + dry_run: bool = False + + def __post_init__(self): + super().__init__(self.addr, b"eink", "screen_pub", self.freq) + if self.rotation is None: + self.rotation = 0 + if self.dry_run: + self.no_loop = True self.first_loop = True - def get_recent_state(self, kind='temps'): + self.halfway = 17 + self.msgs = [self.align_center(msg) for msg in self.msgs] + + logger.info(f"weather location: {self.weather_location}") + + def align_center(self, msg): + if len(msg) >= self.halfway*2: + logger.warning("msg '{msg}' is too long, {len(msg)} chars.") + + msg_padding = max(self.halfway - (len(msg) // 2), 0) + return " "*msg_padding + msg + + def align_right(self, msg): + pad_length = self.halfway * 2 - len(msg) + padding = " "*pad_length + return padding + msg + + def get_recent_state(self, measurement='temp'): state_file = path.join( - self.statedir, simplestate.states[kind]) + self.statedir, f"{measurement}-state.json") return simplestate.get_recent(state_file) - def weather_short(self, location): - state = self.get_recent_state('temps') - return state[location]['weather']['desc'] + def make_weather(self): + try: + state = self.get_recent_state('weather') + weather = state[self.weather_location]['fields']['desc'] + except ValueError as e: + logger.warning(e) + weather = "[err: no recent weather info]" + + return self.align_center(weather) + + def make_rain(self): + try: + state = self.get_recent_state('rain') + rains = any(v['fields']['value'] for v in state.values()) + indicator = "R" if rains else "-" + except ValueError as e: + logger.warning(e) + indicator = "?" + + return self.align_right(indicator) + + def make_people(self): + try: + state = self.get_recent_state('people')['unifi'] + + home = [k for k, v in state['fields'].items() if v] + count = len(home) + indicators = " ".join(home) + except ValueError as e: + logger.warning(e) + indicators = "- - -" + count = 1 + + + return self.align_right(indicators), count + + + def make_text(self): + return random.choice(self.msgs + [self.align_center(bark())]) def make_temps(self): l = list() - state = self.get_recent_state('temps') + try: + state = self.get_recent_state('temp') + except ValueError as e: + logger.warning(e) + state = dict() - for a in ['bedroom', 'outdoor', 'livingroom', 'ls54']: - try: - temp = f"{state[a]['temp']:.1f}" - except KeyError: - logger.warning(f"no value for '{a}'") - temp = " " + for a in ['bedroom', 'study', 'livingroom', 'ls54', 'outdoor']: + # .replace does not mutate original string shortname = a.replace('room', 'r') - l.append(f"{shortname}: {temp} C") + #shortname = a[:min(len(a), 4)] + try: + temp = state[a]['fields']['value'] + tempstr = f"{temp:.1f}" + if temp < 10.0: + tempstr = " " + tempstr + l.append(f"{shortname}: {tempstr} C") + except KeyError: + logger.trace(f"no recent temp for '{a}'") + l.append(f"{shortname}: -- C") + fill = max([len(a) for a in l]) chunks = chunk([a.rjust(fill) for a in l], 2) temp_rows = list() for row in chunks: - temp_rows.append(" | ".join(row)) + if len(row) == 1: + temp_rows.append(f"{row[0]} |") + else: + temp_rows.append(" | ".join(row)) return "\n".join(temp_rows) def publish(self): - temps = self.make_temps() - try: - weather = self.weather_short("fhain") - except KeyError: - logger.warning("no weather data for 'fhain'") - weather = "error: no weather data for 'fhain'" - - halfway = 18 - weather_size = len(weather) - padding = max(halfway - (weather_size // 2), 0) - - weth = " "*padding + weather - rona = " wash hands and shoes off " woof = " " + bark() - text = temps + '\n\n' + weth + '\n\n' + rona #+ '\n' + woof + + weth = self.make_weather() + temps = self.make_temps() + folk, inhabitants = self.make_people() + text = self.make_text() + rain = self.make_rain() + text = f"{weth}\n{temps}\n{folk}\n{text}\n{rain}" + # add back logic to turn update intervals down pr stop when # nodody is home + if inhabitants > 0: + update_interval = 15*60 # 15m + else: + update_interval = 66*60*6 data = { 'name': "screen_pub", 'text': text, - 'timestamp': datetime.now().isoformat(), + 'timestamp': datetime.now(timezone.utc).isoformat(), 'rotation': self.rotation, - 'min_update_interval': 15*60, # 15m - 'force_update': self.first_loop or self.noloop + 'min_update_interval': update_interval, + 'force_update': self.first_loop or self.no_loop } - self.pub(data) + if self.first_loop: + time.sleep(0.3) + self.first_loop = False + if data['force_update'] or self.no_loop: + logger.warning(f"screen should update: \n{data['text']}") + if self.dry_run: + import json + jmsg = json.dumps(data, indent=2) + logger.warning(f"not publishing: \n{jmsg}") - if data['force_update']: - logger.warning("forced an update") - - if self.noloop: raise StopIteration - self.first_loop = False -@catch -def main(): + self.pub(data) + if self.no_loop: + raise StopIteration - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--noloop", action="store_true") - parser.add_argument("--rot", type=int) +def main(args, config): - config, args = init(__name__, parser) addr = config['addr'] - rot = config['rotation'] if not args.rot else args.rot + #people_home = config['people_home'] - noloop = args.noloop + kwargs = {**config['screen'], + **{ + 'rotation': args.rotation, + 'dry_run': args.dry_run, + 'no_loop': args.no_loop, + 'statedir': args.statedir, + }} + with ScreenPublisher(addr=addr, **kwargs) as p: + p.loop() - with ScreenPublisher(addr, noloop=noloop, rot=rot) as publisher: - publisher.loop() - - - -if __name__ == "__main__": - sys.exit(main()) + return 0 diff --git a/sudoisbot/screen/screen_sub.py b/sudoisbot/screen/screen_sub.py index bece7de..24d04a5 100644 --- a/sudoisbot/screen/screen_sub.py +++ b/sudoisbot/screen/screen_sub.py @@ -17,7 +17,7 @@ # sudo systemctl start screen_sub import argparse -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import json from time import sleep @@ -36,7 +36,7 @@ def log(text): # assuming systemd and syslog print(text) else: - ts = datetime.now().isoformat()[:19] + ts = datetime.now().isoformat()[5:19] s = "{}\t{}".format(ts, text) print(s) with open("/tmp/screen_sub.log", 'a') as f: @@ -47,9 +47,8 @@ def should_update(last_updated, min_update_interval, debug=False): if debug: log("last_updated is False") return True - # TBB: discard flood messages - now = datetime.now() + now = datetime.now(timezone.utc) age = now - last_updated next_ = min_update_interval - age.seconds @@ -71,11 +70,10 @@ def gettext(message): text = message['text'] have = len(text.strip().split('\n')) fillers = '\n'*(max(MAX_LINES - have, 0)) - timestamp = message['timestamp'].replace("T", " ")[:16] - updated = timestamp + timestamp = datetime.now().isoformat().replace("T", " ")[5:16] + bottom_right = message.get('bottom_right', '') # doesnt handle too long messagse fix later - #return text.strip() + fillers + updated - return text + fillers + updated + return text + fillers + timestamp + bottom_right @@ -141,7 +139,15 @@ def sub(addr, topic, timeout, debug): force_update = j.get('force_update', False) color = j.get('color', 'black') - if should_update(last_updated, mui, debug) or force_update: + # TBB: discard flood messages + + ts = datetime.strptime(j['timestamp'][:-6], "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=timezone.utc) + + now = datetime.now(timezone.utc) + if ts - now > timedelta(seconds=1.0): + log("discarding old message: '{}'".format(j['timestamp'])) + + elif should_update(last_updated, mui, debug) or force_update: if mui == 0 or force_update: log("starting forced update") if not have_inky: @@ -151,7 +157,7 @@ def sub(addr, topic, timeout, debug): inky_write(text, rotation, color) if have_inky: log("e-ink screen updated") - last_updated = datetime.now() + last_updated = datetime.now(timezone.utc) else: pass diff --git a/sudoisbot/temps/dht.c b/sudoisbot/sensors/dht.c similarity index 100% rename from sudoisbot/temps/dht.c rename to sudoisbot/sensors/dht.c diff --git a/sudoisbot/sensors/old.txt b/sudoisbot/sensors/old.txt new file mode 100644 index 0000000..61547ab --- /dev/null +++ b/sudoisbot/sensors/old.txt @@ -0,0 +1,28 @@ + def read2(self): + """ this code would handle multiple Temper's connected, or + a Temper with both internal/external sensors. but i dont have that + so this isnt used""" + + data = self._read() + mapping = { + 'internal temperature': 'temp', + 'internal humidity': 'humidity', + 'external temperature': 'temp', + 'external humidity': 'humidity' + } + + results = [] + for item in data: + # get a dict with the old keys and their values, each of these + # values will be their own dict + + sources = [key for key in mapping.keys() if key in item.keys()] + + base = {k: v for (k, v) in item.items() if k not in mapping.keys()} + + for oldkey in sources: + newkey = mapping[oldkey] + fixed = {newkey: item[oldkey], 'source': oldkey} + results.append({**base, **fixed}) + + return results diff --git a/sudoisbot/sensors/rain_pub.py b/sudoisbot/sensors/rain_pub.py new file mode 100644 index 0000000..fdceb93 --- /dev/null +++ b/sudoisbot/sensors/rain_pub.py @@ -0,0 +1,115 @@ +#!/usr/bin/python3 + +import json +from time import time, sleep +from socket import gethostname +from datetime import datetime, timezone + +from loguru import logger + +from sudoisbot.network.pub import Publisher +from sudoisbot.sensors.sensors import ArduinoRainSensor, NoSensorDetectedError + + +class RainPublisher(Publisher): + def __init__(self, sensor, addr, location, freq): + super().__init__(addr, b"rain", None, freq) + + self.sensor = sensor + self.location = location + self.freq = freq + + self.pub_at = time() + + self.tags = { + 'hostname': gethostname(), + 'name': self.sensor.name, + 'location':self.location, + 'kind': self.sensor.kind, + 'frequency': self.freq, + } + logger.info(f"emitting data as '{self.sensor.name}' every {freq}s") + + + def publish(self, line): + # LOW = water on the rain sensor, resistance has been LOWered + + msg = { + 'measurement': 'rain', + 'time': datetime.now(timezone.utc).isoformat(), + 'tags': self.tags, + 'fields': { + 'value': line['rain'], + 'digital': line['digital'], + 'value_int': int(line['rain']) + } + } + + logmsg = f"{msg['tags']['name']} rain: {bool(line['rain'])}" + logger.log("RAIN", logmsg) + self.pub(msg) + + + def start(self): + try: + return self._start() + except KeyboardInterrupt: + logger.info("ok im leaving") + + + def _start(self): + rain_state = False + for rainline in self.sensor.iter_lines(): + + #logger.debug(rainline) + + now = time() + if now >= self.pub_at or rainline['rain'] != rain_state: + + if rainline['rain'] != rain_state: + if rainline['rain']: + logger.warning("started raining") + else: + logger.info("stopped raining") + + self.publish(rainline) + self.pub_at = now + self.freq + + rain_state = rainline['rain'] + +def main(config): + broker = config['broker'] + freq = config['frequency'] + loc = config['location'] + + log_no = config.get('sensor_log_no', 9) + logger.level("RAIN", no=log_no, color="") + logger.info(f"logging level RAIN on no {log_no}") + + try: + if len(config['sensors']['rain']) > 1: + raise NotImplementedError("does not make sense at the moment") + sensor = config['sensors']['rain'][0] + + with ArduinoRainSensor(**sensor) as rain_sensor: + with RainPublisher(rain_sensor, broker, loc, freq) as pub: + pub.start() + + except (IndexError, KeyError) as e: + raise SystemExit("no rain sensors configured, exiting") from e + except NoSensorDetectedError as e: + logger.error(e) + return 1 + + except KeyboardInterrupt: + logger.info("Exiting..") + return 0 + + + #name = "balcony" + #loc = "s21" + #freq = 60 + + + + return 0 diff --git a/sudoisbot/sensors/sensors.py b/sudoisbot/sensors/sensors.py new file mode 100644 index 0000000..a901e3f --- /dev/null +++ b/sudoisbot/sensors/sensors.py @@ -0,0 +1,308 @@ +#!/usr/bin/python3 + +from subprocess import check_output, STDOUT, CalledProcessError +import json +from json.decoder import JSONDecodeError +import os.path +from dataclasses import dataclass, asdict, InitVar +import time + +import serial +from loguru import logger +from temper.temper import Temper + +W1ROOT = "/sys/bus/w1/devices" +W1LIST = "w1_bus_master1/w1_master_slaves" + +class SensorDisconnectedError(Exception): pass +class NoSensorDetectedError(Exception): pass + +@dataclass +class TempSensor(object): + + name: str + kind: str + environment: bool + + def as_dict(self): + return asdict(self) + + def __str__(self): + return f"<{self.name} [kind: {self.kind}, environment: {self.environment}]>" + + @classmethod + def from_kind(cls, **kwargs): + kind = kwargs['kind'].lower() + objname = kind + "sensor" + sensorobjdict = {a.__name__.lower(): a for a in cls.__subclasses__()} + + try: + sensorobj = sensorobjdict[objname] + return sensorobj(**kwargs) + except KeyError as e: + e.args += ("unknown sensor kind", ) + raise + + @classmethod + def autodetect(cls, name): + """autodetects what kind of sensor is connected, only works + when the syste has one and only one sensor conncted and only supports + the name arg.""" + for sensorobj in cls.__subclasses__(): + try: + sensr = sensorobj(name) + logger.info(f"found '{sensor.kind}' sensor") + return connected + except (SensorDisconnectedError, NoSensorDetectedError): + continue + else: + raise NoSensorDetectedError("audotdetect found no sensors connected") + + + +@dataclass(init=True) +class TemperSensor(TempSensor): + + def _read(self): + # this function is to abstract away some error handling and make + # read() look nicer + try: + data = self._temper.read() + if len(data) == 0: + raise SensorDisconnectedError("temper: no data returned") + if len(data) > 1: + # i just have the model with one sensor. will expand if i get + # a different model at some point. + raise NotImplementedError("only supports Tempers with one sensor") + return data[0] + except FileNotFoundError as e: + msg = f"temper: {e.args[1]}" + logger.error(msg) + raise SensorDisconnectedError(msg) from e + except PermissionError as e: + raise NoSensorDetectedError(e) from e + + def read(self): + reading = self._read() + try: + return { + 'measurements': {'temp': reading['internal temperature'] } + } + except KeyError: + if 'firmware' in reading: + logger.error(f"temper usb: temp value missing from '{reading}'") + # makes the for loop just not loop over anything + return dict() + else: + raise + + def __post_init__(self): + self._temper = Temper() + # so we bail if temper is configured but not connected/functional + # on start + # call .read() because it is doing error handling, some funky errors + # will slip past if youre trying to be smart about the exception stack + try: + firstreading = self._read() + logger.trace(firstreading) + except SensorDisconnectedError as e: + # NoSensorDetected is already raised in ._read() + #raise NoSensorDetectedError("temper: not connected") from e + raise + + +@dataclass +class DhtSensor(TempSensor): + + dht_pin: InitVar[int] + + def __post_init__(self, dht_pin): + if dht_pin: + self.dht_cmd = ["dht", str(dht_pin)] + else: + self.dht_cmd = ["dht"] + + + def read(self): + # the dht.c binary doesnt write to stderr at the moment + # but lets redirect stderr to stdout now in case i change + # that so this wont break + try: + output = check_output(self.dht_cmd, shell=False, stderr=STDOUT) + logger.trace(output) + joutput = json.loads(output) + + return { + 'measurements': { + 'temp': joutput['temp'], + 'humidity': joutput['humidity'] + }} + except CalledProcessError as e: + raise SensorDisconnectedError("dht disconnected") from e + + +@dataclass +class Ds18b20Sensor(TempSensor): + + sensor_id: str = None + # study: 28-0300a279f70f + # outdoor: 28-0300a279bbc9 + + def __post_init__(self): + ds18b20s = self._list_detected_ds18b20() + + if len(ds18b20s) > 1 and self.sensor_id is None: + raise RuntimeError("need 'sensor_id' when > 1 ds18b20's connected") + + elif self.sensor_id is None: + self.sensor_id = ds18b20s[0] + logger.info(f"set ds18b20 sensor_id to '{self.sensor_id}'") + + self.sensorpath = os.path.join(W1ROOT, self.sensor_id, "w1_slave") + + + def _read_sensor(self): + try: + with open(self.sensorpath, 'r') as f: + return f.read().splitlines() + except FileNotFoundError: + raise SensorDisconnectedError(f"ds18b20: '{self.sensorpath}' not found") + + def _parse_sensor_data(self): + # YES = checksum matches + data = self._read_sensor() + if len(data) == 0: + # File "sudoisbot/temps/sensors.py", line 94, in _parse_data + # if not data[0].endswith("YES"): + # â”” [] + raise SensorDisconnectedError(f"ds18b20: no data") + if not data[0].endswith("YES"): + raise SensorDisconnectedError(f"ds18b20: got '{data}'") + tempstr = data[1].rsplit(" ", 1)[1][2:] + + return int(tempstr)/1000.0 + + + def _list_detected_ds18b20(self): + w1_listfile = os.path.join(W1ROOT, W1LIST) + with open(w1_listfile, 'r') as f: + w1_ids = f.read().splitlines() + + if len(w1_ids) == 0: + raise NoSensorDetectedError("no ds18b20 sensors connected") + + if not all(a.startswith("28-") for a in w1_ids): + # something funky is going on, if this error happens + # then investigate + raise NoSensorDetectedError(f"unexpected values in '{w1_listfile}': {w1_ids}") + + return w1_ids + + def read(self): + return { + 'measurements': { 'temp': self._parse_sensor_data() }, + 'meta': {'sensorid': self.sensor_id } + } + + +@dataclass +class ArduinoSensor(object): + + # ard_loop_time = how often arduino should send a value in seconds + # called 'timeout' in arduino code + # needs a better name + # especially since the next line also has a timeout variable + # but thats the serial read timeout + + name: str + kind: str + device: InitVar[str] = "/dev/ttyUSB0" # linux is a sane default + baudrate: InitVar[int] = 9600 + ard_loop_timeout: int = 5 # seconds + + + # device = "/dev/cu.usbserial-A800eGKH" + # device="/dev/ttyUSB0" + def __post_init__(self, device, baudrate): + assert self.kind == "arduino-rain" + + ser_timeout = float(self.ard_loop_timeout) # seconds + logger.debug(f"serial timeout: {ser_timeout}s") + + try: + self.ser = serial.Serial(device, baudrate, timeout=ser_timeout) + except serial.SerialException as e: + raise NoSensorDetectedError(e) + + + def as_dict(self): + return asdict(self) + + def hello(self): + for i in range(5): + try: + data = self.ser.readline() + jdata = json.loads(data) + + # 'true' is hardcoded.. + return jdata['ready'] + + except (KeyError, JSONDecodeError, UnicodeDecodeError) as e: + # need to polish this when im able to reproduce + # maybe figure out why it happens + logger.warning(f"got invalid json: {data}") + except serial.serialutil.SerialException as e: + logger.error(e) + + logger.debug(f"waiting 5s to try again {i}/5") + time.sleep(5.0) + + else: + raise NoSensorDetectedError("no data from arduino") + + + def start(self): + ready = self.hello() + + # \n is important ! + logger.success(f"{self.name} ready: {ready}") + timeout_ms = self.ard_loop_timeout * 1000 + logger.info(f"getting data on {timeout_ms}ms interval") + self.ser.write(f"{timeout_ms}\r\n".encode()) + + + def __enter__(self): + # if i want to use this not as a context manager ill need + # self.started + self.ser.__enter__() + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.ser.__exit__(exc_type, exc_value, traceback) + + def iter_lines(self): + while True: + line = self.ser.readline() + logger.trace(line) + if line == b"": + continue + + try: + yield json.loads(line) + except JSONDecodeError: + logger.warning(f"discarging garbage: '{line}'") + + + +@dataclass +class ArduinoRainSensor(ArduinoSensor): + + def iter_lines(self): + for jline in super().iter_lines(): + rain = jline['digital'] == "LOW" + + yield { + 'digital': jline['digital'], + 'rain': rain + } diff --git a/sudoisbot/sensors/temp_pub.py b/sudoisbot/sensors/temp_pub.py new file mode 100644 index 0000000..9c8669c --- /dev/null +++ b/sudoisbot/sensors/temp_pub.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 -u + +import time +from datetime import datetime, timezone + +from loguru import logger +from socket import gethostname + +from sudoisbot.network.pub import Publisher +from sudoisbot.sensors.sensors import TempSensor +from sudoisbot.sensors.sensors import SensorDisconnectedError, NoSensorDetectedError + + +class TempPublisher(Publisher): + def __init__(self, addr, freq, location, sensors): + super().__init__(addr, b"temp", None, freq) + + logger.info(f"HWM: {self.socket.get_hwm()}") + + # might ditch 'frequency' here.. + self.tags = { + 'location': location, + 'frequency': freq, + 'hostname': gethostname(), + 'source': 'sensor' + } + self.sensors = sensors + + def publish(self): + for sensor in self.sensors: + + reading = sensor.read() + now = datetime.now(timezone.utc).isoformat() + + for measurement, value in reading['measurements'].items(): + msg = { + 'measurement': measurement, + 'time': now, + 'tags': {**self.tags, **sensor.as_dict()}, + 'fields': { + 'value': float(value) + } + } + logmsg = f"{msg['tags']['name']} {measurement}: {value}" + logger.log("TEMP", logmsg) + self.pub(msg) + +def main(config): + broker = config['broker'] + freq = config['frequency'] + loc = config['location'] + + log_no = config.get('sensor_log_no', 9) + logger.level("TEMP", no=log_no, color="") + logger.info(f"logging level TEMP on no {log_no}") + + while True: + try: + conf_sensors = config['sensors']['temp'] + sensors = [TempSensor.from_kind(**a) for a in conf_sensors] + for sensor in sensors: + logger.info(f"emitting data for {sensor}") + + with TempPublisher(broker, freq, loc, sensors) as publisher: + publisher.loop() + return 0 + except NoSensorDetectedError as e: + logger.error(e) + return 1 + except SensorDisconnectedError as e: + # especially usb sensors can be unplugged for a short time + # for various reasons + logger.error(e) + logger.info("waiting 30s for sensor to come back") + time.sleep(30.0) + continue + except KeyboardInterrupt: + logger.info("Exiting..") + return 0 diff --git a/sudoisbot/sink/models.py b/sudoisbot/sink/models.py index cd0f743..f5c679e 100644 --- a/sudoisbot/sink/models.py +++ b/sudoisbot/sink/models.py @@ -1,27 +1,105 @@ #!/usr/bin/python3 -import peewee -from peewee import DateTimeField, TextField, DecimalField, IntegerField -from playhouse.db_url import connect +import json -db = peewee.DatabaseProxy() +import peewee +from peewee import DateTimeField, TextField, DecimalField, CharField +from peewee import MySQLDatabase +from peewee import IntegrityError +from loguru import logger + +db_proxy = peewee.DatabaseProxy() + +def dbconnect(**mysqlconf): + db = MySQLDatabase(**mysqlconf) + db_proxy.initialize(db) + return db + +# db = connect(dburl) +# models.db.initialize(db) +# try: +# with models.db: +# models.Temps.create(timestamp=j['timestamp'], +# name=j['name'], +# temp=j['temp'], +# extra=extra) +# except IntegrityError as e: +# logger.error(e) class BaseModel(peewee.Model): class Meta: - database = db + database = db_proxy -class Temps(BaseModel): - timestamp = DateTimeField(index=True) - name = TextField() +class Temperatures(BaseModel): + time = DateTimeField(index=True) + name = CharField(max_length=32) + location = TextField() + environment = TextField() + source = TextField() temp = DecimalField() - humidity = IntegerField(null=True) - extra = TextField(null=True) + json = TextField(null=False) + + + def as_msg(self): + return json.loads(self.json) + + @classmethod + def insert_msg(cls, msg): + name = msg['tags']['name'] + + try: + return cls.create( + time = msg['time'], + name = name, + location = msg['tags']['location'], + environment = msg['tags']['environment'], + source = msg['tags']['source'], + temp = msg['fields']['value'], + json = json.dumps(msg) + ) + except IntegrityError as e: + logger.error(f"error on message from {name}") + logger.error(e) + return None + class Meta: indexes = ( - (('timestamp', 'name'), True), + (('time', 'name'), True), ) +class Humidities(BaseModel): + time = DateTimeField(index=True) + name = TextField() + location = TextField() + environment = TextField() + source = TextField() + humidity = DecimalField() + json = TextField(null=False) + + @classmethod + def insert_msg(cls, msg): + name = msg['tags']['name'] + + try: + return cls.create( + time = msg['time'], + name = msg['tags']['name'], + location = msg['tags']['location'], + environment = msg['tags']['environment'], + source = msg['tags']['source'], + humidity = msg['fields']['value'], + json = json.dumps(msg) + ) + except IntegrityError as e: + logger.error(f"error on message from {name}") + logger.error(e) + return None + + class Meta: + indexes = ( + (('time', 'humidity'), True), + ) class Sensor(BaseModel): name = TextField() @@ -29,8 +107,3 @@ class Sensor(BaseModel): host = TextField() comment = TextField() created = DateTimeField() - -def create_tables(uri): - db.initialize(connect(uri)) - with db: - db.create_tables([Temps]) diff --git a/sudoisbot/sink/newdb.py b/sudoisbot/sink/newdb.py index 1063f98..a7bf1b0 100644 --- a/sudoisbot/sink/newdb.py +++ b/sudoisbot/sink/newdb.py @@ -2,12 +2,34 @@ import sys import os +from peewee import MySQLDatabase, ProgrammingError +from loguru import logger from sudoisbot.sink import models +from sudoisbot.config import read_config -sqlitefile = sys.argv[1] +conf_file = sys.argv[1] -if os.path.exists(sqlitefile): - raise SystemExit(f"file '{sqlitefile}' exists, not doing anything") +config = read_config(conf_file) +with MySQLDatabase(**config['mysql']) as db: + models.db_proxy.initialize(db) -models.create_tables("sqlite:///" + sqlitefile) + should_exist = [models.Temperatures, models.Humidities] + create = [] + for table in should_exist: + try: + count = table.select().count() + if count > 0: + logger.info(f"{table} table has {count} rows, ignoring") + continue + except ProgrammingError as e: + if not e.args[1].endswith("doesn't exist"): + raise + + create.append(table) + + if len(create) > 0: + db.create_tables(create) + logger.info(f"created {create}") + else: + logger.warning("did nothing") diff --git a/sudoisbot/sink/simplestate.py b/sudoisbot/sink/simplestate.py index a7097a2..866f717 100644 --- a/sudoisbot/sink/simplestate.py +++ b/sudoisbot/sink/simplestate.py @@ -1,21 +1,37 @@ #!/usr/bin/env python3 import json -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from loguru import logger import sudoisbot.datatypes def get_recent(statefile, grace=10): + state = get_state(statefile) - now = datetime.now() + + now = datetime.now(timezone.utc) temps = dict() - for name, values in state.items(): - okdiff = timedelta(minutes=grace, seconds=int(values['frequency'])) - dt = datetime.fromisoformat(values['timestamp']) - if now - dt < okdiff: - temps[name] = values + for name, data in state.items(): + okdiff = timedelta(minutes=grace, seconds=int(data['tags'].get('frequency', 240))) + dt = datetime.fromisoformat(data['time']) + + try: + diff = now - dt + is_recent = diff < okdiff + except TypeError as e: + if "offset-naive and offset-aware" in e.args[0]: + logger.warning(f"record for '{name}' doesnt have a tz") + continue + else: + raise + + if is_recent: + logger.trace(f"age of '{name}' state: {diff}") + temps[name] = data + else: + logger.warning(f"record for '{name}' is too old (diff {diff})") if not any(temps.values()): raise ValueError("no recent temp data was found") else: diff --git a/sudoisbot/sink/sink.py b/sudoisbot/sink/sink.py index 872de33..c591939 100644 --- a/sudoisbot/sink/sink.py +++ b/sudoisbot/sink/sink.py @@ -4,15 +4,14 @@ import os import json from time import sleep -import dateutil.parser -from datetime import timezone +import sys from loguru import logger import zmq from sudoisbot.sink import simplestate -from sudoisbot.config import read_config from sudoisbot.network.sub import Subscriber, SubscriberTimedOutError +from sudoisbot.sink.models import Temperatures, Humidities, dbconnect def as_bytes(astring): @@ -50,7 +49,8 @@ class ZFluxClient(object): return self def __exit__(self, exc_type, exc_value, traceback): - self.disconnect() + #self.disconnect() + pass def send(self, msg): self.socket.send_multipart([self.topic, json.dumps(msg).encode()]) @@ -62,14 +62,11 @@ class Sink(object): self.setup_loggers(write_path) self.state_dir = write_path - self.handlers = { - b'temp': self.handle_temp, - b'weather': self.handle_weather - } def setup_loggers(self, writepath): # change to 11 or 19 to show with debug logging logger.level("TXT", no=9, color="") + logger.level("SINK", no=11, color="") for topic in self.topics: def matcher(topic): @@ -78,6 +75,7 @@ class Sink(object): return extra_topic == as_bytes(topic) return inner + logger.add(os.path.join(writepath, f"{topic}.txt"), level="TXT", format="{message}", filter=matcher(topic)) @@ -87,67 +85,43 @@ class Sink(object): def listen(self, addr): try: - with self.make_subscriber(addr) as sub: - for topic, msg in sub.recv(): - self.handle_msg(topic, msg) + # with self.make_subscriber(addr) as sub: + # for topic, msg in sub.recv(): + # self.handle_msg(topic, msg) + # + # commented out because testing to no gracefully disconnected to get + # publishers to buffer when sink is dead + sub = self.make_subscriber(addr) + sub.connect() + for topic, msg, cached in sub.recv(): + if cached: + logger.info(f"got a cached {topic} message from {msg['time']}") + self.handle_msg(topic, msg) except zmq.error.Again: logger.info(f"timeout after {sub.rcvtimeo_secs}s..") raise SubscriberTimedOutError - def handle_msg(self, topic, msg_in): - # get a message handle if it is temp/weather which need - # special handling right now, and format the messags - # to the influxdb-style format. - # when everythign sends properly formatted messages - # we can change this logic - handler = self.handlers.get(topic, lambda _, msg: msg) - msg = handler(topic, msg_in) + def handle_msg(self, topic, msg): + self.log(topic, msg) - # universal send/write for all topics - #self.update_db(topic, msg) # todo: keep records in sql self.append_file(topic, msg) - self.update_state(topic, msg) + self.update_db(topic, msg) # todo: keep records in sql self.send_zflux(msg) - - return msg - - def handle_weather(self, topic, msg): - # weather data was has been sent on the b'temp' topic for a while so its - # in the same csv and influxdb measurement. - # do this hack until i sort it out (parse the weather temps from the csv file - # and insert to own file and measuremetn) - legacy_msg = { 'timestamp': msg['time'], - 'name': msg['tags']['name'], - 'temp': msg['fields']['temp'] } - - # logs csv in rain.txt - weather_as_temp_msg = self.handle_temp(b'temp', legacy_msg) - # send to 'temp' measurement in influxdb - self.send_zflux(weather_as_temp_msg) - - return msg - - def handle_temp(self, topic, msg): - short_timestamp = msg['timestamp'][:19] # no millisec - temp = msg['temp'] - csv = f"{short_timestamp},{msg['name']},{temp}" - logger.bind(topic=topic).log("TXT", csv) + self.update_state(topic, msg) - ts = msg['timestamp'] - dt = dateutil.parser.parse(ts).astimezone(timezone.utc).isoformat() - return { - 'measurement': topic.decode(), - 'tags': { 'name': msg['name'] }, - 'time': dt, - 'fields': { - "value": float(f"{float(temp):.2f}") # ugh...... - } - } + def update_db(self, topic, msg): + if topic == b"temp": + if msg['measurement'] == "temp": + Temperatures.insert_msg(msg) + elif msg['measurement'] == "humidity": + Humidities.insert_msg(msg) + def update_state(self, topic, newstate): - filename = os.path.join(self.state_dir, f"{topic.decode()}-state.json") + measurement = newstate['measurement'] + filename = os.path.join(self.state_dir, f"{measurement}-state.json") simplestate.update_state(newstate, filename) def send_zflux(self, msg): @@ -157,14 +131,31 @@ class Sink(object): def append_file(self, topic, msg): logger.bind(topic=topic).log("TXT", json.dumps(msg)) + def log(self, topic, msg): + measurement = msg['measurement'] -def main(): - config = read_config() + + name = msg['tags']['name'] + if 'value' in msg['fields']: + value = f": {msg['fields']['value']}" + else: + value = "" + logger.log("SINK", f"{topic}: {measurement} from '{name}'{value}") + + + + + +def main(args, config): + + db = dbconnect(**config['mysql']) with ZFluxClient(topic=config['zflux']['topic']) as zflux: zflux.connect(config['zflux']['addr']) - sink = Sink(config['sink']['topics'], config['sink']['write_path'], zflux) + write_path = args.write_path or config['sink']['write_path'] + + sink = Sink(config['sink']['topics'], write_path, zflux) while True: try: addr = config['sink']['addr'] @@ -182,25 +173,3 @@ def suicide_snail(timestamp, max_delay): if min(delay.seconds, 0) > max_delay: logger.error(f"suicide snail: {delay.seconds} secs") raise SystemExit("suicide snail") - - - -#from sudoisbot.sink import models -#from playhouse.db_url import connect -#from peewee import IntegrityError -# db = connect(dburl) -# models.db.initialize(db) - -# try: -# if j['type'] == "weather": -# extra = json.dumps(j["weather"]) -# elif j["type"] == "temp": -# extra = json.dumps(j.get('metadata')) - -# with models.db: -# models.Temps.create(timestamp=j['timestamp'], -# name=j['name'], -# temp=j['temp'], -# extra=extra) -# except IntegrityError as e: -# logger.error(e) diff --git a/sudoisbot/temps/exceptions.py b/sudoisbot/temps/exceptions.py deleted file mode 100644 index 1d80091..0000000 --- a/sudoisbot/temps/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/python3 - - -class SensorDisconnectedError(Exception): pass - -class NoSensorDetectedError(Exception): pass diff --git a/sudoisbot/temps/rain_pub.py b/sudoisbot/temps/rain_pub.py deleted file mode 100644 index 9d9bc79..0000000 --- a/sudoisbot/temps/rain_pub.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/python3 - -import json -from json.decoder import JSONDecodeError -from time import time, sleep -from datetime import datetime, timezone - -from loguru import logger -import serial - -from sudoisbot.network.pub import Publisher - -#device = "/dev/cu.usbserial-A800eGKH" -BAUDRATE = 9600 - - -class ArduinoSensor(serial.Serial): - def __init__(self, wait_timeout=1000, device="/dev/ttyUSB0"): - # wait_timeout = how often arduino should send a value in ms - # called 'timeout' in arduino code - # needs a better name - # especially since the next line also has a timeout variable - # but thats the serial read timeout - super().__init__(device, BAUDRATE, timeout=3600) - self.wait_timeout = wait_timeout - self.kind = "arduino" - - def start(self): - try: - data = self.readline() - if not json.loads(data)['ready']: - # 'true' is hardcoded.. - raise JSONDecodeError - except (KeyError, JSONDecodeError) as e: - # need to polish this when im able to reproduce - # maybe figure out why it happens - logger.error(f"invalid json: {data}") - raise SystemExit - - # \n is important ! - logger.info(f"setting {self.wait_timeout} as interval") - self.write(f"{self.wait_timeout}\r\n".encode()) - - - def __enter__(self): - # if i want to use this not as a context manager ill need - # self.started - super().__enter__() - self.start() - return self - - def iter_lines(self, f=json.loads): - while True: - line = self.readline() - if line == b"": - continue - if f is not None: - yield f(line) - else: - yield line - - -class ArduinoRainSensor(ArduinoSensor): - - def iter_lines(self, f=json.loads): - for jline in super().iter_lines(f): - rain = jline['digital'] == "LOW" - now = datetime.now(timezone.utc).isoformat() - yield { - 'digital': jline['digital'], - 'time': now, - 'rain': rain - } - - -class RainPublisher(Publisher): - def __init__(self, sensor, addr, name, loc, freq): - super().__init__(addr, b"rain", name, freq) - - self.sensor = sensor - self.freq = freq - self.pub_at = time() - - self.tags = { - 'name': name, - 'location': loc, - 'kind': self.sensor.kind - } - - - logger.info(f"emitting data as '{name}' every {freq}s") - - def publish(self, line): - data = { - 'measurement': 'rain', - 'time': line['time'], - 'tags': self.tags, - 'fields': { - 'value': line['rain'], - 'value_int': int(line['rain']) - } - } - - logger.info(json.dumps(data, indent=2)) - self.pub(data) - - - def start(self): - try: - return self._start() - except KeyboardInterrupt: - logger.info("ok im leaving") - - - def _start(self): - rain_state = False - for rainline in self.sensor.iter_lines(): - - #logger.debug(rainline) - - now = time() - if now >= self.pub_at or rainline['rain'] != rain_state: - - if rainline['rain'] != rain_state: - logger.warning("state change!") - - self.publish(rainline) - self.pub_at = now + self.freq - - rain_state = rainline['rain'] - -def main(): - addr = "tcp://broker.sudo.is:5559" - name = "balcony" - loc = "s21" - freq = 60 - - with ArduinoRainSensor(3000) as rain_sensor: - with RainPublisher(rain_sensor, addr, name, loc, freq) as pub: - pub.start() - -if __name__ == "__main__": - import sys - sys.exit(main()) diff --git a/sudoisbot/temps/sensors.py b/sudoisbot/temps/sensors.py deleted file mode 100644 index 08d3960..0000000 --- a/sudoisbot/temps/sensors.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/python3 - -from loguru import logger -from temper.temper import Temper - -import os.path - -from sudoisbot.temps.exceptions import * - -W1ROOT = "/sys/bus/w1/devices" -W1LIST = "w1_bus_master1/w1_master_slaves" - -class TempSensorBase(object): - pass - -class TemperSensor(Temper, TempSensorBase): - sensortype = "temper" - sample_interval = 30 - - @classmethod - def get(cls): - if cls.is_connected(): - return cls() - else: - raise NoSensorDetectedError - - @classmethod - def is_connected(cls): - try: - temper = cls() - return len(temper.read()) > 0 - except SensorDisconnectedError: - return False - - - def _read(self): - # error handling - try: - data = super().read() - if len(data) == 0: raise SensorDisconnectedError("temper: no data") - return data - except FileNotFoundError as e: - msg = f"temper: {e.args[1]}" - logger.error(msg) - raise SensorDisconnectedError(msg) - except PermissionError as e: - msg = f"temper found but got: {e}" - logger.error(msg) - raise SensorDisconnectedError(msg) - - - def read(self): - data = self._read() - mapping = { - 'internal temperature': 'temp', - 'internal humidity': 'humidity', - 'external temperature': 'temp', - 'external humidity': 'humidity' - } - - results = [] - for item in data: - # get a dict with the old keys and their values, each of these - # values will be their own dict - - sources = [key for key in mapping.keys() if key in item.keys()] - - base = {k: v for (k, v) in item.items() if k not in mapping.keys()} - - for oldkey in sources: - newkey = mapping[oldkey] - fixed = {newkey: item[oldkey], 'source': oldkey} - results.append({**base, **fixed}) - - return results - - -class Ds18b20Sensor(TempSensorBase): - sensortype = "ds18b20" - sample_interval = 10 - - # study: 28-0300a279f70f - # outdoor: 28-0300a279bbc9 - - # File "/home/ben/sudoisbot/sudoisbot/temps/sensors.py", line 94, in _parse_data - # if not data[0].endswith("YES"): - # â”” [] - - - def __init__(self, sensor_ids): - def w1path(sensor_id): - return os.path.join(W1ROOT, sensor_id, "w1_slave") - self.sensors = [(a, w1path(a)) for a in sensor_ids] - - def _read_sensor(self, sensor): - try: - with open(sensor, 'r') as f: - return f.read().splitlines() - except FileNotFoundError: - raise SensorDisconnectedError(sensor) - - def _parse_data(self, data): - # YES = checksum matches - if len(data) == 0: - raise SensorDisconnectedError - if not data[0].endswith("YES"): - raise SensorDisconnectedError - tempstr = data[1].rsplit(" ", 1)[1][2:] - - return int(tempstr)/1000.0 - - - def read(self): - # just expecting one sensor now - if not self.sensors: - raise SensorDisconnectedError(sensorid) - - for sensorid, sensorpath in self.sensors: - data = self._read_sensor(sensorpath) - temp = self._parse_data(data) - - # figure out the rest and do checksums in the future - yield {'temp': temp, - 'sensorid': sensorid } - - @classmethod - def get(cls): - - with open(os.path.join(W1ROOT, W1LIST), 'r') as f: - w1_ids = f.read().splitlines() - - if not all(a.startswith("28-") for a in w1_ids) and len(w1_ids) > 0: - raise NoSensorDetectedError - - return cls(w1_ids) - - @classmethod - def is_connected(cls): - return len(cls.get().sensors) > 0 - - -def detect_sensor(sensortype=None): - if sensortype: - logger.info(f"skipping detection, attempting to use '{sensortype}'") - return supported_sensors[sensortype].get() - - for sensor in supported_sensors.values(): - if sensor.is_connected(): - logger.info(f"found '{sensor.sensortype}' sensor") - return sensor.get() - else: - raise NoSensorDetectedError - -supported_sensors = {a.sensortype: a for a in TempSensorBase.__subclasses__()} diff --git a/sudoisbot/temps/temp_pub.py b/sudoisbot/temps/temp_pub.py deleted file mode 100644 index f9ce0d5..0000000 --- a/sudoisbot/temps/temp_pub.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/python3 -u - -import argparse -import time -import sys - -import zmq -from temper.temper import Temper as TemperBase -from loguru import logger - -from sudoisbot.common import init, catch -from sudoisbot.network.pub import Publisher -from sudoisbot.temps.sensors import TemperSensor, Ds18b20Sensor -from sudoisbot.temps.sensors import supported_sensors, detect_sensor -from sudoisbot.temps.exceptions import * - -# TODO: -# use tmpfs on raspi for state -# set up ntp on raspbi - -# How to decide name: -# -# - check if the config tells us to expect a sensor -# and then also what its name is -# - autodetect sensors and load sensor classes -# - -# - -class TempPublisher(Publisher): - def __init__(self, addr, name, freq, sensor=None): - super().__init__(addr, b"temp", name, freq) - - self.sensor = sensor - self.sensortype = self.sensor.sensortype - - logger.info(f"emitting data from a {self.sensortype} as '{self.name}'") - - def publish(self): - try: - temp = self.sensor.read() - for t in temp: - data = { 'temp': t['temp'], - 'metadata': { 'sensortype': self.sensortype, - 'firmware': t.get('firmware'), - 'sensorid': t.get('sensorid')} } - # adds name, timestamp, frequency, type - self.send(data) - - except KeyError as e: - if self.sensortype == "temper" and e.args[0] == 'temp': - # seems to happen intermittently - logger.error(t) - else: - raise - except SensorDisconnectedError: - # temper was most likely unplugged - # disconnect handled by __exit__ - logger.warning(f"{self.sensortype} sensor unplugged, disconnecting") - raise - - -def wait_for_sensor(sensortype=None): - sleep_mode = False - while True: - try: - return detect_sensor(sensortype) - except NoSensorDetectedError: - if not sleep_mode: - logger.info("entering sleep mode, checking for sensors every 15m") - sleep_mode = True - time.sleep(15.0*60) - - -@catch -def main(): - parser = argparse.ArgumentParser( - description="emit temp data from therm sensor", - add_help=False) - parser.add_argument("--name", help="set temper name") - parser.add_argument("--sleep", help="publish interval", type=int, default=240) - parser.add_argument("--sensortype", choices=supported_sensors.keys()) - - config, args = init("temper_pub", parser) - - addr = config['addr'] - name = config['name'] if not args.name else args.name - sleep = config['sleep'] if not args.sleep else args.sleep - - - while True: - try: - sensor = wait_for_sensor(args.sensortype) - - with TempPublisher(addr, name, sleep, sensor) as publisher: - publisher.loop() - return 0 - except SensorDisconnectedError as e: - # especially usb sensors can be unplugged for a short time - # for various reasons - logger.info("waiting 30s for sensor to come back") - time.sleep(30.0) - continue - except PermissionError as e: - logger.error(e) - return 2 - except KeyboardInterrupt: - logger.info("Exiting..") - return 0 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/sudoisbot/unifi_clients.py b/sudoisbot/unifi_clients.py deleted file mode 100644 index 89eef65..0000000 --- a/sudoisbot/unifi_clients.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python3 - -from loguru import logger - -from sudoisbot.unifi import UnifiApi -from sudoisbot.common import init - -def show_clients(): - config = init("unifi") - api = UnifiApi(config) - for client in api.get_clients_short(): - logger.info(client) diff --git a/sudoisbot/util/csv2json.py b/sudoisbot/util/csv2json.py new file mode 100644 index 0000000..f3c2575 --- /dev/null +++ b/sudoisbot/util/csv2json.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 +import sys +import argparse +from datetime import datetime +import os +import json +import dateutil.parser +from datetime import timezone +import fileinput + +from loguru import logger +import requests.exceptions + +#from sudoisbot.sink import models +from sudoisbot.config import read_config + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("csv") + parser.add_argument("json") + parser.add_argument("--state") + args = parser.parse_args() + + with open(args.state, 'r') as f: + state = json.load(f) + + def mktags(name): + if name == "inside": + return { + 'name': name, + 'environment': 'inside', + 'kind': 'temper', + 'source': 'sensor', + 'frequency': 240 + } + + else: + tags = state[name]['tags'] + tags['frequency'] = 240 + return tags + + + def mkjson(dt, name, temp): + dt = dateutil.parser.parse(dt).astimezone(timezone.utc).isoformat() + return json.dumps({ + "measurement": "temp", + "tags": mktags(name), + "time": dt, + "fields": { + "value": float(f"{float(temp):.2f}") # ugh...... + } + }) + + + + name_input = "" + with open(args.csv, 'r') as f: + with open(args.json, 'w') as j: + for line in f.readlines(): + d = dict() + items = line.strip().split(",") + if len(items) == 2: + # before i was smart enough to log the name + if not name_input: + name_input = input("enter name: ") + dt, d['temp'] = items + d['name'] = name_input + + else: + dt, name, temp = items + d['name'], d['temp'] = name, temp + + #d['timestamp'] = datetime.fromisoformat(dt) + d['timestamp'] = dt + + j.write(mkjson(dt, name, temp)) + j.write("\n") diff --git a/sudoisbot/util/json2influx.py b/sudoisbot/util/json2influx.py new file mode 100644 index 0000000..214689c --- /dev/null +++ b/sudoisbot/util/json2influx.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +import argparse +import json +from time import sleep + +from loguru import logger + +from sudoisbot.sink.sink import ZFluxClient +from sudoisbot.config import read_config + + +if __name__ == "__main__": + config = read_config('/usr/local/etc/sudoisbot-sink.yml') + + parser = argparse.ArgumentParser() + parser.add_argument("--json-file", required=True) + parser.add_argument("--last", type=int) + args = parser.parse_args() + + zflux = ZFluxClient(topic=config['zflux']['topic']) + zflux.connect(config['zflux']['addr']) + + logger.info(f"reading {args.json_file}...") + l = list() + with open(args.json_file, 'r') as f: + for line in f.readlines(): + jline = json.loads(line) + l.append(jline) + + if args.last: + sendthis = l[-args.last:] + else: + sendthis = l + + logger.info(f"read: {len(l)}, sending: {len(sendthis)}") + + + logger.info("sleeping to avoid the late joiner syndrome") + sleep(1.0) + for item in sendthis: + tochange = [k for k, v in item['fields'].items() if isinstance(v, int)] + if tochange: + n = item['tags']['name'] + m = item['measurement'] + for k in tochange: + logger.warning(f"field: '{k}', measurement: {m}, name: {n} to float") + tosend = { + 'measurement': item['measurement'], + 'fields': { + k: float(v) if isinstance(v, int) else v + for k, v in item['fields'].items() + }, + 'tags': item['tags'], + 'time': item['time'], + } + zflux.send(tosend) + + print(f"oldets sent: {sendthis[0]['time']}") + print(f"newestsent: {sendthis[-1]['time']}") diff --git a/sudoisbot/util/json2mariadb.py b/sudoisbot/util/json2mariadb.py new file mode 100644 index 0000000..4669f5c --- /dev/null +++ b/sudoisbot/util/json2mariadb.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +import sys +import argparse +from datetime import datetime +import os +import json +import dateutil.parser +from datetime import timezone +import fileinput + +from peewee import IntegrityError +from loguru import logger +import requests.exceptions + +#from sudoisbot.sink import models +from sudoisbot.config import read_config +from sudoisbot.sink.models import Temperatures, Humidities, dbconnect + +if __name__ == "__main__": + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("json") + parser.add_argument("--config") + parser.add_argument("--ignore-dups") + args = parser.parse_args() + + config = read_config(args.config) + db = dbconnect(**config['mysql']) + + temp_count = Temperatures.select().count() + humi_count = Humidities.select().count() + logger.info(f"temp count: {temp_count}") + logger.info(f"humi count: {humi_count}") + + with open(args.json, 'r') as j: + for line in j.readlines(): + msg = json.loads(line) + msg['tags'].setdefault('location', 'unknown') + try: + if msg['measurement'] == "temp": + Temperatures.insert_msg(msg) + elif msg['measurement'] == "humidity": + Humidities.insert_msg(msg) + except IntegrityError as e: + if e.args[1].startswith("Duplicate") and args.ignore_dups: + name = msg['tags']['name'] + time = msg['time'] + logger.info(f"ignoring from {name} on {time}") + pass + else: + raise + + temp_count = Temperatures.select().count() + humi_count = Humidities.select().count() + logger.success(f"temp count: {temp_count}") + logger.success(f"humi count: {humi_count}") diff --git a/sudoisbot/util/suball.py b/sudoisbot/util/suball.py index c66a007..6d696df 100644 --- a/sudoisbot/util/suball.py +++ b/sudoisbot/util/suball.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 import argparse +import sys import zmq from loguru import logger @@ -11,15 +12,15 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--topic", default="") - # just get the config, so logger is just default config - #config, args = init('suball', parser, fullconfig=True) + parser.add_argument("--broker", default="broker.s21.sudo.is") + args = parser.parse_args() context = zmq.Context() socket = context.socket(zmq.SUB) socket.setsockopt(zmq.SUBSCRIBE, b'') #args.topic.encode()) - #addr = config['temper_sub']['addr'] - addr = "tcp://broker.s21.sudo.is:5560" + + addr = f"tcp://{args.broker}:5560" socket.connect(addr) logger.info(f"connected to '{addr}'") -- 2.40.1