This commit is contained in:
Evgeny Zinoviev 2021-05-08 01:53:47 +03:00
parent a9f26cf874
commit 1024cc0b8c
5 changed files with 36 additions and 107 deletions

View File

@ -1,14 +1,18 @@
# inverter-bot
This is a Telegram bot for querying information from an InfiniSolar V family of hybrid solar inverters, in particular
inverters supported by **isv** utility, which is an older version of **infinisolarctl** from **infinisolar-tools**
package.
This is a Telegram bot for querying information from an InfiniSolar V family of hybrid solar inverters.
It supports querying general status, such as battery voltage or power usage, printing amounts of energy generated in
the last days, dumping status or rated information and more.
It requires Python 3.6+ or so.
## Requirements
- **`inverterd`** from [inverter-tools](https://github.com/gch1p/inverter-tools)
- **[`inverterd`](https://pypi.org/project/inverterd/)** python library
- Python 3.6+ or so
## Configuration
Configuration is stored in `config.ini` file in `~/.config/inverter-bot`.

View File

@ -1,51 +0,0 @@
import os, re
from configparser import ConfigParser
CONFIG_DIR = os.environ['HOME'] + '/.config/inverter-bot'
CONFIG_FILE = 'config.ini'
__config__ = None
def _get_config_path() -> str:
return "%s/%s" % (CONFIG_DIR, CONFIG_FILE)
def get_config():
global __config__
if __config__ is not None:
return __config__['root']
if not os.path.exists(CONFIG_DIR):
raise IOError("%s directory not found" % CONFIG_DIR)
if not os.path.isdir(CONFIG_DIR):
raise IOError("%s is not a directory" % CONFIG_DIR)
config_path = _get_config_path()
if not os.path.isfile(config_path):
raise IOError("%s file not found" % config_path)
__config__ = ConfigParser()
with open(config_path) as config_content:
__config__.read_string("[root]\n" + config_content.read())
return __config__['root']
def get_token() -> str:
return get_config()['token']
def get_admins() -> tuple:
config = get_config()
return tuple([int(s) for s in re.findall(r'\b\d+\b', config['admins'], flags=re.MULTILINE)])
def get_isv_bin() -> str:
return get_config()['isv_bin']
def use_sudo() -> bool:
config = get_config()
return 'use_sudo' in config and config['use_sudo'] == '1'

37
main.py → inverter-bot Normal file → Executable file
View File

@ -1,10 +1,10 @@
import logging
import re
import datetime
import isv
import configstore
#!/usr/bin/env python3
import logging, re, datetime, json, inverterd
from typing import Optional
from argparse import ArgumentParser
from html import escape
from pprint import pprint
from time import sleep
from strings import lang as _
from telegram import (
@ -21,6 +21,7 @@ from telegram.ext import (
CallbackContext
)
inverter: Optional[inverterd.Client] = None
#
# helpers
@ -57,11 +58,12 @@ def start(update: Update, context: CallbackContext) -> None:
def msg_status(update: Update, context: CallbackContext) -> None:
try:
gs = isv.general_status()
gs = json.loads(inverter.exec('get-status'))['data']
pprint(gs)
# render response
power_direction = gs['battery_power_direction'].lower()
power_direction = re.sub(r'ge$', 'ging', power_direction)
power_direction = re.sub(r'ges$', 'ging', power_direction)
charging_rate = ''
if power_direction == 'charging':
@ -162,15 +164,30 @@ def msg_all(update: Update, context: CallbackContext) -> None:
if __name__ == '__main__':
config = configstore.get_config()
# command-line arguments
parser = ArgumentParser()
parser.add_argument('--token', required=True, type=str,
help='Telegram bot token')
parser.add_argument('--users-whitelist', nargs='+',
help='ID of users allowed to use the bot')
parser.add_argument('--inverterd-host', default='127.0.0.1', type=str)
parser.add_argument('--inverterd-port', default=8305, type=int)
args = parser.parse_args()
whitelist = list(map(lambda x: int(x), args.users_whitelist))
# connect to inverterd
inverter = inverterd.Client(host=args.inverterd_host, port=args.inverterd_port)
# configure logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
updater = Updater(configstore.get_token(), request_kwargs={'read_timeout': 6, 'connect_timeout': 7})
# configure bot
updater = Updater(args.token, request_kwargs={'read_timeout': 6, 'connect_timeout': 7})
dispatcher = updater.dispatcher
user_filter = Filters.user(configstore.get_admins())
user_filter = Filters.user(whitelist)
dispatcher.add_handler(CommandHandler('start', start))
dispatcher.add_handler(MessageHandler(Filters.text(_('status')) & user_filter, msg_status))

42
isv.py
View File

@ -1,42 +0,0 @@
import subprocess
import configstore
import json
def __run(argv: list, fmt='json-w-units'):
argv.insert(0, configstore.get_isv_bin())
if configstore.use_sudo():
argv.insert(0, 'sudo')
argv.append('--format')
argv.append(fmt)
result = subprocess.run(argv, capture_output=True)
if result.returncode != 0:
raise ChildProcessError("isv returned %d: %s" % (result.returncode, result.stderr))
return json.loads(result.stdout) if 'json' in fmt else result.stdout.decode('utf-8')
def general_status(as_table=False):
kwargs = {}
if as_table:
kwargs['fmt'] = 'table'
return __run(['--get-general-status'], **kwargs)
def day_generated(y: int, m: int, d: int):
return __run(['--get-day-generated', str(y), str(m), str(d)])
def rated_information(as_table=False):
kwargs = {}
if as_table:
kwargs['fmt'] = 'table'
return __run(['--get-rated-information'], **kwargs)
def faults(as_table=False):
kwargs = {}
if as_table:
kwargs['fmt'] = 'table'
return __run(['--get-faults-warnings'], **kwargs)

View File

@ -1 +1,2 @@
python-telegram-bot~=13.1
inverterd