Skip to content

Commit 1face8f

Browse files
committed
check in OSPi hardware design and software demos
1 parent cea22e4 commit 1face8f

File tree

12 files changed

+22062
-0
lines changed

12 files changed

+22062
-0
lines changed

OpenSprinkler Pi/hardware/ospi_v10.brd

+1,952
Large diffs are not rendered by default.

OpenSprinkler Pi/hardware/ospi_v10.sch

+19,504
Large diffs are not rendered by default.
33 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
============================================
2+
OpenSprinkler Pi (OSPi) Google Calendar Demo
3+
Feb 2013, http://rayshobby.net
4+
============================================
5+
6+
Description
7+
-----------
8+
This demo makes use of a public Google calendar to program water events. To do so, you need to first create a public Google calendar, set the calendar ID in the Python program, then the program will periodically queries the calendar to check which stations are scheduled to be on at the current time. Each calendar event is treated as a water program, where the title of the event is the station name.
9+
10+
Using the Google calendar provides the advantage that you can easily set recurring events. Also, you can access Google calendar on both desktop and mobile computers. It is a convenient and suitable interface for programming complex water schedules.
11+
12+
13+
Preparation
14+
-----------
15+
1. Before running this demo, it is assumed that you have the RPi.GPIO Python module installed (which should come by default with the latest raspbian distribution).
16+
17+
18+
2. In addition, you need to install the Google Data (gdata) API Python module, available at:
19+
http://code.google.com/p/gdata-python-client/downloads/list
20+
21+
22+
3. As mentioned above, you need to create a Google calendar, set it to be public, and copy the calendar ID (available in the calendar settings page) to the correpsonding location in ospi_gc.py. For exmaple:
23+
24+
CALENDAR_ID = 'ma2lg95i25jantdiciij85aq0s@group.calendar.google.com'
25+
26+
The reason to set the calendar public is to avoid the complexity of account authentication. If you need, you can also use a private calendar, but in that case you should follow the Google Data API for the appropriate authentication procedure.
27+
28+
29+
4. Next, set desired station names in ospi_gc.py. For each station, provide the station name and the corresponding station index (the first station is indexed 0). The station name will be used to match the Google calendar event title, and if a match is found, the corresponding station will be set to open. For example, an event named 'Front Yard' scheduled for every Monday morning from 9am to 10am will trigger the Front Yard station weekly during the designated time.
30+
31+
Note that the station names are ***case sensitive***. So 'Front Yard' is different from 'Front yard'. Also, 'Front Yard' is different from 'FrontYard'. So be careful, otherwise the program will not be able to match the calendar events correctly.
32+
33+
Note that you can map multiple names to the same station. For example, map 'Front Yard', 'FrontYard', 'Front_Yard' all to index 1. This can help avoid mismatches if you don't remember the name exactly.
34+
35+
36+
Running the Demo
37+
----------------
38+
To run the demo, type in command line:
39+
> sudo python ospi_gc.py
40+
41+
The program will check the specified Google calendar once every minute (60 seconds). The frequency can be increased or decreased by modifying the sleep() time in the source code. All events that overlap with the current time will trigger the corresponding stations.
42+
43+
44+
Error Handling
45+
--------------
46+
If the program cannot access Google calendar (e.g. due to lost connection), it will reset all stations to prevent them from potentially running for a long time. Also, if an event title has no matched station name, that event will be ignored and will not trigger any station.
47+
48+
The program also outputs the time stamp and the detected stations. This can be used as an event log.
49+
50+
Because the program requires Internet connection to run properly, it is not suitable in cases that the network is unreliable. It is possible to introduce a data cache to pre-load the events whenever the network is available.
51+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/python
2+
3+
import time
4+
import sys
5+
import string
6+
import datetime
7+
import RPi.GPIO as GPIO
8+
import atexit
9+
10+
try:
11+
from xml.etree import ElementTree # for Python 2.5 users
12+
except ImportError:
13+
from elementtree import ElementTree
14+
import gdata.calendar
15+
import gdata.calendar.service
16+
17+
# ======================================================
18+
# !!! MODIFY THE CALENDAR ID AND STATION NAMES BELOW !!!
19+
# ======================================================
20+
21+
# PUBLIC GOOGLE CALENDAR ID
22+
# - the calendar should be set as public
23+
# - calendar id can be found in calendar settings
24+
# - !!!!!!!! PLEASE CHANGE THIS TO YOUR OWN CALENDAR ID !!!!!!
25+
CALENDAR_ID = 'ma2lg95i25jantdiciij85aq0s@group.calendar.google.com'
26+
27+
# STATION NAMES
28+
# - specify the name : index for each station
29+
# - station index starts from 0
30+
# - station names are case sensitive
31+
# - you can define multiple names for each station
32+
33+
STATIONS = {
34+
"master" : 0,
35+
36+
"front yard" : 1, # you can map multiple common names
37+
"frontyard" : 1, # to the same station index
38+
39+
"back yard" : 2,
40+
"backyard" : 2,
41+
42+
"s04" : 3,
43+
"s05" : 4,
44+
"s06" : 5,
45+
46+
"s09" : 8,
47+
"s10" : 9,
48+
49+
"s16" : 15
50+
}
51+
52+
# ======================================================
53+
54+
# MAXIMUM NUMBER OF STATIONS
55+
MAX_NSTATIONS = 64
56+
57+
# OSPI PIN DEFINES
58+
pin_sr_clk = 4
59+
pin_sr_noe = 17
60+
pin_sr_dat = 21
61+
pin_sr_lat = 22
62+
63+
calendar_service = gdata.calendar.service.CalendarService()
64+
query = gdata.calendar.service.CalendarEventQuery(CALENDAR_ID, 'public', 'full')
65+
query.orderby = 'starttime'
66+
query.singleevents = 'true'
67+
query.sortorder = 'a'
68+
station_bits = [0]*MAX_NSTATIONS
69+
70+
def enableShiftRegisterOutput():
71+
GPIO.output(pin_sr_noe, False)
72+
73+
def disableShiftRegisterOutput():
74+
GPIO.output(pin_sr_noe, True)
75+
76+
def shiftOut(station_bits):
77+
GPIO.output(pin_sr_clk, False)
78+
GPIO.output(pin_sr_lat, False)
79+
for s in range(0,MAX_NSTATIONS):
80+
GPIO.output(pin_sr_clk, False)
81+
GPIO.output(pin_sr_dat, 1 if (station_bits[MAX_NSTATIONS-1-s]==1) else 0)
82+
GPIO.output(pin_sr_clk, True)
83+
GPIO.output(pin_sr_lat, True)
84+
85+
86+
def runOSPI():
87+
88+
global station_bits
89+
now = datetime.datetime.utcnow();
90+
print datetime.datetime.now();
91+
nextminute = now + datetime.timedelta(minutes=1)
92+
93+
query.start_min = now.isoformat()
94+
query.start_max = nextminute.isoformat()
95+
96+
station_bits = [0]*MAX_NSTATIONS;
97+
try:
98+
feed = calendar_service.CalendarQuery(query)
99+
print '(',
100+
for i, an_event in enumerate(feed.entry):
101+
if (i!=0):
102+
print ',',
103+
try:
104+
print an_event.title.text,
105+
station_bits[STATIONS[an_event.title.text]] = 1;
106+
except:
107+
print "-> #name not found#",
108+
# print '%s' % (an_event.title.text)
109+
print ')'
110+
except:
111+
print "#error getting calendar data#"
112+
try:
113+
shiftOut(station_bits)
114+
except:
115+
print "#shiftOut error#"
116+
117+
def main():
118+
print('OpenSprinkler Pi has started...')
119+
120+
GPIO.cleanup()
121+
# setup GPIO pins to interface with shift register
122+
GPIO.setmode(GPIO.BCM)
123+
GPIO.setup(pin_sr_clk, GPIO.OUT)
124+
GPIO.setup(pin_sr_noe, GPIO.OUT)
125+
disableShiftRegisterOutput()
126+
GPIO.setup(pin_sr_dat, GPIO.OUT)
127+
GPIO.setup(pin_sr_lat, GPIO.OUT)
128+
129+
shiftOut(station_bits)
130+
enableShiftRegisterOutput()
131+
132+
while True:
133+
try:
134+
runOSPI()
135+
except:
136+
pass
137+
time.sleep(60) # check every 60 seconds
138+
139+
def progexit():
140+
global station_bits
141+
station_bits = [0]*MAX_NSTATIONS
142+
shiftOut(station_bits)
143+
GPIO.cleanup()
144+
145+
if __name__ == "__main__":
146+
atexit.register(progexit)
147+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
===========================================
2+
OpenSprinkler Pi (OSPi) Manual_Buttons Demo
3+
Feb 2013, http://rayshobby.net
4+
===========================================
5+
6+
Description
7+
-----------
8+
This demo starts a Python HTTP server, which presents a simple webpage with a list of buttons, each corresponding to a stations. Clicking on each button to manually turn on/off a station.
9+
10+
Before running this demo, it is assumed that you have the RPi.GPIO Python module installed (which should come by default with the latest raspbian distribution).
11+
12+
To run the demo, type in command line:
13+
> sudo python ospi_manual.py
14+
15+
This will start the Python HTTP server. Next, open a browser, and type into the following url:
16+
17+
http://raspberrypi:8080
18+
19+
or alternatively:
20+
21+
http://x.x.x.x:8080
22+
23+
where x.x.x.x is the ip address of your Raspi.
24+
25+
You should see a list of 16 buttons. Clicking on each button to toggle the corresponding station.
26+
27+
28+
Modification
29+
------------
30+
You can change the number of stations by modifying the 'num_stations' variable in ospi_manual.py.
31+
32+
The code consists of two files: ospi_manual.py runs the HTTP server and maintains station variables; the webpage is formatted using the Javascripts in manual.js. You can follow the examples to extend the functionality of this demo.
33+
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function id(s) {return document.getElementByid(s);}
2+
function w(s) {document.write(s);}
3+
function action_cv(i,v) {window.location=('./cv?sid='+i+'&v='+v);}
4+
w('<h3><b>OpenSprinkler Pi Manual Mode</b></h3>');
5+
var i,s;
6+
for(i=0;i<nstations;i++) {
7+
w('<p>Station '+((i+1)/10>>0)+((i+1)%10)+': <button style=\"width:100;height:28;background-color:');
8+
s=values[i];
9+
if(s) {
10+
w('lightgreen\" onclick=action_cv('+i+',0)>Turn Off');
11+
} else {
12+
w('lightgray\" onclick=action_cv('+i+',1)>Turn On');
13+
}
14+
w('</button></p>');
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python
2+
3+
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
4+
import urlparse
5+
import os
6+
import RPi.GPIO as GPIO
7+
import atexit
8+
9+
# GPIO PIN DEFINES
10+
pin_sr_clk = 4
11+
pin_sr_noe = 17
12+
pin_sr_dat = 21
13+
pin_sr_lat = 22
14+
15+
# NUMBER OF STATIONS
16+
num_stations = 16
17+
18+
# STATION BITS
19+
values = [0]*num_stations
20+
21+
def enableShiftRegisterOutput():
22+
GPIO.output(pin_sr_noe, False)
23+
24+
def disableShiftRegisterOutput():
25+
GPIO.output(pin_sr_noe, True)
26+
27+
def setShiftRegister(values):
28+
GPIO.output(pin_sr_clk, False)
29+
GPIO.output(pin_sr_lat, False)
30+
for s in range(0,num_stations):
31+
GPIO.output(pin_sr_clk, False)
32+
GPIO.output(pin_sr_dat, values[num_stations-1-s])
33+
GPIO.output(pin_sr_clk, True)
34+
GPIO.output(pin_sr_lat, True)
35+
36+
#Create custom HTTPRequestHandler class
37+
class KodeFunHTTPRequestHandler(BaseHTTPRequestHandler):
38+
39+
#handle GET command
40+
def do_GET(self):
41+
global values
42+
rootdir = '.' #file location
43+
try:
44+
if self.path.endswith('.js'):
45+
f = open(rootdir + self.path) #open requested file
46+
47+
#send code 200 response
48+
self.send_response(200)
49+
50+
#send header first
51+
self.send_header('Content-type','text/html')
52+
self.end_headers()
53+
54+
#send file content to client
55+
self.wfile.write(f.read())
56+
f.close()
57+
return
58+
elif '/cv?' in self.path:
59+
self.send_response(200)
60+
self.send_header('Content-type','text/html')
61+
self.end_headers()
62+
parsed=urlparse.parse_qs(urlparse.urlparse(self.path).query)
63+
sn = int(parsed['sid'][0])
64+
v = int(parsed['v'][0])
65+
if sn<0 or sn>(num_stations-1) or v<0 or v>1:
66+
self.wfile.write('<script>alert(\"Wrong value!\");</script>');
67+
else:
68+
if v==0:
69+
values[sn] = 0
70+
else:
71+
values[sn] = 1
72+
setShiftRegister(values)
73+
74+
self.wfile.write('<script>window.location=\".\";</script>')
75+
else:
76+
self.send_response(200)
77+
self.send_header('Content-type','text/html')
78+
self.end_headers()
79+
self.wfile.write('<script>\nvar nstations=')
80+
self.wfile.write(num_stations)
81+
self.wfile.write(', values=[')
82+
for s in range(0,num_stations):
83+
self.wfile.write(values[s])
84+
self.wfile.write(',')
85+
self.wfile.write('0];\n</script>\n')
86+
self.wfile.write('<script src=\'manual.js\'></script>')
87+
88+
except IOError:
89+
self.send_error(404, 'file not found')
90+
91+
def run():
92+
GPIO.cleanup()
93+
# setup GPIO pins to interface with shift register
94+
GPIO.setmode(GPIO.BCM)
95+
GPIO.setup(pin_sr_clk, GPIO.OUT)
96+
GPIO.setup(pin_sr_noe, GPIO.OUT)
97+
disableShiftRegisterOutput()
98+
GPIO.setup(pin_sr_dat, GPIO.OUT)
99+
GPIO.setup(pin_sr_lat, GPIO.OUT)
100+
101+
setShiftRegister(values)
102+
enableShiftRegisterOutput()
103+
104+
#ip and port of servr
105+
#by default http server port is 8080
106+
server_address = ('', 8080)
107+
httpd = HTTPServer(server_address, KodeFunHTTPRequestHandler)
108+
print('OpenSprinkler Pi is running...')
109+
while True:
110+
httpd.handle_request()
111+
112+
def progexit():
113+
global values
114+
values = [0]*num_stations
115+
setShiftRegister(values)
116+
GPIO.cleanup()
117+
118+
if __name__ == '__main__':
119+
atexit.register(progexit)
120+
run()

0 commit comments

Comments
 (0)