From 3dbda1a2938f9c4ea91267be8d384aaffbf2301a Mon Sep 17 00:00:00 2001 From: Manuel Kamper Date: Tue, 16 Apr 2024 18:29:19 +0200 Subject: [PATCH] main.py aktualisiert --- main.py | 808 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 404 insertions(+), 404 deletions(-) diff --git a/main.py b/main.py index c67a59d..d3b7997 100644 --- a/main.py +++ b/main.py @@ -1,405 +1,405 @@ -import rp2, re, os -import TimeUtils, Logger, Networking, NTP, OTA -import uasyncio as asyncio -import utime as time -import usocket as socket -from machine import ADC, Pin, Timer - -from secrets import secrets -from configs import configs - -rp2.country(configs['country']) -version = "0.8" - -TimeUtils = TimeUtils.TimeUtils() -Logger = Logger.Logger(configs['log_housekeeping_days']) -Networking = Networking.Networking(Logger, secrets['ssid'], secrets['pw']) -NTP = NTP.NTP(Logger) -ota_host = configs['ota_host'] -project_name = "watering" -filenames = ["configs.py", "Logger.py", "main.py", "Networking.py", "NTP.py", "OTA.py", "secrets.py", "TimeUtils.py"] - -boottime = time.time() -pumpRelais = Pin(15, Pin.OUT, value=0) #gpio15 -heartbeatLed = Pin(0, Pin.OUT, value=0) #gpio0 -wifiLed = Pin(1, Pin.OUT, value=0) #gpio1 -autoLed = Pin(2, Pin.OUT, value=0) #gpio2 -pumpLed = Pin(3, Pin.OUT, value=0) #gpio3 -tempSensor = ADC(4) #internal temperature sensor -soilSensor1 = ADC(0) #gpio26 -soilSensor2 = ADC(1) #gpio27 -soilSensor3 = ADC(2) #gpio28 -waterLevelSensor = Pin(5, Pin.IN, Pin.PULL_UP) #gpio5 -pumpState = False -autoState = False -soilDry = False -waterLevel = False -wateringPulseState = False -temperature = 0.0 -lastSoil1 = 0 -lastSoil2 = 0 -lastSoil3 = 0 -lastWateringPulse = 0 - -# Checks if string is integer -def IsInt(possibleint): - try: - int(possibleint) - except: - return False - else: - return True - -# Reboots the Pico W (f.e. in case of an error) -def Reboot(): - Logger.LogMessage("Performing Reboot") - machine.reset() - -# Calculates the uptime in hours and minutes -def Uptime(): - global boottime - seconds = (time.time() - boottime) % (24 * 3600) - hours = seconds // 3600 - seconds %= 3600 - minutes = seconds // 60 - seconds %= 60 - return "%d:%02d" % (hours, minutes) - -# Turns on the watering pump -def WateringOn(): - global pumpState, waterLevel - if (waterLevel): - pumpRelais.on() - pumpLed.on() - pumpState = True - Logger.LogMessage("Pump turned on") - else: - pumpRelais.off() - pumpLed.off() - pumpState = False - Logger.LogMessage("Could not turn on the pump - water level low!") - -# Turns off the watering pump -def WateringOff(value = ""): - global pumpState, wateringPulseState - pumpRelais.off() - pumpLed.off() - pumpState = False - wateringPulseState = False - Logger.LogMessage("Pump turned off") - -# Returns the state of the watering pump -def WateringState(): - global pumpState - if (pumpState): - return "on" - else: - return "off" - -# Reads the onboard temperature sensor's value -def ReadTemp(): - global temperature - temperature = 27 - ((tempSensor.read_u16() * (3.3 / (65535))) - 0.706) / 0.001721 - -# Turns on the automatic watering mode and writes file to set automode on reboot -def AutoModeOn(): - global autoState - if not "auto" in os.listdir(): - file = open("auto","w") - file.write("auto") - file.close() - autoLed.on() - autoState = True - Logger.LogMessage("Automatic mode turned on") - -# Turns off the automatic watering mode and removes file for automode on reboot -def AutoModeOff(): - global autoState - if "auto" in os.listdir(): - os.remove("auto") - autoLed.off() - autoState = False - Logger.LogMessage("Automatic mode turned off") - -# Returns the state of the automatic watering mode -def AutoModeState(): - global autoState - if (autoState): - return "on" - else: - return "off" - -# Turns on the watering pump for pulse_duration seconds and turns it off after that -def WateringPulse(): - global waterLevel, wateringPulseState, lastWateringPulse - if (waterLevel): - WateringOn() - lastWateringPulse = time.time() - wateringPulseState = True - timer3 = Timer(period=(1000 * configs["pulse_duration"]), mode=Timer.ONE_SHOT, callback=WateringOff) - Logger.LogMessage("Triggered watering pulse") - else: - Logger.LogMessage("Could not trigger watering pulse - Water level low!") - -# Reads the soil moisture sensors and triggers watering pulse if automatic mode is enabled -def ReadSoil(): - global soilDry, autoState, lastSoil1, lastSoil2, lastSoil3 - read1 = soilSensor1.read_u16() - read2 = soilSensor2.read_u16() - read3 = soilSensor3.read_u16() - Logger.LogSoil(str(read1) + "," + str(read2) + "," + str(read3) + ",") - lastSoil1 = read1 - lastSoil2 = read2 - lastSoil3 = read3 - # eliminate false readings - if (lastSoil1 > 25000): - lastSoil1 = 100 - if (lastSoil2 > 25000): - lastSoil2 = 100 - if (lastSoil3 > 25000): - lastSoil3 = 100 - if ((read1 + read2 + read3) / 3 <= configs["soil_dry_value"]): - soilDry = False - Logger.LogMessage("Soil is wet") - else: - soilDry = True - Logger.LogMessage("Soil is dry!") - if (autoState): - Logger.LogMessage("Triggering watering pulse due to automatic mode") - WateringPulse() - -# Returns if soil is dry or wet -def SoilState(): - global soilDry - if (soilDry): - return "dry" - else: - return "wet" - -# Returns the water level state -def WaterLevelState(): - global waterLevel, pumpState - if (waterLevel): - return "full" - else: - return "empty" - -# Reads the water level sensor -def ReadWaterLevel(): - global waterLevel - if (waterLevelSensor.value() == 0): - waterLevel = True - else: - waterLevel = False - if (pumpState): - Logger.LogMessage("Water level low - protecting the pump") - WateringOff() - -# This function makes sure, the watering pulse got turned off by the timer (can happen that it fails) -def CheckWateringPulseOff(): - global pumpState, lastWateringPulse - if (pumpState): - if (lastWateringPulse + 1 + configs["pulse_duration"] <= time.time()): - Logger.LogMessage("Turning watering pump off, because pulse timer failed to do so") - WateringOff() - -##################################################################### - -# Helper-method to allow error handling and output in asyncio -def set_global_exception(): - def handle_exception(loop, context): - Logger.LogMessage("Fatal error: " + str(context["exception"])) - import sys - sys.print_exception(context["exception"]) - sys.exit() - Reboot() - loop = asyncio.get_event_loop() - loop.set_exception_handler(handle_exception) - -# Main method for all the Watering-handling -async def WateringHandling(): - Logger.LogMessage("Watering handling started") - while True: - ReadTemp() - ReadWaterLevel() - CheckWateringPulseOff() - #todo any super cool action here - heartbeatLed.toggle() - await asyncio.sleep(0.5) - -# Main method for the API -html = """ - - Bewässerung -

