Tweet-a-watt - hur man gör en kvittrande kraftmätare... (16 / 19 steg)
Steg 16: Design - lagra
OK vi får bra data från våra sensorer, låter corral det till mer användbara bitar och förvara den i en databas. Vi kunde göra en databas på datorn, men eftersom vi skulle vilja dela dessa data, det är mer förnuftigt att sätta det på nätet. Det finns anpassade tjänster som är särskilt utformade för att göra sådant som Pachube men jag ska uppfinna hjulet på nytt och designa min egen webb-app som lagrar och visar Energidata. (Mest jag vill leka med Google App Engine!)
Du har 5 minuter!
Vi få data efter några sekunder från XBee modemet inuti den kill-a-watten. Vi kunde, i teorin, lägga data i vår databas var 2 sekunder men som snabbt skulle ballong mängden lagringsutrymme krävs. Det skulle också göra sortering igenom data svårt. Så istället kan lägga upp alla sensordata för 5 minuter och sedan ta medelvärdet.
Vi gör detta genom att hålla två timers och en stämmer. En timer kommer att spåra hur länge dess varit sedan den senaste signalen skickades, och den andra kommer att följa upp om dess varit 5 minuter. Stämmer kommer att lagra upp alla wattimmar (Watt mätningar * tid sedan senaste sensordata). Sedan i slutet vi kan i genomsnitt av de 5 minuterna
Denna bit av kod går i början, det skapar timers och kontrollräkna och initierar dem
...
fiveminutetimer = lasttime = time.time() # få aktuell tid
cumulativewatthr = 0
...
Då sedermera, efter att vi får våra data vi kan sätta i denna del av koden:
# lägga upp delta-watthr används sedan förra behandlingen
# Räkna ut hur många watt timmar användes sedan förra behandlingen
elapsedseconds = time.time() - lasttime
dwatthr = (avgwatt * elapsedseconds) / (60,0 * 60,0) # 60 sekunder i 60 minuter = 1 timme
lasttime = time.time()
skriva ut "\t\tWh används i sista", elapsedseconds, "sekunder:", dwatthr
cumulativewatthr += dwatthr
# Avgöra i minuten i timmen (dvs 6:42 -> "42")
currminute = (int(time.time())/60) % 10
# Räkna ut om dess varit fem minuter sedan våra senaste spara
om (((time.time()-fiveminutetimer) > = 60,0) och (currminute % 5 == 0)):
# Skriv ut debug data, Wh används i senaste 5 minuter
avgwattsused = cumulativewatthr * (60,0 * 60,0 / (time.time() - fiveminutetimer))
Skriv ut time.strftime ("%Y %m %d % H: %M"),",", cumulativewatthr, "Wh =", avgwattsused, "W genomsnittet")
# Återställa våra 5 minuters timer
fiveminutetimer = time.time()
cumulativewatthr = 0
Observera att vi beräkna delta-watthours, den lilla mängden makt används efter några sekunder. Då kan vi få de genomsnittliga watt som används av watthours divideras med antalet timmar som gått (ca 1/12). Istället för att gå med exakta 5 minuter, jag bestämde mig att endast rapportera om 5 timmen (: 05,: 10, etc) så att det är lättare att skicka alla data på en gång om theres flera sensorer som startade vid olika tidpunkter.
Ladda ner wattcher-5minreporter.py från nedladdningssidan. Om du kör detta, får du en stadig ström
I slutet kan du se tidsstämpeln, Watthrs används under senaste några minuter och den genomsnittliga effekt
Multisensor!
Vi har bra data men så länge det fungerar bara med en sensor. Flera sensorer kommer att strula till det! Dags att lägga till stöd för mer än en XBee så att jag kan spåra några rum. Jag ska göra det genom att skapa en objektklass i python och använder XBee adress (kom ihåg att från del 1?) att spåra. Jag ska ersätta koden skrev vi bara med följande:
På toppen, i stället för variablerna som timer, jag har en full klassdeklaration, och skapa en array för att lagra dem:
### lagra sensordata och mängd historier per sensor
klass Fiveminutehistory:
def init (self, sensornum):
Self.sensornum = sensornum
Self.fiveminutetimer = time.time() # spåra data över 5 minuter
Self.lasttime = time.time()
Self.cumulativewatthr = 0
def addwatthr (self, deltawatthr):
Self.cumulativewatthr += float(deltawatthr)
def reset5mintimer(self):
Self.cumulativewatthr = 0
Self.fiveminutetimer = time.time()
def avgwattover5min(self):
återgå self.cumulativewatthr * (60,0 * 60,0 / (time.time() - self.fiveminutetimer))
def str(self):
returnera "[id #: %d, 5mintimer: %f, lasttime; %f, cumulativewatthr: %f] "% (self.sensornum, self.fiveminutetimer, self.lasttime, self.cumulativewatthr)
### en mängd historier
sensorhistories =]
När objektet initieras med sensor-ID nummer, det sätter också upp två timers och kumulativa Watthrs spåras. Jag skapade också några hjälpare funktioner som kommer att göra koden renare
Precis nedanför som jag ska skapa en liten funktion att hjälpa mig skapa och hämta dessa objekt. Givet ett XBee ID-nummer gör det antingen ett nytt eller blir hänvisningen till det
### retriever
def findsensorhistory(sensornum):
för historia i sensorhistories:
om history.sensornum == sensornum:
returnera historia
# ingen finner, skapa den!
historia = Fiveminutehistory(sensornum)
sensorhistories.append(History)
returnera historia
Slutligen, power som retreives föremål och spår i stället för den genomsnittliga Watt beräkning kod skrivet ovan, vi ska ersätta det med följande bit, användning med objektet timers
# retreive historia för denna sensor
sensorhistory = findsensorhistory(xb.address_16)
#print sensorhistory
# lägga upp delta-watthr används sedan förra behandlingen
# Räkna ut hur många watt timmar användes sedan förra behandlingen
elapsedseconds = time.time() - sensorhistory.lasttime
dwatthr = (avgwatt * elapsedseconds) / (60,0 * 60,0) # 60 sekunder i 60 minuter = 1 timme
sensorhistory.lasttime = time.time()
skriva ut "\t\tWh används i sista", elapsedseconds, "sekunder:", dwatthr
sensorhistory.addwatthr(dwatthr)
# Avgöra i minuten i timmen (dvs 6:42 -> "42")
currminute = (int(time.time())/60) % 10
# Räkna ut om dess varit fem minuter sedan våra senaste spara
om (((time.time()-sensorhistory.fiveminutetimer) > = 60,0) och (currminute % 5 == 0)):
# Skriv ut debug data, Wh används i senaste 5 minuter
avgwattsused = sensorhistory.avgwattover5min()
Skriv ut time.strftime ("%Y %m %d % H: %M"), "," sensorhistory.cumulativewatthr,"Wh =", avgwattsused, "W genomsnittliga"
# Återställa våra 5 minuters timer
sensorhistory.reset5mintimer()
Koden fungerar ungefär samma utom nu det kommer inte att kvävas på flera sensordata! Nedan, min två Kill-a-watt, en med en dator som är ansluten (100W) och en annan med en lampa (40W)
Till databasen!
Den App Engine
Så vill vi ha en nätverksansluten dator att lagra dessa uppgifter så vi kan dela data, men vi vill verkligen inte att köra en server hemifrån! Vad göra? Väl som tidigare nämnts, kan du använda Pachube eller liknande, men jag kommer att visa hur till rulle egna med Google App Engine (GAE). GAE är i princip en gratis mini-webserver värd Google, som kommer att köra grundläggande webapps utan hassle av administrera en databasserver. Varje webapp har lagring, vissa ramar och kan använda Google-konton för autentisering. För att komma igång föreslår jag att kolla på GAE hemsida, dokumentation, osv Jag antar du har gått igenom tutorials och hoppa rakt in i utforma min power data storage app som kallas Wattcher (lite förvirrande jag vet)
Första filen app.yaml som definierar min app ser ut så här:
ansökan: wattcher
version: 1
runtime: python
api_version: 1
handtag:
-url: /. *
skript: wattcherapp.py
Ganska enkelt, bara säger att app använder wattcherapp.py som källfilen
Nästa, vi ska dyka in i python koden för våra webapp. Första, det innehåller och databas index. För att skapa en databas, definiera vi faktiskt det - i filen python - GAE siffror sedan vilken typ av databas för att skapa för dig genom att följa dessa anvisningar (mycket annorlunda än MySQL där du vill skapa DB separat)
import cgi, datetime
importera användare från google.appengine.api
från google.appengine.ext importera webapp
importera run_wsgi_app från google.appengine.ext.webapp.util
från google.appengine.ext importera db
klass Powerusage(db. Modell):
författare = db. UserProperty() # användaren
sensornum = db. IntegerProperty() # kan ha flera sensorer
watt = db. FloatProperty() # varje skicka oss senaste Watt mätning
datum = db. DateTimeProperty(auto_now_add=True) # tidsstämpel
Vi använder standard ingår. Vi har en enda databastabell som kallas Powerusage, och den har 4 poster: en för användaren, en för antalet sensor, en för sist rapporterade watt används och en för en datumstämpel
Varje "sida" och funktion av våra webapp måste ha sin egen klass. Kan börja med funktionen som tillåter oss att lagra data i DB. Jag kallar det PowerUpdate.
klass PowerUpdate(webapp. RequestHandler):
def get(self):
# gör användaren logga in
om inte users.get_current_user():
Self.Redirect(users.create_login_url(Self.Request.URI))
powerusage = Powerusage()
om users.get_current_user():
powerusage.author = users.get_current_user()
#print self.request
om self.request.get('watt'):
powerusage.watt = float(self.request.get('watt'))
annat:
Self.Response.out.write ("kunde inte hitta \'watt\"Få egendom!")
returnera
om self.request.get('sensornum'):
powerusage.sensornum = int(self.request.get('sensornum'))
annat:
powerusage.sensornum = 0 # antar theres bara ett eller något
powerusage.Put()
Self.Response.out.write('OK!')
När vi skickar en begäran att göra som med en GET kallar ska (dvs begär webbsidan), vi först se till användaren är autentiserad och inloggad så vi vet deras namn. Sedan skapar vi en ny databaspost av initierar en ny instansiering av Powerusage. Då vi ska se en GET-begäran för watt data, vilket skulle vara i format watt = 39,2 eller liknande. Som analyseras för oss, tack och lov och vi kan också få numret sensor som förs i format sensornum = 3. Slutligen kan vi lagra data i databasen permanent
Nästa är en användbar felsökning funktion, helt enkelt skrivs ut alla uppgifter den har fått för ditt konto!
klass DumpData(webapp. RequestHandler):
def get(self):
# gör användaren logga in
om inte users.get_current_user():
Self.Redirect(users.create_login_url(Self.Request.URI))
Self.Response.out.write ("< html >< kropp > här är alla data du har skickat oss: < p >')
powerusages = db. GqlQuery ("Välj * från Powerusage där författaren =: 1 ORDER BY date", users.get_current_user())
för powerused i powerusages:
om powerused.sensornum:
Self.Response.out.write ("< b > %s < /b > \'s sensor #%d ' %
(powerused.author.nickname(), powerused.sensornum))
annat:
Self.Response.out.write (< b > %s < /b >' % powerused.author.nickname())
Self.Response.out.write ("används: %f watt på %s < p >' % (powerused.watt, powerused.date))
Self.Response.out.write ("</body >< / html >")
Detta fungerar helt enkelt markera (hämtar) alla poster, sorterar dem efter datum och skriver ut var och en i taget
Slutligen vi kommer att göra en grundläggande "första sidan" som kommer att visa de senaste par datapoints skickas
klass MainPage(webapp. RequestHandler):
def get(self):
Self.Response.out.write ("< html >< kropp > Välkommen till Wattcher! < p > här är de senaste 10 datapunkter: < p >')
powerusages = db. GqlQuery ("Välj * från Powerusage ORDER BY datum DESC LIMIT 10")
för powerused i powerusages:
om powerused.sensornum:
Self.Response.out.write ("< b > %s < /b > \'s sensor #%d ' %
(powerused.author.nickname(), powerused.sensornum))
annat:
Self.Response.out.write ('< b > %s < /b >' % powerused.author.nickname())
Self.Response.out.write ("används: %f watt på %s < p >' % (powerused.watt, powerused.date))
Self.Response.out.write ("</body >< / html >")
Dess mycket liknar funktionen DataDump men dess endast 10 poäng av data och från alla användare, trevligt att använda när du bara vill "kolla upp" men inte vill logga in
Slutligen har vi en liten initierare struktur som berättar GAE vilka sidor som länkar till vilka funktioner
ansökan = webapp. () WSGIApplication
[('/', Huvudsidan)]
("/ rapportera", PowerUpdate),
("/ dumpa", DumpData)],
Felsöka = True)
def main ():
run_wsgi_app(Application)
om namn == "main":
Main)
Test!
Kan OK prova det, låter först besöka http://wattcher.appspot.com/report
Kom ihåg att vi gjort det ett krav att leverera - några - data. Låter prova igen http://wattcher.appspot.com/report?watt=19.22&sensornum=1
Yay vi fick en OK! Kan kolla in de data som lagras genom att besöka http://wattcher.appspot.com/dump
Det finns två poster eftersom jag gjorde lite tester förväg men du kan se att det finns 2 poster. Trevligt!
Vi kan också besöka GAE Kontrollpanelen och bläddra data "för hand"
Hur som helst, nu att det fungerar, kan gå tillbaka och lägga rapportering tekniken till vår sensor-reader skript
Hämtar
Bara lite fler hacking på datorn skript och vi är klar. Vi vill lägga till stöd för att skicka data till GAE. Tyvärr just nu vår autentisering sker via Google-konton så det är inte lätt att köra på en Arduino. För att anpassa det skulle du behöva skicka användarnamnet i rapporten få och hoppas att ingen annan använder samma (om inte du också lägga till en grundläggande lösenord system)
Hur som helst, jag helt lurade hur detta från några trevliga människor på Internet
Hämta appengineauth.py från data överför sida, och vid behov ändra de första raderna. Vi hård kort URL: en ska vi samt konto/lösenord och GAE app namn
users_email_address = "" mitt konto "
users_password = "mittlösenord"
my_app_name = "wattcher"
target_authenticated_google_app_engine_uri = "http://wattcher.appspot.com/report"
Det verkliga arbetet händer på denna funktion sendreport där den ansluter och Watt informationen skickas till webbplatsen GAE
def sendreport (sensornum, watt):
# Det här är där jag vill faktiskt gå till
serv_uri = target_authenticated_google_app_engine_uri + "? watt="+str(watt) + "& sensornum="+str(sensornum)
serv_args = {}
serv_args ['continue'] = serv_uri
serv_args ["auth"] = authtoken
full_serv_uri = "http://wattcher.appspot.com/_ah/login?%s" % (urllib.urlencode(serv_args))
serv_req = urllib2. Request(full_serv_uri)
serv_resp = urllib2.urlopen(serv_req)
serv_resp_body = serv_resp.read()
# serv_resp_body bör innehålla innehållet i den
# target_authenticated_google_app_engine_uri sida - som vi kommer att ha varit
# omdirigeras till sidan automatiskt
#
# för att bevisa detta, jag bara ska skriva ut den
skriva ut serv_resp_body
Slutligen, vi avsluta genom att lägga till följande rader till vår dator script, som sänder data fint över till GAE!
# Också, skicka det till app-motorn
appengineauth.sendreport (xb.address_16, avgwattsused)
Du kan hämta den sista skriften wattcher.py - final från Hämtningssida!
Glöm inte att besöka wattcher.appspot.com att kolla in de senaste avläsningar