Base: Add support for weather adjustments options
This commit is contained in:
131
application.py
131
application.py
@@ -1,8 +1,19 @@
|
||||
#!/usr/bin/python
|
||||
import urllib, urllib2, cgi, re, math
|
||||
import json, datetime, time, sys, calendar
|
||||
import pytz, ephem
|
||||
import urllib
|
||||
import urllib2
|
||||
import cgi
|
||||
import re
|
||||
import math
|
||||
import json
|
||||
import datetime
|
||||
import time
|
||||
import sys
|
||||
import calendar
|
||||
import pytz
|
||||
import ephem
|
||||
from datetime import datetime, timedelta, date
|
||||
|
||||
|
||||
def safe_float(s, dv):
|
||||
r = dv
|
||||
try:
|
||||
@@ -11,6 +22,7 @@ def safe_float(s, dv):
|
||||
return dv
|
||||
return r
|
||||
|
||||
|
||||
def safe_int(s, dv):
|
||||
r = dv
|
||||
try:
|
||||
@@ -19,6 +31,7 @@ def safe_int(s, dv):
|
||||
return dv
|
||||
return r
|
||||
|
||||
|
||||
def isInt(s):
|
||||
try:
|
||||
_v = int(s)
|
||||
@@ -26,6 +39,7 @@ def isInt(s):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def isFloat(s):
|
||||
try:
|
||||
_f = float(s)
|
||||
@@ -33,35 +47,42 @@ def isFloat(s):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def F2C(temp):
|
||||
return (temp - 32) * 5 / 9
|
||||
|
||||
|
||||
def C2F(temp):
|
||||
return temp * 9 / 5 + 32
|
||||
|
||||
|
||||
def mm2in(x):
|
||||
return x * 0.03937008
|
||||
|
||||
|
||||
def ft2m(x):
|
||||
return x * 0.3048
|
||||
|
||||
|
||||
def IP2Int(ip):
|
||||
o = map(int, ip.split('.'))
|
||||
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
|
||||
return res
|
||||
|
||||
|
||||
def getClientAddress(environ):
|
||||
try:
|
||||
return environ['HTTP_X_FORWARDED_FOR'].split(',')[-1].strip()
|
||||
except KeyError:
|
||||
return environ['REMOTE_ADDR']
|
||||
|
||||
|
||||
def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hum_high, hum_low, hum_avg, wind, solar):
|
||||
tm = time.gmtime()
|
||||
dayofyear = tm.tm_yday
|
||||
|
||||
latitude = safe_float(latitude, 0);
|
||||
longitude = safe_float(longitude, 0);
|
||||
latitude = safe_float(latitude, 0)
|
||||
longitude = safe_float(longitude, 0)
|
||||
|
||||
# Converted values
|
||||
El = ft2m(elevation)
|
||||
@@ -83,7 +104,8 @@ def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hu
|
||||
dr = 1 + 0.033 * math.cos(2 * math.pi * dayofyear / 365)
|
||||
delta = 0.409 * math.sin(2 * math.pi * dayofyear / 365 - 1.39)
|
||||
omegas = math.acos(-math.tan(phi) * math.tan(delta))
|
||||
Ra = (24*60/math.pi)*Gsc*dr*(omegas*math.sin(delta)*math.sin(phi)+math.cos(phi)*math.cos(delta)*math.sin(omegas))
|
||||
Ra = (24 * 60 / math.pi) * Gsc * dr * (omegas * math.sin(delta)
|
||||
* math.sin(phi) + math.cos(phi) * math.cos(delta) * math.sin(omegas))
|
||||
|
||||
# Step 2: Daily net radiation
|
||||
|
||||
@@ -97,7 +119,8 @@ def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hu
|
||||
ea = (esTx + esTn) / 2 # 22
|
||||
|
||||
epsilonp = 0.34 - 0.14 * math.sqrt(ea) # 12
|
||||
Rnl = -f*epsilonp*sigma*((Tx+273.14)**4 + (Tn+273.15)**4)/2 # 13
|
||||
Rnl = -f * epsilonp * sigma * \
|
||||
((Tx + 273.14) ** 4 + (Tn + 273.15) ** 4) / 2 # 13
|
||||
Rn = Rns + Rnl
|
||||
|
||||
# Step 3: variables needed to compute ET
|
||||
@@ -117,46 +140,63 @@ def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hu
|
||||
# Step 5: calculate ET0
|
||||
|
||||
R0 = 0.408 * Delta * (Rn - G) / (Delta + gamma * (1 + 0.34 * U2)) # 24
|
||||
A0 = (900*gamma/(Tm+273))*U2*(ea-ed) / (Delta+gamma*(1+0.34*U2)) # 25
|
||||
A0 = (900 * gamma / (Tm + 273)) * U2 * (ea - ed) / \
|
||||
(Delta + gamma * (1 + 0.34 * U2)) # 25
|
||||
ET0 = R0 + A0
|
||||
|
||||
# Step 6: calculate ETr
|
||||
|
||||
Rr = 0.408 * Delta * (Rn - G) / (Delta + gamma * (1 + 0.38 * U2)) # 27
|
||||
Ar = (1600*gamma/(Tm+273))*U2*(ea-ed) / (Delta+gamma*(1+0.38*U2)) # 28
|
||||
Ar = (1600 * gamma / (Tm + 273)) * U2 * (ea - ed) / \
|
||||
(Delta + gamma * (1 + 0.38 * U2)) # 28
|
||||
ETr = Rr + Ar
|
||||
|
||||
return (mm2in(ETh), mm2in(ET0), mm2in(ETr))
|
||||
|
||||
|
||||
def not_found(environ, start_response):
|
||||
"""Called if no URL matches."""
|
||||
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
|
||||
return ['Not Found']
|
||||
|
||||
|
||||
def application(environ, start_response):
|
||||
path = environ.get('PATH_INFO')
|
||||
uwt = re.match('/weather(\d+)\.py', path)
|
||||
parameters = cgi.parse_qs(environ.get('QUERY_STRING', ''))
|
||||
status = '200 OK'
|
||||
wto = {}
|
||||
|
||||
if uwt:
|
||||
if uwt is not None:
|
||||
uwt = safe_int(uwt.group(1), 0)
|
||||
else:
|
||||
uwt = 0
|
||||
return not_found(environ, start_response)
|
||||
|
||||
if 'loc' in parameters:
|
||||
loc = parameters['loc'][0]
|
||||
else:
|
||||
loc = ''
|
||||
|
||||
if 'key' in parameters:
|
||||
key = parameters['key'][0]
|
||||
else:
|
||||
key = ''
|
||||
|
||||
if 'format' in parameters:
|
||||
of = parameters['format'][0]
|
||||
else:
|
||||
of = ''
|
||||
|
||||
if 'fwv' in parameters:
|
||||
fwv = parameters['fwv'][0]
|
||||
else:
|
||||
fwv = ''
|
||||
|
||||
solar, wind, avehumidity, minhumidity, maxhumidity, maxt, mint, elevation, restrict, maxh, minh, meant, pre, pre_today, h_today, sunrise, sunset, scale, toffset = [0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -500, -1, -1, -1, -1, -1, -1, -1]
|
||||
if 'wto' in parameters:
|
||||
wto = json.loads('{' + parameters['wto'][0] + '}')
|
||||
|
||||
solar, wind, avehumidity, minhumidity, maxhumidity, maxt, mint, elevation, restrict, maxh, minh, meant, pre, pre_today, h_today, sunrise, sunset, scale, toffset = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -500, -1, -1, -1, -1, -1, -1, -1]
|
||||
ET = [0, 0, 0]
|
||||
eip = IP2Int(getClientAddress(environ))
|
||||
|
||||
@@ -178,16 +218,20 @@ def application(environ, start_response):
|
||||
# if loc is pws, query wunderground geolookup to get GPS coordinates
|
||||
if loc.startswith('pws:') or loc.startswith('icao:'):
|
||||
try:
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/'+key+'/conditions/forecast/q/'+urllib.quote(loc)+'.json')
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/' +
|
||||
key + '/conditions/forecast/q/' + urllib.quote(loc) + '.json')
|
||||
dat = json.load(req)
|
||||
if 'current_observation' in dat:
|
||||
v = dat['current_observation']['observation_location']['latitude']
|
||||
v = dat['current_observation'][
|
||||
'observation_location']['latitude']
|
||||
if v and isFloat(v):
|
||||
lat = v
|
||||
v = dat['current_observation']['observation_location']['longitude']
|
||||
v = dat['current_observation'][
|
||||
'observation_location']['longitude']
|
||||
if v and isFloat(v):
|
||||
lon = v
|
||||
v = dat['current_observation']['observation_location']['elevation']
|
||||
v = dat['current_observation'][
|
||||
'observation_location']['elevation']
|
||||
if v:
|
||||
elevation = safe_int(int(v.split()[0]), 0)
|
||||
v = dat['current_observation']['solarradiation']
|
||||
@@ -230,7 +274,8 @@ def application(environ, start_response):
|
||||
# now do autocomplete lookup to get GPS coordinates
|
||||
if lat == None or lon == None:
|
||||
try:
|
||||
req = urllib2.urlopen('http://autocomplete.wunderground.com/aq?h=0&query='+urllib.quote(loc))
|
||||
req = urllib2.urlopen(
|
||||
'http://autocomplete.wunderground.com/aq?h=0&query=' + urllib.quote(loc))
|
||||
dat = json.load(req)
|
||||
if dat['RESULTS']:
|
||||
v = dat['RESULTS'][0]['lat']
|
||||
@@ -264,20 +309,23 @@ def application(environ, start_response):
|
||||
sun = ephem.Sun()
|
||||
sun.compute(home)
|
||||
|
||||
sunrise = calendar.timegm(home.next_rising(sun).datetime().utctimetuple())
|
||||
sunset = calendar.timegm(home.next_setting(sun).datetime().utctimetuple())
|
||||
sunrise = calendar.timegm(
|
||||
home.next_rising(sun).datetime().utctimetuple())
|
||||
sunset = calendar.timegm(
|
||||
home.next_setting(sun).datetime().utctimetuple())
|
||||
|
||||
if tzone:
|
||||
try:
|
||||
tnow = pytz.utc.localize(datetime.utcnow())
|
||||
tdelta = tnow.astimezone(pytz.timezone(tzone)).utcoffset()
|
||||
toffset = tdelta.days*96+tdelta.seconds/900+48;
|
||||
toffset = tdelta.days * 96 + tdelta.seconds / 900 + 48
|
||||
except:
|
||||
toffset = -1
|
||||
|
||||
if (key != ''):
|
||||
try:
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/'+key+'/yesterday/conditions/q/'+urllib.quote(loc)+'.json')
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/' +
|
||||
key + '/yesterday/conditions/q/' + urllib.quote(loc) + '.json')
|
||||
dat = json.load(req)
|
||||
|
||||
if dat['history'] and dat['history']['dailysummary']:
|
||||
@@ -306,7 +354,8 @@ def application(environ, start_response):
|
||||
|
||||
# Check which weather method is being used
|
||||
if ((uwt & ~(1 << 7)) == 1):
|
||||
# calculate water time scale, per https://github.com/rszimm/sprinklers_pi/blob/master/Weather.cpp
|
||||
# calculate water time scale, per
|
||||
# https://github.com/rszimm/sprinklers_pi/blob/master/Weather.cpp
|
||||
hf = 0
|
||||
if (maxh >= 0) and (minh >= 0):
|
||||
hf = 30 - (maxh + minh) / 2
|
||||
@@ -320,6 +369,16 @@ def application(environ, start_response):
|
||||
rf -= pre * 200
|
||||
if (pre_today >= 0):
|
||||
rf -= pre_today * 200
|
||||
|
||||
if 'temp' in wto:
|
||||
tf = tf * (wto['temp'] / 100.0)
|
||||
|
||||
if 'humidity' in wto:
|
||||
hf = hf * (wto['humidity'] / 100.0)
|
||||
|
||||
if 'rain' in wto:
|
||||
rf = rf * (wto['rain'] / 100.0)
|
||||
|
||||
scale = (int)(100 + hf + tf + rf)
|
||||
|
||||
if (scale < 0):
|
||||
@@ -328,18 +387,22 @@ def application(environ, start_response):
|
||||
scale = 200
|
||||
|
||||
elif ((uwt & ~(1 << 7)) == 2):
|
||||
ET = computeETs(lat, lon, elevation, maxt, mint, meant, maxhumidity, minhumidity, avehumidity, wind, solar)
|
||||
# TODO: Actually generate correct scale using ET (ET[1] is ET0 for short canopy)
|
||||
ET = computeETs(lat, lon, elevation, maxt, mint, meant,
|
||||
maxhumidity, minhumidity, avehumidity, wind, solar)
|
||||
# TODO: Actually generate correct scale using ET (ET[1] is ET0
|
||||
# for short canopy)
|
||||
scale = safe_int(ET[1] * 100, -1)
|
||||
|
||||
# Check weather modifier bits and apply scale modification
|
||||
if ((uwt >> 7) & 1):
|
||||
# California modification to prevent watering when rain has occured within 48 hours
|
||||
# California modification to prevent watering when rain has
|
||||
# occured within 48 hours
|
||||
|
||||
# Get before yesterday's weather data
|
||||
beforeYesterday = date.today() - timedelta(2)
|
||||
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/'+key+'/history_'+beforeYesterday.strftime('%Y%m%d')+'/q/'+urllib.quote(loc)+'.json')
|
||||
req = urllib2.urlopen('http://api.wunderground.com/api/' + key + '/history_' +
|
||||
beforeYesterday.strftime('%Y%m%d') + '/q/' + urllib.quote(loc) + '.json')
|
||||
dat = json.load(req)
|
||||
|
||||
if dat['history'] and dat['history']['dailysummary']:
|
||||
@@ -356,9 +419,11 @@ def application(environ, start_response):
|
||||
except:
|
||||
pass
|
||||
|
||||
urllib2.urlopen('https://ssl.google-analytics.com/collect?v=1&t=event&ec=weather&ea=lookup&el=results&ev='+str(scale)+'&cd1='+str(fwv)+'&cd2='+urllib.quote(loc)+'&cd3='+str(toffset)+'&cid=555&tid=UA-57507808-1&z='+str(time.time()))
|
||||
urllib2.urlopen('https://ssl.google-analytics.com/collect?v=1&t=event&ec=weather&ea=lookup&el=results&ev=' + str(scale) +
|
||||
'&cd1=' + str(fwv) + '&cd2=' + urllib.quote(loc) + '&cd3=' + str(toffset) + '&cid=555&tid=UA-57507808-1&z=' + str(time.time()))
|
||||
else:
|
||||
urllib2.urlopen('https://ssl.google-analytics.com/collect?v=1&t=event&ec=timezone&ea=lookup&el=results&ev='+str(toffset)+'&cd1='+str(fwv)+'&cd2='+urllib.quote(loc)+'&cid=555&tid=UA-57507808-1&z='+str(time.time()))
|
||||
urllib2.urlopen('https://ssl.google-analytics.com/collect?v=1&t=event&ec=timezone&ea=lookup&el=results&ev=' + str(
|
||||
toffset) + '&cd1=' + str(fwv) + '&cd2=' + urllib.quote(loc) + '&cid=555&tid=UA-57507808-1&z=' + str(time.time()))
|
||||
|
||||
# prepare sunrise sunset time
|
||||
delta = 3600 / 4 * (toffset - 48)
|
||||
@@ -368,12 +433,14 @@ def application(environ, start_response):
|
||||
sunset = int(((sunset + delta) % 86400) / 60)
|
||||
|
||||
if of == 'json' or of == 'JSON':
|
||||
output = '{"scale":%d, "restrict":%d, "tz":%d, "sunrise":%d, "sunset":%d, "maxh":%d, "minh":%d, "meant":%d, "pre":%f, "prec":%f, "hc":%d, "eip":%d}' % (scale, restrict, toffset, sunrise, sunset, int(maxh), int(minh), int(meant), pre, pre_today, int(h_today), eip)
|
||||
output = '{"scale":%d, "restrict":%d, "tz":%d, "sunrise":%d, "sunset":%d, "maxh":%d, "minh":%d, "meant":%d, "pre":%f, "prec":%f, "hc":%d, "eip":%d}' % (
|
||||
scale, restrict, toffset, sunrise, sunset, int(maxh), int(minh), int(meant), pre, pre_today, int(h_today), eip)
|
||||
else:
|
||||
output = '&scale=%d&restrict=%d&tz=%d&sunrise=%d&sunset=%d&maxh=%d&minh=%d&meant=%d&pre=%f&prec=%f&hc=%d&eip=%d' % (scale, restrict, toffset, sunrise, sunset, int(maxh), int(minh), int(meant), pre, pre_today, int(h_today), eip)
|
||||
output = '&scale=%d&restrict=%d&tz=%d&sunrise=%d&sunset=%d&maxh=%d&minh=%d&meant=%d&pre=%f&prec=%f&hc=%d&eip=%d' % (
|
||||
scale, restrict, toffset, sunrise, sunset, int(maxh), int(minh), int(meant), pre, pre_today, int(h_today), eip)
|
||||
|
||||
response_headers = [('Content-type', 'text/plain'),('Content-Length', str(len(output)))]
|
||||
response_headers = [
|
||||
('Content-type', 'text/plain'), ('Content-Length', str(len(output)))]
|
||||
start_response(status, response_headers)
|
||||
|
||||
return [output]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user