From 57b97eaf10c8e8b44f8313faee0767a88dab980b Mon Sep 17 00:00:00 2001 From: Hauke Petersen Date: Fri, 26 Aug 2022 14:49:20 +0200 Subject: [PATCH] steckie initial port to pip based service --- setup.py | 45 ++++++++++++++ steckie.py | 139 -------------------------------------------- steckie/__init__.py | 1 + steckie/__main__.py | 35 +++++++++++ steckie/steckie.py | 77 ++++++++++++++++++++++++ test.sh | 6 ++ 6 files changed, 164 insertions(+), 139 deletions(-) create mode 100644 setup.py delete mode 100755 steckie.py create mode 100644 steckie/__init__.py create mode 100644 steckie/__main__.py create mode 100755 steckie/steckie.py create mode 100755 test.sh diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..67a56bb --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +# Copyright (C) 2022 Hauke Petersen +# +# 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. + +import setuptools + +name = "steckie" +ver = "0.0.1" +desc = 'Read state from Tasmota smart plugs' + +setuptools.setup( + name=name, + version=ver, + author="Hauke Petersen", + author_email="devel@haukepetersen.de", + description=desc, + url=f'https://git.deichnet.com/hauke/{name}', + license='MIT', + packages=[name], + install_requires=[ + "apscheduler", + "requests", + "pydeichlib@git+ssh://git@git.deichnet.com:22223" + "/hauke/pydeichlib.git@v0.0.13#egg=pydeichlib-0.0.13", + ], + entry_points={ + "console_scripts": [f'{name} = {name}.__main__:main'] + } +) diff --git a/steckie.py b/steckie.py deleted file mode 100755 index c5c2ce4..0000000 --- a/steckie.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Copyright (C) 2019 Hauke Petersen -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import yaml -import json -import logging -import argparse -import urllib.request -from influxdb import InfluxDBClient -from apscheduler.schedulers.blocking import BlockingScheduler - - -CONFIGFILE = "/opt/steckie/config.yml" -CMD_SENSOR = "cm?cmnd=Status%208" - -CFG_DEFAULTS = { - "STECKIE_ITVL": 5, - "INFLUXDB_PORT": 8086, -} - -CFG_NEEDED = { - "INFLUXDB_HOST": "hostname", - "INFLUXDB_DB": "database_name", - "INFLUXDB_USER": "username", - "INFLUXDB_USER_PASSWORD": "SuperSecure", -} - -CFG_VAR = { - "name": r'STECKIE_DEV_\d+_NAME', - "url": r'STECKIE_DEV_\d+_URL', -} - - -class Steckie: - def __init__(self, env_file): - self.cfg = dict() - env = os.environ - - print("Env file: {}".format(env_file)) - - # read configuration - if env_file: - env = self.read_env(env_file) - for val in CFG_DEFAULTS: - self.cfg[val] = env[val] if val in env else CFG_DEFAULTS[val] - for val in CFG_NEEDED: - if val in env: - self.cfg[val] = env[val] - else: - logging.error("unable to read {} fron env".format(val)) - sys.exit(1) - - if cfgfile: - try: - with open(cfgfile, 'r', encoding="utf-8") as f: - self.cfg.update(yaml.load(f, Loader=yaml.BaseLoader)) - except: - logging.error("unable to read config file '{}'".format(cfgfile)) - sys.exit(1) - - - - self.scheduler = BlockingScheduler() - self.db = InfluxDBClient(host=self.cfg['db']['host'], - port=int(self.cfg['db']['port']), - database=self.cfg['db']['name'], - username=self.cfg['db']['user'], - password=self.cfg['db']['pass']) - - def run(self): - self.scheduler.add_job(self.update, 'interval', - seconds=int(self.cfg['update_itvl'])) - self.scheduler.start() - - def update(self): - for dev in self.cfg['devs']: - url = "{}/{}".format(dev['url'], CMD_SENSOR) - data = {} - try: - with urllib.request.urlopen(url) as resp: - data = json.loads(resp.read().decode("utf-8")) - print(data) - except: - logging.warning("unable to read data from '{}'".format(dev['name'])) - continue - - point = [{ - "measurement": "stromie", - "tags": { - "name": dev['name'], - "url": dev['url'], - "type": "tasmota_awp07l", - }, - "time": data['StatusSNS']['Time'], - "fields": { - "voltage": float(data['StatusSNS']['ENERGY']['Voltage']), - "current": float(data['StatusSNS']['ENERGY']['Current']), - "pwr": float(data['StatusSNS']['ENERGY']['ApparentPower']), - "pwr_r": float(data['StatusSNS']['ENERGY']['ReactivePower']), - "factor": float(data['StatusSNS']['ENERGY']['Factor']), - "e_daily": float(data['StatusSNS']['ENERGY']['Today']), - } - }] - - try: - self.db.write_points(point) - except: - logging.warning("{}: unable to write to DB".format(dev['name'])) - - -def main(args): - logging.basicConfig(format='%(asctime)s %(message)s', - level=logging.WARNING) - steckie = Steckie(args.env_file) - steckie.run() - - -if __name__ == "__main__": - p = argparse.ArgumentParser() - p.add_argument("env_file", default=None, nargs="?", help="output dump") - args = p.parse_args() - main(args) diff --git a/steckie/__init__.py b/steckie/__init__.py new file mode 100644 index 0000000..a667849 --- /dev/null +++ b/steckie/__init__.py @@ -0,0 +1 @@ +from .steckie import Steckie diff --git a/steckie/__main__.py b/steckie/__main__.py new file mode 100644 index 0000000..c2ed64e --- /dev/null +++ b/steckie/__main__.py @@ -0,0 +1,35 @@ +# Copyright (C) 2022 Hauke Petersen +# +# 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. + +import argparse +from steckie import Steckie + + +def main(args=None): + parser = argparse.ArgumentParser(description="Steckie") + parser.add_argument("cfg_file", help="Configuration file") + args = parser.parse_args() + + app = Steckie(args.cfg_file) + app.run() + + +if __name__ == "__main__": + main() diff --git a/steckie/steckie.py b/steckie/steckie.py new file mode 100755 index 0000000..f08eb87 --- /dev/null +++ b/steckie/steckie.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (C) 2019 Hauke Petersen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +import json +import pytz +import logging +import urllib.request +from datetime import datetime +from deichapp import Deichapp +from deichflux import Deichflux +from apscheduler.schedulers.blocking import BlockingScheduler + + +CONFIG_DEFAULT = { + "interval": 5.0, +} + + +class Steckie(Deichapp): + def __init__(self, cfg_file): + super().__init__(cfg_file, CONFIG_DEFAULT) + + self.scheduler = BlockingScheduler() + self.db = Deichflux(self.cfg["db"]) + + def run(self): + for name, url in self.cfg["devs"].items(): + self.scheduler.add_job(self.query, + "interval", + seconds=int(self.cfg["interval"]), + args=(name, url)) + self.scheduler.start() + + def query(self, name, url): + logging.warning(f'query {name} -> {url}') + + try: + with urllib.request.urlopen(f'{url}/cm?cmnd=Status%208') as raw: + resp = json.loads(raw.read().decode("utf-8")) + logging.warning(resp) + except Exception as e: + logging.warning(f'no response from {name} -> {e}') + return + + point = [{ + "measurement": self.cfg["measurement"], + "tags": { + "name": name, + }, + "time": pytz.UTC.localize(datetime.utcnow()).isoformat(), + "fields": { + "voltage": float(resp['StatusSNS']['ENERGY']['Voltage']), + "current": float(resp['StatusSNS']['ENERGY']['Current']), + "pwr": float(resp['StatusSNS']['ENERGY']['ApparentPower']), + "pwr_r": float(resp['StatusSNS']['ENERGY']['ReactivePower']), + "factor": float(resp['StatusSNS']['ENERGY']['Factor']), + "e_daily": float(resp['StatusSNS']['ENERGY']['Today']), + } + }] + + self.db.write(point) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..ec987bf --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "### INSTALL\n" +pip3 install --user . +echo "\n### RUN\n" +steckie config.herrstat.yml