import time
import ujson
import network
import json
import _thread
from time import sleep
from pump import PUMP
from environment_sensor import ENVIRONMENT_SENSOR
from cistern import CISTERN
from machine import Pin, PWM,
from oled import OLED
from ldr import LDR
from fan import FAN
from ground_sensor import GROUND_SENSOR
from mqtt_client import MQTT

#from umqtt.simple import MQTTClient

"""PARAMETERS DEFINITION"""

# MQTT Server parameters
MQTT_CLIENT_ID = "gruppo09"
MQTT_BROKER    = "test.mosquitto.org"
MQTT_USER      = ""
MQTT_PASSWORD  = ""

#MQTT topic to witch the client must publish informations
MQTT_GROUND_HUMIDITY = "SmartGreenhouse/Ground/Humidity"
MQTT_ENVIRONMENT_TEMP = "SmartGreenhouse/Environment/Temp"
MQTT_ENVIRONMENT_HUMIDITY = "SmartGreenhouse/Environment/Humidity"
MQTT_CISTERN_LEVEL = "SmartGreenhouse/Irrigation/Cistern"
MQTT_OUTSIDE_LUX = "SmartGreenhouse/External/Lum"
MQTT_FAN_SPEED = "SmartGreenhouse/Ventilation/Fan"
MQTT_PUMP_STATE = "SmartGreenhouse/Irrigation/Pump"

#List of the keys of Json object to public
KEYS = ["humidity", "temp", "humidity", "cistern_level", "light", "fan", "pump"]

#MQTT topic to which the client must subscribe
MQTT_SUBSCRIBE_TOPIC =  { 
    "MQTT_IRRIGATION_MANUALCONTROL" : b'SmartGreenhouse/Irrigation/ManualState',
    "MQTT_IRRIGATION_MANUALPUMP" : b'SmartGreenhouse/Irrigation/ManualPump',
    "MQTT_IRRIGATION_HUMIDITY" : b'SmartGreenhouse/Irrigation/Humidity',
    "MQTT_FAN_MANUALSPEED" : b'SmartGreenhouse/Ventilation/ManualFan',
    "MQTT_FAN_MANUALCONTROL" : b'SmartGreenhouse/Ventilation/ManualState',
    "MQTT_FAN_ACTIVATIONTEMP" : b'SmartGreenhouse/Ventilation/Temp',
    "MQTT_RESET_SYSTEM" : b'SmartGreenhouse/Restart'
}

#dict of value that comes from the sensors to publish on the respective topic: k = topic, v = value to publish on the topic 
MQTT_PUBLISH_VALUE = {
    MQTT_GROUND_HUMIDITY :  None,
    MQTT_ENVIRONMENT_TEMP :  None,
    MQTT_ENVIRONMENT_HUMIDITY :  None,
    MQTT_CISTERN_LEVEL:  None,
    MQTT_OUTSIDE_LUX :  None,
    MQTT_FAN_SPEED :  None,
    MQTT_PUMP_STATE : None
}

SENSORS_ACTUAL_VALUE = {
    MQTT_GROUND_HUMIDITY :  None,
    MQTT_ENVIRONMENT_TEMP :  None,
    MQTT_ENVIRONMENT_HUMIDITY :  None,
    MQTT_CISTERN_LEVEL:  None,
    MQTT_OUTSIDE_LUX :  None,
}

#dict of parameter that are setted based on the informations that comes from the broker and from the system computation
SYSTEM_PARAMS = {
    "CRITICAL_CISTERN_LEVEL" : 10,
    "HUMIDITY_CRITICAL_LEVEL" : 10,
    "MANUAL_IRRIGATION_CONTROL" : False,
    "MANUAL_IRRIGATION_PUMP": False,
    "MANUAL_FAN_SPEED" : 0,
    "MANUAL_FAN_CONTROL" : False,
    "BLOCK_IRRIGATION_FOR_CRITICAL_CISTERN_LEVEL": False,
    "RESTART_SYSTEM": False,
    "FAN_ACTIVATION_TEMP": 30,
    "PUMP_CICLE": False
}

#CISTERN parameters
CISTERN_TRIGGER_PIN = 26
CISTERN_ECHO_PIN = 25
CISTERN_HEIGHT = 30

#PUMP parameters
PUMP_PIN = 27

#ENVIRONMENT_SENSOR parameters
ENVIRONMENT_SENSOR_PIN = 4

#PHOTO_RESISTOR parameters
PHOTO_RESISTOR_PIN = 35
PHOTO_RESISTOR_MIN_VALUE = 0
PHOTO_RESISTOR_MAX_VALUE = 100

#LED_ZONE_1 parameters 
LED_ZONE_1_PIN = 13
LED_ZONE_1_FREQ = 50

#FAN parameters
FAN_PIN = 14
FAN_MIN_SPEED = 0
FAN_MAX_SPEED = 80
FAN_MAX_SPEED_DASHBOARD = 100
FAN_FREQ_VALUE = 50

#GROUND_SENSOR parameters
GROUND_SENSOR_PIN = 33
GROUND_SENSOR_MAX_READ = 2093
GROUND_SENSOR_MIN_READ = 462
GROUND_SENSOR_MIN_VALUE = 0
GROUND_SENSOR_MAX_VALUE = 100

#OLED_DISPLAY parameters
OLED_WIDTH = 128
OLED_HEIGHT = 64
OLED_SLC_PIN = 22
OLED_SDA_PIN = 32

#RESET_BTN parameters
RESET_BTN_PIN = 15

#BOUNCING BTN VAR
LAST = 0 

"""IMPLEMENTATION OF SYSTEM LOGIC"""

#INSTANTIATION OF THE SYSTEM OBJECTS
reset_btn = Pin(RESET_BTN_PIN, Pin.IN, Pin.PULL_DOWN)
cistern = CISTERN(CISTERN_TRIGGER_PIN, CISTERN_ECHO_PIN, CISTERN_HEIGHT)
pump = PUMP(PUMP_PIN)
environment_sensor = ENVIRONMENT_SENSOR(ENVIRONMENT_SENSOR_PIN)
photo_resistor = LDR(PHOTO_RESISTOR_PIN, PHOTO_RESISTOR_MIN_VALUE, PHOTO_RESISTOR_MAX_VALUE)
ground_sensor = GROUND_SENSOR(GROUND_SENSOR_PIN, GROUND_SENSOR_MIN_VALUE, GROUND_SENSOR_MAX_VALUE, GROUND_SENSOR_MAX_READ, GROUND_SENSOR_MIN_READ)
client = MQTT(MQTT_CLIENT_ID, MQTT_BROKER)
oled = OLED(OLED_WIDTH, OLED_HEIGHT, OLED_SLC_PIN, OLED_SDA_PIN)
fan = FAN(FAN_PIN, FAN_FREQ_VALUE, FAN_MAX_SPEED, FAN_MIN_SPEED)
led_zone_1 = PWM(Pin(LED_ZONE_1_PIN, Pin.OUT), freq=LED_ZONE_1_FREQ)
led_zone_1.duty(0)


#START THE CONNECTIVITY PROCEDURE

oled.print_booting_info()

#IMPORTED IN THE BOOT FILE
try:
    shift_point = 0
    print("Connecting to WiFi", end="")
    oled.print_line("Connecting", 0, 1, 1)
    oled.print_line_no_fill("to Wifi", 0, 10, 1)
    sta_if = network.WLAN(network.STA_IF)
    sta_if.active(True)
    sta_if.connect('Wokwi-GUEST', '')
    while not sta_if.isconnected():
        print(".", end="")
        oled.print_line_no_fill(".", 53 + shift_point, 10, 1)
        time.sleep(0.1)
        shift_point = shift_point + 6
    print(" Connected!")
    oled.print_line_no_fill("Connected!", 0, 20, 1)
