From 76ba188f8543e42b85b217bbfe600fa1daa52001 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Wed, 13 Apr 2016 12:12:47 +0100 Subject: [PATCH 001/104] Add missing SnowPi docs --- docs/api_boards.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api_boards.rst b/docs/api_boards.rst index 622bf05..6b2604b 100644 --- a/docs/api_boards.rst +++ b/docs/api_boards.rst @@ -106,6 +106,13 @@ Energenie :inherited-members: :members: +SnowPi +====== + +.. autoclass:: SnowPi + :inherited-members: + :members: + Base Classes ============ From 66264001e50642e383d431bb9f287a4995ac979e Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Wed, 13 Apr 2016 16:30:50 +0100 Subject: [PATCH 002/104] Docs update - Motor is composed of two PWMOutputDevices --- docs/images/composed_devices.dot | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/images/composed_devices.dot b/docs/images/composed_devices.dot index fa43d3b..2a13174 100644 --- a/docs/images/composed_devices.dot +++ b/docs/images/composed_devices.dot @@ -16,4 +16,5 @@ digraph classes { TrafficLightsBuzzer->Button; Robot->Motor; + Motor->PWMOutputDevice; } From b694ec838ab460edd2a7b5b4048503f523c1caf2 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 14 Apr 2016 02:02:40 +0100 Subject: [PATCH 003/104] Tiny typo --- gpiozero/pins/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index b824d7f..df0a8ef 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -512,7 +512,7 @@ def _parse_pi_revision(revision): # CCCC - Manufacturer (0=Sony, 1=Egoman, 2=Embest) # PPPP - Processor (0=2835, 1=2836, 2=2837) # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) - # RRR - Revision (0, 1, or 2) + # RRRR - Revision (0, 1, or 2) i = int(revision, base=16) if not (i & 0x800000): raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision) From 43c69b9435e44b2135c1f4f52c01cd2da8112a89 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Fri, 15 Apr 2016 16:52:31 +0100 Subject: [PATCH 004/104] Update graphs for #277 --- docs/images/composed_devices.pdf | Bin 11968 -> 12376 bytes docs/images/composed_devices.png | Bin 20903 -> 24861 bytes docs/images/composed_devices.svg | 94 ++++++++++++--------- docs/images/composite_device_hierarchy.pdf | Bin 14029 -> 14029 bytes 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/docs/images/composed_devices.pdf b/docs/images/composed_devices.pdf index e334ce7a171f9dcb525dfb621c2262bc15b9a0df..8c1805613c688ae1adcfad3b0fa140cfdb366558 100644 GIT binary patch delta 9827 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 diff --git a/docs/images/composed_devices.png b/docs/images/composed_devices.png index 07965eff741431bb7fab2a1909d1640ecba0a0b4..12cfc538d12609e37e3b859ce76ea54cacf9270a 100644 GIT binary patch literal 24861 zcmbrmWmuJ4)HS?8q`RfN8wqJ?2|=Vwy1P518$?Pv73uCy0i{#AyHo02`#j(K>-+b- z=eo{0xHtRW_gZVtF~^+u7)B_+mq9}&LWV#fXzyetRUi=9PVn;qB0Tu7`Ckfd@IN?b z@pr0-;2$4ElW_27BnMe7XYl*8(0^gX8PYw#i?3XyG+jR0o4L3fI+;S;-QC$N?W~=R z4INC`?48WhkA;XJ5DLgU$+xN=8HY>ms<^ZFqNg{=oZnDs4zBh7OS< z+wX-$a`*U$LS70Je3gUENK1cr(28oMS!P&F(>D#0Ko@*l4F2RT+DUP*BvN)s)tDr~m--6^)lYeXyj|`PeSO`66e}&+P zgO}swLa+^>KVYg9EC2sjE{??UKRcw#07D1eD#|2e+Rp!eC{W3TONG7?tjhGizF6eU zY->}!nr-7;BLw1f;f$3;60&oWMbKnJ{a5;jwH|@lKeKSfsJ^4{zp@F15J>NWk;nAK zdU6bSEk9LeP-EM9a({j*s49!|SZbMB)vd=0?L$wsbnf2lCtVrs_e6__O9s|vDE)*J zP7|0Z_z0HOxK4!+)*|iwdzJ^eI&F51gkD(l=Qeeo(#_JRr3YHO!WD_1HVj83Gzm3x zY=7570)(W|a4jdvK0rU{a2b%;i_{dU+bZV~ z(vklZ8d)q?@R0ZL?{~$Vt2sgUa!8UkXzmyUW?;75=bXu&v0-GqBk)-yt-WW4-VE` zd^hyJ5x)@VsfFqFjIX|oQe%gYwR|`++8HS9qZA`Ll`>sO|C^s}E&96FHXM&YgMyJ6 zX{F(bjEBW~)#KC=tdD|T6d~+0x^FFsaTTY)^jE$Cv#h3aE%3Gb9;5x^ZPV? zFxea=H99&zdaAb-8(WAzEX1X37SFYGA2$thV>O4Or`x=0dHAtz@9p?o+2MCRVhw=~ zRgP~5|6&sE6LoiW!w6^#X!_d<$0CqFe^bl(Q1q|p^k5@Q%+eqiZf;@_+t2sG;eOh- zuJ=iQQ&=e46Oldw=i%S&n$MtTcrcYJPjMtsGLOU4yk}{9^|!w{?Uv_gi=>NZG}lNV zR!7dh1^p;sKwT2PEWyr8e$QGp<;|G)F%Rn>FRqt>*Y3^&MzePF>B8#O9_&-TbbX8g z3ol#UIgg=J*f0L;UCji9(Y?&?)G}E?jhFm(-&mCTX*(~xMkG9Z_}95NC^^4&&QH9m z_^5BZcUS0ve}gL4^=m6nKg!|FAy+UgWxM@_aP(C)r_BvV3~6NuEG0;W9M*{$S>MfJ zaPKZZCnLO%j#p#BwOil$zJMUcB0a6c-TLc2hiM>HRGzQPNq(0!b}1RJ5n4p(>6 zO6{|nHql@|o}8ED+ft~gIAQ-jG(7Hb4OLgj1;9{HaefwTDgRU_g6`{%Xoqfx7|t~J z>S_0mx-_P2-1;NC$+oG}Ney>RP*Y%&w$?Bk%ZB#%{yNeyN85f!q|@(k18~Mexr}{( zI*2_9qDI-a_1%mAzAW)or{?6wYM{3BUk+nMFw0C0G0}3Wx)8NNs^YEhw}15S`LZm} zan>nm=e2quHX_1F3^Dj&9Zo!H2L+18SAIBRZ>!f0)*!y5zF_A{jz+F(uX1Ro#Le{S z4u2mdlnhF>Q;}Q$tLO?nAg(-l5~=g>;@$oAXkTCr7a|u3aK=h=M$E{J3Sd=EMLq@s z3c@Dy1*Ky0&$@GvB2+qAXn|r75P$mLTiINkAP^8cl{__4l19Bd;qWgVnO#x?%$N^+eWD^?O?zvc^|E-!OW*xlx)dwZ&Qa-(^rF z4r8X{m#BxyQhW~Th4EVO0x+d*t1~-09;QqY%%TJj?!{!wl#-Jfiz@KPs`no`@S-|) z$>*=wO)BN}ar|dJNF?Y2(Y`k~Zy_)&sH_wLkkIDPO6~@U-M`8ZF}1{>@q}`xY#XH5 zTsRQiL(vhmk@1OQ+WLEY8Bj+rT`$M$&2&ioT}bRFSHu}G*Y__X;OxMu3&w!lLUBxW8xt@n5-qz1I+&*5PKp^1v7^`v1yJSmQ zLjeS*HKmPB&IARP2WsmwB_T7;Gjh!Gg(?474&r2Hj}kP*M3Verr%x)Li#b8VIQy9% zJJKM}{4=iB+gI2UNcwcG54!?15BCn_?bR!C3O1r2SYc-HEqmUr2VZ7f8dc0`6YEGQ z>0nHlSmyS#IQu%!rOr`sF@CvMjH>G!o1O@1j}@T_#b)@5T(DJ;G(o>Pph4eB9;py1 z+0I1{kPi}&Fo*c!8+vD>@5U5*I0Q41C@6I*)o6G~V_Sj(^Q(!66p6)a2@dqGcJnr;9btz`?*_<`k(Z-ez9(fKS3e zB!olr(j>T3!xaq}czYvOaWIjCWs_+)D(?O9n4U)_xfh>nTFu@vVCKEMqKn`9Rk?AE z4${u(Lxog4-8(~bI|RIshKucvi-DFqm6-rqq#L}J;=g4gTN_&|ym|f7o$AjVpMrI_NQbL$oibk&SQS9zYfoT28vi_#i^D)_`)lTHfzi0R+ z6?7b7oK}w;*pC>BQgUv{;QSrk*0p+z`kVyU@4jz4myNA7+>!lB{`1338*PEgmc+vW zk|NG-cprZR!Z5yKaG=S2NA9sEal)|FJX&Yc-Z1@{7uAIr5rkK}pih3xDv1AcxA(S?vwz*tF)MM3S*{uq=FPn~^>a5)EX)MRDFRJIq_d&HTFNJ|z+Ab2* zw3$(ZV*BdNtHEERUE!iKKCgK-1T|O=T2QmBQJTYe+-+h(S(Ontg9U8R;duT{t%Fry zR{GN$tP|DmIJ_8(sA;VfU73C+0#w-49EBUWyL-EaW({eBzeoGla(ei&d|(B`j)HrY zKR4{zuNkZbfbzaNbipjupI9*x26;Ka12V@>u_L6u-i+Qs<=QrRsSZ zyl_Z-UA`W8<*5{}w)Le8;<(f~_x;HgbY#55Zs@8jA15-ti3xdT_J_QDbdXpV7b!uJ zN@>7_{Ny4uZEp) z<3dxnbr1p91y@zh0PLmZ)OT^(PG7zI?z(2?8v#%e1y2rH-0p53u~u%b;F14a6I|;e z28sm;sJq6qj`r^lm%!lG(y?CYM*>#2+ECK*_LB^GI)YvQW^7{B>4^B|=1$H|QP$wd z0mz$K;1@+M2A%#i-cDEKtXu%K2^;L?{^Uu1bJH13MIKc-_UhY zCJu3Gnv&J1tFX!ynm#*^{#olfKsa4miyC{Go9g0#?as3{H)lNOXpZ2{RSQ;7eyFWx z1#O>F-8_iOgY{ru1Ob}Y9vmx+Fx;8~O~AwE8Wh0y=iZc_x=rM0zi`o8!2{I%*7H8s zJSa=k-If$TTQ8an&1-eXSkzgMLSXg?6=Y)_oOUVe_EQ5ODcxBpl-QAD;Ip!ch5YBc zgXVqSLbv?0QWcRO@JL9U<6Cfr%|;3Bn!1jIIP>}tGyJ@WKm{z_>wpJ5*zjg)37}l+ zim`~_{y9z&**D~z)oPC{DhyKp1I-_!uNl)>P+63J*AnU4=#F(*`s<>$A?8p#Yj3xq zdc$5Dv^=|kGQED2+p*EYyW&V6JJUZCE*IPV9Q%j?)^%%qOHMfBLkMbKn1(OAi|byb ze!AD&Z!A(_3#65HodOS}SA2hjgoEmN{;Cj!$2nFxb_H@G07@9DOwrq!`JzdF{p4O{ zYtXrIYsWP^EYR)+4CH%OM)$}DcavvAM1t(E_irOvUXoh5i~ap(+w@}ld%;e@###D+ z)uc$0gXwThZbkmH)>aAur6Xnhuji(Whj!CkepJ*q=OlGk@mYznmF|XP*j8_K3eflv z_~vxxsD&s=P*I%(fcb`7K48U;7Ac!LCa)%3~%P*?yv;H2Y|6X6Mn34}Zz zSEGjQK9vX?sTe`2HQ&*S2A*i&hxKr#el>@~aZV170+a8nPIIO%6)SqSP5BscPWNLv zYYiOLeX^x3_2oaPurjgHc9{MUE?K<>J&L%3RkU`;79f0}F$8>x(AdIemXK|R?UT#& z2au&Y@U~rh-@iL<=6ankwyS$9Sct+%xMN6x!v#DUN0{|`^BOED{1I+-8W1(8X8OnBR9Pzp3t>(~Z427gsK z@xGY;YCL~HUf^%&a(h0q(5YbseRR-kE#w@7)c_MME^YogF$Lu_ja}x*iuVEJeL~%* zqM;HR@&t*3@B1VDn8)1ZKMf{1Nz%%|1S|wP{A!}rbh1O*cForK;Xeu5+9r`sdh+bv zrgu9In`;SiZb=>|iu=SQJ`Sf*Mh$!oJ9g_o7~YGB+>MKLKAX|-eokvoTkrx&vp%!c zet%!tZ;mEiOBncuen+g7w!iRKiUZ{(tbVI^;MYT3559AC`J9!TXKy8tL$)Ogi`IA_ zyG#@0b0rBO;w!}+4X!aW0Jo50Uy}FK%$dt373PI}M5!~)`;yoAJn1wMpPUH~h=koj zST-x@Uh?^xzRCnnbjDr7u*tbz>?8W&jI<>)Z(03S8ig0;tT|A~@b$tsgExz3-U?TI z{JvsuqCzu*Is(DXSk+DQdI??U@NJ6h=(~Y{q8C&3Cm1qom|_qBkbtjm7x~_jemUP; zp)Ru_t;KT+A@P@ajOpwZdtEw%2`>QO*LuGJuGQXL@iVuavU+RYif`z#g~MB|L(w?{+vRu}u)vwi37!<<)Rs>jRl&M%yQs4~@-j;q+tN97VURdYLWPuYE= zHC-vidHDh!Os&?QrJu;*n=rb;DHzxp#9n5&)Qx9QB~nc{4*;4+#mW7_w%t7QGuzwk zf7>3|_mXAhKMH<`-I@|T%qybAF|X+hK$aCTX>!%9|G;cHvD$D zd_Jjk5aW$cWH#o-my3(ik#<2Esa&0B-{J8JU79&F3jqTyu(7UTq zh8-@)KhT)aiD}4dlnM1e-5I?i1Y@9(k|oh4UXcg%cXyX-?uQm@;i1z+>jOQaMoOJz zy8e!OWy8NyBHu@Zh6N!=!jAj1QPc6j{}cGPIF<=P&Q$Q1)Au8Dsdbqu5{~o?-jgZH z|6O4lpOqe)OC}NJ>t8QK%tQ0?UZqJT7SMsYZwpQrqK89p5bl=W!GP+Rk!2UVNlgyT zA*UnQ_7BN3H?f65C=|bl>lDOLW5gM(rG;4epV-&lpL$bm$tzf)ZfAds107(u!U`&9 zF(=0~x7u*xw|PDZmwizQJ)%9L++;P@y1Nsl2O|~Zv61#yd`jR-pzX#?RB^ygP0!ZJ zmDy$-Phb7YM9aIh7~I!}4{b6*BWO6DCN;Wqk5pNd2N2qcU!FX&} zvxOQ=IysI`U)RZ}fDR1IUr?Gw=UQ@Q9?9l>oWVS+M#5a){Fs8PZevO*_Pfs~FLHV^ zf<>0<{RyVvcC?T$jN^0loDd%ulGPuZ3?MDAXFuu7Ozta=ml#%v4@1ehy>3Srl(UYT z!hg;SVp{Y_2P3c_kL`dNH_89{qn3dJ8p+n?9#Rf#_2P)qRu)-Q+8-KqWbnMi;hFAk zKlLK?p#B)`AE-#?XH6SfmNw^`PY=nBC-VO0c4t=>D@jwg_uPvEI&$XA}Y@W6Vuj|eB zGv@pQXzbZhzwx_l+PRAxGEkm!{HFg|xp(uq+mz2d9;|HW(VNh%jwEv57LKytE=7P3*yowG3XQ5mM z1McHP%iCl1TUJziP7*D?>ecPlP_ygbjmFCW0>?@W*%jtQVE|=>VQ7qCV-rT#L9mq? zJD>mq7-uw;cUtc}60hNRB>otK-e%6zo1Uys=yO>btCf1OR(a?HP2Yw8R@`Z@L$oCi ze24xMUSP!wC=8djHRD4EkWf;>PivXh|$$H=ndLgXFXb`NH9_|o-{(KBhg21L#^=Ax7eG) zPU&W&yjA3AZSXMmxj)mKvc^)lQlL~!SO@hJ+D(de5#WM-dQ87YP#zQIytHN5&dBIl zvbmzWLUXclKGi!oncDYGIzg>+q| z%yw*VSZC;dsLcA;e65!52RaEbR}1Z&4TO#;tS+)zJwL}cP!10M76XGPzy<=nG5dpN zF*xpfZR&#GFfk(6o7Ja|`N66xgcOw0jED-8n`h%Mj*~yo5qdu@2bnGp$AcDo$=#ir zl|H5ghKWtMeC$*R!f`SsbZIw7yd2ZU`P-Su#OYNxPB-UaoS|+?1c<{rc^=5=z>+9O z`=iICPaYWp=Z@3vu zg;yCIRquW2&FI!aXW((ZjkK>h`T3WRa@5hb+Tfb=y!}fF4j13iPsM&Yv%Sl@J=S4U*+w!U$!rxwT!N3 z6}zr{p_6aIxfp3xKwah`oD}|UoPQ#{$$0J{vME$9(h-^QKmxAUOL6noiZcsQBoOS5 z)!+M!L}!koD?&gJNpnxct3(|-7H<0i3>3!@XJjRo^R_<}5vPxR|3D6!!qAU#N|c~$ z-&lwV3JQ6#KC$k=cg_|oEC3JjNPofyPX`v=l9CfcN!{I69mYhLl6Y4Zqd&097=jyt zlYLS7(Ct*tGYST%1ZV6VO&cb5chI1C^XB_%tUt9p%kJH1%xC|lXA}YncB7dyt=snB zH>p#G*kB}wl}OanM>CwbW;oIl=TlQq>Ph%mIR=KX)2@WmR=6&8E}UDdxDTLrEhVkQ z-+QwMZOZtXRiy3ULHpQWw=-J_2HarqZPVBuDDUtIFqVR#M2_+!epE;D&tvYngiiww zb}cQ}4@5NZ*wI!z$PsA~Ry{`QaIosVSC}bMY*MT`d&1u?evB0^eZfq_Ep{W|yz`M6 zAnQv`*NG%`+qH)BC4_zwhBK!NTu+B9=f}&>oB7vq%g<^-tvIe=a(Ak^*5yuEC`W_& z$W2!+7M$7RMg`JSU0?f`vx#XOcvSZA{odc}tx7=<2D;H*-4>2ST#@QKNt&f{8gb*n= zSFYg>-zvLcg2^3-$+*e}!|cO~_lU)*;KA9}LYR^db#Q##FOBV#7HPW+-Vr$3QYW{`g}Vx-U~;#I+5E{R+w?*s9Y z9}&wjtvsX%C(BZ;J$tU-$IZ3LRULn*Y;sZM{!rcL#U@rub@j7;W}Ta^3o!4C!N8a~N`W0Ia(|z^GwA5Hp_Qu2`F3E9RGv2Lru@+ntxji3 zx^7`wt}g3~k)6RGcSO9%df66*ldwE8nYI1n62gaJgWXqqN!r3N^e+sIEI$jd9(=WE zdZYD*zj75K;6MAS-ql9F_HY6WfBW4Lx4wa4b{o<4x*)1h>nY|!^Mb`#F3d`9=kj9K z3FZ`MH=!{LH+Q1%#c#fm&dvZ*0hjO-hoLg*7^12bzx%SO1FdQkB%BCIo1=xgLVbcy zSt7neR?|QGtY%7oWeItz>|gC29MpXL{yxb|IM}C{RM3sX*4CES``V^RrC6@6yj-uP zI@|xLNp%SPHWxPshrw*Q9&^$;(p`gn$76B~sbDgA_Di!5d-nW_h-iHau@wpd}_!5`D<#U|$}_Y?h*j5jzq^zVaJiZ=xdu&?}|Z~RYBPsQ52u1;6GUc9zY zZZ zOPiR`ppyw9Qd3idYgyHp{qFv$R#Is*&-HLQsYF!F&BTNZS>4t2($he^!**&Sp_Ym5 zP2)CqxIWZ!*_#MCiI?lG=y*cb)6=`$n?wjhCl${Y@x4Wlh?k4d4v1Ra+6w9H6sxtF z|MF#pt6Xx7L*V~v0ZfOJZ|_e!42_Ib%5_3THoXYAHNcn5*ZXCIQsI|kxXt2c)hDITu^UZfz z)Kqel7o-yeEe>n2(A7dL{?@ZD)Z3Pf|0`5sboHk&{jACq zBAy$Nl|>dy@XjBx6 z{S~>0520c@Z{OL*fM?X2JZQs4vPH>-y)IXI9NirqyT)=QJWH}MA*VMtGLuD$RmKD8 z%SOA4P2AsjZGR>-_Zo3R4ECprZ!bngc~t0&broL#xbOc4$auCuT2 zB}jlx!3?fq@0;VfI%|Rz!_I*L1U|dPPH+iMi!qA!9!44p3W#2_OBdL04<$c5e%p$6Z~ z$;s&x1`8P+8WPvlB>_+oLhk>NaN@uZ-iZHYQdwWWEr_dvbagO`@d6$-AU&OUB^Lr& z-`FrTHtvyMk_bf&)MV)go4K~J@xt8PTz{PcPsYfI>ipv34n1EX4}bMpi~_TBv2~TR z6r`T`^V1_`;xCWbNZh{VWwJ7z26zaGpd0Gb(^G3N77-D$yZa57r{zbcByk4Z?mvHG zPn2b8DU;-y5?r-wEk2liS*eEZ;D-+%zCK{93^Bkw$@PR`$T&D~eB-l^4G%{Iu|d9e za)P0xq;$B{qLF{~bT#|Q!QEXZ2p-Mh_S7(D=J8E|3gb`pGC5;?oEVSORT$_eGX>qx zN#)XL6J#Nf2D_#1R?iEM*wFaM2<+7g)e3!KICyw)H|TbDc8wlqR4bGTMi1A2?KSId zUWH+6%LSLnxBP8OY!B2(g_({|E!PEFyO|HAObKV~5 zI_Y@U-iD#YePxQK4E#B zrj~@YN}kydMMXv3OG~7)6`!6*P5TB0^1;r`HM=qwz0cHEp-dJC5f7A4VWXs{4+8=C z5{G~Q!QS4!)%yk=GNgD;Oin(E=B$+rO(6z`a5Am;?Xn=`za%F97xHk8xTKARd+IHRAmIyib_alN5LdoTO%*6<+)Ou^{;DeT_Zu ze1ko?fZJhsmazBwpG?86W3%cXKNxoR_a`JcWFwk|#I>c7C*c_e)Mzwi8eU zB(Ncy+uP@)=#*R7w4Fp89;XJNm~31h{pI(%WLQ~?H&VzFdIb&5P&DGzgM)i5gk<$H zZ9_mP=GuJ)U0hvxFin3&Mn?y?wFxh&O33ws?;g*W!BEKH#{xmhobp%FQ#oH6ZL!%E zg_4qTcd3QX)sSjaGzU&o0+g0Wuq{rH4Z9O+^Hbnc&_YI+_%>G=w`rute6*2`73ADj zvy#4viAj@n9i3^Scy8zMQcIQTF#he`-S|5-#*z>D@OI0s5gHZxxd2cvU#p0>V}hla z7#aN!B7#~`Y5SKK6k93F#YWCQ8E;-lNJxMJC@Cf7cyWC={{jO8re+XS>~7^rAy zoX*<{3Ta#~SNxxa>)=xi{h#jWb@lWdj~CxWlM3SJOUHDt_eD7Z^g;~T^1t{sGE!jp zZ_qzEH8mW7h|zeS6hxV_KH|K8W(FTzJ~%s@d?goCrF@LPQwSLmQLMx7(ZJq5E?*|D zyT3p5B)(l6Kt1?g5RD`547B2fDu&>W=9--8qhn$m|6N+vT26+ZR3fe|4X3b!m~7XYWW28h)qzlzG<-Ti&O zR22RXod&jt$46#`a}B+nqm|(2AVF?+6PI5APGEn#cC3Jvt``)aY9!7Z(?YyK_@O*z$evuRZRs zSyw2rvX)vsac+*6HUy)&BAUg%a)LuYn5`HHM#33df}D$Wp_u>>V9-#KxNqp~m4xQu zXtt<~q-5~I&FRCPGYuV`dbk}Xh~vVvETlwCm%gVRu=)

;l4)Z@GhYNKmV9}4I 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 dze4$gK;{;W@}eHuSa1cX%7$gMn>$!x{0{HEP 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 IT)mm_A8dTne*gdg diff --git a/docs/images/composed_devices.svg b/docs/images/composed_devices.svg index fed5a09..6e83761 100644 --- a/docs/images/composed_devices.svg +++ b/docs/images/composed_devices.svg @@ -4,108 +4,118 @@ - - + + classes - + RGBLED - -RGBLED + +RGBLED PWMLED - -PWMLED + +PWMLED RGBLED->PWMLED - - + + LEDBoard - -LEDBoard + +LEDBoard LEDBoard->PWMLED - - + + LED - -LED + +LED LEDBoard->LED - - + + LEDBarGraph - -LEDBarGraph + +LEDBarGraph LEDBarGraph->PWMLED - - + + LEDBarGraph->LED - - + + TrafficLightsBuzzer - -TrafficLightsBuzzer + +TrafficLightsBuzzer TrafficLights - -TrafficLights + +TrafficLights TrafficLightsBuzzer->TrafficLights - - + + Buzzer - -Buzzer + +Buzzer TrafficLightsBuzzer->Buzzer - - + + Button - -Button + +Button TrafficLightsBuzzer->Button - - + + Robot - -Robot + +Robot Motor - -Motor + +Motor Robot->Motor + + + + +PWMOutputDevice + +PWMOutputDevice + + +Motor->PWMOutputDevice diff --git a/docs/images/composite_device_hierarchy.pdf b/docs/images/composite_device_hierarchy.pdf index 05ad15698451ae8a7374f1d359c167c37108d2ce..6579003cdab8ba889bd2c7a74589f9aeb84b5108 100644 GIT binary patch delta 8872 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 delta 8872 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 neL>3qzqj`PUni4m Date: Sat, 16 Apr 2016 00:21:51 +0100 Subject: [PATCH 005/104] Fix #278 Allow the creation of remote pins easily with PiGPIOPin. Also changes DefaultPin to a pin_factory callable which accepts the input pin number. --- docs/api_pins.rst | 35 ++++++++++++------- gpiozero/devices.py | 10 +++--- gpiozero/pins/data.py | 72 +++++++++++++++++++++------------------- gpiozero/pins/pigpiod.py | 21 +++++------- tests/test_boards.py | 4 +-- 5 files changed, 75 insertions(+), 67 deletions(-) diff --git a/docs/api_pins.rst b/docs/api_pins.rst index 83a9dae..5ad818c 100644 --- a/docs/api_pins.rst +++ b/docs/api_pins.rst @@ -25,35 +25,46 @@ integer number instead, it uses one of the following classes to provide the 4. :class:`gpiozero.pins.native.NativePin` You can change the default pin implementation by over-writing the -``DefaultPin`` global in the ``devices`` module like so:: +``pin_factory`` global in the ``devices`` module like so:: from gpiozero.pins.native import NativePin import gpiozero.devices # Force the default pin implementation to be NativePin - gpiozero.devices.DefaultPin = NativePin + gpiozero.devices.pin_factory = NativePin from gpiozero import LED # This will now use NativePin instead of RPiGPIOPin led = LED(16) +``pin_factory`` is simply a callable that accepts a single argument: the number +of the pin to be constructed (this prototype *may* be expanded in future). This +means you can define it as a function that provides additional parameters to an +underlying class. For example, to default to creating pins with +:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi``:: + + from gpiozero.pins.pigpiod import PiGPIOPin + import gpiozero.devices + + def my_pin_factory(number): + return PiGPIOPin(number, host='remote-pi') + + gpiozero.devices.pin_factory = my_pin_factory + + from gpiozero import TrafficLights + + # This will now use pins on remote-pi (assuming it has the + # pigpiod daemon installed and running) + tl = TrafficLights(13, 19, 26) + Alternatively, instead of passing an integer to the device constructor, you -can pass a :class:`Pin` object itself:: +can pass an object derived from :class:`Pin` itself:: from gpiozero.pins.native import NativePin from gpiozero import LED led = LED(NativePin(16)) -This is particularly useful with implementations that can take extra parameters -such as :class:`~gpiozero.pins.pigpiod.PiGPIOPin` which can address pins on -remote machines:: - - from gpiozero.pins.pigpiod import PiGPIOPin - from gpiozero import LED - - led = LED(PiGPIOPin(16, host='my_other_pi')) - In future, this separation of pins and devices should also permit the library to utilize pins that are part of IO extender chips. For example:: diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 7aa5e32..97c4040 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -36,18 +36,18 @@ from .exc import ( from .pins import _pins_shutdown try: from .pins.rpigpio import RPiGPIOPin - DefaultPin = RPiGPIOPin + pin_factory = RPiGPIOPin except ImportError: try: from .pins.rpio import RPIOPin - DefaultPin = RPIOPin + pin_factory = RPIOPin except ImportError: try: from .pins.pigipod import PiGPIOPin - DefaultPin = PiGPIOPin + pin_factory = PiGPIOPin except ImportError: from .pins.native import NativePin - DefaultPin = NativePin + pin_factory = NativePin _PINS = set() @@ -365,7 +365,7 @@ class GPIODevice(Device): if pin is None: raise GPIOPinMissing('No pin given') if isinstance(pin, int): - pin = DefaultPin(pin) + pin = pin_factory(pin) with _PINS_LOCK: if pin in _PINS: raise GPIOPinInUse( diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index df0a8ef..86a91e5 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -243,29 +243,28 @@ CM_SODIMM = { PI_REVISIONS = { # rev model pcb_rev released soc manufacturer ram storage usb eth wifi bt csi dsi headers - 'beta': ('B', '?', '2012Q1', 'BCM2835', '?', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0002': ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0003': ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), - '0004': ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0005': ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0006': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0007': ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0008': ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0009': ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000d': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000e': ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '000f': ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), - '0010': ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0011': ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), - '0012': ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0013': ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '0014': ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), - '0015': ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), - 'a01041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - 'a21041': ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - '900092': ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), - 'a02082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), - 'a22082': ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + 0x2: ('B', '1.0', '2012Q1', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + 0x3: ('B', '1.0', '2012Q3', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV1_P1}, ), + 0x4: ('B', '2.0', '2012Q3', 'BCM2835', 'Sony', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x5: ('B', '2.0', '2012Q4', 'BCM2835', 'Qisda', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x6: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 256, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x7: ('A', '2.0', '2013Q1', 'BCM2835', 'Egoman', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x8: ('A', '2.0', '2013Q1', 'BCM2835', 'Sony', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x9: ('A', '2.0', '2013Q1', 'BCM2835', 'Qisda', 256, 'SD', 1, 0, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xd: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), + 0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), + 0x900092: ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), + 0xa02082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + 0xa22082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), } @@ -513,9 +512,8 @@ def _parse_pi_revision(revision): # PPPP - Processor (0=2835, 1=2836, 2=2837) # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) # RRRR - Revision (0, 1, or 2) - i = int(revision, base=16) - if not (i & 0x800000): - raise ValueError('cannot parse "%s"; this is not a new-style revision' % revision) + if not (revision & 0x800000): + raise ValueError('cannot parse "%x"; this is not a new-style revision' % revision) try: model = { 0: 'A', @@ -526,15 +524,15 @@ def _parse_pi_revision(revision): 6: 'CM', 8: '3B', 9: 'Zero', - }[(i & 0xff0) >> 4] + }[(revision & 0xff0) >> 4] if model in ('A', 'B'): pcb_revision = { 0: '1.0', # is this right? 1: '1.0', 2: '2.0', - }[i & 0x0f] + }[revision & 0x0f] else: - pcb_revision = '1.%d' % (i & 0x0f) + pcb_revision = '1.%d' % (revision & 0x0f) released = { 'A': '2013Q1', 'B': '2012Q1' if pcb_revision == '1.0' else '2012Q4', @@ -549,17 +547,17 @@ def _parse_pi_revision(revision): 0: 'BCM2835', 1: 'BCM2836', 2: 'BCM2837', - }[(i & 0xf000) >> 12] + }[(revision & 0xf000) >> 12] manufacturer = { 0: 'Sony', 1: 'Egoman', 2: 'Embest', - }[(i & 0xf0000) >> 16] + }[(revision & 0xf0000) >> 16] memory = { 0: 256, 1: 512, 2: 1024, - }[(i & 0x700000) >> 20] + }[(revision & 0x700000) >> 20] storage = { 'A': 'SD', 'B': 'SD', @@ -595,7 +593,7 @@ def _parse_pi_revision(revision): 'CM': {'SODIMM': CM_SODIMM}, }.get(model, {'P1': PLUS_P1}) except KeyError: - raise ValueError('unable to parse new-style revision "%s"' % revision) + raise ValueError('unable to parse new-style revision "%x"' % revision) else: return ( model, @@ -635,6 +633,10 @@ def pi_info(revision=None): except IOError: _PI_REVISION = 'unknown' revision = _PI_REVISION + try: + revision_int = int(revision, base=16) + except ValueError: + raise PinUnknownPi('unknown RPi revision "%s"' % revision) try: ( model, @@ -651,7 +653,7 @@ def pi_info(revision=None): csi, dsi, headers, - ) = PI_REVISIONS[revision] + ) = PI_REVISIONS[revision_int] except KeyError: try: ( @@ -669,7 +671,7 @@ def pi_info(revision=None): csi, dsi, headers, - ) = _parse_pi_revision(revision) + ) = _parse_pi_revision(revision_int) except ValueError: raise PinUnknownPi('unknown RPi revision "%s"' % revision) headers = { diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index 3dafefc..a14ef9e 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -98,25 +98,20 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} - PI_INFO = None - def __new__(cls, number, host='localhost', port=8888): - # XXX What about remote pins? This should probably be instance - # specific rather than class specific for pigpio. Need to check how - # to query remote info though... - if cls.PI_INFO is None: - cls.PI_INFO = pi_info() try: return cls._PINS[(host, port, number)] except KeyError: self = super(PiGPIOPin, cls).__new__(cls) try: - self._connection = cls._CONNECTIONS[(host, port)] + self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] except KeyError: self._connection = pigpio.pi(host, port) - cls._CONNECTIONS[(host, port)] = self._connection + revision = hex(self._connection.get_hardware_revision())[2:] + self._pi_info = pi_info(revision) + cls._CONNECTIONS[(host, port)] = (self._connection, self._pi_info) try: - cls.PI_INFO.physical_pin('GPIO%d' % number) + self._pi_info.physical_pin('GPIO%d' % number) except PinNoPins: warnings.warn( PinNonPhysical( @@ -124,7 +119,7 @@ class PiGPIOPin(Pin): self._host = host self._port = port self._number = number - self._pull = 'up' if cls.PI_INFO.pulled_up('GPIO%d' % number) else 'floating' + self._pull = 'up' if self._pi_info.pulled_up('GPIO%d' % number) else 'floating' self._pwm = False self._bounce = None self._when_changed = None @@ -167,7 +162,7 @@ class PiGPIOPin(Pin): self.frequency = None self.when_changed = None self.function = 'input' - self.pull = 'up' if self.PI_INFO.pulled_up('GPIO%d' % self.number) else 'floating' + self.pull = 'up' if self._pi_info.pulled_up('GPIO%d' % self.number) else 'floating' def _get_function(self): return self.GPIO_FUNCTION_NAMES[self._connection.get_mode(self._number)] @@ -204,7 +199,7 @@ class PiGPIOPin(Pin): def _set_pull(self, value): if self.function != 'input': raise PinFixedPull('cannot set pull on non-input pin %r' % self) - if value != 'up' and self.PI_INFO.pulled_up('GPIO%d' % self._number): + if value != 'up' and self._pi_info.pulled_up('GPIO%d' % self._number): raise PinFixedPull('%r has a physical pull-up resistor' % self) try: self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[value]) diff --git a/tests/test_boards.py b/tests/test_boards.py index 304e128..627325e 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -19,9 +19,9 @@ def setup_function(function): import gpiozero.devices # dirty, but it does the job if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'): - gpiozero.devices.DefaultPin = MockPWMPin + gpiozero.devices.pin_factory = MockPWMPin else: - gpiozero.devices.DefaultPin = MockPin + gpiozero.devices.pin_factory = MockPin def teardown_function(function): MockPin.clear_pins() From 8f9799f0cbbe3ecb20a543244829f87241323adb Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Sun, 17 Apr 2016 10:24:49 +0100 Subject: [PATCH 006/104] Include tests in release source tarball I'm thinking about packaging gpiozero so it can be included in Debian. It would be great to include the test suite in the pypi tarball so the tests can be run when the Debian package is built. --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 9561fb1..40b7bf4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include README.rst +recursive-include tests *.py From 40f64bc2b56398bfd3ba4a020be38fe851b78564 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Sun, 17 Apr 2016 23:10:35 +0100 Subject: [PATCH 007/104] correct spelling of Artificial (#285) * correct spelling of Artificial * add extra = to fix heading --- docs/api_tools.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api_tools.rst b/docs/api_tools.rst index f23cf71..8b46d2b 100644 --- a/docs/api_tools.rst +++ b/docs/api_tools.rst @@ -54,8 +54,8 @@ Combining sources .. autofunction:: averaged -Artifical sources -================= +Artificial sources +================== .. autofunction:: cos_values From 53b78f1e327cb69aa9e77d27a4fbb657c42a04ec Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Apr 2016 16:14:31 +0100 Subject: [PATCH 008/104] Minor docs fix (#286) * Minor docs fix * Another doc fix --- gpiozero/boards.py | 2 +- gpiozero/input_devices.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 904f137..a42dc5f 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -572,7 +572,7 @@ class SnowPi(LEDBoard): class TrafficLightsBuzzer(CompositeOutputDevice): """ - Extends :class:`CompositeDevice` and is a generic class for HATs with + Extends :class:`CompositeOutputDevice` and is a generic class for HATs with traffic lights, a button and a buzzer. :param TrafficLights lights: diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 3807d84..3f38a86 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -279,7 +279,7 @@ Button.wait_for_release = Button.wait_for_inactive class LineSensor(SmoothedInputDevice): """ - Extends :class:`DigitalInputDevice` and represents a single pin line sensor + Extends :class:`SmoothedInputDevice` and represents a single pin line sensor like the TCRT5000 infra-red proximity sensor found in the `CamJam #3 EduKit`_. From af3476d570146b3d99145d1fb475c03736acc850 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Apr 2016 17:25:23 +0100 Subject: [PATCH 009/104] Update Recipes to use the generic Robot class instead of the specific RyanteckRobot Fixes #288 --- docs/recipes.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index ed02720..f77a1f7 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -539,10 +539,10 @@ Button controlled robot Use four GPIO buttons as forward/back/left/right controls for a robot:: - from gpiozero import RyanteckRobot, Button + from gpiozero import Robot, Button from signal import pause - robot = RyanteckRobot() + robot = Robot(left=(4, 14), right=(17, 18)) left = Button(26) right = Button(16) @@ -570,9 +570,9 @@ Keyboard controlled robot Use up/down/left/right keys to control a robot:: import curses - from gpiozero import RyanteckRobot + from gpiozero import Robot - robot = RyanteckRobot() + robot = Robot(left=(4, 14), right=(17, 18)) actions = { curses.KEY_UP: robot.forward, @@ -614,10 +614,10 @@ If you prefer a version that works under IDLE, the following recipe should suffice, but will require that you install the evdev library with ``sudo pip install evdev`` first:: - from gpiozero import RyanteckRobot + from gpiozero import Robot from evdev import InputDevice, list_devices, ecodes - robot = RyanteckRobot() + robot = Robot(left=(4, 14), right=(17, 18)) devices = [InputDevice(device) for device in list_devices()] keyboard = devices[0] # this may vary From 9a02029c159b3412a983dad1dbcfd5a81324b5fc Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Apr 2016 21:51:15 +0100 Subject: [PATCH 010/104] Add docs/_build to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9b62b37..583b828 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ coverage .coverage .tox .cache + +# Generated documentation +docs/_build From 9ca4af39de1cea4108cd1981683db5988e401c42 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Apr 2016 22:02:01 +0100 Subject: [PATCH 011/104] Add extra Source Tools functions: smoothed, summed and multiplied --- docs/api_tools.rst | 6 ++++ gpiozero/tools.py | 75 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_tools.py | 22 +++++++++++++ 3 files changed, 103 insertions(+) diff --git a/docs/api_tools.rst b/docs/api_tools.rst index 8b46d2b..4add9c6 100644 --- a/docs/api_tools.rst +++ b/docs/api_tools.rst @@ -43,6 +43,8 @@ Single source conversions .. autofunction:: queued +.. autofunction:: smoothed + .. autofunction:: scaled Combining sources @@ -54,6 +56,10 @@ Combining sources .. autofunction:: averaged +.. autofunction:: summed + +.. autofunction:: multiplied + Artificial sources ================== diff --git a/gpiozero/tools.py b/gpiozero/tools.py index 11a079a..4268cea 100644 --- a/gpiozero/tools.py +++ b/gpiozero/tools.py @@ -218,6 +218,54 @@ def averaged(*values): yield mean(v) +def summed(*values): + """ + Returns the sum of all supplied values. One or more *values* can be + specified. For example, to light a :class:`PWMLED` as the (scaled) sum of + several potentiometers connected to an :class:`MCP3008` ADC:: + + from gpiozero import MCP3008, PWMLED + from gpiozero.tools import summed, scaled + from signal import pause + + pot1 = MCP3008(channel=0) + pot2 = MCP3008(channel=1) + pot3 = MCP3008(channel=2) + led = PWMLED(4) + led.source = scaled(summed(pot1.values, pot2.values, pot3.values), 0, 1, 0, 3) + pause() + """ + for v in zip(*values): + yield sum(v) + + +def multiplied(*values): + """ + Returns the product of all supplied values. One or more *values* can be + specified. For example, to light a :class:`PWMLED` as the product (i.e. + multiplication) of several potentiometers connected to an :class:`MCP3008` + ADC:: + + from gpiozero import MCP3008, PWMLED + from gpiozero.tools import multiplied + from signal import pause + + pot1 = MCP3008(channel=0) + pot2 = MCP3008(channel=1) + pot3 = MCP3008(channel=2) + led = PWMLED(4) + led.source = multiplied(pot1.values, pot2.values, pot3.values) + pause() + """ + def _product(it): + p = 1 + for n in it: + p *= n + return p + for v in zip(*values): + yield _product(v) + + def queued(values, qsize): """ Queues up readings from *values* (the number of readings queued is @@ -248,6 +296,33 @@ def queued(values, qsize): break +def smoothed(values, qsize, average=mean): + """ + Queues up readings from *values* (the number of readings queued is + determined by *qsize*) and begins yielding the *average* of the last + *qsize* values when the queue is full. The larger the *qsize*, the more the + values are smoothed. For example, to smooth the analog values read from an + ADC:: + + from gpiozero import MCP3008 + from gpiozero.tools import smoothed + + with MCP3008(channel=0) as adc: + for smoothvalue in smoothed(adc.values, 5): + print smoothvalue + """ + q = [] + it = iter(values) + for i in range(qsize): + q.append(next(it)) + for i in cycle(range(qsize)): + yield average(q) + try: + q[i] = next(it) + except StopIteration: + break + + def pre_delayed(values, delay): """ Waits for *delay* seconds before returning each item from *values*. diff --git a/tests/test_tools.py b/tests/test_tools.py index 9749b80..422dbec 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -16,6 +16,14 @@ try: from math import isclose except ImportError: from gpiozero.compat import isclose +try: + from statistics import mean +except ImportError: + from gpiozero.compat import mean +try: + from statistics import median +except ImportError: + from gpiozero.compat import median def test_negated(): @@ -56,10 +64,24 @@ def test_averaged(): assert list(averaged(())) == [] assert list(averaged((0, 0.5, 1), (1, 1, 1))) == [0.5, 0.75, 1] +def test_summed(): + assert list(summed(())) == [] + assert list(summed((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [1, 1, 1.5, 2] + +def test_multiplied(): + assert list(multiplied(())) == [] + assert list(multiplied((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [0, 0.25, 0.5, 1] + def test_queued(): assert list(queued((1, 2, 3, 4, 5), 5)) == [1] assert list(queued((1, 2, 3, 4, 5, 6), 5)) == [1, 2] +def test_smoothed(): + assert list(smoothed((1, 2, 3, 4, 5), 5)) == [3.0] + assert list(smoothed((1, 2, 3, 4, 5, 6), 5)) == [3.0, 4.0] + assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=mean)) == [2.4, 3.2] + assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=median)) == [1, 4] + def test_pre_delayed(): start = time() for v in pre_delayed((0, 0, 0), 0.01): From 5f336ad2b290b2b63227149424145c63b9f6b421 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 19 Apr 2016 22:16:42 +0100 Subject: [PATCH 012/104] Make Potentiometer Recipe more efficient --- docs/recipes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index ed02720..7b26bc2 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -676,8 +676,8 @@ connected to a :class:`MCP3008` analog to digital converter:: from gpiozero import MCP3008 - while True: - with MCP3008(channel=0) as pot: + with MCP3008(channel=0) as pot: + while True: print(pot.value) Present the value of a potentiometer on an LED bar graph using PWM to represent From 848d030ac9d1611c915965f0f77bd72199b64b0f Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Thu, 21 Apr 2016 23:55:04 +0100 Subject: [PATCH 013/104] Remove with blocks for consistency, re: #239 --- docs/recipes.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 7b26bc2..ef6482b 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -221,14 +221,14 @@ Capture a picture with the camera module every time a button is pressed:: from picamera import PiCamera button = Button(2) + camera = PiCamera() - with PiCamera() as camera: - camera.start_preview() - frame = 1 - while True: - button.wait_for_press() - camera.capture('/home/pi/frame%03d.jpg' % frame) - frame += 1 + camera.start_preview() + frame = 1 + while True: + button.wait_for_press() + camera.capture('/home/pi/frame%03d.jpg' % frame) + frame += 1 See `Push Button Stop Motion`_ for a full resource. @@ -676,9 +676,10 @@ connected to a :class:`MCP3008` analog to digital converter:: from gpiozero import MCP3008 - with MCP3008(channel=0) as pot: - while True: - print(pot.value) + pot = MCP3008(channel=0) + + while True: + print(pot.value) Present the value of a potentiometer on an LED bar graph using PWM to represent states that won't "fill" an LED:: From d244b9a0abdbef1c261c3cfe39b0d9843237a8c5 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sun, 24 Apr 2016 20:05:39 +0100 Subject: [PATCH 014/104] Add coverage badge to README --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 95ce7f3..22c832a 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,10 @@ gpiozero :target: https://travis-ci.org/RPi-Distro/python-gpiozero :alt: Build Tests +.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 + :target: https://codecov.io/github/RPi-Distro/python-gpiozero + :alt: Code Coverage + A simple interface to everyday GPIO components used with Raspberry Pi. Created by `Ben Nuttall`_ of the `Raspberry Pi Foundation`_, `Dave Jones`_, and From 01d5cb928f3415658d76a5322dac1f607bd654ec Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 25 Apr 2016 10:41:27 +0100 Subject: [PATCH 015/104] New source tools: booleanized, pre_periodic_filtered & post_periodic_filtered Also adds extra parameter validation to the existing source tools, adds input min and max to inverted, and adds many more source tools unit tests. --- docs/api_tools.rst | 12 +++- gpiozero/tools.py | 169 ++++++++++++++++++++++++++++++++++++++++---- tests/test_tools.py | 168 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 330 insertions(+), 19 deletions(-) diff --git a/docs/api_tools.rst b/docs/api_tools.rst index 4add9c6..69b2a60 100644 --- a/docs/api_tools.rst +++ b/docs/api_tools.rst @@ -10,7 +10,7 @@ the :attr:`~gpiozero.SourceMixin.source` and library. These utility routines are in the ``tools`` module of GPIO Zero and are typically imported as follows:: - from gpiozero.tools import scaled, negated, conjunction + from gpiozero.tools import scaled, negated, all_values Given that :attr:`~gpiozero.SourceMixin.source` and :attr:`~gpiozero.ValuesMixin.values` deal with infinite iterators, another @@ -29,6 +29,8 @@ Single source conversions .. autofunction:: absoluted +.. autofunction:: booleanized + .. autofunction:: clamped .. autofunction:: inverted @@ -37,8 +39,12 @@ Single source conversions .. autofunction:: post_delayed +.. autofunction:: post_periodic_filtered + .. autofunction:: pre_delayed +.. autofunction:: pre_periodic_filtered + .. autofunction:: quantized .. autofunction:: queued @@ -56,10 +62,10 @@ Combining sources .. autofunction:: averaged -.. autofunction:: summed - .. autofunction:: multiplied +.. autofunction:: summed + Artificial sources ================== diff --git a/gpiozero/tools.py b/gpiozero/tools.py index 4268cea..c3611f2 100644 --- a/gpiozero/tools.py +++ b/gpiozero/tools.py @@ -41,10 +41,13 @@ def negated(values): yield not v -def inverted(values): +def inverted(values, input_min=0, input_max=1): """ - Returns the inversion of the supplied values (1 becomes 0, 0 becomes 1, - 0.1 becomes 0.9, etc.). For example:: + Returns the inversion of the supplied values (*input_min* becomes + *input_max*, *input_max* becomes *input_min*, `input_min + 0.1` becomes + `input_max - 0.1`, etc.). All items in *values* are assumed to be between + *input_min* and *input_max* (which default to 0 and 1 respectively), and + the output will be in the same range. For example:: from gpiozero import MCP3008, PWMLED from gpiozero.tools import inverted @@ -55,8 +58,10 @@ def inverted(values): led.source = inverted(pot.values) pause() """ + if input_min >= input_max: + raise ValueError('input_min must be smaller than input_max') for v in values: - yield 1 - v + yield input_min + input_max - v def scaled(values, output_min, output_max, input_min=0, input_max=1): @@ -82,6 +87,8 @@ def scaled(values, output_min, output_max, input_min=0, input_max=1): *input_max* (inclusive) then the function will not produce values that lie within *output_min* to *output_max* (inclusive). """ + if input_min >= input_max: + raise ValueError('input_min must be smaller than input_max') input_size = input_max - input_min output_size = output_max - output_min for v in values: @@ -104,6 +111,8 @@ def clamped(values, output_min=0, output_max=1): led.source = clamped(pot.values, 0.5, 1.0) pause() """ + if output_min >= output_max: + raise ValueError('output_min must be smaller than output_max') for v in values: yield min(max(v, output_min), output_max) @@ -128,11 +137,11 @@ def absoluted(values): yield abs(v) -def quantized(values, steps, output_min=0, output_max=1): +def quantized(values, steps, input_min=0, input_max=1): """ Returns *values* quantized to *steps* increments. All items in *values* are - assumed to be between *output_min* and *output_max* (use :func:`scaled` to - ensure this if necessary). + assumed to be between *input_min* and *input_max* (which default to 0 and + 1 respectively), and the output will be in the same range. For example, to quantize values between 0 and 1 to 5 "steps" (0.0, 0.25, 0.5, 0.75, 1.0):: @@ -146,9 +155,72 @@ def quantized(values, steps, output_min=0, output_max=1): led.source = quantized(pot.values, 4) pause() """ - output_size = output_max - output_min - for v in scaled(values, 0, 1, output_min, output_max): - yield ((int(v * steps) / steps) * output_size) + output_min + if steps < 1: + raise ValueError("steps must be 1 or larger") + if input_min >= input_max: + raise ValueError('input_min must be smaller than input_max') + input_size = input_max - input_min + for v in scaled(values, 0, 1, input_min, input_max): + yield ((int(v * steps) / steps) * input_size) + input_min + + +def booleanized(values, min_value, max_value, hysteresis=0): + """ + Returns True for each item in *values* between *min_value* and + *max_value*, and False otherwise. *hysteresis* can optionally be used to + add `hysteresis`_ which prevents the output value rapidly flipping when + the input value is fluctuating near the *min_value* or *max_value* + thresholds. For example, to light an LED only when a potentiometer is + between 1/4 and 3/4 of its full range:: + + from gpiozero import LED, MCP3008 + from gpiozero.tools import booleanized + from signal import pause + + led = LED(4) + pot = MCP3008(channel=0) + led.source = booleanized(pot.values, 0.25, 0.75) + pause() + + .. _hysteresis: https://en.wikipedia.org/wiki/Hysteresis + """ + if min_value >= max_value: + raise ValueError('min_value must be smaller than max_value') + min_value = float(min_value) + max_value = float(max_value) + if hysteresis < 0: + raise ValueError("hysteresis must be 0 or larger") + else: + hysteresis = float(hysteresis) + if (max_value - min_value) <= hysteresis: + raise ValueError('The gap between min_value and max_value must be larger than hysteresis') + last_state = None + for v in values: + if v < min_value: + new_state = 'below' + elif v > max_value: + new_state = 'above' + else: + new_state = 'in' + switch = False + if last_state == None or not hysteresis: + switch = True + elif new_state == last_state: + pass + else: # new_state != last_state + if last_state == 'below' and new_state == 'in': + switch = v >= min_value + hysteresis + elif last_state == 'in' and new_state == 'below': + switch = v < min_value - hysteresis + elif last_state == 'in' and new_state == 'above': + switch = v > max_value + hysteresis + elif last_state == 'above' and new_state == 'in': + switch = v <= max_value - hysteresis + else: # above->below or below->above + switch = True + if switch: + last_state = new_state + yield last_state == 'in' def all_values(*values): @@ -284,6 +356,8 @@ def queued(values, qsize): leds[4].source = btn.values pause() """ + if qsize < 1: + raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): @@ -308,9 +382,11 @@ def smoothed(values, qsize, average=mean): from gpiozero.tools import smoothed with MCP3008(channel=0) as adc: - for smoothvalue in smoothed(adc.values, 5): - print smoothvalue + for value in smoothed(adc.values, 5): + print value """ + if qsize < 1: + raise ValueError("qsize must be 1 or larger") q = [] it = iter(values) for i in range(qsize): @@ -327,6 +403,8 @@ def pre_delayed(values, delay): """ Waits for *delay* seconds before returning each item from *values*. """ + if delay < 0: + raise ValueError("delay must be 0 or larger") for v in values: sleep(delay) yield v @@ -336,11 +414,78 @@ def post_delayed(values, delay): """ Waits for *delay* seconds after returning each item from *values*. """ + if delay < 0: + raise ValueError("delay must be 0 or larger") for v in values: yield v sleep(delay) +def pre_periodic_filtered(values, block, repeat_after): + """ + Blocks the first *block* items from *values*, repeating the block after + every *repeat_after* items, if *repeat_after* is non-zero. For example, to + discard the first 50 values read from an ADC:: + + from gpiozero import MCP3008 + from gpiozero.tools import pre_periodic_filtered + + with MCP3008(channel=0) as adc: + for value in pre_periodic_filtered(adc.values, 50, 0): + print value + + Or to only display every even item read from an ADC:: + + from gpiozero import MCP3008 + from gpiozero.tools import pre_periodic_filtered + + with MCP3008(channel=0) as adc: + for value in pre_periodic_filtered(adc.values, 1, 1): + print value + """ + if block < 1: + raise ValueError("block must be 1 or larger") + if repeat_after < 0: + raise ValueError("repeat_after must be 0 or larger") + it = iter(values) + if repeat_after == 0: + for _ in range(block): + next(it) + while True: + yield next(it) + else: + while True: + for _ in range(block): + next(it) + for _ in range(repeat_after): + yield next(it) + + +def post_periodic_filtered(values, repeat_after, block): + """ + After every *repeat_after* items, blocks the next *block* items from + *values*. Note that unlike :func:`pre_periodic_filtered`, *repeat_after* + can't be 0. For example, to block every tenth item read from an ADC:: + + from gpiozero import MCP3008 + from gpiozero.tools import post_periodic_filtered + + with MCP3008(channel=0) as adc: + for value in post_periodic_filtered(adc.values, 9, 1): + print value + """ + if repeat_after < 1: + raise ValueError("repeat_after must be 1 or larger") + if block < 1: + raise ValueError("block must be 1 or larger") + it = iter(values) + while True: + for _ in range(repeat_after): + yield next(it) + for _ in range(block): + next(it) + + def random_values(): """ Provides an infinite source of random values between 0 and 1. For example, diff --git a/tests/test_tools.py b/tests/test_tools.py index 422dbec..adeef77 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -31,24 +31,108 @@ def test_negated(): assert list(negated((True, True, False, False))) == [False, False, True, True] def test_inverted(): + with pytest.raises(ValueError): + list(inverted((), 0, 0)) + with pytest.raises(ValueError): + list(inverted((), 1, 1)) + with pytest.raises(ValueError): + list(inverted((), 1, 0)) assert list(inverted(())) == [] assert list(inverted((1, 0, 0.1, 0.5))) == [0, 1, 0.9, 0.5] + assert list(inverted((1, 0, 0.1, 0.5), 0, 1)) == [0, 1, 0.9, 0.5] + assert list(inverted((-1, 0, -0.1, -0.5), -1, 0)) == [0, -1, -0.9, -0.5] + assert list(inverted((1, 0, 0.1, 0.5, -1, -0.1, -0.5), -1, 1)) == [-1, 0, -0.1, -0.5, 1, 0.1, 0.5] + assert list(inverted((2, 1, 1.1, 1.5), 1, 2)) == [1, 2, 1.9, 1.5] def test_scaled(): + with pytest.raises(ValueError): + list(scaled((), 0, 1, 0, 0)) + with pytest.raises(ValueError): + list(scaled((), 0, 1, 1, 1)) + with pytest.raises(ValueError): + list(scaled((), 0, 1, 1, 0)) + assert list(scaled((), 0, 1)) == [] + # no scale assert list(scaled((0, 1, 0.5, 0.1), 0, 1)) == [0, 1, 0.5, 0.1] + assert list(scaled((0, 1, 0.5, 0.1), 0, 1, 0, 1)) == [0, 1, 0.5, 0.1] + # multiply by 2 + assert list(scaled((0, 1, 0.5, 0.1), 0, 2, 0, 1)) == [0, 2, 1, 0.2] + # add 1 + assert list(scaled((0, 1, 0.5, 0.1), 1, 2, 0, 1)) == [1, 2, 1.5, 1.1] + # multiply by 2 then add 1 + assert list(scaled((0, 1, 0.5, 0.1), 1, 3, 0, 1)) == [1, 3, 2, 1.2] + # add 1 then multiply by 2 + assert list(scaled((0, 1, 0.5, 0.1), 2, 4, 0, 1)) == [2, 4, 3, 2.2] + # invert + assert list(scaled((0, 1, 0.5, 0.1), 1, 0, 0, 1)) == [1, 0, 0.5, 0.9] + # multiply by -1 then subtract 1 + assert list(scaled((0, 1, 0.5, 0.1), -1, -2, 0, 1)) == [-1, -2, -1.5, -1.1] + # scale 0->1 to -1->+1 assert list(scaled((0, 1, 0.5, 0.1), -1, 1)) == [-1, 1, 0.0, -0.8] + assert list(scaled((0, 1, 0.5, 0.1), -1, 1, 0, 1)) == [-1, 1, 0.0, -0.8] + # scale -1->+1 to 0->1 + assert list(scaled((-1, 1, 0.0, -0.5), 0, 1, -1, 1)) == [0, 1, 0.5, 0.25] def test_clamped(): - assert list(clamped((-1, 0, 1, 2))) == [0, 0, 1, 1] + with pytest.raises(ValueError): + list(clamped((), 0, 0)) + with pytest.raises(ValueError): + list(clamped((), 1, 1)) + with pytest.raises(ValueError): + list(clamped((), 1, 0)) + assert list(clamped(())) == [] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2))) == [0, 0, 0, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), 0, 1)) == [0, 0, 0, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -1, 1)) == [-1, -1, -0.5, 0, 0.5, 1, 1] + assert list(clamped((-2, -1, -0.5, 0, 0.5, 1, 2), -2, 2)) == [-2, -1, -0.5, 0, 0.5, 1, 2] def test_absoluted(): + assert list(absoluted(())) == [] assert list(absoluted((-2, -1, 0, 1, 2))) == [2, 1, 0, 1, 2] def test_quantized(): + with pytest.raises(ValueError): + list(quantized((), 0)) + with pytest.raises(ValueError): + list(quantized((), 4, 0, 0)) + with pytest.raises(ValueError): + list(quantized((), 4, 1, 1)) + with pytest.raises(ValueError): + list(quantized((), 4, 1, 0)) + assert list(quantized((), 4)) == [] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4)) == [ 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] + assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 4, 0, 1)) == [ + 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 1.0] assert list(quantized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 5)) == [ 0.0, 0.0, 0.2, 0.2, 0.4, 0.4, 0.6, 0.6, 0.8, 0.8, 1.0] + assert list(quantized((0, 0.25, 0.5, 0.75, 1.0, 1.5, 1.75, 2.0), 2, 0, 2)) == [ + 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0] + assert list(quantized((1, 1.25, 1.5, 1.75, 2.0, 2.5, 2.75, 3.0), 2, 1, 3)) == [ + 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0] + +def test_booleanized(): + with pytest.raises(ValueError): + list(booleanized((), 0, 0)) + with pytest.raises(ValueError): + list(booleanized((), 1, 1)) + with pytest.raises(ValueError): + list(booleanized((), 1, 0)) + with pytest.raises(ValueError): + list(booleanized((), 0, 0.5, -0.2)) + with pytest.raises(ValueError): + list(booleanized((), 0, 0.5, 0.5)) + assert list(booleanized((), 0, 0.5)) == [] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1), 0, 0.5)) == [ + True, True, True, True, True, True, False, False, False, False, False] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75)) == [ + False, False, False, True, True, True, True, True, False, False, False, False] + assert list(booleanized((0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0), 0.25, 0.75, 0.2)) == [ + False, False, False, False, False, True, True, True, True, True, False, False] + assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75)) == [ + False, False, False, True, True, True, True, True, False, False, False, False] + assert list(booleanized((1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0, 1), 0.25, 0.75, 0.2)) == [ + False, False, False, False, False, True, True, True, True, True, False, False] def test_all_values(): assert list(all_values(())) == [] @@ -62,52 +146,128 @@ def test_any_values(): def test_averaged(): assert list(averaged(())) == [] + assert list(averaged((0, 0.5, 1))) == [0, 0.5, 1] assert list(averaged((0, 0.5, 1), (1, 1, 1))) == [0.5, 0.75, 1] def test_summed(): assert list(summed(())) == [] + assert list(summed((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(summed((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [1, 1, 1.5, 2] def test_multiplied(): assert list(multiplied(())) == [] + assert list(multiplied((0, 0.5, 0.5, 1))) == [0, 0.5, 0.5, 1] assert list(multiplied((0, 0.5, 0.5, 1), (1, 0.5, 1, 1))) == [0, 0.25, 0.5, 1] def test_queued(): + with pytest.raises(ValueError): + list(queued((), 0)) + assert list(queued((), 5)) == [] assert list(queued((1, 2, 3, 4, 5), 5)) == [1] assert list(queued((1, 2, 3, 4, 5, 6), 5)) == [1, 2] def test_smoothed(): + with pytest.raises(ValueError): + list(smoothed((), 0)) + assert list(smoothed((), 5)) == [] assert list(smoothed((1, 2, 3, 4, 5), 5)) == [3.0] assert list(smoothed((1, 2, 3, 4, 5, 6), 5)) == [3.0, 4.0] + assert list(smoothed((1, 2, 3, 4, 5, 6), 5, average=mean)) == [3.0, 4.0] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=mean)) == [2.4, 3.2] assert list(smoothed((1, 1, 1, 4, 5, 5), 5, average=median)) == [1, 4] def test_pre_delayed(): + with pytest.raises(ValueError): + list(pre_delayed((), -1)) + assert list(pre_delayed((), 0.01)) == [] + count = 0 start = time() for v in pre_delayed((0, 0, 0), 0.01): + count += 1 assert v == 0 assert time() - start >= 0.01 start = time() + assert count == 3 def test_post_delayed(): + with pytest.raises(ValueError): + list(post_delayed((), -1)) + assert list(post_delayed((), 0.01)) == [] + count = 0 start = time() for v in post_delayed((1, 2, 2), 0.01): + count += 1 if v == 1: assert time() - start < 0.01 - else: + elif v == 2: assert time() - start >= 0.01 + else: + assert False start = time() assert time() - start >= 0.01 + assert count == 3 + +def test_pre_periodic_filtered(): + with pytest.raises(ValueError): + list(pre_periodic_filtered((), 2, -1)) + with pytest.raises(ValueError): + list(pre_periodic_filtered((), 0, 0)) + assert list(pre_periodic_filtered((), 2, 0)) == [] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 0)) == [3, 4, 5, 6, 7, 8, 9, 10] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [2, 4, 6, 8, 10] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [2, 3, 5, 6, 8, 9] + assert list(pre_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [3, 6, 9] + +def test_post_periodic_filtered(): + with pytest.raises(ValueError): + list(post_periodic_filtered((), 1, 0)) + with pytest.raises(ValueError): + list(post_periodic_filtered((), 0, 1)) + assert list(pre_periodic_filtered((), 1, 1)) == [] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 1)) == [1, 3, 5, 7, 9] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 1, 2)) == [1, 4, 7, 10] + assert list(post_periodic_filtered((1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 1)) == [1, 2, 4, 5, 7, 8, 10] def test_random_values(): - for i, v in zip(range(1000), random_values()): + for _, v in zip(range(1000), random_values()): assert 0 <= v <= 1 def test_sin_values(): + firstval = None for i, v in zip(range(1000), sin_values()): + assert -1 <= v <= 1 assert isclose(v, sin(radians(i)), abs_tol=1e-9) + if i == 0: + firstval = v + else: + if i % 360 == 0: + assert v == firstval + for period in (360, 100): + firstval = None + for i, v in zip(range(1000), sin_values(period)): + assert -1 <= v <= 1 + if i == 0: + firstval = v + else: + if i % period == 0: + assert v == firstval def test_cos_values(): + firstval = None for i, v in zip(range(1000), cos_values()): + assert -1 <= v <= 1 assert isclose(v, cos(radians(i)), abs_tol=1e-9) - + if i == 0: + firstval = v + else: + if i % 360 == 0: + assert v == firstval + for period in (360, 100): + firstval = None + for i, v in zip(range(1000), cos_values(period)): + assert -1 <= v <= 1 + if i == 0: + firstval = v + else: + if i % period == 0: + assert v == firstval From c522d60e25cac46855b51aabbae955135975962d Mon Sep 17 00:00:00 2001 From: Alex Eames Date: Thu, 28 Apr 2016 15:36:51 +0100 Subject: [PATCH 016/104] Update recipes.rst Small correction. LED to PWMLED in the Light Sensor 2nd example --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index ef6482b..a966fac 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -442,7 +442,7 @@ Run a function when the light changes:: Or make a :class:`PWMLED` change brightness according to the detected light level:: - from gpiozero import LightSensor, LED + from gpiozero import LightSensor, PWMLED from signal import pause sensor = LightSensor(18) From 3e8a998661ae2a9f2fe4975c28f45074018e54ab Mon Sep 17 00:00:00 2001 From: goloplo Date: Thu, 28 Apr 2016 21:07:37 +0100 Subject: [PATCH 017/104] Update output_devices.py --- gpiozero/output_devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 037f749..189a581 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -753,8 +753,8 @@ class Motor(SourceMixin, CompositeDevice): 'forward and backward pins must be provided' ) super(Motor, self).__init__( - forward_device=PWMOutputDevice(forward), - backward_device=PWMOutputDevice(backward), + forward_device=DigitalOutputDevice(forward), + backward_device=DigitalOutputDevice(backward), _order=('forward_device', 'backward_device')) @property From 708157c8fd95a415b1ec84d5aa81590b8d138463 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 30 Apr 2016 12:19:45 +0100 Subject: [PATCH 018/104] Minor Energenie tweaks --- gpiozero/boards.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index a42dc5f..16a5bb3 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -846,6 +846,7 @@ class Energenie(SourceMixin, Device): raise EnergenieSocketMissing('socket number must be provided') if not (1 <= socket <= 4): raise EnergenieBadSocket('socket number must be between 1 and 4') + self._value = None super(Energenie, self).__init__() self._socket = socket self._master = _EnergenieMaster() @@ -877,8 +878,9 @@ class Energenie(SourceMixin, Device): @value.setter def value(self, value): - self._master.transmit(self._socket, bool(value)) - self._value = bool(value) + value = bool(value) + self._master.transmit(self._socket, value) + self._value = value def on(self): self.value = True From 28afa63ba84eee400da7b601480d9bfa8abd11ef Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 30 Apr 2016 12:24:05 +0100 Subject: [PATCH 019/104] test tweak --- tests/test_boards.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_boards.py b/tests/test_boards.py index 627325e..80668a1 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -430,6 +430,8 @@ def test_energenie_bad_init(): Energenie() with pytest.raises(ValueError): Energenie(0) + with pytest.raises(ValueError): + Energenie(5) def test_energenie(): pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)] From 864c4f93278cc7a2ef197329df56a0f84ed0a546 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 30 Apr 2016 14:12:51 +0100 Subject: [PATCH 020/104] test Energenie repr --- tests/test_boards.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_boards.py b/tests/test_boards.py index 80668a1..8b12dea 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -437,6 +437,8 @@ def test_energenie(): pins = [MockPin(n) for n in (17, 22, 23, 27, 24, 25)] with Energenie(1, initial_value=True) as device1, \ Energenie(2, initial_value=False) as device2: + assert repr(device1) == '' + assert repr(device2) == '' assert device1.value assert not device2.value [pin.clear_states() for pin in pins] @@ -457,4 +459,5 @@ def test_energenie(): pins[3].assert_states_and_times([(0.0, True), (0.0, True)]) pins[4].assert_states_and_times([(0.0, False)]) pins[5].assert_states_and_times([(0.0, False), (0.1, True), (0.25, False)]) - + device1.close() + assert repr(device1) == '' From fddb95b84bdcedfd87141f81eb0889bb629713c0 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 30 Apr 2016 16:47:29 +0100 Subject: [PATCH 021/104] Add more unit tests, fix a few small bugs --- gpiozero/boards.py | 3 ++ gpiozero/input_devices.py | 2 +- gpiozero/output_devices.py | 2 +- tests/test_boards.py | 66 ++++++++++++++++++++++++++++++++ tests/test_compat.py | 1 + tests/test_inputs.py | 17 ++++++++- tests/test_outputs.py | 77 +++++++++++++++++++++++++++++++++++++- 7 files changed, 164 insertions(+), 4 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index a42dc5f..b8f06a7 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -18,6 +18,7 @@ from .exc import ( GPIOPinMissing, EnergenieSocketMissing, EnergenieBadSocket, + OutputDeviceBadValue, ) from .input_devices import Button from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor @@ -402,6 +403,8 @@ class LEDBarGraph(LEDCollection): @value.setter def value(self, value): + if not -1 <= value <= 1: + raise OutputDeviceBadValue('LEDBarGraph value must be between -1 and 1') count = len(self) leds = self if value < 0: diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 3f38a86..05d0949 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -165,7 +165,7 @@ class SmoothedInputDevice(EventsMixin, InputDevice): if self.partial or self._queue.full.wait(0): return super(SmoothedInputDevice, self).__repr__() else: - return "" % ( + return "" % ( self.__class__.__name__, self.pin, self.pull_up) @property diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 037f749..49a1b47 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -581,7 +581,7 @@ class RGBLED(SourceMixin, Device): @property def closed(self): - return bool(self._leds) + return len(self._leds) == 0 @property def value(self): diff --git a/tests/test_boards.py b/tests/test_boards.py index 627325e..ed3a0bb 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -85,6 +85,36 @@ def test_led_board_on_off(): assert not pin1.state assert pin2.state assert pin3.state + board.toggle(0,1) + assert board.value == (1, 0, 1) + assert pin1.state + assert not pin2.state + assert pin3.state + board.off(2) + assert board.value == (1, 0, 0) + assert pin1.state + assert not pin2.state + assert not pin3.state + board.on(1) + assert board.value == (1, 1, 0) + assert pin1.state + assert pin2.state + assert not pin3.state + board.off(0,1) + assert board.value == (0, 0, 0) + assert not pin1.state + assert not pin2.state + assert not pin3.state + board.on(1,2) + assert board.value == (0, 1, 1) + assert not pin1.state + assert pin2.state + assert pin3.state + board.toggle(0) + assert board.value == (1, 1, 1) + assert pin1.state + assert pin2.state + assert pin3.state def test_led_board_nested(): pin1 = MockPin(2) @@ -321,12 +351,48 @@ def test_led_bar_graph_pwm_value(): pin3.state = 1 assert graph.value == -1/2 +def test_led_bar_graph_bad_value(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBarGraph(pin1, pin2, pin3) as graph: + with pytest.raises(ValueError): + graph.value = -2 + with pytest.raises(ValueError): + graph.value = 2 + def test_led_bar_graph_bad_init(): pin1 = MockPin(2) pin2 = MockPin(3) pin3 = MockPin(4) with pytest.raises(TypeError): LEDBarGraph(pin1, pin2, foo=pin3) + with pytest.raises(ValueError): + LEDBarGraph(pin1, pin2, pin3, initial_value=-2) + with pytest.raises(ValueError): + LEDBarGraph(pin1, pin2, pin3, initial_value=2) + +def test_led_bar_graph_initial_value(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBarGraph(pin1, pin2, pin3, initial_value=1/3) as graph: + assert graph.value == 1/3 + assert pin1.state and not (pin2.state or pin3.state) + with LEDBarGraph(pin1, pin2, pin3, initial_value=-1/3) as graph: + assert graph.value == -1/3 + assert pin3.state and not (pin1.state or pin2.state) + +def test_led_bar_graph_pwm_initial_value(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(3) + pin3 = MockPWMPin(4) + with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=0.5) as graph: + assert graph.value == 0.5 + assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) + with LEDBarGraph(pin1, pin2, pin3, pwm=True, initial_value=-0.5) as graph: + assert graph.value == -0.5 + assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) def test_pi_liter(): pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] diff --git a/tests/test_compat.py b/tests/test_compat.py index a3e6e28..c58fe7c 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -120,6 +120,7 @@ def test_mean(): values = list(values) random.shuffle(values) assert mean(values) == result + assert mean(iter(values)) == result def test_mean_big_data(): c = 1e9 diff --git a/tests/test_inputs.py b/tests/test_inputs.py index ee1d62a..eec55e4 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -34,13 +34,25 @@ def test_input_initial_values(): assert pin.pull == 'down' assert not device.pull_up -def test_input_is_active(): +def test_input_is_active_low(): pin = MockPin(2) with InputDevice(pin, pull_up=True) as device: pin.drive_high() assert not device.is_active + assert repr(device) == '' pin.drive_low() assert device.is_active + assert repr(device) == '' + +def test_input_is_active_high(): + pin = MockPin(2) + with InputDevice(pin, pull_up=False) as device: + pin.drive_high() + assert device.is_active + assert repr(device) == '' + pin.drive_low() + assert not device.is_active + assert repr(device) == '' def test_input_pulled_up(): pin = MockPulledUpPin(2) @@ -83,11 +95,14 @@ def test_input_wait_inactive(): def test_input_smoothed_attrib(): pin = MockPin(2) with SmoothedInputDevice(pin, threshold=0.5, queue_len=5, partial=False) as device: + assert repr(device) == '' assert device.threshold == 0.5 assert device.queue_len == 5 assert not device.partial device._queue.start() assert not device.is_active + with pytest.raises(InputDeviceError): + device.threshold = 1 def test_input_smoothed_values(): pin = MockPin(2) diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 27bc97d..cd3abf2 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -53,6 +53,9 @@ def test_output_write_active_low(): def test_output_write_closed(): with OutputDevice(MockPin(2)) as device: device.close() + assert device.closed + device.close() + assert device.closed with pytest.raises(GPIODeviceClosed): device.on() @@ -294,6 +297,67 @@ def test_output_pwm_fade_foreground(): (0.04, 0), ]) +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_output_pwm_pulse_background(): + pin = MockPWMPin(2) + with PWMOutputDevice(pin) as device: + device.pulse(0.2, 0.2, n=2) + device._blink_thread.join() + pin.assert_states_and_times([ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ]) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_output_pwm_pulse_foreground(): + pin = MockPWMPin(2) + with PWMOutputDevice(pin) as device: + device.pulse(0.2, 0.2, n=2, background=False) + pin.assert_states_and_times([ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ]) + def test_output_pwm_blink_interrupt(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: @@ -409,7 +473,7 @@ def test_rgbled_fade_background(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) -def test_output_rgbled_blink_interrupt(): +def test_rgbled_blink_interrupt(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: device.blink(1, 0.1) @@ -419,6 +483,15 @@ def test_output_rgbled_blink_interrupt(): g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) +def test_rgbled_close(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b) as device: + assert not device.closed + device.close() + assert device.closed + device.close() + assert device.closed + def test_motor_missing_pins(): with pytest.raises(ValueError): Motor() @@ -438,6 +511,8 @@ def test_motor_close(): assert device.closed assert device.forward_device.pin is None assert device.backward_device.pin is None + device.close() + assert device.closed def test_motor_value(): f = MockPWMPin(1) From 019347db2667d8d7154a1c950dddc8a7e486836c Mon Sep 17 00:00:00 2001 From: Steveis Date: Sun, 1 May 2016 18:04:02 +0100 Subject: [PATCH 022/104] Corrected value of capacitor used in LightSensor CamJam kit has a 1uF capacitor in it. Plus I physically tested with the 1uF cap in my CamJam #2 kit. --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 3f38a86..f7d0566 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -443,7 +443,7 @@ class LightSensor(SmoothedInputDevice): :param float charge_time_limit: If the capacitor in the circuit takes longer than this length of time to charge, it is assumed to be dark. The default (0.01 seconds) is - appropriate for a 0.01µf capacitor coupled with the LDR from the + appropriate for a 1µf capacitor coupled with the LDR from the `CamJam #2 EduKit`_. You may need to adjust this value for different valued capacitors or LDRs. From bdaa92727934cd8e95075e59f25898191cc39aa9 Mon Sep 17 00:00:00 2001 From: Steveis Date: Sun, 1 May 2016 19:34:30 +0100 Subject: [PATCH 023/104] Correct capacitance SI unit --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index f7d0566..fea7c59 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -443,7 +443,7 @@ class LightSensor(SmoothedInputDevice): :param float charge_time_limit: If the capacitor in the circuit takes longer than this length of time to charge, it is assumed to be dark. The default (0.01 seconds) is - appropriate for a 1µf capacitor coupled with the LDR from the + appropriate for a 1µF capacitor coupled with the LDR from the `CamJam #2 EduKit`_. You may need to adjust this value for different valued capacitors or LDRs. From 191cda29c09f7bb60cfca5f389473c5ac5c00021 Mon Sep 17 00:00:00 2001 From: Steveis Date: Mon, 2 May 2016 11:20:59 +0100 Subject: [PATCH 024/104] Another SI unit correction --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index fea7c59..ab163cf 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -418,7 +418,7 @@ class LightSensor(SmoothedInputDevice): Extends :class:`SmoothedInputDevice` and represents a light dependent resistor (LDR). - Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µf + Connect one leg of the LDR to the 3V3 pin; connect one leg of a 1µF capacitor to a ground pin; connect the other leg of the LDR and the other leg of the capacitor to the same GPIO pin. This class repeatedly discharges the capacitor, then times the duration it takes to charge (which will vary From 67261e44b2cf675678e6174fce96df09e7e28920 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 2 May 2016 19:53:33 +0100 Subject: [PATCH 025/104] Typo in SPISoftwareBus._shared_key It's a @classmethod, so the first parameter is cls not self --- gpiozero/spi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/spi.py b/gpiozero/spi.py index 9a4e712..85352df 100644 --- a/gpiozero/spi.py +++ b/gpiozero/spi.py @@ -163,7 +163,7 @@ class SPISoftwareBus(SharedMixin, Device): return self.lock is None @classmethod - def _shared_key(self, clock_pin, mosi_pin, miso_pin): + def _shared_key(cls, clock_pin, mosi_pin, miso_pin): return (clock_pin, mosi_pin, miso_pin) def read(self, n): From 18bb3f559e065f5b3bde9b62833e7c9a2953a907 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Wed, 4 May 2016 11:14:36 +0100 Subject: [PATCH 026/104] Add RGBLED.pulse method Also add timing tests to all the blink unit-tests --- docs/api_output.rst | 4 +- gpiozero/output_devices.py | 44 ++++++++- tests/test_outputs.py | 198 ++++++++++++++++++++++++++++++++++++- 3 files changed, 239 insertions(+), 7 deletions(-) diff --git a/docs/api_output.rst b/docs/api_output.rst index 845079c..1824f11 100644 --- a/docs/api_output.rst +++ b/docs/api_output.rst @@ -23,13 +23,13 @@ PWMLED ====== .. autoclass:: PWMLED(pin, active_high=True, initial_value=0, frequency=100) - :members: on, off, toggle, blink, pin, is_lit, value + :members: on, off, toggle, blink, pulse, pin, is_lit, value RGBLED ====== .. autoclass:: RGBLED(red, green, blue, active_high=True, initial_value=(0, 0, 0)) - :members: on, off, toggle, blink, red, green, blue, is_lit, color + :members: on, off, toggle, blink, pulse, red, green, blue, is_lit, color Buzzer ====== diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 037f749..4fc1658 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -435,12 +435,12 @@ class PWMOutputDevice(OutputDevice): Number of seconds to spend fading out. Defaults to 1. :param int n: - Number of times to blink; ``None`` (the default) means forever. + Number of times to pulse; ``None`` (the default) means forever. :param bool background: If ``True`` (the default), start a background thread to continue - blinking and return immediately. If ``False``, only return when the - blink is finished (warning: the default value of *n* will result in + pulsing and return immediately. If ``False``, only return when the + pulse is finished (warning: the default value of *n* will result in this method never returning). """ on_time = off_time = 0 @@ -670,13 +670,49 @@ class RGBLED(SourceMixin, Device): self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, - args=(on_time, off_time, fade_in_time, fade_out_time, on_color, off_color, n) + args=( + on_time, off_time, fade_in_time, fade_out_time, + on_color, off_color, n + ) ) self._blink_thread.start() if not background: self._blink_thread.join() self._blink_thread = None + def pulse( + self, fade_in_time=1, fade_out_time=1, + on_color=(1, 1, 1), off_color=(0, 0, 0), n=None, background=True): + """ + Make the device fade in and out repeatedly. + + :param float fade_in_time: + Number of seconds to spend fading in. Defaults to 1. + + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 1. + + :param tuple on_color: + The color to use when the LED is "on". Defaults to white. + + :param tuple off_color: + The color to use when the LED is "off". Defaults to black. + + :param int n: + Number of times to pulse; ``None`` (the default) means forever. + + :param bool background: + If ``True`` (the default), start a background thread to continue + pulsing and return immediately. If ``False``, only return when the + pulse is finished (warning: the default value of *n* will result in + this method never returning). + """ + on_time = off_time = 0 + self.blink( + on_time, off_time, fade_in_time, fade_out_time, + on_color, off_color, n, background + ) + def _stop_blink(self, led=None): # If this is called with a single led, we stop all blinking anyway if self._blink_thread: diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 27bc97d..fa42df6 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -8,7 +8,7 @@ str = type('') import sys -from time import sleep +from time import sleep, time try: from math import isclose except ImportError: @@ -92,8 +92,11 @@ def test_output_digital_toggle(): def test_output_blink_background(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: + start = time() device.blink(0.1, 0.1, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() # naughty, but ensures no arbitrary waits in the test + assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), @@ -107,7 +110,9 @@ def test_output_blink_background(): def test_output_blink_foreground(): pin = MockPin(2) with DigitalOutputDevice(pin) as device: + start = time() device.blink(0.1, 0.1, n=2, background=False) + assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, False), (0.0, True), @@ -209,8 +214,11 @@ def test_output_pwm_write_silly(): def test_output_pwm_blink_background(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: + start = time() device.blink(0.1, 0.1, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() + assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), @@ -224,7 +232,9 @@ def test_output_pwm_blink_background(): def test_output_pwm_blink_foreground(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: + start = time() device.blink(0.1, 0.1, n=2, background=False) + assert isclose(time() - start, 0.4, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.0, 1), @@ -238,8 +248,11 @@ def test_output_pwm_blink_foreground(): def test_output_pwm_fade_background(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: + start = time() device.blink(0, 0, 0.2, 0.2, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() + assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), @@ -269,7 +282,75 @@ def test_output_pwm_fade_background(): def test_output_pwm_fade_foreground(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: + start = time() device.blink(0, 0, 0.2, 0.2, n=2, background=False) + assert isclose(time() - start, 0.8, abs_tol=0.05) + pin.assert_states_and_times([ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ]) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_output_pwm_pulse_background(): + pin = MockPWMPin(2) + with PWMOutputDevice(pin) as device: + start = time() + device.pulse(0.2, 0.2, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) + device._blink_thread.join() + assert isclose(time() - start, 0.8, abs_tol=0.05) + pin.assert_states_and_times([ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ]) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_output_pwm_pulse_foreground(): + pin = MockPWMPin(2) + with PWMOutputDevice(pin) as device: + start = time() + device.pulse(0.2, 0.2, n=2, background=False) + assert isclose(time() - start, 0.8, abs_tol=0.05) pin.assert_states_and_times([ (0.0, 0), (0.04, 0.2), @@ -345,8 +426,11 @@ def test_rgbled_toggle(): def test_rgbled_blink_background(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: + start = time() device.blink(0.1, 0.1, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() + assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), @@ -363,7 +447,9 @@ def test_rgbled_blink_background(): def test_rgbled_blink_foreground(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: + start = time() device.blink(0.1, 0.1, n=2, background=False) + assert isclose(time() - start, 0.4, abs_tol=0.05) expected = [ (0.0, 0), (0.0, 1), @@ -380,8 +466,118 @@ def test_rgbled_blink_foreground(): def test_rgbled_fade_background(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: + start = time() device.blink(0, 0, 0.2, 0.2, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) device._blink_thread.join() + assert isclose(time() - start, 0.8, abs_tol=0.05) + expected = [ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ] + r.assert_states_and_times(expected) + g.assert_states_and_times(expected) + b.assert_states_and_times(expected) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_rgbled_fade_foreground(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b) as device: + start = time() + device.blink(0, 0, 0.2, 0.2, n=2, background=False) + assert isclose(time() - start, 0.8, abs_tol=0.05) + expected = [ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ] + r.assert_states_and_times(expected) + g.assert_states_and_times(expected) + b.assert_states_and_times(expected) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_rgbled_pulse_background(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b) as device: + start = time() + device.pulse(0.2, 0.2, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) + device._blink_thread.join() + assert isclose(time() - start, 0.8, abs_tol=0.05) + expected = [ + (0.0, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + (0.04, 0.2), + (0.04, 0.4), + (0.04, 0.6), + (0.04, 0.8), + (0.04, 1), + (0.04, 0.8), + (0.04, 0.6), + (0.04, 0.4), + (0.04, 0.2), + (0.04, 0), + ] + r.assert_states_and_times(expected) + g.assert_states_and_times(expected) + b.assert_states_and_times(expected) + +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_rgbled_pulse_foreground(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b) as device: + start = time() + device.pulse(0.2, 0.2, n=2, background=False) + assert isclose(time() - start, 0.8, abs_tol=0.05) expected = [ (0.0, 0), (0.04, 0.2), From 86aeab41296aa362125f39feb9d0afd18147d7a9 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 6 May 2016 11:53:00 +0100 Subject: [PATCH 027/104] Add active_high parameter to LEDBarGraph's constructor Also adds an active_high property to LEDCollection, and fixes up some of the LEDBoard docstrings --- gpiozero/boards.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index a42dc5f..662089a 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -122,6 +122,10 @@ class LEDCollection(CompositeOutputDevice): else: yield item + @property + def active_high(self): + return self[0].active_high + class LEDBoard(LEDCollection): """ @@ -148,21 +152,23 @@ class LEDBoard(LEDCollection): :param bool active_high: If ``True`` (the default), the :meth:`on` method will set all the - associates pins to HIGH. If ``False``, the :meth:`on` method will set - all pins to LOW (the :meth:`off` method always does the opposite). + associated pins to HIGH. If ``False``, the :meth:`on` method will set + all pins to LOW (the :meth:`off` method always does the opposite). This + parameter can only be specified as a keyword parameter. :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, - the device will be switched on initially. + the device will be switched on initially. This parameter can only be + specified as a keyword parameter. :param \*\*named_pins: - Sepcify GPIO pins that LEDs of the board are attached to, associated + Specify GPIO pins that LEDs of the board are attached to, associating each LED with a property name. You can designate as many pins as - necessary and any name provided it's not already in use by something - else. You can also specify :class:`LEDBoard` instances to create - trees of LEDs. + necessary and use any names, provided they're not already in use by + something else. You can also specify :class:`LEDBoard` instances to + create trees of LEDs. """ def __init__(self, *args, **kwargs): self._blink_leds = [] @@ -345,15 +351,21 @@ class LEDBarGraph(LEDCollection): Specify the GPIO pins that the LEDs of the bar graph are attached to. You can designate as many pins as necessary. - :param float initial_value: - The initial :attr:`value` of the graph given as a float between -1 and - +1. Defaults to 0.0. This parameter can only be specified as a keyword - parameter. - :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. This parameter can only be specified as a keyword parameter. + + :param bool active_high: + If ``True`` (the default), the :meth:`on` method will set all the + associated pins to HIGH. If ``False``, the :meth:`on` method will set + all pins to LOW (the :meth:`off` method always does the opposite). This + parameter can only be specified as a keyword parameter. + + :param float initial_value: + The initial :attr:`value` of the graph given as a float between -1 and + +1. Defaults to 0.0. This parameter can only be specified as a keyword + parameter. """ def __init__(self, *pins, **kwargs): @@ -361,10 +373,11 @@ class LEDBarGraph(LEDCollection): for pin in pins: assert not isinstance(pin, LEDCollection) pwm = kwargs.pop('pwm', False) + active_high = kwargs.pop('active_high', True) initial_value = kwargs.pop('initial_value', 0) if kwargs: raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0]) - super(LEDBarGraph, self).__init__(*pins, pwm=pwm) + super(LEDBarGraph, self).__init__(*pins, pwm=pwm, active_high=active_high) try: self.value = initial_value except: From 97e873dd2e9b094cd83a088417de55fe14991866 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 8 May 2016 11:39:25 +0100 Subject: [PATCH 028/104] Revert "Update output_devices.py" --- gpiozero/output_devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 109988f..49a1b47 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -753,8 +753,8 @@ class Motor(SourceMixin, CompositeDevice): 'forward and backward pins must be provided' ) super(Motor, self).__init__( - forward_device=DigitalOutputDevice(forward), - backward_device=DigitalOutputDevice(backward), + forward_device=PWMOutputDevice(forward), + backward_device=PWMOutputDevice(backward), _order=('forward_device', 'backward_device')) @property From c9461c50d3a2fe346fc10c2166f6a0b46a6f75f8 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 29 Apr 2016 12:06:29 +0100 Subject: [PATCH 029/104] Add a `pwm` option to the RGBLED and Motor constructors ...along with the other necessary changes required, to allow them to optionally be used with non-PWM-capable pins --- docs/api_output.rst | 4 +- docs/images/composed_devices.dot | 2 + docs/images/composed_devices.pdf | Bin 12376 -> 12641 bytes docs/images/composed_devices.png | Bin 24861 -> 30588 bytes docs/images/composed_devices.svg | 145 ++++++++------- gpiozero/output_devices.py | 75 ++++++-- tests/test_outputs.py | 293 ++++++++++++++++++++++++------- 7 files changed, 378 insertions(+), 141 deletions(-) diff --git a/docs/api_output.rst b/docs/api_output.rst index 1824f11..b6710c2 100644 --- a/docs/api_output.rst +++ b/docs/api_output.rst @@ -28,7 +28,7 @@ PWMLED RGBLED ====== -.. autoclass:: RGBLED(red, green, blue, active_high=True, initial_value=(0, 0, 0)) +.. autoclass:: RGBLED(red, green, blue, active_high=True, initial_value=(0, 0, 0), pwm=True) :members: on, off, toggle, blink, pulse, red, green, blue, is_lit, color Buzzer @@ -40,7 +40,7 @@ Buzzer Motor ===== -.. autoclass:: Motor(forward, backward) +.. autoclass:: Motor(forward, backward, pwm=True) :members: forward, backward, stop Base Classes diff --git a/docs/images/composed_devices.dot b/docs/images/composed_devices.dot index 2a13174..b1611f2 100644 --- a/docs/images/composed_devices.dot +++ b/docs/images/composed_devices.dot @@ -5,6 +5,7 @@ digraph classes { node [shape=rect, style=filled, color="#298029", fontname=Sans, fontcolor="#ffffff", fontsize=10]; edge [arrowhead=onormal, style=dashed]; + RGBLED->LED; RGBLED->PWMLED; LEDBoard->LED; LEDBoard->PWMLED; @@ -16,5 +17,6 @@ digraph classes { TrafficLightsBuzzer->Button; Robot->Motor; + Motor->DigitalOutputDevice; Motor->PWMOutputDevice; } diff --git a/docs/images/composed_devices.pdf b/docs/images/composed_devices.pdf index 8c1805613c688ae1adcfad3b0fa140cfdb366558..f0dfaf8647c1c4bd6828c56ca52e7401d0493337 100644 GIT binary patch delta 7851 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Ⓢ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 literal 24861 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

;l4)Z@GhYNKmV9}4I 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 dze4$gK;{;W@}eHuSa1c - + classes - + RGBLED RGBLED + +LED + +LED + + +RGBLED->LED + + + -PWMLED - -PWMLED +PWMLED + +PWMLED -RGBLED->PWMLED - - +RGBLED->PWMLED + + -LEDBoard - -LEDBoard - - -LEDBoard->PWMLED - - - - -LED - -LED +LEDBoard + +LEDBoard -LEDBoard->LED - - +LEDBoard->LED + + + + +LEDBoard->PWMLED + + LEDBarGraph - -LEDBarGraph - - -LEDBarGraph->PWMLED - - + +LEDBarGraph -LEDBarGraph->LED - - +LEDBarGraph->LED + + + + +LEDBarGraph->PWMLED + + TrafficLightsBuzzer - -TrafficLightsBuzzer + +TrafficLightsBuzzer TrafficLights - -TrafficLights + +TrafficLights -TrafficLightsBuzzer->TrafficLights - - +TrafficLightsBuzzer->TrafficLights + + Buzzer - -Buzzer + +Buzzer -TrafficLightsBuzzer->Buzzer - - +TrafficLightsBuzzer->Buzzer + + Button - -Button + +Button -TrafficLightsBuzzer->Button - - +TrafficLightsBuzzer->Button + + Robot - -Robot + +Robot Motor - -Motor + +Motor -Robot->Motor - - +Robot->Motor + + + + +DigitalOutputDevice + +DigitalOutputDevice + + +Motor->DigitalOutputDevice + + -PWMOutputDevice - -PWMOutputDevice +PWMOutputDevice + +PWMOutputDevice -Motor->PWMOutputDevice - - +Motor->PWMOutputDevice + + diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 5c13956..ecb2a5b 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -555,16 +555,22 @@ class RGBLED(SourceMixin, Device): :param bool initial_value: The initial color for the LED. Defaults to black ``(0, 0, 0)``. + + :param bool pwm: + If ``True`` (the default), construct :class:`PWMLED` instances for + each component of the RGBLED. If ``False``, construct regular + :class:`LED` instances, which prevents smooth color graduations. """ def __init__( self, red=None, green=None, blue=None, active_high=True, - initial_value=(0, 0, 0)): + initial_value=(0, 0, 0), pwm=True): self._leds = () self._blink_thread = None if not all([red, green, blue]): raise GPIOPinMissing('red, green, and blue pins must be provided') + LEDClass = PWMLED if pwm else LED super(RGBLED, self).__init__() - self._leds = tuple(PWMLED(pin, active_high) for pin in (red, green, blue)) + self._leds = tuple(LEDClass(pin, active_high) for pin in (red, green, blue)) self.value = initial_value red = _led_property(0) @@ -587,7 +593,8 @@ class RGBLED(SourceMixin, Device): def value(self): """ Represents the color of the LED as an RGB 3-tuple of ``(red, green, - blue)`` where each value is between 0 and 1. + blue)`` where each value is between 0 and 1 if ``pwm`` was ``True`` + when the class was constructed (and only 0 or 1 if not). For example, purple would be ``(1, 0, 1)`` and yellow would be ``(1, 1, 0)``, while orange would be ``(1, 0.5, 0)``. @@ -596,6 +603,12 @@ class RGBLED(SourceMixin, Device): @value.setter def value(self, value): + for component in value: + if not 0 <= component <= 1: + raise OutputDeviceBadValue('each RGB color component must be between 0 and 1') + if isinstance(self._leds[0], LED): + if component not in (0, 1): + raise OutputDeviceBadValue('each RGB color component must be 0 or 1 with non-PWM RGBLEDs') self._stop_blink() self.red, self.green, self.blue = value @@ -647,10 +660,14 @@ class RGBLED(SourceMixin, Device): Number of seconds off. Defaults to 1 second. :param float fade_in_time: - Number of seconds to spend fading in. Defaults to 0. + Number of seconds to spend fading in. Defaults to 0. Must be 0 if + ``pwm`` was ``False`` when the class was constructed + (:exc:`ValueError` will be raised if not). :param float fade_out_time: - Number of seconds to spend fading out. Defaults to 0. + Number of seconds to spend fading out. Defaults to 0. Must be 0 if + ``pwm`` was ``False`` when the class was constructed + (:exc:`ValueError` will be raised if not). :param tuple on_color: The color to use when the LED is "on". Defaults to white. @@ -667,6 +684,11 @@ class RGBLED(SourceMixin, Device): blink is finished (warning: the default value of *n* will result in this method never returning). """ + if isinstance(self._leds[0], LED): + if fade_in_time: + raise ValueError('fade_in_time must be 0 with non-PWM RGBLEDs') + if fade_out_time: + raise ValueError('fade_out_time must be 0 with non-PWM RGBLEDs') self._stop_blink() self._blink_thread = GPIOThread( target=self._blink_device, @@ -782,22 +804,31 @@ class Motor(SourceMixin, CompositeDevice): :param int backward: The GPIO pin that the backward input of the motor driver chip is connected to. + + :param bool pwm: + If ``True`` (the default), construct :class:`PWMOutputDevice` + instances for the motor controller pins, allowing both direction and + variable speed control. If ``False``, construct + :class:`DigitalOutputDevice` instances, allowing only direction + control. """ - def __init__(self, forward=None, backward=None): + def __init__(self, forward=None, backward=None, pwm=True): if not all([forward, backward]): raise GPIOPinMissing( 'forward and backward pins must be provided' ) + PinClass = PWMOutputDevice if pwm else DigitalOutputDevice super(Motor, self).__init__( - forward_device=PWMOutputDevice(forward), - backward_device=PWMOutputDevice(backward), + forward_device=PinClass(forward), + backward_device=PinClass(backward), _order=('forward_device', 'backward_device')) @property def value(self): """ Represents the speed of the motor as a floating point value between -1 - (full speed backward) and 1 (full speed forward). + (full speed backward) and 1 (full speed forward), with 0 representing + stopped. """ return self.forward_device.value - self.backward_device.value @@ -806,9 +837,15 @@ class Motor(SourceMixin, CompositeDevice): if not -1 <= value <= 1: raise OutputDeviceBadValue("Motor value must be between -1 and 1") if value > 0: - self.forward(value) + try: + self.forward(value) + except ValueError as e: + raise OutputDeviceBadValue(e) elif value < 0: - self.backward(-value) + try: + self.backward(-value) + except ValueError as e: + raise OutputDeviceBadValue(e) else: self.stop() @@ -826,8 +863,14 @@ class Motor(SourceMixin, CompositeDevice): :param float speed: The speed at which the motor should turn. Can be any value between - 0 (stopped) and the default 1 (maximum speed). + 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was + ``True`` when the class was constructed (and only 0 or 1 if not). """ + if not 0 <= speed <= 1: + raise ValueError('forward speed must be between 0 and 1') + if isinstance(self.forward_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError('forward speed must be 0 or 1 with non-PWM Motors') self.backward_device.off() self.forward_device.value = speed @@ -837,8 +880,14 @@ class Motor(SourceMixin, CompositeDevice): :param float speed: The speed at which the motor should turn. Can be any value between - 0 (stopped) and the default 1 (maximum speed). + 0 (stopped) and the default 1 (maximum speed) if ``pwm`` was + ``True`` when the class was constructed (and only 0 or 1 if not). """ + if not 0 <= speed <= 1: + raise ValueError('backward speed must be between 0 and 1') + if isinstance(self.backward_device, DigitalOutputDevice): + if speed not in (0, 1): + raise ValueError('backward speed must be 0 or 1 with non-PWM Motors') self.forward_device.off() self.backward_device.value = speed diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 435cf17..444b1d9 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -378,67 +378,6 @@ def test_output_pwm_pulse_foreground(): (0.04, 0), ]) -@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), - reason='timing is too random on pypy') -def test_output_pwm_pulse_background(): - pin = MockPWMPin(2) - with PWMOutputDevice(pin) as device: - device.pulse(0.2, 0.2, n=2) - device._blink_thread.join() - pin.assert_states_and_times([ - (0.0, 0), - (0.04, 0.2), - (0.04, 0.4), - (0.04, 0.6), - (0.04, 0.8), - (0.04, 1), - (0.04, 0.8), - (0.04, 0.6), - (0.04, 0.4), - (0.04, 0.2), - (0.04, 0), - (0.04, 0.2), - (0.04, 0.4), - (0.04, 0.6), - (0.04, 0.8), - (0.04, 1), - (0.04, 0.8), - (0.04, 0.6), - (0.04, 0.4), - (0.04, 0.2), - (0.04, 0), - ]) - -@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), - reason='timing is too random on pypy') -def test_output_pwm_pulse_foreground(): - pin = MockPWMPin(2) - with PWMOutputDevice(pin) as device: - device.pulse(0.2, 0.2, n=2, background=False) - pin.assert_states_and_times([ - (0.0, 0), - (0.04, 0.2), - (0.04, 0.4), - (0.04, 0.6), - (0.04, 0.8), - (0.04, 1), - (0.04, 0.8), - (0.04, 0.6), - (0.04, 0.4), - (0.04, 0.2), - (0.04, 0), - (0.04, 0.2), - (0.04, 0.4), - (0.04, 0.6), - (0.04, 0.8), - (0.04, 1), - (0.04, 0.8), - (0.04, 0.6), - (0.04, 0.4), - (0.04, 0.2), - (0.04, 0), - ]) - def test_output_pwm_blink_interrupt(): pin = MockPWMPin(2) with PWMOutputDevice(pin) as device: @@ -461,9 +400,47 @@ def test_rgbled_initial_value(): assert isclose(g.state, 0.2) assert isclose(b.state, 0.0) +def test_rgbled_initial_value_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False, initial_value=(0, 1, 1)) as device: + assert r.state == 0 + assert g.state == 1 + assert b.state == 1 + +def test_rgbled_initial_bad_value(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) + +def test_rgbled_initial_bad_value_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, pwm=False, initial_value=(0.1, 0.2, 0)) + def test_rgbled_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: + assert isinstance(device._leds[0], PWMLED) + assert isinstance(device._leds[1], PWMLED) + assert isinstance(device._leds[2], PWMLED) + assert not device.is_active + assert device.value == (0, 0, 0) + device.on() + assert device.is_active + assert device.value == (1, 1, 1) + device.off() + assert not device.is_active + assert device.value == (0, 0, 0) + device.value = (0.5, 0.5, 0.5) + assert device.is_active + assert device.value == (0.5, 0.5, 0.5) + +def test_rgbled_value_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + assert isinstance(device._leds[0], LED) + assert isinstance(device._leds[1], LED) + assert isinstance(device._leds[2], LED) assert not device.is_active assert device.value == (0, 0, 0) device.on() @@ -473,6 +450,33 @@ def test_rgbled_value(): assert not device.is_active assert device.value == (0, 0, 0) +def test_rgbled_bad_value(): + r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b) as device: + with pytest.raises(ValueError): + device.value = (2, 0, 0) + with RGBLED(r, g, b) as device: + with pytest.raises(ValueError): + device.value = (0, -1, 0) + +def test_rgbled_bad_value_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = (2, 0, 0) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = (0, -1, 0) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = (0.5, 0, 0) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = (0, 0.5, 0) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = (0, 0, 0.5) + def test_rgbled_toggle(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: @@ -485,6 +489,18 @@ def test_rgbled_toggle(): assert not device.is_active assert device.value == (0, 0, 0) +def test_rgbled_toggle_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + assert not device.is_active + assert device.value == (0, 0, 0) + device.toggle() + assert device.is_active + assert device.value == (1, 1, 1) + device.toggle() + assert not device.is_active + assert device.value == (0, 0, 0) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_background(): @@ -506,6 +522,27 @@ def test_rgbled_blink_background(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_rgbled_blink_background_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + start = time() + device.blink(0.1, 0.1, n=2) + assert isclose(time() - start, 0, abs_tol=0.05) + device._blink_thread.join() + assert isclose(time() - start, 0.4, abs_tol=0.05) + expected = [ + (0.0, 0), + (0.0, 1), + (0.1, 0), + (0.1, 1), + (0.1, 0) + ] + r.assert_states_and_times(expected) + g.assert_states_and_times(expected) + b.assert_states_and_times(expected) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_blink_foreground(): @@ -525,6 +562,25 @@ def test_rgbled_blink_foreground(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +@pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), + reason='timing is too random on pypy') +def test_rgbled_blink_foreground_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + start = time() + device.blink(0.1, 0.1, n=2, background=False) + assert isclose(time() - start, 0.4, abs_tol=0.05) + expected = [ + (0.0, 0), + (0.0, 1), + (0.1, 0), + (0.1, 1), + (0.1, 0) + ] + r.assert_states_and_times(expected) + g.assert_states_and_times(expected) + b.assert_states_and_times(expected) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_background(): @@ -562,6 +618,12 @@ def test_rgbled_fade_background(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +def test_rgbled_fade_background_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.blink(0, 0, 0.2, 0.2, n=2) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_fade_foreground(): @@ -597,6 +659,12 @@ def test_rgbled_fade_foreground(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +def test_rgbled_fade_foreground_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.blink(0, 0, 0.2, 0.2, n=2, background=False) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_background(): @@ -634,6 +702,12 @@ def test_rgbled_pulse_background(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +def test_rgbled_pulse_background_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.pulse(0.2, 0.2, n=2) + @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') def test_rgbled_pulse_foreground(): @@ -669,6 +743,12 @@ def test_rgbled_pulse_foreground(): g.assert_states_and_times(expected) b.assert_states_and_times(expected) +def test_rgbled_pulse_foreground_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + with pytest.raises(ValueError): + device.pulse(0.2, 0.2, n=2, background=False) + def test_rgbled_blink_interrupt(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: @@ -679,6 +759,16 @@ def test_rgbled_blink_interrupt(): g.assert_states([0, 1, 0]) b.assert_states([0, 1, 0]) +def test_rgbled_blink_interrupt_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + device.blink(1, 0.1) + sleep(0.2) + device.off() # should interrupt while on + r.assert_states([0, 1, 0]) + g.assert_states([0, 1, 0]) + b.assert_states([0, 1, 0]) + def test_rgbled_close(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b) as device: @@ -688,6 +778,15 @@ def test_rgbled_close(): device.close() assert device.closed +def test_rgbled_close_nonpwm(): + r, g, b = (MockPin(i) for i in (1, 2, 3)) + with RGBLED(r, g, b, pwm=False) as device: + assert not device.closed + device.close() + assert device.closed + device.close() + assert device.closed + def test_motor_missing_pins(): with pytest.raises(ValueError): Motor() @@ -697,7 +796,18 @@ def test_motor_pins(): b = MockPWMPin(2) with Motor(f, b) as device: assert device.forward_device.pin is f + assert isinstance(device.forward_device, PWMOutputDevice) assert device.backward_device.pin is b + assert isinstance(device.backward_device, PWMOutputDevice) + +def test_motor_pins_nonpwm(): + f = MockPin(1) + b = MockPin(2) + with Motor(f, b, pwm=False) as device: + assert device.forward_device.pin is f + assert isinstance(device.forward_device, DigitalOutputDevice) + assert device.backward_device.pin is b + assert isinstance(device.backward_device, DigitalOutputDevice) def test_motor_close(): f = MockPWMPin(1) @@ -710,6 +820,15 @@ def test_motor_close(): device.close() assert device.closed +def test_motor_close_nonpwm(): + f = MockPin(1) + b = MockPin(2) + with Motor(f, b, pwm=False) as device: + device.close() + assert device.closed + assert device.forward_device.pin is None + assert device.backward_device.pin is None + def test_motor_value(): f = MockPWMPin(1) b = MockPWMPin(2) @@ -726,6 +845,27 @@ def test_motor_value(): assert device.is_active assert device.value == 0.5 assert b.state == 0 and f.state == 0.5 + device.value = -0.5 + assert device.is_active + assert device.value == -0.5 + assert b.state == 0.5 and f.state == 0 + device.value = 0 + assert not device.is_active + assert not device.value + assert b.state == 0 and f.state == 0 + +def test_motor_value_nonpwm(): + f = MockPin(1) + b = MockPin(2) + with Motor(f, b, pwm=False) as device: + device.value = -1 + assert device.is_active + assert device.value == -1 + assert b.state == 1 and f.state == 0 + device.value = 1 + assert device.is_active + assert device.value == 1 + assert b.state == 0 and f.state == 1 device.value = 0 assert not device.is_active assert not device.value @@ -735,9 +875,24 @@ def test_motor_bad_value(): f = MockPWMPin(1) b = MockPWMPin(2) with Motor(f, b) as device: + with pytest.raises(ValueError): + device.value = -2 with pytest.raises(ValueError): device.value = 2 +def test_motor_bad_value_nonpwm(): + f = MockPin(1) + b = MockPin(2) + with Motor(f, b, pwm=False) as device: + with pytest.raises(ValueError): + device.value = -2 + with pytest.raises(ValueError): + device.value = 2 + with pytest.raises(ValueError): + device.value = 0.5 + with pytest.raises(ValueError): + device.value = -0.5 + def test_motor_reverse(): f = MockPWMPin(1) b = MockPWMPin(2) @@ -748,4 +903,20 @@ def test_motor_reverse(): device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 + device.backward(0.5) + assert device.value == -0.5 + assert b.state == 0.5 and f.state == 0 + device.reverse() + assert device.value == 0.5 + assert b.state == 0 and f.state == 0.5 +def test_motor_reverse_nonpwm(): + f = MockPin(1) + b = MockPin(2) + with Motor(f, b, pwm=False) as device: + device.forward() + assert device.value == 1 + assert b.state == 0 and f.state == 1 + device.reverse() + assert device.value == -1 + assert b.state == 1 and f.state == 0 From 0c2a13367d28301f905d40470c0492036afc12f3 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 9 May 2016 00:18:10 +0100 Subject: [PATCH 030/104] Tweak test for better coverage report --- tests/test_tools.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index adeef77..a06109e 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -199,10 +199,9 @@ def test_post_delayed(): count += 1 if v == 1: assert time() - start < 0.01 - elif v == 2: - assert time() - start >= 0.01 else: - assert False + assert v == 2 + assert time() - start >= 0.01 start = time() assert time() - start >= 0.01 assert count == 3 From fd4075835e5a64bcf6b070ad3fd9479d0f67c457 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 9 May 2016 11:10:31 +0100 Subject: [PATCH 031/104] Mark optional sleeps with no coverage Codecov frequently whinges about "unexpected coverage changes" on PRs; this is because the sleep lines affected sometimes execute and sometimes don't (they're only there to ensure something exists before we test it). Hopefully codecov notices the pragmas... --- tests/test_boards.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_boards.py b/tests/test_boards.py index 534a6f9..062ae2a 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -189,7 +189,7 @@ def test_led_board_blink_control(): board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: - sleep(0.00001) + sleep(0.00001) # pragma: no cover board[1][0].off() # immediately take over the second LED board._blink_thread.join() # naughty, but ensures no arbitrary waits in the test test = [ @@ -236,7 +236,7 @@ def test_led_board_blink_control_all(): board.blink(0.1, 0.1, n=2) # make sure the blink thread's started while not board._blink_leds: - sleep(0.00001) + sleep(0.00001) # pragma: no cover board[0].off() # immediately take over all LEDs board[1][0].off() board[1][1].off() From a9d827663d52b0c9ae9469a0cac2f646715a253d Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 10 May 2016 05:51:28 +0100 Subject: [PATCH 032/104] typo --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 213edc5..f0d8ec3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,8 +35,8 @@ phrase "fix #123" where `#123` is the issue number. ## Backwards compatibility -Since this library reached v1.0 we aim to maintain backwards-compatability -thereafter. Changes which break backwards-compatability will not be accepted. +Since this library reached v1.0 we aim to maintain backwards-compatibility +thereafter. Changes which break backwards-compatibility will not be accepted. ## Python From eaf0d5ce1e84a18241d041293560bb34ae263e42 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 10 May 2016 05:38:15 +0100 Subject: [PATCH 033/104] Add tests for LEDBoard.active_high and LEDBarGraph.active_high Also add LEDBoard.value tests --- tests/test_boards.py | 130 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tests/test_boards.py b/tests/test_boards.py index 062ae2a..ff324c9 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -71,6 +71,10 @@ def test_led_board_on_off(): assert isinstance(board[0], LED) assert isinstance(board[1], LED) assert isinstance(board[2], LED) + assert board.active_high + assert board[0].active_high + assert board[1].active_high + assert board[2].active_high board.on() assert all((pin1.state, pin2.state, pin3.state)) board.off() @@ -116,6 +120,91 @@ def test_led_board_on_off(): assert pin2.state assert pin3.state +def test_led_board_active_low(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBoard(pin1, pin2, foo=pin3, active_high=False) as board: + assert not board.active_high + assert not board[0].active_high + assert not board[1].active_high + assert not board[2].active_high + board.on() + assert not any ((pin1.state, pin2.state, pin3.state)) + board.off() + assert all((pin1.state, pin2.state, pin3.state)) + board[0].on() + assert board.value == (1, 0, 0) + assert not pin1.state + assert pin2.state + assert pin3.state + board.toggle() + assert board.value == (0, 1, 1) + assert pin1.state + assert not pin2.state + assert not pin3.state + +def test_led_board_value(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBoard(pin1, pin2, foo=pin3) as board: + assert board.value == (0, 0, 0) + board.value = (0, 1, 0) + assert board.value == (0, 1, 0) + board.value = (1, 0, 1) + assert board.value == (1, 0, 1) + +def test_led_board_pwm_value(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(3) + pin3 = MockPWMPin(4) + with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: + assert board.value == (0, 0, 0) + board.value = (0, 1, 0) + assert board.value == (0, 1, 0) + board.value = (0.5, 0, 0.75) + assert board.value == (0.5, 0, 0.75) + +def test_led_board_pwm_bad_value(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(3) + pin3 = MockPWMPin(4) + with LEDBoard(pin1, pin2, foo=pin3, pwm=True) as board: + with pytest.raises(ValueError): + board.value = (-1, 0, 0) + with pytest.raises(ValueError): + board.value = (0, 2, 0) + +def test_led_board_initial_value(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBoard(pin1, pin2, foo=pin3, initial_value=0) as board: + assert board.value == (0, 0, 0) + with LEDBoard(pin1, pin2, foo=pin3, initial_value=1) as board: + assert board.value == (1, 1, 1) + +def test_led_board_pwm_initial_value(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(3) + pin3 = MockPWMPin(4) + with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0) as board: + assert board.value == (0, 0, 0) + with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=1) as board: + assert board.value == (1, 1, 1) + with LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=0.5) as board: + assert board.value == (0.5, 0.5, 0.5) + +def test_led_board_pwm_bad_initial_value(): + pin1 = MockPWMPin(2) + pin2 = MockPWMPin(3) + pin3 = MockPWMPin(4) + with pytest.raises(ValueError): + LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=-1) + with pytest.raises(ValueError): + LEDBoard(pin1, pin2, foo=pin3, pwm=True, initial_value=2) + def test_led_board_nested(): pin1 = MockPin(2) pin2 = MockPin(3) @@ -315,13 +404,24 @@ def test_led_bar_graph_value(): pin2 = MockPin(3) pin3 = MockPin(4) with LEDBarGraph(pin1, pin2, pin3) as graph: + assert isinstance(graph[0], LED) + assert isinstance(graph[1], LED) + assert isinstance(graph[2], LED) + assert graph.active_high + assert graph[0].active_high + assert graph[1].active_high + assert graph[2].active_high graph.value = 0 + assert graph.value == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 + assert graph.value == 1 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 + assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 + assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) pin1.state = True pin2.state = True @@ -332,20 +432,50 @@ def test_led_bar_graph_value(): pin1.state = False assert graph.value == -2/3 +def test_led_bar_graph_active_low(): + pin1 = MockPin(2) + pin2 = MockPin(3) + pin3 = MockPin(4) + with LEDBarGraph(pin1, pin2, pin3, active_high=False) as graph: + assert not graph.active_high + assert not graph[0].active_high + assert not graph[1].active_high + assert not graph[2].active_high + graph.value = 0 + assert graph.value == 0 + assert all((pin1.state, pin2.state, pin3.state)) + graph.value = 1 + assert graph.value == 1 + assert not any((pin1.state, pin2.state, pin3.state)) + graph.value = 1/3 + assert graph.value == 1/3 + assert not pin1.state and pin2.state and pin3.state + graph.value = -1/3 + assert graph.value == -1/3 + assert not pin3.state and pin1.state and pin2.state + def test_led_bar_graph_pwm_value(): pin1 = MockPWMPin(2) pin2 = MockPWMPin(3) pin3 = MockPWMPin(4) with LEDBarGraph(pin1, pin2, pin3, pwm=True) as graph: + assert isinstance(graph[0], PWMLED) + assert isinstance(graph[1], PWMLED) + assert isinstance(graph[2], PWMLED) graph.value = 0 + assert graph.value == 0 assert not any((pin1.state, pin2.state, pin3.state)) graph.value = 1 + assert graph.value == 1 assert all((pin1.state, pin2.state, pin3.state)) graph.value = 1/3 + assert graph.value == 1/3 assert pin1.state and not (pin2.state or pin3.state) graph.value = -1/3 + assert graph.value == -1/3 assert pin3.state and not (pin1.state or pin2.state) graph.value = 1/2 + assert graph.value == 1/2 assert (pin1.state, pin2.state, pin3.state) == (1, 0.5, 0) pin1.state = 0 pin3.state = 1 From 87d00f90474c4bd369400c99c69338e39e50c751 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 10 May 2016 15:03:28 +0100 Subject: [PATCH 034/104] Replace Event.wait(0) with Event.is_set() The functionality is identical, and IMHO the latter is much more readable --- gpiozero/input_devices.py | 2 +- gpiozero/mixins.py | 8 ++++---- tests/test_inputs.py | 10 +++++----- tests/test_mock_pin.py | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index c3e5840..f1153f7 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -162,7 +162,7 @@ class SmoothedInputDevice(EventsMixin, InputDevice): except DeviceClosed: return super(SmoothedInputDevice, self).__repr__() else: - if self.partial or self._queue.full.wait(0): + if self.partial or self._queue.full.is_set(): return super(SmoothedInputDevice, self).__repr__() else: return "" % ( diff --git a/gpiozero/mixins.py b/gpiozero/mixins.py index fd110ae..62eed6c 100644 --- a/gpiozero/mixins.py +++ b/gpiozero/mixins.py @@ -234,7 +234,7 @@ class EventsMixin(object): The length of time (in seconds) that the device has been active for. When the device is inactive, this is ``None``. """ - if self._active_event.wait(0): + if self._active_event.is_set(): return time() - self._last_changed else: return None @@ -245,7 +245,7 @@ class EventsMixin(object): The length of time (in seconds) that the device has been inactive for. When the device is active, this is ``None``. """ - if self._inactive_event.wait(0): + if self._inactive_event.is_set(): return time() - self._last_changed else: return None @@ -434,11 +434,11 @@ class HoldThread(GPIOThread): self.start() def held(self, parent): - while not self.stopping.wait(0): + while not self.stopping.is_set(): if self.holding.wait(0.1): self.holding.clear() while not ( - self.stopping.wait(0) or + self.stopping.is_set() or parent._inactive_event.wait(parent.hold_time) ): if parent._held_from is None: diff --git a/tests/test_inputs.py b/tests/test_inputs.py index eec55e4..41a8e56 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -64,20 +64,20 @@ def test_input_event_activated(): pin = MockPin(2) with DigitalInputDevice(pin) as device: device.when_activated = lambda: event.set() - assert not event.wait(0) + assert not event.is_set() pin.drive_high() - assert event.wait(0) + assert event.is_set() def test_input_event_deactivated(): event = Event() pin = MockPin(2) with DigitalInputDevice(pin) as device: device.when_deactivated = lambda: event.set() - assert not event.wait(0) + assert not event.is_set() pin.drive_high() - assert not event.wait(0) + assert not event.is_set() pin.drive_low() - assert event.wait(0) + assert event.is_set() def test_input_wait_active(): pin = MockPin(2) diff --git a/tests/test_mock_pin.py b/tests/test_mock_pin.py index 92e1d3d..c8bdb95 100644 --- a/tests/test_mock_pin.py +++ b/tests/test_mock_pin.py @@ -160,15 +160,15 @@ def test_mock_pin_edges(): pin.when_changed = changed pin.drive_high() assert pin.state - assert fired.wait(0) + assert fired.is_set() fired.clear() pin.edges = 'falling' pin.drive_low() assert not pin.state - assert fired.wait(0) + assert fired.is_set() fired.clear() pin.drive_high() assert pin.state - assert not fired.wait(0) + assert not fired.is_set() assert pin.edges == 'falling' From 4460f8047f91e279f03f845b27c992433e3df1f8 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Tue, 10 May 2016 23:07:00 +0100 Subject: [PATCH 035/104] Correct Motor usage in recipe Thanks to Antonio Spadaro for pointing this out --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index a966fac..ae869d6 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -493,7 +493,7 @@ Spin a :class:`Motor` around forwards and backwards:: from gpiozero import Motor from time import sleep - motor = Motor(forward=4, back=14) + motor = Motor(forward=4, backward=14) while True: motor.forward() From da9b0bb644fd742c821ca4687b86be98493ac7ff Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Wed, 11 May 2016 15:08:28 +0100 Subject: [PATCH 036/104] Always make PWMOutputDevice operate on floats. And better 'encapsulation' of the active_high properties. --- gpiozero/devices.py | 5 ++++- gpiozero/output_devices.py | 30 +++++++++++------------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 97c4040..ba2ecc4 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -376,9 +376,12 @@ class GPIODevice(Device): self._active_state = True self._inactive_state = False + def _state_to_value(self, state): + return bool(state == self._active_state) + def _read(self): try: - return self.pin.state == self._active_state + return self._state_to_value(self.pin.state) except (AttributeError, TypeError): self._check_open() raise diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index ecb2a5b..c0dd6e2 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -43,16 +43,15 @@ class OutputDevice(SourceMixin, GPIODevice): self.active_high = active_high if initial_value is None: self.pin.function = 'output' - elif initial_value: - self.pin.output_with_state(self._active_state) else: - self.pin.output_with_state(self._inactive_state) + self.pin.output_with_state(self._value_to_state(initial_value)) + + def _value_to_state(self, value): + return bool(self._active_state if value else self._inactive_state) def _write(self, value): - if not self.active_high: - value = not value try: - self.pin.state = bool(value) + self.pin.state = self._value_to_state(value) except AttributeError: self._check_open() raise @@ -318,23 +317,16 @@ class PWMOutputDevice(OutputDevice): pass super(PWMOutputDevice, self).close() - def _read(self): - self._check_open() - if self.active_high: - return self.pin.state - else: - return 1 - self.pin.state + def _state_to_value(self, state): + return float(state if self.active_high else 1 - state) + + def _value_to_state(self, value): + return float(value if self.active_high else 1 - value) def _write(self, value): - if not self.active_high: - value = 1 - value if not 0 <= value <= 1: raise OutputDeviceBadValue("PWM value must be between 0 and 1") - try: - self.pin.state = value - except AttributeError: - self._check_open() - raise + super(PWMOutputDevice, self)._write(value) @property def value(self): From 3ee154189cd823ce409fec3858612980dbb96853 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 23 May 2016 20:57:58 +0100 Subject: [PATCH 037/104] Don't (implicitly) set an initial_value in PWMOutputDevice's init method via the super() call fixes #326 --- gpiozero/output_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index c0dd6e2..cf10944 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -299,7 +299,7 @@ class PWMOutputDevice(OutputDevice): self._controller = None if not 0 <= initial_value <= 1: raise OutputDeviceBadValue("initial_value must be between 0 and 1") - super(PWMOutputDevice, self).__init__(pin, active_high) + super(PWMOutputDevice, self).__init__(pin, active_high, initial_value=None) try: # XXX need a way of setting these together self.pin.frequency = frequency From 48bac77d29e2b9048dd328a41de19bccb6ed8b67 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 24 May 2016 10:25:47 +0100 Subject: [PATCH 038/104] Remove redundant line of code self._bits is already set in AnalogInputDevice so no need to also set it in MCP3xxx --- gpiozero/spi_devices.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gpiozero/spi_devices.py b/gpiozero/spi_devices.py index 84cc8cc..78c2a5d 100644 --- a/gpiozero/spi_devices.py +++ b/gpiozero/spi_devices.py @@ -112,7 +112,6 @@ class MCP3xxx(AnalogInputDevice): def __init__(self, channel=0, bits=10, differential=False, **spi_args): self._channel = channel - self._bits = bits self._differential = bool(differential) super(MCP3xxx, self).__init__(bits, **spi_args) From 3c61b71d7efa0c7b27f90cfde972bee1772e8d00 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Tue, 24 May 2016 22:56:04 +0100 Subject: [PATCH 039/104] Update RTD links to .io --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 22c832a..25c2f87 100644 --- a/README.rst +++ b/README.rst @@ -73,7 +73,7 @@ or:: Documentation ============= -Comprehensive documentation is available at https://gpiozero.readthedocs.org/. +Comprehensive documentation is available at https://gpiozero.readthedocs.io/. Development =========== @@ -99,7 +99,7 @@ Contributors .. _Raspberry Pi Foundation: https://www.raspberrypi.org/ .. _GitHub: https://github.com/RPi-Distro/python-gpiozero .. _issues: https://github.com/RPi-Distro/python-gpiozero/issues -.. _recipes: http://gpiozero.readthedocs.org/en/latest/recipes.html +.. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html .. _Contribute: CONTRIBUTING.md .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 From 9278a2f8574b31fa069ccfb3f058b9b760000652 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Thu, 26 May 2016 17:06:05 +0100 Subject: [PATCH 040/104] Add hold/held properties & methods to Button, close #287 --- docs/api_input.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api_input.rst b/docs/api_input.rst index d61ca6b..5facbae 100644 --- a/docs/api_input.rst +++ b/docs/api_input.rst @@ -17,7 +17,7 @@ Button ====== .. autoclass:: Button(pin, pull_up=True, bounce_time=None) - :members: wait_for_press, wait_for_release, pin, is_pressed, pull_up, when_pressed, when_released + :members: wait_for_press, wait_for_release, pin, is_pressed, is_held, hold_time, held_time, hold_repeat, pull_up, when_pressed, when_released, when_held Line Sensor (TRCT5000) From d2833e988f519a62ec98d26ecb07aaff669c5088 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 26 May 2016 19:24:19 +0100 Subject: [PATCH 041/104] Doc fix: RGBLED's initial_value is a tuple --- gpiozero/output_devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index c0dd6e2..e5277ed 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -545,8 +545,8 @@ class RGBLED(SourceMixin, Device): Set to ``True`` (the default) for common cathode RGB LEDs. If you are using a common anode RGB LED, set this to ``False``. - :param bool initial_value: - The initial color for the LED. Defaults to black ``(0, 0, 0)``. + :param tuple initial_value: + The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. :param bool pwm: If ``True`` (the default), construct :class:`PWMLED` instances for From fea0e79beacb827b7449be65775258bce68ed1c6 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 26 May 2016 20:14:51 +0100 Subject: [PATCH 042/104] DistanceSensor: make the max_distance setter more like __init__ --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index f1153f7..05682cb 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -615,7 +615,7 @@ class DistanceSensor(SmoothedInputDevice): @max_distance.setter def max_distance(self, value): - if not (value > 0): + if value <= 0: raise ValueError('invalid maximum distance (must be positive)') t = self.threshold_distance self._max_distance = value From 3a7acbf89ddc3b18992b18c177cb935e32bdcf9b Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 26 May 2016 18:34:51 +0000 Subject: [PATCH 043/104] Add LedBorg as a subclass of RGBLED --- docs/api_boards.rst | 7 +++++ docs/images/output_device_hierarchy.dot | 1 + docs/images/output_device_hierarchy.pdf | Bin 8304 -> 8653 bytes docs/images/output_device_hierarchy.png | Bin 21699 -> 23066 bytes docs/images/output_device_hierarchy.svg | 34 +++++++++++++-------- gpiozero/__init__.py | 1 + gpiozero/boards.py | 39 +++++++++++++++++++++++- tests/test_boards.py | 7 ++++- 8 files changed, 75 insertions(+), 14 deletions(-) diff --git a/docs/api_boards.rst b/docs/api_boards.rst index 6b2604b..2db1d83 100644 --- a/docs/api_boards.rst +++ b/docs/api_boards.rst @@ -36,6 +36,13 @@ TrafficLights :inherited-members: :members: +LedBorg +======= + +.. autoclass:: LedBorg + :inherited-members: + :members: + PiLITEr ======= diff --git a/docs/images/output_device_hierarchy.dot b/docs/images/output_device_hierarchy.dot index 1cd5e02..6a92a52 100644 --- a/docs/images/output_device_hierarchy.dot +++ b/docs/images/output_device_hierarchy.dot @@ -21,5 +21,6 @@ digraph classes { PWMOutputDevice->OutputDevice; PWMLED->PWMOutputDevice; RGBLED->Device; + LedBorg->RGBLED; } diff --git a/docs/images/output_device_hierarchy.pdf b/docs/images/output_device_hierarchy.pdf index 91cb8c143a9c06373374c67288383a1c502ef4b4..b7ffe954af8490ca234b62f9bb51599bc5451f79 100644 GIT binary patch delta 7062 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 LiAh#n4*P!qHLH_o delta 6744 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

)22c;Xs?#mmX!^t4RddUE$~eR%lHc1jr{rx676 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 O8FE+4KobSGef2*9ll3wH literal 21699 zcmb@Obx_wq*S8S_1VKPR8blF#cj?(XiEZjf$}Zg`jbnRn*-|GAxE z_<`TpJ$v@-p3ilbAQ>rPM0jj?2nYy7Q4s+-2#7ZV;49-D4EPNxYB4$Z_ST+HRN)=? z=kd-U5c~{hBcf^#uAh7Tdc#MS;sSn&B~M^y9P1yVy@;GRb`H^e>j&NjpmYVmS`Jx>=YEEdjI;)vtjm2NH zWBL*yNZD!g{K@xpLoL~U)ul~Zu-Ulu?+JaA-ex27C5nEA|F3-1K#~niu`%fvvSXO! z&FWYfg+f|`@bKZ2DKgt3Qp}z^>x>n4-AS{qsC6!gV(z^H@3lqsdO2e_udaUgE+mtd zCSlrls$iNq)Z+kMulc(5id2TI!r4J6tKlk7;6Ings9o)i|LrGzqeGuGf4M~C!F(l} z=hc|_cVH*}SnQHr?xWP^(MeEB0u6*=ZY~=7*80(oY}V+nWs4f8>v@wJ&c`kZ#=2|t z*wo!j}5>&05!*l6}a21#jT}eo^}fp-ff6 zQh41iS9x?-Ww`6&xs{Vjfx=rdrHE1}qGBs>-s53aW;rWn|L{VdHbTv--q zd}=8v>qQN5$EMoCVu~=1c-+x7zPiDq4{K}l00Ti!le!yr#_yv4h}`CKY^sI3np7=( zGk0*+^!Z}dJ8Z5^Q;OF_KN$Wz@mj>Yu1t-u|G7JGe>#MU z0)xTf(E!4!jACt!=QqlIb{G8d^JOO^f?;k;y#vSCNqSFVX0af84l&Iyl~Ps>OFb4`e5sH^OiSDA-mR|S2=P#N;3t{ zY&y2Pxy}YWgVWpKDre^^`HJuf6MUvx^+So|6Wy}@HO1Q^-PfwIw1*Aqmvp&?j(vAE=G&wwh&?JpXf zEhY<%EFZ?=tlt#XR`>UIhY)eFpiCE-`Z;PnBg!nzBd^p^Sh}ycyKAX%efde&BRM1@ zx;3AEjJCaN_k1Ke`*@1el~SL*TOkaw$7%Dmc*nD|1u1W0d-Y+owFAoJ7&kD~5|%gr zO?MLAI`@bylCo!68&c@*IP{v{?v}*xn5T$%aDV3ldb_{wrMnGeyVnIFgrvIT zR~Ck_9B=E~n}N#u7s-ZUk=IFs&1g2%h#GKrm9oIv=(@3v)4`MH%@@<~A>I-+ zhW^v2Yl^h?o$=jf7ZKLKftYwpNIo3YrCb{5#sz`;Lo?Mf1#;8nDk^fvyL|>v0?ddo zp<(}Ap6bhCq<@I;x|dsXZ!p=t;Kxq1N}lg1cDz~foC$dJ*sqGh>etJ1Xf<&s(6jOH zy&l*?&rvFD>J%AD41d%PS70X@e&Tz_04G4`OB=(B_Fw(D!937n^L$;Gk?f(qR9+6k zUV$#h?Bh^XId?r0JYFfJ+V@E7 zTH#D{|IqpP`CV_40$jZA-4QU)WWs6>3lwKm)SbV;3CNZ^B3u+iIR%G=xZbV@s5aOT zLUi9dWZS!p)@a7@uPj#Ub!x08v19QoeXKSbkLT~NudnNm*$$zAt*koNy94&8 z@{zExu;OViFD^D8@0K_Uv#0iL92|N~7$Gw!OacP~|3>gUcdV_w)N$XeNZil!E&o<5 zR+`9@!~yT6)P46SPcmicdD+Ry$=BCc0=<@vadAv zo$nhcn9QktG75@n%KF;c(m39?CTne6&KtHhauq&wHAdjt1LJ@=KlG+QHw!$TN{CDdPR% zcx)cG|Cs78E-$}wakVO$4Gj->&&<@y(Oii=-CybV2BX`lNihv@d=+47XlS5yzqaV= z>LSO>(<%c7sa1-pu4ef8^QW?Axm=;iTp6Bq+g+ZoF4JH$xc=&3CPcqK{OqLpbmQ;e zA3`~W6>ZF<3e;vxbyVt#oHUKVv83 z<&hK-hgqOdAzLZoXAO^AuYspI_yEW3{odAwwRg1lJRSN+FgT?S9f2fJ!Pw>vW`|<$0wD|Ni}d zZj>l`XY1pSL?C5nN3ULQRf}6Tmcf&uT5U9QFFK*Bs_L*m=?A(KphJe-|qcD8mpnwK?&?A4)FV3lpEQ@Cmg!JygcgeBM-h$8a! z_n&o>bS4M=EGjH12~AAIT&y+kSJw74Jhm$fmwW;Bm!dWMrAPn|+};Ea7w7-YbjS%T;vxeKzM?qIH%_O8YnC*{P{cL_LZliF8o4 zYNO&btp>+(W<4dU)vO-3v^ck#><t0fGy#(b9<@hL+CRMlUY6$GqLl@x2t-iB=2ayZZ76e5f>^ zD|54z&wG6Iz-F-;X@9<=(ek=81RWp+9eDF<>2N%bD$^+vc85KkV>`1^B%a6DK*Q$s z$ol5Zn_24qgRjVft;Y08CR2HcQBhHkx0?~bZc0fKueb1;n2-TuRy(mNk^Uq*Xf1Dl zP8N+Ju?5zAe0l=bEM01KXR%zWGu-S8b2yrxSlm{Ldz)P_XJ>C86d0%j{Hx`5BLuw0 ztS=0wKb^~czSZ5i6_ckm`Tk+Z4?@fP$x%Z?!{vIxxKuM(aWs`JhQWAj8RaIpF%1mp zK`EGQlP#|H@l@(UFVEnG;WMM@Tq#%#Mm8HgaLdcfC7R)iBA>oHHV&V*2c5=15Bn1qQGb}%$BI~gU6mOuGwlw zfNPvD-MA!Em<_>{ZMm9Nf1MhyA-A!y(Rn4#tB9XOQK0n#L^e4Mjgjd>`PYHHe115s zwp?QVM6F>0yfW<)STi~?QN+ODtG34(G!oZkBzRNxYs~R_JQD`9Avi594g8AfV_>!x z7LyqoSU@Zms(Kf5r%eR~1qFnJ=s}DEON|j2t}xIj7G|qukqCUQ`9^!v%F4>uMm=mN zXuBL$_OrTOl7UbO=4vgUuv<-gRVf8pH_Eb&DA9|X9$Ytr=XOj8%DV(w#&<|5W$b7lkM|UQuJ?OhO|F3RM z8hJ|^bQes&rOvm9)ckhA)mVOKI++?tLPdFqbh<1cbr1H?X`1hX1G~EOAKe`M?i!Dl z8vN@#VRGs!D>+H-j^L#5<|TEtsNZCv2-F`3i+P&fa8`Pg?u+h%1Ztue{eKcD*(DRR zBS%l#ZP`AFr0{%iA8hk#>I3|Bzv5Oz64b2&F}CRBaX(Ja+~XQE;$wcUcNcaYCckb& z`Iys&GDlRr)<=+$VD+$LwU1_Xe2Kofri#%N{1J-iTkSFaWJi)yA@|^!eET<3QBft2 zhN0}OWqp*L0f@UQXJuk>|XZQrwgr*jMs z5%7?a7c$a?gBZU?*v;1r zQiR#C_xv(2mk<^0zHfHydQUI@xy{R3KK%DqRJ?X*eonj%jqnD;dsq7zx({jN%~Yqa zGofxO;D-Lx$F$|&nY2JpO(i7_1(oh;9us<9*zsh*L%Iw8kF{N|v99s8?EVV6xJAwe zORm>cVY)p2BjzW#4o}qd%_;Af7=l$VKjXEBu`0_LB|MkQT=5&()oNR&4C%DMkY>_R zj>`g>@w;33c@Fl?eT;WMCQLr_aJv6Ox1gtb4~b5L3H zWoE~PIaLC7$h}0~P4M5HPv!E%{wF(!+!^oAnXmeR*^5eJY5a%yPZYj{KNf|+C#bUf z2BA?kStT_wDIv97NAJf+;xw4>8u0&OrAEMa>}{6ZMyl(C1o0@&kRgSG}_+r#;Z^MB(`{D6NiacTVzV(FUfC^qK|TGs zMl6^%OYU(jm_Dg0H;(z}9bAuL)z!@c0imgTcRNo*(Ed}%eE0~x8AOr!^M@~O$=pfK zpR5Zf=th?Yd&m4ClsWh~@myN;-{DC8&Y@v|L_XObD7@@4Td5fumL0D7DJe^iCh))! z6J2clT-IYYM@Py0W46;O{w1SDrlWpoqrAy8X`*L6mXog)x9AC%g%46zA*GC#Ks`X5 zV`G8nA&J|nV*5qgnk|G+c6C_gYAT^b^Af68Q1BKy;G*Y{AV)@t`||__dUj>$y^XxN zaX{JUmt>}eMK+ldnl1yzD(^#f5FyAYsJ8!JBSO6O7q-(|`vAkq86^ef&u}>SfW>v^ z$f%|chEgQJ>qb#^Ed&{v;)2BxSk0`Z9Yj9|n_VEn6H|Tb zJb#JIqq;RR_sG?GyT^S=cpq^`>J%~!xOuY{3Guan->2}_0D-B79CF-N4N7AX?XIaDMJGLZ6 zfAz|;NIgvy(fshJw0J$$RoA~F)69$JvuX(*=X0iQYw16lTm5PJd(JdY-x-x~!o*6~ zIe=8R!t(}+8FIVRrIFJUtNo~Ln#iNZlb__qOyQV!87sqcl&+_U(V>3#)P8~GGVvgq z7j0&?%HA3TdSah`ZlAu{ljJ3{<0EoO9R=b%duwK@7XnXw%m6u8Q-9}8>kHeneFpx#j|sjFYoFe+akMKaDNe{tWSOZJ#_2=>BH7y zk2<>K+`i$+_IGDn+(beAo}2chsl2>wn4EmwuNPMJJmQ*iHDzRC+JiY`XzPwU%oJl| z?7CW&Wm-$O@}~|Gtb!OcjQ(f@X&-ds#FsyQH?ye?&wVW4fp@BxqeN6*jBk%T_S>AK z&_cKWwHXo2G=Z@blk5^-`@XGvW-6dFLK-qt@y7iYXfhrwhlXy;EHD``gkRG+`AkLWsMxRH zW@@exQa*Zo+TTUpXL0LNJUn19!{za$MU-CSUA)8=krKiNGUVR@BLo+hFYV(`xUj|; z$@QmNURK+AOv@L4c&rY%D(a^8p-b3PhvZkfT_N*Cs2ssq_?OJP)7R??%SQ z{wU68>5Lk6(O<$FrxT|mOTrtTV|=8yqXf@GHZF@0@!taFMc z5(QNo5?mQj>1bo{6zxixeDQ%AUrV5`uK1^OgA*=$hWG(Gsowa4i~kGGpSLu^RQu4q z9&Kj`0rKZAnT}9>P6&A;IvWx~Z^miZJKQeTZNOIOH!hyE)BA1%`lw=x?S4GCku^`# zcU!!6_+c%0rd!7*s!oK`<7>NB3mLKEvF!}9&tvFj3wGpCsQ;Y7UaX62ANkZ4{18cEFl^=M28N7sq{0xnxd#CIpZrLh&Q?!TYBm{zTpM{4(q=qNPS zOs2)OaQ?(*(LK+tVX`3sxu5cXDgXbykIPYDE{JfuyPp&j9?-xEy!lZ%Ni}#_2emX? zZM)!Mosd5&{`$HquvVZ!O3cK?Zp)}7wpbpr%|Np>UW7) z8vML#tthn#m>lZMuj{}~w*7yL@meRg4eBpk0=E78GsPI*PgfX1IViBOu=ETJ6K1uu)Z)v~APYMo+uz@hr+xe9 z@Tx?kadJp8sM!x}RC6d*Dj;f`HhOwMPN|b?*0Un?`hr}LKpidC7QZSz{6a!7Qc_Y% zMPkcTuMfi99ZS={Ia!H|Pmv%2`IwnsFt`g+cA3uW`OcUTTQYYW#^>j0`^zdkdQfv1 zE`11xPz;GAD@cgq4r9fL3kwTfTwQ1E6gRA1TP@6Lw>6;Hjoi`K=MVk<)3l!8hSk@f zWGC-W z38X2jsSSZlTwQm;dCjxG&GR9V%iYOu`}%t3g|Ho28N_AB^sq3*l_tmH3JV~U_4M~E zt5NrqzRETKeKGdGr|inBH$%v}bFaDh_mq4wPmm><k=>XEsP7j-=Wgi+$k@m8@f) zQ{{wpmE2`OpH!mhTY$&EYq5Fj_v(vfdi^0>HdS%l!AygM)&E79^&c`e*gGc>hsL{v z?3{edF0V&xN6VG~HQ2~9?XOh&yJ18{pXnPqJXmLP{lzWsS(hdWr|}>(3!`E+@OG<$ z2dK$QD|bD?VhSIENI4&)pl=!6BreE)PqxfA z^ck~m*?%5uRi|ugUQU_h;P|4Ht zy-HWPLYIdA*rwvsmXrKwahy{*C}mE}%Phtme#C|l9+mr)?V;wwPqsfFYba21`F=M0 z_=o<|8{Ry!P$-ClKMxtUsir~VLxM%bu8oHD{FW<(3T`*}6#sX>pcm{AtFinrM=>nt z3S#HY6}I-MuTD}eW@51=&=Ro0miT+247d*Zb92GlACvS%@}KZ4IP-p{;1Ps}uFpJ6 zy6B#LAGU)={%aQCqcoZn$|o*8H8Td|S@`D3uvN8GEhtUWm1-C%PsOao!!s}+iUc)P zjH1J4!3{PnSqI5o?&1^lWs4x!1hjKVCrI}&ISJ%c|^MT zqiuD)?q+ORP3l?xc7=;YfDYvHk&EFzq1FKoTt~@6vMyuNtw%SOoSh@3M>bdck<@Q< z-vnZ0(vcPZ^tRX!y7-(+)hIqZYi6#i^{3{2c`;azPM`g}9g+HnMTbdq^dMZkCUTKm zH1z7i_uB~!SLc~MYEIBZ+1MX7EbeR!-al#{fg$glnDzG_T^r82aOIpL!u8|ptl1W( zw3OIK{|Wlvo`AQ}xQYT&_B)+pQ3#amI=s{WZ(%ijC5tvc9M!t-(A0YAaz&QLa8>D=Ok)Htq~(|8dQp`m6Q7)x-Y zJ?U>BoJzR4{nCObn}XDMdgsFM0h>uXG<-WllRwEZ?au%W^=8;w#QA-zB-Yi;hv9Q% z=>YP2p2XCUPJ=Jg!#}|yXN?fNAtJ_&a<7Zzj2LCjXxB4kHw}vOJPTm^qTppas#PqJ+qpU4RHhJssGZu)3G zy^1Sq?a;9|mrZ(pQ^3T)Qk2v>ew@~WAP8))xgvZ4TB0$-`!lCmCCm^s=TB?~pW<2? z&oR5zVE&b#toypK{+aI&!t%*6IoLHwQXykVo_Ax9>F4lA-q{`?I>!FEeQIHv(@^rb z+zYGDk{MKvO1-0%35PjH=WF6^?8b7YYZA)y^3eSF*e1UZy>%$=TEowe?aFa3x7IhW z@##mv`0l!ead(H27ufA)F$I+JWo1fMaNzNAVdCR-VC3jy&5uMV$K&Ts>-?V9*;=xM zlQ_Z_doj^oK4ubd;h@j6j09@gqn=ax=!Opp)qF}Ke>B_g^49?Z@w|KuM^K-D)FaK3 zDq!)#DV?aN`==-uHpZ!+CKyaRy(TYc(v0j`i8DU^q~@Gp^F7Lz^Ww0<_biO!+O>mV^~(A| z=4}kqe;bo3&rQVfg*uW&34arW3J2?>-e2u8ceed(;wi8(7ke1n!v#t|Ex2L^_b>Uz z4AAVDbCAI#9efY%V7es=dgzoo%2%j>e6TH&qT`(XlvX)c0QRHZ6vH24A|3btco=&y zi{<`$P=`Ku$wyv%+Z>ZxTuv*MSWH7aIhRD>oLY^52l3W@K1NlhD=L=r(okp?Pdr~( zp9cRBS>na04TR$g4#JdQ8}zgI(!1#<+I>zY1DT(m8x|KTwjGRWrO*i0zV&=O?_vi# z+rpx(kG2@t?8Z3#G+XM&cSJ3Cs<#5}$9bXSXmCDi6r3Tll7-0mpc!OY)P<7uo3l_|Yt`u;gt_*3muA>=%98nfmw0 zSFh?i9tmpW!)zquiz8Z5lcVC^2-ORb=gQ5;P`;BU<%~m}{pPp;Ax@!&GBaFskpuXKcur!5xB`0zd$rLHnY6XNH>{vCDn}sjS3obK1 zTp#OC{uZ@ZZa{%hS68RjX!sE$e;jarbqTN};B;AXh5v4Db^!)3p0Cga8{BDnFtM0U zmK*Iu@wt-c=T!hG`!#65S3Cn$_<43Le~5AJ2TH&X0a5sfii*W%_tn`gD%EDisZs6eIH*zAlvjOqs!z@xDXLml)Fz!a?EmSxM zd@pmxjA|G>00Q7b5_0ljI8>6zWbzx|#uQN`*=0t+n#OrvMlQyAq}|-ya`D#%$>V~- zu9((g?~hv9zdi#)!}Z-=VNp@hxI>`bfwCJ6ZI7tk3~zj4;eNF;nPsbwfg5?P0DH7N zo)6EhjP>^iY;74RC?z(w%0_VClkvPfUMTXPWdjsi?R2b$2A_o0mNGzC-C#=7cKZd8 zS4Oez8E2wWrQs=Q9#3Ik!j9MPqqNf|j5TI6!G7;C#4uO=z!G4!z;W8r9YWJ4qSzd* zR!YLghGlJSO`+gq518#dAgQ@?`uh6(0N$w7n4kdMu2dwoECn`R^+$KR8M~GJ`22+m zN_$MEjMze;_ofue&YMEvxT?Bc0$DP&R?DowQ98B;V@g$$8jdCSmVb?=u%sLv9Z8h8UMCNN zO|jGQq9H(94Byb;+g7`g<1WAVfX3^Xo15ptTWflFaDwzDF5dfoKVKr5 zF(_9&fnKNM$0CiyGN>rn@HaFzo+^|lU0zuMGZECd+;4|qd>Eicqr;ygb)90mT5=hd|Q2)O6F4~#0{%@ky0fuMw;(rSf0-R!== z$J6TdyxSQ`BGUfqj0v_CgMfhV{mFuQ#iO0SpyygvvkDf$r0a~4<{sSW4KXZO{{V`e z$OndovgPxoKxSL%c69)MgNedov{+->@^rlf7`HzFxN2ai5I+WXZJgU5ypB;wNJy=C z(R*S?ihFtgv;YuziORD z(j)Z^Gf3rMha9-L+snggla$afG-!e0^Wz<*cAG}mKjw_Ubem^&lXP1Di zOn(_OeBW$i`UTWo$mR&?(qfiqMi=~gtH)Jmv(A_9x`Xu$ zE*$ccGXJil^BG*I2NPz;Q>S=)30@fES;MV2|3N(X(_nIdF_ZqEt`IV?t@^w{w zsIILwT&OZEm@$L-fEy^4U+pA%75#QY^9ZaR8(?WM1eeyBPW1shAKExN8qO3cg7wE9 zEHK06swKRTXVh1qu!Gk1+@S6G-WxD5b-z!F2JZ(9S--br-ER1Jdj{4V8W3GZK=sF! z88I@_*IjyV<&9&nWVh3;va6$~O>YGEeSQTq98g;^Z1kbK`fWiuZkGA^$&HsQO22b+li5+?$f}|HaR?zT9SnE)J(~|rC$;zqEc*do9 zV~pXqpLZ_Jidu?-?6>WIlJ8{e2PyxXGRNW~w8nIP5Yh)VbRpE=yBPM|%EEj`C<)r% z=@!@g0v4J7<`M@K&FpY=Ctr-lSK~^K3g^mX%J3#t-p?7Na+3uUyT<^HSYLH#S5F-5 zH$g7UmLfYaJCy{sZi>yLAqn3ZmU;2!^mF9&Xp_k`tgD3etu>COF(o5aWk&8axXzLU znVclWq=l(YKbZIBteuucT_3=EV{k0`H@pGKOjM#~&(Gf)61;%8gsU+vjcYZf>w(YT zPp%)m$ZGj;=nyq-pJd1h@jrlkXB}j%uRF~;S%@3vn|>X}s*S3CNXGXzS*}0c1FD%* zcxS;AGZ^+b!$?k?3$uyHwc`eud9R_+@XN72x<(_lvVB?t@H$d z+}5a^Lsj2WOF9j%XZJxMQc;CZF8qTtEFvf<{Jo8FFD3uxFT5Ju+h*kgQ0zTlgz{J^+pGyh#Gqd(ly2yrt6e)@)c+iJ(zO_1YoAW(Yy(V^y$C^&IlSJ}=NMRK0PH zhxbR+y`*vm1=`x~wck1igA6JU(PAj0f3jk(zy#-LY_J9r|IUjAH1@IBot4Yjy2j!K zWN9D;HUQZG7WB3*En{B>0OP=4J{k<@aboL@>cdrn<2o3(( zS_jol$gO&30NNo}C=U@~OCvt~+k2$Qe45f(^V`j&F$vX*_AZRO(tmc-Rk~uzYAt7^ zx&G%e4%KtC`ZkP*(!b?3jCz3ZMPw~pmo~Ujs@Tt#;eL1%NBxaKPC>2tK_v}N;KyAv zs%q6KCxltQ(QL(B<&$&vjR^@>1aKMC!_y^oXytqnR&Lw$ zXgK)_-3v1<1`T`#JMJTJs~?8Dj*~^Ed%6*2(;^;5>x%AHh(H+9mqClcz}GphWE?*K z*80W8S;Zd4tI1E!2SvCP8<{;x%G-;}JlZDHs6Jn_RA zgY_<3=41D@$b-KJXA$wu=z^GsQB-z=7%@uFmOJC=+R6d5vGYq*Oq_R8C@EZOQkFc2EFOWrQ z91mLygDbP9b-uh*>7-YlV-B)yukV@}cZU*AjNmW)f^A!sU9a9c{@n|BG*A#7aZgWrKk{%W3Aeg;vb`!hz8TJ2-V_~{oiI!Dg*^EN(vg+ZItNyo1IT^l}Xf0egW$9Qkukeg6F zoSwIwl^^ZfTZe8_jbn>FtbN3e;ioS^5U4P070NXLDkWgfdzdGpyEf85>)45rrE39_ zE`;^kT&O8jO2x?n^$7N2=;*j}j(1IYZWsZ+x#WWk((p?PY*WQvkP`&weA62c0XbLc ze5HkY|JgkFE6!TeCIQOK36zm@lLzrDu2s53DjJ8)7Z}F%aZyc|Tq zt$kIcK*+d~Z`v@k_9+jc0zEfU-95AzUaquMxht1?l|sdzC(@1HP(uJMr(%oX`0iw4 z*2BRYfg=4tq#&O^&T*DW)sOhA!KaN=z2Q3zJOH)SV4I=-^o$4jmV9AO70GwBxznu^ z%Ka^vNx?zgEcvt3MDMN>&A;y!N~c(9vN?#Q{e(w*y%GQLPvmJGu}T;6Q1`}WyA<6Q zYGhbR*B3(|dwc5!N2D=M}cgfHJ5_s(sE=d zpPkOwfs*27CdmKf8sfHj5yYN3_*x+Q^i^}=y!i)&BZv|FiC@58f2w|($DJyIS|DX_ zE(v`CVRk=PNHAZ)(4M#7)wrj;hAp=w9tt0|TLxo?VR%afbF2h^HnZWEoo4Wsz;Lb|kYtw2uGnM<)eR}V$f4=7PK zUsrY|HREC>dV3o*f-;W52#&FJtMc{3fXyO{T@^F#H7m&ihk=%T1(YEeMl-d@|L5ff*kfK0xBwMv;V})CROVA3}}aL zuRPuYSdt)-4U{Se_BX6;(I%7>)5on{&s_xY*a97)Vjl2ssEPd`gy z^4y#rwjt3_vGw?W({$Yfs~W$NH%2pgeJkq``oVSwP^>`#6fTIuW2Ys;+D5;Pf3q;a z3RiC)2`S-9tlx2?fIv$`*W4n^^0jZxF4lz&Ed8fpCY`%DQWu-y_TW{iHQQ%19i!QzG3Tk8XQ8kkFsLzZb4Gx255aiF>gx3>OzrE1e6e%t1t9%@%M z^Yae*T2Fm;L66S9>80gCcjuM{Fv=dK};-eB^ zMRs3TT5snsjXUe`J`=ue)4Z7}T(VwcsS*^K{E4u5-CUw0vlC#7q_vKRhom8LXdQHx zCAiaj+MsD$vY9W`JM|h*F#m1|mZ z0GwR}a5r`Vm;(=;Nl|*s+EKN(a==~pr~@xZ3==#YO1Y9C(JXVbbk0IX-gxP{H#Hpq zWc(U|LPsm~f#ZXOVO+oZFi06Sxb;CD8l5{bQICY-1IW8cim8mRQ6dZA{BAGc~+G%Z?I za(#cJZnbA+?Me-p8CdP(`7wWhm?xDNx0ZJSwEb5+2liU5W-~7ACaTUY=Z6l}_21v5 ziWGaeOM_d4VuBhngE@AyTL(=P#2oq#q@!ozIW@oCc-6(p`AmHLhjAP_-Z!@p|Jm7H zjoD_qw-@%!Etn`uomODx7#}K(uv1Yzgd$ zKlBK#hC5hp^-cj0WV+H=@P(eGRmX;kbp+O+0+)>*}WZdC-KH@?1;ql#;rB; z;)#{p{*V-}EMc1z6|RdTchfKUVGIo`{CTI+v07Oj(r)I2FiH4P1XzZF5@&kc(H>=>EW z&i%C8c;;ST4nGl2y4+QFJI#Y7HBj=qgO zk5=56x(vlhb;UW zM5l7vPRI%h!jKPHC%|9J|KM029F8Q`eSh1y#8B(~6%LflF?)AgvLu1<^2G3Jb}a;9Z;c)-LQ0KF)rlm z&bG1nJ69T&VA$YY$E4@r^3rmId$wmWr-uJITna%Q=_^Y0QNP`^D$fFN=uMfE0Y+Ta z-_x(4x=rAegu7v3=}*sGkZdWo##L+CA;`HIIGKNFCP=0p1&?p6?s#E_<`suuZ10N) zxo%45y?E6OT~*Jef0_Ib*)l!a6ySGue+?i{rI1OD=ZS{RU=75YX?hdpnTmnWY?E1we3b8jFdc>O2>nla*>&E`irC+D3 zmF76b^Hf zCA*Cp0lXnPheCI;Bd_M2!?%nMgo{jZqH*%2rcX0Krpa6Rmt*w@B#^WC;>|mvom7yT za94J}75P~J^#nQo*>iFX<-C1i|GHj&XTPdB8|TxZ3wzl$HrP`|$L4J6*x|2^sl_K5 zpQ^oWs(O+j!Gb0R_Nh$v##W#FN_SUr3dCO{g2Uxz>YMMzQT~21AhbPu1haWP|)=>YU z^hC!>Iy&sIyK>=<_21`-Hbx|e^>KYu1dE;iBBDZHcMkeWbFzCmB2d_+!{$lGo%7C^ zg9((9clQs*(=exZu6#G&7ws`d+gQ)kl?j1JwY0o_&~Uxg_TYKaJR&1!`gf+CpJdLB z2s<=BrQuE)<|WG=dW<>p_mzQ}zhcgk>~rgNAR<(LxB6;;e%5{cc38jG3>}n%Q8Km9 zfhDuDR+f&FIZ~M8E$V?PcR_kSjs8U;f`)}G_u#f3!u`atUjjJOi9>-cM$%e$GX0+deh=ldN{V%V%)$) zYzzC-`@rHmp!d+f7b|PeLT2eSp+8&BhC&~6M^kLhfvW7$DWTo5$zSwD&4+zwOH{^0 zLQY*MUp)fYsO0|#0V}GD}~Np z|Ii|(>lkN{M*4gx8oQQWu(KyG6?NT@!L*o%+z9*p^<~cp)Nzr{3|%2Z0SB61KE20T z{TXhp-fgYj@~b+5r+bm5aH1~alS`Gu1pQ)PFY=VTOIV$zwqw}8p0(VArNLQ@46mk7 z?impNoaY6#;*~`&)~93LIYpD4;oj!M_*FRX_#lIRAS0V*rU(kaqG&)gW7P;YfA)pF zH#JP{7v5Luv;8S#@?CHW@gDQ7Ky0b((HnNMssZvyY}UT-i?ZmvnO7i>lgyP{*cnaPy=1PlS}}vN zzz*xX2AGz^^CkO&&FLr+v7&IsO#S=!pOf1ScuD*%(BM#3C@x#HukTw6W*qD=|4RLS z_Ex+!sQ{(oUZZL{)5))(iZN}~e@}ziV*W=!0IXVRd>9T3xn}qhP@@3`747Nki;j-| zH8@=%QP8WW3yv2`S&QdS=~gdPsFh+Oz>r8J@+vCEO)a6M`OKIZ9xv8nh6w?l3;3&2 z)7N^UpDsQnN)*;DXE3NF5}DcAT`2w#&MS@>goK1r*7D#%wak<&X3QyrLqmFi=Y(7G zEr)tOT+6E64}UBnl}_L4f>zC~qJ!(fkpbt)_>s7J0O6}tE1k!tGCW>i@AQR3)tEeq zTR%q!Dc?%7GXvlm2{okbbpS~tgb8ilE`b_PaQur*CF%imDZD!X2^9xt_$L%BZOZ7V zRlQm%brO%SKmNwdh!F!gY^*<;%tUP}?;s(R69p{6jI}U5H+KP!+>$HD;zaYd_@h%O z(Yjrl02)n)t**tgpjT-Fph5HRr+7$|4x?%m0)p{QA6U)FvOO2T5B)m-JQ^Hb?2gAI zCl~Pwo`^zLqWAXpS}fMUrHuObPTrRb0qkVTr5#7t3HDb3 z1gAi*SbdzHp6>B{w+pYZmHri39R(YE2$Wumh=_D7Ej9PrOzt~Sr|`5?z09hVDiswM zC$ZXoVPayUF8*HQ4ZeFkUCmKR-GP1p#+giFKa-a9c7v%vfgG?zaWyq;Ko70x2@w(b zm6b;cx@w_NcuSS+PnOvL%GXr#F?neNRL?v;Nd^!}a4c}K;>!*3PZuaiNXUWVVSR9T z@%H|HV}1Qi!^vYeMa|+)d&%YH<=af_wKhtvmc*78&UEi**MynRi|tJ2vl{k`!ueB` zUQhSTB?F#?7L&?tW#Hs#wfBoxbWF@QDk?*8-0*0*F$VN6IzE1<%E{i&Zf_zdAjkM% zcQ*hWir(yxNXYTo@Q;m;|A>#jx?$5;85xOuesNLGLB7AYxV7=&SXEB$Bk(_?wKh+{ z3sF!|IsqP3Y4_p+C)=ql)r)7cXUtxY)dxjH>;l3>4A>k1!+Mn}$%yNI4as7;U%q`S z9IOL%0e@=EmD9ngXp$vK68U^20Z_-s@94+`P8hz@w3dgHX3N#KL4)j~qM||BqwMT2 z;NN_kXR|ocUSPQSQ0zBwVD@a){q&n=@KO|8k9i`GH#eAKf+3&2O}0tpn>&d~tEgZ` zaGnRICQ|vnABb#3cfUC?+#ZVacsQ;HRk&oDQmAgN>42}w%gTP|<^3_a{cb{61gR1l z)Bpo!O=moYn~<(#UySd5MCak@c?Sql9JOHJ2SLb6=<>FEaxg0ZbS2Xi3*qGCbU7%@ zH<+jNjS>B`>&HK4!GWYd0pPA*9m4!7la6uEm zL0byVF(yPj_E^BN#c5>Ukw)`cYytS2UsV-tI2`{?a|m$)R6ncNOL+y5`^JcTUVH-Q zWLqxAI6y^obVmno?r1#O9=-t`w0 z5O^&(z+!&C{`F&E2bl4V9(VKl{H4;L? zPU821jf=70M;aO$z_UrZjR;dS z& zmi?vB1C@&78X8#pLb3QYG*TzGS<|%2jR7>Co}DGv3`Rbd%ZGohHIIyqMSDGE4c-z2 z4*LV=p0Be^c{LcdV>0i^$jBCr_uYH(1nT38{5&v&=^YOgUK<(0F8F!Tf_YfMRHJ0l`bZ z6zLs_5fBuvA^{Aj7^?Js=_NF!B!UE`1q?+gLMS3cKw1J43?LAC3+2)~L5g&E2fyC` zdw*-3pR@N`bFRI{IeShcoC&bXM}VI}KJW+lto&z54*-V?e4$I(DU_J6s__ zsBZrC@OI;)MaylL9n)G+0mA)|r-N(POmdLTUyZx}4)ieOrcN4B{Pb8L;D!Z*B-J$T zfKaifOno%_geBfRQ|V=x#c^gWrR7%N-l~&oMc$^I#sI5*pvmW6`n{Hf6at0Im7@T&kB|D#P-<>vZKzNwNzmDC(V(oA`7j*djB0(jJQm5^qneGGZ$ zKAD<xR0^3>hP+``Z@sM!&Jn;=3}X3NzHHB%O?YSCJy4rHr|Gh2J+a&x~Q zw3{=x;Uq5ew4Oq|)eyaL8{LIV{EG$@YXJfz>=i5FFddAqEX|6^sihArck-6L?|4N4 z?haH{RJaj)I-1robtV4+M%<%$agpsLFx)e&RYIr{PX62~2t-pga)~^=Jfy-!qb#$~ zey`KhpluJ~^Y#0kiL^ht-)E`|UoF*b$=Vj?=VybCwMv}Oe9y^w8u&px#>S`WRh;RT zq6g4gudnm8;KpLPE_ijNOh!{W2QOwnu{c}A=Q75WO0h4zDhf7)jLx!e!k7z0=bAY`^lsqf zKjA0p0T**^wQs&N{XVA9@UyoFrv$&e*~7kADpLA6b{IvM=+l1g8W(3rduTfPn2#d6 z0F?I#QfWe+l!Gzo{UeW0HkzELMGm@YrcjEG(@Q4^OM$Xj=qw#Gt$HLE@pHF;X1_Vf znh3K*^qn&1e>D}HD0!XZa*~?db=bE)6?t3pmgPp*cf0c5y;16AA`v5pwm2g(T`=;X z!e+3Hr*;pXqa6~o5I~eB%;op7@vJsCOR;Q_{=LNinU#DFsNemr8;)HE9D4}oeX1v; zLY|`_P=FexR_s0;wm&h^yf<%{JDi$9rp5S*m({15t)OAgL|nBe(OZ~eT#*_F`@fy3 zNi|R1kU=6s0-{7X~pKo2ipUgWN}E9_tJWzn%OD@ zc&h-6m};Pf&9_;NFD2H>JiacqYrmAQH)ivdK}p93W@&2L0MiqWI@T7AxA zCaSU+&e29@l-*ddY?@6SN<^3@tY)_b9eF+U1omvx``%*}0*mNzbIzYjji(7f?NA9b zw3o49G9q84z*}mOZ;-&n>{G1u<(a#ISo}Fj*RpXyvv=KNcgMH5VA>ntP=9)<$v}Dy z(NIDpm~jhs(itiw9hfJek@YO=P`IY;Vqi6$+{wvygY-oD{GvN#Of!R$VDUUHlwBgS z=VdiAW>H=fx}Z|;a0+p~Qdp<+O<|^v4sD-&GRldc^zaXOI60UQ)=qY8 zSoFdhnB|WOTx55lDX?9a-dpw4M!J_h4S2Q6*sR1RIhv%XP>#@~JElg)L$i1@kFQ}g z;&-Q}vdmD~@uFvO1K%;4mbEzYHppXduw7EYd?AbP$6!(oTAk$CCyV!(msibh&CjU{ z62!rucDT%=By_h~rgpRcY;xM4i%8BcNDYVFj_MTm=3^v&apAP6_ajB^U+s08SXxQ0 z7u!NDFu`eUao3ZKO>~ZGXZo=+on7zG7%4zY5Cbq4u!rzE@m5jgoj5$M^RszsA}_Gs z=2T@E*ZKVyEa)tC-Q7NQBU+FYxL03xXQ6er1LO0tOgQ~?DN0}%3r0{~CSSy+Q_GI0e63 z`dFn-hM0o}ngboAk{7_^%m-t3_QlSb$Xa%Q^s5x;m-}n$c7Ia@T@1K_F9s>CJqhr} zb!gQ#Aggvm*nP;`0FJK$N6-FR!#H2UiVspiKm694>VQn0(nsoCF%(&qjov-N7|&v7 zMV>GD7jJI#w|20`X!wgD*6&8oY-bO*pC3U~Al(ZrgM2j-t}NAWrM6Hor0>GGZj--s z@$22}dZfNR1JkpmYj#{i-KX*4Rz9b*hi#ezA=vZ)FP-!d`kkU-nV_Z2qi>18Ux zpzfiru}aIp4)BhRg7O}qXq!^Ydrw@>Q4@V`b*iJfmCltT1saqM;yh1Q$_QYeC(Q!b&eqSJBw#~UW^C&TC0)6tR}pfSmR@JmV+(R zhbiT^MGG?K>L~a*JK0LHD~7DJHei-AdgMM!j9{H3Vn*(Z82iM?=VStC#fz!T@ReoKU zfV2fHYjD#Z+BTQ#_tv@0!j6q%sH~&De??9_NF3S+1Fe7f_}jJoA8#fBmrD}o?+;^u zK~NN%|Nl8;su-S?Y}fvp Device - -Device + +Device GPIODevice - -GPIODevice + +GPIODevice GPIODevice->Device - - + + OutputDevice @@ -31,8 +31,8 @@ OutputDevice->GPIODevice - - + + DigitalOutputDevice @@ -86,13 +86,23 @@ RGBLED - -RGBLED + +RGBLED RGBLED->Device - - + + + + +LedBorg + +LedBorg + + +LedBorg->RGBLED + + diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index 63191c1..e2933be 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -106,6 +106,7 @@ from .boards import ( LEDCollection, LEDBoard, LEDBarGraph, + LedBorg, PiLiter, PiLiterBarGraph, TrafficLights, diff --git a/gpiozero/boards.py b/gpiozero/boards.py index e8bb0b9..31419a8 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -21,7 +21,14 @@ from .exc import ( OutputDeviceBadValue, ) from .input_devices import Button -from .output_devices import OutputDevice, LED, PWMLED, Buzzer, Motor +from .output_devices import ( + OutputDevice, + LED, + PWMLED, + RGBLED, + Buzzer, + Motor, + ) from .threads import GPIOThread from .devices import Device, CompositeDevice from .mixins import SharedMixin, SourceMixin @@ -431,6 +438,36 @@ class LEDBarGraph(LEDCollection): led.value = calc_value(index) +class LedBorg(RGBLED): + """ + Extends :class:`RGBLED` for the `PiBorg LedBorg`_: an add-on board + containing a very bright RGB LED. + + The LedBorg pins are fixed and therefore there's no need to specify them + when constructing this class. The following example turns the LedBorg + purple:: + + from gpiozero import LedBorg + + led = LedBorg() + led.color = (1, 0, 1) + + :param tuple initial_value: + The initial color for the LedBorg. Defaults to black ``(0, 0, 0)``. + + :param bool pwm: + If ``True`` (the default), construct :class:`PWMLED` instances for + each component of the LedBorg. If ``False``, construct regular + :class:`LED` instances, which prevents smooth color graduations. + + .. _PiBorg LedBorg: https://www.piborg.org/ledborg + """ + + def __init__(self, initial_value=(0, 0, 0), pwm=True): + super(LedBorg, self).__init__(red=17, green=27, blue=22, + initial_value=initial_value, pwm=pwm) + + class PiLiter(LEDBoard): """ Extends :class:`LEDBoard` for the `Ciseco Pi-LITEr`_: a strip of 8 very bright diff --git a/tests/test_boards.py b/tests/test_boards.py index ff324c9..1d519df 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -18,7 +18,7 @@ from gpiozero import * def setup_function(function): import gpiozero.devices # dirty, but it does the job - if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot'): + if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg'): gpiozero.devices.pin_factory = MockPWMPin else: gpiozero.devices.pin_factory = MockPin @@ -524,6 +524,11 @@ def test_led_bar_graph_pwm_initial_value(): assert graph.value == -0.5 assert (pin1.state, pin2.state, pin3.state) == (0, 0.5, 1) +def test_led_borg(): + pins = [MockPWMPin(n) for n in (17, 27, 22)] + with LedBorg() as board: + assert [device.pin for device in board._leds] == pins + def test_pi_liter(): pins = [MockPin(n) for n in (4, 17, 27, 18, 22, 23, 24, 25)] with PiLiter() as board: From 0a5499dfbe466d6c4de3a380105748ee2b3d93c7 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Thu, 26 May 2016 23:52:51 +0100 Subject: [PATCH 044/104] Add recipe for GPIO Zero Travis build LED indicator, close #191 --- docs/recipes.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index ae869d6..7ea0f06 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -212,6 +212,38 @@ Using :class:`LED` components:: red.off() +Travis build LED indicator +========================== + +Use LEDs to indicate the status of a Travis build. A green light means the +tests are passing, a red light means the build is broken:: + + from travispy import TravisPy + from gpiozero import LED + from time import sleep + from signal import pause + + def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600): + t = TravisPy() + r = t.repo(repo) + while True: + yield r.last_build_state == 'passed' + sleep(delay) # Sleep an hour before hitting travis again + + def invert(values): + for value in values: + yield not value + + red = LED(12) + green = LED(16) + + red.source = invert(green.values) + green.source = build_passed() + pause() + + +Note this recipe requires travispy. Install with ``sudo pip3 install travispy``. + Push button stop motion ======================= @@ -611,7 +643,7 @@ Use up/down/left/right keys to control a robot:: *not* work in environments like IDLE. If you prefer a version that works under IDLE, the following recipe should -suffice, but will require that you install the evdev library with ``sudo pip +suffice, but will require that you install the evdev library with ``sudo pip3 install evdev`` first:: from gpiozero import RyanteckRobot From 35d1de644b9d4daa16593bf3542c7a5bf9de75ae Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 27 May 2016 00:01:48 +0100 Subject: [PATCH 045/104] Add PWMLED recipes --- docs/recipes.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index 7ea0f06..4d3b8bc 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -59,6 +59,40 @@ Alternatively:: :ref:`keep-your-script-running` for more information. +LED with variable brightness +============================ + +Any regular LED can have its brightness value set using PWM (pulse-width-modulation). +In GPIO Zero, this can be achieved using :class:`PWMLED` using values between 0 +and 1:: + + from gpiozero import PWMLED + from time import sleep + + led = PWMLED(17) + + while True: + led.value = 0 # off + sleep(1) + led.value = 0.5 # half brightness + sleep(1) + led.value = 1 # full brightness + sleep(1) + + +Similarly to blinking on and off continuously, a PWMLED can pulse (fade in and +out continuously):: + + from gpiozero import PWMLED + from signal import pause + + led = PWMLED(17) + + led.pulse() + + pause() + + Button ====== From 466ff341cb9d91105bfe2b615f46af239c7ea78f Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 27 May 2016 00:25:31 +0100 Subject: [PATCH 046/104] Add to button recipes --- docs/recipes.rst | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index 4d3b8bc..f12d3d7 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -133,6 +133,33 @@ Run a function every time the button is pressed:: pause() +.. note:: + + Note that the line ``button.when_pressed = say_hello`` does not run the + function ``say_hello``, rather it creates a reference to the function to + be called when the button is pressed. Accidental use of + ``button.when_pressed = say_hello()`` would set the ``when_pressed`` action + to ``None`` (the return value of this function) which would mean nothing + happens when the button is pressed. + +Similarly, functions can be attached to button releases:: + + from gpiozero import Button + from signal import pause + + def say_hello(): + print("Hello!") + + def say_goodbye(): + print("Goodbye!") + + button = Button(2) + + button.when_pressed = say_hello + button.when_released = say_goodbye + + pause() + Button controlled LED ===================== @@ -165,6 +192,53 @@ Alternatively:: pause() +Button controlled camera +======================== + +Using the button press to trigger picamera to take a pitcure using +``button/when_pressed = camera.capture`` would not work because it requires an +``output`` parameter. However, this can be achieved using a custom function +which requires no parameters:: + + from gpiozero import Button + from picamera import PiCamera + from datetime import datetime + from signal import pause + + button = Button(2) + camera = PiCamera() + + def capture(): + datetime = datetime.now().isoformat() + camera.capture('/home/pi/%s.jpg' % datetime) + + button.when_pressed = capture + + pause() + +Another example could use one button to start and stop the camera preview, and +another to capture:: + + from gpiozero import Button + from picamera import PiCamera + from datetime import datetime + from signal import pause + + left_button = Button(2) + right_button = Button(3) + camera = PiCamera() + + def capture(): + datetime = datetime.now().isoformat() + camera.capture('/home/pi/%s.jpg' % datetime) + + left_button.when_pressed = camera.start_preview + left_button.when_released = camera.stop_preview + right_button.when_pressed = capture + + pause() + + Traffic Lights ============== From 2bd1cbc7494dd3ddcce0280e69768e484090322a Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 27 May 2016 00:29:57 +0100 Subject: [PATCH 047/104] Fix typo in recipes --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index f12d3d7..b73e3aa 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -196,7 +196,7 @@ Button controlled camera ======================== Using the button press to trigger picamera to take a pitcure using -``button/when_pressed = camera.capture`` would not work because it requires an +``button.when_pressed = camera.capture`` would not work because it requires an ``output`` parameter. However, this can be achieved using a custom function which requires no parameters:: From bd219d9e598062d7d0fff1e52066eeac5be80461 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 27 May 2016 00:33:45 +0100 Subject: [PATCH 048/104] Fix Travis-build Recipe Swap tabs to spaces, and use gpiozero.tools.negated --- docs/recipes.rst | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index b73e3aa..3ed590a 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -196,7 +196,7 @@ Button controlled camera ======================== Using the button press to trigger picamera to take a pitcure using -``button.when_pressed = camera.capture`` would not work because it requires an +``button/when_pressed = camera.capture`` would not work because it requires an ``output`` parameter. However, this can be achieved using a custom function which requires no parameters:: @@ -326,28 +326,25 @@ Travis build LED indicator Use LEDs to indicate the status of a Travis build. A green light means the tests are passing, a red light means the build is broken:: - from travispy import TravisPy - from gpiozero import LED - from time import sleep - from signal import pause + from travispy import TravisPy + from gpiozero import LED + from gpiozero.tools import negated + from time import sleep + from signal import pause - def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600): - t = TravisPy() - r = t.repo(repo) - while True: - yield r.last_build_state == 'passed' - sleep(delay) # Sleep an hour before hitting travis again + def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600): + t = TravisPy() + r = t.repo(repo) + while True: + yield r.last_build_state == 'passed' + sleep(delay) # Sleep an hour before hitting travis again - def invert(values): - for value in values: - yield not value + red = LED(12) + green = LED(16) - red = LED(12) - green = LED(16) - - red.source = invert(green.values) - green.source = build_passed() - pause() + red.source = negated(green.values) + green.source = build_passed() + pause() Note this recipe requires travispy. Install with ``sudo pip3 install travispy``. From fa440bbe831211fa5eb5ce719d824a46a2e243e7 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 27 May 2016 00:37:16 +0100 Subject: [PATCH 049/104] Revert accidental typo --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 3ed590a..11b7663 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -196,7 +196,7 @@ Button controlled camera ======================== Using the button press to trigger picamera to take a pitcure using -``button/when_pressed = camera.capture`` would not work because it requires an +``button.when_pressed = camera.capture`` would not work because it requires an ``output`` parameter. However, this can be achieved using a custom function which requires no parameters:: From a116e9ded01b5f8bd34891a2dd40450748a18d12 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 27 May 2016 21:51:20 +0100 Subject: [PATCH 050/104] Add PWM support to PiLiterBarGraph --- gpiozero/boards.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 31419a8..67370b9 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -508,16 +508,28 @@ class PiLiterBarGraph(LEDBarGraph): graph = PiLiterBarGraph() graph.value = 0.5 - :param bool initial_value: - The initial value of the graph given as a float between -1 and +1. - Defaults to 0.0. + :param bool pwm: + If ``True``, construct :class:`PWMLED` instances for each pin. If + ``False`` (the default), construct regular :class:`LED` instances. This + parameter can only be specified as a keyword parameter. + + :param bool active_high: + If ``True`` (the default), the :meth:`on` method will set all the + associated pins to HIGH. If ``False``, the :meth:`on` method will set + all pins to LOW (the :meth:`off` method always does the opposite). This + parameter can only be specified as a keyword parameter. + + :param float initial_value: + The initial :attr:`value` of the graph given as a float between -1 and + +1. Defaults to 0.0. This parameter can only be specified as a keyword + parameter. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ - def __init__(self, initial_value=0): - super(PiLiterBarGraph, self).__init__( - 4, 17, 27, 18, 22, 23, 24, 25, initial_value=initial_value) + def __init__(self, **kwargs): + pins = (4, 17, 27, 18, 22, 23, 24, 25) + super(PiLiterBarGraph, self).__init__(*pins, **kwargs) class TrafficLights(LEDBoard): From a13a7bb1c4d987ec284074ee05069cd6aeff0d75 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 27 May 2016 22:05:09 +0100 Subject: [PATCH 051/104] Correct PiLiterBarGraph init params --- gpiozero/boards.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 67370b9..192fda3 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -510,26 +510,19 @@ class PiLiterBarGraph(LEDBarGraph): :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If - ``False`` (the default), construct regular :class:`LED` instances. This - parameter can only be specified as a keyword parameter. - - :param bool active_high: - If ``True`` (the default), the :meth:`on` method will set all the - associated pins to HIGH. If ``False``, the :meth:`on` method will set - all pins to LOW (the :meth:`off` method always does the opposite). This - parameter can only be specified as a keyword parameter. + ``False`` (the default), construct regular :class:`LED` instances. :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and - +1. Defaults to 0.0. This parameter can only be specified as a keyword - parameter. + +1. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ - def __init__(self, **kwargs): + def __init__(self, pwm=False, initial_value=0.0): pins = (4, 17, 27, 18, 22, 23, 24, 25) - super(PiLiterBarGraph, self).__init__(*pins, **kwargs) + super(PiLiterBarGraph, self).__init__(*pins, pwm=pwm, + initial_value=initial_value) class TrafficLights(LEDBoard): From 90ba151d26f458a40f2360321198e8fd3814d088 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 27 May 2016 23:27:58 +0100 Subject: [PATCH 052/104] Doc fix: initial_value for PWMOutputDevice and PWMLED are float (not bool) --- gpiozero/output_devices.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 470f10c..fd30a46 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -284,7 +284,7 @@ class PWMOutputDevice(OutputDevice): HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). - :param bool initial_value: + :param float initial_value: If ``0`` (the default), the device's duty cycle will be 0 initially. Other values between 0 and 1 can be specified as an initial duty cycle. Note that ``None`` cannot be specified (unlike the parent class) as @@ -491,7 +491,7 @@ class PWMLED(PWMOutputDevice): HIGH. If ``False``, the :meth:`on` method will set the GPIO to LOW (the :meth:`off` method always does the opposite). - :param bool initial_value: + :param float initial_value: If ``0`` (the default), the LED will be off initially. Other values between 0 and 1 can be specified as an initial brightness for the LED. Note that ``None`` cannot be specified (unlike the parent class) as From 25ccf9389c4163614b999489c8db39d6f0195cb6 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sat, 28 May 2016 00:43:12 +0100 Subject: [PATCH 053/104] Add LEDBoard and LEDBarGraph recipes --- docs/recipes.rst | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index 11b7663..a8b09aa 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -239,6 +239,81 @@ another to capture:: pause() +LEDBoard +======== + +A collection of LEDs can be accessed using :class:`LEDBoard`:: + + from gpiozero import LEDBoard + from time import sleep + from signal import pause + + leds = LEDBoard(5, 6, 13, 19, 26) + + leds.on() + sleep(1) + leds.off() + sleep(1) + leds.value = (1, 0, 1, 0, 1) + sleep(1) + leds.blink() + + pause() + +Using :class:`LEDBoard` with ``pwm=True`` allows each LED's brightness to be +controlled:: + + from gpiozero import LEDBoard + + leds = LEDBoard(5, 6, 13, 19, 26, pwm=True) + + leds.value = (0.2, 0.4, 0.6, 0.8, 1.0) + +LEDBarGraph +=========== + +A collection of LEDs can be treated like a bar graph using +:class:`LEDBarGraph`:: + + from gpiozero import LEDBarGraph + from time import sleep + + graph = LEDBarGraph(5, 6, 13, 19, 26, 20) + + graph.value = 1 # (1, 1, 1, 1, 1, 1) + sleep(1) + graph.value = 1/2 # (1, 1, 1, 0, 0, 0) + sleep(1) + graph.value = -1/2 # (0, 0, 0, 1, 1, 1) + sleep(1) + graph.value = 1/4 # (1, 0, 0, 0, 0, 0) + sleep(1) + graph.value = -1 # (1, 1, 1, 1, 1, 1) + sleep(1) + +Note values are essentially rounded to encounter the fact LEDs can only be on +or off when ``pwm=False`` (the default). + +However, using :class:`LEDBarGraph` with ``pwm=True`` allows more precise +values using LED brightness:: + + from gpiozero import LEDBarGraph + from time import sleep + + graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) + + graph.value = 1/10 # (0.5, 0, 0, 0, 0) + sleep(1) + graph.value = 3/10 # (1, 0.5, 0, 0, 0) + sleep(1) + graph.value = -3/10 # (0, 0, 0, 0.5, 1) + sleep(1) + graph.value = 9/10 # (1, 1, 1, 1, 0.5) + sleep(1) + graph.value = 95/100 # (1, 1, 1, 1, 0.75) + sleep(1) + + Traffic Lights ============== From b581719c8cb5ad2e1f2f46fe4519f286f559f42e Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sat, 28 May 2016 00:45:13 +0100 Subject: [PATCH 054/104] Rename RGB -> Full Color LED in recipes --- docs/recipes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index a8b09aa..84e5d34 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -565,8 +565,8 @@ Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components:: pause() -RGB LED -======= +Full color LED +============== .. image:: images/rgb_led_bb.* From 871c9268b204bcdc695c2b1a0b87d2a6d27e3f97 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 10:42:49 +0100 Subject: [PATCH 055/104] Change LEDBoard.leds to return a static tuple fixes #337 --- gpiozero/boards.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 192fda3..83fa3d2 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -116,19 +116,22 @@ class LEDCollection(CompositeOutputDevice): LEDClass(pin_or_collection, active_high, initial_value) for name, pin_or_collection in kwargs.items() }) + leds = [] + for item in self: + if isinstance(item, LEDCollection): + for subitem in item.leds: + leds.append(subitem) + else: + leds.append(item) + self._leds = tuple(leds) @property def leds(self): """ - A flat iterator over all LEDs contained in this collection (and all + A flat tuple of all LEDs contained in this collection (and all sub-collections). """ - for item in self: - if isinstance(item, LEDCollection): - for subitem in item.leds: - yield subitem - else: - yield item + return self._leds @property def active_high(self): From e832cfeb2366e80031aaaba26570fad541a3fd47 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 12:17:52 +0100 Subject: [PATCH 056/104] Add extra init-method params adds `initial_value` to PiLiter, TrafficLights, PiTraffic & SnowPi and adds `pwm` to PiTraffic --- gpiozero/boards.py | 77 ++++++++++++++++++++++++++++++++------------ tests/test_boards.py | 18 ++++++++++- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 192fda3..fe43d83 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -372,8 +372,8 @@ class LEDBarGraph(LEDCollection): :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and - +1. Defaults to 0.0. This parameter can only be specified as a keyword - parameter. + +1. Defaults to ``0.0``. This parameter can only be specified as a + keyword parameter. """ def __init__(self, *pins, **kwargs): @@ -382,7 +382,7 @@ class LEDBarGraph(LEDCollection): assert not isinstance(pin, LEDCollection) pwm = kwargs.pop('pwm', False) active_high = kwargs.pop('active_high', True) - initial_value = kwargs.pop('initial_value', 0) + initial_value = kwargs.pop('initial_value', 0.0) if kwargs: raise TypeError('unexpected keyword argument: %s' % kwargs.popitem()[0]) super(LEDBarGraph, self).__init__(*pins, pwm=pwm, active_high=active_high) @@ -465,7 +465,7 @@ class LedBorg(RGBLED): def __init__(self, initial_value=(0, 0, 0), pwm=True): super(LedBorg, self).__init__(red=17, green=27, blue=22, - initial_value=initial_value, pwm=pwm) + pwm=pwm, initial_value=initial_value) class PiLiter(LEDBoard): @@ -484,14 +484,20 @@ class PiLiter(LEDBoard): :param bool pwm: If ``True``, construct :class:`PWMLED` instances for each pin. If - ``False`` (the default), construct regular :class:`LED` instances. This - parameter can only be specified as a keyword parameter. + ``False`` (the default), construct regular :class:`LED` instances. + + :param: bool initial_value: + If ``False`` (the default), all LEDs will be off initially. If + ``None``, each device will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If ``True``, + the device will be switched on initially. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ - def __init__(self, pwm=False): - super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, pwm=pwm) + def __init__(self, pwm=False, initial_value=False): + super(PiLiter, self).__init__(4, 17, 27, 18, 22, 23, 24, 25, + pwm=pwm, initial_value=initial_value) class PiLiterBarGraph(LEDBarGraph): @@ -514,15 +520,15 @@ class PiLiterBarGraph(LEDBarGraph): :param float initial_value: The initial :attr:`value` of the graph given as a float between -1 and - +1. + +1. Defaults to ``0.0``. .. _Ciseco Pi-LITEr: http://shop.ciseco.co.uk/pi-liter-8-led-strip-for-the-raspberry-pi/ """ def __init__(self, pwm=False, initial_value=0.0): pins = (4, 17, 27, 18, 22, 23, 24, 25) - super(PiLiterBarGraph, self).__init__(*pins, pwm=pwm, - initial_value=initial_value) + super(PiLiterBarGraph, self).__init__(*pins, + pwm=pwm, initial_value=initial_value) class TrafficLights(LEDBoard): @@ -551,14 +557,22 @@ class TrafficLights(LEDBoard): If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. + + :param bool initial_value: + If ``False`` (the default), all LEDs will be off initially. If + ``None``, each device will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If ``True``, + the device will be switched on initially. """ - def __init__(self, red=None, amber=None, green=None, pwm=False): + def __init__(self, red=None, amber=None, green=None, + pwm=False, initial_value=False): if not all([red, amber, green]): raise GPIOPinMissing( 'red, amber and green pins must be provided' ) super(TrafficLights, self).__init__( - red=red, amber=amber, green=green, pwm=pwm, + red=red, amber=amber, green=green, + pwm=pwm, initial_value=initial_value, _order=('red', 'amber', 'green')) @@ -579,11 +593,22 @@ class PiTraffic(TrafficLights): To use the PI-TRAFFIC board when attached to a non-standard set of pins, simply use the parent class, :class:`TrafficLights`. + :param bool pwm: + If ``True``, construct :class:`PWMLED` instances to represent each + LED. If ``False`` (the default), construct regular :class:`LED` + instances. + + :param bool initial_value: + If ``False`` (the default), all LEDs will be off initially. If + ``None``, each device will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If ``True``, + the device will be switched on initially. + .. _Low Voltage Labs PI-TRAFFIC: http://lowvoltagelabs.com/products/pi-traffic/ """ - - def __init__(self): - super(PiTraffic, self).__init__(9, 10, 11) + def __init__(self, pwm=False, initial_value=False): + super(PiTraffic, self).__init__(9, 10, 11, + pwm=pwm, initial_value=initial_value) class SnowPi(LEDBoard): @@ -606,24 +631,34 @@ class SnowPi(LEDBoard): LED. If ``False`` (the default), construct regular :class:`LED` instances. + :param bool initial_value: + If ``False`` (the default), all LEDs will be off initially. If + ``None``, each device will be left in whatever state the pin is found + in when configured for output (warning: this can be on). If ``True``, + the device will be switched on initially. + .. _Ryanteck SnowPi: https://ryanteck.uk/raspberry-pi/114-snowpi-the-gpio-snowman-for-raspberry-pi-0635648608303.html """ - def __init__(self, pwm=False): + def __init__(self, pwm=False, initial_value=False): super(SnowPi, self).__init__( arms=LEDBoard( left=LEDBoard( - top=17, middle=18, bottom=22, pwm=pwm, + top=17, middle=18, bottom=22, + pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom')), right=LEDBoard( - top=7, middle=8, bottom=9, pwm=pwm, + top=7, middle=8, bottom=9, + pwm=pwm, initial_value=initial_value, _order=('top', 'middle', 'bottom')), _order=('left', 'right') ), eyes=LEDBoard( - left=23, right=24, pwm=pwm, + left=23, right=24, + pwm=pwm, initial_value=initial_value, _order=('left', 'right') ), - nose=25, pwm=pwm, + nose=25, + pwm=pwm, initial_value=initial_value, _order=('eyes', 'nose', 'arms') ) diff --git a/tests/test_boards.py b/tests/test_boards.py index 1d519df..9ce00f2 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -18,7 +18,7 @@ from gpiozero import * def setup_function(function): import gpiozero.devices # dirty, but it does the job - if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg'): + if function.__name__ in ('test_robot', 'test_ryanteck_robot', 'test_camjam_kit_robot', 'test_led_borg', 'test_snow_pi_initial_value_pwm'): gpiozero.devices.pin_factory = MockPWMPin else: gpiozero.devices.pin_factory = MockPin @@ -566,6 +566,22 @@ def test_snow_pi(): with SnowPi() as board: assert [device.pin for device in board.leds] == pins +def test_snow_pi_initial_value(): + with SnowPi() as board: + assert all(device.pin.state == False for device in board.leds) + with SnowPi(initial_value=False) as board: + assert all(device.pin.state == False for device in board.leds) + with SnowPi(initial_value=True) as board: + assert all(device.pin.state == True for device in board.leds) + with SnowPi(initial_value=0.5) as board: + assert all(device.pin.state == True for device in board.leds) + +def test_snow_pi_initial_value_pwm(): + pins = [MockPWMPin(n) for n in (23, 24, 25, 17, 18, 22, 7, 8, 9)] + with SnowPi(pwm=True, initial_value=0.5) as board: + assert [device.pin for device in board.leds] == pins + assert all(device.pin.state == 0.5 for device in board.leds) + def test_traffic_lights_buzzer(): red_pin = MockPin(2) amber_pin = MockPin(3) From 42371294a2b7e44fe43a2a001356ac14caf29686 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 13:14:45 +0100 Subject: [PATCH 057/104] remove usage of CompositeDevice.all since it's marked as deprecated --- gpiozero/boards.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 83fa3d2..fe85515 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -52,7 +52,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice): """ Turn all the output devices on. """ - for device in self.all: + for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.on() @@ -60,7 +60,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice): """ Turn all the output devices off. """ - for device in self.all: + for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.off() @@ -69,7 +69,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice): Toggle all the output devices. For each device, if it's on, turn it off; if it's off, turn it on. """ - for device in self.all: + for device in self: if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.toggle() @@ -83,7 +83,7 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice): @value.setter def value(self, value): - for device, v in zip(self.all, value): + for device, v in zip(self, value): if isinstance(device, (OutputDevice, CompositeOutputDevice)): device.value = v # Simply ignore values for non-output devices @@ -866,7 +866,7 @@ class _EnergenieMaster(SharedMixin, CompositeOutputDevice): with self._lock: try: code = (8 * bool(enable)) + (8 - socket) - for bit in self.all[:4]: + for bit in self[:4]: bit.value = (code & 1) code >>= 1 sleep(0.1) From b3035d306ea1b5b7fe3a25f779b744f7fd6c4655 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 13:50:47 +0100 Subject: [PATCH 058/104] Add value-setter for Robot class Fixes #305 --- gpiozero/boards.py | 14 ++++++++++++++ tests/test_boards.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 3ff605a..dbd8ee4 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -776,6 +776,20 @@ class Robot(SourceMixin, CompositeDevice): right_motor=Motor(*right), _order=('left_motor', 'right_motor')) + @property + def value(self): + """ + Represents the motion of the robot as a tuple of (left_motor_speed, + right_motor_speed) with ``(-1, -1)`` representing full speed backwards, + ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` + representing stopped. + """ + return super(Robot, self).value + + @value.setter + def value(self, value): + self.left_motor.value, self.right_motor.value = value + def forward(self, speed=1): """ Drive the robot forward by running both motors forward. diff --git a/tests/test_boards.py b/tests/test_boards.py index 9ce00f2..7562a5d 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -617,20 +617,34 @@ def test_robot(): assert ( [device.pin for device in robot.left_motor] + [device.pin for device in robot.right_motor]) == pins + assert robot.value == (0, 0) robot.forward() assert [pin.state for pin in pins] == [1, 0, 1, 0] + assert robot.value == (1, 1) robot.backward() assert [pin.state for pin in pins] == [0, 1, 0, 1] + assert robot.value == (-1, -1) robot.forward(0.5) assert [pin.state for pin in pins] == [0.5, 0, 0.5, 0] + assert robot.value == (0.5, 0.5) robot.left() assert [pin.state for pin in pins] == [0, 1, 1, 0] + assert robot.value == (-1, 1) robot.right() assert [pin.state for pin in pins] == [1, 0, 0, 1] + assert robot.value == (1, -1) robot.reverse() assert [pin.state for pin in pins] == [0, 1, 1, 0] + assert robot.value == (-1, 1) robot.stop() assert [pin.state for pin in pins] == [0, 0, 0, 0] + assert robot.value == (0, 0) + robot.value = (-1, -1) + assert robot.value == (-1, -1) + robot.value = (0.5, 1) + assert robot.value == (0.5, 1) + robot.value = (0, -0.5) + assert robot.value == (0, -0.5) def test_ryanteck_robot(): pins = [MockPWMPin(n) for n in (17, 18, 22, 23)] From d8ae4b681448cbd4513d0b6a2929e462e32395a7 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 13:56:04 +0100 Subject: [PATCH 059/104] Typo --- gpiozero/boards.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 3ff605a..84979e2 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -489,7 +489,7 @@ class PiLiter(LEDBoard): If ``True``, construct :class:`PWMLED` instances for each pin. If ``False`` (the default), construct regular :class:`LED` instances. - :param: bool initial_value: + :param bool initial_value: If ``False`` (the default), all LEDs will be off initially. If ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, From ffc07950e1b9db910e9698fd24d22c848c746297 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 28 May 2016 13:57:37 +0100 Subject: [PATCH 060/104] Typo --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 0c57827..dae163d 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -291,7 +291,7 @@ A collection of LEDs can be treated like a bar graph using graph.value = -1 # (1, 1, 1, 1, 1, 1) sleep(1) -Note values are essentially rounded to encounter the fact LEDs can only be on +Note values are essentially rounded to account for the fact LEDs can only be on or off when ``pwm=False`` (the default). However, using :class:`LEDBarGraph` with ``pwm=True`` allows more precise From 963b96286751b234471a00f06a479eeee8a55309 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sun, 29 May 2016 20:19:09 +0100 Subject: [PATCH 061/104] Add Shutdown button example to recipes, close #325 --- docs/recipes.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index dae163d..0cadcf7 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -239,6 +239,25 @@ another to capture:: pause() +Shutdown button +=============== + +The :class:`Button` class also provides the ability to run a function when the +button has been held for a given length of time. This example will shut down +the Raspberry Pi when the button is held for 2 seconds:: + + from gpiozero import Button + from subprocess import check_call + from signal import pause + + def shutdown(): + check_call(['sudo', 'poweroff']) + + shutdown_btn = Button(17, hold_time=2) + shutdown_btn.when_held = shutdown + + pause() + LEDBoard ======== From 5455034a1210147aeae52e22d14c062a22ab55ff Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 31 May 2016 16:52:14 +0100 Subject: [PATCH 062/104] Make CompositeDevice._named a frozendict (and add frozendict to compat.py) This prevents it being modified post-construction (just like the way CompositeDevice._all and CompositeDevice._order are already 'frozen' by being tuples) --- gpiozero/compat.py | 30 ++++++++++++++++++++++++++++++ gpiozero/devices.py | 7 ++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/gpiozero/compat.py b/gpiozero/compat.py index 8387038..31e5f35 100644 --- a/gpiozero/compat.py +++ b/gpiozero/compat.py @@ -9,6 +9,9 @@ from __future__ import ( str = type('') import cmath +import collections +import operator +import functools # Back-ported from python 3.5; see @@ -51,3 +54,30 @@ def median(data): i = n // 2 return (data[i - 1] + data[i]) / 2 + +# Copied from the MIT-licensed https://github.com/slezica/python-frozendict +class frozendict(collections.Mapping): + def __init__(self, *args, **kwargs): + self.__dict = dict(*args, **kwargs) + self.__hash = None + + def __getitem__(self, key): + return self.__dict[key] + + def copy(self, **add_or_replace): + return frozendict(self, **add_or_replace) + + def __iter__(self): + return iter(self.__dict) + + def __len__(self): + return len(self.__dict) + + def __repr__(self): + return '' % repr(self.__dict) + + def __hash__(self): + if self.__hash is None: + hashes = map(hash, self.items()) + self.__hash = functools.reduce(operator.xor, hashes, 0) + return self.__hash diff --git a/gpiozero/devices.py b/gpiozero/devices.py index ba2ecc4..5c1b35b 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -28,6 +28,7 @@ from .exc import ( GPIOPinInUse, GPIODeviceClosed, ) +from .compat import frozendict # Get a pin implementation to use as the default; we prefer RPi.GPIO's here # as it supports PWM, and all Pi revisions. If no third-party libraries are @@ -263,7 +264,7 @@ class CompositeDevice(Device): """ def __init__(self, *args, **kwargs): self._all = () - self._named = {} + self._named = frozendict({}) self._namedtuple = None self._order = kwargs.pop('_order', None) if self._order is None: @@ -278,7 +279,7 @@ class CompositeDevice(Device): for dev in self._all: if not isinstance(dev, Device): raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev) - self._named = kwargs + self._named = frozendict(kwargs) self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain( (str(i) for i in range(len(args))), self._order), rename=True) @@ -286,7 +287,7 @@ class CompositeDevice(Device): def __getattr__(self, name): # if _named doesn't exist yet, pretend it's an empty dict if name == '_named': - return {} + return frozendict({}) try: return self._named[name] except KeyError: From 2587612403ffd1e0bd9efb5dd97b962c0e0717a7 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 3 Jun 2016 14:40:02 +0100 Subject: [PATCH 063/104] Replace tab with spaces --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 0cadcf7..3a6cf02 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -251,7 +251,7 @@ the Raspberry Pi when the button is held for 2 seconds:: from signal import pause def shutdown(): - check_call(['sudo', 'poweroff']) + check_call(['sudo', 'poweroff']) shutdown_btn = Button(17, hold_time=2) shutdown_btn.when_held = shutdown From 4db57cd0ac6995f59b74e7d4ae1129a62e0e74be Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Fri, 3 Jun 2016 16:37:11 +0100 Subject: [PATCH 064/104] Use keyword args for echo & trigger in DistanceSensor example --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 05682cb..7e89709 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -534,7 +534,7 @@ class DistanceSensor(SmoothedInputDevice): from gpiozero import DistanceSensor from time import sleep - sensor = DistanceSensor(18, 17) + sensor = DistanceSensor(echo=18, trigger=17) while True: print('Distance: ', sensor.distance * 100) sleep(1) From 87046ac645611bdad1fcb4c9df487d31edf7593c Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 3 Jun 2016 16:42:26 +0100 Subject: [PATCH 065/104] Update sourcetools examples Remove blue.source_delay, as it's actually red.source_delay that controls the speed of the fading --- gpiozero/tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gpiozero/tools.py b/gpiozero/tools.py index c3611f2..fc77cfe 100644 --- a/gpiozero/tools.py +++ b/gpiozero/tools.py @@ -518,7 +518,6 @@ def sin_values(period=360): red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 - blue.source_delay = 0.01 red.source = scaled(sin_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() @@ -543,7 +542,6 @@ def cos_values(period=360): red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 - blue.source_delay = 0.01 red.source = scaled(cos_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() From ea1ec451ef7bd71de2dc70ceb7c4a89bdd1f66a3 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sat, 4 Jun 2016 17:34:05 +0100 Subject: [PATCH 066/104] Add Pi Zero v1.3 @waveform80 does this look ok to you? --- gpiozero/pins/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index 86a91e5..b1b5f1a 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -265,6 +265,7 @@ PI_REVISIONS = { 0x900092: ('Zero', '1.2', '2015Q4', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 0, 0, {'P1': PLUS_P1}, ), 0xa02082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Sony', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), 0xa22082: ('3B', '1.2', '2016Q1', 'BCM2837', 'Embest', 1024, 'MicroSD', 4, 1, True, True, 1, 1, {'P1': PLUS_P1}, ), + 0x900093: ('Zero', '1.3', '2016Q2', 'BCM2835', 'Sony', 512, 'MicroSD', 1, 0, False, False, 1, 0, {'P1': PLUS_P1}, ), } From 97202b9250ddb6bbac5e19d14b8142d9a9da5c49 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sun, 5 Jun 2016 11:59:14 +0100 Subject: [PATCH 067/104] Add extra product URLs to the boards.py docstrings --- gpiozero/boards.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 506574b..6275525 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -690,7 +690,7 @@ class TrafficLightsBuzzer(CompositeOutputDevice): class FishDish(TrafficLightsBuzzer): """ - Extends :class:`TrafficLightsBuzzer` for the Pi Supply FishDish: traffic + Extends :class:`TrafficLightsBuzzer` for the `Pi Supply FishDish`_: traffic light LEDs, a button and a buzzer. The FishDish pins are fixed and therefore there's no need to specify them @@ -707,6 +707,8 @@ class FishDish(TrafficLightsBuzzer): If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. + + .. _Pi Supply FishDish: https://www.pi-supply.com/product/fish-dish-raspberry-pi-led-buzzer-board/ """ def __init__(self, pwm=False): @@ -719,7 +721,7 @@ class FishDish(TrafficLightsBuzzer): class TrafficHat(TrafficLightsBuzzer): """ - Extends :class:`TrafficLightsBuzzer` for the Ryanteck Traffic HAT: traffic + Extends :class:`TrafficLightsBuzzer` for the `Ryanteck Traffic HAT`_: traffic light LEDs, a button and a buzzer. The Traffic HAT pins are fixed and therefore there's no need to specify @@ -736,6 +738,8 @@ class TrafficHat(TrafficLightsBuzzer): If ``True``, construct :class:`PWMLED` instances to represent each LED. If ``False`` (the default), construct regular :class:`LED` instances. + + .. _Ryanteck Traffic HAT: https://ryanteck.uk/hats/1-traffichat-0635648607122.html """ def __init__(self, pwm=False): @@ -856,7 +860,7 @@ class Robot(SourceMixin, CompositeDevice): class RyanteckRobot(Robot): """ - Extends :class:`Robot` for the Ryanteck MCB robot. + Extends :class:`Robot` for the `Ryanteck MCB`_ robot. The Ryanteck MCB pins are fixed and therefore there's no need to specify them when constructing this class. The following example turns the robot @@ -866,6 +870,8 @@ class RyanteckRobot(Robot): robot = RyanteckRobot() robot.left() + + .. _Ryanteck MCB: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html """ def __init__(self): From 8b81057f8b9ac020d763300c386819ee398b4a1c Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sun, 5 Jun 2016 13:00:24 +0100 Subject: [PATCH 068/104] Fix typo in pigoiod import --- gpiozero/devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/devices.py b/gpiozero/devices.py index ba2ecc4..4c9ca2b 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -43,7 +43,7 @@ except ImportError: pin_factory = RPIOPin except ImportError: try: - from .pins.pigipod import PiGPIOPin + from .pins.pigpiod import PiGPIOPin pin_factory = PiGPIOPin except ImportError: from .pins.native import NativePin From 68f5f746689a56daad5eb786ff538cce3bf4c749 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 6 Jun 2016 02:38:41 +0100 Subject: [PATCH 069/104] Minor CompositeDevice tweaks Always make `__repr__` print subdevices in the same order as `value` No need to check for missing keys if _order wasn't explicitly supplied --- gpiozero/devices.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gpiozero/devices.py b/gpiozero/devices.py index 4c9ca2b..a56ab89 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -268,9 +268,10 @@ class CompositeDevice(Device): self._order = kwargs.pop('_order', None) if self._order is None: self._order = sorted(kwargs.keys()) + else: + for missing_name in set(kwargs.keys()) - set(self._order): + raise CompositeDeviceBadOrder('%s missing from _order' % missing_name) self._order = tuple(self._order) - for missing_name in set(kwargs.keys()) - set(self._order): - raise CompositeDeviceBadOrder('%s missing from _order' % missing_name) super(CompositeDevice, self).__init__() for name in set(self._order) & set(dir(self)): raise CompositeDeviceBadName('%s is a reserved name' % name) @@ -303,7 +304,7 @@ class CompositeDevice(Device): self._check_open() return "" % ( self.__class__.__name__, - len(self), ','.join(self._named), + len(self), ','.join(self._order), len(self) - len(self._named) ) except DeviceClosed: From d3071a278062f9fff7d7f0db5e705a67d6ca12d9 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 6 Jun 2016 03:30:23 +0100 Subject: [PATCH 070/104] Doc tweak The default value should only be documented for the init-method, not on the property itself --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 7e89709..706352d 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -50,7 +50,7 @@ class InputDevice(GPIODevice): def pull_up(self): """ If ``True``, the device uses a pull-up resistor to set the GPIO pin - "high" by default. Defaults to ``False``. + "high" by default. """ return self.pin.pull == 'up' From c1c9bcebee89f26598a72c34b69fb897cf570374 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 6 Jun 2016 03:46:32 +0100 Subject: [PATCH 071/104] Document Button default values --- gpiozero/input_devices.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 7e89709..51c087c 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -256,11 +256,13 @@ class Button(HoldMixin, DigitalInputDevice): :param float hold_time: The length of time (in seconds) to wait after the button is pushed, - until executing the :attr:`when_held` handler. + until executing the :attr:`when_held` handler. Defaults to ``1``. :param bool hold_repeat: If ``True``, the :attr:`when_held` handler will be repeatedly executed - as long as the device remains active, every *hold_time* seconds. + as long as the device remains active, every *hold_time* seconds. If + ``False`` (the default) the :attr:`when_held` handler will be only be + executed once per hold. """ def __init__( self, pin=None, pull_up=True, bounce_time=None, From ee458214a047ec28315fa8694550f0abbb488a04 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Mon, 6 Jun 2016 10:59:31 +0100 Subject: [PATCH 072/104] Typo fix --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 51c087c..8fa1a6b 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -251,7 +251,7 @@ class Button(HoldMixin, DigitalInputDevice): :param float bounce_time: If ``None`` (the default), no software bounce compensation will be - performed. Otherwise, this is the length in time (in seconds) that the + performed. Otherwise, this is the length of time (in seconds) that the component will ignore changes in state after an initial change. :param float hold_time: From 754c9c9b9c6398665e9b2a9d1cedd8a187ffb634 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 9 Jun 2016 19:45:22 +0100 Subject: [PATCH 073/104] Initial version of ButtonBoard for testing... ...not ready to be merged into master yet! --- gpiozero/__init__.py | 1 + gpiozero/boards.py | 112 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index e2933be..7b3fde4 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -103,6 +103,7 @@ from .output_devices import ( ) from .boards import ( CompositeOutputDevice, + ButtonBoard, LEDCollection, LEDBoard, LEDBarGraph, diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 6275525..6a00845 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -31,7 +31,7 @@ from .output_devices import ( ) from .threads import GPIOThread from .devices import Device, CompositeDevice -from .mixins import SharedMixin, SourceMixin +from .mixins import SharedMixin, SourceMixin, HoldMixin class CompositeOutputDevice(SourceMixin, CompositeDevice): @@ -89,12 +89,120 @@ class CompositeOutputDevice(SourceMixin, CompositeDevice): # Simply ignore values for non-output devices +class ButtonBoard(HoldMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a generic button board or + collection of buttons. + + :param int \*pins: + Specify the GPIO pins that the buttons of the board are attached to. + You can designate as many pins as necessary. + + :param bool pull_up: + If ``True`` (the default), the GPIO pins will be pulled high by + default. In this case, connect the other side of the buttons to + ground. If ``False``, the GPIO pins will be pulled low by default. In + this case, connect the other side of the buttons to 3V3. This + parameter can only be specified as a keyword parameter. + + :param float bounce_time: + If ``None`` (the default), no software bounce compensation will be + performed. Otherwise, this is the length of time (in seconds) that the + buttons will ignore changes in state after an initial change. This + parameter can only be specified as a keyword parameter. + + :param float hold_time: + The length of time (in seconds) to wait after any button is pushed, + until executing the :attr:`when_held` handler. Defaults to ``1``. This + parameter can only be specified as a keyword parameter. + + :param bool hold_repeat: + If ``True``, the :attr:`when_held` handler will be repeatedly executed + as long as any buttons remain held, every *hold_time* seconds. If + ``False`` (the default) the :attr:`when_held` handler will be only be + executed once per hold. This parameter can only be specified as a + keyword parameter. + + :param \*\*named_pins: + Specify GPIO pins that buttons of the board are attached to, + associating each button with a property name. You can designate as + many pins as necessary and use any names, provided they're not already + in use by something else. + """ + def __init__(self, *args, **kwargs): + pull_up = kwargs.pop('pull_up', True) + bounce_time = kwargs.pop('bounce_time', None) + hold_time = kwargs.pop('hold_time', 1) + hold_repeat = kwargs.pop('hold_repeat', False) + order = kwargs.pop('_order', None) + super(ButtonBoard, self).__init__( + *( + Button(pin, pull_up, bounce_time, hold_time, hold_repeat) + for pin in args + ), + _order=order, + **{ + name: Button(pin, pull_up, bounce_time, hold_time, hold_repeat) + for name, pin in kwargs.items() + }) + def get_new_handler(device): + def fire_both_events(): + device._fire_events() + self._fire_events() + return fire_both_events + for button in self: + button.pin.when_changed = get_new_handler(button) + self._when_changed = None + self._last_value = None + # Call _fire_events once to set initial state of events + self._fire_events() + self.hold_time = hold_time + self.hold_repeat = hold_repeat + + @property + def pull_up(self): + """ + If ``True``, the device uses a pull-up resistor to set the GPIO pin + "high" by default. + """ + return self[0].pull_up + + @property + def when_changed(self): + return self._when_changed + + @when_changed.setter + def when_changed(self, value): + self._when_changed = self._wrap_callback(value) + + def _fire_changed(self): + if self.when_changed: + self.when_changed() + + def _fire_events(self): + super(ButtonBoard, self)._fire_events() + old_value = self._last_value + new_value = self._last_value = self.value + if old_value is None: + # Initial "indeterminate" value; don't do anything + pass + elif old_value != new_value: + self._fire_changed() + + +ButtonBoard.is_pressed = ButtonBoard.is_active +ButtonBoard.pressed_time = ButtonBoard.active_time +ButtonBoard.when_pressed = ButtonBoard.when_activated +ButtonBoard.when_released = ButtonBoard.when_deactivated +ButtonBoard.wait_for_press = ButtonBoard.wait_for_active +ButtonBoard.wait_for_release = ButtonBoard.wait_for_inactive + + class LEDCollection(CompositeOutputDevice): """ Extends :class:`CompositeOutputDevice`. Abstract base class for :class:`LEDBoard` and :class:`LEDBarGraph`. """ - def __init__(self, *args, **kwargs): self._blink_thread = None pwm = kwargs.pop('pwm', False) From 382966e4e41bf670d5890b3db071cbe3225436d6 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 10 Jun 2016 11:49:53 +0100 Subject: [PATCH 074/104] Allow TrafficLights, RGBLED and Motor to be constructed using GPIO 0 --- gpiozero/boards.py | 2 +- gpiozero/output_devices.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 6275525..788c05b 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -569,7 +569,7 @@ class TrafficLights(LEDBoard): """ def __init__(self, red=None, amber=None, green=None, pwm=False, initial_value=False): - if not all([red, amber, green]): + if not all(p is not None for p in [red, amber, green]): raise GPIOPinMissing( 'red, amber and green pins must be provided' ) diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index fd30a46..ae62347 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -558,7 +558,7 @@ class RGBLED(SourceMixin, Device): initial_value=(0, 0, 0), pwm=True): self._leds = () self._blink_thread = None - if not all([red, green, blue]): + if not all(p is not None for p in [red, green, blue]): raise GPIOPinMissing('red, green, and blue pins must be provided') LEDClass = PWMLED if pwm else LED super(RGBLED, self).__init__() @@ -805,7 +805,7 @@ class Motor(SourceMixin, CompositeDevice): control. """ def __init__(self, forward=None, backward=None, pwm=True): - if not all([forward, backward]): + if not all(p is not None for p in [forward, backward]): raise GPIOPinMissing( 'forward and backward pins must be provided' ) From 7987f5a7861a912fcdbaa3a71a1063d492e97aa3 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 10 Jun 2016 12:23:13 +0100 Subject: [PATCH 075/104] Tweak RGBLED tests - increases coverage --- tests/test_outputs.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 444b1d9..3d2303c 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -410,7 +410,17 @@ def test_rgbled_initial_value_nonpwm(): def test_rgbled_initial_bad_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) + RGBLED(r, g, b, initial_value=(1.1, 0.2, 0.3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(0.1, 1.2, 0.3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(-0.1, 0.2, 0.3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(0.1, -0.2, 0.3)) + with pytest.raises(ValueError): + RGBLED(r, g, b, initial_value=(0.1, 0.2, -0.3)) def test_rgbled_initial_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) @@ -458,6 +468,9 @@ def test_rgbled_bad_value(): with RGBLED(r, g, b) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) + with RGBLED(r, g, b) as device: + with pytest.raises(ValueError): + device.value = (0, 0, -0.5) def test_rgbled_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) @@ -622,7 +635,9 @@ def test_rgbled_fade_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.blink(0, 0, 0.2, 0.2, n=2) + device.blink(0, 0, 0.2, 0, n=2) + with pytest.raises(ValueError): + device.blink(0, 0, 0, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -663,7 +678,9 @@ def test_rgbled_fade_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.blink(0, 0, 0.2, 0.2, n=2, background=False) + device.blink(0, 0, 0.2, 0, n=2, background=False) + with pytest.raises(ValueError): + device.blink(0, 0, 0, 0.2, n=2, background=False) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -706,7 +723,7 @@ def test_rgbled_pulse_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.pulse(0.2, 0.2, n=2) + device.pulse(n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -747,7 +764,7 @@ def test_rgbled_pulse_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.pulse(0.2, 0.2, n=2, background=False) + device.pulse(n=2, background=False) def test_rgbled_blink_interrupt(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) From 4e58e25bca383bf0b6d5e502d5b1534e722f4206 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 10 Jun 2016 12:32:37 +0100 Subject: [PATCH 076/104] Revert "Tweak RGBLED tests - increases coverage" --- tests/test_outputs.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 3d2303c..444b1d9 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -410,17 +410,7 @@ def test_rgbled_initial_value_nonpwm(): def test_rgbled_initial_bad_value(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(1.1, 0.2, 0.3)) - with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(0.1, 1.2, 0.3)) - with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.3)) - with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(-0.1, 0.2, 0.3)) - with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(0.1, -0.2, 0.3)) - with pytest.raises(ValueError): - RGBLED(r, g, b, initial_value=(0.1, 0.2, -0.3)) + RGBLED(r, g, b, initial_value=(0.1, 0.2, 1.2)) def test_rgbled_initial_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) @@ -468,9 +458,6 @@ def test_rgbled_bad_value(): with RGBLED(r, g, b) as device: with pytest.raises(ValueError): device.value = (0, -1, 0) - with RGBLED(r, g, b) as device: - with pytest.raises(ValueError): - device.value = (0, 0, -0.5) def test_rgbled_bad_value_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) @@ -635,9 +622,7 @@ def test_rgbled_fade_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.blink(0, 0, 0.2, 0, n=2) - with pytest.raises(ValueError): - device.blink(0, 0, 0, 0.2, n=2) + device.blink(0, 0, 0.2, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -678,9 +663,7 @@ def test_rgbled_fade_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.blink(0, 0, 0.2, 0, n=2, background=False) - with pytest.raises(ValueError): - device.blink(0, 0, 0, 0.2, n=2, background=False) + device.blink(0, 0, 0.2, 0.2, n=2, background=False) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -723,7 +706,7 @@ def test_rgbled_pulse_background_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.pulse(n=2) + device.pulse(0.2, 0.2, n=2) @pytest.mark.skipif(hasattr(sys, 'pypy_version_info'), reason='timing is too random on pypy') @@ -764,7 +747,7 @@ def test_rgbled_pulse_foreground_nonpwm(): r, g, b = (MockPin(i) for i in (1, 2, 3)) with RGBLED(r, g, b, pwm=False) as device: with pytest.raises(ValueError): - device.pulse(n=2, background=False) + device.pulse(0.2, 0.2, n=2, background=False) def test_rgbled_blink_interrupt(): r, g, b = (MockPWMPin(i) for i in (1, 2, 3)) From 38262a125f4b96cf65b4c1d535029375657180e6 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 10 Jun 2016 01:46:00 +0100 Subject: [PATCH 077/104] Add 'yellow' as an alias of 'amber' for TrafficLights Fixes #345 --- gpiozero/boards.py | 27 ++++++++++++++++++++++++--- tests/test_boards.py | 5 +++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 788c05b..7851988 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -536,11 +536,11 @@ class PiLiterBarGraph(LEDBarGraph): class TrafficLights(LEDBoard): """ - Extends :class:`LEDBoard` for devices containing red, amber, and green + Extends :class:`LEDBoard` for devices containing red, yellow, and green LEDs. The following example initializes a device connected to GPIO pins 2, 3, - and 4, then lights the amber LED attached to GPIO 3:: + and 4, then lights the amber (yellow) LED attached to GPIO 3:: from gpiozero import TrafficLights @@ -566,9 +566,20 @@ class TrafficLights(LEDBoard): ``None``, each device will be left in whatever state the pin is found in when configured for output (warning: this can be on). If ``True``, the device will be switched on initially. + + :param int yellow: + The GPIO pin that the yellow LED is attached to. This is merely an + alias for the ``amber`` parameter - you can't specify both ``amber`` + and ``yellow``. """ def __init__(self, red=None, amber=None, green=None, - pwm=False, initial_value=False): + pwm=False, initial_value=False, yellow=None): + if amber is not None and yellow is not None: + raise OutputDeviceBadValue( + 'Only one of amber or yellow can be specified' + ) + if amber is None: + amber = yellow if not all(p is not None for p in [red, amber, green]): raise GPIOPinMissing( 'red, amber and green pins must be provided' @@ -578,6 +589,16 @@ class TrafficLights(LEDBoard): pwm=pwm, initial_value=initial_value, _order=('red', 'amber', 'green')) + def __getattr__(self, name): + if name == 'yellow': + name = 'amber' + return super(TrafficLights, self).__getattr__(name) + + def __setattr__(self, name, value): + if name == 'yellow': + name = 'amber' + return super(TrafficLights, self).__setattr__(name, value) + class PiTraffic(TrafficLights): """ diff --git a/tests/test_boards.py b/tests/test_boards.py index 7562a5d..9b5c72c 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -551,6 +551,11 @@ def test_traffic_lights(): assert red_pin.state assert not amber_pin.state assert not green_pin.state + with TrafficLights(red=red_pin, yellow=amber_pin, green=green_pin) as board: + board.yellow.on() + assert not red_pin.state + assert amber_pin.state + assert not green_pin.state def test_traffic_lights_bad_init(): with pytest.raises(ValueError): From f96ab609e20b808707bfb507610d019e5f040186 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Fri, 10 Jun 2016 02:28:44 +0100 Subject: [PATCH 078/104] TrafficLights.value also reports 'yellow' instead of 'amber' when appropriate --- gpiozero/boards.py | 26 ++++++++++++++++++-------- tests/test_boards.py | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 7851988..5d4c3f3 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -12,6 +12,7 @@ except ImportError: from time import sleep from itertools import repeat, cycle, chain from threading import Lock +from collections import OrderedDict from .exc import ( DeviceClosed, @@ -578,24 +579,33 @@ class TrafficLights(LEDBoard): raise OutputDeviceBadValue( 'Only one of amber or yellow can be specified' ) - if amber is None: - amber = yellow - if not all(p is not None for p in [red, amber, green]): + devices = OrderedDict((('red', red), )) + self._display_yellow = amber is None and yellow is not None + if self._display_yellow: + devices['yellow'] = yellow + else: + devices['amber'] = amber + devices['green'] = green + if not all(p is not None for p in devices.values()): raise GPIOPinMissing( - 'red, amber and green pins must be provided' + ', '.join(devices.keys())+' pins must be provided' ) super(TrafficLights, self).__init__( - red=red, amber=amber, green=green, pwm=pwm, initial_value=initial_value, - _order=('red', 'amber', 'green')) + _order=devices.keys(), + **devices) def __getattr__(self, name): - if name == 'yellow': + if name == 'amber' and self._display_yellow: + name = 'yellow' + elif name == 'yellow' and not self._display_yellow: name = 'amber' return super(TrafficLights, self).__getattr__(name) def __setattr__(self, name, value): - if name == 'yellow': + if name == 'amber' and self._display_yellow: + name = 'yellow' + elif name == 'yellow' and not self._display_yellow: name = 'amber' return super(TrafficLights, self).__setattr__(name, value) diff --git a/tests/test_boards.py b/tests/test_boards.py index 9b5c72c..d1accde 100644 --- a/tests/test_boards.py +++ b/tests/test_boards.py @@ -548,11 +548,19 @@ def test_traffic_lights(): green_pin = MockPin(4) with TrafficLights(red_pin, amber_pin, green_pin) as board: board.red.on() + assert board.red.value + assert not board.amber.value + assert not board.yellow.value + assert not board.green.value assert red_pin.state assert not amber_pin.state assert not green_pin.state with TrafficLights(red=red_pin, yellow=amber_pin, green=green_pin) as board: board.yellow.on() + assert not board.red.value + assert board.amber.value + assert board.yellow.value + assert not board.green.value assert not red_pin.state assert amber_pin.state assert not green_pin.state @@ -560,6 +568,12 @@ def test_traffic_lights(): def test_traffic_lights_bad_init(): with pytest.raises(ValueError): TrafficLights() + red_pin = MockPin(2) + amber_pin = MockPin(3) + green_pin = MockPin(4) + yellow_pin = MockPin(5) + with pytest.raises(ValueError): + TrafficLights(red=red_pin, amber=amber_pin, yellow=yellow_pin, green=green_pin) def test_pi_traffic(): pins = [MockPin(n) for n in (9, 10, 11)] From c64e54116766f619db84955dfe40d3038b54a0f6 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sat, 11 Jun 2016 01:56:50 +0100 Subject: [PATCH 080/104] Update sourcetools examples Explicitly set blue.source_delay to the same value as red.source_delay --- gpiozero/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gpiozero/tools.py b/gpiozero/tools.py index fc77cfe..9ae8346 100644 --- a/gpiozero/tools.py +++ b/gpiozero/tools.py @@ -518,6 +518,7 @@ def sin_values(period=360): red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 + blue.source_delay = red.source_delay red.source = scaled(sin_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() @@ -542,6 +543,7 @@ def cos_values(period=360): red = PWMLED(2) blue = PWMLED(3) red.source_delay = 0.01 + blue.source_delay = red.source_delay red.source = scaled(cos_values(100), 0, 1, -1, 1) blue.source = inverted(red.values) pause() From 9c15afe77e05127ac3485a1df88e2bddebf1ee03 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sun, 12 Jun 2016 22:09:55 +0100 Subject: [PATCH 081/104] Use robot.forward() not robot.left() in examples --- gpiozero/boards.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gpiozero/boards.py b/gpiozero/boards.py index 788c05b..29f4485 100644 --- a/gpiozero/boards.py +++ b/gpiozero/boards.py @@ -758,12 +758,12 @@ class Robot(SourceMixin, CompositeDevice): backward pins of the left and right controllers respectively. For example, if the left motor's controller is connected to GPIOs 4 and 14, while the right motor's controller is connected to GPIOs 17 and 18 then the following - example will turn the robot left:: + example will drive the robot forward:: from gpiozero import Robot robot = Robot(left=(4, 14), right=(17, 18)) - robot.left() + robot.forward() :param tuple left: A tuple of two GPIO pins representing the forward and backward inputs @@ -863,13 +863,13 @@ class RyanteckRobot(Robot): Extends :class:`Robot` for the `Ryanteck MCB`_ robot. The Ryanteck MCB pins are fixed and therefore there's no need to specify - them when constructing this class. The following example turns the robot - left:: + them when constructing this class. The following example drives the robot + forward:: from gpiozero import RyanteckRobot robot = RyanteckRobot() - robot.left() + robot.forward() .. _Ryanteck MCB: https://ryanteck.uk/add-ons/6-ryanteck-rpi-motor-controller-board-0635648607160.html """ @@ -883,13 +883,13 @@ class CamJamKitRobot(Robot): Extends :class:`Robot` for the `CamJam #3 EduKit`_ robot controller. The CamJam robot controller pins are fixed and therefore there's no need - to specify them when constructing this class. The following example turns - the robot left:: + to specify them when constructing this class. The following example drives + the robot forward:: from gpiozero import CamJamKitRobot robot = CamJamKitRobot() - robot.left() + robot.forward() .. _CamJam #3 EduKit: http://camjam.me/?page_id=1035 """ From f5c2d3a889393338ec330f5e75ed7ab16b491aed Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 14 Jun 2016 16:13:35 +0100 Subject: [PATCH 082/104] Doc typo --- gpiozero/input_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index dc1b6a5..0c06657 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -71,7 +71,7 @@ class DigitalInputDevice(EventsMixin, InputDevice): straight forward on / off states with (reasonably) clean transitions between the two. - :param float bouncetime: + :param float bounce_time: Specifies the length of time (in seconds) that the component will ignore changes in state after an initial change. This defaults to ``None`` which indicates that no bounce compensation will be performed. From 74a0871353744bcd4266a988b0dbb28bd8ff99e9 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Tue, 14 Jun 2016 23:18:13 +0100 Subject: [PATCH 083/104] Doc typos --- gpiozero/input_devices.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 0c06657..eebe4ba 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -302,7 +302,7 @@ class LineSensor(SmoothedInputDevice): pause() :param int pin: - The GPIO pin which the button is attached to. See :doc:`notes` for + The GPIO pin which the sensor is attached to. See :doc:`notes` for valid pin numbers. :param int queue_len: @@ -371,7 +371,7 @@ class MotionSensor(SmoothedInputDevice): print("Motion detected!") :param int pin: - The GPIO pin which the button is attached to. See :doc:`notes` for + The GPIO pin which the sensor is attached to. See :doc:`notes` for valid pin numbers. :param int queue_len: @@ -435,7 +435,7 @@ class LightSensor(SmoothedInputDevice): print("Light detected!") :param int pin: - The GPIO pin which the button is attached to. See :doc:`notes` for + The GPIO pin which the sensor is attached to. See :doc:`notes` for valid pin numbers. :param int queue_len: From 8d457df2e259ab6182cabda3f3c2ce6c5f8e77fe Mon Sep 17 00:00:00 2001 From: ubuntu Date: Sat, 18 Jun 2016 20:44:46 +0100 Subject: [PATCH 084/104] Document how to get the gpiozero version number --- docs/notes.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/notes.rst b/docs/notes.rst index f111d80..7c4ba69 100644 --- a/docs/notes.rst +++ b/docs/notes.rst @@ -70,3 +70,26 @@ In this case, all references to items within GPIO Zero must be prefixed:: button = gpiozero.Button(2) +How can I tell what version of gpiozero I have installed? +========================================================= + +The gpiozero library relies on the setuptools package for installation +services. You can use the setuptools ``pkg_resources`` API to query which +version of gpiozero is available in your Python environment like so:: + + >>> from pkg_resources import require + >>> require('gpiozero') + [gpiozero 1.2.0 (/usr/local/lib/python2.7/dist-packages)] + >>> require('gpiozero')[0].version + '1.2.0' + +If you have multiple versions installed (e.g. from ``pip`` and ``apt-get``) +they will not show up in the list returned by the ``require`` method. However, +the first entry in the list will be the version that ``import gpiozero`` will +import. + +If you receive the error "No module named pkg_resources", you need to install +the ``pip`` utility. This can be done with the following command in Raspbian:: + + $ sudo apt-get install python-pip + From 23e153df504bf01112db466a1501a4d85d47f10e Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sun, 19 Jun 2016 11:30:58 +0100 Subject: [PATCH 085/104] Correct manufacturer of 0x14 ComputeModule --- gpiozero/pins/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index b1b5f1a..fba4b6b 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -258,7 +258,7 @@ PI_REVISIONS = { 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), From 23fabb68ce424c32903ccfa27e0482848301c800 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Tue, 26 Jul 2016 22:45:38 +0200 Subject: [PATCH 086/104] Use PIGPIO_ADDR and PIGPIO_PORT environment variable if they exist to configure PiGPIO, otherwise it use the default value 'localhost' and 8888. This is the same behaviour as the pigpio library for initialisation. --- gpiozero/pins/pigpiod.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index a14ef9e..463e049 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -8,6 +8,7 @@ str = type('') import warnings import pigpio +import os from . import Pin from .data import pi_info @@ -98,7 +99,7 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} - def __new__(cls, number, host='localhost', port=8888): + def __new__(cls, number, host=os.getenv("PIGPIO_ADDR", 'localhost'), port=os.getenv("PIGPIO_PORT", 8888)): try: return cls._PINS[(host, port, number)] except KeyError: From 76ab6badd26946658aa0d160b440fcf36e796b5b Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 27 Jul 2016 00:17:20 +0200 Subject: [PATCH 087/104] Applying suggestion by lurch in https://github.com/RPi-Distro/python-gpiozero/pull/395 comment. --- gpiozero/pins/pigpiod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index 463e049..bbb63f9 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -99,7 +99,7 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} - def __new__(cls, number, host=os.getenv("PIGPIO_ADDR", 'localhost'), port=os.getenv("PIGPIO_PORT", 8888)): + def __new__(cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'), port=int(os.getenv('PIGPIO_PORT', 8888))): try: return cls._PINS[(host, port, number)] except KeyError: From 9c08c77ae871556ae0d6344d707c6afa8d902311 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 14 Aug 2016 10:48:01 +0100 Subject: [PATCH 088/104] Update debian/control as suggested in #359 --- debian/control | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/debian/control b/debian/control index 72f2eb4..77a9cf3 100644 --- a/debian/control +++ b/debian/control @@ -11,8 +11,9 @@ X-Python3-Version: >= 3.2 Package: python-gpiozero Architecture: all Section: python -Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio -Suggests: python-spidev, python-gpiozero-docs +Depends: ${misc:Depends}, ${python:Depends} +Recommends: python-rpi.gpio, python-spidev +Suggests: python-gpiozero-docs Description: Simple API for controlling devices attached to the GPIO pins. gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify interaction with devices connected to the GPIO pins, from simple buttons and @@ -24,8 +25,9 @@ Description: Simple API for controlling devices attached to the GPIO pins. Package: python3-gpiozero Architecture: all Section: python -Depends: ${misc:Depends}, ${python3:Depends}, python3-rpi.gpio -Suggests: python3-spidev, python-gpiozero-docs +Depends: ${misc:Depends}, ${python3:Depends} +Recommends: python3-rpi.gpio, python3-spidev +Suggests: python-gpiozero-docs Description: Simple API for controlling devices attached to the GPIO pins. gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify interaction with devices connected to the GPIO pins, from simple buttons and From 1ec2b763f5cc61507236fc0f1360a26e7fb08e74 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 14 Aug 2016 11:27:58 +0100 Subject: [PATCH 089/104] Fix #359, close #396 --- gpiozero/devices.py | 50 ++++++++++++++++++++++++++++----------------- gpiozero/exc.py | 3 +++ setup.py | 8 ++++++++ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/gpiozero/devices.py b/gpiozero/devices.py index df78557..b5a2648 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -7,6 +7,7 @@ from __future__ import ( nstr = str str = type('') +import os import atexit import weakref from collections import namedtuple @@ -14,12 +15,16 @@ from itertools import chain from types import FunctionType from threading import RLock +import pkg_resources + from .threads import _threads_shutdown +from .pins import _pins_shutdown from .mixins import ( ValuesMixin, SharedMixin, ) from .exc import ( + BadPinFactory, DeviceClosed, CompositeDeviceBadName, CompositeDeviceBadOrder, @@ -30,25 +35,32 @@ from .exc import ( ) from .compat import frozendict -# Get a pin implementation to use as the default; we prefer RPi.GPIO's here -# as it supports PWM, and all Pi revisions. If no third-party libraries are -# available, however, we fall back to a pure Python implementation which -# supports platforms like PyPy -from .pins import _pins_shutdown -try: - from .pins.rpigpio import RPiGPIOPin - pin_factory = RPiGPIOPin -except ImportError: - try: - from .pins.rpio import RPIOPin - pin_factory = RPIOPin - except ImportError: - try: - from .pins.pigpiod import PiGPIOPin - pin_factory = PiGPIOPin - except ImportError: - from .pins.native import NativePin - pin_factory = NativePin + +def _default_pin_factory(name=os.getenv('GPIOZERO_PIN_FACTORY', None)): + group = 'gpiozero_pin_factories' + if name is None: + # If no factory is explicitly specified, try various names in + # "preferred" order. Note that in this case we only select from + # gpiozero distribution so without explicitly specifying a name (via + # the environment) it's impossible to auto-select a factory from + # outside the base distribution + # + # We prefer RPi.GPIO here as it supports PWM, and all Pi revisions. If + # no third-party libraries are available, however, we fall back to a + # pure Python implementation which supports platforms like PyPy + dist = pkg_resources.get_distribution('gpiozero') + for name in ('RPiGPIOPin', 'RPIOPin', 'PiGPIOPin', 'NativePin'): + try: + return pkg_resources.load_entry_point(dist, group, name) + except ImportError: + pass + raise BadPinFactory('Unable to locate any default pin factory!') + else: + for factory in pkg_resources.iter_entry_points(group, name): + return factory.load() + raise BadPinFactory('Unable to locate pin factory "%s"' % name) + +pin_factory = _default_pin_factory() _PINS = set() diff --git a/gpiozero/exc.py b/gpiozero/exc.py index 3e8ed50..1afd214 100644 --- a/gpiozero/exc.py +++ b/gpiozero/exc.py @@ -22,6 +22,9 @@ class BadWaitTime(GPIOZeroError, ValueError): class BadQueueLen(GPIOZeroError, ValueError): "Error raised when non-positive queue length is specified" +class BadPinFactory(GPIOZeroError, ImportError): + "Error raised when an unknown pin factory name is specified" + class CompositeDeviceError(GPIOZeroError): "Base class for errors specific to the CompositeDevice hierarchy" diff --git a/setup.py b/setup.py index 396aa34..1256aba 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,14 @@ if sys.version_info[:2] == (3, 2): __extra_requires__['test'][1] = 'coverage<4.0dev' __entry_points__ = { + 'gpiozero_pin_factories': [ + 'PiGPIOPin = gpiozero.pins.pigpiod:PiGPIOPin', + 'RPiGPIOPin = gpiozero.pins.rpigpio:RPiGPIOPin', + 'RPIOPin = gpiozero.pins.rpio:RPIOPin', + 'NativePin = gpiozero.pins.native:NativePin', + 'MockPin = gpiozero.pins.mock:MockPin', + 'MockPWMPin = gpiozero.pins.mock:MockPWMPin', + ], } From d401a726828a9868c6ebc913508121af481faaf9 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 14 Aug 2016 13:13:54 +0100 Subject: [PATCH 090/104] Annotate recipe with LED sys/class names --- docs/recipes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 3a6cf02..3ec224f 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -1002,8 +1002,8 @@ Now you can control the LEDs with gpiozero like so:: from gpiozero import LED from signal import pause - power = LED(35) - activity = LED(47) + power = LED(35) # /sys/class/leds/led1 + activity = LED(47) # /sys/class/leds/led0 activity.blink() power.blink() From ab8dcf8794d7632ba9bb0153f65d3832f18f6f6b Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sun, 14 Aug 2016 13:31:05 +0100 Subject: [PATCH 091/104] Add missing closing-bracket --- docs/recipes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/recipes.rst b/docs/recipes.rst index 3a6cf02..c2205c2 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -1020,7 +1020,7 @@ run the following commands:: On the Pi Zero you can control the activity LED with this recipe, but there's no separate power LED to control (it's also worth noting the activity LED is active low, so set ``active_high=False`` when constructing - your LED component. + your LED component). On the original Pi 1 (model A or B), the activity LED can be controlled with GPIO16 (after disabling its trigger as above) but the power LED is From 335ccdfb6020833fa843fc8fcd3de370a5e0e5b6 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 14 Aug 2016 22:51:02 +0100 Subject: [PATCH 092/104] Fix #320 Yup, dirty horrid hack but the result is okay. --- CONTRIBUTING.md | 44 ------------------------------------ README.rst | 2 +- docs/contributing.rst | 52 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 2 ++ 4 files changed, 55 insertions(+), 45 deletions(-) delete mode 100644 CONTRIBUTING.md create mode 100644 docs/contributing.rst diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f0d8ec3..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,44 +0,0 @@ -# Contributing - -This module was designed for use in education; particularly for young children. -It is intended to provide a simple interface to everyday components. - -If a proposed change added an advanced feature but made basic usage more -complex, it is unlikely to be added. - -## Suggestions - -Please make suggestions for additional components or enhancements to the -codebase by opening an -[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining your -reasoning clearly. - -## Bugs - -Please submit bug reports by opening an -[issue](https://github.com/RPi-Distro/python-gpiozero/issues) explaining the -problem clearly using code examples. - -## Documentation - -The documentation source lives in the -[docs](https://github.com/RPi-Distro/python-gpiozero/tree/master/docs) folder. -Contributions to the documentation are welcome but should be easy to read and -understand. - -## Commit messages and pull requests - -Commit messages should be concise but descriptive, and in the form of a patch -description, i.e. instructional not past tense ("Add LED example" not "Added -LED example"). Commits that close (or intend to close) an issue should use the -phrase "fix #123" where `#123` is the issue number. - -## Backwards compatibility - -Since this library reached v1.0 we aim to maintain backwards-compatibility -thereafter. Changes which break backwards-compatibility will not be accepted. - -## Python - -- Python 2/3 compatibility -- PEP8-compliance (with exceptions) diff --git a/README.rst b/README.rst index 25c2f87..028e3cf 100644 --- a/README.rst +++ b/README.rst @@ -100,7 +100,7 @@ Contributors .. _GitHub: https://github.com/RPi-Distro/python-gpiozero .. _issues: https://github.com/RPi-Distro/python-gpiozero/issues .. _recipes: http://gpiozero.readthedocs.io/en/latest/recipes.html -.. _Contribute: CONTRIBUTING.md +.. _contribute: http://gpiozero.readthedocs.io/en/latest/contributing.html .. _Ben Nuttall: https://github.com/bennuttall .. _Dave Jones: https://github.com/waveform80 .. _Martin O'Hanlon: https://github.com/martinohanlon diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..d1c6d17 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,52 @@ +.. _contributing: + +============ +Contributing +============ + +This module was designed for use in education; particularly for young children. +It is intended to provide a simple interface to everyday components. + +If a proposed change added an advanced feature but made basic usage more +complex, it is unlikely to be added. + +Suggestions +=========== + +Please make suggestions for additional components or enhancements to the +codebase by opening an `issue`_ explaining your reasoning clearly. + +Bugs +==== + +Please submit bug reports by opening an `issue`_ explaining the problem clearly +using code examples. + +Documentation +============= + +The documentation source lives in the `docs`_ folder. Contributions to the +documentation are welcome but should be easy to read and understand. + +Commit messages and pull requests +================================= + +Commit messages should be concise but descriptive, and in the form of a patch +description, i.e. instructional not past tense ("Add LED example" not "Added +LED example"). Commits that close (or intend to close) an issue should use the +phrase "fix #123" where ``#123`` is the issue number. + +Backwards compatibility +======================= + +Since this library reached v1.0 we aim to maintain backwards-compatibility +thereafter. Changes which break backwards-compatibility will not be accepted. + +Python +====== + +* Python 2/3 compatibility +* PEP8-compliance (with exceptions) + +.. _docs: https://github.com/RPi-Distro/python-gpiozero/tree/master/docs +.. _issue: https://github.com/RPi-Distro/python-gpiozero/issues diff --git a/docs/index.rst b/docs/index.rst index cb32ed0..256db1e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,7 @@ Table of Contents recipes notes + contributing api_input api_output api_spi @@ -19,3 +20,4 @@ Table of Contents api_exc changelog license + From 65285d1b3301f618aa3f98bf32fbd7416983e47e Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Sun, 14 Aug 2016 23:43:59 +0100 Subject: [PATCH 093/104] Correct number of ComputeModule USB ports Fixes #384 --- gpiozero/pins/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index fba4b6b..8db8142 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -255,10 +255,10 @@ PI_REVISIONS = { 0xe: ('B', '2.0', '2012Q4', 'BCM2835', 'Sony', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0xf: ('B', '2.0', '2012Q4', 'BCM2835', 'Egoman', 512, 'SD', 2, 1, False, False, 1, 1, {'P1': REV2_P1, 'P5': REV2_P5},), 0x10: ('B+', '1.2', '2014Q3', 'BCM2835', 'Sony', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x11: ('CM', '1.2', '2014Q2', 'BCM2835', 'Sony', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x12: ('A+', '1.2', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0x13: ('B+', '1.2', '2015Q1', 'BCM2835', 'Egoman', 512, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), - 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 0, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), + 0x14: ('CM', '1.1', '2014Q2', 'BCM2835', 'Embest', 512, 'eMMC', 1, 0, False, False, 2, 2, {'SODIMM': CM_SODIMM}, ), 0x15: ('A+', '1.1', '2014Q4', 'BCM2835', 'Sony', 256, 'MicroSD', 1, 0, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa01041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Sony', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), 0xa21041: ('2B', '1.1', '2015Q1', 'BCM2836', 'Embest', 1024, 'MicroSD', 4, 1, False, False, 1, 1, {'P1': PLUS_P1}, ), From 37b91ced74fca44ddeb86a8853f4f176500907e1 Mon Sep 17 00:00:00 2001 From: Andrew Scheller Date: Thu, 18 Aug 2016 00:22:07 +0100 Subject: [PATCH 094/104] Correct number of ComputeModule USB ports (part 2) Correct associated documentation --- gpiozero/pins/data.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index 8db8142..c854999 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -389,10 +389,6 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( .. note:: This does *not* include the micro-USB port used to power the Pi. - On the Compute Module this is listed as 0 as the compute module - itself doesn't have any physical USB headers, despite providing one - on the I/O development board and having the pins for one on the - module itself. .. attribute:: ethernet From 6cc308e44a987db1cc3e038b07627add91e1c5ad Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 15 Aug 2016 21:17:44 +0100 Subject: [PATCH 095/104] Fix #354, fix #389 Overhaul the pi_info system: Pin factories are now capable of generating pi_info themselves (although currently they all just look up the revision and call pi_info with a specific one). PiGPIOPin will now return pi_info for the remote pi which can be specified by parameter or implicitly by the environment vars. Overvolted Pis should work properly no matter what (some argument over whether the revision 7 or 8 chars in this case; both should work). Added some minor tweaks for the new camera-capable Pi Zero Finally, added a bunch of tests for pins.data --- docs/api_pins.rst | 38 +++++++-------- gpiozero/__init__.py | 1 + gpiozero/pins/__init__.py | 49 +++++++++++++++++++ gpiozero/pins/data.py | 100 ++++++++++++++++++-------------------- gpiozero/pins/mock.py | 5 ++ gpiozero/pins/native.py | 4 +- gpiozero/pins/pigpiod.py | 28 +++++++---- gpiozero/pins/rpigpio.py | 4 +- gpiozero/pins/rpio.py | 4 +- tests/test_pins_data.py | 90 ++++++++++++++++++++++++++++++++++ 10 files changed, 237 insertions(+), 86 deletions(-) create mode 100644 tests/test_pins_data.py diff --git a/docs/api_pins.rst b/docs/api_pins.rst index 5ad818c..b640af4 100644 --- a/docs/api_pins.rst +++ b/docs/api_pins.rst @@ -37,28 +37,21 @@ You can change the default pin implementation by over-writing the # This will now use NativePin instead of RPiGPIOPin led = LED(16) -``pin_factory`` is simply a callable that accepts a single argument: the number -of the pin to be constructed (this prototype *may* be expanded in future). This -means you can define it as a function that provides additional parameters to an -underlying class. For example, to default to creating pins with -:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi``:: +``pin_factory`` is a concrete descendent of the abstract :class:`Pin` class. +The descendent may take additional parameters in its constructor provided they +are optional; GPIO Zero will expect to be able to construct instances with +nothing more than an integer pin number. - from gpiozero.pins.pigpiod import PiGPIOPin - import gpiozero.devices +However, the descendent may take default information from additional sources. +For example, to default to creating pins with +:class:`gpiozero.pins.pigpiod.PiGPIOPin` on a remote pi called ``remote-pi`` +you can set the :envvar:`PIGPIO_ADDR` environment variable when running your +script:: - def my_pin_factory(number): - return PiGPIOPin(number, host='remote-pi') + $ PIGPIO_ADDR=remote-pi python my_script.py - gpiozero.devices.pin_factory = my_pin_factory - - from gpiozero import TrafficLights - - # This will now use pins on remote-pi (assuming it has the - # pigpiod daemon installed and running) - tl = TrafficLights(13, 19, 26) - -Alternatively, instead of passing an integer to the device constructor, you -can pass an object derived from :class:`Pin` itself:: +It is worth noting that instead of passing an integer to device constructors, +you can pass an object derived from :class:`Pin` itself:: from gpiozero.pins.native import NativePin from gpiozero import LED @@ -121,6 +114,13 @@ Abstract Pin :members: +Local Pin +========= + +.. autoclass:: LocalPin + :members: + + Utilities ========= diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index e2933be..6a6dcb1 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -7,6 +7,7 @@ from __future__ import ( from .pins import ( Pin, + LocalPin, ) from .pins.data import ( PiBoardInfo, diff --git a/gpiozero/pins/__init__.py b/gpiozero/pins/__init__.py index e753076..3503145 100644 --- a/gpiozero/pins/__init__.py +++ b/gpiozero/pins/__init__.py @@ -6,6 +6,9 @@ from __future__ import ( ) str = type('') +import io + +from .data import pi_info from ..exc import ( PinInvalidFunction, PinSetInput, @@ -47,6 +50,7 @@ class Pin(object): * :meth:`_set_edges` * :meth:`_get_when_changed` * :meth:`_set_when_changed` + * :meth:`pi_info` * :meth:`output_with_state` * :meth:`input_with_pull` @@ -243,3 +247,48 @@ class Pin(object): property will raise :exc:`PinEdgeDetectUnsupported`. """) + @classmethod + def pi_info(cls): + """ + Returns a :class:`PiBoardInfo` instance representing the Pi that + instances of this pin class will be attached to. + + If the pins represented by this class are not *directly* attached to a + Pi (e.g. the pin is attached to a board attached to the Pi, or the pins + are not on a Pi at all), this may return ``None``. + """ + return None + + +class LocalPin(Pin): + """ + Abstract base class representing pins attached locally to a Pi. This forms + the base class for local-only pin interfaces (:class:`RPiGPIOPin`, + :class:`RPIOPin`, and :class:`NativePin`). + """ + _PI_REVISION = None + + @classmethod + def pi_info(cls): + """ + Returns a :class:`PiBoardInfo` instance representing the local Pi. + The Pi's revision is determined by reading :file:`/proc/cpuinfo`. If + no valid revision is found, returns ``None``. + """ + # Cache the result as we can reasonably assume it won't change during + # runtime (this is LocalPin after all; descendents that deal with + # remote Pis should inherit from Pin instead) + if cls._PI_REVISION is None: + with io.open('/proc/cpuinfo', 'r') as f: + for line in f: + if line.startswith('Revision'): + revision = line.split(':')[1].strip().lower() + overvolted = revision.startswith('100') + if overvolted: + revision = revision[-4:] + cls._PI_REVISION = revision + break + if cls._PI_REVISION is None: + return None # something weird going on + return pi_info(cls._PI_REVISION) + diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index c854999..6490042 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -324,6 +324,12 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( a tuple, it is strongly recommended that you use the following named attributes to access the data contained within. + .. automethod:: physical_pin + + .. automethod:: physical_pins + + .. automethod:: pulled_up + .. attribute:: revision A string indicating the revision of the Pi. This is unique to each @@ -426,7 +432,7 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( def physical_pins(self, function): """ - Return the physical pins supporting the specified *function* as a tuple + Return the physical pins supporting the specified *function* as tuples of ``(header, pin_number)`` where *header* is a string specifying the header containing the *pin_number*. Note that the return value is a :class:`set` which is not indexable. Use :func:`physical_pin` if you @@ -483,19 +489,6 @@ class PiBoardInfo(namedtuple('PiBoardInfo', ( return self.headers[header][number].pull_up -_PI_REVISION = None -def _get_pi_revision(): - with io.open('/proc/cpuinfo', 'r') as f: - for line in f: - if line.startswith('Revision'): - revision = line.split(':')[1].strip().lower() - overvolted = revision.startswith('1000') - if overvolted: - revision = revision[4:] - return revision - raise IOError('unable to locate Pi revision in /proc/cpuinfo') - - def _parse_pi_revision(revision): # For new-style revisions the value's bit pattern is as follows: # @@ -510,7 +503,7 @@ def _parse_pi_revision(revision): # TTTTTTTT - Type (0=A, 1=B, 2=A+, 3=B+, 4=2B, 5=Alpha (??), 6=CM, 8=3B, 9=Zero) # RRRR - Revision (0, 1, or 2) if not (revision & 0x800000): - raise ValueError('cannot parse "%x"; this is not a new-style revision' % revision) + raise PinUnknownPi('cannot parse "%x"; this is not a new-style revision' % revision) try: model = { 0: 'A', @@ -538,7 +531,7 @@ def _parse_pi_revision(revision): '2B': '2015Q1', 'CM': '2014Q2', '3B': '2016Q1', - 'Zero': '2015Q4', + 'Zero': '2015Q4' if pcb_revision == '1.0' else '2016Q2', }[model] soc = { 0: 'BCM2835', @@ -580,7 +573,7 @@ def _parse_pi_revision(revision): '3B': True, }.get(model, False) csi = { - 'Zero': 0, + 'Zero': 0 if pcb_revision == '0.0' else 1, 'CM': 2, }.get(model, 1) dsi = csi @@ -590,7 +583,7 @@ def _parse_pi_revision(revision): 'CM': {'SODIMM': CM_SODIMM}, }.get(model, {'P1': PLUS_P1}) except KeyError: - raise ValueError('unable to parse new-style revision "%x"' % revision) + raise PinUnknownPi('unable to parse new-style revision "%x"' % revision) else: return ( model, @@ -620,20 +613,26 @@ def pi_info(revision=None): or ``None`` (the default), then the library will attempt to determine the model of Pi it is running on and return information about that. """ - # cache the result as we can reasonably assume the revision of the Pi isn't - # going to change at runtime... if revision is None: - global _PI_REVISION - if _PI_REVISION is None: - try: - _PI_REVISION = _get_pi_revision() - except IOError: - _PI_REVISION = 'unknown' - revision = _PI_REVISION - try: - revision_int = int(revision, base=16) - except ValueError: - raise PinUnknownPi('unknown RPi revision "%s"' % revision) + # NOTE: This import is declared locally for two reasons. Firstly it + # avoids a circular dependency (devices->pins->pins.data->devices). + # Secondly, pin_factory is one global which might potentially be + # re-written by a user's script at runtime hence we should re-import + # here in case it's changed since initialization + from ..devices import pin_factory + result = pin_factory.pi_info() + if result is None: + raise PinUnknownPi('The default pin_factory is not attached to a Pi') + else: + return result + else: + if isinstance(revision, bytes): + revision = revision.decode('ascii') + if isinstance(revision, str): + revision = int(revision, base=16) + else: + # be nice to people passing an int (or something numeric anyway) + revision = int(revision) try: ( model, @@ -650,27 +649,24 @@ def pi_info(revision=None): csi, dsi, headers, - ) = PI_REVISIONS[revision_int] + ) = PI_REVISIONS[revision] except KeyError: - try: - ( - model, - pcb_revision, - released, - soc, - manufacturer, - memory, - storage, - usb, - ethernet, - wifi, - bluetooth, - csi, - dsi, - headers, - ) = _parse_pi_revision(revision_int) - except ValueError: - raise PinUnknownPi('unknown RPi revision "%s"' % revision) + ( + model, + pcb_revision, + released, + soc, + manufacturer, + memory, + storage, + usb, + ethernet, + wifi, + bluetooth, + csi, + dsi, + headers, + ) = _parse_pi_revision(revision) headers = { header: { number: PinInfo(number, function, pull_up) @@ -679,7 +675,7 @@ def pi_info(revision=None): for header, header_data in headers.items() } return PiBoardInfo( - revision, + '%04x' % revision, model, pcb_revision, released, diff --git a/gpiozero/pins/mock.py b/gpiozero/pins/mock.py index 101ae7d..f865c30 100644 --- a/gpiozero/pins/mock.py +++ b/gpiozero/pins/mock.py @@ -16,6 +16,7 @@ except ImportError: from ..compat import isclose from . import Pin +from .data import pi_info from ..exc import PinSetInput, PinPWMUnsupported, PinFixedPull @@ -32,6 +33,10 @@ class MockPin(Pin): def clear_pins(cls): cls._PINS.clear() + @classmethod + def pi_info(cls): + return pi_info('a21041') # Pretend we're a Pi 2B + def __new__(cls, number): if not (0 <= number < 54): raise ValueError('invalid pin %d specified (must be 0..53)' % number) diff --git a/gpiozero/pins/native.py b/gpiozero/pins/native.py index 8244bac..290ea9a 100644 --- a/gpiozero/pins/native.py +++ b/gpiozero/pins/native.py @@ -17,7 +17,7 @@ from time import sleep from threading import Thread, Event, Lock from collections import Counter -from . import Pin, PINS_CLEANUP +from . import LocalPin, PINS_CLEANUP from .data import pi_info from ..exc import ( PinInvalidPull, @@ -149,7 +149,7 @@ class GPIOFS(object): f.write(str(pin).encode('ascii')) -class NativePin(Pin): +class NativePin(LocalPin): """ Uses a built-in pure Python implementation to interface to the Pi's GPIO pins. This is the default pin implementation if no third-party libraries diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index bbb63f9..f3ad7d5 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -69,7 +69,7 @@ class PiGPIOPin(Pin): .. _pigpio: http://abyz.co.uk/rpi/pigpio/ """ - _CONNECTIONS = {} + _CONNECTIONS = {} # maps (host, port) to (connection, pi_info) _PINS = {} GPIO_FUNCTIONS = { @@ -99,18 +99,15 @@ class PiGPIOPin(Pin): GPIO_PULL_UP_NAMES = {v: k for (k, v) in GPIO_PULL_UPS.items()} GPIO_EDGES_NAMES = {v: k for (k, v) in GPIO_EDGES.items()} - def __new__(cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'), port=int(os.getenv('PIGPIO_PORT', 8888))): + def __new__( + cls, number, host=os.getenv('PIGPIO_ADDR', 'localhost'), + port=int(os.getenv('PIGPIO_PORT', 8888))): try: return cls._PINS[(host, port, number)] except KeyError: self = super(PiGPIOPin, cls).__new__(cls) - try: - self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] - except KeyError: - self._connection = pigpio.pi(host, port) - revision = hex(self._connection.get_hardware_revision())[2:] - self._pi_info = pi_info(revision) - cls._CONNECTIONS[(host, port)] = (self._connection, self._pi_info) + cls.pi_revision(host, port) # implicitly creates connection + self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] try: self._pi_info.physical_pin('GPIO%d' % number) except PinNoPins: @@ -259,3 +256,16 @@ class PiGPIOPin(Pin): self._number, self._edges, lambda gpio, level, tick: value()) + @classmethod + def pi_info( + cls, host=os.getenv('PIGPIO_ADDR', 'localhost'), + port=int(os.getenv('PIGPIO_PORT', 8888))): + try: + connection, info = cls._CONNECTIONS[(host, port)] + except KeyError: + connection = pigpio.pi(host, port) + revision = '%04x' % connection.get_hardware_revision() + info = pi_info(revision) + cls._CONNECTIONS[(host, port)] = (connection, info) + return info + diff --git a/gpiozero/pins/rpigpio.py b/gpiozero/pins/rpigpio.py index 9fea142..1597fb0 100644 --- a/gpiozero/pins/rpigpio.py +++ b/gpiozero/pins/rpigpio.py @@ -9,7 +9,7 @@ str = type('') import warnings from RPi import GPIO -from . import Pin +from . import LocalPin from .data import pi_info from ..exc import ( PinInvalidFunction, @@ -24,7 +24,7 @@ from ..exc import ( ) -class RPiGPIOPin(Pin): +class RPiGPIOPin(LocalPin): """ Uses the `RPi.GPIO`_ library to interface to the Pi's GPIO pins. This is the default pin implementation if the RPi.GPIO library is installed. diff --git a/gpiozero/pins/rpio.py b/gpiozero/pins/rpio.py index 5118150..58d5893 100644 --- a/gpiozero/pins/rpio.py +++ b/gpiozero/pins/rpio.py @@ -12,7 +12,7 @@ import RPIO import RPIO.PWM from RPIO.Exceptions import InvalidChannelException -from . import Pin, PINS_CLEANUP +from . import LocalPin, PINS_CLEANUP from .data import pi_info from ..exc import ( PinInvalidFunction, @@ -27,7 +27,7 @@ from ..exc import ( ) -class RPIOPin(Pin): +class RPIOPin(LocalPin): """ Uses the `RPIO`_ library to interface to the Pi's GPIO pins. This is the default pin implementation if the RPi.GPIO library is not installed, diff --git a/tests/test_pins_data.py b/tests/test_pins_data.py new file mode 100644 index 0000000..27395d4 --- /dev/null +++ b/tests/test_pins_data.py @@ -0,0 +1,90 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +str = type('') + + +import pytest +from mock import patch, MagicMock + +import gpiozero.devices +import gpiozero.pins.data +import gpiozero.pins.native +from gpiozero.pins.data import pi_info +from gpiozero import PinMultiplePins, PinNoPins, PinUnknownPi + + +def test_pi_revision(): + save_factory = gpiozero.devices.pin_factory + try: + # Can't use MockPin for this as we want something that'll actually try + # and read /proc/cpuinfo (MockPin simply parrots the 2B's data); + # NativePin is used as we're guaranteed to be able to import it + gpiozero.devices.pin_factory = gpiozero.pins.native.NativePin + with patch('io.open') as m: + m.return_value.__enter__.return_value = ['lots of irrelevant', 'lines', 'followed by', 'Revision: 0002', 'Serial: xxxxxxxxxxx'] + assert pi_info().revision == '0002' + # LocalPin caches the revision (because realistically it isn't going to + # change at runtime); we need to wipe it here though + gpiozero.pins.native.NativePin._PI_REVISION = None + m.return_value.__enter__.return_value = ['Revision: a21042'] + assert pi_info().revision == 'a21042' + # Check over-volting result (some argument over whether this is 7 or + # 8 character result; make sure both work) + gpiozero.pins.native.NativePin._PI_REVISION = None + m.return_value.__enter__.return_value = ['Revision: 1000003'] + assert pi_info().revision == '0003' + gpiozero.pins.native.NativePin._PI_REVISION = None + m.return_value.__enter__.return_value = ['Revision: 100003'] + assert pi_info().revision == '0003' + with pytest.raises(PinUnknownPi): + m.return_value.__enter__.return_value = ['nothing', 'relevant', 'at all'] + gpiozero.pins.native.NativePin._PI_REVISION = None + pi_info() + with pytest.raises(PinUnknownPi): + pi_info('0fff') + finally: + gpiozero.devices.pin_factory = save_factory + +def test_pi_info(): + r = pi_info('900011') + assert r.model == 'B' + assert r.pcb_revision == '1.0' + assert r.memory == 512 + assert r.manufacturer == 'Sony' + assert r.storage == 'SD' + assert r.usb == 2 + assert not r.wifi + assert not r.bluetooth + assert r.csi == 1 + assert r.dsi == 1 + with pytest.raises(PinUnknownPi): + pi_info('9000f1') + +def test_pi_info_other_types(): + with pytest.raises(PinUnknownPi): + pi_info(b'9000f1') + with pytest.raises(PinUnknownPi): + pi_info(0x9000f1) + +def test_physical_pins(): + # Assert physical pins for some well-known Pi's; a21041 is a Pi2B + assert pi_info('a21041').physical_pins('3V3') == {('P1', 1), ('P1', 17)} + assert pi_info('a21041').physical_pins('GPIO2') == {('P1', 3)} + assert pi_info('a21041').physical_pins('GPIO47') == set() + +def test_physical_pin(): + with pytest.raises(PinMultiplePins): + assert pi_info('a21041').physical_pin('GND') + assert pi_info('a21041').physical_pin('GPIO3') == ('P1', 5) + with pytest.raises(PinNoPins): + assert pi_info('a21041').physical_pin('GPIO47') + +def test_pulled_up(): + assert pi_info('a21041').pulled_up('GPIO2') + assert not pi_info('a21041').pulled_up('GPIO4') + assert not pi_info('a21041').pulled_up('GPIO47') + From 32803a7988b45c9667453702e3f795dbfdaa3faa Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 29 Aug 2016 20:41:11 +0100 Subject: [PATCH 096/104] Convert recipe examples to includes Makes it much easier to test things - no copying'n'pasting just run the examples straight from the dir (after wiring stuff up) --- docs/conf.py | 1 + docs/examples/all_on_1.py | 9 + docs/examples/all_on_2.py | 9 + docs/examples/all_on_3.py | 23 + docs/examples/button_1.py | 9 + docs/examples/button_2.py | 6 + docs/examples/button_3.py | 11 + docs/examples/button_4.py | 15 + docs/examples/button_camera_1.py | 15 + docs/examples/button_camera_2.py | 18 + docs/examples/button_led_1.py | 10 + docs/examples/button_led_2.py | 9 + docs/examples/button_shutdown.py | 11 + docs/examples/button_stop_motion.py | 12 + docs/examples/distance_sensor_1.py | 8 + docs/examples/distance_sensor_2.py | 10 + docs/examples/led_1.py | 10 + docs/examples/led_2.py | 8 + docs/examples/led_bargraph_1.py | 15 + docs/examples/led_bargraph_2.py | 15 + docs/examples/led_board_1.py | 15 + docs/examples/led_board_2.py | 5 + docs/examples/led_builtin.py | 9 + docs/examples/led_pulse.py | 8 + docs/examples/led_travis.py | 19 + docs/examples/led_variable_brightness.py | 12 + docs/examples/light_sensor_1.py | 9 + docs/examples/light_sensor_2.py | 10 + docs/examples/light_sensor_3.py | 9 + docs/examples/motion_sensor.py | 10 + docs/examples/motor.py | 10 + docs/examples/music_box.py | 18 + docs/examples/pot_1.py | 6 + docs/examples/pot_2.py | 7 + docs/examples/reaction_game.py | 22 + docs/examples/rgbled.py | 28 + docs/examples/rgbled_pot_1.py | 11 + docs/examples/rgbled_pot_2.py | 11 + docs/examples/robot_1.py | 10 + docs/examples/robot_2.py | 9 + docs/examples/robot_buttons.py | 23 + docs/examples/robot_keyboard_1.py | 34 + docs/examples/robot_keyboard_2.py | 21 + docs/examples/robot_motion_1.py | 10 + docs/examples/robot_motion_2.py | 9 + docs/examples/thermometer.py | 12 + docs/examples/traffic_lights_1.py | 20 + docs/examples/traffic_lights_2.py | 20 + docs/examples/traffic_lights_3.py | 24 + docs/recipes.rst | 802 ++++------------------- 50 files changed, 745 insertions(+), 692 deletions(-) create mode 100644 docs/examples/all_on_1.py create mode 100644 docs/examples/all_on_2.py create mode 100644 docs/examples/all_on_3.py create mode 100644 docs/examples/button_1.py create mode 100644 docs/examples/button_2.py create mode 100644 docs/examples/button_3.py create mode 100644 docs/examples/button_4.py create mode 100644 docs/examples/button_camera_1.py create mode 100644 docs/examples/button_camera_2.py create mode 100644 docs/examples/button_led_1.py create mode 100644 docs/examples/button_led_2.py create mode 100644 docs/examples/button_shutdown.py create mode 100644 docs/examples/button_stop_motion.py create mode 100644 docs/examples/distance_sensor_1.py create mode 100644 docs/examples/distance_sensor_2.py create mode 100644 docs/examples/led_1.py create mode 100644 docs/examples/led_2.py create mode 100644 docs/examples/led_bargraph_1.py create mode 100644 docs/examples/led_bargraph_2.py create mode 100644 docs/examples/led_board_1.py create mode 100644 docs/examples/led_board_2.py create mode 100644 docs/examples/led_builtin.py create mode 100644 docs/examples/led_pulse.py create mode 100644 docs/examples/led_travis.py create mode 100644 docs/examples/led_variable_brightness.py create mode 100644 docs/examples/light_sensor_1.py create mode 100644 docs/examples/light_sensor_2.py create mode 100644 docs/examples/light_sensor_3.py create mode 100644 docs/examples/motion_sensor.py create mode 100644 docs/examples/motor.py create mode 100644 docs/examples/music_box.py create mode 100644 docs/examples/pot_1.py create mode 100644 docs/examples/pot_2.py create mode 100644 docs/examples/reaction_game.py create mode 100644 docs/examples/rgbled.py create mode 100644 docs/examples/rgbled_pot_1.py create mode 100644 docs/examples/rgbled_pot_2.py create mode 100644 docs/examples/robot_1.py create mode 100644 docs/examples/robot_2.py create mode 100644 docs/examples/robot_buttons.py create mode 100644 docs/examples/robot_keyboard_1.py create mode 100644 docs/examples/robot_keyboard_2.py create mode 100644 docs/examples/robot_motion_1.py create mode 100644 docs/examples/robot_motion_2.py create mode 100644 docs/examples/thermometer.py create mode 100644 docs/examples/traffic_lights_1.py create mode 100644 docs/examples/traffic_lights_2.py create mode 100644 docs/examples/traffic_lights_3.py diff --git a/docs/conf.py b/docs/conf.py index fea2f1c..6fb6ea2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,6 +74,7 @@ autodoc_member_order = 'groupwise' intersphinx_mapping = { 'python': ('http://docs.python.org/3.4', None), + 'picamera': ('http://picamera.readthedocs.io/en/latest', None), } # -- Options for HTML output ---------------------------------------------- diff --git a/docs/examples/all_on_1.py b/docs/examples/all_on_1.py new file mode 100644 index 0000000..978e4e1 --- /dev/null +++ b/docs/examples/all_on_1.py @@ -0,0 +1,9 @@ +from gpiozero import FishDish +from signal import pause + +fish = FishDish() + +fish.button.when_pressed = fish.on +fish.button.when_released = fish.off + +pause() diff --git a/docs/examples/all_on_2.py b/docs/examples/all_on_2.py new file mode 100644 index 0000000..0dccc78 --- /dev/null +++ b/docs/examples/all_on_2.py @@ -0,0 +1,9 @@ +from gpiozero import TrafficHat +from signal import pause + +th = TrafficHat() + +th.button.when_pressed = th.on +th.button.when_released = th.off + +pause() diff --git a/docs/examples/all_on_3.py b/docs/examples/all_on_3.py new file mode 100644 index 0000000..682adb4 --- /dev/null +++ b/docs/examples/all_on_3.py @@ -0,0 +1,23 @@ +from gpiozero import LED, Buzzer, Button +from signal import pause + +button = Button(2) +buzzer = Buzzer(3) +red = LED(4) +amber = LED(5) +green = LED(6) + +things = [red, amber, green, buzzer] + +def things_on(): + for thing in things: + thing.on() + +def things_off(): + for thing in things: + thing.off() + +button.when_pressed = things_on +button.when_released = things_off + +pause() diff --git a/docs/examples/button_1.py b/docs/examples/button_1.py new file mode 100644 index 0000000..5ea7e49 --- /dev/null +++ b/docs/examples/button_1.py @@ -0,0 +1,9 @@ +from gpiozero import Button + +button = Button(2) + +while True: + if button.is_pressed: + print("Button is pressed") + else: + print("Button is not pressed") diff --git a/docs/examples/button_2.py b/docs/examples/button_2.py new file mode 100644 index 0000000..4067627 --- /dev/null +++ b/docs/examples/button_2.py @@ -0,0 +1,6 @@ +from gpiozero import Button + +button = Button(2) + +button.wait_for_press() +print("Button was pressed") diff --git a/docs/examples/button_3.py b/docs/examples/button_3.py new file mode 100644 index 0000000..55694e1 --- /dev/null +++ b/docs/examples/button_3.py @@ -0,0 +1,11 @@ +from gpiozero import Button +from signal import pause + +def say_hello(): + print("Hello!") + +button = Button(2) + +button.when_pressed = say_hello + +pause() diff --git a/docs/examples/button_4.py b/docs/examples/button_4.py new file mode 100644 index 0000000..d3f9d34 --- /dev/null +++ b/docs/examples/button_4.py @@ -0,0 +1,15 @@ +from gpiozero import Button +from signal import pause + +def say_hello(): + print("Hello!") + +def say_goodbye(): + print("Goodbye!") + +button = Button(2) + +button.when_pressed = say_hello +button.when_released = say_goodbye + +pause() diff --git a/docs/examples/button_camera_1.py b/docs/examples/button_camera_1.py new file mode 100644 index 0000000..0affb17 --- /dev/null +++ b/docs/examples/button_camera_1.py @@ -0,0 +1,15 @@ +from gpiozero import Button +from picamera import PiCamera +from datetime import datetime +from signal import pause + +button = Button(2) +camera = PiCamera() + +def capture(): + datetime = datetime.now().isoformat() + camera.capture('/home/pi/%s.jpg' % datetime) + +button.when_pressed = capture + +pause() diff --git a/docs/examples/button_camera_2.py b/docs/examples/button_camera_2.py new file mode 100644 index 0000000..18552d4 --- /dev/null +++ b/docs/examples/button_camera_2.py @@ -0,0 +1,18 @@ +from gpiozero import Button +from picamera import PiCamera +from datetime import datetime +from signal import pause + +left_button = Button(2) +right_button = Button(3) +camera = PiCamera() + +def capture(): + datetime = datetime.now().isoformat() + camera.capture('/home/pi/%s.jpg' % datetime) + +left_button.when_pressed = camera.start_preview +left_button.when_released = camera.stop_preview +right_button.when_pressed = capture + +pause() diff --git a/docs/examples/button_led_1.py b/docs/examples/button_led_1.py new file mode 100644 index 0000000..3dce993 --- /dev/null +++ b/docs/examples/button_led_1.py @@ -0,0 +1,10 @@ +from gpiozero import LED, Button +from signal import pause + +led = LED(17) +button = Button(2) + +button.when_pressed = led.on +button.when_released = led.off + +pause() diff --git a/docs/examples/button_led_2.py b/docs/examples/button_led_2.py new file mode 100644 index 0000000..ba66df5 --- /dev/null +++ b/docs/examples/button_led_2.py @@ -0,0 +1,9 @@ +from gpiozero import LED, Button +from signal import pause + +led = LED(17) +button = Button(2) + +led.source = button.values + +pause() diff --git a/docs/examples/button_shutdown.py b/docs/examples/button_shutdown.py new file mode 100644 index 0000000..2a91a40 --- /dev/null +++ b/docs/examples/button_shutdown.py @@ -0,0 +1,11 @@ +from gpiozero import Button +from subprocess import check_call +from signal import pause + +def shutdown(): + check_call(['sudo', 'poweroff']) + +shutdown_btn = Button(17, hold_time=2) +shutdown_btn.when_held = shutdown + +pause() diff --git a/docs/examples/button_stop_motion.py b/docs/examples/button_stop_motion.py new file mode 100644 index 0000000..8111b48 --- /dev/null +++ b/docs/examples/button_stop_motion.py @@ -0,0 +1,12 @@ +from gpiozero import Button +from picamera import PiCamera + +button = Button(2) +camera = PiCamera() + +camera.start_preview() +frame = 1 +while True: + button.wait_for_press() + camera.capture('/home/pi/frame%03d.jpg' % frame) + frame += 1 diff --git a/docs/examples/distance_sensor_1.py b/docs/examples/distance_sensor_1.py new file mode 100644 index 0000000..c777aa9 --- /dev/null +++ b/docs/examples/distance_sensor_1.py @@ -0,0 +1,8 @@ +from gpiozero import DistanceSensor +from time import sleep + +sensor = DistanceSensor(23, 24) + +while True: + print('Distance to nearest object is', sensor.distance, 'm') + sleep(1) diff --git a/docs/examples/distance_sensor_2.py b/docs/examples/distance_sensor_2.py new file mode 100644 index 0000000..86dfe74 --- /dev/null +++ b/docs/examples/distance_sensor_2.py @@ -0,0 +1,10 @@ +from gpiozero import DistanceSensor, LED +from signal import pause + +sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) +led = LED(16) + +sensor.when_in_range = led.on +sensor.when_out_of_range = led.off + +pause() diff --git a/docs/examples/led_1.py b/docs/examples/led_1.py new file mode 100644 index 0000000..5e031b9 --- /dev/null +++ b/docs/examples/led_1.py @@ -0,0 +1,10 @@ +from gpiozero import LED +from time import sleep + +red = LED(17) + +while True: + red.on() + sleep(1) + red.off() + sleep(1) diff --git a/docs/examples/led_2.py b/docs/examples/led_2.py new file mode 100644 index 0000000..c09e5dd --- /dev/null +++ b/docs/examples/led_2.py @@ -0,0 +1,8 @@ +from gpiozero import LED +from signal import pause + +red = LED(17) + +red.blink() + +pause() diff --git a/docs/examples/led_bargraph_1.py b/docs/examples/led_bargraph_1.py new file mode 100644 index 0000000..96534b6 --- /dev/null +++ b/docs/examples/led_bargraph_1.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBarGraph +from time import sleep + +graph = LEDBarGraph(5, 6, 13, 19, 26, 20) + +graph.value = 1 # (1, 1, 1, 1, 1, 1) +sleep(1) +graph.value = 1/2 # (1, 1, 1, 0, 0, 0) +sleep(1) +graph.value = -1/2 # (0, 0, 0, 1, 1, 1) +sleep(1) +graph.value = 1/4 # (1, 0, 0, 0, 0, 0) +sleep(1) +graph.value = -1 # (1, 1, 1, 1, 1, 1) +sleep(1) diff --git a/docs/examples/led_bargraph_2.py b/docs/examples/led_bargraph_2.py new file mode 100644 index 0000000..9544990 --- /dev/null +++ b/docs/examples/led_bargraph_2.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBarGraph +from time import sleep + +graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) + +graph.value = 1/10 # (0.5, 0, 0, 0, 0) +sleep(1) +graph.value = 3/10 # (1, 0.5, 0, 0, 0) +sleep(1) +graph.value = -3/10 # (0, 0, 0, 0.5, 1) +sleep(1) +graph.value = 9/10 # (1, 1, 1, 1, 0.5) +sleep(1) +graph.value = 95/100 # (1, 1, 1, 1, 0.75) +sleep(1) diff --git a/docs/examples/led_board_1.py b/docs/examples/led_board_1.py new file mode 100644 index 0000000..e4a3a68 --- /dev/null +++ b/docs/examples/led_board_1.py @@ -0,0 +1,15 @@ +from gpiozero import LEDBoard +from time import sleep +from signal import pause + +leds = LEDBoard(5, 6, 13, 19, 26) + +leds.on() +sleep(1) +leds.off() +sleep(1) +leds.value = (1, 0, 1, 0, 1) +sleep(1) +leds.blink() + +pause() diff --git a/docs/examples/led_board_2.py b/docs/examples/led_board_2.py new file mode 100644 index 0000000..2b01a6d --- /dev/null +++ b/docs/examples/led_board_2.py @@ -0,0 +1,5 @@ +from gpiozero import LEDBoard + +leds = LEDBoard(5, 6, 13, 19, 26, pwm=True) + +leds.value = (0.2, 0.4, 0.6, 0.8, 1.0) diff --git a/docs/examples/led_builtin.py b/docs/examples/led_builtin.py new file mode 100644 index 0000000..80a06ff --- /dev/null +++ b/docs/examples/led_builtin.py @@ -0,0 +1,9 @@ +from gpiozero import LED +from signal import pause + +power = LED(35) # /sys/class/leds/led1 +activity = LED(47) # /sys/class/leds/led0 + +activity.blink() +power.blink() +pause() diff --git a/docs/examples/led_pulse.py b/docs/examples/led_pulse.py new file mode 100644 index 0000000..bc6ac49 --- /dev/null +++ b/docs/examples/led_pulse.py @@ -0,0 +1,8 @@ +from gpiozero import PWMLED +from signal import pause + +led = PWMLED(17) + +led.pulse() + +pause() diff --git a/docs/examples/led_travis.py b/docs/examples/led_travis.py new file mode 100644 index 0000000..c5b237d --- /dev/null +++ b/docs/examples/led_travis.py @@ -0,0 +1,19 @@ +from travispy import TravisPy +from gpiozero import LED +from gpiozero.tools import negated +from time import sleep +from signal import pause + +def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600): + t = TravisPy() + r = t.repo(repo) + while True: + yield r.last_build_state == 'passed' + sleep(delay) # Sleep an hour before hitting travis again + +red = LED(12) +green = LED(16) + +red.source = negated(green.values) +green.source = build_passed() +pause() diff --git a/docs/examples/led_variable_brightness.py b/docs/examples/led_variable_brightness.py new file mode 100644 index 0000000..4b9c232 --- /dev/null +++ b/docs/examples/led_variable_brightness.py @@ -0,0 +1,12 @@ +from gpiozero import PWMLED +from time import sleep + +led = PWMLED(17) + +while True: + led.value = 0 # off + sleep(1) + led.value = 0.5 # half brightness + sleep(1) + led.value = 1 # full brightness + sleep(1) diff --git a/docs/examples/light_sensor_1.py b/docs/examples/light_sensor_1.py new file mode 100644 index 0000000..48ed945 --- /dev/null +++ b/docs/examples/light_sensor_1.py @@ -0,0 +1,9 @@ +from gpiozero import LightSensor + +sensor = LightSensor(18) + +while True: + sensor.wait_for_light() + print("It's light! :)") + sensor.wait_for_dark() + print("It's dark :(") diff --git a/docs/examples/light_sensor_2.py b/docs/examples/light_sensor_2.py new file mode 100644 index 0000000..b618365 --- /dev/null +++ b/docs/examples/light_sensor_2.py @@ -0,0 +1,10 @@ +from gpiozero import LightSensor, LED +from signal import pause + +sensor = LightSensor(18) +led = LED(16) + +sensor.when_dark = led.on +sensor.when_light = led.off + +pause() diff --git a/docs/examples/light_sensor_3.py b/docs/examples/light_sensor_3.py new file mode 100644 index 0000000..fb22dec --- /dev/null +++ b/docs/examples/light_sensor_3.py @@ -0,0 +1,9 @@ +from gpiozero import LightSensor, PWMLED +from signal import pause + +sensor = LightSensor(18) +led = PWMLED(16) + +led.source = sensor.values + +pause() diff --git a/docs/examples/motion_sensor.py b/docs/examples/motion_sensor.py new file mode 100644 index 0000000..0ff122a --- /dev/null +++ b/docs/examples/motion_sensor.py @@ -0,0 +1,10 @@ +from gpiozero import MotionSensor, LED +from signal import pause + +pir = MotionSensor(4) +led = LED(16) + +pir.when_motion = led.on +pir.when_no_motion = led.off + +pause() diff --git a/docs/examples/motor.py b/docs/examples/motor.py new file mode 100644 index 0000000..a0d6fd3 --- /dev/null +++ b/docs/examples/motor.py @@ -0,0 +1,10 @@ +from gpiozero import Motor +from time import sleep + +motor = Motor(forward=4, backward=14) + +while True: + motor.forward() + sleep(5) + motor.backward() + sleep(5) diff --git a/docs/examples/music_box.py b/docs/examples/music_box.py new file mode 100644 index 0000000..872b08e --- /dev/null +++ b/docs/examples/music_box.py @@ -0,0 +1,18 @@ +from gpiozero import Button +import pygame.mixer +from pygame.mixer import Sound +from signal import pause + +pygame.mixer.init() + +sound_pins = { + 2: Sound("samples/drum_tom_mid_hard.wav"), + 3: Sound("samples/drum_cymbal_open.wav"), +} + +buttons = [Button(pin) for pin in sound_pins] +for button in buttons: + sound = sound_pins[button.pin.number] + button.when_pressed = sound.play + +pause() diff --git a/docs/examples/pot_1.py b/docs/examples/pot_1.py new file mode 100644 index 0000000..1a8e703 --- /dev/null +++ b/docs/examples/pot_1.py @@ -0,0 +1,6 @@ +from gpiozero import MCP3008 + +pot = MCP3008(channel=0) + +while True: + print(pot.value) diff --git a/docs/examples/pot_2.py b/docs/examples/pot_2.py new file mode 100644 index 0000000..b770edf --- /dev/null +++ b/docs/examples/pot_2.py @@ -0,0 +1,7 @@ +from gpiozero import LEDBarGraph, MCP3008 +from signal import pause + +graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) +pot = MCP3008(channel=0) +graph.source = pot.values +pause() diff --git a/docs/examples/reaction_game.py b/docs/examples/reaction_game.py new file mode 100644 index 0000000..f8e3a52 --- /dev/null +++ b/docs/examples/reaction_game.py @@ -0,0 +1,22 @@ +from gpiozero import Button, LED +from time import sleep +import random + +led = LED(17) + +player_1 = Button(2) +player_2 = Button(3) + +time = random.uniform(5, 10) +sleep(time) +led.on() + +while True: + if player_1.is_pressed: + print("Player 1 wins!") + break + if player_2.is_pressed: + print("Player 2 wins!") + break + +led.off() diff --git a/docs/examples/rgbled.py b/docs/examples/rgbled.py new file mode 100644 index 0000000..b80c9dc --- /dev/null +++ b/docs/examples/rgbled.py @@ -0,0 +1,28 @@ +from gpiozero import RGBLED +from time import sleep + +led = RGBLED(red=9, green=10, blue=11) + +led.red = 1 # full red +sleep(1) +led.red = 0.5 # half red +sleep(1) + +led.color = (0, 1, 0) # full green +sleep(1) +led.color = (1, 0, 1) # magenta +sleep(1) +led.color = (1, 1, 0) # yellow +sleep(1) +led.color = (0, 1, 1) # cyan +sleep(1) +led.color = (1, 1, 1) # white +sleep(1) + +led.color = (0, 0, 0) # off +sleep(1) + +# slowly increase intensity of blue +for n in range(100): + led.blue = n/100 + sleep(0.1) diff --git a/docs/examples/rgbled_pot_1.py b/docs/examples/rgbled_pot_1.py new file mode 100644 index 0000000..1efa5d0 --- /dev/null +++ b/docs/examples/rgbled_pot_1.py @@ -0,0 +1,11 @@ +from gpiozero import RGBLED, MCP3008 + +led = RGBLED(red=2, green=3, blue=4) +red_pot = MCP3008(channel=0) +green_pot = MCP3008(channel=1) +blue_pot = MCP3008(channel=2) + +while True: + led.red = red_pot.value + led.green = green_pot.value + led.blue = blue_pot.value diff --git a/docs/examples/rgbled_pot_2.py b/docs/examples/rgbled_pot_2.py new file mode 100644 index 0000000..a5d49cb --- /dev/null +++ b/docs/examples/rgbled_pot_2.py @@ -0,0 +1,11 @@ +from gpiozero import RGBLED, MCP3008 +from signal import pause + +led = RGBLED(2, 3, 4) +red_pot = MCP3008(0) +green_pot = MCP3008(1) +blue_pot = MCP3008(2) + +led.source = zip(red_pot.values, green_pot.values, blue_pot.values) + +pause() diff --git a/docs/examples/robot_1.py b/docs/examples/robot_1.py new file mode 100644 index 0000000..8c0a791 --- /dev/null +++ b/docs/examples/robot_1.py @@ -0,0 +1,10 @@ +from gpiozero import Robot +from time import sleep + +robot = Robot(left=(4, 14), right=(17, 18)) + +for i in range(4): + robot.forward() + sleep(10) + robot.right() + sleep(1) diff --git a/docs/examples/robot_2.py b/docs/examples/robot_2.py new file mode 100644 index 0000000..55107be --- /dev/null +++ b/docs/examples/robot_2.py @@ -0,0 +1,9 @@ +from gpiozero import Robot, DistanceSensor +from signal import pause + +sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) +robot = Robot(left=(4, 14), right=(17, 18)) + +sensor.when_in_range = robot.backward +sensor.when_out_of_range = robot.stop +pause() diff --git a/docs/examples/robot_buttons.py b/docs/examples/robot_buttons.py new file mode 100644 index 0000000..58a285c --- /dev/null +++ b/docs/examples/robot_buttons.py @@ -0,0 +1,23 @@ +from gpiozero import Robot, Button +from signal import pause + +robot = Robot(left=(4, 14), right=(17, 18)) + +left = Button(26) +right = Button(16) +fw = Button(21) +bw = Button(20) + +fw.when_pressed = robot.forward +fw.when_released = robot.stop + +left.when_pressed = robot.left +left.when_released = robot.stop + +right.when_pressed = robot.right +right.when_released = robot.stop + +bw.when_pressed = robot.backward +bw.when_released = robot.stop + +pause() diff --git a/docs/examples/robot_keyboard_1.py b/docs/examples/robot_keyboard_1.py new file mode 100644 index 0000000..366993a --- /dev/null +++ b/docs/examples/robot_keyboard_1.py @@ -0,0 +1,34 @@ +import curses +from gpiozero import Robot + +robot = Robot(left=(4, 14), right=(17, 18)) + +actions = { + curses.KEY_UP: robot.forward, + curses.KEY_DOWN: robot.backward, + curses.KEY_LEFT: robot.left, + curses.KEY_RIGHT: robot.right, + } + +def main(window): + next_key = None + while True: + curses.halfdelay(1) + if next_key is None: + key = window.getch() + else: + key = next_key + next_key = None + if key != -1: + # KEY DOWN + curses.halfdelay(3) + action = actions.get(key) + if action is not None: + action() + next_key = key + while next_key == key: + next_key = window.getch() + # KEY UP + robot.stop() + +curses.wrapper(main) diff --git a/docs/examples/robot_keyboard_2.py b/docs/examples/robot_keyboard_2.py new file mode 100644 index 0000000..e956338 --- /dev/null +++ b/docs/examples/robot_keyboard_2.py @@ -0,0 +1,21 @@ +from gpiozero import Robot +from evdev import InputDevice, list_devices, ecodes + +robot = Robot(left=(4, 14), right=(17, 18)) + +devices = [InputDevice(device) for device in list_devices()] +keyboard = devices[0] # this may vary + +keypress_actions = { + ecodes.KEY_UP: robot.forward, + ecodes.KEY_DOWN: robot.backward, + ecodes.KEY_LEFT: robot.left, + ecodes.KEY_RIGHT: robot.right, +} + +for event in keyboard.read_loop(): + if event.type == ecodes.EV_KEY: + if event.value == 1: # key down + keypress_actions[event.code]() + if event.value == 0: # key up + robot.stop() diff --git a/docs/examples/robot_motion_1.py b/docs/examples/robot_motion_1.py new file mode 100644 index 0000000..11ae2a4 --- /dev/null +++ b/docs/examples/robot_motion_1.py @@ -0,0 +1,10 @@ +from gpiozero import Robot, MotionSensor +from signal import pause + +robot = Robot(left=(4, 14), right=(17, 18)) +pir = MotionSensor(5) + +pir.when_motion = robot.forward +pir.when_no_motion = robot.stop + +pause() diff --git a/docs/examples/robot_motion_2.py b/docs/examples/robot_motion_2.py new file mode 100644 index 0000000..35580cb --- /dev/null +++ b/docs/examples/robot_motion_2.py @@ -0,0 +1,9 @@ +from gpiozero import Robot, MotionSensor +from signal import pause + +robot = Robot(left=(4, 14), right=(17, 18)) +pir = MotionSensor(5) + +robot.source = zip(pir.values, pir.values) + +pause() diff --git a/docs/examples/thermometer.py b/docs/examples/thermometer.py new file mode 100644 index 0000000..d18b6f7 --- /dev/null +++ b/docs/examples/thermometer.py @@ -0,0 +1,12 @@ +from gpiozero import MCP3008 +from time import sleep + +def convert_temp(gen): + for value in gen: + yield (value * 3.3 - 0.5) * 100 + +adc = MCP3008(channel=0) + +for temp in convert_temp(adc.values): + print('The temperature is', temp, 'C') + sleep(1) diff --git a/docs/examples/traffic_lights_1.py b/docs/examples/traffic_lights_1.py new file mode 100644 index 0000000..d102b0b --- /dev/null +++ b/docs/examples/traffic_lights_1.py @@ -0,0 +1,20 @@ +from gpiozero import TrafficLights +from time import sleep + +lights = TrafficLights(2, 3, 4) + +lights.green.on() + +while True: + sleep(10) + lights.green.off() + lights.amber.on() + sleep(1) + lights.amber.off() + lights.red.on() + sleep(10) + lights.amber.on() + sleep(1) + lights.green.on() + lights.amber.off() + lights.red.off() diff --git a/docs/examples/traffic_lights_2.py b/docs/examples/traffic_lights_2.py new file mode 100644 index 0000000..a3f866c --- /dev/null +++ b/docs/examples/traffic_lights_2.py @@ -0,0 +1,20 @@ +from gpiozero import TrafficLights +from time import sleep +from signal import pause + +lights = TrafficLights(2, 3, 4) + +def traffic_light_sequence(): + while True: + yield (0, 0, 1) # green + sleep(10) + yield (0, 1, 0) # amber + sleep(1) + yield (1, 0, 0) # red + sleep(10) + yield (1, 1, 0) # red+amber + sleep(1) + +lights.source = traffic_light_sequence() + +pause() diff --git a/docs/examples/traffic_lights_3.py b/docs/examples/traffic_lights_3.py new file mode 100644 index 0000000..8bf0aa5 --- /dev/null +++ b/docs/examples/traffic_lights_3.py @@ -0,0 +1,24 @@ +from gpiozero import LED +from time import sleep + +red = LED(2) +amber = LED(3) +green = LED(4) + +green.on() +amber.off() +red.off() + +while True: + sleep(10) + green.off() + amber.on() + sleep(1) + amber.off() + red.on() + sleep(10) + amber.on() + sleep(1) + green.on() + amber.off() + red.off() diff --git a/docs/recipes.rst b/docs/recipes.rst index eea1602..1f0e071 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -23,34 +23,19 @@ components: .. image:: images/pin_layout.* + LED === .. image:: images/led.* -Turn an :class:`LED` on and off repeatedly:: +Turn an :class:`LED` on and off repeatedly: - from gpiozero import LED - from time import sleep +.. literalinclude:: examples/led_1.py - red = LED(17) +Alternatively: - while True: - red.on() - sleep(1) - red.off() - sleep(1) - -Alternatively:: - - from gpiozero import LED - from signal import pause - - red = LED(17) - - red.blink() - - pause() +.. literalinclude:: examples/led_2.py .. note:: @@ -62,35 +47,16 @@ Alternatively:: LED with variable brightness ============================ -Any regular LED can have its brightness value set using PWM (pulse-width-modulation). -In GPIO Zero, this can be achieved using :class:`PWMLED` using values between 0 -and 1:: - - from gpiozero import PWMLED - from time import sleep - - led = PWMLED(17) - - while True: - led.value = 0 # off - sleep(1) - led.value = 0.5 # half brightness - sleep(1) - led.value = 1 # full brightness - sleep(1) +Any regular LED can have its brightness value set using PWM +(pulse-width-modulation). In GPIO Zero, this can be achieved using +:class:`PWMLED` using values between 0 and 1: +.. literalinclude:: examples/led_variable_brightness.py Similarly to blinking on and off continuously, a PWMLED can pulse (fade in and -out continuously):: +out continuously): - from gpiozero import PWMLED - from signal import pause - - led = PWMLED(17) - - led.pulse() - - pause() +.. literalinclude:: examples/led_pulse.py Button @@ -98,40 +64,18 @@ Button .. image:: images/button.* -Check if a :class:`Button` is pressed:: +Check if a :class:`Button` is pressed: - from gpiozero import Button +.. literalinclude:: examples/button_1.py - button = Button(2) +Wait for a button to be pressed before continuing: - while True: - if button.is_pressed: - print("Button is pressed") - else: - print("Button is not pressed") +.. literalinclude:: examples/button_2.py -Wait for a button to be pressed before continuing:: +Run a function every time the button is pressed: - from gpiozero import Button - - button = Button(2) - - button.wait_for_press() - print("Button was pressed") - -Run a function every time the button is pressed:: - - from gpiozero import Button - from signal import pause - - def say_hello(): - print("Hello!") - - button = Button(2) - - button.when_pressed = say_hello - - pause() +.. literalinclude:: examples/button_3.py + :emphasize-lines: 9 .. note:: @@ -142,23 +86,9 @@ Run a function every time the button is pressed:: to ``None`` (the return value of this function) which would mean nothing happens when the button is pressed. -Similarly, functions can be attached to button releases:: +Similarly, functions can be attached to button releases: - from gpiozero import Button - from signal import pause - - def say_hello(): - print("Hello!") - - def say_goodbye(): - print("Goodbye!") - - button = Button(2) - - button.when_pressed = say_hello - button.when_released = say_goodbye - - pause() +.. literalinclude:: examples/button_4.py Button controlled LED @@ -166,77 +96,31 @@ Button controlled LED .. image:: images/led_button_bb.* -Turn on an :class:`LED` when a :class:`Button` is pressed:: +Turn on an :class:`LED` when a :class:`Button` is pressed: - from gpiozero import LED, Button - from signal import pause +.. literalinclude:: examples/button_led_1.py - led = LED(17) - button = Button(2) +Alternatively: - button.when_pressed = led.on - button.when_released = led.off - - pause() - -Alternatively:: - - from gpiozero import LED, Button - from signal import pause - - led = LED(17) - button = Button(2) - - led.source = button.values - - pause() +.. literalinclude:: examples/button_led_2.py Button controlled camera ======================== -Using the button press to trigger picamera to take a pitcure using -``button.when_pressed = camera.capture`` would not work because it requires an -``output`` parameter. However, this can be achieved using a custom function -which requires no parameters:: - - from gpiozero import Button - from picamera import PiCamera - from datetime import datetime - from signal import pause +Using the button press to trigger :class:`~picamera.PiCamera` to take a picture +using ``button.when_pressed = camera.capture`` would not work because the +:meth:`~picamera.PiCamera.capture` method requires an ``output`` parameter. +However, this can be achieved using a custom function which requires no +parameters: - button = Button(2) - camera = PiCamera() - - def capture(): - datetime = datetime.now().isoformat() - camera.capture('/home/pi/%s.jpg' % datetime) - - button.when_pressed = capture - - pause() +.. literalinclude:: examples/button_camera_1.py + :emphasize-lines: 9-11 Another example could use one button to start and stop the camera preview, and -another to capture:: +another to capture: - from gpiozero import Button - from picamera import PiCamera - from datetime import datetime - from signal import pause - - left_button = Button(2) - right_button = Button(3) - camera = PiCamera() - - def capture(): - datetime = datetime.now().isoformat() - camera.capture('/home/pi/%s.jpg' % datetime) - - left_button.when_pressed = camera.start_preview - left_button.when_released = camera.stop_preview - right_button.when_pressed = capture - - pause() +.. literalinclude:: examples/button_camera_2.py Shutdown button @@ -244,93 +128,39 @@ Shutdown button The :class:`Button` class also provides the ability to run a function when the button has been held for a given length of time. This example will shut down -the Raspberry Pi when the button is held for 2 seconds:: +the Raspberry Pi when the button is held for 2 seconds: - from gpiozero import Button - from subprocess import check_call - from signal import pause +.. literalinclude:: examples/button_shutdown.py - def shutdown(): - check_call(['sudo', 'poweroff']) - - shutdown_btn = Button(17, hold_time=2) - shutdown_btn.when_held = shutdown - - pause() LEDBoard ======== -A collection of LEDs can be accessed using :class:`LEDBoard`:: +A collection of LEDs can be accessed using :class:`LEDBoard`: - from gpiozero import LEDBoard - from time import sleep - from signal import pause - - leds = LEDBoard(5, 6, 13, 19, 26) - - leds.on() - sleep(1) - leds.off() - sleep(1) - leds.value = (1, 0, 1, 0, 1) - sleep(1) - leds.blink() - - pause() +.. literalinclude:: examples/led_board_1.py Using :class:`LEDBoard` with ``pwm=True`` allows each LED's brightness to be -controlled:: +controlled: - from gpiozero import LEDBoard +.. literalinclude:: examples/led_board_2.py - leds = LEDBoard(5, 6, 13, 19, 26, pwm=True) - - leds.value = (0.2, 0.4, 0.6, 0.8, 1.0) LEDBarGraph =========== A collection of LEDs can be treated like a bar graph using -:class:`LEDBarGraph`:: +:class:`LEDBarGraph`: - from gpiozero import LEDBarGraph - from time import sleep - - graph = LEDBarGraph(5, 6, 13, 19, 26, 20) - - graph.value = 1 # (1, 1, 1, 1, 1, 1) - sleep(1) - graph.value = 1/2 # (1, 1, 1, 0, 0, 0) - sleep(1) - graph.value = -1/2 # (0, 0, 0, 1, 1, 1) - sleep(1) - graph.value = 1/4 # (1, 0, 0, 0, 0, 0) - sleep(1) - graph.value = -1 # (1, 1, 1, 1, 1, 1) - sleep(1) +.. literalinclude:: examples/led_bargraph_2.py Note values are essentially rounded to account for the fact LEDs can only be on or off when ``pwm=False`` (the default). However, using :class:`LEDBarGraph` with ``pwm=True`` allows more precise -values using LED brightness:: +values using LED brightness: - from gpiozero import LEDBarGraph - from time import sleep - - graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) - - graph.value = 1/10 # (0.5, 0, 0, 0, 0) - sleep(1) - graph.value = 3/10 # (1, 0.5, 0, 0, 0) - sleep(1) - graph.value = -3/10 # (0, 0, 0, 0.5, 1) - sleep(1) - graph.value = 9/10 # (1, 1, 1, 1, 0.5) - sleep(1) - graph.value = 95/100 # (1, 1, 1, 1, 0.75) - sleep(1) +.. literalinclude:: examples/led_bargraph_2.py Traffic Lights @@ -340,126 +170,37 @@ Traffic Lights A full traffic lights system. -Using a :class:`TrafficLights` kit like Pi-Stop:: +Using a :class:`TrafficLights` kit like Pi-Stop: - from gpiozero import TrafficLights - from time import sleep +.. literalinclude:: examples/traffic_lights_1.py - lights = TrafficLights(2, 3, 4) +Alternatively: - lights.green.on() +.. literalinclude:: examples/traffic_lights_2.py - while True: - sleep(10) - lights.green.off() - lights.amber.on() - sleep(1) - lights.amber.off() - lights.red.on() - sleep(10) - lights.amber.on() - sleep(1) - lights.green.on() - lights.amber.off() - lights.red.off() +Using :class:`LED` components: -Alternatively:: - - from gpiozero import TrafficLights - from time import sleep - from signal import pause - - lights = TrafficLights(2, 3, 4) - - def traffic_light_sequence(): - while True: - yield (0, 0, 1) # green - sleep(10) - yield (0, 1, 0) # amber - sleep(1) - yield (1, 0, 0) # red - sleep(10) - yield (1, 1, 0) # red+amber - sleep(1) - - lights.source = traffic_light_sequence() - - pause() - -Using :class:`LED` components:: - - from gpiozero import LED - from time import sleep - - red = LED(2) - amber = LED(3) - green = LED(4) - - green.on() - amber.off() - red.off() - - while True: - sleep(10) - green.off() - amber.on() - sleep(1) - amber.off() - red.on() - sleep(10) - amber.on() - sleep(1) - green.on() - amber.off() - red.off() +.. literalinclude:: examples/traffic_lights_3.py Travis build LED indicator ========================== Use LEDs to indicate the status of a Travis build. A green light means the -tests are passing, a red light means the build is broken:: +tests are passing, a red light means the build is broken: - from travispy import TravisPy - from gpiozero import LED - from gpiozero.tools import negated - from time import sleep - from signal import pause +.. literalinclude:: examples/led_travis.py - def build_passed(repo='RPi-Distro/python-gpiozero', delay=3600): - t = TravisPy() - r = t.repo(repo) - while True: - yield r.last_build_state == 'passed' - sleep(delay) # Sleep an hour before hitting travis again +Note this recipe requires `travispy`_. Install with ``sudo pip3 install +travispy``. - red = LED(12) - green = LED(16) - - red.source = negated(green.values) - green.source = build_passed() - pause() - - -Note this recipe requires travispy. Install with ``sudo pip3 install travispy``. Push button stop motion ======================= -Capture a picture with the camera module every time a button is pressed:: +Capture a picture with the camera module every time a button is pressed: - from gpiozero import Button - from picamera import PiCamera - - button = Button(2) - camera = PiCamera() - - camera.start_preview() - frame = 1 - while True: - button.wait_for_press() - camera.capture('/home/pi/frame%03d.jpg' % frame) - frame += 1 +.. literalinclude:: examples/button_stop_motion.py See `Push Button Stop Motion`_ for a full resource. @@ -471,30 +212,7 @@ Reaction Game When you see the light come on, the first person to press their button wins! -:: - - from gpiozero import Button, LED - from time import sleep - import random - - led = LED(17) - - player_1 = Button(2) - player_2 = Button(3) - - time = random.uniform(5, 10) - sleep(time) - led.on() - - while True: - if player_1.is_pressed: - print("Player 1 wins!") - break - if player_2.is_pressed: - print("Player 2 wins!") - break - - led.off() +.. literalinclude:: examples/reaction_game.py See `Quick Reaction Game`_ for a full resource. @@ -504,26 +222,7 @@ GPIO Music Box Each button plays a different sound! -:: - - from gpiozero import Button - import pygame.mixer - from pygame.mixer import Sound - from signal import pause - - pygame.mixer.init() - - sound_pins = { - 2: Sound("samples/drum_tom_mid_hard.wav"), - 3: Sound("samples/drum_cymbal_open.wav"), - } - - buttons = [Button(pin) for pin in sound_pins] - for button in buttons: - sound = sound_pins[button.pin.number] - button.when_pressed = sound.play - - pause() +.. literalinclude:: examples/music_box.py See `GPIO Music Box`_ for a full resource. @@ -533,55 +232,17 @@ All on when pressed While the button is pressed down, the buzzer and all the lights come on. -:class:`FishDish`:: +:class:`FishDish`: - from gpiozero import FishDish - from signal import pause +.. literalinclude:: examples/all_on_1.py - fish = FishDish() +Ryanteck :class:`TrafficHat`: - fish.button.when_pressed = fish.on - fish.button.when_released = fish.off +.. literalinclude:: examples/all_on_2.py - pause() +Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components: -Ryanteck :class:`TrafficHat`:: - - from gpiozero import TrafficHat - from signal import pause - - th = TrafficHat() - - th.button.when_pressed = th.on - th.button.when_released = th.off - - pause() - -Using :class:`LED`, :class:`Buzzer`, and :class:`Button` components:: - - from gpiozero import LED, Buzzer, Button - from signal import pause - - button = Button(2) - buzzer = Buzzer(3) - red = LED(4) - amber = LED(5) - green = LED(6) - - things = [red, amber, green, buzzer] - - def things_on(): - for thing in things: - thing.on() - - def things_off(): - for thing in things: - thing.off() - - button.when_pressed = things_on - button.when_released = things_off - - pause() +.. literalinclude:: examples/all_on_3.py Full color LED @@ -589,36 +250,9 @@ Full color LED .. image:: images/rgb_led_bb.* -Making colours with an :class:`RGBLED`:: +Making colours with an :class:`RGBLED`: - from gpiozero import RGBLED - from time import sleep - - led = RGBLED(red=9, green=10, blue=11) - - led.red = 1 # full red - sleep(1) - led.red = 0.5 # half red - sleep(1) - - led.color = (0, 1, 0) # full green - sleep(1) - led.color = (1, 0, 1) # magenta - sleep(1) - led.color = (1, 1, 0) # yellow - sleep(1) - led.color = (0, 1, 1) # cyan - sleep(1) - led.color = (1, 1, 1) # white - sleep(1) - - led.color = (0, 0, 0) # off - sleep(1) - - # slowly increase intensity of blue - for n in range(100): - led.blue = n/100 - sleep(0.1) +.. literalinclude:: examples/rgbled.py Motion sensor @@ -626,18 +260,9 @@ Motion sensor .. image:: images/motion_sensor_bb.* -Light an :class:`LED` when a :class:`MotionSensor` detects motion:: +Light an :class:`LED` when a :class:`MotionSensor` detects motion: - from gpiozero import MotionSensor, LED - from signal import pause - - pir = MotionSensor(4) - led = LED(16) - - pir.when_motion = led.on - pir.when_no_motion = led.off - - pause() +.. literalinclude:: examples/motion_sensor.py Light sensor @@ -645,43 +270,18 @@ Light sensor .. image:: images/light_sensor_bb.* -Have a :class:`LightSensor` detect light and dark:: +Have a :class:`LightSensor` detect light and dark: - from gpiozero import LightSensor +.. literalinclude:: examples/light_sensor_1.py - sensor = LightSensor(18) +Run a function when the light changes: - while True: - sensor.wait_for_light() - print("It's light! :)") - sensor.wait_for_dark() - print("It's dark :(") - -Run a function when the light changes:: - - from gpiozero import LightSensor, LED - from signal import pause - - sensor = LightSensor(18) - led = LED(16) - - sensor.when_dark = led.on - sensor.when_light = led.off - - pause() +.. literalinclude:: examples/light_sensor_2.py Or make a :class:`PWMLED` change brightness according to the detected light -level:: +level: - from gpiozero import LightSensor, PWMLED - from signal import pause - - sensor = LightSensor(18) - led = PWMLED(16) - - led.source = sensor.values - - pause() +.. literalinclude:: examples/light_sensor_3.py Distance sensor @@ -689,29 +289,13 @@ Distance sensor .. IMAGE TBD -Have a :class:`DistanceSensor` detect the distance to the nearest object:: +Have a :class:`DistanceSensor` detect the distance to the nearest object: - from gpiozero import DistanceSensor - from time import sleep +.. literalinclude:: examples/distance_sensor_1.py - sensor = DistanceSensor(23, 24) +Run a function when something gets near the sensor: - while True: - print('Distance to nearest object is', sensor.distance, 'm') - sleep(1) - -Run a function when something gets near the sensor:: - - from gpiozero import DistanceSensor, LED - from signal import pause - - sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) - led = LED(16) - - sensor.when_in_range = led.on - sensor.when_out_of_range = led.off - - pause() +.. literalinclude:: examples/distance_sensor_2.py Motors @@ -719,18 +303,9 @@ Motors .. image:: images/motor_bb.* -Spin a :class:`Motor` around forwards and backwards:: +Spin a :class:`Motor` around forwards and backwards: - from gpiozero import Motor - from time import sleep - - motor = Motor(forward=4, backward=14) - - while True: - motor.forward() - sleep(5) - motor.backward() - sleep(5) +.. literalinclude:: examples/motor.py Robot @@ -738,163 +313,54 @@ Robot .. IMAGE TBD -Make a :class:`Robot` drive around in (roughly) a square:: +Make a :class:`Robot` drive around in (roughly) a square: - from gpiozero import Robot - from time import sleep - - robot = Robot(left=(4, 14), right=(17, 18)) - - for i in range(4): - robot.forward() - sleep(10) - robot.right() - sleep(1) +.. literalinclude:: examples/robot_1.py Make a robot with a distance sensor that runs away when things get within -20cm of it:: +20cm of it: - from gpiozero import Robot, DistanceSensor - from signal import pause - - sensor = DistanceSensor(23, 24, max_distance=1, threshold_distance=0.2) - robot = Robot(left=(4, 14), right=(17, 18)) - - sensor.when_in_range = robot.backward - sensor.when_out_of_range = robot.stop - pause() +.. literalinclude:: examples/robot_2.py Button controlled robot ======================= -Use four GPIO buttons as forward/back/left/right controls for a robot:: +Use four GPIO buttons as forward/back/left/right controls for a robot: - from gpiozero import Robot, Button - from signal import pause - - robot = Robot(left=(4, 14), right=(17, 18)) - - left = Button(26) - right = Button(16) - fw = Button(21) - bw = Button(20) - - fw.when_pressed = robot.forward - fw.when_released = robot.stop - - left.when_pressed = robot.left - left.when_released = robot.stop - - right.when_pressed = robot.right - right.when_released = robot.stop - - bw.when_pressed = robot.backward - bw.when_released = robot.stop - - pause() +.. literalinclude:: examples/robot_buttons.py Keyboard controlled robot ========================= -Use up/down/left/right keys to control a robot:: +Use up/down/left/right keys to control a robot: - import curses - from gpiozero import Robot - - robot = Robot(left=(4, 14), right=(17, 18)) - - actions = { - curses.KEY_UP: robot.forward, - curses.KEY_DOWN: robot.backward, - curses.KEY_LEFT: robot.left, - curses.KEY_RIGHT: robot.right, - } - - def main(window): - next_key = None - while True: - curses.halfdelay(1) - if next_key is None: - key = window.getch() - else: - key = next_key - next_key = None - if key != -1: - # KEY DOWN - curses.halfdelay(3) - action = actions.get(key) - if action is not None: - action() - next_key = key - while next_key == key: - next_key = window.getch() - # KEY UP - robot.stop() - - curses.wrapper(main) +.. literalinclude:: examples/robot_keyboard_1.py .. note:: - This recipe uses the ``curses`` module. This module requires that Python is - running in a terminal in order to work correctly, hence this recipe will - *not* work in environments like IDLE. + This recipe uses the standard :mod:`curses` module. This module requires + that Python is running in a terminal in order to work correctly, hence this + recipe will *not* work in environments like IDLE. If you prefer a version that works under IDLE, the following recipe should suffice, but will require that you install the evdev library with ``sudo pip3 -install evdev`` first:: +install evdev`` first: - from gpiozero import Robot - from evdev import InputDevice, list_devices, ecodes - - robot = Robot(left=(4, 14), right=(17, 18)) - - devices = [InputDevice(device) for device in list_devices()] - keyboard = devices[0] # this may vary - - keypress_actions = { - ecodes.KEY_UP: robot.forward, - ecodes.KEY_DOWN: robot.backward, - ecodes.KEY_LEFT: robot.left, - ecodes.KEY_RIGHT: robot.right, - } - - for event in keyboard.read_loop(): - if event.type == ecodes.EV_KEY: - if event.value == 1: # key down - keypress_actions[event.code]() - if event.value == 0: # key up - robot.stop() +.. literalinclude:: examples/robot_keyboard_2.py Motion sensor robot =================== -Make a robot drive forward when it detects motion:: +Make a robot drive forward when it detects motion: - from gpiozero import Robot, MotionSensor - from signal import pause +.. literalinclude:: examples/robot_motion_1.py - robot = Robot(left=(4, 14), right=(17, 18)) - pir = MotionSensor(5) +Alternatively: - pir.when_motion = robot.forward - pir.when_no_motion = robot.stop - - pause() - -Alternatively:: - - from gpiozero import Robot, MotionSensor - from signal import pause - - robot = Robot(left=(4, 14), right=(17, 18)) - pir = MotionSensor(5) - - robot.source = zip(pir.values, pir.values) - - pause() +.. literalinclude:: examples/robot_motion_2.py Potentiometer @@ -903,25 +369,14 @@ Potentiometer .. image:: images/potentiometer_bb.* Continually print the value of a potentiometer (values between 0 and 1) -connected to a :class:`MCP3008` analog to digital converter:: +connected to a :class:`MCP3008` analog to digital converter: - from gpiozero import MCP3008 - - pot = MCP3008(channel=0) - - while True: - print(pot.value) +.. literalinclude:: examples/pot_1.py Present the value of a potentiometer on an LED bar graph using PWM to represent -states that won't "fill" an LED:: +states that won't "fill" an LED: - from gpiozero import LEDBarGraph, MCP3008 - from signal import pause - - graph = LEDBarGraph(5, 6, 13, 19, 26, pwm=True) - pot = MCP3008(channel=0) - graph.source = pot.values - pause() +.. literalinclude:: examples/pot_2.py Measure temperature with an ADC @@ -930,54 +385,24 @@ Measure temperature with an ADC .. IMAGE TBD Wire a TMP36 temperature sensor to the first channel of an :class:`MCP3008` -analog to digital converter:: +analog to digital converter: - from gpiozero import MCP3008 - from time import sleep - - def convert_temp(gen): - for value in gen: - yield (value * 3.3 - 0.5) * 100 - - adc = MCP3008(channel=0) - - for temp in convert_temp(adc.values): - print('The temperature is', temp, 'C') - sleep(1) +.. literalinclude:: examples/thermometer.py Full color LED controlled by 3 potentiometers ============================================= Wire up three potentiometers (for red, green and blue) and use each of their -values to make up the colour of the LED:: +values to make up the colour of the LED: - from gpiozero import RGBLED, MCP3008 - - led = RGBLED(red=2, green=3, blue=4) - red_pot = MCP3008(channel=0) - green_pot = MCP3008(channel=1) - blue_pot = MCP3008(channel=2) - - while True: - led.red = red_pot.value - led.green = green_pot.value - led.blue = blue_pot.value +.. literalinclude:: examples/rgbled_pot_1.py Alternatively, the following example is identical, but uses the -:attr:`~SourceMixin.source` property rather than a :keyword:`while` loop:: +:attr:`~SourceMixin.source` property rather than a :keyword:`while` loop: - from gpiozero import RGBLED, MCP3008 - from signal import pause - - led = RGBLED(2, 3, 4) - red_pot = MCP3008(0) - green_pot = MCP3008(1) - blue_pot = MCP3008(2) - - led.source = zip(red_pot.values, green_pot.values, blue_pot.values) - - pause() +.. literalinclude:: examples/rgbled_pot_2.py + :emphasize-lines: 8 Please note the example above requires Python 3. In Python 2, :func:`zip` doesn't support lazy evaluation so the script will simply hang. @@ -997,17 +422,9 @@ be done from the terminal with the following commands:: $ echo none | sudo tee /sys/class/leds/led0/trigger $ echo gpio | sudo tee /sys/class/leds/led1/trigger -Now you can control the LEDs with gpiozero like so:: +Now you can control the LEDs with gpiozero like so: - from gpiozero import LED - from signal import pause - - power = LED(35) # /sys/class/leds/led1 - activity = LED(47) # /sys/class/leds/led0 - - activity.blink() - power.blink() - pause() +.. literalinclude:: examples/led_builtin.py To revert the LEDs to their usual purpose you can either reboot your Pi or run the following commands:: @@ -1030,6 +447,7 @@ run the following commands:: accessible from gpiozero (yet). +.. _travispy: https://travispy.readthedocs.io/ .. _Push Button Stop Motion: https://www.raspberrypi.org/learning/quick-reaction-game/ .. _Quick Reaction Game: https://www.raspberrypi.org/learning/quick-reaction-game/ .. _GPIO Music Box: https://www.raspberrypi.org/learning/gpio-music-box/ From 07c95598d18e7eacf128b3276bd4d2c8ec87b966 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 29 Aug 2016 22:18:04 +0100 Subject: [PATCH 097/104] Fix #289 Tweak the second keyboard robot recipe so it detects keyboards and doesn't throw exceptions on unknown keys. Also add a note to the recipe that it won't work over remote connections like SSH. --- docs/examples/robot_keyboard_2.py | 19 +++++++++++++++++-- docs/recipes.rst | 9 +++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/examples/robot_keyboard_2.py b/docs/examples/robot_keyboard_2.py index e956338..2150f86 100644 --- a/docs/examples/robot_keyboard_2.py +++ b/docs/examples/robot_keyboard_2.py @@ -3,8 +3,23 @@ from evdev import InputDevice, list_devices, ecodes robot = Robot(left=(4, 14), right=(17, 18)) +# Get the list of available input devices devices = [InputDevice(device) for device in list_devices()] -keyboard = devices[0] # this may vary +# Filter out everything that's not a keyboard. Keyboards are defined as any +# device which has keys, and which specifically has keys 1..31 (roughly Esc, +# the numeric keys, the first row of QWERTY plus a few more) and which does +# *not* have key 0 (reserved) +must_have = {i for i in range(1, 32)} +must_not_have = {0} +devices = [ + device + for device in devices + for keys in (set(device.capabilities().get(ecodes.EV_KEY, [])),) + if must_have.issubset(keys) + and must_not_have.isdisjoint(keys) +] +# Pick the first keyboard +keyboard = devices[0] keypress_actions = { ecodes.KEY_UP: robot.forward, @@ -14,7 +29,7 @@ keypress_actions = { } for event in keyboard.read_loop(): - if event.type == ecodes.EV_KEY: + if event.type == ecodes.EV_KEY and event.code in keypress_actions: if event.value == 1: # key down keypress_actions[event.code]() if event.value == 0: # key up diff --git a/docs/recipes.rst b/docs/recipes.rst index 1f0e071..ccc7e51 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -345,11 +345,16 @@ Use up/down/left/right keys to control a robot: recipe will *not* work in environments like IDLE. If you prefer a version that works under IDLE, the following recipe should -suffice, but will require that you install the evdev library with ``sudo pip3 -install evdev`` first: +suffice: .. literalinclude:: examples/robot_keyboard_2.py +.. note:: + + This recipe uses the third-party ``evdev`` module. Install this library + with ``sudo pip3 install evdev`` first. Be aware that ``evdev`` will only + work with local input devices; this recipe will *not* work over SSH. + Motion sensor robot =================== From 38541889cabb11685878cb2779c2ca99611a7531 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 29 Aug 2016 22:44:27 +0100 Subject: [PATCH 098/104] Fix #295 At least, I *think* this should fix this. It does locally, but we'll have to see what happens when I push it to RTD! --- README.rst | 20 +++++++++++--------- docs/conf.py | 7 ++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 028e3cf..c943958 100644 --- a/README.rst +++ b/README.rst @@ -2,17 +2,19 @@ gpiozero ======== -.. image:: https://badge.fury.io/py/gpiozero.svg - :target: https://badge.fury.io/py/gpiozero - :alt: Latest Version +.. ifconfig:: html_theme == 'sphinx_rtd_theme' -.. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master - :target: https://travis-ci.org/RPi-Distro/python-gpiozero - :alt: Build Tests + .. image:: https://badge.fury.io/py/gpiozero.svg + :target: https://badge.fury.io/py/gpiozero + :alt: Latest Version -.. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 - :target: https://codecov.io/github/RPi-Distro/python-gpiozero - :alt: Code Coverage + .. image:: https://travis-ci.org/RPi-Distro/python-gpiozero.svg?branch=master + :target: https://travis-ci.org/RPi-Distro/python-gpiozero + :alt: Build Tests + + .. image:: https://img.shields.io/codecov/c/github/RPi-Distro/python-gpiozero/master.svg?maxAge=2592000 + :target: https://codecov.io/github/RPi-Distro/python-gpiozero + :alt: Code Coverage A simple interface to everyday GPIO components used with Raspberry Pi. diff --git a/docs/conf.py b/docs/conf.py index 6fb6ea2..d73e36e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,12 @@ sys.modules['spidev'] = Mock() # -- General configuration ------------------------------------------------ -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] +extensions = [ + 'sphinx.ext.autodoc', # support for automethod, autoclass, etc. + 'sphinx.ext.viewcode', # support for "Source" links in output + 'sphinx.ext.intersphinx', # support links to Python library docs etc. + 'sphinx.ext.ifconfig', # support for ifconfig conditional includes + ] templates_path = ['_templates'] source_suffix = '.rst' #source_encoding = 'utf-8-sig' From 04075380e1e40e8579a3bc7cbc18e7c4ef0171fe Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 29 Aug 2016 23:40:53 +0100 Subject: [PATCH 099/104] Fix #294 Still need to sort out threads for the internal devices to continually poll their values so that the wait-states work properly --- docs/api_other.rst | 5 +++ gpiozero/__init__.py | 1 + gpiozero/other_devices.py | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/docs/api_other.rst b/docs/api_other.rst index c83c3e4..a8efaa4 100644 --- a/docs/api_other.rst +++ b/docs/api_other.rst @@ -25,6 +25,11 @@ PingServer .. autoclass:: PingServer +CPUTemperature +============== + +.. autoclass:: CPUTemperature + Base Classes ============ diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index 6a6dcb1..b756bf1 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -124,5 +124,6 @@ from .boards import ( from .other_devices import ( InternalDevice, PingServer, + CPUTemperature, TimeOfDay, ) diff --git a/gpiozero/other_devices.py b/gpiozero/other_devices.py index 47ca6ae..ccc77b5 100644 --- a/gpiozero/other_devices.py +++ b/gpiozero/other_devices.py @@ -73,6 +73,81 @@ class PingServer(InternalDevice): return True +class CPUTemperature(InternalDevice): + """ + Extends :class:`InternalDevice` to provide a device which is active when + the CPU temperature exceeds the *threshold* value. + + The following example plots the CPU's temperature on an LED bar graph:: + + from gpiozero import LEDBarGraph, CPUTemperature + from signal import pause + + # Use minimums and maximums that are closer to "normal" usage so the + # bar graph is a bit more "lively" + temp = CPUTemperature(min_temp=50, max_temp=90) + graph = LEDBarGraph(5, 6, 13, 19, 25, pwm=True) + graph.source = temp.values + pause() + + :param str sensor_file: + The file from which to read the temperature. This defaults to the + sysfs file :file:`/sys/class/thermal/thermal_zone0/temp`. Whatever + file is specified is expected to contain a single line containing the + temperature in milli-degrees celsius. + + :param float min_temp: + The temperature at which :attr:`value` will read 0.0. This defaults to + 0.0. + + :param float max_temp: + The temperature at which :attr:`value` will read 1.0. This defaults to + 100.0. + + :param float threshold: + The temperature above which the device will be considered "active". + This defaults to 80.0. + """ + def __init__(self, sensor_file='/sys/class/thermal/thermal_zone0/temp', + min_temp=0.0, max_temp=100.0, threshold=80.0): + self.sensor_file = sensor_file + super(CPUTemperature, self).__init__() + self.min_temp = min_temp + self.max_temp = max_temp + self.threshold = threshold + self._fire_events() + + def __repr__(self): + return '' % self.temperature + + @property + def temperature(self): + """ + Returns the current CPU temperature in degrees celsius. + """ + with io.open(self.sensor_file, 'r') as f: + return float(f.readline().strip()) / 1000 + + @property + def value(self): + """ + Returns the current CPU temperature as a value between 0.0 + (representing the *min_temp* value) and 1.0 (representing the + *max_temp* value). These default to 0.0 and 100.0 respectively, hence + :attr:`value` is :attr:`temperature` divided by 100 by default. + """ + temp_range = self.max_temp - self.min_temp + return (self.temperature - self.min_temp) / temp_range + + @property + def is_active(self): + """ + Returns ``True`` when the CPU :attr:`temperature` exceeds the + :attr:`threshold`. + """ + return self.temperature > self.threshold + + class TimeOfDay(InternalDevice): """ Extends :class:`InternalDevice` to provide a device which is active when From 6f67a973cf2124b06bc37db383206ec754ba8ebe Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Tue, 30 Aug 2016 15:16:15 +0100 Subject: [PATCH 100/104] Fix all the stuff you broke last night... In particular the `pi_revision` thing in PiGPIOPin, all the stuff @lurch picked up in `pins/data.py` (thank goodness *someone's* watching!), and make all those links pointing to "Notes" point somewhere useful like "Pin Numbering"... --- docs/examples/robot_keyboard_2.py | 6 +++--- docs/recipes.rst | 7 +++++-- gpiozero/input_devices.py | 24 ++++++++++++------------ gpiozero/output_devices.py | 15 ++++++++------- gpiozero/pins/data.py | 6 ++++-- gpiozero/pins/pigpiod.py | 19 +++++++++++++------ 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/docs/examples/robot_keyboard_2.py b/docs/examples/robot_keyboard_2.py index 2150f86..abd7e50 100644 --- a/docs/examples/robot_keyboard_2.py +++ b/docs/examples/robot_keyboard_2.py @@ -12,9 +12,9 @@ devices = [InputDevice(device) for device in list_devices()] must_have = {i for i in range(1, 32)} must_not_have = {0} devices = [ - device - for device in devices - for keys in (set(device.capabilities().get(ecodes.EV_KEY, [])),) + dev + for dev in devices + for keys in (set(dev.capabilities().get(ecodes.EV_KEY, [])),) if must_have.issubset(keys) and must_not_have.isdisjoint(keys) ] diff --git a/docs/recipes.rst b/docs/recipes.rst index ccc7e51..6ca48d7 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -9,6 +9,8 @@ library. Please note that all recipes are written assuming Python 3. Recipes *may* work under Python 2, but no guarantees! +.. _pin_numbering: + Pin Numbering ============= @@ -18,8 +20,9 @@ configurable. .. _RPi.GPIO: https://pypi.python.org/pypi/RPi.GPIO -Any pin marked ``GPIO`` in the diagram below can be used for generic -components: +Any pin marked "GPIO" in the diagram below can be used as a pin number. For +example, if an LED was attached to "GPIO17" you would specify the pin number as +17 rather than 11: .. image:: images/pin_layout.* diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index eebe4ba..0076f3d 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -240,8 +240,8 @@ class Button(HoldMixin, DigitalInputDevice): print("The button was pressed!") :param int pin: - The GPIO pin which the button is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the button is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param bool pull_up: If ``True`` (the default), the GPIO pin will be pulled high by default. @@ -302,8 +302,8 @@ class LineSensor(SmoothedInputDevice): pause() :param int pin: - The GPIO pin which the sensor is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This @@ -371,8 +371,8 @@ class MotionSensor(SmoothedInputDevice): print("Motion detected!") :param int pin: - The GPIO pin which the sensor is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. This @@ -435,8 +435,8 @@ class LightSensor(SmoothedInputDevice): print("Light detected!") :param int pin: - The GPIO pin which the sensor is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the sensor is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the circuit. @@ -542,12 +542,12 @@ class DistanceSensor(SmoothedInputDevice): sleep(1) :param int echo: - The GPIO pin which the ECHO pin is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the ECHO pin is attached to. See + :ref:`pin_numbering` for valid pin numbers. :param int trigger: - The GPIO pin which the TRIG pin is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the TRIG pin is attached to. See + :ref:`pin_numbering` for valid pin numbers. :param int queue_len: The length of the queue used to store values read from the sensor. diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index ae62347..62bc911 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -217,8 +217,8 @@ class LED(DigitalOutputDevice): led.on() :param int pin: - The GPIO pin which the LED is attached to. See :doc:`notes` for valid - pin numbers. + The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for + valid pin numbers. :param bool active_high: If ``True`` (the default), the LED will operate normally with the @@ -252,8 +252,8 @@ class Buzzer(DigitalOutputDevice): bz.on() :param int pin: - The GPIO pin which the buzzer is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the buzzer is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param bool active_high: If ``True`` (the default), the buzzer will operate normally with the @@ -276,8 +276,8 @@ class PWMOutputDevice(OutputDevice): Generic output device configured for pulse-width modulation (PWM). :param int pin: - The GPIO pin which the device is attached to. See :doc:`notes` for - valid pin numbers. + The GPIO pin which the device is attached to. See :ref:`pin_numbering` + for valid pin numbers. :param bool active_high: If ``True`` (the default), the :meth:`on` method will set the GPIO to @@ -483,7 +483,7 @@ class PWMLED(PWMOutputDevice): an optional resistor to prevent the LED from burning out. :param int pin: - The GPIO pin which the LED is attached to. See :doc:`notes` for + The GPIO pin which the LED is attached to. See :ref:`pin_numbering` for valid pin numbers. :param bool active_high: @@ -897,3 +897,4 @@ class Motor(SourceMixin, CompositeDevice): """ self.forward_device.off() self.backward_device.off() + diff --git a/gpiozero/pins/data.py b/gpiozero/pins/data.py index 6490042..2d39fe1 100644 --- a/gpiozero/pins/data.py +++ b/gpiozero/pins/data.py @@ -573,10 +573,12 @@ def _parse_pi_revision(revision): '3B': True, }.get(model, False) csi = { - 'Zero': 0 if pcb_revision == '0.0' else 1, + 'Zero': 0 if pcb_revision == '1.0' else 1, 'CM': 2, }.get(model, 1) - dsi = csi + dsi = { + 'Zero': 0, + }.get(model, csi) headers = { 'A': {'P1': REV2_P1, 'P5': REV2_P5}, 'B': {'P1': REV2_P1, 'P5': REV2_P5} if pcb_revision == '2.0' else {'P1': REV1_P1}, diff --git a/gpiozero/pins/pigpiod.py b/gpiozero/pins/pigpiod.py index f3ad7d5..537eb48 100644 --- a/gpiozero/pins/pigpiod.py +++ b/gpiozero/pins/pigpiod.py @@ -106,7 +106,7 @@ class PiGPIOPin(Pin): return cls._PINS[(host, port, number)] except KeyError: self = super(PiGPIOPin, cls).__new__(cls) - cls.pi_revision(host, port) # implicitly creates connection + cls.pi_info(host, port) # implicitly creates connection self._connection, self._pi_info = cls._CONNECTIONS[(host, port)] try: self._pi_info.physical_pin('GPIO%d' % number) @@ -129,7 +129,6 @@ class PiGPIOPin(Pin): raise ValueError(e) self._connection.set_pull_up_down(self._number, self.GPIO_PULL_UPS[self._pull]) self._connection.set_glitch_filter(self._number, 0) - self._connection.set_PWM_range(self._number, 255) cls._PINS[(host, port, number)] = self return self @@ -175,14 +174,19 @@ class PiGPIOPin(Pin): def _get_state(self): if self._pwm: - return self._connection.get_PWM_dutycycle(self._number) / 255 + return ( + self._connection.get_PWM_dutycycle(self._number) / + self._connection.get_PWM_range(self._number) + ) else: return bool(self._connection.read(self._number)) def _set_state(self, value): if self._pwm: try: - self._connection.set_PWM_dutycycle(self._number, int(value * 255)) + value = int(value * self._connection.get_PWM_range(self._number)) + if value != self._connection.get_PWM_dutycycle(self._number): + self._connection.set_PWM_dutycycle(self._number, value) except pigpio.error: raise PinInvalidState('invalid state "%s" for pin %r' % (value, self)) elif self.function == 'input': @@ -213,12 +217,15 @@ class PiGPIOPin(Pin): def _set_frequency(self, value): if not self._pwm and value is not None: self._connection.set_PWM_frequency(self._number, value) + self._connection.set_PWM_range(self._number, 10000) self._connection.set_PWM_dutycycle(self._number, 0) self._pwm = True elif self._pwm and value is not None: - self._connection.set_PWM_frequency(self._number, value) + if value != self._connection.get_PWM_frequency(self._number): + self._connection.set_PWM_frequency(self._number, value) + self._connection.set_PWM_range(self._number, 10000) elif self._pwm and value is None: - self._connection.set_PWM_dutycycle(self._number, 0) + self._connection.write(self._number, 0) self._pwm = False def _get_bounce(self): From 02f7d20bc3a8519854ec007e077087768d97b943 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Wed, 15 Jun 2016 23:34:50 +0100 Subject: [PATCH 101/104] Fix #248 Add Servo and AngularServo implementation along with docs and tests. This is a deliberately minimal implementation designed to be added to as we agree on new extensions (better than making an all-singing, all-dancing version in which I get things wrong and then wind up making backward incompatible changes to get it right :) --- docs/api_output.rst | 14 + docs/images/composite_device_hierarchy.dot | 2 + docs/images/composite_device_hierarchy.pdf | Bin 14029 -> 14649 bytes docs/images/composite_device_hierarchy.png | Bin 47420 -> 53677 bytes docs/images/composite_device_hierarchy.svg | 78 ++++-- gpiozero/__init__.py | 2 + gpiozero/output_devices.py | 310 +++++++++++++++++++++ tests/test_outputs.py | 112 ++++++++ 8 files changed, 489 insertions(+), 29 deletions(-) diff --git a/docs/api_output.rst b/docs/api_output.rst index b6710c2..7bd888a 100644 --- a/docs/api_output.rst +++ b/docs/api_output.rst @@ -43,6 +43,20 @@ Motor .. autoclass:: Motor(forward, backward, pwm=True) :members: forward, backward, stop +Servo +===== + +.. autoclass:: Servo(pin, initial_value=0, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000) + :inherited-members: + :members: + +AngularServo +============ + +.. autoclass:: AngularServo(pin, initial_angle=0, min_angle=-90, max_angle=90, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000) + :inherited-members: + :members: + Base Classes ============ diff --git a/docs/images/composite_device_hierarchy.dot b/docs/images/composite_device_hierarchy.dot index d57cc2d..5c28394 100644 --- a/docs/images/composite_device_hierarchy.dot +++ b/docs/images/composite_device_hierarchy.dot @@ -32,5 +32,7 @@ digraph classes { RyanteckRobot->Robot; CamJamKitRobot->Robot; Motor->CompositeDevice; + Servo->CompositeDevice; + AngularServo->Servo; Energenie->Device; } diff --git a/docs/images/composite_device_hierarchy.pdf b/docs/images/composite_device_hierarchy.pdf index 6579003cdab8ba889bd2c7a74589f9aeb84b5108..500833ed6786c815bbade6e55db5eef6ce99d4f4 100644 GIT binary patch delta 12039 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 a-y3tWgCK4^3MgDaE)HY>KvGEx`M&^+n(2!G delta 11421 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 diff --git a/docs/images/composite_device_hierarchy.png b/docs/images/composite_device_hierarchy.png index ef7d22692365f276660422c95789c07cd8b3a105..96623f40c3517056e53aafc9940142e2de759d0e 100644 GIT binary patch literal 53677 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 %+jbuXo98Yya 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�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%0DFz;-FG&S}yBy zjd&lzxtSA1NJZ*oc0@2A*PY5RJvC*aSn{zx<<3%!P#0#X&)-LV9@=tbYMa%NZ$6oJ^WXpdKnL~xIo3flh?e3g!ZH0)!N*QD z1-S}G|0HtN4RP;dg-QwwxfvU9+N?yZ0AbfJLq2%9YrXj*-W00O*oocTtXAX1z#gl7 z6KD^W{zgY0ua7>I)8G>prnpH@LxWc^Nc8HFxH57x@16>5S`PocF+0oQgLl8rp#uQJ z{A>UGUnf?Fp*Cp$XNoniA~g{{)V8@pD*)HXxsnaQ@TJA={gkXM(g=*l%nm^1IyySn zUfrOoNciV+lBMoC0l5X+7qcsN_k;GJ+!)utuBQvDlWkh>!o*E|wRkmH0omD3jR9H& z?uszEMBcVn<1}oy4rEDR#sQ5y%s+>ro-MTwc%Bdg#Qjz{QucmVhYSr=2H)! z(O;OoMYkq)ji>Lv0ef_DD<1&zPbK9}883eSSLmmU`tRbxvK~o`gO2$asBaN<;ucek zUalj}Q@YQ=e+Ge#Iads&hYQmwiQkrnaMNuy1CpIT6HOe2bUh#Le;LmN+Wh4~A5$7m z(Zwp~$t<=Xl@|*-t#D8Xwtr}XngK}L)*Ayo z4&Gh-ctJxI0ENcc@xQD z-lSB|<~ZgIXyh-u*5H9Y^o+XI?yBK+SeSQ!3=L2a6bd#@2t_6QvvJ~Mpu6w{iyJ(1 z{2iRwmYQmqi*QcDyHq(g_kHoj;5jU_sk ze&)YlX$Mt4U^|73$XQA=HLX1Zo*P`vOO2!AqOmorCx=Bo?PwjR=;bI#3_)-XzvRD= z8Q@~8d}6Kr1QJRH5KzPGa~%n2K=zK5qKt4*h+^)GFXk3EA>)!yjIk*cx1`UvQ-B+Fzb90ZGP=}sbLLZ=Zmy!B;;EtNVkJ)M{QUQE(~G>) zQx;94e%$1Sr`rEm7Jr-Tsynv{0$$h$()D!;zp(oQf*_($b=st{*C>NXQ3g@mHeXg{ z^=+z@N*`GbPDcU^2huimfCg8SBy7fWhO7hGk=V+Q#WW_vV+mjh1m8$s5UWOi4!T;& z{(ne^v9d#4@|^PY@#_MepenE4QsaV0^$T(>jBPFp0SfRRLA`liw~u;9GTE4BgR3WRjL=ituCKnMIRg45Y}yN0z%F*#MyWXR1YL;R_yQ243B^ z1$@O!kq^gHlOG>fsW_EWmqWQ`I{5vL&bz@mc4yrvQo%V9pmI1@dKsGzUc#UwN!n^* zDNh;@2?jZroB|KS$&?2P2#2{jaoku^@gWf~yS!=C%U#vrU%&$qapdE{=Dw1874O&H zPY=Ns!IGYb&z_ujtAqqQ=GU0p%fHjDpA}5ES2G6VmOBip7avgxWH5%lsXw8s+nrdt zexa!bRqjgxx=Aj1-7v{$LRe-@-w;Kjtsj&Mgh;fN$nO8l1e}`ni^Sx#Np19Wdys4)7@I#0PGMd=5vAf$z^*L@kg8^XkkWX&CSF9;SKUdBF z_NkPR_(aj)hZ0lIe^$)%|1d-Q3D529=bNh}&;9)Q&zyG!(d^8BUZ8y{Dkm{v|BDYc zSq%z>L8yLDB{~dgRh&>q8bh^nm&FNr0IAX9{ADcF)Tr7J0vCDXg#BCN&Ak*`Di;)d NT~*grt5C5D{~ypK#&iGx diff --git a/docs/images/composite_device_hierarchy.svg b/docs/images/composite_device_hierarchy.svg index 441ce3f..c66e1a8 100644 --- a/docs/images/composite_device_hierarchy.svg +++ b/docs/images/composite_device_hierarchy.svg @@ -4,25 +4,25 @@ - + classes - + Device - -Device + +Device CompositeDevice - -CompositeDevice + +CompositeDevice CompositeDevice->Device - - + + CompositeOutputDevice @@ -31,8 +31,8 @@ CompositeOutputDevice->CompositeDevice - - + + LEDCollection @@ -136,13 +136,13 @@ Robot - -Robot + +Robot Robot->CompositeDevice - - + + RyanteckRobot @@ -151,8 +151,8 @@ RyanteckRobot->Robot - - + + CamJamKitRobot @@ -161,28 +161,48 @@ CamJamKitRobot->Robot - - + + Motor - -Motor + +Motor Motor->CompositeDevice - - + + + + +Servo + +Servo + + +Servo->CompositeDevice + + + + +AngularServo + +AngularServo + + +AngularServo->Servo + + -Energenie - -Energenie +Energenie + +Energenie -Energenie->Device - - +Energenie->Device + + diff --git a/gpiozero/__init__.py b/gpiozero/__init__.py index b756bf1..a89e973 100644 --- a/gpiozero/__init__.py +++ b/gpiozero/__init__.py @@ -100,6 +100,8 @@ from .output_devices import ( LED, Buzzer, Motor, + Servo, + AngularServo, RGBLED, ) from .boards import ( diff --git a/gpiozero/output_devices.py b/gpiozero/output_devices.py index 62bc911..1b3c2e2 100644 --- a/gpiozero/output_devices.py +++ b/gpiozero/output_devices.py @@ -898,3 +898,313 @@ class Motor(SourceMixin, CompositeDevice): self.forward_device.off() self.backward_device.off() + +class Servo(SourceMixin, CompositeDevice): + """ + Extends :class:`CompositeDevice` and represents a PWM-controlled servo + motor connected to a GPIO pin. + + Connect a power source (e.g. a battery pack or the 5V pin) to the power + cable of the servo (this is typically colored red); connect the ground + cable of the servo (typically colored black or brown) to the negative of + your battery pack, or a GND pin; connect the final cable (typically colored + white or orange) to the GPIO pin you wish to use for controlling the servo. + + The following code will make the servo move between its minimum, maximum, + and mid-point positions with a pause between each:: + + from gpiozero import Servo + from time import sleep + + servo = Servo(17) + while True: + servo.min() + sleep(1) + servo.mid() + sleep(1) + servo.max() + sleep(1) + + :param int pin: + The GPIO pin which the device is attached to. See :ref:`pin_numbering` + for valid pin numbers. + + :param float initial_value: + If ``0`` (the default), the device's mid-point will be set + initially. Other values between -1 and +1 can be specified as an + initial position. ``None`` means to start the servo un-controlled (see + :attr:`value`). + + :param float min_pulse_width: + The pulse width corresponding to the servo's minimum position. This + defaults to 1ms. + + :param float max_pulse_width: + The pulse width corresponding to the servo's maximum position. This + defaults to 2ms. + + :param float frame_width: + The length of time between servo control pulses measured in seconds. + This defaults to 20ms which is a common value for servos. + """ + def __init__( + self, pin=None, initial_value=0.0, + min_pulse_width=1/1000, max_pulse_width=2/1000, + frame_width=20/1000): + if min_pulse_width >= max_pulse_width: + raise ValueError('min_pulse_width must be less than max_pulse_width') + if max_pulse_width >= frame_width: + raise ValueError('max_pulse_width must be less than frame_width') + self._frame_width = frame_width + self._min_dc = min_pulse_width / frame_width + self._dc_range = (max_pulse_width - min_pulse_width) / frame_width + self._min_value = -1 + self._value_range = 2 + super(Servo, self).__init__( + pwm_device=PWMOutputDevice(pin, frequency=int(1 / frame_width))) + try: + self.value = initial_value + except: + self.close() + raise + + @property + def frame_width(self): + """ + The time between control pulses, measured in seconds. + """ + return self._frame_width + + @property + def min_pulse_width(self): + """ + The control pulse width corresponding to the servo's minimum position, + measured in seconds. + """ + return self._min_dc * self.frame_width + + @property + def max_pulse_width(self): + """ + The control pulse width corresponding to the servo's maximum position, + measured in seconds. + """ + return (self._dc_range * self.frame_width) + self.min_pulse_width + + @property + def pulse_width(self): + """ + Returns the current pulse width controlling the servo. + """ + if self.pwm_device.pin.frequency is None: + return None + else: + return self.pwm_device.pin.state * self.frame_width + + def min(self): + """ + Set the servo to its minimum position. + """ + self.value = -1 + + def mid(self): + """ + Set the servo to its mid-point position. + """ + self.value = 0 + + def max(self): + """ + Set the servo to its maximum position. + """ + self.value = 1 + + def detach(self): + """ + Temporarily disable control of the servo. This is equivalent to + setting :attr:`value` to ``None``. + """ + self.value = None + + def _get_value(self): + if self.pwm_device.pin.frequency is None: + return None + else: + return ( + ((self.pwm_device.pin.state - self._min_dc) / self._dc_range) * + self._value_range + self._min_value) + + @property + def value(self): + """ + Represents the position of the servo as a value between -1 (the minimum + position) and +1 (the maximum position). This can also be the special + value ``None`` indicating that the servo is currently "uncontrolled", + i.e. that no control signal is being sent. Typically this means the + servo's position remains unchanged, but that it can be moved by hand. + """ + result = self._get_value() + if result is None: + return result + else: + # NOTE: This round() only exists to ensure we don't confuse people + # by returning 2.220446049250313e-16 as the default initial value + # instead of 0. The reason _get_value and _set_value are split + # out is for descendents that require the un-rounded values for + # accuracy + return round(result, 14) + + @value.setter + def value(self, value): + if value is None: + self.pwm_device.pin.frequency = None + elif -1 <= value <= 1: + self.pwm_device.pin.frequency = int(1 / self.frame_width) + self.pwm_device.pin.state = ( + self._min_dc + self._dc_range * + ((value - self._min_value) / self._value_range) + ) + else: + raise OutputDeviceBadValue( + "Servo value must be between -1 and 1, or None") + + @property + def is_active(self): + return self.value is not None + + +class AngularServo(Servo): + """ + Extends :class:`Servo` and represents a rotational PWM-controlled servo + motor which can be set to particular angles (assuming valid minimum and + maximum angles are provided to the constructor). + + Connect a power source (e.g. a battery pack or the 5V pin) to the power + cable of the servo (this is typically colored red); connect the ground + cable of the servo (typically colored black or brown) to the negative of + your battery pack, or a GND pin; connect the final cable (typically colored + white or orange) to the GPIO pin you wish to use for controlling the servo. + + Next, calibrate the angles that the servo can rotate to. In an interactive + Python session, construct a :class:`Servo` instance. The servo should move + to its mid-point by default. Set the servo to its minimum value, and + measure the angle from the mid-point. Set the servo to its maximum value, + and again measure the angle:: + + >>> from gpiozero import Servo + >>> s = Servo(17) + >>> s.min() # measure the angle + >>> s.max() # measure the angle + + You should now be able to construct an :class:`AngularServo` instance + with the correct bounds:: + + >>> from gpiozero import AngularServo + >>> s = AngularServo(17, min_angle=-42, max_angle=44) + >>> s.angle = 0.0 + >>> s.angle + 0.0 + >>> s.angle = 15 + >>> s.angle + 15.0 + + .. note:: + + You can set *min_angle* greater than *max_angle* if you wish to reverse + the sense of the angles (e.g. ``min_angle=45, max_angle=-45``). This + can be useful with servos that rotate in the opposite direction to your + expectations of minimum and maximum. + + :param int pin: + The GPIO pin which the device is attached to. See :ref:`pin_numbering` + for valid pin numbers. + + :param float initial_angle: + Sets the servo's initial angle to the specified value. The default is + 0. The value specified must be between *min_angle* and *max_angle* + inclusive. ``None`` means to start the servo un-controlled (see + :attr:`value`). + + :param float min_angle: + Sets the minimum angle that the servo can rotate to. This defaults to + -90, but should be set to whatever you measure from your servo during + calibration. + + :param float max_angle: + Sets the maximum angle that the servo can rotate to. This defaults to + 90, but should be set to whatever you measure from your servo during + calibration. + + :param float min_pulse_width: + The pulse width corresponding to the servo's minimum position. This + defaults to 1ms. + + :param float max_pulse_width: + The pulse width corresponding to the servo's maximum position. This + defaults to 2ms. + + :param float frame_width: + The length of time between servo control pulses measured in seconds. + This defaults to 20ms which is a common value for servos. + """ + def __init__( + self, pin=None, initial_angle=0.0, + min_angle=-90, max_angle=90, + min_pulse_width=1/1000, max_pulse_width=2/1000, + frame_width=20/1000): + self._min_angle = min_angle + self._angular_range = max_angle - min_angle + initial_value = 2 * ((initial_angle - min_angle) / self._angular_range) - 1 + super(AngularServo, self).__init__( + pin, initial_value, min_pulse_width, max_pulse_width, frame_width) + + @property + def min_angle(self): + """ + The minimum angle that the servo will rotate to when :meth:`min` is + called. + """ + return self._min_angle + + @property + def max_angle(self): + """ + The maximum angle that the servo will rotate to when :meth:`max` is + called. + """ + return self._min_angle + self._angular_range + + @property + def angle(self): + """ + The position of the servo as an angle measured in degrees. This will + only be accurate if *min_angle* and *max_angle* have been set + appropriately in the constructor. + + This can also be the special value ``None`` indicating that the servo + is currently "uncontrolled", i.e. that no control signal is being sent. + Typically this means the servo's position remains unchanged, but that + it can be moved by hand. + """ + result = self._get_value() + if result is None: + return None + else: + # NOTE: Why round(n, 12) here instead of 14? Angle ranges can be + # much larger than -1..1 so we need a little more rounding to + # smooth off the rough corners! + return round( + self._angular_range * + ((result - self._min_value) / self._value_range) + + self._min_angle, 12) + + @angle.setter + def angle(self, value): + if value is None: + self.value = None + else: + self.value = ( + self._value_range * + ((value - self._min_angle) / self._angular_range) + + self._min_value) + diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 444b1d9..eabc414 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -920,3 +920,115 @@ def test_motor_reverse_nonpwm(): device.reverse() assert device.value == -1 assert b.state == 1 and f.state == 0 + +def test_servo_pins(): + p = MockPWMPin(1) + with Servo(p) as device: + assert device.pwm_device.pin is p + assert isinstance(device.pwm_device, PWMOutputDevice) + +def test_servo_bad_value(): + p = MockPWMPin(1) + with pytest.raises(ValueError): + Servo(p, initial_value=2) + with pytest.raises(ValueError): + Servo(p, min_pulse_width=30/1000) + with pytest.raises(ValueError): + Servo(p, max_pulse_width=30/1000) + +def test_servo_pins_nonpwm(): + p = MockPin(2) + with pytest.raises(PinPWMUnsupported): + Servo(p) + +def test_servo_close(): + p = MockPWMPin(2) + with Servo(p) as device: + device.close() + assert device.closed + assert device.pwm_device.pin is None + device.close() + assert device.closed + +def test_servo_pulse_width(): + p = MockPWMPin(2) + with Servo(p, min_pulse_width=5/10000, max_pulse_width=25/10000) as device: + assert isclose(device.min_pulse_width, 5/10000) + assert isclose(device.max_pulse_width, 25/10000) + assert isclose(device.frame_width, 20/1000) + assert isclose(device.pulse_width, 15/10000) + device.value = -1 + assert isclose(device.pulse_width, 5/10000) + device.value = 1 + assert isclose(device.pulse_width, 25/10000) + device.value = None + assert device.pulse_width is None + +def test_servo_values(): + p = MockPWMPin(1) + with Servo(p) as device: + device.min() + assert device.is_active + assert device.value == -1 + assert isclose(p.state, 0.05) + device.max() + assert device.is_active + assert device.value == 1 + assert isclose(p.state, 0.1) + device.mid() + assert device.is_active + assert device.value == 0.0 + assert isclose(p.state, 0.075) + device.value = 0.5 + assert device.is_active + assert device.value == 0.5 + assert isclose(p.state, 0.0875) + device.detach() + assert not device.is_active + assert device.value is None + device.value = 0 + assert device.value == 0 + device.value = None + assert device.value is None + +def test_angular_servo_range(): + p = MockPWMPin(1) + with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: + assert device.min_angle == 0 + assert device.max_angle == 90 + +def test_angular_servo_angles(): + p = MockPWMPin(1) + with AngularServo(p) as device: + device.angle = 0 + assert device.angle == 0 + assert isclose(device.value, 0) + device.max() + assert device.angle == 90 + assert isclose(device.value, 1) + device.min() + assert device.angle == -90 + assert isclose(device.value, -1) + device.detach() + assert device.angle is None + with AngularServo(p, initial_angle=15, min_angle=0, max_angle=90) as device: + assert device.angle == 15 + assert isclose(device.value, -2/3) + device.angle = 0 + assert device.angle == 0 + assert isclose(device.value, -1) + device.angle = 90 + assert device.angle == 90 + assert isclose(device.value, 1) + device.angle = None + assert device.angle is None + with AngularServo(p, min_angle=45, max_angle=-45) as device: + assert device.angle == 0 + assert isclose(device.value, 0) + device.angle = -45 + assert device.angle == -45 + assert isclose(device.value, 1) + device.angle = -15 + assert device.angle == -15 + assert isclose(device.value, 1/3) + From 8c7712c3aa0fb19e622199f76406cb782c50d442 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Tue, 30 Aug 2016 23:23:11 +0100 Subject: [PATCH 102/104] Quick addition of ButtonBoard docs Nothing special (no recipes yet); just add the stuff necessary to the reST to pull it from source and add it to the diagrams --- docs/api_boards.rst | 7 + docs/images/composed_devices.dot | 2 + docs/images/composed_devices.pdf | Bin 12641 -> 12740 bytes docs/images/composed_devices.png | Bin 30588 -> 33197 bytes docs/images/composed_devices.svg | 156 +++++++++++---------- docs/images/composite_device_hierarchy.dot | 1 + docs/images/composite_device_hierarchy.pdf | Bin 14649 -> 14703 bytes docs/images/composite_device_hierarchy.png | Bin 53677 -> 56963 bytes docs/images/composite_device_hierarchy.svg | 76 +++++----- 9 files changed, 136 insertions(+), 106 deletions(-) diff --git a/docs/api_boards.rst b/docs/api_boards.rst index 2db1d83..ca3620b 100644 --- a/docs/api_boards.rst +++ b/docs/api_boards.rst @@ -29,6 +29,13 @@ LEDBarGraph :inherited-members: :members: +ButtonBoard +=========== + +.. autoclass:: ButtonBoard(\*pins, pull_up=True, bounce_time=None, hold_time=1, hold_repeat=False, \*\*named_pins) + :inherited-members: + :members: + TrafficLights ============= diff --git a/docs/images/composed_devices.dot b/docs/images/composed_devices.dot index b1611f2..5a1fe74 100644 --- a/docs/images/composed_devices.dot +++ b/docs/images/composed_devices.dot @@ -12,6 +12,8 @@ digraph classes { LEDBarGraph->LED; LEDBarGraph->PWMLED; + ButtonBoard->Button; + TrafficLightsBuzzer->TrafficLights; TrafficLightsBuzzer->Buzzer; TrafficLightsBuzzer->Button; diff --git a/docs/images/composed_devices.pdf b/docs/images/composed_devices.pdf index f0dfaf8647c1c4bd6828c56ca52e7401d0493337..d1178acf0ae7c5cc15260e05992094a502f545b9 100644 GIT binary patch delta 9977 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 delta 9877 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 literal 30588 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Ⓢ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 diff --git a/docs/images/composed_devices.svg b/docs/images/composed_devices.svg index 7313446..1fd2dff 100644 --- a/docs/images/composed_devices.svg +++ b/docs/images/composed_devices.svg @@ -1,38 +1,38 @@ - - + classes - + RGBLED - + RGBLED LED - -LED + +LED RGBLED->LED - - + + PWMLED - -PWMLED + +PWMLED RGBLED->PWMLED - - + + LEDBoard @@ -41,98 +41,108 @@ LEDBoard->LED - - + + LEDBoard->PWMLED - - + + LEDBarGraph - + LEDBarGraph LEDBarGraph->LED - - + + LEDBarGraph->PWMLED - - + + - -TrafficLightsBuzzer - -TrafficLightsBuzzer - - -TrafficLights - -TrafficLights - - -TrafficLightsBuzzer->TrafficLights - - - - -Buzzer - -Buzzer - - -TrafficLightsBuzzer->Buzzer - - + +ButtonBoard + +ButtonBoard -Button - -Button +Button + +Button + + +ButtonBoard->Button + + + + +TrafficLightsBuzzer + +TrafficLightsBuzzer -TrafficLightsBuzzer->Button - - +TrafficLightsBuzzer->Button + + + + +TrafficLights + +TrafficLights + + +TrafficLightsBuzzer->TrafficLights + + + + +Buzzer + +Buzzer + + +TrafficLightsBuzzer->Buzzer + + -Robot - -Robot +Robot + +Robot -Motor - -Motor +Motor + +Motor -Robot->Motor - - +Robot->Motor + + -DigitalOutputDevice - -DigitalOutputDevice +DigitalOutputDevice + +DigitalOutputDevice -Motor->DigitalOutputDevice - - +Motor->DigitalOutputDevice + + -PWMOutputDevice - -PWMOutputDevice +PWMOutputDevice + +PWMOutputDevice -Motor->PWMOutputDevice - - +Motor->PWMOutputDevice + + diff --git a/docs/images/composite_device_hierarchy.dot b/docs/images/composite_device_hierarchy.dot index 5c28394..2d8c5aa 100644 --- a/docs/images/composite_device_hierarchy.dot +++ b/docs/images/composite_device_hierarchy.dot @@ -35,4 +35,5 @@ digraph classes { Servo->CompositeDevice; AngularServo->Servo; Energenie->Device; + ButtonBoard->CompositeDevice; } diff --git a/docs/images/composite_device_hierarchy.pdf b/docs/images/composite_device_hierarchy.pdf index 500833ed6786c815bbade6e55db5eef6ce99d4f4..8d7b3f34b0bb6141bfb1338e24d41a72f57c8080 100644 GIT binary patch delta 2336 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 delta 2281 zcmZXQc{~%28^_Dh+(!v%Mp!lHX6z`+IY(xOB;`ni&}QWbGql{~$Wh3VT)DDIj-gDW zz^Rilml?Kd6iET@+8K~s{*%ireCK2Mc#M zlS3Gz^%O}YGdAOjmgu}QQ}RUzK&$S_`hxk%-ZkaG_SbIu*VZ0tCk#C^yZMi04trF5 zrc!vmTiA$ot?Ab5rt{?WD6d2(PB;6pIVwL;q$_5Gn&`!6YU|bB!KI3rKlUY)7I}^G z0xTTnYL*^l7L@^Fg>mw?Z7Hvtyr$O%=%r-peswjU4RooJ2{# zX;BQ+FSGy`H`S8sj0R^TXjlQ79%WtY7R;dAoW-T|=``J~5D3vj2Czm@j!^H~;U3JgZY96`07^A4ZvSPAwu@c5l~%BXna46EK|{u|dY5iO<2 z<4q(cUmfp}S531O0DFn~w8^h|$d2HW>3{zOYq~UlRu}exM0`{cRp`vTB;k@Ifap0@+pVEn+HS13>Edj%pp+9(+ctUtJ z&k%fBpgffs-2}sRvANTbI`a$h_JF>#J*B5OM!k$Tz8B_BD;fo6i2`)wR9u*f2v^)W zt;}7&X^)UqsXWr4eiU?`^1@AdKrW72{2*0(k|1&qZW`rIMWy8<=hEAcFbh6@>{jyx zwU!X&SMR^RR;4qTWf%BErcb$tsp~pSdsO;H++lf2agC?cm}5d`sY)`sl>PGbMIbQs znde~fD0k+MXQ|f@0=Sce+VO@UQ9EC2gZl-f&Wr9>azUP$hwd?A6qx}7){}9u!V>+* zn50Q)2Y}@f`fXO!CyTQ{uB@_{yw13t5b_P=FD_9@sHhm```sjCzP#{Z2+>O?_gFH< zm!_RE#{QToy>3c;o-I1qnj?iZMb!YEHwA@+0E$eZ-$p@v@8b;sFA&yA?`2%KVv9x@ z;Fd+0k;?`?4o?ADnXO1VKP(bB8w=rK3~<;h$-GH3Iz1!>ex>Y6S%Bj+P6<0UFY;F9 zoyZ4U-Sb_Q?LSzNmuC$~wSTUs($SMR9%n(CG9hNrayG)|I4Hr+MyOyVC*iO{ zAY})*`zwyVD?e&79R;${n>~D&Xy=orn&OGe<((p)9Hv|@HtB;=&v=)BHxWsIQZgpn zn13i@>Ie-2bG$8@2l{kdYp8LLv4+|{c&*lO<u`b|9Fm7f5X2<7Tf`FXOg(O#sLfQE0M@ff8UoZTZ?Vdn0UatiNI`3NX8RWT zZeITO>VcsHOPqA?FD~EY(Z#1p-p*@VdJSNT?8~cbOD>#a2P@Z&q~OxpS_psbPFPJNEKueWRpUF)PLyavHS1b`N#tyw|VAht8LF$@2_lsy`gPpT^XA^ zEuEj=OD{MHpKD7NXOFeXQ#>a+N+6r$G*0p<&qAGD`_X{-bVVZL)!2!cExawuMX7}1 zI!4QUOAl^no{ucDTpe_vsjl4F{7sI49$GJIz1$N{S| zg@)Jb)1oLzt5e@VD@Mu}+6I`qD15m>_?HuQ242(v0( z{KRD}rY64Jz@I3JZM;$1dQg3feq0&+UXmNHX}F~pKVC1- zp%Bj#_*C`c-1m83R#$n0Y)&M*8eNn8{ZV#jNX`!b0SklflwJUc6NQAQtN>a$5hx_> z70*Aa5-2o67W)5?y(P={H-W;?C{#)nQh@?PBQ*CEVNea|J_bj^_lc;(VEY*y4&9>% zMI&JF{p_zE<-e|AP#6rhr|}PiYwTkvByulPN1^sEhN+{W`)1+l(ErS0{=NeSSKsFv h2G@k{GXwu~%HG8qP!u{v5^c__q0R>e8{#m0{{rrk`icMm diff --git a/docs/images/composite_device_hierarchy.png b/docs/images/composite_device_hierarchy.png index 96623f40c3517056e53aafc9940142e2de759d0e..81f75d7aed60cedc547bb309960b7fecaee68335 100644 GIT binary patch literal 56963 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 %+jbuXo98Yya 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 - + classes - + Device - -Device + +Device CompositeDevice - -CompositeDevice + +CompositeDevice CompositeDevice->Device - - + + CompositeOutputDevice @@ -31,8 +31,8 @@ CompositeOutputDevice->CompositeDevice - - + + LEDCollection @@ -136,13 +136,13 @@ Robot - -Robot + +Robot Robot->CompositeDevice - - + + RyanteckRobot @@ -151,8 +151,8 @@ RyanteckRobot->Robot - - + + CamJamKitRobot @@ -161,28 +161,28 @@ CamJamKitRobot->Robot - - + + Motor - -Motor + +Motor Motor->CompositeDevice - - + + Servo - -Servo + +Servo Servo->CompositeDevice - - + + AngularServo @@ -191,18 +191,28 @@ AngularServo->Servo - - + + Energenie - -Energenie + +Energenie Energenie->Device - - + + + + +ButtonBoard + +ButtonBoard + + +ButtonBoard->CompositeDevice + + From ab61fa9b6d4fb71a38e45c67a4fe9cf20bd8b1a9 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sat, 25 Jun 2016 12:35:45 +0100 Subject: [PATCH 103/104] Patch for #385 DistanceSensor currently doesn't work well in Python 2 due to large lags in thread event primitives. The "full" fix will require enhancing the pins API, so this is a temporary patch to provide a fix (at least under RPi.GPIO) until then. --- gpiozero/input_devices.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gpiozero/input_devices.py b/gpiozero/input_devices.py index 0076f3d..8a4e592 100644 --- a/gpiozero/input_devices.py +++ b/gpiozero/input_devices.py @@ -585,11 +585,19 @@ class DistanceSensor(SmoothedInputDevice): self._max_distance = max_distance self._trigger = GPIODevice(trigger) self._echo = Event() + self._echo_rise = None + self._echo_fall = None self._trigger.pin.function = 'output' self._trigger.pin.state = False self.pin.edges = 'both' self.pin.bounce = None - self.pin.when_changed = self._echo.set + def callback(): + if self._echo_rise is None: + self._echo_rise = time() + else: + self._echo_fall = time() + self._echo.set() + self.pin.when_changed = callback self._queue.start() except: self.close() @@ -672,14 +680,15 @@ class DistanceSensor(SmoothedInputDevice): self._trigger.pin.state = False # Wait up to 1 second for the echo pin to rise if self._echo.wait(1): - start = time() self._echo.clear() # Wait up to 40ms for the echo pin to fall (35ms is maximum pulse # time so any longer means something's gone wrong). Calculate # distance as time for echo multiplied by speed of sound divided by # two to compensate for travel to and from the reflector - if self._echo.wait(0.04): - distance = (time() - start) * self.speed_of_sound / 2.0 + if self._echo.wait(0.04) and self._echo_fall is not None and self._echo_rise is not None: + distance = (self._echo_fall - self._echo_rise) * self.speed_of_sound / 2.0 + self._echo_fall = None + self._echo_rise = None return min(1.0, distance / self._max_distance) else: # If we only saw one edge it means we missed the echo because From 3ac838f85fced9853bdd232f0d91eea55021c176 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Sun, 12 Jun 2016 13:31:13 +0100 Subject: [PATCH 104/104] Raise warning when spidev missing for hardware SPI --- gpiozero/spi.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/gpiozero/spi.py b/gpiozero/spi.py index 85352df..7a816fe 100644 --- a/gpiozero/spi.py +++ b/gpiozero/spi.py @@ -396,24 +396,30 @@ def SPI(**spi_args): raise SPIBadArgs( 'unrecognized keyword argument %s' % kwargs.popitem()[0]) if all(( - SpiDev is not None, spi_args['clock_pin'] == 11, spi_args['mosi_pin'] == 10, spi_args['miso_pin'] == 9, spi_args['select_pin'] in (7, 8), )): - try: - if shared: - return SharedSPIHardwareInterface( - port=0, device={8: 0, 7: 1}[spi_args['select_pin']]) - else: - return SPIHardwareInterface( - port=0, device={8: 0, 7: 1}[spi_args['select_pin']]) - except Exception as e: + if SpiDev is None: warnings.warn( SPISoftwareFallback( - 'failed to initialize hardware SPI, falling back to ' - 'software (error was: %s)' % str(e))) + 'failed to import spidev, falling back to software SPI')) + else: + try: + hardware_spi_args = { + port: 0, + device: {8: 0, 7: 1}[spi_args['select_pin']], + } + if shared: + return SharedSPIHardwareInterface(**hardware_spi_args) + else: + return SPIHardwareInterface(**hardware_spi_args) + except Exception as e: + warnings.warn( + SPISoftwareFallback( + 'failed to initialize hardware SPI, falling back to ' + 'software (error was: %s)' % str(e))) if shared: return SharedSPISoftwareInterface(**spi_args) else: