forked from BenRoe/rpi-magicmirror-eink
-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.py
132 lines (116 loc) · 5.26 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env python3
import asyncio
import logging
import tempfile
import argparse
import sys
import numpy as np
from aiocron import crontab
from pyppeteer import launch
from PIL import Image
# TODO fix ignored KeyboardInterrupt when running in the the event loop (run_forever)
# TODO implement proper reset and shutdown of the screen when the systemd service is stopped or the raspberry pi is shut down
# TODO maybe a "last updated" time (just a clock module with a different header)
# Import the waveshare folder (containing the waveshare display drivers) without refactoring it to a module
# TODO maybe switch to a git submodule here and upgrade to the latest version:
# https://github.com/waveshare/e-Paper/blob/master/RaspberryPi%26JetsonNano/python/lib/waveshare_epd/epd7in5bc.py
sys.path.insert(0, './waveshare')
import epd7in5b
# Global config
display_width = 384 # Width of the display
display_height = 640 # Height of the display
is_portrait = True # True of the display should be in landscape mode (make sure to adjust the width and height accordingly)
wait_to_load = 60 # Page load timeout
wait_after_load = 18 # Time to evaluate the JS afte the page load (f.e. to lazy-load the calendar data)
url = 'http://localhost:8080' # URL to create the screenshot of
def reset_screen():
global display_width
global display_height
epd = epd7in5b.EPD()
epd.init()
epd.display_frame([0xFF] * int(display_width * display_height / 4))
epd.sleep()
async def create_screenshot(file_path):
global display_width
global display_height
global wait_to_load
global wait_after_load
global url
logging.debug('Creating screenshot')
browser = await launch(headless=True, args=['--no-sandbox', '--disable-setuid-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'], executablePath='/usr/bin/chromium-browser')
page = await browser.newPage()
await page.setViewport({
"width": display_width,
"height": display_height
})
await page.goto(url, timeout=wait_to_load * 1000)
await page.waitFor(wait_after_load * 1000);
await page.screenshot({'path': file_path})
await browser.close()
logging.debug('Finished creating screenshot')
def remove_aliasing_artefacts(image):
red = (255,000,000)
black = (000,000,000)
white = (255,255,255)
img = image.convert('RGB')
data = np.array(img)
# If the R value of the pixel is less than 50, make it black
black_mask = np.bitwise_and(data[:,:,0] <= 230, data[:,:,1] <= 135, data[:,:,2] <= 135)
# If the R value is higher than
red_mask = np.bitwise_and(data[:,:,0] >= 230, data[:,:,1] <= 135, data[:,:,2] <= 135)
# Everything else should be white
white_mask = np.bitwise_not(np.bitwise_or(red_mask, black_mask))
data[black_mask] = black
data[red_mask] = red
data[white_mask] = white
return Image.fromarray(data, mode='RGB')
async def refresh():
logging.info('Starting refresh.')
logging.debug('Initializing / waking screen.')
epd = epd7in5b.EPD()
epd.init()
with tempfile.NamedTemporaryFile(suffix='.png') as tmp_file:
logging.debug(f'Created temporary file at {tmp_file.name}.')
await create_screenshot(tmp_file.name)
logging.debug('Opening screenshot.')
image = Image.open(tmp_file)
# Replace all colors with are neither black nor red with white
image = remove_aliasing_artefacts(image)
# Rotate the image by 90°
if is_portrait:
logging.debug('Rotating image (portrait mode).')
image = image.rotate(90)
logging.debug('Sending image to screen.')
epd.display_frame(epd.get_frame_buffer(image))
logging.debug('Sending display back to sleep.')
epd.sleep()
logging.info('Refresh finished.')
def main():
try:
parser = argparse.ArgumentParser(description='Python EInk MagicMirror')
parser.add_argument('-d', '--debug', action='store_true', dest='debug',
help='Enable debug logs.', default=False)
parser.add_argument('-c', '--cron', action='store', dest='cron',
help='Sets a schedule using cron syntax')
parser.add_argument('-r', '--reset', action='store_true', dest='reset',
help='Ignore all other settings and just reset the screen.', default=False)
args = parser.parse_args()
level = logging.DEBUG if args.debug else logging.INFO
logging.basicConfig(level=level, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
if not args.reset:
if args.cron:
logging.info(f'Scheduling the refresh using the schedule "{args.cron}".')
crontab(args.cron, func=refresh)
# Initially refresh the display before relying on the schedule
asyncio.get_event_loop().run_until_complete(refresh())
asyncio.get_event_loop().run_forever()
else:
logging.info('Only running the refresh once.')
asyncio.get_event_loop().run_until_complete(refresh())
except KeyboardInterrupt:
logging.info('Shutting down after receiving a keyboard interrupt.')
finally:
logging.info('Resetting screen.')
reset_screen()
if __name__ == '__main__':
main()