Compare commits
936 commits
Author | SHA1 | Date | |
---|---|---|---|
4df439018e | |||
d830281789 | |||
cd68d4c02b | |||
|
4faf625a70 | ||
4a7eb40bb1 | |||
eaa3de9d79 | |||
b7ede5a026 | |||
83f8f37447 | |||
30eb1e4cdd | |||
d3f46c820e | |||
89f9f6d8fc | |||
36f8c35170 | |||
79acea5668 | |||
8e4fbfdfd9 | |||
1e0f59739f | |||
5d881753b8 | |||
ae3b72c86a | |||
387fdb813f | |||
3309dde202 | |||
6bda730c61 | |||
016d022303 | |||
eeb9e03547 | |||
059fc84cb3 | |||
fdb4331aec | |||
464f489ebd | |||
2269bcf3fa | |||
0200116d88 | |||
ad79095a9d | |||
b58357927f | |||
b74def5bbf | |||
2a36a9a6ce | |||
ba8c95ff67 | |||
ccf28e52b7 | |||
8652ef187c | |||
2bbb19b53e | |||
00a05a1a6e | |||
2a94c1fb90 | |||
|
d8a1c1c250 | ||
f53faee1a5 | |||
95bbc8fb59 | |||
9b1ceba95e | |||
aa34b8bc4f | |||
1a62d9d929 | |||
|
d768712181 | ||
f1cc271e3b | |||
08d30bf7d7 | |||
d4df70ce39 | |||
62dd01fb00 | |||
|
f22f4b825f | ||
|
61fd7af37a | ||
3bd7b55010 | |||
1a50ff5cca | |||
3bac199949 | |||
f417fdbe3e | |||
4d3c2cfbd7 | |||
51d4e20553 | |||
083f4bbf6f | |||
710255604a | |||
d2fa6d8fd7 | |||
a7c7a3e553 | |||
b01942b39d | |||
99929d63a4 | |||
1e8d02dc41 | |||
8211b9829e | |||
07c2484849 | |||
5dcae2d435 | |||
28938b45f8 | |||
7ead3c39c1 | |||
6504851aa1 | |||
69f12de1e5 | |||
64b704c10f | |||
08cc2b7daa | |||
6c4eb5c220 | |||
69d1041f98 | |||
76495bd16c | |||
16f38a9102 | |||
d431dae196 | |||
29eddbbd18 | |||
843163dbda | |||
460c1e8a54 | |||
54a53316cc | |||
|
a6ee7fffa1 | ||
383c7b7121 | |||
b0e5e931f3 | |||
1390f3630a | |||
426291f828 | |||
ce73624443 | |||
d2c7428c62 | |||
b6691de9e8 | |||
bf99ecd8e0 | |||
7526c47735 | |||
95cee6287e | |||
6bb0fe247b | |||
f88a4bf702 | |||
f8eddb66d5 | |||
2ea9b09954 | |||
f7a589354f | |||
077d841cab | |||
c09a5d51c4 | |||
0e0803a5e6 | |||
5b858bc121 | |||
904c50224c | |||
63b702cbb4 | |||
8c629051b5 | |||
bad6d280da | |||
c33a34f9d8 | |||
242e7acd09 | |||
188b0497cb | |||
8552874e7d | |||
f8fac5f2d3 | |||
bdd443eea8 | |||
09464dd07d | |||
8ab6826aaf | |||
61a41138fa | |||
002f20504d | |||
06bffa1aea | |||
320a79a869 | |||
43c5fafdba | |||
317a6765f3 | |||
720f053d6a | |||
e6fa543406 | |||
b82bfa2aa7 | |||
bb6f121c42 | |||
1c796f0cc0 | |||
7fd2771efb | |||
956c4a300c | |||
260f9bd70a | |||
b1a796079b | |||
bd69088e0b | |||
d7137ac9c1 | |||
5cb04d9dcf | |||
d206b5e1c6 | |||
c8365ecc63 | |||
009f3cd08b | |||
168d52f86e | |||
2d79ac26fd | |||
33a2e9b2fe | |||
82188fd3af | |||
fdc0197da1 | |||
a6aebf1c37 | |||
0a523d5de9 | |||
5ec99c31bf | |||
6b726ac6b6 | |||
367218ed67 | |||
f0d8507818 | |||
0b665f4976 | |||
4d0b7009cd | |||
7d0b38c479 | |||
efdcf57df0 | |||
ef634bad7c | |||
0df90b39bc | |||
39fa0bce21 | |||
25f2288bdd | |||
4720774011 | |||
a7605e3473 | |||
e9a1142443 | |||
fd1edb02b7 | |||
3bf7bc0205 | |||
10af39c985 | |||
fd14986b7c | |||
eb23b7236f | |||
5ada365bbc | |||
d525683a27 | |||
7decd58eee | |||
49f50dc944 | |||
4ca32e49ab | |||
b10de72b14 | |||
a5bfd90b5e | |||
c48af815b9 | |||
c9fc0341c4 | |||
05f0412b94 | |||
d9b83cca9d | |||
2cebf0a60d | |||
913313245f | |||
56ee85b60f | |||
fdd35a157f | |||
e86de45c63 | |||
2763abeeb4 | |||
66b5b95f74 | |||
a9a639ed2b | |||
0dd1ca9b45 | |||
d7f485cba6 | |||
afd94ea10f | |||
4b656ee819 | |||
22242d894f | |||
9e67af6adc | |||
3e11ec4df0 | |||
6a37255b57 | |||
c93210356a | |||
9519e3aaed | |||
f2581e57a6 | |||
757a66eb6e | |||
1ff09a5591 | |||
a4be256b50 | |||
a65bfddeeb | |||
45dd474bf3 | |||
5faa0c5d1c | |||
bd03a2b604 | |||
225963aaeb | |||
1c56323ff0 | |||
eaeea2083e | |||
ab9e34980a | |||
6146fc7c2d | |||
b01969d2da | |||
59d2401f81 | |||
9e08553232 | |||
ddb5f727d9 | |||
4ef90e14b3 | |||
bef88f0b85 | |||
319e6947b1 | |||
94591ec51e | |||
6dd6de16ce | |||
019d150e78 | |||
86c64eeba5 | |||
99bba341a9 | |||
bcca8a8f8a | |||
88ace91239 | |||
c854e7eec1 | |||
544c23954d | |||
0e1b6bef57 | |||
571846ae39 | |||
3a2f145e22 | |||
4b1cbed0f7 | |||
d838ee37d0 | |||
ba7c437a13 | |||
7073d5bc5d | |||
11ffdeac13 | |||
4cfe34ed93 | |||
139c59bfab | |||
dcbaa4893e | |||
3e63f5ff8b | |||
5838734c9f | |||
672d7058d8 | |||
f7c5f6b05e | |||
8686637271 | |||
b1705941e8 | |||
c0a6ebe33f | |||
27f2bd5eef | |||
c99bb1538b | |||
5f3ef1091b | |||
ac570e1323 | |||
ab2303bf64 | |||
0cd0cea1d8 | |||
dddfe812f9 | |||
0acc946b44 | |||
a9b9de7d80 | |||
9c42262604 | |||
21280cfccd | |||
9837a20800 | |||
36cd0bc66d | |||
1116513539 | |||
a139cd33f0 | |||
4ce74fbc5e | |||
ae1a8b0057 | |||
08c9960232 | |||
cbe3b1da4a | |||
87175a1264 | |||
c9c6ce4207 | |||
c79f0b8888 | |||
73ccfba021 | |||
4466618e2c | |||
88a549aad3 | |||
8b43ee3287 | |||
82f4499586 | |||
50960c7c05 | |||
37c252dc65 | |||
e1e25e3d2a | |||
895a7cda1c | |||
e4f77aff7c | |||
fe838f5964 | |||
9c5a791019 | |||
14ca6e9cdf | |||
b4b8898b2e | |||
0a6d0ed2b1 | |||
53f823fdf2 | |||
e1d05f2f65 | |||
297318af30 | |||
145930494a | |||
dcef52b08a | |||
c5a3efbc0f | |||
74e73524f3 | |||
8e57bf30a4 | |||
332aceb9cf | |||
9e7788b618 | |||
c72ffa88ab | |||
c923a52a9f | |||
f6f98fdf9d | |||
064d4033d3 | |||
6c50dd4db2 | |||
59cade023c | |||
8f2e4c1c19 | |||
477894b251 | |||
5ad6dff6fb | |||
26bdceb22e | |||
65f36f6039 | |||
e327d359a5 | |||
b7b4d4f0fd | |||
8372d78268 | |||
bd119bb8f9 | |||
545657ecbb | |||
f15c9fdb39 | |||
cec0ee83da | |||
964570bc4a | |||
2ba92e1b6d | |||
53c135d398 | |||
1dbae38b3f | |||
036c5add23 | |||
91567bef60 | |||
06b6d697f2 | |||
cb5f6cddd7 | |||
69fce8c8d2 | |||
81a4e5c4c5 | |||
0291c24e23 | |||
1b92692b51 | |||
0400d29b10 | |||
fdcefd1663 | |||
9756a6abb8 | |||
635ab20c3b | |||
f0be84ccb2 | |||
1ac32e2545 | |||
b7e8a5c662 | |||
a19324d2bf | |||
36e33b1340 | |||
bf69933382 | |||
beeac85894 | |||
e514fee924 | |||
44cf83c53e | |||
cdd9d1b834 | |||
be1a89ae7b | |||
b746c13113 | |||
e235d1bfb4 | |||
5ac46a2e9d | |||
44deafa978 | |||
b61ebd2d36 | |||
d9bb84b4c3 | |||
ad07a03283 | |||
7043911cb8 | |||
3ce63dd74d | |||
dbfafda49a | |||
0678a6711a | |||
6f8762d6ff | |||
eec658612d | |||
547dac8562 | |||
118997b1ae | |||
385ae3da11 | |||
a8cad34b32 | |||
c51f9643f2 | |||
2f53c639b5 | |||
b7692efa35 | |||
10f006b1ef | |||
63b33f44cc | |||
66195a9655 | |||
b9091c2dc7 | |||
83ac33043b | |||
a01619610b | |||
c58ebcbe33 | |||
b28341ada5 | |||
6af80788e8 | |||
ae24eb7413 | |||
60b94c6783 | |||
e19bfa0b0b | |||
749f88bdb1 | |||
c7522535ea | |||
31f712a93f | |||
7edd5fec65 | |||
b537c8c767 | |||
0d1f065208 | |||
e012fbfebc | |||
261eb5660f | |||
3981bf8a80 | |||
856e7b5b94 | |||
4c2c049c4b | |||
b09e97c93f | |||
9381aeaec1 | |||
435fd8d989 | |||
e58fc28588 | |||
c6a9ce0ce8 | |||
b3aebcb780 | |||
a5d849c1dd | |||
7f9a6a3102 | |||
615e338e9a | |||
82c28841a7 | |||
bd077866d9 | |||
c572180d5b | |||
7c8dc46b45 | |||
1a330d9e73 | |||
b57800b0d8 | |||
d102c7c639 | |||
1b9ed82b40 | |||
4c127179a9 | |||
d62719ceda | |||
e3efe6ebd2 | |||
f29b5f0dbf | |||
e13b581055 | |||
5d88baca08 | |||
022530063e | |||
f05c25a7e9 | |||
2eaffe3b63 | |||
d65d1ed743 | |||
2b4406d613 | |||
3d0f86a17c | |||
91a160512f | |||
2738dae878 | |||
a6cede3554 | |||
2785542aa0 | |||
679fed425d | |||
31ee4359ee | |||
452b1fba57 | |||
15fb84641c | |||
ff8f0a98ef | |||
ed4b511002 | |||
4a1c3ea9ca | |||
a630dd2d54 | |||
c2d345dea0 | |||
7596b7703d | |||
c9afe86c95 | |||
d0e7ee3fa8 | |||
a67987b78f | |||
c3e51da446 | |||
9e7fb46859 | |||
d57c41e813 | |||
|
86cdc5630e | ||
|
17dd30b56b | ||
|
e5e9bb2478 | ||
|
ede448c7c1 | ||
|
de9292b525 | ||
|
0b84d46c43 | ||
|
617ef8a6c1 | ||
|
3725601462 | ||
|
a40b8db7e7 | ||
|
e6f08e83fd | ||
|
272ad2f594 | ||
|
72a5f13db4 | ||
|
fff6361140 | ||
|
b7c9c11bbb | ||
|
e9aa1b336d | ||
|
ef5b5c8eff | ||
|
08748e480c | ||
|
d3a8297473 | ||
|
b665f4c6c6 | ||
|
e7b91ca9fb | ||
|
3b48667ec7 | ||
|
a761e0bdb2 | ||
|
b202746029 | ||
|
10cb3610c3 | ||
|
f59f84c6d2 | ||
|
0070990765 | ||
|
c8efdade43 | ||
|
d6bd526e15 | ||
|
72c4e3d032 | ||
|
f63f08acc0 | ||
|
06f84e9d29 | ||
|
4fa5f6dc90 | ||
|
322367f226 | ||
|
bca4c2245a | ||
|
385b3a5793 | ||
|
8e9056f109 | ||
|
06980ced57 | ||
|
3c3a9eb68d | ||
|
f07c66145e | ||
|
be85450491 | ||
|
73b3497d14 | ||
|
f2273deede | ||
|
e362d36e13 | ||
|
818f56106d | ||
|
9e930167c1 | ||
|
eeeac8253b | ||
|
adda2c4345 | ||
|
3bcd56448c | ||
|
9a0864cca1 | ||
|
7276407797 | ||
|
89cfb7b762 | ||
|
f601179988 | ||
|
bfa3d00ae9 | ||
|
0715414191 | ||
|
ec3bd09536 | ||
|
fa8637db2f | ||
|
0da4aa286e | ||
|
dd96ca1297 | ||
|
6bf541f82a | ||
|
613bc87e5f | ||
|
7e0515e5d5 | ||
|
e217171192 | ||
|
3cc7a9f22f | ||
|
a9c67c0bce | ||
|
493fa5aae8 | ||
|
0840e6e2bc | ||
|
c4257672d4 | ||
|
8b8d0c5132 | ||
|
942f70f6f3 | ||
|
8360cd770f | ||
|
6c6e4722b3 | ||
|
96abd50288 | ||
|
6f80b5d89c | ||
|
9535d9b40e | ||
|
36177b4e4a | ||
|
e3457a325d | ||
|
2baf7b3a08 | ||
|
34c0ca4d74 | ||
|
76546d3561 | ||
|
d01706f79e | ||
|
9ec6f5262c | ||
|
b4bc71dfc2 | ||
|
abda71f74f | ||
|
c58f9d9687 | ||
|
f8c2ec736d | ||
|
0e613c39e5 | ||
|
6623987e1f | ||
|
44facd0826 | ||
|
9a06553ea3 | ||
|
8a9fb1dc91 | ||
|
d25e5bc800 | ||
|
aabddcf483 | ||
|
427f91785e | ||
|
0d29b4fed0 | ||
|
ae31f80338 | ||
|
bda71065b7 | ||
|
8a0884cecd | ||
|
64da394a1f | ||
|
2c92419706 | ||
|
87c9d8dbc0 | ||
|
e10a5fe55f | ||
|
59064cf6c1 | ||
|
c7b4e32660 | ||
|
c917fb84ce | ||
|
2d37d7fd6c | ||
|
fd44e1f659 | ||
|
257a24ecbd | ||
|
0388322416 | ||
|
405ef556fc | ||
|
ec34f5acae | ||
|
24b87d2612 | ||
|
50688a0174 | ||
|
c603a14592 | ||
|
c014815aba | ||
|
e12e4b4678 | ||
|
606217bc76 | ||
|
09d3e15acc | ||
|
3bb342b966 | ||
|
24542940ca | ||
|
5fd482cfe7 | ||
|
4c54adf2e5 | ||
|
dfbcdb0d7b | ||
|
bfd8e166d3 | ||
|
c70534d6cc | ||
|
501e24413e | ||
|
1fd005fe07 | ||
|
90c367a073 | ||
|
1e642dcd55 | ||
|
52d01cb76f | ||
|
5dbc9c1722 | ||
|
e61137957b | ||
|
4e30f25b24 | ||
|
0b7a6200bc | ||
|
deb881d573 | ||
|
1a9ef227e1 | ||
|
611e48cac8 | ||
|
481bf95952 | ||
|
81e904be93 | ||
|
0173e8b266 | ||
|
10fda81754 | ||
|
ee23dbe957 | ||
|
203217bfc2 | ||
|
11ec355e48 | ||
|
305e983574 | ||
|
f490e1927e | ||
|
893dc98867 | ||
|
294c0fe1dd | ||
|
f3a0dba1e6 | ||
|
b9dc628c12 | ||
|
dd73abc8d0 | ||
|
1f990c5c7a | ||
|
327a854d6e | ||
|
6f0111a7c3 | ||
|
f2d05baba3 | ||
|
309f98e186 | ||
|
36e7aef041 | ||
|
05ec911a47 | ||
|
5425b7742c | ||
|
62c1ea638e | ||
|
1ef9673fd6 | ||
|
bccc3ae5e1 | ||
|
f62914dd06 | ||
|
4032639e13 | ||
|
6d243514ce | ||
|
11fa6c70fd | ||
|
2b7867211e | ||
|
aea1d4929f | ||
|
2d4ea70538 | ||
|
cb0fede996 | ||
|
9319e9e84e | ||
|
01ae72e5c0 | ||
|
2bd465c507 | ||
|
ebbc198c79 | ||
|
e0a57bc8d3 | ||
|
32f3636ca6 | ||
|
2bb190061b | ||
|
7d10d6fca5 | ||
|
7b334d5c84 | ||
|
99781d78a2 | ||
|
186c4ac966 | ||
|
c4d3bad2c5 | ||
|
b5bc862052 | ||
|
e6c85c716d | ||
|
7094934b72 | ||
|
9c8c64b82a | ||
|
b77ec2612c | ||
|
161dcae8eb | ||
|
d780d590b3 | ||
|
4953c04dda | ||
|
18a2ae60e7 | ||
|
3ac5b48c1d | ||
|
0bbb1a15ff | ||
|
2812cbdecb | ||
|
7c49c4fd22 | ||
|
557131a7a1 | ||
|
8f8f72cd96 | ||
|
054bd71382 | ||
|
6ee43f5fee | ||
|
aef454f69f | ||
|
74ad96fea9 | ||
|
de0db3546e | ||
|
a909b139fe | ||
|
2d5a332ce4 | ||
|
af736bbba7 | ||
|
7e30694d0d | ||
|
c419d74ba0 | ||
|
0b222625b4 | ||
|
6352179358 | ||
|
c28627489e | ||
|
187d71e43e | ||
|
b9f1342e62 | ||
|
c36624363c | ||
|
97ea602e92 | ||
|
5b23a854ef | ||
|
e7d9d0a658 | ||
|
b5d495c237 | ||
|
42c5234037 | ||
|
ad3cc04fb1 | ||
|
c94c700886 | ||
|
3ac44198bf | ||
|
4e7f5e27da | ||
|
61aecedec6 | ||
|
0f11ae3727 | ||
|
c9673be33e | ||
|
a244b8a4fd | ||
|
05819a1d1c | ||
|
8208ad8601 | ||
|
21dd78ee22 | ||
|
3881c24939 | ||
|
9c5bd9734f | ||
|
d96ab1ced0 | ||
|
de3b668156 | ||
|
b63fd572e4 | ||
|
5844a72821 | ||
|
50c98b7640 | ||
|
d381993021 | ||
|
8aed428ba8 | ||
|
4c594469e6 | ||
|
43220f8191 | ||
|
dc692c03a2 | ||
|
6357cd3838 | ||
|
63a3a74d58 | ||
|
4d529214df | ||
|
c82fc0d55c | ||
|
cff34f4b5e | ||
|
5bcb8afbb0 | ||
|
e1471ed224 | ||
|
9067f72e0b | ||
|
1195055503 | ||
|
81916435e9 | ||
|
4119728a10 | ||
|
1ff7aa1088 | ||
|
2a2d490b63 | ||
|
1ed477fc62 | ||
|
0f9822e77d | ||
|
fe54eb00f7 | ||
|
701c0d224f | ||
|
c8c676c584 | ||
|
8e53ef63f4 | ||
|
35718b7151 | ||
|
c9fd493577 | ||
|
2e66377220 | ||
|
5a24eafd5e | ||
|
6532818acf | ||
|
8d3e683f4a | ||
|
ec33bcff46 | ||
|
b887fd0f69 | ||
|
5d668d0515 | ||
|
7f042c6c0a | ||
|
6a31ef5a8c | ||
|
159a45bbb8 | ||
|
9c07b414c4 | ||
|
a5a858ec4f | ||
|
29e4b6f3a9 | ||
|
68111c534c | ||
|
9f19092477 | ||
|
a0dd50497f | ||
|
f9c9ad3b4e | ||
|
8654c3e84e | ||
|
e342673c59 | ||
|
8fbe2cf05c | ||
|
2de4052806 | ||
|
fa0b0c18d8 | ||
|
456a118596 | ||
|
d127d88abd | ||
|
4a41b9258c | ||
|
a97fa6f937 | ||
|
98ae207b74 | ||
|
c613876f0e | ||
|
980661196e | ||
|
1bde274e24 | ||
|
77e9f42db1 | ||
|
95997eae52 | ||
|
3ffc4e7a81 | ||
|
e2c17ca6bc | ||
|
654ff05d6e | ||
|
813c6b2adb | ||
|
0e03d518d5 | ||
|
ed31c59b12 | ||
|
23c682821c | ||
|
1d6634ee9f | ||
|
f2181f2f54 | ||
|
f9d7001eea | ||
|
b44ece508f | ||
|
4e0aaeb352 | ||
|
0755c4c210 | ||
|
3084dba052 | ||
|
e4a49faf56 | ||
|
8e806d10c1 | ||
|
3ca4ee8113 | ||
|
25b90f71a0 | ||
|
be7dc2e59e | ||
|
8edf75e728 | ||
|
8c206df8a7 | ||
|
9fb6d35f48 | ||
|
15cc6c85ab | ||
|
18e50e3d53 | ||
|
7234463527 | ||
|
7e3cbd52cd | ||
|
e9ddbc040a | ||
|
9f4d8ae6a0 | ||
|
fe5895bb8d | ||
|
5e9691faaa | ||
|
7773eb1455 | ||
|
56e8f25b6a | ||
|
7b3403994f | ||
|
2322c882b4 | ||
|
6675cc178c | ||
|
af690e5ee7 | ||
|
413ba4f068 | ||
|
320521e5e4 | ||
|
39a984e54c | ||
|
6b3411cf6e | ||
|
80e5f738f2 | ||
|
82adb68370 | ||
|
bfde67a1e4 | ||
|
8ddd6acc49 | ||
|
25bb3c9ef3 | ||
|
383c0116ae | ||
|
bfd8663f35 | ||
|
5424c8e0e9 | ||
|
b6a68f7bb6 | ||
|
9e5b2c9747 | ||
|
76476acb9c | ||
|
72f45f7e14 | ||
|
21b8734e14 | ||
|
03c3598841 | ||
|
2a93f92b72 | ||
|
02f632089c | ||
|
10cf0e1977 | ||
|
25dccbc60b | ||
|
bed369dd96 | ||
|
d55bc7db76 | ||
|
c7bcbc32bf | ||
|
d6d21cd7fe | ||
|
76ebeee572 | ||
|
b8a5cf3e60 | ||
|
cce75faaf6 | ||
|
677281e25f | ||
|
b7c55f2167 | ||
|
0770a0f8b1 | ||
|
ae0442df91 | ||
|
dcd9e3d422 | ||
|
5f6a0dc087 | ||
|
403ace490f | ||
|
97472ba72d | ||
|
a4920f008c | ||
|
5883e699a9 | ||
|
4c777f172b | ||
|
855e46cd00 | ||
|
7805ee8b93 | ||
|
c7770a6af9 | ||
|
6ba5d9c371 | ||
|
e64f4aaba6 | ||
|
0742963e30 | ||
|
e3b8f49b2a | ||
|
d2fa76097a | ||
|
091631fe1f | ||
|
e57211253c | ||
|
7fab1569cd | ||
|
014f89dec3 | ||
|
2bd0d99449 | ||
|
a030e8c2fd | ||
|
5e746e5ae2 | ||
|
8a3396c461 | ||
|
4d98e34bf4 | ||
|
96ea05d1cd | ||
|
02a76debe1 | ||
|
ab74c32b71 | ||
|
79e33d12cd | ||
|
9f49c12c95 | ||
|
013b89c95a | ||
|
9c52190af5 | ||
|
5b1bb5af43 | ||
|
f070ba8d5c | ||
|
61eb3ecfae | ||
|
40cff57313 | ||
|
8c432424e7 | ||
|
10dfb78e6c | ||
|
a39371490a | ||
|
50db242bfa | ||
|
1603e20e60 | ||
|
71ce767f49 | ||
|
b6e868da67 | ||
|
b6d1e46815 | ||
|
1dd5605dcd | ||
|
9c744ddce8 | ||
|
7e0c61922f | ||
|
dc2fd1de3e | ||
|
6a44b845c0 | ||
|
02901cd270 | ||
|
43c5c7fc59 | ||
|
844a824827 | ||
|
ffb948ee0f | ||
|
3d174a201b | ||
|
7bf088f4ff | ||
|
71d927c7e9 | ||
|
6f54db6169 | ||
|
f7ef1858ac | ||
|
a2f900ec35 | ||
|
dd109dd137 | ||
|
e8c475b46d | ||
|
eb215c72a8 | ||
|
1c968c95f8 | ||
|
7e29c41aff | ||
|
d8b45858d7 | ||
|
ff5c9a6d00 | ||
|
7612fed892 | ||
|
f45c0a44c0 | ||
|
5b904c6d2e | ||
|
de5c8a9ac2 | ||
|
9a2673f4e0 | ||
|
9623d570f1 | ||
|
703b2b913c | ||
|
7f57bbf1fd | ||
|
1d849a92d6 | ||
|
b79b7e822b | ||
|
b58e9db17e | ||
|
b971acb025 | ||
|
951226c182 | ||
|
9b4619bdcf | ||
|
d81937f6ae | ||
|
82fbbb082a | ||
|
9a0ee04449 | ||
|
f87a2bfef8 | ||
|
3bca5bf410 | ||
|
6c454b67fb | ||
|
466befacf1 | ||
|
9c52187999 | ||
|
015cacf81c | ||
|
cb37ba4e40 | ||
|
c071467b27 | ||
|
68448c4d75 | ||
|
44abcd7ac5 | ||
|
568ebf98e6 | ||
|
1fdfeab281 | ||
|
49385924e4 | ||
|
9c9c778404 | ||
|
f641099068 | ||
|
d2b0f53528 | ||
|
cb7030f67a | ||
|
6d7fde92f3 | ||
|
aa9559e613 | ||
|
322ef37fe0 | ||
|
05f479a32f | ||
|
cc4a51f1f4 | ||
|
74b286198b | ||
|
51a3d15ef0 | ||
|
aac4c5ee7d | ||
|
265a29acb2 | ||
|
2d0b9a5b14 | ||
|
447fe60680 | ||
|
97629b6899 | ||
|
021139d54e | ||
|
6337e613c5 | ||
|
614be29242 | ||
|
9686c48c44 | ||
|
277865c0c5 | ||
|
74714b312c | ||
|
d1b85f1d7e | ||
|
6e90edcfc3 | ||
|
6efec16562 | ||
|
c597f7d741 | ||
|
cd24f3f4f6 | ||
|
df7f8db0b4 | ||
|
c3f0aeca7f | ||
|
a255fb146f | ||
|
d84494d0de | ||
|
4e44a3e945 | ||
|
60e0e0725d | ||
|
c1c6ffc49c | ||
|
52eaf39940 | ||
|
1e05c7d054 | ||
|
f2af7255b4 | ||
|
03d0882e01 | ||
|
cd4d98b7da | ||
|
431cdef97a | ||
|
73fee1ebe0 | ||
|
99ad4401a1 | ||
|
858732402f | ||
|
44b1f8bb3c | ||
|
b432a91e09 | ||
|
5c9666e5d6 | ||
|
2165eb8293 | ||
|
a1b377199e | ||
|
8cc9dea51e | ||
|
d955abbd22 | ||
|
7e8b7f5588 | ||
|
cbd7b28827 | ||
|
b6aefe5d7b | ||
|
c7fc3ad487 | ||
|
986e438069 | ||
|
f8d23e9f2e | ||
|
24eacca197 | ||
|
ff1ec85d6e |
21
.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
################################################################################
|
||||
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||
################################################################################
|
||||
|
||||
/.vs
|
||||
/Calendar/settings.py
|
||||
/Calendar/__pycache__/settings.cpython-36.pyc
|
||||
/Calendar/__pycache__
|
||||
/Calendar/design_exported.png
|
||||
/design_exported.png
|
||||
/Calendar/icon_positions_locations.pyc
|
||||
/Calendar/DebugInterface.pyc
|
||||
/Calendar/DebugConsole.pyc
|
||||
/Calendar/DataSourceInterface.pyc
|
||||
/Calendar/CalendarInterface.pyc
|
||||
/Calendar/CalendarEvent.pyc
|
||||
/Calendar/design_exported_old.png
|
||||
/Calendar/settings_dev.py
|
||||
/Calendar/settings_pers.py
|
||||
/.vscode
|
||||
/Calendar/images/
|
|
@ -1,78 +0,0 @@
|
|||
"""
|
||||
This is a python image converter specific for the E-Paper-Calendar
|
||||
on github by aceisace from the link below.
|
||||
(https://github.com/aceisace/E-Paper-Calendar-with-iCal-sync-and-live-weather)
|
||||
|
||||
It will convert the 3-colour .bmp's to 2-colour .bmp's so that they can be
|
||||
used even with the 2-Colour 7.5" E-Paper Display from waveshare.
|
||||
|
||||
Please use with caution. If the input and output paths are the same, the
|
||||
convertr will overwrite all .bmp files. It's highly recommended to make a
|
||||
backup of the entire 'Calendar" folder first.
|
||||
|
||||
Copyright by Ace-Laboratory
|
||||
"""
|
||||
|
||||
"""
|
||||
Info: These path contain the bmps that require converting.
|
||||
1) /home/pi/E-Paper-Master/Calendar/months/
|
||||
3) /home/pi/E-Paper-Master/Calendar/other/
|
||||
"""
|
||||
path = '/home/pi/E-Paper-Master/Calendar/'
|
||||
#--------------only change the following two lines-----------------#
|
||||
input_path_1 = path+'other/'
|
||||
output_path_1 = path+'other/'
|
||||
input_path_2 = path+'months/'
|
||||
output_path_2 = path+'months/'
|
||||
#-----------------no need to change anything below-----------------#
|
||||
|
||||
import glob, os, errno
|
||||
from PIL import Image
|
||||
import PIL.ImageOps
|
||||
|
||||
imagenames_1 = []
|
||||
imagenames_2 = []
|
||||
|
||||
print('opening the specified directory...')
|
||||
|
||||
os.chdir(input_path_1) #folder containg files
|
||||
for files in glob.glob('*.bmp'): #find bmp files
|
||||
imagenames_1.append(files) #add these files to a list
|
||||
print('Found these files:', imagenames_1) #print this list
|
||||
|
||||
os.chdir(input_path_2) #folder containg files
|
||||
for files in glob.glob('*.bmp'): #find bmp files
|
||||
imagenames_2.append(files) #add these files to a list
|
||||
print('Found these files:', imagenames_2) #print this list
|
||||
|
||||
# 0 is black, 255 is white, 127 is red.
|
||||
# The following will convert the 'red' parts to white parts.
|
||||
thresh = 100 # any value below 127 works.
|
||||
fn = lambda x : 255 if x > thresh else 0
|
||||
|
||||
try:
|
||||
print('checking if the first output path exists...')
|
||||
os.makedirs(output_path_1)
|
||||
except OSError as e:
|
||||
print('Oh, the first output path exists already. Assuming you know what you are doing.')
|
||||
print('Will attempt to overwrite all .bmp files')
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
try:
|
||||
print('checking if the second output path exists...')
|
||||
os.makedirs(output_path_2)
|
||||
except OSError as e:
|
||||
print('Oh, the second output path exists already. Assuming you know what you are doing.')
|
||||
print('Will attempt to overwrite all .bmp files')
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
print('attempting to convert images...')
|
||||
for files in imagenames_1:
|
||||
((Image.open(input_path_1+files)).convert('L').point(fn, mode='1').save(output_path_1+files))
|
||||
for files in imagenames_2:
|
||||
((Image.open(input_path_2+files)).convert('L').point(fn, mode='1').save(output_path_2+files))
|
||||
|
||||
print('All done!')
|
||||
print('The bmp have been converted. Good luck!')
|
|
@ -1,31 +0,0 @@
|
|||
"""
|
||||
Calibration module for the 2-Colour E-Paper Calendar display. Running this script
|
||||
helps to 'flush' all the pixels and retain the colour on the display.
|
||||
"""
|
||||
import epd7in5
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
EPD_WIDTH = 640
|
||||
EPD_HEIGHT = 384
|
||||
epd = epd7in5.EPD()
|
||||
|
||||
def calibration():
|
||||
for i in range(2):
|
||||
epd.init()
|
||||
black = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 0)
|
||||
print('calibrating black...')
|
||||
ImageDraw.Draw(black)
|
||||
epd.display_frame(epd.get_frame_buffer(black))
|
||||
|
||||
white = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 255)
|
||||
ImageDraw.Draw(white)
|
||||
print('calibrating white...')
|
||||
epd.display_frame(epd.get_frame_buffer(white))
|
||||
epd.sleep()
|
||||
print('Cycle complete!')
|
||||
|
||||
def main():
|
||||
for i in range(1):
|
||||
calibration()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB |
|
@ -1,181 +0,0 @@
|
|||
import epdif
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 640
|
||||
EPD_HEIGHT = 384
|
||||
|
||||
# EPD7IN5 commands
|
||||
PANEL_SETTING = 0x00
|
||||
POWER_SETTING = 0x01
|
||||
POWER_OFF = 0x02
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03
|
||||
POWER_ON = 0x04
|
||||
POWER_ON_MEASURE = 0x05
|
||||
BOOSTER_SOFT_START = 0x06
|
||||
DEEP_SLEEP = 0x07
|
||||
DATA_START_TRANSMISSION_1 = 0x10
|
||||
DATA_STOP = 0x11
|
||||
DISPLAY_REFRESH = 0x12
|
||||
IMAGE_PROCESS = 0x13
|
||||
LUT_FOR_VCOM = 0x20
|
||||
LUT_BLUE = 0x21
|
||||
LUT_WHITE = 0x22
|
||||
LUT_GRAY_1 = 0x23
|
||||
LUT_GRAY_2 = 0x24
|
||||
LUT_RED_0 = 0x25
|
||||
LUT_RED_1 = 0x26
|
||||
LUT_RED_2 = 0x27
|
||||
LUT_RED_3 = 0x28
|
||||
LUT_XON = 0x29
|
||||
PLL_CONTROL = 0x30
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40
|
||||
TEMPERATURE_CALIBRATION = 0x41
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42
|
||||
TEMPERATURE_SENSOR_READ = 0x43
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
|
||||
LOW_POWER_DETECTION = 0x51
|
||||
TCON_SETTING = 0x60
|
||||
TCON_RESOLUTION = 0x61
|
||||
SPI_FLASH_CONTROL = 0x65
|
||||
REVISION = 0x70
|
||||
GET_STATUS = 0x71
|
||||
AUTO_MEASUREMENT_VCOM = 0x80
|
||||
READ_VCOM_VALUE = 0x81
|
||||
VCM_DC_SETTING = 0x82
|
||||
|
||||
class EPD:
|
||||
def __init__(self):
|
||||
self.reset_pin = epdif.RST_PIN
|
||||
self.dc_pin = epdif.DC_PIN
|
||||
self.busy_pin = epdif.BUSY_PIN
|
||||
self.width = EPD_WIDTH
|
||||
self.height = EPD_HEIGHT
|
||||
|
||||
def digital_write(self, pin, value):
|
||||
epdif.epd_digital_write(pin, value)
|
||||
|
||||
def digital_read(self, pin):
|
||||
return epdif.epd_digital_read(pin)
|
||||
|
||||
def delay_ms(self, delaytime):
|
||||
epdif.epd_delay_ms(delaytime)
|
||||
|
||||
def send_command(self, command):
|
||||
self.digital_write(self.dc_pin, GPIO.LOW)
|
||||
# the parameter type is list but not int
|
||||
# so use [command] instead of command
|
||||
epdif.spi_transfer([command])
|
||||
|
||||
def send_data(self, data):
|
||||
self.digital_write(self.dc_pin, GPIO.HIGH)
|
||||
# the parameter type is list but not int
|
||||
# so use [data] instead of data
|
||||
epdif.spi_transfer([data])
|
||||
|
||||
def init(self):
|
||||
if (epdif.epd_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
|
||||
self.send_command(POWER_SETTING)
|
||||
self.send_data(0x37)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(PANEL_SETTING)
|
||||
self.send_data(0xCF)
|
||||
self.send_data(0x08)
|
||||
|
||||
self.send_command(BOOSTER_SOFT_START)
|
||||
self.send_data(0xc7)
|
||||
self.send_data(0xcc)
|
||||
self.send_data(0x28)
|
||||
|
||||
self.send_command(POWER_ON)
|
||||
self.wait_until_idle()
|
||||
|
||||
self.send_command(PLL_CONTROL)
|
||||
self.send_data(0x3c)
|
||||
|
||||
self.send_command(TEMPERATURE_CALIBRATION)
|
||||
self.send_data(0x00)
|
||||
|
||||
self.send_command(VCOM_AND_DATA_INTERVAL_SETTING)
|
||||
self.send_data(0x77)
|
||||
|
||||
self.send_command(TCON_SETTING)
|
||||
self.send_data(0x22)
|
||||
|
||||
self.send_command(TCON_RESOLUTION)
|
||||
self.send_data(0x02) #source 640
|
||||
self.send_data(0x80)
|
||||
self.send_data(0x01) #gate 384
|
||||
self.send_data(0x80)
|
||||
|
||||
self.send_command(VCM_DC_SETTING)
|
||||
self.send_data(0x1E) #decide by LUT file
|
||||
|
||||
self.send_command(0xe5) #FLASH MODE
|
||||
self.send_data(0x03)
|
||||
|
||||
def wait_until_idle(self):
|
||||
while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle
|
||||
self.delay_ms(100)
|
||||
|
||||
def reset(self):
|
||||
self.digital_write(self.reset_pin, GPIO.LOW) # module reset
|
||||
self.delay_ms(200)
|
||||
self.digital_write(self.reset_pin, GPIO.HIGH)
|
||||
self.delay_ms(200)
|
||||
|
||||
def get_frame_buffer(self, image):
|
||||
buf = [0x00] * int(self.width * self.height / 8)
|
||||
# Set buffer to value of Python Imaging Library image.
|
||||
# Image must be in mode 1.
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
if imwidth != self.width or imheight != self.height:
|
||||
raise ValueError('Image must be same dimensions as display \
|
||||
({0}x{1}).' .format(self.width, self.height))
|
||||
|
||||
pixels = image_monocolor.load()
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] != 0:
|
||||
buf[int((x + y * self.width) / 8)] |= 0x80 >> (x % 8)
|
||||
return buf
|
||||
|
||||
def display_frame(self, frame_buffer):
|
||||
self.send_command(DATA_START_TRANSMISSION_1)
|
||||
for i in range(0, 30720):
|
||||
temp1 = frame_buffer[i]
|
||||
j = 0
|
||||
while (j < 8):
|
||||
if(temp1 & 0x80):
|
||||
temp2 = 0x03
|
||||
else:
|
||||
temp2 = 0x00
|
||||
temp2 = (temp2 << 4) & 0xFF
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
j += 1
|
||||
if(temp1 & 0x80):
|
||||
temp2 |= 0x03
|
||||
else:
|
||||
temp2 |= 0x00
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
self.send_data(temp2)
|
||||
j += 1
|
||||
self.send_command(DISPLAY_REFRESH)
|
||||
self.delay_ms(100)
|
||||
self.wait_until_idle()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(POWER_OFF)
|
||||
self.wait_until_idle()
|
||||
self.send_command(DEEP_SLEEP)
|
||||
self.send_data(0xa5)
|
||||
|
||||
### END OF FILE ###
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import spidev
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
|
||||
# Pin definition
|
||||
RST_PIN = 17
|
||||
DC_PIN = 25
|
||||
CS_PIN = 8
|
||||
BUSY_PIN = 24
|
||||
|
||||
# SPI device, bus = 0, device = 0
|
||||
SPI = spidev.SpiDev(0, 0)
|
||||
|
||||
def epd_digital_write(pin, value):
|
||||
GPIO.output(pin, value)
|
||||
|
||||
def epd_digital_read(pin):
|
||||
return GPIO.input(BUSY_PIN)
|
||||
|
||||
def epd_delay_ms(delaytime):
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_transfer(data):
|
||||
SPI.writebytes(data)
|
||||
|
||||
def epd_init():
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setup(RST_PIN, GPIO.OUT)
|
||||
GPIO.setup(DC_PIN, GPIO.OUT)
|
||||
GPIO.setup(CS_PIN, GPIO.OUT)
|
||||
GPIO.setup(BUSY_PIN, GPIO.IN)
|
||||
SPI.max_speed_hz = 2000000
|
||||
SPI.mode = 0b00
|
||||
return 0;
|
||||
|
||||
### END OF FILE ###
|
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 710 B |
Before Width: | Height: | Size: 142 B |
Before Width: | Height: | Size: 122 B |
Before Width: | Height: | Size: 710 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 278 B |
|
@ -1,248 +0,0 @@
|
|||
"""
|
||||
E-Paper Software (main script) adapted for the 2-colour E-Paper display
|
||||
A full and detailed breakdown for this code can be found in the wiki.
|
||||
If you have any questions, feel free to open an issue at Github.
|
||||
|
||||
Copyright by Ace-Laboratory
|
||||
"""
|
||||
|
||||
# url refers to the iCal url. It's the link you can copy when you click on
|
||||
# 'export' Calendar in Google or Yahoo (and many more online) Calendars
|
||||
|
||||
# api-key refers to your openweathermap api key. It can be generated for free
|
||||
# when you sign up for an account and consists of a bunch of numbers and letters
|
||||
|
||||
# location refers to the city you live in. You api key will be used to grab live
|
||||
# weather data for this city. Use the format below (city-name, country code)
|
||||
|
||||
# week_starts_on refers to the day on which the week starts on in your country.
|
||||
# Choose between Monday and Sunday.
|
||||
|
||||
""" To quickly get started, fill in the following details:"""
|
||||
|
||||
url = "https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
|
||||
api_key = ""
|
||||
location = "California, US"
|
||||
week_starts_on = "Monday"
|
||||
|
||||
"""That's all. The software will do the rest. You don't need to modify anything below this."""
|
||||
|
||||
import epd7in5
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps #image operations
|
||||
import calendar, pyowm #calendar and openweathermap wrapper
|
||||
from ics import Calendar, Event #icalendar parser
|
||||
from datetime import datetime #time operations
|
||||
from time import sleep #more time operations
|
||||
from urllib.request import urlopen #allows url to be 'read'
|
||||
import arrow #icalendar parser compatible dates
|
||||
from calibration import calibration
|
||||
|
||||
epd = epd7in5.EPD() #required
|
||||
|
||||
if (week_starts_on == "Monday"):
|
||||
calendar.setfirstweekday(calendar.MONDAY)
|
||||
|
||||
if (week_starts_on == "Sunday"):
|
||||
calendar.setfirstweekday(calendar.Sunday)
|
||||
|
||||
c = Calendar(urlopen(url).read().decode('UTF-8'))
|
||||
e = Event()
|
||||
open = Image.open
|
||||
EPD_WIDTH = 640
|
||||
EPD_HEIGHT = 384
|
||||
|
||||
path = '/home/pi/E-Paper-Master/Calendar/'
|
||||
wpath = path+'weather-icons/'
|
||||
mpath = path+'months/'
|
||||
dpath = path+'days/'
|
||||
font = ImageFont.truetype(path+'Assistant-Bold.ttf', 18)
|
||||
|
||||
weekday = open(path+'other/weekday.bmp')
|
||||
eventicon = open(path+'other/event.bmp')
|
||||
dateicon = open(path+'other/today.bmp')
|
||||
tempicon = open(path+'other/temp-icon.bmp')
|
||||
humicon = open(path+'other/hum-icon.bmp')
|
||||
weekmon = open(path+'other/week-mon.bmp')
|
||||
weeksun = open(path+'other/week-sun.bmp')
|
||||
bar = open(path+'other/bar.bmp')
|
||||
|
||||
wiconplace = (570, 219)
|
||||
tempplace = (605, 310)
|
||||
humplace = (572, 308)
|
||||
monthplace = (443, 0)
|
||||
weekplace = (415,0)
|
||||
barplace = (555, 0)
|
||||
|
||||
weekdaysmon = {'Mon': (416,3), 'Tue': (416,57), 'Wed': (416,111), 'Thu': (416,165), 'Fri': (416,219), 'Sat': (416,273), 'Sun':(416,327)}
|
||||
weekdayssun = {'Sun': (416,3), 'Mon': (416,57), 'Tue': (416,111), 'Wed': (416,165), 'Thu': (416,219), 'Fri': (416,273), 'Sat':(416,327)}
|
||||
|
||||
positions = {'a1': (351, 3), 'a2': (351, 57), 'a3': (351, 111), 'a4': (351, 165), 'a5': (351, 219), 'a6': (351, 273), 'a7': (351, 327),
|
||||
'b1': (284, 3), 'b2': (284, 57), 'b3': (284, 111), 'b4': (284, 165), 'b5': (284, 219), 'b6': (284, 273), 'b7': (284, 327),
|
||||
'c1': (217, 3), 'c2': (217, 57), 'c3': (217, 111), 'c4': (217, 165), 'c5': (217, 219), 'c6': (217, 273), 'c7': (217, 327),
|
||||
'd1': (150, 3), 'd2': (150, 57), 'd3': (150, 111), 'd4': (150, 165), 'd5': (150, 219), 'd6': (150, 273), 'd7': (150, 327),
|
||||
'e1': (83, 3), 'e2': (83, 57), 'e3': (83, 111), 'e4': (83, 165), 'e5': (83, 219), 'e6': (83, 273), 'e7': (83, 327),
|
||||
'f1': (16, 3), 'f2': (16, 57), 'f3': (16, 111), 'f4': (16, 165), 'f5': (16, 219), 'f6': (16, 273), 'f7': (16, 327)}
|
||||
|
||||
weathericons = {'01d': 'wi-day-sunny', '02d':'wi-day-cloudy', '03d': 'wi-cloudy',
|
||||
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d':'wi-rain',
|
||||
'11d':'wi-thunderstorm', '13d':'wi-snow', '50d': 'wi-fog',
|
||||
'01n': 'wi-night-clear', '02n':'wi-night-cloudy',
|
||||
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
|
||||
'09n': 'wi-night-showers', '10n':'wi-night-rain',
|
||||
'11n':'wi-night-thunderstorm', '13n':'wi-night-snow',
|
||||
'50n': 'wi-night-alt-cloudy-windy'}
|
||||
|
||||
def main():
|
||||
while True:
|
||||
|
||||
time = datetime.now()
|
||||
hour = int(time.strftime("%-H"))
|
||||
|
||||
for i in range(1):
|
||||
if hour is 0:
|
||||
calibration()
|
||||
if hour is 12:
|
||||
calibration()
|
||||
if hour is 18:
|
||||
calibration()
|
||||
|
||||
epd.init()
|
||||
image = Image.new('1', (EPD_WIDTH, EPD_HEIGHT), 255)
|
||||
draw = (ImageDraw.Draw(image)).bitmap
|
||||
|
||||
#background image
|
||||
draw(monthplace, Image.open(mpath+str(time.strftime("%B"))+'.bmp'))
|
||||
|
||||
if calendar.firstweekday() == 0:
|
||||
#print('Your week starts on Monday') #->debug
|
||||
draw(weekplace, weekmon)
|
||||
|
||||
if calendar.firstweekday() == 6:
|
||||
#print('Your week starts on Sunday') #->debug
|
||||
draw(weekplace, weeksun)
|
||||
|
||||
draw(barplace, bar)
|
||||
|
||||
cal = calendar.monthcalendar(time.year, time.month)
|
||||
|
||||
for i in cal[0]:
|
||||
draw(positions['a'+str(cal[0].index(i)+1)] ,open(dpath+str(i)+'.bmp'))
|
||||
for i in cal[1]:
|
||||
draw(positions['b'+str(cal[1].index(i)+1)] ,open(dpath+str(i)+'.bmp'))
|
||||
for i in cal[2]:
|
||||
draw(positions['c'+str(cal[2].index(i)+1)] ,open(dpath+str(i)+'.bmp'))
|
||||
for i in cal[3]:
|
||||
draw(positions['d'+str(cal[3].index(i)+1)] ,open(dpath+str(i)+'.bmp'))
|
||||
for i in cal[4]:
|
||||
draw(positions['e'+str(cal[4].index(i)+1)] ,open(dpath+str(i)+'.bmp'))
|
||||
try:
|
||||
for i in cal[5]:
|
||||
draw(positions['f'+str(cal[5].index(i)+1)] ,Image.open(dpath+str(i)+'.bmp'))
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# openweathermap api
|
||||
owm = pyowm.OWM(api_key)
|
||||
observation = owm.weather_at_place(location)
|
||||
weather = observation.get_weather()
|
||||
weathericon = weather.get_weather_icon_name()
|
||||
Temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
|
||||
Humidity = str(weather.get_humidity())
|
||||
#print('temp: '+Temperature +'°C') #->debug
|
||||
#print('humidity: '+Humidity+'%') #->debug
|
||||
#print(weathericon) #->debug
|
||||
|
||||
#weather icon handler
|
||||
draw(wiconplace, open(wpath+weathericons[weathericon]+'.bmp'))
|
||||
|
||||
# date writing function
|
||||
space1=Image.new('1', (115,25), color=255)
|
||||
measure1= ImageDraw.Draw(space1)
|
||||
date = ImageDraw.Draw(space1)
|
||||
date.text((2, 3), (time.strftime('%a %-d %b %y')), font=font, fill=0)
|
||||
rotate1 = space1.rotate(270, expand=1)
|
||||
image.paste(rotate1, (595,20))
|
||||
|
||||
# temperature writing function
|
||||
space2 = Image.new('1', (50,35), color=255)
|
||||
measure2= ImageDraw.Draw(space2)
|
||||
temperature = ImageDraw.Draw(space2)
|
||||
temperature.text((2, 8), (Temperature + " °C"), fill=0 ,font=font)
|
||||
rotate2 = space2.rotate(270, expand=1)
|
||||
image.paste(rotate2, (605,334))
|
||||
|
||||
# humidity writing function
|
||||
space3 = Image.new('1', (50,35), color=255)
|
||||
measure3= ImageDraw.Draw(space3)
|
||||
humidity = ImageDraw.Draw(space3)
|
||||
humidity.text((4, 8), (Humidity +'%'), fill=0 ,font=font)
|
||||
rotate3 = space3.rotate(270, expand=1)
|
||||
image.paste(rotate3, (570,334))
|
||||
|
||||
# weekday handler
|
||||
if calendar.firstweekday() == 0:
|
||||
draw(weekdaysmon[(time.strftime("%a"))], weekday)
|
||||
|
||||
if calendar.firstweekday() == 6:
|
||||
draw(weekdayssun[(time.strftime("%a"))], weekday)
|
||||
|
||||
print('It is currently:',time.strftime('%a %-d %b %y')) #--debug
|
||||
print('The current time is:', time.strftime('%H:%M')) #--debug
|
||||
|
||||
elist = []
|
||||
for events in c.events:
|
||||
if str(time.year) in str((events.begin).format('YYYY')):
|
||||
if str(time.month) in str((events.begin).format('M')):
|
||||
elist.append(int((events.begin).format('D')))
|
||||
|
||||
print('In this month, you have',len(elist),'Events')
|
||||
|
||||
for x in elist:
|
||||
if x in cal[0]:
|
||||
draw(positions['a'+str(cal[0].index(x)+1)] ,eventicon)
|
||||
if x in cal[1]:
|
||||
draw(positions['b'+str(cal[1].index(x)+1)] ,eventicon)
|
||||
if x in cal[2]:
|
||||
draw(positions['c'+str(cal[2].index(x)+1)] ,eventicon)
|
||||
if x in cal[3]:
|
||||
draw(positions['d'+str(cal[3].index(x)+1)] ,eventicon)
|
||||
if x in cal[4]:
|
||||
draw(positions['e'+str(cal[4].index(x)+1)] ,eventicon)
|
||||
try:
|
||||
if x in cal[5]:
|
||||
draw(positions['f'+str(cal[5].index(x)+1)] ,eventicon)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
today = time.day
|
||||
if today in cal[0]:
|
||||
draw(positions['a'+str(cal[0].index(today)+1)] ,dateicon)
|
||||
if today in cal[1]:
|
||||
draw(positions['b'+str(cal[1].index(today)+1)] ,dateicon)
|
||||
if today in cal[2]:
|
||||
draw(positions['c'+str(cal[2].index(today)+1)] ,dateicon)
|
||||
if today in cal[3]:
|
||||
draw(positions['d'+str(cal[3].index(today)+1)] ,dateicon)
|
||||
if today in cal[4]:
|
||||
draw(positions['e'+str(cal[4].index(today)+1)] ,dateicon)
|
||||
try:
|
||||
if today in cal[5]:
|
||||
draw(positions['f'+str(cal[5].index(today)+1)] ,dateicon)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
draw(tempplace, tempicon)
|
||||
draw(humplace, humicon)
|
||||
epd.display_frame(epd.get_frame_buffer(image))
|
||||
|
||||
# delete the list so deleted events can be removed from the list
|
||||
del elist[:]
|
||||
epd.sleep()
|
||||
|
||||
for i in range(1):
|
||||
nexthour = ((60 - int(time.strftime("%-M")))*60) - (int(time.strftime("%-S")))
|
||||
sleep(nexthour)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6 KiB |
101
Calendar/AgendaListDesign.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
from DesignEntity import DesignEntity
|
||||
from Assets import defaultfontsize, colors, defaultfont, path
|
||||
from datetime import datetime, date, timedelta
|
||||
from TableDesign import TableDesign
|
||||
from PIL import ImageDraw, ImageFont
|
||||
from TextFormatter import date_summary_str, event_prefix_str
|
||||
from settings import line_thickness
|
||||
|
||||
separator_width = line_thickness
|
||||
|
||||
|
||||
class AgendaListDesign (DesignEntity):
|
||||
'''Lists upcoming events in chronological order and groups them by days'''
|
||||
|
||||
def __init__(self, size, calendar, line_spacing=0, col_spacing=8, text_size=defaultfontsize, start_date=date.today(), always_add_start_row=True, day_limit_foresight=91):
|
||||
super(AgendaListDesign, self).__init__(size)
|
||||
self.calendar = calendar
|
||||
self.line_spacing = line_spacing
|
||||
self.col_spacing = col_spacing
|
||||
self.text_size = text_size
|
||||
self.day_limit_foresight = day_limit_foresight
|
||||
self.start_dt = date(start_date.year, start_date.month, start_date.day)
|
||||
self.always_add_start_row = always_add_start_row
|
||||
|
||||
def __finish_image__(self):
|
||||
self.__calculate_parameter__()
|
||||
self.__create_infos_events__()
|
||||
self.__draw_infos__()
|
||||
self.__draw_lines__()
|
||||
|
||||
def __calculate_parameter__(self):
|
||||
self.__line_height__ = self.line_spacing + self.__get_text_height__()
|
||||
self.__event_number__ = int(int(self.size[1]) // self.__line_height__)
|
||||
self.__date_fontsize__ = self.text_size
|
||||
self.__date_linespace__ = self.line_spacing
|
||||
|
||||
def __create_infos_events__(self):
|
||||
self.infos = []
|
||||
self.cell_props = []
|
||||
fetch_day = self.start_dt
|
||||
days_foresight = 0
|
||||
while len(self.infos) < self.__event_number__ and days_foresight < self.day_limit_foresight:
|
||||
day_events = self.calendar.get_day_events(fetch_day)
|
||||
fetch_day_added_once = False
|
||||
for event in day_events:
|
||||
row = [""]
|
||||
if fetch_day_added_once is False:
|
||||
row.append(date_summary_str(fetch_day))
|
||||
fetch_day_added_once = True
|
||||
else:
|
||||
row.append("")
|
||||
|
||||
row.append(event_prefix_str(event, fetch_day))
|
||||
row.append(event.title)
|
||||
self.cell_props.append(self.__get_row_props__(event))
|
||||
|
||||
self.infos.append(row)
|
||||
fetch_day = fetch_day + timedelta(1)
|
||||
days_foresight = days_foresight + 1
|
||||
|
||||
if self.infos[0][1] != date_summary_str(self.start_dt) and self.always_add_start_row:
|
||||
row = ["", date_summary_str(self.start_dt), "", ""]
|
||||
props = self.__get_row_props__()
|
||||
self.infos.insert(0, row)
|
||||
self.cell_props.insert(0, props)
|
||||
|
||||
def __draw_infos__(self):
|
||||
table = TableDesign(self.size, self.infos, fontsize=self.__date_fontsize__,
|
||||
line_spacing=self.__date_linespace__, col_spacing=self.col_spacing, cell_properties=self.cell_props)
|
||||
self.draw_design(table)
|
||||
|
||||
def __draw_lines__(self):
|
||||
for i, (_, date, _, _) in enumerate(self.infos[1:]):
|
||||
if date is not "":
|
||||
self.__draw_line__(i + 1)
|
||||
|
||||
def __draw_line__(self, index):
|
||||
ypos = index * self.__line_height__ - self.line_spacing / 2
|
||||
pos = (0, ypos)
|
||||
positions = [pos, (self.size[0], ypos)]
|
||||
|
||||
ImageDraw.Draw(self.__image__).line(
|
||||
positions, fill=colors["fg"], width=separator_width)
|
||||
|
||||
def __get_row_props__(self, event=None):
|
||||
color = colors["fg"]
|
||||
bg_color = colors["bg"]
|
||||
default_cell = {
|
||||
"color": color,
|
||||
"background_color": bg_color
|
||||
}
|
||||
if event is not None and event.highlight:
|
||||
color = colors["hl"]
|
||||
cell = {
|
||||
"color": color,
|
||||
"background_color": bg_color
|
||||
}
|
||||
return [default_cell, default_cell, cell, cell]
|
||||
|
||||
def __get_text_height__(self):
|
||||
return ImageFont.truetype(path + defaultfont, self.text_size).font.height
|
90
Calendar/AgendaListPanel.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
from PanelDesign import PanelDesign
|
||||
from AgendaListDesign import AgendaListDesign
|
||||
from WeatherHeaderDesign import WeatherHeaderDesign
|
||||
from settings import general_settings, line_thickness
|
||||
from PIL import ImageDraw
|
||||
from Assets import colors
|
||||
from RssPostListDesign import RssPostListDesign
|
||||
from CryptoListDesign import CryptoListDesign
|
||||
|
||||
agenda_ypadding = 5
|
||||
weatherheader_height = 0.113
|
||||
seperator_width = line_thickness
|
||||
infolist_size = (1, 0.24)
|
||||
infolist_padding = 0
|
||||
|
||||
|
||||
class AgendaListPanel (PanelDesign):
|
||||
'''Lists upcoming events in chronological order and groups them by days'''
|
||||
|
||||
def __init__(self, size):
|
||||
super(AgendaListPanel, self).__init__(size)
|
||||
self.weather_size = (0, 0)
|
||||
self.info_size = (0, 0)
|
||||
if general_settings["weather-info"]:
|
||||
self.weather_size = (
|
||||
self.size[0], self.size[1] * weatherheader_height)
|
||||
|
||||
def add_weather(self, weather):
|
||||
self.weather = weather
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
self.calendar = calendar
|
||||
|
||||
def add_rssfeed(self, rss):
|
||||
if general_settings["info-area"] != "rss":
|
||||
return
|
||||
|
||||
self.info_size = self.__abs_pos__(infolist_size)
|
||||
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
|
||||
|
||||
list = RssPostListDesign(self.info_size, rss)
|
||||
list.pos = pos
|
||||
self.draw_design(list)
|
||||
|
||||
self.__draw_seperator__(1-infolist_size[1], colors["fg"])
|
||||
|
||||
def add_tasks(self, tasks):
|
||||
pass
|
||||
|
||||
def add_crypto(self, crypto):
|
||||
if general_settings["info-area"] != "crypto":
|
||||
return
|
||||
|
||||
self.info_size = self.__abs_pos__(infolist_size)
|
||||
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
|
||||
|
||||
list = CryptoListDesign(self.info_size, crypto)
|
||||
height = list.get_estimated_height()
|
||||
list.pos = (pos[0], pos[1] + (self.info_size[1] - height))
|
||||
self.draw_design(list)
|
||||
|
||||
self.info_size = (self.size[0], height)
|
||||
self.__draw_seperator__(list.pos[1] / self.size[1], colors["fg"])
|
||||
|
||||
def __finish_panel__(self):
|
||||
self.__draw_calendar__()
|
||||
if general_settings["weather-info"]:
|
||||
self.__draw_weather__()
|
||||
|
||||
def __draw_seperator__(self, height, color):
|
||||
ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
|
||||
(0, height)), self.__abs_pos__((1, height))], fill=color, width=seperator_width)
|
||||
|
||||
def __abs_pos__(self, pos, size=None):
|
||||
if size is None:
|
||||
size = self.size
|
||||
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
||||
|
||||
def __draw_calendar__(self):
|
||||
size = (self.size[0], self.size[1] - self.weather_size[1] -
|
||||
self.info_size[1] - agenda_ypadding)
|
||||
|
||||
agenda = AgendaListDesign(size, self.calendar)
|
||||
agenda.pos = (0, agenda_ypadding + self.weather_size[1])
|
||||
self.draw_design(agenda)
|
||||
|
||||
def __draw_weather__(self):
|
||||
header = WeatherHeaderDesign(self.weather_size, self.weather)
|
||||
self.draw_design(header)
|
||||
self.__draw_seperator__(weatherheader_height, colors["hl"])
|
95
Calendar/Assets.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from PIL import Image, ImageFont
|
||||
from settings import font_boldness, font_size
|
||||
import os
|
||||
im_open = Image.open
|
||||
|
||||
path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
|
||||
if path != "" and path[-1] != "/":
|
||||
path += "/"
|
||||
|
||||
wpath = path+'weather-icons/'
|
||||
opath = path+'other/'
|
||||
fpath = 'fonts/'
|
||||
|
||||
tempicon = im_open(opath+'temperature.jpeg')
|
||||
humicon = im_open(opath+'humidity.jpeg')
|
||||
no_response = im_open(opath+'cloud-no-response.jpeg')
|
||||
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
|
||||
sunseticon = im_open(opath+'wi-sunset.jpeg')
|
||||
windicon = im_open(opath+'wi-strong-wind.jpeg')
|
||||
|
||||
fonts = {
|
||||
"extralight": fpath + "Assistant-ExtraLight.otf",
|
||||
"light": fpath + "Assistant-Light.otf",
|
||||
"regular": fpath + "Assistant-Regular.otf",
|
||||
"semibold": fpath + "Assistant-SemiBold.otf",
|
||||
"bold": fpath + "Assistant-Bold.otf",
|
||||
"extrabold": fpath + "Assistant-ExtraBold.otf"
|
||||
}
|
||||
|
||||
defaultfont = fonts[font_boldness]
|
||||
defaultfontsize = int(font_size)
|
||||
|
||||
weathericons = {
|
||||
'01d': 'wi-day-sunny', '02d': 'wi-day-cloudy', '03d': 'wi-cloudy',
|
||||
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d': 'wi-rain',
|
||||
'11d': 'wi-thunderstorm', '13d': 'wi-snow', '50d': 'wi-fog',
|
||||
'01n': 'wi-night-clear', '02n': 'wi-night-cloudy',
|
||||
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
|
||||
'09n': 'wi-night-showers', '10n': 'wi-night-rain',
|
||||
'11n': 'wi-night-thunderstorm', '13n': 'wi-night-snow',
|
||||
'50n': 'wi-night-alt-cloudy-windy'}
|
||||
|
||||
colors = {
|
||||
"hl": "red",
|
||||
"fg": "black",
|
||||
"bg": "white"
|
||||
}
|
||||
|
||||
supported_img_formats = [
|
||||
"BMP",
|
||||
"DIB",
|
||||
"EPS",
|
||||
"GIF",
|
||||
"ICNS",
|
||||
"ICO",
|
||||
"IM",
|
||||
"JPG",
|
||||
"JPEG",
|
||||
"J2K",
|
||||
"J2P",
|
||||
"JPX",
|
||||
"MSP",
|
||||
"PCX",
|
||||
"PNG",
|
||||
"PPM",
|
||||
"SGI",
|
||||
"SPI",
|
||||
"TGA",
|
||||
"TIFF",
|
||||
"WEBP",
|
||||
"XBM",
|
||||
"BLP",
|
||||
"CUR",
|
||||
"DCX",
|
||||
"DDS",
|
||||
"FLI",
|
||||
"FLC",
|
||||
"FPX",
|
||||
"FTEX",
|
||||
"GBR",
|
||||
"GD",
|
||||
"IMT",
|
||||
"IPTC",
|
||||
"NAA",
|
||||
"MCIDAS",
|
||||
"MIC",
|
||||
"MPO",
|
||||
"PCD",
|
||||
"PIXAR",
|
||||
"PSD",
|
||||
"WAL",
|
||||
"XPM",
|
||||
]
|
23
Calendar/BoxDesign.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from DesignEntity import DesignEntity
|
||||
from PIL import ImageDraw, ImageOps
|
||||
|
||||
|
||||
class BoxDesign (DesignEntity):
|
||||
"""Redefinition of ImageDraw.Draw.Rectangle"""
|
||||
|
||||
def __init__(self, size, fill=None, outline=None, width=0):
|
||||
super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True)
|
||||
self.size = size
|
||||
self.__define_corners__()
|
||||
self.fill = fill
|
||||
self.outline = outline
|
||||
self.width = width
|
||||
|
||||
def __define_corners__(self):
|
||||
topleft = (0, 0)
|
||||
bottomright = self.size
|
||||
self.corners = [topleft, bottomright]
|
||||
|
||||
def __finish_image__(self):
|
||||
ImageDraw.Draw(self.__image__).rectangle(
|
||||
self.corners, fill=self.fill, outline=self.outline, width=self.width)
|
24
Calendar/CalendarEvent.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
class CalendarEvent (object):
|
||||
"""Defines a calendar event, independent of any implementation"""
|
||||
|
||||
def __init__(self):
|
||||
self.begin_datetime = None
|
||||
self.end_datetime = None
|
||||
self.duration = None
|
||||
self.allday = None
|
||||
self.multiday = None
|
||||
self.rrule = None
|
||||
|
||||
self.title = None
|
||||
self.description = None
|
||||
self.attendees = []
|
||||
self.highlight = None
|
||||
|
||||
self.calendar_name = None
|
||||
self.calendar_url = None
|
||||
|
||||
self.location = None
|
||||
self.fetch_datetime = None
|
||||
|
||||
def __repr__(self):
|
||||
return self.title
|
182
Calendar/CalendarInterface.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
from DataSourceInterface import DataSourceInterface
|
||||
from datetime import datetime, timezone, timedelta, date
|
||||
from dateutil.rrule import rrulestr
|
||||
from dateutil.parser import parse
|
||||
import calendar
|
||||
from CalendarEvent import CalendarEvent
|
||||
|
||||
|
||||
class CalendarInterface (DataSourceInterface):
|
||||
"""Interface for fetching and processing calendar event information."""
|
||||
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
self.excluded_urls = []
|
||||
|
||||
def reload(self):
|
||||
if self.is_available() == False:
|
||||
return
|
||||
self.events = self.__get_events__()
|
||||
self.events = self.__sort_events__(self.events)
|
||||
|
||||
def exclude_calendars(self, urls=[]):
|
||||
self.excluded_urls = urls
|
||||
|
||||
def __sort_events__(self, events):
|
||||
events.sort(key=lambda x: x.begin_datetime)
|
||||
return events
|
||||
|
||||
def __sort_event_types__(self, events):
|
||||
multiday = [ev for ev in events if ev.multiday]
|
||||
allday = [ev for ev in events if ev.allday and ev.multiday == False]
|
||||
timed = [ev for ev in events if ev.allday ==
|
||||
False and ev.multiday == False]
|
||||
return multiday + allday + timed
|
||||
|
||||
def __get_events__(self):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def get_upcoming_events(self, timespan=None, start_time=None):
|
||||
if timespan is None:
|
||||
timespan = timedelta(31)
|
||||
if start_time == None:
|
||||
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||
start_time = datetime.now(local_tzinfo)
|
||||
return self.__get_events_in_range__(start_time, timespan)
|
||||
|
||||
def get_today_events(self):
|
||||
return self.get_day_events(date.today())
|
||||
|
||||
def get_day_events(self, day):
|
||||
if type(day) is not type(date.today()):
|
||||
raise TypeError(
|
||||
"get_day_events only takes date-objects as parameters, not \"%s\"" % str(type(day)))
|
||||
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||
day_start = datetime(day.year, day.month, day.day,
|
||||
0, 0, 0, 0, local_tzinfo)
|
||||
return self.__get_events_in_range__(day_start, timedelta(1))
|
||||
|
||||
def get_month_events(self, month=-1, year=-1):
|
||||
if month < 0:
|
||||
month = datetime.now().month
|
||||
if year < 0:
|
||||
year = datetime.now().year
|
||||
|
||||
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||
month_start = datetime(year, month, 1, 0, 0, 0, 0, local_tzinfo)
|
||||
month_days = calendar.monthrange(
|
||||
month_start.year, month_start.month)[1]
|
||||
return self.__get_events_in_range__(month_start, timedelta(month_days))
|
||||
|
||||
def __get_events_in_range__(self, start, duration):
|
||||
if self.events is None:
|
||||
return []
|
||||
|
||||
if start.tzinfo is None:
|
||||
raise TypeError("start datetime needs to be timezone-aware")
|
||||
|
||||
events_in_range = []
|
||||
for event in self.events:
|
||||
# Is excluded?
|
||||
if event.calendar_url in self.excluded_urls:
|
||||
continue
|
||||
|
||||
event_occurrence = self.__get_if_event_in_range__(
|
||||
event, start, duration)
|
||||
if event_occurrence:
|
||||
events_in_range.extend(event_occurrence)
|
||||
|
||||
events_in_range = self.__sort_events__(events_in_range)
|
||||
return self.__sort_event_types__(events_in_range)
|
||||
|
||||
def __get_if_event_in_range__(self, event, start, duration):
|
||||
'''Returns list or None'''
|
||||
if event is None:
|
||||
return None
|
||||
|
||||
if event.rrule is None:
|
||||
return self.__is_onetime_in_range__(event, start, duration)
|
||||
else:
|
||||
return self.__is_repeating_in_range__(event, start, duration)
|
||||
|
||||
def __is_onetime_in_range__(self, event, start, duration):
|
||||
if event.begin_datetime > start:
|
||||
first_start = start
|
||||
first_duration = duration
|
||||
second_start = event.begin_datetime
|
||||
else:
|
||||
first_start = event.begin_datetime
|
||||
first_duration = event.duration
|
||||
second_start = start
|
||||
|
||||
if (second_start - first_start) < first_duration:
|
||||
return [event]
|
||||
else:
|
||||
return None
|
||||
|
||||
def __is_repeating_in_range__(self, event, start, duration):
|
||||
end = start + duration
|
||||
occurrences = []
|
||||
|
||||
try:
|
||||
r_string = ""
|
||||
r_string = self.__add_timezoneawarness__(event.rrule)
|
||||
rule = rrulestr(r_string, dtstart=event.begin_datetime)
|
||||
for occurrence in rule:
|
||||
if occurrence - end > timedelta(0):
|
||||
return occurrences
|
||||
merged_event = self.__merge_event_data__(
|
||||
event, start=occurrence)
|
||||
if self.__is_onetime_in_range__(merged_event, start, duration):
|
||||
occurrences.append(merged_event)
|
||||
return occurrences
|
||||
except Exception as ex:
|
||||
print("\"is_repeating_in_range\" failed while processing: dtstart="+str(event.begin_datetime) +
|
||||
" dtstart.tzinfo="+str(event.begin_datetime.tzinfo)+" rrule="+r_string)
|
||||
raise ex
|
||||
|
||||
def __merge_event_data__(self, event, start=None):
|
||||
merged_event = CalendarEvent()
|
||||
|
||||
merged_event.begin_datetime = event.begin_datetime
|
||||
merged_event.end_datetime = event.end_datetime
|
||||
merged_event.duration = event.duration
|
||||
merged_event.allday = event.allday
|
||||
merged_event.multiday = event.multiday
|
||||
merged_event.rrule = event.rrule
|
||||
|
||||
merged_event.title = event.title
|
||||
merged_event.description = event.description
|
||||
merged_event.attendees = event.attendees
|
||||
merged_event.highlight = event.highlight
|
||||
|
||||
merged_event.calendar_name = event.calendar_name
|
||||
merged_event.calendar_url = event.calendar_url
|
||||
|
||||
merged_event.location = event.location
|
||||
merged_event.fetch_datetime = event.fetch_datetime
|
||||
|
||||
if start is not None:
|
||||
merged_event.begin_datetime = start
|
||||
merged_event.end_datetime = start + event.duration
|
||||
|
||||
return merged_event
|
||||
|
||||
def __add_timezoneawarness__(self, rrule):
|
||||
"""UNTIL must be specified in UTC when DTSTART is timezone-aware (which it is)"""
|
||||
if "UNTIL" not in rrule:
|
||||
return rrule
|
||||
|
||||
timezone_str = "T000000Z"
|
||||
until_template = "UNTIL=YYYYMMDD"
|
||||
|
||||
until_index = rrule.index("UNTIL")
|
||||
|
||||
tz_index = until_index + len(until_template)
|
||||
if until_index < 0 or (tz_index < len(rrule) and rrule[tz_index] is "T"):
|
||||
return rrule
|
||||
|
||||
if tz_index == len(rrule):
|
||||
return rrule + timezone_str
|
||||
else:
|
||||
return rrule[:tz_index] + timezone_str + rrule[tz_index:]
|
10
Calendar/CryptoCoin.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
class CryptoCoin(object):
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.symbol = None
|
||||
self.price = None
|
||||
self.day_change = None
|
||||
self.currency = None
|
||||
self.datetime = None
|
||||
|
||||
self.fetch_datetime = None
|
17
Calendar/CryptoInterface.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from DataSourceInterface import DataSourceInterface
|
||||
|
||||
|
||||
class CryptoInterface(DataSourceInterface):
|
||||
def __init__(self):
|
||||
self.crypto_coins = []
|
||||
|
||||
def reload(self):
|
||||
if self.is_available() == False:
|
||||
return
|
||||
self.crypto_coins = self.__get_coins__()
|
||||
|
||||
def __get_coins__(self):
|
||||
raise NotImplementedError("Function needs to be implemented")
|
||||
|
||||
def get_coins(self):
|
||||
return self.crypto_coins
|
39
Calendar/CryptoListDesign.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from DesignEntity import DesignEntity
|
||||
from TableDesign import TableDesign
|
||||
from Assets import defaultfontsize
|
||||
from GeckoCrypto import GeckoCrypto
|
||||
from settings import crypto_coins
|
||||
|
||||
xpadding = 5
|
||||
|
||||
|
||||
class CryptoListDesign (DesignEntity):
|
||||
def __init__(self, size, crypto, text_size=defaultfontsize):
|
||||
super(CryptoListDesign, self).__init__(size)
|
||||
self.crypto = crypto
|
||||
self.text_size = text_size
|
||||
self.matrix = self.__get_matrix__()
|
||||
|
||||
def __finish_image__(self):
|
||||
col_spacing = 10
|
||||
if len(self.matrix) > 0:
|
||||
col_spacing = (self.size[0] / len(self.matrix[0])) * 0.5
|
||||
|
||||
table_design = TableDesign(self.size, matrix=self.matrix, col_spacing=col_spacing,
|
||||
fontsize=self.text_size, mask=False, truncate_rows=True)
|
||||
table_design.pos = (xpadding, 0)
|
||||
self.draw_design(table_design)
|
||||
|
||||
def __get_matrix__(self):
|
||||
matrix = []
|
||||
coins = self.crypto.get_coins()
|
||||
for coin in coins:
|
||||
row = [coin.symbol.upper(), coin.name, coin.currency + " " +
|
||||
str(coin.price), "% " + str(coin.day_change)]
|
||||
matrix.append(row)
|
||||
return matrix
|
||||
|
||||
def get_estimated_height(self):
|
||||
line_height = self.text_size * 1.25
|
||||
height = line_height * len(self.matrix)
|
||||
return height
|
8
Calendar/DataSourceInterface.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
class DataSourceInterface (object):
|
||||
"""Interface for child interfaces that fetch data."""
|
||||
|
||||
def is_available(self):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def reload(self):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
33
Calendar/DayBoxDesign.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from DesignEntity import DesignEntity
|
||||
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||
from TextDesign import TextDesign
|
||||
|
||||
header_height = 0.2
|
||||
|
||||
|
||||
class DayBoxDesign (DesignEntity):
|
||||
"""Represents a day with its events in a box."""
|
||||
|
||||
def __init__(self, size, date):
|
||||
super(DayBoxDesign, self).__init__(size)
|
||||
self.date = date
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
self.calendar = calendar
|
||||
|
||||
def __finish_image__(self):
|
||||
self.__draw_header__()
|
||||
self.__draw_events__()
|
||||
|
||||
def __draw_header__(self):
|
||||
pass
|
||||
|
||||
def __draw_events__(self):
|
||||
events = self.calendar.get_day_events(self.date)
|
||||
|
||||
pos = (0, self.size[1] * header_height)
|
||||
size = (self.size[0], self.size[1] - pos[1])
|
||||
|
||||
event_list = SingelDayEventListDesign(size, events)
|
||||
event_list.pos = pos
|
||||
self.draw_design(event_list)
|
151
Calendar/DayFocusListPanel.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from datetime import date, datetime, timedelta, timezone
|
||||
from settings import line_thickness, general_settings
|
||||
from DayHeaderDesign import DayHeaderDesign
|
||||
from HourListDesign import HourListDesign
|
||||
from DayRowDesign import DayRowDesign
|
||||
from PanelDesign import PanelDesign
|
||||
from Assets import colors
|
||||
from PIL import ImageDraw
|
||||
|
||||
HEADER_SIZE = (1, 0.2)
|
||||
HOURLIST_HEIGHT = 0.3
|
||||
HOURLIST_SIZE = (1, HOURLIST_HEIGHT)
|
||||
DAYLIST_YPOS = HEADER_SIZE[1] + HOURLIST_SIZE[1]
|
||||
DAYLIST_HEIGHT = 1 - HEADER_SIZE[1] - HOURLIST_SIZE[1]
|
||||
DAYLIST_SIZE = (1, DAYLIST_HEIGHT)
|
||||
HOURS_COUNT = 6
|
||||
DAYROW_MIN_FORMAT = 40 / 384
|
||||
DAYROW_MAX_FORMAT = 60 / 384
|
||||
PANEL_LINE_THICKNESS = line_thickness
|
||||
|
||||
|
||||
class DayFocusListPanel (PanelDesign):
|
||||
"""Shows Day-View for today and a short Day-List for
|
||||
the upcoming days."""
|
||||
|
||||
def __init__(self, size):
|
||||
super(DayFocusListPanel, self).__init__(size)
|
||||
self.hours_count = HOURS_COUNT
|
||||
self.__init_modules__()
|
||||
|
||||
def __abs_co__(self, coordinates):
|
||||
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||
|
||||
def __init_modules__(self):
|
||||
self.__init_header__()
|
||||
self.__init_hourlist__()
|
||||
self.__init_daylist__()
|
||||
|
||||
def __init_header__(self):
|
||||
self.__header__ = DayHeaderDesign(
|
||||
self.__abs_co__(HEADER_SIZE), date.today())
|
||||
self.__header__.pos = (0, 0)
|
||||
|
||||
def __init_hourlist__(self):
|
||||
start, end = self.__get_current_hour_range__()
|
||||
size = self.__abs_co__(HOURLIST_SIZE)
|
||||
|
||||
self.__hourlist__ = HourListDesign(size, start, end)
|
||||
self.__hourlist__.pos = (0, self.__header__.size[1])
|
||||
|
||||
def __init_daylist__(self):
|
||||
self.__daylist_rows__ = []
|
||||
self.__calc_dayrow_size__()
|
||||
self.__create_day_rows__()
|
||||
|
||||
def __calc_dayrow_size__(self):
|
||||
max_area_height = DAYLIST_HEIGHT * self.size[1]
|
||||
max_row_number = max_area_height / (DAYROW_MIN_FORMAT * self.size[0])
|
||||
min_row_number = max_area_height / (DAYROW_MAX_FORMAT * self.size[0])
|
||||
average_row_number = (max_row_number + min_row_number) / 2
|
||||
self.dayrow_count = round(average_row_number)
|
||||
row_height = max_area_height / self.dayrow_count
|
||||
self.dayrow_size = (1, row_height / self.size[1])
|
||||
|
||||
def __create_day_rows__(self):
|
||||
following_days = self.__get_following_days__()
|
||||
for i, date in enumerate(following_days):
|
||||
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
|
||||
row.pos = self.__get_day_row_pos__(i)
|
||||
self.__daylist_rows__.append(row)
|
||||
|
||||
def __get_following_days__(self):
|
||||
following_days = []
|
||||
for i in range(self.dayrow_count):
|
||||
following_days.append(date.today() + timedelta(days=i + 1))
|
||||
return following_days
|
||||
|
||||
def __get_day_row_pos__(self, i):
|
||||
ypos = self.size[1] * DAYLIST_YPOS
|
||||
down_shift = i * self.dayrow_size[1] * self.size[1]
|
||||
return (0, int(ypos + down_shift))
|
||||
|
||||
def __finish_panel__(self):
|
||||
self.draw_design(self.__header__)
|
||||
self.draw_design(self.__hourlist__)
|
||||
|
||||
for row in self.__daylist_rows__:
|
||||
self.draw_design(row)
|
||||
self.__draw_daylist_lines__()
|
||||
|
||||
def __draw_daylist_lines__(self):
|
||||
positions = []
|
||||
for i in range(len(self.__daylist_rows__)):
|
||||
positions.append(self.__get_day_row_pos__(i)[1])
|
||||
for ypos in positions:
|
||||
line_start = (0, ypos)
|
||||
line_end = (self.size[0], ypos)
|
||||
ImageDraw.Draw(self.__image__).line(
|
||||
[line_start, line_end], fill=colors["fg"], width=PANEL_LINE_THICKNESS)
|
||||
|
||||
def __get_current_hour_range__(self):
|
||||
start_hour = datetime.now().hour
|
||||
additional_hours = self.hours_count - 1
|
||||
|
||||
if start_hour + additional_hours > 23:
|
||||
start_hour = 23 - additional_hours
|
||||
|
||||
return start_hour, start_hour + additional_hours
|
||||
|
||||
def add_weather(self, weather):
|
||||
self.__header__.add_weather(weather)
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
allday_ev, timed_ev = self.__split_events__(
|
||||
calendar.get_today_events())
|
||||
self.__header__.add_events(allday_ev)
|
||||
self.__hourlist__.add_events(timed_ev)
|
||||
|
||||
self.__add_calendar_daylist__(calendar)
|
||||
|
||||
def __split_events__(self, events):
|
||||
allday_ev = []
|
||||
timed_ev = []
|
||||
|
||||
for event in events:
|
||||
if event.allday:
|
||||
allday_ev.append(event)
|
||||
elif event.multiday:
|
||||
if self.__is_today__(event.begin_datetime):
|
||||
timed_ev.append(event)
|
||||
elif self.__is_today__(event.end_datetime):
|
||||
timed_ev.append(event)
|
||||
else:
|
||||
allday_ev.append(event)
|
||||
else:
|
||||
timed_ev.append(event)
|
||||
return allday_ev, timed_ev
|
||||
|
||||
def __is_today__(self, dt):
|
||||
today = date.today()
|
||||
return dt.day == today.day and \
|
||||
dt.month == today.month and \
|
||||
dt.year == today.year
|
||||
|
||||
def __add_calendar_daylist__(self, calendar):
|
||||
calendar.exclude_calendars(general_settings["extra-excluded-urls"])
|
||||
|
||||
for row in self.__daylist_rows__:
|
||||
row.add_calendar(calendar)
|
||||
|
||||
calendar.exclude_calendars()
|
154
Calendar/DayHeaderDesign.py
Normal file
|
@ -0,0 +1,154 @@
|
|||
from DesignEntity import DesignEntity
|
||||
from PIL import ImageDraw
|
||||
from TextDesign import TextDesign
|
||||
from WeatherColumnDesign import WeatherColumnDesign
|
||||
from datetime import date, timedelta, datetime, timezone
|
||||
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||
from Assets import fonts, colors, defaultfontsize
|
||||
from settings import general_settings
|
||||
from BoxDesign import BoxDesign
|
||||
|
||||
numberbox_ypos = 0.15
|
||||
numberbox_height = 1 - 2 * numberbox_ypos
|
||||
number_height = numberbox_height * 0.83
|
||||
number_boxypos = 0.17
|
||||
month_height = numberbox_height / 4
|
||||
monthbox_xpadding = 0.013
|
||||
monthbox_ypadding = -0.05
|
||||
monthbox_width = 1 - numberbox_ypos - monthbox_xpadding
|
||||
weathercolumn_y_size = (0.4, 1)
|
||||
weekday_height = numberbox_height * 0.19
|
||||
weekdaybox_height = (weekday_height / numberbox_height) * 1.5
|
||||
eventlist_static_fontsize = defaultfontsize
|
||||
eventlist_xpadding = monthbox_xpadding
|
||||
eventlist_ypadding = 0.01
|
||||
|
||||
numberbox_font_color = colors["bg"]
|
||||
numberbox_background_color = colors["hl"]
|
||||
weekday_font = fonts["bold"]
|
||||
|
||||
|
||||
class DayHeaderDesign (DesignEntity):
|
||||
"""Detailed and big view of a given date."""
|
||||
|
||||
def __init__(self, size, date):
|
||||
super(DayHeaderDesign, self).__init__(size)
|
||||
self.weather_column_width = 0
|
||||
self.date = date
|
||||
|
||||
def add_weather(self, weather):
|
||||
if general_settings["weather-info"] == False:
|
||||
return
|
||||
|
||||
forecast = weather.get_forecast_in_days(
|
||||
self.date.day - date.today().day)
|
||||
self.weather_column_width = weathercolumn_y_size[0] * self.size[1]
|
||||
size = (self.weather_column_width,
|
||||
weathercolumn_y_size[1] * self.size[1])
|
||||
pos = (self.size[0] - size[0], 0)
|
||||
|
||||
design = WeatherColumnDesign(size, forecast)
|
||||
design.pos = pos
|
||||
self.draw_design(design)
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||
now = datetime.now(local_tzinfo)
|
||||
time_until_tomorrow = (datetime(
|
||||
now.year, now.month, now.day, 0, 0, 0, 0, local_tzinfo) + timedelta(1)) - now
|
||||
self.__draw_event_list__(
|
||||
calendar.get_upcoming_events(time_until_tomorrow, now))
|
||||
|
||||
def add_events(self, events):
|
||||
self.__draw_event_list__(events)
|
||||
|
||||
def add_rssfeed(self, rss):
|
||||
pass
|
||||
|
||||
def add_crypto(self, crypto):
|
||||
pass
|
||||
|
||||
def __finish_image__(self):
|
||||
self.__draw_number_square__()
|
||||
self.__draw_month__()
|
||||
|
||||
def __draw_event_list__(self, events):
|
||||
box_ypos = numberbox_ypos * self.size[1]
|
||||
box_xpos = numberbox_ypos * self.size[1]
|
||||
box_height = numberbox_height * self.size[1]
|
||||
xpadding = eventlist_xpadding * self.size[0]
|
||||
ypadding = eventlist_ypadding * self.size[1]
|
||||
monthbox_height = (monthbox_ypadding + month_height) * self.size[1]
|
||||
pos = (box_xpos + box_height + xpadding,
|
||||
box_ypos + monthbox_height + ypadding)
|
||||
size = (self.size[0] - pos[0] - self.weather_column_width,
|
||||
self.size[1] - pos[1] - box_ypos)
|
||||
fontsize = eventlist_static_fontsize
|
||||
|
||||
rel_dates = [self.date for _ in range(len(events))]
|
||||
event_list = SingelDayEventListDesign(
|
||||
size, events, fontsize, event_prefix_rel_dates=rel_dates)
|
||||
event_list.pos = pos
|
||||
self.draw_design(event_list)
|
||||
|
||||
def __draw_month__(self):
|
||||
font_size = int(month_height * self.size[1])
|
||||
xpadding = int(monthbox_xpadding * self.size[0])
|
||||
ypadding = int(monthbox_ypadding * self.size[1])
|
||||
box_ypos = int(numberbox_ypos * self.size[1])
|
||||
box_height = int(numberbox_height * self.size[1])
|
||||
box_pos = (box_ypos + box_height + xpadding, box_ypos + ypadding)
|
||||
box_size = (int(monthbox_width * self.size[0]), box_height)
|
||||
|
||||
month_name = self.date.strftime("%B")
|
||||
month = TextDesign(box_size, text=month_name, fontsize=font_size)
|
||||
month.pos = box_pos
|
||||
self.draw_design(month)
|
||||
|
||||
def __draw_number_square__(self):
|
||||
box_height = numberbox_height * self.size[1]
|
||||
box_ypos = numberbox_ypos * self.size[1]
|
||||
box_pos = (box_ypos, box_ypos)
|
||||
box_size = (box_height, box_height)
|
||||
|
||||
box = BoxDesign(box_size, fill=numberbox_background_color)
|
||||
box.pos = box_pos
|
||||
self.draw_design(box)
|
||||
|
||||
self.__draw_today_number__()
|
||||
self.__draw_weekday__()
|
||||
|
||||
def __draw_today_number__(self):
|
||||
font_size = number_height * self.size[1]
|
||||
box_height = numberbox_height * self.size[1]
|
||||
box_ypos = numberbox_ypos * self.size[1]
|
||||
ypadding = number_boxypos * box_height
|
||||
size = (box_height, box_height - ypadding)
|
||||
pos = (box_ypos, box_ypos + ypadding)
|
||||
|
||||
day_text = self.__get_day_text__()
|
||||
number = TextDesign(size, text=day_text, background_color=numberbox_background_color,
|
||||
color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
|
||||
number.pos = pos
|
||||
number.mask = False
|
||||
self.draw_design(number)
|
||||
|
||||
def __draw_weekday__(self):
|
||||
font_size = weekday_height * self.size[1]
|
||||
box_height = numberbox_height * self.size[1]
|
||||
size = (box_height, weekdaybox_height * box_height)
|
||||
box_ypos = numberbox_ypos * self.size[1]
|
||||
pos = (box_ypos, box_ypos)
|
||||
|
||||
week_day_name = self.date.strftime("%A")
|
||||
week_day = TextDesign(size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color,
|
||||
fontsize=font_size, horizontalalignment="center", verticalalignment="center", font=weekday_font)
|
||||
week_day.pos = pos
|
||||
week_day.mask = False
|
||||
self.draw_design(week_day)
|
||||
|
||||
def __abs_co__(self, coordinates):
|
||||
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||
|
||||
def __get_day_text__(self):
|
||||
return str(self.date.day)
|
140
Calendar/DayListPanel.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
from PanelDesign import PanelDesign
|
||||
from Assets import colors
|
||||
from settings import general_settings
|
||||
import calendar as callib
|
||||
from datetime import datetime, timedelta, date
|
||||
from PIL import ImageDraw
|
||||
from TextDesign import TextDesign
|
||||
from DayHeaderDesign import DayHeaderDesign
|
||||
from DayRowDesign import DayRowDesign
|
||||
from RssPostListDesign import RssPostListDesign
|
||||
from CryptoListDesign import CryptoListDesign
|
||||
from settings import line_thickness
|
||||
from math import ceil
|
||||
|
||||
todayheader_pos = (0, 0)
|
||||
todayheader_size = (1, 0.25)
|
||||
lines_thickness = line_thickness
|
||||
infoarea_replacedrowscount = 3
|
||||
|
||||
dayrowsarea_ypos = todayheader_size[1]
|
||||
dayrowsarea_height = 1 - todayheader_size[1]
|
||||
dayrow_min_format = 50 / 384
|
||||
dayrow_max_format = 70 / 384
|
||||
rss_y_padding = 5
|
||||
crypto_y_padding = 5
|
||||
|
||||
|
||||
class DayListPanel (PanelDesign):
|
||||
"""Overview that focuses on the current day and
|
||||
lists following days in a list below."""
|
||||
|
||||
def __init__(self, size):
|
||||
super(DayListPanel, self).__init__(size)
|
||||
self.__day_rows__ = []
|
||||
self.__calc_dayrow_size__()
|
||||
self.__first_render__()
|
||||
|
||||
def __first_render__(self):
|
||||
self.__draw_today_header__()
|
||||
self.__draw_day_rows__()
|
||||
|
||||
def add_weather(self, weather):
|
||||
for row in self.__day_rows__:
|
||||
row.add_weather(weather)
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
for row in self.__day_rows__:
|
||||
row.add_calendar(calendar)
|
||||
|
||||
def add_rssfeed(self, rss):
|
||||
for row in self.__day_rows__:
|
||||
row.add_rssfeed(rss)
|
||||
if general_settings["info-area"] is "rss":
|
||||
self.__day_rows__ = self.__day_rows__[:-infoarea_replacedrowscount]
|
||||
self.__draw_rss_infoarea__(rss)
|
||||
|
||||
def add_crypto(self, crypto):
|
||||
if general_settings["info-area"] is "crypto":
|
||||
self.__draw_crypto_infoarea__(crypto)
|
||||
|
||||
def add_tasks(self, tasks):
|
||||
pass
|
||||
|
||||
def __draw_rss_infoarea__(self, rss):
|
||||
height = infoarea_replacedrowscount * \
|
||||
self.dayrow_size[1] * self.size[1] - rss_y_padding
|
||||
ypos = self.size[1] - height
|
||||
size = (self.size[0], height)
|
||||
pos = (0, ypos)
|
||||
|
||||
design = RssPostListDesign(size, rss)
|
||||
design.pos = pos
|
||||
self.draw_design(design)
|
||||
|
||||
def __draw_crypto_infoarea__(self, crypto):
|
||||
height = infoarea_replacedrowscount * \
|
||||
self.dayrow_size[1] * self.size[1] - crypto_y_padding
|
||||
ypos = self.size[1] - height
|
||||
size = (self.size[0], height)
|
||||
pos = (0, ypos)
|
||||
|
||||
design = CryptoListDesign(size, crypto)
|
||||
acutal_height = design.get_estimated_height()
|
||||
design.pos = (pos[0], pos[1] + (height - acutal_height))
|
||||
self.draw_design(design)
|
||||
|
||||
replaced_rows = ceil(
|
||||
acutal_height / (self.dayrow_size[1] * self.size[1]))
|
||||
self.__day_rows__ = self.__day_rows__[:-replaced_rows]
|
||||
|
||||
def __draw_day_rows__(self):
|
||||
following_days = self.__get_following_days__()
|
||||
for i, date in enumerate(following_days):
|
||||
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
|
||||
row.pos = self.__get_day_row_pos__(i)
|
||||
self.__day_rows__.append(row)
|
||||
|
||||
def __get_day_row_pos__(self, i):
|
||||
ypos = self.size[1] * dayrowsarea_ypos
|
||||
down_shift = i * self.dayrow_size[1] * self.size[1]
|
||||
return (0, int(ypos + down_shift))
|
||||
|
||||
def __calc_dayrow_size__(self):
|
||||
max_area_height = dayrowsarea_height * self.size[1]
|
||||
max_row_number = max_area_height / (dayrow_min_format * self.size[0])
|
||||
min_row_number = max_area_height / (dayrow_max_format * self.size[0])
|
||||
average_row_number = (max_row_number + min_row_number) / 2
|
||||
self.dayrow_count = round(average_row_number)
|
||||
row_height = max_area_height / self.dayrow_count
|
||||
self.dayrow_size = (1, row_height / self.size[1])
|
||||
|
||||
def __get_following_days__(self):
|
||||
following_days = []
|
||||
for i in range(self.dayrow_count):
|
||||
following_days.append(date.today() + timedelta(days=i + 1))
|
||||
return following_days
|
||||
|
||||
def __draw_today_header__(self):
|
||||
header = DayHeaderDesign(self.__abs_co__(
|
||||
todayheader_size), date.today())
|
||||
header.pos = self.__abs_co__(todayheader_pos)
|
||||
self.__day_rows__.append(header)
|
||||
|
||||
def __draw_lines__(self):
|
||||
positions = []
|
||||
for i in range(len(self.__day_rows__)):
|
||||
positions.append(self.__get_day_row_pos__(i)[1])
|
||||
for ypos in positions:
|
||||
line_start = (0, ypos)
|
||||
line_end = (self.size[0], ypos)
|
||||
ImageDraw.Draw(self.__image__).line(
|
||||
[line_start, line_end], fill=colors["fg"], width=lines_thickness)
|
||||
|
||||
def __finish_panel__(self):
|
||||
for design in self.__day_rows__:
|
||||
self.draw_design(design)
|
||||
self.__draw_lines__()
|
||||
|
||||
def __abs_co__(self, coordinates):
|
||||
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
123
Calendar/DayRowDesign.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
from PIL import ImageDraw, Image
|
||||
from TextDesign import TextDesign
|
||||
from settings import week_starts_on, owm_paid_subscription, general_settings
|
||||
from DesignEntity import DesignEntity
|
||||
from datetime import datetime
|
||||
from Assets import weathericons, wpath, fonts, colors, defaultfontsize
|
||||
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||
|
||||
daynumber_y_size = (1, 0.60)
|
||||
weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1])
|
||||
weekday_ypos = daynumber_y_size[1]
|
||||
daynumber_fontsize = daynumber_y_size[1] * 0.85
|
||||
daynumber_ypadding = 0.1
|
||||
weekday_fontsize = weekday_y_size[1] * 0.65
|
||||
weathericon_ypos = 0.1
|
||||
weathericon_height = 1 - 2 * weathericon_ypos
|
||||
eventlist_xpadding = 5
|
||||
eventlist_ypos = 0.02
|
||||
eventlist_y_fontsize = 0.2
|
||||
|
||||
font = fonts["light"]
|
||||
|
||||
|
||||
class DayRowDesign (DesignEntity):
|
||||
"""Detailed view of a given date."""
|
||||
|
||||
def __init__(self, size, date):
|
||||
super(DayRowDesign, self).__init__(size)
|
||||
self.__init_image__()
|
||||
self.date = date
|
||||
|
||||
def add_weather(self, weather):
|
||||
if weather.is_available is False:
|
||||
return
|
||||
self.__draw_forecast__(weather)
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
self.__draw_event_list__(calendar)
|
||||
|
||||
def add_rssfeed(self, rss):
|
||||
pass
|
||||
|
||||
def __draw_event_list__(self, calendar):
|
||||
number_width = daynumber_y_size[0] * self.size[1]
|
||||
ypos = eventlist_ypos * self.size[1]
|
||||
weather_width = 0
|
||||
if owm_paid_subscription and general_settings["weather-info"]:
|
||||
weather_width = weathericon_height * self.size[1]
|
||||
pos = (number_width + eventlist_xpadding, ypos)
|
||||
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1])
|
||||
fontsize = eventlist_y_fontsize * self.size[1]
|
||||
|
||||
events = calendar.get_day_events(self.date)
|
||||
rel_dates = [self.date for _ in range(len(events))]
|
||||
event_list = SingelDayEventListDesign(
|
||||
size, events, fontsize, event_prefix_rel_dates=rel_dates)
|
||||
event_list.pos = pos
|
||||
self.draw_design(event_list)
|
||||
|
||||
def __draw_forecast__(self, weather):
|
||||
forecast = weather.get_forecast_in_days(
|
||||
self.date.day - datetime.today().day)
|
||||
|
||||
if forecast is None:
|
||||
return
|
||||
|
||||
height = int(weathericon_height * self.size[1])
|
||||
size = (height, height)
|
||||
ypos = weathericon_ypos * self.size[1]
|
||||
pos = (self.size[0] - ypos - size[0], ypos)
|
||||
icon = Image.open(wpath + weathericons[forecast.icon] + ".jpeg")
|
||||
resized_icon = icon.resize(size, resample=Image.LANCZOS)
|
||||
self.draw(resized_icon, pos)
|
||||
|
||||
def __finish_image__(self):
|
||||
self.__draw_weekday__()
|
||||
self.__draw_day_number__()
|
||||
|
||||
def __draw_weekday__(self):
|
||||
font_size = int(weekday_fontsize * self.size[1])
|
||||
size = (weekday_y_size[0] * self.size[1],
|
||||
weekday_y_size[1] * self.size[1])
|
||||
ypos = weekday_ypos * self.size[1]
|
||||
pos = (0, ypos)
|
||||
|
||||
color = self.__get_day_color__()
|
||||
week_day_name = self.date.strftime("%a")
|
||||
|
||||
week_day = TextDesign(size, text=week_day_name, font=font, color=color,
|
||||
fontsize=font_size, horizontalalignment="center", verticalalignment="top")
|
||||
week_day.pos = pos
|
||||
week_day.mask = False
|
||||
self.draw_design(week_day)
|
||||
|
||||
def __draw_day_number__(self):
|
||||
font_size = int(daynumber_fontsize * self.size[1])
|
||||
ypadding = daynumber_ypadding * self.size[1]
|
||||
size = (daynumber_y_size[0] * self.size[1],
|
||||
daynumber_y_size[1] * self.size[1])
|
||||
pos = (0, ypadding)
|
||||
|
||||
day_text = self.__get_day_text__()
|
||||
color = self.__get_day_color__()
|
||||
|
||||
number = TextDesign(size, text=day_text, font=font, color=color,
|
||||
fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
|
||||
number.pos = pos
|
||||
self.draw_design(number)
|
||||
|
||||
def __abs_co__(self, coordinates):
|
||||
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||
|
||||
def __get_day_text__(self):
|
||||
return str(self.date.day)
|
||||
|
||||
def __get_day_color__(self):
|
||||
"""Depending on week_starts_on"""
|
||||
if week_starts_on == "Monday" and self.date.strftime("%w") == "0":
|
||||
return colors["hl"]
|
||||
elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6":
|
||||
return colors["hl"]
|
||||
else:
|
||||
return colors["fg"]
|
155
Calendar/DayViewPanel.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
from PanelDesign import PanelDesign
|
||||
from datetime import datetime, timedelta, date
|
||||
from DayHeaderDesign import DayHeaderDesign
|
||||
from HourListDesign import HourListDesign
|
||||
from settings import general_settings
|
||||
from RssPostListDesign import RssPostListDesign
|
||||
from PIL import ImageDraw
|
||||
from Assets import colors
|
||||
from EventListDesign import EventListDesign
|
||||
from CryptoListDesign import CryptoListDesign
|
||||
|
||||
|
||||
header_size = (1, 0.2)
|
||||
hourlist_size = (1, 1 - header_size[1])
|
||||
default_shownhours_count = 12
|
||||
|
||||
infoarea_replaced_hours = 4
|
||||
infoarea_borderline_width = 1
|
||||
infoarea_padding = 5
|
||||
|
||||
|
||||
class DayViewPanel (PanelDesign):
|
||||
"""Overview that focuses on the current day and
|
||||
shows a timeline split into hours."""
|
||||
|
||||
def __init__(self, size):
|
||||
super(DayViewPanel, self).__init__(size)
|
||||
self.shownhours_count = default_shownhours_count
|
||||
if general_settings["info-area"] not in ["", "empty"]:
|
||||
self.shownhours_count -= infoarea_replaced_hours
|
||||
self.__first_render__()
|
||||
|
||||
def __first_render__(self):
|
||||
self.__init_header__()
|
||||
self.__init_hourlist__()
|
||||
|
||||
def add_weather(self, weather):
|
||||
self.__header__.add_weather(weather)
|
||||
|
||||
def add_calendar(self, calendar):
|
||||
allday_ev, timed_ev = self.__split_events__(
|
||||
calendar.get_today_events())
|
||||
self.__header__.add_events(allday_ev)
|
||||
self.__hourlist__.add_events(timed_ev)
|
||||
|
||||
if general_settings["info-area"] == "events":
|
||||
self.__draw_event_list__(calendar)
|
||||
self.__draw_infoarea_line__()
|
||||
|
||||
def add_rssfeed(self, rss):
|
||||
if general_settings["info-area"] == "rss":
|
||||
self.__draw_rss_feed__(rss)
|
||||
self.__draw_infoarea_line__()
|
||||
|
||||
def add_crypto(self, crypto):
|
||||
if general_settings["info-area"] == "crypto":
|
||||
self.__draw_crypto_feed__(crypto)
|
||||
self.__draw_infoarea_line__()
|
||||
|
||||
def __draw_infoarea_line__(self):
|
||||
height = infoarea_replaced_hours * self.__hourlist__.row_size[1]
|
||||
ypos = self.size[1] - height
|
||||
|
||||
line_start = (0, ypos)
|
||||
line_end = (self.size[0], ypos)
|
||||
ImageDraw.Draw(self.__image__).line(
|
||||
[line_start, line_end], fill=colors["fg"], width=infoarea_borderline_width)
|
||||
|
||||
def __draw_rss_feed__(self, rss):
|
||||
height = infoarea_replaced_hours * \
|
||||
self.__hourlist__.row_size[1] - infoarea_padding
|
||||
size = (self.size[0], height)
|
||||
pos = (0, self.size[1] - size[1])
|
||||
|
||||
rss = RssPostListDesign(size, rss)
|
||||
rss.pos = pos
|
||||
self.draw_design(rss)
|
||||
|
||||
def __draw_crypto_feed__(self, crypto):
|
||||
height = infoarea_replaced_hours * \
|
||||
self.__hourlist__.row_size[1] - infoarea_padding
|
||||
size = (self.size[0], height)
|
||||
pos = (0, self.size[1] - size[1])
|
||||
|
||||
crypto = CryptoListDesign(size, crypto)
|
||||
acutal_height = crypto.get_estimated_height()
|
||||
crypto.pos = (pos[0], pos[1] + (height - acutal_height))
|
||||
self.draw_design(crypto)
|
||||
|
||||
def __draw_event_list__(self, calendar):
|
||||
height = infoarea_replaced_hours * \
|
||||
self.__hourlist__.row_size[1] - infoarea_padding
|
||||
size = (self.size[0], height)
|
||||
pos = (0, self.size[1] - size[1])
|
||||
|
||||
events = EventListDesign(size, calendar.get_upcoming_events())
|
||||
events.pos = pos
|
||||
self.draw_design(events)
|
||||
|
||||
def add_tasks(self, tasks):
|
||||
pass
|
||||
|
||||
def __finish_panel__(self):
|
||||
self.draw_design(self.__header__)
|
||||
self.draw_design(self.__hourlist__)
|
||||
|
||||
def __init_header__(self):
|
||||
self.__header__ = DayHeaderDesign(
|
||||
self.__abs_co__(header_size), date.today())
|
||||
self.__header__.pos = (0, 0)
|
||||
|
||||
def __init_hourlist__(self):
|
||||
start, end = self.__get_current_hour_range__()
|
||||
size = self.__abs_co__(hourlist_size)
|
||||
size = (size[0], size[1] * self.shownhours_count /
|
||||
default_shownhours_count)
|
||||
|
||||
self.__hourlist__ = HourListDesign(size, start, end)
|
||||
self.__hourlist__.pos = (0, self.__header__.size[1])
|
||||
|
||||
def __get_current_hour_range__(self):
|
||||
start_hour = datetime.now().hour
|
||||
additional_hours = self.shownhours_count - 1
|
||||
|
||||
if start_hour + additional_hours > 23:
|
||||
start_hour = 23 - additional_hours
|
||||
|
||||
return start_hour, start_hour + additional_hours
|
||||
|
||||
def __abs_co__(self, coordinates):
|
||||
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||
|
||||
def __split_events__(self, events):
|
||||
allday_ev = []
|
||||
timed_ev = []
|
||||
|
||||
for event in events:
|
||||
if event.allday:
|
||||
allday_ev.append(event)
|
||||
elif event.multiday:
|
||||
if self.__is_today__(event.begin_datetime):
|
||||
timed_ev.append(event)
|
||||
elif self.__is_today__(event.end_datetime):
|
||||
timed_ev.append(event)
|
||||
else:
|
||||
allday_ev.append(event)
|
||||
else:
|
||||
timed_ev.append(event)
|
||||
return allday_ev, timed_ev
|
||||
|
||||
def __is_today__(self, dt):
|
||||
today = date.today()
|
||||
return dt.day == today.day and \
|
||||
dt.month == today.month and \
|
||||
dt.year == today.year
|
65
Calendar/DebugConsole.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
from DebugInterface import DebugInterface
|
||||
from Assets import weathericons
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
|
||||
|
||||
class DebugConsole (DebugInterface):
|
||||
"""Defines concrete console export of debug objects"""
|
||||
|
||||
def print_event(self, event):
|
||||
print("\nCalendarEvent:")
|
||||
print("---------------------")
|
||||
print('Begin datetime: ' + str(event.begin_datetime))
|
||||
print('End datetime: ' + str(event.end_datetime))
|
||||
print('Duration: ' + str(event.duration))
|
||||
print('All day: ' + str(event.allday))
|
||||
print('Multi-day: ' + str(event.multiday))
|
||||
print('RRULE: ' + str(event.rrule))
|
||||
print('Title: ' + str(event.title))
|
||||
print('Description: ' + str(event.description))
|
||||
print('Attendees: ' + str(event.attendees))
|
||||
print('Highlight: ' + str(event.highlight))
|
||||
print('Calendar name: ' + str(event.calendar_name))
|
||||
print('Location: ' + str(event.location))
|
||||
print('Fetch datetime: ' + str(event.fetch_datetime))
|
||||
|
||||
def print_forecast(self, forecast):
|
||||
print("\nWeatherForecast:")
|
||||
print("---------------------")
|
||||
print('Air temperature: ' + str(forecast.air_temperature))
|
||||
print('Air humidity: ' + str(forecast.air_humidity))
|
||||
print('Air pressure: ' + str(forecast.air_pressure))
|
||||
print('Rain probability: ' + str(forecast.rain_probability))
|
||||
print('Rain amount: ' + str(forecast.rain_amount))
|
||||
print('Snow amount: ' + str(forecast.snow_amount))
|
||||
print('Sunrise-time: ' + str(forecast.sunrise))
|
||||
print('Sunset time: ' + str(forecast.sunset))
|
||||
print('Moon phase: ' + str(forecast.moon_phase))
|
||||
print('Wind speed: ' + str(forecast.wind_speed))
|
||||
print('Cloudiness: ' + str(forecast.clouds))
|
||||
print('Icon code: ' + str(forecast.icon))
|
||||
print('weather-icon name: ' + str(weathericons[forecast.icon]))
|
||||
print('Short description: ' + str(forecast.short_description))
|
||||
print('Detailed description: ' + str(forecast.detailed_description))
|
||||
print('Units: ' + str(forecast.units))
|
||||
print('Datetime: ' + str(forecast.datetime))
|
||||
print('Location: ' + str(forecast.location))
|
||||
print('Fetch datetime: ' + str(forecast.fetch_datetime))
|
||||
|
||||
def print_line(self, content):
|
||||
if content is None:
|
||||
return
|
||||
print(str(content))
|
||||
|
||||
def print_err(self, exception, msg=""):
|
||||
if exception is None:
|
||||
return
|
||||
|
||||
content = "[ERR: "
|
||||
content += datetime.now().strftime("")
|
||||
content += "]\n" + str(exception)
|
||||
content += "\n" + str(msg) + "\n"
|
||||
traceback.print_exc()
|
||||
|
||||
self.print_line(str(content))
|
14
Calendar/DebugInterface.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
class DebugInterface (object):
|
||||
"""Defines general interface for debugging operations"""
|
||||
|
||||
def print_event(self, event):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def print_forecast(self, forecast):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def print_line(self, content):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def print_err(self, exception, msg=""):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
61
Calendar/DesignEntity.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from PIL import Image, ImageOps, ImageDraw
|
||||
from Assets import colors
|
||||
|
||||
masking_threshold = 200
|
||||
|
||||
|
||||
class DesignEntity (object):
|
||||
"""General entity that can be drawn on to a panel design or
|
||||
other design entities."""
|
||||
|
||||
def __init__(self, size, mask=False, invert_mask=False, color_key=False):
|
||||
self.size = size
|
||||
# Are dimensions >= 0?
|
||||
if self.size[0] < 0:
|
||||
self.size = (0, self.size[1])
|
||||
if self.size[1] < 0:
|
||||
self.size = (self.size[0], 0)
|
||||
|
||||
self.pos = (0, 0)
|
||||
self.mask = mask
|
||||
self.invert_mask = invert_mask
|
||||
self.__init_image__()
|
||||
self.__finished_image__ = False
|
||||
self.color_key = color_key
|
||||
|
||||
def __init_image__(self, color=colors["bg"]):
|
||||
rounded_size = (int(self.size[0]), int(self.size[1]))
|
||||
self.__image__ = Image.new('RGBA', rounded_size, color=color)
|
||||
|
||||
def get_image(self):
|
||||
if self.__finished_image__ is False:
|
||||
self.__finish_image__()
|
||||
self.__finished_image__ = True
|
||||
return self.__image__
|
||||
|
||||
def draw(self, subimage, pos, mask=False, invert_mask=False, color_key=False):
|
||||
rounded_pos = (int(pos[0]), int(pos[1]))
|
||||
img_mask = None
|
||||
if mask:
|
||||
img_mask = self.__get_mask__(
|
||||
subimage, invert_mask=invert_mask, color_key=color_key)
|
||||
self.__image__.paste(subimage, rounded_pos, mask=img_mask)
|
||||
|
||||
def draw_design(self, entity):
|
||||
self.draw(entity.get_image(), entity.pos, entity.mask,
|
||||
entity.invert_mask, entity.color_key)
|
||||
|
||||
def draw_image(self, path, pos):
|
||||
self.draw(Image.open(path), pos)
|
||||
|
||||
def __finish_image__(self):
|
||||
pass
|
||||
|
||||
def __get_mask__(self, image, invert_mask, color_key):
|
||||
mask = image.convert('L')
|
||||
if color_key:
|
||||
mask = mask.point(lambda p: 0 if p <
|
||||
masking_threshold else 255, '1').convert('L')
|
||||
if invert_mask:
|
||||
mask = ImageOps.invert(mask)
|
||||
return ImageOps.invert(mask)
|
99
Calendar/Dictionary.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
default_language = "en"
|
||||
|
||||
'''Characters following '*' are placeholders and will be replaced by some number/text/etc.'''
|
||||
|
||||
'''Events'''
|
||||
more_events = {
|
||||
'en': '+ *0 more',
|
||||
'de': '+ *0 weitere'
|
||||
}
|
||||
multiday_events = {
|
||||
'en': 'Multi-day',
|
||||
'de': 'Mehrtägig'
|
||||
}
|
||||
allday_events = {
|
||||
'en': 'All-day',
|
||||
'de': 'Ganztägig'
|
||||
}
|
||||
|
||||
'''Weather'''
|
||||
rain_weather = {
|
||||
'en': 'Rain',
|
||||
'de': 'Regen'
|
||||
}
|
||||
clear_weather = {
|
||||
'en': 'Clear',
|
||||
'de': 'Klar'
|
||||
}
|
||||
clouds_weather = {
|
||||
'en': 'Clouds',
|
||||
'de': 'Wolken'
|
||||
}
|
||||
thunderstorm_weather = {
|
||||
'en': 'Thunderstorm',
|
||||
'de': 'Gewitter'
|
||||
}
|
||||
drizzle_weather = {
|
||||
'en': 'Drizzle',
|
||||
'de': 'Niesel'
|
||||
}
|
||||
snow_weather = {
|
||||
'en': 'Snow',
|
||||
'de': 'Schnee'
|
||||
}
|
||||
mist_weather = {
|
||||
'en': 'Mist',
|
||||
'de': 'Nebel'
|
||||
}
|
||||
smoke_weather = {
|
||||
'en': 'Smoke',
|
||||
'de': 'Rauch'
|
||||
}
|
||||
haze_weather = {
|
||||
'en': 'Haze',
|
||||
'de': 'Nebel'
|
||||
}
|
||||
dust_weather = {
|
||||
'en': 'Dust',
|
||||
'de': 'Staub'
|
||||
}
|
||||
fog_weather = {
|
||||
'en': 'Fog',
|
||||
'de': 'Nebel'
|
||||
}
|
||||
sand_weather = {
|
||||
'en': 'Sand',
|
||||
'de': 'Sand'
|
||||
}
|
||||
ash_weather = {
|
||||
'en': 'Ash',
|
||||
'de': 'Asche'
|
||||
}
|
||||
squall_weather = {
|
||||
'en': 'Squall',
|
||||
'de': 'Sturm'
|
||||
}
|
||||
tornado_weather = {
|
||||
'en': 'Tornado',
|
||||
'de': 'Tornado'
|
||||
}
|
||||
dictionary_collection = [
|
||||
rain_weather,
|
||||
clear_weather,
|
||||
dust_weather,
|
||||
squall_weather,
|
||||
tornado_weather,
|
||||
clouds_weather,
|
||||
thunderstorm_weather,
|
||||
smoke_weather,
|
||||
ash_weather,
|
||||
sand_weather,
|
||||
fog_weather,
|
||||
haze_weather,
|
||||
mist_weather,
|
||||
drizzle_weather,
|
||||
snow_weather,
|
||||
more_events,
|
||||
allday_events,
|
||||
multiday_events
|
||||
]
|
29
Calendar/DictionaryMapper.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from Dictionary import default_language
|
||||
from settings import language
|
||||
|
||||
'''Takes a collection of phrases and outputs the necessary text
|
||||
according to the language and inserts parameters.'''
|
||||
|
||||
|
||||
def get_text(dictionary, *params):
|
||||
text = dictionary[default_language]
|
||||
if language in dictionary.keys():
|
||||
text = dictionary[language]
|
||||
|
||||
return __insert_params__(text, params)
|
||||
|
||||
|
||||
def __insert_params__(text, params):
|
||||
index = 0
|
||||
while '*%d' % index in text and index < len(params):
|
||||
splitted = text.split('*%d' % index)
|
||||
text = splitted[0] + str(params[index]) + splitted[1]
|
||||
index += 1
|
||||
while '*' in text:
|
||||
splitted = text.split('*%d' % index)
|
||||
if len(splitted) > 1:
|
||||
text = splitted[0] + splitted[1].lstrip(' ')
|
||||
else:
|
||||
text = splitted[0].rsplit(' ')
|
||||
index += 1
|
||||
return text
|
12
Calendar/DisplayAdapter.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
class DisplayAdapter (object):
|
||||
"""Interface for CalendarDesign output channels."""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def render(self, design):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
||||
|
||||
def calibrate(self):
|
||||
raise NotImplementedError("Functions needs to be implemented")
|
135
Calendar/E-Paper.py
Normal file
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
E-Paper Software (main script) for the 3-colour and 2-Colour E-Paper display
|
||||
A full and detailed breakdown for this code can be found in the wiki.
|
||||
If you have any questions, feel free to open an issue at Github.
|
||||
|
||||
Copyright by aceisace
|
||||
"""
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
from Assets import path
|
||||
from LoopTimer import LoopTimer
|
||||
import locale
|
||||
from DebugConsole import DebugConsole
|
||||
from settings import datetime_encoding, language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours, crypto_coins, max_loop_count, run_on_hour
|
||||
from MonthOvPanel import MonthOvPanel
|
||||
from DayListPanel import DayListPanel
|
||||
from DayViewPanel import DayViewPanel
|
||||
from DayFocusListPanel import DayFocusListPanel
|
||||
from MonthViewPanel import MonthViewPanel
|
||||
from AgendaListPanel import AgendaListPanel
|
||||
from ImageFramePanel import ImageFramePanel
|
||||
import OwmForecasts
|
||||
import IcalEvents
|
||||
import RssParserPosts
|
||||
import GeckoCrypto
|
||||
|
||||
all_locales = locale.locale_alias
|
||||
if language.lower() not in all_locales.keys():
|
||||
raise Exception(
|
||||
"The locale for \"%s\" is currently not supported! If you need support, please open an issue on github." % language)
|
||||
locale.setlocale(locale.LC_ALL, "%s.%s" % (
|
||||
all_locales[language.lower()].split('.')[0], datetime_encoding))
|
||||
|
||||
debug = DebugConsole()
|
||||
output_adapters = []
|
||||
|
||||
if render_to_file:
|
||||
import ImageFileAdapter
|
||||
epd = ImageFileAdapter.ImageFileAdapter(path)
|
||||
output_adapters.append(epd)
|
||||
|
||||
if render_to_display:
|
||||
if display_colours == "bwr":
|
||||
import Epd7in5bAdapter
|
||||
epd = Epd7in5bAdapter.Epd7in5bAdapter()
|
||||
output_adapters.append(epd)
|
||||
elif display_colours == "bw":
|
||||
import Epd7in5Adapter
|
||||
epd = Epd7in5Adapter.Epd7in5Adapter()
|
||||
output_adapters.append(epd)
|
||||
|
||||
available_panels = {
|
||||
"day-list": DayListPanel,
|
||||
"month-overview": MonthOvPanel,
|
||||
"day-view": DayViewPanel,
|
||||
"day-focus-list": DayFocusListPanel,
|
||||
"agenda-list": AgendaListPanel,
|
||||
"month-view": MonthViewPanel,
|
||||
"image-frame": ImageFramePanel,
|
||||
}
|
||||
|
||||
loop_timer = LoopTimer(
|
||||
update_interval, run_on_hour=run_on_hour, max_loop_count=max_loop_count)
|
||||
|
||||
"""Main loop starts from here"""
|
||||
|
||||
|
||||
def main():
|
||||
owm = OwmForecasts.OwmForecasts(
|
||||
location, api_key, paid_api=owm_paid_subscription)
|
||||
events_cal = IcalEvents.IcalEvents(ical_urls, highlighted_ical_urls)
|
||||
rss = RssParserPosts.RssParserPosts(rss_feeds)
|
||||
crypto = GeckoCrypto.GeckoCrypto(crypto_coins)
|
||||
|
||||
while True:
|
||||
loop_timer.begin_loop()
|
||||
start_time = loop_timer.get_current()[0]
|
||||
|
||||
if start_time.hour in calibrate_hours and loop_timer.is_new_hour_loop():
|
||||
debug.print_line("Calibrating outputs")
|
||||
for output in output_adapters:
|
||||
output.calibrate()
|
||||
|
||||
if choosen_design in available_panels.keys():
|
||||
design = available_panels[choosen_design]((epd.width, epd.height))
|
||||
else:
|
||||
raise ImportError(
|
||||
"choosen_design must be valid (" + choosen_design + ")")
|
||||
|
||||
debug.print_line("Fetching weather information from open weather map")
|
||||
owm.reload()
|
||||
design.add_weather(owm)
|
||||
|
||||
debug.print_line('Fetching events from your calendar')
|
||||
events_cal.reload()
|
||||
design.add_calendar(events_cal)
|
||||
|
||||
debug.print_line('Fetching posts from your rss-feeds')
|
||||
rss.reload()
|
||||
design.add_rssfeed(rss)
|
||||
|
||||
debug.print_line('Fetching crypto prices from coin gecko')
|
||||
crypto.reload()
|
||||
design.add_crypto(crypto)
|
||||
|
||||
debug.print_line("\nStarting to render")
|
||||
for i, output in enumerate(output_adapters):
|
||||
try:
|
||||
output.render(design)
|
||||
debug.print_line(str(i + 1) + " of " +
|
||||
str(len(output_adapters)) + " rendered")
|
||||
except BaseException as ex:
|
||||
debug.print_err(ex, "Failed to render output " +
|
||||
str(i + 1) + " of " + str(len(output_adapters)))
|
||||
|
||||
debug.print_line("=> Finished rendering" + "\n")
|
||||
|
||||
loop_timer.end_loop()
|
||||
|
||||
if loop_timer.was_last_loop():
|
||||
debug.print_line("Maximum loop count " +
|
||||
str(loop_timer.loop_count) + " reached, exiting.")
|
||||
return
|
||||
|
||||
sleep_time = loop_timer.time_until_next()
|
||||
debug.print_line("This loop took " +
|
||||
str(loop_timer.get_last_duration()) + " to execute.")
|
||||
debug.print_line("Sleeping " + str(sleep_time) + " until next loop.")
|
||||
sleep(sleep_time.total_seconds())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|