Compare commits

...

239 commits
v1.6 ... master

Author SHA1 Message Date
4df439018e Fixed agenda view getting stuck when there are not enough events 2024-02-06 20:17:42 +01:00
d830281789 Removed "code badge" 2023-12-06 23:50:02 +01:00
cd68d4c02b Fixed image paths to work on gitea 2023-12-06 23:48:32 +01:00
Max
4faf625a70
Update README.md 2023-03-16 02:22:45 +01:00
Max
4a7eb40bb1
Update README.md 2021-12-28 22:33:20 +01:00
Max
eaa3de9d79 Fixed event merging bug 2020-05-08 10:10:31 +02:00
Max
b7ede5a026 Fixed size bug 2020-04-28 13:30:29 +02:00
Max
83f8f37447 Fixed general size bug 2020-04-21 14:00:01 +02:00
Max
30eb1e4cdd Added new design and minor improvements 2020-04-19 22:25:50 +02:00
Max
d3f46c820e Added new loop limiting and on_hour to settings 2020-01-12 09:33:20 +01:00
89f9f6d8fc
Removed "json" because it is part of the python3 standard library 2019-10-20 19:31:56 +02:00
36f8c35170
Typo 2019-10-11 11:18:40 +02:00
79acea5668
Corrected grammar 2019-10-11 11:16:02 +02:00
8e4fbfdfd9
Added missing word 2019-10-11 11:14:35 +02:00
Max
1e0f59739f Formatted code 2019-07-13 08:05:35 +02:00
Max
5d881753b8 Fine tuned 2019-07-12 14:05:36 +02:00
Max
ae3b72c86a Fine tuned 2019-07-12 14:04:46 +02:00
Max
387fdb813f Added image-frame example 2019-07-12 14:02:44 +02:00
Max
3309dde202 Added image-frame design 2019-07-12 13:46:50 +02:00
Max
6bda730c61 Fixed timezone awareness adder 2019-07-07 18:16:52 +02:00
Max
016d022303 Improved error handling 2019-07-05 22:09:17 +02:00
Max
eeb9e03547 Improved error handling 2019-07-05 21:33:19 +02:00
Max
059fc84cb3 Improved efficency 2019-07-05 21:32:01 +02:00
Max
fdb4331aec Improved error handling 2019-07-05 21:30:14 +02:00
Max
464f489ebd Fixed loop issue 2019-05-28 14:38:17 +02:00
Max
2269bcf3fa Implemented dynamic crypto list in all designs 2019-05-21 14:52:38 +02:00
Max
0200116d88 Implemented crypto interface to panels 2019-05-19 22:25:59 +02:00
Max
ad79095a9d Fine tuned design 2019-05-19 22:20:26 +02:00
Max
b58357927f Improved naming 2019-05-19 22:08:14 +02:00
Max
b74def5bbf Fine tuned design 2019-05-19 22:06:02 +02:00
Max
2a36a9a6ce Implemented availability 2019-05-19 22:05:49 +02:00
Max
ba8c95ff67 Simple 2019-05-19 00:22:24 +02:00
Max
ccf28e52b7 Improved 2019-05-18 09:39:17 +02:00
Max
8652ef187c General fixes 2019-05-18 09:35:49 +02:00
Max
2bbb19b53e Reorganized 2019-05-18 09:31:09 +02:00
Max
00a05a1a6e Head start from @dicijr 2019-05-18 09:28:29 +02:00
Max
2a94c1fb90 Implemented line_thickness setting in all designs 2019-05-17 09:04:26 +02:00
dicijr
d8a1c1c250 Line-thickness setting for day-list panel only 2019-05-17 08:50:21 +02:00
Max
f53faee1a5 Fixed Readme 2019-05-17 08:48:24 +02:00
Max
95bbc8fb59 Updated Readme 2019-05-17 08:47:51 +02:00
Max
9b1ceba95e Fixed issues 2019-05-15 19:13:38 +02:00
Max
aa34b8bc4f Fixed stability issue 2019-05-15 19:13:09 +02:00
Max
1a62d9d929 Fixed stability 2019-05-15 09:14:22 +02:00
dicijr
d768712181 Heading format 2019-05-15 08:40:39 +02:00
Max
f1cc271e3b Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-05-15 08:22:32 +02:00
Max
08d30bf7d7 Removed print statement 2019-05-15 08:22:31 +02:00
Max
d4df70ce39 Progressed month-view prototype 2019-05-15 08:19:49 +02:00
Max
62dd01fb00 Progressed month-view prototype 2019-05-15 08:16:41 +02:00
dicijr
f22f4b825f
Fixed celcius 2019-05-15 00:23:08 -04:00
dicijr
61fd7af37a
Added Aviation feature (Windir,knots) 2019-05-15 00:15:38 -04:00
Max
3bd7b55010 Fixed readability issue 2019-05-12 18:23:44 +02:00
Max
1a50ff5cca Improved table text 2019-05-12 18:23:12 +02:00
Max
3bac199949 Extended definition to year 2019-05-12 17:40:42 +02:00
Max
f417fdbe3e Fixed default text padding (cutoff) 2019-05-11 21:37:16 +02:00
Max
4d3c2cfbd7 Improved code 2019-05-10 08:19:22 +02:00
Max
51d4e20553 Improved reliability for language codes 2019-05-10 08:11:13 +02:00
Max
083f4bbf6f Updated README 2019-05-10 07:52:26 +02:00
Max
710255604a Simple translator based on local dictionary 2019-05-09 17:41:34 +02:00
Max
d2fa6d8fd7 Added font size to cell properties 2019-05-06 16:19:47 +02:00
Max
a7c7a3e553 Implemented missing setting relation 2019-05-06 16:12:18 +02:00
Max
b01942b39d Implemented multi-language support of owm 2019-05-06 16:09:14 +02:00
Max
99929d63a4 Removed dead code 2019-05-06 16:07:43 +02:00
Max
1e8d02dc41 Dynamic locales, simple dictionary, advanced table 2019-05-06 11:05:10 +02:00
Max
8211b9829e Fixed design issues 2019-05-01 19:06:37 +02:00
Max
07c2484849 Improved code 2019-05-01 07:22:27 +02:00
Max
5dcae2d435 Generalized TableDesign and improved imports 2019-05-01 07:17:40 +02:00
Max
28938b45f8 Fixed output of bw e-paper screen 2019-04-30 09:14:04 +02:00
Max
7ead3c39c1 Fixed highlight box bug 2019-04-29 20:23:44 +02:00
Max
6504851aa1 Fixed mutliday bug in day-view design 2019-04-29 18:47:47 +02:00
Max
69f12de1e5 Improved allday handling 2019-04-29 18:35:45 +02:00
Max
64b704c10f Fixed and improved path handling 2019-04-29 18:14:25 +02:00
Max
08cc2b7daa Fixed bug related to event fetching 2019-04-29 18:14:10 +02:00
Max
6c4eb5c220 Fixed bug related to day-view 2019-04-29 18:08:53 +02:00
Max
69d1041f98 Fixed timezone related bug and minor improvements 2019-04-28 15:24:15 +02:00
Max
76495bd16c Added backwards compatible timezoning 2019-04-26 09:45:50 +02:00
Max
16f38a9102 Now sorting the result events by type 2019-04-24 14:27:45 +02:00
Max
d431dae196 Fixed bug 2019-04-23 07:30:08 +02:00
Max
29eddbbd18 Removed dead code 2019-04-22 21:26:29 +02:00
Max
843163dbda Preventing time consuming load at beginning 2019-04-22 20:53:17 +02:00
Max
460c1e8a54 Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-22 09:49:57 +02:00
Max
54a53316cc Removed dead code 2019-04-22 09:49:52 +02:00
The Codacy Badger
a6ee7fffa1 Add Codacy badge 2019-04-19 08:17:14 +02:00
383c7b7121
Update README.md 2019-04-18 08:56:11 +02:00
b0e5e931f3
Update New-version-planning.md 2019-04-18 08:54:32 +02:00
1390f3630a
Update Changelog.md 2019-04-18 08:52:39 +02:00
Max
426291f828 Removed dead code 2019-04-18 08:31:50 +02:00
Max
ce73624443 Bug fix 2019-04-18 08:30:52 +02:00
Max
d2c7428c62 Added offline support for services 2019-04-18 08:30:08 +02:00
Max
b6691de9e8 Added description for font_size 2019-04-17 17:35:24 +02:00
Max
bf99ecd8e0 Moved font size setting to settings-file 2019-04-17 17:30:34 +02:00
Max
7526c47735 Improved default font size integration 2019-04-17 17:23:41 +02:00
Max
95cee6287e Removed dead code 2019-04-17 16:52:56 +02:00
Max
6bb0fe247b Removed dead code 2019-04-17 16:52:56 +02:00
Max
f88a4bf702 Integrated new dependencies in installer 2019-04-17 16:52:56 +02:00
Max
f8eddb66d5 First variant 2019-04-17 16:52:56 +02:00
Max
2ea9b09954 Standarized message 2019-04-14 16:37:53 +02:00
Max
f7a589354f Typo 2019-04-14 11:24:01 +02:00
Max
077d841cab Fixed timeout exception realated to weather 2019-04-14 10:42:14 +02:00
Max
c09a5d51c4 Removed dead code 2019-04-14 10:38:44 +02:00
Max
0e0803a5e6 Preventing buffer overflow 2019-04-14 10:32:11 +02:00
Max
5b858bc121 Removed easter egg 2019-04-14 10:27:45 +02:00
Max
904c50224c Added info about current day 2019-04-14 10:26:35 +02:00
Max
63b702cbb4 Removed dead code 2019-04-14 10:21:22 +02:00
Max
8c629051b5 Fine tuned deisn 2019-04-14 10:21:00 +02:00
Max
bad6d280da Improved text 2019-04-13 20:46:00 +02:00
Max
c33a34f9d8 Now using absolute path 2019-04-13 19:30:15 +02:00
Max
242e7acd09 Removed debug print-statements 2019-04-13 19:28:46 +02:00
Max
188b0497cb Made the main path properly independent 2019-04-13 19:17:14 +02:00
Max
8552874e7d Ignoring more 2019-04-13 19:16:42 +02:00
Max
f8fac5f2d3 Thinned out font 2019-04-12 08:15:28 +02:00
Max
bdd443eea8 Added exception handling to output 2019-04-11 08:06:54 +02:00
Max
09464dd07d Added and updatet features 2019-04-11 07:59:58 +02:00
Max
8ab6826aaf Improved text print 2019-04-10 12:12:54 +02:00
Max
61a41138fa Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-10 10:47:36 +02:00
Max
002f20504d Fixed bug in recognition of multi-day events 2019-04-10 10:47:33 +02:00
06bffa1aea Add files via upload 2019-04-09 13:57:21 +02:00
320a79a869
Delete day-view_example.png 2019-04-09 13:57:12 +02:00
Max
43c5fafdba Fine tuned design 2019-04-09 13:52:14 +02:00
Max
317a6765f3 Fixed different language bug 2019-04-09 13:41:29 +02:00
Max
720f053d6a Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-09 13:38:28 +02:00
Max
e6fa543406 Fixed interpretation of last_hour 2019-04-09 13:37:39 +02:00
Max
b82bfa2aa7 Fine tuned design 2019-04-09 13:35:32 +02:00
bb6f121c42
Update README.md 2019-04-09 12:53:55 +02:00
1c796f0cc0 Add files via upload 2019-04-09 12:52:50 +02:00
Max
7fd2771efb Fine tuned design 2019-04-09 12:49:57 +02:00
Max
956c4a300c Fixed language and time-format mix-up 2019-04-09 12:48:13 +02:00
Max
260f9bd70a Fine tuned design 2019-04-09 12:42:55 +02:00
Max
b1a796079b Added support for rss-feed and event-list 2019-04-09 12:40:10 +02:00
Max
bd69088e0b Remanaged calculation of parameters 2019-04-09 12:39:48 +02:00
Max
d7137ac9c1 Added dynamic hour range 2019-04-09 12:17:12 +02:00
Max
5cb04d9dcf Made property public 2019-04-09 12:16:57 +02:00
Max
d206b5e1c6 Design changes 2019-04-09 12:05:27 +02:00
Max
c8365ecc63 Improved text 2019-04-09 11:35:35 +02:00
Max
009f3cd08b Added always shown event titles 2019-04-09 10:57:51 +02:00
Max
168d52f86e Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-09 10:49:19 +02:00
Max
2d79ac26fd Added column aware event drawing and event title wrapping 2019-04-09 10:48:59 +02:00
Max
33a2e9b2fe Added duration manipulation for multi-day events 2019-04-09 10:48:34 +02:00
82188fd3af
Update README.md 2019-04-09 10:16:46 +02:00
Max
fdc0197da1 Possible refix 2019-04-09 10:08:15 +02:00
Max
a6aebf1c37 Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-09 10:06:13 +02:00
Max
0a523d5de9 Added recognition of necessary number of columns 2019-04-09 09:34:00 +02:00
Max
5ec99c31bf Fixed overwritten timezone bug 2019-04-09 09:33:43 +02:00
Max
6b726ac6b6 Integrated first event box prototype 2019-04-09 09:15:53 +02:00
Max
367218ed67 Fix for timezone-awareness adder out-of-range exception 2019-04-09 08:41:40 +02:00
Max
f0d8507818 Improved truncating 2019-04-08 16:23:59 +02:00
Max
0b665f4976 Fixed multi-day sorting 2019-04-08 16:21:06 +02:00
Max
4d0b7009cd Reset default values 2019-04-08 16:08:20 +02:00
Max
7d0b38c479 Reset default values 2019-04-08 16:06:56 +02:00
efdcf57df0
Update README.md 2019-04-08 15:54:06 +02:00
Max
ef634bad7c Improved event sorting 2019-04-08 15:42:55 +02:00
Max
0df90b39bc Changed to default font size 2019-04-08 15:42:30 +02:00
Max
39fa0bce21 Moved multi-day identifier to event properties 2019-04-08 15:29:09 +02:00
Max
25f2288bdd Typo 2019-04-08 15:20:09 +02:00
Max
4720774011 Added some optional technical data to print 2019-04-08 11:20:33 +02:00
Max
a7605e3473 Improved masking 2019-04-08 11:20:04 +02:00
e9a1142443 Added support for optional weather info and fixed events-info error. 2019-04-07 17:08:42 +02:00
fd1edb02b7 Added optional weather support 2019-04-07 16:48:07 +02:00
3bf7bc0205 Removed unnecessary code 2019-04-07 16:44:27 +02:00
10af39c985 Better images 2019-04-07 16:35:53 +02:00
fd14986b7c Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-07 13:14:19 +02:00
eb23b7236f Edited for better translation to e-paper 2019-04-07 13:14:15 +02:00
5ada365bbc
Add files via upload 2019-04-07 12:57:57 +02:00
d525683a27
Delete month-overview_example.png 2019-04-07 12:57:44 +02:00
7decd58eee
Updated 2019-04-07 12:57:18 +02:00
49f50dc944
Updated 2019-04-07 12:55:52 +02:00
4ca32e49ab
Updated 2019-04-07 12:54:50 +02:00
b10de72b14 Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-07 12:54:37 +02:00
a5bfd90b5e Added simplified, multiday-aware prefix 2019-04-07 12:54:34 +02:00
c48af815b9 Minor design changes 2019-04-07 12:50:50 +02:00
c9fc0341c4
Update README.md 2019-04-07 12:48:18 +02:00
05f0412b94
Update README.md 2019-04-07 12:47:01 +02:00
d9b83cca9d
Update 2019-04-07 12:45:37 +02:00
2cebf0a60d Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-04-07 12:45:24 +02:00
913313245f Implemented rss-feed 2019-04-07 12:45:21 +02:00
56ee85b60f Added default x-padding to rss list 2019-04-07 11:47:47 +02:00
fdd35a157f
Example render of agenda-list panel 2019-04-07 11:41:13 +02:00
e86de45c63 Added new option weather-info to sample settings file 2019-04-07 11:38:44 +02:00
2763abeeb4 Added weather info 2019-04-07 11:35:26 +02:00
66b5b95f74 Fixed missing integration of col_spacing in column resizing 2019-04-07 11:16:40 +02:00
a9a639ed2b Added highlighted event support 2019-04-05 22:28:16 +02:00
0dd1ca9b45 Fine tuned all-day events 2019-04-05 22:24:09 +02:00
d7f485cba6 Fine tuned displayed texts for multi-day events 2019-04-05 22:23:01 +02:00
afd94ea10f Added day dependent event prefixes 2019-04-05 22:07:47 +02:00
4b656ee819 Redefined special day recognition 2019-04-05 22:07:33 +02:00
22242d894f Added support for date-relative event prefixes 2019-04-05 18:57:37 +02:00
9e67af6adc Extended definition to years 2019-04-05 18:57:16 +02:00
3e11ec4df0 Added missing conversion of end_datetime 2019-04-05 18:53:16 +02:00
6a37255b57 Added global color settings 2019-04-05 11:37:51 +02:00
c93210356a Fixed wrong reference 2019-04-05 11:37:41 +02:00
9519e3aaed Added lines and subtext to hours and formatted them accordingly 2019-04-04 21:27:29 +02:00
f2581e57a6 Fine tuned visible_event_count 2019-04-04 13:15:15 +02:00
757a66eb6e Fixed color bug in "+ x more" 2019-04-04 10:40:18 +02:00
1ff09a5591 Fixed google timezone issue 2019-04-02 19:36:09 +02:00
a4be256b50 Added missing event information 2019-04-02 14:36:17 +02:00
a65bfddeeb Fixed timezone bug 2019-04-02 10:48:15 +02:00
45dd474bf3 Fixed overlapping month name 2019-04-02 10:39:22 +02:00
5faa0c5d1c New prototype 2019-03-27 18:26:54 +01:00
bd03a2b604 Added and changed formats 2019-03-27 18:26:45 +01:00
225963aaeb Fixed timezone-issue 2019-03-27 17:58:26 +01:00
1c56323ff0 Some bug fixes related to rrule 2019-03-27 17:48:28 +01:00
eaeea2083e Revert "Extended definition of event"
This reverts commit 6146fc7c2d.
2019-03-27 13:56:48 +01:00
ab9e34980a Some more files ignored 2019-03-27 13:52:56 +01:00
6146fc7c2d Extended definition of event 2019-03-27 13:52:37 +01:00
b01969d2da Partially integrated TextFormatter 2019-03-26 22:31:05 +01:00
59d2401f81 Added default font size 2019-03-26 22:30:37 +01:00
9e08553232 Added centralized formatting 2019-03-26 22:30:19 +01:00
ddb5f727d9 Next agenda-list prototype 2019-03-26 22:30:01 +01:00
4ef90e14b3 First prototyping of AgendaList 2019-03-24 22:14:58 +01:00
bef88f0b85 Fixed wrong calculation of visible_event_count 2019-03-24 21:42:00 +01:00
319e6947b1 Fixed negative sleep_time error 2019-03-24 21:35:05 +01:00
94591ec51e Implemented new panel-template 2019-03-24 21:30:44 +01:00
6dd6de16ce Equalized font size 2019-03-24 21:27:32 +01:00
019d150e78 Added filter for upcoming events 2019-03-24 21:13:32 +01:00
86c64eeba5 Added general background_color option to table_design 2019-03-24 21:09:10 +01:00
99bba341a9 Added missing color parameter tranmission 2019-03-24 21:05:12 +01:00
bcca8a8f8a Fixed some google calendar event issues based on timezones. 2019-03-24 19:49:00 +01:00
88ace91239 First version of rrule support. Redesigned CalendarInterface. Dropped Support for get_week_events 2019-03-24 11:07:22 +01:00
c854e7eec1 Made more info language dependent 2019-03-23 12:54:37 +01:00
544c23954d Added rrule property 2019-03-23 12:24:43 +01:00
0e1b6bef57 Removed spacing of weather icon 2019-03-23 12:23:14 +01:00
571846ae39 Added option to show number of additional, not shown events 2019-03-23 12:22:55 +01:00
3a2f145e22 Renamed for better understanding 2019-03-23 12:22:02 +01:00
4b1cbed0f7 Fixed false interpreted allday and multiday events 2019-03-21 22:52:42 +01:00
d838ee37d0 Expanded definitions for events in specific time ranges 2019-03-21 22:32:02 +01:00
ba7c437a13 Recognizing multiday events in get_day_events 2019-03-21 22:29:46 +01:00
7073d5bc5d Corrected property name 2019-03-21 22:29:23 +01:00
11ffdeac13 Added info about additional, not displayed events. 2019-03-21 22:02:08 +01:00
4cfe34ed93 Changed default datatype of update_interval to integer 2019-03-19 16:45:48 +01:00
139c59bfab Fixed type issue for update_interval 2019-03-19 16:34:14 +01:00
dcbaa4893e Added hourListDesign 2019-03-17 22:05:49 +01:00
3e63f5ff8b Merge branch 'master' of https://github.com/mgfcf/E-Paper-Calendar-with-iCal-sync-and-live-weather 2019-03-17 21:49:58 +01:00
5838734c9f Changed transmitting of calendar to transmitting of events 2019-03-17 20:55:45 +01:00
672d7058d8 Fine tuned numbers font size 2019-03-17 20:37:42 +01:00
f7c5f6b05e Added broad multiline alignment support 2019-03-17 20:32:32 +01:00
8686637271 Removed dead code 2019-03-17 20:25:49 +01:00
b1705941e8 Added missing library feedparser 2019-03-17 16:33:33 +01:00
c0a6ebe33f
Update README.md 2019-03-17 12:39:38 +01:00
27f2bd5eef
Update README.md 2019-03-17 12:38:34 +01:00
c99bb1538b Implemented DayView design canvas and options 2019-03-17 12:12:58 +01:00
69 changed files with 2907 additions and 797 deletions

4
.gitignore vendored
View file

@ -15,3 +15,7 @@
/Calendar/CalendarInterface.pyc /Calendar/CalendarInterface.pyc
/Calendar/CalendarEvent.pyc /Calendar/CalendarEvent.pyc
/Calendar/design_exported_old.png /Calendar/design_exported_old.png
/Calendar/settings_dev.py
/Calendar/settings_pers.py
/.vscode
/Calendar/images/

View 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

View 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"])

View file

@ -1,44 +1,95 @@
#!/usr/bin/python3 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from PIL import Image, ImageFont from PIL import Image, ImageFont
from settings import * from settings import font_boldness, font_size
import os
im_open = Image.open im_open = Image.open
path = '' path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
wpath = path+'weather-icons/' if path != "" and path[-1] != "/":
opath = path+'other/' path += "/"
fpath = path+'fonts/'
tempicon = im_open(opath+'temperature.jpeg') wpath = path+'weather-icons/'
humicon = im_open(opath+'humidity.jpeg') opath = path+'other/'
no_response= im_open(opath+'cloud-no-response.jpeg') fpath = 'fonts/'
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
sunseticon = im_open(opath+'wi-sunset.jpeg') tempicon = im_open(opath+'temperature.jpeg')
windicon = im_open(opath+'wi-strong-wind.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 = { fonts = {
"extralight" : fpath + "Assistant-ExtraLight.otf", "extralight": fpath + "Assistant-ExtraLight.otf",
"light" : fpath + "Assistant-Light.otf", "light": fpath + "Assistant-Light.otf",
"regular" : fpath + "Assistant-Regular.otf", "regular": fpath + "Assistant-Regular.otf",
"semibold" : fpath + "Assistant-SemiBold.otf", "semibold": fpath + "Assistant-SemiBold.otf",
"bold" : fpath + "Assistant-Bold.otf", "bold": fpath + "Assistant-Bold.otf",
"extrabold" : fpath + "Assistant-ExtraBold.otf" "extrabold": fpath + "Assistant-ExtraBold.otf"
} }
defaultfont = fonts[font_boldness] defaultfont = fonts[font_boldness]
defaultfontsize = int(font_size)
datetime_locals = {
"de" : "de_DE.UTF-8",
"en" : "en_US.UTF-8",
"zh_TW" : "zh_TW.UTF-8"
}
weathericons = { weathericons = {
'01d': 'wi-day-sunny', '02d':'wi-day-cloudy', '03d': 'wi-cloudy', '01d': 'wi-day-sunny', '02d': 'wi-day-cloudy', '03d': 'wi-cloudy',
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d':'wi-rain', '04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d': 'wi-rain',
'11d':'wi-thunderstorm', '13d':'wi-snow', '50d': 'wi-fog', '11d': 'wi-thunderstorm', '13d': 'wi-snow', '50d': 'wi-fog',
'01n': 'wi-night-clear', '02n':'wi-night-cloudy', '01n': 'wi-night-clear', '02n': 'wi-night-cloudy',
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy', '03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
'09n': 'wi-night-showers', '10n':'wi-night-rain', '09n': 'wi-night-showers', '10n': 'wi-night-rain',
'11n':'wi-night-thunderstorm', '13n':'wi-night-snow', '11n': 'wi-night-thunderstorm', '13n': 'wi-night-snow',
'50n': 'wi-night-alt-cloudy-windy'} '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",
]

View file

@ -1,8 +1,10 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from PIL import ImageDraw, ImageOps from PIL import ImageDraw, ImageOps
class BoxDesign (DesignEntity): class BoxDesign (DesignEntity):
"""Redefinition of ImageDraw.Draw.Rectangle""" """Redefinition of ImageDraw.Draw.Rectangle"""
def __init__(self, size, fill=None, outline=None, width=0): def __init__(self, size, fill=None, outline=None, width=0):
super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True) super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True)
self.size = size self.size = size
@ -12,9 +14,10 @@ class BoxDesign (DesignEntity):
self.width = width self.width = width
def __define_corners__(self): def __define_corners__(self):
topleft = (0,0) topleft = (0, 0)
bottomright = self.size bottomright = self.size
self.corners = [topleft, bottomright] self.corners = [topleft, bottomright]
def __finish_image__ (self): def __finish_image__(self):
ImageDraw.Draw(self.__image__).rectangle(self.corners, fill=self.fill, outline=self.outline, width=self.width) ImageDraw.Draw(self.__image__).rectangle(
self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -1,10 +1,13 @@
class CalendarEvent (object): class CalendarEvent (object):
"""Defines a calendar event, independent of any implementation""" """Defines a calendar event, independent of any implementation"""
def __init__ (self):
def __init__(self):
self.begin_datetime = None self.begin_datetime = None
self.end_datetime = None self.end_datetime = None
self.duration = None self.duration = None
self.all_day = None self.allday = None
self.multiday = None
self.rrule = None
self.title = None self.title = None
self.description = None self.description = None
@ -12,9 +15,10 @@ class CalendarEvent (object):
self.highlight = None self.highlight = None
self.calendar_name = None self.calendar_name = None
self.calendar_url = None
self.location = None self.location = None
self.fetch_datetime = None self.fetch_datetime = None
def __repr__(self): def __repr__(self):
return self.title return self.title

View file

@ -1,44 +1,182 @@
from DataSourceInterface import DataSourceInterface from DataSourceInterface import DataSourceInterface
from datetime import datetime, timezone, timedelta 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): class CalendarInterface (DataSourceInterface):
"""Interface for fetching and processing calendar event information.""" """Interface for fetching and processing calendar event information."""
def __init__(self):
self.loaded_events = self.__get_events__()
self.__sort_events__()
def __sort_events__(self): def __init__(self):
self.loaded_events.sort(key=lambda x : x.begin_datetime) 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): def __get_events__(self):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def get_upcoming_events(self): def get_upcoming_events(self, timespan=None, start_time=None):
return self.__get_events_to_filter__(lambda x : (x.begin_datetime - datetime.now(timezone.utc)) > timedelta(days=-1)) 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): def get_today_events(self):
return self.get_day_events(datetime.now(timezone.utc)) return self.get_day_events(date.today())
def get_day_events(self, date): def get_day_events(self, day):
return self.__get_events_to_filter__(lambda x : x.begin_datetime.strftime('%d-%m-%y') == date.strftime('%d-%m-%y')) 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): def get_month_events(self, month=-1, year=-1):
if month < 0: if month < 0:
month = datetime.now().month month = datetime.now().month
return self.__get_events_to_filter__(lambda x : x.begin_datetime.month == month) if year < 0:
year = datetime.now().year
def get_week_events(self, week = -1): local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
if week < 0 and week_starts_on == "Monday": month_start = datetime(year, month, 1, 0, 0, 0, 0, local_tzinfo)
week = int(datetime.now().strftime('%W')) + 1 month_days = calendar.monthrange(
elif week < 0: month_start.year, month_start.month)[1]
week = int(datetime.now().strftime('%U')) + 1 return self.__get_events_in_range__(month_start, timedelta(month_days))
if week_starts_on == "Monday": def __get_events_in_range__(self, start, duration):
return self.__get_events_to_filter__(lambda x : int(x.begin_datetime.strftime('%W')) + 1 == week) if self.events is None:
else:
return self.__get_events_to_filter__(lambda x : int(x.begin_datetime.strftime('%U')) + 1 == week)
def __get_events_to_filter__(self, event_filter):
if self.loaded_events is None:
return [] return []
return [event for event in self.loaded_events if event_filter(event)]
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
View 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

View 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

View 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

View file

@ -1,4 +1,8 @@
class DataSourceInterface (object): class DataSourceInterface (object):
"""Interface for child interfaces that fetch data.""" """Interface for child interfaces that fetch data."""
def is_available (self):
raise NotImplementedError("Functions needs to be implemented") 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
View 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)

View 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()

View file

@ -2,116 +2,153 @@ from DesignEntity import DesignEntity
from PIL import ImageDraw from PIL import ImageDraw
from TextDesign import TextDesign from TextDesign import TextDesign
from WeatherColumnDesign import WeatherColumnDesign from WeatherColumnDesign import WeatherColumnDesign
from datetime import date, timedelta from datetime import date, timedelta, datetime, timezone
from SingelDayEventListDesign import SingelDayEventListDesign from SingelDayEventListDesign import SingelDayEventListDesign
from Assets import fonts from Assets import fonts, colors, defaultfontsize
from settings import general_settings
from BoxDesign import BoxDesign
numberbox_ypos = 0.15 numberbox_ypos = 0.15
numberbox_height = 1 - 2 * numberbox_ypos numberbox_height = 1 - 2 * numberbox_ypos
number_height = numberbox_height * 0.83 number_height = numberbox_height * 0.83
number_boxypos = 0.17
month_height = numberbox_height / 4 month_height = numberbox_height / 4
monthbox_xpadding = 0.013 monthbox_xpadding = 0.013
monthbox_ypadding = -0.05
monthbox_width = 1 - numberbox_ypos - monthbox_xpadding monthbox_width = 1 - numberbox_ypos - monthbox_xpadding
weekday_height = numberbox_height * 0.19
weekday_ypadding = 0.02
weathercolumn_y_size = (0.4, 1) weathercolumn_y_size = (0.4, 1)
eventlist_y_fontsize = 0.093 weekday_height = numberbox_height * 0.19
eventlist_padding = monthbox_xpadding weekdaybox_height = (weekday_height / numberbox_height) * 1.5
eventlist_static_fontsize = defaultfontsize
eventlist_xpadding = monthbox_xpadding
eventlist_ypadding = 0.01
numberbox_font_color = "white" numberbox_font_color = colors["bg"]
numberbox_background_color = "red" numberbox_background_color = colors["hl"]
general_text_color = "black"
background_color = "white"
highlight_color = "red"
weekday_font = fonts["bold"] weekday_font = fonts["bold"]
class DayHeaderDesign (DesignEntity): class DayHeaderDesign (DesignEntity):
"""Detailed and big view of a given date.""" """Detailed and big view of a given date."""
def __init__ (self, size, date):
def __init__(self, size, date):
super(DayHeaderDesign, self).__init__(size) super(DayHeaderDesign, self).__init__(size)
self.__init_image__(color=background_color) self.weather_column_width = 0
self.date = date self.date = date
def add_weather (self, weather): def add_weather(self, weather):
forecast = weather.get_forecast_in_days(self.date.day - date.today().day) if general_settings["weather-info"] == False:
size = (weathercolumn_y_size[0] * self.size[1], weathercolumn_y_size[1] * self.size[1]) 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) pos = (self.size[0] - size[0], 0)
design = WeatherColumnDesign(size, forecast) design = WeatherColumnDesign(size, forecast)
design.pos = pos design.pos = pos
self.draw_design(design) self.draw_design(design)
def add_calendar (self, calendar): def add_calendar(self, calendar):
self.__draw_event_list__(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_rssfeed (self, rss): def add_events(self, events):
self.__draw_event_list__(events)
def add_rssfeed(self, rss):
pass pass
def __finish_image__ (self): def add_crypto(self, crypto):
pass
def __finish_image__(self):
self.__draw_number_square__() self.__draw_number_square__()
self.__draw_month__() self.__draw_month__()
self.__draw_weekday__()
def __draw_event_list__ (self, calendar): def __draw_event_list__(self, events):
box_ypos = numberbox_ypos * self.size[1] box_ypos = numberbox_ypos * self.size[1]
box_xpos = numberbox_ypos * self.size[1] box_xpos = numberbox_ypos * self.size[1]
box_height = numberbox_height * self.size[1] box_height = numberbox_height * self.size[1]
padding = eventlist_padding * self.size[0] xpadding = eventlist_xpadding * self.size[0]
month_height = weekday_height * self.size[1] ypadding = eventlist_ypadding * self.size[1]
weather_width = weathercolumn_y_size[0] * self.size[1] monthbox_height = (monthbox_ypadding + month_height) * self.size[1]
pos = (box_xpos + box_height + padding, box_ypos + month_height + padding) pos = (box_xpos + box_height + xpadding,
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1] - box_ypos) box_ypos + monthbox_height + ypadding)
fontsize = eventlist_y_fontsize * self.size[1] size = (self.size[0] - pos[0] - self.weather_column_width,
self.size[1] - pos[1] - box_ypos)
fontsize = eventlist_static_fontsize
event_list = SingelDayEventListDesign(size, calendar, self.date, fontsize, general_color=general_text_color, background_color=background_color, highlight_color=highlight_color) 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 event_list.pos = pos
self.draw_design(event_list) self.draw_design(event_list)
def __draw_weekday__ (self): def __draw_month__(self):
font_size = int(weekday_height * self.size[1])
padding = int(weekday_ypadding * self.size[1])
box_ypos = int((numberbox_ypos) * self.size[1]) + padding
box_xpos = int(numberbox_ypos * self.size[1])
box_height = numberbox_height * self.size[1]
box_pos = (box_xpos, box_ypos)
box_size = (int(numberbox_height * self.size[1]), box_height)
week_day_name = self.date.strftime("%A")
week_day = TextDesign(box_size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", font=weekday_font)
week_day.pos = box_pos
self.draw_design(week_day)
def __draw_month__ (self):
font_size = int(month_height * self.size[1]) font_size = int(month_height * self.size[1])
padding = int(monthbox_xpadding * self.size[0]) xpadding = int(monthbox_xpadding * self.size[0])
ypadding = int(monthbox_ypadding * self.size[1])
box_ypos = int(numberbox_ypos * self.size[1]) box_ypos = int(numberbox_ypos * self.size[1])
box_height = int(numberbox_height * self.size[1]) box_height = int(numberbox_height * self.size[1])
box_pos = (box_ypos + box_height + padding, box_ypos) box_pos = (box_ypos + box_height + xpadding, box_ypos + ypadding)
box_size = (int(monthbox_width * self.size[0]), box_height) box_size = (int(monthbox_width * self.size[0]), box_height)
month_name = self.date.strftime("%B") month_name = self.date.strftime("%B")
month = TextDesign(box_size, text=month_name, fontsize=font_size, color=general_text_color, background_color=background_color) month = TextDesign(box_size, text=month_name, fontsize=font_size)
month.pos = box_pos month.pos = box_pos
self.draw_design(month) self.draw_design(month)
def __draw_number_square__ (self): def __draw_number_square__(self):
box_ypos = int(numberbox_ypos * self.size[1]) box_height = numberbox_height * self.size[1]
box_height = int(numberbox_height * self.size[1]) box_ypos = numberbox_ypos * self.size[1]
box_topleft = (box_ypos,box_ypos) box_pos = (box_ypos, box_ypos)
box_bottomright = (box_topleft[0] + box_height, box_topleft[1] + box_height)
ImageDraw.Draw(self.__image__).rectangle([ box_topleft, box_bottomright ], fill=numberbox_background_color)
font_size = int(number_height * self.size[1])
box_size = (box_height, box_height) 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__() day_text = self.__get_day_text__()
number = TextDesign(box_size, text=day_text, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center") number = TextDesign(size, text=day_text, background_color=numberbox_background_color,
number.pos = box_topleft color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
number.pos = pos
number.mask = False
self.draw_design(number) self.draw_design(number)
def __abs_co__ (self, coordinates): def __draw_weekday__(self):
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1])) 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)
def __get_day_text__ (self): week_day_name = self.date.strftime("%A")
if self.date.day is 14 and self.date.month is 3: #PI-Day week_day = TextDesign(size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color,
return "π" fontsize=font_size, horizontalalignment="center", verticalalignment="center", font=weekday_font)
return str(self.date.day) 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)

View file

@ -1,78 +1,106 @@
from PanelDesign import PanelDesign from PanelDesign import PanelDesign
from Assets import * from Assets import colors
from settings import * from settings import general_settings
import calendar as callib import calendar as callib
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
from PIL import ImageDraw from PIL import ImageDraw
from TextDesign import TextDesign from TextDesign import TextDesign
from DayHeaderDesign import DayHeaderDesign from DayHeaderDesign import DayHeaderDesign
from DayRowDesign import DayRowDesign from DayRowDesign import DayRowDesign
from RssPostListDesign import RssPostListDesign from RssPostListDesign import RssPostListDesign
from CryptoListDesign import CryptoListDesign
from settings import line_thickness
from math import ceil
todayheader_pos = (0,0) todayheader_pos = (0, 0)
todayheader_size = (1,0.25) todayheader_size = (1, 0.25)
line_color = "black" lines_thickness = line_thickness
lines_thickness = 1
infoarea_replacedrowscount = 3 infoarea_replacedrowscount = 3
dayrowsarea_ypos = todayheader_size[1] dayrowsarea_ypos = todayheader_size[1]
dayrowsarea_height = 1 - todayheader_size[1] dayrowsarea_height = 1 - todayheader_size[1]
dayrow_min_format = 50 / 384 dayrow_min_format = 50 / 384
dayrow_max_format = 70 / 384 dayrow_max_format = 70 / 384
rss_x_fontsize = 14 / 384
rss_y_padding = 5 rss_y_padding = 5
crypto_y_padding = 5
class DayListPanel (PanelDesign): class DayListPanel (PanelDesign):
"""Overview that focuses on the current day and """Overview that focuses on the current day and
lists following days in a list below.""" lists following days in a list below."""
def __init__ (self, size):
def __init__(self, size):
super(DayListPanel, self).__init__(size) super(DayListPanel, self).__init__(size)
self.__day_rows__ = [] self.__day_rows__ = []
self.__calc_dayrow_size__() self.__calc_dayrow_size__()
self.__first_render__() self.__first_render__()
def __first_render__ (self): def __first_render__(self):
self.__draw_today_header__() self.__draw_today_header__()
self.__draw_day_rows__() self.__draw_day_rows__()
def add_weather (self, weather): def add_weather(self, weather):
for row in self.__day_rows__: for row in self.__day_rows__:
row.add_weather(weather) row.add_weather(weather)
def add_calendar (self, calendar): def add_calendar(self, calendar):
for row in self.__day_rows__: for row in self.__day_rows__:
row.add_calendar(calendar) row.add_calendar(calendar)
def add_rssfeed (self, rss): def add_rssfeed(self, rss):
for row in self.__day_rows__: for row in self.__day_rows__:
row.add_rssfeed(rss) row.add_rssfeed(rss)
if general_settings["info-area"] is "rss": if general_settings["info-area"] is "rss":
self.__day_rows__ = self.__day_rows__[:-infoarea_replacedrowscount]
self.__draw_rss_infoarea__(rss) self.__draw_rss_infoarea__(rss)
def __draw_rss_infoarea__ (self, rss): def add_crypto(self, crypto):
height = infoarea_replacedrowscount * self.dayrow_size[1] * self.size[1] - rss_y_padding 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 ypos = self.size[1] - height
size = (self.size[0], height) size = (self.size[0], height)
fontsize = rss_x_fontsize * size[0]
pos = (0, ypos) pos = (0, ypos)
design = RssPostListDesign(size, rss, fontsize) design = RssPostListDesign(size, rss)
design.pos = pos design.pos = pos
self.draw_design(design) self.draw_design(design)
def __draw_day_rows__ (self): 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__() following_days = self.__get_following_days__()
for i, date in enumerate(following_days): for i, date in enumerate(following_days):
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date) row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
row.pos = self.__get_day_row_pos__(i) row.pos = self.__get_day_row_pos__(i)
self.__day_rows__.append(row) self.__day_rows__.append(row)
def __get_day_row_pos__ (self, i): def __get_day_row_pos__(self, i):
ypos = self.size[1] * dayrowsarea_ypos ypos = self.size[1] * dayrowsarea_ypos
down_shift = i * self.dayrow_size[1] * self.size[1] down_shift = i * self.dayrow_size[1] * self.size[1]
return (0, int(ypos + down_shift)) return (0, int(ypos + down_shift))
def __calc_dayrow_size__ (self): def __calc_dayrow_size__(self):
max_area_height = dayrowsarea_height * self.size[1] max_area_height = dayrowsarea_height * self.size[1]
max_row_number = max_area_height / (dayrow_min_format * self.size[0]) max_row_number = max_area_height / (dayrow_min_format * self.size[0])
min_row_number = max_area_height / (dayrow_max_format * self.size[0]) min_row_number = max_area_height / (dayrow_max_format * self.size[0])
@ -81,33 +109,32 @@ class DayListPanel (PanelDesign):
row_height = max_area_height / self.dayrow_count row_height = max_area_height / self.dayrow_count
self.dayrow_size = (1, row_height / self.size[1]) self.dayrow_size = (1, row_height / self.size[1])
if general_settings["info-area"] in ["rss"]:
self.dayrow_count -= infoarea_replacedrowscount
def __get_following_days__(self): def __get_following_days__(self):
following_days = [] following_days = []
for i in range(self.dayrow_count): for i in range(self.dayrow_count):
following_days.append(date.today() + timedelta(days=i + 1)) following_days.append(date.today() + timedelta(days=i + 1))
return following_days return following_days
def __draw_today_header__ (self): def __draw_today_header__(self):
header = DayHeaderDesign(self.__abs_co__(todayheader_size), date.today()) header = DayHeaderDesign(self.__abs_co__(
todayheader_size), date.today())
header.pos = self.__abs_co__(todayheader_pos) header.pos = self.__abs_co__(todayheader_pos)
self.__day_rows__.append(header) self.__day_rows__.append(header)
def __draw_lines__(self): def __draw_lines__(self):
positions = [] positions = []
for i in range(self.dayrow_count + 1): for i in range(len(self.__day_rows__)):
positions.append(self.__get_day_row_pos__(i)[1]) positions.append(self.__get_day_row_pos__(i)[1])
for ypos in positions: for ypos in positions:
line_start = (0, ypos) line_start = (0, ypos)
line_end = (self.size[0], ypos) line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line([line_start, line_end], fill=line_color, width=lines_thickness) ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=lines_thickness)
def __finish_image__(self): def __finish_panel__(self):
for design in self.__day_rows__: for design in self.__day_rows__:
self.draw_design(design) self.draw_design(design)
self.__draw_lines__() self.__draw_lines__()
def __abs_co__(self, coordinates): def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1])) return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))

View file

@ -1,62 +1,66 @@
from PIL import ImageDraw, Image from PIL import ImageDraw, Image
from TextDesign import TextDesign from TextDesign import TextDesign
from settings import week_starts_on, owm_paid_subscription from settings import week_starts_on, owm_paid_subscription, general_settings
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from datetime import datetime from datetime import datetime
from Assets import weathericons, wpath, fonts from Assets import weathericons, wpath, fonts, colors, defaultfontsize
from SingelDayEventListDesign import SingelDayEventListDesign from SingelDayEventListDesign import SingelDayEventListDesign
daynumber_y_size = (1, 0.65) daynumber_y_size = (1, 0.60)
weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1]) weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1])
weekday_ypos = daynumber_y_size[1] weekday_ypos = daynumber_y_size[1]
daynumber_fontsize = daynumber_y_size[1] * 0.8 daynumber_fontsize = daynumber_y_size[1] * 0.85
weekday_fontsize = weekday_y_size[1] * 0.75 daynumber_ypadding = 0.1
weekday_fontsize = weekday_y_size[1] * 0.65
weathericon_ypos = 0.1 weathericon_ypos = 0.1
weathericon_height = 1 - 2 * weathericon_ypos weathericon_height = 1 - 2 * weathericon_ypos
eventlist_xpadding = 5 eventlist_xpadding = 5
eventlist_ypos = 0.1 eventlist_ypos = 0.02
eventlist_y_fontsize = 0.2 eventlist_y_fontsize = 0.2
general_text_color = "black" font = fonts["light"]
highlight_text_color = "red"
background_color = "white"
font = fonts["regular"]
class DayRowDesign (DesignEntity): class DayRowDesign (DesignEntity):
"""Detailed view of a given date.""" """Detailed view of a given date."""
def __init__ (self, size, date):
def __init__(self, size, date):
super(DayRowDesign, self).__init__(size) super(DayRowDesign, self).__init__(size)
self.__init_image__(color=background_color) self.__init_image__()
self.date = date self.date = date
def add_weather (self, weather): def add_weather(self, weather):
if weather.is_available is False: if weather.is_available is False:
return return
self.__draw_forecast__(weather) self.__draw_forecast__(weather)
def add_calendar (self, calendar): def add_calendar(self, calendar):
self.__draw_event_list__(calendar) self.__draw_event_list__(calendar)
def add_rssfeed (self, rss): def add_rssfeed(self, rss):
pass pass
def __draw_event_list__ (self, calendar): def __draw_event_list__(self, calendar):
number_width = daynumber_y_size[0] * self.size[1] number_width = daynumber_y_size[0] * self.size[1]
ypos = eventlist_ypos * self.size[1] ypos = eventlist_ypos * self.size[1]
weather_width = 0 weather_width = 0
if owm_paid_subscription: if owm_paid_subscription and general_settings["weather-info"]:
weather_width = weathericon_height * self.size[1] weather_width = weathericon_height * self.size[1]
pos = (number_width + eventlist_xpadding, ypos) pos = (number_width + eventlist_xpadding, ypos)
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1]) size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1])
fontsize = eventlist_y_fontsize * self.size[1] fontsize = eventlist_y_fontsize * self.size[1]
event_list = SingelDayEventListDesign(size, calendar, self.date, fontsize, line_spacing=0, general_color=general_text_color, background_color=background_color, highlight_color=highlight_text_color) 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 event_list.pos = pos
self.draw_design(event_list) self.draw_design(event_list)
def __draw_forecast__ (self, weather): def __draw_forecast__(self, weather):
forecast = weather.get_forecast_in_days(self.date.day - datetime.today().day) forecast = weather.get_forecast_in_days(
self.date.day - datetime.today().day)
if forecast is None: if forecast is None:
return return
@ -68,46 +72,52 @@ class DayRowDesign (DesignEntity):
resized_icon = icon.resize(size, resample=Image.LANCZOS) resized_icon = icon.resize(size, resample=Image.LANCZOS)
self.draw(resized_icon, pos) self.draw(resized_icon, pos)
def __finish_image__ (self): def __finish_image__(self):
self.__draw_day_number__()
self.__draw_weekday__() self.__draw_weekday__()
self.__draw_day_number__()
def __draw_weekday__ (self): def __draw_weekday__(self):
font_size = int(weekday_fontsize * self.size[1]) font_size = int(weekday_fontsize * self.size[1])
size = (weekday_y_size[0] * self.size[1], weekday_y_size[1] * self.size[1]) size = (weekday_y_size[0] * self.size[1],
weekday_y_size[1] * self.size[1])
ypos = weekday_ypos * self.size[1] ypos = weekday_ypos * self.size[1]
pos = (0, ypos) pos = (0, ypos)
color = self.__get_day_color__() color = self.__get_day_color__()
week_day_name = self.date.strftime("%a") week_day_name = self.date.strftime("%a")
week_day = TextDesign(size, text=week_day_name, font=font, background_color=background_color, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="top") 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.pos = pos
week_day.mask = False
self.draw_design(week_day) self.draw_design(week_day)
def __draw_day_number__ (self): def __draw_day_number__(self):
font_size = int(daynumber_fontsize * self.size[1]) font_size = int(daynumber_fontsize * self.size[1])
size = (daynumber_y_size[0] * self.size[1], daynumber_y_size[1] * self.size[1]) ypadding = daynumber_ypadding * self.size[1]
pos = (0, 0) size = (daynumber_y_size[0] * self.size[1],
daynumber_y_size[1] * self.size[1])
pos = (0, ypadding)
day_text = self.__get_day_text__() day_text = self.__get_day_text__()
color = self.__get_day_color__() color = self.__get_day_color__()
number = TextDesign(size, text=day_text, font=font, background_color=background_color, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="bottom") number = TextDesign(size, text=day_text, font=font, color=color,
fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
number.pos = pos number.pos = pos
self.draw_design(number) self.draw_design(number)
def __abs_co__ (self, coordinates): def __abs_co__(self, coordinates):
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1])) return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
def __get_day_text__ (self): def __get_day_text__(self):
return str(self.date.day) return str(self.date.day)
def __get_day_color__ (self): def __get_day_color__(self):
"""Depending on week_starts_on""" """Depending on week_starts_on"""
if week_starts_on == "Monday" and self.date.strftime("%w") == "0": if week_starts_on == "Monday" and self.date.strftime("%w") == "0":
return highlight_text_color return colors["hl"]
elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6": elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6":
return highlight_text_color return colors["hl"]
else: else:
return general_text_color return colors["fg"]

155
Calendar/DayViewPanel.py Normal file
View 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

View file

@ -1,13 +1,21 @@
from DebugInterface import DebugInterface from DebugInterface import DebugInterface
from Assets import weathericons from Assets import weathericons
from datetime import datetime
import traceback
class DebugConsole (DebugInterface): class DebugConsole (DebugInterface):
"""Defines concrete console export of debug objects""" """Defines concrete console export of debug objects"""
def print_event (self, event):
def print_event(self, event):
print("\nCalendarEvent:") print("\nCalendarEvent:")
print("---------------------") print("---------------------")
print('Begin datetime: ' + str(event.begin_datetime)) print('Begin datetime: ' + str(event.begin_datetime))
print('End datetime: ' + str(event.end_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('Title: ' + str(event.title))
print('Description: ' + str(event.description)) print('Description: ' + str(event.description))
print('Attendees: ' + str(event.attendees)) print('Attendees: ' + str(event.attendees))
@ -16,7 +24,7 @@ class DebugConsole (DebugInterface):
print('Location: ' + str(event.location)) print('Location: ' + str(event.location))
print('Fetch datetime: ' + str(event.fetch_datetime)) print('Fetch datetime: ' + str(event.fetch_datetime))
def print_forecast (self, forecast): def print_forecast(self, forecast):
print("\nWeatherForecast:") print("\nWeatherForecast:")
print("---------------------") print("---------------------")
print('Air temperature: ' + str(forecast.air_temperature)) print('Air temperature: ' + str(forecast.air_temperature))
@ -39,7 +47,19 @@ class DebugConsole (DebugInterface):
print('Location: ' + str(forecast.location)) print('Location: ' + str(forecast.location))
print('Fetch datetime: ' + str(forecast.fetch_datetime)) print('Fetch datetime: ' + str(forecast.fetch_datetime))
def print_line (self, content): def print_line(self, content):
if content is None: if content is None:
return return
print(str(content)) 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))

View file

@ -1,10 +1,14 @@
class DebugInterface (object): class DebugInterface (object):
"""Defines general interface for debugging operations""" """Defines general interface for debugging operations"""
def print_event (self, event):
def print_event(self, event):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def print_forecast (self, forecast): def print_forecast(self, forecast):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def print_line (self, content): def print_line(self, content):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def print_err(self, exception, msg=""):
raise NotImplementedError("Functions needs to be implemented")

View file

@ -1,12 +1,21 @@
from PIL import Image, ImageOps, ImageDraw from PIL import Image, ImageOps, ImageDraw
from Assets import colors
masking_threshold = 200
masking_threshold = 100
class DesignEntity (object): class DesignEntity (object):
"""General entity that can be drawn on to a panel design or """General entity that can be drawn on to a panel design or
other design entities.""" other design entities."""
def __init__ (self, size, mask=False, invert_mask=False, color_key=False):
def __init__(self, size, mask=False, invert_mask=False, color_key=False):
self.size = size 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.pos = (0, 0)
self.mask = mask self.mask = mask
self.invert_mask = invert_mask self.invert_mask = invert_mask
@ -14,36 +23,39 @@ class DesignEntity (object):
self.__finished_image__ = False self.__finished_image__ = False
self.color_key = color_key self.color_key = color_key
def __init_image__ (self, color = 'white'): def __init_image__(self, color=colors["bg"]):
rounded_size = (int(self.size[0]),int(self.size[1])) rounded_size = (int(self.size[0]), int(self.size[1]))
self.__image__ = Image.new('RGB', rounded_size, color=color) self.__image__ = Image.new('RGBA', rounded_size, color=color)
def get_image (self): def get_image(self):
if self.__finished_image__ is False: if self.__finished_image__ is False:
self.__finish_image__() self.__finish_image__()
self.__finished_image__ = True self.__finished_image__ = True
return self.__image__ return self.__image__
def draw (self, subimage, pos, mask=False, invert_mask=False, color_key=False): def draw(self, subimage, pos, mask=False, invert_mask=False, color_key=False):
rounded_pos = (int(pos[0]),int(pos[1])) rounded_pos = (int(pos[0]), int(pos[1]))
img_mask = None img_mask = None
if mask: if mask:
img_mask = self.__get_mask__(subimage, invert_mask=invert_mask, color_key=color_key) img_mask = self.__get_mask__(
subimage, invert_mask=invert_mask, color_key=color_key)
self.__image__.paste(subimage, rounded_pos, mask=img_mask) self.__image__.paste(subimage, rounded_pos, mask=img_mask)
def draw_design (self, entity): def draw_design(self, entity):
self.draw(entity.get_image(), entity.pos, entity.mask, entity.invert_mask, entity.color_key) self.draw(entity.get_image(), entity.pos, entity.mask,
entity.invert_mask, entity.color_key)
def draw_image (self, path, pos): def draw_image(self, path, pos):
self.draw(Image.open(path), pos) self.draw(Image.open(path), pos)
def __finish_image__ (self): def __finish_image__(self):
pass pass
def __get_mask__ (self, image, invert_mask, color_key): def __get_mask__(self, image, invert_mask, color_key):
mask = image.convert('L') mask = image.convert('L')
if color_key: if color_key:
mask = mask.point(lambda p : 255 if p >= masking_threshold else 0) mask = mask.point(lambda p: 0 if p <
masking_threshold else 255, '1').convert('L')
if invert_mask: if invert_mask:
mask = ImageOps.invert(mask) mask = ImageOps.invert(mask)
return ImageOps.invert(mask) return ImageOps.invert(mask)

99
Calendar/Dictionary.py Normal file
View 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
]

View 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

View file

@ -1,11 +1,12 @@
class DisplayAdapter (object): class DisplayAdapter (object):
"""Interface for CalendarDesign output channels.""" """Interface for CalendarDesign output channels."""
def __init__(self, width, height): def __init__(self, width, height):
self.width = width self.width = width
self.height = height self.height = height
def render (self, design): def render(self, design):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def calibrate (self): def calibrate(self):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")

View file

@ -9,24 +9,36 @@ Copyright by aceisace
""" """
from datetime import datetime from datetime import datetime
from time import sleep from time import sleep
from Assets import datetime_locals from Assets import path
from LoopTimer import LoopTimer from LoopTimer import LoopTimer
import locale import locale
from DebugConsole import DebugConsole from DebugConsole import DebugConsole
from settings import * 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 MonthOvPanel import MonthOvPanel
from DayListPanel import DayListPanel 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 OwmForecasts
import IcalEvents import IcalEvents
import RssParserPosts 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))
locale.setlocale(locale.LC_ALL, datetime_locals[language])
debug = DebugConsole() debug = DebugConsole()
output_adapters = [] output_adapters = []
if render_to_file: if render_to_file:
import ImageFileAdapter import ImageFileAdapter
epd = ImageFileAdapter.ImageFileAdapter() epd = ImageFileAdapter.ImageFileAdapter(path)
output_adapters.append(epd) output_adapters.append(epd)
if render_to_display: if render_to_display:
@ -40,14 +52,28 @@ if render_to_display:
output_adapters.append(epd) output_adapters.append(epd)
available_panels = { available_panels = {
"day-list" : DayListPanel, "day-list": DayListPanel,
"month-overview" : MonthOvPanel "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=True) loop_timer = LoopTimer(
update_interval, run_on_hour=run_on_hour, max_loop_count=max_loop_count)
"""Main loop starts from here""" """Main loop starts from here"""
def main ():
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: while True:
loop_timer.begin_loop() loop_timer.begin_loop()
start_time = loop_timer.get_current()[0] start_time = loop_timer.get_current()[0]
@ -57,36 +83,53 @@ def main ():
for output in output_adapters: for output in output_adapters:
output.calibrate() output.calibrate()
if choosen_design in available_panels.keys(): if choosen_design in available_panels.keys():
design = available_panels[choosen_design]((epd.width, epd.height)) design = available_panels[choosen_design]((epd.width, epd.height))
else: else:
raise ImportError("choosen_design must be valid (" + choosen_design + ")") raise ImportError(
"choosen_design must be valid (" + choosen_design + ")")
debug.print_line("Fetching weather information from open weather map") debug.print_line("Fetching weather information from open weather map")
owm = OwmForecasts.OwmForecasts(location, api_key, paid_api=owm_paid_subscription) owm.reload()
design.add_weather(owm) design.add_weather(owm)
debug.print_line('Fetching events from your calendar') debug.print_line('Fetching events from your calendar')
events_cal = IcalEvents.IcalEvents(ical_urls, highlighted_ical_urls) events_cal.reload()
design.add_calendar(events_cal) design.add_calendar(events_cal)
debug.print_line('Fetching posts from your rss-feeds') debug.print_line('Fetching posts from your rss-feeds')
rss = RssParserPosts.RssParserPosts(rss_feeds) rss.reload()
design.add_rssfeed(rss) 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") debug.print_line("\nStarting to render")
for i, output in enumerate(output_adapters): for i, output in enumerate(output_adapters):
output.render(design) try:
debug.print_line(str(i + 1) + " of " + str(len(output_adapters)) + " rendered") 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") debug.print_line("=> Finished rendering" + "\n")
loop_timer.end_loop() loop_timer.end_loop()
sleep_time = loop_timer.time_until_next()
debug.print_line("This loop took " + str(loop_timer.get_last_duration()) + " to execute.") 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.") debug.print_line("Sleeping " + str(sleep_time) + " until next loop.")
sleep(sleep_time.total_seconds()) sleep(sleep_time.total_seconds())
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -1,10 +1,14 @@
from BoxDesign import BoxDesign from BoxDesign import BoxDesign
from PIL import ImageDraw, ImageOps from PIL import ImageDraw, ImageOps
class EllipseDesign (BoxDesign): class EllipseDesign (BoxDesign):
"""Redefinition of ImageDraw.Draw.Rectangle""" """Redefinition of ImageDraw.Draw.Rectangle"""
def __init__(self, size, fill=None, outline=None, width=0):
super(EllipseDesign, self).__init__(size, fill=fill, outline=outline, width=width)
def __finish_image__ (self): def __init__(self, size, fill=None, outline=None, width=0):
ImageDraw.Draw(self.__image__).ellipse(self.corners, fill=self.fill, outline=self.outline, width=self.width) super(EllipseDesign, self).__init__(
size, fill=fill, outline=outline, width=width)
def __finish_image__(self):
ImageDraw.Draw(self.__image__).ellipse(
self.corners, fill=self.fill, outline=self.outline, width=self.width)

View file

@ -2,11 +2,12 @@ from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
from settings import display_colours from settings import display_colours
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
class Epd7in5Adapter (EpdAdapter): class Epd7in5Adapter (EpdAdapter):
def __init__ (self): def __init__(self):
super(Epd7in5Adapter, self).__init__(384, 640) super(Epd7in5Adapter, self).__init__(384, 640)
def display_frame (self, frame_buffer): def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1) self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, 30720): for i in range(0, 30720):
temp1 = frame_buffer[i] temp1 = frame_buffer[i]
@ -30,11 +31,11 @@ class Epd7in5Adapter (EpdAdapter):
self.delay_ms(100) self.delay_ms(100)
self.wait_until_idle() self.wait_until_idle()
def get_frame_buffer (self, image): def get_frame_buffer(self, image):
buf = [0x00] * int(self.height * self.width / 8) buf = [0x00] * int(self.height * self.width / 8)
# Set buffer to value of Python Imaging Library image. # Set buffer to value of Python Imaging Library image.
# Image must be in mode 1. # Image must be in mode 1.
image_monocolor = image.convert('1') #with ot withour dithering? image_monocolor = image.convert('L') # with ot withour dithering?
imwidth, imheight = image_monocolor.size imwidth, imheight = image_monocolor.size
if imwidth != self.height or imheight != self.width: if imwidth != self.height or imheight != self.width:
raise ValueError('Image must be same dimensions as display \ raise ValueError('Image must be same dimensions as display \
@ -44,11 +45,11 @@ class Epd7in5Adapter (EpdAdapter):
for y in range(self.width): for y in range(self.width):
for x in range(self.height): for x in range(self.height):
# Set the bits for the column of pixels at the current position. # Set the bits for the column of pixels at the current position.
if pixels[x, y] != 0: if pixels[x, y] >= 240: # White
buf[int((x + y * self.height) / 8)] |= 0x80 >> (x % 8) buf[int((x + y * self.height) / 8)] |= 0x80 >> (x % 8)
return buf return buf
def calibrate (self): def calibrate(self):
for _ in range(2): for _ in range(2):
self.init_render() self.init_render()
black = Image.new('1', (self.height, self.width), 'black') black = Image.new('1', (self.height, self.width), 'black')
@ -61,4 +62,4 @@ class Epd7in5Adapter (EpdAdapter):
print('calibrating white...') print('calibrating white...')
self.display_frame(self.get_frame_buffer(white)) self.display_frame(self.get_frame_buffer(white))
self.sleep() self.sleep()
print('Calibration complete') print('Calibration complete')

View file

@ -2,17 +2,19 @@ from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
from settings import display_colours from settings import display_colours
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from math import sqrt, pow from math import sqrt, pow
import numpy as np
class Epd7in5bAdapter (EpdAdapter): class Epd7in5bAdapter (EpdAdapter):
def __init__ (self): def __init__(self):
super(Epd7in5bAdapter, self).__init__(384, 640) super(Epd7in5bAdapter, self).__init__(384, 640)
def display_frame (self, frame_buffer): def display_frame(self, frame_buffer):
self.send_command(DATA_START_TRANSMISSION_1) self.send_command(DATA_START_TRANSMISSION_1)
for i in range(0, int(self.height / 4 * self.width)): for i in range(0, int(self.height / 4 * self.width)):
#the above line had to be modified due to python2 -> python3 # the above line had to be modified due to python2 -> python3
#the issue lies in division, which returns integers in python2 # the issue lies in division, which returns integers in python2
#but floats in python3 # but floats in python3
temp1 = frame_buffer[i] temp1 = frame_buffer[i]
j = 0 j = 0
while (j < 4): while (j < 4):
@ -38,50 +40,38 @@ class Epd7in5bAdapter (EpdAdapter):
self.delay_ms(100) self.delay_ms(100)
self.wait_until_idle() self.wait_until_idle()
def get_frame_buffer (self, image): def get_frame_buffer(self, image):
buf = [ 0x00 ] * int(self.height * self.width / 4) buf = [0x00] * int(self.height * self.width / 4)
image_rgb = image imwidth, imheight = image.size
imwidth, imheight = image_rgb.size
if imwidth != self.height or imheight != self.width: if imwidth != self.height or imheight != self.width:
raise ValueError('Image must be same dimensions as display \ raise ValueError('Image must be same dimensions as display \
({0}x{1}).' .format(self.height, self.width)) ({0}x{1}).' .format(self.height, self.width))
for y in range(self.width): image_buf = self.__prepare_image__(image)
for x in range(self.height): for x in range(self.width):
for y in range(self.height):
# Set the bits for the column of pixels at the current # Set the bits for the column of pixels at the current
# position. # position.
pixel = image_rgb.getpixel((x, y)) if image_buf[x, y, 1] == 255: # White
color = self.__get_color__(pixel) buf[int((y + x * self.height) / 4)] |= 0xC0 >> (y % 4 * 2)
if color is 'white': elif image_buf[x, y, 0] == 0: # Black
buf[int((x + y * self.height) / 4)] |= 0xC0 >> (x % 4 * 2) buf[int((y + x * self.height) / 4)
elif color is 'black': ] &= ~(0xC0 >> (y % 4 * 2))
buf[int((x + y * self.height) / 4)] &= ~(0xC0 >> (x % 4 * 2)) else: # Red
if color is 'red': buf[int((y + x * self.height) / 4)
buf[int((x + y * self.height) / 4)] &= ~(0xC0 >> (x % 4 * 2)) ] &= ~(0xC0 >> (y % 4 * 2))
buf[int((x + y * self.height) / 4)] |= 0x40 >> (x % 4 * 2) buf[int((y + x * self.height) / 4)] |= 0x40 >> (y % 4 * 2)
return buf #due to python2 -> python3, int had to be added in 'get_frame return buf
#_buffer
def __get_color__ (self, pixel): def __prepare_image__(self, image):
color_percent = self.__get_color_percentage__(pixel) buffer = np.array(image)
brightness = self.__brightness__(pixel) r, g = buffer[:, :, 0], buffer[:, :, 1]
if brightness > 240 or (brightness > 220 and color_percent[0] > 40): buffer[np.logical_and(r > 220, g > 220)] = [255, 255, 255]
return 'white' buffer[r > g] = [255, 0, 0]
elif color_percent[0] > 35: buffer[r != 255] = [0, 0, 0]
return 'red' return buffer
else:
return 'black'
def __get_color_percentage__ (self, pixel): def calibrate(self):
sum = pixel[0] + pixel[1] + pixel[2]
if sum is 0:
return (0,0,0)
return (pixel[0] / sum * 100, pixel[1] / sum * 100, pixel[2] / sum * 100)
def __brightness__ (self, pixel):
return (pixel[0] + pixel[1] + pixel[2]) / 3
def calibrate (self):
for _ in range(2): for _ in range(2):
self.init_render() self.init_render()
black = Image.new('RGB', (self.height, self.width), 'black') black = Image.new('RGB', (self.height, self.width), 'black')
@ -99,4 +89,4 @@ class Epd7in5bAdapter (EpdAdapter):
print('calibrating white...') print('calibrating white...')
self.display_frame(self.get_frame_buffer(white)) self.display_frame(self.get_frame_buffer(white))
self.sleep() self.sleep()
print('Calibration complete') print('Calibration complete')

View file

@ -4,53 +4,55 @@ import RPi.GPIO as GPIO
import time import time
from PIL import Image from PIL import Image
RST_PIN = 17 RST_PIN = 17
DC_PIN = 25 DC_PIN = 25
CS_PIN = 8 CS_PIN = 8
BUSY_PIN = 24 BUSY_PIN = 24
# Commands # Commands
PANEL_SETTING = 0x00 PANEL_SETTING = 0x00
POWER_SETTING = 0x01 POWER_SETTING = 0x01
POWER_OFF = 0x02 POWER_OFF = 0x02
POWER_OFF_SEQUENCE_SETTING = 0x03 POWER_OFF_SEQUENCE_SETTING = 0x03
POWER_ON = 0x04 POWER_ON = 0x04
POWER_ON_MEASURE = 0x05 POWER_ON_MEASURE = 0x05
BOOSTER_SOFT_START = 0x06 BOOSTER_SOFT_START = 0x06
DEEP_SLEEP = 0x07 DEEP_SLEEP = 0x07
DATA_START_TRANSMISSION_1 = 0x10 DATA_START_TRANSMISSION_1 = 0x10
DATA_STOP = 0x11 DATA_STOP = 0x11
DISPLAY_REFRESH = 0x12 DISPLAY_REFRESH = 0x12
IMAGE_PROCESS = 0x13 IMAGE_PROCESS = 0x13
LUT_FOR_VCOM = 0x20 LUT_FOR_VCOM = 0x20
LUT_BLUE = 0x21 LUT_BLUE = 0x21
LUT_WHITE = 0x22 LUT_WHITE = 0x22
LUT_GRAY_1 = 0x23 LUT_GRAY_1 = 0x23
LUT_GRAY_2 = 0x24 LUT_GRAY_2 = 0x24
LUT_RED_0 = 0x25 LUT_RED_0 = 0x25
LUT_RED_1 = 0x26 LUT_RED_1 = 0x26
LUT_RED_2 = 0x27 LUT_RED_2 = 0x27
LUT_RED_3 = 0x28 LUT_RED_3 = 0x28
LUT_XON = 0x29 LUT_XON = 0x29
PLL_CONTROL = 0x30 PLL_CONTROL = 0x30
TEMPERATURE_SENSOR_COMMAND = 0x40 TEMPERATURE_SENSOR_COMMAND = 0x40
TEMPERATURE_CALIBRATION = 0x41 TEMPERATURE_CALIBRATION = 0x41
TEMPERATURE_SENSOR_WRITE = 0x42 TEMPERATURE_SENSOR_WRITE = 0x42
TEMPERATURE_SENSOR_READ = 0x43 TEMPERATURE_SENSOR_READ = 0x43
VCOM_AND_DATA_INTERVAL_SETTING = 0x50 VCOM_AND_DATA_INTERVAL_SETTING = 0x50
LOW_POWER_DETECTION = 0x51 LOW_POWER_DETECTION = 0x51
TCON_SETTING = 0x60 TCON_SETTING = 0x60
TCON_RESOLUTION = 0x61 TCON_RESOLUTION = 0x61
SPI_FLASH_CONTROL = 0x65 SPI_FLASH_CONTROL = 0x65
REVISION = 0x70 REVISION = 0x70
GET_STATUS = 0x71 GET_STATUS = 0x71
AUTO_MEASUREMENT_VCOM = 0x80 AUTO_MEASUREMENT_VCOM = 0x80
READ_VCOM_VALUE = 0x81 READ_VCOM_VALUE = 0x81
VCM_DC_SETTING = 0x82 VCM_DC_SETTING = 0x82
class EpdAdapter (DisplayAdapter):
class EpdAdapter (DisplayAdapter):
"""Generalized adapter for epd7in5 and epd7in5b""" """Generalized adapter for epd7in5 and epd7in5b"""
def __init__ (self, width, height):
def __init__(self, width, height):
super(EpdAdapter, self).__init__(width, height) super(EpdAdapter, self).__init__(width, height)
self.reset_pin = RST_PIN self.reset_pin = RST_PIN
@ -59,19 +61,19 @@ class EpdAdapter (DisplayAdapter):
self.epd_init() self.epd_init()
def display_frame (self, frame_buffer): def display_frame(self, frame_buffer):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def get_frame_buffer (self, image): def get_frame_buffer(self, image):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def render (self, design): def render(self, design):
self.init_render() self.init_render()
time.sleep(5) time.sleep(5)
print('Converting image to data and sending it to the display') print('Converting image to data and sending it to the display')
print('This may take a while...' + '\n') print('This may take a while...' + '\n')
prepared_image = design.get_image().rotate(270, expand=1) prepared_image = design.get_image().rotate(270, expand=1).convert("RGB")
self.display_frame(self.get_frame_buffer(prepared_image)) self.display_frame(self.get_frame_buffer(prepared_image))
# Powering off the E-Paper until the next loop # Powering off the E-Paper until the next loop
@ -79,7 +81,7 @@ class EpdAdapter (DisplayAdapter):
print('Data sent successfully') print('Data sent successfully')
print('Powering off the E-Paper until the next loop' + '\n') print('Powering off the E-Paper until the next loop' + '\n')
def init_render (self): def init_render(self):
if (self.epd_init() != 0): if (self.epd_init() != 0):
return -1 return -1
self.reset() self.reset()
@ -113,30 +115,30 @@ class EpdAdapter (DisplayAdapter):
self.send_data(0x22) self.send_data(0x22)
self.send_command(TCON_RESOLUTION) self.send_command(TCON_RESOLUTION)
self.send_data(0x02) #source 640 self.send_data(0x02) # source 640
self.send_data(0x80) self.send_data(0x80)
self.send_data(0x01) #gate 384 self.send_data(0x01) # gate 384
self.send_data(0x80) self.send_data(0x80)
self.send_command(VCM_DC_SETTING) self.send_command(VCM_DC_SETTING)
self.send_data(0x1E) #decide by LUT file self.send_data(0x1E) # decide by LUT file
self.send_command(0xe5) #FLASH MODE self.send_command(0xe5) # FLASH MODE
self.send_data(0x03) self.send_data(0x03)
def digital_write (self, pin, value): def digital_write(self, pin, value):
GPIO.output(pin, value) GPIO.output(pin, value)
def digital_read (self, pin): def digital_read(self, pin):
return GPIO.input(pin) return GPIO.input(pin)
def delay_ms (self, delaytime): def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0) time.sleep(delaytime / 1000.0)
def spi_transfer (self, data): def spi_transfer(self, data):
self.SPI.writebytes(data) self.SPI.writebytes(data)
def epd_init (self): def epd_init(self):
# SPI device, bus = 0, device = 0 # SPI device, bus = 0, device = 0
self.SPI = spidev.SpiDev(0, 0) self.SPI = spidev.SpiDev(0, 0)
#self.SPI.no_cs = True #self.SPI.no_cs = True
@ -151,30 +153,35 @@ class EpdAdapter (DisplayAdapter):
self.SPI.mode = 0b00 self.SPI.mode = 0b00
return 0 return 0
def sleep (self): def sleep(self):
self.send_command(POWER_OFF) self.send_command(POWER_OFF)
self.wait_until_idle() self.wait_until_idle()
self.send_command(DEEP_SLEEP) self.send_command(DEEP_SLEEP)
self.send_data(0xa5) self.send_data(0xa5)
def wait_until_idle (self):
while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle
self.delay_ms(100)
def reset (self): def wait_until_idle(self, max_wait_seconds=60):
wait_ms = 100
count = 0
while(self.digital_read(self.busy_pin) == 0 and wait_ms * count < max_wait_seconds * 1000): # 0: busy, 1: idle
self.delay_ms(wait_ms)
count += 1
if wait_ms * count >= max_wait_seconds * 1000:
print("Skipped idle confirmation")
def reset(self):
self.digital_write(self.reset_pin, GPIO.LOW) # module reset self.digital_write(self.reset_pin, GPIO.LOW) # module reset
self.delay_ms(200) self.delay_ms(200)
self.digital_write(self.reset_pin, GPIO.HIGH) self.digital_write(self.reset_pin, GPIO.HIGH)
self.delay_ms(200) self.delay_ms(200)
def send_command (self, command): def send_command(self, command):
self.digital_write(self.dc_pin, GPIO.LOW) self.digital_write(self.dc_pin, GPIO.LOW)
# the parameter type is list but not int # the parameter type is list but not int
# so use [command] instead of command # so use [command] instead of command
self.spi_transfer([ command ]) self.spi_transfer([command])
def send_data (self, data): def send_data(self, data):
self.digital_write(self.dc_pin, GPIO.HIGH) self.digital_write(self.dc_pin, GPIO.HIGH)
# the parameter type is list but not int # the parameter type is list but not int
# so use [data] instead of data # so use [data] instead of data
self.spi_transfer([ data ]) self.spi_transfer([data])

