## # @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, )