from waterlevel_sensor import WaterLevelSensor
import bme280
import sh1106
from homie.constants import BOOLEAN, FALSE, TRUE, FLOAT
from homie.property import HomieProperty
from homie.node import HomieNode
from homie.device import HomieDevice, await_ready_state
from machine import Pin, ADC, I2C
import settings

import uasyncio as asyncio
from time import ticks_ms, ticks_add, ticks_diff

import gc

class PlantNode(HomieNode):

    def __init__(
            self,
            name="Planze",
            pin_watering=Pin(13, Pin.OUT, value=0),
            pin_moisture=Pin(34),
            pin_water_tank=Pin(23),
            i2c=I2C(scl=Pin(18), sda=Pin(19)),
            interval=60*5,
            interval_watering=0.1):
        super().__init__(id="plant", name=name, type="watering")
        self.i2c = i2c
        self.display = sh1106.SH1106_I2C(
            i2c=i2c, width=128, height=64)

        # Update Interval
        self.interval = interval
        self.interval_watering = interval_watering
        self.interval_changed = False
        self.property_interval = HomieProperty(
            id="update_interval",
            name="Aktualisierungsrate",
            datatype=FLOAT,  # TODO ISO8601
            settable=True,
            on_message=self._set_interval,
            unit="s",
        )
        self.add_property(self.property_interval)

        # WaterLevelSensor
        self.waterlevel_sensor = WaterLevelSensor()
        self.property_waterlevel = HomieProperty(
            id="waterlevel",
            name="Wassertankstand",
            datatype=FLOAT,
            unit="L",
        )
        self.add_property(self.property_waterlevel)
        self.property_waterlevel_percent = HomieProperty(
            id="waterlevel_percent",
            name="Wassertankstand [%]",
            datatype=FLOAT,
            format="0.00:100.00",
            unit="%",
        )
        self.add_property(self.property_waterlevel_percent)
        # self.property_waterlevel_max_value = HomieProperty(
        #    id="waterlevel_max",
        #    name="Wassertankstandsensor Max-Wert",
        #    settable=True,
        #    datatype=FLOAT,
        #    default=1,
        #    unit="#",
        #    on_message=self._set_waterlevel_max_value
        # )
        # self.add_property(self.property_waterlevel_max_value)
        # self.property_waterlevel_min_value = HomieProperty(
        #    id="waterlevel_min",
        #    name="Wassertankstandsensor Min-Wert",
        #    settable=True,
        #    datatype=FLOAT,
        #    default=0,
        #    unit="#",
        #    on_message=self._set_waterlevel_min_value
        # )
        # self.add_property(self.property_waterlevel_min_value)
        self.property_waterlevel_volume_liter = HomieProperty(
            id="waterlevel_volume_max",
            name="Wassertankgröße",
            settable=True,
            datatype=FLOAT,
            unit="L",
            on_message=self._set_waterlevel_volume
        )
        self.add_property(self.property_waterlevel_volume_liter)

        # Moisture
        self.adc = ADC(pin_moisture)
        self.adc.atten(ADC.ATTN_11DB)
        self.property_moisture = HomieProperty(
            id="moisture",
            name="Feuchte",
            datatype=FLOAT,
            format="0.00:100.00",
            unit="%",
        )
        self.add_property(self.property_moisture)

        # BMP280
        self.bmp280 = bme280.BME280(i2c=self.i2c)
        self.property_temerature = HomieProperty(
            id="temperature",
            name="Temperatur",
            datatype=FLOAT,
            unit="°C",
        )
        self.add_property(self.property_temerature)
        self.property_pressure = HomieProperty(
            id="pressure",
            name="Druck",
            datatype=FLOAT,
            unit="Pa",
        )
        self.add_property(self.property_pressure)

        # Watering Motor
        self.pin_watering_motor = pin_watering
        self.pin_watering_motor.init(mode=Pin.OUT, value=0)
        self.property_water_power = HomieProperty(
            id="power",
            name="Bewässerung",
            settable=True,
            datatype=BOOLEAN,
            default=FALSE,
            on_message=self.toggle_motor,
        )
        self.add_property(self.property_water_power)

        self.property_watering_max_duration = HomieProperty(
            id="watering_duration_max",
            name="Bewässerungszeit",
            settable=True,
            datatype=FLOAT,
            default=3,
            unit="s",
        )
        self.add_property(self.property_watering_max_duration)

        asyncio.create_task(self.update_data())

    @await_ready_state
    async def update_data(self):
        while True:
            self.property_moisture.value = "{:1.2f}".format(
                (4096 - self.adc.read()) / 40.96)

            self.property_waterlevel_percent.value = "{:1.0f}".format(
                self.waterlevel_sensor.level_percent)
            self.property_waterlevel.value = "{:1.2f}".format(
                self.waterlevel_sensor.level)
            self.property_waterlevel_volume_liter.value = "{:1.4f}".format(
                self.waterlevel_sensor.volume)

            self.property_pressure.value = "{:1.0f}".format(
                self.bmp280.pressure * 100)  # hPa = 100 Pa
            self.property_temerature.value = "{:1.2f}".format(
                self.bmp280.temperature)

            # TODO ISO8601 self.property_interval.value = "PT{:1.3f}S".format(self.interval)
            self.property_interval.value = "{:1.3f}".format(self.interval)

            # We don't simply wait the update interval, as it can change while waiting.
            last_update = ticks_ms()
            wait_till = ticks_add(last_update, int(self.interval * 1000.0))
            while ticks_diff(ticks_ms(), wait_till) < 0:
                watering = self.pin_watering_motor.value() == 1
                if watering or self.interval_changed:
                    self.interval_changed = False
                    wait_till = ticks_add(
                        last_update,
                        int(self.interval if not watering else self.interval_watering) * 1000)
                sleep_for = min(int(self.interval_watering * 1000.0),
                                ticks_diff(wait_till, ticks_ms()))
                await asyncio.sleep_ms(sleep_for)

    def toggle_motor(self, topic, payload, retained):
        ONOFF = {FALSE: 0, TRUE: 1}
        v = ONOFF[payload]
        self.pin_watering_motor(v)
        if v == 1:
            asyncio.create_task(self.stop_motor())

    def stop_motor(self):
        await asyncio.sleep_ms(
            int(float(self.property_watering_max_duration.value) * 1000))
        self.pin_watering_motor.value(0)
        self.property_water_power.value = FALSE

    def _set_waterlevel_min_value(self, topic, payload, retained):
        self.waterlevel_sensor.value_min = float(payload)

    def _set_waterlevel_max_value(self, topic, payload, retained):
        self.waterlevel_sensor.value_max = float(payload)

    def _set_waterlevel_volume(self, topic, payload, retained):
        self.waterlevel_sensor.volume = float(payload)

    def _set_interval(self, topic, payload, retained):
        self.interval = float(payload)
        self.interval_changed = True


def main():
    global plantNode
    # Homie device setup
    plantNode = PlantNode()
    homie = HomieDevice(settings)
    homie.add_node(plantNode)

    # run forever
    homie.run_forever()


if __name__ == "__main__":
    main()
