From 3fe9675befdb8f61bc276559025a2e903d3382ab Mon Sep 17 00:00:00 2001 From: Ace Date: Tue, 12 May 2020 19:26:10 +0200 Subject: [PATCH] modules refactoring + slight changes to folder structure --- inkycal/Inkycal.py | 14 +- inkycal/__init__.py | 1 + inkycal/__pycache__/Inkycal.cpython-37.pyc | Bin 0 -> 1700 bytes inkycal/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 170 bytes inkycal/config/__init__.py | 4 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 265 bytes .../config/__pycache__/layout.cpython-37.pyc | Bin 0 -> 2975 bytes .../config/__pycache__/parser.cpython-37.pyc | Bin 0 -> 3813 bytes .../settings_parser.cpython-37.pyc | Bin 0 -> 3829 bytes inkycal/config/layout.py | 102 ++++ inkycal/config/parser.py | 137 +++++ inkycal/config/settings.json | 42 ++ inkycal/custom/__init__.py | 1 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 164 bytes .../custom/__pycache__/fonts.cpython-37.pyc | Bin 0 -> 1402 bytes .../__pycache__/functions.cpython-37.pyc | Bin 0 -> 5173 bytes inkycal/custom/functions.py | 362 +++++++++++++ inkycal/display/__init__.py | 2 - .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 208 bytes .../display/__pycache__/layout.cpython-37.pyc | Bin 0 -> 2976 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 139 bytes .../__pycache__/epd_4_in_2.cpython-37.pyc | Bin 0 -> 8576 bytes .../__pycache__/epd_7_in_5.cpython-37.pyc | Bin 0 -> 4287 bytes .../epd_7_in_5_colour.cpython-37.pyc | Bin 0 -> 4072 bytes .../epd_7_in_5_v2_colour.cpython-37.pyc | Bin 0 -> 3751 bytes .../__pycache__/epdconfig.cpython-37.pyc | Bin 0 -> 4245 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 131 bytes .../inkycal_calendar.cpython-37.pyc | Bin 0 -> 5296 bytes .../__pycache__/inkycal_rss.cpython-37.pyc | Bin 0 -> 4121 bytes .../inkycal_weather.cpython-37.pyc | Bin 0 -> 8361 bytes inkycal/modules/inkycal_rss.py | 28 +- inkycal/modules/inkycal_weather.py | 479 ++++++++++++++++++ 32 files changed, 1155 insertions(+), 17 deletions(-) create mode 100644 inkycal/__pycache__/Inkycal.cpython-37.pyc create mode 100644 inkycal/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/config/__init__.py create mode 100644 inkycal/config/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/config/__pycache__/layout.cpython-37.pyc create mode 100644 inkycal/config/__pycache__/parser.cpython-37.pyc create mode 100644 inkycal/config/__pycache__/settings_parser.cpython-37.pyc create mode 100644 inkycal/config/layout.py create mode 100644 inkycal/config/parser.py create mode 100644 inkycal/config/settings.json create mode 100644 inkycal/custom/__init__.py create mode 100644 inkycal/custom/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/custom/__pycache__/fonts.cpython-37.pyc create mode 100644 inkycal/custom/__pycache__/functions.cpython-37.pyc create mode 100644 inkycal/custom/functions.py create mode 100644 inkycal/display/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/display/__pycache__/layout.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/epd_4_in_2.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/epd_7_in_5.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/epd_7_in_5_colour.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/epd_7_in_5_v2_colour.cpython-37.pyc create mode 100644 inkycal/display/drivers/__pycache__/epdconfig.cpython-37.pyc create mode 100644 inkycal/modules/__pycache__/__init__.cpython-37.pyc create mode 100644 inkycal/modules/__pycache__/inkycal_calendar.cpython-37.pyc create mode 100644 inkycal/modules/__pycache__/inkycal_rss.cpython-37.pyc create mode 100644 inkycal/modules/__pycache__/inkycal_weather.cpython-37.pyc create mode 100644 inkycal/modules/inkycal_weather.py diff --git a/inkycal/Inkycal.py b/inkycal/Inkycal.py index 900ec58..a09f5ff 100644 --- a/inkycal/Inkycal.py +++ b/inkycal/Inkycal.py @@ -1,9 +1,6 @@ from importlib import import_module -from inkycal.configuration.settings_parser import inkycal_settings as settings -from inkycal.display.layout import inkycal_layout as layout - - +from inkycal.config import settings, layout ##modules = settings.which_modules() ##for module in modules: @@ -12,23 +9,28 @@ from inkycal.display.layout import inkycal_layout as layout ## #import_module('modules.'+module) ##print(module) -settings_file = '/home/pi/Desktop/settings.json' - +settings_file = "/home/pi/Desktop/Inkycal/inkycal/config/settings.json" class inkycal: + """Main class for inkycal + """ + def __init__(self, settings_file_path): """Load settings file from path""" + # Load settings file self.settings = settings(settings_file_path) self.model = self.settings.model def create_canvas(self): """Create a canvas with same size as the specified model""" + self.layout = layout(model=self.model) def create_custom_canvas(self, width=None, height=None, supports_colour=False): """Create a custom canvas by specifying height and width""" + self.layout = layout(model=model, width=width, height=height, supports_colour=supports_colour) diff --git a/inkycal/__init__.py b/inkycal/__init__.py index e69de29..96cd837 100644 --- a/inkycal/__init__.py +++ b/inkycal/__init__.py @@ -0,0 +1 @@ +from .Inkycal import inkycal diff --git a/inkycal/__pycache__/Inkycal.cpython-37.pyc b/inkycal/__pycache__/Inkycal.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..440f34fb688ed8e3756ea1d8ad310ce9b6f81cbd GIT binary patch literal 1700 zcmah~&5qkP5GEzrmc8rU%_f^PKoAfCa!`t{fdIJ$*`g^@v`BsFO@|U%QC@SSABUt9 zSnw@-%>%Tj_E_}TQ{SNlde*fC7JY@BIzv&L+6_qoOHtn#0g`gyXdmZp9*nO8+NS;)!fnSNoa#pKKK#VSqm ziFEI@DrfR+(w#g$*HxLKgKk`f`xsU-5>r89Pte#Gp71Y79JB$(J#kHhVt7Fz%#qlE zoq;$M`{DprLL2z?X5hTmx4%wg$;I)N_vUjn7kf^hY2JS<^RkO*mPT zb8?Ov6%-QU)pwp(iC`TDHj{bAW~wUKA~Ex6Q-$N!r)m&W78stGxlr8Jx$Vx$2G5{zhl;4mF`+bP8@82HA3EHXR<$N-O^Z!J3E z0VcnG^i*Yu$ymbDq2! z)CL%FU`|&q@)gu8n`iQDZdg(ZKq<^T3T)s_&(4~DyWi~Vw+aSCsc0fjUc#tV&0q z$Y#khH=vC z4Gy6-z30%~s*Xh;fV7qPzyV2ZB0j$_W-X=CsF>d2o?pGCg9 zh92+=s_P)4AztPZt{CUdh`YuL`Yz`$mr3q+2Aqp3KpS34;7Q6M|&7K-;pXEnTT z3UiHhqR}k$0xukdJwiwQFd)?KPNP9HYkWw TeBAyYo?zRtDcbc8hJp7FpkS=m literal 0 HcmV?d00001 diff --git a/inkycal/__pycache__/__init__.cpython-37.pyc b/inkycal/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad32c12490507525cea34fa3f7fe69806369fa51 GIT binary patch literal 170 zcmZ?b<>g`k0vYekak4=AF^B^LOhASM5Elyoi4=wu#vF!R#wbQch7_h?22JLdj6h*c z##`)}dD)f8i8+3nOi}Ef5Oxtr?@ERuW*`M7e(C6E&M4u=4F<|$LkeT-r}&y%}*)KNwovn@fnC20E1yCg8%>k literal 0 HcmV?d00001 diff --git a/inkycal/config/__init__.py b/inkycal/config/__init__.py new file mode 100644 index 0000000..b3de81b --- /dev/null +++ b/inkycal/config/__init__.py @@ -0,0 +1,4 @@ +from .parser import settings +print('loaded settings') +from .layout import layout +print('loaded layout') diff --git a/inkycal/config/__pycache__/__init__.cpython-37.pyc b/inkycal/config/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8f4ea870c6da0789b51752f621a29060751c771 GIT binary patch literal 265 zcmXv|u?oU45Y07Oidg&w*MgYARYb(a#l_9RQcTofnuH_{b@gkU{hO{%{(_U&st@ko zz4y4|7HQfe2)}ylPRP$j{1M?W$MPd=oN!R2ge?FO5sz-j=ut&Wx}~kxX}#`-Wbq4d zC4)NgA_S?A{yI>m;DQhT28OXJDsu_f^cQ{QI_uQ2Eu0t5H6PSn>!c2j;(RAAMrzx8 z!cL|X%u2Qt{v3>DD}Ao^g<|r@d!rBX$nsoj8S;E=t2&vMhF_GJH4pmcSRWGT!w0U4 BLvjEB literal 0 HcmV?d00001 diff --git a/inkycal/config/__pycache__/layout.cpython-37.pyc b/inkycal/config/__pycache__/layout.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd9d9ac48fe282cd6f8a2e5a7147beb85503f6fc GIT binary patch literal 2975 zcmaJDOKjZ6b!PZkimQ+1Zfd7>+X+&;-oTa=CqV)u@lUo>1BP1Uhn5IRP@LHnB`(R% zaAj$`28`H1eJPMj3MA;k272kar{0R3Q=o^QcufE!w*bBM)c0n%wj?AO;yu23^Lz8Y zb8TTEB+zdD^6$U<^8z7XVrSMIAg%!Dp8^sjB7zE*(}=!Jgd^MsM7Z}^!~}bn^u5W| z@OH9aj%z+D#W8()OQjkSHktO+E>|dtLB^gJV|Ay0fk?~ zF=Qh}JZmlxR{->NK$1ih#9)F(j&vV+l02r7FWv19Kp@zkC?#T1c*1``qdBo8LeT{( z6!T)?0g1Z80YQ&7es1KP^nRTTCAS^El@ub+ieaBNUM_cKzHu}4;7n{jz#PCjfc`5W zL8l}pf&r#eDx4ihMN-L8WtCJ?q0wX>a%k^sX?xGs@Vex7#Xc9Au0Ut}SMQkF z>nf=wPk1`kbvb%EdMB&5_%Pd*1;3LCph3NSOQOje@6J|M%f{QxlXPdJ&oT~D-pjQXGQ#4yn7)|%e)+`X5kRGSw7HhXN1pK&2gk( z!#&=3Wwt~20MD*}6(>AB!K}Be$hUjGH|eh@#V=}}Drptb`V%SugTd-Rg9&uM8!d0b zm4~Vv7ot5h>eh0vo=LFSYB#p3iWX*lqr|9JZz(C;$*~?+RjKM`sU`Gm7q3!TQ8&wV zS;bnWbygO!*`Vo-G9hw#l)G8hV7w#TC3~eASNHwK)iNJ6p79khBO12xt*B}V;vzae zlUHOTS!g&||Ba~UVd#G^uWpqixmsnb*QMS8(N-}zq)EP-wg0p%2H9}cDsZLRZ-N-q zpVe`E3yPCyyZ_)V7D-DW#I`Rr3{|G50*UteJR z>hcrZmXLp6PBGVD!?4GY2d^ahKY;#71{71@gW#@f!ky_;ZJ6J-ef5%X9zsfJxo?;r zok9wj(v!eFN$yh^XT`NCow5_aIYETCHJJh|WrEjw4sZR*#J*e^uJE;$3;Zp9VeK?Ods`;ElFLz5@0+DU4w7+R^W7vL zgZrB`@5&moNws>|L-4=DEYhdCESL zJlep=;50gII(a!9LJe&^Avedv#)l`sD5=#l&Nz+;&C~acvYYOKlbglEdpC=RMQA(? zI+n&jGh?BhR1eVW0Qw9d;?Fb81U_a0)-yC?Aq{~J=pu|DOn&(7hD@Q_n1+6YunwR< z1OzcocRH$9W67uY*+;avK8t%Oz3>>=W&?JY6)yqTg-5^@TadS1!|egr1MYjk^#FY2 zPT|oceDKW9-72)dYh1(CVXGh`ePod#df8p9xJQ_=p%L4X)7X3pj literal 0 HcmV?d00001 diff --git a/inkycal/config/__pycache__/parser.cpython-37.pyc b/inkycal/config/__pycache__/parser.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61cc88149fb2f0f51d6902d0311e2455626749c5 GIT binary patch literal 3813 zcmZ`+OK%&=5uO)E6h+Y#C0P&Ka!c#oh=D}eT0b@$Cvf6j+xrrBZ5Vb4B!dxqNRBw1 z8TSljYnJrI8z8qB`33f6k>rp|{zi~fPIC&7lW#c(d&pNk97;lzA-cM|y1uIJ>gwv| zFK1^<3ckPJ{p)XbYl`wO>g1n}#vPRCOAxL!6|Qp4Q=2Mk-P8JdQ&)*+plvh_Rk^S5 z0yn=?xcN$J7Wfn|ey21|XNphvH1WVOUn!8+9x3%w@{7{vQQ))4iDTFAL@cyLT*}0VT5+vZ4o>F z=Fqr<5+xvxf|)B#jjK(aYh33B&6Z}#r+JCb@G_s}6@HFa`5dox=9&hd=L;QL$s#|G z+T=@o8TAxj;j8=tzsN7~HGY}1PO(|!>wE*No#yZHE2vBSef|OJ8U7=zcQY;KD^0F_ z8Q7fJK4*uv=W;uCj=j(pL7&CllN{G`QqA#GjiVNWRC8nWqf~1P)LyDZhpFbH4!x!} zu+iR6wL>>8BwZ_ho}k)p0eygxUXYr7Cl+q2S#fvBn@OalV^*_9?Hi=yqYrZpGbeZ% z{yGUvEJ%FDYH64qeI^P;>s^%SFW?@1K2+Zz0^h3QB3g*fw}{LYWu*DWP#tNpHq>6J zsuJs5eWm`sHqv9`O<}0NRfmdYK2f;wjq*@=sytEri-rP-;=l>gdoyNRuy1ElX%qS(bD8ht#z+najVSYX!QNCc0Lot_#qz^x~z|b8$ua zMEP|%GKL1fw4(5}p7vT3SJ1mGEv%r$WNX#GH0EF7?G9J$396pWNAlo?q^<*}ONM=&mIn@^`4#ZxvwHHjl635!X z0BHt^Gzy)T+jbq!yr5;rZs4ay&-Ob5yW=D~kNs#6hJlD_e9ZlreLGrjut(3`xYb35 zW^uqAzvH=4R}^3_Eew1&j*{)iRI$x(M|I{0F`H=GVuOfqS;Apbx*--Mw2K@PC08Fg zp3@?uvQHMzR3ke=!aAwddFoWl&Pi>@3T&Af#0%*H10z zR^!G7bH28b3faDAw|d*pyDswa=HazEw#4?leK6}nrpLJ!()GTO-m|h5T>8wHIh>ss zI$lh&@mP9*J8gU5#p5Y#v8#4}>)I~J&9E;P;gEN@c$IXXyHV)bM^+$Q+TCdQ6Mw%}Dt?Azoyu|&4a(DofKLJ;KPpvOU&8u3x+q-HDd+is_6 z_Fc|#`b#P=vXKNTVnX(1G@ABz=&uT3N^YZ6XY9+;81LPB~Ji_~n>!eX&XoYga7 zGVa8M|1Xe5mQL0$k<^7V)ySZW#2QH$I0Ug^)Z9E%_>7HAXW(B5x8&KYo#@~~ByXpc z%-a*0#nb(XShg9*VyFAL*z~6_T8<3))D$=mfnPUM181V0*7AL@D7RRYe#nuOeo}*2 z^j4Acj8fJ4lOpb6rT@9M(+&F0PU!A@;zT{nf9Es5cZ9g#amRJGj-ByCE<8$$7H)Jm zwybq1h}s~Erl}>hsu@}l^{Q4;E1IE})m62kn)-3QHtthhlq1WsRfbFzt&Cd*zND5< z&??4jYBHCvu#wMT@`Rw&WuV9>gg|46r@(=N=P53`13Vf0tCFLEa|Ik7oHfZYz_|%d z0i1Gd(u0B?vm@=r2jEW8vjdmX@<@j?o|Y@5a}TWoo+HMyy(h|08yanT{F=$_`wlXb z=P|tg?CciEr<^5jymLbl9J*X)Uc3hpe*@29zTJ0{g*+#mV6N{8^0GV|Vgs#uQG7&< zpAh+&$WMvfCh{{P4I*10^@6yADxJyHS!6@JPaHGjNPRL2GIIZjv_aOZU{J(3T=(n(MRe~h1=BR;X@}L2tPtn zrz6OM_Si4Df8zphM}IwHTf}uebCdKCIj62=-q1aq zafUl#wnq_dYR4v`P8z~k~IdSUAC-e1KS&qOj&qt zw=7HSkjY&l9}>AmY-R@7L4?><4~0W%MiR#V?9A zDc6F?EF&@-1wiP&C+e$pZlJ|(viA9T&(qfNVXDKh<&bH*|CM2Q? P2ql>E6jDQkC9D4h)w?!$ literal 0 HcmV?d00001 diff --git a/inkycal/config/__pycache__/settings_parser.cpython-37.pyc b/inkycal/config/__pycache__/settings_parser.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..572c0a2875bfcf40bb5549926e0e85d04b4e0e86 GIT binary patch literal 3829 zcmZ`+&2JmW72hweD2k#fO0qs|%Ogce#X_QN#j?}XEu1?3h?@wG4Z|*hcCq3t$rYEo z>)EAjZAf370=?DfUtnK?wufBuZxlWCw5I|+`POrhLw|3Uq$E^bV&2TWdB68&-n@A; z{MGbyLBaR;+duqnr=lqTqR!~k(YS>YeF?&qy24eid1_rnt$SKmuj?xD47829p(^(j zp5x}X3O8SC^&Fqz`EQlF=}hp+jwbFq=4%D=+GC|!NIok(hytHQP8_>_D`KH7B1hEN z?Vb>hAA3j4^_lI3ZF{wFI|z@2+iJ&b|A^TQ$BjS=?=dN5ud1fGMw`0=4I{MUc7xcX zZw8H9C{Y69D44lY*SK2OxyE&F&}?aze3BRV6fg2=UgGC?na}V_Yo>1SSw7dImCW<= zs7=1W7g0~}CBDor@QeHsU*VTIYvt>CzRK6I+DZNaUq@ZwAM%e-Pw}5%y&GwMw9@42 zmx0Zh?Q?c$doH(Q=hzEP5p-GHKFM)CC)FH3)i`R=OEot}KT5TxK<%YkbeL*B>d>oe zJsa))R6BIjT++7U7YVAZ2G9o>=>)0SbzUp;tI>NQR_eFT-{US(e`hn=$UVW+; zMlN?O9=!1Db56*u8M6-Ap1a9JA^bYVVScPd*-Lm^#7B z@YhISY(e5PRzt(==rd6$T5qF7--CPfps&6~1in+nMYIr|?+}?w%0TmtzB?_bhD3i}hE%Rv4L;epEKM(wQY5lK2%mF_? z#$ybt*ZFzA(9)rWkS1T0S{BfzvMA^B52KJCs?KGWW+a7sfId{k5UY8e~-Q<5=d> zSZ3vo`uxYI6cr!PY!ELs#LMMQ0e1haDubM=B&9gt&mFLA9J$39lW4`)06OV~=0Baifh4 z&EkMLe#>*Cw#dO;n(O&)93@*%sA3!6464izVm8*a$$AmtvV_B=bWO}jXd5{sO0GP1 zJf}fMWuGjbtzzfemXt`fooi{X8*s-)Uflt?9(Kh%9P%C)uaeGkHwr!b$O?pu8&KLwR!0`3 zgNG7`lCeZB**dkPv9V*3O?Z_O`xbdlERZcTw0*~m5Co%e(BmLXjrb^ZQnL~GO}AAy zyDsOrea!tJj)QJ;ZWMF~BAYwBx|-%4pGOFhcACT7-B?hnPK_YMg=J9urUuETMPa|N zM(}jzE`oY5i0@K(-Vq`YX)YA5AB!b|uZ$%vYaCA2C@>?jPC|MVi_~n=!eX&ZoaHlN zGVa8M|1Xe5mQL0$k<^7V)ySZW#0p6mI0Ug^)Z92z_>7HAr{G@*H{{u?oao>}ByXps z%-ds``P2QeShg9*VyF8fvB^6x8;%V5)D$=mfnPOK181UHuZ;G=qTFIp`XNVB`biC9 z(OX5%GfGwGFN(N>mHy|m?RL<0wnKOOb0_Lx{@Y*pog>8kwmYn|b@Z|WGEPv?FCTW{ zQJS}K!@IF%twLec1W`0iEvRM9(DJC4wUS!W47I2(t0mRckL#6TpX$6ES(L3JWXfn| z+%oV5wRnP-F)_pAh3`L_Q_*b0Rm1{DMf0$R!+Ke??+j#8o|Wlk^Zd zr>bS%&^?@aIteG%JS`Pk*%G2x7>p>E6uqn_=SQ|{r-b&ZMLGxMQ_D&V))0(#(XyWR zY;QO+Vd44Ruq?4nCU=N@OymZUT_X30JRm~<&Q45CPLlRS&}8Upi6}pk_bcig_5(kh zGWzB9{IByBDc69=_@fPw&Zj>IF&@}5wiP&C+eZm1-?_O z)v9V1RL5Q$Pc1dNw(I{M+9R^1sN!o-@(l4U3(@)z4DnOqYC$CL6`>Q07A2*{SJXO7 adH!a$CBLLU3{eS$5=?OdsUgCW)&Bz9xjeuC literal 0 HcmV?d00001 diff --git a/inkycal/config/layout.py b/inkycal/config/layout.py new file mode 100644 index 0000000..07f8e2c --- /dev/null +++ b/inkycal/config/layout.py @@ -0,0 +1,102 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Layout module for Inky-Calendar software. +Copyright by aceisace +""" + +import logging + +class layout: + """Page layout handling""" + + logger = logging.getLogger(__name__) + logging.basicConfig(level=logging.DEBUG) + + def __init__(self, model=None, width=None, height=None, + supports_colour=False): + """Initialize parameters for specified epaper model + Use model parameter to specify display OR + Crate a custom display with given width and height""" + + self.background_colour = 'white' + self.text_colour = 'black' + + if (model != None) and (width == None) and (height == None): + display_dimensions = { + 'epd_7_in_5_v2_colour': (800, 400), + 'epd_7_in_5_v2': (800, 400), + 'epd_7_in_5_colour': (640, 384), + 'epd_7_in_5': (640, 384), + 'epd_5_in_83_colour': (600, 448), + 'epd_5_in_83': (600, 448), + 'epd_4_in_2_colour': (400, 300), + 'epd_4_in_2': (400, 300), + } + + self.display_height, self.display_width = display_dimensions[model] + if 'colour' in model: + self.three_colour_support = True + + elif width and height: + self.display_height = width + self.display_width = height + self.supports_colour = supports_colour + + else: + print("Can't create a layout without given sizes") + raise + + self.top_section_width = self.display_width + self.middle_section_width = self.display_width + self.bottom_section_width = self.display_width + self.create_sections() + + def create_sections(self, top_section=0.10, middle_section=0.65, + bottom_section=0.25): + """Allocate fixed percentage height for top and middle section + e.g. 0.2 = 20% (Leave empty for default values) + Set top/bottom_section to 0 to allocate more space for the middle section + """ + scale = lambda percentage: round(percentage * self.display_height) + + if top_section == 0 or bottom_section == 0: + if top_section == 0: + self.top_section_height = 0 + + if bottom_section == 0: + self.bottom_section_height = 0 + + self.middle_section_height = scale(1 - top_section - bottom_section) + else: + if top_section + middle_section + bottom_section > 1.0: + print('All percentages should add up to max 100%, not more!') + raise + + self.top_section_height = scale(top_section) + self.middle_section_height = scale(middle_section) + self.bottom_section_height = (self.display_height - + self.top_section_height - self.middle_section_height) + + logging.debug('top-section size: {} x {} px'.format( + self.top_section_width, self.top_section_height)) + logging.debug('middle-section size: {} x {} px'.format( + self.middle_section_width, self.middle_section_height)) + logging.debug('bottom-section size: {} x {} px'.format( + self.bottom_section_width, self.bottom_section_height)) + + + def get_section_size(self, section): + """Enter top/middle/bottom to get the size of the section as a tuple: + (width, height)""" + + if section not in ['top','middle','bottom']: + raise Exception('Invalid section: ', section) + else: + if section == 'top': + size = (self.top_section_width, self.top_section_height) + elif section == 'middle': + size = (self.middle_section_width, self.middle_section_height) + elif section == 'bottom': + size = (self.bottom_section_width, self.bottom_section_height) + return size diff --git a/inkycal/config/parser.py b/inkycal/config/parser.py new file mode 100644 index 0000000..f0a097d --- /dev/null +++ b/inkycal/config/parser.py @@ -0,0 +1,137 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Json settings parser. Currently in alpha! +Copyright by aceisace +""" + +import json +from os import chdir #Ad-hoc + +# TODO: +# Check of jsmin can/should be used to parse jsonc settings file +# Remove check of fixed settings file location. Ask user to specify path +# to settings file + +from os import path + +class settings: + """Load and validate settings from the settings file""" + + __supported_languages = ['en', 'de', 'ru', 'it', 'es', 'fr', 'el', 'sv', 'nl', + 'pl', 'ua', 'nb', 'vi', 'zh_tw', 'zh-cn', 'ja', 'ko'] + __supported_units = ['metric', 'imperial'] + __supported_hours = [12, 24] + __supported_display_orientation = ['normal', 'upside_down'] + __supported_models = [ + 'epd_7_in_5_v2_colour', 'epd_7_in_5_v2', + 'epd_7_in_5_colour', 'epd_7_in_5', + 'epd_5_in_83_colour','epd_5_in_83', + 'epd_4_in_2_colour', 'epd_4_in_2' + ] + + def __init__(self, settings_file_path): + """Load settings from path (folder or settings.json file)""" + try: + if settings_file_path.endswith('settings.json'): + folder = settings_file_path.split('/settings.json')[0] + else: + folder = settings_file_path + + chdir(folder) + with open("settings.json") as file: + self.raw_settings = json.load(file) + + except FileNotFoundError: + print('No settings file found in specified location') + + try: + self.language = self.raw_settings['language'] + if self.language not in self.__supported_languages or type(self.language) != str: + print('Unsupported language: {}!. Switching to english'.format(language)) + self.language = 'en' + + + self.units = self.raw_settings['units'] + if self.units not in self.__supported_units or type(self.units) != str: + print('Units ({}) not supported, using metric units.'.format(units)) + self.units = 'metric' + + + self.hours = self.raw_settings['hours'] + if self.hours not in self.__supported_hours or type(self.hours) != int: + print('Selected hours: {} not supported, using 24-hours'.format(hours)) + self.hours = '24' + + + self.model = self.raw_settings['model'] + if self.model not in self.__supported_models or type(self.model) != str: + print('Model: {} not supported. Please select a valid option'.format(model)) + print('Switching to 7.5" ePaper black-white (v1) (fallback)') + self.model = 'epd_7_in_5' + + + self.calibration_hours = self.raw_settings['calibration_hours'] + if not self.calibration_hours or type(self.calibration_hours) != list: + print('Invalid calibration hours: {}'.format(calibration_hours)) + print('Using default option, 0am,12am,6pm') + self.calibration_hours = [0,12,18] + + + self.display_orientation = self.raw_settings['display_orientation'] + if self.display_orientation not in self.__supported_display_orientation or type( + self.display_orientation) != str: + print('Invalid ({}) display orientation.'.format(display_orientation)) + print('Switching to default orientation, normal-mode') + self.display_orientation = 'normal' + + ### Check if empty, If empty, set to none + for sections in self.raw_settings['panels']: + + if sections['location'] == 'top': + self.top_section = sections['type'] + self.top_section_config = sections['config'] + + elif sections['location'] == 'middle': + self.middle_section = sections['type'] + self.middle_section_config = sections['config'] + + elif sections['location'] == 'bottom': + self.bottom_section = sections['type'] + self.bottom_section_config = sections['config'] + + + print('settings loaded') + except Exception as e: + print(e.reason) + + def module_init(self, module_name): + """Get all data from settings file by providing the module name""" + if module_name == self.top_section: + config = self.top_section_config + elif module_name == self.middle_section: + config = self.middle_section_config + elif module_name == self.bottom_section: + config = self.bottom_section_config + else: + print('Invalid module name!') + config = None + + for module in self.raw_settings['panels']: + if module_name == module['type']: + location = module['location'] + + return config, location + + def which_modules(self): + """Returns a list of modules (from settings file) which should be loaded + on start""" + lst = [self.top_section, self.middle_section, self.bottom_section] + return lst + + +def main(): + print('running settings parser as standalone...') + +if __name__ == '__main__': + main() diff --git a/inkycal/config/settings.json b/inkycal/config/settings.json new file mode 100644 index 0000000..8af98b2 --- /dev/null +++ b/inkycal/config/settings.json @@ -0,0 +1,42 @@ +{ + "language": "en", + "units": "metric", + "hours": 24, + "model": "epd_7_in_5_v2_colour", + "update_interval": 60, + "calibration_hours": [ + 0, + 12, + 18 + ], + "display_orientation": "normal", + "panels": [ + { + "location": "top", + "type": "inkycal_weather", + "config": { + "api_key": "topsecret", + "location": "Stuttgart, DE" + } + }, + { + "location": "middle", + "type": "inkycal_calendar", + "config": { + "week_starts_on": "Monday", + "ical_urls": [ + "https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics" + ] + } + }, + { + "location": "bottom", + "type": "inkycal_rss", + "config": { + "rss_urls": [ + "http://feeds.bbci.co.uk/news/world/rss.xml#" + ] + } + } + ] +} diff --git a/inkycal/custom/__init__.py b/inkycal/custom/__init__.py new file mode 100644 index 0000000..c8f7548 --- /dev/null +++ b/inkycal/custom/__init__.py @@ -0,0 +1 @@ +from .functions import * diff --git a/inkycal/custom/__pycache__/__init__.cpython-37.pyc b/inkycal/custom/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5dea5985a762b129bc6fa0c0b13867285779be6 GIT binary patch literal 164 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 literal 0 HcmV?d00001 diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py new file mode 100644 index 0000000..525c30b --- /dev/null +++ b/inkycal/custom/functions.py @@ -0,0 +1,362 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Inky-Calendar custom-functions for ease-of-use + +Copyright by aceisace +""" +import logging +from PIL import Image, ImageDraw, ImageFont, ImageColor +from urllib.request import urlopen +import os +import time + + +##from glob import glob +##import importlib +##import subprocess as subp +##import numpy +##import arrow +##from pytz import timezone + + + +##"""Set some display parameters""" +##driver = importlib.import_module('drivers.'+model) + +# Get the path to the Inky-Calendar folder +top_level = os.path.dirname( + os.path.abspath(os.path.dirname(__file__))).split('/inkycal')[0] + +# Get path of 'fonts' and 'images' folders within Inky-Calendar folder +fonts_location = top_level + '/fonts/' +images = top_level + '/images/' + +# Get available fonts within fonts folder +fonts = {} + +for path,dirs,files in os.walk(fonts_location): + for filename in files: + if filename.endswith('.otf'): + name = filename.split('.otf')[0] + fonts[name] = os.path.join(path, filename) + + if filename.endswith('.ttf'): + name = filename.split('.ttf')[0] + fonts[name] = os.path.join(path, filename) + +del name, filename, files + +available_fonts = [key for key,values in fonts.items()] + +def get_fonts(): + """Print all available fonts by name""" + for fonts in available_fonts: + print(fonts) + + +def get_system_tz(): + """Get the timezone set by the system""" + try: + local_tz = time.tzname[1] + except: + print('System timezone could not be parsed!') + print('Please set timezone manually!. Setting timezone to None...') + local_tz = None + return local_tz + + +def auto_fontsize(font, max_height): + """Adjust the fontsize to fit 80% of max_height + returns the font object with modified size""" + fontsize = font.getsize('hg')[1] + while font.getsize('hg')[1] <= (max_height * 0.80): + fontsize += 1 + font = ImageFont.truetype(font.path, fontsize) + return font + + +def write(image, xy, box_size, text, font=None, **kwargs): + """Write text on specified image + image = on which image should the text be added? + xy = xy-coordinates as tuple -> (x,y) + box_size = size of text-box -> (width,height) + text = string (what to write) + font = which font to use + """ + allowed_kwargs = ['alignment', 'autofit', 'colour', 'rotation' + 'fill_width', 'fill_height'] + + # Validate kwargs + for key, value in kwargs.items(): + if key not in allowed_kwargs: + print('{0} does not exist'.format(key)) + + # Set kwargs if given, it not, use defaults + alignment = kwargs['alignment'] if 'alignment' in kwargs else 'center' + autofit = kwargs['autofit'] if 'autofit' in kwargs else False + fill_width = kwargs['fill_width'] if 'fill_width' in kwargs else 1.0 + fill_height = kwargs['fill_height'] if 'fill_height' in kwargs else 0.8 + colour = kwargs['colour'] if 'colour' in kwargs else 'black' + rotation = kwargs['rotation'] if 'rotation' in kwargs else None + + x,y = xy + box_width, box_height = box_size + + # Increase fontsize to fit specified height and width of text box + if (autofit == True) or (fill_width != 1.0) or (fill_height != 0.8): + size = 8 + font = ImageFont.truetype(font.path, size) + text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] + while (text_width < int(box_width * fill_width) and + text_height < int(box_height * fill_height)): + size += 1 + font = ImageFont.truetype(font.path, size) + text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] + + text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] + + + # Truncate text if text is too long so it can fit inside the box + if (text_width, text_height) > (box_width, box_height): + logging.debug('text too big for space, truncating now...') + while (text_width, text_height) > (box_width, box_height): + text=text[0:-1] + text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] + logging.debug('truncated text:', text) + + # Align text to desired position + if alignment == "center" or None: + x = int((box_width / 2) - (text_width / 2)) + elif alignment == 'left': + x = 0 + elif alignment == 'right': + x = int(box_width - text_width) + + y = int((box_height / 2) - (text_height / 2)) + + # Draw the text in the text-box + draw = ImageDraw.Draw(image) + space = Image.new('RGBA', (box_width, box_height)) + ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) +## space = Image.new('RGBA', (box_width, box_height), color= 'red') +## ImageDraw.Draw(space).text((x, y), text, fill='white', font=font) + + if rotation != None: + space.rotate(rotation, expand = True) + + # Update only region with text (add text with transparent background) + image.paste(space, xy, space) + + +def text_wrap(text, font=None, max_width = None): + """Split long text (text-wrapping). Returns a list""" + lines = [] + if font.getsize(text)[0] < max_width: + lines.append(text) + else: + words = text.split(' ') + i = 0 + while i < len(words): + line = '' + while i < len(words) and font.getsize(line + words[i])[0] <= max_width: + line = line + words[i] + " " + i += 1 + if not line: + line = words[i] + i += 1 + lines.append(line) + return lines + + +def internet_available(): + """check if the internet is available""" + + try: + urlopen('https://google.com',timeout=5) + return True + except URLError as err: + return False + + +def draw_square(image, xy, size, radius=5, thickness=2): + """Draws a square with round corners at (x,y) + xy = position e.g: (5,10) + size = size of square (width, height) + radius: corner radius + thickness = border thickness + """ + + x, y, diameter = xy[0], xy[1], radius*2 + colour='black' + width, height = size + lenght = width - diameter + + # Set coordinates for round square + p1, p2 = (x+radius, y), (x+radius+lenght, y) + p3, p4 = (x+width, y+radius), (x+width, y+radius+lenght) + p5, p6 = (p2[0], y+height), (p1[0], y+height) + p7, p8 = (x, p4[1]), (x,p3[1]) + c1, c2 = (x,y), (x+diameter, y+diameter) + c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter) + c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height) + c7, c8 = (x, (y+height)-diameter), (x+diameter, y+height) + + # Draw lines and arcs, creating a square with round corners + draw = ImageDraw.Draw(image) + + draw.line( (p1, p2) , fill=colour, width = thickness) + draw.line( (p3, p4) , fill=colour, width = thickness) + draw.line( (p5, p6) , fill=colour, width = thickness) + draw.line( (p7, p8) , fill=colour, width = thickness) + draw.arc( (c1, c2) , 180, 270, fill=colour, width=thickness) + draw.arc( (c3, c4) , 270, 360, fill=colour, width=thickness) + draw.arc( (c5, c6) , 0, 90, fill=colour, width=thickness) + draw.arc( (c7, c8) , 90, 180, fill=colour, width=thickness) + + +##"""Custom function to add text on an image""" +##def write_text(space_width, space_height, text, tuple, +## font=default, alignment='middle', autofit = False, fill_width = 1.0, +## fill_height = 0.8, colour = text_colour, rotation = None): +## """tuple refers to (x,y) position on display""" +## if autofit == True or fill_width != 1.0 or fill_height != 0.8: +## size = 8 +## font = ImageFont.truetype(font.path, size) +## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] +## while text_width < int(space_width * fill_width) and text_height < int(space_height * fill_height): +## size += 1 +## font = ImageFont.truetype(font.path, size) +## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] +## +## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] +## +## while (text_width, text_height) > (space_width, space_height): +## text=text[0:-1] +## text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] +## if alignment is "" or "middle" or None: +## x = int((space_width / 2) - (text_width / 2)) +## if alignment is 'left': +## x = 0 +## if font != w_font: +## y = int((space_height / 2) - (text_height / 1.7)) +## else: +## y = y = int((space_height / 2) - (text_height / 2)) +## +## space = Image.new('RGBA', (space_width, space_height)) +## ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) +## if rotation != None: +## space.rotate(rotation, expand = True) +## +## if colour == 'black' or 'white': +## image.paste(space, tuple, space) +## else: +## image_col.paste(space, tuple, space) + + +"""Not required anymore?""" +##def clear_image(section, colour = background_colour): +## """Clear the image""" +## width, height = eval(section+'_width'), eval(section+'_height') +## position = (0, eval(section+'_offset')) +## box = Image.new('RGB', (width, height), colour) +## image.paste(box, position) +## +## if three_colour_support == True: +## image_col.paste(box, position) + + +"""Not required anymore?""" +##def crop_image(input_image, section): +## """Crop an input image to the desired section""" +## x1, y1 = 0, eval(section+'_offset') +## x2, y2 = eval(section+'_width'), y1 + eval(section+'_height') +## image = input_image.crop((x1,y1,x2,y2)) +## return image + + +"""Not required anymore?""" +##def fix_ical(ical_url): +## """Use iCalendars in compatability mode (without alarms)""" +## ical = str(urlopen(ical_url).read().decode()) +## beginAlarmIndex = 0 +## while beginAlarmIndex >= 0: +## beginAlarmIndex = ical.find('BEGIN:VALARM') +## if beginAlarmIndex >= 0: +## endAlarmIndex = ical.find('END:VALARM') +## ical = ical[:beginAlarmIndex] + ical[endAlarmIndex+12:] +## return ical + + +"""Not required anymore?""" +##def image_cleanup(): +## """Delete all files in the image folder""" +## print('Cleanup of previous images...', end = '') +## for temp_files in glob(image_path+'*'): +## os.remove(temp_files) +## print('Done') + + +##def optimise_colours(image, threshold=220): +## buffer = numpy.array(image.convert('RGB')) +## red, green = buffer[:, :, 0], buffer[:, :, 1] +## buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black +## image = Image.fromarray(buffer) +## return image +## +##def calibrate_display(no_of_cycles): +## """How many times should each colour be calibrated? Default is 3""" +## epaper = driver.EPD() +## epaper.init() +## +## white = Image.new('1', (display_width, display_height), 'white') +## black = Image.new('1', (display_width, display_height), 'black') +## +## print('----------Started calibration of E-Paper display----------') +## if 'colour' in model: +## for _ in range(no_of_cycles): +## print('Calibrating...', end= ' ') +## print('black...', end= ' ') +## epaper.display(epaper.getbuffer(black), epaper.getbuffer(white)) +## print('colour...', end = ' ') +## epaper.display(epaper.getbuffer(white), epaper.getbuffer(black)) +## print('white...') +## epaper.display(epaper.getbuffer(white), epaper.getbuffer(white)) +## print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) +## else: +## for _ in range(no_of_cycles): +## print('Calibrating...', end= ' ') +## print('black...', end = ' ') +## epaper.display(epaper.getbuffer(black)) +## print('white...') +## epaper.display(epaper.getbuffer(white)), +## print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) +## +## print('-----------Calibration complete----------') +## epaper.sleep() + + +"""Not required anymore?""" +##def check_for_updates(): +## with open(path+'release.txt','r') as file: +## lines = file.readlines() +## installed_release = lines[0].rstrip() +## +## temp = subp.check_output(['curl','-s','https://github.com/aceisace/Inky-Calendar/releases/latest']) +## latest_release_url = str(temp).split('"')[1] +## latest_release = latest_release_url.split('/tag/')[1] +## +## def get_id(version): +## if not version.startswith('v'): +## print('incorrect release format!') +## v = ''.join(version.split('v')[1].split('.')) +## if len(v) == 2: +## v += '0' +## return int(v) +## +## if get_id(installed_release) < get_id(latest_release): +## print('New update available!. Please update to the latest version') +## print('current release:', installed_release, 'new version:', latest_release) +## else: +## print('You are using the latest version of the Inky-Calendar software:', end = ' ') +## print(installed_release) diff --git a/inkycal/display/__init__.py b/inkycal/display/__init__.py index 35b3f46..e69de29 100644 --- a/inkycal/display/__init__.py +++ b/inkycal/display/__init__.py @@ -1,2 +0,0 @@ -from .layout import inkycal_layout -print('imported layout class') diff --git a/inkycal/display/__pycache__/__init__.cpython-37.pyc b/inkycal/display/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f19d538ed1d1d418614198d29b5766466b7d0d4c GIT binary patch literal 208 zcmZ?b<>g`kf^d(`aaKV3F^B^LOhASM5En}Ti4=wu#vF!RhA0L`hE&EBre;P)h7{&t z22GZij6i8k##?MTiIw@KB~_xCxdr(}C8;S25CMhcoW$Z{KTW10kb$>Y3yL!HN>(xy zu>g5s;+KhjMt*LpenFy0Zfd7>8wQD8Z(vKRlOTbS*p6j8HDD-3Vi)Zo=wLMDu11=j znUb@zv|R&6Y@oRm$R!05^k4(I^xRW!Mb8P)Lr-&U03){oz2(&R$XQzw(sJ zkMBobU04VSv>U(p>u>+GK*+zaGiweIR{-=+0SOWjK?TccMBgRC5$;1G+y^XTf_*^x z-sGL|R9qf#{m)Iwqwp~SBj7>jC!gLd zvN}uhY$AD;sAME-skCv7u4I}GGAX#Ml1eIcn#@BE?T(hV_iPTYOKxZEbCKx^Y{uVv z-$<{kq?SD4=~&n0=;`R)tlr|oY*!ZiZYF>R^YSf;E^oZMTUjj|Z!=HQosB-rI0Rbu z2_*#R`wZy&mi{%;f3q|WXXlU(nx3pg{6?G=@$2#Kg*Ywqa;%z#L)>QhK)00$pRtmDZJ@ygy5EhK zH{r-bRgMeMZW?uKxmV9LSZu8uTT?{~v%WE6)T_6Yl&x^A$5mCTx>;%oJzK@AR94i@ za$Q!jmT8@pMQj#mdZSE;Tps0amNhu<2zSXIX~xxkzj3w92aRW91>A^^ZG5Y$T7tZY zj?eTJ*+>=|Hr9V3>UkLY?!xL;Ig+bYwt7wK9Z+o*i$j{^t6BSR=dM}{u2lO?5QF`* zI*yNF=B)uDzE6F2k}-Pl3!Gy#paBagjMBgfC<7Y5$x^%8Y^5ike}?VL%TK=k3fniA zpWw2D{Nr+p#RePZJ?1=wCDH!`^anDaSofs z)PN~H3EY$90fli^T%FP>I{};%M0h){8q|*P+pGpzBSj$NnU(*TY`XyDBmvA2$F%ky zK}C7g0ZxEpkJ)3W3WggP?i|V<;LaIt2wZBpB82+To&5csJTFrW+d;Mmr2y(cDvKJc zqIIe%72qn9y*6}s8&D?s<;rk{udQ6*Z}SUlr}^1iGTD_}j;ea!OcipFjPsiBCixiR z-^}?y)=*BW)x$A|6=n@lGE-?(DhWjg?ggvG7E~n@yoWSMp(2=ufArT60Q4fD8mc6o zxCfSp(^4ojjb}1X;cy@Bg=gpOQ}rA$O{A(H0o4R=<;iGMB$wWX-hTkd? zZRTTg8k;tqyc`bU5^X#oH^;-qhd00|sns${97lxi>3hc5P4~dg&Eny;o5jN_G@b?< zOXHxKv(Qhf2j~p|eFhNm=NVQ4A1eXt8Je+>hCl~&5k?RuKm2}2rf}VuhH-?j4xm2- z1UXK3I;vOWolhUIk7#jimiKV?!fRlg4cI+ayaHSoUIAC^jlAs|ZV$K~aBlCYUtKTYVm4V}tIL6Z;LxJDGkjCYR8_@X!&Y7o^6RmrW&gr`w z8yh!Z!&A8KPY~h5v_FscL;#PpN;C(H--2M3 z3Z8@JA9Xhqou%t#F~}f*uU)@-=Uw$<+yp~Ry^6>j=ovBrh(Uu-XpLzQP#3~&kr5i6 g0eDg2C}@lkYBn~E?^_27j#c~;-aHzwB9{g1zYc>#tN;K2 literal 0 HcmV?d00001 diff --git a/inkycal/display/drivers/__pycache__/__init__.cpython-37.pyc b/inkycal/display/drivers/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f3f6e44d6d22cfdd75d791c49609631109d5e1c GIT binary patch literal 139 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 literal 0 HcmV?d00001 diff --git a/inkycal/display/drivers/__pycache__/epd_7_in_5.cpython-37.pyc b/inkycal/display/drivers/__pycache__/epd_7_in_5.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..033b27f887fc6f52a79ac97184042c6ce8059818 GIT binary patch literal 4287 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 YllD!WH?w13|2Az-u@R`(4U`A}1yQj;W&i*H literal 0 HcmV?d00001 diff --git a/inkycal/display/drivers/__pycache__/epd_7_in_5_v2_colour.cpython-37.pyc b/inkycal/display/drivers/__pycache__/epd_7_in_5_v2_colour.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..862aa0e2de3647387d2eeccf32eb444352614a92 GIT binary patch literal 3751 zcmbVP%WoUU8K2qbkRl~oVO%9`TEajc1QIDV(zGb@P-UgDfrKz5w@MeTHxy?@uf&&T zS20B_fdbB{ryK$l=t%q3EH9q`z;LTuHPLq+NmEeDnU^ztL8; zT4wlaXJ0y|?Tt-`xE3$^REN{qFv`g|iSw~xu&qK1CsrkYE z?G6b>AGLJ(9sHs7jDu{yEAB6+Y*}kKP9NE!*vGtqX-{}#te-fSMYwqmkYdR72(-H*q_wH>!xSQHf zLK#0!%QSNLgL@y|eULhjgRuKJhE=U7=pFoxsbyULpUsWO!+x+a3OBZc=qMhJHp0Qt zX{X)WkYP0HwNE#s3O@@}v;klEKlj6d|1L%vrx>&Luw-*&DX(?&uJuJPdMYYg^=8L$aJ1OaO-0grN$G{DU zw1k}*vzRj>I`a3NPx<&(!lij+sIBYgk8ka<*gSKjj>=i|O)9|~_uHdDH7HrLW89Pb z@CLZ1o4UPWw;K++sV#&3ldeJuWqB>A%wtmpZK+%uuhX8~rJhCSEt)~>VC@zrwy`pz zz-0)|GJHw2HcP?zqQVrE7lkSQjSiXO9QFFhR5fYrYJ;4k(m`?5(wT{@Tv3c&=3pwl z4z+g1nbIK+XQJ-T#+>4`xxpK9zepUpR{fY3Ute`BDy$!4K6(p;!K0QT4Dzb})12;K9m{(}jftQ< zpks~0ylrip6YU2XLU9;5}td0Y9{8P_0X7FQ#&!w>~l7C zV&}}2R$?Xgxe4D>!f>R?-xI*zp+THpq2povuUI7ZQ@+i9zcY2mAE9UI-sV&!P9hTb zz<2?#0qmM2fKVEt^XlvecsYoouA4BxJ?I9_r5qOQu;1gcu|L{0JPM>0b-0Af19u6Jhw1X<8SeaF4x8W4&@kqgrS7bI#&M-26YGVO1V3J zGe*3|20VfX*P&Hs(xgavY9Pv15wWN|2~W%y7_B~j1^%GDmfm~&JNDLRdkxyVlJDN1 z?bgw(V@g*yoG7TK>9X&(vKl1J3;hPTj8HnG5~6fLDCL>|HVxb+QaDiQYCuX~DhZZ$ zmsi#<+V8bHN8}W^P6aaM;UWsSv-jE`aY{s&5(qT-D#}2E*L6vlEKxC-tt)VVAhEFV z7wBBF@$27Vr{yUs1**HYXkp(x-@ng3{k%JVG_!1u7F1K_XmMd#(NYBP7nW7))YTPw zlequKyg8z(U(x=>$KIV@&{luK%q2rEYeNz(^Zd!DR~2Tw0;lBO3p0WPuxfxrIimo? z2>u}=4sQpm2qCMLkvCKWB>O7}UPk(DKp1fmB;FiTf^mok{q+yls22pIzhGZPVMf7_ zBSkK|p2dG4^m-q(`+@JLWgm4wLzSOaeE;~Q-OFc`4(kQz(h|KvV!cabiwI?@`ZbXY zgKCbzQ&BFzf?q_ahkp=CLI?+cCGoOY6ID^wE1HfjTxtFuQYG3+h@~g5J2w^Yb>P-f4wjQ31YClrUCERw zlHR54lb{2Np3%n^1&TiF9`i5lYoGit#hOU z&f>4X+q+^If5*=1Dnnx*iu@A@W-t;NUS1QA=sxwRu1(MU&|s9AuMB3MQqO{(#Y)gC zL9fJ(k-aE0`;3S~Zk-yQ6WhFm_3w5-wd4oIL zJ*B56F`kb;0~|f0Re24VjBX$0>Y~uthaw+?;09s@e8e*uAY!IhVwPvK60=_!UOE4+ z=dcQMpUms;Z9ynI3T=ribC{H{d(3_fR+} zc0u$B2)s-v7L)@h@<$*Tz=@0#qHA(O=X6BphEL~YVZsbsqk+Kv@=VbD!VooRe|fv}G#T?w8g}k+ z`7BG)P8dI%_JgRyLYYRvw8KRBf(zNMR_FSrGXzMVRrQ!H~qgHppzRh-X8quQfmkYXqh@0?o1lb%uU)4yP2x zh=yu3Vh0p2!l~DA7G%Li7&yh_u)6m{qw>CyM`I&*+p^@@IAL6wcMm>N zRp`A8L>$IL3A@F!Nvg_^dXN3X2VG@;{K;cw-#gG9`=F<5=k6!H&vlDF=n8G#+Bi7& zWy(48pU#wpZ*P&@57A)@kx+%XNyK%0?!%NsUoyxJsgbM88zPm=t`|MB@XU5&--UOe zL*}N{t(C#)0u2J$b7|nbUn=zNGh^v5R(Dp0t!?s{%{u)kk-T~P(|4L5X~-~|L8fWm zY^KReE}D>TWhOh_kHF1{?cx~s`>u&Nf6~T=Rm}F5Yl&1 zG*IAPh^r`&3B)&0d>;j-|3awfJ;lT(FgJYf0x|PI+!J>x`aQ`Uz)l!BL?-C!@;3#Wp^1XgqI!hB0n4E@9$s$j$G^d*zz0Z61 zdmFCrXCjE@fQuC}Jk?%Ah6!z2KvvwMe}mFz=kWe7ln$n-E^3;cD7b^!y`Nj5o;$@2 zTp@;m_$G>Pq0k)D#1;}wA-`ji&W*Rb#dt>?C~G?#X#$wT(-MzZn@sJJ*|ma`eI-pl zgcqFCH2V3%#N}-ewza?$k-aA9yk9Q#o&O?XSHuqhl=vYEeU!Pj#XN8x*v8|c1yB7G zt07||ok$E019Fl}m+Za8VoUNC_`4$BLGdn%^?{+VkN+W0x8Z%CLb=G(SJpi33e=gk zI=~s7qlxUm?^QNP#CZLSHB3YJ85_TQ)F0pa#ZPaqDHQ2^O`#ZKLkQw0mrvlgX#u}X zXGB_a@)!Et!jz_n55kpj^v>kVFnbDr3bcJwk>d-&`lWj(0;cIFg`k0@0MYaUl9Jh=2h`Aj1KOi&=m~3PUi1CZpdHNt}4nG)R}xH8rM;xp8{~jRygBokG)T|)>Ca&)|#!|R=CcM zhYC0LRa^HAZf>gLJe1gCfao)Bf~Pt)tvTiGBwVD;sRA#u|9mIRnq?l9DKnk*I)X!ak(BfMRhCu*h}5 z5;p+Lab=?#*SJY5Hp;wkNONbn1vtx#fOEVASm$NH6TAZ0;8nnryaqVWX8;%C#eF() zDPHEY<5Sql$YnkUUh0E$cLG25-JU=2noqNpYhj?RLe>o2q{hy=AA6~Bzvs3$A6I}H z%&&!Uc+U-@SMPaS{>?DpUJ!X4O3)*0+Wkf254Zm|_~o_kZ+=bR=hss6E+)Bq@26(; zVA~bm+XM5~t>)dkcT?312BlwI8a8{O%RRoG(a* zaZ)4d_xw23CE9FwC5#hf|%ZwAgn!_k*spd)(Yhi#wjT8O5%M({i*P?l|7Z9=5bg zvUYsXWmo#b$C;B6?#7-I#d|$)({qFFHV(F{T`zW`y(sp!oOqC0vA^XF!T|D?i&GQV zVattM8bs^$I(8+(P4L6Ok<`~Fy-W*1DnvF~5JsPGedq* z`at0-j9DAJa@P&E-Cl368F_It0^iNPD_}CQC!%K9X~ye*G_mSB85$40s2PND)7$l< zc&%lm1`K>FBF4Gj2IFY~d%1Be1c^YL259Nx464-J+;K%WO0`XIFEu`Pd)uCrc?Oy^ zMZ@Cnpb<&ms?}ME)zlgrG@cOCpr1@I~b2t07Vh6u5S%4fP2IuPXJ8Y!7ZUu;Zgi&!pba z#45a4cyE87&$1-GkoStRX9YE)U}P*RadA-@7I+Dlt@tRTT3J>e=}93eqSoX(Hqz(u z%AcYWO4&|Haf&IAjKo|}lJbJ`NKHzQ*nx7u4%7qfK<^m5%qtJAxRez4*)3XM!I*wv zbTnSgMn=HOk!oDtsDQ32uN|@>QCQcIWwnz>Cj-xr|N^tA1JXgHu-Wk>qs3ol1fq^H)*#c#9PCY&>K+GL5=Nvl3J*(F)#r*hm%d~W=F0*!2(Pv(=w zMI~8~5N+5B+6ziY=PUd)KXV9&KArtSaw=KLHH1^SE;V!ZP>1G?@ry|#If-3q#mxAn zV@dKR_&Obz$1lS+TKqYjdtu`h?0)VLln>A3Bj0*v2eWOVw(i}IKpN2mEL3e+|z-VlPGx;*^(({UX1 ze)kReeQ~XeaeRNd)>0pncY92Wryhnp3n5ORM6UyM;r>3lApMEFKOe0O)e-J#0#CD` zjMR~qz`sDo>Sx-6*$4W)%;g~>GX(iV#1HD~rIhWZY!?q+brQu=Koq5wuJAcR6GStJ za-Qt%rDY0boII{7$&QS*jvf4$r7Jx=r0sC4e^n5{BomVj-uMbb3KgUT=oOX{Xbjm%8L^Dl5&Y+8>XcVW5VSzzN}1Er#COpW->2@}79zQI zheF1jm1FY7Wz5({$>^q3LX8K)0)_%wM6FR42|;cdPOqXQ{R~KF8%n}TNTv=MJryHmL*?pW&O}SYV@0`&t#MB8*LAd^HzQj2JFyHx z#4@2W7R3*;wtU_e(SC}fL!fd^h)ZZsB}1@eKeFz1>AzDKq;+MMu+|jMH#&IjgN#rNZYB7t9~QD#s9u0$VAs#DpCc%*GmiGcHHv= zGL5$}AXhvld8dXnKr)_q;0K*>ps)6W?x36}B-CkLN;RYlqE0L437jCXAeYN5CDj6N zM>Gh#1Yp-F2Z4P~O!_rB)fFP#!PIa^oT8~@8>uz9@aYVK&WTk(kAjR#QfSw&ia*(s|iMv@)M&7toaS(5OsuZ#Q(Y#WGwp35kd8WJ9N5aXcAd5_+C9&<_9iV;xZK~4(h>Y)_{wC1)vzqY?)a|q-M1mQYoZUY*wvl77{DM*0l=L)jIOi8mnkE z^eU{OR)ANTicHl)zN%v;@R*h1vOKn?TdaW{7N^(}o7HF8EY7R2dDg&h9;3^Y!f6e4 z7B$KzO0mHPXTQdWthLINQOY;RNlT8C<=n_FD~^LdL3;T}p%-?$u#dEeT;s=RdxD-A zs>O5QUc5|zEML4rV2i-9OO>rA;mBLlWsm4T8cQvy){<4i&yXWT%a-d0jx#tTwu1mC zHYpqSgC-t+{5jLZzYdfqd-A`ZRy8dk9S+-$V=v~MuEM6cCssS#f&33567SNvq$qiW y2&gqpnMIkpM2x5sptnZM5cmq};iWVAT%~9zA}6S;7IbITYM&^dD6d=4NbRHP18onLFyFksd410x zzqhd9F#Nt{fBDm2EHd_I>YV&+G_Ii}zXB1g&jc4n$lsyb42`kbH#s|OqGyFx-@3!1 zXDlWwdye`(XY`jtN7FS3W6b`sWY)@`R(oVRb^cE_=phSD9z%Dols9lmtk z4`n2L<-VojT{+Os^>}iqg3*q4w+~%^AcF+Nd4yN7F3_D}7}srI`3C zk;)AcH;T0zM2YspP>QGAw?gSB@?^xl8|aTRVX&V+&dVDPY>vlCqDy?YbeRLK_u&Q zD8(S9e9Zc$FolKM5*1NJT@es6>Z+&<2X##}AhKrO2q20|mAJbpIKKku%==a6zsq^8>GdGNV zA;ngAB{$+Ew@tdgwpf!N-_xpom<6lbr46xV1#P~0{qn-Nw$?Q zZsRs16;C7K4dO7Ks(xb_NBX2=Q-?0ouiqN`Bl#w}eOs%k)Q1z6N$qf~iXo&{wZ+?8IZaF$p$q z$Yf8)lMV6}7{bO})ElK6B`19{5J;Sa5I`@UcM;np7eJV?#;d$(R6(oU;!8!HuAI|Z z?_|+{6h=QXuL{Z!P?BpP2b&o`W)Im7_I~d@V`j`uGOk^|S5?nzw9qq;5McHlZ3>uS zW|YRV@7!U+fGwNp3qSOuDID|AO{8`cyw8Q>LUUTG#Eplp-hl^~?@E_Q-y)W!OUr{G z(Ocb0UV$Zz6MCfx26!bRfz-a%O0D6NiXPpy)HPdQ14V(N^d8ns|=Nm+xCPHR* zUKCjO3WolzD8O?n3bGU`MG&f@0c&8;Gf$tniFZKHZag77Ph9x_V8+33@qO*j+(+>>+%Uv(NZGyTkVXa);>)S+ReH(?#p&#&?-$i_XVv)*3OfXs}s( z);V}HbFkah*@9lp7U<1cO)Nnsi&;mnW$hylx_HD_*{f{U$vT<6hZEFR);?a!7U$<} z-CuzPJ4D9Qb6`@#FdlaICK~ad8z;hO?}4ZVclzCR(uiOX=mCGLp*INQRUy z3Vd@epgkfB&ds-Pey?jNve3LjSx2cF+K8$eLwD!3#z;iTRWwePzLQ=+5|7Hn=4zQ}g!Tj(J$-UZYG9;2 zcMwlQQQ$KK0VVyxjuh_ZbBeOev;p#`kA8=kzv+JX%53XY4kQ@5(ruKa1ybUt4EBD+ zDXW~ZlFL)tDp)C~6e3%iL0m8}duC+D=jMKs1a=l~x~6&&tT{i-`N7$+B9W_?K_ZkB zp4K9aUhc+=-BPPJiAHH-j*F<%y_}ms zHfo7j31TQRQ?&|`R|>F{n*hg(f^m5x%1Q)x@`|G9-(SLVsUie<{{ue={q0Z|2|TaA zaWIfY+EDnuVYzb#&w-~1=I1rsUj;JBtNvtyFNY%O=M^335R=JtJoeS0B5UZ^wLH+? zUFA=56CW9XfC~6Qb!cZZ(F%yn|3K!|!mg9>N2Z3s0MSSl=mnr2-0FP(SInis4C|05&5n_M63fgjal608rBRMQ+iR`xS8MB(Dua zDHTL8AWxSTWRHFy(AFt5?$CQ_)E=uNytIM8AbU9~}^06MMFJUEpud=(SPTOiC@ z=j+@ymw3mt_zKYBn%S||42L_WZCHlIJHSLu?pn6#ST=Vs(*Yk*+rldFw}6|7t7oq8 zbp2l&?N-+&+(wujxXtmr66vAc^t^ol!*WJFLAQQ}$g@PgNrdit&SeVhQn)C^NV2;E zw%CTT>!#I+JH)Zj$(p}7JfR*vU^ZIM^ zXY>>dOsd{+8WorwMh62H@wl)r^*Wu>xHX={N*4}QqEHI&h4~iOP(PwBAyY+RPSFRD tS|CF4Otp!efw9x4AtaOnx=L?JD4{i8}kNn=hRPqs4r7HPM`75XA-U|Zc zZlwYgx_f$hdS2Z#-Ge(_T`3Kp-kq=B|Kgga{Tmgc&ruMI_<8?sXd2UOjp@uN>1D$< zbfV3YX`8^YQtU}kj@xm?PuK~bSWDU#i`yOerSR)y2|JBn7fagR9Ncw7*vxbdX4yR) z5-6{Q$Jj|lkMdzY!ZQ8Zu*Oau)9f*RjE{!p(I_zC+oKh2L1Y4#a@M&Yyktit1bT;X&4oWc`) zLgDlLq@CqiK87|I_yvV8LPvJRurC2$0KN=eRGtLR3~7p<0!?+(A&=-8&=-McLmts{ z=%3oo1Cz`ZVA4VKRnR1JE#wh>9pfbV8^9!U6PWlfVcf*O1x);xfr)<`^C12LF!2|G ziT?^eulRR>iGLTE_)F|0JHyVhadwVPu=6a-F0hO261&VMi(U3THpQkt)9m|fhRp)M z%5JkcHvgG!KVVnvhy1mj-{G&bs~G>oUBiBZU9*=zyvpC$dBm>o>dYF^_!wXA*Vgqf z4e?L>A+xS&%~m&}R$m$)eDzdgH`z<*;Z1gny$t*of9r(?domwu*@gO_r=Ie>T;(ED zuCQ8(XI3gA^Uz)2ynHub;x5aJj8|Ea8+pMeQ>iDbJX5N@ZJG7AoaQXvn+knRhF-uwM0t{xkUv?p4ZA zSz-KCDs{I~-4sr7Rc4-TX7UB@cmSzAa@DNvcUOhu%G^rLEl}UfVCfg0-1kklvazQ_ zratu0b)=IoIiBMdGaJDiCnqPr(=!k@F*6{mly{4@e3ARHrHadICR+a2$J-(PG{ip( z@lQkiixB@j#D5avKMwI%A^s-Be-q-r4)H&P_-{k}%WR)*PR}raSpste<_X*Yn3*O( zwKG(4g^JTNF2F2-IRf(pZUD?o6QJ4|s<<-k7Ju<``uu94_)-5iul{TOtA#xrZrUDA zbuS4}#1EhUStzckPiir2G7u3b7sIOYh;uO{MrIa^bM*PE`AFt^MBG{|{`0r^yuP@& z$IwB6wuL^i@OKy3dj~*j4Vi;Ze5J`~j%(qIXkn~TzH=r44Z z9MZN-@GS72fM#|ava^TBw)2YC{y{MdvT%x+^Te6Z< zy-TR@dI2<}SMS#?eA5Q~#29$Z9V7#ypW49-ASCt=J8&BNXpG=wAsV|GBh2Dkx*KDO zExn;*Pm;?|1X;p&`eo4vY@32JKmH60vYs{keu-7*xk9B>sfnCdt5z#QiXjMQ4ZGjv zMVvLydE3Gz0%0}(0E|N1x1d+aeS(r_0ciS|o-ks%I0d>u6QB<{0{zrv-Ubm)=28RZ zhzVpG+77OM9J(0CH z)?>sfKtz4c15)%v%jJEOh?eThg!nc2?z*WcW5?Vj8?dPKWn!~e##oFZ7#U}A2LGiP zM;bc`Ns9#E-?4Tzc)hXHp*V(2?R4&HKi0m`*3vAA_`_N^MS~@y@-X}x7M0vG!Rcz4 zDUEe(>pR^n1%0t54`)LQKi=ebw(%29e!7jHY#2K|jo4Z*bXtmzE4o9`2}P$Aom6zE zJkrp2`Y`jZ5kya0X_T#&au>=SE#+>MQ!V8llsj9>z0h_P>q1%x_n|f6qi99AAJT*e zuv-0)9oW@&^_@XE^a6NmFys!mafjpxB#-UV_=Xids^}3#k16_CLti^ivF5hUdPlS$ z>*)VDW_4sl+Zt_)JrQ*lEqS6b%8pUI$VLY>$c)L8($6fJSt1$4 zE$I8W`E%P5YybyA*Yr<4vCBRhe1r{Q|X!9An6pMiT4+_Q>% zChYI)#&I^jbG9+It6zik=NkG(xNMG@`1Vuw25I9*R~CLyKR&cSkpeN=>mGX z&Z70h>NmRNqM9#qnwI&Jrg*67l|!12Lerf?n!4p(rK$UnrbMV|>5!&=Xu5YuQ;)o_ zH1!5XZ~c;gP518*-W5piFG*jH0uOKHE6G_DH_j zID_BWl^Dh9bUrHXTj0KRi2F9UZy(}52KR9*S3XgG5f|Ue-(7peEV(Sd2a9_u-vLy# z>MhA`ZJWC0s&V{ZQu1B-I?i%qYrHYOW6STemxplLz@CG?65;|RexM}IHO?uC+o439 zEzlkCRC8ralkcska69~&u5C@|TKx%bhQ;uPcqPPl8WS(*o`~*_ag2N!em{Yg#lDCB z`&-6?oM(wGZB6`e;vMZ>%{AUZ8Q1W=`uyFB>*5xrSnpAlyI~Mi&Q~+9y!kMbcUfkI z%ff1uFl2E1=Ix#^hKgb)Uwh(rBDTtTP=(;De!D$~x|s>Pah^MJGgDaQh4oA|?|GbE z%Dl>5E)c~aD?un&5 z7)5S&+^ceq%mCq{uOr%6zUS-reB&|DW1y$L{uIoodzVQU-Q#V+9L2Hp_~D%ZDB?~RogB2XLHa9pN3%^}iw#`87AEz*yJ%RfZDF*)t) zIgGDsUl7UJet`B}>K2kSiKMW4+o!%MFGy&HUO!z7mWbzq1-ezLAb|C5PwqFs0(C=N zP&DHF;SWGjELk_pQ=XuS*k-x9T92RqXnHH#3}0xL6t=6GQdsl_Vm-um2wj-5Ya*;b zSklneOdNHD5wsw+KxtINMME?=4Z`skdOz_H-=TyUZ;;ZE_)elh$w-1~fs-$EGXAX? zvx3JRmS$bSGtSqROg5m69a+Ss3DfLbcK!u6AZU!v$8W(YQqz;O)AMsTuFcKVyQU|n z=jN_ootwEad-aCezq&a+c_m07KmDT+hY9JYUx($L5dSj7e;VRnhxnVUrOZwBs@77O z;2!OSpfo^SB9M+i*7D)1(u@(Fn%Wryq61G@@J@_$%uB(20rBta1-$ag{ zjoUHitgMJT&|-Jc;{myh=Udf^hYY>qiW{iGhN@mEi4iJ(??xB7a;4(t)KkNMpu(d) z*YrNb@%?&QKZby|e(tv!OM7#2l$DArgPio+|6Ir#;vtkFpK?m2+=j#CYOpGw{4A{U zJ5?1jK)gbPb$@?9B)$xn@GrBb(j#6YWtON&HN8-U$bkEuJ|1EoC!&Lmg7s4R??-m3(q#8R$2`#Uq=*QJ)>qifqN|RN$SRXpTq)dt`1Q~ukc|Y%z zI6GAzzK3lTc$#S*bU}J}s&2i_^IpZhnL%%K6e+NreP3Llu9NwylUwJTzFFBQ+g*;A z!?BsGxFyHsc4wi)^CECsKdxSA?B4K%&E;i~^y1A0-c0*$chVMx^>&!}sgxy$ql6do z$b=WD(?m@c=;*kpMj*IKp2aiovAMvE@qPgs3+tyzGDju&p)z5=Q!PkYrGkDsTvg(Eet)#a z!I}lF?8F9k%9VafaXmb2l>FhAS4HVPvcu0ZDlE`n^ zF@m_|i6QROqW84~LXnll=sfVP&bs1%WV3jmD38`RCkDsY0%bYsl^hXj0m7@52JGlDlNj+XID~z}sH) z@IfRcYgIcLokdo3{@50sN4a9YYMa;}0<#3>2+Z5*NG?Z}t)wpX&jp2>>**(D-sLnic z1bZktJ{b23!cqI6D$;@D2XCBqcl$A5$EYkQWVO4?xVXF=-c;4AmUuwS4svA`9Uu%L z>r9i~(P1lO4#%-7;iVsRS2O};684d{qB1)@ctXRqrvi~%=%5*hsE9obM|V)+4bs(U zoYrFqLFkv#Ei+|Wdbi$d#LRwun7(EjK?=TRuO5CO5q`}m2FA zk1+kZ&2&e?>;s)PQz+vLNz?&$L%J8*NwQhnlprlMuCN|j6ME|ZQBqnB8YjNgn?7uG z2b3BCdN=y;I;>`|#!52c_pvg~kQ+4qY6>4$DOi`9`tONCULF71DCw4`%l+^Vfh1BS zwDr_Otu8)C>ntAA-}aNa9IF&^IXjiZKS1<&p34c6vg6fF6_?=7T5*asO0y7OE>Tr7 zyiV=pQt$>ZULp1o^)TNSBlHBU=FK6#2SM?az%v3ifk6O!xc!P&x0p(a#G6!YK6?6w zSSD$Uz#{_h5ukHZY!dhpfsYA%Lf~Bj^p}X;@ga^d#;^o-$7V!|9MvP+!EqU$tusw( zwn@#2k4UaTV2i*HsYMJ?z>H6DR?&0f`&9Qnf!`y*2;>QH0g#d-6VwJ!e0f)dk zff9i-0hd69K$XA;1Ox$(0R62XY6PAW*dRdBjtbJ$YrMKvZJfKV^qe{mZ=Cxm+LjbG zE;p;(ww5ch@;L7X;fjj5{UjnPY7rO`*~o9SChJxZ-`*x0^mYJZuvoI)UOf?u!H2_d z44w+F>ej7T0{#+w)3MNt$&*d^JN@>*L5X~u{wo1r{gh`YD)9Q>0O8e!)n#?0x?>ne Q%%Fs3(6D}_J+RFG1?ZR literal 0 HcmV?d00001 diff --git a/inkycal/modules/inkycal_rss.py b/inkycal/modules/inkycal_rss.py index 763ddbc..52bbd7e 100644 --- a/inkycal/modules/inkycal_rss.py +++ b/inkycal/modules/inkycal_rss.py @@ -5,7 +5,7 @@ RSS module for Inky-Calendar Project Copyright by aceisace """ -from inkycal.render.functions import * +from inkycal.custom import * from random import shuffle try: @@ -20,7 +20,13 @@ size = (384, 160) config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']} -class inkycal_rss: +class rss: + """RSS class + parses rss feeds from given urls and writes them on the image + """ + + logger = logging.getLogger(__name__) + logging.basicConfig(level=logging.DEBUG) def __init__(self, section_size, section_config): """Initialize inkycal_rss module""" @@ -31,8 +37,8 @@ class inkycal_rss: self.background_colour = 'white' self.font_colour = 'black' self.fontsize = 12 - self.font = ImageFont.truetype( - fonts['NotoSans-SemiCondensed'], size = self.fontsize) + self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], + size = self.fontsize) self.padding_x = 0.02 self.padding_y = 0.05 print('{0} loaded'.format(self.name)) @@ -66,11 +72,20 @@ class inkycal_rss: im_width = int(self.width - (self.width * 2 * self.padding_x)) im_height = int(self.height - (self.height * 2 * self.padding_y)) im_size = im_width, im_height + logging.info('image size: {} x {} px'.format(im_width, im_height)) # Create an image for black pixels and one for coloured pixels im_black = Image.new('RGB', size = im_size, color = self.background_colour) im_colour = Image.new('RGB', size = im_size, color = 'white') + # Check if internet is available + if internet_available() == True: + logging.info('Connection test passed') + else: + logging.error('Network could not be reached :/') + raise Exception('Network could not be reached :(') + + # Set some parameters for formatting rss feeds line_spacing = 1 line_height = self.font.getsize('hg')[1] + line_spacing @@ -84,11 +99,6 @@ class inkycal_rss: line_positions = [ (0, spacing_top + _ * line_height ) for _ in range(max_lines)] - if internet_available() == True: - print('Connection test passed') - else: - # write 'No network available :(' - raise Exception('Network could not be reached :(') try: # Create list containing all rss-feeds from all rss-feed urls diff --git a/inkycal/modules/inkycal_weather.py b/inkycal/modules/inkycal_weather.py new file mode 100644 index 0000000..dc135e3 --- /dev/null +++ b/inkycal/modules/inkycal_weather.py @@ -0,0 +1,479 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +Weather module for Inky-Calendar software. +Copyright by aceisace +""" + +from inkycal.custom import * +import pyowm +import math, decimal +import arrow +from locale import getdefaultlocale as sys_locale + +config = {'api_key': 'top-secret', 'location': 'Stuttgart, DE'} +size = (384,80) + +class weather: + logger = logging.getLogger(__name__) + logging.basicConfig(level=logging.INFO) + + def __init__(self, section_size, section_config): + """Initialize inkycal_weather module""" + self.name = os.path.basename(__file__).split('.py')[0] + self.config = section_config + self.width, self.height = section_size + self.background_colour = 'white' + self.font_colour = 'black' + self.fontsize = 12 + self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], + size = self.fontsize) + self.padding_x = 0.02 + self.padding_y = 0.05 + + # Weather-specfic options + self.owm = pyowm.OWM(config['api_key']) + self.units = 'metric' # metric # imperial + self.hour_format = '12' # 12 #24 + self.timezone = get_system_tz() + self.round_temperature = True + self.round_windspeed = True + self.use_beaufort = True + self.show_wind_direction = False + self.use_wind_direction_icon = False + self.forecast_interval = 'hourly' # daily # hourly + self.locale = sys_locale()[0] + self.weatherfont = ImageFont.truetype(fonts['weathericons-regular-webfont'], + size = self.fontsize) + + print('{0} loaded'.format(self.name)) + + def set(self, **kwargs): + """Manually set some parameters of this module""" + + for key, value in kwargs.items(): + if key in self.__dict__: + setattr(self, key, value) + else: + print('{0} does not exist'.format(key)) + pass + + def get(self, **kwargs): + """Manually get some parameters of this module""" + + for key, value in kwargs.items(): + if key in self.__dict__: + getattr(self, key, value) + else: + print('{0} does not exist'.format(key)) + pass + + + def get_options(self): + """Get all options which can be changed""" + + return self.__dict__ + + + def generate_image(self): + """Generate image for this module""" + + # Define new image size with respect to padding + im_width = int(self.width - (self.width * 2 * self.padding_x)) + im_height = int(self.height - (self.height * 2 * self.padding_y)) + im_size = im_width, im_height + logging.info('image size: {} x {} px'.format(im_width, im_height)) + + # Create an image for black pixels and one for coloured pixels + im_black = Image.new('RGB', size = im_size, color = self.background_colour) + im_colour = Image.new('RGB', size = im_size, color = 'white') + + # Check if internet is available + if internet_available() == True: + logging.info('Connection test passed') + else: + raise Exception('Network could not be reached :(') + + def get_moon_phase(): + """Calculate the current (approximate) moon phase""" + + dec = decimal.Decimal + diff = now - arrow.get(2001, 1, 1) + days = dec(diff.days) + (dec(diff.seconds) / dec(86400)) + lunations = dec("0.20439731") + (days * dec("0.03386319269")) + position = lunations % dec(1) + index = math.floor((position * dec(8)) + dec("0.5")) + return {0: '\uf095',1: '\uf099',2: '\uf09c',3: '\uf0a0', + 4: '\uf0a3',5: '\uf0a7',6: '\uf0aa',7: '\uf0ae' }[int(index) & 7] + + + def is_negative(temp): + """Check if temp is below freezing point of water (0°C/30°F) + returns True if temp below freezing point, else False""" + answer = False + + if temp_unit == 'celsius' and round(float(temp.split('°')[0])) <= 0: + answer = True + elif temp_unit == 'fahrenheit' and round(float(temp.split('°')[0])) <= 0: + answer = True + return answer + + # Lookup-table for weather icons and weather codes + weathericons = { + '01d': '\uf00d', '02d': '\uf002', '03d': '\uf013', + '04d': '\uf012', '09d': '\uf01a ', '10d': '\uf019', + '11d': '\uf01e', '13d': '\uf01b', '50d': '\uf014', + '01n': '\uf02e', '02n': '\uf013', '03n': '\uf013', + '04n': '\uf013', '09n': '\uf037', '10n': '\uf036', + '11n': '\uf03b', '13n': '\uf038', '50n': '\uf023' + } + + + def draw_icon(image, xy, box_size, icon, rotation = None): + """Custom function to add icons of weather font on image + image = on which image should the text be added? + xy = xy-coordinates as tuple -> (x,y) + box_size = size of text-box -> (width,height) + icon = icon-unicode, looks this up in weathericons dictionary + """ + x,y = xy + box_width, box_height = box_size + text = icon + font = self.weatherfont + + # Increase fontsize to fit specified height and width of text box + size = 8 + font = ImageFont.truetype(font.path, size) + text_width, text_height = font.getsize(text) + while (text_width < int(box_width * 0.9) and + text_height < int(box_height * 0.9)): + size += 1 + font = ImageFont.truetype(font.path, size) + text_width, text_height = font.getsize(text) + + text_width, text_height = font.getsize(text) + + # Align text to desired position + x = int((box_width / 2) - (text_width / 2)) + y = int((box_height / 2) - (text_height / 2)) + + # Draw the text in the text-box + draw = ImageDraw.Draw(image) + space = Image.new('RGBA', (box_width, box_height)) + ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) + + if rotation != None: + space.rotate(rotation, expand = True) + + # Update only region with text (add text with transparent background) + image.paste(space, xy, space) + + + +# column1 column2 column3 column4 column5 column6 column7 +# |----------|----------|----------|----------|----------|----------|----------| +# | time | temperat.| moonphase| forecast1| forecast2| forecast3| forecast4| +# | current |----------|----------|----------|----------|----------|----------| +# | weather | humidity | sunrise | icon1 | icon2 | icon3 | icon4 | +# | icon |----------|----------|----------|----------|----------|----------| +# | | windspeed| sunset | temperat.| temperat.| temperat.| temperat.| +# |----------|----------|----------|----------|----------|----------|----------| + + + # Calculate size rows and columns + col_width = im_width // 7 + + if (im_height // 3) > col_width//2: + row_height = (im_height // 4) + else: + row_height = (im_height // 3) + + + # Adjust the fontsize to make use of most free space + # self.font = auto_fontsize(self.font, row_height) + + # Calculate spacings for better centering + spacing_top = int( (im_width % col_width) / 2 ) + spacing_left = int( (im_height % row_height) / 2 ) + + # Define sizes for weather icons + icon_small = int(col_width / 3) + icon_medium = icon_small * 2 + icon_large = icon_small * 3 + + print('col_width=', col_width, 'row_height:', row_height) + print('small, medium ,large:', icon_small, icon_medium, icon_large) + + # Calculate the x-axis position of each col + col1 = spacing_top + col2 = col1 + col_width + col3 = col2 + col_width + col4 = col3 + col_width + col5 = col4 + col_width + col6 = col5 + col_width + col7 = col6 + col_width + + # Calculate the y-axis position of each row + row1 = spacing_left + row2 = row1 + row_height + row3 = row2 + row_height + + # Positions for current weather details + weather_icon_pos = (col1, row1) + temperature_icon_pos = (col2, row1) + temperature_pos = (col2+icon_small, row1) + + print('temp icon pos:', temperature_icon_pos) + print('temp:', temperature_pos) + + humidity_icon_pos = (col2, row2) + humidity_pos = (col2+icon_small, row2) + + print('hum icon pos:', humidity_icon_pos) + print('hum pos:', humidity_pos) + + windspeed_icon_pos = (col2, row3) + windspeed_pos = (col2+icon_small, row3) + + # Positions for sunrise, sunset, moonphase + moonphase_pos = (col3, row1) + sunrise_icon_pos = (col3, row2) + sunrise_time_pos = (col3+icon_small, row2) + sunset_icon_pos = (col3, row3) + sunset_time_pos = (col3+ icon_small, row3) + + # Positions for forecast 1 + stamp_fc1 = (col4, row1) + icon_fc1 = (col4, row2) + temp_fc1 = (col4, row3) + + # Positions for forecast 2 + stamp_fc2 = (col5, row1) + icon_fc2 = (col5, row2) + temp_fc2 = (col5, row3) + + # Positions for forecast 3 + stamp_fc3 = (col6, row1) + icon_fc3 = (col6, row2) + temp_fc3 = (col6, row3) + + # Positions for forecast 4 + stamp_fc4 = (col7, row1) + icon_fc4 = (col7, row2) + temp_fc4 = (col7, row3) + + # Create current-weather and weather-forecast objects + weather = self.owm.weather_at_place(self.config['location']).get_weather() + forecast = self.owm.three_hours_forecast(self.config['location']) + + # Set decimals + dec_temp = None if self.round_temperature == True else 1 + dec_wind = None if self.round_windspeed == True else 1 + + # Set correct temperature units + if self.units == 'metric': + temp_unit = 'celsius' + elif self.units == 'imperial': + temp_unit = 'fahrenheit' + + + # Get current time + now = arrow.utcnow() + + if self.forecast_interval == 'hourly': + + # Forecasts are provided for every 3rd full hour + # find out how many hours there are until the next 3rd full hour + if (now.hour % 3) != 0: + hour_gap = 3 - (now.hour % 3) + else: + hour_gap = 3 + + # Create timings for hourly forcasts + forecast_timings = [now.shift(hours = + hour_gap + _).floor('hour') + for _ in range(0,12,3)] + + # Create forecast objects for given timings + forecasts = [forecast.get_weather_at(forecast_time.datetime) for + forecast_time in forecast_timings] + + # Add forecast-data to fc_data dictionary + fc_data = {} + for forecast in forecasts: + temp = '{}°'.format(round( + forecast.get_temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) + + icon = forecast.get_weather_icon_name() + fc_data['fc'+str(forecasts.index(forecast)+1)] = { + 'temp':temp, + 'icon':icon, + 'stamp': forecast_timings[forecasts.index(forecast)].format('H.00' + if self.hour_format == '24' else 'h a') + } + + elif self.forecast_interval == 'daily': + + def calculate_forecast(days_from_today): + """Get temperature range and most frequent icon code for forecast + days_from_today should be int from 1-4: e.g. 2 -> 2 days from today + """ + + # Create a list containing time-objects for every 3rd hour of the day + time_range = list(arrow.Arrow.range('hour', + now.shift(days=days_from_today).floor('day'), + now.shift(days=days_from_today).ceil('day') + ))[::3] + + # Get forecasts for each time-object + forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range] + + + # Get all temperatures for this day + daily_temp = [round(_.get_temperature(unit=temp_unit)['temp'], + ndigits=dec_temp) for _ in forecasts] + # Calculate min. and max. temp for this day + temp_range = '{}°/{}°'.format(max(daily_temp), min(daily_temp)) + + + # Get all weather icon codes for this day + daily_icons = [_.get_weather_icon_name() for _ in forecasts] + # Find most common element from all weather icon codes + status = max(set(daily_icons), key=daily_icons.count) + + weekday = now.shift(days=days_from_today).format('ddd', locale= + self.locale) + return {'temp':temp_range, 'icon':status, 'stamp': weekday} + + fc_data = [calculate_forecast(days) for days in range (1,5)] + + for key,val in fc_data.items(): + logging.info((key,val)) + + # Get some current weather details + temperature = '{}°'.format(weather.get_temperature(unit=temp_unit)['temp']) + weather_icon = weather.get_weather_icon_name() + humidity = str(weather.get_humidity()) + windspeed = weather.get_wind(unit='meters_sec')['speed'] + wind_angle = weather.get_wind()['deg'] + wind_direction = ["N","NE","E","SE","S","SW","W","NW"][round( + wind_angle/45) % 8] + sunrise_raw = arrow.get(weather.get_sunrise_time()).to(self.timezone) + sunset_raw = arrow.get(weather.get_sunset_time()).to(self.timezone) + + if self.hour_format == '12': + sunrise = sunrise_raw.format('h:mm a') + sunset = sunset_raw.format('h:mm a') + elif self.hour_format == '24': + sunrise = sunrise_raw.format('H:mm') + sunset = sunset_raw.format('H:mm') + + # Format the windspeed to user preference + if self.use_beaufort == True: + windspeed_to_beaufort = [0.02, 1.5, 3.3, 5.4, 7.9, 10.7, 13.8, 17.1, + 20.7, 24.4, 28.4, 32.6, 100] + wind = str([windspeed_to_beaufort.index(_) for _ in windspeed_to_beaufort + if windspeed < _][0]) + + elif self.use_beaufort == False: + meters_sec = round(windspeed, ndigits = dec_wind) + miles_per_hour = round(windspeed * 2.23694, ndigits = dec_wind) + + if self.units == 'metric': + wind = str(meters_sec) + 'm/s' + + elif self.units == 'imperial': + wind = str(miles_per_hour) + 'mph' + + if self.show_wind_direction == True: + wind += '({0})'.format(wind_direction) + + + dec = decimal.Decimal + moonphase = get_moon_phase() + + # Fill weather details in col 1 (current weather icon) + # write(im_black, (col_width, row_height), now_str, text_now_pos, font = font) + draw_icon(im_colour, weather_icon_pos, (icon_large, icon_large), + weathericons[weather_icon]) + + # Fill weather details in col 2 (temp, humidity, wind) + draw_icon(im_colour, temperature_icon_pos, (row_height, row_height), + '\uf053') + + if is_negative(temperature): + write(im_black, temperature_pos, (col_width-icon_small, row_height), + temperature, font = self.font, fill_height = 0.9) + else: + write(im_black, temperature_pos, (col_width-icon_small, row_height), + temperature, font = self.font) + + draw_icon(im_colour, humidity_icon_pos, (row_height, row_height), + '\uf07a') + + write(im_black, humidity_pos, (col_width-icon_small, row_height), + humidity+'%', font = self.font, fill_height = 0.9) + + if self.use_wind_direction_icon == False: + draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small), + '\uf050') + else: + draw_icon(im_colour, windspeed_icon_pos, (icon_small, icon_small), + '\uf0b1', rotation = -wind_degrees) + + write(im_black, windspeed_pos, (col_width-icon_small, row_height), + wind, font=self.font, fill_height = 0.9) + + # Fill weather details in col 3 (moonphase, sunrise, sunset) + draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase) + + draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051') + write(im_black, sunrise_time_pos, (col_width-icon_small, icon_small), sunrise, + font = self.font, autofit = True) + + draw_icon(im_colour, sunset_icon_pos, (icon_small, icon_small), '\uf052') + write(im_black, sunset_time_pos, (col_width-icon_small, icon_small), sunset, + font = self.font, autofit = True) + + # Add the forecast data to the correct places + for pos in range(1, len(fc_data)+1): + stamp = fc_data['fc'+str(pos)]['stamp'] + icon = weathericons[fc_data['fc'+str(pos)]['icon']] + temp = fc_data['fc'+str(pos)]['temp'] + + write(im_black, eval('stamp_fc'+str(pos)), (col_width, row_height), + stamp, font = self.font) + draw_icon(im_colour, eval('icon_fc'+str(pos)), (col_width, row_height), + icon) + write(im_black, eval('temp_fc'+str(pos)), (col_width, row_height), + temp, font = self.font) + + # Add borders around each sub-section + square_h = int((row_height*3)*0.9) + square_w = int((col_width*0.9)) + draw_square(im_colour, (col1, row1), (col_width*3, square_h)) + draw_square(im_colour, (col4, row1), (square_w, square_h)) + draw_square(im_colour, (col5, row1), (square_w, square_h)) + draw_square(im_colour, (col6, row1), (square_w, square_h)) + draw_square(im_colour, (col7, row1), (square_w, square_h)) + +## except Exception as e: +## """If something went wrong, print a Error message on the Terminal""" +## print('Failed!') +## print('Error in weather module!') +## print('Reason: ',e) +## clear_image('top_section') +## write(top_section_width, top_section_height, str(e), +## (0, 0), font = font) +## weather_image = crop_image(image, 'top_section') +## weather_image.save(image_path+'inkycal_weather.png') +## pass +## + # Save image of black and colour channel in image-folder + im_black.save(images+self.name+'.png') + im_colour.save(images+self.name+'_colour.png') + +if __name__ == '__main__': + print('running {0} in standalone mode'.format( + os.path.basename(__file__).split('.py')[0])) + a = weather(size, config) + a.generate_image()