except OSError as ex:
    print("Failure connecting to WiFi!!")
    oled.print_line_no_fill("Not Connected!", 0, 20, 1)

#DEFINE THE CALLBACK PROCEDURE FOR THE RECIVED MSG FROM THE BROKER
def subCallback(topic, msg):

    global SYSTEM_PARAMS, SYSTEM_PARAMS_BACKUP, MQTT_PUBLISH_VALUE_RESET, MQTT_PUBLISH_VALUE
    
    print(topic, msg)

    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_RESET_SYSTEM"]:
        if msg == b'true':
            SYSTEM_PARAMS["RESTART_SYSTEM"] = True
        else:
            SYSTEM_PARAMS["RESTART_SYSTEM"] = False

    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_IRRIGATION_MANUALCONTROL"]:
        if msg == b'true':
            SYSTEM_PARAMS["MANUAL_IRRIGATION_CONTROL"] = True
        elif msg == b'false':
            SYSTEM_PARAMS["MANUAL_IRRIGATION_CONTROL"] = False
    
    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_IRRIGATION_MANUALPUMP"]:
        if msg == b'true':
            SYSTEM_PARAMS["MANUAL_IRRIGATION_PUMP"] = True
        elif msg == b'false':
            SYSTEM_PARAMS["MANUAL_IRRIGATION_PUMP"] = False
    
    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_IRRIGATION_HUMIDITY"]:
        SYSTEM_PARAMS["HUMIDITY_CRITICAL_LEVEL"] = int(msg)

    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_FAN_MANUALCONTROL"]:
        if msg == b'true':
            SYSTEM_PARAMS["MANUAL_FAN_CONTROL"] = True
        elif msg == b'false':
            SYSTEM_PARAMS["MANUAL_FAN_CONTROL"] = False

    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_FAN_MANUALSPEED"]:
        SYSTEM_PARAMS["MANUAL_FAN_SPEED"] = int(msg)
    
    if topic == MQTT_SUBSCRIBE_TOPIC["MQTT_FAN_ACTIVATIONTEMP"]:
        SYSTEM_PARAMS["FAN_ACTIVATION_TEMP"] = int(msg)


# PROCEDURE OF CONNECTION TO THE MQTT SERVER
print("Connecting to MQTT server", end="")
shift_point = 0
oled.print_line("Connecting to", 0, 1, 1)
oled.print_line_no_fill("MQTT server", 0, 10, 1)
client.connect(MQTT_SUBSCRIBE_TOPIC, subCallback)
while shift_point < 28:
    print(".", end="")
    oled.print_line_no_fill(".", 85 + shift_point, 10, 1)
    time.sleep(0.1)
    shift_point = shift_point + 6
if client.get_conn_state():
    print(" Connected")
    oled.print_line_no_fill("Connected!", 0, 20, 1)
    oled.fill_clr()
else:
    print("Failure connecting to MQTT server!!")
    oled.print_line_no_fill("Not Connected!", 0, 20, 1)
    oled.fill_clr()
    raise OSException

#UTILITIES FUNCTIONS USED FOR THE TESTS
def print_dict(dic):
    for k, v in dic.items():
        if v != None:
            print(k, ": ", v)

def pump_cicle():
    while True:
        if SYSTEM_PARAMS["PUMP_CICLE"]:
            print(" Pump activated")
            pump.pump_cicle(1)
            sleep(1)

#MANAGE THE FAN ACTIVATION AND SPEED
def manage_fan():
    
    global SYSTEM_PARAMS, MQTT_PUBLISH_VALUE

    if SYSTEM_PARAMS["MANUAL_FAN_CONTROL"]:
        oled.print_line_no_fill("Manual Fan: ON", 0, 20, 1)
        fan.set_max_speed(FAN_MAX_SPEED_DASHBOARD) #BASED ON THE MAX VALUE OF THE DASHBOARD
        if SYSTEM_PARAMS["MANUAL_FAN_SPEED"] != None and isinstance(SYSTEM_PARAMS["MANUAL_FAN_SPEED"], int):
            oled.print_line_no_fill("Fan Speed: "+ str(SYSTEM_PARAMS["MANUAL_FAN_SPEED"]), 0, 30, 1)
            MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED] = ujson.dumps({"fan" : SYSTEM_PARAMS["MANUAL_FAN_SPEED"]})
            fan.set_speed(SYSTEM_PARAMS["MANUAL_FAN_SPEED"])
            try:
                client.publish(MQTT_FAN_SPEED, MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
        else:
            oled.print_line_no_fill("Fan Speed: "+ str(SYSTEM_PARAMS["MANUAL_FAN_SPEED"]), 0, 30, 1)
            fan.set_speed(0)
            MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED] = ujson.dumps({"fan" : SYSTEM_PARAMS["MANUAL_FAN_SPEED"] })
            try:
                client.publish(MQTT_FAN_SPEED, MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
    else:
        oled.print_line_no_fill("Manual Fan: OFF", 0, 20, 1)
        actual_temp = environment_sensor.get_last_temperature()
        if SYSTEM_PARAMS["FAN_ACTIVATION_TEMP"] != None and actual_temp > SYSTEM_PARAMS["FAN_ACTIVATION_TEMP"]:
            fan.set_max_speed(FAN_MAX_SPEED) #BASED ON THE MAX VALUE OF THE ENVIRONMENT SENSOR
            fan.set_speed(actual_temp)
            oled.print_line_no_fill("Fan Speed: "+ str(FAN_MAX_SPEED_DASHBOARD * actual_temp / FAN_MAX_SPEED), 0, 30, 1)
            MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED] = ujson.dumps({"fan" : FAN_MAX_SPEED_DASHBOARD * actual_temp / FAN_MAX_SPEED})
            try:
                client.publish(MQTT_FAN_SPEED, MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
        else:
            fan.set_speed(0)
            oled.print_line_no_fill("Fan Speed: 0", 0, 30, 1)
            MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED] = ujson.dumps({"fan": 0})
            try:
                client.publish(MQTT_FAN_SPEED, MQTT_PUBLISH_VALUE[MQTT_FAN_SPEED])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)

