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
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]