diff --git a/apps/predbat/hass.py b/apps/predbat/hass.py new file mode 100644 index 00000000..0bc736de --- /dev/null +++ b/apps/predbat/hass.py @@ -0,0 +1,92 @@ +import io +import yaml +import sys +import asyncio +import predbat +import time +from datetime import datetime, timedelta +import logging +import logging.config +from multiprocessing import Pool, cpu_count, set_start_method + + +async def main(): + print("Starting Standalone Predbat") + try: + p_han = predbat.PredBat() + p_han.initialize() + except Exception as e: + print("Failed to start predbat {}".format(e)) + return + + # Runtime loop + print("Started Predbat run loop") + while True: + time.sleep(1) + p_han.timer_tick() + + +if __name__ == "__main__": + import hass as hass + + print("Starting Predbat standalone") + set_start_method("fork") + asyncio.run(main()) + sys.exit(0) + + +class Hass: + def log(self, msg): + message = "{}: {}\n".format(datetime.now(), msg) + self.logfile.write(message) + print(message, end="") + + def create_task(self, task): + return asyncio.create_task(task) + + def __init__(self): + """ + Start Predbat + """ + self.args = {} + self.run_list = [] + self.tasks = [] + + self.logfile = open("predbat.log", "a") + + # Open YAML file apps.yaml and read it + print("Loading apps.yaml") + with io.open("apps.yaml", "r") as stream: + try: + config = yaml.safe_load(stream) + self.args = config["pred_bat"] + except yaml.YAMLError as exc: + print(exc) + sys.exit(1) + + if "ha_url" not in self.args: + print("Error: ha_url not found in apps.yaml") + sys.exit(1) + if "ha_key" not in self.args: + print("Error: ha_key not found in apps.yaml") + sys.exit(1) + + def run_every(self, callback, next_time, run_every, **kwargs): + print("Run every triggered next time {} every {}".format(next_time, run_every)) + self.run_list.append({"callback": callback, "next_time": next_time, "run_every": run_every, "kwargs": kwargs}) + return True + + def timer_tick(self): + now = datetime.now() + print("Timer tick at {}".format(now)) + for task in self.tasks: + if task.done(): + print("Task done") + self.tasks.remove(task) + for item in self.run_list: + if now > item["next_time"]: + print("Running callback next time {} and now is {}".format(item["next_time"], now)) + self.tasks.append(self.create_task(item["callback"](None))) + while now > item["next_time"]: + item["next_time"] += item["run_every"] + print("Task completed next time {}".format(item["next_time"])) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 2ed1a0c6..f3a21342 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -3,25 +3,29 @@ see Readme for information """ - import copy import os import re import time import math +from datetime import datetime, timedelta # fmt off # pylint: disable=consider-using-f-string # pylint: disable=line-too-long # pylint: disable=attribute-defined-outside-init -from datetime import datetime, timedelta -import adbase as ad -import appdaemon.plugins.hass.hassapi as hass +# Import AppDaemon or our standalone wrapper +try: + import adbase as ad + import appdaemon.plugins.hass.hassapi as hass +except: + import hass as hass + import pytz import requests import yaml -from multiprocessing import Pool, cpu_count +from multiprocessing import Pool, cpu_count, set_start_method import asyncio import aiohttp from aiohttp import web @@ -2827,7 +2831,7 @@ def create_entity(self, entity_name, value, uom=None, device_class="None"): entity_id = f"sensor.{prefix}_{self.inverter_type}_{self.id}_{entity_name}" - if not self.base.entity_exists(entity_id): + if self.base.get_state_wrapper(entity_id) is None: attributes = { "state_class": "measurement", } @@ -13928,7 +13932,6 @@ def update_time(self, print=True): self.minutes_to_midnight = 24 * 60 - self.minutes_now self.log("--------------- PredBat - update at {} with clock skew {} minutes, minutes now {}".format(now_utc, skew, self.minutes_now)) - @ad.app_lock def update_pred(self, scheduled=True): """ Update the prediction state, everything is called from here right now @@ -15015,10 +15018,9 @@ def initialize(self): run_every = RUN_EVERY * 60 now = self.now - self.ha_interface = HAInterface(self) - try: self.reset() + self.ha_interface = HAInterface(self) self.sanity() self.ha_interface.update_states() self.auto_config() @@ -15088,7 +15090,6 @@ async def terminate(self): self.pool = None self.log("Predbat terminated") - @ad.app_lock def update_time_loop(self, cb_args): """ Called every 15 seconds @@ -15109,7 +15110,6 @@ def update_time_loop(self, cb_args): self.prediction_started = False self.prediction_started = False - @ad.app_lock def run_time_loop(self, cb_args): """ Called every N minutes @@ -15173,6 +15173,8 @@ def __init__(self, base): else: self.log("Info: Connected to Home Assistant at {}".format(self.ha_url)) self.base.create_task(self.socketLoop()) + self.websocket_active = True + self.log("Info: Web Socket task started") async def socketLoop(self): """ @@ -15202,7 +15204,6 @@ async def socketLoop(self): sid += 1 self.log("Info: Web Socket active") - self.websocket_active = True async for message in websocket: if message.type == aiohttp.WSMsgType.TEXT: