diff --git a/recordGenerators/__init__.py b/recordGenerators/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/recordGenerators/achesAndPains.py b/recordGenerators/achesAndPains.py
new file mode 100644
index 0000000..323c34b
--- /dev/null
+++ b/recordGenerators/achesAndPains.py
@@ -0,0 +1,85 @@
+import shutil
+import requests
+import logging,coloredlogs
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+import gzip
+from os import remove
+import xml.dom.minidom
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+geocodes = []
+coopIds = []
+
+for i in MPC.getPrimaryLocations():
+ coopIds.append(LFR.getCoopId(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(coopId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/achePain/daypart/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ if r.status != 200:
+ l.error(f"Failed to write AchesAndPains record -- status code {r.status}")
+ return
+
+ data = await r.text()
+
+
+ newData = data[63:-26]
+
+ i2Doc = f'\n \n {newData}\n {coopId}\n '
+
+ async with aiofiles.open('./.temp/AchesAndPains.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ l.info("Writing AchesAndPains record.")
+
+ header = ''
+ footer = ''
+
+ async with aiofiles.open('./.temp/AchesAndPains.i2m', 'a') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(coopIds, geocodes):
+ await getData(x,y)
+
+ async with aiofiles.open('./.temp/AchesAndPains.i2m', 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse('./.temp/AchesAndPains.i2m')
+ xmlPretty = dom.toprettyxml(indent= " ")
+
+ async with aiofiles.open('./.temp/AchesAndPains.i2m', 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+
+ # Compresss i2m to gzip
+ with open ('./.temp/AchesAndPains.i2m', 'rb') as f_in:
+ with gzip.open('./.temp/AchesAndPains.gz', 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ file = "./.temp/AchesAndPains.gz"
+ command = ''
+
+ bit.sendFile([file], [command], 1, 0)
+
+ remove('./.temp/AchesAndPains.i2m')
+ remove('./.temp/AchesAndPains.gz')
diff --git a/recordGenerators/airQuality.py b/recordGenerators/airQuality.py
new file mode 100644
index 0000000..86520c9
--- /dev/null
+++ b/recordGenerators/airQuality.py
@@ -0,0 +1,110 @@
+import requests
+import gzip
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+locationIds = []
+zipCodes = []
+epaIds = []
+
+for i in MPC.getPrimaryLocations():
+ locationIds.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+ epaIds.append(LFR.getEpaId(i))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(epaId, zipcode):
+ url = f"https://api.weather.com/v1/location/{zipcode}:4:US/airquality.xml?language=en-US&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(url) as r:
+ data = await r.text()
+
+ newData = data[57:-11]
+
+ # Write to i2doc file
+ i2Doc = f'' + '' + newData + f'{epaId}'
+
+ async with aiofiles.open("./.temp/AirQuality.i2m", 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def writeData():
+ loop = asyncio.get_running_loop()
+ useData = False
+ workingEpaIds = []
+
+ for i in epaIds:
+ if i == None:
+ l.debug(f"No EPA ID found for location -- Skipping.")
+ else:
+ l.debug(f"EPA ID found for location! Writing data for Air Quality.")
+ workingEpaIds.append(i)
+ useData = True
+
+
+ # Check to see if we even have EPA ids, as some areas don't have air quality reports
+ if (useData):
+ try:
+ l.info("Writing an AirQuality record.")
+ header = ''
+ footer = ""
+
+ async with aiofiles.open("./.temp/AirQuality.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(workingEpaIds, zipCodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/AirQuality.i2m", 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse("./.temp/AirQuality.i2m")
+ xmlPretty = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/AirQuality.i2m", 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/AirQuality.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/AirQuality.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/AirQuality.gz"
+
+ files.append(gZipFile)
+ comand = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/AirQuality.i2m")
+ os.remove("./.temp/AirQuality.gz")
+ except Exception as e:
+ l.error("DO NOT REPORT THE ERROR BELOW")
+ l.error("Failed to write an AirQuality record.")
+ os.remove('./.temp/AirQuality.i2m')
+ else:
+ l.info("Not writing an AirQuality record due to a lack of working EPA ids.")
+
+
+
diff --git a/recordGenerators/airportDelays.py b/recordGenerators/airportDelays.py
new file mode 100644
index 0000000..2440ef9
--- /dev/null
+++ b/recordGenerators/airportDelays.py
@@ -0,0 +1,103 @@
+import requests
+import gzip
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+locationIds = []
+zipCodes = []
+airports = []
+
+for i in MPC.getPrimaryLocations():
+ locationIds.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+airports = MPC.getAirportCodes()
+l.debug(airports)
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(airport):
+ url = f"https://api.weather.com/v1/airportcode/{airport}/airport/delays.xml?language=en-US&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(url) as r:
+ data = await r.text()
+
+ newData = data[48:-11].replace('¿', '-')
+
+ # Write to i2doc file
+ i2Doc = f'' + '' + newData + f'{airport}'
+
+ async with aiofiles.open("./.temp/AirportDelays.i2m", 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def writeData():
+ loop = asyncio.get_running_loop()
+ useData = False
+ airportsWithDelays = []
+
+ for x in airports:
+ async with aiohttp.ClientSession() as s:
+ async with s.get(f"https://api.weather.com/v1/airportcode/{x}/airport/delays.xml?language=en-US&apiKey={apiKey}") as r:
+ if r.status != 200:
+ l.debug(f"No delay for {x} found, skipping..")
+ else:
+ airportsWithDelays.append(x)
+ useData = True
+
+ if (useData):
+ l.info("Writing an AirportDelays record.")
+ header = ''
+ footer = ""
+
+ async with aiofiles.open("./.temp/AirportDelays.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for x in airportsWithDelays:
+ await getData(x)
+
+ async with aiofiles.open("./.temp/AirportDelays.i2m", 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse("./.temp/AirportDelays.i2m")
+ prettyXml = dom.toprettyxml(indent=" ")
+
+ async with aiofiles.open("./.temp/AirportDelays.i2m", 'w') as g:
+ await g.write(prettyXml)
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/AirportDelays.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/AirportDelays.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/AirportDelays.gz"
+
+ files.append(gZipFile)
+ comand = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/AirportDelays.i2m")
+ os.remove("./.temp/AirportDelays.gz")
+ else:
+ l.info("No airport delays found.")
diff --git a/recordGenerators/alerts.py b/recordGenerators/alerts.py
new file mode 100644
index 0000000..3665af4
--- /dev/null
+++ b/recordGenerators/alerts.py
@@ -0,0 +1,365 @@
+import requests
+import json
+import os
+from datetime import datetime,timedelta
+from util.machineProductCfg import getAlertZones
+import time
+import pytz
+import xml.dom.minidom
+import shutil
+import gzip
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+import py2Lib.bit
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+#Zones/Counties to fetch alerts for
+alertLocations = getAlertZones()
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+headlineApiKey = cfg["twcApiKey"]
+detailsApiKey = cfg["twcApiKey"]
+
+k = 0
+async def getAlerts(location):
+ global k
+ fetchUrl = 'https://api.weather.com/v3/alerts/headlines?areaId=' + location + ':US&format=json&language=en-US&apiKey=' + headlineApiKey
+ # response = requests.get(fetchUrl)
+
+ # theCode = response.status_code
+
+ theCode = 0
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ theCode = r.status
+
+ #Set the actions based on response code
+ if theCode == 204:
+ l.info('No alerts for area ' + location + '.\n')
+ return
+ elif theCode == 403:
+ l.critical("Uh oh! Your API key may not be authorized for alerts. Tsk Tsk. Maybe you shouldn't pirate IBM data :)\n")
+ return
+ elif theCode == 401:
+ l.critical("Uh oh! This request requires authentication. Maybe you shouldn't try to access resources for IBM employee's only :)\n")
+ return
+ elif theCode == 404:
+ l.error("Uh oh! The requested resource cannot be found. This means either the URL is wrong or IBM is having technical difficulties :(\n Or.... They deleted the API :O\n")
+ return
+ elif theCode == 405:
+ l.error("Uh oh! Got a 405! This means that somehow.... someway..... this script made an invalid request. So sad..... So terrible..... :(\n")
+ return
+ elif theCode == 406:
+ l.critical("Uh oh! Got a 406! This means that IBM doesn't like us. :(\n")
+ return
+ elif theCode == 408:
+ l.error("Uh oh! We were too slow in providing IBM our alert request. Although I prefer to say we were Slowly Capable! :)\n")
+ return
+ elif theCode == 500:
+ l.error("Uh oh! Seems IBM's on call IT Tech spilled coffee on the server! Looks like no alerts for a while. Please check back later :)\n")
+ return
+ elif theCode == 502 or theCode == 503 or theCode == 504:
+ l.error("Uh oh! This is why you don't have interns messing with the server configuration. Please stand by while IBM's on call IT Tech resolves the issue :)\n")
+ return
+ elif theCode == 200:
+ pass
+
+ # Map headline variables
+ l.debug('Found Alert for ' + location + '\n')
+ dataH = await r.json()
+ alertsRoot = dataH['alerts']
+
+ for x in alertsRoot:
+ detailKey = x['detailKey']
+ #Lets get map our detail variables.
+ detailsUrl = 'https://api.weather.com/v3/alerts/detail?alertId=' + detailKey + '&format=json&language=en-US&apiKey=' + detailsApiKey
+ detailsResponse = requests.get(detailsUrl)
+ dataD = detailsResponse.json()
+ detailsRoot = dataD['alertDetail']
+ theDetailsText = detailsRoot['texts']
+ detailsText = theDetailsText[0]
+ descriptionRaw = detailsText['description']
+ language = detailsText['languageCode']
+ Identifier = location + '_' + x['phenomena'] + '_' + x['significance'] + '_' + str(x['processTimeUTC'])
+
+ #Is this for a NWS Zone or County?
+ last4 = location[2:]
+ locationType = None
+ if 'C' in last4:
+ locationType = 'C'
+ elif 'Z' in last4:
+ locationType = 'Z'
+
+ #theIdent = str(Identifier)
+ try:
+ async with aiofiles.open('./.temp/alertmanifest.txt', 'r' ) as checkFile:
+ c = await checkFile.read()
+
+ if c.find(Identifier) != -1:
+ l.debug(f"{Identifier} was sent already, skipping..")
+ return
+ except FileNotFoundError:
+ l.warning("alert manifest does not exist (yet)")
+
+ k += 1 #We have an alert to send!
+
+ #Lets Map Our Vocal Codes!
+ vocalCheck = x['phenomena'] + '_' + x['significance']
+ vocalCode = None
+
+ if vocalCheck == 'HU_W':
+ vocalCode = 'HE001'
+ elif vocalCheck == 'TY_W':
+ vocalCode = 'HE002'
+ elif vocalCheck == 'HI_W':
+ vocalCode = 'HE003'
+ elif vocalCheck == 'TO_A':
+ vocalCode = 'HE004'
+ elif vocalCheck == 'SV_A':
+ vocalCode = 'HE005'
+ elif vocalCheck == 'HU_A':
+ vocalCode = 'HE006'
+ elif vocalCheck == 'TY_A':
+ vocalCode = 'HE007'
+ elif vocalCheck == 'TR_W':
+ vocalCode = 'HE008'
+ elif vocalCheck == 'TR_A':
+ vocalCode = 'HE009'
+ elif vocalCheck == 'TI_W':
+ vocalCode = 'HE010'
+ elif vocalCheck == 'HI_A':
+ vocalCode = 'HE011'
+ elif vocalCheck == 'TI_A':
+ vocalCode = 'HE012'
+ elif vocalCheck == 'BZ_W':
+ vocalCode = 'HE013'
+ elif vocalCheck == 'IS_W':
+ vocalCode = 'HE014'
+ elif vocalCheck == 'WS_W':
+ vocalCode = 'HE015'
+ elif vocalCheck == 'HW_W':
+ vocalCode = 'HE016'
+ elif vocalCheck == 'LE_W':
+ vocalCode = 'HE017'
+ elif vocalCheck == 'ZR_Y':
+ vocalCode = 'HE018'
+ elif vocalCheck == 'CF_W':
+ vocalCode = 'HE019'
+ elif vocalCheck == 'LS_W':
+ vocalCode = 'HE020'
+ elif vocalCheck == 'WW_Y':
+ vocalCode = 'HE021'
+ elif vocalCheck == 'LB_Y':
+ vocalCode = 'HE022'
+ elif vocalCheck == 'LE_Y':
+ vocalCode = 'HE023'
+ elif vocalCheck == 'BZ_A':
+ vocalCode = 'HE024'
+ elif vocalCheck == 'WS_A':
+ vocalCode = 'HE025'
+ elif vocalCheck == 'FF_A':
+ vocalCode = 'HE026'
+ elif vocalCheck == 'FA_A':
+ vocalCode = 'HE027'
+ elif vocalCheck == 'FA_Y':
+ vocalCode = 'HE028'
+ elif vocalCheck == 'HW_A':
+ vocalCode = 'HE029'
+ elif vocalCheck == 'LE_A':
+ vocalCode = 'HE030'
+ elif vocalCheck == 'SU_W':
+ vocalCode = 'HE031'
+ elif vocalCheck == 'LS_Y':
+ vocalCode = 'HE032'
+ elif vocalCheck == 'CF_A':
+ vocalCode = 'HE033'
+ elif vocalCheck == 'ZF_Y':
+ vocalCode = 'HE034'
+ elif vocalCheck == 'FG_Y':
+ vocalCode = 'HE035'
+ elif vocalCheck == 'SM_Y':
+ vocalCode = 'HE036'
+ elif vocalCheck == 'EC_W':
+ vocalCode = 'HE037'
+ elif vocalCheck == 'EH_W':
+ vocalCode = 'HE038'
+ elif vocalCheck == 'HZ_W':
+ vocalCode = 'HE039'
+ elif vocalCheck == 'FZ_W':
+ vocalCode = 'HE040'
+ elif vocalCheck == 'HT_Y':
+ vocalCode = 'HE041'
+ elif vocalCheck == 'WC_Y':
+ vocalCode = 'HE042'
+ elif vocalCheck == 'FR_Y':
+ vocalCode = 'HE043'
+ elif vocalCheck == 'EC_A':
+ vocalCode = 'HE044'
+ elif vocalCheck == 'EH_A':
+ vocalCode = 'HE045'
+ elif vocalCheck == 'HZ_A':
+ vocalCode = 'HE046'
+ elif vocalCheck == 'DS_W':
+ vocalCode = 'HE047'
+ elif vocalCheck == 'WI_Y':
+ vocalCode = 'HE048'
+ elif vocalCheck == 'SU_Y':
+ vocalCode = 'HE049'
+ elif vocalCheck == 'AS_Y':
+ vocalCode = 'HE050'
+ elif vocalCheck == 'WC_W':
+ vocalCode = 'HE051'
+ elif vocalCheck == 'FZ_A':
+ vocalCode = 'HE052'
+ elif vocalCheck == 'WC_A':
+ vocalCode = 'HE053'
+ elif vocalCheck == 'AF_W':
+ vocalCode = 'HE054'
+ elif vocalCheck == 'AF_Y':
+ vocalCode = 'HE055'
+ elif vocalCheck == 'DU_Y':
+ vocalCode = 'HE056'
+ elif vocalCheck == 'LW_Y':
+ vocalCode = 'HE057'
+ elif vocalCheck == 'LS_A':
+ vocalCode = 'HE058'
+ elif vocalCheck == 'HF_W':
+ vocalCode = 'HE059'
+ elif vocalCheck == 'SR_W':
+ vocalCode = 'HE060'
+ elif vocalCheck == 'GL_W':
+ vocalCode = 'HE061'
+ elif vocalCheck == 'HF_A':
+ vocalCode = 'HE062'
+ elif vocalCheck == 'UP_W':
+ vocalCode = 'HE063'
+ elif vocalCheck == 'SE_W':
+ vocalCode = 'HE064'
+ elif vocalCheck == 'SR_A':
+ vocalCode = 'HE065'
+ elif vocalCheck == 'GL_A':
+ vocalCode = 'HE066'
+ elif vocalCheck == 'MF_Y':
+ vocalCode = 'HE067'
+ elif vocalCheck == 'MS_Y':
+ vocalCode = 'HE068'
+ elif vocalCheck == 'SC_Y':
+ vocalCode = 'HE069'
+ elif vocalCheck == 'UP_Y':
+ vocalCode = 'HE073'
+ elif vocalCheck == 'LO_Y':
+ vocalCode = 'HE074'
+ elif vocalCheck == 'AF_V':
+ vocalCode = 'HE075'
+ elif vocalCheck == 'UP_A':
+ vocalCode = 'HE076'
+ elif vocalCheck == 'TAV_W':
+ vocalCode = 'HE077'
+ elif vocalCheck == 'TAV_A':
+ vocalCode = 'HE078'
+ elif vocalCheck == 'TO_W':
+ vocalCode = 'HE110'
+ else:
+ vocalCode = ''
+
+ #Do some date/time conversions
+ EndTimeUTCEpoch = x['expireTimeUTC']
+ EndTimeUTC = datetime.utcfromtimestamp(EndTimeUTCEpoch).strftime('%Y%m%d%H%M')
+ #EndTimeUTC = EndTimeUTCString.astimezone(pytz.UTC)
+
+ expireTimeEpoch = x['expireTimeUTC']
+ expireTimeUTC = datetime.utcfromtimestamp(expireTimeEpoch).strftime('%Y%m%d%H%M')
+
+ #V3 Alert API doesn't give us issueTime in UTC. So we have to convert ourselves. Ughhh!!
+ iTLDTS = x['issueTimeLocal']
+ iTLDTO = datetime.strptime(iTLDTS, '%Y-%m-%dT%H:%M:%S%z')
+ issueTimeToUTC = iTLDTO.astimezone(pytz.UTC)
+ issueTimeUtc = issueTimeToUTC.strftime('%Y%m%d%H%M')
+
+ processTimeEpoch = x['processTimeUTC']
+ processTime = datetime.fromtimestamp(processTimeEpoch).strftime('%Y%m%d%H%M%S')
+
+ #What is the action of this alert?
+ Action = None
+ if x['messageType'] == 'Update':
+ Action = 'CON'
+ elif x['messageType'] == 'New':
+ Action = 'NEW'
+
+ #Fix description to replace new lines with space and add XML escape Chars. when needed
+
+ description = ' '.join(descriptionRaw.splitlines())
+ description = description.replace('&', '&')
+ description = description.replace('<', '<')
+ description = description.replace('>', '>')
+ description = description.replace('-', '')
+ description = description.replace(':', '')
+
+ #Is this alert urgent?
+ urgency ='piss'
+ if vocalCheck == 'TO_W' or vocalCheck == 'SV_W' or vocalCheck == 'FF_W':
+ urgency = 'BEUrgent'
+ else:
+ urgency = 'BERecord'
+
+ alertMsg = 'NOT_USED' + x['productIdentifier'] + 'NOT_USED' + Action + '' + x['officeCode'] + '' + x['phenomena'] + '' + x['significance'] + '' + x['eventTrackingNumber'] + '' + x['eventDescription'] + 'NOT_USED' + EndTimeUTC + '' + str(x['severityCode']) + 'NOT_USED' + expireTimeUTC + '' + location + '' + x['adminDistrictCode'] + 'NOT_USEDNOT_USEDNOT_USED' + x['identifier'] + '' + processTime + '' + issueTimeUtc + '' + x['headlineText'] + '' + vocalCode + 'NOT_USED' + description + 'NOT_USED' + location + '_' + x['phenomena'] + '_' + x['significance'] + '_' + x['eventTrackingNumber'] + '_' + x['officeCode'] + ''
+
+ #Append BERecord
+ async with aiofiles.open('./.temp/BERecord.xml', "a") as b:
+ await b.write(alertMsg)
+ await b.close()
+
+ #Add our alert to the manifest so we don't keep sending in the same alert every 60 seconds unless an update is issued.
+ async with aiofiles.open('./.temp/alertmanifest.txt', "a") as c:
+ await c.write('\n' + location + '_' + x['phenomena'] + '_' + x['significance'] + '_' + str(x['processTimeUTC']))
+ await c.close()
+
+
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ global k
+
+ async with aiofiles.open("./.temp/BERecord.xml", 'w') as BERecord:
+ await BERecord.write('')
+ await BERecord.close()
+
+ for z in alertLocations:
+ await getAlerts(z)
+
+ async with aiofiles.open('./.temp/BERecord.xml', 'a') as BERecord:
+ await BERecord.write("")
+ await BERecord.close()
+
+ dom = xml.dom.minidom.parse("./.temp/BERecord.xml")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/BERecord.i2m", 'w') as h:
+ await h.write(pretty_xml_as_string[23:])
+ await h.close()
+
+ # The BERecord XML doesn't need to be written if there's no alerts.
+ if k > 0:
+ l.info("Sending alert(s) to the IntelliStar 2!")
+ with open("./.temp/BERecord.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/BERecord.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ files = []
+ commands = []
+ gZipFile = "./.temp/BERecord.gz"
+ files.append(gZipFile)
+ command = commands.append('')
+ bit.sendFile(files, commands, 1, 0)
+ os.remove(gZipFile)
+ k = 0
+
+ os.remove("./.temp/BERecord.xml")
+ os.remove("./.temp/BERecord.i2m")
+
diff --git a/recordGenerators/breathing.py b/recordGenerators/breathing.py
new file mode 100644
index 0000000..9dab8c1
--- /dev/null
+++ b/recordGenerators/breathing.py
@@ -0,0 +1,93 @@
+import requests
+import gzip
+import uuid
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+coopIds = []
+geocodes = []
+
+
+# Auto-grab the tecci and zip codes
+for i in MPC.getPrimaryLocations():
+ coopIds.append(LFR.getCoopId(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+l.debug(coopIds, geocodes)
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(coopId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/breathing/daypart/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+
+ #Fetch data
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ data = await r.text()
+
+ newData = data[63:-26]
+
+ l.debug('Gathering data for location id ' + coopId)
+ #Write to .i2m file
+ i2Doc = '' + '' + newData + '' + str(coopId) + ''
+
+ async with aiofiles.open("./.temp/Breathing.i2m", "a") as f:
+ await f.write(i2Doc)
+ await f.close()
+
+
+async def makeDataFile():
+ loop = asyncio.get_running_loop()
+ l.info("Writing a Breathing forecast record.")
+ header = ''
+ footer = ''
+
+ async with aiofiles.open("./.temp/Breathing.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for x, y in zip(coopIds, geocodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/Breathing.i2m", 'a') as end:
+ await end.write(footer)
+
+
+ dom = xml.dom.minidom.parse("./.temp/Breathing.i2m")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/Breathing.i2m", "w") as g:
+ await g.write(pretty_xml_as_string[23:])
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/Breathing.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/Breathing.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/Breathing.gz"
+
+ files.append(gZipFile)
+ command = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/Breathing.i2m")
+ os.remove("./.temp/Breathing.gz")
diff --git a/recordGenerators/currentObservations.py b/recordGenerators/currentObservations.py
new file mode 100644
index 0000000..d3d693e
--- /dev/null
+++ b/recordGenerators/currentObservations.py
@@ -0,0 +1,99 @@
+import requests
+import py2Lib.bit as bit
+import gzip
+import uuid
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+tecciId = []
+zipCodes = []
+
+# Auto-grab the tecci and zip codes
+for i in MPC.getPrimaryLocations():
+ tecciId.append("T" + LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+# Obtain metro map city TECCI and zips:
+for i in MPC.getMetroCities():
+ tecciId.append("T" + LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(tecci, zipCode):
+ l.debug('Gathering data for location id ' + tecci)
+ fetchUrl = 'https://api.weather.com/v1/location/' + zipCode + ':4:US/observations/current.xml?language=en-US&units=e&apiKey=' + apiKey
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ data = await r.text()
+
+ newData = data[67:-30]
+
+ #Write to .i2m file
+ i2Doc = '' + '' + newData + '' + str(tecci) + ''
+ async with aiofiles.open("./.temp/CurrentObservations.i2m", 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+
+async def makeDataFile():
+ loop = asyncio.get_running_loop()
+ l.info("Writing a CurrentObservations record.")
+ header = ''
+ footer = ''
+
+ async with aiofiles.open("./.temp/CurrentObservations.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for x, y in zip(tecciId, zipCodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/CurrentObservations.i2m", 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse("./.temp/CurrentObservations.i2m")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/CurrentObservations.i2m", "w") as g:
+ await g.write(pretty_xml_as_string[23:])
+ await g.close()
+
+ files = []
+ commands = []
+
+ """
+ TODO: This can be ran in a seperate thread using loop.run_in_executor() according to the python discord.
+ ! This should probably be implemented ASAP.
+ """
+ with open("./.temp/CurrentObservations.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/CurrentObservations.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/CurrentObservations.gz"
+
+ files.append(gZipFile)
+ command = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/CurrentObservations.i2m")
+ os.remove("./.temp/CurrentObservations.gz")
diff --git a/recordGenerators/dailyForecast.py b/recordGenerators/dailyForecast.py
new file mode 100644
index 0000000..41f1441
--- /dev/null
+++ b/recordGenerators/dailyForecast.py
@@ -0,0 +1,94 @@
+import requests
+import gzip
+import uuid
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+tecciId = []
+zipCodes = []
+
+# Auto-grab the tecci and zip codes
+for i in MPC.getPrimaryLocations():
+ tecciId.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+# Grab metro map city tecci and zip codes
+for i in MPC.getMetroCities():
+ tecciId.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(tecci, zipCode):
+ fetchUrl = 'https://api.weather.com/v1/location/' + zipCode + ':4:US/forecast/daily/7day.xml?language=en-US&units=e&apiKey=' + apiKey
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ data = await r.text()
+
+ newData = data[61:-24]
+
+ l.debug('Gathering data for location id ' + tecci)
+ #Write to .i2m file
+ i2Doc = '' + '' + newData + '' + str(tecci) + ''
+
+ async with aiofiles.open('./.temp/DailyForecast.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+
+async def makeDataFile():
+ loop = asyncio.get_running_loop()
+ l.info("Writing a DailyForecast record.")
+ header = ''
+ footer = ''
+
+ async with aiofiles.open("./.temp/DailyForecast.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for x, y in zip(tecciId, zipCodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/DailyForecast.i2m", 'a') as end:
+ await end.write(footer)
+
+
+ dom = xml.dom.minidom.parse("./.temp/DailyForecast.i2m")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/DailyForecast.i2m", "w") as g:
+ await g.write(pretty_xml_as_string[23:])
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/DailyForecast.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/DailyForecast.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/DailyForecast.gz"
+
+ files.append(gZipFile)
+ command = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/DailyForecast.i2m")
+ os.remove("./.temp/DailyForecast.gz")
diff --git a/recordGenerators/heatingAndCooling.py b/recordGenerators/heatingAndCooling.py
new file mode 100644
index 0000000..58f866a
--- /dev/null
+++ b/recordGenerators/heatingAndCooling.py
@@ -0,0 +1,85 @@
+import shutil
+import requests
+import logging,coloredlogs
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+import gzip
+from os import remove
+import xml.dom.minidom
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+geocodes = []
+coopIds = []
+
+for i in MPC.getPrimaryLocations():
+ coopIds.append(LFR.getCoopId(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(coopId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/heatCool/daypart/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ if r.status != 200:
+ l.error(f"Failed to write HeatingAndCooling record -- Status code {r.status}")
+ return
+
+ data = await r.text()
+
+ # data = res.text
+ newData = data[63:-26]
+
+ i2Doc = f'\n \n {newData}\n {coopId}\n '
+
+ async with aiofiles.open('./.temp/HeatingAndCooling.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ l.info("Writing HeatingAndCooling record.")
+
+ header = ''
+ footer = ''
+
+ async with aiofiles.open('./.temp/HeatingAndCooling.i2m', 'a') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(coopIds, geocodes):
+ await getData(x,y)
+
+ async with aiofiles.open('./.temp/HeatingAndCooling.i2m', 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse('./.temp/HeatingAndCooling.i2m')
+ xmlPretty = dom.toprettyxml(indent= " ")
+
+ async with aiofiles.open('./.temp/HeatingAndCooling.i2m', 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+
+ # Compresss i2m to gzip
+ with open ('./.temp/HeatingAndCooling.i2m', 'rb') as f_in:
+ with gzip.open('./.temp/HeatingAndCooling.gz', 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ file = "./.temp/HeatingAndCooling.gz"
+ command = ''
+
+ bit.sendFile([file], [command], 1, 0)
+
+ remove('./.temp/HeatingAndCooling.i2m')
+ remove('./.temp/HeatingAndCooling.gz')
diff --git a/recordGenerators/hourlyForecast.py b/recordGenerators/hourlyForecast.py
new file mode 100644
index 0000000..7cb3e48
--- /dev/null
+++ b/recordGenerators/hourlyForecast.py
@@ -0,0 +1,96 @@
+import requests
+import gzip
+import uuid
+import os
+import shutil
+import xml.dom.minidom
+import logging,coloredlogs
+import aiohttp, aiofiles, asyncio, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+tecciId = []
+zipCodes = []
+
+# Auto-grab the tecci and zip codes
+for i in MPC.getPrimaryLocations():
+ tecciId.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+for i in MPC.getMetroCities():
+ tecciId.append(LFR.getCoopId(i))
+ zipCodes.append(LFR.getZip(i))
+
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(tecci, zipCode):
+ l.debug('Gathering data for location id ' + tecci)
+ fetchUrl = 'https://api.weather.com/v1/location/' + zipCode + ':4:US/forecast/hourly/360hour.xml?language=en-US&units=e&apiKey=' + apiKey
+ data = ""
+
+ #Fetch data
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ data = await r.text()
+
+ newData = data[48:-11]
+
+ #Write to .i2m file
+ i2Doc = '' + '' + newData + '' + str(tecci) + ''
+
+ async with aiofiles.open('./.temp/HourlyForecast.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+
+async def makeDataFile():
+ loop = asyncio.get_running_loop()
+ l.info("Writing an HourlyForecast record.")
+ header = ''
+ footer = ''
+
+ async with aiofiles.open("./.temp/HourlyForecast.i2m", 'w') as doc:
+ await doc.write(header)
+
+
+ for x, y in zip(tecciId, zipCodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/HourlyForecast.i2m", 'a') as end:
+ await end.write(footer)
+
+
+ dom = xml.dom.minidom.parse("./.temp/HourlyForecast.i2m")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/HourlyForecast.i2m", "w") as g:
+ await g.write(pretty_xml_as_string[23:])
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/HourlyForecast.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/HourlyForecast.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/HourlyForecast.gz"
+
+ files.append(gZipFile)
+ command = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/HourlyForecast.i2m")
+ os.remove("./.temp/HourlyForecast.gz")
diff --git a/recordGenerators/mosquitoActivity.py b/recordGenerators/mosquitoActivity.py
new file mode 100644
index 0000000..94b51e7
--- /dev/null
+++ b/recordGenerators/mosquitoActivity.py
@@ -0,0 +1,85 @@
+import shutil
+import requests
+import logging,coloredlogs
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+import gzip
+from os import remove
+import xml.dom.minidom
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+geocodes = []
+coopIds = []
+
+for i in MPC.getPrimaryLocations():
+ coopIds.append(LFR.getCoopId(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(coopId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/mosquito/daily/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ if r.status != 200:
+ l.error(f"Failed to write MosquitoActivity record -- status code {r.status}")
+ return
+
+ data = await r.text()
+
+
+ newData = data[63:-26]
+
+ i2Doc = f'\n \n {newData}\n {coopId}\n '
+
+ async with aiofiles.open('./.temp/MosquitoActivity.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ l.info("Writing MosquitoActivity record.")
+
+ header = ''
+ footer = ''
+
+ async with aiofiles.open('./.temp/MosquitoActivity.i2m', 'a') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(coopIds, geocodes):
+ await getData(x,y)
+
+ async with aiofiles.open('./.temp/MosquitoActivity.i2m', 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse('./.temp/MosquitoActivity.i2m')
+ xmlPretty = dom.toprettyxml(indent= " ")
+
+ async with aiofiles.open('./.temp/MosquitoActivity.i2m', 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+
+ # Compresss i2m to gzip
+ with open ('./.temp/MosquitoActivity.i2m', 'rb') as f_in:
+ with gzip.open('./.temp/MosquitoActivity.gz', 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ file = "./.temp/MosquitoActivity.gz"
+ command = ''
+
+ bit.sendFile([file], [command], 1, 0)
+
+ remove('./.temp/MosquitoActivity.i2m')
+ remove('./.temp/MosquitoActivity.gz')
diff --git a/recordGenerators/pollenForecast.py b/recordGenerators/pollenForecast.py
new file mode 100644
index 0000000..3d28ca1
--- /dev/null
+++ b/recordGenerators/pollenForecast.py
@@ -0,0 +1,93 @@
+import requests
+import gzip
+import uuid
+import os
+import shutil
+import xml.dom.minidom
+import logging, coloredlogs
+import aiohttp, aiofiles, asyncio
+
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+pollenIds = []
+geocodes = []
+
+
+# Auto-grab the tecci and zip codes
+for i in MPC.getPrimaryLocations():
+ pollenIds.append(LFR.getPollenInfo(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+l.debug(pollenIds, geocodes)
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(pollenId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/pollen/daypart/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+ #Fetch data
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ data = await r.text()
+
+ newData = data[63:-26]
+
+ l.debug('Gathering data for location id ' + pollenId)
+ #Write to .i2m file
+ i2Doc = '' + '' + newData + '' + str(pollenId) + ''
+
+ async with aiofiles.open("./.temp/PollenForecast.i2m", "a") as f:
+ await f.write(i2Doc)
+ await f.close()
+
+
+async def makeDataFile():
+ loop = asyncio.get_running_loop()
+ l.info("Writing a PollenForecast record.")
+ header = ''
+ footer = ''
+
+ async with aiofiles.open("./.temp/PollenForecast.i2m", 'w') as doc:
+ await doc.write(header)
+
+ for x, y in zip(pollenIds, geocodes):
+ await getData(x, y)
+
+ async with aiofiles.open("./.temp/PollenForecast.i2m", 'a') as end:
+ await end.write(footer)
+
+
+ dom = xml.dom.minidom.parse("./.temp/PollenForecast.i2m")
+ pretty_xml_as_string = dom.toprettyxml(indent = " ")
+
+ async with aiofiles.open("./.temp/PollenForecast.i2m", "w") as g:
+ await g.write(pretty_xml_as_string[23:])
+ await g.close()
+
+ files = []
+ commands = []
+ with open("./.temp/PollenForecast.i2m", 'rb') as f_in:
+ with gzip.open("./.temp/PollenForecast.gz", 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ gZipFile = "./.temp/PollenForecast.gz"
+
+ files.append(gZipFile)
+ command = commands.append('')
+ numFiles = len(files)
+
+ bit.sendFile(files, commands, numFiles, 0)
+
+ os.remove("./.temp/PollenForecast.i2m")
+ os.remove("./.temp/PollenForecast.gz")
diff --git a/recordGenerators/tideForecast.py b/recordGenerators/tideForecast.py
new file mode 100644
index 0000000..2756297
--- /dev/null
+++ b/recordGenerators/tideForecast.py
@@ -0,0 +1,94 @@
+import shutil
+import logging,coloredlogs
+import datetime
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+import gzip
+from os import remove
+import xml.dom.minidom
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+geocodes = []
+tideStations = []
+
+for i in MPC.getTideStations():
+ tideStations.append(i)
+ geocodes.append(LFR.getLatLong(i))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(tideStation, geocode):
+ today = datetime.date.today()
+ startDate = today.strftime('%Y%m%d')
+ endDate_unformatted = datetime.datetime.strptime(startDate, '%Y%m%d') + datetime.timedelta(days=5)
+ endDate = endDate_unformatted.strftime('%Y%m%d')
+ data = ""
+
+ fetchUrl = f"https://api.weather.com/v1/geocode/{geocode}/forecast/tides.xml?language=en-US&units=e&startDate={startDate}&endDate={endDate}&apiKey={apiKey}"
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ if r.status != 200:
+ l.error(f"Failed to write TideForecast -- status code {r.status}")
+ return
+
+ data = await r.text()
+
+
+ newData = data[53:-16]
+
+ i2Doc = f'\n \n {newData}\n {tideStation}\n '
+
+ async with aiofiles.open('./.temp/TidesForecast.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ if len(tideStations) < 1:
+ l.debug("Skipping TidesForecast -- No locations.")
+ return
+
+ l.info("Writing TidesForecast record.")
+
+ header = ''
+ footer = ''
+
+ async with aiofiles.open('./.temp/TidesForecast.i2m', 'a') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(tideStations, geocodes):
+ await getData(x,y)
+
+ async with aiofiles.open('./.temp/TidesForecast.i2m', 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse('./.temp/TidesForecast.i2m')
+ xmlPretty = dom.toprettyxml(indent= " ")
+
+ async with aiofiles.open('./.temp/TidesForecast.i2m', 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+
+ # Compresss i2m to gzip
+ with open ('./.temp/TidesForecast.i2m', 'rb') as f_in:
+ with gzip.open('./.temp/TidesForecast.gz', 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ file = "./.temp/TidesForecast.gz"
+ command = ''
+
+ bit.sendFile([file], [command], 1, 0)
+
+ remove('./.temp/TidesForecast.i2m')
+ remove('./.temp/TidesForecast.gz')
diff --git a/recordGenerators/wateringNeeds.py b/recordGenerators/wateringNeeds.py
new file mode 100644
index 0000000..cd44f4a
--- /dev/null
+++ b/recordGenerators/wateringNeeds.py
@@ -0,0 +1,84 @@
+import shutil
+import requests
+import logging,coloredlogs
+import py2Lib.bit
+import util.machineProductCfg as MPC
+import records.lfRecord as LFR
+import gzip
+from os import remove
+import xml.dom.minidom
+import aiohttp, aiofiles, asyncio
+
+l = logging.getLogger(__name__)
+coloredlogs.install()
+
+geocodes = []
+coopIds = []
+
+for i in MPC.getPrimaryLocations():
+ coopIds.append(LFR.getCoopId(i))
+ geocodes.append(LFR.getLatLong(i).replace('/', ','))
+
+# Open the config file and make it accessible via "cfg"
+import json
+with open("config.json", "r") as file:
+ cfg = json.load(file)
+
+apiKey = cfg["twcApiKey"]
+
+async def getData(coopId, geocode):
+ fetchUrl = f"https://api.weather.com/v2/indices/wateringNeeds/daypart/7day?geocode={geocode}&language=en-US&format=xml&apiKey={apiKey}"
+ data = ""
+
+ async with aiohttp.ClientSession() as s:
+ async with s.get(fetchUrl) as r:
+ if r.status != 200:
+ l.error(f"Failed to WateringNeeds -- status code {r.status}")
+ return
+
+ data = await r.text()
+
+ newData = data[63:-26]
+
+ i2Doc = f'\n \n {newData}\n {coopId}\n '
+
+ async with aiofiles.open('./.temp/WateringNeeds.i2m', 'a') as f:
+ await f.write(i2Doc)
+ await f.close()
+
+async def makeRecord():
+ loop = asyncio.get_running_loop()
+ l.info("Writing WateringNeeds record.")
+
+ header = ''
+ footer = ''
+
+ async with aiofiles.open('./.temp/WateringNeeds.i2m', 'a') as doc:
+ await doc.write(header)
+
+ for (x, y) in zip(coopIds, geocodes):
+ await getData(x,y)
+
+ async with aiofiles.open('./.temp/WateringNeeds.i2m', 'a') as end:
+ await end.write(footer)
+
+ dom = xml.dom.minidom.parse('./.temp/WateringNeeds.i2m')
+ xmlPretty = dom.toprettyxml(indent= " ")
+
+ async with aiofiles.open('./.temp/WateringNeeds.i2m', 'w') as g:
+ await g.write(xmlPretty[23:])
+ await g.close()
+
+
+ # Compresss i2m to gzip
+ with open ('./.temp/WateringNeeds.i2m', 'rb') as f_in:
+ with gzip.open('./.temp/WateringNeeds.gz', 'wb') as f_out:
+ shutil.copyfileobj(f_in, f_out)
+
+ file = "./.temp/WateringNeeds.gz"
+ command = ''
+
+ bit.sendFile([file], [command], 1, 0)
+
+ remove('./.temp/WateringNeeds.i2m')
+ remove('./.temp/WateringNeeds.gz')
diff --git a/records/LFRecord.db b/records/LFRecord.db
new file mode 100644
index 0000000..d3fe24c
Binary files /dev/null and b/records/LFRecord.db differ
diff --git a/records/lfRecord.py b/records/lfRecord.py
new file mode 100644
index 0000000..cd15b95
--- /dev/null
+++ b/records/lfRecord.py
@@ -0,0 +1,40 @@
+import sqlite3
+
+# Make a connection to the LFRecord database
+con = sqlite3.connect("records/LFRecord.db")
+cur = con.cursor()
+
+
+def getZip(locId: str):
+ """ Returns the zip code for a given location """
+ COMMAND = (f"SELECT zip2locId FROM lfrecord WHERE locId='{locId}'")
+ cur.execute(COMMAND)
+ return cur.fetchone()[0]
+
+def getCoopId(locId: str):
+ """ Returns the TWC co-op ID for a given location """
+ COMMAND = (f"SELECT coopId FROM lfrecord WHERE locId='{locId}'")
+ cur.execute(COMMAND)
+ return cur.fetchone()[0]
+
+def getEpaId(locId: str):
+ """ Return the Air Quality station id for a given location. """
+ COMMAND = (f"SELECT epaId FROM lfrecord WHERE locId='{locId}'")
+ cur.execute(COMMAND)
+ return cur.fetchone()[0]
+
+def getPollenInfo(locId: str):
+ """ Return the Pollen forecast id for a given location. """
+ COMMAND = (f"SELECT pllnId FROM lfrecord WHERE locId='{locId}'")
+ cur.execute(COMMAND)
+ return cur.fetchone()[0]
+
+def getLatLong(locId: str):
+ """ Return the Pollen forecast id for a given location. """
+ COMMAND = (f"SELECT lat,long FROM lfrecord WHERE locId='{locId}'")
+ cur.execute(COMMAND)
+ fetched = cur.fetchone()
+ return fetched[0] + "/" + fetched[1]
+
+def getLocationInfo(locId: str):
+ pass
\ No newline at end of file
diff --git a/util/__init__.py b/util/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/util/machineProductCfg.py b/util/machineProductCfg.py
new file mode 100644
index 0000000..6549eb2
--- /dev/null
+++ b/util/machineProductCfg.py
@@ -0,0 +1,160 @@
+import json
+import sys
+import xmltodict
+
+
+# Open the MachineProductCfg.xml file in the root directory
+try:
+ with open("MachineProductCfg.xml", mode = 'r', encoding= 'utf-8') as MPCxml:
+ MPCdict = xmltodict.parse(MPCxml.read())
+ MPCdump = json.dumps(MPCdict)
+ data = json.loads(MPCdump)
+except Exception as e:
+ print(e)
+ sys.exit("There was an error opening your MachineProductCfg.xml. Is the file in the root folder?")
+
+
+def getPrimaryLocations():
+ """ Returns all of the primary locations in the MachineProductCfg """
+ locationIds = []
+ # iterate on the json data and grab anything that has PrimaryLocation.
+ # Also needs to return anything in the Regional area.
+ for i in data['Config']['ConfigDef']['ConfigItems']['ConfigItem']:
+ if "PrimaryLocation" in i['@key'] and i['@value'] != "":
+ # Split the string up
+ locationIds.append(i['@value'].split("_")[2])
+
+ if "NearbyLocation" in i['@key'] and i['@value'] != "":
+ locationIds.append(i['@value'].split("_")[2])
+
+ return locationIds
+
+def getMetroCities():
+ """ Returns all Metro Map locations in the MPC """
+ locationIds = []
+
+ for i in data['Config']['ConfigDef']['ConfigItems']['ConfigItem']:
+ if 'MetroMapCity' in i['@key'] and i['@value'] != "":
+ locationIds.append(i['@value'].split("_")[2])
+
+ return locationIds
+
+def getTideStations():
+ """ Returns all of the tide stations present in the MachineProductCfg """
+ stations = []
+ for i in data['Config']['ConfigDef']['ConfigItems']['ConfigItem']:
+ if "TideStation" in i['@key'] and i['@value'] != "":
+ stations.append(i['@value'].split("_")[2])
+
+ return stations
+
+def getAirportCodes():
+ """ Returns all of the airport identifiers present in the MachineProductCfg """
+ airports = [
+ 'ATL',
+ 'LAX',
+ 'ORD',
+ 'DFW',
+ 'JFK',
+ 'DEN',
+ 'SFO',
+ 'CLT',
+ 'LAS',
+ 'PHX',
+ 'IAH',
+ 'MIA',
+ 'SEA',
+ 'EWR',
+ 'MCO',
+ 'MSP',
+ 'DTW',
+ 'BOS',
+ 'PHL',
+ 'LGA',
+ 'FLL',
+ 'BWI',
+ 'IAD',
+ 'MDW',
+ 'SLC',
+ 'DCA',
+ 'HNL',
+ 'SAN',
+ 'TPA',
+ 'PDX',
+ 'STL',
+ 'HOU',
+ 'BNA',
+ 'AUS',
+ 'OAK',
+ 'MSY',
+ 'RDU',
+ 'SJC',
+ 'SNA',
+ 'DAL',
+ 'SMF',
+ 'SAT',
+ 'RSW',
+ 'PIT',
+ 'CLE',
+ 'IND',
+ 'MKE',
+ 'CMH',
+ 'OGG',
+ 'PBI',
+ 'BDL',
+ 'CVG',
+ 'JAX',
+ 'ANC',
+ 'BUF',
+ 'ABQ',
+ 'ONT',
+ 'OMA',
+ 'BUR',
+ 'OKC',
+ 'MEM',
+ 'PVD',
+ 'RIC',
+ 'SDF',
+ 'RNO',
+ 'TUS',
+ 'CHS',
+ 'ORF',
+ 'PWM',
+ 'GRR',
+ 'BHM',
+ 'LIT',
+ 'DSM',
+ 'FAR',
+ 'FSD',
+ 'ICT',
+ 'LBB',
+ 'BIL',
+ 'BOI',
+ 'GEG'
+ ]
+ for i in data['Config']['ConfigDef']['ConfigItems']['ConfigItem']:
+ if "Airport" in i['@key'] and i['@value'] != "" and not i['@value'] in airports:
+ # Split the string up
+ airports.append(i['@value'].split("_")[2])
+
+ return airports
+
+def getAlertZones():
+ """ Returns a list of zones present in the MachineProductCfg """
+ zones = []
+ for i in data['Config']['ConfigDef']['ConfigItems']['ConfigItem']:
+ if i['@key'] == "primaryZone" and i['@value'] != "":
+ zones.append(i['@value']) # This should only be one value
+
+ if i['@key'] == "secondaryZones" and i['@value'] != "":
+ for x in i['@value'].split(','):
+ zones.append(x)
+
+ if i['@key'] == 'primaryCounty' and i['@value'] != "":
+ zones.append(i['@value'])
+
+ if i['@key'] == "secondaryCounties" and i['@value'] != "":
+ for x in i['@value'].split(','):
+ zones.append(x)
+
+ return zones
\ No newline at end of file
diff --git a/util/util.py b/util/util.py
new file mode 100644
index 0000000..c67f32c
--- /dev/null
+++ b/util/util.py
@@ -0,0 +1,7 @@
+import re
+
+def sort_alphanumeric(data):
+ """ Sorts a list alphanumerically """
+ convert = lambda text: int(text) if text.isdigit() else text.lower()
+ alphanum_key = lambda key: [convert(c) for c in re.split('([0.9]+)', key)]
+ return(sorted(data, key=alphanum_key))
\ No newline at end of file