Pierre-Olivier Mercier
b3f79020f4
All checks were successful
continuous-integration/drone/push Build is passing
258 lines
9.7 KiB
Python
258 lines
9.7 KiB
Python
##
|
|
# @filename : main.cpp
|
|
# @brief : 7.5inch e-paper display demo
|
|
# @author : Yehui from Waveshare
|
|
#
|
|
# Copyright (C) Waveshare July 28 2017
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documnetation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
##
|
|
|
|
from datetime import datetime, timedelta
|
|
import io
|
|
import locale
|
|
import logging
|
|
import math
|
|
import os.path
|
|
import time
|
|
|
|
locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')
|
|
|
|
NEXT_STOP_Y = 255
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
class WidgetPlacement:
|
|
|
|
def __init__(self, module, *args, size, position, **kwargs):
|
|
self.module = module
|
|
self.args = args
|
|
self.size = size
|
|
self.position = position
|
|
self.kwargs = kwargs
|
|
|
|
def byteToStr(v):
|
|
return chr((v & 0xF) + 97) + chr(((v >> 4) & 0xF) + 97)
|
|
|
|
def wordToStr(v):
|
|
return byteToStr(v & 0xFF) + byteToStr((v >> 8) & 0xFF)
|
|
|
|
def main(only_on_coming_evt=False, ignore_module=[], force_coming_event=True, expand_alerts=False, send_to=None, **config_args):
|
|
image = Image.new('1', (480, 800), 255)
|
|
#image = Image.new('L', (480, 800), 'white')
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
shape = []
|
|
|
|
import modules
|
|
config = modules.Config(**config_args)
|
|
|
|
# alerts
|
|
alerts = []
|
|
|
|
# Weather
|
|
|
|
# Current Weather
|
|
from modules.weather import WeatherJumboCurrentModule
|
|
shape.append(WidgetPlacement(WeatherJumboCurrentModule, size=(480,150), position=(0,0)))
|
|
|
|
# rule
|
|
shape.append(WidgetPlacement(modules.RuleModule, size=(450, 1), position=(30, 142)))
|
|
|
|
# pluie
|
|
from modules.weather import WeatherRainModule
|
|
shape.append(WidgetPlacement(WeatherRainModule, size=(480-int(480/1.6), 94), position=(0, 155)))
|
|
|
|
# moon phase
|
|
from modules.weather import WeatherMoonPhaseModule
|
|
shape.append(WidgetPlacement(WeatherMoonPhaseModule, size=(65, 65), position=(0, 113)))
|
|
|
|
# ical
|
|
from modules.ical import IcalModule
|
|
shape.append(WidgetPlacement(IcalModule, config, size=(480-int(480/1.6), 255), position=(0, 250)))
|
|
|
|
occuped_space = 0
|
|
if "IcalModule" not in ignore_module or force_coming_event:
|
|
ical = IcalModule(config)
|
|
evt_coming = force_coming_event or ical.non_local_event_coming(config) or ical.local_event_ending(config)
|
|
if evt_coming:
|
|
from modules.ratp import RATPNextStopModule
|
|
nstops, nalerts = RATPNextStopModule().draw_module(config, ["RB/cite+universitaire", "M7/porte+d+italie", "B125/raspail+++jean+jaures+++saint+eloi"], int(480/1.6), 275)
|
|
image.paste(nstops, (480-int(480/1.6), NEXT_STOP_Y))
|
|
occuped_space = nstops.height
|
|
alerts += nalerts
|
|
elif only_on_coming_evt:
|
|
# stop here in this case
|
|
return
|
|
|
|
if occuped_space < 250:
|
|
# weekly weather
|
|
from modules.weather import WeeklyWeatherModule
|
|
shape.append(WidgetPlacement(WeeklyWeatherModule, size=(int(480/1.6), 265), position=(480-int(480/1.6), NEXT_STOP_Y + occuped_space + (5 if occuped_space else 0))))
|
|
|
|
# RATP weather
|
|
major_lines = ["M7", "M14", "RB", "TT3A"]
|
|
weekday = datetime.now().weekday()
|
|
if weekday == 1 or weekday == 3 or weekday == 5:
|
|
major_lines.append("M6")
|
|
from modules.ratp import RATPWeatherModule
|
|
shape.append(WidgetPlacement(RATPWeatherModule, size=(int(480/1.6), 94), position=(480-int(480/1.6), 155)))
|
|
alerts.append({"module": RATPWeatherModule, "args_module": [major_lines], "args_func": [config]})
|
|
|
|
# Toolbar
|
|
from modules.weather import WeatherToolbarModule
|
|
shape.append(WidgetPlacement(WeatherToolbarModule, size=(480, 50), position=(0, 530)))
|
|
|
|
from modules.sncf import SNCFWeatherModule
|
|
alerts.append({"module": SNCFWeatherModule, "args_func": [config, "normandie"]})
|
|
from modules.weather import WeatherAlerts
|
|
alerts.append(WeatherAlerts)
|
|
|
|
# sunrise/set
|
|
from modules.weather import WeatherSunModule
|
|
shape.append(WidgetPlacement(WeatherSunModule, size=(int(480/2), 20), position=(0, 780), start_align=5))
|
|
|
|
# Draw shapes
|
|
last_height = 0
|
|
last_y = 0
|
|
for s in shape:
|
|
if s.module.__name__ in ignore_module:
|
|
logging.info("Skip module " + s.module.__name__ + ", as requested by arguments")
|
|
continue
|
|
|
|
try:
|
|
x,y = s.position
|
|
if y < 0:
|
|
y = last_height + last_y
|
|
width, height = s.size
|
|
if height < 0:
|
|
height = 480 - last_height - last_y
|
|
|
|
img = s.module(*s.args).draw_module(config, width, height, **s.kwargs)
|
|
if img.mode == "RGBA":
|
|
image.paste(img, (x,y), img)
|
|
else:
|
|
image.paste(img, (x,y))
|
|
except BaseException as e:
|
|
logging.exception(e)
|
|
alerts.append({
|
|
"title": "Impossible de dessiner " + s.module.__name__,
|
|
"description": type(e).__name__ + ": " + (e.message if hasattr(e, 'message') else str(e)),
|
|
"icon": "weather/wi-earthquake.png",
|
|
})
|
|
|
|
|
|
from modules import AlertsModule
|
|
mod, more_alerts = AlertsModule(config, alerts, ignore_module).draw_module(config, 480, min(317, 640 - NEXT_STOP_Y - occuped_space))
|
|
if NEXT_STOP_Y + occuped_space + mod.height > 580 or mod.height > 260:
|
|
image.paste(mod, (0, 640-mod.height), mod)
|
|
elif mod.height < 100:
|
|
image.paste(mod, (0, 542-mod.height), mod)
|
|
else:
|
|
image.paste(mod, (0, 580-mod.height), mod)
|
|
|
|
# températures
|
|
if "WeatherTemperatureModule" not in ignore_module:
|
|
from modules.weather import WeatherTemperatureModule
|
|
if NEXT_STOP_Y + occuped_space + mod.height > 580 or mod.height > 260:
|
|
image.paste(WeatherTemperatureModule().draw_module(config, 480, 140), (0, 640))
|
|
else:
|
|
image.paste(WeatherTemperatureModule().draw_module(config, 480, 200), (0, 580))
|
|
|
|
if expand_alerts and more_alerts > 0:
|
|
mod, more_alerts = AlertsModule(config, alerts, ignore_module).draw_module(config, 480, 785 - NEXT_STOP_Y - occuped_space)
|
|
image.paste(mod, (0, NEXT_STOP_Y + occuped_space), mod)
|
|
|
|
|
|
fnt = ImageFont.truetype(config.fnt_R_path, 11)
|
|
draw.text(
|
|
(475, 798),
|
|
"Dernière génération le " + datetime.now().strftime("%c"),
|
|
fill="black", anchor="rb", font=fnt
|
|
)
|
|
|
|
logging.info("image generated")
|
|
if send_to is not None:
|
|
import urllib.request
|
|
|
|
def do_req(url):
|
|
with urllib.request.urlopen(url, b""):
|
|
pass
|
|
|
|
do_req(send_to + "EPDw_")
|
|
pixels = image.load()
|
|
width, height = image.size
|
|
buf = [0xFF] * (int(width/8) * height)
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if pixels[x, y] == 0:
|
|
buf[int((y + (width - x - 1) * height) / 8)] &= ~(0x80 >> (y % 8))
|
|
for i in range(math.ceil(len(buf) / 500)):
|
|
data = ''.join([byteToStr(b) for b in buf[500*i:500*(i+1)]])
|
|
size = wordToStr(len(data))
|
|
do_req(send_to + data + size + "LOAD_")
|
|
do_req(send_to + "SHOW_")
|
|
|
|
else:
|
|
try:
|
|
import epd7in5
|
|
|
|
epd = epd7in5.EPD()
|
|
epd.init()
|
|
logging.info("initialized")
|
|
|
|
epd.display(epd.getbuffer(image))
|
|
|
|
logging.info("time to sleep")
|
|
epd.sleep()
|
|
|
|
except:
|
|
image.save("screen.png")
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='E-ink status generator.')
|
|
|
|
parser.add_argument('--ignore-module', '-I', nargs="*", default=[],
|
|
help='Ignore the given modules')
|
|
parser.add_argument('--expand-alerts', '-A', action='store_const', const=True,
|
|
help='If needed, expand alerts on graphic')
|
|
parser.add_argument('--force-coming-evt', '-E', action='store_const', const=True,
|
|
help='Consider an event coming, whatever calendar says')
|
|
parser.add_argument('--only-on-coming-evt', '-O', action='store_const', const=True,
|
|
help='Refresh screen only if there is upcoming event')
|
|
parser.add_argument('--cache-timeout', '-C', type=int, default=90,
|
|
help='How many minutes to keep the infos in cache')
|
|
parser.add_argument('--max-cache-timeout', type=int, default=120,
|
|
help='Maximum time to serve the infos in cache in case of issue')
|
|
parser.add_argument('--send-to', help='Send the request to a HTTP server')
|
|
|
|
args = parser.parse_args()
|
|
|
|
main(
|
|
args.only_on_coming_evt or (datetime.now().minute % 10 == 0 and not datetime.now().minute % 30 == 0),
|
|
args.ignore_module,
|
|
args.force_coming_evt,
|
|
args.expand_alerts,
|
|
cache_timeout=args.cache_timeout,
|
|
max_cache_timeout=args.max_cache_timeout,
|
|
send_to=args.send_to,
|
|
)
|