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:- wateron
- wateroff
- waterstate
- waterpulse
- autoon
- autooff
- autostate
- waterlevel
- ping
- stats
- reboot
- logs
- soils
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:- wateron
- wateroff
- waterstate
- waterpulse
- autoon
- autooff
- autostate
- waterlevel
- ping
- stats
- reboot
- logs
- soils
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