diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2022-10-24 04:39:09 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2022-10-24 04:39:09 +0300 |
commit | ed8f8839affd30d7fa3c8f51afaf714be6109551 (patch) | |
tree | a4e30195f8d8eac9aaf847852d6a0cad0c88e6d6 | |
parent | b66958643466b70011bcd0ed1f03e4938bb97f85 (diff) | |
parent | 1b8237280d10f4ed19facceb5441243471536e5f (diff) |
Merge branch 'master' of ch1p.io:homekit
-rw-r--r-- | src/home/inverter/__init__.py | 9 | ||||
-rw-r--r-- | src/home/inverter/monitor.py | 84 | ||||
-rw-r--r-- | src/home/inverter/types.py | 55 | ||||
-rwxr-xr-x | src/inverter_bot.py | 30 |
4 files changed, 110 insertions, 68 deletions
diff --git a/src/home/inverter/__init__.py b/src/home/inverter/__init__.py index 967ddff..8831ef3 100644 --- a/src/home/inverter/__init__.py +++ b/src/home/inverter/__init__.py @@ -1,10 +1,3 @@ -from .monitor import ( - ChargingEvent, - InverterMonitor, - BatteryState, - BatteryPowerDirection, - ACMode, - ACPresentEvent -) +from .monitor import InverterMonitor from .inverter_wrapper import wrapper_instance from .util import beautify_table diff --git a/src/home/inverter/monitor.py b/src/home/inverter/monitor.py index 2fcd55a..f661d73 100644 --- a/src/home/inverter/monitor.py +++ b/src/home/inverter/monitor.py @@ -1,7 +1,7 @@ import logging import time -from enum import Enum, auto +from .types import * from threading import Thread from typing import Callable, Optional from .inverter_wrapper import wrapper_instance as inverter @@ -12,55 +12,6 @@ from ..config import config logger = logging.getLogger(__name__) -class BatteryPowerDirection(Enum): - DISCHARGING = auto() - CHARGING = auto() - DO_NOTHING = auto() - - -class ChargingEvent(Enum): - AC_CHARGING_UNAVAILABLE_BECAUSE_SOLAR = auto() - AC_NOT_CHARGING = auto() - AC_CHARGING_STARTED = auto() - AC_DISCONNECTED = auto() - AC_CURRENT_CHANGED = auto() - AC_MOSTLY_CHARGED = auto() - AC_CHARGING_FINISHED = auto() - - UTIL_CHARGING_STARTED = auto() - UTIL_CHARGING_STOPPED = auto() - UTIL_CHARGING_STOPPED_SOLAR = auto() - - -class ACPresentEvent(Enum): - CONNECTED = auto() - DISCONNECTED = auto() - - -class ChargingState(Enum): - NOT_CHARGING = auto() - AC_BUT_SOLAR = auto() - AC_WAITING = auto() - AC_OK = auto() - AC_DONE = auto() - - -class CurrentChangeDirection(Enum): - UP = auto() - DOWN = auto() - - -class BatteryState(Enum): - NORMAL = auto() - LOW = auto() - CRITICAL = auto() - - -class ACMode(Enum): - GENERATOR = 'generator' - UTILITIES = 'utilities' - - def _pd_from_string(pd: str) -> BatteryPowerDirection: if pd == 'Discharge': return BatteryPowerDirection.DISCHARGING @@ -94,6 +45,8 @@ class InverterMonitor(Thread): battery_event_handler: Optional[Callable] util_event_handler: Optional[Callable] error_handler: Optional[Callable] + osp_change_cb: Optional[Callable] + osp: Optional[OutputSourcePriority] def __init__(self): super().__init__() @@ -102,12 +55,14 @@ class InverterMonitor(Thread): self.interrupted = False self.min_allowed_current = 0 self.ac_mode = None + self.osp = None # Event handlers for the bot. self.charging_event_handler = None self.battery_event_handler = None self.util_event_handler = None self.error_handler = None + self.osp_change_cb = None # Currents list, defined in the bot config. self.currents = cfg.gen_currents @@ -156,6 +111,12 @@ class InverterMonitor(Thread): self.min_allowed_current = min(allowed_currents) + # Reading rated configuration + rated = inverter.exec('get-rated')['data'] + self.osp = OutputSourcePriority.SolarBatteryUtility \ + if rated['output_source_priority'] == 'Solar-Battery-Utility' \ + else OutputSourcePriority.SolarUtilityBattery + # Read data and run implemented programs every 2 seconds. while not self.interrupted: try: @@ -167,6 +128,7 @@ class InverterMonitor(Thread): ac = gs['grid_voltage']['value'] > 0 or gs['grid_freq']['value'] > 0 solar = gs['pv1_input_voltage']['value'] > 0 or gs['pv2_input_voltage']['value'] > 0 + solar_input = gs['pv1_input_power']['value'] v = float(gs['battery_voltage']['value']) load_watts = int(gs['ac_output_active_power']['value']) pd = _pd_from_string(gs['battery_power_direction']) @@ -177,7 +139,7 @@ class InverterMonitor(Thread): self.gen_charging_program(ac, solar, v, pd) elif self.ac_mode == ACMode.UTILITIES: - self.utilities_monitoring_program(ac, solar, pd) + self.utilities_monitoring_program(ac, solar, v, load_watts, solar_input, pd) if not ac or pd != BatteryPowerDirection.CHARGING: # if AC is disconnected or not charging, run the low voltage checking program @@ -195,6 +157,9 @@ class InverterMonitor(Thread): def utilities_monitoring_program(self, ac: bool, # whether AC is connected solar: bool, # whether MPPT is active + v: float, # battery voltage + load_watts: int, # load, wh + solar_input: int, # input from solar panels, wh pd: BatteryPowerDirection # current power direction ): pd_event_send = False @@ -204,6 +169,15 @@ class InverterMonitor(Thread): self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED_SOLAR) pd_event_send = True + if solar: + if v <= 48 and self.osp == OutputSourcePriority.SolarBatteryUtility: + self.osp_change_cb(OutputSourcePriority.SolarUtilityBattery, solar_input=solar_input, v=v) + self.osp = OutputSourcePriority.SolarUtilityBattery + + if self.osp == OutputSourcePriority.SolarUtilityBattery and solar_input >= 900: + self.osp_change_cb(OutputSourcePriority.SolarBatteryUtility, solar_input=solar_input, v=v) + self.osp = OutputSourcePriority.SolarBatteryUtility + if self.util_ac_present is None or ac != self.util_ac_present: self.util_event_handler(ACPresentEvent.CONNECTED if ac else ACPresentEvent.DISCONNECTED) self.util_ac_present = ac @@ -213,6 +187,7 @@ class InverterMonitor(Thread): if not pd_event_send and not solar: if pd == BatteryPowerDirection.CHARGING: self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STARTED) + elif pd == BatteryPowerDirection.DISCHARGING: self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED) @@ -493,9 +468,15 @@ class InverterMonitor(Thread): def set_error_handler(self, handler: Callable): self.error_handler = handler + def set_osp_need_change_callback(self, cb: Callable): + self.osp_change_cb = cb + def set_ac_mode(self, mode: ACMode): self.ac_mode = mode + def notify_osp(self, osp: OutputSourcePriority): + self.osp = osp + def stop(self): self.interrupted = True @@ -513,6 +494,7 @@ class InverterMonitor(Thread): 'time_now': time.time(), 'next_current_enter_time': self.next_current_enter_time, 'ac_mode': self.ac_mode, + 'osp': self.osp, 'util_ac_present': self.util_ac_present, 'util_pd': self.util_pd.name, 'util_solar': self.util_solar diff --git a/src/home/inverter/types.py b/src/home/inverter/types.py new file mode 100644 index 0000000..0292c15 --- /dev/null +++ b/src/home/inverter/types.py @@ -0,0 +1,55 @@ +from enum import Enum, auto + + +class BatteryPowerDirection(Enum): + DISCHARGING = auto() + CHARGING = auto() + DO_NOTHING = auto() + + +class ChargingEvent(Enum): + AC_CHARGING_UNAVAILABLE_BECAUSE_SOLAR = auto() + AC_NOT_CHARGING = auto() + AC_CHARGING_STARTED = auto() + AC_DISCONNECTED = auto() + AC_CURRENT_CHANGED = auto() + AC_MOSTLY_CHARGED = auto() + AC_CHARGING_FINISHED = auto() + + UTIL_CHARGING_STARTED = auto() + UTIL_CHARGING_STOPPED = auto() + UTIL_CHARGING_STOPPED_SOLAR = auto() + + +class ACPresentEvent(Enum): + CONNECTED = auto() + DISCONNECTED = auto() + + +class ChargingState(Enum): + NOT_CHARGING = auto() + AC_BUT_SOLAR = auto() + AC_WAITING = auto() + AC_OK = auto() + AC_DONE = auto() + + +class CurrentChangeDirection(Enum): + UP = auto() + DOWN = auto() + + +class BatteryState(Enum): + NORMAL = auto() + LOW = auto() + CRITICAL = auto() + + +class ACMode(Enum): + GENERATOR = 'generator' + UTILITIES = 'utilities' + + +class OutputSourcePriority(Enum): + SolarUtilityBattery = 'SUB' + SolarBatteryUtility = 'SBU'
\ No newline at end of file diff --git a/src/inverter_bot.py b/src/inverter_bot.py index f2be2c6..223c7b3 100755 --- a/src/inverter_bot.py +++ b/src/inverter_bot.py @@ -4,7 +4,6 @@ import re import datetime import json -from enum import Enum from inverterd import Format, InverterError from html import escape from typing import Optional, Tuple, Union @@ -20,12 +19,14 @@ from home.bot import ( from home.inverter import ( wrapper_instance as inverter, beautify_table, - InverterMonitor, +) +from home.inverter.types import ( ChargingEvent, ACPresentEvent, BatteryState, - ACMode + ACMode, + OutputSourcePriority ) from home.api.types import BotType from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton @@ -57,11 +58,6 @@ SETACMODE_STARTED, = range(1) SETOSP_STARTED, = range(1) -class OutputSourcePriority(Enum): - SolarUtilityBattery = 'SUB' - SolarBatteryUtility = 'SBU' - - def monitor_charging(event: ChargingEvent, **kwargs) -> None: args = [] is_util = False @@ -135,6 +131,18 @@ def monitor_error(error: str) -> None: ) +def osp_change_cb(new_osp: OutputSourcePriority, + solar_input: int, + v: float): + + setosp(new_osp) + + bot.notify_all( + lambda lang: bot.lang.get('osp_auto_changed_notification', lang, + bot.lang.get(f'setosp_{new_osp.value.lower()}', lang), v, solar_input), + ) + + def full_status(ctx: Context) -> None: status = inverter.exec('get-status', format=Format.TABLE) ctx.reply(beautify_table(status)) @@ -294,6 +302,7 @@ def setacmode(mode: ACMode): def setosp(sp: OutputSourcePriority): logger.debug(f'setosp: sp={sp}') inverter.exec('set-output-source-priority', (sp.value,)) + monitor.notify_osp(sp) # /setacmode @@ -554,6 +563,7 @@ class InverterBot(Wrapper): # other notifications ac_mode_changed_notification='Пользователь <a href="tg://user?id=%d">%s</a> установил режим AC: <b>%s</b>.', osp_changed_notification='Пользователь <a href="tg://user?id=%d">%s</a> установил приоритет источника питания нагрузки: <b>%s</b>.', + osp_auto_changed_notification='ℹ️ Бот установил приоритет источника питания нагрузки: <b>%s</b>. Причины: напряжение АКБ %.1f V, мощность заряда с панелей %d W.', bat_state_normal='Нормальный', bat_state_low='Низкий', @@ -630,7 +640,8 @@ class InverterBot(Wrapper): # other notifications ac_mode_changed_notification='User <a href="tg://user?id=%d">%s</a> set AC mode to <b>%s</b>.', - osp_changed_notification='Пользователь <a href="tg://user?id=%d">%s</a> set output source priority: <b>%s</b>.', + osp_changed_notification='User <a href="tg://user?id=%d">%s</a> set output source priority: <b>%s</b>.', + osp_auto_changed_notification='Bot changed output source priority to <b>%s</b>. Reasons: battery voltage is %.1f V, solar input is %d W.', bat_state_normal='Normal', bat_state_low='Low', @@ -743,6 +754,7 @@ if __name__ == '__main__': monitor.set_battery_event_handler(monitor_battery) monitor.set_util_event_handler(monitor_util) monitor.set_error_handler(monitor_error) + monitor.set_osp_need_change_callback(osp_change_cb) setacmode(getacmode()) |