#!/usr/bin/env /usr/bin/python3

"""
smartHomeService.py
08.12.2021
"""

# PACKAGES
import logging
from logging.handlers import RotatingFileHandler
import time
import mysql.connector
from mysql.connector import errorcode
import datetime
from mylib2 import *
from os.path import *
import json
# import socket
# import base64
import random
import requests
from libShelly import *

# KONSTANTEN
BMELDER = 114 # Bewegungsmelder
WOMOSTB = 104 # Womo Starterbatterie
TSTEP = 3     # Zeitschritt 3 min

# KLASSEN UND FUNKTIONEN
def tminhhmm (tmin):
    return ('{}:{}'.format(int(tmin)//60, int(tmin)%60))


class Geraete():
    liste = {}

    def __init__(self, shelly):
        self.shelly=shelly
        self.readdb()

    def __str__(self):
        return(str(self.liste))

    def readdb(self):
        self.liste.clear()
        QUERY = 'SELECT ID, Typ, Name, Raum FROM Geraet'
        SPALTEN = ['ID', 'Typ', 'MAC', 'Name', 'Raum']
        homedb.connect()
        result = homedb.read_all(QUERY)
        for row in result:
            g = Geraet(id=row[0], typ=row[1], name=row[2], raum=row[3], shelly=self.shelly)
            #logger.debug(g)
            self.liste.update(g.dict())


class ZeitPlan():
    def __init__(self, shelly):
        self.zeitplan = {} # {Zeitpunkt (min seit 0:00) : Aktion}
        self.geraete = Geraete(shelly)
        self.talt = 24*60 # Mitternacht
        self.sa = 6*60
        self.su = 20*60
        self.lux = 999 # sehr Hell
        self.shelly=shelly
        #Extra-Geräte
        self.bmelder = ShellyMotion(BMELDER, 'Bewegungsmelder', 'Haus', self.shelly)
        logger.debug('bmelder: {}'.format(self.bmelder))
        self.womostb = ShellyUni(WOMOSTB, 'Starterbatterie', 'Womo', self.shelly)
        logger.debug('womostb: {}'.format(self.womostb))
    # ZeitPlan.__init__()

    def reset(self):
        # Löschen
        self.zeitplan.clear()
        # Geräte
        self.geraete.readdb()
        # Sonnenauf-/untergang
        self._astro()
    # Zeitplan.reset()

    def add(self, tmin, emin, id, kdo, luxmin = 0, luxmax = 0): 
        while self.zeitplan.get(str(tmin)) != None:
            tmin += 1;
        self.zeitplan.update({str(tmin):{'id':id, 'ende':emin, 'luxmin':luxmin, 'luxmax':luxmax, 'kdo':kdo, 'done':False}})
        logger.info('{}({}): {}'.format(tmin, tminhhmm(tmin), self.zeitplan[str(tmin)]))
    # ZeitPlan.add()

    def _astro(self):
        wreq = "https://weather.ls.hereapi.com/weather/1.0/report.json?apiKey=3IoPwljk9X_EgEs3HjstZDsMM2AqA3Lea1nF2FL6Vq8&product=forecast_astronomy&&latitude=51.297&longitude=12.4317&language=de"
        wrsp = requests.get(wreq)
        if (wrsp.status_code != 200):
            logger.error("wrsp.status_code: {} {}".format(wrsp.status_code, wreq))
        else:
            logger.debug("wrsp.status_code: {} {}".format(wrsp.status_code, wreq))
            w = json.loads(wrsp.text)['astronomy']['astronomy'][0]
            sa = w.get('sunrise')
            if sa != None:
                self.sa = convertHHMM(convert24(sa), self.sa, self.su)
            su = w.get('sunset')
            if sa != None:
                self.su = convertHHMM(convert24(su), self.sa, self.su)
            logger.info('SA:{} SU:{}'.format(sa, su))
    # ZeitPlan.astro()


    def readdb(self):
        ###
        # ZeitPlan aus DB lesen und in 24h-Programm schreiben
        ###
        self.reset() # alles neu aufbauen

        # Szenen und Aktionen
        QUERY = 'SELECT SA.geraet, T.* FROM `SzeneAktion` as SA, `Szene` as S, `Timer` as T WHERE S.ID = SA.szene and SA.timer = T.ID and S.aktiv = 1'
        SPALTEN = ['geraet', 'id', 'Name', 'Tag', 'beginn', 'beginn_rand', 'luxmin', 'ende', 'ende_rand', 'luxmax', 'on_min', 'on_max', 'off_min', 'off_max', 'kdo1', 'p1min', 'p1max', 'kdo2', 'p2min', 'p2max']

        homedb.connect()
        result = homedb.read_all(QUERY)
        timer = [dict(zip(SPALTEN, row)) for row in result]
        logger.debug('timer: {}'.format(timer))
        tnow = datetime.datetime.now()
        tmin = int(tnow.hour) * 60 + int(tnow.minute) # min seit 00:00

        # alle Timer auswerten
        for t in timer:
            # Beginn
            logger.debug('Timer: {}'.format(t['geraet']))
            begm = convertHHMM(t['beginn'], self.sa, self.su) + int(random.uniform(-t['beginn_rand'], t['beginn_rand']))
            begm = max(begm, tmin) # frühestens jetzt
            # Ende
            endm = convertHHMM(t['ende'], self.sa, self.su) + int(random.uniform(-t['ende_rand'], t['ende_rand']))
            logger.debug('{}({}) - {}({})'.format(t['beginn'], tminhhmm(begm), t['ende'], tminhhmm(endm)))

            # Endezeit vorbei?
            if (begm >= endm):
                logger.debug('Endezeit vorbei')
                continue # keine Aktion

            # Helligkeit?
            luxmin = t.get('luxmin', 0)
            luxmax = t.get('luxmax', 0)
            if luxmin > 0 or luxmax > 0: # Geamtintervall, Helligkeit berücksichtigen
                self.add(begm, endm, t['geraet'], {'EIN': None}, luxmin=luxmin, luxmax=luxmax) 
                continue

            # Ein/Aus, ggf. Intervalle, Helligkeit nicht berücksichtigen
            on = True
            while begm < endm:
                if on: # Intervall AN
                    d = int(random.uniform(t['on_min'], t['on_max'])) # Dauerss
                    if d <= 0: # ganze Periode
                        d = endm - begm
                    kdos = {'EIN': None, 'TIMER': int(d * 60)}
                    # Zusatzkommando 1
                    k = t.get('kdo1')
                    if k != None:
                        pmin = t.get('p1min')
                        if pmin is None: 
                            logger.error('{}: Pmin zu {} fehlt. Ignoriert'.format(t['geraet'], k))
                        else:
                            pmax = t.get('p1max')
                            if pmax is None: 
                                logger.error('{}: Pmax zu {} fehlt. Ignoriert'.format(t['geraet'], k))
                            else:
                                kdos.update({k: int(random.uniform(pmin, pmax))})
                    # Zusatzkommando 2
                    k = t.get('kdo2')
                    if k != None:
                        pmin = t.get('p2min')
                        if pmin is None: 
                            logger.error('{}: Pmin zu {} fehlt. Ignoriert'.format(t['geraet'], k))
                        else:
                            pmax = t.get('p2max')
                            if pmax is None: 
                                logger.error('{}: Pmax zu {} fehlt. Ignoriert'.format(t['geraet'], k))
                            else:
                                kdos.update({k: int(random.uniform(pmin, pmax))})
                    self.add(begm, endm, t['geraet'], kdos, t.get('luxmin', 0), t.get('luxmax', 0))
                    begm += d
                    on = False
                else: # Intervall AUS
                    d = int(random.uniform(t['off_min'], t['off_max']))
                    if d > 0:
                        begm += d
                    else:
                        begm = endm
                    on = True
        logger.debug('ZeitPlan: {}'.format(self.zeitplan))
    # ZeitPlan.read()
        
    # Helligkeit Bewegungsmelder
    def helligkeit(self):
        ret, res = self.bmelder.status(attr='lux', logger=logger)
        logger.debug('ret: {}, res:{}'.format(ret, res))
        return res
    # Zeitplan.helligkeit()
            
    # Akku
    def akkucheck(self):
        ret, res = self.bmelder.status(attr='bat', logger=logger)
        logger.debug('ret: {}, res:{}'.format(ret, res))
        if res < 10: # %
            logger.info('Akku Bewegungsmelder!: {}'.format(akku))
            mail.send('HomeServer: Akku', 'Bewegungsmelder: Akku schwach {}%'.format(res))
        return res
    # ZeitPlan.akkucheck()
            
    # Starterbatterie
    def stbcheck(self):
        volt = self.womostb.voltage(logger=logger)
        if volt < 12.1: # %
            logger.info('Starterbatterie Spannung!: {}'.format(volt))
            mail.send('Womo: Starterbatterie', 'Womo Starterbatterie schwach {} V'.format(volt))
        # return res
    # ZeitPlan.stbcheck()

    def check(self):
        tnow = datetime.datetime.now()
        tmin = int(tnow.hour) * 60 + int(tnow.minute)
        #logger.debug('now: {} ({})'.format(tmin, tminhhmm(tmin)))

        # initialisieren, tägliche Prozesse
        if self.talt > tmin: # gerade eben nach Mitternacht
            # neu einlesen
            self.readdb()
            # Bewegungsmelder Akku
            self.akkucheck()
            # Womo-Starterbatterie
            self.stbcheck()

        
        self.talt = tmin
        for t,a in self.zeitplan.items():
            if a.get('done'): # erledigt
                # logger.debug('{}: erledigt ({}) '.format(a.get('id'), t))
                continue
            # Gerät ermitteln
            g = self.geraete.liste.get(str(a.get('id')))
            if g == None:
                logger.error('Gerät {} unbekannt'.format(a.get('id')))
                continue
            if a.get('ende') > 0 and tmin > a.get('ende'):
                # logger.debug('{}: vorbei ({})'.format(a.get('id'), t))
                a['done'] = True # ggf. noch ausschalten
                if g.kommando({'AUS': None}):
                    logger.debug('{} AUS Ende'.format(g.name))
                    continue
            elif int(t) <= tmin: # es ist Zeit
                k = a.get('kdo')
                #logger.debug('KDO: {}'.format(k))
                hell = -1
                luxmin = a.get('luxmin')
                luxmax = a.get('luxmax')
                if luxmin > 0 or luxmax > 0: # Hellichkeitsgesteuert
                    hell = self.helligkeit()
                    if hell < 0:
                        continue
                    if k == {'EIN':None}: # dunkel?
                        if hell < luxmin:
                            self.zeitplan.update({str(t):{'id':a.get('id'), 'ende':a.get('ende'), 'luxmin':luxmin, 'luxmax':luxmax, 'kdo':{'AUS': None}, 'done':False}})
                        else:
                            continue
                    if k == {'AUS': None}: # hell?
                        if hell > luxmax:
                            self.zeitplan.update({str(t):{'id':a.get('id'), 'ende':a.get('ende'), 'luxmin':luxmin, 'luxmax':luxmax, 'kdo':{'EIN': None}, 'done':False}})
                        else:
                            continue
                #logger.debug('luxmin: {}'.format(luxmin))
                if g.kommando(k):
                    logger.debug('{} {} - {} lux'.format(g.name, k, hell))
                    if hell < 0:
                        a['done'] = True
            #else:
                #logger.debug('t:{} tmin:{}'.format(int(t), tmin))
    # ZeitPlan.check()


# <<< START ---------------------------------------------------------------
# logging
logger = startlogging('homesv', logging.DEBUG)

# Initialisieren
# Datenbank
prz = prozess('HOME', 120)
logger.info('===== START ===== pid:{}'.format(prz.pid))
homedb = mydb()
mail = Mail('HomeServer <homeserver@muehmel-net.de>', 'HomeServerLog <log.homeserver@muehmel-net.de>')
mail.send('HomeServer: START', 'Homeserver: Prozess neu gestartet')

shelly = Shelly('192.168.168.')
zeitplan = ZeitPlan(shelly)

while True:  # immer
    prz.start(False)
    # wms = aktualisieren()
    zeitplan.check()
    prz.sleep(TSTEP * 60)
# while
# ENDE