View file

@ -1,16 +1,22 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from TableTextDesign import TableTextDesign from TableDesign import TableDesign
from Assets import defaultfontsize, colors
from TextFormatter import date_str
from DictionaryMapper import get_text
from Dictionary import more_events
class EventListDesign (DesignEntity): class EventListDesign (DesignEntity):
"""Creates a TableTextDesign filled with event """Creates a TableDesign filled with event
begin date and title""" begin date and title"""
def __init__ (self, size, calendar, text_size = 16, filter_date=None, line_spacing=2, col_spacing=10, event_prefix_func=None, font_family=None, general_color="black", background_color="white", highlight_color="red"):
def __init__(self, size, events, text_size=defaultfontsize, line_spacing=0, col_spacing=10, event_prefix_rel_dates=[], event_prefix_func=None, font_family=None, general_color=colors["fg"], background_color=colors["bg"], highlight_color=colors["hl"], show_more_info=False):
super(EventListDesign, self).__init__(size) super(EventListDesign, self).__init__(size)
self.calendar = calendar self.events = events
self.__event_matrix__ = [] self.__event_matrix__ = []
self.__props_matrix__ = [] self.__props_matrix__ = []
self.show_more_info = show_more_info
self.text_size = text_size self.text_size = text_size
self.filter_date = filter_date
self.line_spacing = line_spacing self.line_spacing = line_spacing
self.col_spacing = col_spacing self.col_spacing = col_spacing
self.font_family = font_family self.font_family = font_family
@ -18,45 +24,51 @@ class EventListDesign (DesignEntity):
self.background_color = background_color self.background_color = background_color
self.highlight_color = highlight_color self.highlight_color = highlight_color
self.event_prefix_func = event_prefix_func self.event_prefix_func = event_prefix_func
self.event_prefix_rel_dates = event_prefix_rel_dates
if self.event_prefix_func is None: if self.event_prefix_func is None:
self.event_prefix_func = lambda x : self.__remove_leading_zero__(x.begin_datetime.strftime('%d %b')) self.event_prefix_func = lambda x, y: date_str(x.begin_datetime)
def __finish_image__ (self): def __finish_image__(self):
self.visible_event_count = int(int(
self.size[1] + self.line_spacing) // (self.line_spacing + int(self.text_size)))
self.__fill_event_matrix__() self.__fill_event_matrix__()
col_hori_alignment = ['right', 'left'] col_hori_alignment = ['right', 'left']
table_design = TableDesign(self.size, background_color=self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing,
table_design = TableTextDesign(self.size, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing, text_matrix=self.__event_matrix__, fontsize = self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__) matrix=self.__event_matrix__, fontsize=self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__)
self.draw_design(table_design) self.draw_design(table_design)
def __get_formatted_event__ (self, event):
prefix = self.event_prefix_func(event)
return [ prefix, event.title ]
def __remove_leading_zero__(self, text): def __get_formatted_event__(self, event, index):
while text[0] is '0': rel_date = None if index < 0 or index >= len(
text = text[1:] self.event_prefix_rel_dates) else self.event_prefix_rel_dates[index]
return text prefix = self.event_prefix_func(event, rel_date)
return [prefix, event.title]
def __fill_event_matrix__ (self):
for event in self.__get_events__(): def __fill_event_matrix__(self):
row = self.__get_formatted_event__(event) visible_events = self.events
if self.show_more_info and len(visible_events) > self.visible_event_count:
visible_events = visible_events[:self.visible_event_count - 1]
for i, event in enumerate(visible_events):
row = self.__get_formatted_event__(event, i)
self.__event_matrix__.append(row) self.__event_matrix__.append(row)
self.__props_matrix__.append(self.__get_row_props__(event)) self.__props_matrix__.append(self.__get_row_props__(event))
def __get_events__(self): if self.show_more_info is False:
upcoming = self.calendar.get_upcoming_events() return
if self.filter_date is not None:
upcoming = [event for event in upcoming if event.begin_datetime.date() == self.filter_date]
return upcoming
def __get_row_props__(self, event): additional_events_count = len(self.events) - len(visible_events)
more_text = " " + get_text(more_events, additional_events_count)
if additional_events_count > 0:
self.__event_matrix__.append(["", more_text])
self.__props_matrix__.append(self.__get_row_props__())
def __get_row_props__(self, event=None):
color = self.general_color color = self.general_color
bg_color = self.background_color bg_color = self.background_color
if event.highlight: if event is not None and event.highlight:
color = self.highlight_color color = self.highlight_color
cell = { cell = {
"color" : color, "color": color,
"background_color" : bg_color "background_color": bg_color
} }
return [cell, cell] return [cell, cell]

68
Calendar/GeckoCrypto.py Normal file
View file

@ -0,0 +1,68 @@
from CryptoInterface import CryptoInterface
from datetime import datetime
from CryptoCoin import CryptoCoin
from urllib.request import urlopen
import json
import math
api_test_url = "https://api.coingecko.com/api/v3/ping"
api_url = "https://api.coingecko.com/api/v3/"
api_metadata_url = api_url + "coins/list"
api_price_url = api_url + "simple/price"
price_currency = "usd"
price_currency_sign = "$"
class GeckoCrypto(CryptoInterface):
def __init__(self, coins):
self.coin_ids = coins
self.metadata = None
super(GeckoCrypto, self).__init__()
def is_available(self):
try:
urlopen(api_test_url)
return True
except:
return False
def __get_coins__(self):
if len(self.coin_ids) == 0:
return []
self.__prepare_metadata__()
coins = []
for id in self.coin_ids:
try:
data = urlopen(api_price_url + "?include_24hr_change=true&ids=" +
self.metadata[id]['id'] + "&vs_currencies=" + price_currency).read()
dataJSON = json.loads(data.decode('utf-8'))
raw = dataJSON[id][price_currency]
price = math.ceil(raw*100) / 100
change = dataJSON[id]['usd_24h_change']
coins.append(self.__build_coin__(id, price, change))
except:
print("Gecko-Error [" + id + "]")
return coins
def __build_coin__(self, id, value, change):
coin = CryptoCoin()
coin.name = self.metadata[id]['name']
coin.day_change = round(change, 2)
coin.price = value
coin.datetime = datetime.now()
coin.fetch_datetime = datetime.now()
coin.currency = price_currency_sign
coin.symbol = self.metadata[id]['symbol']
return coin
def __prepare_metadata__(self):
self.metadata = None
data = urlopen(api_metadata_url).read()
dataJSON = json.loads(data.decode('utf-8'))
self.metadata = {coin['id'].lower(
): coin for coin in dataJSON if coin['id'].lower() in self.coin_ids}

199
Calendar/HourListDesign.py Normal file
View file

@ -0,0 +1,199 @@
from DesignEntity import DesignEntity
from settings import hours, language, line_thickness
from TextDesign import TextDesign
from PIL import ImageDraw
from Assets import colors, defaultfontsize, fonts
from BoxDesign import BoxDesign
from datetime import timedelta, datetime
hourbox_y_width = 1
hour_box_fontsize = 0.85
hour_ypadding = 0.1
hoursubtext_fontsize = 0.7
hoursubtext_height = 0.45
event_title_fontsize = defaultfontsize
event_title_xpadding = 3
event_title_ypadding = 5
line_thickness = line_thickness
currenttimeline_thickness = line_thickness
event_title_font = fonts['bold']
class HourListDesign (DesignEntity):
"""Hours of a day are listed vertically and
resemble a timeline."""
def __init__(self, size, first_hour=0, last_hour=23):
super(HourListDesign, self).__init__(size)
self.first_hour = first_hour
self.last_hour = last_hour
self.__calc_parameters__()
self.events = []
def add_events(self, events):
self.events.extend(events)
self.events.sort(key=lambda x: x.begin_datetime)
def __finish_image__(self):
self.number_columns = self.__get_max_num_simultaneous_events__()
self.__draw_lines__()
self.__draw_events__()
self.__draw_current_time_line__()
self.__draw_hour_rows__()
def __calc_parameters__(self):
self.hour_count = self.last_hour - self.first_hour + 1
self.row_size = (self.size[0], self.size[1] / self.hour_count)
def __get_hour_text__(self, hour):
if hour <= 12 or hours is "24":
return str(hour)
else:
short = hour - 12
return str(short) if short > 0 else "12"
def __get_ypos_for_time__(self, hour, minute=0):
return self.__get_height_for_duration__(hour, minute) - self.__get_height_for_duration__(self.first_hour)
def __get_height_for_duration__(self, hours, minutes=0):
row_height = self.row_size[1]
return row_height * (hours + minutes / 60)
def __draw_events__(self):
column_events = []
for _ in range(self.number_columns):
column_events.append(None)
for event in self.events:
column_events = self.__update_columns_events__(
column_events, event)
self.__draw_event__(event, column_events.index(event))
def __update_columns_events__(self, column_events, new_event):
current_time = new_event.begin_datetime
new_event_added = False
for index in range(len(column_events)):
if column_events[index] != None and column_events[index].end_datetime <= current_time:
column_events[index] = None
if new_event_added == False and column_events[index] == None:
column_events[index] = new_event
new_event_added = True
return column_events
def __draw_hour_rows__(self):
for hour in range(self.first_hour, self.last_hour + 1):
self.__draw_row__(hour)
def __draw_row__(self, hour):
subtext_height = self.row_size[1] * hoursubtext_height
sub_fontsize = subtext_height * hoursubtext_fontsize
ypadding = hour_ypadding * self.row_size[1]
width = hourbox_y_width * self.row_size[1]
height = self.row_size[1] - subtext_height
size = (width, height)
pos = (0, self.__get_ypos_for_time__(hour) + ypadding)
fontsize = size[1] * hour_box_fontsize
txt = TextDesign(size, text=self.__get_hour_text__(
hour), fontsize=fontsize, verticalalignment="bottom", horizontalalignment="center")
txt.pos = pos
self.draw_design(txt)
sub = TextDesign((width, subtext_height), text=self.__get_hour_sub_text__(
hour), fontsize=sub_fontsize, verticalalignment="top", horizontalalignment="center")
sub.pos = (0, height + self.__get_ypos_for_time__(hour))
self.draw_design(sub)
def __draw_lines__(self):
for i in range(self.hour_count):
ypos = i * self.row_size[1]
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["fg"], width=line_thickness)
def __get_hour_sub_text__(self, hour):
if hours == "12":
return "AM" if hour < 12 else "PM"
elif language is "de":
return "Uhr"
elif language is "en":
return "o'c"
return ""
def __draw_event__(self, event, column=0):
xoffset = hourbox_y_width * self.row_size[1]
column_width = (self.size[0] - xoffset) / self.number_columns
begin = event.begin_datetime
time_ypos = self.__get_ypos_for_time__(begin.hour, begin.minute)
hours = event.duration.total_seconds() / 3600
time_height = self.__get_height_for_duration__(hours)
yoffset_correction = 0
if time_ypos < 0:
yoffset_correction = time_ypos
pos = (xoffset + column_width * column, time_ypos - yoffset_correction)
size = (column_width, time_height + yoffset_correction)
if size[1] < 0:
return # Event not in shown time range
self.__draw_event_block__(pos, size, event)
def __draw_event_block__(self, pos, size, event):
box_color = colors["hl"] if event.highlight else colors["fg"]
box = BoxDesign(size, fill=box_color)
box.mask = False
box.pos = pos
self.draw_design(box)
text = event.title
text_color = colors["bg"]
textbox_size = (size[0] - event_title_xpadding,
size[1] - event_title_ypadding)
txt = TextDesign(textbox_size, text=text, font=event_title_font,
fontsize=event_title_fontsize, color=text_color, background_color=box_color, wrap=True)
txt.mask = False
txt.pos = (pos[0] + event_title_xpadding,
pos[1] + event_title_ypadding)
self.draw_design(txt)
half_ypadding = int(event_title_ypadding / 2)
line_start = (pos[0] + event_title_xpadding, pos[1] + half_ypadding)
line_end = (pos[0] + size[0] - event_title_xpadding,
pos[1] + half_ypadding)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["bg"], width=1)
def __get_max_num_simultaneous_events__(self):
parallelity_count = 1
for index, event in enumerate(self.events):
current_parallelity = 1
# Assumption: Events are ordered chronologically
preceding = self.events[:index]
for pre_event in preceding:
if self.__are_simultaneous__(pre_event, event):
current_parallelity += 1
if parallelity_count < current_parallelity:
parallelity_count = current_parallelity
return parallelity_count
def __are_simultaneous__(self, ev_a, ev_b):
if ev_a.begin_datetime > ev_b.begin_datetime:
ev_a, ev_b = ev_b, ev_a
mes_dur = ev_b.begin_datetime - ev_a.begin_datetime
return mes_dur < ev_a.duration
def __draw_current_time_line__(self):
now = datetime.now()
ypos = self.__get_ypos_for_time__(now.hour, now.minute)
line_start = (0, ypos)
line_end = (self.size[0], ypos)
ImageDraw.Draw(self.__image__).line(
[line_start, line_end], fill=colors["hl"], width=currenttimeline_thickness)

View file

@ -2,19 +2,28 @@ from CalendarInterface import CalendarInterface
from CalendarEvent import CalendarEvent from CalendarEvent import CalendarEvent
from ics import Calendar from ics import Calendar
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import re
from settings import week_starts_on from settings import week_starts_on
try: from urllib.request import urlopen
from urllib.request import urlopen
except Exception as e:
print("Something didn't work right, maybe you're offline?" + e.reason)
class IcalEvents(CalendarInterface): class IcalEvents(CalendarInterface):
"""Fetches events from ical addresses.""" """Fetches events from ical addresses."""
def __init__(self, urls, highlighted_urls=None): def __init__(self, urls, highlighted_urls=None):
self.urls = urls self.urls = urls
self.highlighted_urls = highlighted_urls self.highlighted_urls = highlighted_urls
super(IcalEvents, self).__init__() super(IcalEvents, self).__init__()
def is_available(self):
for url in self.urls + self.highlighted_urls:
try:
urlopen(url)
return True
except:
pass
return False
def __get_events__(self): def __get_events__(self):
events = self.__get_events_from_urls__(self.urls) events = self.__get_events_from_urls__(self.urls)
@ -30,19 +39,21 @@ class IcalEvents(CalendarInterface):
def __get_events_from_urls__(self, urls): def __get_events_from_urls__(self, urls):
loaded_events = [] loaded_events = []
try:
if urls is None:
return loaded_events
for calendar in urls: if urls is None:
return loaded_events
for calendar in urls:
try:
decode = str(urlopen(calendar).read().decode()) decode = str(urlopen(calendar).read().decode())
fixed_decode = self.__fix_errors__(decode) decode = self.__remove_alarms__(decode)
ical = Calendar(fixed_decode) ical = Calendar(decode)
for event in ical.events: for event in ical.events:
cal_event = CalendarEvent() cal_event = CalendarEvent()
cal_event.calendar_url = calendar
cal_event.fetch_datetime = datetime.now() cal_event.fetch_datetime = datetime.now(timezone.utc)
cal_event.begin_datetime = event.begin.datetime cal_event.begin_datetime = event.begin.datetime
cal_event.end_datetime = event.end.datetime cal_event.end_datetime = event.end.datetime
cal_event.duration = event.duration cal_event.duration = event.duration
@ -50,16 +61,36 @@ class IcalEvents(CalendarInterface):
cal_event.description = event.description cal_event.description = event.description
cal_event.location = event.location cal_event.location = event.location
cal_event.allday = event.all_day cal_event.allday = event.all_day
cal_event.rrule = self.__extract_rrule__(event)
cal_event.begin_datetime = cal_event.begin_datetime.astimezone(
None)
cal_event.end_datetime = cal_event.end_datetime.astimezone(
None)
if cal_event.allday:
cal_event = self.__fix_allday__(cal_event)
cal_event.multiday = self.__is_multiday__(cal_event)
loaded_events.append(cal_event) loaded_events.append(cal_event)
return loaded_events except BaseException as ex:
except: print("ICal-Error [" + calendar + "]")
return loaded_events print(ex)
return loaded_events
def __fix_errors__(self, decode): def __fix_allday__(self, event):
decode = self.__remove_alarms__(decode) local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
return decode.replace('BEGIN:VALARM\r\nACTION:NONE','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:') \ begin_utc = event.begin_datetime.astimezone(timezone.utc)
.replace('BEGIN:VALARM\r\nACTION:EMAIL','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:') end_utc = event.end_datetime.astimezone(timezone.utc)
event.begin_datetime = datetime(
begin_utc.year, begin_utc.month, begin_utc.day, 0, 0, 0, 0, local_tzinfo)
event.end_datetime = datetime(
end_utc.year, end_utc.month, end_utc.day, 0, 0, 0, 0, local_tzinfo) - timedelta(1)
event.duration = event.end_datetime - event.begin_datetime
return event
def __remove_alarms__(self, decode): def __remove_alarms__(self, decode):
alarm_begin = 'BEGIN:VALARM' alarm_begin = 'BEGIN:VALARM'
@ -71,5 +102,21 @@ class IcalEvents(CalendarInterface):
beginAlarmIndex = decode.find(alarm_begin) beginAlarmIndex = decode.find(alarm_begin)
if beginAlarmIndex >= 0: if beginAlarmIndex >= 0:
endAlarmIndex = decode.find(alarm_end, beginAlarmIndex) endAlarmIndex = decode.find(alarm_end, beginAlarmIndex)
decode = decode[:beginAlarmIndex] + decode[endAlarmIndex + len(alarm_end) + len(lineseparation):] decode = decode[:beginAlarmIndex] + \
return decode decode[endAlarmIndex +
len(alarm_end) + len(lineseparation):]
return decode
def __extract_rrule__(self, event):
if re.search('RRULE', str(event)) is None:
return None
return re.search('RRULE:(.+?)\n', str(event)).group(1).rstrip()
def __is_multiday__(self, event):
if event.allday and event.duration == timedelta(1):
return False
return event.begin_datetime.day != event.end_datetime.day or \
event.begin_datetime.month != event.end_datetime.month or \
event.begin_datetime.year != event.end_datetime.year

86
Calendar/ImageDesign.py Normal file
View file

@ -0,0 +1,86 @@
from DesignEntity import DesignEntity
from Assets import path as application_path
from PIL import Image, ExifTags
class ImageDesign (DesignEntity):
"""Creates a TableDesign filled with rss post
date and title"""
# fill: "none" : original size, "stretch" : strech to fill, "scale" : scale to fill, "border" : scale until one side touches border
def __init__(self, size, path, fill="none", color="RGBA", dither=None):
super(ImageDesign, self).__init__(size)
self.set_path(path)
self.fill = fill
self.color = color
self.dither = dither
def set_path(self, path):
path = path.replace('\\', '/')
if path[0] != '/' and ':' not in path[0:3]:
path = application_path + '/' + path
self.path = path
def __finish_image__(self):
img = Image.open(self.path)
img = img.convert(self.color, dither=self.dither)
img = self.__fix_orientation__(img)
img = self.__resize_image__(img)
pos = self.__get_centered_position__(img)
self.__init_image__("#00000000")
self.draw(img, pos)
def __resize_image__(self, img):
if self.fill is "none":
return img
if self.fill is "stretch":
img = img.resize(self.size, resample=Image.LANCZOS)
if self.fill is "scale":
size = self.size
img_proportions = img.width / img.height
if img_proportions < size[0] / size[1]:
size = (size[0], int(size[0] * (1 / img_proportions)))
else:
size = (int(size[1] * img_proportions), size[1])
img = img.resize(size, resample=Image.LANCZOS)
if self.fill is "border":
size = self.size
img_proportions = img.width / img.height
if img_proportions < size[0] / size[1]:
size = (int(size[1] * img_proportions), size[1])
else:
size = (size[0], int(size[0] * (1 / img_proportions)))
img = img.resize(size, resample=Image.LANCZOS)
return img
def __get_centered_position__(self, img):
screen_size = self.size
img_size = img.size
delta_size = [s - i for s, i in zip(screen_size, img_size)]
delta_center_pos = [s / 2 for s in delta_size]
return delta_center_pos
def __fix_orientation__(self, img):
if "parsed_exif" not in img.info.keys():
return img
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = img.info["parsed_exif"]
if exif[orientation] == 3:
img = img.rotate(180, expand=True)
elif exif[orientation] == 6:
img = img.rotate(270, expand=True)
elif exif[orientation] == 8:
img = img.rotate(90, expand=True)
return img

View file

@ -1,13 +1,18 @@
from DisplayAdapter import DisplayAdapter from DisplayAdapter import DisplayAdapter
from Assets import path
class ImageFileAdapter (DisplayAdapter): class ImageFileAdapter (DisplayAdapter):
"""Saves design to an image file, can be used for debugging""" """Saves design to an image file, can be used for debugging"""
def __init__ (self, file_path = ""):
def __init__(self, file_path=""):
super(ImageFileAdapter, self).__init__(384, 640) super(ImageFileAdapter, self).__init__(384, 640)
self.file_path = file_path self.file_path = file_path
if self.file_path == "":
self.file_path = path
def render (self, design): def render(self, design):
design.get_image().save(self.file_path + 'design_exported.png') design.get_image().save(self.file_path + 'design_exported.png')
def calibrate (self): def calibrate(self):
pass pass

View file

@ -0,0 +1,46 @@
from PanelDesign import PanelDesign
from settings import general_settings
from Assets import supported_img_formats, path as application_path
from os import listdir
from os.path import isfile, join
from ImageDesign import ImageDesign
from random import choice
class ImageFramePanel (PanelDesign):
"""Converts the display into a digital frame and
shows a slide show of images, iterating on each update"""
def __init__(self, size):
super(ImageFramePanel, self).__init__(size)
self.overlay_path = self.__complete_path__(
general_settings["overlay-image"])
self.image_folder_path = self.__complete_path__(
general_settings["image-folder"])
self.images = self.__extract_valid_img_paths__()
self.__first_render__()
def __extract_valid_img_paths__(self):
images = []
for file in listdir(self.image_folder_path):
file_path = join(self.image_folder_path, file).replace('\\', '/')
if isfile(file_path) and self.overlay_path != file_path:
if file.split('.')[-1].upper() in supported_img_formats:
images.append(file_path)
return images
def __complete_path__(self, path):
path = path.replace('\\', '/')
if path[0] != '/' and ':' not in path[0:3]:
path = join(application_path, path)
return path
def __first_render__(self):
current_image = choice(self.images)
img = ImageDesign(self.size, current_image, fill="scale", color="1")
self.draw_design(img)
if self.overlay_path != "":
overlay = ImageDesign(self.size, self.overlay_path)
overlay.__finish_image__()
self.__image__.alpha_composite(overlay.__image__)

View file

@ -1,22 +1,33 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
class LoopTimer(object): min_sleep_minutes = 0
max_history_entries = 25
class LoopTimer (object):
"""Manages loop times and sleeps until """Manages loop times and sleeps until
next loop.""" next loop."""
def __init__(self, loop_interval, run_on_hour=False):
self.interval = loop_interval def __init__(self, loop_interval, run_on_hour=False, max_loop_count=0):
self.interval = int(str(loop_interval))
self.on_hour = run_on_hour self.on_hour = run_on_hour
self.loop_history = [] self.loop_history = []
self.loop_count = 0
self.max_loop_count = int(str(max_loop_count))
def begin_loop(self): def begin_loop(self):
begin_time = datetime.now() begin_time = datetime.now()
print('\n_________Starting new loop___________') print('\n__________Starting new loop [' + str(self.loop_count) + ']__________')
print('Date: ' + begin_time.strftime('%a %d %b %y') + ', time: ' + begin_time.strftime('%H:%M') + '\n') print('Datetime: ' + str(begin_time) + '\n')
self.__add_beginning__(begin_time) self.__add_beginning__(begin_time)
def __add_beginning__(self, time): def __add_beginning__(self, time):
self.loop_history.append((time,)) self.loop_history.append((time,))
if len(self.loop_history) > max_history_entries:
dif = len(self.loop_history) - max_history_entries
self.loop_history = self.loop_history[dif:]
def __add_ending__(self, time): def __add_ending__(self, time):
current = self.get_current() current = self.get_current()
self.loop_history[-1] = (current[0], time) self.loop_history[-1] = (current[0], time)
@ -24,6 +35,15 @@ class LoopTimer(object):
def end_loop(self): def end_loop(self):
end_time = datetime.now() end_time = datetime.now()
self.__add_ending__(end_time) self.__add_ending__(end_time)
self.loop_count += 1
while self.loop_count > 86400:
self.loop_count -= 86400
def was_last_loop(self):
if self.max_loop_count == 0:
return False
return self.max_loop_count <= self.loop_count
def get_current(self): def get_current(self):
return self.loop_history[-1] return self.loop_history[-1]
@ -36,6 +56,9 @@ class LoopTimer(object):
time_until_hour = self.get_time_to_next_hour() time_until_hour = self.get_time_to_next_hour()
if time_until_hour < sleep_time: if time_until_hour < sleep_time:
return time_until_hour return time_until_hour
if sleep_time < timedelta(0):
sleep_time = timedelta(0, 0, 0, 0, min_sleep_minutes)
return sleep_time return sleep_time
def get_last_duration(self): def get_last_duration(self):
@ -48,7 +71,7 @@ class LoopTimer(object):
cur = datetime.now() cur = datetime.now()
rounded = datetime(cur.year, cur.month, cur.day, cur.hour) rounded = datetime(cur.year, cur.month, cur.day, cur.hour)
next_hour_time = rounded + timedelta(hours=1) next_hour_time = rounded + timedelta(hours=1)
return next_hour_time - datetime.now() return next_hour_time - datetime.now()
def is_new_hour_loop(self): def is_new_hour_loop(self):
if len(self.loop_history) < 2: if len(self.loop_history) < 2:
@ -59,4 +82,4 @@ class LoopTimer(object):
if previous_loop[0].hour != current_loop[0].hour: if previous_loop[0].hour != current_loop[0].hour:
return True return True
else: else:
return False return False

View file

@ -2,18 +2,21 @@ from DesignEntity import DesignEntity
import calendar as callib import calendar as callib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from TextDesign import TextDesign from TextDesign import TextDesign
from Assets import * from Assets import colors
from settings import * from settings import week_starts_on
from BoxDesign import BoxDesign from BoxDesign import BoxDesign
daynumberboxsize = (0.143, 0.286) daynumberboxsize = (0.143, 0.2)
dayhighlightboxsize = (0.143, 0.14) dayhighlightboxsize = (0.143, 0.14)
daynumbersize = 25 daynumbersize = daynumberboxsize[0] * 0.45
day_number_ypadding = -0.002
class MonthBlockDesign (DesignEntity): class MonthBlockDesign (DesignEntity):
"""Creates a view containing one week of the month in """Creates a view containing one week of the month in
one row""" one row"""
def __init__(self, size, datetime_month, highlight_today = False):
def __init__(self, size, datetime_month, highlight_today=False):
super(MonthBlockDesign, self).__init__(size, mask=True) super(MonthBlockDesign, self).__init__(size, mask=True)
self.month = datetime_month.month self.month = datetime_month.month
self.year = datetime_month.year self.year = datetime_month.year
@ -23,49 +26,53 @@ class MonthBlockDesign (DesignEntity):
def __finish_image__(self): def __finish_image__(self):
self.__draw_month_overview__() self.__draw_month_overview__()
def __draw_month_overview__ (self): def __draw_month_overview__(self):
"""Using the built-in calendar function, draw icons for each """Using the built-in calendar function, draw icons for each
number of the month (1,2,3,...29,30,31)""" number of the month (1,2,3,...29,30,31)"""
cal = callib.monthcalendar(self.year, self.month) cal = callib.monthcalendar(self.year, self.month)
for week in cal: for week in cal:
for numbers in week: for numbers in week:
self.__draw_day_number__(numbers, self.get_day_pos(cal.index(week), week.index(numbers))) self.__draw_day_number__(numbers, self.get_day_pos(
cal.index(week), week.index(numbers)))
if self.highlight_today:
self.__draw_highlight_box__(self.__abs_pos__(dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
def __draw_highlight_box__ (self, size, pos, color='black', width=1): if self.highlight_today:
design = BoxDesign(size, outline=color, width = width) self.__draw_highlight_box__(self.__abs_pos__(
dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
def __draw_highlight_box__(self, size, pos, color=colors["fg"], width=1):
design = BoxDesign(size, outline=color, width=width)
design.pos = pos design.pos = pos
self.draw_design(design) self.draw_design(design)
def __draw_day_number__ (self, number, pos): def __draw_day_number__(self, number, pos):
if number <= 0: if number <= 0:
return return
txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize, text=str(number), verticalalignment="center", horizontalalignment="center") txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize *
txt.pos = pos self.size[0], text=str(number), verticalalignment="center", horizontalalignment="center")
txt.pos = (pos[0], pos[1] + day_number_ypadding * self.size[1])
self.draw_design(txt) self.draw_design(txt)
def get_day_pos (self, week_in_month, day_of_week, rel_pos=(0,0)): def get_day_pos(self, week_in_month, day_of_week, rel_pos=(0, 0)):
maxwidth, maxheight = self.size maxwidth, maxheight = self.size
partialwidth = maxwidth / 7 partialwidth = maxwidth / 7
partialheight = maxheight / 5 partialheight = maxheight / 5
return (int(rel_pos[0] + day_of_week * partialwidth), int(rel_pos[1] + week_in_month * partialheight)) return (int(rel_pos[0] + day_of_week * partialwidth), int(rel_pos[1] + week_in_month * partialheight))
def __get_today_box_pos__ (self): def __get_today_box_pos__(self):
x, y = self.get_day_pos(self.__get_week_of_month__(datetime.now()), self.__get_day_of_week__(datetime.now())) x, y = self.get_day_pos(self.__get_week_of_month__(
datetime.now()), self.__get_day_of_week__(datetime.now()))
return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2)) return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2))
def __get_week_of_month__ (self, date): def __get_week_of_month__(self, date):
for wof, week in enumerate(callib.monthcalendar(date.year, date.month)): for wof, week in enumerate(callib.monthcalendar(date.year, date.month)):
if date.day in week: if date.day in week:
return wof return wof
return 0 return 0
def __get_day_of_week__ (self, date): def __get_day_of_week__(self, date):
return self.__week_days__.index(date.strftime("%a")) return self.__week_days__.index(date.strftime("%a"))
def __get_week_days_ordered__ (self): def __get_week_days_ordered__(self):
cur_weekday = datetime.now().weekday() cur_weekday = datetime.now().weekday()
correction = -cur_weekday correction = -cur_weekday
if week_starts_on == "Sunday": if week_starts_on == "Sunday":
@ -73,17 +80,18 @@ class MonthBlockDesign (DesignEntity):
weekdays = [] weekdays = []
for i in range(7): for i in range(7):
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a")) weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays return weekdays
def __abs_pos__ (self, pos, size = None): def __abs_pos__(self, pos, size=None):
if size is None: if size is None:
size = self.size size = self.size
return (int(pos[0] * size[0]), int(pos[1] * size[1])) return (int(pos[0] * size[0]), int(pos[1] * size[1]))
def get_real_height (self): def get_real_height(self):
weeks_in_month = callib.monthcalendar(self.year, self.month) weeks_in_month = callib.monthcalendar(self.year, self.month)
num_size = self.__abs_pos__(daynumberboxsize) num_size = self.__abs_pos__(daynumberboxsize)
num_pos = self.get_day_pos(len(weeks_in_month) - 1, 6) num_pos = self.get_day_pos(len(weeks_in_month) - 1, 6)
return num_size[1] + num_pos[1] return num_size[1] + num_pos[1]

View file

@ -1,6 +1,6 @@
from PanelDesign import PanelDesign from PanelDesign import PanelDesign
from Assets import * from Assets import colors
from settings import * from settings import general_settings, week_starts_on
import calendar as callib import calendar as callib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from WeatherHeaderDesign import WeatherHeaderDesign from WeatherHeaderDesign import WeatherHeaderDesign
@ -11,31 +11,37 @@ from EllipseDesign import EllipseDesign
from MonthBlockDesign import MonthBlockDesign, daynumberboxsize from MonthBlockDesign import MonthBlockDesign, daynumberboxsize
from EventListDesign import EventListDesign from EventListDesign import EventListDesign
from RssPostListDesign import RssPostListDesign from RssPostListDesign import RssPostListDesign
from settings import general_settings
from CryptoListDesign import CryptoListDesign
monthtextsize = 40
monthovsize = (1, 0.5) weatherheadersize = (1, 0.113)
monthovposition = (0, 0.23)
weatherheadersize = (1,0.113)
seperatorplace = (0, 0.113)
monthplace = (0, 0.12)
monthboxsize = (1, 0.085) monthboxsize = (1, 0.085)
weekdayrowpos = (0, 0.209) monthtextsize = monthboxsize[1] * 0.75
monthplace = (0, 0.11 - weatherheadersize[1])
monthovsize = (1, 0.48)
monthovposition = (0, 0.25 - weatherheadersize[1])
seperatorplace = (0, 0.113)
weekdayrowpos = (0, 0.209 - weatherheadersize[1])
weekrowboxsize = (1, 0.044) weekrowboxsize = (1, 0.044)
weekdaytextsize = 18 weekdaytextsize = 0.7 * weekrowboxsize[1]
weekdaytextpadding = -0.001
weekrownameboxsize = (0.143, 0.044) weekrownameboxsize = (0.143, 0.044)
eventcirclehorizontalsize = 0.100 eventcirclehorizontalsize = 0.100
infolisthorizontalpos = 0.008
infolistsize = (1, 0.77)
infolisttextsizeize = 16
class MonthOvPanel (PanelDesign): class MonthOvPanel (PanelDesign):
"""Overview that focuses on the current month and """Overview that focuses on the current month and
some additional information in the bottom.""" some additional information in the bottom."""
def __init__ (self, size):
def __init__(self, size):
super(MonthOvPanel, self).__init__(size) super(MonthOvPanel, self).__init__(size)
self.weather_header_height = 0
if general_settings["weather-info"]:
self.weather_header_height = self.size[1] * weatherheadersize[1]
self.__first_render__() self.__first_render__()
def __first_render__ (self): def __first_render__(self):
if week_starts_on == "Monday": if week_starts_on == "Monday":
callib.setfirstweekday(callib.MONDAY) callib.setfirstweekday(callib.MONDAY)
elif week_starts_on == "Sunday": elif week_starts_on == "Sunday":
@ -43,99 +49,140 @@ class MonthOvPanel (PanelDesign):
self.__week_days__ = self.__get_week_days_ordered__() self.__week_days__ = self.__get_week_days_ordered__()
self.__draw_month_name__() self.__draw_month_name__()
self.__draw_seperator__()
self.__draw_week_row__() self.__draw_week_row__()
self.month_block = MonthBlockDesign(self.__abs_pos__(monthovsize), datetime.now(), highlight_today = True) if general_settings["weather-info"]:
self.month_block.pos = self.__abs_pos__(monthovposition) self.__draw_seperator__()
self.month_block = MonthBlockDesign(self.__abs_pos__(
monthovsize), datetime.now(), highlight_today=True)
pos = self.__abs_pos__(monthovposition)
pos = (pos[0], pos[1] + self.weather_header_height)
self.month_block.pos = pos
self.draw_design(self.month_block) self.draw_design(self.month_block)
def add_weather (self, weather): def add_weather(self, weather):
self.draw_design(WeatherHeaderDesign(self.__abs_pos__(weatherheadersize), weather)) if general_settings["weather-info"] == False:
return
self.draw_design(WeatherHeaderDesign(
self.__abs_pos__(weatherheadersize), weather))
def add_rssfeed (self, rss): def add_rssfeed(self, rss):
if general_settings["info-area"] is "rss": if general_settings["info-area"] is "rss":
self.__draw_rss_post_list_to_bottom__(rss) self.__draw_rss_post_list_to_bottom__(rss)
def add_calendar (self, calendar): def add_crypto(self, crypto):
if general_settings["info-area"] is "crypto":
self.__draw_crypto_post_list_to_bottom__(crypto)
def add_tasks(self, tasks):
pass
def add_calendar(self, calendar):
if general_settings["highlight-event-days"]: if general_settings["highlight-event-days"]:
month_events = list(set([ (event.begin_datetime.day, event.begin_datetime.month, event.begin_datetime.year) for event in calendar.get_month_events()])) month_events = list(set([(event.begin_datetime.day, event.begin_datetime.month,
event.begin_datetime.year) for event in calendar.get_month_events()]))
for event in month_events: for event in month_events:
self.__draw_highlight_event_day__(event) self.__draw_highlight_event_day__(event)
if general_settings["info-area"] is "events": if general_settings["info-area"] is "events":
self.__draw_event_list_to_bottom__(calendar) self.__draw_event_list_to_bottom__(calendar)
def __draw_rss_post_list_to_bottom__ (self, rss): def __draw_rss_post_list_to_bottom__(self, rss):
month_pos = self.__abs_pos__(monthovposition) month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height() month_height = self.month_block.get_real_height()
size = self.__abs_pos__(infolistsize) size = (self.size[0], self.size[1] - (month_pos[1] +
size = (size[0], size[1] - month_height) month_height + self.weather_header_height))
info_list = RssPostListDesign(size, rss, text_size=infolisttextsizeize) info_list = RssPostListDesign(size, rss)
info_list.pos = (int(month_pos[0] + infolisthorizontalpos * self.size[0]), int(month_pos[1] + month_height)) info_list.pos = (
int(month_pos[0]), month_pos[1] + month_height + self.weather_header_height)
self.draw_design(info_list) self.draw_design(info_list)
def __draw_event_list_to_bottom__ (self, calendar): def __draw_crypto_post_list_to_bottom__(self, crypto):
month_pos = self.__abs_pos__(monthovposition) month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height() month_height = self.month_block.get_real_height()
size = self.__abs_pos__(infolistsize) size = (self.size[0], self.size[1] - (month_pos[1] +
size = (size[0], size[1] - month_height) month_height + self.weather_header_height))
info_list = EventListDesign(size, calendar, text_size=infolisttextsizeize)
info_list.pos = (int(month_pos[0] + infolisthorizontalpos * self.size[0]), int(month_pos[1] + month_height)) info_list = CryptoListDesign(size, crypto)
list_height = info_list.get_estimated_height()
info_list.pos = (int(month_pos[0]), month_pos[1] + month_height +
self.weather_header_height + (size[1] - list_height))
self.draw_design(info_list) self.draw_design(info_list)
def __draw_highlight_event_day__ (self, date): def __draw_event_list_to_bottom__(self, calendar):
month_pos = self.__abs_pos__(monthovposition)
month_height = self.month_block.get_real_height()
size = (self.size[0], self.size[1] - (month_pos[1] +
month_height + self.weather_header_height))
events = calendar.get_upcoming_events()
info_list = EventListDesign(size, events)
info_list.pos = (int(month_pos[0]), int(
month_pos[1] + month_height + self.weather_header_height))
self.draw_design(info_list)
def __draw_highlight_event_day__(self, date):
first_month_week = datetime(date[2], date[1], 1).isocalendar()[1] first_month_week = datetime(date[2], date[1], 1).isocalendar()[1]
cur_date = datetime(date[2], date[1], date[0]) cur_date = datetime(date[2], date[1], date[0])
side_length = int(eventcirclehorizontalsize * self.size[0]) side_length = int(eventcirclehorizontalsize * self.size[0])
circle_size = (side_length,side_length) circle_size = (side_length, side_length)
pos = self.month_block.get_day_pos(cur_date.isocalendar()[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos = self.__abs_pos__(monthovposition)) pos = self.month_block.get_day_pos(cur_date.isocalendar(
place_size = (self.month_block.size[0] * daynumberboxsize[0], self.month_block.size[1] * daynumberboxsize[1]) )[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos=self.month_block.pos)
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2), int(pos[1] + (place_size[1] - circle_size[1]) / 2)) place_size = (self.month_block.size[0] * daynumberboxsize[0],
self.month_block.size[1] * daynumberboxsize[1])
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2),
int(pos[1] + (place_size[1] - circle_size[1]) / 2))
self.__draw_highlight_circle__(circle_size, pos, 'red', width=2) self.__draw_highlight_circle__(circle_size, pos, 'red', width=2)
def __abs_pos__ (self, pos, size = None): def __abs_pos__(self, pos, size=None):
if size is None: if size is None:
size = self.size size = self.size
return (int(pos[0] * size[0]), int(pos[1] * size[1])) return (int(pos[0] * size[0]), int(pos[1] * size[1]))
def __draw_seperator__ (self): def __draw_seperator__(self):
"""Draw a line seperating the weather and Calendar section""" """Draw a line seperating the weather and Calendar section"""
ImageDraw.Draw(self.__image__).line([ self.__abs_pos__(seperatorplace), self.__abs_pos__((1, seperatorplace[1])) ], fill='red', width=5) ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
seperatorplace), self.__abs_pos__((1, seperatorplace[1]))], fill='red', width=5)
def __draw_month_name__ (self): def __draw_month_name__(self):
"""Draw the icon with the current month's name""" """Draw the icon with the current month's name"""
month = datetime.now().strftime("%B") month = datetime.now().strftime("%B")
txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize, text=month, verticalalignment="center", horizontalalignment="center") txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize *
txt.pos = self.__abs_pos__(monthplace) self.size[1], text=month, verticalalignment="center", horizontalalignment="center")
pos = self.__abs_pos__(monthplace)
txt.pos = (pos[0], pos[1] + self.weather_header_height)
self.draw_design(txt) self.draw_design(txt)
def __draw_week_row__ (self): def __draw_week_row__(self):
for day_of_week, day in enumerate(self.__week_days__): for day_of_week, day in enumerate(self.__week_days__):
txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize, text=str(day), verticalalignment="center", horizontalalignment="center") txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize *
txt.pos = self.__get_week_day_pos__(day_of_week) self.size[1], text=str(day), verticalalignment="center", horizontalalignment="center")
pos = self.__get_week_day_pos__(day_of_week)
txt.pos = (pos[0], pos[1] + weekdaytextpadding * self.size[1])
self.draw_design(txt) self.draw_design(txt)
self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(self.__get_day_of_week__(datetime.now())), width=1)
def __get_week_day_pos__ (self, day_of_week): self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(
self.__get_day_of_week__(datetime.now())), width=1)
def __get_week_day_pos__(self, day_of_week):
maxwidth, _ = self.__abs_pos__(monthovsize) maxwidth, _ = self.__abs_pos__(monthovsize)
partialwidth = maxwidth / 7 partialwidth = maxwidth / 7
posx, posy = self.__abs_pos__(weekdayrowpos) posx, posy = self.__abs_pos__(weekdayrowpos)
return (int(posx + day_of_week * partialwidth), int(posy)) return (int(posx + day_of_week * partialwidth), int(posy + self.weather_header_height))
def __draw_highlight_box__ (self, size, pos, color = 'black', width = 1): def __draw_highlight_box__(self, size, pos, color=colors["fg"], width=1):
design = BoxDesign(size, outline=color, width = width) design = BoxDesign(size, outline=color, width=width)
design.pos = pos design.pos = pos
self.draw_design(design) self.draw_design(design)
def __draw_highlight_circle__ (self, size, pos, color = 'black', width = 1): def __draw_highlight_circle__(self, size, pos, color=colors["fg"], width=1):
design = EllipseDesign(size, outline=color, width = width) design = EllipseDesign(size, outline=color, width=width)
design.pos = pos design.pos = pos
self.draw_design(design) self.draw_design(design)
def __get_week_days_ordered__ (self): def __get_week_days_ordered__(self):
cur_weekday = datetime.now().weekday() cur_weekday = datetime.now().weekday()
correction = -cur_weekday correction = -cur_weekday
if week_starts_on == "Sunday": if week_starts_on == "Sunday":
@ -143,9 +190,10 @@ class MonthOvPanel (PanelDesign):
weekdays = [] weekdays = []
for i in range(7): for i in range(7):
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a")) weekdays.append(
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
return weekdays return weekdays
def __get_day_of_week__ (self, date): def __get_day_of_week__(self, date):
return self.__week_days__.index(date.strftime("%a")) return self.__week_days__.index(date.strftime("%a"))

133
Calendar/MonthViewPanel.py Normal file
View file

@ -0,0 +1,133 @@
from PanelDesign import PanelDesign
from settings import general_settings, week_starts_on, line_thickness
from PIL import ImageDraw
from datetime import date
from Assets import colors
import calendar as callib
from TableDesign import TableDesign
from DayBoxDesign import DayBoxDesign
from RssPostListDesign import RssPostListDesign
from WeatherHeaderDesign import WeatherHeaderDesign
from CryptoListDesign import CryptoListDesign
weather_height = 0.113
info_height = 0.25
info_padding = 5
seperator_width = line_thickness
class MonthViewPanel (PanelDesign):
"""Displays a grid of the day of the current month
with detailed event descriptions."""
def __init__(self, size, month=None, year=None):
super(MonthViewPanel, self).__init__(size)
self.day_table = []
self.month = month
if self.month == None:
self.month = date.today().month
self.year = year
if self.year == None:
self.year = date.today().year
self.__init_sizes__()
self.__init_day_boxes__()
def __init_sizes__(self):
self.weather_height = 0
self.info_height = 0
if general_settings["info-area"] in ["events", "rss", "crypto"]:
self.info_height = info_height
if general_settings["weather-info"]:
self.weather_height = weather_height
self.day_area_height = 1 - self.weather_height - self.info_height
self.day_area_ypos = self.weather_height
self.week_count = self.__get_week_count__()
area_height = self.size[1] * self.day_area_height
area_width = self.size[0]
self.day_box_size = (area_width / 7, area_height / self.week_count)
def add_weather(self, weather):
if general_settings["weather-info"] == False:
return
size = (self.size[0], self.size[1] * self.weather_height)
header = WeatherHeaderDesign(size, weather)
self.draw_design(header)
self.__draw_seperator__(size[1], colors["hl"])
def add_calendar(self, calendar):
self.__add_calendar_to_days__(calendar)
def __add_calendar_to_days__(self, calendar):
for week in self.day_table:
for day in week:
if day != None:
day.add_calendar(calendar)
def add_rssfeed(self, rss):
if general_settings["info-area"] != "rss":
return
size = (self.size[0], self.size[1] * self.info_height)
pos = (0, self.size[1] - size[1] + info_padding)
rss = RssPostListDesign(size, rss)
rss.pos = pos
self.draw_design(rss)
def add_tasks(self, tasks):
pass
def add_crypto(self, crypto):
if general_settings["info-area"] == "crypto":
self.__draw_crypto__(crypto)
def __draw_crypto__(self, crypto):
size = (self.size[0], self.size[1] * self.info_height)
pos = (0, self.size[1] - size[1])
crypto = CryptoListDesign(size, crypto)
crypto.pos = (pos[0], pos[1] + (size[1] -
crypto.get_estimated_height()))
self.draw_design(crypto)
def __finish_panel__(self):
self.__draw_days__()
def __draw_days__(self):
size = (self.size[0], self.size[1] * self.day_area_height)
pos = (0, self.size[1] * self.day_area_ypos)
table = TableDesign(size, matrix=self.day_table)
table.pos = pos
self.draw_design(table)
def __draw_seperator__(self, height, color):
ImageDraw.Draw(self.__image__).line(
[(0, height * self.size[1]), (self.size[0], height * self.size[1])], fill=color, width=seperator_width)
def __init_day_boxes__(self):
if week_starts_on == "Monday":
callib.setfirstweekday(callib.MONDAY)
elif week_starts_on == "Sunday":
callib.setfirstweekday(callib.SUNDAY)
weeks = callib.monthcalendar(self.year, self.month)
for i, week in enumerate(weeks):
self.day_table.append([])
for day in week:
self.day_table[i].append(self.__create_day__(day))
def __create_day__(self, day):
if day == None or day == 0:
return None
design = DayBoxDesign(self.day_box_size, date(
self.year, self.month, int(day)))
return design
def __get_week_count__(self):
return len(callib.monthcalendar(self.year, self.month))

View file

@ -2,72 +2,100 @@ from WeatherForecast import WeatherForecast
from WeatherInterface import WeatherInterface from WeatherInterface import WeatherInterface
import pyowm import pyowm
from datetime import datetime from datetime import datetime
from settings import units from settings import units, language
from Translator import translate
class OwmForecasts (WeatherInterface): class OwmForecasts (WeatherInterface):
"""Fetches weather through the Openweathermap-api.""" """Fetches weather through the Openweathermap-api."""
def __init__ (self, location, api_key, paid_api=False):
subscription = "pro" if paid_api else None def __init__(self, location, api_key, paid_api=False):
self.api = pyowm.OWM(api_key, subscription_type=subscription) self.subscription = "pro" if paid_api else None
self.api_key = api_key
self.units = units self.units = units
self.location = location self.location = location
self.api = pyowm.OWM(
self.api_key, subscription_type=self.subscription, language=language)
def is_available (self): def is_available(self):
try: try:
return self.api.is_API_online() return self.api.is_API_online()
except: except:
return False return False
def get_today_forecast (self, location=None): def reload(self):
pass
def get_today_forecast(self, location=None):
if self.is_available() is False: if self.is_available() is False:
return None return None
location = self.location if location is None else location
observation = self.api.weather_at_place(location) try:
weather = observation.get_weather() location = self.location if location is None else location
return self.__get_forecast_from_weather__(weather, location=location) observation = self.api.weather_at_place(location)
weather = observation.get_weather()
def get_forecast_in_days (self, offset_by_days, location=None): return self.__get_forecast_from_weather__(weather, location=location)
except:
return None
def get_forecast_in_days(self, offset_by_days, location=None):
if offset_by_days is 0: if offset_by_days is 0:
return self.get_today_forecast(location) return self.get_today_forecast(location)
if self.is_available() is False: if self.is_available() is False:
return None return None
location = self.location if location is None else location location = self.location if location is None else location
try: try:
forecast = self.api.daily_forecast(location, limit=offset_by_days) forecast = self.api.daily_forecast(location, limit=offset_by_days)
target_weather = forecast.get_forecast().get_weathers()[-1] target_weather = forecast.get_forecast().get_weathers()[-1]
return self.__get_forecast_from_weather__(target_weather, location=location) return self.__get_forecast_from_weather__(target_weather, location=location)
except: # only allowed for paied membership except: # only allowed for paid membership
return None return None
def __get_forecast_from_weather__ (self, weather, location): def __get_forecast_from_weather__(self, weather, location):
forecast_object = WeatherForecast() forecast_object = WeatherForecast()
forecast_object.units = self.units forecast_object.units = self.units
forecast_object.fetch_datetime = datetime.now() forecast_object.fetch_datetime = datetime.now()
forecast_object.location = location forecast_object.location = location
forecast_object.datetime = weather.get_reference_time(timeformat='date') forecast_object.datetime = weather.get_reference_time(
timeformat='date')
forecast_object.icon = weather.get_weather_icon_name() forecast_object.icon = weather.get_weather_icon_name()
forecast_object.air_humidity = str(weather.get_humidity()) forecast_object.air_humidity = str(weather.get_humidity())
forecast_object.clouds = str(weather.get_clouds()) forecast_object.clouds = str(weather.get_clouds())
forecast_object.short_description = str(weather.get_status()) forecast_object.short_description = translate(
forecast_object.detailed_description = str(weather.get_detailed_status()) str(weather.get_status()))
forecast_object.detailed_description = str(
weather.get_detailed_status())
forecast_object.air_pressure = str(weather.get_pressure()['press']) forecast_object.air_pressure = str(weather.get_pressure()['press'])
if 'deg' in weather.get_wind().keys():
forecast_object.wind_deg = str(int(weather.get_wind()['deg']))
if forecast_object.units == "metric": if forecast_object.units == "metric":
forecast_object.air_temperature = str(int(weather.get_temperature(unit='celsius')['temp'])) forecast_object.air_temperature = str(
forecast_object.wind_speed = str(int(weather.get_wind()['speed'])) int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'])) # kmh
if forecast_object.units == "aviation":
forecast_object.air_temperature = str(
int(weather.get_temperature(unit='celsius')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'] * 1.94384)) # knots
if forecast_object.units == "imperial": if forecast_object.units == "imperial":
forecast_object.air_temperature = str(int(weather.get_temperature('fahrenheit')['temp'])) forecast_object.air_temperature = str(
forecast_object.wind_speed = str(int(weather.get_wind()['speed'] * 0.621)) int(weather.get_temperature('fahrenheit')['temp']))
forecast_object.wind_speed = str(
int(weather.get_wind()['speed'] * 0.621)) # mph
forecast_object.sunrise = datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))) forecast_object.sunrise = datetime.fromtimestamp(
forecast_object.sunset = datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))) int(weather.get_sunrise_time(timeformat='unix')))
forecast_object.sunset = datetime.fromtimestamp(
int(weather.get_sunset_time(timeformat='unix')))
return forecast_object return forecast_object

View file

@ -1,18 +1,39 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from TechnicalDataDesign import TechnicalDataDesign
from settings import print_technical_data
from datetime import datetime
class PanelDesign (DesignEntity): class PanelDesign (DesignEntity):
"""Defined general interface for panel designs.""" """Defined general interface for panel designs."""
def __init__ (self, size):
def __init__(self, size):
super(PanelDesign, self).__init__(size) super(PanelDesign, self).__init__(size)
self.start_timestamp = datetime.now()
def add_weather (self, weather): def add_weather(self, weather):
raise NotImplementedError("Functions needs to be implemented") pass
def add_calendar (self, calendar): def add_calendar(self, calendar):
raise NotImplementedError("Functions needs to be implemented") pass
def add_rssfeed (self, rss): def add_rssfeed(self, rss):
raise NotImplementedError("Functions needs to be implemented") pass
def add_taks (self, tasks): def add_tasks(self, tasks):
raise NotImplementedError("Functions needs to be implemented") pass
def add_crypto(self, crypto):
pass
def __finish_panel__(self):
pass
def __finish_image__(self):
self.__finish_panel__()
if print_technical_data:
td = TechnicalDataDesign(
self.size, self.start_timestamp, datetime.now())
td.mask = True
self.draw_design(td)

View file

@ -1,9 +1,16 @@
from DataSourceInterface import DataSourceInterface from DataSourceInterface import DataSourceInterface
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
class RssInterface(DataSourceInterface): class RssInterface(DataSourceInterface):
"""Interface for fetching and processing rss post information.""" """Interface for fetching and processing rss post information."""
def __init__(self): def __init__(self):
self.loaded_posts = []
def reload(self):
if self.is_available() == False:
return
self.loaded_posts = self.__get_posts__() self.loaded_posts = self.__get_posts__()
self.__sort_posts__() self.__sort_posts__()
@ -17,7 +24,7 @@ class RssInterface(DataSourceInterface):
return self.get_day_posts(datetime.now()) return self.get_day_posts(datetime.now())
def get_day_posts(self, day): def get_day_posts(self, day):
return self.__get_posts_to_filter__(lambda x : x.datetime.strftime('%d-%m-%y') == date.strftime('%d-%m-%y')) return self.__get_posts_to_filter__(lambda x: x.datetime.strftime('%d-%m-%y') == day.strftime('%d-%m-%y'))
def __get_posts_to_filter__(self, post_filter): def __get_posts_to_filter__(self, post_filter):
if self.loaded_posts is None: if self.loaded_posts is None:
@ -25,4 +32,4 @@ class RssInterface(DataSourceInterface):
return [post for post in self.loaded_posts if post_filter(post)] return [post for post in self.loaded_posts if post_filter(post)]
def __sort_posts__(self): def __sort_posts__(self):
self.loaded_posts.sort(key=lambda x : x.datetime, reverse=True) self.loaded_posts.sort(key=lambda x: x.datetime, reverse=True)

View file

@ -1,17 +1,31 @@
from RssInterface import RssInterface from RssInterface import RssInterface
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
import feedparser import feedparser
import arrow
import RssPost import RssPost
from urllib.request import urlopen
max_range_days = 14 max_range_days = 14
class RssParserPosts (RssInterface): class RssParserPosts (RssInterface):
"""Fetches posts from url-addresses via rss parser.""" """Fetches posts from url-addresses via rss parser."""
def __init__(self, urls): def __init__(self, urls):
self.urls = urls self.urls = urls
super(RssParserPosts, self).__init__() super(RssParserPosts, self).__init__()
def is_available(self):
try:
testurl = ""
if self.urls:
testurl = self.urls[0]
else:
return False
urlopen(testurl)
return True
except:
return False
def __get_posts__(self): def __get_posts__(self):
posts = [] posts = []
@ -40,5 +54,4 @@ class RssParserPosts (RssInterface):
def __get_webpage__(self, link): def __get_webpage__(self, link):
start_index = link.find('://') + 3 start_index = link.find('://') + 3
end_index = link[start_index:].find('/') + start_index end_index = link[start_index:].find('/') + start_index
return link[start_index : end_index] return link[start_index: end_index]

View file

@ -1,9 +1,10 @@
class RssPost(object): class RssPost(object):
"""Defines a rss post, independent of any implementation""" """Defines a rss post, independent of any implementation"""
def __init__ (self):
def __init__(self):
self.title = None self.title = None
self.description = None self.description = None
self.source = None self.source = None
self.datetime = None self.datetime = None
self.fetch_datetime = None self.fetch_datetime = None

View file

@ -1,32 +1,36 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from TableTextDesign import TableTextDesign from TableDesign import TableDesign
from Assets import defaultfontsize
class RssPostListDesign (DesignEntity): class RssPostListDesign (DesignEntity):
"""Creates a TableTextDesign filled with rss post """Creates a TableDesign filled with rss post
date and title""" date and title"""
def __init__ (self, size, rssfeed, text_size = 18):
def __init__(self, size, rssfeed, text_size=defaultfontsize):
super(RssPostListDesign, self).__init__(size) super(RssPostListDesign, self).__init__(size)
self.rssfeed = rssfeed self.rssfeed = rssfeed
self.__post_matrix__ = [] self.__post_matrix__ = []
self.text_size = text_size self.text_size = text_size
def __finish_image__ (self): def __finish_image__(self):
self.__fill_post_matrix__() self.__fill_post_matrix__()
table_design = TableTextDesign(self.size, line_spacing=5, col_spacing=3, text_matrix=self.__post_matrix__, fontsize = self.text_size, mask=False, truncate_cols=False, wrap=True) table_design = TableDesign(self.size, line_spacing=2, col_spacing=3, matrix=self.__post_matrix__,
fontsize=self.text_size, mask=False, wrap=True, truncate_rows=True)
self.draw_design(table_design) self.draw_design(table_design)
def __get_formatted_post__ (self, post): def __get_formatted_post__(self, post):
date = post.datetime.strftime('%d %b') date = post.datetime.strftime('%d %b')
date = self.__remove_leading_zero__(date) date = self.__remove_leading_zero__(date)
return [ '', post.title ] return ['', '', post.title]
def __remove_leading_zero__(self, text): def __remove_leading_zero__(self, text):
while text[0] is '0': while text[0] is '0':
text = text[1:] text = text[1:]
return text return text
def __fill_post_matrix__ (self): def __fill_post_matrix__(self):
for post in self.rssfeed.get_latest_posts(): for post in self.rssfeed.get_latest_posts():
row = self.__get_formatted_post__(post) row = self.__get_formatted_post__(post)
self.__post_matrix__.append(row) self.__post_matrix__.append(row)

View file

@ -1,24 +1,15 @@
from EventListDesign import EventListDesign from EventListDesign import EventListDesign
from settings import hours from settings import hours
from Assets import fonts from Assets import fonts, defaultfontsize, colors
from TextFormatter import event_prefix_str_sum
eventlist_allday_char = ""
font = fonts["regular"] font = fonts["regular"]
class SingelDayEventListDesign (EventListDesign): class SingelDayEventListDesign (EventListDesign):
"""Specialized event list for day list design.""" """Specialized event list for day list design."""
def __init__ (self, size, calendar, date, font_size = 16, line_spacing=2, col_spacing=5, general_color="black", background_color="white", highlight_color="red"):
prefix_func = lambda x : self.__get_event_prefix__(x)
super().__init__(size, calendar, filter_date=date, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_func=prefix_func, font_family=font)
def __get_event_prefix__ (self, event):
if event.allday:
return eventlist_allday_char
else:
return self.__get_time__(event.begin_datetime)
def __get_time__ (self, time): def __init__(self, size, events, font_size=defaultfontsize, line_spacing=0, event_prefix_rel_dates=[], col_spacing=5, general_color=colors["fg"], background_color=colors["bg"], highlight_color=colors["hl"]):
if hours == "24": def prefix_func(x, rel_date): return event_prefix_str_sum(x, rel_date)
return time.strftime('%H:%M') super().__init__(size, events, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_rel_dates=event_prefix_rel_dates,
else: event_prefix_func=prefix_func, font_family=font, show_more_info=True, general_color=general_color, background_color=background_color, highlight_color=highlight_color)
return time.strftime('%I:%M')

View file

