Base: Add support for weather adjustments options

This commit is contained in:
Samer Albahra
2015-06-05 21:45:37 -05:00
parent b4fa0f3350
commit 75e167522b

View File

@@ -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
return (temp - 32) * 5 / 9
def C2F(temp):
return temp*9/5 + 32
return temp * 9 / 5 + 32
def mm2in(x):
return x*0.03937008
return x * 0.03937008
def ft2m(x):
return x*0.3048
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)
@@ -72,7 +93,7 @@ def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hu
RHx = float(hum_high)
RHn = float(hum_low)
RHm = float(hum_avg)
Td = Tm-(100-RHm)/5 # approx. dewpoint (daily mean)
Td = Tm - (100 - RHm) / 5 # approx. dewpoint (daily mean)
U2 = float(wind) * 0.44704 # wind speed in m/s
# Step 1: Extraterrestrial radiation
@@ -80,89 +101,108 @@ def computeETs(latitude, longitude, elevation, temp_high, temp_low, temp_avg, hu
Gsc = 0.082
sigma = 4.90e-9
phi = math.pi * latitude / 180
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))
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))
# Step 2: Daily net radiation
Rso = Ra*(0.75 + 2.0e-5 * El) # 5
Rns = (1-0.23)*Rs
f = 1.35*Rs/Rso - 0.35 # 7
Rso = Ra * (0.75 + 2.0e-5 * El) # 5
Rns = (1 - 0.23) * Rs
f = 1.35 * Rs / Rso - 0.35 # 7
esTx = 0.6108*math.exp(17.27*Tx/(Tx+237.3)) # 8
esTn = 0.6108*math.exp(17.27*Tn/(Tn+237.3))
ed = (esTx*RHn/100 + esTn*RHx/100)/2 # 10
ea = (esTx+esTn)/2 # 22
esTx = 0.6108 * math.exp(17.27 * Tx / (Tx + 237.3)) # 8
esTn = 0.6108 * math.exp(17.27 * Tn / (Tn + 237.3))
ed = (esTx * RHn / 100 + esTn * RHx / 100) / 2 # 10
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
epsilonp = 0.34 - 0.14 * math.sqrt(ea) # 12
Rnl = -f * epsilonp * sigma * \
((Tx + 273.14) ** 4 + (Tn + 273.15) ** 4) / 2 # 13
Rn = Rns + Rnl
# Step 3: variables needed to compute ET
beta = 101.3*((293-0.0065*El)/293)**5.26 # 15
beta = 101.3 * ((293 - 0.0065 * El) / 293) ** 5.26 # 15
lam = 2.45
gamma = 0.00163*beta/lam
e0 = 0.6108*math.exp(17.27*Tm/(Tm+237.3)) # 19
Delta = 4099*e0/(Tm+237.3)**2 # 20
gamma = 0.00163 * beta / lam
e0 = 0.6108 * math.exp(17.27 * Tm / (Tm + 237.3)) # 19
Delta = 4099 * e0 / (Tm + 237.3) ** 2 # 20
G = 0
ea = (esTx+esTn)/2
ea = (esTx + esTn) / 2
# Step 4: calculate ETh
ETh = 0.408*(0.0023*Ra*(Tm+17.8)*math.sqrt(Tx-Tn)) # 23
ETh = 0.408 * (0.0023 * Ra * (Tm + 17.8) * math.sqrt(Tx - Tn)) # 23
# 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
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
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
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
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)
uwt = re.match('/weather(\d+)\.py', path)
parameters = cgi.parse_qs(environ.get('QUERY_STRING', ''))
status = '200 OK'
wto = {}
if uwt:
uwt = safe_int(uwt.group(1),0)
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]
ET = [0,0,0]
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))
# if loc is GPS coordinate itself
sp = loc.split(',', 1)
if len(sp)==2 and isFloat(sp[0]) and isFloat(sp[1]):
if len(sp) == 2 and isFloat(sp[0]) and isFloat(sp[1]):
lat = sp[0]
lon = sp[1]
else:
@@ -171,23 +211,27 @@ def application(environ, start_response):
# if loc is US 5+4 zip code, strip the last 4
sp = loc.split('-', 1)
if len(sp)==2 and isInt(sp[0]) and len(sp[0])==5 and isInt(sp[1]) and len(sp[1])==4:
loc=sp[0]
if len(sp) == 2 and isInt(sp[0]) and len(sp[0]) == 5 and isInt(sp[1]) and len(sp[1]) == 4:
loc = sp[0]
tzone = None
# 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']
@@ -228,9 +272,10 @@ def application(environ, start_response):
tzone = None
# now do autocomplete lookup to get GPS coordinates
if lat==None or lon==None:
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']
@@ -254,7 +299,7 @@ def application(environ, start_response):
if (lat) and (lon):
if not loc.startswith('pws:') and not loc.startswith('icao:'):
loc = ''+lat+','+lon
loc = '' + lat + ',' + lon
home = ephem.Observer()
@@ -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
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']:
@@ -300,46 +348,61 @@ def application(environ, start_response):
v = info['precip_today_in']
if v:
pre_today = safe_float(v, pre_today)
v = info['relative_humidity'].replace('%','')
v = info['relative_humidity'].replace('%', '')
if v:
h_today = safe_float(v, h_today)
# 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
#elif (h_today>=0):
if (maxh >= 0) and (minh >= 0):
hf = 30 - (maxh + minh) / 2
# elif (h_today>=0):
# hf = 30 - h_today
tf = 0
if (meant > -500):
tf = (meant - 70) * 4
rf = 0
if (pre>=0):
if (pre >= 0):
rf -= pre * 200
if (pre_today>=0):
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):
if (scale < 0):
scale = 0
if (scale>200):
if (scale > 200):
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
if ((uwt >> 7) & 1):
# 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,24 +419,28 @@ 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)
delta = 3600 / 4 * (toffset - 48)
if (sunrise >= 0):
sunrise = int(((sunrise+delta)%86400)/60)
sunrise = int(((sunrise + delta) % 86400) / 60)
if (sunset >= 0):
sunset = int(((sunset +delta)%86400)/60)
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)
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)
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]