Compare commits

..

2 Commits

Author SHA1 Message Date
Hauke Petersen
1563945858 verbump to 0.0.2 2022-08-26 14:49:40 +02:00
Hauke Petersen
57b97eaf10 steckie initial port to pip based service 2022-08-26 14:49:20 +02:00
6 changed files with 164 additions and 139 deletions

45
setup.py Normal file
View File

@@ -0,0 +1,45 @@
# Copyright (C) 2022 Hauke Petersen <devel@haukepetersen.de>
#
# 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.2"
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']
}
)

View File

@@ -1,139 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 Hauke Petersen <devel@haukepetersen.de>
#
# 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 <http://www.gnu.org/licenses/>.
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)

1
steckie/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .steckie import Steckie

35
steckie/__main__.py Normal file
View File

@@ -0,0 +1,35 @@
# Copyright (C) 2022 Hauke Petersen <devel@haukepetersen.de>
#
# 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()

77
steckie/steckie.py Executable file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 Hauke Petersen <devel@haukepetersen.de>
#
# 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 <http://www.gnu.org/licenses/>.
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)

6
test.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
echo "### INSTALL\n"
pip3 install --user .
echo "\n### RUN\n"
steckie config.herrstat.yml