@ -1,17 +1,25 @@
from TextDesign import TextDesign from TextDesign import TextDesign
from TextWraper import wrap_text_with_font from TextWraper import wrap_text_with_font
from Assets import defaultfontsize, colors
from DesignEntity import DesignEntity
default_props = { default_props = {
"color" : "black", "color": colors["fg"],
"background_color" : "white" "background_color": colors["bg"],
"font-size": defaultfontsize
} }
class TableTextDesign (TextDesign):
"""Gets a matrix with text that is than class TableDesign (TextDesign):
"""Gets a matrix with text or designs that is than
displayed in a table without borders.""" displayed in a table without borders."""
def __init__ (self, size, text_matrix, max_col_size = None, max_row_size = None, font = None, fontsize = 12, column_horizontal_alignments = [], mask = True, line_spacing = 0, col_spacing = 0, truncate_rows = True, truncate_cols = True, wrap = False, truncate_text=True, truncate_suffix="...", cell_properties=None):
super(TableTextDesign, self).__init__(size, font=font, fontsize=fontsize, mask=mask) def __init__(self, size, matrix, max_col_size=None, max_row_size=None, font=None, fontsize=defaultfontsize, column_horizontal_alignments=[], mask=True, line_spacing=0, col_spacing=0, truncate_rows=True, truncate_cols=True, wrap=False, truncate_text=True, truncate_suffix="...", cell_properties=None, background_color=colors["bg"]):
self.matrix = text_matrix super(TableDesign, self).__init__(
size, font=font, fontsize=fontsize, mask=mask)
self.__init_image__(background_color)
self.matrix = matrix
self.max_col_size = max_col_size self.max_col_size = max_col_size
self.max_row_size = max_row_size self.max_row_size = max_row_size
self.line_spacing = line_spacing self.line_spacing = line_spacing
@ -25,8 +33,9 @@ class TableTextDesign (TextDesign):
self.truncate_text = truncate_text self.truncate_text = truncate_text
self.truncate_suffix = truncate_suffix self.truncate_suffix = truncate_suffix
self.cell_properties = cell_properties self.cell_properties = cell_properties
default_props["font-size"] = fontsize
def __finish_image__ (self): def __finish_image__(self):
if len(self.matrix) is 0: if len(self.matrix) is 0:
return return
self.__reform_col_size__() self.__reform_col_size__()
@ -35,48 +44,64 @@ class TableTextDesign (TextDesign):
self.max_col, self.max_row = self.__get_truncated_counts__() self.max_col, self.max_row = self.__get_truncated_counts__()
self.__print_table__(self.matrix) self.__print_table__(self.matrix)
def __reform_col_size__ (self): def __reform_col_size__(self):
if self.max_col_size is not None: if self.max_col_size is not None:
return return
font = self.__get_font__()
col_sizes = [] col_sizes = []
for c in range(len(self.matrix[0])): #amout of columns for c in range(len(self.matrix[0])): # amout of columns
for r in range(len(self.matrix)): for r in range(len(self.matrix)):
row_col_size = font.getsize(self.matrix[r][c])[0] #get width of text in that row/col row_col_size = self.__get_cell_size__(r, c)[0]
if len(col_sizes) - 1 < c: if len(col_sizes) - 1 < c:
col_sizes.append(row_col_size) col_sizes.append(row_col_size)
elif row_col_size > col_sizes[c]: elif row_col_size > col_sizes[c]:
col_sizes[c] = row_col_size col_sizes[c] = row_col_size
for index, size in enumerate(col_sizes): for index, size in enumerate(col_sizes):
preceding_size = sum(col_sizes[:index]) preceding_size = sum(col_sizes[:index]) + index * self.col_spacing
if preceding_size + size > self.size[0]: if preceding_size + size > self.size[0]:
col_sizes[index] = self.size[0] - preceding_size col_sizes[index] = self.size[0] - preceding_size
break break
self.max_col_size = col_sizes self.max_col_size = col_sizes
def __reform_row_size__ (self): def __reform_row_size__(self):
if self.max_row_size is not None: if self.max_row_size is not None:
return return
font = self.__get_font__()
row_sizes = [] row_sizes = []
for r in range(len(self.matrix)): for r in range(len(self.matrix)):
for c in range(len(self.matrix[0])): #amout of columns for c in range(len(self.matrix[0])): # amout of columns
cell_text = self.matrix[r][c] col_row_size = self.__get_cell_size__(r, c)[1]
if self.wrap:
cell_text = wrap_text_with_font(cell_text, self.max_col_size[c], font)
col_row_size = font.getsize_multiline(cell_text)[1] #get height of text in that col/row
if len(row_sizes) - 1 < r: if len(row_sizes) - 1 < r:
row_sizes.append(col_row_size) row_sizes.append(col_row_size)
elif col_row_size > row_sizes[r]: elif col_row_size > row_sizes[r]:
row_sizes[r] = col_row_size row_sizes[r] = col_row_size
self.max_row_size = row_sizes self.max_row_size = row_sizes
def __get_truncated_counts__ (self): def __get_cell_size__(self, r, c):
content = self.matrix[r][c]
size = (0, 0)
if content == None:
return size
elif type(content) == str:
font = self.__get_font__()
# get width of text in that row/col
width = font.getsize(self.matrix[r][c])[0]
if self.wrap and self.max_col_size != None:
content = wrap_text_with_font(
content, self.max_col_size[c], font)
line_count = content.count('\n') + 1
height = font.font.height * line_count # get height of text in that col/row
size = (width, height)
else: # DesignEntity
size = content.size
return size
def __get_truncated_counts__(self):
max_col = 0 max_col = 0
if self.truncate_cols: if self.truncate_cols:
while max_col < len(self.matrix[0]) and self.__get_cell_pos__(0, max_col + 1)[0] - self.col_spacing <= self.size[0]: while max_col < len(self.matrix[0]) and self.__get_cell_pos__(0, max_col + 1)[0] - self.col_spacing <= self.size[0]:
@ -86,29 +111,54 @@ class TableTextDesign (TextDesign):
max_row = 0 max_row = 0
if self.truncate_rows: if self.truncate_rows:
while max_row < len(self.matrix) and self.__get_cell_pos__(max_row + 1,0)[1] - self.line_spacing <= self.size[1]: while max_row < len(self.matrix) and self.__get_cell_pos__(max_row + 1, 0)[1] - self.line_spacing <= self.size[1]:
max_row += 1 max_row += 1
else: else:
max_row = len(self.matrix) max_row = len(self.matrix)
return (max_col, max_row) return (max_col, max_row)
def __print_table__ (self, matrix): def __print_table__(self, matrix):
for r in range(self.max_row): for r in range(self.max_row):
for c in range(self.max_col): for c in range(self.max_col):
size = self.cell_sizes[r][c] self.__draw_cell__(r, c)
pos = self.__get_cell_pos__(r,c)
self.__draw_text__(pos, size, r, c) def __draw_text__(self, pos, size, row, col):
def __draw_text__ (self, pos, size, row, col):
color = self.__get_cell_prop__(row, col, "color") color = self.__get_cell_prop__(row, col, "color")
bg_color = self.__get_cell_prop__(row, col, "background_color") bg_color = self.__get_cell_prop__(row, col, "background_color")
fontsize = self.__get_cell_prop__(row, col, "font-size")
design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=self.font_size, horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix) design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=fontsize,
horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix)
design.pos = pos design.pos = pos
design.mask = False
self.draw_design(design) self.draw_design(design)
def __get_cell_pos__ (self, row, col): def __draw_design__(self, pos, size, row, col):
bg_color = self.__get_cell_prop__(row, col, "background_color")
source_design = self.matrix[row][col]
source_design.mask = False
framed_design = DesignEntity(size, mask=False)
framed_design.__init_image__(color=bg_color)
framed_design.draw_design(source_design)
framed_design.pos = pos
self.draw_design(framed_design)
def __draw_cell__(self, row, col):
size = self.cell_sizes[row][col]
pos = self.__get_cell_pos__(row, col)
if self.matrix[row][col] == None:
return
elif type(self.matrix[row][col]) == str:
self.__draw_text__(pos, size, row, col)
else:
self.__draw_design__(pos, size, row, col)
def __get_cell_pos__(self, row, col):
xpos, ypos = (0, 0) xpos, ypos = (0, 0)
for c in range(col): for c in range(col):
xpos += self.cell_sizes[row][c][0] xpos += self.cell_sizes[row][c][0]
@ -118,7 +168,7 @@ class TableTextDesign (TextDesign):
ypos += self.line_spacing ypos += self.line_spacing
return (xpos, ypos) return (xpos, ypos)
def __get_cell_sizes__ (self): def __get_cell_sizes__(self):
size_matrix = [] size_matrix = []
for r in range(len(self.matrix)): for r in range(len(self.matrix)):
size_matrix.append([]) size_matrix.append([])
@ -127,7 +177,7 @@ class TableTextDesign (TextDesign):
size_matrix[r].append(size) size_matrix[r].append(size)
return size_matrix return size_matrix
def __get_col_hori_alignment__ (self, c): def __get_col_hori_alignment__(self, c):
if len(self.column_horizontal_alignments) <= c: if len(self.column_horizontal_alignments) <= c:
return "left" return "left"
return self.column_horizontal_alignments[c] return self.column_horizontal_alignments[c]
@ -135,7 +185,8 @@ class TableTextDesign (TextDesign):
def __get_cell_prop__(self, r, c, prop): def __get_cell_prop__(self, r, c, prop):
if self.cell_properties is None: if self.cell_properties is None:
return default_props[prop] return default_props[prop]
try:
if r < len(self.cell_properties) and c < len(self.cell_properties[r]) and prop in self.cell_properties[r][c].keys():
return self.cell_properties[r][c][prop] return self.cell_properties[r][c][prop]
except: else:
return default_props[prop] return default_props[prop]

View file

@ -0,0 +1,42 @@
from DesignEntity import DesignEntity
from TextDesign import TextDesign
from Assets import colors
font_size = 20
class TechnicalDataDesign(DesignEntity):
'''Prints data about the current loop ontop of the panel'''
def __init__(self, size, start, stop):
super(TechnicalDataDesign, self).__init__(size, mask=True)
self.start = start
self.stop = stop
def __finish_image__(self):
data = self.__get_data__()
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["fg"]
txt.pos = (0, 0)
self.draw_design(txt)
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["hl"]
txt.pos = (1, 1)
self.draw_design(txt)
txt = TextDesign(self.size, text=data, fontsize=font_size)
txt.color = colors["fg"]
txt.pos = (2, 2)
self.draw_design(txt)
def __get_data__(self):
data = "START: "
data += str(self.start)
data += "\nDURATION BEFORE RENDER: "
dur = self.stop - self.start
data += str(dur)
data += "\nSTOP: "
data += str(self.stop)
return data

View file

@ -1,16 +1,17 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from PIL import ImageFont, ImageDraw, ImageOps from PIL import ImageFont, ImageDraw, ImageOps
from Assets import path, defaultfont from Assets import path, defaultfont, colors, defaultfontsize
from TextWraper import wrap_text_with_font from TextWraper import wrap_text_with_font
paddingcorrection = -3
truncateerror_fontsize = 0.5 truncateerror_fontsize = 0.5
class TextDesign (DesignEntity): class TextDesign (DesignEntity):
"""Object that manages all information relevant to text """Object that manages all information relevant to text
and prints it to an image""" and prints it to an image"""
def __init__ (self, size, color="black", background_color="white", font = None, fontsize = 12, text = "", horizontalalignment = "left", verticalalignment = "top", mask=True, truncate=False, truncate_suffix = '...', wrap=False):
super(TextDesign, self).__init__(size, mask = mask) def __init__(self, size, color=colors["fg"], background_color=colors["bg"], font=None, fontsize=defaultfontsize, text="", horizontalalignment="left", verticalalignment="top", mask=True, truncate=False, truncate_suffix='...', wrap=False):
super(TextDesign, self).__init__(size, mask=mask)
if font is None: if font is None:
font = defaultfont font = defaultfont
self.font_family = font self.font_family = font
@ -24,10 +25,10 @@ class TextDesign (DesignEntity):
self.color = color self.color = color
self.background_color = background_color self.background_color = background_color
def __finish_image__ (self): def __finish_image__(self):
if self.color is "white": if self.color is "white":
self.invert_mask = True self.invert_mask = True
if self.background_color not in ["white", "black"]: if self.background_color not in ["white", "black"] or self.color in ["red"]:
self.color_key = True self.color_key = True
self.__init_image__(self.background_color) self.__init_image__(self.background_color)
@ -37,21 +38,24 @@ class TextDesign (DesignEntity):
if self.wrap: if self.wrap:
self.__wrap_text__() self.__wrap_text__()
pos = self.__pos_from_alignment__() pos = self.__pos_from_alignment__()
ImageDraw.Draw(self.__image__).text(pos, self.text, fill=self.color, font=self.__font__) ImageDraw.Draw(self.__image__).text(
pos, self.text, fill=self.color, font=self.__font__)
def __truncate_text__ (self):
if self.__font__.getsize(self.text)[0] <= self.size[0]: #does not need truncating def __truncate_text__(self):
# does not need truncating
if self.__font__.getsize_multiline(self.text)[0] <= self.size[0]:
return return
error = truncateerror_fontsize * self.__font__.getsize("A")[0] suffix_length = self.__font__.getsize_multiline(
suffix_length = self.__font__.getsize(self.truncate_suffix)[0] self.truncate_suffix)[0]
while len(self.text) > 1 and self.__font__.getsize(self.text)[0] + suffix_length >= self.size[0] - error: while len(self.text) > 1 and self.__font__.getsize_multiline(self.text)[0] + suffix_length >= self.size[0]:
self.text = self.text[0:-1] self.text = self.text[0:-1]
self.text = self.text.rstrip(' ')
self.text += self.truncate_suffix self.text += self.truncate_suffix
def __pos_from_alignment__ (self): def __pos_from_alignment__(self):
width, height = self.__font__.getsize(self.text) width, height = self.__get_text_size__()
x, y = 0, 0 x, y = 0, 0
if self.vertical_alignment == "center": if self.vertical_alignment == "center":
y = int((self.size[1] / 2) - (height / 2)) y = int((self.size[1] / 2) - (height / 2))
elif self.vertical_alignment == "bottom": elif self.vertical_alignment == "bottom":
@ -62,10 +66,15 @@ class TextDesign (DesignEntity):
elif self.horizontal_alignment == "right": elif self.horizontal_alignment == "right":
x = int(self.size[0] - width) x = int(self.size[0] - width)
return (x, y + paddingcorrection) return (x, y)
def __wrap_text__ (self): def __get_text_size__(self):
widht = self.__font__.getsize_multiline(self.text)[0]
height = (self.text.count('\n') + 1) * self.__font__.font.height
return widht, height
def __wrap_text__(self):
self.text = wrap_text_with_font(self.text, self.size[0], self.__font__) self.text = wrap_text_with_font(self.text, self.size[0], self.__font__)
def __get_font__(self): def __get_font__(self):
return ImageFont.truetype(path + self.font_family, int(self.font_size)) return ImageFont.truetype(path + self.font_family, int(self.font_size))

110
Calendar/TextFormatter.py Normal file
View file

