save
This commit is contained in:
parent
a9f26cf874
commit
1024cc0b8c
10
README.md
10
README.md
@ -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`.
|
||||
|
@ -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
37
main.py → inverter-bot
Normal file → Executable 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
42
isv.py
@ -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)
|
@ -1 +1,2 @@
|
||||
python-telegram-bot~=13.1
|
||||
inverterd
|
Loading…
x
Reference in New Issue
Block a user