def manage_irrigation():

    global SYSTEM_PARAMS, MQTT_PUBLISH_VALUE, SENSORS_ACTUAL_VALUE

    if json.loads(SENSORS_ACTUAL_VALUE[MQTT_CISTERN_LEVEL])["cistern_level"] < SYSTEM_PARAMS["CRITICAL_CISTERN_LEVEL"]:
        print("Livello di cisterna critico, ricarica il serbatoio per proseguire l'irrigazione")
        oled.print_line_no_fill("Cistern Level: " + str(json.loads(SENSORS_ACTUAL_VALUE[MQTT_CISTERN_LEVEL])["cistern_level"]), 0, 20, 1)
        SYSTEM_PARAMS["BLOCK_IRRIGATION_FOR_CRITICAL_CISTERN_LEVEL"] = True
        #SYSTEM_PARAMS["PUMP_CICLE"] = False
        MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE] = ujson.dumps({"pump": 0})
        try:
            client.publish(MQTT_PUMP_STATE, MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE])
        except OSError as e:
                print("Error during the sensors data pubblication: ", e)
        pump.stop_pump()
    
    elif json.loads(SENSORS_ACTUAL_VALUE[MQTT_CISTERN_LEVEL])["cistern_level"] > SYSTEM_PARAMS["CRITICAL_CISTERN_LEVEL"] and SYSTEM_PARAMS["BLOCK_IRRIGATION_FOR_CRITICAL_CISTERN_LEVEL"]:
        print("Sistema di irrigazione ripristinato")
        SYSTEM_PARAMS["BLOCK_IRRIGATION_FOR_CRITICAL_CISTERN_LEVEL"] = False

    elif SYSTEM_PARAMS["MANUAL_IRRIGATION_CONTROL"]:
        oled.print_line_no_fill("Irrigation: Man", 0, 40, 1)
        if SYSTEM_PARAMS["MANUAL_IRRIGATION_PUMP"]:
            print("Starting pump...", end="")
            oled.print_line_no_fill("Pump: ON", 0, 50, 1)
            MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE] = ujson.dumps({"pump": 1})
            try:
                client.publish(MQTT_PUMP_STATE, MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
            #SYSTEM_PARAMS["PUMP_CICLE"] = True
            #pump_cicle()
            pump.start_pump()
        else:
            oled.print_line_no_fill("Pump: OFF", 0, 50, 1)
            MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE] = ujson.dumps({"pump": 0})
            try:
                client.publish(MQTT_PUMP_STATE, MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
            #SYSTEM_PARAMS["PUMP_CICLE"] = False
            pump.stop_pump()
    else:
        oled.print_line_no_fill("Irrigation: Aut", 0, 40, 1)
        if int(json.loads(SENSORS_ACTUAL_VALUE[MQTT_GROUND_HUMIDITY])["humidity"]) < SYSTEM_PARAMS["HUMIDITY_CRITICAL_LEVEL"]:
            print("Starting pump...", end="")
            oled.print_line_no_fill("Pump: ON", 0, 50, 1)
            MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE] = ujson.dumps({"pump": 1})
            try:
                client.publish(MQTT_PUMP_STATE, MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
            #SYSTEM_PARAMS["PUMP_CICLE"] = True
            #pump_cicle()
            pump.start_pump()
        else:
            oled.print_line_no_fill("Pump: OFF", 0, 50, 1)
            MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE] = ujson.dumps({"pump": 0})
            try:
                client.publish(MQTT_PUMP_STATE, MQTT_PUBLISH_VALUE[MQTT_PUMP_STATE])
            except OSError as e:
                print("Error during the sensors data pubblication: ", e)
            #SYSTEM_PARAMS["PUMP_CICLE"] = False
            pump.stop_pump()
#_thread.start_new_thread(pump_cicle, ())

def read_sensor_value():

    global MQTT_PUBLISH_VALUE

    print("Reading sensors values...")
    oled.print_line("sensors: ", 0, 1, 1)
    SENSORS_ACTUAL_VALUE[MQTT_ENVIRONMENT_TEMP] = environment_sensor.get_ujson_temp() 
    SENSORS_ACTUAL_VALUE[MQTT_ENVIRONMENT_HUMIDITY] = environment_sensor.get_ujson_humidity()
    SENSORS_ACTUAL_VALUE[MQTT_OUTSIDE_LUX] = photo_resistor.get_ujson_value()
    SENSORS_ACTUAL_VALUE[MQTT_CISTERN_LEVEL] = cistern.get_ujson_level()
    SENSORS_ACTUAL_VALUE[MQTT_GROUND_HUMIDITY] = ground_sensor.get_ujson_value()
    oled.print_line_no_fill("Read", 65, 1, 1)