@ -0,0 +1,110 @@
from settings import hours, language
from datetime import timedelta, datetime, timezone
from DictionaryMapper import get_text
from Dictionary import multiday_events, allday_events
first_occurrence_char = '['
middle_occurrence_char = '|'
last_occurrence_char = ']'
multiday_begin_character = ' >'
multiday_end_character = '< '
until_character = ' - '
allday_character = ""
multiday_character = allday_character + allday_character
def time_str(dt):
if hours is "12":
return dt.strftime("%I:%M%p")
elif hours is "24":
return dt.strftime("%H:%M")
else:
return str(dt)
def event_prefix_str_md_dif(event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
if event.multiday is False:
return event_time_summary(event)
# Relative to
# First day
elif __equal__(event.begin_datetime, relative_date):
return event_time_summary(event) + multiday_begin_character
# Last day
elif __equal__(event.end_datetime, relative_date) or \
(__day_duration__(event.end_datetime) == timedelta(0) and __equal__(relative_date + timedelta(1), event.end_datetime)):
return multiday_end_character + event_time_summary(event)
# Some day
else:
event.allday = True
return multiday_end_character + event_time_summary(event) + multiday_begin_character
def event_prefix_str(event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
if event.multiday:
return get_text(multiday_events)
else:
return event_time_detailed(event)
def event_prefix_str_sum(event, relative_date=None):
if relative_date is None:
relative_date = event.begin_datetime.date()
if event.multiday:
return multiday_character
else:
return event_time_summary(event)
def event_time_summary(event):
if event.allday:
return allday_character
else:
return time_str(event.begin_datetime)
def event_time_detailed(event):
if event.allday:
return get_text(allday_events)
else:
return time_str(event.begin_datetime) + until_character + time_str(event.end_datetime)
def date_str(dt):
return remove_leading_zero(dt.strftime('%d. %b'))
def remove_leading_zero(text):
while text[0] is '0':
text = text[1:]
return text
def date_summary_str(dt):
day = remove_leading_zero(dt.strftime("%d"))
if language is "en":
return dt.strftime('%a ' + day + '. %b')
elif language is "de":
return dt.strftime(day + '. %b., %a')
else:
return dt.strftime('%a ' + day + '. %b')
def __equal__(dt1, dt2):
return dt1.day == dt2.day and \
dt1.month == dt2.month and \
dt1.year == dt2.year
def __day_duration__(dt):
day_begin = datetime(dt.year, dt.month, dt.day, 0, 0, 0, 0, timezone.utc)
return dt - day_begin

View file

@ -1,10 +1,11 @@
from Assets import path, defaultfont from Assets import path, defaultfont
from PIL import ImageFont from PIL import ImageFont
def wrap_text_with_font (text, width, font):
def wrap_text_with_font(text, width, font):
words = text.split(' ') words = text.split(' ')
result = "" result = ""
for index, word in enumerate(words): for word in words:
until_current = (result + " " + word).strip() until_current = (result + " " + word).strip()
txt_width, _ = font.getsize_multiline(until_current) txt_width, _ = font.getsize_multiline(until_current)
if txt_width > width: if txt_width > width:
@ -14,5 +15,6 @@ def wrap_text_with_font (text, width, font):
result += word result += word
return result.strip() return result.strip()
def wrap_text (text, width, font_size, font_family = defaultfont):
return wrap_text_with_font(text, width, ImageFont.truetype(path + font_family, int(font_size))) def wrap_text(text, width, font_size, font_family=defaultfont):
return wrap_text_with_font(text, width, ImageFont.truetype(path + font_family, int(font_size)))

26
Calendar/Translator.py Normal file
View file

@ -0,0 +1,26 @@
from Dictionary import default_language, dictionary_collection
from settings import language
'''Looks up a phrase in a given dictionary-collection
and returns the translated phrase'''
def translate(phrase, target_lang=language, dictionary_collection=dictionary_collection):
dictionary = find_dictionary(phrase, dictionary_collection)
if dictionary == None:
return phrase
if target_lang in dictionary.keys():
return dictionary[target_lang]
elif '_' in target_lang and target_lang.split('_')[0] in dictionary.keys():
return dictionary[target_lang.split('_')[0]]
else:
return dictionary[default_language]
def find_dictionary(phrase, dictionary_collection=dictionary_collection):
for dictionary in dictionary_collection:
if phrase in dictionary.values():
return dictionary
return None

View file

@ -1,127 +1,84 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from TextDesign import TextDesign from TextDesign import TextDesign
from TableTextDesign import TableTextDesign from TableDesign import TableDesign
from Assets import wpath, weathericons, tempicon, humicon, windicon, no_response from Assets import wpath, weathericons, tempicon, humicon, windicon, no_response, colors, defaultfontsize
from PIL import Image from PIL import Image
from settings import hours from settings import hours
icon_xpos = 0.1 icon_xpos = 0.1
icon_x_ypos = -0.1 icon_x_ypos = 0
icon_width = 1 - 2 * icon_xpos icon_width = 1 - 2 * icon_xpos
info_x_height = icon_width * 0.3
info_x_ypos = icon_x_ypos + icon_width info_x_ypos = icon_x_ypos + icon_width
fontsize_y = 0.1 info_yresize = -0.05
numbers_x_ypos = icon_x_ypos + icon_width + info_x_height + 0.1 fontsize_static = defaultfontsize
numbers_x_ypadding = 0.05
max_symbol_y_width = 0.15 max_symbol_y_width = 0.15
general_text_color = "black"
background_color = "white"
class WeatherColumnDesign (DesignEntity): class WeatherColumnDesign (DesignEntity):
"""Displays weather information in a column""" """Displays weather information in a column"""
def __init__ (self, size, forecast):
def __init__(self, size, forecast):
super().__init__(size) super().__init__(size)
self.forecast = forecast self.forecast = forecast
def __finish_image__ (self): def __finish_image__(self):
if self.forecast is None: if self.forecast is None:
self.__draw_no_response__() self.__draw_no_response__()
return return
self.__draw_icon__(self.forecast.icon) self.__draw_icon__(self.forecast.icon)
self.__draw_info__(self.forecast.short_description) self.__draw_infos__(self.forecast)
#self.__draw_number_symbols__()
#self.__draw_numbers__(self.forecast) def __draw_infos__(self, forecast):
self.__draw_numbers_text__(self.forecast) temperature = forecast.air_temperature + \
" " + self.__get_unit__(("°C", "°F"))
humidity = forecast.air_humidity + "%"
if forecast.units == "aviation":
if forecast.wind_deg == None:
forecast.wind_deg = ""
elif len(forecast.wind_deg) == 1:
forecast.wind_deg = "00" + forecast.wind_deg
elif len(forecast.wind_deg) == 2:
forecast.wind_deg = "0" + forecast.wind_deg
if int(forecast.wind_speed) < 10:
windspeed = forecast.wind_deg + "@" + "0" + forecast.wind_speed + \
self.__get_unit__(
("", "")) # added degrees, if wind<10 add a 0 to make two digit
else:
windspeed = forecast.wind_deg + "@" + forecast.wind_speed + \
self.__get_unit__(("", "")) # added degrees
else:
windspeed = forecast.wind_speed + " " + \
self.__get_unit__(("km/h", "mph"))
numbers_list = [[forecast.short_description],
[temperature],
[humidity],
[windspeed]]
def __draw_info__ (self, info):
height = info_x_height * self.size[0]
ypos = info_x_ypos * self.size[0] ypos = info_x_ypos * self.size[0]
size = (self.size[0], height)
pos = (0, ypos) pos = (0, ypos)
size = (self.size[0], self.size[1] +
info_yresize * self.size[1] - pos[1])
line_spacing = (size[1] - len(numbers_list) *
fontsize_static) / (len(numbers_list) + 1)
txt = TextDesign(size, text=info, fontsize=height, horizontalalignment="center") table = TableDesign(size, numbers_list, fontsize=fontsize_static, line_spacing=line_spacing, column_horizontal_alignments=[
txt.pos = pos "center"], max_col_size=[size[0]], truncate_text=False, truncate_rows=False)
self.draw_design(txt)
def __draw_number_symbols__ (self):
symbols = [ tempicon,
humicon,
windicon ]
ypos = numbers_x_ypos * self.size[0]
fontsize = fontsize_y * self.size[1]
line_spacing = (self.size[1] - ypos - len(symbols) * fontsize) / len(symbols)
line_height = fontsize + line_spacing
width = line_height
max_width = max_symbol_y_width * self.size[1]
if width > max_width:
width = max_width
size = (width, width)
pos = (0, ypos)
for index, symbol in enumerate(symbols):
sym_ypos = pos[1] + line_height * index
self.__draw_resized_image_at__(symbol, (0, sym_ypos), size)
def __draw_numbers__ (self, forecast):
temperature = forecast.air_temperature + " " + self.__get_unit__(("°C", "°F"))
humidity = forecast.air_humidity + "%"
windspeed = forecast.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
numbers_list = [ [ temperature ],
[ humidity ],
[ windspeed ] ]
fontsize = fontsize_y * self.size[1]
ypadding = numbers_x_ypadding * self.size[0]
ypos = numbers_x_ypos * self.size[0]
line_spacing = (self.size[1] - ypos - len(numbers_list) * fontsize) / len(numbers_list)
symbol_width = fontsize + line_spacing
max_symbol_width = max_symbol_y_width * self.size[1]
if symbol_width > max_symbol_width:
symbol_width = max_symbol_width
xpos = symbol_width
pos = (xpos, ypos + ypadding)
size = (self.size[0] - pos[0], self.size[1] - pos[1])
table = TableTextDesign(size, numbers_list, fontsize=fontsize, line_spacing=line_spacing)
table.pos = pos table.pos = pos
self.draw_design(table) self.draw_design(table)
def __draw_numbers_text__ (self, forecast): def __draw_icon__(self, icon_id):
temperature = forecast.air_temperature + " " + self.__get_unit__(("°C", "°F"))
humidity = forecast.air_humidity + "%"
windspeed = forecast.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
sunrise = self.__get_time__(forecast.sunrise)
sunset = self.__get_time__(forecast.sunset)
numbers_list = [ [ temperature ],
[ humidity ],
[ windspeed ] ]
fontsize = fontsize_y * self.size[1]
ypos = numbers_x_ypos * self.size[0]
line_spacing = (self.size[1] - ypos - len(numbers_list) * fontsize) / len(numbers_list)
pos = (0, ypos)
size = (self.size[0], self.size[1] - pos[1])
table = TableTextDesign(size, numbers_list, fontsize=fontsize, line_spacing=line_spacing, column_horizontal_alignments=[ "center" ], max_col_size=[ size[0] ])
table.pos = pos
self.draw_design(table)
def __draw_icon__ (self, icon_id):
width = int(icon_width * self.size[0]) width = int(icon_width * self.size[0])
size = (width, width) size = (width, width)
xpos = icon_xpos * self.size[0] xpos = icon_xpos * self.size[0]
ypos = icon_x_ypos * self.size[0] ypos = icon_x_ypos * self.size[0]
pos = (xpos, ypos) pos = (xpos, ypos)
self.__draw_resized_path_at__(wpath + weathericons[icon_id] + ".jpeg", pos, size) self.__draw_resized_path_at__(
wpath + weathericons[icon_id] + ".jpeg", pos, size)
def __draw_no_response__ (self): def __draw_no_response__(self):
width = int(icon_width * self.size[0]) width = int(icon_width * self.size[0])
size = (width, width) size = (width, width)
xpos = icon_xpos * self.size[0] xpos = icon_xpos * self.size[0]
@ -130,27 +87,26 @@ class WeatherColumnDesign (DesignEntity):
self.__draw_resized_image_at__(no_response, pos, size) self.__draw_resized_image_at__(no_response, pos, size)
def __draw_resized_path_at__ (self, path, pos, size): def __draw_resized_path_at__(self, path, pos, size):
img = Image.open(path) img = Image.open(path)
self.__draw_resized_image_at__(img, pos, size) self.__draw_resized_image_at__(img, pos, size)
def __draw_resized_image_at__ (self, img, pos, size): def __draw_resized_image_at__(self, img, pos, size):
size = (int(size[0]), int(size[1])) size = (int(size[0]), int(size[1]))
resized_img = img.resize(size, resample=Image.LANCZOS) resized_img = img.resize(size, resample=Image.LANCZOS)
self.draw(resized_img, pos) self.draw(resized_img, pos)
def __get_unit__(self, tuple):
def __get_unit__ (self, tuple): if self.forecast.units == "metric" or self.forecast.units == "aviation":
if self.forecast.units == "metric":
return tuple[0] return tuple[0]
else: else:
return tuple[1] return tuple[1]
def __abs_co__ (self, coordinates): def __abs_co__(self, coordinates):
return (coordinates[0] * self.size[0], coordinates[1] * self.size[1]) return (coordinates[0] * self.size[0], coordinates[1] * self.size[1])
def __get_time__ (self, time): def __get_time__(self, time):
if hours == "24": if hours == "24":
return time.strftime('%H:%M') return time.strftime('%H:%M')
else: else:
return time.strftime('%I:%M') return time.strftime('%I:%M')

View file

@ -1,6 +1,7 @@
class WeatherForecast (object): class WeatherForecast (object):
"""Defines a weather forecast, independent of any implementation""" """Defines a weather forecast, independent of any implementation"""
def __init__ (self):
def __init__(self):
self.air_temperature = None self.air_temperature = None
self.air_pressure = None self.air_pressure = None
self.air_humidity = None self.air_humidity = None
@ -11,6 +12,7 @@ class WeatherForecast (object):
self.sunset = None self.sunset = None
self.moon_phase = None self.moon_phase = None
self.wind_speed = None self.wind_speed = None
self.wind_deg = None
self.clouds = None self.clouds = None
self.icon = None self.icon = None
@ -20,4 +22,4 @@ class WeatherForecast (object):
self.units = None self.units = None
self.datetime = None self.datetime = None
self.location = None self.location = None
self.fetch_datetime = None self.fetch_datetime = None

View file

@ -1,5 +1,5 @@
from DesignEntity import DesignEntity from DesignEntity import DesignEntity
from Assets import * from Assets import no_response, windicon, tempicon, sunseticon, sunriseicon, wpath, weathericons, humicon
from TextDesign import TextDesign from TextDesign import TextDesign
from settings import units, hours from settings import units, hours
@ -10,56 +10,86 @@ windiconspace = (0.206, 0)
sunriseplace = (0.55, 0) sunriseplace = (0.55, 0)
sunsetplace = (0.55, 0.486) sunsetplace = (0.55, 0.486)
class WeatherHeaderDesign (DesignEntity): class WeatherHeaderDesign (DesignEntity):
"""Defines a top area that displays basic weather information""" """Defines a top area that displays basic weather information"""
def __init__ (self, size, weather):
def __init__(self, size, weather):
super(WeatherHeaderDesign, self).__init__(size) super(WeatherHeaderDesign, self).__init__(size)
self.__weather__ = weather self.__weather__ = weather
self.__first_render__() self.__first_render__()
def __first_render__ (self): def __first_render__(self):
if self.__weather__.is_available() is False: if self.__weather__.is_available() is False:
self.__render_missing_connection__() self.__render_missing_connection__()
return return
cur_weather = self.__weather__.get_today_forecast() cur_weather = self.__weather__.get_today_forecast()
temperature = cur_weather.air_temperature + " " + self.__get_unit__(("°C", "°F")) if cur_weather == None:
windspeed = cur_weather.wind_speed + " " + self.__get_unit__(("km/h", "mph")) self.__render_missing_connection__()
return
self.__draw_text__(temperature, self.__abs_pos__((0.87, 0)), (50,35)) temperature = cur_weather.air_temperature + \
self.__draw_text__(windspeed, self.__abs_pos__((0.297, 0.05)), (100,35)) " " + self.__get_unit__(("°C", "°F"))
self.__draw_text__(self.__get_time__(cur_weather.sunrise), self.__abs_pos__((0.64,0)), (50,35)) if units == "aviation": # pick up aviation
self.__draw_text__(self.__get_time__(cur_weather.sunset), self.__abs_pos__((0.64,0.486)), (50,35)) if cur_weather.wind_deg == None:
self.__draw_text__(cur_weather.air_humidity + " %", self.__abs_pos__((0.87,0.486)), (50,35)) cur_weather.wind_deg = ""
self.__draw_text__(cur_weather.short_description, self.__abs_pos__((0.182,0.486)), (144,35)) elif len(cur_weather.wind_deg) == 1: # if deg is 2, add two zeros for format
cur_weather.wind_deg = "00" + cur_weather.wind_deg
elif len(cur_weather.wind_deg) == 2:
cur_weather.wind_deg = "0" + cur_weather.wind_deg
if int(cur_weather.wind_speed) < 10:
windspeed = cur_weather.wind_deg + "@" + "0" + cur_weather.wind_speed + \
self.__get_unit__(
("", "")) # added degrees, if wind<10 add a 0 to make two digit
else:
windspeed = cur_weather.wind_deg + "@" + cur_weather.wind_speed + \
self.__get_unit__(("", "")) # added degrees
else:
windspeed = cur_weather.wind_speed + " " + \
self.__get_unit__(("km/h", "mph"))
self.__draw_text__(temperature, self.__abs_pos__((0.87, 0)), (50, 35))
self.__draw_text__(windspeed, self.__abs_pos__(
(0.297, 0.05)), (100, 35))
self.__draw_text__(self.__get_time__(cur_weather.sunrise),
self.__abs_pos__((0.64, 0)), (50, 35))
self.__draw_text__(self.__get_time__(cur_weather.sunset),
self.__abs_pos__((0.64, 0.486)), (50, 35))
self.__draw_text__(cur_weather.air_humidity + " %",
self.__abs_pos__((0.87, 0.486)), (50, 35))
self.__draw_text__(cur_weather.short_description,
self.__abs_pos__((0.182, 0.486)), (144, 35))
self.draw(windicon, self.__abs_pos__(windiconspace)) self.draw(windicon, self.__abs_pos__(windiconspace))
self.draw(sunseticon, self.__abs_pos__(sunsetplace)) self.draw(sunseticon, self.__abs_pos__(sunsetplace))
self.draw(sunriseicon, self.__abs_pos__(sunriseplace)) self.draw(sunriseicon, self.__abs_pos__(sunriseplace))
self.draw(humicon, self.__abs_pos__(humplace)) self.draw(humicon, self.__abs_pos__(humplace))
self.draw(tempicon, self.__abs_pos__(tempplace)) self.draw(tempicon, self.__abs_pos__(tempplace))
self.draw_image(wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace)) self.draw_image(
wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace))
def __render_missing_connection__ (self): def __render_missing_connection__(self):
self.draw_image(no_response, self.__abs_pos__(wiconplace)) self.draw(no_response, self.__abs_pos__(wiconplace))
def __abs_pos__ (self, pos): def __abs_pos__(self, pos):
return (int(pos[0] * self.size[0]), int(pos[1] * self.size[1])) return (int(pos[0] * self.size[0]), int(pos[1] * self.size[1]))
def __draw_text__ (self, text, pos, size): def __draw_text__(self, text, pos, size):
txt = TextDesign(size, fontsize=18, text=text, verticalalignment="center", horizontalalignment="center") txt = TextDesign(size, fontsize=18, text=text,
verticalalignment="center", horizontalalignment="center")
txt.pos = pos txt.pos = pos
self.draw_design(txt) self.draw_design(txt)
def __get_unit__ (self, tuple): def __get_unit__(self, tuple):
if units == "metric": if units == "metric" or units == "aviation":
return tuple[0] return tuple[0]
else: else:
return tuple[1] return tuple[1]
def __get_time__ (self, time): def __get_time__(self, time):
if hours == "24": if hours == "24":
return time.strftime('%H:%M') return time.strftime('%H:%M')
else: else:
return time.strftime('%I:%M') return time.strftime('%I:%M')

View file

@ -1,9 +1,11 @@
from DataSourceInterface import DataSourceInterface from DataSourceInterface import DataSourceInterface
class WeatherInterface (DataSourceInterface): class WeatherInterface (DataSourceInterface):
"""Interface for fetching and processing weather forecast information.""" """Interface for fetching and processing weather forecast information."""
def get_forecast_in_days (self, offset_by_days, location=None):
def get_forecast_in_days(self, offset_by_days, location=None):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")
def get_today_forecast (self, location=None): def get_today_forecast(self, location=None):
raise NotImplementedError("Functions needs to be implemented") raise NotImplementedError("Functions needs to be implemented")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,37 +1,48 @@
""" To quickly get started, fill in the following details:""" """ To quickly get started, fill in the following details:"""
ical_urls = [ ical_urls = [
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
] ]
highlighted_ical_urls = [ highlighted_ical_urls = [
] ]
rss_feeds = [ rss_feeds = [
"http://feeds.bbci.co.uk/news/world/rss.xml#" ]
crypto_coins = [
] ]
api_key = "" api_key = ""
owm_paid_subscription = False owm_paid_subscription = False
location = "Julich, DE" location = "Berlin, DE"
week_starts_on = "Monday" week_starts_on = "Monday"
display_colours = "bwr" display_colours = "bwr"
language = "en" language = "en"
units = "metric" datetime_encoding = "UTF-8" # UTF-8
units = "metric" # aviation (celcius,degrees/knots), metric (celcius,kmh), imperial(f,mph)
hours = "24" hours = "24"
update_interval = "60" update_interval = 60
max_loop_count = 0 # From 0 to 86400 representing the maximum number the loop is executed, with 0 being unlimited
run_on_hour = True # If True, updates calendar every full hour, ignoring differing update_interval
"""DESIGN""" """DESIGN"""
font_size = 14 # does not affect every text
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
choosen_design = "month-overview" # month-overview, day-list line_thickness = 1 # 1-3 Thickness advised
choosen_design = "month-overview" # month-overview, day-list, day-view, day-focus-list, agenda-list, month-view, image-frame
general_settings = { # General settings that designs may use general_settings = { # General settings that designs may use
"info-area" : "rss", # empty, events, rss "info-area" : "rss", # empty, events, rss, crypto
"highlight-event-days" : True "highlight-event-days" : True,
"weather-info" : True,
"image-folder" : "",
"overlay-image" : "", # Size must be 384x640px with default display
"extra-excluded-urls" : []
} }
"""DEBUG""" """DEBUG"""
render_to_display = True render_to_display = True
render_to_file = False # Will be called "design_exported.png" in Calendar-directory render_to_file = False # Will be called "design_exported.png" in Calendar-directory
calibrate_hours = [0, 12, 18] calibrate_hours = [0, 12, 18]
print_technical_data = False

View file

@ -4,6 +4,21 @@ The order is from latest to oldest and structured in the following way:
* Version name with date of publishing * Version name with date of publishing
* Sections with either 'added', 'fixed', 'updated' and 'changed' * Sections with either 'added', 'fixed', 'updated' and 'changed'
## [1.7] Mid April 2019
### Added
* Added agenda-list panel
* Added day-view panel
* Added offline calendar and rss support
* Added more settings
* Added support for multi-day and repeating events
### Changed
* Improved internal event management
### Removed
* Bugs
## [1.6] Mid March 2019 ## [1.6] Mid March 2019
### Added ### Added

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View file

@ -21,7 +21,7 @@ if [ -z "$option" ]; then
fi fi
if [ "$option" = 3 ]; then if [ "$option" = 3 ]; then
echo -e "Removing the E-Paper software now..." echo -e "Removing the E-Paper software now..."
pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y pip3 uninstall feedparser -y && sudo pip3 uninstall feedparser -y && pip3 uninstall numpy -y && sudo pip3 uninstall numpy -y && pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y
if [ -e /etc/supervisor/conf.d/E-Paper.conf ]; then if [ -e /etc/supervisor/conf.d/E-Paper.conf ]; then
sudo rm /etc/supervisor/conf.d/E-Paper.conf sudo rm /etc/supervisor/conf.d/E-Paper.conf
fi fi
@ -81,8 +81,12 @@ if [ "$option" = 2 ]; then
echo -e "\e[1;36m"Installing a few required packages for the E-Paper Software"\e[0m" echo -e "\e[1;36m"Installing a few required packages for the E-Paper Software"\e[0m"
sudo pip3 install pyowm sudo pip3 install pyowm
sudo pip3 install ics sudo pip3 install ics
sudo pip3 install feedparser
sudo pip3 install numpy
pip3 install pyowm pip3 install pyowm
pip3 install ics pip3 install ics
pip3 install feedparser
pip3 install numpy
echo -e "\e[1;36m"Finished installing libraries"\e[0m" echo -e "\e[1;36m"Finished installing libraries"\e[0m"
fi fi

View file

@ -1,15 +1,13 @@
### This file contains features in planning for the next release ### This file contains features in planning for the next release
# For version 1.7 # For version 1.8
## Installer ## Installer
* Optimise the installer by adding a few more options when updating * Optimise the installer by adding a few more options when updating
## Main script ## Main script
* Implement weekly view (may take a lot of time) * Implement multi-calendar panel
* Implement feature to fetch tasks * Implement feature to fetch tasks
* Add code in E-Paper.py for fixing errors related to the iCalendar (ics.py is pretty picky)
* Fix a bug where past events are shown along with ones in the future
* Add support for ics files along with iCalendar URLs * Add support for ics files along with iCalendar URLs
* Allow connecting to the openweathermap API servers even when the SSL certificate has expired * Allow connecting to the openweathermap API servers even when the SSL certificate has expired
* Try using the weathericons.io font instead of icons * Try using the weathericons.io font instead of icons

View file

@ -1,27 +1,36 @@
# NOT ACTIVELY MAINTAINED ANY LONGER
Since [aceisace/Inkycal](https://github.com/aceisace/Inkycal) has grown significantly and is far superior in many ways, I will no longer focus on mainting my own fork. The ical implementation might also not be super reliable in this project.
# E-Paper Calendar # E-Paper Calendar
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online. This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online. It can also be used as an image frame.
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH). This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
**To get started, follow the instructions below.** **To get started, follow the instructions below.**
## News: ## News:
* **Version 1.7 released with two new designs, offline support and improved support for events** (Mid April 2019)
* **Specified README for this fork, added new design and many options** (Mid March 2019) * **Specified README for this fork, added new design and many options** (Mid March 2019)
* **Version 1.5 released with a new layout, displayed events and many back-end improvements** (Early February 2019) * **Version 1.5 released with a new layout, displayed events and many back-end improvements** (Early February 2019)
* **Added Support for the 2-Colour E-Paper Display as well!** (Late September 2018) * **Added Support for the 2-Colour E-Paper Display as well!** (Late September 2018)
* **Added Support for Raspbian Stretch lite.** (Late September 2018) * **Added Support for Raspbian Stretch lite.** (Late September 2018)
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/day-list_example.png" width="400"><img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/month-overview_example.png" width="400"> <img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/month-overview_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/agenda-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-view_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-focus-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/image-frame_example.png" width="270">
Left: Day-List Panel 1.: Day-List Panel&ensp;
Right: Month-Overview Panel 2.: Month-Overview Panel&ensp;
3.: Agenda-List Panel<br>
4.: Day-View Panel&ensp;
5.: Day-Focus-List Panel&ensp;
6.: Image-Frame Panel
## Main features ## Main features
* Display the date and a full monthly calendar or a list of today and the next days * Display a calendar with one of multiple designes
* Optionally get RSS-Feed fetched and shown * Optionally get RSS-Feed fetched and shown
* Syncronise events from any online calendar (like google, yahoo etc.) * Syncronise events from any online calendar (like google, yahoo, etc.)
* Get live weather data (including temperature, humidity, etc.) using openweathermap api * Get live weather data (including temperature, humidity, etc.) using openweathermap api
* Only show a slideshow of images
## Hardware required ## Hardware required
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm) * 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
@ -54,33 +63,52 @@ If the Installer should fail for any reason, kindly open an issue and paste the
This is how the installer will run: This is how the installer will run:
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/Installer-v1.2-screenshot.png" width="700"> <img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/Installer-v1.2-screenshot.png" width="700">
## Adding details to the programm ## Adding details to the programm
Once the packages are installed, navigate to the home directory, open 'E-Paper-Master' and open the file 'settings.py' inside the Calendar folder. Adjust the values as needed. You can use the table below as a reference. Once the packages are installed, navigate to the home directory, open 'E-Paper-Master' and open the file 'settings.py' inside the Calendar folder. Adjust the values as needed. You can use the table below as a reference.
### General
| Parameter | Description | | Parameter | Description |
| --- | --- | | --- | --- |
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma. | | ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma. |
| highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison the ical_urls. To add more than one URL, seperate each with a comma. | | highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison to the ical_urls. To add more than one URL, seperate each with a comma. |
| rss_feeds | All the sources for your rss-feed. To add more than one URL, seperate each with a comma. | | rss_feeds | All the sources for your rss-feed. To add more than one URL, seperate each with a comma. |
| api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info. | | api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info. |
| owm_paid_subscription | If you have a paid owm subscription you can set it to `true` and in some panels receive forecast information. | | owm_paid_subscription | If you have a paid owm subscription you can set it to `True` and in some panels receive forecast information. |
| location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). | | location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). |
| week_starts_on | When does the work start on your Region? Possible options are `"Monday"` or `"Sunday"`. | | week_starts_on | When does the work start in your region? Possible options are `"Monday"` or `"Sunday"`. |
| display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.| | display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.|
| language | Choosing the language allows changing the language of the month and week-icons. Possible options are `"en"` for english and `"de"` for german.| | language | Sets the language and the related locale for datetime-information. Some texts depend on additional translations that can be added to the dictionary-file.|
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"` or `"imperial"`. | | datetime_encoding | Sets the encoding that will be used in the datetime-texts (month, weekday, ...). Default is `"UTF-8"`. Not to confuse with the full locale, this is only the encoding. |
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"`, `"imperial"` or `"aviation"` (Celsius, Degree/Knots). |
|hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.| |hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.|
|update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.| |update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.|
|max_loop_count | From 0 to 86400 representing the maximum number of times the loop is going to be executed, with `0` being unlimited.|
|run_on_hour | If `True`, updates calendar every full hour, ignoring differing update_interval.|
## iCalendar ### Design
It is a bit tricky to set up the iCalendar so it works correctly without throwing any errors. If you encounter errors related to your iCalendar, please open up an issue and paste the error message there. | Parameter | Description |
| --- | --- |
| font_size | Varies the size of the font. Can be any number >0. In some cases the size of the font is fixed by the design. Default value is `14`. |
| font_boldness | Varies the boldness of the font. Can be one of `extralight`, `light`, `regular`, `semibold`, `bold` or `extrabold`. In some cases the boldness of the font is fixed by the design. Default value is `regular`. |
| choosen_design | Sets the desired panel design. |
| line_thickness | Varies the boldness of separation lines in the chosen design. |
| general_settings | A dictionary containing options that some designs use to optimize there design. Possible options are as follows: |
| `"info-area"` | Defines the content type of an additionaly info area on the design. Can be one of `"rss"`, `"events"` or empty, to remove this area or keep it clean. |
| `"highlight-event-days"` | If set to `True`, days with events are highlighted in contrast to days without events. |
| `"weather-info"` | If set to `False`, weather info areas disappear and make room for events/rss/etc. (depends on the design). |
| `"image-folder"` | Set a relative or absolute path to a folder containing images that you want to see fullscreen with the `"image-frame"` design activated. |
| `"overlay-image"` | Set a relative or absolute path to an image with the same size as the screen (default: 384x640px) to show some static information over every image shown in the `"image-frame"` design. If the overlay image is contained within the `"image-folder"`, it will not be included into the slideshow. |
| `"extra-excluded-urls"` | A list of calendar urls that may be excluded in some panels in certain areas. |
A more detailed section about the iCalendar will be added to the wiki soon, but for now, here are some suggestions to prevent error messages: ### Debug
1) Ensure your iCalendar URL is fine. If you receive an error showing error 404, it means the URL is wrong. | Parameter | Description |
2) If your existing iCalendar doesn't work at all, export the Calendar as a file, then create a new Calendar and import the file from before. | --- | --- |
3) If you receive errors related to 'alarm' or 'trigger', please make sure your iCalendar does not use reminders. The problem is that some actions are not supported by the Raspberry and cause errors. For example, the Rapsberry can't send a mail, make a noise or display a message as soon as an event starts. | render_to_display | Set to `True` it adds the e-paper display to the outputs. |
| render_to_file | Set to `True` it adds a image-file export to the outputs. The exported image can be found in `"/Calendar/design_exported.png"`. |
| calibrate_hours | A list containing all the hours in which the outputs get calibrated. That should prevent ghosting on e-paper displays. Takes a little while to execute. |
| print_technical_data | Set to `True` it writes start and stop time of the current update cyle and the duration on top of the panel. |
## Updating ## Updating
If you want to update to the latest version, run the Installer from above again and select the 'update' option. If you want to update to the latest version, run the Installer from above again and select the 'update' option.