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
|
#!/usr/bin/python
|
||||||
import urllib, urllib2, cgi, re, math
|
import urllib
|
||||||
import json, datetime, time, sys, calendar
|
import urllib2
|
||||||
import pytz, ephem
|
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
|
from datetime import datetime, timedelta, date
|
||||||
|
|
||||||
|
|
||||||
def safe_float(s, dv):
|
def safe_float(s, dv):
|
||||||
r = dv
|
r = dv
|
||||||
try:
|
try:
|
||||||
@@ -11,6 +22,7 @@ def safe_float(s, dv):
|
|||||||
return dv
|
return dv
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def safe_int(s, dv):
|
def safe_int(s, dv):
|
||||||
r = dv
|
r = dv
|
||||||
try:
|
try:
|
||||||
@@ -19,6 +31,7 @@ def safe_int(s, dv):
|
|||||||
return dv
|
return dv
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def isInt(s):
|
def isInt(s):
|
||||||
try:
|
try:
|
||||||
_v = int(s)
|
_v = int(s)
|
||||||
@@ -26,6 +39,7 @@ def isInt(s):
|
|||||||
return 0
|
return 0
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def isFloat(s):
|
def isFloat(s):
|
||||||
try:
|
try:
|
||||||
_f = float(s)
|
_f = float(s)
|
||||||
@@ -33,35 +47,42 @@ def isFloat(s):
|
|||||||
return 0
|
return 0
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def F2C(temp):
|
def F2C(temp):
|
||||||
return (temp - 32) * 5 / 9
|
return (temp - 32) * 5 / 9
|
||||||
|
|
||||||
|
|
||||||
def C2F(temp):
|
def C2F(temp):
|
||||||
return temp * 9 / 5 + 32
|
return temp * 9 / 5 + 32
|
||||||
|
|
||||||
|
|
||||||
def mm2in(x):
|
def mm2in(x):
|
||||||
return x * 0.03937008
|
return x * 0.03937008
|
||||||
|
|
||||||
|
|
||||||
def ft2m(x):
|
def ft2m(x):
|
||||||
return x * 0.3048
|
return x * 0.3048
|
||||||
|
|
||||||
|
|
||||||
def IP2Int(ip):
|
def IP2Int(ip):
|
||||||
o = map(int, ip.split('.'))
|
o = map(int, ip.split('.'))
|
||||||
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
|
res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def getClientAddress(environ):
|
def getClientAddress(environ):
|
||||||
try:
|
try:
|
||||||
return environ['HTTP_X_FORWARDED_FOR'].split(',')[-1].strip()
|
return environ['HTTP_X_FORWARDED_FOR'].split(',')[-1].strip()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return environ['REMOTE_ADDR']
|
return environ['REMOTE_ADDR']
|
||||||
|
|
||||||
|
|
||||||
def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hum_high, hum_low, hum_avg, wind, solar):
|
def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hum_high, hum_low, hum_avg, wind, solar):
|
||||||
tm = time.gmtime()
|
tm = time.gmtime()
|
||||||
dayofyear = tm.tm_yday
|
dayofyear = tm.tm_yday
|
||||||
|
|
||||||
latitude = safe_float(latitude, 0);
|
latitude = safe_float(latitude, 0)
|
||||||
longitude = safe_float(longitude, 0);
|
longitude = safe_float(longitude, 0)
|
||||||
|
|
||||||
# Converted values
|
# Converted values
|
||||||
El = ft2m(elevation)
|
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)
|
dr = 1 + 0.033 * math.cos(2 * math.pi * dayofyear / 365)
|
||||||
delta = 0.409 * math.sin(2 * math.pi * dayofyear / 365 - 1.39)
|
delta = 0.409 * math.sin(2 * math.pi * dayofyear / 365 - 1.39)
|
||||||
omegas = math.acos(-math.tan(phi) * math.tan(delta))
|
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
|
# 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
|
ea = (esTx + esTn) / 2 # 22
|
||||||
|
|
||||||
epsilonp = 0.34 - 0.14 * math.sqrt(ea) # 12
|
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
|
Rn = Rns + Rnl
|
||||||
|
|
||||||
# Step 3: variables needed to compute ET
|
# 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
|
# Step 5: calculate ET0
|
||||||
|
|
||||||
R0 = 0.408 * Delta * (Rn - G) / (Delta + gamma * (1 + 0.34 * U2)) # 24
|
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
|
ET0 = R0 + A0
|
||||||
|
|
||||||
# Step 6: calculate ETr
|
# Step 6: calculate ETr
|
||||||
|
|
||||||
Rr = 0.408 * Delta * (Rn - G) / (Delta + gamma * (1 + 0.38 * U2)) # 27
|
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
|
ETr = Rr + Ar
|
||||||
|
|
||||||
return (mm2in(ETh), mm2in(ET0), mm2in(ETr))
|
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):
|
def application(environ, start_response):
|
||||||
path = environ.get('PATH_INFO')
|
path = environ.get('PATH_INFO')
|
||||||
uwt = re.match('/weather(\d+)\.py', path)
|
uwt = re.match('/weather(\d+)\.py', path)
|
||||||
parameters = cgi.parse_qs(environ.get('QUERY_STRING', ''))
|
parameters = cgi.parse_qs(environ.get('QUERY_STRING', ''))
|
||||||
status = '200 OK'
|
status = '200 OK'
|
||||||
|
wto = {}
|
||||||
|
|
||||||
if uwt:
|
if uwt is not None:
|
||||||
uwt = safe_int(uwt.group(1), 0)
|
uwt = safe_int(uwt.group(1), 0)
|
||||||
else:
|
else:
|
||||||
uwt = 0
|
return not_found(environ, start_response)
|
||||||
|
|
||||||
if 'loc' in parameters:
|
if 'loc' in parameters:
|
||||||
loc = parameters['loc'][0]
|
loc = parameters['loc'][0]
|
||||||
else:
|
else:
|
||||||
loc = ''
|
loc = ''
|
||||||
|
|
||||||
if 'key' in parameters:
|
if 'key' in parameters:
|
||||||
key = parameters['key'][0]
|
key = parameters['key'][0]
|
||||||
else:
|
else:
|
||||||
key = ''
|
key = ''
|
||||||
|
|
||||||
if 'format' in parameters:
|
if 'format' in parameters:
|
||||||
of = parameters['format'][0]
|
of = parameters['format'][0]
|
||||||
else:
|
else:
|
||||||
of = ''
|
of = ''
|
||||||
|
|
||||||
if 'fwv' in parameters:
|
if 'fwv' in parameters:
|
||||||
fwv = parameters['fwv'][0]
|
fwv = parameters['fwv'][0]
|
||||||
else:
|
else:
|
||||||
fwv = ''
|
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]
|
ET = [0, 0, 0]
|
||||||
eip = IP2Int(getClientAddress(environ))
|
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 is pws, query wunderground geolookup to get GPS coordinates
|
||||||
if loc.startswith('pws:') or loc.startswith('icao:'):
|
if loc.startswith('pws:') or loc.startswith('icao:'):
|
||||||
try:
|
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)
|
dat = json.load(req)
|
||||||
if 'current_observation' in dat:
|
if 'current_observation' in dat:
|
||||||
v = dat['current_observation']['observation_location']['latitude']
|
v = dat['current_observation'][
|
||||||
|
'observation_location']['latitude']
|
||||||
if v and isFloat(v):
|
if v and isFloat(v):
|
||||||
lat = v
|
lat = v
|
||||||
v = dat['current_observation']['observation_location']['longitude']
|
v = dat['current_observation'][
|
||||||
|
'observation_location']['longitude']
|
||||||
if v and isFloat(v):
|
if v and isFloat(v):
|
||||||
lon = v
|
lon = v
|
||||||
v = dat['current_observation']['observation_location']['elevation']
|
v = dat['current_observation'][
|
||||||
|
'observation_location']['elevation']
|
||||||
if v:
|
if v:
|
||||||
elevation = safe_int(int(v.split()[0]), 0)
|
elevation = safe_int(int(v.split()[0]), 0)
|
||||||
v = dat['current_observation']['solarradiation']
|
v = dat['current_observation']['solarradiation']
|
||||||
@@ -230,7 +274,8 @@ def application(environ, start_response):
|
|||||||
# now do autocomplete lookup to get GPS coordinates
|
# now do autocomplete lookup to get GPS coordinates
|
||||||
if lat == None or lon == None:
|
if lat == None or lon == None:
|
||||||
try:
|
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)
|
dat = json.load(req)
|
||||||
if dat['RESULTS']:
|
if dat['RESULTS']:
|
||||||
v = dat['RESULTS'][0]['lat']
|
v = dat['RESULTS'][0]['lat']
|
||||||
@@ -264,20 +309,23 @@ def application(environ, start_response):
|
|||||||
sun = ephem.Sun()
|
sun = ephem.Sun()
|
||||||
sun.compute(home)
|
sun.compute(home)
|
||||||
|
|
||||||
sunrise = calendar.timegm(home.next_rising(sun).datetime().utctimetuple())
|
sunrise = calendar.timegm(
|
||||||
sunset = calendar.timegm(home.next_setting(sun).datetime().utctimetuple())
|
home.next_rising(sun).datetime().utctimetuple())
|
||||||
|
sunset = calendar.timegm(
|
||||||
|
home.next_setting(sun).datetime().utctimetuple())
|
||||||
|
|
||||||
if tzone:
|
if tzone:
|
||||||
try:
|
try:
|
||||||
tnow = pytz.utc.localize(datetime.utcnow())
|
tnow = pytz.utc.localize(datetime.utcnow())
|
||||||
tdelta = tnow.astimezone(pytz.timezone(tzone)).utcoffset()
|
tdelta = tnow.astimezone(pytz.timezone(tzone)).utcoffset()
|
||||||
toffset = tdelta.days*96+tdelta.seconds/900+48;
|
toffset = tdelta.days * 96 + tdelta.seconds / 900 + 48
|
||||||
except:
|
except:
|
||||||
toffset = -1
|
toffset = -1
|
||||||
|
|
||||||
if (key != ''):
|
if (key != ''):
|
||||||
try:
|
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)
|
dat = json.load(req)
|
||||||
|
|
||||||
if dat['history'] and dat['history']['dailysummary']:
|
if dat['history'] and dat['history']['dailysummary']:
|
||||||
@@ -306,7 +354,8 @@ def application(environ, start_response):
|
|||||||
|
|
||||||
# Check which weather method is being used
|
# Check which weather method is being used
|
||||||
if ((uwt & ~(1 << 7)) == 1):
|
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
|
hf = 0
|
||||||
if (maxh >= 0) and (minh >= 0):
|
if (maxh >= 0) and (minh >= 0):
|
||||||
hf = 30 - (maxh + minh) / 2
|
hf = 30 - (maxh + minh) / 2
|
||||||
@@ -320,6 +369,16 @@ def application(environ, start_response):
|
|||||||
rf -= pre * 200
|
rf -= pre * 200
|
||||||
if (pre_today >= 0):
|
if (pre_today >= 0):
|
||||||
rf -= pre_today * 200
|
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)
|
scale = (int)(100 + hf + tf + rf)
|
||||||
|
|
||||||
if (scale < 0):
|
if (scale < 0):
|
||||||
@@ -328,18 +387,22 @@ def application(environ, start_response):
|
|||||||
scale = 200
|
scale = 200
|
||||||
|
|
||||||
elif ((uwt & ~(1 << 7)) == 2):
|
elif ((uwt & ~(1 << 7)) == 2):
|
||||||
ET = computeETs(lat, lon, elevation, maxt, mint, meant, maxhumidity, minhumidity, avehumidity, wind, solar)
|
ET = computeETs(lat, lon, elevation, maxt, mint, meant,
|
||||||
# TODO: Actually generate correct scale using ET (ET[1] is ET0 for short canopy)
|
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)
|
scale = safe_int(ET[1] * 100, -1)
|
||||||
|
|
||||||
# Check weather modifier bits and apply scale modification
|
# Check weather modifier bits and apply scale modification
|
||||||
if ((uwt >> 7) & 1):
|
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
|
# Get before yesterday's weather data
|
||||||
beforeYesterday = date.today() - timedelta(2)
|
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)
|
dat = json.load(req)
|
||||||
|
|
||||||
if dat['history'] and dat['history']['dailysummary']:
|
if dat['history'] and dat['history']['dailysummary']:
|
||||||
@@ -356,9 +419,11 @@ def application(environ, start_response):
|
|||||||
except:
|
except:
|
||||||
pass
|
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:
|
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
|
# prepare sunrise sunset time
|
||||||
delta = 3600 / 4 * (toffset - 48)
|
delta = 3600 / 4 * (toffset - 48)
|
||||||
@@ -368,12 +433,14 @@ def application(environ, start_response):
|
|||||||
sunset = int(((sunset + delta) % 86400) / 60)
|
sunset = int(((sunset + delta) % 86400) / 60)
|
||||||
|
|
||||||
if of == 'json' or of == 'JSON':
|
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:
|
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)
|
start_response(status, response_headers)
|
||||||
|
|
||||||
return [output]
|
return [output]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user