Browse Source

Added: weather API shit for monitoring the outside temperature vs inside and send a push notification if the "state" changed || Changed: muhconf global var to contain dicts per config section instead of 1 dict w/ everything, all optional config vars should be specified as a string, changed config checking ifs to a style more suited for checking individual val00s, check if sensor path actually exists during config check instead of simply handling the exception on read, check for string values (such as MySQL h0st) being zero-length, sorta standardised error message formats and some other v min0r fixes/optimisations ;]];];;];];];];];]];;]];];

Wazakindjes 1 year ago
parent
commit
aeda07e1a3
4 changed files with 342 additions and 46 deletions
  1. 1 0
      .gitignore
  2. 24 2
      README.md
  3. 22 0
      muhconf.ini.example
  4. 295 44
      temps.py

+ 1 - 0
.gitignore

@@ -5,6 +5,7 @@ $RECYCLE.BIN
 *-bak*
 *.ini
 /ssl
+/weathermon.state
 
 /app/release
 /app/src/.idea/assetWizardSettings.xml

+ 24 - 2
README.md

@@ -4,10 +4,16 @@ This shit is a qt lil Pyth0n implementation ov reading ein `DS18B20` temperature
 It will run 5 samples (with a required 1 second delay between em) and get the average of that to store in the DB. It also supports multiple sensors but it will only store the first average. xd
 
 # Dependencies
-You prolly need to install a few Pythinz modules cuz they may not be available by default:
+You prolly need to install a few Pythinz modules cuz they may not be available by default. I'll just list everything in use:
 * `argparse`
 * `ConfigParser`
+* `datetime`
+* `json`
 * `MySQLdb`
+* `os`
+* `requests`
+* `sys`
+* `time`
 
 # Installation
 Copy `muhconf.ini.example` to `muhconf.ini` and fire up een editor. All the options are self-explanatory or explained in there so git to reading fam. The config file must be kept in the same dir as the skrip.
@@ -22,4 +28,20 @@ ez pz ;]
 # Usage
 You can just simply run `./temps.py` obviously, but in case you need to troubleshoot something you can use `./temps.py --debug` to force debug mode without having to touch the config. [=[=[[=[==[=[=[
 
-Or just do `./temps.py -h` and check the built-in halp. ;]
+Or just do `./temps.py -h` and check the built-in halp for more inf0z. ;]
+
+# Weathermon
+So this thing also supports sending a notification when the outside temperature gets below the indoor one. I maed dis cuz I have an underpowered AC unit and if the outdoors temp is lower I can open el windows to make it cool off faster inside. =]] This also allows the air pressure to equalise again. The same is true for the other way around; when the outside temperature gets higher I should closem wind0ze, so it will also notify about that.
+
+### Weather API
+For checkin' em outdoors temperature I'm using an API from OpenWeatherMap, which as of writing this shit is free for 60 calls/minute up to 1 million/month. Should be more than enough. =]
+* Sign up through [the pricing page](https://openweathermap.org/price)
+* Generate an API key
+* Figure out your city ID by using one of the API URLs for searching by name, mentioned [in the API docs](https://openweathermap.org/current#name) (simply open the full URL in your browser and look for a top-level `id` variable)
+* Go to the `weathermon` section in the config file and update the `enable`, `weather_key` and `city_id` fields
+* Possibly update the `weather_url` too, if there's a newer version of the API (it may not work with our skrip though)
+
+OWP supports Celsius, Fahrenheit and Kelvin as temperature units, but the sensor always uses Celsius afaik so we'll force OWP to work with that too, deal w/ it. ;]
+
+### Notification shit
+__For now I'm currently using a demo application myself but I'm working on converting it to the style of mein other apps and will publish em at that time. ;];];];];]];;];]__

+ 22 - 0
muhconf.ini.example

@@ -35,3 +35,25 @@ column = temp
 # MySQL must be explicitly configured for SSL/TLS and is usually implemented in the form of STARTTLS (explicit TLS, running on the same port as plaintext)
 # This is optional and defaults to false
 tls = false
+
+[weathermon]
+# Refer to em README for moar deetz on getting the required keys etc lol
+# If this shit is not enabled (which is the default) then the other variables are not necessary obv m8
+#enable = false
+
+# Interval is in minutes and is *not* relative to the skrip's startup time, rather it simply checks if the current hour to minutes + current minutes is evenly divisible by the interval
+# Therefore it can't be more than 60 and it should be evenly divisible by the time used in your crontab as well ;]
+# Free weather APIs generally don't update faster than once every 10 minutes, but you should checkem at least once per hour anyways
+#interval = 20
+
+#weather_url = https://api.openweathermap.org/data/2.5/weather
+#weather_key = 11a11a111a1a11a111111111aa1a1aaa
+#city_id = 123456789
+
+#pushy_url = https://api.pushy.me
+#pushy_key = 1a11aa1a11aa1a11aa1aa1aaa11aaa1a1a111aa1a111a111a11111111a1a1111
+#device_id = aaa11111111a111111111a
+
+# TLS certificate bundle file used by the requests module
+# This shit is optional and defaults to the below value
+#tls_ca_file = /etc/ssl/certs/ca-certificates.crt

+ 295 - 44
temps.py

@@ -5,8 +5,11 @@ __version__ = "2.0.0"
 
 import argparse
 import ConfigParser
+import datetime
+import json
 import MySQLdb
 import os
+import requests
 import sys
 import time
 
@@ -14,9 +17,11 @@ import time
 RET_GUCCI = 0
 RET_ERR_CONF = 1
 RET_ERR_SENSOR = 2
+RET_ERR_WEATHERMON = 3
 
 # Globals y0
 muhconf = {}
+skripdir = os.path.dirname(os.path.realpath(__file__))
 
 def printem(txt):
 	# Because apparently we need to flush after every print to ensure it happens in real-time -.-
@@ -40,8 +45,16 @@ def show_version():
 	printem("temps.py v{0}, written by: {1}".format(__version__, __author__))
 
 def readem_conf():
+	global muhconf
+
 	err = False
 
+	muhconf = {
+		"main": {},
+		"mysql": {},
+		"weathermon": {},
+	}
+
 	try:
 		cfg = ConfigParser.SafeConfigParser({
 			# Some variables are optional ;]
@@ -52,63 +65,139 @@ def readem_conf():
 			"sample_maxattempts": "5",
 
 			# [mysql] section
-			"port": 3306,
+			"port": "3306",
 			"tls": "false",
+
+			# [weathermon] section
+			"enable": "false",
+			"tls_ca_file": "/etc/ssl/certs/ca-certificates.crt",
+			# The other variables (interval, url, key) are not optional if enable == true, so they shouldn't be specified here either ;]
 		})
-		cfg.read("{0}/muhconf.ini".format(os.path.dirname(os.path.realpath(__file__))))
+		cfg.read("{0}/muhconf.ini".format(skripdir))
 
 		### [main] section BEGIN
 		# Booleans first
 		for main_boolopt in ["debug", "dryrun"]:
-			muhconf[main_boolopt] = cfg.getboolean("main", main_boolopt)
+			muhconf["main"][main_boolopt] = cfg.getboolean("main", main_boolopt)
 
 		# Integers next
 		for main_intopt in ["sample_maxattempts"]:
 			optval = cfg.getint("main", main_intopt)
 
-			# Should prolly restrict it to 30 attempts, anything more seems way too excessive ;];];]
-			if main_intopt == "sample_maxattempts" and (optval <= 0 or optval > 30):
-				printem("Invalid value '{0}' for option '{1}': must be in the range of 1-30".format(optval, main_intopt))
-				err = True
-				continue
+			# Should prolly restrict it to 30 attempts, anything more seems way too excessive [==[[==[[=
+			if main_intopt == "sample_maxattempts":
+				if optval <= 0 or optval > 30:
+					printem("Invalid value '{0}' for option '{1}': must be in the range of 1-30".format(optval, main_intopt))
+					err = True
+					continue
 
-			muhconf[main_intopt] = optval
+			muhconf["main"][main_intopt] = optval
 
 		# Multiple string values last
 		for main_stropt_multi in ["sensor"]:
 			optval_multi = cfg.get("main", main_stropt_multi).split("\n")
 
 			for optval in optval_multi:
-				# Even though sensor is currently the only option, let's set it up for easy expansion laturd anyways ;]
-				if main_stropt_multi == "sensor" and "/" in optval:
-					printem("Invalid value '{0}' for option '{1}': may not contain a slash".format(optval, main_stropt_multi))
+				if len(optval) == 0:
+					printem("Invalid value for option '{0}': may not be left empty".format(main_stropt_multi))
 					err = True
 					continue
 
-			muhconf[main_stropt_multi] = optval_multi
+				if main_stropt_multi == "sensor":
+					if "/" in optval:
+						printem("Invalid value '{0}' for option '{1}': may not contain a slash".format(optval, main_stropt_multi))
+						err = True
+						continue
+
+					if not os.access("/sys/bus/w1/devices/{0}/w1_slave".format(optval), os.R_OK):
+						printem("Invalid value '{0}' for option '{1}': file '/sys/bus/w1/devices/{0}/w1_slave' doesn't exist or is not readable".format(optval, main_stropt_multi))
+						err = True
+						continue
+
+			muhconf["main"][main_stropt_multi] = optval_multi
 		### [main] section END
 
 		### [mysql] section BEGIN
 		# Booleans first
 		for mysql_boolopt in ["tls"]:
-			muhconf[mysql_boolopt] = cfg.getboolean("mysql", mysql_boolopt)
+			muhconf["mysql"][mysql_boolopt] = cfg.getboolean("mysql", mysql_boolopt)
 
 		# Integers next
 		for mysql_intopt in ["port"]:
 			optval = cfg.getint("mysql", mysql_intopt)
 
-			if mysql_intopt == "port" and (optval <= 0 or optval > 65535):
-				printem("Invalid value '{0}' for option '{1}': must be in the range of 1-65535".format(optval, mysql_intopt))
-				err = True
-				continue
+			if mysql_intopt == "port":
+				if optval <= 0 or optval > 65535:
+					printem("Invalid value '{0}' for option '{1}': must be in the range of 1-65535".format(optval, mysql_intopt))
+					err = True
+					continue
 
-			muhconf[mysql_intopt] = optval
+			muhconf["mysql"][mysql_intopt] = optval
 
 		# String values last
 		for mysql_stropt in ["host", "user", "pass", "db", "table", "column"]:
-			muhconf[mysql_stropt] = cfg.get("mysql", mysql_stropt)
+			optval = cfg.get("mysql", mysql_stropt)
+			if len(optval) == 0:
+				printem("Invalid value for option '{0}': may not be left empty".format(mysql_stropt))
+				err = True
+				continue
+
+			muhconf["mysql"][mysql_stropt] = optval
 		### [mysql] section END
 
+		### [weathermon] section BEGIN
+		# Booleans first
+		for weathermon_boolopt in ["enable"]:
+			muhconf["weathermon"][weathermon_boolopt] = cfg.getboolean("weathermon", weathermon_boolopt)
+
+		if muhconf["weathermon"]["enable"]:
+			# Integers next, but only if shit's enabled lol (obviously)
+			for weathermon_intopt in ["interval", "city_id"]:
+				optval = cfg.getint("weathermon", weathermon_intopt)
+
+				# Free weather APIs generally don't update faster than once every 10 minutes, but you should checkem at least once per hour
+				if weathermon_intopt == "interval":
+					if optval < 10 or optval > 60:
+						printem("Invalid value '{0}' for option '{1}': must be in the range of 10-60".format(optval, weathermon_intopt))
+						err = True
+						continue
+
+				elif weathermon_intopt == "city_id":
+					if optval <= 0:
+						printem("Invalid value '{0}' for option '{1}': must be greater than 0".format(optval, weathermon_intopt))
+						err = True
+						continue
+
+				muhconf["weathermon"][weathermon_intopt] = optval
+
+			# String values last
+			for weathermon_stropt in ["weather_url", "weather_key", "tls_ca_file", "pushy_url", "pushy_key", "device_id"]:
+				optval = cfg.get("weathermon", weathermon_stropt)
+				if len(optval) == 0:
+					printem("Invalid value for option '{0}': may not be left empty".format(weathermon_stropt))
+					err = True
+					continue
+
+				elif weathermon_stropt in ["weather_url", "pushy_url"]:
+					if not optval.startswith("https://"):
+						printem("Invalid value '{0}' for option '{1}': must start with https://".format(optval, weathermon_stropt))
+						err = True
+						continue
+
+				elif weathermon_stropt == "tls_ca_file":
+					if not optval.startswith("/"):
+						printem("Invalid value '{0}' for option '{1}': must start with a slash (absolute path y0)".format(optval, weathermon_stropt))
+						err = True
+						continue
+
+					if not os.access(optval, os.R_OK):
+						printem("Invalid value '{0}' for option '{1}': file doesn't exist or is not readable".format(optval, weathermon_stropt))
+						err = True
+						continue
+
+				muhconf["weathermon"][weathermon_stropt] = cfg.get("weathermon", weathermon_stropt)
+		### [weathermon] section END
+
 	except KeyboardInterrupt:
 		printem("\nCTRL + C")
 		sys.exit(RET_GUCCI)
@@ -129,20 +218,29 @@ def readem_temp():
 	cursor = None
 
 	try:
-		if muhconf["debug"]:
+		if muhconf["main"]["debug"]:
 			printem("Connecting to MySQL lol")
 
-		if muhconf["tls"]:
-			db = MySQLdb.connect(host=muhconf["host"], port=muhconf["port"], user=muhconf["user"], passwd=muhconf["pass"], db=muhconf["db"], ssl={"cipher": "AES256-SHA"})
+		if muhconf["mysql"]["tls"]:
+			db = MySQLdb.connect(host=muhconf["mysql"]["host"],
+								port=muhconf["mysql"]["port"],
+								user=muhconf["mysql"]["user"],
+								passwd=muhconf["mysql"]["pass"],
+								db=muhconf["mysql"]["db"],
+								ssl={"cipher": "AES256-SHA"})
 		else:
-			db = MySQLdb.connect(host=muhconf["host"], port=muhconf["port"], user=muhconf["user"], passwd=muhconf["pass"], db=muhconf["db"])
+			db = MySQLdb.connect(host=muhconf["mysql"]["host"],
+								port=muhconf["mysql"]["port"],
+								user=muhconf["mysql"]["user"],
+								passwd=muhconf["mysql"]["pass"],
+								db=muhconf["mysql"]["db"])
 
 		cursor = db.cursor()
-		if muhconf["debug"]:
+		if muhconf["main"]["debug"]:
 			printem("Connected y0")
 
-		for sensor in muhconf["sensor"]:
-			if muhconf["debug"]:
+		for sensor in muhconf["main"]["sensor"]:
+			if muhconf["main"]["debug"]:
 				printem("\tSensor: {0}".format(sensor))
 
 			temperatures = []
@@ -150,13 +248,13 @@ def readem_temp():
 				text = '';
 				attempts = 0
 				while text.split("\n")[0].find("YES") == -1:
-					if attempts >= muhconf["sample_maxattempts"]:
+					if attempts >= muhconf["main"]["sample_maxattempts"]:
 						plural = "s" if attempts > 1 else ""
-						if muhconf["debug"]:
-							printem("Ayyy rip readem_temp(): unable to read temperature after {0} attempt{1} (sample #{2})".format(attempts, plural, i))
+						if muhconf["main"]["debug"]:
+							printem("RIP readem_temp(): unable to read temperature after {0} attempt{1} (sample #{2})".format(attempts, plural, i))
 						else:
 							# Non-debug mode doesn't print the sensor name at an earlier point, so let's make it clear here ;;]];];
-							printem("Ayyy rip readem_temp(): unable to read temperature for sensor {0} after {1} attempt{2} (sample #{3})".format(sensor, attempts, plural, i))
+							printem("RIP readem_temp(): unable to read temperature for sensor {0} after {1} attempt{2} (sample #{3})".format(sensor, attempts, plural, i))
 						sys.exit(RET_ERR_SENSOR)
 
 					if attempts > 0:
@@ -166,7 +264,7 @@ def readem_temp():
 					# Contents of the file should be something leik dis:
 					# 79 01 80 80 7f ff 7f 80 02 : crc=02 YES
 					# 79 01 80 80 7f ff 7f 80 02 t=23562
-					tfile = open("/sys/bus/w1/devices/{0}/w1_slave".format(sensor))
+					tfile = open("/sys/bus/w1/devices/{0}/w1_slave".format(sensor), "r")
 					text = tfile.read()
 					tfile.close()
 					attempts += 1
@@ -176,15 +274,15 @@ def readem_temp():
 				temperature = float(temperaturedata[2:]) / 1000
 				temperatures.append(temperature)
 
-				if muhconf["debug"]:
+				if muhconf["main"]["debug"]:
 					printem("\t\tSample #{0}: {1}".format(i, temperature))
 
 			avgtemperatures.append(sum(temperatures) / float(len(temperatures)))
 
-		if not muhconf["dryrun"]:
-			if muhconf["debug"]:
+		if not muhconf["main"]["dryrun"]:
+			if muhconf["main"]["debug"]:
 				printem("\t\tInserting average: {0}".format(avgtemperatures[0]))
-			cursor.execute("INSERT INTO `{0}` (`{1}`) VALUES ({2})".format(muhconf["table"], muhconf["column"], avgtemperatures[0]))
+			cursor.execute("INSERT INTO `{0}` (`{1}`) VALUES ({2})".format(muhconf["mysql"]["table"], muhconf["mysql"]["column"], avgtemperatures[0]))
 			db.commit()
 
 		try_db_cleanup(db, cursor)
@@ -196,21 +294,156 @@ def readem_temp():
 
 	except SystemExit:
 		try_db_cleanup(db, cursor)
-		pass
+		sys.exit(RET_ERR_SENSOR)
 
 	except:
-		printem("\nFAT RIP: {0}".format(sys.exc_info()[0]))
+		exc_info = sys.exc_info()
+		printem("\nRIP readem_temp() ({0}): {1}".format(exc_info[0], exc_info[1]))
 		try_db_cleanup(db, cursor)
 		sys.exit(RET_ERR_SENSOR)
 
+	return avgtemperatures
+
+def checkem_weathermon(inside_temp):
+	# Get current state first so that if something goes wrong, we don't spend an API call lel
+	statefile = "{0}/weathermon.state".format(skripdir)
+	curstate = "outdoors_higher"
+	notify = False
+
+	try:
+		if os.access(statefile, os.F_OK):
+			sfile = open(statefile, "r")
+			text = sfile.read().strip() # We'll write the file with a trailing newline, so trim that shit for ez comparison
+			sfile.close()
+
+			if text in ["outdoors_higher", "outdoors_lower"]:
+				curstate = text
+			else:
+				if muhconf["main"]["debug"]:
+					printem("\tState file exists but contents were unexpected, resetting to '{1}': '{0}'".format(text, curstate))
+
+	except:
+		# If we can't read the file we should bail entirely, as we can't reliably tell what to do anymore ;]
+		exc_info = sys.exc_info()
+		printem("\nRIP checkem_weathermon() state check ({0}): {1}".format(exc_info[0], exc_info[1]))
+		sys.exit(RET_ERR_WEATHERMON)
+
+	# Now try the API call imo tbh
+	url = "{0}?units=metric&id={1}&appid={2}".format(muhconf["weathermon"]["weather_url"], muhconf["weathermon"]["city_id"], muhconf["weathermon"]["weather_key"])
+	if muhconf["main"]["debug"]:
+		printem("\tURL: {0}".format(url))
+
+	try:
+		req = requests.get(url, verify=muhconf["weathermon"]["tls_ca_file"])
+		weather = req.json()
+		outside_temp = weather["main"]["temp"]
+
+		if outside_temp > inside_temp:
+			if curstate == "outdoors_higher":
+				if muhconf["main"]["debug"]:
+					printem("\tAyy outside temp ({0}) is still ab0ve inside temp ({1}) br0, not proceeding".format(outside_temp, inside_temp))
+				return
+			else:
+				curstate = "outdoors_higher" # Flip state lol
+				if muhconf["main"]["debug"]:
+					printem("\tAyy outside temp ({0}) is now ab0ve inside temp ({1}) br0, sending notification".format(outside_temp, inside_temp))
+				notify = True
+
+		elif outside_temp < inside_temp:
+			if curstate == "outdoors_lower":
+				if muhconf["main"]["debug"]:
+					printem("\tAyy outside temp ({0}) is still bel0w inside temp ({1}) br0, not proceeding".format(outside_temp, inside_temp))
+				return
+			else:
+				curstate = "outdoors_lower"
+				if muhconf["main"]["debug"]:
+					printem("\tAyy outside temp ({0}) is now bel0w inside temp ({1}) br0, sending notification".format(outside_temp, inside_temp))
+				notify = True
+
+		else:
+			# Temps are equal, don't do anything cuz it could go either way lel
+			if muhconf["main"]["debug"]:
+				printem("\tAyy outside temp ({0}) is equal to inside temp ({1}) br0, not proceeding".format(outside_temp, inside_temp))
+			return
+
+	except:
+		exc_info = sys.exc_info()
+		printem("\nRIP checkem_weathermon() API call ({0}): {1}".format(exc_info[0], exc_info[1]))
+		sys.exit(RET_ERR_WEATHERMON)
+
+	# Update state file lol
+	try:
+		sfile = open(statefile, "w")
+		sfile.write("{0}\n".format(curstate))
+		sfile.close()
+	except:
+		# If we can't write the file we should bail entirely, as we can't reliably tell what to do anymore either ;]
+		exc_info = sys.exc_info()
+		printem("\nRIP checkem_weathermon() state update ({0}): {1}".format(exc_info[0], exc_info[1]))
+		sys.exit(RET_ERR_WEATHERMON)
+
+	# We'll send the notification as the very last thing cuz if something would go wrong with updating the state file, we won't keep repeating that shit ;]];;]];;];];]];
+	if notify:
+		try:
+			if curstate == "outdoors_higher":
+				msg = "Outside temperature is now ab0ve inside temperature"
+			else:
+				msg = "Outside temperature is now bel0w inside temperature"
+
+			payload = json.dumps({
+				"to": muhconf["weathermon"]["device_id"],
+				"data": { # General payload
+					"title": "Temperature state changed",
+					"message": msg,
+				},
+				"notification": { # iOS
+					"title": "Temperature state changed",
+					"body": msg,
+					"badge": 1, # Badge number
+					"sound": "ping.aiff",
+				}
+			})
+
+			url = "{0}/push?api_key={1}".format(muhconf["weathermon"]["pushy_url"], muhconf["weathermon"]["pushy_key"])
+			req = requests.post(url, data=payload, headers={"Content-Type": "application/json"}, verify=muhconf["weathermon"]["tls_ca_file"])
+
+			if not req.text.startswith("{"):
+				printem("Unable to send notification: received invalid JSON response")
+				printem("\t{0}".format(req.text))
+				sys.exit(RET_ERR_WEATHERMON)
+
+			if req.status_code != 200:
+				printem("Unable to send notification: received {0} HTTP status code instead of 200".format(req.status_code))
+				printem("\t{0}".format(req.text))
+				sys.exit(RET_ERR_WEATHERMON)
+
+			notification = req.json()
+			if "error" in notification.keys():
+				printem("Unable to send notification: [{0}] {1}".format(notification["code"], notification["error"]))
+				sys.exit(RET_ERR_WEATHERMON)
+
+			if muhconf["main"]["debug"]:
+				printem("\t{0}".format(req.text))
+
+		except SystemExit:
+			sys.exit(RET_ERR_WEATHERMON)
+
+		except:
+			exc_info = sys.exc_info()
+			printem("\nRIP checkem_weathermon() notification ({0}): {1}".format(exc_info[0], exc_info[1]))
+			sys.exit(RET_ERR_WEATHERMON)
+
 if __name__ == "__main__":
+	dt = datetime.datetime.now()
+	weathermon_mins = (dt.hour * 60) + dt.minute
+
 	# Read config first lol
 	readem_conf()
 
 	# Then parse args imo tbh
 	p = argparse.ArgumentParser()
-	p.add_argument("--debug", help="force debug mode (current setting from config: {0})".format(muhconf["debug"]), action="store_true")
-	p.add_argument("--dryrun", help="do almost everything as usual (connect to SQL, read temperature) but don't actually insert into the database (current setting from config: {0})".format(muhconf["dryrun"]), action="store_true")
+	p.add_argument("--debug", help="force debug mode (current setting from config: {0})".format(muhconf["main"]["debug"]), action="store_true")
+	p.add_argument("--dryrun", help="do almost everything as usual (connect to SQL, read temperature) but don't actually insert into the database (current setting from config: {0})".format(muhconf["main"]["dryrun"]), action="store_true")
 	p.add_argument("--version", help="print version and exit lol", action="store_true")
 	args, noargs = p.parse_known_args() # noargs contains the 'topkek' in: ./temps.py --debug topkek
 
@@ -219,12 +452,30 @@ if __name__ == "__main__":
 		sys.exit(RET_GUCCI)
 
 	if args.debug:
-		muhconf["debug"] = True
+		muhconf["main"]["debug"] = True
 
 	if args.dryrun:
-		muhconf["dryrun"] = True
+		muhconf["main"]["dryrun"] = True
 
-	if muhconf["debug"]:
+	if muhconf["main"]["debug"]:
 		printem("Reading em temp")
-	readem_temp()
+	avgtemperatures = readem_temp()
+
+	if muhconf["weathermon"]["enable"]:
+		if len(avgtemperatures) == 0:
+			printem("Wanted to run weathermon but avgtemperatures was unexpectedly empty lol")
+			sys.exit(RET_ERR_WEATHERMON)
+
+		weathermon_remaining = weathermon_mins % muhconf["weathermon"]["interval"]
+		if muhconf["main"]["debug"]:
+			printem("Checking if we need to run weathermon: {0} % {1} = {2}".format(weathermon_mins, muhconf["weathermon"]["interval"], weathermon_remaining))
+
+		if weathermon_remaining == 0:
+			if muhconf["main"]["debug"]:
+				printem("Ayy running weathermon fam")
+			checkem_weathermon(avgtemperatures[0])
+		else:
+			if muhconf["main"]["debug"]:
+				printem("Not running weathermon fam")
+
 	sys.exit(RET_GUCCI) # Let's do an explicit exit w/ code imo tbh famlamlaml