Merge branch 'master' of ch1p.io:homekit
This commit is contained in:
commit
ed8f8839af
@ -1,10 +1,3 @@
|
|||||||
from .monitor import (
|
from .monitor import InverterMonitor
|
||||||
ChargingEvent,
|
|
||||||
InverterMonitor,
|
|
||||||
BatteryState,
|
|
||||||
BatteryPowerDirection,
|
|
||||||
ACMode,
|
|
||||||
ACPresentEvent
|
|
||||||
)
|
|
||||||
from .inverter_wrapper import wrapper_instance
|
from .inverter_wrapper import wrapper_instance
|
||||||
from .util import beautify_table
|
from .util import beautify_table
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from enum import Enum, auto
|
from .types import *
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
from .inverter_wrapper import wrapper_instance as inverter
|
from .inverter_wrapper import wrapper_instance as inverter
|
||||||
@ -12,55 +12,6 @@ from ..config import config
|
|||||||
logger = logging.getLogger(__name__)
|
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:
|
def _pd_from_string(pd: str) -> BatteryPowerDirection:
|
||||||
if pd == 'Discharge':
|
if pd == 'Discharge':
|
||||||
return BatteryPowerDirection.DISCHARGING
|
return BatteryPowerDirection.DISCHARGING
|
||||||
@ -94,6 +45,8 @@ class InverterMonitor(Thread):
|
|||||||
battery_event_handler: Optional[Callable]
|
battery_event_handler: Optional[Callable]
|
||||||
util_event_handler: Optional[Callable]
|
util_event_handler: Optional[Callable]
|
||||||
error_handler: Optional[Callable]
|
error_handler: Optional[Callable]
|
||||||
|
osp_change_cb: Optional[Callable]
|
||||||
|
osp: Optional[OutputSourcePriority]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -102,12 +55,14 @@ class InverterMonitor(Thread):
|
|||||||
self.interrupted = False
|
self.interrupted = False
|
||||||
self.min_allowed_current = 0
|
self.min_allowed_current = 0
|
||||||
self.ac_mode = None
|
self.ac_mode = None
|
||||||
|
self.osp = None
|
||||||
|
|
||||||
# Event handlers for the bot.
|
# Event handlers for the bot.
|
||||||
self.charging_event_handler = None
|
self.charging_event_handler = None
|
||||||
self.battery_event_handler = None
|
self.battery_event_handler = None
|
||||||
self.util_event_handler = None
|
self.util_event_handler = None
|
||||||
self.error_handler = None
|
self.error_handler = None
|
||||||
|
self.osp_change_cb = None
|
||||||
|
|
||||||
# Currents list, defined in the bot config.
|
# Currents list, defined in the bot config.
|
||||||
self.currents = cfg.gen_currents
|
self.currents = cfg.gen_currents
|
||||||
@ -156,6 +111,12 @@ class InverterMonitor(Thread):
|
|||||||
|
|
||||||
self.min_allowed_current = min(allowed_currents)
|
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.
|
# Read data and run implemented programs every 2 seconds.
|
||||||
while not self.interrupted:
|
while not self.interrupted:
|
||||||
try:
|
try:
|
||||||
@ -167,6 +128,7 @@ class InverterMonitor(Thread):
|
|||||||
|
|
||||||
ac = gs['grid_voltage']['value'] > 0 or gs['grid_freq']['value'] > 0
|
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 = 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'])
|
v = float(gs['battery_voltage']['value'])
|
||||||
load_watts = int(gs['ac_output_active_power']['value'])
|
load_watts = int(gs['ac_output_active_power']['value'])
|
||||||
pd = _pd_from_string(gs['battery_power_direction'])
|
pd = _pd_from_string(gs['battery_power_direction'])
|
||||||
@ -177,7 +139,7 @@ class InverterMonitor(Thread):
|
|||||||
self.gen_charging_program(ac, solar, v, pd)
|
self.gen_charging_program(ac, solar, v, pd)
|
||||||
|
|
||||||
elif self.ac_mode == ACMode.UTILITIES:
|
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 not ac or pd != BatteryPowerDirection.CHARGING:
|
||||||
# if AC is disconnected or not charging, run the low voltage checking program
|
# 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,
|
def utilities_monitoring_program(self,
|
||||||
ac: bool, # whether AC is connected
|
ac: bool, # whether AC is connected
|
||||||
solar: bool, # whether MPPT is active
|
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: BatteryPowerDirection # current power direction
|
||||||
):
|
):
|
||||||
pd_event_send = False
|
pd_event_send = False
|
||||||
@ -204,6 +169,15 @@ class InverterMonitor(Thread):
|
|||||||
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED_SOLAR)
|
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED_SOLAR)
|
||||||
pd_event_send = True
|
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:
|
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_event_handler(ACPresentEvent.CONNECTED if ac else ACPresentEvent.DISCONNECTED)
|
||||||
self.util_ac_present = ac
|
self.util_ac_present = ac
|
||||||
@ -213,6 +187,7 @@ class InverterMonitor(Thread):
|
|||||||
if not pd_event_send and not solar:
|
if not pd_event_send and not solar:
|
||||||
if pd == BatteryPowerDirection.CHARGING:
|
if pd == BatteryPowerDirection.CHARGING:
|
||||||
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STARTED)
|
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STARTED)
|
||||||
|
|
||||||
elif pd == BatteryPowerDirection.DISCHARGING:
|
elif pd == BatteryPowerDirection.DISCHARGING:
|
||||||
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED)
|
self.charging_event_handler(ChargingEvent.UTIL_CHARGING_STOPPED)
|
||||||
|
|
||||||
@ -493,9 +468,15 @@ class InverterMonitor(Thread):
|
|||||||
def set_error_handler(self, handler: Callable):
|
def set_error_handler(self, handler: Callable):
|
||||||
self.error_handler = handler
|
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):
|
def set_ac_mode(self, mode: ACMode):
|
||||||
self.ac_mode = mode
|
self.ac_mode = mode
|
||||||
|
|
||||||
|
def notify_osp(self, osp: OutputSourcePriority):
|
||||||
|
self.osp = osp
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.interrupted = True
|
self.interrupted = True
|
||||||
|
|
||||||
@ -513,6 +494,7 @@ class InverterMonitor(Thread):
|
|||||||
'time_now': time.time(),
|
'time_now': time.time(),
|
||||||
'next_current_enter_time': self.next_current_enter_time,
|
'next_current_enter_time': self.next_current_enter_time,
|
||||||
'ac_mode': self.ac_mode,
|
'ac_mode': self.ac_mode,
|
||||||
|
'osp': self.osp,
|
||||||
'util_ac_present': self.util_ac_present,
|
'util_ac_present': self.util_ac_present,
|
||||||
'util_pd': self.util_pd.name,
|
'util_pd': self.util_pd.name,
|
||||||
'util_solar': self.util_solar
|
'util_solar': self.util_solar
|
||||||
|
55
src/home/inverter/types.py
Normal file
55
src/home/inverter/types.py
Normal file
@ -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'
|
@ -4,7 +4,6 @@ import re
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from enum import Enum
|
|
||||||
from inverterd import Format, InverterError
|
from inverterd import Format, InverterError
|
||||||
from html import escape
|
from html import escape
|
||||||
from typing import Optional, Tuple, Union
|
from typing import Optional, Tuple, Union
|
||||||
@ -20,12 +19,14 @@ from home.bot import (
|
|||||||
from home.inverter import (
|
from home.inverter import (
|
||||||
wrapper_instance as inverter,
|
wrapper_instance as inverter,
|
||||||
beautify_table,
|
beautify_table,
|
||||||
|
|
||||||
InverterMonitor,
|
InverterMonitor,
|
||||||
|
)
|
||||||
|
from home.inverter.types import (
|
||||||
ChargingEvent,
|
ChargingEvent,
|
||||||
ACPresentEvent,
|
ACPresentEvent,
|
||||||
BatteryState,
|
BatteryState,
|
||||||
ACMode
|
ACMode,
|
||||||
|
OutputSourcePriority
|
||||||
)
|
)
|
||||||
from home.api.types import BotType
|
from home.api.types import BotType
|
||||||
from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
@ -57,11 +58,6 @@ SETACMODE_STARTED, = range(1)
|
|||||||
SETOSP_STARTED, = range(1)
|
SETOSP_STARTED, = range(1)
|
||||||
|
|
||||||
|
|
||||||
class OutputSourcePriority(Enum):
|
|
||||||
SolarUtilityBattery = 'SUB'
|
|
||||||
SolarBatteryUtility = 'SBU'
|
|
||||||
|
|
||||||
|
|
||||||
def monitor_charging(event: ChargingEvent, **kwargs) -> None:
|
def monitor_charging(event: ChargingEvent, **kwargs) -> None:
|
||||||
args = []
|
args = []
|
||||||
is_util = False
|
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:
|
def full_status(ctx: Context) -> None:
|
||||||
status = inverter.exec('get-status', format=Format.TABLE)
|
status = inverter.exec('get-status', format=Format.TABLE)
|
||||||
ctx.reply(beautify_table(status))
|
ctx.reply(beautify_table(status))
|
||||||
@ -294,6 +302,7 @@ def setacmode(mode: ACMode):
|
|||||||
def setosp(sp: OutputSourcePriority):
|
def setosp(sp: OutputSourcePriority):
|
||||||
logger.debug(f'setosp: sp={sp}')
|
logger.debug(f'setosp: sp={sp}')
|
||||||
inverter.exec('set-output-source-priority', (sp.value,))
|
inverter.exec('set-output-source-priority', (sp.value,))
|
||||||
|
monitor.notify_osp(sp)
|
||||||
|
|
||||||
|
|
||||||
# /setacmode
|
# /setacmode
|
||||||
@ -554,6 +563,7 @@ class InverterBot(Wrapper):
|
|||||||
# other notifications
|
# other notifications
|
||||||
ac_mode_changed_notification='Пользователь <a href="tg://user?id=%d">%s</a> установил режим AC: <b>%s</b>.',
|
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_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_normal='Нормальный',
|
||||||
bat_state_low='Низкий',
|
bat_state_low='Низкий',
|
||||||
@ -630,7 +640,8 @@ class InverterBot(Wrapper):
|
|||||||
|
|
||||||
# other notifications
|
# other notifications
|
||||||
ac_mode_changed_notification='User <a href="tg://user?id=%d">%s</a> set AC mode to <b>%s</b>.',
|
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_normal='Normal',
|
||||||
bat_state_low='Low',
|
bat_state_low='Low',
|
||||||
@ -743,6 +754,7 @@ if __name__ == '__main__':
|
|||||||
monitor.set_battery_event_handler(monitor_battery)
|
monitor.set_battery_event_handler(monitor_battery)
|
||||||
monitor.set_util_event_handler(monitor_util)
|
monitor.set_util_event_handler(monitor_util)
|
||||||
monitor.set_error_handler(monitor_error)
|
monitor.set_error_handler(monitor_error)
|
||||||
|
monitor.set_osp_need_change_callback(osp_change_cb)
|
||||||
|
|
||||||
setacmode(getacmode())
|
setacmode(getacmode())
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user