RaspberryPi:FullStackAwhirlwindtouroffull-stackwebapplication
developmentontheRaspberryPi
PeterDalmaris,PhD
PublishedbyTechExplorations
Copyright©PeterDalmaris,2015
EbookFormattingbyGuidoHenkel
Allrightsreserved.ExceptaspermittedundertheU.S.CopyrightActof1976,nopartofthispublicationmaybereproduced,distributedortransmittedinanyformorbyanymeans,orstoredinadatabaseorretrievalsystem,without
thepriorwrittenpermissionofthepublisher.
AboutthisbookThisbookisaproject.Usingastep-by-stepapproach,itwillhelpyouexploreyourRaspberryPiinawaythatwillhelpyouappreciatebothitshardwareanditssoftwarecapabilities.
Inwritingthisbook,Ihavetriedtobreakawayfromtheclassictextbookformatofchapters,sectionsandlongparagraphs,andinsteadpresentthishighlytechnicaltopicasanengineerwouldapproachit:withalotofiterations,eachdeliveringagradualimprovementtotheoverallfunctionalityofthesystem,witheachiterationcomprisingofseveralstep.
Eachstepismarkedwithanumbersothatyoucanrefertoitinyourownnotes,orinyourcommunicationwithmeorotherreadersofthisbook.
AllofthecodethatIdescribeinthisprojectishostedonGithub,fromwhereyoucandownloaditonyourcomputer.ItismuchbettertocopycodefromtheGithubrepositoryratherthantryingtomanuallycopyitfromthisbook.IprovidelinkstothefilethatIamdiscussingatdifferentpartsoftheproject;simplyclicktoalinkandcopythecodefromthescreen.
Inwritingthisproject,Imakeafewassumptionsaboutyou:
1. Youcanprogramacomputerinatleastoneprogramminglanguage,notnecessarilyPython
2. Youarenotafraidoflearninganewprogramminglanguage3. Youarecomfortableworkingwithelectronics.Youwillneedsomebasicbeginner-
levelskills.4. Youarenoteasilyfrustrated.Whatyouareabouttodointhisprojectrequires
patience!5. Youliketoexploredifferenttechnologies.Themodernmakermustbegoodin
multipletechnologies,hardware,softwareanddifferent“sub-categories”ofeachone.
ThegoalistoshowyouhowtosetupaRaspberryPicomputersothat:
Itmeasurestemperatureandhumidity.Itreportsthevaluesinrealtimeviaawebbrowser.Itrecordthesevaluesinadatabase.Itretrievestheserecordsanddisplaysthemintabularformatandingraphicalformatinawebbrowser.Itsendsthevaluestoagraphicalanalysiscloudservice.
Youwilllearn:
HowtosetuptheminimalRaspbianoperatingsystemtotheRPi.InstalltheaPythonvirtualenvironmentInstallanduseFlask,aPython-basedwebmicro-frameworkInstallanduseuWSGIastheapplicationserverforFlaskInstallanduseNginxlight-weightwebserver
UsetheRPiGPIOsasdigitalinputandoutputsUseaDHT22humidityandtemperaturesensorInstallandusetheSQLitedatabaseUsetheGoogleChartAPItocreatevisualrepresentationsofthesensordataUseJQuerytoaddinteractivitytowebpagesUsePlotlyforgraphicalanalysisofsensordata
VideocoursecompanionAdetailedvideo-basedversionofthisprojectisalsoavailable.Itcontainstheexactsameinstructionsasthisbook,butinfullHDvideo.Youcanseetheprojectcomingtolife.Hardwarewiring,blinkingLEDs,sensors,codewalkthroughs,everythinghappensonscreen.
Thisvideocoursepresentsthisprojectin57lecturesand7hoursofcontent.Seebelowtheimageforalistofsectionsandlectures.
Iinviteyoutowatchafewsamplevideolectures.Ifyouwishtopurchasethisvideocourse,youcantakeadvantageofthespeciale-bookreaderpricingof$9byusingcouponcodeebookatcheckout,orclickonthislink.
Videocourse,listofsectionsandlecturesSection:1-Introductiontothecourse
Lecture1:Introduction02:17Lecture2:AbouttheRaspberryPi04:45Lecture3:Components04:22Lecture4:DetailedListofComponentsyouwillneedLecture5:Pleasereadthisbeforecontinuing!
Section:2-TheOperatingSystemLecture6:SectionIntro00:25Lecture7:InstallingminiRaspbianusingMacOSX16:35Lecture8:InstallingminiRaspbianusingWindows14:42Quiz1:SectionQuiz3questionsLecture9:SectionConclusion00:14
Section:3-PythonandGPIOsLecture10:SectionIntro00:25Lecture11:SetupPython13:55Lecture12:GPIObasics08:36Lecture13:MakeanLEDblink17:57Lecture14:Readthestatusofabutton09:52Lecture15:Readtemperatureandhumidityfromadigitalsensor15:53Quiz2:SectionQuiz4questionsLecture16:SectionConclusion00:21
Section:4-SetuptheWebapplicationstackLecture17:SectionIntro00:24Lecture18:WhatistheWebapplicationstack?07:03Lecture19:InstallNginx,theWebserver02:49Lecture20:InstallFlaskandvenv09:17Lecture21:InstalluWSGI18:40Lecture22:SetupUpstart10:19Lecture23:Aboutlogfiles08:49Lecture24:ServingstaticassetsandSkeleton11:55Lecture25:StylingourWebapplicationwithSkeleton09:12Lecture26:DebuggingaFlaskapplication05:46Quiz3:SectionQuiz6questionsLecture27:SectionConclusion00:19
Section:5-BuildingasimpleFlaskapplicationontheRaspberryPiLecture28:SectionIntro00:31Lecture29:ShowDHT22sensordatainthebrowser14:07Lecture30:InstalltheSQLite3database14:05Lecture31:UseaPythonscripttostoresensorreadingtothedatabase07:34Lecture32:AutomatesensordataloggingwithcronandSQLite312:57Lecture33:Showhistoricalsensordatainthebrowser12:48Quiz4:SectionQuiz3questionsLecture34:SectionConclusion00:22
Section:6-Improvingourapplicationwithdate-timerangerecordselectorLecture35:SectionIntro00:26Lecture36:Selectinghistoricalsensordatarecordswithatime-daterange07:12Lecture37:Defineadate-timerangeintheURL10:49Lecture38:TimezonesinRasbian02:03Lecture39:Validatingtimestamps06:24Lecture40:Tidyingup:refactorourapplicationcode03:32Quiz5:SectionQuiz3questionsLecture41:SectionConclusion00:31
Section:7-ImprovingtheuserinterfaceLecture42:SectionIntro00:29Lecture43:Addingdaterangeradiobuttons19:17Lecture44:VisualisesensordatawithGoogleCharts18:35Lecture45:Installadatetimepickerwidgets07:11Lecture46:Settingupthedatetimepickerwidget07:57Lecture47:Settinguptimezonesontheclientside08:31Lecture48:Settinguptimezonesontheserverside14:00Lecture49:Linkthetwopagesoftheapplication05:29Quiz6:SectionQuiz3questionsLecture50:SectionConclusion00:28
Section:8-SetupcloudchartingandanalysiswithPlotlyLecture51:SectionIntro00:15Lecture52:SetupPlotly10:19Lecture53:AddPlotlylinks10:05Lecture54:AddPlotlysupporttotheFlaskapplicationscript11:11Quiz7:SectionQuiz3questionsLecture55:SectionConclusion00:28
Section:9-OtherusefulthingstoknowLecture56:InstallandconfigureaWifiUSBdongleforwirelessnetworking22:22
Section:10-ConclusionLecture57:Conclusion
DiscussionforumandemaillistThisbookhasawebpage!Inthatpage,Iampostingupdatesanderrata,aswellashostthebook’sdiscussionforum.
Ifyouwishtobenotifiedautomaticallyofupdatesandcorrections,pleaseconsidersigninguptothebook’semaillistathttp://txplore.com/rpi-full-stack-book/.
Herearesomemoredetailsaboutthebook’sdiscussionforum.Infact,therearetwodiscussionforums:
1. Ifyouhavepurchasedaccesstothevideocourseversionofthisbookattxplore.tv(describedearlier),thereisadiscussionforumforeachlecture.Youcanusethesededicatedforumstoaskquestionsordiscussthespecifictopicofalecture.
2. Ifyouhavenotpurchasedthevideocourseversionofthisbook,youcanusethegeneraldiscussionforumathttp://txplore.com/rpi-full-stack-book/.
TomakethemostofthisbookAllcodediscussedinthisbookisavailableonGithubathttps://github.com/futureshocked/RaspberryPi-FullStack/.Linksthattakeyoudirectlytoindividualfilesfromwhereyoucancopythiscodearegiventhroughoutthetext.Youcanalsodownloadtheentirerepositorywithasingleclicksothatyoucanhavethiscodeonyourcomputer.
Tocompletetheproject,youwillneedthesematerials:ARaspberryPi,anyversionAWindows,MacorLinuxcomputerADHT11orDHT22sensorAn5mmLEDResistorsAbreadboardandjumperwiresAccesstotheInternetAUSBWifidongle,ifavailable
Purchasethepartsfromthevendorofyourchoice.Youcanalsopurchaseabundlewith
everythingyouneedfrommyAmazonaffiliateshop,athttp://txplore.com/raspberry-pi-full-stack-parts-bundle/.
IfyouarenotintheUSA,youcanuseaforwardingservicelikeShipitotohaveyourbundlecomponentsshippedanywhereintheworld.
CHAPTERONEIntroduction
TheRaspberryPiisalowcostcomputer,popularwithpeoplewhowantdirectaccesstoitshardware.Ithasrevolutionisedcomputereducationbycombininglowpricewithaccessibility.
TheRaspberryPiModelA
Thestudent,forthefirsttimesincetheearlydaysofthePCrevolution,hasdirectphysicalaccesstothehardware.TheRaspberryPiismadeforlearning.Tomakethemostofit,youmustspendtimetogainanunderstandingofthebasicsofitshardware,itsoperatingsystem,programming,andtheperipheralsthatyoucanconnecttoit.
Thisprojectisdesignedforthatpurpose.It’sobjectiveistotakeyoutoawhirlwindtouroftheRaspberryPi,andintroduceyoutoeverythingthatisgreataboutit.
Wewillstartwithinstallingandconfiguringtheoperatingsystem,thehardway.Nopointandclick,nographicaluserinterface.Justakeyboardandacommandlineonthescreenwillseparateyoufromthecomputer.
Then,wewilllookintotheRaspberryPi’sGeneralPurposeInput/OutputpinsandinteractwithLEDsandswitchesusingPython.PythonisoneofthemanyprogramminglanguagesyoucanuseontheRPi.
We’llmoveontolearnhowtoconnectanenvironmentsensor,andaddtoourPython
skillstheabilitytoinstallthird-partylibraries.
Next,we’llsetupawebserversothatwecanaccessoursensordataviaawebbrowser.Thiswillexposeyoutoavarietyofwebdevelopmentskills,likethewebapplicationdevelopmentframework,Flask,theuWSGIapplicationserver,asimplesingleuserdatabase,SQLite,andJavascript/JQueryforwebclient-sideprogramming.
Youwillalsolearnhowtotakeadvantageofcloudcomputingresources,andinparticularyouwilluseGoogleChartstocreatechartsofthesensordata,andPlotlyformoreadvancedprocessing.
Let’sstart!
CHAPTERTWOAbouttheRaspberryPi
TheRaspberryPi,atthetimeIamcreatingthisproject,isavailableinseveraldifferentmodels.
ModelA,ModelA+,ModelBandModelB+.InthisprojectIamusingtheModelB.
SomeoftheavailableRaspberryPimodels
AllRaspberryPi’ssharesomecommonfeatures.Lookingatthecircuitboard,youcansee:
1. TheProcessorandRAMchip2. TheLANcontrollerchip3. AHDMIvideooutputconnector4. Acompositeanalogvideoconnector(onmodelsAandB)5. AnSDcardconnector6. Amicro-USBpowerconnector7. AUSBport8. AnEthernetport(onmodelBs)9. Acameraconnector
10. AndtheveryimportantGPIOheaders
TheRaspberryPi,commoncomponents
TheRaspberryPicomesasasinglePCB.Nokeyboardandmouse,noscreen,notevenapowersupply.Youhavetoprovideallthat.Inthisproject,wewillbeusingtheRaspberryPiinsocalled“headless”mode.Thismeansthatwewillnotbeconnectingittoakeyboardormouse.Instead,wewillworkwiththeRaspberryPiviaanSSHnetworkconnection.Don’tworryifyoudon’tquiteunderstandwhatthismeans,Iwillshowyoueverythingyouneedtoknow,stepbystep.
Asacomputer,andunlikemicro-controllersliketheArduino,theRaspberryPineedsanoperatingsystem.Thereareseveraloptionstochoosefrom:
Rasbian,theRaspberryPiFoundation’spreferredoperatingsystemdistributionUbuntu,OpenelecOSMCPidoraRISCOSAndMinibian,mypreferreddistribution.
AllofthemexceptforRISCOSareflavoursofLinux.
MinibianisaminimalistversionofRaspbian.Itkeepseverythingthatisimportantandthrowsawaythegraphicaluserinterfaceandafewotherthingsthatarenotreallyneededforourpurposes.Inreturn,wegetasmalldiskfootprintsowecanuseevensmall4GByteSDCards.
Althoughthereisandistributiondesignedforabsolutebeginners,NOOBS,IwillshowyouhowtoinstallMinibianinthenextsection.
TheRaspberryPicomeswith512MBytesor1GByteofRAM,dependingonthemodel.Videomemoryissharedwithgeneralpurposememory.Inourproject,wewillnotbeusinganyvideooutput,sowewillconfigureourPitonotuseanyvideomemory.
AlthoughthisamountofRAMmayseemtoolittleatatimewhencomputerscomewithmultipleofgigabytes,foranembeddedcomputeritismorethanenough.PeoplerunmultiplayergameserverslikeMinecraftonit,andsmallproductionwebserversanddatabaseserver.OthershaveevenusedtheRPiasanodeforsmallsupercomputers.Withabitofplanning,theRaspberryPicandoamazingthings.
RaspberryPirunningaMinecraftserver
TomakesomethingusefulwiththeRaspberryPi,justlikewithanycomputer,youneedapplicationsoftware.Youcaneitherdownloadreadymadesoftware,orwriteyourown.Inthisproject,Iwillshowyouboth.Youwilldownload,installandconfigurevarioustypesofservers,andyouwillwriteyourownapplicationinPython.
YouwillnotbecomeanexpertPythonprogrammer,butyouwillbecomefamiliarwithitenoughtobebothusefulanddangerous.That’sagreatstart!
Finally,theRaspberryPirarelyworksinisolation.IthasafastEthernetcommunicationssocketthroughwhichyoucanconnectittotheInternet.YoucanalsoattachedaWifiUSBmoduleandgowireless.WewilltakeadvantageofthiscapabilityandmakeitpossibleforourapplicationtointeractwithInternetbasedwebservices.Youwillalsobeabletoaccessyourapplicationviaawebbrowser,potentiallymakingitpossibletoaccessyourRaspberryPifromanywhereintheworld.
Ok,enoughwiththisgeneralintroductiontotheRaspberryPi.Inthenextlecture,Iwilltalkaboutthecomponentsthatyouwillneedforthisproject.
CHAPTERTHREEInstallanOperatingSystem
WewilluseMiniRasbian,availableatminibianpi.wordpress.com
ThedecisiontouseMiniRaspbianinsteadofRasbianispurelypractical.Inthisproject,weonlyneedsomeofthemanyfeaturesandcapabilitiesofthefullRasbiandistribution.MiniRaspbianisaminimalversionofRasbian.WithMiniRasbian,wegetafullyRaspberryPicompatibleoperatingsystemwithaverysmalldiskfootprintandwithoutanyfeaturesthatarenotstrictlynecessaryforourpurposes.
WecaninstallitonaverysmallSDcard,4GBytesareenough.AndonceitisonthissmallSDcard,wecanmouldittosupporttheexactcapabilitiesweneed.
Let’sgetstarted!
Step1.1DownloadtheOSimagefromminibianpi.wordpress.com.Inthisproject,wewillbeusingthe4threleaseofMinibian
Step1.2OnaMac,useApplePi-Baker(www.tweaking4all.com/news/applepi-baker-v1-6-update)toinstalltheOSimageonanSDcard.
Step1.3Oncethetoolcompletestheprocess,ejecttheSDcardandinsertitintotheRPi.
Step1.4PluginanEthernetcabletotheRPiandturniton.
Step1.5PluginanEthernetcabletotheRPiandturniton.
Step1.6Usingaterminalwindow,likeiTermontheMacorPuttyonWindows,logontoyourRPiforthefirsttime:>[email protected]
Theauthenticityofhost’192.168.111.63(192.168.111.63)’can’tbeestablished.
RSAkeyfingerprintis54:ce:1f:28:34:87:65:48:1f:e6:fd:bb:e0:d9:a8:27.
Areyousureyouwanttocontinueconnecting(yes/no)?
SayYestotheprompt.
Step1.7We’llupdatetheinstallation,andtheninstallraspi-configsothatwecanusethisutilitytodosomebasicconfigurationontheRPi:>apt-getupdate#Toupdatethepackagerepositorylists
>apt-getupgrade#ToupgradetheOSwiththelatestfiles
>apt-getinstallraspi-config
Step1.8We’llrunraspi-configtwice.First,toexpandthefilesystemontheSDcardtousealloftheavailablespace.Second,toenablesomeoftheRPi’ssubsystemsthatwillbeusefullater.>raspi-config
Chooseoption1“ExpandFilesystem”.
Whendone,youwillgetaconfirmationscreen:
HitEntertoclosethiswindow.ThenTabtwicetohighlight“Finish”andEnteragaintocloseraspi-config.Choose“Yes”tomaketheRPitoreboot.
Thiswillstarttheprocessofexpandingthefilesystem.GiveyourRPiaround10minutestofinish(thisdependsonthesizeofyourSDCardandthespeedofyourRPi).
Step1.9Awhilelater,trytologonagainsothatwecanfinishthebasicconfiguration.Remember,thedefaultpasswordis“raspberry”:>[email protected]
Step1.10Useraspi-configagaintochangesomeofthesettings:>raspi-config
First,setupanewpassword:
Youwillseethis,justhitOk:
Ifthisworkedforyou,gotothenextstep.
Atthetimeofwritingthis,thisdidn’tworkforme.Ireceivedthiserror:
Goback,we’llchangethepasswordonthecommandlinelater.Let’smoveonwiththeAdvancedOptions.
Choose“8-AdvancedOptions”
WewanttoenableSPI,I2CandSerial.Thesearecommunicationschannelsthatwewillfindusefullater.Don’tworryaboutanythingelse,althoughyoucanfeelfreetolookaround.
First,SPI:
Select“Yes”,then“Ok”,“Yes”and“Ok”attheprompts:
Thelast“Ok”willtakeyoubacktothefirstwindowofthetool.Again,select“8-AdvancedOptions”andthen“A7I2C”:
Choose“Yes”,“Ok”,“Yes”,and“Ok”.ThengobacktoAdvancedOptionsandchoose“Serial”:
Select“Yes”toenableSerial.Reboottomakethesechangeseffective:HitTabtwicetohighlightthe“Finish”option,andhitEnter.Hit“Yes”toreboot.
Again,itwilltakearoundaminutefortheRPitoberebooted.
Step1.11Finally,letschangetherootdefaultpassword:>[email protected]
>passwdroot
EnternewUNIXpassword:
RetypenewUNIXpassword:
passwd:passwordupdatedsuccessfully
Chooseagoodpassword.Ifyouforgetit,there’snowaytoresetit.
CHAPTERFOURSetupPythonandtryouttheGPIOs
AlthoughRaspbiancomeswithPython,wewillnotusethedefaultversion.Instead,wewillusethePythonVirtualEnvironment,orvirtualenv.Withvirtualenv,wecanisolatemultiplePythonenvironments,eachwithitsowninterpreterversionandlibraryset.
Thisisgreatfordoingexperimentswithouthavingtomodifyourmain“production”Pythonenvironment.Ifyoumakeamistake,youcanjustdeleteyourtestvirtualenvironment.Needlesstosay,virtualenvifperfectforexperimenting,whichiswhatwearedoingrightnow.
We’llinstallvirtualenvandtestitbymakinganLEDblink.
Step2.1StartbyinstallingthenecessaryheaderfilesandastaticlibraryforPython.>apt-getinstallpython-dev
Step2.2InstallthePythonvirtualenv>apt-getinstallpython-virtualenv
Step2.3NowwewillcreateanewworkingfolderandactivateanewPythonvirtualenvironmentinit.>cd/var
>mkdirworking
>cdworking
>virtualenvvenv#venvisthenameofthefolderthatwillcontainthevirtualenvironment
Newpythonexecutableinvenv/bin/python
Installingdistribute………….done.
Installingpip……………done.
Step2.4Noticethatnowyouhaveanewfolderinthe“working”directory,calledvenv.Inthatfolder,youhaveacompletecopyofthePythonenvironment.
Let’sactivateit:>.venv/bin/activate
(venv)root@raspberrypi:/var/working#
Youexecutethe“activate”scriptinthevenv/bindirectory.Thepromptisupdatedtoincludethe“(venv)”indicator,showingthatyourvirtualenvironmentwiththatnameisactivated.Ifyouhavemultipleenvironments,theonethatisactuallyactivatedwillalwaysbenamedinthecommandlineprompt.
PlayaroundwithPythononthecommandinterpretertoshowthatitworks.
Todeactivate,youcanjustdothis(don’titnowthough!):>deactivate
Step2.5Let’sinstallthePythonRPiGPIOpackageusingthevirtualenvironment’spackagemanager:>pipinstallrpi.gpio
Step2.6We’llmakeanLEDblink.Youwillneedtwomale-femalejumperwires,a5mmredLEDanda~150Ohmresistor.Usethisdiagramtofindoutthepinstouse.Rememberthatpin1isatthebottomoftheboardclosesttothepowerredLED.
TakeawireandconnectthelongpinoftheLEDtopin7(GPIO4).
ConnecttheresistorfromtheshortpinoftheLEDtoawholeinavacantcolumnonthebreadboard.
Takeawireandconnectthefreepinoftheresistortopin6(Ground)ontheRPi.
Step2.7UsethisprogramtomaketheLEDblink.First,letsinstallatexteditor,thencopytheprogramtotheeditor(don’tworry,lateronwewilluseyourfavouritetexteditoronyourMacorWindowsmachine):>apt-getinstallvim
Whileinyour“working”directory,openupanewvimeditor:>vimblinky.py
Hitthe“i”keytogetviminto“insertmode”,andcopythisprograminthefile:importRPi.GPIOasGPIO##ImportGPIOLibrary
importtime##Import‘time’library(for‘sleep’)
pin=7##We’reworkingwithpin7
GPIO.setmode(GPIO.BOARD)##UseBOARDpinnumbering
GPIO.setup(pin,GPIO.OUT)##Setpin7toOUTPUT
foriinrange(0,20):##Repeat20times
GPIO.output(pin,GPIO.HIGH)##TurnonGPIOpin(HIGH)
time.sleep(1)##Wait1second
GPIO.output(pin,GPIO.LOW)##TurnoffGPIOpin(LOW)
time.sleep(1)##Wait1second
GPIO.cleanup()
InPython,itisveryimportantthatyoukeeptheindentationcorrect.Insidethe“for”loop,thecodeyoubeintentbyexactlyonelevel,eitherspace(s)ortab(s).
Makesuretheprogramwascopiedcorrectly.Thenhittheescapekeytogetvimintocommandmode.Then,saveandquitvimbytyping“:wq”(wforwrite,qforquit).
Nowruntheprogram:>pythonblinky.py
…andwitnessthisbrilliantLEDblinking20times!
Step2.8Spendabitoftimelookingattheprogram.NoticehowweimporttheGPIOlibraryatthefirsttime,andthe“time”libraryinthesecondsothatwecanusethesleepfunction.
Then,wesettheboardpinnumberto7.WecanrefertoRPipinintwoways,physicalnumberingorGPIOnumbering.Physicalnumberingaretheactualnumberofeachpinthatwecancount,startingwith1fromthepinthatistheclosesttotheredpowerLED,2theonenexttoit,etc.GPIOnumberingusesthereferencesthattheRPiitselfusesinternally.Asmostofusarehumans,Ithinkweshouldusethephysicalnumberingsystem.
Thatiswhyinline5wesetGPIOmodetoBOARD.
TouseGPIOnumbering,youwouldwrite“GPIO.BCM”.
Next,wetelltheRPithatwewillbeusingthepin7asadigitaloutputpin.
Insidetheloop,weturntheLEDonandofusingtheGPIO.outputmethod.
Whentheloopfinishes,wecleanuptheRPi’sGPIOregisters,andwearedone!YoushouldalwaysfinishyourRPiprogramswiththisinstructionsothatthepinsareleftinaknownandsafestate.
Step2.9Nowlet’sdotheopposite:UseaGPIOpinandaninput,toreadthestateofabutton.
Takeabuttonorswitchandplaceitonyourbreadboard.Abuttonusuallyhas4pins,connectedinpairs.Lookforthepinsthatpointawayfromthebutton,oneithersideofthebutton.Takeajumperwireandconnectoneofthetwotoground.
Onthesecondpin,connectajumperwiretothephysicalpin8ontheRPi,anda10KOhmresistor.Theothersideoftheresistorshouldbeconnectedtopin1ontheRPi,whichprovides3Vofpower.Theresistorcreatesapull-up,sothatwhenthebuttonisnotpressed,itwillconveythe3Vtotheinputpin8.
Thisiswhatyourcircuitshouldlooklikenow:
Step2.10JustlikeintheLEDexperiment,openupvimlikethis(downloadbutton.py):>vimbutton.py
Thiswillcreateanewfilewiththefilename“button.py”.Hitthe“i”keytogointotextinsertmode,andcopythistextinit:importRPi.GPIOasGPIO##ImportGPIOLibrary
inPin=8##Switchconnectedtopin8
GPIO.setmode(GPIO.BOARD)##UseBOARDpinnumbering
GPIO.setup(inPin,GPIO.IN)##Setpin8toINPUT
whileTrue:##Dothisforever
value=GPIO.input(inPin)##Readinputfromswitch
ifvalue:##Ifswitchisreleased
print“NotPressed”
else:##Elseswitchispressed
print“Pressed”
GPIO.cleanup()
Becareful,theindentationsareimportant!
Whenyouhavefinishedcopyingtheprogram,typeEsc-:togointocommandmode,and“wq”tosavethechangestothefileandquit.
Inthisprogram,inthesecondlinewearedeclaringthevariableinPintoholdthepinnumberwherethebuttonisconnected.Inthenexttwolines,wesetthispintobeaninput,andaskingtheRPitousephysicalpinreferences.
Insidethewhileloop,wetakeareadingfromtheinPinusingtheGPIO.inputmethod.Ifthevalueistrue,wewillprint“NotPressed”,otherwisewewillprint“Pressed”.
WhentheGPIO.inputreadsaHIGH(anythingaround3V),thenitwillreturnaTRUEvalue,otherwiseFALSE.
Onthecommandline,runtheprogram:>pythonbutton.py
Hint:iftheRPicomplainslikethis:Traceback(mostrecentcalllast):
File“button.py”,line1,in<module>
importRPi.GPIOasGPIO##ImportGPIOLibrary
ImportError:NomodulenamedRPi.GPIO
Thenyouhaveprobablyforgottentostartyourvirtualenvironment.Dothatnow(fromyour“working”directory):>.venv/bin/activate
Yourrunningprogramwilllooklikethisintheterminal:
Nowpressthebutton,andseethenewmessagescrollingup:
Thereyouhaveit!YounowknowhowtouseGPIOpinsasinputsandoutputs!
Toendtheexecutionoftheprogram,typeControl-C.
Step2.11Alittleexercise:canyouaddanLEDtothebreadboardandgetittolightupwhenyouarepressingthebutton?
CHAPTERFIVEUseaDHT22sensor
InthissectionwewillconnectaDHT22temperatureandhumiditysensor.Dodothis,wewillinstalltheDHTlibraryfromitsGithubrepository.ThelibraryiswrittenandmaintainedbyAdafruit,andisavailableatthislocation:github.com/adafruit/Adafruit_Python_DHT
TheprocessinvolvesinstallingGitonyourRPi.GitisaversioncontrolsystemthatGithubusestomanagetherepositoriesthatarestoredonit.
Let’sbegin!
Step3.1LogontoyourRPiifyouarenotalready,gotoyourworkingfolder,andactivateyourPythonvirtualenvironment:>[email protected]
>cd/var/working
>.venv/bin/activate
Yourcommandpromptnowhasthe“(venv)”flagshowing:(venv)root@raspberrypi:/var/working#
Step3.2InstallGit:>apt-getinstallgit-core
Thisisalargepackage,soinstallationwilltakeawhile.
Step3.3ConfigureGitbytellingitwhoyouare:>gitconfig—globaluser.name“Peter”
>gitconfig—[email protected]
Toconfirmyoursettings,typethis:>gitconfig—list
user.name=Peter
Step3.4WecannowgoaheadandclonetheAdafruitDHTlibraryfromGithub.Useyourbrowsertovisitthelibrarypage:github.com/adafruit/Adafruit_Python_DHT
Ontherightsideofthepage,noticethecloneURLtextbox.ClickontheclipboardicontocopytheURLintoyourclipboard.
Step3.5OnyourRPi,typethistoclonethelibraryrepositorytoyourcomputer:>gitclonehttps://github.com/adafruit/Adafruit_Python_DHT.git
Cloninginto‘Adafruit_Python_DHT’…
remote:Countingobjects:112,done.
remote:Total112(delta0),reused0(delta0),pack-reused112
Receivingobjects:100%(112/112),38.30KiB,done.
Resolvingdeltas:100%(66/66),done.
Note:IusedCommand-VontheMacorCtr-VonWindows/LinuxtopasttheURLfromtheclipboardintothecommandline.
Step3.6Younowhaveanewdirectoryinyourworkingfolder,namedAdafruit_Python_DHT.
Changeinit,andrunthesetupscripttoinstallthelibrary.>cdAdafruit_Python_DHT
>pythonsetup.pyinstall
Step3.7
Thelibraryrepositorycomeswithexamples,let’susethemtomakesurethatoursensorworks.Firstthough,weneedtoconnectoursensor.Pluginthesensortoyourbreadboard.Inmyexample,IhaveleftthecircuitfromtheearlierLEDexperimentintactincaseIwanttouseitlater.Don’tworryifitlooksabitmessy,thereareonly4connectionstobemade.
Step3.8LookattheDHT22sensorfromthefront:
Hereisthepurposeofeachpin:
1. 3.3V2. Data3. Leaveunconnected4. GND
Startbytakinga10KOhmresistorandconnectingitbetweenpins1and2.Thisresistorwillbeapullupforthedatapin.
Next,connectpin2to3.3V.Ifyoualreadyhaveajumperwireconnectedtopin1ontheRPi,thenfollowthiswiretothebreadboardandconnectyoursensorpowerwiretoafreeholeinthesamecolumnasthewirethatiscomingfromtheRPi.
Similarly,connectawirefromthesensorpin4toanavailablegroundpinontheRPi,ortoagroundcolumnonthebreadboard.
Finally,let’sconnectthedatapin.IwouldnormallyconnectthedatapintotheRPiGPIO4,whichisthephysicalpin7.However,pin7isalreadyconnectedtotheLED,soIwilluseGPIOpin17instead,whichisphysicalpin11.
Double-checktheconnections,andlet’smoveonthethePythonprogramsothatwecantakeameasurement.
ThisinteractivepinoutdiagramcreditGadgetoid,pi.gadgetoid.com/pinout
Step3.9ChangeintotheexamplesfolderoftheAdafruitPythonDHTlibrary.Forme,thisisdonelikethis:>cd/var/working/Adafruit_Python_DHT/examples
TheexampleIwanttouseistitled“AdafruitDHT.py”.Let’shavealookinsideoutofcuriosity,toseehowitworks.We’llusecattodothis:>catAdafruitDHT.py
Noticehowthereisanarraythatcontainsthevalidsensornames.Theprogramalsoreadsthesensordatapinfromthecommandline.
ThereadingisdonewiththeinstructionAdafruit_DHT.read_retry(sensor,pin).Thisreturnsthehumidityandtemperaturevalues,whichareassignedtolocalvariablesthroughmultipleassignment.
Let’sruntheprogram!>pythonAdafruitDHT.py230217
Temp=25.9*CHumidity=57.1%
Itworked!Themodelofmysensoris2302,andIhaditconnectedtoGPIO17.TakecarenottoconfusetheGPIOnumberwiththephysicalpinnumberbecausetheyaredifferent.UsethepinmapabovetofigureoutyourGPIO.
Curiously,thisprogramworkedwithmysensorbothwhenIuse2302or22asthenameargument.ThereadingsIreceivedineithercasewerealmostidentical,soIguessthatthetwosensortypesaredirectlycompatible.Buttheprogramdidn’tworkwhenIused11asthesensorname.
Awesome!WehavesetupourRPi,andwecanuseit’sGPIOs.WeevenhavealibrarytomakeiteasytoworkwiththeDHTsensor.
Let’smoveontosettingupawebapplicationserversothatwecanmultiplyourRPi’sfunctionalityandgetourdatatothecloud!
CHAPTERSIXCreateawebapplicationstackontheRPi
Atthispoint,wehavesetuptheRaspberryPiwithanoperatingsystemandPython.WecanconnectdevicestoitsGPIOsandreadthestatusofabutton,controlanLED,andtaketemperatureandhumidityreadingsfromasensor.
Next,youwilllearnhowtousetheWebasaninterfacefortheRPi.Thisway,youwillbeabletocontrolfunctionalityontheRaspberryPiviaawebbrowser.
TheWebapplicationstack.Eachlayerimplementsafunctiononwhichsoftwareonthelayeraboveitdepends.Thisdiagramalsoshowsthespecificsoftwarethatwewilluseinsideeachlayer(theblueboxes),thehardware(theredbox)
andtheclientcomponents(thegreenboxes).
Todothis,wewillcreatesomethingcalleda“webapplicationstack”.Thestackcontainssoftwarethat:
allowsawebbrowsertointeractwiththeRPi(thisisthewebserversoftware),allowsforPythonscriptstobeexecutedinresponsetowebbrowserrequests(anapplicationserver)makesiteasyforustocreateawebapplication,thatis,anapplicationthatwecaninteractwithviaourwebbrowser(awebapplicationframework).
There’sothersoftwarecomponentswewillneed,likeadatabasetostoreandretrievedatafromoursensors,andrelatedlibraries.
Let’sgetstartedwiththewebserver.WewilluseNginx,whichisaverypopularserver,fastandwithasmallmemoryanddiskfootprintthatisverypopularforrunningonsmallcomputersliketheRPioronvirtualmachines.
Then,wewillinstallFlask,aPythonwebdevelopmentmicro-frameworkthatmakesiteasytobuildgreatwebapplicationsquickly.
Next,we’llinstallaPythonapplicationserver,uWSGI,whichwillexecuteourPythonprogramsonbehalfoftheNginxwebserver.
Step4.1LogontoyourRPiifyouarenotalready,gotoyourworkingfolder,andactivateyourPythonvirtualenvironment:>[email protected]
Step4.2InstallNginx:>apt-getinstallnginx
Step4.3Installationwilltakeawhile.Onceit’sdone,youcantestyournewwebserver.
First,rememberwhatyourIPaddressis(incaseyouforgotsinceyouloggedonviaSSH!).Hint:usetheifconfigcommand.
StartNginx:>/etc/init.d/nginxstart
Startingnginx:nginx.
Step4.4Gotoyourwebbrowser,andnavigatetoyourRPi’sIPaddress:http://192.168.111.63
YoushouldseeNginx’sdefaulthomepage:
Step4.5Wehaveaworkingwebserver!Let’smoveontotherestofthestack.BeforeweinstallFlaskanduWSGI,we’llcreateanewdirectorythatwillbethehomeofourwebapplication.Init,wewillinstalladedicatedPythonvirtualenvironment,justlikewedidearlierwiththesimpleLEDandDHTexamples.
We’llsetupthisdirectoryinthe/vardirectory.Within/varlet’screateasubdirectorycalledwww,andwithinthatonecalledlab_app.Ifyoudecidetocreatemorewebapplicationslater,youcanputtheminthewwwtoo.Ifyoudon’tlikethenameIchose,“lab_app”,chooseonethatyoudo.>cd/var
>mkdirwww
>cd/www
>mkdirlab_app
Step4.6Nowwe’llsetupaPythonvirtualenvironmentinlab_app.>cdlab_app
>virtualenvvenv
Newpythonexecutableinvenv/bin/python
Installingdistribute…………….done.
Installingpip……………done.
Step4.7Activatethenewvirtualenvironment:>.venv/bin/activate
(venv)root@raspberrypi:/var/www/lab_app#
Onceactivated,yourcommandpromptwillcontainthenameofyourvirtualenvironmentatthebeginningoftheprompt(“venv”).
Step4.8OntoFlask!Remember,Flaskisamicro-frameworkthatmakesiteasytocreatewebapplicationusingPython.ItselfisaPythonpackage,sowewillinstallitusingthepippackagemanager:>pipinstallflask
Eventually,youwillseethissuccessmessage:SuccessfullyinstalledflaskWerkzeugJinja2itsdangerousmarkupsafe
Cleaningup…
Step4.9Let’stestFlasktomakesureitisreallyworking.
Usevimandcopythisprograminit(hereitisonGithub):>vimhello.py
…thentype“i”togetintoinsertmode.fromflaskimportFlask
app=Flask(__name__)
@app.route(“/”)
defhello():
return“HelloWorld!”
if__name__==“__main__”:
app.run(host=‘0.0.0.0’,port=8080)
Saveitashello.pyusingESC-wqandENTER,andthenrunit:>pythonhello.py
*Runningonhttp://0.0.0.0:8080/(PressCTRL+Ctoquit)
Flaskisnowwaitingforabrowsertomakearequest.Itwilloutputlogmessagestotheconsolesothatyoucanseewhatishappening.
GotoyourbrowserandnavigatetothisURL:http://192.168.111.63:8080(changetheIPaddresstotheactuallIPaddressofyourRPi,butkeepthe8080portnumberthesame).Youshouldseethis:
ToexitFlaskandcontinuewithoursetup,typeCtrl-Cinthecommandline.
Step4.10Next,it’stheturnoftheuWSGIapplicationserver.ThiswillexecutePythonscriptsonbehalfofthewebserver.ItisalsoaPythonpackage,soinstallitlikethis:>pipinstalluwsgi
ThistakesawhilebecausetheuWSGIisactuallycompilerfromsourceonyourRPi.Eventually,itwillsaythisandfinish:Successfullyinstalleduwsgi
Cleaningup…
Step4.11NowweneedtoconfigureNginxanduWSGItoworktogether.
We’llstartwithNginx.Firstremovetheconfigurationfileforthedefaultapplication(theonethatproducedtheHelloWorldpageinourtestearlier),thencreateanewapplicationconfigurationfileforournewapplication,andenableit.
Then,gotouWSGI.Createaconfigurationfileforit,andmakeitsothatitsservicestartsautomaticallywhentheRPistarts.Thismayseemabitcomplex,butIassureyouitnothingyoucan’thandle.
Step4.12Nginx:removethedefaultapplicationconfigurationfile.Thisfileisin/etc/nginx/sites-enabled/default.Thefile“default”isactuallyasymboliclink(likeaWindowsshortcut),pointingto/etc/nginx/sites-available/default.
Justremovethislinkandwearedone:>rm/etc/nginx/sites-enabled/default
Thencreateanewconfiguration.Createafilecalledlab_app_nginx.conf:>cd/var/www/lab_app/
>vimlab_app_nginx.conf
Press“i”togointoinputmode,andcopythiscode:server{
listen80;
server_namelocalhost;
charsetutf-8;
client_max_body_size75M;
location/static{
root/var/www/lab_app/;
}
location/{try_files$uri@labapp;}
location@labapp{
includeuwsgi_params;
uwsgi_passunix:/var/www/lab_app/lab_app_uwsgi.sock;
}
}
Youareconfiguringanapplicationthatlistensforrequestsonport80.Thelocationofitsstaticassets(thingslikeHTMLfiles,CSSfilesandimages)areinadirectorycalled“static”,whichisinsidetherootfolder.Therootfolderisat/var/www/lab_app.
TheconnectiontotheuWSGIapplicationserverisdoneviaasocketfile.ThiswillbegeneratedlaterbyuWSGI,butnowwecandefineitslocationat/var/www/lab_app/labapp_uwsgi.sock.
Step4.13Toenablethisnewapplicationwefirstcreateasymboliclinkfromitsreallocationto/etc/nginx/conf.d/.,andthenwewillrestartNginxtomakethechangeseffective.Nginxalsolooksin/etc/nginx/conf.d/tofindconfigurationfilesforapplications,inadditionto/etc/nginx/sites-enabled.
Createthesymboliclink:>ln-s/var/www/lab_app/lab_app_nginx.conf/etc/nginx/conf.d/
Step4.14RestartNginxtomakethechangeseffective:>/etc/init.d/nginxrestart
Restartingnginx:nginx.
Step4.15Let’sswitchtouWSGI.Createanewfile:>vim/var/www/lab_app/lab_app_uwsgi.ini
…andcopythiscodeinit:[uwsgi]
#application’sbasefolder
base=/var/www/lab_app
#pythonmoduletoimport
app=hello
module=%(app)
home=%(base)/venv
pythonpath=%(base)
#socketfile’slocation
socket=/var/www/lab_app/%n.sock
#permissionsforthesocketfile
chmod-socket=666
#thevariablethatholdsaflaskapplicationinsidethemoduleimportedatline#6
callable=app
#locationoflogfiles
logto=/var/log/uwsgi/%n.log
Therearetwothingstonotice.
First,thenameofourappis“hello”,whichmatchesthenameofourtexthello.pyprogramthatwecreatedearlier.
Second,thelastlineisforthelogfile,whichwillbestoredin/var/log/uwsgi.Thisdirectorydoesnotexist,soweneedtocreateit:>mkdir-p/var/log/uwsgi
Step4.16Let’strytostarttheuWSGIdaemonnow:>uwsgi—ini/var/www/lab_app/lab_app_uwsgi.ini
[uWSGI]gettingINIconfigurationfrom/var/www/lab_app/lab_app_uwsgi.ini
Thedaemonwillstart,andjustlikeourtestoftheFlaskserverearlier,itwilloutputlogmessagetotheconsole.
YoucanverifythatuWSGIviaNginxandFlaskisrespondingtoyourwebbrowserrequestsbypointingyourbrowsertohttp://192.168.111.63/(changemyIPaddresstoyours):
Youcanseethatthepageatport80isnowreturningfromourhello.pyprogram.
Atthispoint,sincewedidn’tgetanyerrormessages,wecanbeconfidentthatuWSGIworksfine.Tomakeituseful,wemustmakethedaemonrunasabackgroundservice.
HitCtrl-Ctodothisnext.
Step4.17DebiancomeswithaservicemanagementsystemcalledUpstart.UpstartcanbeusedtodothingslikestartupaservicewhentheRaspberryPirestarts,ortorestartitwhenitcrushes.uWSGIworkswellwithUpstart.InuWSGI,thereisaspecialmodecalledEmperor,whichmanagesapplicationinstances.Emperorwilllookforconfigurationfilesthatexistinafoldercalled,appropriately,“vassals”.Emperorthenwillspawninstancesoftheapplicationserverbasedonthosefiles.
Inshort,UpstartwillstartEmperor,andEmperorwillstartouruWSGIapplicationserverbasedonavassalconfigurationfile.
FirstinstallUpstart:>apt-getinstallupstart
…andreboot:>reboot
Step4.18OncetherebootiscompleteandyouhaveloggedbackintotherootaccountofyourRPi,createtheUpstartconfigurationfile.Thisshouldgoin/etc/init,sincethisiswhereUpstartlooksforconfigurationfiles:>vim/etc/init/uwsgi.conf
Copythefollowingcodetothisfile:description“uWSGI”
startonrunlevel[2345]
stoponrunlevel[06]
respawn
envUWSGI=/var/www/lab_app/venv/bin/uwsgi
envLOGTO=/var/log/uwsgi/emperor.log
exec$UWSGI—master—emperor/etc/uwsgi/vassals—die-on-term—uidroot—gidroot—logto$LOGTO
Noticethatthelastline,startingwith“exec”islookingforEmperorconfigurationfilesin/etc/uwsgi/vassals.Solet’screateaconfigurationfileforourapplicationintherenow.
Step4.19Createthevassalsdirectory:>mkdir-p/etc/uwsgi/vassals
Noticethatthe“p”switchinstructsthemkdircommandtocreateanydirectoriesbelow“vassals”thatdon’texist.So“uwsgi”and“vassals”willbecreatedfromasinglecommand.
Step4.20Now,wewilllinktheuWSGIconfigurationfilewecreatedinstep15inside/var/www/lab_app/lab_app_uwsgi.initothevassalsdirectoryviaasymboliclink:>ln-s/var/www/lab_app/lab_app_uwsgi.ini/etc/uwsgi/vassals/
Step4.21Let’strytostartuWSGIviaUpstartnow:>startuWSGI
Step4.22Verify.DirectyourbrowsertoyourRPi,andyoushouldseethefamiliarHelloWorldmessage:
Step4.23Trouble?Problems?Logfilesareheretohelp.Wheneversomethingisnotquiteright,startbyexaminingthelogfiles.Youhavetwo:
1. Nginxaccessat/var/log/nginx/access.log2. Nginxerrorat/var/log/nginx/error.log3. uWSGIat/var/log/uwsgi/lab_app_uwsgi.log
Step4.24YourapplicationshouldalsobeabletostartautomaticallyafteraRPireboot.TryrebootingyourRPi,waitforacoupleofminutesfortheprocesstocomplete,andwithoutloggingon,requesthttp://192.168.111.63inyourbrowser.Thepageshouldbeloadingwithoutanyissues.
CHAPTERSEVENServingstaticassets
InthissectionIwillshowyouhowtoservestaticwebassetsfromyourRPi.Staticassetsarefileslikeimages,CSSandJavascript,oranythingthatisnotexecutablecode.
Inthissection,wewillsetupNginxtoservestaticassets,andmakeourhumble“HelloWorld!”messagealittlebitnicertolookatusingsomeCSS.
Step5.1Wewillcreateanewdirectoryinthe/var/www/lab_appdirectoryinwhichwewillstorestaticassets.Let’scallthisnewdirectory“static”:
(AssumingyouhaveloggedontoyourRPiandarenowinthe/var/www/lab_appdirectory):>mkdirstatic
>cdstatic
>mkdircss
>mkdirimages
Step5.2Let’sseeanexampleofastaticfile.First,let’screateasimpleHTMLfile,andstoreitinthestaticdirectorywiththename“a_static_file.html”.Giveitthiscontent:<html>
<head>
<title>Staticpage</title>
</head>
<body>
<h1>Thisisanexampleofastaticpage</h1>
<p>Neat,isn’tit?</p>
</body>
</html>
Step5.3Navigateyourbrowsertohttp://192.168.111.63/static/a_static_file.html.
Youshouldseethis:
Step5.4Let’saddaCSSfiletomakethispagelookabitbetter.IfyouarenotfamiliarwithCSS,fornowitisenoughtoknowthatCSSisalanguageforstylingwebpages.Itcontainsinformationthatthebrowserusestochangethedefaultwayitrendersthevariouswebpageelements.
Inthisproject,Ihaveselectedasimpleandpopularboilerplatedesigncalled“Skeleton”.Youcanfinditanddownloaditathttp://getskeleton.com.ThedownloadedarchivecontainstwoCSSfilesandseveralimagestomatchthestyles.
DownloadtheSkeletonZIPfileandexpandit.Youwillhavesomethinglikethis:
Thearchivecontainsanexampleindexfileinitsroot,thetwoCSSfilesinsidethecssdirectory,andafaviconimagefileinimages.
Step5.5YounowneedtotransferthesefilestotheappropriatelocationsonyourRPi.TheeasiestwaytodothisistouseanSFT/SSHutility.MyfavouriteutilityisCyberduck(http://cyberduck.en.softonic.com/mac)ontheMac,andFilezilla(http://filezilla-project.org)onWindows.
Let’sworkwithCyberduck.Startitup,andclickontheOpenConnectionicon.ChooseSFTPfromtheprotocoldrop-downmenu.TypeintheRPiaddressandcredentials,likeintheimagebelow:
Click“Connect”.Youwillgetawindowaskingyoutoaccepttheserverfingerprint,towhichyoushouldclick“Allow”.
TheprocessofcopyingfilesusingSFTPisverysimilaracrossdifferentgraphicalFTPclients.
Step5.6
Navigatetothestaticdirectoryofyourapplication,likeinthisimage:
Now,copythefilesfromthecssdirectoryintheSkeletonarchive(whichisonyourlocalcomputer)tothecssdirectoryonyourRPi(acceptanyrequestforconfirmation):
Dothesamefortheimagefile.Don’tworryabouttheHTMLfile.
Step5.7OntheRPi,youshouldnowhavethesefilesinyourstaticdirectory:
Step5.8NowyouneedtoconnectyourHTMLfiletotheCSS.Addthefollowingtexttoa_static_file.html(additionsareinred):<html>
<head>
<metacharset=“utf-8”>
<title>Staticpage</title>
<!—MobileSpecificMetas
––––––––––––––––——>
<metaname=“viewport”content=“width=device-width,initial-scale=1”>
<!—FONT
––––––––––––––––——>
<linkhref=”//fonts.googleapis.com/css?family=Raleway:400,300,600”rel=“stylesheet”
type=“text/css”>
<!—CSS
––––––––––––––––——>
<linkrel=“stylesheet”href=“css/normalize.css”>
<linkrel=“stylesheet”href=“css/skeleton.css”>
<!—Favicon
––––––––––––––––——>
<linkrel=“icon”type=“image/png”href=“images/favicon.png”>
</head>
<body>
<h1>Thisisanexampleofastaticpage</h1>
<p>Neat,isn’tit?</p>
</body>
</html>
ThenewcontentprovideslinkstothetwoCSSfiles,tothefaviconimage,andthethefonttypethatwewanttouseinourHTMLpage.
Step5.9AssumingyouhavesavedtheupdatedHTMLfile,refreshyourbrowser.Thisiswhatyoushouldseenow:
Muchnicerlookingtext!Thistemplatecontainsmanymoreusefulfeaturesthatwewillexplorelater.
Ok,wehaveastylestaticpage.HowaboutwecreatethesamestylingforourFlask“HelloWorld!”app?Let’sdothisinthenextsection.
CHAPTEREIGHTFlasktemplatesandCSSstyling
Ireallydon’tlikeworkingwithuglylookingwebpages,sobeforegoinganyfurtherI’dliketoapplytheSkeletonstyletothepagesthatareservedbyFlask.Atthemoment,there’sonlyone,implementedbythe“hello”methodinthehello.pyfile.
InFlask,youcanstylepagesintwosteps.First,youcreateatemplateforyourpageorpages(onetemplateperpage,oryoucansharetemplatesbetweenmultiplepages).Second,youlinkCSSfilesandimageswiththetemplates.
Let’strythisout.
Step6.1Gotoyourappdirectoryandcreateanewdirectory,called“templates”.>cd/var/www/lab_app
>mkdirtemplates
Step6.2Addsometexttothehello.pyprogram.Theaddedtextisinred:fromflaskimportFlask
fromflaskimportrender_template
app=Flask(__name__)
app.debug=True
@app.route(“/”)
defhello():
returnrender_template(‘hello.html’,message=“HelloWorld!”)
if__name__==“__main__”:
app.run(host=‘0.0.0.0’,port=8080)
First,weareimportingthepackagethathandlestemplates.Inthesecondredinsert,weareenablingtheFlaskverbosemodesothatFlaskproducesmoredebugoutputtohelpustroubleshootproblemslateron.
Then,inthethirdredline,weareconnectingatemplatecontainedinfile“hello.html”(whichwewillcreateinaminuteinsidethetemplatesdirectory),andpassingavariablenamed“message’withsometexttoit.
Step6.3Timetocreatetheactualtemplate.Changeinthetemplatesdirectory,andcreateanewfilenamed“hello.html”withthefollowingcode:<html>
<head>
<metacharset=“utf-8”>
<title>Staticpage</title>
<!—MobileSpecificMetas
––––––––––––––––——>
<metaname=“viewport”content=“width=device-width,initial-scale=1”>
<!—FONT
––––––––––––––––——>
<linkhref=”//fonts.googleapis.com/css?family=Raleway:400,300,600”rel=“stylesheet”
type=“text/css”>
<!—CSS
––––––––––––––––——>
<linkrel=“stylesheet”href=”/static/css/normalize.css”>
<linkrel=“stylesheet”href=”/static/css/skeleton.css”>
<!—Favicon
––––––––––––––––——>
<linkrel=“icon”type=”/static/image/png”href=“images/favicon.png”>
</head>
<body>
<h1>{{message}}</h1>
</body>
</html>
ThislookssimilartotheHTMLpageinthestaticdirectory.Itis.Theonlydifferenceisthat:
Ihavereplacedthecontentoftheh1tagwithavariableplaceholder,usingthecurlybrackets.InsidethecurlybracketsIhaveplacedthenameofthevariable(“message”),thatIhavepassedtoitfromthePythonhello.pyprogram.IhaveadjustedthepathoftheCSSandimagefilestomatchtheweblocationofthesefiles.
AtemplatelanguagecalledJinja2willprocessthecontentofthecurlybrackets,andinthisexamplewillreplaceitwiththeactualvalueofthevariable.
Step6.4BecausewehavemadechangestothePythonprogram,weneedtorestarttheapplicationserver,uWSGI.Dothatlikethis:>restartuwsgi
Step6.5Refreshyourbrowser:
Thereyougo,amuchnicerhelloworld!
Younowhaveatemplateconnectedtoanicestylesheet.
Ifyouranintoproblems,thingslikeInternalServererrorsandthelike,keepthesepointsinmind:
AquickwaytotroubleshootistorunyourFlaskapplicationdirectly,insteadviauWSGI.Whileattherootofyourapplication,use“pythonhello.py”tostartit.Youwillneedtopointyourbrowsertohttp://192.168.111.63:8080/,sincethisiswhereyourFlaskapplicationisserved(noticetheportID,inred).Ifthereisaproblemwithyourapplication,thecomprehensiveerrormessageswillhelpyoutofindouttheproblem.WheneveryoumakeachangetothePythonortemplatefiles,youmustrestarttheserverinorderforthechangestobecomeeffective.Dothiswith“restartuwsgi”ifyouareusinguWSGI,orbyhittingCtrl-Candrunning“pythonhello.py”ifyouareusingFlaskdirectly.
Awesome!Nextup,let’sgetthesensordataaccessiblethroughthewebbrowser!
CHAPTERNINEViewsensorreadingsviaawebbrowser
WealreadyknowhowtoworkwiththeDHTsensorusingastand-alonePythonscript.Inthissection,youwilllearnhowtoworkwiththissensorfromwithinaFlaskapplication.
Asyouwillsee,thisisrelativelysimple.YouhavetoinstalltheDHTlibraryinthePythonvirtualenvironment(whichiswhatyouhadtodowiththestand-alonePythonscriptanyway),getreadingsfromthesensorandpassthemtothetemplatefile.
Step7.1First,let’sinstalltheDHTsensorPythonlibrarytothePythonvirtualenvironmentthatweuseforourFlaskapplication.
Iassumethatyouareloggedin.
Awhileago,wedownloadedtheAdafruitDHTlibrary,andsaveditin/var/working/Adafruit_Python_DHT.Wearegoingtousethatpackageagain,butthistimewe’llinstallitinthe/var/www/lab_app/venvvirtualenvironment.>cd/var/working/Adafruit_Python_DHT
>/var/www/lab_app/venv/bin/pythonsetup.pyinstall
Withthesecondcommand,weareusingthePythoninterpreterinourtargetvirtualenvironmenttoexecutethesetupscript.Asaresult,thelibraryisinstalledinthetargetvirtualenvironment.
Step7.2SwitchbacktoourFlaskapplicationdirectory.We’llreplacethehello.pyscriptwithanewone.Thenewscript,calledlab_app.py,willcontainasinglemethod.ThismethodwillgetreadingsfromthesensorandreturnaHTMLpagetotheuser.>cd/var/www/lab_app
>vimlab_app.py
Step7.3Copythefollowingscriptintheeditor(filenameonGithub:lab_app_v1.py):fromflaskimportFlask,request,render_template
app=Flask(__name__)
app.debug=True#MakethisFalseifyouarenolongerdebugging
@app.route(“/”)
defhello():
return“HelloWorld!”
@app.route(“/lab_temp”)
deflab_temp():
importsys
importAdafruit_DHT
humidity,temperature=Adafruit_DHT.read_retry(Adafruit_DHT.AM2302,17)
ifhumidityisnotNoneandtemperatureisnotNone:
returnrender_template(“lab_temp.html”,temp=temperature,hum=humidity)
else:
returnrender_template(“no_sensor.html”)
if__name__==“__main__”:
app.run(host=‘0.0.0.0’,port=8080)
Thewebrootofourwebapplicationstillreturnsthe“HelloWorld”message.Ihaveaddedanewmethod,“lab_temp”,whichisrevokedwhenthebrowsermakesagetrequesttothelab_tempresource.So,thismethodwillrespondtothisaddressinyourbrowser:http://192.168.111.63/lab_temp
(ofproject,theexactIPaddresscouldbedifferentforyourRPi).
Insidethismethod,notethatweareimportingthenecessarylibraries,andgetthevaluesfromthesensor.Ifvaluesareretrievedsuccessfully,theyarepassedtothelab_temp.htmltemplate,butifnotthescriptwillreturntheno_sensor.htmltemplate.
Step7.4Gointhetemplatesdirectoryandcreatethefirsttemplate,lab_temp.html.Itshouldcontainthiscode(onGithub,thisfileisnamed“lab_temp_v1.html”):<html>
<metacharset=“utf-8”>
<title>LabConditionsbyRPi</title>
<metahttp-equiv=“refresh”content=“10”>
<metaname=“description”content=“Labconditions-RPi”>
<metaname=“author”content=“PeterDalmaris”>
<!—MobileSpecificMetas
––––––––––––––––——>
<metaname=“viewport”content=“width=device-width,initial-scale=1”>
<!—FONT
––––––––––––––––——>
<linkhref=”//fonts.googleapis.com/css?family=Raleway:400,300,600”rel=“stylesheet”
type=“text/css”>
<!—CSS
––––––––––––––––——>
<linkrel=“stylesheet”href=”/static/css/normalize.css”>
<linkrel=“stylesheet”href=”/static/css/skeleton.css”>
<!—Favicon
––––––––––––––––——>
<linkrel=“icon”type=“image/png”href=”/static/images/favicon.png”>
</head>
<body>
<divclass=“container”>
<divclass=“row”>
<divclass=“two-thirdcolumn”style=“margin-top:5%”>
<h2>Realtimelabconditions</h2>
<h1>Temperature:{{“{0:0.1f}”.format(temp)}}°C</h1>
<h1>Humidity:{{“{0:0.1f}”.format(hum)}}%</h1>
<p>Thispagerefreshesevery10seconds</p>
</div>
</div>
</div>
</body>
</html>
Inred,ImarkthatlocationswherethevariablesthatarepassedbythePythonscriptareinterpretedandreplacedwiththeiractualvalues.TherestofthisHTMLcodeisalmostidenticaltothecodefromthehello.pytemplatewesawearlier.
Atthetopofthepage,Ihaveaddedametatagthatinstructsthebrowsertorefreshthepageevery10seconds.Thisisprobablytheeasiestwaytocauseapagetoself-refresh,thereforekeepingthereadingscurrentonourscreen.
Step7.5Stillinthetemplatesdirectory,createtheno_sensor.htmlfilewiththiscontent(onGithub,thisfileisnamed“no_sensor_v1.html”):<html>
<head>
<linkrel=“stylesheet”type=“text/css”href=”/static/style/eureka_style.css”>
</head>
<divclass=‘test_container’>
<h1>Sorry,can’taccessthesensor!</h1>
</div>
</html>
Step7.6WehavetoupdatetheuWSGIconfigurationfiletopointtothenewPythonapplicationfile,lab_app.py(onGithub,thisfileisnamed“lab_app_uwsgi_v2.ini”).
Openitandmaketheupdate(updatedtextinred):>vimlab_app_uwsgi.ini
Content:[uwsgi]
#application’sbasefolder
base=/var/www/lab_app
#pythonmoduletoimport
app=lab_app
module=%(app)
home=%(base)/venv
pythonpath=%(base)
#socketfile’slocation
socket=/var/www/lab_app/%n.sock
#permissionsforthesocketfile
chmod-socket=666
#thevariablethatholdsaflaskapplicationinsidethemoduleimportedatline#6
callable=app
#locationoflogfiles
logto=/var/log/uwsgi/%n.log
Step7.7RestartuWSGItomakethechangeeffective:>restartuwsgi
Step7.8GotoyourbrowserandtypeinthisURL:http://192.168.111.63/lab_temp
Youshouldseethisresult:
Nice,isn’tit?
There’snotimeforrestforthewicked,sowhatarewegoingtoworkonnext?
Howaboutinstallingadatabasesothatwecanstoreourreadingsinit?Wecanuseadatabasetologdata,thenretrieveitandcreatetablesandgraphs.That’scomingupnext!
CHAPTERTENSetupasimpledatabaseanduseittologsensordataInthissection,wewillinstallasimpledatabaseanduseittostoresensorreadings.ThedatabasewewilluseisSQlite,averypopularopen-sourcerelationaldatabasethatwasdevelopedforuseinembeddeddevices.MostsmartphoneapplicationsuseSQliteforatleastpartoftheirdatamanagementrequirements.OntheRPi,althoughyoucaninstallamore“heavyduty”databaselikeMySQLorPostgresql,forourrequirementsofalowtrafficdatabasedrivenapplication,SQLiteisjustperfect.
Inthislecture,IwillshowyouhowtoinstallSQLite3,andinteractwithitonthecommandline.
Later,wearegoingtocreateaPythonscriptthattakesareadingfromthesensorandstoresthevaluesinthedatabase.Wewillalsocreateascheduler(“Cron”)taskthatwillinvokethescriptevery10minutesautomatically.Thisway,ourRPiwilltaketemperatureandhumiditymeasurementsevery10minutes,forever(oratleastuntilitrunsoutofdiskspaceorweturnoffpowerorwestoptheCrontask).
Step8.1AssumingyouareloggedontoyourRPi,installSQLite:>apt-getinstallsqlite3
Step8.2Testitonthecommandline.From/var/www/lab_app:>cd/var/www/lab_app
>sqlite3lab_app.db
sqlite>.help
ThefirstlinestartstheSQLitecommandlineinterfacewiththenameofthedatabasewewanttouse.Thisdatabasefileisemptyatthemoment,butwewilladdacoupleoftablesandrecordsinit.
Thesecondlinewillcausealistwiththeavailablecommandswillappear.InSQLite,youcaninputmulti-linestatementsbystartingwiththeBEGIN;commandandendingwiththeCOMMIT;command.Everycommandshouldendwithasemicolon(“;”).
Step8.3Let’screatethedatabasetablewherewewillstorethetemperaturedata,andmanuallycreateacoupleofrecordsinit(continuingfrom2,stillontheSQLitecommandprompt):sqlite>begin;
sqlite>createtabletemperatures(rDatetimedatetime,sensorIDtext,tempnumeric);
sqlite>insertintotemperaturesvalues(datetime(‘now’),“1”,25);
sqlite>insertintotemperaturesvalues(datetime(‘now’),“1”,25.10);
sqlite>commit;
Step8.4Let’sretrievethetworecordswejustcreated:sqlite>select*fromtemperatures;
2015-04-0704:47:00|1|25
2015-04-0704:47:25|1|25.1
Step8.5Dothesameforthehumiditytable:sqlite>begin;
sqlite>createtablehumidities(rDatetimedatetime,sensorIDtext,tempnumeric);
sqlite>insertintohumiditiesvalues(datetime(‘now’),“1”,51);
sqlite>insertintohumiditiesvalues(datetime(‘now’),“1”,51.10);
sqlite>commit;
Step8.6Youcanusethe“.tables”commandtoverifythatyouhavecreatedtwotables:sqlite>.tables
humiditiestemperatures
Notethatthesetablesarenotyetstoredonthedisk,onlyinabuffer.Youcanalsouse“.schema”soseethepropertiesofatable:sqlite>.schematemperatures
CREATETABLEtemperatures(rDatetimedatetime,sensorIDtext,tempnumeric);
Step8.7Exitandcheckthatthenewdatabasefileexistsinyour/var/www/lab_appdirectory:sqlite>.exit
>ls
hello.pylab_app.dblab_app.pylab_app_uwsgi.inistaticvenv
hello.pyclab_app_nginx.conflab_app.pyclab_app_uwsgi.socktemplates
InredImarkthenewdatabasefile.
Step8.8Let’suseaPythonscripttogetareadingfromthesensorandstorethevaluesinanewdatabaserecord.
Createanewfilecalledenv_log.pywiththiscontent:#!/usr/bin/envpython
importsqlite3
importsys
importAdafruit_DHT
deflog_values(sensor_id,temp,hum):
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)#Itisimportanttoprovidean
#absolutepathtothedatabase
#file,otherwiseCronwon’tbe
#abletofindit!
curs=conn.cursor()
curs.execute(“““INSERTINTOtemperaturesvalues(datetime(‘now’),
(?),(?))”””,(sensor_id,temp))
curs.execute(“““INSERTINTOhumiditiesvalues(datetime(‘now’),
(?),(?))”””,(sensor_id,hum))
conn.commit()
conn.close()
humidity,temperature=Adafruit_DHT.read_retry(Adafruit_DHT.AM2302,17)
ifhumidityisnotNoneandtemperatureisnotNone:
log_values(“1”,temperature,humidity)
else:
log_values(“1”,-999,-999)
Thedatabase-relatedcodeismarkedinred.Thisscriptcontainsamethod,log_values,thatmanagestheprocessofcreatingnewrecordsinthedatabasewiththevaluespassedtoit.
Step8.9Runthenewscripttomakesureitworks.Let’sactivatethePythoninterpreterforourvirtualenvironmentfirst.Alternatively,youcancallourapplication’sPythoninterpreterbytypinginthecompletepathtoitsexecutable.I’lldotheactivationsinceitislesstyping:>.venv/bin/activate
>pythonenv_log.py
Step8.10UsetheSqlitecommandlinetooltoverifythatwehaveanewrecordinourdatabase.>sqlite3lab_app.db
SQLiteversion3.7.132012-06-1102:05:22
Enter“.help”forinstructions
EnterSQLstatementsterminatedwitha“;”
sqlite>select*fromtemperatures;
2015-04-0705:13:20|1|24.2999992370605
sqlite>select*fromhumidities;
2015-04-0705:13:20|1|48.7000007629395
sqlite>.exit
Youmayhaveadditionalrecordsinyourdatabase,buttherecordsweareinterestedinherearetheonesjustinsertedbythescriptweexecutedinthepreviousstep.
Allgood,ourPythonscriptcanretrievereadingsfromthesensorandrecordtheminthedatabase.
Step8.11Let’sscheduleameasurementeverytenminutes.WewillusetheLinuxschedulerforthat,Cron.Cronisverysimple.Youuseatexteditortoeditaspecialcronfile.
Foreachtask,youcreatealineoftextthatcontainsthescheduleandthecommandtobeexecuted.TheyimportantthingtorememberistheCrontasksareexecutedwithoutashell,whichmeansthatenvironmentalvariablesarenotretrieved.
Inotherwords,youneedtoalwaysusefullandabsolutepathsforeveryfileyoureferenceinaCronschedule.
StarttheCroneditorlikethis:>crontab-e
Thiswillstartvim.Gotothebottomofthefile(youcanusethe“G”shortcuttogettherefast),gointoinsertmode(“i”)andcopythisscheduleatthatlocation:*/10****/var/www/lab_app/venv/bin/python/var/www/lab_app/env_log.py
The*/10parametermeans“every10minutes”.The“****”means“everyhour,every,minute,everyday,everydayoftheweek”.
NoticethatthepathsarefulltomakesurethattheCrondaemonwillbeabletofindthefilesweneedittoexecute.
Step8.12Tenminuteslater,gobackintotheSQlitecommandlineinterfacetoseeifwehaveanewrecord:sqlite>select*fromtemperatures;
2015-04-0705:13:20|1|24.2999992370605
2015-04-0706:00:06|1|24.3999996185303
sqlite>select*fromhumidities;
2015-04-0705:13:20|1|48.7000007629395
2015-04-0706:00:06|1|47.7999992370606
(Newerrecordsinred)
OneproblemwithCronisthatfailuresaresilent.Ifthereisaproblemwithyourscheduleorwithyourscript,andthetaskfails,wewouldnotknowwhy.
Wouldcanonlyknowthatitdidn’tworkbecause,inourpresentinstance,thereisnoadditionalrecordinthedatabase.Ifthishappenstoyou,doubleandtriplecheckyourscriptandCrontask.
Step8.13HavingtousetheSQLitecommandlineinterfacetogetthelatestvaluesisnotveryconvenient.Let’sgetourwebapplicationtocreateanewpagewithalistthatcontainstherecordsforthetemperatureandhumiditytables.
First,we’lladdanewmethodinthelab_app.pyfilewhichwillrespondtowebrequestsat/lab_env_db.Openlab_app.pyinvimforediting,andmakeitlikethis(additionsinred-thisisfilelab_app_v2.pyonGithub):fromflaskimportFlask,request,render_template
app=Flask(__name__)
app.debug=True#MakethisFalseifyouarenolongerdebugging
@app.route(“/”)
defhello():
return“HelloWorld!”
@app.route(“/lab_temp”)
deflab_temp():
importsys
importAdafruit_DHT
humidity,temperature=Adafruit_DHT.read_retry(Adafruit_DHT.AM2302,4)
ifhumidityisnotNoneandtemperatureisnotNone:
returnrender_template(“lab_temp.html”,temp=temperature,hum=humidity)
else:
returnrender_template(“no_sensor.html”)
@app.route(“/lab_env_db”)
deflab_env_db():
importsqlite3
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)
curs=conn.cursor()
curs.execute(“SELECT*FROMtemperatures”)
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumidities”)
humidities=curs.fetchall()
conn.close()
returnrender_template(“lab_env_db.html”,temp=temperatures,hum=humidities)
if__name__==“__main__”:
app.run(host=‘0.0.0.0’,port=8080)
ThenewcodesubmitstwoSelectstatementstothedatabase.Thedatabasereturnstocollections,oneforthetemperatureandoneforthehumiditytables.Thecodethencallsthelab_env_db.htmltemplateandpassesthetwocollectionstoit.
Step8.14Let’snowcreatethenewtemplate.Usevimtocreateanewfilecalledlab_env_db.htmlandcopythiscodeinit:<!DOCTYPEhtml>
<htmllang=“en”>
<head>
<!—BasicPageNeeds
––––––––––––––––——>
<metacharset=“utf-8”>
<title>LabConditionsbyRPi</title>
<metaname=“description”content=“Labconditions-RPi”>
<metaname=“author”content=“PeterDalmaris”>
<!—MobileSpecificMetas
––––––––––––––––——>
<metaname=“viewport”content=“width=device-width,initial-scale=1”>
<!—FONT
––––––––––––––––——>
<linkhref=”//fonts.googleapis.com/css?family=Raleway:400,300,600”rel=“stylesheet”
type=“text/css”>
<!—CSS
––––––––––––––––——>
<linkrel=“stylesheet”href=”/static/css/normalize.css”>
<linkrel=“stylesheet”href=”/static/css/skeleton.css”>
<!—Favicon
––––––––––––––––——>
<linkrel=“icon”type=“image/png”href=”/static/images/favicon.png”>
</head>
<body>
<divclass=“container”>
<divclass=“row”>
<divclass=“one-thirdcolumn”style=“margin-top:5%”>
<strong>Showingallrecords</strong>
<h2>Temperatures</h2>
<tableclass=“u-full-width”>
<thead>
<tr>
<th>Date</th>
<th>°C</th>
</tr>
</thead>
<tbody>
{%forrowintemp%}
<tr>
<td>{{row[0]}}</td>
<td>{{‘%0.2f’|format(row[1])}}</td>
</tr>
{%endfor%}
</tbody>
</table>
<h2>Humidities</h2>
<tableclass=“u-full-width”>
<thead>
<tr>
<th>Date</th>
<th>%</th>
</tr>
</thead>
<tbody>
{%forrowinhum%}
<tr>
<td>{{row[0]}}</td>
<td>{{‘%0.2f’|format(row[1])}}</td>
</tr>
{%endfor%}
</tbody>
</table>
</div>
</div>
</body>
</html>
Thelineswherethetemplateengineiteratesthroughthecollectionobjectsaremarkedinred.
Forexample,thetemperaturecollectionobjectisnamedtemp.Theiteratorwilltakeonerecordatatime,called“row”.Thisrecordisactuallyanormalarray.Wecanpickvaluesoutofthearraybyusingtheindexnumbers.Forexample,“row[1]”willretrievethevaluefromthecellwithindex1,whichisactuallythesecondcell(arraysinPythonarezero-indexed,whichmeansthatthefirstcellisindex0,thesecondisindex1andsoon).
Step8.15RestartuWSGItomakethechangeseffectiveandloadthenewpageinyourbrowser:>restartuwsgi
Youshouldseesomethinglikethiswhenrequestinghttp://192.168.111.63:8080/lab_env_db:
CHAPTERELEVENSelectrecordstoviewbytimeanddate
IfyouleaveyourRPialoneforawhile,itwillcontinuetocapturesensordataandstoringitinthedatabase.Afteradayorso,youwillhave6*24=124recordsfortemperatureandanother124forhumidity.Aweeklater,youwillhave124*7=1008ofeach,andsoon.
Displayingallthatdatainasinglepagewillproduceverylongpages,andwillnotbeveryuseful.ItwouldbereallynicetobeabletogetourRPitoretrieveanddisplayrecordswithinatimeframethatwecanspecify.
Inthislecture,IwillshowyouhowtomakethispossibleintheeasiestwayIcanthinkof.WewillupdateourapplicationsothatyoucansettheURLintheURLofyourbrowser’srequest.Itwilllooklikethis:http://192.168.111.63/lab_env_db?from=2015-04-13&to=2015-04-14.
Step9.1Let’sworkonthedatabasefirst.WewanttodesignanSQLquerythatwillretrieverecordsbeforeandafteradate/timewespecify.Todothis,wewilluseanSQLquerylikethis:SELECT*FROMhumiditiesWHERErDatetimeBETWEEN“2015-04-12”AND“2015-04-13”
Step9.2Let’strythisstatementintheSQLitedatabaseandseeifitworks.LogontoyourRPiifnotalreadyloggedon,andnavigateintoyourapplication’sworkingdirectory:>cd/var/www/lab_app/
>ls-al
Noticethesizeofthedatabasefile:>-rw-r—r—1rootroot76800Apr1403:10lab_app.db
Over76,000bytes!Ithasbeengrowingoverthelastweek.
Step9.3StarttheSQLiteconsole,withthedatabasefileasaparameter:sqlite3lab_app.db
SQLiteversion3.7.132012-06-1102:05:22
Enter“.help”forinstructions
EnterSQLstatementsterminatedwitha“;”
sqlite>
Step9.4TryoutthesampleSQLqueryfromabove:sqlite>SELECT*FROMhumiditiesWHERErDatetimeBETWEEN“2015-04-12”AND“2015-04-13”;
2015-04-1200:00:02|1|56.9000015258789
2015-04-1200:10:01|1|56.7000007629395
2015-04-1200:20:02|1|56.9000015258789
2015-04-1200:30:02|1|56.7000007629395
2015-04-1200:40:02|1|56.7000007629395
2015-04-1200:50:01|1|56.2999992370606
2015-04-1201:00:02|1|57
2015-04-1201:10:02|1|57.0999984741211
…(continues)
Thedatabasereturnsrecordsinsertedduringthedatesspecified.
Step9.5Wecanbemorespecificandrequestrecordsrecordedwithminute-levelaccuracy.Forexample:sqlite>SELECT*FROMhumiditiesWHERErDatetimeBETWEEN“2015-04-1218:00:00”AND“2015-04-12
19:00:00”;
2015-04-1218:00:01|1|51.2999992370606
2015-04-1218:10:02|1|51.2000007629395
2015-04-1218:20:02|1|51.2999992370606
2015-04-1218:30:02|1|51.2000007629395
2015-04-1218:40:01|1|51.2999992370606
2015-04-1218:50:02|1|51.2999992370606
ThisstatementreturnedrecordscreatedonApril12,2015,between6pmand7pm.
YoucanexittheSQLiteconsole:sqlite>.exit
Step9.6Ok,wehaveawaytogettherecordsweneedoutofthedatabase.Wenowneedawaytogetthedate/timesintotheURL,alongsideourbrowserGETrequests,andintothePythonscript.Andwewanttodothisinthelab_env_dbmethodsincethatmethodisalreadysetuptoretrievedatafromthedatabase.
Weshouldbeabletogetallthisdonewithminimalfuss.
Openuplab_app.py.Inthetopofthefile,addthefollowingimportstatementssothatitlookslikethis(thisislab_app_v3.pyonGithub):fromflaskimportFlask,request,render_template
importtime
importdatetime
Wewillbeworkingwithtimeanddates,soweareimportingthetimeanddatetimepackages.
Next,wewillworkonlyinmethodlab_env_db(),soIamonlyshowingthatpartofthecodehere.Editthatmethodsothatitlookslikethis(thisisalsoinlab_app_v3.pyonGithub):@app.route(“/lab_env_db”,methods=[‘GET’])
deflab_env_db():
from_date_str=request.args.get(‘from’,time.strftime(“%Y-%m-%d%H:%M”))#Getthefromdate
valuefromtheURL
to_date_str=request.args.get(‘to’,time.strftime(“%Y-%m-%d%H:%M”))#Getthetodatevalue
fromtheURL
importsqlite3
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)
curs=conn.cursor()
#curs.execute(“SELECT*FROMtemperatures”)
#temperatures=curs.fetchall()
#curs.execute(“SELECT*FROMhumidities”)
#humidities=curs.fetchall()
#conn.close()
curs.execute(“SELECT*FROMtemperaturesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumiditiesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
humidities=curs.fetchall()
conn.close()
returnrender_template(“lab_env_db.html”,temp=temperatures,hum=humidities)
Ihavecommentedouttheoldlinesthatrelatetothedatabasesothatyoucaneasilyseewhathaschanged.Asfarasthedatabaseisconcerned,weareconstructingtwonewSQLqueries,oneforthehumiditiestableandoneforthetemperatures,sothattheymatchthequerythatwetestedearlier.
Inordertogetthedate/timerangefromtheURL,intheveryfirstlineofthiscodeIamincludingthe“methods”parameter.Withit,IamtellingPythonthatthismethodcanreceiveGETparametersaspartoftheHTTPrequest.AURLcancontainkey-valuepairsafterthe“?”delimiter,andsuchrequestsarecalled“GET”requests.
Inthe3rdand4thlines,Iusethe“request.args.get”methodtoretrievethevalueswiththe“from”and“to”keysfromtheURL.Iamalsoprovidingdefaultvaluesincasethosearenotprovided.Thisisdonebyusingthepython“time”method,andformattingthevaluethatthismethodintheformatthatthedatabasecanaccept.
Step9.7Let’stryoutthenewcode.Saveandcloseyoureditor,restartuWSGI:>restartuwsgi
…andtypethisURLinyourbrowser:http://192.168.111.63/lab_env_db?from=2015-04-13&to=2015-04-14
Yourbrowserwilldisplaysomethinglikethis:
ItwillcontainrecordsfromApril142015only.Youcanalsotrytimes:http://192.168.111.63/lab_env_db?from=2015-04-12+13%3A25&to=2015-04-12+15%3A00
…andyouwillgetsomethinglikethis:
…whereyouonlyhaverecordscreatedonApril14,2015,between13:30and15:00.NoticethattheURLisHTTPencoded,whichmeansthatthewhitespacesandotherspecialcharactershavebeenreplacedbycode.
Youcanuseautilitylikethis:http://www.url-encode-decode.comtoencodeyourURLs.Forexample,this:2015-04-1213:25willbeconvertedtothis:2015-04-12+13%3A25sothatitcanbetransmittedcorrectlyviaHTTP.
Yourbrowserdoesallthisautomatically,andatthemomentwearejusttestingfunctionality.Soonwewilladduserinterfaceelementstomaketheprocessofcreatingtimeanddaterangesmucheasier.
Anotherthingtoconsideristhetimezoneinwhichyouarelocated.Bydefault,theoperatingsystemissettoUTC,orCoordinatedUniversalTime.Toseewhatyourtimesettingsaretrythe“date”command:>date
TueApr1404:48:56BST2015
Apparently,althoughIliveinSydney,Australia,thedefaultsettingformybrand-newRasbianinstallationisBST(BritishSummerTime).
Don’tworryaboutthetimezoneissuesatthemoment,wewillspendtimeadjustingourapplicationforthatlater.
Step9.8IfthetimeanddatestringsthatwetypedintheURLarenotcorrect(example:http://192.168.111.63/lab_env_db?from=2015-04-13&to=2015—14),theSQLquerywillnotreturnanresults.Itwilllooklikethis:
Ifyouwouldratherhaveitretrieveapresetdaterange,thenweneedtoaddsomevalidationtothelab_env_dbmethod.
Goinlab_env_dbandeditthemethodtolooklikethis(thisislab_app_v4.pyonGithub):@app.route(“/lab_env_db”,methods=[‘GET’])
deflab_env_db():
importdatetime
from_date_str=request.args.get(‘from’,time.strftime(“%Y-%m-%d00:00”))#Getthefromdate
valuefromtheURL
to_date_str=request.args.get(‘to’,time.strftime(“%Y-%m-%d%H:%M”))#Getthetodatevalue
fromtheURL
ifnotvalidate_date(from_date_str):#ValidatedatebeforesendingittotheDB
from_date_str=time.strftime(“%Y-%m-%d00:00”)
ifnotvalidate_date(to_date_str):
to_date_str=time.strftime(“%Y-%m-%d%H:%M”)#ValidatedatebeforesendingittotheDB
importsqlite3
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)
curs=conn.cursor()
curs.execute(“SELECT*FROMtemperaturesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumiditiesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
humidities=curs.fetchall()
conn.close()
returnrender_template(“lab_env_db.html”,temp=temperatures,hum=humidities)
defvalidate_date(d):
try:
datetime.datetime.strptime(d,‘%Y-%m-%d%H:%M’)
returnTrue
exceptValueError:
returnFalse
Ihavehighlightedthecodethatdoesthedatevalidationinbold.Thereisanewmethod,called“validate_date”,thatacceptsastring.Itwilltrytomatchthisstringagainstatextpattern,andifitismatchedthemethodwillreturntrue,otherwiseitwillreturnfalse.The“try-except”blockistheretocatchanexception,whichisaconditionthathappenswith,inthiscase,itisnotpossibletomatchthedatestringwiththepatternwehaveprovidedtothestrptimemethod.
RestartuWSGI,andrefreshyourbrowser(whichstillcontainstheincorrectURLfromthepreviousstep,http://192.168.111.63/lab_env_db?from=2015-04-13&to=2015—14).Thisiswhatyoushouldbeseeingnow:
Step9.9Beforegoinganyfurther,let’stidyup.Thelab_env_dbmethodhasgrownabit,anditwillcontinuetogrow.Itisbettertosimplifyitatthispointbeforeweaddmorecodetoit.
AbigpartofwhatishappeninginsidethismethodisthedecodingoftheURLquerystring.Let’ssplitthatpartintoitsownmethod.Here’sistheendresult:@app.route(“/lab_env_db”,methods=[‘GET’])
deflab_env_db():
temperatures,humidities,from_date_str,to_date_str=get_records()
returnrender_template(“lab_env_db.html”,temp=temperatures,hum=humidities)
defget_records():
from_date_str=request.args.get(‘from’,time.strftime(“%Y-%m-%d00:00”))#Getthefromdate
valuefromtheURL
to_date_str=request.args.get(‘to’,time.strftime(“%Y-%m-%d%H:%M”))#Getthetodatevalue
fromtheURL
ifnotvalidate_date(from_date_str):#ValidatedatebeforesendingittotheDB
from_date_str=time.strftime(“%Y-%m-%d00:00”)
ifnotvalidate_date(to_date_str):
to_date_str=time.strftime(“%Y-%m-%d%H:%M”)#ValidatedatebeforesendingittotheDB
importsqlite3
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)
curs=conn.cursor()
curs.execute(“SELECT*FROMtemperaturesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumiditiesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
humidities=curs.fetchall()
conn.close()
return[temperatures,humidities,from_date_str,to_date_str]
Thenewmethod,get_records,inheritsthecodethatdidthedecodingoftheURLandthatworkswiththedatabase.Itreturnsthetwocollectionsandthedatestringsinanarray,sothatwecandomultipleassignmentinthefirstlineofthelab_env_dbmethodtolocalvariable.
Savetheupdatedscript,restartuWSGIandrefreshyourwebpage.Itshouldreloadasnothinghaschanged!
Step9.10Whatwehavenowisnice,butnotuserfriendly.Let’saddsomeUIelementssothatwecanjustclickonabuttonandretrieverecordsfromapredefinedtimerange.Forexample,wecancreatebuttonsthatretrieverecordsfromthepast3,6,12or24hourswithasingleclick.
Todothis,wewillneedtoworkwiththeHTMLtemplateandthePythonscript.Let’sstartwiththeHTMLtemplate.Openlab_env_db.htmlinyoureditor(thefileislocatedinsidethetemplatesdirectory),andupdatethecontentswiththis(thisisnamedlab_env_db_v2.htmlinGithub):<!DOCTYPEhtml>
<htmllang=“en”>
<head>
<!—BasicPageNeeds
––––––––––––––––——>
<metacharset=“utf-8”>
<title>LabConditionsbyRPi</title>
<metaname=“description”content=“Labconditions-RPi”>
<metaname=“author”content=“PeterDalmaris”>
<!—MobileSpecificMetas
––––––––––––––––——>
<metaname=“viewport”content=“width=device-width,initial-scale=1”>
<!—FONT
––––––––––––––––——>
<linkhref=”//fonts.googleapis.com/css?family=Raleway:400,300,600”rel=“stylesheet”
type=“text/css”>
<!—CSS
––––––––––––––––——>
<linkrel=“stylesheet”href=”/static/css/normalize.css”>
<linkrel=“stylesheet”href=”/static/css/skeleton.css”>
<!—Favicon
––––––––––––––––——>
<linkrel=“icon”type=“image/png”href=”/static/images/favicon.png”>
</head>
<body>
<divclass=“container”>
<divclass=“row”>
<divclass=“elevencolumns”>
<formid=“range_select”action=“/lab_env_db”method=“GET”>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“3”id=“radio_3”/><label
for=“radio_3”>3hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“6”id=“radio_6”/><label
for=“radio_6”>6hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“12”id=“radio_12”/><label
for=“radio_12”>12hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“24”id=“radio_24”/><label
for=“radio_24”>24hrs</label>
</div>
</form>
</div>
</div>
<divclass=“row”>
<divclass=“one-thirdcolumn”style=“margin-top:5%”>
<strong>Showingallrecords</strong>
<h2>Temperatures</h2>
<tableclass=“u-full-width”>
<thead>
<tr>
<th>Date</th>
<th>°C</th>
</tr>
</thead>
<tbody>
{%forrowintemp%}
<tr>
<td>{{row[0]}}</td>
<td>{{‘%0.2f’|format(row[2])}}</td>
</tr>
{%endfor%}
</tbody>
</table>
<h2>Humidities</h2>
<tableclass=“u-full-width”>
<thead>
<tr>
<th>Date</th>
<th>%</th>
</tr>
</thead>
<tbody>
{%forrowinhum%}
<tr>
<td>{{row[0]}}</td>
<td>{{‘%0.2f’|format(row[2])}}</td>
</tr>
{%endfor%}
</tbody>
</table>
</div>
</div>
</body>
<scriptsrc=”//code.jquery.com/jquery-1.11.2.min.js”></script>
<scriptsrc=”//code.jquery.com/jquery-migrate-1.2.1.min.js”></script>
<script>
jQuery(“#range_selectinput[type=radio]”).click(function(){
jQuery(“#range_select”).submit();
});
</script>
</html>
Thenewcodeishighlightedinboldletters.Thefirstblockisaformthatcontainsalistofradiobuttons.Eachbuttonrepresentsablockoftimethattheusercanselect.
Sincewewanttheuseronsimplyclickononeofthebuttonsandhavetheformsubmitted,IhaveinsertedsomeJavascriptcode,usingjQuery,tomakethiseasy.ThesecondhighlightedblockloadsthejQuerylibraryfromthejquery.comrepositoryandthensubmitstheformwhenoneoftheradiobuttonsisclicked.
Updatethecode,savethefileandreloadthepageinyourwebbrowser.Youshouldseethis:
Noticethe4buttonsatthetopofthepage.Ifyouclickanyofthem,thepagewillbehaveasifitisreloading.WhathappenedisthatthejQuerystatementatthebottomoftheHTMLcodedetectedtheclickandsubmittedtheform.
HavealookattheURL.Youwillseesomethinglikethis:http://192.168.111.63/lab_env_db?range_h=6
Noticetherange_hvalue?Inmycaseitcarriesthevalue“6”becauseIclickontheradiobutton“6hrs”.InowneedtoupdatemycodeinthePythonfilesothatitcanreadthisvalueandcalculatethestartandenddate/timesforthistimerange.
Step9.11Openlab_app.pyinyoureditor.Updatetheget_recordsmethodwiththiscode:defget_records():
from_date_str=request.args.get(‘from’,time.strftime(“%Y-%m-%d00:00”))#Getthefromdate
valuefromtheURL
to_date_str=request.args.get(‘to’,time.strftime(“%Y-%m-%d%H:%M”))#Getthetodatevalue
fromtheURL
range_h_form=request.args.get(‘range_h’,”);#Thiswillreturnastring,iffieldrange_h
existsintherequest
range_h_int=“nan”#initialisethisvariablewithnotanumber
try:
range_h_int=int(range_h_form)
except:
print“range_h_formnotanumber”
ifnotvalidate_date(from_date_str):#ValidatedatebeforesendingittotheDB
from_date_str=time.strftime(“%Y-%m-%d00:00”)
ifnotvalidate_date(to_date_str):
to_date_str=time.strftime(“%Y-%m-%d%H:%M”)#ValidatedatebeforesendingittotheDB
#Ifrange_hisdefined,wedon’tneedthefromandtotimes
ifisinstance(range_h_int,int):
time_now=datetime.datetime.now()
time_from=time_now-datetime.timedelta(hours=range_h_int)
time_to=time_now
from_date_str=time_from.strftime(“%Y-%m-%d%H:%M”)
to_date_str=time_to.strftime(“%Y-%m-%d%H:%M”)
importsqlite3
conn=sqlite3.connect(‘/var/www/lab_app/lab_app.db’)
curs=conn.cursor()
curs.execute(“SELECT*FROMtemperaturesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumiditiesWHERErDateTimeBETWEEN?AND?”,(from_date_str,
to_date_str))
humidities=curs.fetchall()
conn.close()
return[temperatures,humidities,from_date_str,to_date_str]
Thenewcode(inboldletters)retrievesthehoursrangefromtheURL.Becauseitistransmittedtotheserverasacharacter,wefirstconvertittoaninteger.Then,fromthisintegerwecalculatethedaterangevariables,andfinallywepassthemtotheSQLquery.
Tryout.SavethefileandrestartuWSGI.Thenreloadthepage,andclickonthe3hrsbutton.Youwillgetrecordsthatwerecreatedinthelastthreehours:
Wewillbedoingmoreworkwiththeuserinterfacelater.Fornow,wehaveawaytorecordsensorreadingsevery10minutes,andretrievethosereadingsusingasimpleradio-buttonbaseddate/timerangemechanism.
Beforeaddingmoreformwidgets,let’saddtheabilitytovisualiseourdata.Wewilldothisinthenextsection.
CHAPTERTWELVEAddVisualisations
Ithinkitwouldbenicetoconvertoursensordataintogood-lookingcharts.GoogleoffersanAPIforcreatingcharts,calledGoogleChartAPI.HereIwillshowyouhowtouseittoconvertthedatafromyoursensorintoachartthatisembeddedinyourexistingpage.
TheGoogleChartAPIisextensive.Youcancreateamazingvisualisationswithit.Allthedetailsaredocumentedhere:https://developers.google.com/chart/.
Fornow,I’dliketoshowyouhowtocreatesomethinglikethis:
Tomakeitpossible,wewillworkexclusivelyontheclientside,theHTMLpage.
Step10.1Let’sstartbyaddingHTMLcodetocreateaplaceholderforthechartsinthepage.Wewillcreateoneplaceholderforthetemperaturechart,andoneforthehumidity.Bothwillbeplacedattherightsideofthepage,likeintheexamplescreenshotabove.
Openlab_env_db.htmlinyoureditor(insidethetemplatesdirectory).Justbeforethe</div>tagabove</body>,insertthiscode(allchangesandadditionsareinafiletitled“lab_env_db_v3.html”onGithub):
<divclass=“two-thirdscolumn”style=“margin-top:5%”>
<divclass=“row”>
<divclass=“row”>
<divclass=“threecolumns”>
<divid=“chart_temps”></div>
<divid=“chart_humid”></div>
</div>
</div>
</div>
</div>
</div>
</body>
Thiscreatestwodivboxes,onewithid“chart_temps”andonewithid“chart_humid”.ThisiswherethetwochartswillbeplacedbytheGoogleChartsAPI.
Step10.2Justbeforethe</html>tagattheveryendofthefile,addthisJavascriptcode:<scripttype=“text/javascript”src=“https://www.google.com/jsapi?autoload={‘modules’:
[{‘name’:‘visualization’,‘version’:‘1’,‘packages’:[‘corechart’]}]}”></script>
<script>
google.load(‘visualization’,‘1’,{packages:[‘corechart’]});
google.setOnLoadCallback(drawChart);
functiondrawChart(){
vardata=newgoogle.visualization.DataTable();
data.addColumn(‘datetime’,‘Time’);
data.addColumn(‘number’,‘Temperature’);
data.addRows([
{%forrowintemp%}
[newDate({{row[0][0:4]}},{{row[0][5:7]}},{{row[0][8:10]}},{{row[0][11:13]}},{{row[0][14:16]}}),
{{‘%0.2f’|format(row[2])}}],
{%endfor%}
]);
varoptions={
width:600,
height:563,
hAxis:{
title:“Date”,
gridlines:{count:{{temp_items}},color:‘#CCC’},
format:‘dd-MMM-yyyyHH:mm’},
vAxis:{
title:‘Degrees’
},
title:‘Temperature’,
curveType:‘function’//Makeslinecurved
};
varchart=newgoogle.visualization.LineChart(document.getElementById(‘chart_temps’));
chart.draw(data,options);
}
</script>
<script>
google.load(‘visualization’,‘1’,{packages:[‘corechart’]});
google.setOnLoadCallback(drawChart);
functiondrawChart(){
vardata=newgoogle.visualization.DataTable();
data.addColumn(‘datetime’,‘Time’);
data.addColumn(‘number’,‘Humidity’);
data.addRows([
{%forrowinhum%}
[newDate({{row[0][0:4]}},{{row[0][5:7]}},{{row[0][8:10]}},{{row[0][11:13]}},{{row[0][14:16]}}),
{{‘%0.2f’|format(row[2])}}],
{%endfor%}
]);
varoptions={
width:600,
height:563,
hAxis:{
title:“Date”,
gridlines:{count:{{hum_items}},color:‘#CCC’},
format:‘dd-MMM-yyyyHH:mm’},
vAxis:{
title:‘Percent’
},
title:‘Humidity’,
curveType:‘function’//Makeslinecurved
};
varchart=newgoogle.visualization.LineChart(document.getElementById(‘chart_humid’));
chart.draw(data,options);
}
</script>
</html>
Step10.3WeneedtopassthetotalnumberofrecordsinthehumiditiesandtemperaturescollectionsbecausetheGoogleChartsAPIneedsthisinformationtoconstructthegridforthecharts.
Openlab_env_db.pyinyoureditor,andadjustthelab_env_dbmethodtobelikethis(titled“lab_app_v7.py”onGithub):@app.route(“/lab_env_db”,methods=[‘GET’])
deflab_env_db():
temperatures,humidities,from_date_str,to_date_str=get_records()
returnrender_template(“lab_env_db.html”,temp=temperatures,hum=humidities,temp_items=
len(temperatures),hum_items=len(humidities))
Theadditionsarehighlightedinbold.Wesimplyusethe“len”methodtocounthowmanyitemswehaveinthetwocollections,andpassthisintegertothetemplate.
Step10.4Unlessthere’satypo,thisshouldwork.SavetheupdatedPythonscriptandrestartuWSGI.
Reloadyourbrowser,andmarvelatyournewcharts!
IfyouarenotfamiliarwithJavascript,itisworththeeffortofstudyingthiscodeinsomedetail.Essentially,foreachchart,weareconstructingadatatablethatcontains
informationabouttheaxisandthedataforeachrow.Foreachrow,weplotthevalue,roundedtotwodecimals,againstatimestamp.Thisway,weareconstructingadateandtimechart.ThisisdocumentedintheChartDocumentation,whichIstronglyrecommendyouspendsometimewith.Inthefollowingsections,wewillimprovetheuserexperiencebyaddingelementstoallowtheusertoselectanarbitrarydaterange,addresstheissueofthetimezone,andmakeitpossibletosendthecurrentsensordatasettoPlotly,anamazingcloudvisualisationandanalysisservice,whereyoucandothingslikethis:
CHAPTERTHIRTEENImprovetheUI
Inthissectionwewillimprovetheexistinguserinterfacebyaddingwidgetsthatallowtheusertoeasilyselectdate/timerangesforthecharts.
Todothis,IwillintroduceJQueryandtheDateTimePickerwidget.
JQueryisoneofthemostwidelyusedJavascriptframeworks.JQuerymakesJavascripteasytousebyprovidingalibraryofusefulfunctionsandvisualelements.Iwillshowyousomeofthesefeaturesaswegothroughthecontentofthissection.
JQueryisatechnologythatrunsontheclientside(browser).Therefore,mostoftheworkwewillbedoinginthissectionwillbedoneonthetemplatefiles.
Step11.1Let’smeetJQueryandtheDateTimePickerwidget.Hereisjquery.comfromwhereyoucandownloadorfindtheCDNlinkforthelibraryfile:
ThereareseveralwidgetsthatallowdateandtimeselectionthatworkwithJQuery.The
onethatIprefertouseinthisprojectisdatetimepickerbecauseitisveryconfigurableandlooksgreat:
Step11.2TomakejQueryandthewidgetavailabletoourtemplate,weneedtoincludethemintoourpage.ForjQuery,theeasiestwaytousetheCDN(ContentDeliveryNetwork)link.Thisway,ourtemplatewillalwaysbeusingtheofficiallibrarymaintainedbythemaintainersofjQuery.Thiswasalreadydoneintheprevioussections,sothereisnomoreworktodo.
Toaddthedatetimewidget,downloadtheZIPfilefromhttp://xdsoft.net/jqplugins/datetimepicker/.ACDNisnotsupported.
Takethe.cssfileandcopyitintothestatic/cssfolder,andthe.jsfileintothestatic/javascriptfolder.Ifthejavascriptfolderdoesn’texist,createit.
Yourstaticfoldershouldlooklikethisnow:
Step11.3Openthelab_env_db.htmltemplatefileandaddthesetwolinesrightunderthereferencetothejquery-1.11.2.min.jsfile(thisversionofthefileistitled“lab_env_db_v4.html”onGithub):
<scriptsrc=”//code.jquery.com/jquery-1.11.2.min.js”></script>
<linkrel=“stylesheet”type=“text/css”href=”/static/css/jquery.datetimepicker.css”/>
<scriptsrc=”/static/javascript/jquery.datetimepicker.js”></script>
Step11.4Next,wewilldefinetwoinputfieldsinwhichwewillplacethetwodatetimepickerwidgets.
Let’saddanewCSSrowthatcontainsanewform.Withinthatform,we’llhavetwoinputsthatwillserveasdatetimepickers.Clickingonabuttonwillsubmitthetwodatesselectedbytheuser.
Copythiscodeimmediatelyunderthe<body>tag:<divclass=“container”>
<divclass=“row”>
<formid=“datetime_range”action=”/lab_env_db”method=“GET”>
<!—<divclass=“row”>—>
<divclass=“threecolumns”>
<labelfor=“from”>Fromdate</label>
<inputclass=“u-full-width”id=“datetimepicker1”type=“text”value=”{{from_date}}”name=“from”>
</div>
<!—</div>—>
<!—<divclass=“row”>—>
<divclass=“threecolumns”>
<labelfor=“to”>Todate</label>
<inputclass=“u-full-width”id=“datetimepicker2”type=“text”value=”{{to_date}}”name=“to”>
</div>
<!—</div>—>
<!—<divclass=“row”>—>
<divclass=“twocolumns”>
<inputclass=“button-primary”type=“submit”value=“Submit”style=“position:relative;top:28px”
id=“submit_button”/>
</div>
<!—</div>—>
</form>
</div>
Thetwoinputsarehighlightedbold.
NoticethatthedefaultdateandtimestobedisplayedineachfieldareprovidedbytheFlashapplicationviathe“from_date”and“to_date”variables.WewillneedtoadjustourPythoncodetoincludethesevariablesintheparametersthatarepassedtothetemplate.
Step11.5Scrolltowardsthebottomofthelab_env_db.htmltemplatefile,towheretheJavascriptcodeis.
Undertheinclusionofthedatetimepickerlibrary,pastethiscode:<script>
jQuery(‘#datetimepicker1’).datetimepicker(
{
format:‘Y-m-dH:i’,
defaultDate:’{{from_date}}’
});
jQuery(‘#datetimepicker2’).datetimepicker({
format:‘Y-m-dH:i’,
defaultDate:’{{to_date}}’
});
jQuery(“#range_selectinput[type=radio]”).click(function(){
jQuery(“#range_select”).submit();
});
</script>
Thiscodewillplacethedatetimepickerstotheinputfieldswithnamesdatetimepicker1anddatetimepicker2,andconfigurethewaytheyshoulddisplaytheirvalues.HereistheGistforthisfileandversion:http://txplo.re/1klcHDS
Step11.6Let’sswitchtothePythonscript.Openlab_app.pyinyoureditor.Gotothelab_env_dbfunction,andchangethereturnstatementtothis:@app.route(“/lab_env_db”,methods=[‘GET’])
deflab_env_db():
temperatures,humidities,from_date_str,to_date_str=get_records()
returnrender_template(“lab_env_db.html”,temp=temperatures,
hum=humidities,
from_date=from_date_str,
to_date=to_date_str,
temp_items=len(temperatures),
hum_items=len(humidities))
Thenewtextisinbold.TheGistforthisfileandversionis:http://txplo.re/1l2EY2v
Step11.7Toseethechanges,restartuwsgiandrefreshthepage:>restartuwsgi
uwsgistart/running,process13058
Trychangingthedateandtimerange,andclickingSubmit.Confirmthattherecordsretrievedareactuallywithintherangeyouselected.
Itisnowgettingeasiertointeractwiththedatabase.
Thereisapendingissuethough:thedatesandtimesasrecordedontheRPiserverareinwhichevertimezonetheserverissetfor.Usually,thisisUTC.Ifyouareatadifferenttimezone,thetimedisplayedintheGooglechartandintherecordstablewillnotmatchtheactualtimethattherecordwascreated,butitwillbeoffset.
Let’scorrectthisprobleminthenextsection.
CHAPTERFOURTEENAdjustingforactualtimezone
Itwouldbereallynicefortheapplicationtobeabletodetecttheuser’stimezonebasedontheinformationprovidedbythebrowser,andthentoautomaticallyadjustthetimesofeachrecordforthattimezone.Thisway,timesshownintherecordstableandintheGooglechartwillbecorrectfortheuser’stimezone.
Sotherearetwocomponentsforourtimezonesolution:client(Javascript)andserver(Python).
Step12.1Wewillstartwiththeclient.Wewanttodetecttheuser’stimezonebygettinginformationfromtheirbrowser.WecanthenpassthisinformationtotheserverbypiggybackingitinaformGETrequest.
ThereisaveryusefulJavascriptlibrarythatcanhelpusgetthetimezoneinformation:https://bitbucket.org/pellepim/jstimezonedetect
ThislibraryisavailableviaCDN,sothereisnothingtodownloadtotheRPi.Openlab_env_db.htmlinyoureditorandaddthislineunderthepreviousJavascriptinclusions:<scriptsrc=“https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.4/jstz.min.js”></script>
Step12.2Wewilltransmitthetimezoneinformationtotheserverwiththeuseofhiddenfields.Sincewehavetoforms,wewilladdatimezonehiddenfieldtoeachone.Thetimezoneinformationwilltraveltotheserverwhentheusersubmiteitherform.
Aslongasthehiddenformiswithintheformtags,it’sexactpositiondoesnotmatter.Iusuallyplacethemrightbeforethesubmitbuttonorrightafterthe<form>tagifthereisnobutton(thesecondformonlyhadradiobuttons).
Herearethetwoforms,withthehiddenfieldsinbold(theupdatedfileforthislectureistitled“lab_env_db_v5.htmlonGithub):<divclass=“row”>
<formid=“datetime_range”action=”/lab_env_db”method=“GET”>
<!—<divclass=“row”>—>
<divclass=“threecolumns”>
<labelfor=“from”>Fromdate</label>
<inputclass=“u-full-width”id=“datetimepicker1”type=“text”value=”{{from_date}}”name=“from”>
</div>
<!—</div>—>
<!—<divclass=“row”>—>
<divclass=“threecolumns”>
<labelfor=“to”>Todate</label>
<inputclass=“u-full-width”id=“datetimepicker2”type=“text”value=”{{to_date}}”name=“to”>
</div>
<!—</div>—>
<!—<divclass=“row”>—>
<divclass=“twocolumns”>
<inputtype=“hidden”class=“timezone”name=“timezone”/>
<inputclass=“button-primary”type=“submit”value=“Submit”style=“position:relative;top:28px”
id=“submit_button”/>
</div>
<!—</div>—>
</form>
</div>
<divclass=“row”>
<divclass=“elevencolumns”>
<formid=“range_select”action=“/lab_env_db”method=“GET”>
<inputtype=“hidden”class=“timezone”name=“timezone”/>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“3”id=“radio_3”/><labelfor=“radio_3”>3hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“6”id=“radio_6”/><labelfor=“radio_6”>6hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“12”id=“radio_12”/><labelfor=“radio_12”>12hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“24”id=“radio_24”/><labelfor=“radio_24”>24hrs</label>
</div>
</form>
</div>
</div>
Step12.3Beforeeachformissubmitted,thepagemustfirstgetthetimezonefortheuserandputthevalueinthehiddenfields.WewillusejQueryforthis.Hereistheexamplecode:timezone=jstz.determine();
jQuery(“.timezone”).val(timezone.name());
Wegetthetimezonewithjstsz.determine()andcopythevaluetothehiddenfieldwiththeid“timezone”inthesecondline.
Wewantthiscodetobeexecutedwiththerealisationofaformsubmitevent.Forthedatetimepickerform,thecodeisthis:jQuery(“#datetime_range”).submit(function(event){
timezone=jstz.determine();
jQuery(“.timezone”).val(timezone.name());
});
Fortheradiobuttonsform,itlookslikethis:jQuery(“#range_selectinput[type=radio]”).click(function(){
timezone=jstz.determine();
jQuery(“.timezone”).val(timezone.name());
jQuery(“#range_select”).submit();
});
CompletefileGistforthisversion:http://txplo.re/1N6luAG
Step12.4Uploadthenewtemplatecodetotheserverifyouhaven’tdonesoalready.
Step12.5InPython,wewilldotimeanddatecalculationsusingArrow,apopularpackageforjustthispurpose.
Let’sinstallitinourapp’svirtualenvironment:>/var/www/lab_app/venv/bin/pipinstallarrow
Downloading/unpackingarrow
Downloadingarrow-0.5.4.tar.gz(81Kb):81Kbdownloaded
Runningsetup.pyegg_infoforpackagearrow
Downloading/unpackingpython-dateutil(fromarrow)
Downloadingpython-dateutil-2.4.2.tar.gz(209Kb):209Kbdownloaded
Runningsetup.pyegg_infoforpackagepython-dateutil
Downloading/unpackingsix>=1.5(frompython-dateutil->arrow)
Downloadingsix-1.9.0.tar.gz
Runningsetup.pyegg_infoforpackagesix
nopreviously-includeddirectoriesfoundmatching‘documentation/_build’
Installingcollectedpackages:arrow,python-dateutil,six
Runningsetup.pyinstallforarrow
Runningsetup.pyinstallforpython-dateutil
Runningsetup.pyinstallforsix
nopreviously-includeddirectoriesfoundmatching‘documentation/_build’
Successfullyinstalledarrowpython-dateutilsix
Cleaningup…
Step12.6Openlab_app.pyinyoureditor.Startbyimportingthearrowlibrarytothescriptatthetopofthefile:importarrow
Therestofthechangesareinmethodslab_env_dbandget_records.Youshouldspendabitoftimelookingatthecodeinthesemethodsinordertounderstandhowtimezoneinformationisusedtoconverttimefromonetimezonetotheother.
Afewthingstoremember:
Ifatimezoneisnotdefined,thenweuseEtc/UTCasthedefault.Timesusedinthedatabaserecordsareinthetimezoneoftheserver(RPi).ItisagoodpracticetoalwaysadjustyourserversothatitstimezoneisEtc/UTC.Youcanuse“dpkg-reconfiguretzdata”tomakechanges.
Hereisthecodeforlab_env_db()(theupdatedfileforthislectureistitled“lab_app_v9.py”onGithub):@app.route(“/lab_env_db”,methods=[‘GET’])#AdddatelimitsintheURL#Arguments:from=2015-03-
04&to=2015-03-05
deflab_env_db():
temperatures,humidities,timezone,from_date_str,to_date_str=get_records()
#Createnewrecordtablessothatdatetimesareadjustedbacktotheuserbrowser’stimezone.
time_adjusted_temperatures=[]
time_adjusted_humidities=[]
forrecordintemperatures:
local_timedate=arrow.get(record[0],“YYYY-MM-DDHH:mm”).to(timezone)
time_adjusted_temperatures.append([local_timedate.format(‘YYYY-MM-DDHH:mm’),
round(record[2],2)])
forrecordinhumidities:
local_timedate=arrow.get(record[0],“YYYY-MM-DDHH:mm”).to(timezone)
time_adjusted_humidities.append([local_timedate.format(‘YYYY-MM-DDHH:mm’),round(record[2],2)])
print“renderinglab_env_db.htmlwith:%s,%s,%s”%(timezone,from_date_str,to_date_str)
returnrender_template(“lab_env_db.html”,timezone=timezone,
temp=time_adjusted_temperatures,
hum=time_adjusted_humidities,
from_date=from_date_str,
to_date=to_date_str,
temp_items=len(temperatures),
hum_items=len(humidities))
Thecodeforget_records()isthis:defget_records():
from_date_str=request.args.get(‘from’,time.strftime(“%Y-%m-%d00:00”))#Getthefromdate
valuefromtheURL
to_date_str=request.args.get(‘to’,time.strftime(“%Y-%m-%d%H:%M”))#Getthetodatevalue
fromtheURL
timezone=request.args.get(‘timezone’,‘Etc/UTC’);
range_h_form=request.args.get(‘range_h’,”);#Thiswillreturnastring,iffieldrange_h
existsintherequest
range_h_int=“nan”#initialisethisvariablewithnotanumber
print“REQUEST:”
printrequest.args
try:
range_h_int=int(range_h_form)
except:
print“range_h_formnotanumber”
print“Receivedfrombrowser:%s,%s,%s,%s”%(from_date_str,to_date_str,timezone,
range_h_int)
ifnotvalidate_date(from_date_str):#ValidatedatebeforesendingittotheDB
from_date_str=time.strftime(“%Y-%m-%d00:00”)
ifnotvalidate_date(to_date_str):
to_date_str=time.strftime(“%Y-%m-%d%H:%M”)#ValidatedatebeforesendingittotheDB
print‘2.From:%s,to:%s,timezone:%s’%(from_date_str,to_date_str,timezone)
#CreatedatetimeobjectsothatwecanconverttoUTCfromthebrowser’slocaltime
from_date_obj=datetime.datetime.strptime(from_date_str,’%Y-%m-%d%H:%M’)
to_date_obj=datetime.datetime.strptime(to_date_str,’%Y-%m-%d%H:%M’)
#Ifrange_hisdefined,wedon’tneedthefromandtotimes
ifisinstance(range_h_int,int):
arrow_time_from=arrow.utcnow().replace(hours=-range_h_int)
arrow_time_to=arrow.utcnow()
from_date_utc=arrow_time_from.strftime(“%Y-%m-%d%H:%M”)
to_date_utc=arrow_time_to.strftime(“%Y-%m-%d%H:%M”)
from_date_str=arrow_time_from.to(timezone).strftime(“%Y-%m-%d%H:%M”)
to_date_str=arrow_time_to.to(timezone).strftime(“%Y-%m-%d%H:%M”)
else:
#ConvertdatetimestoUTCsowecanretrievetheappropriaterecordsfromthedatabase
from_date_utc=arrow.get(from_date_obj,timezone).to(‘Etc/UTC’).strftime(“%Y-%m-%d%H:%M”)
to_date_utc=arrow.get(to_date_obj,timezone).to(‘Etc/UTC’).strftime(“%Y-%m-%d%H:%M”)
conn=sqlite3.connect(‘/var/www/smart/temp_hum2.db’)
curs=conn.cursor()
curs.execute(“SELECT*FROMtemperaturesWHERErDateTimeBETWEEN?AND?”,
(from_date_utc.format(‘YYYY-MM-DDHH:mm’),to_date_utc.format(‘YYYY-MM-DDHH:mm’)))
temperatures=curs.fetchall()
curs.execute(“SELECT*FROMhumiditiesWHERErDateTimeBETWEEN?AND?”,
(from_date_utc.format(‘YYYY-MM-DDHH:mm’),to_date_utc.format(‘YYYY-MM-DDHH:mm’)))
humidities=curs.fetchall()
conn.close()
return[temperatures,humidities,timezone,from_date_str,to_date_str]
HereisthecompleteappscriptGistforthisversion:http://txplo.re/1WsscWE
Step12.7Noticethatinthelab_env_dbmethod,wehavecreatedtwonewarrays.Inthosearrayswecopythedatafromthosethatareretrievedfromthedatabase,butwiththeirtimesadjustedfortheuser’slocaltimezone.Thesearraysonlycontain2columns,asopposedtothethreecolumnsthatcomeoutofthedatabase;weonlyneedthetime/dateandvalue.So,weneedtomakeasmallchangeinthetemplatefiletoaccommodateforthischange.
Openlab_env_db.htmlandsearchforthisstring:{{‘%0.2f’|format(row[2])}}
Changebothinstancesittothis:{{‘%0.2f’|format(row[1])}}
AlsomakethesamechangetotheJavascriptcodefortheGoogleChartsAPI.Searchforformat(row[2])
andchangebothinstancesto:format(row[1])
HereisthecompletetemplateGistforthisversion:http://txplo.re/1LL1cyR
Step12.8UploadandsavethenewcodetoyourRPi,andrestartuWSGI.
Ifyourpageisnotloading(“502BadGateway”),checktheerrorlogs:>tail/var/log/uwsgi/lab_app_uwsgi.log
Step12.9Refreshyourbrowser.IfyouareusingGoogleChrome,turnonthedevelopertoolssoyoucanseethecontentsoftheGETrequestwhenyousubmitoneoftheforms.
ForexampleifIclickonthe6hrbutton,Igetthis:
ChecktheURL,andthatitcontainsavalueforthe“timezone”parameter.Youcanseethesamethinginthedevelopertoolssection:clickonNetwork,thenonthefirstrequestintheleftpaneofthewindow.TherequestURLcontainsthetimezoneinformationfromtheuser’sbrowser.
Notmuchmorelefttodo.Inthenextsection,wewilladdcodesothattheusercaneasilymovebetweenthepastrecordsandcurrentvaluespages.Andinthelastsection,wewillmakeiteasytosentourrecordstoPlotlysothatwecanusePlotly’sdataanalysistools.
CHAPTERFIFTEENLinkthetwopages
Inthissection,wewillmakeiteasyfortheusertomovebetweenthetwopagesthatmakeupourapplication.Inthedatabaserecordspage,we’lladdalinktothecurrentconditionspage.
Andinthecurrentconditionspage,we’lladdtheradiobuttonsforthetimerangessothattheusercanquicklyretrieverecenthistory.
Step13.1Let’sstartwiththeCurrentConditionspage.Openlab_temp.htmlinyoureditor.
Thisfilecurrentlylookslikethis(Gist):http://txplo.re/1KRHw8s
Step13.2Thetimerangeradiobuttonsformwewanttoaddinlab_temp.htmlalreadyexistsinlab_env_db.html.Youcanopenthisfileinyoureditorandfindthatcodesegment,orjustcopythefollowingtolab_temp.html(pasteitrightafterthe</div>thatclosesthe<divclass=“row”>(theupdatedfileistitled“lab_temp_v3.html”onGithub):<divclass=“row”>
<divclass=“elevencolumns”>
<formid=“range_select”action=“/lab_env_db”method=“GET”>
<inputtype=“hidden”class=“timezone”name=“timezone”/>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“3”id=“radio_3”/><labelfor=“radio_3”>3hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“6”id=“radio_6”/><labelfor=“radio_6”>6hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“12”id=“radio_12”/><labelfor=“radio_12”>12hrs</label>
</div>
<divclass=“onecolumn”>
<inputtype=“radio”name=“range_h”value=“24”id=“radio_24”/><labelfor=“radio_24”>24hrs</label>
</div>
</form>
</div>
RememberthatwehavesomeJavascriptcodethatdealswiththetimezones,sowewillneedtoalsocopythatacrosstolab_temp.html.Findthiscodeinlab_env_db.html,orcopythecodefrombelow(thiscodegoesrightafterthe</body>:<scriptsrc=”//code.jquery.com/jquery-1.11.2.min.js”></script>
<scriptsrc=“https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.4/jstz.min.js”></script>
<script>
jQuery(document).ready(function(){
timezone=jstz.determine();
jQuery(“#timezone”).text(timezone.name());
});
jQuery(“#range_selectinput[type=radio]”).click(function(){
timezone=jstz.determine();
jQuery(“.timezone”).val(timezone.name());
jQuery(“#range_select”).submit();
});
</script>
ThisGistcontainsthefinalversionforlab_temp.html:http://txplo.re/1klcXmj
Step13.3Loadlab_tempinyourbrowser,andyoushouldseethis:
Youcanclickoneoftheradiobuttonstoseerecordsfromrecentpast:
Noticethatmychartherehasastrangerange,from400to-1200degreescentigrade.Obviouslythisisnotright.Whathappenedisthatthesensorproducedafalsereadingof-999.00at2015-04-2111:50,probablybecauseIwasfiddlingaroundwiththewires.
Itwouldbenicetoaddsomecodetofilteroutvalueslikethesethatareobviouslyerroneous,insteadoflettingthemthroughandcorruptingthechart.Iwillleavethatuptoyoutodoifyouwish.
Step13.4Let’smovetolab_env_db.html.Wewilladdasimplelinkthatwecanfollowtoviewthecurrenttemperatureandhumidityinthelab.Wewilladdthislinkunderthetwoformfieldsusedbythedatetimepickerwidget.
Pastethiscoderightbeforethe<form>tagforthetimerangeradiobuttons(theupdatedfileistitled“lab_env_db_v6.html”onGithub):<divclass=“onecolumn”>
<ahref=”/lab_temp”style=“position:relative;top:15px”>Current</a>
</div>
Yourlab_env_db.htmlshouldnowbethis(Gist):http://txplo.re/1OhQ686
Step13.5Loadlab_env_dbinyourbrowser,youshouldbeseeingthis:
Noticethe“Current”link?Ifyouclickonit,youwillreachtheCurrentConditionspage.
Interconnectingthetwopagesoftheapplicationwasaneasytaskconsideringthatwealreadyhadmostofthenecessarycode.
Inthenextandlastsectionofthisproject,wewillmakeitpossiblefordatafromourapplicationtobetransmittedtoPlotlyforanalysis.
WithPlotly,youcanvisualiseandanalyseyourdataonthecloud,andshareit,ifyouwant,withtherestoftheworld.
CHAPTERSIXTEENUploadingtoPlotly
Inthissection,wewilluploadsensordatafromourapplicationdatabasetoPlotly.OnceyourdataisonPlotly,wewillvisualiseitandscratchthesurfaceofwhatispossibletodointermsofdataanalysis.
Step14.1StartbycreatingafreeaccountonPlotly.Gottoplotly.io,clickontheSignUpbutton,andfollowtheprocess:
Step14.2PlotlyprovidesextensivedocumentationonhowtouseitwithPython.Youcanstarthere:https://plot.ly/python/getting-started/.
GoaheadandinstallthePlotlypackageforPython.Withthispackage,connectingyourPythonapplicationtoPlotlyis(almost)trivial.
FromyourapplicationdirectoryinRPi,runthePIPtooltoinstallthepackageintheapp’svirtualdirectory:root@raspberrypi:/var/www/lab_app#venv/bin/pipinstallplotly
Downloading/unpackingplotly
Downloadingplotly-1.6.16.tar.gz(103Kb):103Kbdownloaded
Runningsetup.pyegg_infoforpackageplotly
Downloading/unpackingrequests(fromplotly)
Downloadingrequests-2.6.0.tar.gz(450Kb):450Kbdownloaded
Runningsetup.pyegg_infoforpackagerequests
Requirementalreadysatisfied(use—upgradetoupgrade):sixin./venv/lib/python2.7/site-packages
(fromplotly)
Downloading/unpackingpytz(fromplotly)
Downloadingpytz-2015.2.tar.bz2(166Kb):166Kbdownloaded
Runningsetup.pyegg_infoforpackagepytz
Installingcollectedpackages:plotly,requests,pytz
Runningsetup.pyinstallforplotly
Runningsetup.pyinstallforrequests
Runningsetup.pyinstallforpytz
Successfullyinstalledplotlyrequestspytz
Cleaningup…
Step14.3Let’stestthepackage.AllcommunicationbetweenyourcomputerandPlotlyissignedwithyouraccountAPI.Beforegoinganyfurther,logontoyourPlotlyaccountandgetyourAPIkey.
Tofindyours,clickonyourusernameinthetoprightcornerofthepage,then“Settings”.TheAPIkeyisinthefirstbox.Keepthiswindowopensoyoucancopythekeyandusernameinthenextstep.
Step14.4NowwecaninitialisethePlotlypackage.
Inthecodebelow,replace“DemoAccount”withyouractualaccountname,and“your_api_key”withyouractualAPIkey.Assumingyouarestillintherootdirectoryofyourapplication,enterthiscommand:>venv/bin/python-c“importplotly;plotly.tools.set_credentials_file(username=‘DemoAccount’,
api_key=‘your_api_key‘)”
Step14.5YouraccountisnowregisteredwiththePlotlypackage.Let’strytocreateanewchart.WecanusethePythoncommandlineinterpreterandanexamplefromthedocumentation.
Type“venv/bin/python”inthecommandlinetoenterthecommandlineinterpreter:>venv/bin/python
Python2.7.3(default,Mar182014,05:13:23)
[GCC4.6.3]onlinux2
Type“help”,“copyright”,“credits”or“license”formoreinformation.
>>>
Step14.6Onelineatatime,copyandpastethisprogramintothecommandline:importplotly.plotlyaspy
fromplotly.graph_objsimport*
trace0=Scatter(
x=[1,2,3,4],
y=[10,15,13,17]
)
trace1=Scatter(
x=[1,2,3,4],
y=[16,5,11,9]
)
data=Data([trace0,trace1])
unique_url=py.plot(data,filename=‘basic-line’)
Theprocesslookedlikethisforme:>>>importplotly.plotlyaspy
>>>fromplotly.graph_objsimport*
>>>trace0=Scatter(
…x=[1,2,3,4],
…y=[10,15,13,17]
…)
>>>trace1=Scatter(
…x=[1,2,3,4],
…y=[16,5,11,9]
…)
>>>data=Data([trace0,trace1])
>>>unique_url=py.plot(data,filename=‘basic-line’)
/var/www/lab_app/venv/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:79:
InsecurePlatformWarning:
AtrueSSLContextobjectisnotavailable.Thispreventsurllib3fromconfiguringSSLappropriately
andmaycausecertainSSLconnectionstofail.Formoreinformation,see
https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
Step14.7PlotlycomplainedaboutanissuewithSSL.Ifyoureceivedthiserrormessageyoucansafelyignoreitfornow.Thecharthasbeencreatedanyway.Atsomelatertime,youcanupgradeyourPythonvirtualenvironmenttoanewerversion,2.7.9orlater,wherethisissuehasbeencorrected.
WeneedtogettheURLforthenewchart.TheURLisstoredintheunique_urlvariable.
TypethisinthePythoninterpreter:>>>printunique_url
https://plot.ly/~futureshocked/8
TheURL(formychart)ishighlightedinbold.Copytoyourwebbrowser.Youshouldseesomethinglikethis:
Step14.8Ok,weknowthatwecansendchartdatatoourPlotlyaccount.Let’supdateourapplicationsothatwecansendsensordataondemand,withtheclickofabutton.
Bystudyingtheexampleprogram,andlookingthroughthedocumentation,welearnthatweneedtocreateaDataobject.Init,wecreatetraceobjects.Thetraceobjectwillcontainthedatatobedisplayed,andsomeattributes,likethetextfortheaxes,thenameofthechart,andmore.
Iwouldlikemycharttobeatimeseries,sincetimeisarrangedonthehorizontalaxis.Iwouldalsoliketodisplaybothhumidityandtemperatureonasinglechart,withtheCelsiusscaleontheleftverticalaxisandtheHumidityPercentscaleontherightverticalaxis.
ThisistheresultIwouldliketoachieve:
Step14.9Fromauserinterfaceperspective,Iwouldliketousertobeabletoclickonalinkinthelab_env_dbpage,andhavethedatasenttoPlotly.Whenthechartisready,IwouldlikealinktoappearunderthePlotlylinkwhichIcanclickandgotothechart.
Thisinvolvesasynchronouscallsattwolevels.First,whenIclickonthePlotlylink,anasynchronousGETrequestissenttotheapplicationontheRPirequestingthechart.
Next,thePlotlypackagemakesit’sownrequesttoPlotly,andretrievestheURLforthechart,whenthechartisready.Last,thisURLisreturnedtothebrowser,asaresponsetothefirstasynchronousrequest.
Step14.10jQuerywillmakeallthiseasy.Let’sstartwiththeHTMLtemplatepage.Openlab_env_db.htmlinyoureditor.
Addthelinkfirst.Copythiscodejustbeforethelinktothelab_temppage:<divclass=“onecolumn”>
<ahref=””id=“plotly”style=“position:relative;top:15px”>Plotly</a>
</div>
Thehrefattributeisempty,becauseitscontentdependsonthedatathatwewanttosenttoPlotly.ItwillbepopulatedbythejQuerycode(next).
Step14.11Wewillalsoaddcodeforalinkthatwillbeabletoclickwhenthechartisready.Hereisthecode:<divclass=“row”>
<ahref=””id=“plotly_url”target=“_blank”></a><spanid=“plotly_wait”></span>
</div>
Pastethiscoderightabovetherecordsrow.
Step14.12Finally,thejQuery.Hereitis:
jQuery(“#plotly”).click(function(){
jQuery(“#plotly_wait”).text(“Sendingdata…”);
jQuery(“#plotly_url”).text(””);
{%autoescapefalse%}
jQuery.get(“/to_plotly?{{query_string}}”)
{%endautoescape%}
.done(function(data){
jQuery(“#plotly_url”).attr(“href”,data);
jQuery(“#plotly_url”).text(“Clicktoseeyourplot”);
jQuery(“#plotly_wait”).text(””);
});
returnfalse;//Thisissothattheclickonthelinkdoesnotcausethepagetorefresh
});
PastethisrightafterthejQuerycodefortherange_selectbuttons.ThisisthecodethatgeneratestheURLtobecalledwhenweclickonthelink.Itpointstoamethodinourapplication,whichwehavenotcreatedyet.
Thismethodconstructsthedataobjectsforthechart,andtransmitsthemtoPlotlyusingthePlotlyAPIpackage.ItthenreadstheresponsefromtheAPIthatcontainsthenewchartURLandputsthatURLinourpagesothattheusercanclickonit.Hereisthefullcodeinlab_env_db.htmlatthispoint(Gist):http://txplo.re/1N6lTTR
Step14.13Let’sswitchtolab_app.py.Hereisthecodethatmakesuptheto_plotlymethod(youcanfindthisversiononGithub,titled“lab_app_v10.py”):@app.route(“/to_plotly”,methods=[‘GET’])#Thismethodwillsendthedatatoploty.
defto_plotly():
importplotly.plotlyaspy
fromplotly.graph_objsimport*
temperatures,humidities,timezone,from_date_str,to_date_str=get_records()
#Createnewrecordtablessothatdatetimesareadjustedbacktotheuserbrowser’stimezone.
time_series_adjusted_tempreratures=[]
time_series_adjusted_humidities=[]
time_series_temprerature_values=[]
time_series_humidity_values=[]
forrecordintemperatures:
local_timedate=arrow.get(record[0],“YYYY-MM-DDHH:mm”).to(timezone)
time_series_adjusted_tempreratures.append(local_timedate.format(‘YYYY-MM-DDHH:mm’))
time_series_temprerature_values.append(round(record[2],2))
forrecordinhumidities:
local_timedate=arrow.get(record[0],“YYYY-MM-DDHH:mm”).to(timezone)
time_series_adjusted_humidities.append(local_timedate.format(‘YYYY-MM-DDHH:mm’))#Besttopass
datetimeintext
#sothatPlotlyrespectsit
time_series_humidity_values.append(round(record[2],2))
temp=Scatter(
x=time_series_adjusted_tempreratures,
y=time_series_temprerature_values,
name=‘Temperature’
)
hum=Scatter(
x=time_series_adjusted_humidities,
y=time_series_humidity_values,
name=‘Humidity’,
yaxis=‘y2’
)
data=Data([temp,hum])
layout=Layout(
title=“TemperatureandhumidityinPeter’slab”,
xaxis=XAxis(
type=‘date’,
autorange=True
),
yaxis=YAxis(
title=‘Celcius’,
type=‘linear’,
autorange=True
),
yaxis2=YAxis(
title=‘Percent’,
type=‘linear’,
autorange=True,
overlaying=‘y’,
side=‘right’
)
)
fig=Figure(data=data,layout=layout)
plot_url=py.plot(fig,filename=‘lab_temp_hum’)
returnplot_url
Copythiscodesomewhereinthescript.ItwillrespondtoGETrequestsonly,constructthedataobjectaspertheparametersprovidedbytheuser,transmitthedataobjecttoPlotly,gettheURLforthenewchartfromthePlotlyresponse,andreturntheURLtothejQuerycodeinthetemplatepage.Hereisthecompletecodeforlab_app.py(Gist):http://txplo.re/1OhQbst
Step14.14Let’stryoutthenewcode.RestartuWSGIandloadtherecordspage:
Step14.15ClickonthePlotlylinktotriggerthechartrequest.The“Sendingdata…”messagewillappearforafewseconds,andthenitwillbereplacedby“Clicktoseeyourplot”link:
Step14.16Clickonit,andmarvelonyournewchart!
Step14.17Ok,mychartisnotveryglamorousbecausethatinfamousbadrecordinmydataset.ButhereisoneIcreatedearlier:
Step14.18Itworks!SowhatelsecanyoudowithchartonPlotly?Startbycheckingouttherestofthetabsinthegraphplotbox.There’sData,CodeandExtras.Then,clickon“EditGraph”fornumerousoptionsonthemesandvariousfunctions.
Forexample,clickingonTracesgivesyouaccesstooptionsthatallowyoutomanipulateeitherofthelines.Thereisalsoafunctionthatcancalculatethebestfittinglinetoatrace,whichisgoodfordetectingtrends.Plotlyisaverycapableonlineresource,worthspendingabitoftimetobecomefamiliarwith.
CHAPTERSEVENTEENConclusion
Congratulations!
YouhavecompletedthisintroductoryprojectontheRaspberryPi.Youarenowfamiliarwithsomeofthethingsthatyoucandowithit,andwiththeflexibilityandpoweritofferswhencomparedtoothertechnologies,liketheArduino.
Let’srecapwhatyouhavelearned:
YouknowhowtoinstallandconfigureRasbian,HowtoinstallandusePythonandthePythonVirtualEnvironment,Youunderstandwhatisthewebapplicationstackanditscomponents,YouunderstandtheuseofnamingsystemofGPIOs,YoucaninteractwithsimpleandmorecomplicateddevicesconnectedtotheGPIOs,Youarefamiliarwithavarietyoffundamentalopensourcesoftwareandservices,likeNginx,Flask,uWSGI,Upstart,Skeleton,SQLite3,cron,Javascript,Jquery.Youhaveexperiencewithpopularwebservices,likeGoogleChartsandPlotly.And,mostimportant,youhavecreatedafull-stackapplicationonyourRaspberryPiinwhichyouappliedallthesecomponentsinasinglesystem.
What’snext?It’suptoyou!However,Icansuggestacoupleofideas.
Howaboutyouextendyourexistingapplication?Addmoresensors,addautomaticnotificationswhenthesesensorsreachatriggervalue,thinkaboutcontrollingsomething.
Howaboutworkingontheuserinterfacetomakeititmoreinteresting?Youcanexperimentwithdifferentwidgetsordatavisualisations.YoucanfigureoutawaytomakeyourapplicationwebsiteaccessiblefromtheWeb.Youcanpublishyourdatatodataloggingservices.YoucanconnectyourRaspberryPitoathermostatandgetittocontrolyourhomeenvironment!
Or,forgetabouttheapplicationyoujustcreated,andstartfromscratch.Thinkbig,orthinksmall,itisuptoyouanditdependsonwhatdrivesyouandhowyoulearn.
Butwhateveryoudecidetodo,maybeIcanhelpyou.Sendmeamessageandletmeknow.
Thiswasjustthebeginning!
TableofContents
AboutthisbookVideocoursecompanionDiscussionforumandemaillistTomakethemostofthisbookCHAPTERONE-IntroductionCHAPTERTWO-AbouttheRaspberryPiCHAPTERTHREE-InstallanOperatingSystemCHAPTERFOUR-SetupPythonandtryouttheGPIOsCHAPTERFIVE-UseaDHT22sensorCHAPTERSIX-CreateawebapplicationstackontheRPiCHAPTERSEVEN-ServingstaticassetsCHAPTEREIGHT-FlasktemplatesandCSSstylingCHAPTERNINE-ViewsensorreadingsviaawebbrowserCHAPTERTEN-SetupasimpledatabaseanduseittologsensordataCHAPTERELEVEN-SelectrecordstoviewbytimeanddateCHAPTERTWELVE-AddVisualisationsCHAPTERTHIRTEEN-ImprovetheUICHAPTERFOURTEEN-AdjustingforactualtimezoneCHAPTERFIFTEEN-LinkthetwopagesCHAPTERSIXTEEN-UploadingtoPlotlyCHAPTERSEVENTEEN-Conclusion
Top Related