Bewässerung

-

%s

- -""" -json = """{ "Watering": { "%s" } }""" -async def APIHandling(reader, writer): - request_line = await reader.readline() - while await reader.readline() != b"\r\n": - pass - request = str(request_line) - try: - request = request.split()[1] - except IndexError: - pass - client_ip = writer.get_extra_info('peername')[0] - if request != "/favicon.ico": - Logger.LogMessage("API request: " + request + " - from client IP: " + client_ip) - if (configs['api_client_ip'] != "") and (configs['api_client_ip'] != client_ip): - Logger.LogMessage("Unauthorized client! Aborting API Handling now.") - stateis = "Error 401: Client '" + client_ip + "' is not authorized to use the API!

Set authorized client IP in configs.py!" - response = html % stateis - writer.write('HTTP/1.0 401 Unauthorized\r\nContent-type: text/html\r\n\r\n') - else: - req = request.split('/') - stateis = "" - if (len(req) == 3 or len(req) == 4 or len(req) == 5): - if (req[1] == secrets['api']): - if (req[2] == "wateron"): - Logger.LogMessage("Watering turned on") - stateis = "Watering turned: on" - WateringOn() - elif (req[2] == "wateroff"): - Logger.LogMessage("Watering turned off") - stateis = "Watering turned: off" - WateringOff() - elif (req[2] == "waterstate"): - Logger.LogMessage("Watering is turned " + WateringState()) - stateis = "Watering is turned: " + WateringState() - elif (req[2] == "waterpulse"): - Logger.LogMessage("Watering Pulse activated") - stateis = "Watering Pulse: activated" - WateringPulse() - elif (req[2] == "autoon"): - Logger.LogMessage("Automatic-Mode turned on") - stateis = "Automatic-Mode turned: on" - AutoModeOn() - elif (req[2] == "autooff"): - Logger.LogMessage("Automatic-Mode turned off") - stateis = "Automatic-Mode turned: off" - AutoModeOff() - elif (req[2] == "autostate"): - Logger.LogMessage("Automatic-Mode is turned " + AutoModeState()) - stateis = "Automatic-Mode is turned: " + AutoModeState() - elif (req[2] == "forcesoilread"): - ReadSoil() - Logger.LogMessage("Forced Soil Measurement Readings: " + str(lastSoil1) + ", " + str(lastSoil2) + ", " + str(lastSoil3)) - stateis = "Forced Soil Measurement Readings: now
soil1: " + str(lastSoil1) + "
soil2: " + str(lastSoil2) + "
soil3: " + str(lastSoil3) - elif (req[2] == "waterlevel"): - Logger.LogMessage("Water level is: " + WaterLevelState()) - stateis = "Water level is: " + WaterLevelState() - elif (req[2] == "ping"): - stateis = "ping: OK" - elif (req[2] == "stats"): - stateis = "IP address: " + Networking.GetIPAddress() + "
MAC address: " + Networking.GetMACAddress() + "
Hostname: " + configs['hostname'] + "
API Port: " + str(configs['api_port']) + "
Uptime (h:m): " + Uptime() + "
Date/Time: " + TimeUtils.DateTimeNow() + "
Version: " + version + "
GMT Timezone Offset (hours): " + str(configs['gmt_offset']) + "
Auto summertime: " + str(configs['auto_summertime']) + "
Housekeep logfiles after days: " + str(configs['log_housekeeping_days']) + "
CPU frequency (MHz): " + str(machine.freq()/1000000) + "
Temperature (°C): " + "%.2f" % temperature + "
Pulse duration (seconds): " + str(configs["pulse_duration"]) + "
Soil moisture measurement interval (minutes): " + str(configs["soil_moisture_measure_interval"]) + "
Soil is: " + SoilState() + "
Soil moisture 1: " + str(lastSoil1) + "
Soil moisture 2: " + str(lastSoil2) + "
Soil moisture 3: " + str(lastSoil3) + "
Automatic watering Mode: " + AutoModeState() + "
Water level status: " + WaterLevelState() + "
Check for updates (minutes): " + str(configs["check_for_update"]) - elif (req[2] == "reboot"): - stateis = "Rebooting device: now..." - Reboot() - elif (req[2] == "logs"): - if (len(req) >= 4 and req[3] != "json"): - if (IsInt(req[3])): - stateis = Logger.LastLogs(int(req[3])) - else: - stateis = "Error: Parameter for log length not an integer!" - else: - stateis = Logger.LastLogs(10) - elif (req[2] == "soils"): - if (len(req) >= 4 and req[3] != "json"): - if (IsInt(req[3])): - stateis = Logger.LastSoils(int(req[3])) - else: - stateis = "Error: Parameter for soil length not an integer!" - else: - stateis = Logger.LastSoils(6) - else: - stateis = "Error: Unknown command!" - else: - stateis = "Error: API key is invalid!" - if ((len(req) == 4 and req[3] == "json") or (len(req) == 5 and req[4] == "json")): - if (req[2] != "logs" and req[2] != "soils"): - stateis = stateis.replace(": ", "\":\"") - stateis = stateis.replace("
", "\", \"") - stateis = stateis.replace("°", "°") - else: - stateis = stateis.replace(";", "\":\"") - stateis = stateis.replace("
", "\", \"") - stateis = stateis.replace("\n", "").replace("\r", "") - response = json % stateis - writer.write('HTTP/1.0 200 OK\r\nContent-type: text/json\r\n\r\n') - else: - response = html % stateis - writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') - else: - stateis = "Error 400: Invalid usage of API!

Usage: http://servername/api_key/command[/json]

Commands:
API Key: set 'api' in secrets.py file." - response = html % stateis - writer.write('HTTP/1.0 400 Bad Request\r\nContent-type: text/html\r\n\r\n') - writer.write(response) - await writer.drain() - await writer.wait_closed() - -# Main method for daily housekeeping -async def Housekeeper(): - Logger.LogMessage("Housekeeper started") - while True: - Logger.LogMessage("Housekeeper is performing actions") - Logger.Housekeeping() - Logger.LogMessage("Housekeeper is performing NTP sync") - NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime']) - Logger.LogMessage("Housekeeper has finished its jobs") - await asyncio.sleep(86400) - -# Main entry point after booting -async def Main(): - global autoState - set_global_exception() - Logger.LogMessage("Entering MainLoop") - boottime = time.time() - loop = asyncio.get_event_loop() - Logger.LogMessage("Setting up API on port " + str(configs['api_port']) + " with key " + secrets['api']) - loop.create_task(asyncio.start_server(APIHandling, Networking.GetIPAddress(), configs['api_port'])) - Logger.LogMessage("API started") - Logger.LogMessage("Booting complete with Firmware " + version) - ReadSoil() - timer = Timer(period=(1000 * 60 * configs["soil_moisture_measure_interval"]), mode=Timer.PERIODIC, callback=lambda t:ReadSoil()) - Logger.LogMessage("Started timer for soil moisture measurement every " + str(configs["soil_moisture_measure_interval"]) + " minutes") - if "auto" in os.listdir(): - autoState = True - autoLed.on() - Logger.LogMessage("Turned Automatic mode on from previous state") - loop.create_task(WateringHandling()) - loop.create_task(Housekeeper()) - loop.run_forever() - -# Booting the device -def Boot(): - Networking.Connect(configs['disable_wifi_powersavingmode']) - if (Networking.Status()): - wifiLed.on() - if (NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime'])): - Logger.DisableTempLogfile() - Logger.DisableTempSoilfile() - if (ota_host != ""): - OTA.ota_update(ota_host, project_name, filenames, use_version_prefix=False, hard_reset_device=True, soft_reset_device=False, timeout=5) - timer2 = Timer(period=(1000 * 60 * configs["check_for_update"]), mode=Timer.PERIODIC, callback=lambda t:OTA.check_for_ota_update(ota_host, project_name, soft_reset_device=False, timeout=5)) - else: - wifiLed.off() - time.sleep(3) - Reboot() - -##################################################################### - -Boot() -try: - asyncio.run(Main()) -except KeyboardInterrupt: - Logger.LogMessage("Shutdown.") - pumpRelais.off() - pumpLed.off() - autoLed.off() - heartbeatLed.off() - wifiLed.off() -finally: +import rp2, re, os +import TimeUtils, Logger, Networking, NTP, OTA +import uasyncio as asyncio +import utime as time +import usocket as socket +from machine import ADC, Pin, Timer + +from secrets import secrets +from configs import configs + +rp2.country(configs['country']) +version = "0.9" + +TimeUtils = TimeUtils.TimeUtils() +Logger = Logger.Logger(configs['log_housekeeping_days']) +Networking = Networking.Networking(Logger, secrets['ssid'], secrets['pw']) +NTP = NTP.NTP(Logger) +ota_host = configs['ota_host'] +project_name = "watering" +filenames = ["configs.py", "Logger.py", "main.py", "Networking.py", "NTP.py", "OTA.py", "secrets.py", "TimeUtils.py"] + +boottime = time.time() +pumpRelais = Pin(15, Pin.OUT, value=0) #gpio15 +heartbeatLed = Pin(0, Pin.OUT, value=0) #gpio0 +wifiLed = Pin(1, Pin.OUT, value=0) #gpio1 +autoLed = Pin(2, Pin.OUT, value=0) #gpio2 +pumpLed = Pin(3, Pin.OUT, value=0) #gpio3 +tempSensor = ADC(4) #internal temperature sensor +soilSensor1 = ADC(0) #gpio26 +soilSensor2 = ADC(1) #gpio27 +soilSensor3 = ADC(2) #gpio28 +waterLevelSensor = Pin(5, Pin.IN, Pin.PULL_UP) #gpio5 +pumpState = False +autoState = False +soilDry = False +waterLevel = False +wateringPulseState = False +temperature = 0.0 +lastSoil1 = 0 +lastSoil2 = 0 +lastSoil3 = 0 +lastWateringPulse = 0 + +# Checks if string is integer +def IsInt(possibleint): + try: + int(possibleint) + except: + return False + else: + return True + +# Reboots the Pico W (f.e. in case of an error) +def Reboot(): + Logger.LogMessage("Performing Reboot") + machine.reset() + +# Calculates the uptime in hours and minutes +def Uptime(): + global boottime + seconds = (time.time() - boottime) % (24 * 3600) + hours = seconds // 3600 + seconds %= 3600 + minutes = seconds // 60 + seconds %= 60 + return "%d:%02d" % (hours, minutes) + +# Turns on the watering pump +def WateringOn(): + global pumpState, waterLevel + if (waterLevel): + pumpRelais.on() + pumpLed.on() + pumpState = True + Logger.LogMessage("Pump turned on") + else: + pumpRelais.off() + pumpLed.off() + pumpState = False + Logger.LogMessage("Could not turn on the pump - water level low!") + +# Turns off the watering pump +def WateringOff(value = ""): + global pumpState, wateringPulseState + pumpRelais.off() + pumpLed.off() + pumpState = False + wateringPulseState = False + Logger.LogMessage("Pump turned off") + +# Returns the state of the watering pump +def WateringState(): + global pumpState + if (pumpState): + return "on" + else: + return "off" + +# Reads the onboard temperature sensor's value +def ReadTemp(): + global temperature + temperature = 27 - ((tempSensor.read_u16() * (3.3 / (65535))) - 0.706) / 0.001721 + +# Turns on the automatic watering mode and writes file to set automode on reboot +def AutoModeOn(): + global autoState + if not "auto" in os.listdir(): + file = open("auto","w") + file.write("auto") + file.close() + autoLed.on() + autoState = True + Logger.LogMessage("Automatic mode turned on") + +# Turns off the automatic watering mode and removes file for automode on reboot +def AutoModeOff(): + global autoState + if "auto" in os.listdir(): + os.remove("auto") + autoLed.off() + autoState = False + Logger.LogMessage("Automatic mode turned off") + +# Returns the state of the automatic watering mode +def AutoModeState(): + global autoState + if (autoState): + return "on" + else: + return "off" + +# Turns on the watering pump for pulse_duration seconds and turns it off after that +def WateringPulse(): + global waterLevel, wateringPulseState, lastWateringPulse + if (waterLevel): + WateringOn() + lastWateringPulse = time.time() + wateringPulseState = True + timer3 = Timer(period=(1000 * configs["pulse_duration"]), mode=Timer.ONE_SHOT, callback=WateringOff) + Logger.LogMessage("Triggered watering pulse") + else: + Logger.LogMessage("Could not trigger watering pulse - Water level low!") + +# Reads the soil moisture sensors and triggers watering pulse if automatic mode is enabled +def ReadSoil(): + global soilDry, autoState, lastSoil1, lastSoil2, lastSoil3 + read1 = soilSensor1.read_u16() + read2 = soilSensor2.read_u16() + read3 = soilSensor3.read_u16() + Logger.LogSoil(str(read1) + "," + str(read2) + "," + str(read3) + ",") + lastSoil1 = read1 + lastSoil2 = read2 + lastSoil3 = read3 + # eliminate false readings + if (lastSoil1 > 25000): + lastSoil1 = configs["soil_dry_value"] + if (lastSoil2 > 25000): + lastSoil2 = configs["soil_dry_value"] + if (lastSoil3 > 25000): + lastSoil3 = configs["soil_dry_value"] + if ((lastSoil1 + lastSoil2 + lastSoil3) / 3 <= configs["soil_dry_value"]): + soilDry = False + Logger.LogMessage("Soil is wet") + else: + soilDry = True + Logger.LogMessage("Soil is dry!") + if (autoState): + Logger.LogMessage("Triggering watering pulse due to automatic mode") + WateringPulse() + +# Returns if soil is dry or wet +def SoilState(): + global soilDry + if (soilDry): + return "dry" + else: + return "wet" + +# Returns the water level state +def WaterLevelState(): + global waterLevel, pumpState + if (waterLevel): + return "full" + else: + return "empty" + +# Reads the water level sensor +def ReadWaterLevel(): + global waterLevel + if (waterLevelSensor.value() == 0): + waterLevel = True + else: + waterLevel = False + if (pumpState): + Logger.LogMessage("Water level low - protecting the pump") + WateringOff() + +# This function makes sure, the watering pulse got turned off by the timer (can happen that it fails) +def CheckWateringPulseOff(): + global pumpState, lastWateringPulse + if (pumpState): + if (lastWateringPulse + 1 + configs["pulse_duration"] <= time.time()): + Logger.LogMessage("Turning watering pump off, because pulse timer failed to do so") + WateringOff() + +##################################################################### + +# Helper-method to allow error handling and output in asyncio +def set_global_exception(): + def handle_exception(loop, context): + Logger.LogMessage("Fatal error: " + str(context["exception"])) + import sys + sys.print_exception(context["exception"]) + sys.exit() + Reboot() + loop = asyncio.get_event_loop() + loop.set_exception_handler(handle_exception) + +# Main method for all the Watering-handling +async def WateringHandling(): + Logger.LogMessage("Watering handling started") + while True: + ReadTemp() + ReadWaterLevel() + CheckWateringPulseOff() + #todo any super cool action here + heartbeatLed.toggle() + await asyncio.sleep(0.5) + +# Main method for the API +html = """ + + Bewässerung +

