#!/usr/bin/python import re, os, json, time, base64, thread # standard Python modules import web # the Web.py module. See webpy.org (Enables the OpenSprinkler web interface) import gv # 'global vars' An empty module, used for storing vars (as attributes), that need to be 'global' across threads and between functions and classes. try: import RPi.GPIO as GPIO # Required for accessing General Purpose Input Output pins on Raspberry Pi except ImportError: pass #### Revision information #### gv.ver = 183 gv.rev = 139 gv.rev_date = '16/October/2013' #### urls is a feature of web.py. When a GET request is received , the corresponding class is executed. urls = [ '/', 'home', '/cv', 'change_values', '/vo', 'view_options', '/co', 'change_options', '/vs', 'view_stations', '/cs', 'change_stations', '/sn(\d+?\Z)', 'get_station', # regular expression, accepts any station number '/sn(\d+?=\d(&t=\d+?\Z)?)', 'set_station', # regular expression, accepts any digits '/vr', 'view_runonce', '/cr', 'change_runonce', '/vp', 'view_programs', '/mp', 'modify_program', '/cp', 'change_program', '/dp', 'delete_program', '/gp', 'graph_programs', '/vl', 'view_log', '/cl', 'clear_log', '/lo', 'log_options', '/rp', 'run_now', '/ttu', 'toggle_temp', '/rev', 'show_revision', ] #### Import ospi_addon module (ospi_addon.py) if it exists. #### try: import ospi_addon #This provides a stub for adding custom features to ospi.py as external modules. except ImportError: print 'add_on not imported' #### Function Definitions #### def approve_pwd(qdict): """Password checking""" try: if not gv.sd['ipas'] and not qdict['pw'] == base64.b64decode(gv.sd['pwd']): raise web.unauthorized() except KeyError: pass def baseurl(): """Return URL app is running under.""" baseurl = web.ctx['home'] return baseurl def clear_mm(): """Clear manual mode settings.""" if gv.sd['mm']: gv.sbits = [0] * (gv.sd['nbrd'] +1) gv.ps = [] for i in range(gv.sd['nst']): gv.ps.append([0,0]) gv.rs = [] for i in range(gv.sd['nst']): gv.rs.append([0,0,0,0]) gv.srvals = [0]*(gv.sd['nst']) set_output() return def CPU_temperature(): """Returns the temperature of the Raspberry Pi's CPU.""" try: res = os.popen('vcgencmd measure_temp').readline() return(res.replace("temp=","").replace("'C\n","")) except: pass def get_temperature(): """Returns the temperature given the stored location info""" import urllib2 weatherBaseURL = "http://api.openweathermap.org/data/2.5/weather?" weatherFormat = "units=metric&" weatherURL = weatherBaseURL + weatherFormat latitude = gv.sd['loc'].split(",")[0] longitude = gv.sd['loc'].split(",")[1] """Check whether the values in lat/lon are numbers""" try: float(latitude) float(longitude) except ValueError: return("") queryURL = weatherURL + "lat=" + str(latitude) + "&lon=" + str(longitude) response = urllib2.urlopen(queryURL) decoder = json.JSONDecoder() temperature = decoder.decode(response.read())["main"]["temp"] return (str(temperature)) def log_run(): """add run data to csv file - most recent first.""" if gv.lg: snames = data('snames') zones=re.findall(r"\'(.+?)\'",snames) if gv.lrun[1] == 98: pgr = 'Run-once' elif gv.lrun[1] == 99: pgr = 'Manual' else: pgr = str(gv.lrun[1]) datastr = (pgr +', '+str(zones[gv.lrun[0]])+', '+str(gv.lrun[2]/60)+'m'+str(gv.lrun[2]%60)+ 's, '+time.strftime("%H:%M:%S, %a. %d %b %Y", time.gmtime(gv.now))+'\n') f = open('./static/log/water_log.csv', 'r') log = f.readlines() f.close() log.insert(1, datastr) f = open('./static/log/water_log.csv', 'w') if gv.lr: f.writelines(log[:gv.lr+1]) else: f.writelines(log) f.close return def prog_match(prog): """Test a program for current date and time match.""" if not prog[0]: return 0 # Skip if program is not enabled devday = int(gv.now/86400) # Check day match lt = time.gmtime(gv.now) if (prog[1]>=128) and (prog[2]>1): #Inverval program if (devday %prog[2]) != (prog[1] - 128): return 0 else: # Weekday program if not prog[1]-128 & 1<=128 and prog[2] == 0: #even days if lt[2]%2 != 0: return 0 if prog[1]>=128 and prog[2] == 1: #Odd days if lt[2]==31 or (lt[1]==2 and lt[2]==29): return 0 elif lt[2]%2 !=1: return 0 this_minute = (lt[3]*60)+lt[4] # Check time match if this_minute < prog[3] or this_minute > prog[4]: return 0 if prog[5] == 0: return 0 if ((this_minute - prog[3]) / prog[5]) * prog[5] == this_minute - prog[3]: return 1 # Program matched return 0 def schedule_stations(stations): """Schedule stattions/valves/zones to run.""" if gv.sd['rd'] or (gv.sd['urs'] and gv.sd['rs']): # If rain delay or rain detected by sensor rain = True else: rain = False accumulate_time = gv.now if gv.sd['seq']: #sequential mode, stations run one after another for b in range(len(stations)): for s in range(8): sid = b*8 + s # station index if gv.rs[sid][2]: # if station has a duration value if not rain or gv.sd['ir'][b]&1<= gv.rs[sid][1]: # check if time is up gv.srvals[sid] = 0 set_output() gv.sbits[b] = gv.sbits[b]&~2**s if gv.sd['mas']-1 != sid: # if not master, fill out log gv.ps[sid] = [0,0] gv.lrun[0] = sid gv.lrun[1] = gv.rs[sid][3] gv.lrun[2] = int(gv.now - gv.rs[sid][0]) gv.lrun[3] = gv.now log_run() gv.pon = None # Program has ended gv.rs[sid] = [0,0,0,0] else: # if this station is not yet on if gv.now >= gv.rs[sid][0] and gv.now < gv.rs[sid][1]: if gv.sd['mas']-1 != sid: # if not master gv.srvals[sid] = 1 # station is turned on set_output() gv.sbits[b] = gv.sbits[b]|1<= gv.sd['rdst']: # Check of rain delay time is up gv.sd['rd'] = 0 gv.sd['rdst'] = 0 # Rain delay stop time jsave(gv.sd, 'sd') time.sleep(1) #### End of timing loop #### def data(dataf): """Return contents of requested text file as string or create file if a missing config file.""" try: f = open('./data/'+dataf+'.txt', 'r') data = f.read() f.close() except IOError: if dataf == 'snames': ## A config file -- return defaults and create file if not found. ## data = "['S01','S02','S03','S04','S05','S06','S07','S08',]" f = open('./data/'+dataf+'.txt', 'w') f.write(data) f.close() else: return None return data def save(dataf, datastr): """Save data to text file. dataf = file to save to, datastr = data string to save.""" f = open('./data/'+dataf+'.txt', 'w') f.write(datastr) f.close() return def jsave(data, fname): """Save data to a json file.""" f = open('./data/'+fname+'.json', 'w') json.dump(data, f) f.close() def load_programs(): """Load program data from json file if it exists into memory, otherwise create an empty programs var.""" try: pf = open('./data/programs.json', 'r') gv.pd = json.load(pf) pf.close() except IOError: gv.pd = [] #A config file -- return default and create file if not found. pf = open('./data/programs.json', 'w') json.dump(gv.pd, pf) pf.close() return gv.pd def output_prog(): """Converts program data to text string and outputs JavaScript vars used to display program page.""" lpd = [] dse = int((time.time()+((gv.sd['tz']/4)-12)*3600)/86400) # days since epoch for p in gv.pd: op = p[:] # Make local copy of each program if op[1] >= 128 and op[2] > 1: rel_rem = (((op[1]-128) + op[2])-(dse%op[2]))%op[2] op[1] = rel_rem + 128 lpd.append(op) progstr = 'var nprogs='+str(len(lpd))+',nboards='+str(gv.sd['nbrd'])+',ipas='+str(gv.sd['ipas'])+',mnp='+str(gv.sd['mnp'])+',pd=[];' for i, pro in enumerate(lpd): #gets both index and object progstr += 'pd['+str(i)+']='+str(pro).replace(' ', '')+';' return progstr ##### GPIO ##### def set_output(): """Activate triacs according to shift register state.""" disableShiftRegisterOutput() setShiftRegister(gv.srvals) # gv.srvals stores shift register state enableShiftRegisterOutput() def to_sec(d=0, h=0, m=0, s=0): """Convert Day, Hour, minute, seconds to number of seconds.""" secs = d*86400 secs += h*3600 secs += m*60 secs += s return secs ################## #### Global vars ##### try: sdf = open('./data/sd.json', 'r') ## A config file ## gv.sd = json.load(sdf) #Settings Dictionary. A set of vars kept in memory and persisted in a file sdf.close() # test for missing or extra vars (update to current state) gv.sd.pop('m0', None) gv.sd.pop('m1', None) gv.sd.pop('m2', None) gv.sd.pop('m3', None) if not 'mo' in gv.sd: gv.sd['mo'] = [0] if not 'lg' in gv.sd: gv.sd['lg'] = 0 if not 'lr' in gv.sd: gv.sd['lr'] = 100 if not 'seq' in gv.sd: gv.sd['seq'] = 1 if not 'tu' in gv.sd: gv.sd['tu'] = "C" if not 'ir' in gv.sd: gv.sd['ir'] = [0]#*gv.sd['nbrd'] if not 'loc' in gv.sd: gv.sd['loc'] = "" if not 'snlen' in gv.sd: gv.sd['snlen'] = 32 if not 'name' in gv.sd: gv.sd['name'] = u"OpenSprinkler Pi" except IOError: # If file does not exist, create with defaults. gv.sd = ({"en": 1, "seq": 1, "mnp": 32, "ir": [0], "rsn": 0, "htp": 8080, "nst": 8, "rdst": 0, "loc": "", "tz": 48, "rs": 0, "rd": 0, "mton": 0, "lr": "100", "sdt": 0, "mas": 0, "wl": 100, "bsy": 0, "lg": "", "urs": 0, "nopts": 13, "pwd": "b3BlbmRvb3I=", "ipas": 0, "rst": 1, "mm": 0, "mo": [0], "rbt": 0, "mtoff": 0, "nprogs": 1, "nbrd": 1, "tu": "C", "snlen":32, "name":u"OpenSprinkler Pi",}) sdf = open('./data/sd.json', 'w') json.dump(gv.sd, sdf) sdf.close() try: gv.lg = gv.sd['lg'] # Controlls logging except KeyError: pass try: gv.lr = int(gv.sd['lr']) except KeyError: pass sdref = {'15':'nbrd', '16':'seq', '18':'mas', '21':'urs', '23':'wl', '25':'ipas'} #lookup table (Dictionary) gv.now = time.time()+((gv.sd['tz']/4)-12)*3600 gv.srvals = [0]*(gv.sd['nst']) #Shift Register values gv.rovals = [0]* gv.sd['nbrd']*7 #Run Once durations gv.pd = load_programs() # Load program data from file gv.ps = [] #Program schedule (used for UI diaplay) for i in range(gv.sd['nst']): gv.ps.append([0,0]) gv.pon = None #Program on (Holds program number of a running program gv.sbits = [0] * (gv.sd['nbrd'] +1) # Used to display stations that are on in UI gv.rs = [] #run schedule for i in range(gv.sd['nst']): gv.rs.append([0,0,0,0]) #scheduled start time, scheduled stop time, duration, program index gv.lrun=[0,0,0,0] #station index, program number, duration, end time (Used in UI) gv.scount = 0 # Station count, used in set station to track on stations with master association. #### GPIO ##### try: GPIO.setwarnings(False) except NameError: pass #### pin defines #### pin_sr_dat = 13 pin_sr_clk = 7 pin_sr_noe = 11 pin_sr_lat = 15 def enableShiftRegisterOutput(): try: GPIO.output(pin_sr_noe, GPIO.LOW) except NameError: pass def disableShiftRegisterOutput(): try: GPIO.output(pin_sr_noe, GPIO.HIGH) except NameError: pass try: GPIO.cleanup() #### setup GPIO pins to interface with shift register #### GPIO.setmode(GPIO.BOARD) #IO channels are identified by header connector pin numbers. Pin numbers are always the same regardless of Raspberry Pi board revision. GPIO.setup(pin_sr_clk, GPIO.OUT) GPIO.setup(pin_sr_noe, GPIO.OUT) disableShiftRegisterOutput() GPIO.setup(pin_sr_dat, GPIO.OUT) GPIO.setup(pin_sr_lat, GPIO.OUT) except NameError: pass def setShiftRegister(srvals): try: GPIO.output(pin_sr_clk, GPIO.LOW) GPIO.output(pin_sr_lat, GPIO.LOW) for s in range(gv.sd['nst']): GPIO.output(pin_sr_clk, GPIO.LOW) GPIO.output(pin_sr_dat, srvals[gv.sd['nst']-1-s]) GPIO.output(pin_sr_clk, GPIO.HIGH) GPIO.output(pin_sr_lat, GPIO.HIGH) except NameError: pass ################## def pass_options(opts): optstring = "var sd = {\n" for o in opts: optstring += "\t" + o + " : " if (type(gv.sd[o]) == unicode): optstring += "'" + gv.sd[o] + "'" else: optstring += str(gv.sd[o]) optstring += ",\n" optstring = optstring[:-2] + "\n}\n" return optstring #### Class Definitions #### class home: """Open Home page.""" def GET(self): homepg = '\n' homepg += data('meta') homepg += '\n' homepg += '\n' homepg += '\n' homepg += '\n' homepg += '\n' homepg += '\n' homepg += '\n' if gv.sd['tu'] == "F": try: homepg += '\n' except ValueError: pass else: try: homepg += '\n' homepg += '\n' except ValueError: pass homepg += '' return homepg class change_values: """Save controller values, return browser to home page.""" def GET(self): qdict = web.input() approve_pwd(qdict) if qdict.has_key('rsn') and qdict['rsn'] == '1': stop_stations() raise web.seeother('/') return if qdict.has_key('en') and qdict['en'] == '': qdict['en'] = '1' #default elif qdict.has_key('en') and qdict['en'] == '0': gv.srvals = [0]*(gv.sd['nst']) # turn off all stations set_output() if qdict.has_key('mm') and qdict['mm'] == '0': clear_mm() if qdict.has_key('rd') and qdict['rd'] != '0' and qdict['rd'] != '': gv.sd['rdst'] = (gv.now+(int(qdict['rd'])*3600)) stop_onrain() elif qdict.has_key('rd') and qdict['rd'] == '0': gv.sd['rdst'] = 0 if qdict.has_key('rbt') and qdict['rbt'] == '1': jsave(gv.sd, 'sd') gv.srvals = [0]*(gv.sd['nst']) set_output() os.system('reboot') raise web.seeother('/') for key in qdict.keys(): try: gv.sd[key] = int(qdict[key]) except: pass jsave(gv.sd, 'sd') raise web.seeother('/')# Send browser back to home page return class view_options: """Open the options page for viewing and editing.""" def GET(self): optpg = '\n' optpg += data('meta') optpg += '\n' optpg += '' return optpg class change_options: """Save changes to options made on the options page.""" def GET(self): qdict = web.input() approve_pwd(qdict) try: if qdict.has_key('oipas') and (qdict['oipas'] == 'on' or qdict['oipas'] == ''): gv.sd['ipas'] = 1 else: gv.sd['ipas'] = 0 except KeyError: pass try: if qdict['cpw'] !='' and qdict['cpw'] == qdict['npw']: gv.sd['pwd'] = base64.b64encode(qdict['npw']) except KeyError: pass gv.sd['name'] = qdict['oname'] gv.sd['loc'] = qdict['oloc'] gv.sd['tz'] = int(qdict['otz']) if int(qdict['onbrd'])+1 != gv.sd['nbrd']: self.update_scount(qdict) gv.sd['nbrd'] = int(qdict['onbrd'])+1 gv.sd['nst'] = gv.sd['nbrd']*8 gv.sd['htp']= int(qdict['ohtp']) gv.sd['sdt']= int(qdict['osdt']) gv.sd['mas'] = int(qdict['omas']) gv.sd['mton']= int(qdict['omton']) gv.sd['mtoff']= int(qdict['omtoff']) gv.sd['wl'] = int(qdict['owl']) if qdict.has_key('ours') and (qdict['ours'] == 'on' or qdict['ours'] == ''): gv.sd['urs'] = 1 else: gv.sd['urs'] = 0 if qdict.has_key('oseq') and (qdict['oseq'] == 'on' or qdict['oseq'] == ''): gv.sd['seq'] = 1 else: gv.sd['seq'] = 0 if qdict.has_key('orst') and (qdict['orst'] == 'on' or qdict['orst'] == ''): gv.sd['rst'] = 1 else: gv.sd['rst'] = 0 if qdict.has_key('olg') and (qdict['olg'] == 'on' or qdict['olg'] == ''): gv.sd['lg'] = 1 else: gv.sd['lg'] = 0 gv.lg = gv.sd['lg'] # necessary to make logging work correctly on Pi (see run_log()) gv.sd['lr'] = int(qdict['olr']) gv.lr = gv.sd['lr'] srvals = [0]*(gv.sd['nst']) # Shift Register values rovals = [0]*(gv.sd['nst']) # Run Once Durations jsave(gv.sd, 'sd') raise web.seeother('/') #alert = '' return #alert # -- Alerts are not considered good interface progrmming. Use sparingly! def update_scount(self, qdict): """Increase or decrease the number of stations shown when expansion boards are added in options.""" if int(qdict['onbrd'])+1 > gv.sd['nbrd']: # Lengthen lists incr = int(qdict['onbrd']) - (gv.sd['nbrd']-1) for i in range(incr): gv.sd['mo'].append(0) for i in range(incr): gv.sd['ir'].append(0) snames = data('snames') nlst = re.findall('[\'"].*?[\'"]', snames) ln = len(nlst) nlst.pop() for i in range((incr*8)+1): nlst.append("'S"+('%d'%(i+ln)).zfill(2)+"'") nstr = '['+','.join(nlst) nstr = nstr.replace("', ", "',")+"]" save('snames', nstr) for i in range(incr*8): gv.srvals.append(0) gv.ps.append([0,0]) gv.rs.append([0,0,0,0]) for i in range(incr): gv.sbits.append(0) elif int(qdict['onbrd'])+1 < gv.sd['nbrd']: # Shorten lists onbrd = int(qdict['onbrd']) decr = gv.sd['nbrd'] - (onbrd+1) gv.sd['mo'] = gv.sd['mo'][:(onbrd+1)] gv.sd['ir'] = gv.sd['ir'][:(onbrd+1)] snames = data('snames') nlst = re.findall('[\'"].*?[\'"]', snames) nstr = '['+','.join(nlst[:8+(onbrd*8)])+']' save('snames', nstr) newlen = gv.sd['nst'] - decr * 8 gv.srvals = gv.srvals[:newlen] gv.ps = gv.ps[:newlen] gv.rs = gv.rs[:newlen] gv.sbits = gv.sbits[:onbrd+1] return class view_stations: """Open a page to view and edit station names and master associations.""" def GET(self): stationpg = '\n' stationpg += data('meta') stationpg += '\n' stationpg += '\n' stationpg += '\n' stationpg += '' return stationpg class change_stations: """Save changes to station names and master associations.""" def GET(self): qdict = web.input() approve_pwd(qdict) for i in range(gv.sd['nbrd']): # capture master associations if qdict.has_key('m'+str(i)): try: gv.sd['mo'][i] = int(qdict['m'+str(i)]) except ValueError: gv.sd['mo'][i] = 0 if qdict.has_key('i'+str(i)): try: gv.sd['ir'][i] = int(qdict['i'+str(i)]) except ValueError: gv.sd['ir'][i] = 0 names = '[' for i in range(gv.sd['nst']): if qdict.has_key('s'+str(i)): # This is to work around a bug introduced during UI changes 10/13 names += "'" + qdict['s'+str(i)] + "'," else: names += "'S0"+str(i+1) + "'," names += ']' save('snames', names.encode('ascii', 'backslashreplace')) jsave(gv.sd, 'sd') raise web.seeother('/') class get_station: """Return a page containing a number representing the state of a station or all stations if 0 is entered as statin number.""" def GET(self, sn): if sn == '0': status = '\n' status += ''.join(str(x) for x in gv.srvals) return status elif int(sn)-1 <= gv.sd['nbrd']*7: status = '\n' status += str(gv.srvals[int(sn)-1]) return status else: return 'Station '+sn+' not found.' class set_station: """turn a station (valve/zone) on=1 or off=0 in manual mode.""" def GET(self, nst, t=None): # nst = station number, status, optional duration nstlst = [int(i) for i in re.split('=|&t=', nst)] if len(nstlst) == 2: nstlst.append(0) sid = int(nstlst[0])-1 # station index b = sid/8 #board index if nstlst[1] == 1 and gv.sd['mm']: # if status is on and manual mode is set gv.rs[sid][0] = gv.now # set start time to current time if nstlst[2]: # if an optional duration time is given gv.rs[sid][2] = nstlst[2] gv.rs[sid][1] = gv.rs[sid][0] + nstlst[2] # stop time = start time + duration else: gv.rs[sid][1] = float('inf') # stop time = infinity gv.rs[sid][3] = 99 # set program index gv.ps[sid][1] = nstlst[2] gv.sd['bsy']=1 time.sleep(1.5) if nstlst[1] == 0 and gv.sd['mm']: # If status is off gv.rs[sid][1] = gv.now time.sleep(1.5) raise web.seeother('/') class view_runonce: """Open a page to view and edit a run once program.""" def GET(self): ropg = '\n' ropg += data('meta') ropg += '\n' ropg += '\n' ropg += '\n' ropg += '' return ropg class change_runonce: """Start a Run Once program. This will override any running program.""" def GET(self): qdict = web.input() approve_pwd(qdict) if not gv.sd['en']: return # check operation status gv.rovals = json.loads(qdict['t']) gv.rovals.pop() stations = [0] * gv.sd['nbrd'] gv.ps = [] # program schedule (for display) gv.rs = [] # run schedule for i in range(gv.sd['nst']): gv.ps.append([0,0]) gv.rs.append([0,0,0,0]) for i, v in enumerate(gv.rovals): if v: # if this element has a value gv.rs[i][0] = gv.now gv.rs[i][2] = v gv.rs[i][3] = 98 gv.ps[i][0] = 98 gv.ps[i][1] = v stations[i/8] += 2**(i%8) schedule_stations(stations) raise web.seeother('/') class view_programs: """Open programs page.""" def GET(self): programpg = '\n' programpg += data('meta') programpg += '\n' programpg += '\n' programpg += '\n' programpg += '' return programpg class modify_program: """Open page to allow program modification""" def GET(self): qdict = web.input() modprogpg = '\n' modprogpg += data('meta') modprogpg += '\n' else: modprogpg += 'var pid=-1;\n' modprogpg += '\n' modprogpg += '' return modprogpg class change_program: """Add a program or modify an existing one.""" def GET(self): qdict = web.input() approve_pwd(qdict) pnum = int(qdict['pid'])+1 # program number cp = json.loads(qdict['v']) if cp[0] == 0 and pnum == gv.pon: # if disabled and program is running for i in range(len(gv.ps)): if gv.ps[i][0] == pnum: gv.ps[i] = [0,0] if gv.srvals[i]: gv.srvals[i] = 0 for i in range(len(gv.rs)): if gv.rs[i][3] == pnum: gv.rs[i] = [0,0,0,0] if cp[1] >= 128 and cp[2] > 1: dse = int(gv.now/86400) ref = dse + cp[1]-128 cp[1] = (ref%cp[2])+128 if int(qdict['pid']) > gv.sd['mnp']: alert = '' return alert elif qdict['pid'] == '-1': #add new program gv.pd.append(cp) else: gv.pd[int(qdict['pid'])] = cp #replace program jsave(gv.pd, 'programs') gv.sd['nprogs'] = len(gv.pd) raise web.seeother('/vp') return class delete_program: """Delete one or all existing program(s).""" def GET(self): qdict = web.input() approve_pwd(qdict) if qdict['pid'] == '-1': del gv.pd[:] jsave(gv.pd, 'programs') else: del gv.pd[int(qdict['pid'])] jsave(gv.pd, 'programs') gv.sd['nprogs'] = len(gv.pd) raise web.seeother('/vp') return class graph_programs: """Open page to display program schedule""" def GET(self): qdict = web.input() t = gv.now lt = time.gmtime(t) if qdict['d'] == '0': dd = str(lt.tm_mday) else: dd = str(qdict['d']) if qdict.has_key('m'): mm = str(qdict['m']) else: mm = str(lt.tm_mon) if qdict.has_key('y'): yy = str(qdict['y']) else: yy = str(lt.tm_year) graphpg = '\n' graphpg += data('meta') graphpg += '\n' graphpg += '\n' graphpg += '\n' graphpg += '' return graphpg class view_log: def __init__(self): self.render = web.template.render('templates/', globals={'sd':gv.sd}) def GET(self): logf = open('static/log/water_log.csv') records = logf.readlines() logf.close() data = [] for r in records: t = r.split(', ') t[1] = t[1].decode('unicode-escape') data.append(t) return self.render.log(data) class clear_log: """Delete all log records""" def GET(self): qdict = web.input() approve_pwd(qdict) f = open('./static/log/water_log.csv', 'w') f.write('Program, Zone, Duration, Finish Time, Date'+'\n') f.close raise web.seeother('/vl') class log_options: """Set log options from dialog.""" def GET(self): qdict = web.input() approve_pwd(qdict) if qdict.has_key('log'): gv.sd['lg'] = 1 else: gv.sd['lg'] = 0 gv.lg = gv.sd['lg'] # necessary to make logging work correctly on Pi (see run_log()) gv.sd['lr'] = int(qdict['nrecords']) gv.lr = int(gv.sd['lr']) jsave(gv.sd, 'sd') raise web.seeother('/vl') class run_now: """Run a scheduled program now. This will override any running programs.""" def GET(self): qdict = web.input() approve_pwd(qdict) pid = int(qdict['pid']) p = gv.pd[int(qdict['pid'])] # program data if not p[0]: # if program is disabled raise web.seeother('/vp') stop_stations() for b in range(len(p[7:7+gv.sd['nbrd']])): # check each station for s in range(8): sid = b*8+s # station index if sid+1 == gv.sd['mas']: continue # skip if this is master valve if p[7+b]&1<
\n' revpg += 'updated ' + gv.rev_date +'\n' return revpg class toggle_temp: """Change units of Raspi's CPU temperature display on home page.""" def GET(self): qdict = web.input() if qdict['tunit'] == "C": gv.sd['tu'] = "F" else: gv.sd['tu'] = "C" jsave(gv.sd, 'sd') raise web.seeother('/') class OSPi_app(web.application): """Allow program to select HTTP port.""" def run(self, port=gv.sd['htp'], *middleware): # get port number from options settings func = self.wsgifunc(*middleware) return web.httpserver.runsimple(func, ('0.0.0.0', port)) if __name__ == '__main__': app = OSPi_app(urls, globals()) thread.start_new_thread(main_loop, ()) app.run()