def restart_procedure():

    global MQTT_PUBLISH_VALUE, SYSTEM_PARAMS, SENSORS_ACTUAL_VALUE
    global MQTT_GROUND_HUMIDITY
    global MQTT_ENVIRONMENT_TEMP
    global MQTT_ENVIRONMENT_HUMIDITY
    global MQTT_CISTERN_LEVEL
    global MQTT_OUTSIDE_LUX
    global MQTT_FAN_SPEED
    global MQTT_PUMP_STATE

    oled.print_line("Restarting the", 0, 1, 1)
    oled.print_line_no_fill("system", 0, 10, 1)
    shift_point = 0
    try:
        client.publish(MQTT_GROUND_HUMIDITY, ujson.dumps({"humidity" : 0}))
        client.publish(MQTT_ENVIRONMENT_TEMP, ujson.dumps({"temp" : 0}))
        client.publish(MQTT_ENVIRONMENT_HUMIDITY, ujson.dumps({"humidity" : 0}))
        client.publish(MQTT_CISTERN_LEVEL, ujson.dumps({"cistern_level" : 0}))
        client.publish(MQTT_OUTSIDE_LUX, ujson.dumps({"light" : 0}))
        client.publish(MQTT_FAN_SPEED, ujson.dumps({"fan" : 0}))
        client.publish(MQTT_PUMP_STATE,  ujson.dumps({"pump" : 0}))
    except OSError as e:
        print("Error during the sensors data pubblication: ", e)

    while shift_point < 28:
        oled.print_line_no_fill(".", 45 + shift_point, 10, 1)
        time.sleep(0.1)
        shift_point = shift_point + 6

    fan.set_max_speed(FAN_MAX_SPEED)
    SYSTEM_PARAMS = {
        "CRITICAL_CISTERN_LEVEL" : 10,
        "HUMIDITY_CRITICAL_LEVEL" : 10,
        "MANUAL_IRRIGATION_CONTROL" : False,
        "MANUAL_IRRIGATION_PUMP": False,
        "MANUAL_FAN_SPEED" : 0,
        "MANUAL_FAN_CONTROL" : False,
        "BLOCK_IRRIGATION_FOR_CRITICAL_CISTERN_LEVEL": False,
        "RESTART_SYSTEM": False,
        "FAN_ACTIVATION_TEMP": 30,
        "PUMP_CICLE": False
    }

    MQTT_PUBLISH_VALUE = {
        MQTT_GROUND_HUMIDITY :  None,
        MQTT_ENVIRONMENT_TEMP :  None,
        MQTT_ENVIRONMENT_HUMIDITY :  None,
        MQTT_CISTERN_LEVEL:  None,
        MQTT_OUTSIDE_LUX :  None,
        MQTT_FAN_SPEED :  None,
        MQTT_PUMP_STATE : None
    }

    SENSORS_ACTUAL_VALUE = {
        MQTT_GROUND_HUMIDITY :  None,
        MQTT_ENVIRONMENT_TEMP :  None,
        MQTT_ENVIRONMENT_HUMIDITY :  None,
        MQTT_CISTERN_LEVEL:  None,
        MQTT_OUTSIDE_LUX :  None,
    }


#DEFINING THE IRQ PROCEDURE FOR THE RESET BTN

def hard_reset(btn_reset):
    global LAST, SYSTEM_PARAMS
    current = time.ticks_ms()
    delta = time.ticks_diff(current, LAST)
    if delta < 200:
        return
    last = current
    SYSTEM_PARAMS["RESTART_SYSTEM"] = True


reset_btn.irq(trigger=Pin.IRQ_RISING, handler=hard_reset)


#MAIN LOOP
while True:

    if SYSTEM_PARAMS["RESTART_SYSTEM"]:
        restart_procedure() 

    else:
        try:
            read_sensor_value()
        except OSError as e:
            print("Error while reading the sensors values:", e)
        
        try:
            MQTT_PUBLISH_VALUE = client.publish_new_sensor_value(MQTT_PUBLISH_VALUE, SENSORS_ACTUAL_VALUE)
        except OSError as e:
            print("Error during the sensors data pubblication: ", e)
        
        try:
            print("Checking messages from the broker...")
            oled.print_line_no_fill("broker msg: ", 0, 10, 1)
            client.check_msg()
            oled.print_line_no_fill("Check", 87, 10, 1)
        except OSError as e:
            oled.print_line_no_fill("Fail", 60, 10, 1)
            print("Checking masseges from the broker fail: ", e)

        manage_fan()
        
        manage_irrigation()





$abcdeabcde151015202530354045505560fghijfghij