Bewässerung

+

%s

+ +""" +json = """{ "Watering": { "%s" } }""" +async def APIHandling(reader, writer): + request_line = await reader.readline() + while await reader.readline() != b"\r\n": + pass + request = str(request_line) + try: + request = request.split()[1] + except IndexError: + pass + client_ip = writer.get_extra_info('peername')[0] + if request != "/favicon.ico": + Logger.LogMessage("API request: " + request + " - from client IP: " + client_ip) + if (configs['api_client_ip'] != "") and (configs['api_client_ip'] != client_ip): + Logger.LogMessage("Unauthorized client! Aborting API Handling now.") + stateis = "Error 401: Client '" + client_ip + "' is not authorized to use the API!

Set authorized client IP in configs.py!" + response = html % stateis + writer.write('HTTP/1.0 401 Unauthorized\r\nContent-type: text/html\r\n\r\n') + else: + req = request.split('/') + stateis = "" + if (len(req) == 3 or len(req) == 4 or len(req) == 5): + if (req[1] == secrets['api']): + if (req[2] == "wateron"): + Logger.LogMessage("Watering turned on") + stateis = "Watering turned: on" + WateringOn() + elif (req[2] == "wateroff"): + Logger.LogMessage("Watering turned off") + stateis = "Watering turned: off" + WateringOff() + elif (req[2] == "waterstate"): + Logger.LogMessage("Watering is turned " + WateringState()) + stateis = "Watering is turned: " + WateringState() + elif (req[2] == "waterpulse"): + Logger.LogMessage("Watering Pulse activated") + stateis = "Watering Pulse: activated" + WateringPulse() + elif (req[2] == "autoon"): + Logger.LogMessage("Automatic-Mode turned on") + stateis = "Automatic-Mode turned: on" + AutoModeOn() + elif (req[2] == "autooff"): + Logger.LogMessage("Automatic-Mode turned off") + stateis = "Automatic-Mode turned: off" + AutoModeOff() + elif (req[2] == "autostate"): + Logger.LogMessage("Automatic-Mode is turned " + AutoModeState()) + stateis = "Automatic-Mode is turned: " + AutoModeState() + elif (req[2] == "forcesoilread"): + ReadSoil() + Logger.LogMessage("Forced Soil Measurement Readings: " + str(lastSoil1) + ", " + str(lastSoil2) + ", " + str(lastSoil3)) + stateis = "Forced Soil Measurement Readings: now
soil1: " + str(lastSoil1) + "
soil2: " + str(lastSoil2) + "
soil3: " + str(lastSoil3) + elif (req[2] == "waterlevel"): + Logger.LogMessage("Water level is: " + WaterLevelState()) + stateis = "Water level is: " + WaterLevelState() + elif (req[2] == "ping"): + stateis = "ping: OK" + elif (req[2] == "stats"): + stateis = "IP address: " + Networking.GetIPAddress() + "
MAC address: " + Networking.GetMACAddress() + "
Hostname: " + configs['hostname'] + "
API Port: " + str(configs['api_port']) + "
Uptime (h:m): " + Uptime() + "
Date/Time: " + TimeUtils.DateTimeNow() + "
Version: " + version + "
GMT Timezone Offset (hours): " + str(configs['gmt_offset']) + "
Auto summertime: " + str(configs['auto_summertime']) + "
Housekeep logfiles after days: " + str(configs['log_housekeeping_days']) + "
CPU frequency (MHz): " + str(machine.freq()/1000000) + "
Temperature (°C): " + "%.2f" % temperature + "
Pulse duration (seconds): " + str(configs["pulse_duration"]) + "
Soil moisture measurement interval (minutes): " + str(configs["soil_moisture_measure_interval"]) + "
Soil is: " + SoilState() + "
Soil moisture 1: " + str(lastSoil1) + "
Soil moisture 2: " + str(lastSoil2) + "
Soil moisture 3: " + str(lastSoil3) + "
Automatic watering Mode: " + AutoModeState() + "
Water level status: " + WaterLevelState() + "
Check for updates (minutes): " + str(configs["check_for_update"]) + elif (req[2] == "reboot"): + stateis = "Rebooting device: now..." + Reboot() + elif (req[2] == "logs"): + if (len(req) >= 4 and req[3] != "json"): + if (IsInt(req[3])): + stateis = Logger.LastLogs(int(req[3])) + else: + stateis = "Error: Parameter for log length not an integer!" + else: + stateis = Logger.LastLogs(10) + elif (req[2] == "soils"): + if (len(req) >= 4 and req[3] != "json"): + if (IsInt(req[3])): + stateis = Logger.LastSoils(int(req[3])) + else: + stateis = "Error: Parameter for soil length not an integer!" + else: + stateis = Logger.LastSoils(6) + else: + stateis = "Error: Unknown command!" + else: + stateis = "Error: API key is invalid!" + if ((len(req) == 4 and req[3] == "json") or (len(req) == 5 and req[4] == "json")): + if (req[2] != "logs" and req[2] != "soils"): + stateis = stateis.replace(": ", "\":\"") + stateis = stateis.replace("
", "\", \"") + stateis = stateis.replace("°", "°") + else: + stateis = stateis.replace(";", "\":\"") + stateis = stateis.replace("
", "\", \"") + stateis = stateis.replace("\n", "").replace("\r", "") + response = json % stateis + writer.write('HTTP/1.0 200 OK\r\nContent-type: text/json\r\n\r\n') + else: + response = html % stateis + writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') + else: + stateis = "Error 400: Invalid usage of API!

Usage: http://servername/api_key/command[/json]

Commands:
API Key: set 'api' in secrets.py file." + response = html % stateis + writer.write('HTTP/1.0 400 Bad Request\r\nContent-type: text/html\r\n\r\n') + writer.write(response) + await writer.drain() + await writer.wait_closed() + +# Main method for daily housekeeping +async def Housekeeper(): + Logger.LogMessage("Housekeeper started") + while True: + Logger.LogMessage("Housekeeper is performing actions") + Logger.Housekeeping() + Logger.LogMessage("Housekeeper is performing NTP sync") + NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime']) + Logger.LogMessage("Housekeeper has finished its jobs") + await asyncio.sleep(86400) + +# Main entry point after booting +async def Main(): + global autoState + set_global_exception() + Logger.LogMessage("Entering MainLoop") + boottime = time.time() + loop = asyncio.get_event_loop() + Logger.LogMessage("Setting up API on port " + str(configs['api_port']) + " with key " + secrets['api']) + loop.create_task(asyncio.start_server(APIHandling, Networking.GetIPAddress(), configs['api_port'])) + Logger.LogMessage("API started") + Logger.LogMessage("Booting complete with Firmware " + version) + ReadSoil() + timer = Timer(period=(1000 * 60 * configs["soil_moisture_measure_interval"]), mode=Timer.PERIODIC, callback=lambda t:ReadSoil()) + Logger.LogMessage("Started timer for soil moisture measurement every " + str(configs["soil_moisture_measure_interval"]) + " minutes") + if "auto" in os.listdir(): + autoState = True + autoLed.on() + Logger.LogMessage("Turned Automatic mode on from previous state") + loop.create_task(WateringHandling()) + loop.create_task(Housekeeper()) + loop.run_forever() + +# Booting the device +def Boot(): + Networking.Connect(configs['disable_wifi_powersavingmode']) + if (Networking.Status()): + wifiLed.on() + if (NTP.SetRTCTimeFromNTP(configs['ntp_host'], configs['gmt_offset'], configs['auto_summertime'])): + Logger.DisableTempLogfile() + Logger.DisableTempSoilfile() + if (ota_host != ""): + OTA.ota_update(ota_host, project_name, filenames, use_version_prefix=False, hard_reset_device=True, soft_reset_device=False, timeout=5) + timer2 = Timer(period=(1000 * 60 * configs["check_for_update"]), mode=Timer.PERIODIC, callback=lambda t:OTA.check_for_ota_update(ota_host, project_name, soft_reset_device=False, timeout=5)) + else: + wifiLed.off() + time.sleep(3) + Reboot() + +##################################################################### + +Boot() +try: + asyncio.run(Main()) +except KeyboardInterrupt: + Logger.LogMessage("Shutdown.") + pumpRelais.off() + pumpLed.off() + autoLed.off() + heartbeatLed.off() + wifiLed.off() +finally: asyncio.new_event_loop() \ No newline at end of file