From 3bedfc1180cc1de4817c909400c21183ce0d378c Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:13:34 +0000 Subject: [PATCH 001/162] my changes --- .gitignore | 0 clearScreen.py | 27 +- driver/epd7in5b-old.py | 212 ++++++++++ driver/epd7in5b.py | 418 ++++++++++---------- driver/epdconfig.py | 154 ++++++++ infowindow.py | 197 +++++---- mod_infowindow/.gitignore | 9 + mod_infowindow/clearScreen.py | 24 ++ mod_infowindow/infowindow.py | 214 +++++----- mod_infowindow/mod_calendar/__init__.py | 0 mod_infowindow/mod_calendar/mod_google.py | 82 ++++ mod_infowindow/mod_infowindow/__init__.py | 0 mod_infowindow/mod_infowindow/infowindow.py | 113 ++++++ mod_infowindow/mod_todo/__init__.py | 0 mod_infowindow/mod_todo/mod_google.py | 37 ++ mod_infowindow/mod_todo/mod_grocy.py | 40 ++ mod_infowindow/mod_todo/mod_teamwork.py | 38 ++ mod_infowindow/mod_todo/mod_todoist.py | 34 ++ mod_infowindow/mod_utils/__init__.py | 0 mod_infowindow/mod_utils/iw_utils.py | 21 + mod_infowindow/mod_utils/mod_google_auth.py | 75 ++++ mod_infowindow/mod_weather/__init__.py | 0 mod_infowindow/mod_weather/mod_owm.py | 109 +++++ mod_infowindow/resources/black.png | Bin 0 -> 662 bytes mod_infowindow/resources/red.png | Bin 0 -> 1101 bytes mod_infowindow/resources/white.png | Bin 0 -> 1100 bytes mod_todo/mod_grocy.py | 40 ++ mod_todo/mod_todoist.py | 4 +- 28 files changed, 1422 insertions(+), 426 deletions(-) mode change 100644 => 100755 .gitignore create mode 100755 driver/epd7in5b-old.py create mode 100755 driver/epdconfig.py create mode 100755 mod_infowindow/.gitignore create mode 100755 mod_infowindow/clearScreen.py create mode 100755 mod_infowindow/mod_calendar/__init__.py create mode 100755 mod_infowindow/mod_calendar/mod_google.py create mode 100755 mod_infowindow/mod_infowindow/__init__.py create mode 100755 mod_infowindow/mod_infowindow/infowindow.py create mode 100755 mod_infowindow/mod_todo/__init__.py create mode 100755 mod_infowindow/mod_todo/mod_google.py create mode 100755 mod_infowindow/mod_todo/mod_grocy.py create mode 100755 mod_infowindow/mod_todo/mod_teamwork.py create mode 100755 mod_infowindow/mod_todo/mod_todoist.py create mode 100755 mod_infowindow/mod_utils/__init__.py create mode 100755 mod_infowindow/mod_utils/iw_utils.py create mode 100755 mod_infowindow/mod_utils/mod_google_auth.py create mode 100755 mod_infowindow/mod_weather/__init__.py create mode 100755 mod_infowindow/mod_weather/mod_owm.py create mode 100755 mod_infowindow/resources/black.png create mode 100755 mod_infowindow/resources/red.png create mode 100755 mod_infowindow/resources/white.png create mode 100755 mod_todo/mod_grocy.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/clearScreen.py b/clearScreen.py index af59ca7..67d9628 100644 --- a/clearScreen.py +++ b/clearScreen.py @@ -1,3 +1,24 @@ -from mod_infowindow import infowindow -iw = infowindow.InfoWindow() -iw.display() +#!/usr/bin/python +# -*- coding:utf-8 -*- +import sys +import os +picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic') +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +from driver import epd7in5b +import time +from PIL import Image,ImageDraw,ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +logging.info("epd7in5b_HD Demo") + +epd = epd7in5b.EPD() + +logging.info("init and Clear") +epd.init() +epd.Clear() diff --git a/driver/epd7in5b-old.py b/driver/epd7in5b-old.py new file mode 100755 index 0000000..2791cf9 --- /dev/null +++ b/driver/epd7in5b-old.py @@ -0,0 +1,212 @@ +## + # @filename : epd7in5.py + # @brief : Implements for Dual-color e-paper library + # @author : Yehui from Waveshare + # + # Copyright (C) Waveshare July 10 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. + # + +import epdif +from PIL import Image +import RPi.GPIO as GPIO +import logging + +# Display resolution +EPD_WIDTH = 640 +EPD_HEIGHT = 384 + +# EPD7IN5 commands +PANEL_SETTING = 0x00 +POWER_SETTING = 0x01 +POWER_OFF = 0x02 +POWER_OFF_SEQUENCE_SETTING = 0x03 +POWER_ON = 0x04 +POWER_ON_MEASURE = 0x05 +BOOSTER_SOFT_START = 0x06 +DEEP_SLEEP = 0x07 +DATA_START_TRANSMISSION_1 = 0x10 +DATA_STOP = 0x11 +DISPLAY_REFRESH = 0x12 +IMAGE_PROCESS = 0x13 +LUT_FOR_VCOM = 0x20 +LUT_BLUE = 0x21 +LUT_WHITE = 0x22 +LUT_GRAY_1 = 0x23 +LUT_GRAY_2 = 0x24 +LUT_RED_0 = 0x25 +LUT_RED_1 = 0x26 +LUT_RED_2 = 0x27 +LUT_RED_3 = 0x28 +LUT_XON = 0x29 +PLL_CONTROL = 0x30 +TEMPERATURE_SENSOR_COMMAND = 0x40 +TEMPERATURE_CALIBRATION = 0x41 +TEMPERATURE_SENSOR_WRITE = 0x42 +TEMPERATURE_SENSOR_READ = 0x43 +VCOM_AND_DATA_INTERVAL_SETTING = 0x50 +LOW_POWER_DETECTION = 0x51 +TCON_SETTING = 0x60 +TCON_RESOLUTION = 0x61 +SPI_FLASH_CONTROL = 0x65 +REVISION = 0x70 +GET_STATUS = 0x71 +AUTO_MEASUREMENT_VCOM = 0x80 +READ_VCOM_VALUE = 0x81 +VCM_DC_SETTING = 0x82 + +class EPD: + def __init__(self): + self.reset_pin = epdif.RST_PIN + self.dc_pin = epdif.DC_PIN + self.busy_pin = epdif.BUSY_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + def digital_write(self, pin, value): + epdif.epd_digital_write(pin, value) + + def digital_read(self, pin): + return epdif.epd_digital_read(pin) + + def delay_ms(self, delaytime): + epdif.epd_delay_ms(delaytime) + + def send_command(self, command): + self.digital_write(self.dc_pin, GPIO.LOW) + # the parameter type is list but not int + # so use [command] instead of command + epdif.spi_transfer([command]) + + def send_data(self, data): + self.digital_write(self.dc_pin, GPIO.HIGH) + # the parameter type is list but not int + # so use [data] instead of data + epdif.spi_transfer([data]) + + def init(self): + if (epdif.epd_init() != 0): + return -1 + self.reset() + self.send_command(POWER_SETTING) + self.send_data(0x37) + self.send_data(0x00) + self.send_command(PANEL_SETTING) + self.send_data(0xCF) + self.send_data(0x08) + self.send_command(BOOSTER_SOFT_START) + self.send_data(0xc7) + self.send_data(0xcc) + self.send_data(0x28) + self.send_command(POWER_ON) + self.wait_until_idle() + self.send_command(PLL_CONTROL) + self.send_data(0x3c) + self.send_command(TEMPERATURE_CALIBRATION) + self.send_data(0x00) + self.send_command(VCOM_AND_DATA_INTERVAL_SETTING) + self.send_data(0x77) + self.send_command(TCON_SETTING) + self.send_data(0x22) + self.send_command(TCON_RESOLUTION) + self.send_data(0x02) #source 640 + self.send_data(0x80) + self.send_data(0x01) #gate 384 + self.send_data(0x80) + self.send_command(VCM_DC_SETTING) + self.send_data(0x1E) #decide by LUT file + self.send_command(0xe5) #FLASH MODE + self.send_data(0x03) + + def wait_until_idle(self): + while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle + #logging.debug("DRIVER: (wait_until_idle)") + #self.delay_ms(100) + self.delay_ms(50) + + def reset(self): + self.digital_write(self.reset_pin, GPIO.LOW) # module reset + self.delay_ms(200) + self.digital_write(self.reset_pin, GPIO.HIGH) + self.delay_ms(200) + + def get_frame_buffer(self, image): + buf = [0x00] * int(self.width * self.height / 4) + # Set buffer to value of Python Imaging Library image. + # Image must be in mode L. + image_grayscale = image.convert('L') + imwidth, imheight = image_grayscale.size + if imwidth != self.width or imheight != self.height: + raise ValueError('Image must be same dimensions as display \ + ({0}x{1}).' .format(self.width, self.height)) + + pixels = image_grayscale.load() + for y in range(self.height): + for x in range(self.width): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] < 64: # black + buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) + elif pixels[x, y] < 192: # convert gray to red + buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) + buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2) + else: # white + buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2) + return buf + + def display_frame(self, frame_buffer): + self.send_command(DATA_START_TRANSMISSION_1) + logging.debug("DRIVER: ENTERING FOR LOOP") + for i in range(0, int(self.width / 4 * self.height)): + temp1 = frame_buffer[i] + j = 0 + while (j < 4): + if ((temp1 & 0xC0) == 0xC0): + temp2 = 0x03 + elif ((temp1 & 0xC0) == 0x00): + temp2 = 0x00 + else: + temp2 = 0x04 + temp2 = (temp2 << 4) & 0xFF + temp1 = (temp1 << 2) & 0xFF + j += 1 + if((temp1 & 0xC0) == 0xC0): + temp2 |= 0x03 + elif ((temp1 & 0xC0) == 0x00): + temp2 |= 0x00 + else: + temp2 |= 0x04 + temp1 = (temp1 << 2) & 0xFF + self.send_data(temp2) + j += 1 + logging.debug("SENDING DISPLAY_REFRESH COMMAND") + self.send_command(DISPLAY_REFRESH) + logging.debug("DELAY 100 MS") + self.delay_ms(100) + logging.debug("WAIT UNTIL IDLE") + self.wait_until_idle() + + def sleep(self): + self.send_command(POWER_OFF) + self.wait_until_idle() + self.send_command(DEEP_SLEEP) + self.send_data(0xa5) + +### END OF FILE ### + diff --git a/driver/epd7in5b.py b/driver/epd7in5b.py index 2791cf9..5261770 100644 --- a/driver/epd7in5b.py +++ b/driver/epd7in5b.py @@ -1,212 +1,206 @@ -## - # @filename : epd7in5.py - # @brief : Implements for Dual-color e-paper library - # @author : Yehui from Waveshare - # - # Copyright (C) Waveshare July 10 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. - # - -import epdif -from PIL import Image -import RPi.GPIO as GPIO -import logging - -# Display resolution -EPD_WIDTH = 640 -EPD_HEIGHT = 384 - -# EPD7IN5 commands -PANEL_SETTING = 0x00 -POWER_SETTING = 0x01 -POWER_OFF = 0x02 -POWER_OFF_SEQUENCE_SETTING = 0x03 -POWER_ON = 0x04 -POWER_ON_MEASURE = 0x05 -BOOSTER_SOFT_START = 0x06 -DEEP_SLEEP = 0x07 -DATA_START_TRANSMISSION_1 = 0x10 -DATA_STOP = 0x11 -DISPLAY_REFRESH = 0x12 -IMAGE_PROCESS = 0x13 -LUT_FOR_VCOM = 0x20 -LUT_BLUE = 0x21 -LUT_WHITE = 0x22 -LUT_GRAY_1 = 0x23 -LUT_GRAY_2 = 0x24 -LUT_RED_0 = 0x25 -LUT_RED_1 = 0x26 -LUT_RED_2 = 0x27 -LUT_RED_3 = 0x28 -LUT_XON = 0x29 -PLL_CONTROL = 0x30 -TEMPERATURE_SENSOR_COMMAND = 0x40 -TEMPERATURE_CALIBRATION = 0x41 -TEMPERATURE_SENSOR_WRITE = 0x42 -TEMPERATURE_SENSOR_READ = 0x43 -VCOM_AND_DATA_INTERVAL_SETTING = 0x50 -LOW_POWER_DETECTION = 0x51 -TCON_SETTING = 0x60 -TCON_RESOLUTION = 0x61 -SPI_FLASH_CONTROL = 0x65 -REVISION = 0x70 -GET_STATUS = 0x71 -AUTO_MEASUREMENT_VCOM = 0x80 -READ_VCOM_VALUE = 0x81 -VCM_DC_SETTING = 0x82 - -class EPD: - def __init__(self): - self.reset_pin = epdif.RST_PIN - self.dc_pin = epdif.DC_PIN - self.busy_pin = epdif.BUSY_PIN - self.width = EPD_WIDTH - self.height = EPD_HEIGHT - - def digital_write(self, pin, value): - epdif.epd_digital_write(pin, value) - - def digital_read(self, pin): - return epdif.epd_digital_read(pin) - - def delay_ms(self, delaytime): - epdif.epd_delay_ms(delaytime) - - def send_command(self, command): - self.digital_write(self.dc_pin, GPIO.LOW) - # the parameter type is list but not int - # so use [command] instead of command - epdif.spi_transfer([command]) - - def send_data(self, data): - self.digital_write(self.dc_pin, GPIO.HIGH) - # the parameter type is list but not int - # so use [data] instead of data - epdif.spi_transfer([data]) - - def init(self): - if (epdif.epd_init() != 0): - return -1 - self.reset() - self.send_command(POWER_SETTING) - self.send_data(0x37) - self.send_data(0x00) - self.send_command(PANEL_SETTING) - self.send_data(0xCF) - self.send_data(0x08) - self.send_command(BOOSTER_SOFT_START) - self.send_data(0xc7) - self.send_data(0xcc) - self.send_data(0x28) - self.send_command(POWER_ON) - self.wait_until_idle() - self.send_command(PLL_CONTROL) - self.send_data(0x3c) - self.send_command(TEMPERATURE_CALIBRATION) - self.send_data(0x00) - self.send_command(VCOM_AND_DATA_INTERVAL_SETTING) - self.send_data(0x77) - self.send_command(TCON_SETTING) - self.send_data(0x22) - self.send_command(TCON_RESOLUTION) - self.send_data(0x02) #source 640 - self.send_data(0x80) - self.send_data(0x01) #gate 384 - self.send_data(0x80) - self.send_command(VCM_DC_SETTING) - self.send_data(0x1E) #decide by LUT file - self.send_command(0xe5) #FLASH MODE - self.send_data(0x03) - - def wait_until_idle(self): - while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle - #logging.debug("DRIVER: (wait_until_idle)") - #self.delay_ms(100) - self.delay_ms(50) - - def reset(self): - self.digital_write(self.reset_pin, GPIO.LOW) # module reset - self.delay_ms(200) - self.digital_write(self.reset_pin, GPIO.HIGH) - self.delay_ms(200) - - def get_frame_buffer(self, image): - buf = [0x00] * int(self.width * self.height / 4) - # Set buffer to value of Python Imaging Library image. - # Image must be in mode L. - image_grayscale = image.convert('L') - imwidth, imheight = image_grayscale.size - if imwidth != self.width or imheight != self.height: - raise ValueError('Image must be same dimensions as display \ - ({0}x{1}).' .format(self.width, self.height)) - - pixels = image_grayscale.load() - for y in range(self.height): - for x in range(self.width): - # Set the bits for the column of pixels at the current position. - if pixels[x, y] < 64: # black - buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) - elif pixels[x, y] < 192: # convert gray to red - buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) - buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2) - else: # white - buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2) - return buf - - def display_frame(self, frame_buffer): - self.send_command(DATA_START_TRANSMISSION_1) - logging.debug("DRIVER: ENTERING FOR LOOP") - for i in range(0, int(self.width / 4 * self.height)): - temp1 = frame_buffer[i] - j = 0 - while (j < 4): - if ((temp1 & 0xC0) == 0xC0): - temp2 = 0x03 - elif ((temp1 & 0xC0) == 0x00): - temp2 = 0x00 - else: - temp2 = 0x04 - temp2 = (temp2 << 4) & 0xFF - temp1 = (temp1 << 2) & 0xFF - j += 1 - if((temp1 & 0xC0) == 0xC0): - temp2 |= 0x03 - elif ((temp1 & 0xC0) == 0x00): - temp2 |= 0x00 - else: - temp2 |= 0x04 - temp1 = (temp1 << 2) & 0xFF - self.send_data(temp2) - j += 1 - logging.debug("SENDING DISPLAY_REFRESH COMMAND") - self.send_command(DISPLAY_REFRESH) - logging.debug("DELAY 100 MS") - self.delay_ms(100) - logging.debug("WAIT UNTIL IDLE") - self.wait_until_idle() - - def sleep(self): - self.send_command(POWER_OFF) - self.wait_until_idle() - self.send_command(DEEP_SLEEP) - self.send_data(0xa5) - -### END OF FILE ### - +# ***************************************************************************** +# * | File : epd7in5bc_HD.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-20 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# 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. +# + + +import logging +from . import epdconfig + +# Display resolution +EPD_WIDTH = 880 +EPD_HEIGHT = 528 + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(4) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(200) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + logging.debug("e-Paper busy") + busy = epdconfig.digital_read(self.busy_pin) + while(busy == 1): + busy = epdconfig.digital_read(self.busy_pin) + epdconfig.delay_ms(200) + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.send_command(0x12); #SWRESET + self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal + + self.send_command(0x46); # Auto Write RAM + self.send_data(0xF7); + self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal + + self.send_command(0x47); # Auto Write RAM + self.send_data(0xF7); + self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal + + self.send_command(0x0C); # Soft start setting + self.send_data(0xAE); + self.send_data(0xC7); + self.send_data(0xC3); + self.send_data(0xC0); + self.send_data(0x40); + + self.send_command(0x01); # Set MUX as 527 + self.send_data(0xAF); + self.send_data(0x02); + self.send_data(0x01); + + self.send_command(0x11); # Data entry mode + self.send_data(0x01); + + self.send_command(0x44); + self.send_data(0x00); # RAM x address start at 0 + self.send_data(0x00); + self.send_data(0x6F); # RAM x address end at 36Fh -> 879 + self.send_data(0x03); + self.send_command(0x45); + self.send_data(0xAF); # RAM y address start at 20Fh; + self.send_data(0x02); + self.send_data(0x00); # RAM y address end at 00h; + self.send_data(0x00); + + self.send_command(0x3C); # VBD + self.send_data(0x01); # LUT1, for white + + self.send_command(0x18); + self.send_data(0X80); + self.send_command(0x22); + self.send_data(0XB1); #Load Temperature and waveform setting. + self.send_command(0x20); + self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal + + self.send_command(0x4E); + self.send_data(0x00); + self.send_data(0x00); + self.send_command(0x4F); + self.send_data(0xAF); + self.send_data(0x02); + + return 0 + + def getbuffer(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width/8) * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + logging.debug('imwidth = %d imheight = %d ',imwidth, imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Horizontal") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif(imwidth == self.height and imheight == self.width): + logging.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, imageblack, imagered): + self.send_command(0x4F); + self.send_data(0xAf); + + self.send_command(0x24) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imageblack[i]); + + + self.send_command(0x26) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(~imagered[i]); + + self.send_command(0x22); + self.send_data(0xC7); #Load LUT from MCU(0x32) + self.send_command(0x20); + epdconfig.delay_ms(200); #!!!The delay here is necessary, 200uS at least!!! + self.ReadBusy(); + + def Clear(self): + self.send_command(0x4F); + self.send_data(0xAf); + + self.send_command(0x24) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xff); + + + self.send_command(0x26) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00); + + self.send_command(0x22); + self.send_data(0xC7); #Load LUT from MCU(0x32) + self.send_command(0x20); + epdconfig.delay_ms(200); #!!!The delay here is necessary, 200uS at least!!! + self.ReadBusy(); + + def sleep(self): + self.send_command(0x10); #deep sleep + self.send_data(0x01); + + def Dev_exit(self): + epdconfig.module_exit() +### END OF FILE ### + diff --git a/driver/epdconfig.py b/driver/epdconfig.py new file mode 100755 index 0000000..861f43d --- /dev/null +++ b/driver/epdconfig.py @@ -0,0 +1,154 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-21 +# * | Info : +# ****************************************************************************** +# 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. +# + +import os +import logging +import sys +import time + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import spidev + import RPi.GPIO + + self.GPIO = RPi.GPIO + + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(pin) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.writebytes(data) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + + +### END OF FILE ### diff --git a/infowindow.py b/infowindow.py index 89098bd..2773d61 100755 --- a/infowindow.py +++ b/infowindow.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2 +# 880 528 import sys import os.path @@ -13,9 +14,9 @@ # CALENDAR: mod_google, mod_ical # WEATHER: mod_owm, mod_wunderground from mod_utils import iw_utils -from mod_todo import mod_google as modTodo # TODO +from mod_todo import mod_todoist as modTodo # TODO from mod_calendar import mod_google as modCalendar # CALENDAR -from mod_weather import mod_owm as modWeather # WEATHER +from mod_todo import mod_grocy as modGrocy # TODO: Create dictionaries for API args. so that they can be custom. @@ -28,6 +29,7 @@ rotation = config_data["general"]["rotation"] charset = config_data["general"]["charset"] todo_opts = config_data["todo"] +grocy_opts = config_data["grocy"] calendar_opts = config_data["calendar"] weather_opts = config_data["weather"] infowindow_opts = {} @@ -49,101 +51,118 @@ # display since this will run headless most of the time. This gives the user # enough info to know that they need to troubleshoot. def HandleException(et, val, tb): - iw = infowindow.InfoWindow() - iw.text(0, 10, "EXCEPTION IN PROGRAM", 'robotoBlack18', 'black') - iw.text(0, 30, val.encode(charset).strip(), 'robotoBlack18', 'black') - iw.text(0, 60, "Please run program from command line interactivly to resolve", 'robotoBlack18', 'black') + red = infowindow.InfoWindow() + red.text(0, 10, "EXCEPTION IN PROGRAM", 'robotoBlack18', 'black') + red.text(0, 30, val.encode(charset).strip(), 'robotoBlack18', 'black') + red.text(0, 60, "Please run program from command line interactivly to resolve", 'robotoBlack18', 'black') print("EXCEPTION IN PROGRAM ==================================") print("error message: %s" % val) print("type: %s" % et) print("traceback: %s" % tb) print("line: %s" % tb.lineno) print("END EXCEPTION =========================================") - iw.display(rotation) + red.display(rotation) sys.excepthook = HandleException # helper to calculate max char width and height -def get_max_char_size(iw, chars, font): +def get_max_char_size(black, chars, font): max_x = 0 max_y = 0 for char in chars: - (x, y) = iw.getFont(font).getsize(char) + (x, y) = black.getFont(font).getsize(char) if x > max_x: max_x = x if y > max_y: max_y = y return max_x, max_y - # Main Program ################################################################ def main(): # Instantiate API modules todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) - weather = modWeather.Weather(weather_opts) + grocy = modGrocy.test(grocy_opts) # Setup e-ink initial drawings - iw = infowindow.InfoWindow(infowindow_opts) - - # Set some things - calendar_date_font = "robotoRegular14" - calendar_entry_font = "robotoRegular18" - tasks_font = "robotoRegular18" - - # Weather Grid - temp_rect_width = 102 - temp_rect_left = (iw.width / 2) - (temp_rect_width / 2) - temp_rect_right = (iw.width / 2) + (temp_rect_width / 2) - - iw.line(268, 0, 268, 64, 'black') # First Vertical Line - iw.rectangle(temp_rect_left, 0, temp_rect_right, 64, 'red') - iw.line(372, 0, 372, 64, 'black') # Second Vertical Line + red = infowindow.InfoWindow(infowindow_opts, "red") + black = infowindow.InfoWindow(infowindow_opts, "black") - iw.bitmap(375, 0, "windSmall.bmp") # Wind Icon - iw.line(461, 0, 461, 64, 'black') # Third Vertical Line + # Calendar / Todo Title Line + black.line(0, 0, 880, 0) # Top Line + red.rectangle(1, 1, 880, 24) # Red Rectangle + black.line(0, 24, 880, 24) # Bottom Black Line - iw.bitmap(464, 0, "rainSmall.bmp") # Rain Icon - iw.line(550, 0, 550, 64, 'black') # Fourth Vertical Line + # Titles + text_width = red.textwidth("CALENDAR", 'robotoBlack24') + red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') + text_width = red.textwidth("FRIDGE", 'robotoBlack24') + red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') + text_width = red.textwidth("TASKS", 'robotoBlack24') + red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') - iw.bitmap(554, 0, "snowSmall.bmp") # Snow Icon - # Center cal/todo divider line - iw.line(314, 90, 314, 384, 'black') # Left Black line - iw.rectangle(315, 64, 325, 384, 'red') # Red Rectangle - iw.line(326, 90, 326, 384, 'black') # Right Black line + # Set some things + calendar_date_font = "robotoRegular14" + calendar_entry_font = "robotoBlack18" + tasks_font = "robotoBlack18" - # Calendar / Todo Title Line - iw.line(0, 64, 640, 64, 'black') # Top Line - iw.rectangle(0, 65, 640, 90, 'red') # Red Rectangle - iw.line(0, 91, 640, 91, 'black') # Bottom Black Line + # Dividing line + black.line(286, 24, 286, 528) # Left Black line + red.rectangle(287, 24, 296, 528) # Red Rectangle + black.line(297, 24, 297, 528) # Right Black line - # Todo / Weather Titles - iw.text(440, 64, "TODO", 'robotoBlack24', 'white') - iw.text(95, 64, "CALENDAR", 'robotoBlack24', 'white') + black.line(583, 24, 583, 528) # Left Black line + red.rectangle(584, 24, 593, 528) # Red Rectangle + black.line(594, 24, 594, 528) # Right Black line - # DISPLAY TODO INFO + # DISPLAY TODO INFO # ========================================================================= todo_items = todo.list() logging.debug("Todo Items") logging.debug("-----------------------------------------------------------------------") - #(t_x, t_y) = iw.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(iw, string.printable, tasks_font) + #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - current_task_y = 92 + current_task_y = 25 for todo_item in todo_items: - iw.text(333, (current_task_y + infowindow_opts["cell_spacing"]), todo_item['content'].encode(charset).strip(), - tasks_font, 'black') - iw.line(327, (current_task_y + line_height + 1), 640, (current_task_y + line_height + 1), 'black') + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), + tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) # set next loop height current_task_y = (current_task_y + line_height + 2) logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # DISPLAY GROCY INFO + # ========================================================================= + grocy_items = grocy.list() + logging.debug("Grocy Items") + logging.debug("-----------------------------------------------------------------------") + + #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for grocy_item in grocy_items: + if int(grocy_item['days']) < 3: + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), + tasks_font) + else: + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), + tasks_font) + red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) + + + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) + # DISPLAY CALENDAR INFO # ========================================================================= cal_items = cal.list() @@ -151,53 +170,53 @@ def main(): logging.debug("-----------------------------------------------------------------------") if calendar_opts['timeformat'] == "12h": - (t_x, t_y) = get_max_char_size(iw, string.digits, calendar_date_font) - (dt_x, dt_y) = iw.getFont(calendar_date_font).getsize(': pm') + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') dt_x = dt_x + (4 * t_x) if t_y > dt_y: dt_y = t_y else: - (t_x, t_y) = get_max_char_size(iw, string.digits, calendar_date_font) - (dt_x, dt_y) = iw.getFont(calendar_date_font).getsize('.') + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') dt_x = dt_x + (4 * t_x) - (it_x, it_y) = get_max_char_size(iw, string.printable, calendar_entry_font) + (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) - current_calendar_y = 92 + current_calendar_y = 25 for cal_item in cal_items: font_color = 'black' if cal_item['today']: font_color = calendar_opts['today_text_color'] - iw.rectangle(0, current_calendar_y, - 313, (current_calendar_y + line_height), + black.rectangle(0, current_calendar_y, + 285, (current_calendar_y + line_height), calendar_opts['today_background_color']) # draw horizontal line - iw.line(0, (current_calendar_y + line_height + 1), - 313, (current_calendar_y + line_height + 1), + red.line(0, (current_calendar_y + line_height + 1), + 285, (current_calendar_y + line_height + 1), 'black') # draw vertical line - iw.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, + red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, (dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), (current_calendar_y + line_height), 'black') # draw event date - iw.text((infowindow_opts["cell_spacing"]), + black.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), cal_item['date'].encode(charset).strip(), calendar_date_font, font_color) # draw event time - iw.text((infowindow_opts["cell_spacing"]), + black.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), cal_item['time'].encode(charset).strip(), calendar_date_font, font_color) # draw event text calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 - max_event_text_length = 313 - calendar_event_text_start - infowindow_opts["cell_spacing"] - iw.text(calendar_event_text_start, + max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] + black.text(calendar_event_text_start, (current_calendar_y + ((line_height - it_y) / 2)), - iw.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), + black.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), calendar_entry_font, font_color) # set new line height for next round @@ -205,50 +224,12 @@ def main(): # logging.debug("ITEM: "+str(cal_item['date']), str(cal_item['time']), str(cal_item['content'])) logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) - # DISPLAY WEATHER INFO - # ========================================================================= - weather = weather.list() - logging.debug("Weather Info") - logging.debug("-----------------------------------------------------------------------") - # Set unit descriptors - if weather_opts['units'] == 'imperial': - u_speed = u"mph" - u_temp = u"F" - elif weather_opts['units'] == 'metric': - u_speed = u"m/sec" - u_temp = u"C" - else: - u_speed = u"m/sec" - u_temp = u"K" - - deg_symbol = u"\u00b0" - iw.bitmap(2, 2, weather['icon']) - iw.text(70, 2, weather['description'].title().encode(charset).strip(), 'robotoBlack24', 'black') - iw.text(70, 35, weather['sunrise'], 'robotoRegular18', 'black') - iw.text(154, 35, weather['sunset'], 'robotoRegular18', 'black') - - # Temp ( adjust for str length ) - (t_x, t_y) = iw.getFont('robotoBlack48').getsize(str(weather['temp_cur']) + deg_symbol) - temp_left = (iw.width / 2) - (t_x / 2) - iw.text(temp_left, 2, str(weather['temp_cur']) + deg_symbol, 'robotoBlack48', 'white') - t_desc_posx = (temp_left + t_x) - 15 - iw.text(t_desc_posx, 25, u_temp, 'robotoBlack18', 'white') - - # Wind - iw.text(405, 5, weather['wind']['dir'], 'robotoBlack18', 'black') - iw.text(380, 35, str(weather['wind']['speed']) + u_speed, 'robotoRegular18', 'black') - - # Rain - iw.text(481, 29, "1hr: " + str(weather['rain']['1h']), 'robotoRegular18', 'black') - iw.text(481, 44, "3hr: " + str(weather['rain']['3h']), 'robotoRegular18', 'black') - - # Snow - iw.text(573, 29, "1hr: " + str(weather['snow']['1h']), 'robotoRegular18', 'black') - iw.text(573, 44, "3hr: " + str(weather['snow']['3h']), 'robotoRegular18', 'black') - - # Write to screen + # Write to screen # ========================================================================= - iw.display(rotation) + red.display(rotation) + black.display(rotation) + red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) + if __name__ == '__main__': diff --git a/mod_infowindow/.gitignore b/mod_infowindow/.gitignore new file mode 100755 index 0000000..fc842ce --- /dev/null +++ b/mod_infowindow/.gitignore @@ -0,0 +1,9 @@ +google_secret.json +*.pickle +*.code-workspace +*.pyc +icons/*.png +test.py +config.json +infowindow.jpg +venv/ diff --git a/mod_infowindow/clearScreen.py b/mod_infowindow/clearScreen.py new file mode 100755 index 0000000..67d9628 --- /dev/null +++ b/mod_infowindow/clearScreen.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- +import sys +import os +picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic') +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +from driver import epd7in5b +import time +from PIL import Image,ImageDraw,ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +logging.info("epd7in5b_HD Demo") + +epd = epd7in5b.EPD() + +logging.info("init and Clear") +epd.init() +epd.Clear() diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index dab37e8..56a1874 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -1,101 +1,113 @@ -from driver import epd7in5b -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont -from PIL import ImageChops -import os, sys -import logging -import tempfile - - -class InfoWindow: - def __init__(self, options): - self.epd = epd7in5b.EPD() - self.epd.init() - self.width = 640 - self.height = 384 - self.image = Image.new('L', (640, 384), 255) - self.draw = ImageDraw.Draw(self.image) - self.fonts = {} - self.initFonts() - self.tmpImagePath = os.path.join(tempfile.gettempdir(), "InfoWindow.png") - self.timeformat = options['timeformat'] - - def getCWD(self): - path = os.path.dirname(os.path.realpath(sys.argv[0])) - return path - - def getImage(self): - return self.image - - def getDraw(self): - return self.draw - - def getEpd(self): - return self.epd - - def line(self, left_1, top_1, left_2, top_2, fill, width=1): - self.draw.line((left_1, top_1, left_2, top_2), fill=fill) - - def rectangle(self, tl, tr, bl, br, fill): - self.draw.rectangle(((tl, tr), (bl, br)), fill=fill) - - def text(self, left, top, text, font, fill): - font = self.fonts[font] - self.draw.text((left, top), text, font=font, fill=fill) - return self.draw.textsize(text, font=font) - - def rotate(self, angle): - self.image.rotate(angle) - - # def chord(self, x, y, xx, yy, xxx, yyy, fill): - # self.draw.chord((x, y, xx, yy), xxx, yyy, fill) - - def bitmap(self, x, y, image_path): - bitmap = Image.open(self.getCWD()+"/icons/"+image_path) - # self.image.paste((0, 0), (x, y), 'black', bitmap) - self.draw.bitmap((x, y), bitmap) - - def getFont(self, font_name): - return self.fonts[font_name] - - def initFonts(self): - roboto = self.getCWD()+"/fonts/roboto/Roboto-" - self.fonts = { - - 'robotoBlack24': ImageFont.truetype(roboto+"Black.ttf", 24), - 'robotoBlack18': ImageFont.truetype(roboto+"Black.ttf", 18), - 'robotoRegular18': ImageFont.truetype(roboto+"Regular.ttf", 18), - 'robotoRegular14': ImageFont.truetype(roboto+"Regular.ttf", 14), - 'robotoBlack48': ImageFont.truetype(roboto+"Black.ttf", 48) - } - - def truncate(self, string, font, max_size): - num_chars = len(string) - for char in string: - (np_x, np_y) = self.getFont(font).getsize(string) - if np_x >= max_size: - string = string[:-1] - - if np_x <= max_size: - return string - - return string - - def display(self, angle): - self.image = self.image.rotate(angle) - - new_image_found = True - if os.path.exists(self.tmpImagePath): - old_image = Image.open(self.tmpImagePath) - diff = ImageChops.difference(self.image, old_image) - if not diff.getbbox(): - new_image_found = False - - if new_image_found: - logging.info("New information in the image detected. Updating the screen.") - self.image.save(self.tmpImagePath) - self.epd.display_frame(self.epd.get_frame_buffer(self.image)) - self.epd.sleep() - else: - logging.info("No new information found. Not updating the screen.") +from driver import epd7in5b +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageChops +import os, sys +import logging +import tempfile + + +class InfoWindow: + def __init__(self, options, colour): + self.epd = epd7in5b.EPD() + self.epd.init() + self.width = 880 + self.height = 528 + self.image = Image.new('L', (880, 528), 255) + self.draw = ImageDraw.Draw(self.image) + self.fonts = {} + self.colour = colour + self.initFonts() + self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") + self.timeformat = options['timeformat'] + + def getCWD(self): + path = os.path.dirname(os.path.realpath(sys.argv[0])) + return path + + def getImage(self): + return self.image + + def getDraw(self): + return self.draw + + def getEpd(self): + return self.epd + + def line(self, left_1, top_1, left_2, top_2, fill=0, width=1): + self.draw.line((left_1, top_1, left_2, top_2), fill=fill) + + def rectangle(self, tl, tr, bl, br, fill=0): + self.draw.rectangle(((tl, tr), (bl, br)), fill=fill) + + def text(self, left, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((left, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) + + def textwidth(self, text, font): + font = self.fonts[font] + ascent, descent = font.getmetrics() + text_width = font.getmask(text).getbbox()[2] + text_height = font.getmask(text).getbbox()[3] + descent + text_width = (text_width/2) + return text_width + + def rotate(self, angle): + self.image.rotate(angle) + + # def chord(self, x, y, xx, yy, xxx, yyy, fill): + # self.draw.chord((x, y, xx, yy), xxx, yyy, fill) + + def bitmap(self, x, y, image_path): + bitmap = Image.open(self.getCWD()+"/icons/"+image_path) + # self.image.paste((0, 0), (x, y), 'black', bitmap) + self.draw.bitmap((x, y), bitmap) + + + + def getFont(self, font_name): + return self.fonts[font_name] + + def initFonts(self): + roboto = self.getCWD()+"/fonts/roboto/Roboto-" + self.fonts = { + + 'robotoBlack24': ImageFont.truetype(roboto+"Black.ttf", 24), + 'robotoBlack18': ImageFont.truetype(roboto+"Black.ttf", 18), + 'robotoRegular18': ImageFont.truetype(roboto+"Regular.ttf", 18), + 'robotoRegular14': ImageFont.truetype(roboto+"Regular.ttf", 14), + 'robotoBlack48': ImageFont.truetype(roboto+"Black.ttf", 48), + 'robotoBlack36': ImageFont.truetype(roboto+"Black.ttf", 36) + } + + def truncate(self, string, font, max_size): + num_chars = len(string) + for char in string: + (np_x, np_y) = self.getFont(font).getsize(string) + if np_x >= max_size: + string = string[:-1] + + if np_x <= max_size: + return string + + return string + + def display(self, angle): + + Limage = Image.new('1', (880, 528), 255) # 255: clear the frame + self.image = self.image.rotate(angle) + + new_image_found = True + if os.path.exists(self.tmpImagePath): + old_image = Image.open(self.tmpImagePath) + diff = ImageChops.difference(self.image, old_image) + if not diff.getbbox(): + new_image_found = False + + if new_image_found: + logging.info("New information in the image detected. Updating the screen.") + self.image.save(self.tmpImagePath) + else: + logging.info("No new information found. Not updating the screen.") diff --git a/mod_infowindow/mod_calendar/__init__.py b/mod_infowindow/mod_calendar/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mod_infowindow/mod_calendar/mod_google.py b/mod_infowindow/mod_calendar/mod_google.py new file mode 100755 index 0000000..563963a --- /dev/null +++ b/mod_infowindow/mod_calendar/mod_google.py @@ -0,0 +1,82 @@ +from mod_utils import mod_google_auth +from googleapiclient.discovery import build +from dateutil.parser import parse as dtparse +from datetime import datetime as dt +import logging + +# Silence goofy google deprecated errors +logging.getLogger('googleapiclient.discovery_cache').setLevel(logging.ERROR) + + +class Cal: + def __init__(self, options): + ga = mod_google_auth.GoogleAuth() + self.creds = ga.login() + self.timeformat = options["timeformat"] + self.additional = options["additional"] + self.ignored = options["ignored"] + + def list(self): + calendar_ids = [] + events = {} + items = [] + + service = build('calendar', 'v3', credentials=self.creds) + now = dt.utcnow().isoformat() + 'Z' + + page_token = None + while True: + calendar_list = service.calendarList().list(pageToken=page_token).execute() + for calendar_list_entry in calendar_list['items']: + if "primary" in calendar_list_entry.keys(): + if calendar_list_entry['primary']: + calendar_ids.append(calendar_list_entry['id']) + elif calendar_list_entry['summary'] in self.additional: + calendar_ids.append(calendar_list_entry['id']) + page_token = calendar_list.get('nextPageToken') + if not page_token: + break + + for id in calendar_ids: + result = service.events().list(calendarId=id, timeMin=now, + maxResults=20, + singleEvents=True, + orderBy='startTime').execute() + + for event in result.get('items', []): + if event['summary'] in self.ignored: + continue + initial_start = event['start'].get('dateTime', event['start'].get('date')) + start = "%s-0" % initial_start + counter = 0 + while start in events.keys(): + counter += 1 + start = "%s-%s" % (initial_start, counter) + + events[start] = event + + # 2019-11-05T10:00:00-08:00 + + for event_key in sorted(events.keys()): + start = events[event_key]['start'].get('dateTime', events[event_key]['start'].get('date')) + if int(dt.strftime(dtparse(start), format='%Y%m%d')) <= int(dt.strftime(dt.today(), format='%Y%m%d')): + today = True + else: + today = False + + # Sunrise and Sunset. + if self.timeformat == "12h": + st_date = dt.strftime(dtparse(start), format='%m-%d') + st_time = dt.strftime(dtparse(start), format='%I:%M %p') + else: + st_date = dt.strftime(dtparse(start), format='%d.%m') + st_time = dt.strftime(dtparse(start), format='%H:%M') + + items.append({ + "date": st_date, + "time": st_time, + "content": events[event_key]['summary'], + "today": today + }) + + return items diff --git a/mod_infowindow/mod_infowindow/__init__.py b/mod_infowindow/mod_infowindow/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py new file mode 100755 index 0000000..56a1874 --- /dev/null +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -0,0 +1,113 @@ +from driver import epd7in5b +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageChops +import os, sys +import logging +import tempfile + + +class InfoWindow: + def __init__(self, options, colour): + self.epd = epd7in5b.EPD() + self.epd.init() + self.width = 880 + self.height = 528 + self.image = Image.new('L', (880, 528), 255) + self.draw = ImageDraw.Draw(self.image) + self.fonts = {} + self.colour = colour + self.initFonts() + self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") + self.timeformat = options['timeformat'] + + def getCWD(self): + path = os.path.dirname(os.path.realpath(sys.argv[0])) + return path + + def getImage(self): + return self.image + + def getDraw(self): + return self.draw + + def getEpd(self): + return self.epd + + def line(self, left_1, top_1, left_2, top_2, fill=0, width=1): + self.draw.line((left_1, top_1, left_2, top_2), fill=fill) + + def rectangle(self, tl, tr, bl, br, fill=0): + self.draw.rectangle(((tl, tr), (bl, br)), fill=fill) + + def text(self, left, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((left, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) + + def textwidth(self, text, font): + font = self.fonts[font] + ascent, descent = font.getmetrics() + text_width = font.getmask(text).getbbox()[2] + text_height = font.getmask(text).getbbox()[3] + descent + text_width = (text_width/2) + return text_width + + def rotate(self, angle): + self.image.rotate(angle) + + # def chord(self, x, y, xx, yy, xxx, yyy, fill): + # self.draw.chord((x, y, xx, yy), xxx, yyy, fill) + + def bitmap(self, x, y, image_path): + bitmap = Image.open(self.getCWD()+"/icons/"+image_path) + # self.image.paste((0, 0), (x, y), 'black', bitmap) + self.draw.bitmap((x, y), bitmap) + + + + def getFont(self, font_name): + return self.fonts[font_name] + + def initFonts(self): + roboto = self.getCWD()+"/fonts/roboto/Roboto-" + self.fonts = { + + 'robotoBlack24': ImageFont.truetype(roboto+"Black.ttf", 24), + 'robotoBlack18': ImageFont.truetype(roboto+"Black.ttf", 18), + 'robotoRegular18': ImageFont.truetype(roboto+"Regular.ttf", 18), + 'robotoRegular14': ImageFont.truetype(roboto+"Regular.ttf", 14), + 'robotoBlack48': ImageFont.truetype(roboto+"Black.ttf", 48), + 'robotoBlack36': ImageFont.truetype(roboto+"Black.ttf", 36) + } + + def truncate(self, string, font, max_size): + num_chars = len(string) + for char in string: + (np_x, np_y) = self.getFont(font).getsize(string) + if np_x >= max_size: + string = string[:-1] + + if np_x <= max_size: + return string + + return string + + def display(self, angle): + + Limage = Image.new('1', (880, 528), 255) # 255: clear the frame + self.image = self.image.rotate(angle) + + new_image_found = True + if os.path.exists(self.tmpImagePath): + old_image = Image.open(self.tmpImagePath) + diff = ImageChops.difference(self.image, old_image) + if not diff.getbbox(): + new_image_found = False + + if new_image_found: + logging.info("New information in the image detected. Updating the screen.") + self.image.save(self.tmpImagePath) + else: + logging.info("No new information found. Not updating the screen.") diff --git a/mod_infowindow/mod_todo/__init__.py b/mod_infowindow/mod_todo/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mod_infowindow/mod_todo/mod_google.py b/mod_infowindow/mod_todo/mod_google.py new file mode 100755 index 0000000..88bef1d --- /dev/null +++ b/mod_infowindow/mod_todo/mod_google.py @@ -0,0 +1,37 @@ +from mod_utils import mod_google_auth +from googleapiclient.discovery import build +import logging + +logger = logging.getLogger(__name__) + + +class ToDo: + def __init__(self, api_key): + # This module authenticates from Google Auth API. We pull in the auth module + # wrapper to keep it clean. + logger.info("Initializing Module: ToDo: GOOGLE") + ga = mod_google_auth.GoogleAuth() + self.creds = ga.login() + + def list(self): + logging.info("Entering ToDo.list()") + service = build('tasks', 'v1', credentials=self.creds) + + items = [] + + # Fetch Results from all lists where todo is in the name + tasklists = service.tasklists().list().execute() + for tasklist in tasklists['items']: + if "todo" in tasklist['title'].lower(): + results = service.tasks().list(tasklist=tasklist['id']).execute() + + # Loop through results and format them for ingest + if 'items' in results.keys(): + for task in results['items']: + items.append({ + "content": task['title'], + "priority": task['position'] + }) + + # Return results to main program + return items diff --git a/mod_infowindow/mod_todo/mod_grocy.py b/mod_infowindow/mod_todo/mod_grocy.py new file mode 100755 index 0000000..eb13d26 --- /dev/null +++ b/mod_infowindow/mod_todo/mod_grocy.py @@ -0,0 +1,40 @@ +import requests +import logging +import json +import datetime + + +class test: + def __init__(self, opts): + logging.debug("Grocy API: GROCY") + self.api = False + headers = { + 'accept': 'application/json', + 'GROCY-API-KEY': opts['api_key'], + } + if not opts['api_key']: + logging.warning("Not loading Todo API, since no api key is configured") + else: + self.api = requests.get("https://"+opts['server']+":"+opts['port']+"/api/stock", headers=headers) + + def list(self): + today = datetime.date.today() + items = [] + # Loop through original array from Grocy and pull out items by soonest best before date + if self.api: + data = json.loads(self.api.content) + for item in data: + logging.debug(item['product']['name']) + best_before = item['best_before_date'] + best_before_date = datetime.date(int(best_before[0:4]),int(best_before[5:7]),int(best_before[8:10])) + days = best_before_date - today + items.append({ + "content": str(days.days) + " " + item['product']['name'], + "best_before": item['best_before_date'], + "days": str(days.days) + }) + + items = sorted(items, key = lambda i: i['best_before']) + + + return items diff --git a/mod_infowindow/mod_todo/mod_teamwork.py b/mod_infowindow/mod_todo/mod_teamwork.py new file mode 100755 index 0000000..dc41a92 --- /dev/null +++ b/mod_infowindow/mod_todo/mod_teamwork.py @@ -0,0 +1,38 @@ +import urllib2, base64 +import json +import logging + + +class ToDo: + def __init__(self, opts): + logging.debug("Todo API: TEAMWORK") + self.company = opts['site'] + self.key = opts['api_key'] + + def list(self): + action = "tasks.json?sort=priority" + request = urllib2.Request("https://{0}/{1}".format(self.company, action)) + request.add_header("Authorization", "BASIC " + base64.b64encode(self.key + ":xxx")) + + response = urllib2.urlopen(request) + data = json.loads(response.read()) + items = [] + + for task in data['todo-items']: + if task['priority'] == 'high': + priority = 1 + elif task['priority'] == 'medium': + priority = 2 + elif task['priority'] == 'low': + priority = 3 + elif task['priority'] == 'None': + priority = 4 + else: + priority = 8 + + items.append({ + "content": task['content'], + "priority": priority + }) + + return items diff --git a/mod_infowindow/mod_todo/mod_todoist.py b/mod_infowindow/mod_todo/mod_todoist.py new file mode 100755 index 0000000..c35cc81 --- /dev/null +++ b/mod_infowindow/mod_todo/mod_todoist.py @@ -0,0 +1,34 @@ +import todoist +import logging + + +class ToDo: + def __init__(self, opts): + logging.debug("Todo API: TODOIST") + self.api = False + if not opts['api_key']: + logging.warning("Not loading Todo API, since no api key is configured") + else: + self.api = todoist.TodoistAPI(opts['api_key']) + self.api.sync() + + def list(self): + items = [] + # Loop through original array from Todoist and pull out + # items of interest + if self.api: + for item in self.api.state['items']: + if item['checked'] == 0: + items.append({ + "content": item['content'], + "priority": item['priority'], + }) + + # Sort the array by priority + items = sorted(items, key = lambda i: i['priority']) + unicode(items).encode('utf8') + # Reverse list, since Todoist sets priority in reverse. + # On web interface HIGH=Priority1, but stored in API as 4. who knows?! + items.reverse() + + return items diff --git a/mod_infowindow/mod_utils/__init__.py b/mod_infowindow/mod_utils/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mod_infowindow/mod_utils/iw_utils.py b/mod_infowindow/mod_utils/iw_utils.py new file mode 100755 index 0000000..6551943 --- /dev/null +++ b/mod_infowindow/mod_utils/iw_utils.py @@ -0,0 +1,21 @@ +import os +import sys + +def isCron(): + if len(sys.argv) == 2: + if(sys.argv[1] == '--cron'): + return True + return False + +def getCWD(): + path = os.path.dirname(os.path.realpath(sys.argv[0])) + return path + +# Custom Error handler. This function will display the error message +# on the e-ink display and exit. +def HandleError(msg): + print("ERROR IN PROGRAM ======================================") + print("Program requires user input. Please run from console") + print("ERR: " + msg) + print("END ERROR =============================================") + quit() diff --git a/mod_infowindow/mod_utils/mod_google_auth.py b/mod_infowindow/mod_utils/mod_google_auth.py new file mode 100755 index 0000000..5e5aa80 --- /dev/null +++ b/mod_infowindow/mod_utils/mod_google_auth.py @@ -0,0 +1,75 @@ +from google_auth_oauthlib import flow +from google.auth.transport.requests import Request +from mod_utils import iw_utils +import pickle +import os.path +import sys +import logging + +logger = logging.getLogger(__name__) + + +class GoogleAuth: + def __init__(self): + logger.info("Initializing Module: GoogleAuth") + self.scopes = [ + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/tasks' + ] + + self.creds = None + self.path = os.path.dirname(os.path.realpath(sys.argv[0])) + + def login(self): + + # Check for pickle. + # if os.path.exists('token.pickle'): + pickle_token_file_path = os.path.join(self.path, 'token.pickle') + if os.path.exists(pickle_token_file_path): + logger.info("token.pickle Exists. Attempting read") + with open(pickle_token_file_path, 'rb') as token: + self.creds = pickle.load(token) + else: + logger.info("%s NOT FOUND" % pickle_token_file_path) + + # If there are no valid creds, let user login. + # If we get to this point there is a user interaction that needs + # to happen. Must generate some sort of display on e-ink to let the + # user know that they need to run interactivly. + if not self.creds or not self.creds.valid: + logger.info("Credentials do not exist, or are not valid.") + + # Requires input from user. Write error to e-ink if is run from cron. + # if iw_utils.isCron(): + # iw_utils.HandleError("Google Credentials do not exist, or are not valid") + + if self.creds and self.creds.expired and self.creds.refresh_token: + logging.info("Refreshing Google Auth Credentials") + self.creds.refresh(Request()) + else: + # Check to see if google_secret.json exists. Throw error if not + google_secrets_file_path = os.path.join(self.path, 'google_secret.json') + if not os.path.exists(google_secrets_file_path): + logger.info("%s does not exist" % google_secrets_file_path) + + # Requires input from user. Write error to e-ink if is run from cron. + if iw_utils.isCron(): + iw_utils.HandleError('Message') + + appflow = flow.InstalledAppFlow.from_client_secrets_file( + google_secrets_file_path, self.scopes + ) + + if iw_utils.isCron(): + appflow.run_local_server() + else: + appflow.run_console() + + self.creds = appflow.credentials + + # Write pickle file + logger.info("Writing %s file", pickle_token_file_path) + with open(pickle_token_file_path, 'wb') as token: + pickle.dump(self.creds, token) + + return self.creds diff --git a/mod_infowindow/mod_weather/__init__.py b/mod_infowindow/mod_weather/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/mod_infowindow/mod_weather/mod_owm.py b/mod_infowindow/mod_weather/mod_owm.py new file mode 100755 index 0000000..82352df --- /dev/null +++ b/mod_infowindow/mod_weather/mod_owm.py @@ -0,0 +1,109 @@ +import requests +from datetime import datetime as dt +import os +import math +from PIL import Image +import logging + + +class Weather: + def __init__(self, options): + logging.debug("Weather API: Open Weather Map") + self.api_key = options['api_key'] + self.icon_path = "icons/" + self.city = options['city'] + self.units = options['units'] + self.timeformat = options['timeformat'] + + def pngToBmp(self, icon): + img = Image.open(self.icon_path+str(icon)) + r,g,b,a = img.split() + # img.merge("RGB", (r, g, b)) + basename = os.path.splitext(icon)[0] + img = img.convert('1') + img.save(self.icon_path+basename+".bmp") + return basename+".bmp" + + def getIcon(self, iconUrl): + # check for icon + bn = os.path.basename(iconUrl) + for root, dirs, files in os.walk(self.icon_path): + if not bn in files: + with open(self.icon_path+bn, "wb") as file: + response = requests.get(iconUrl) + file.write(response.content) + file.close() + + return self.pngToBmp(bn) + + def degreesToTextDesc(self, deg): + if deg > 337.5: return u"N" + if deg > 292.5: return u"NW" + if deg > 247.5: return u"W" + if deg > 202.5: return u"SW" + if deg > 157.5: return u"S" + if deg > 122.5: return u"SE" + if deg > 67.5: return u"E" + if deg > 22.5: return u"NE" + return u"N" + + def list(self): + url = 'http://api.openweathermap.org/data/2.5/weather' + r = requests.get('{}?q={}&units={}&appid={}'.format(url, self.city, self.units, self.api_key)) + + data = r.json() + + # Sunrise and Sunset. + if self.timeformat == "12h": + sunrise = dt.fromtimestamp(data['sys'].get('sunrise')).strftime('%I:%M %p') + sunset = dt.fromtimestamp(data['sys'].get('sunset')).strftime('%I:%M %p') + else: + sunrise = dt.fromtimestamp(data['sys'].get('sunrise')).strftime('%H:%M') + sunset = dt.fromtimestamp(data['sys'].get('sunset')).strftime('%H:%M') + + # Rain and Snow + wTypes = ['rain', 'snow'] + for wType in wTypes: + # Check to see if dictionary has values for rain or snow. + # if it does NOT, set zero values for consistancy. + if data.has_key(wType): + setattr(self, wType, { + "1h": data[wType].get('1h'), + "3h": data[wType].get('3h') + }) + else: + setattr(self, wType, { + "1h": 0, + "3h": 0 + }) + + # Fetch Wind Data + wind = { + "dir": self.degreesToTextDesc(data['wind'].get('deg')), + "speed": int(round(data['wind'].get('speed'))) + #"speed": 33 + } + + #icon = self.getIcon("http://openweathermap.org/img/wn/"+data['weather'][0].get('icon')+".png") + icon = os.path.basename(data['weather'][0].get('icon'))+".bmp" + + return { + "description": data['weather'][0].get('description'), + "humidity": data['main'].get('humidity'), + "temp_cur": int(math.ceil(data['main'].get('temp'))), + #"temp_cur": int(9), + "temp_min": int(math.ceil(data['main'].get('temp_min'))), + "temp_max": int(math.ceil(data['main'].get('temp_max'))), + #"temp_min": int(100), + #"temp_max": int(112), + "sunrise": sunrise, + "sunset": sunset, + "rain": self.rain, + "snow": self.snow, + "wind": wind, + "icon": icon + } + + + + diff --git a/mod_infowindow/resources/black.png b/mod_infowindow/resources/black.png new file mode 100755 index 0000000000000000000000000000000000000000..41e62e226a9dee366b3e4960696d1850b149d2eb GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU~J$33NT3c^&|l~Dw)pC0iMpz3I#>^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6O}g9TNEG+3|Cf97srr_TW=2c%lRHw5O||%Q~loCIAeDt(yP< literal 0 HcmV?d00001 diff --git a/mod_infowindow/resources/red.png b/mod_infowindow/resources/red.png new file mode 100755 index 0000000000000000000000000000000000000000..9298929c318bafaf5e25d5071b48ba6642ff8562 GIT binary patch literal 1101 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU~J$33NT3c^&|l~Dw)pC0iMpz3I#>^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6P30;SH6=M7|?q>T^vIyZoR$aD9FITaoE7&{-4*5Y&Jri zd&{`q^{_|?FgGz8Dv(UEu`nk&7$}fV9cf5#;Nf5+k?LSP*dQUmLk2~L-=Kj(y5B%y zLb~6efkB4fKw-k{)j&@C0X32rf8_g-^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6O~qRuHW(l7|^>tT^vIyZoR$aD9FITaoFI%{^yF!a-A)4 z)~w}jOgtTo2OA^=NT!&Z7!4I91W2cn91IlLSeQwqjx;1V@Nlq^L6PA%Xkd`;H&B?6 z?l)*)kl{CAm~=3z9v~=3> BK^p)7 literal 0 HcmV?d00001 diff --git a/mod_todo/mod_grocy.py b/mod_todo/mod_grocy.py new file mode 100755 index 0000000..eb13d26 --- /dev/null +++ b/mod_todo/mod_grocy.py @@ -0,0 +1,40 @@ +import requests +import logging +import json +import datetime + + +class test: + def __init__(self, opts): + logging.debug("Grocy API: GROCY") + self.api = False + headers = { + 'accept': 'application/json', + 'GROCY-API-KEY': opts['api_key'], + } + if not opts['api_key']: + logging.warning("Not loading Todo API, since no api key is configured") + else: + self.api = requests.get("https://"+opts['server']+":"+opts['port']+"/api/stock", headers=headers) + + def list(self): + today = datetime.date.today() + items = [] + # Loop through original array from Grocy and pull out items by soonest best before date + if self.api: + data = json.loads(self.api.content) + for item in data: + logging.debug(item['product']['name']) + best_before = item['best_before_date'] + best_before_date = datetime.date(int(best_before[0:4]),int(best_before[5:7]),int(best_before[8:10])) + days = best_before_date - today + items.append({ + "content": str(days.days) + " " + item['product']['name'], + "best_before": item['best_before_date'], + "days": str(days.days) + }) + + items = sorted(items, key = lambda i: i['best_before']) + + + return items diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index c0b74ff..c35cc81 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -14,7 +14,7 @@ def __init__(self, opts): def list(self): items = [] - # Loop through original array from Todoist and pull out + # Loop through original array from Todoist and pull out # items of interest if self.api: for item in self.api.state['items']: @@ -26,7 +26,7 @@ def list(self): # Sort the array by priority items = sorted(items, key = lambda i: i['priority']) - + unicode(items).encode('utf8') # Reverse list, since Todoist sets priority in reverse. # On web interface HIGH=Priority1, but stored in API as 4. who knows?! items.reverse() From fef55c6e838f1a09a1dc458ce0be364ee955327f Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:16:24 +0000 Subject: [PATCH 002/162] Update README.md --- README.md | 102 ++++-------------------------------------------------- 1 file changed, 6 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 9d15504..e6f6154 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,10 @@ -![alt text](infowindow.jpg) +I've personalised the project for my own use. +I'm using the HD version of the screen, the existing code did not work for this. +I've therefore updated to use with the latest drivers -# Infowindow -Rapsberry pi powered e-ink display for displaying information in an always on state. There are several other iterations -of this project online, but they didnt do quite what I wanted them to. This is my version. Also keeping up my python -skills as they dont get used as much as they used to! +I've removed the weather functions as these were not useful to me. -The functionality is not meant to be an "end all solution for calendaring and Todo lists" The intent is to provide an -*always on* display to show me what is coming up next. I can then check in browser, phone, etc for details and updates -to the data. In your face reminder. - - -## Features -* **Calendar** - * Google Calendar is the only calendar currently supported -* **Todo List** - * Todoist - * Teamwork.com -* **Weather** - * Open Weather Map current data only. Future plan for forecast data. - -## Installation -### Raspberry Pi setup -Activate SPI on your Raspberry Pi by using the `raspi-config` tool under Interface Options and reboot. - -### Get software -Clone this repo onto your raspberry pi. Does not really matter where it is, but good option is in the `pi` users home -directory: `/home/pi/InfoWindow` - -### Setup python modules -Run `pip install -r requirements.txt`. This should install all required modules. I stuck to basic standard modules for -ease of installation. - -## Configuration -You will need to configure a few things such as API Keys and location. Copy config.json-sample to config.json. Edit -config.json to add your api keys and other information. - -## Optional: Increase lifetime of your SD-Card -If you want to increase the lifetime of the SD-Card, add the following line to `/etc/fstab` and reboot: - -`tmpfs /tmp tmpfs defaults,noatime,nosuid,size=100m 0 0` - -With this line, the `/tmp` folder will be held in RAM and will not be written to the SD-Card. - -## Optional: Screen saver -Always displaying the same colors at the same spots might have some negative effect on your E-Ink screen. To remedy -this, there is a simple additional script, which displays all three colors on the whole screen: I recommend to let -this run once every night, i.e. at 1 minute past 5 with: -* Run `crontab -e` -* insert `1 5 * * * /usr/bin/python /home/pi/InfoWindow/screensaver.py > /dev/null 2>&1` - -### General -* rotation: 0 - This is the rotation of the display in degrees. Leave at zero if you use it as a desktop display. Change -to 180 if you have it mounted and hanging from a shelf. -* timeformat: 12h / 24h -* charset: utf-8 (or something else). I.e. to get äöü working, use latin1 - -### Todo (Module) -Todoist is the current active module in this code. It only requires `api_key`. Teamwork also requires a 'site' key. If -using google tasks, leave this as null `todo: null` -* api_key: Enter your todoist API key. - -### Weather (Module) -Open Weather Map is where the data is coming from in the default module. This requires a few keys. -* api_key: Get your api key from OWM website. -* city: Look at OWM docs to figure what your city name is. Mine is "Sacramento,US" -* units: This can either be `imperial` or `metric` - -### Google calendar and ToDo list (Modules) -To use the google APIs, you first have to login to the [google cloud console](https://console.cloud.google.com/apis/). -In the google cloud console, do the following things: -1) Create a project and give it a name, i.e. `infowindow` and switch to the context of this project if not already - active. -2) Create a [new oauth consent screen](https://console.cloud.google.com/apis/credentials/consent) (just enter a name - should be enough). -3) Create a [new oauth 2.0 client id](https://console.cloud.google.com/apis/credentials). Choosing type `other` should - work just fine. Finally, download the json file provided by the google cloud console and store it in the repo - directory (i.e. `/home/pi/InfoWindow/google_secret.json`) on the Raspberry Pi. - -#### Calendar -There are are additional sections in the config for this module: -* additional: A list of additional calendar names (summary) to fetch. To use i.e. birthdays, add "Contacts" (also if - you use google in german. -* ignored: A list of events to be removed from the calendar display. - -## Running -### First Run -You should run the script manually the first time so that Googles auth modules can run interactivly. Once that has -completed you will want to add this to CRON so it runs every few minutes automatically. - -### Cron Run (Normal use) -* Run `crontab -e` -* insert `*/6 * * * * /usr/bin/python /home/pi/InfoWindow/infowindow.py --cron` +I have added in a todo module to work with "Grocy" +I've left 3 columns each to be used with any todo or calendar app From 82dd2780ab2feca4cfc44ef0350148b964fa6025 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:35:07 +0000 Subject: [PATCH 003/162] Update infowindow.py --- infowindow.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2773d61..028e200 100755 --- a/infowindow.py +++ b/infowindow.py @@ -228,7 +228,29 @@ def main(): # ========================================================================= red.display(rotation) black.display(rotation) - red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) + + new_image_found = 0 + if os.path.exists(red.tmpImagePath): + old_image = Image.open(red.tmpImagePath) + diff = ImageChops.difference(red.image, old_image) + if not diff.getbbox(): + new_image_found += 1 + + if os.path.exists(black.tmpImagePath): + old_image = Image.open(black.tmpImagePath) + diff = ImageChops.difference(black.image, old_image) + if not diff.getbbox(): + new_image_found += 1 + + if new_image_found < 2: + logging.info("New information in the image detected. Updating the screen.") + red.image.save(self.tmpImagePath) + red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) + red.epd.sleep() + else: + logging.info("No new information found. Not updating the screen.") + + From f611435a91c8a56708a59e5774e6bc5178eb0185 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:38:44 +0000 Subject: [PATCH 004/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 028e200..0791fae 100755 --- a/infowindow.py +++ b/infowindow.py @@ -242,7 +242,7 @@ def main(): if not diff.getbbox(): new_image_found += 1 - if new_image_found < 2: + if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") red.image.save(self.tmpImagePath) red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) From 5ccc9da5ac92473d93ffb2d4b5d2d52307d75618 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:39:19 +0000 Subject: [PATCH 005/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 0791fae..84ea613 100755 --- a/infowindow.py +++ b/infowindow.py @@ -247,7 +247,7 @@ def main(): red.image.save(self.tmpImagePath) red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) red.epd.sleep() - else: + else: logging.info("No new information found. Not updating the screen.") From b8a752c3a01eb7a35f6db837f09642ad06246252 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:44:21 +0000 Subject: [PATCH 006/162] Update infowindow.py --- infowindow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/infowindow.py b/infowindow.py index 84ea613..6d44e56 100755 --- a/infowindow.py +++ b/infowindow.py @@ -231,20 +231,21 @@ def main(): new_image_found = 0 if os.path.exists(red.tmpImagePath): - old_image = Image.open(red.tmpImagePath) - diff = ImageChops.difference(red.image, old_image) + old_image = red.Image.open(red.tmpImagePath) + diff = red.ImageChops.difference(red.image, old_image) if not diff.getbbox(): new_image_found += 1 if os.path.exists(black.tmpImagePath): - old_image = Image.open(black.tmpImagePath) - diff = ImageChops.difference(black.image, old_image) + old_image = black.Image.open(black.tmpImagePath) + diff = black.ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") - red.image.save(self.tmpImagePath) + red.image.save(red.tmpImagePath) + balck.image.save(black.tmpImagePath) red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) red.epd.sleep() else: From aae02eaf0a2c0756e09d02de11e891e1a206ef39 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 15:49:17 +0000 Subject: [PATCH 007/162] Update infowindow.py --- infowindow.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 6d44e56..b34b2d9 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,6 +6,10 @@ import json import logging import string +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageChops from mod_infowindow import infowindow # Select pluggable module for todo list, calendar and weather. @@ -231,14 +235,14 @@ def main(): new_image_found = 0 if os.path.exists(red.tmpImagePath): - old_image = red.Image.open(red.tmpImagePath) - diff = red.ImageChops.difference(red.image, old_image) + old_image = Image.open(red.tmpImagePath) + diff = ImageChops.difference(red.image, old_image) if not diff.getbbox(): new_image_found += 1 if os.path.exists(black.tmpImagePath): - old_image = black.Image.open(black.tmpImagePath) - diff = black.ImageChops.difference(black.image, old_image) + old_image = Image.open(black.tmpImagePath) + diff = ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 From 3148dc8c085ae14ad819bf6192099cd5c0357e8e Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:01:52 +0000 Subject: [PATCH 008/162] Update infowindow.py --- infowindow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infowindow.py b/infowindow.py index b34b2d9..b42f28c 100755 --- a/infowindow.py +++ b/infowindow.py @@ -239,12 +239,14 @@ def main(): diff = ImageChops.difference(red.image, old_image) if not diff.getbbox(): new_image_found += 1 + logging.info("No change to red") if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 + logging.info("No change to red") if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") From 5f02209d65434ea6692c6579885fe54539c1f266 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:05:57 +0000 Subject: [PATCH 009/162] Update infowindow.py --- mod_infowindow/infowindow.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index 56a1874..36bb65e 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -93,21 +93,3 @@ def truncate(self, string, font, max_size): return string return string - - def display(self, angle): - - Limage = Image.new('1', (880, 528), 255) # 255: clear the frame - self.image = self.image.rotate(angle) - - new_image_found = True - if os.path.exists(self.tmpImagePath): - old_image = Image.open(self.tmpImagePath) - diff = ImageChops.difference(self.image, old_image) - if not diff.getbbox(): - new_image_found = False - - if new_image_found: - logging.info("New information in the image detected. Updating the screen.") - self.image.save(self.tmpImagePath) - else: - logging.info("No new information found. Not updating the screen.") From 27db359520aea580149a430e31e92b31517c7caa Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:07:49 +0000 Subject: [PATCH 010/162] Update infowindow.py --- infowindow.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/infowindow.py b/infowindow.py index b42f28c..354fb71 100755 --- a/infowindow.py +++ b/infowindow.py @@ -230,8 +230,8 @@ def main(): # Write to screen # ========================================================================= - red.display(rotation) - black.display(rotation) + red.image = red.image.rotate(rotation) + black.image = black.image.rotate(rotation) new_image_found = 0 if os.path.exists(red.tmpImagePath): @@ -246,7 +246,7 @@ def main(): diff = ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 - logging.info("No change to red") + logging.info("No change to black") if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") @@ -257,9 +257,5 @@ def main(): else: logging.info("No new information found. Not updating the screen.") - - - - if __name__ == '__main__': main() From 9a324cee44bb32e84c0b1aa212d2432351bdfba1 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:10:00 +0000 Subject: [PATCH 011/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 354fb71..6474cd0 100755 --- a/infowindow.py +++ b/infowindow.py @@ -251,7 +251,7 @@ def main(): if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) - balck.image.save(black.tmpImagePath) + black.image.save(black.tmpImagePath) red.epd.display(black.epd.getbuffer(black.image),red.epd.getbuffer(red.image)) red.epd.sleep() else: From 5d2ed82c9beabb0b0b28b120afeb36750663eac0 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:16:26 +0000 Subject: [PATCH 012/162] Update screensaver.py --- screensaver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screensaver.py b/screensaver.py index c1aad2b..c8aba45 100755 --- a/screensaver.py +++ b/screensaver.py @@ -18,7 +18,8 @@ def main(): for image in images: logging.info("Display %s" % image) image_data = Image.open(os.path.join("resources", image)) - epd.display_frame(epd.get_frame_buffer(image_data)) + #epd.display_frame(epd.get_frame_buffer(image_data)) + epd.epd.display(epd.epd.getbuffer(image_data),epd.epd.getbuffer(red.image)) epd.sleep() logging.info("Screen saver finished") From eb2a7b48aee907727f01892ceb83ccbef96f6086 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:17:23 +0000 Subject: [PATCH 013/162] Update screensaver.py --- screensaver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screensaver.py b/screensaver.py index c8aba45..add868b 100755 --- a/screensaver.py +++ b/screensaver.py @@ -19,7 +19,7 @@ def main(): logging.info("Display %s" % image) image_data = Image.open(os.path.join("resources", image)) #epd.display_frame(epd.get_frame_buffer(image_data)) - epd.epd.display(epd.epd.getbuffer(image_data),epd.epd.getbuffer(red.image)) + epd.display(epd.getbuffer(image_data),epd.getbuffer(red.image)) epd.sleep() logging.info("Screen saver finished") From 0dfd1dcbd2463e09ac19556fedf57f10b24b5cc4 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:21:10 +0000 Subject: [PATCH 014/162] Update screensaver.py --- screensaver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screensaver.py b/screensaver.py index add868b..4c1bcf4 100755 --- a/screensaver.py +++ b/screensaver.py @@ -19,7 +19,7 @@ def main(): logging.info("Display %s" % image) image_data = Image.open(os.path.join("resources", image)) #epd.display_frame(epd.get_frame_buffer(image_data)) - epd.display(epd.getbuffer(image_data),epd.getbuffer(red.image)) + epd.display(epd.getbuffer(image_data),epd.getbuffer(image_data)) epd.sleep() logging.info("Screen saver finished") From e370c1c1484421ff8f3d5737421892c4b0ddafb5 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:30:38 +0000 Subject: [PATCH 015/162] Update screensaver.py --- screensaver.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/screensaver.py b/screensaver.py index 4c1bcf4..4d65fb3 100755 --- a/screensaver.py +++ b/screensaver.py @@ -14,12 +14,14 @@ def main(): epd = epd7in5b.EPD() epd.init() - images = ["red.png", "black.png", "white.png"] - for image in images: - logging.info("Display %s" % image) - image_data = Image.open(os.path.join("resources", image)) - #epd.display_frame(epd.get_frame_buffer(image_data)) - epd.display(epd.getbuffer(image_data),epd.getbuffer(image_data)) + black = Image.open(os.path.join("resources", "black.png")) + white = Image.open(os.path.join("resources", "white.png")) + epd.display(epd.getbuffer(black),epd.getbuffer(white)) + logging.info("Display black") + epd.display(epd.getbuffer(white),epd.getbuffer(black)) + logging.info("Display red") + epd.display(epd.getbuffer(white),epd.getbuffer(white)) + logging.info("Display white") epd.sleep() logging.info("Screen saver finished") From a0b721ed834d7b7ab5fe54d1312a3149932b09ef Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:34:06 +0000 Subject: [PATCH 016/162] Update screensaver.py --- screensaver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screensaver.py b/screensaver.py index 4d65fb3..5a54d94 100755 --- a/screensaver.py +++ b/screensaver.py @@ -15,10 +15,11 @@ def main(): epd.init() black = Image.open(os.path.join("resources", "black.png")) + red = Image.open(os.path.join("resources", "red.png")) white = Image.open(os.path.join("resources", "white.png")) epd.display(epd.getbuffer(black),epd.getbuffer(white)) logging.info("Display black") - epd.display(epd.getbuffer(white),epd.getbuffer(black)) + epd.display(epd.getbuffer(white),epd.getbuffer(red)) logging.info("Display red") epd.display(epd.getbuffer(white),epd.getbuffer(white)) logging.info("Display white") From 7eed1187de0b8d67d7ab379653621b90f2f12ae5 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 16:42:15 +0000 Subject: [PATCH 017/162] Update screensaver.py --- screensaver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/screensaver.py b/screensaver.py index 5a54d94..54a8e4a 100755 --- a/screensaver.py +++ b/screensaver.py @@ -19,14 +19,15 @@ def main(): white = Image.open(os.path.join("resources", "white.png")) epd.display(epd.getbuffer(black),epd.getbuffer(white)) logging.info("Display black") - epd.display(epd.getbuffer(white),epd.getbuffer(red)) + epd.display(epd.getbuffer(white),epd.getbuffer(black)) logging.info("Display red") epd.display(epd.getbuffer(white),epd.getbuffer(white)) logging.info("Display white") - + epd.sleep() logging.info("Screen saver finished") + os.remove(os.path.join(tempfile.gettempdir(), "black.png")) if __name__ == '__main__': main() From 795f9636e55724d579af9ce2bc6db728697cb2ab Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 17:53:49 +0000 Subject: [PATCH 018/162] Update screensaver.py --- screensaver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screensaver.py b/screensaver.py index 54a8e4a..64ae683 100755 --- a/screensaver.py +++ b/screensaver.py @@ -14,9 +14,9 @@ def main(): epd = epd7in5b.EPD() epd.init() - black = Image.open(os.path.join("resources", "black.png")) - red = Image.open(os.path.join("resources", "red.png")) - white = Image.open(os.path.join("resources", "white.png")) + black = Image.open(os.path.join(os.getcwd(), "resources", "black.png")) + red = Image.open(os.path.join(os.getcwd(),"resources", "red.png")) + white = Image.open(os.path.join(os.getcwd(),"resources", "white.png")) epd.display(epd.getbuffer(black),epd.getbuffer(white)) logging.info("Display black") epd.display(epd.getbuffer(white),epd.getbuffer(black)) From 69e7315e177de7a1a1fdecba6a60a99b0cf064dc Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 17:58:52 +0000 Subject: [PATCH 019/162] Update screensaver.py --- screensaver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/screensaver.py b/screensaver.py index 64ae683..505d17f 100755 --- a/screensaver.py +++ b/screensaver.py @@ -2,6 +2,7 @@ import logging import os +import tempfile from driver import epd7in5b from PIL import Image From 229d814e5ccb2ba6db64a5093bef1910a028a59c Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 18:01:42 +0000 Subject: [PATCH 020/162] Update screensaver.py --- screensaver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screensaver.py b/screensaver.py index 505d17f..037b1bf 100755 --- a/screensaver.py +++ b/screensaver.py @@ -15,9 +15,9 @@ def main(): epd = epd7in5b.EPD() epd.init() - black = Image.open(os.path.join(os.getcwd(), "resources", "black.png")) - red = Image.open(os.path.join(os.getcwd(),"resources", "red.png")) - white = Image.open(os.path.join(os.getcwd(),"resources", "white.png")) + black = Image.open(os.path.join(os.path.realpath(__file__), "resources", "black.png")) + red = Image.open(os.path.join(os.path.realpath(__file__),"resources", "red.png")) + white = Image.open(os.path.join(os.path.realpath(__file__),"resources", "white.png")) epd.display(epd.getbuffer(black),epd.getbuffer(white)) logging.info("Display black") epd.display(epd.getbuffer(white),epd.getbuffer(black)) From 6a1ebe53d5b741962f9367c29fba0c8541c6deb6 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 18:04:14 +0000 Subject: [PATCH 021/162] Update screensaver.py --- screensaver.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/screensaver.py b/screensaver.py index 037b1bf..020f16a 100755 --- a/screensaver.py +++ b/screensaver.py @@ -15,9 +15,9 @@ def main(): epd = epd7in5b.EPD() epd.init() - black = Image.open(os.path.join(os.path.realpath(__file__), "resources", "black.png")) - red = Image.open(os.path.join(os.path.realpath(__file__),"resources", "red.png")) - white = Image.open(os.path.join(os.path.realpath(__file__),"resources", "white.png")) + black = Image.open(os.path.join(os.path.dirname(sys.argv[0]), "resources", "black.png")) + red = Image.open(os.path.join(os.path.dirname(sys.argv[0]),"resources", "red.png")) + white = Image.open(os.path.join(os.path.dirname(sys.argv[0]),"resources", "white.png")) epd.display(epd.getbuffer(black),epd.getbuffer(white)) logging.info("Display black") epd.display(epd.getbuffer(white),epd.getbuffer(black)) From 65c62cbe6c0c2c6bb24d89390a36e8c5af34ba59 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 30 Jan 2021 18:05:02 +0000 Subject: [PATCH 022/162] Update screensaver.py --- screensaver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/screensaver.py b/screensaver.py index 020f16a..3708846 100755 --- a/screensaver.py +++ b/screensaver.py @@ -2,6 +2,7 @@ import logging import os +import sys import tempfile from driver import epd7in5b from PIL import Image From a4a8c5fa796df1ad737323382d846b3ae09fb00c Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:14:24 +0000 Subject: [PATCH 023/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index c35cc81..f99ad55 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -22,6 +22,7 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], + "labels":item['labels'] }) # Sort the array by priority From 505d3bb7480b0bdf158e38b9711fb04e483476a8 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:20:28 +0000 Subject: [PATCH 024/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index f99ad55..5e2e9c9 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -22,7 +22,8 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], - "labels":item['labels'] + "labels":item['labels'], + "due":item['date'] }) # Sort the array by priority From ad53ebd161396d4dd5c704e1a95079260f7d4351 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:29:37 +0000 Subject: [PATCH 025/162] Update infowindow.py --- infowindow.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infowindow.py b/infowindow.py index 6474cd0..d83812d 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,6 +6,7 @@ import json import logging import string +import datetime from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -134,6 +135,9 @@ def main(): current_task_y = 25 for todo_item in todo_items: + if 2156103501 in todo_item['labels'] and datetime.now()-todo_item['due] >= 0: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), + tasks_font) black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) From 11db9e80e535f022c5d75b96da72122c5a26e0a8 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:31:27 +0000 Subject: [PATCH 026/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index d83812d..dc35bd5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,7 +135,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: - if 2156103501 in todo_item['labels'] and datetime.now()-todo_item['due] >= 0: + if 2156103501 in todo_item['labels'] and datetime.now()-todo_item['due] >= 0 : red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), From 125cad1892a547d547cf0711b88ef55c0a6b2b37 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:32:34 +0000 Subject: [PATCH 027/162] Update infowindow.py --- infowindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index dc35bd5..edb1a1c 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,12 +135,12 @@ def main(): current_task_y = 25 for todo_item in todo_items: - if 2156103501 in todo_item['labels'] and datetime.now()-todo_item['due] >= 0 : + if (2156103501 in todo_item['labels']) and (datetime.now()-todo_item['due] >= 0): red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), + #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) # set next loop height current_task_y = (current_task_y + line_height + 2) From f3f2a19d61cb3ca3968065eaac870f51e1f78647 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:35:52 +0000 Subject: [PATCH 028/162] Update infowindow.py --- infowindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index edb1a1c..2452e51 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,7 +135,8 @@ def main(): current_task_y = 25 for todo_item in todo_items: - if (2156103501 in todo_item['labels']) and (datetime.now()-todo_item['due] >= 0): + #todo_itemstime.strptime(date2, "%d/%m/%Y") + if (2156103501 in todo_item['labels']) and (datetime.now()-todo_item['due'] >= 0): red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), From 3ac9c49ce2f5c5f8cd83140fa885a3bbb9d7485f Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:36:46 +0000 Subject: [PATCH 029/162] Update infowindow.py --- infowindow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 2452e51..5e7ac06 100755 --- a/infowindow.py +++ b/infowindow.py @@ -137,10 +137,8 @@ def main(): for todo_item in todo_items: #todo_itemstime.strptime(date2, "%d/%m/%Y") if (2156103501 in todo_item['labels']) and (datetime.now()-todo_item['due'] >= 0): - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), - tasks_font) - #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), - tasks_font) + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) # set next loop height From a91051f7e4b55a1d4e4f029bd5c88a9a4bffae7e Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:37:51 +0000 Subject: [PATCH 030/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 5e2e9c9..3185557 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -23,7 +23,7 @@ def list(self): "content": item['content'], "priority": item['priority'], "labels":item['labels'], - "due":item['date'] + "due":item['due'] }) # Sort the array by priority From c97d998bea67a44d67e333bff64b3dc348667cdd Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:38:52 +0000 Subject: [PATCH 031/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 5e7ac06..ffdafa8 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,7 +6,7 @@ import json import logging import string -import datetime +import time from PIL import Image from PIL import ImageDraw from PIL import ImageFont From c43b68f127cbabe0a90beab7d8ee5315ce9486e3 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:40:14 +0000 Subject: [PATCH 032/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index ffdafa8..5e7ac06 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,7 +6,7 @@ import json import logging import string -import time +import datetime from PIL import Image from PIL import ImageDraw from PIL import ImageFont From 3edacce1a750af431e55583f5f7ace9d6d5152e5 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:42:50 +0000 Subject: [PATCH 033/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 5e7ac06..fabefc5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: #todo_itemstime.strptime(date2, "%d/%m/%Y") - if (2156103501 in todo_item['labels']) and (datetime.now()-todo_item['due'] >= 0): + if (2156103501 in todo_item['labels']) and (datetime.datetime.now()-todo_item['due'] >= 0): red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) From 56178cc34753c4d5b9ca498c6a9554225c877df6 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:47:25 +0000 Subject: [PATCH 034/162] Update infowindow.py --- infowindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index fabefc5..265cd8c 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,8 +135,9 @@ def main(): current_task_y = 25 for todo_item in todo_items: + due = datetime.datetime(todo_item['due'][1:4],todo_item['due'][6:7],todo_item['due'][9:10]) #todo_itemstime.strptime(date2, "%d/%m/%Y") - if (2156103501 in todo_item['labels']) and (datetime.datetime.now()-todo_item['due'] >= 0): + if (2156103501 in todo_item['labels']) and (datetime.datetime.now() - due >= 0): red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) From 0bdf3832b5af1fb316cbabd221a0b50b569dac0a Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:49:57 +0000 Subject: [PATCH 035/162] Update infowindow.py --- infowindow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/infowindow.py b/infowindow.py index 265cd8c..b2d9746 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,7 +135,12 @@ def main(): current_task_y = 25 for todo_item in todo_items: + try: due = datetime.datetime(todo_item['due'][1:4],todo_item['due'][6:7],todo_item['due'][9:10]) + logging.info(str(due)) + except: + pass + finally: #todo_itemstime.strptime(date2, "%d/%m/%Y") if (2156103501 in todo_item['labels']) and (datetime.datetime.now() - due >= 0): red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) From 0079fd5363c36beaed54b8a8eff850f2b80c4f26 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:51:02 +0000 Subject: [PATCH 036/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index b2d9746..081ddc2 100755 --- a/infowindow.py +++ b/infowindow.py @@ -139,7 +139,7 @@ def main(): due = datetime.datetime(todo_item['due'][1:4],todo_item['due'][6:7],todo_item['due'][9:10]) logging.info(str(due)) except: - pass + due = 0 finally: #todo_itemstime.strptime(date2, "%d/%m/%Y") if (2156103501 in todo_item['labels']) and (datetime.datetime.now() - due >= 0): From 26bc99e26e3ad14a3eccd04f8d62a0a1770117ba Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 21:53:39 +0000 Subject: [PATCH 037/162] Update infowindow.py --- infowindow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 081ddc2..0567e0d 100755 --- a/infowindow.py +++ b/infowindow.py @@ -138,11 +138,13 @@ def main(): try: due = datetime.datetime(todo_item['due'][1:4],todo_item['due'][6:7],todo_item['due'][9:10]) logging.info(str(due)) + if datetime.datetime.now() - due >= 0: + continue except: - due = 0 + pass finally: #todo_itemstime.strptime(date2, "%d/%m/%Y") - if (2156103501 in todo_item['labels']) and (datetime.datetime.now() - due >= 0): + if 2156103501 in todo_item['labels']: red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) From ba628dc8cf2a7f6578c98c1ae2ee864940f2eb26 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 22:12:53 +0000 Subject: [PATCH 038/162] Update infowindow.py --- infowindow.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/infowindow.py b/infowindow.py index 0567e0d..0a365b6 100755 --- a/infowindow.py +++ b/infowindow.py @@ -146,12 +146,17 @@ def main(): #todo_itemstime.strptime(date2, "%d/%m/%Y") if 2156103501 in todo_item['labels']: red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - #black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # DISPLAY GROCY INFO # ========================================================================= From 814821a117d2f918ff7cda08ceb45f971825ce4a Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 22:59:41 +0000 Subject: [PATCH 039/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 56a1874..f2bb9bc 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -93,6 +93,20 @@ def truncate(self, string, font, max_size): return string return string + + def rightalign(self, string, font, max_size): + spacechar = string.find(" ") + num_chars = len(string) + for char in string: + (np_x, np_y) = self.getFont(font).getsize(string) + if np_x < max_size: + oldstring = string + string = string[0:spacechar] + " " + string [:-1] + + if np_x > max_size: + return oldstring + + return oldstring def display(self, angle): From 88ced3bdd8c669eef6b4713ead8a7a55dc36d679 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:00:55 +0000 Subject: [PATCH 040/162] Update infowindow.py --- infowindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 0a365b6..9bd2a83 100755 --- a/infowindow.py +++ b/infowindow.py @@ -171,10 +171,11 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), + spacechr = + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(red.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 166a45176e01a1f99320a41ae1a98ca3661a30e4 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:02:00 +0000 Subject: [PATCH 041/162] Update infowindow.py --- infowindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 9bd2a83..69708fb 100755 --- a/infowindow.py +++ b/infowindow.py @@ -171,7 +171,6 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: if int(grocy_item['days']) < 3: - spacechr = red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(red.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font) else: From 70946ecff1e4aad1e788f832a79f9c6df2d31d26 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:03:28 +0000 Subject: [PATCH 042/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index f2bb9bc..1b80dea 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -94,7 +94,7 @@ def truncate(self, string, font, max_size): return string - def rightalign(self, string, font, max_size): + def rightalign(self, string, font, max_size): spacechar = string.find(" ") num_chars = len(string) for char in string: From ff9851ac9a3d07ea22430b7d84043633047c87df Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:08:34 +0000 Subject: [PATCH 043/162] Update infowindow.py --- infowindow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 69708fb..9ddc005 100755 --- a/infowindow.py +++ b/infowindow.py @@ -171,11 +171,9 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(red.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), - tasks_font) + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font, 286), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), - tasks_font) + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From c603c5225a2cd8fddf6915c900ad2e71a27e0528 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:11:08 +0000 Subject: [PATCH 044/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 9ddc005..0af9662 100755 --- a/infowindow.py +++ b/infowindow.py @@ -171,9 +171,9 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font, 286), tasks_font) + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip()), tasks_font, 286), tasks_font, 286), tasks_font) + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From dfe812c7f846fe7d94495584b2f54870dce16410 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:16:13 +0000 Subject: [PATCH 045/162] Update infowindow.py mj --- mod_infowindow/mod_infowindow/infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 1b80dea..3e4f25f 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -94,7 +94,7 @@ def truncate(self, string, font, max_size): return string - def rightalign(self, string, font, max_size): + def align(self, string, font, max_size): spacechar = string.find(" ") num_chars = len(string) for char in string: From c6b7960652608de9565623197e76ab1f39ab390f Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:16:47 +0000 Subject: [PATCH 046/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 0af9662..31d3488 100755 --- a/infowindow.py +++ b/infowindow.py @@ -171,9 +171,9 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.rightalign(truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.align(truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.rightalign(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.align(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 39cbac8d2fa399243cbf2d9c04cb67a51530040e Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:18:45 +0000 Subject: [PATCH 047/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 3e4f25f..409cc99 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -21,7 +21,21 @@ def __init__(self, options, colour): self.initFonts() self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") self.timeformat = options['timeformat'] + + def align(self, string, font, max_size): + spacechar = string.find(" ") + num_chars = len(string) + for char in string: + (np_x, np_y) = self.getFont(font).getsize(string) + if np_x < max_size: + oldstring = string + string = string[0:spacechar] + " " + string [:-1] + + if np_x > max_size: + return oldstring + return oldstring + def getCWD(self): path = os.path.dirname(os.path.realpath(sys.argv[0])) return path @@ -93,20 +107,6 @@ def truncate(self, string, font, max_size): return string return string - - def align(self, string, font, max_size): - spacechar = string.find(" ") - num_chars = len(string) - for char in string: - (np_x, np_y) = self.getFont(font).getsize(string) - if np_x < max_size: - oldstring = string - string = string[0:spacechar] + " " + string [:-1] - - if np_x > max_size: - return oldstring - - return oldstring def display(self, angle): From b573e4baab0a801430b690c8196cb82741f41e84 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:21:25 +0000 Subject: [PATCH 048/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 409cc99..4e3c622 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -20,22 +20,8 @@ def __init__(self, options, colour): self.colour = colour self.initFonts() self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") - self.timeformat = options['timeformat'] - - def align(self, string, font, max_size): - spacechar = string.find(" ") - num_chars = len(string) - for char in string: - (np_x, np_y) = self.getFont(font).getsize(string) - if np_x < max_size: - oldstring = string - string = string[0:spacechar] + " " + string [:-1] - - if np_x > max_size: - return oldstring - - return oldstring - + self.timeformat = options['timeformat'] + def getCWD(self): path = os.path.dirname(os.path.realpath(sys.argv[0])) return path From 3b03290b519e23962849caeda6fcd4a00190568c Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:22:42 +0000 Subject: [PATCH 049/162] Update mod_grocy.py --- mod_todo/mod_grocy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_todo/mod_grocy.py b/mod_todo/mod_grocy.py index eb13d26..d8e28cb 100755 --- a/mod_todo/mod_grocy.py +++ b/mod_todo/mod_grocy.py @@ -29,7 +29,7 @@ def list(self): best_before_date = datetime.date(int(best_before[0:4]),int(best_before[5:7]),int(best_before[8:10])) days = best_before_date - today items.append({ - "content": str(days.days) + " " + item['product']['name'], + "content": item['product']['name'], "best_before": item['best_before_date'], "days": str(days.days) }) From ceafb3276bff6a17d08041956cd823d79e801eb3 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:26:32 +0000 Subject: [PATCH 050/162] Update infowindow.py --- infowindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 31d3488..482f6f4 100755 --- a/infowindow.py +++ b/infowindow.py @@ -170,10 +170,11 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: + (np_x, np_y) = self.getFont(font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), red.align(truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.align(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font, 286), tasks_font) + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 664b2bd20f8219bdec3f3034c1b50aee59a7a841 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:28:46 +0000 Subject: [PATCH 051/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 482f6f4..33a9e24 100755 --- a/infowindow.py +++ b/infowindow.py @@ -170,7 +170,7 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: - (np_x, np_y) = self.getFont(font).getsize(str(grocy_item['days'])) + (np_x, np_y) = getfont.getFont(font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: From 2ea0632870f1e1fe26ea8780977b43761faef470 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:31:16 +0000 Subject: [PATCH 052/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 33a9e24..7496e93 100755 --- a/infowindow.py +++ b/infowindow.py @@ -170,7 +170,7 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: - (np_x, np_y) = getfont.getFont(font).getsize(str(grocy_item['days'])) + (np_x, np_y) = red.getFont(font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: From c5a6a904407da03fd071c30c8ebcb1cd92a7a499 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:32:44 +0000 Subject: [PATCH 053/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 7496e93..c8eab8b 100755 --- a/infowindow.py +++ b/infowindow.py @@ -170,7 +170,7 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: - (np_x, np_y) = red.getFont(font).getsize(str(grocy_item['days'])) + (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: From 22c2686110c47f3d0bbfc410c1f5f209fe7dca97 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:40:41 +0000 Subject: [PATCH 054/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 4e3c622..3ae892c 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -46,6 +46,11 @@ def text(self, left, top, text, font, fill=0): self.draw.text((left, top), text, font=font, fill=fill) return self.draw.textsize(text, font=font) + def rtext(self, right, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((right, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) + def textwidth(self, text, font): font = self.fonts[font] ascent, descent = font.getmetrics() From 4e8ccb67a1255ef0613712fb4366fc1d9705fa89 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:40:51 +0000 Subject: [PATCH 055/162] Update infowindow.py --- infowindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index c8eab8b..6c573c5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -172,7 +172,9 @@ def main(): for grocy_item in grocy_items: (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) + red.rtext(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + else: black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 97e06d50f60ca11a531a44826e1ea977df6e5410 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:42:22 +0000 Subject: [PATCH 056/162] Update infowindow.py --- mod_infowindow/mod_infowindow/infowindow.py | 28 ++++----------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py index 3ae892c..33ba920 100755 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ b/mod_infowindow/mod_infowindow/infowindow.py @@ -45,12 +45,7 @@ def text(self, left, top, text, font, fill=0): font = self.fonts[font] self.draw.text((left, top), text, font=font, fill=fill) return self.draw.textsize(text, font=font) - - def rtext(self, right, top, text, font, fill=0): - font = self.fonts[font] - self.draw.text((right, top), text, font=font, fill=fill) - return self.draw.textsize(text, font=font) - + def textwidth(self, text, font): font = self.fonts[font] ascent, descent = font.getmetrics() @@ -99,20 +94,7 @@ def truncate(self, string, font, max_size): return string - def display(self, angle): - - Limage = Image.new('1', (880, 528), 255) # 255: clear the frame - self.image = self.image.rotate(angle) - - new_image_found = True - if os.path.exists(self.tmpImagePath): - old_image = Image.open(self.tmpImagePath) - diff = ImageChops.difference(self.image, old_image) - if not diff.getbbox(): - new_image_found = False - - if new_image_found: - logging.info("New information in the image detected. Updating the screen.") - self.image.save(self.tmpImagePath) - else: - logging.info("No new information found. Not updating the screen.") + def rtext(self, right, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((right, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) From 29328b57b9aa3fa65068a9f22138012430423166 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:48:58 +0000 Subject: [PATCH 057/162] Update infowindow.py --- mod_infowindow/infowindow.py | 44 ++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index 36bb65e..f675d7a 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -7,7 +7,6 @@ import logging import tempfile - class InfoWindow: def __init__(self, options, colour): self.epd = epd7in5b.EPD() @@ -20,32 +19,37 @@ def __init__(self, options, colour): self.colour = colour self.initFonts() self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") - self.timeformat = options['timeformat'] - + self.timeformat = options['timeformat'] + def getCWD(self): path = os.path.dirname(os.path.realpath(sys.argv[0])) return path - + def getImage(self): return self.image - + def getDraw(self): return self.draw - + def getEpd(self): return self.epd - + def line(self, left_1, top_1, left_2, top_2, fill=0, width=1): self.draw.line((left_1, top_1, left_2, top_2), fill=fill) - + def rectangle(self, tl, tr, bl, br, fill=0): self.draw.rectangle(((tl, tr), (bl, br)), fill=fill) - + def text(self, left, top, text, font, fill=0): font = self.fonts[font] self.draw.text((left, top), text, font=font, fill=fill) return self.draw.textsize(text, font=font) + def rtext(self, right, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((right, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) + def textwidth(self, text, font): font = self.fonts[font] ascent, descent = font.getmetrics() @@ -53,27 +57,24 @@ def textwidth(self, text, font): text_height = font.getmask(text).getbbox()[3] + descent text_width = (text_width/2) return text_width - + def rotate(self, angle): self.image.rotate(angle) - + # def chord(self, x, y, xx, yy, xxx, yyy, fill): # self.draw.chord((x, y, xx, yy), xxx, yyy, fill) - + def bitmap(self, x, y, image_path): bitmap = Image.open(self.getCWD()+"/icons/"+image_path) # self.image.paste((0, 0), (x, y), 'black', bitmap) self.draw.bitmap((x, y), bitmap) - - - + def getFont(self, font_name): return self.fonts[font_name] - + def initFonts(self): roboto = self.getCWD()+"/fonts/roboto/Roboto-" self.fonts = { - 'robotoBlack24': ImageFont.truetype(roboto+"Black.ttf", 24), 'robotoBlack18': ImageFont.truetype(roboto+"Black.ttf", 18), 'robotoRegular18': ImageFont.truetype(roboto+"Regular.ttf", 18), @@ -81,15 +82,18 @@ def initFonts(self): 'robotoBlack48': ImageFont.truetype(roboto+"Black.ttf", 48), 'robotoBlack36': ImageFont.truetype(roboto+"Black.ttf", 36) } - + def truncate(self, string, font, max_size): num_chars = len(string) for char in string: (np_x, np_y) = self.getFont(font).getsize(string) if np_x >= max_size: string = string[:-1] - if np_x <= max_size: return string - return string + + def display(self, right, top, text, font, fill=0): + font = self.fonts[font] + self.draw.text((right, top), text, font=font, fill=fill) + return self.draw.textsize(text, font=font) From 47f103f81f7cca50a7893102df8e66bd12c50ad1 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:49:51 +0000 Subject: [PATCH 058/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 6c573c5..ce19fbb 100755 --- a/infowindow.py +++ b/infowindow.py @@ -173,7 +173,7 @@ def main(): (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) - red.rtext(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + red.display(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) From b8a8191720db03f5d42a873c2a54dc7f00ffa9a9 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:51:47 +0000 Subject: [PATCH 059/162] Update infowindow.py --- mod_infowindow/infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index f675d7a..5304c86 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -93,7 +93,7 @@ def truncate(self, string, font, max_size): return string return string - def display(self, right, top, text, font, fill=0): + def rtext(self, right, top, text, font, fill=0): font = self.fonts[font] self.draw.text((right, top), text, font=font, fill=fill) return self.draw.textsize(text, font=font) From 73ddc4370f2cbda2c4d6eef0143f594923aa0d8b Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:52:26 +0000 Subject: [PATCH 060/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index ce19fbb..6c573c5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -173,7 +173,7 @@ def main(): (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) - red.display(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + red.rtext(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) From 1bfe4386845462451295c262f982758f0d4160de Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:56:20 +0000 Subject: [PATCH 061/162] Update infowindow.py --- infowindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 6c573c5..e4352f5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -173,7 +173,8 @@ def main(): (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) if int(grocy_item['days']) < 3: red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) - red.rtext(583, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + (op_x, op_y) = red.getFont(tasks_font).getsize(red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) + red.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) From e63d081aaae7bfe0cddfe1001796357629caac6e Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 31 Jan 2021 23:58:34 +0000 Subject: [PATCH 062/162] Update infowindow.py --- mod_infowindow/infowindow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index 5304c86..490cd06 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -93,7 +93,5 @@ def truncate(self, string, font, max_size): return string return string - def rtext(self, right, top, text, font, fill=0): - font = self.fonts[font] - self.draw.text((right, top), text, font=font, fill=fill) - return self.draw.textsize(text, font=font) + def spare(): + From b115bc07d33d9e6598e2e5f9cda13158c4d6d4df Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 00:01:04 +0000 Subject: [PATCH 063/162] Update infowindow.py --- infowindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index e4352f5..90719b3 100755 --- a/infowindow.py +++ b/infowindow.py @@ -177,7 +177,9 @@ def main(): red.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) + (op_x, op_y) = black.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) + black.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 0a03654c1fe7e2959b14fbe30a50de2069b2ee80 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 00:01:47 +0000 Subject: [PATCH 064/162] Update infowindow.py --- mod_infowindow/infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/infowindow.py b/mod_infowindow/infowindow.py index 490cd06..cc7dcd8 100644 --- a/mod_infowindow/infowindow.py +++ b/mod_infowindow/infowindow.py @@ -94,4 +94,4 @@ def truncate(self, string, font, max_size): return string def spare(): - + return From 7438f85b0aab95ceb91d0a46c7f31bb0b56bcf6c Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 00:50:04 +0000 Subject: [PATCH 065/162] Update infowindow.py --- infowindow.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/infowindow.py b/infowindow.py index 90719b3..6b502a9 100755 --- a/infowindow.py +++ b/infowindow.py @@ -170,16 +170,19 @@ def main(): current_task_y = 25 for grocy_item in grocy_items: - (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days'])) + (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days']) + " ") if int(grocy_item['days']) < 3: - red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) - (op_x, op_y) = red.getFont(tasks_font).getsize(red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) - red.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) - + text = red else: - black.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']), tasks_font) - (op_x, op_y) = black.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) - black.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + text = black + #red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + " ", tasks_font) + #(op_x, op_y) = red.getFont(tasks_font).getsize(red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) + #red.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + + #else: + text.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days'] + " "), tasks_font) + (op_x, op_y) = text.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) + text.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), text.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) From 85e763b27f253904daa30a0ef38e1ddcb08ee40b Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 01:00:14 +0000 Subject: [PATCH 066/162] Update infowindow.py --- infowindow.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/infowindow.py b/infowindow.py index 6b502a9..2b9fc62 100755 --- a/infowindow.py +++ b/infowindow.py @@ -175,11 +175,7 @@ def main(): text = red else: text = black - #red.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days']) + " ", tasks_font) - #(op_x, op_y) = red.getFont(tasks_font).getsize(red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) - #red.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) - - #else: + text.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days'] + " "), tasks_font) (op_x, op_y) = text.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) text.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), text.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) From 3d6e2d6e9d0da262e649f575ace682508bb17ac1 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 01:27:50 +0000 Subject: [PATCH 067/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2b9fc62..a0649d2 100755 --- a/infowindow.py +++ b/infowindow.py @@ -98,7 +98,7 @@ def main(): # Calendar / Todo Title Line black.line(0, 0, 880, 0) # Top Line red.rectangle(1, 1, 880, 24) # Red Rectangle - black.line(0, 24, 880, 24) # Bottom Black Line + black.line(0, 25, 880, 25) # Bottom Black Line # Titles text_width = red.textwidth("CALENDAR", 'robotoBlack24') From 6a97e8273b2fd2f55912e1ae33a9be92beb8ecf7 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 01:31:07 +0000 Subject: [PATCH 068/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index a0649d2..9ed4555 100755 --- a/infowindow.py +++ b/infowindow.py @@ -208,7 +208,7 @@ def main(): line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) - current_calendar_y = 25 + current_calendar_y = 26 for cal_item in cal_items: font_color = 'black' if cal_item['today']: From 41092f948d402e0191ddc7a9a36de88989d8742c Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:13:54 +0000 Subject: [PATCH 069/162] Delete infowindow.jpg --- infowindow.jpg | Bin 78483 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 infowindow.jpg diff --git a/infowindow.jpg b/infowindow.jpg deleted file mode 100644 index 6ea104047e0af644eb0b968c593df9c27135619e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78483 zcmbTdbyOVBwlF$)aEAmB&Jf()-3AyKoL~v=Zb5>(3?vY2uwk&^F2N-b2(Ah4E`eYl zzjN<7_x*9#_uhK7y4R}OTDxlR-PP6APxDV}fR_rscJBZH6%`=h1>k?m(;@yai;r6qJ^c4 zm!|~%b5H-v1SeMwjsFP#Uq{Qy=^wlPMeX6KWAi`V_+P0#z<#baTsk%$E?(|dHqYb? z|7Lz3yZ^hQe+Zv@BdXUj`A*6UQRa7o+|PZ^v@VhYddREMP5Y_8ATaBK0$6? z1sNV55jj~I89r_qUTy_B5e06Uf6Msacm?@+g?R-PMYwtS1$lTBWJCmb6lD~Jg#RT| zL`L}EyeiHfo)*qlHviUZ_pJB7d4>P4yrOdMHWr>P?qC-e$A5bO$kxTv#lzOcl}=7q zkPc$!Z0+LX@z3P^m#y+P?sncb){5>fPIUhoT~WLLMY|&3|C`nS#cTclXT-RkJHz#l zYy3Z4<=>!Z|NE!1x^h>58vDJiLF|5K1KFfed& za9ogu+9Ns?AG+Mk+&Z!65sz2*5!>dhS08AwUWs$BM6EEwCM? zBsxuc?(^1~NDK9%L4YKxQ{3}?#?+@*)DNxJ zgmabr(p67@uNyOc!%`Q-Iyh=4f2cn6&`OvWX=sO<-dX7dtkZ5h0SNTK*(Xb6i3~Wm zXU#y|#;)&@O!ZFyH_74;ssOl-)V55B*85P?Rsm6VBk1Sq3*JSA%n(Sp8Z0a2%ZJZ5 zJ+n&(5+905X80a$>7M`(k~?t?q*?e^GWbt`y&cJVkb?PkBo|ljIN<|pe$UD6XQ$vQ zOuJL>F!;<9Ks&+gafA8(cuN~fyRA_lg8qx;(TU#?|ALQ%F|T)YS|m6fT>mT}B=oOD z*WQO$iwQhW00diOO}Een9tGU)3BaEkC60`LKPCMHnBk9$JK9W{danVfzEEO^6gDMm zPn!u9AwL1W&wDX)IWIl|1WqLQJq*Wh3T#4E&+vHD#>Y`W?xSOeTVa|&og4^J)qJ3S zS%TV2(u~FXNI`{GDRuD~o&Xr$rgv>xFD(;rPXf*_RtSJLc7jzf5aU?PeQ83D_{Oxa zmAZFnzsb*s6*(1un2@@HCHTn8EjbdXlMn4t!!JPXg)H?)T5YtFLQjANy&MKE2T#ji zf1d!?AQiqwxXMgiU^(o^huZL0&_x5QP!tBIUv~|^p6A~N)R)jO zncz!{^6L=6SCib$|DkOaw14!>g6_F6-W03P!F7@#5orC&_{V$jP(7w>oEWO@%|u(peB$0K4+c)_iSkw50j1B z04d{r@*c*hJvyI(g!C7kSbrtQz-iT34`J@|FFMitM@d;RG1VgB^n1N> zuI||{Z{f399=>BHZXXk?rf=b+ugBMZAjRnYy|dberHos)@`wgf*GPSn@6q(3;&=i$ ztWEvZ-Odkw91NO!0%QeF)BU*?JMOxll1=`x5qeo;B951GPlEmHYs^{#z{@)_HnS`L z(n{(=D=St>jNp8L@Cm?u3m*ih0x2)0lc7NEWz>Q=QVs%gpN|vJk)6}czAp8|p zBs`ra#4InI2!jh0l~0J|TOfaa27J`H6LN z(r7fT_6flh7!_9&6#HlulQ^ss^jZoe4HPJb`2uEe0gwb3pq=l(|Joi=q#;ACC*vXA zgFwD#l_p)w=X8tSfEdyqa0UzMBUM~}BIH;bLGptwFbCr~3^`R~`UUeP5!;%cb`zyG(i(_C zx*I{0#mXiDegTRKP5~(;@OO($sjI^9_)X|v{8mWP!$F28!@^~uifRcc@N53%YWJQ)p=ailE@1%_uCIB5HoK~N z<(mMCJep9H$7D2KI}@$^3?G&N^Dn(UV1s;f<|pzi0&oPGN|>YJyu}p%i?4xWZStNd zxzkPp{916*ux4+~zl66f$?wIiZ(`bScs%EzS21dZL5kZtKk~dA8)_m(3hcU|Snkt} zElpJ{#aqHD)mHBgM*isia(E0s2p=mne6$L|$6b_Xp1ccpKq;UYRzjLlh==6c@srQz zO=I!?>cV1R>er@L%um(8C_pm$R{qAH8NShkS;yW1c5|WpfTqe#1A)Us6;vZhk^BK5 zu?cc`I22?SY8Fbtb_rt8#=DZ!0-GG2-(jZafkOm|~=xPYR%zFNdHEDS1l)NZP z0y0Vh(nu`VGai72@ocx?^Ze#S$^nw?EyG;wQSyM=?4DpG8j7!gMUR!9nvw6; z)pPAvh%x)KrtL+F%^Xt_cTYm3!jQ1*0L*wnP3~1j+KGuZ%XAh3 z!Y;8wu`scaqfG?>E+cSO6a{t)6j%uQB^sPj1cV+6qO!#Xku@MmgGgi)NYDsh_k_~0 zRDB}X3M)bABf_IM6gU}ILt{~GshdI6{4YT2cyxZYjJry>=ntL*r2MK`c&Ib@-*iDo z!Bp^4uo!AwB0r#}EQA`2gXA3O&nAu3fzr|>`j*J=7ZkM$S$PMf_y8uNA57vKuPU?c zBGkiiCF`P`ur}W3xcmQ@=V0 zMe>YAgL`yg(QziqhDo&S!}ev?Tw)`WT}dg=5#VeCmLyJu1yEty97?Wao1F$y(9v_d zP*T8BI8|b&u|&|;vzY*zBE#&{VQ&=RRupC^iP*G&>Yd2eOv|T?hc_>%eY7egB0-K~KZl9tS@%_-l##m53drMF(<+I^K4e9261upvNr{SQdh{0p{(jW|9uw zMbNYYID4VeKX+)nWT%nx1-#q^0L2tY52SA5!2nMxrvtWRM{vE1uDJq zNa`_ECpqm}1ij_ra}ER|q@mb^y|4&5)zA&?v^TA9oVw4b?5naUjXG%4?O#+jG&=fC zx-Mx`?Z(Nh!#&WZk5W6LMDdB6;kKhWjr^}C+V%&LzL=k1Vhm1LoQiC(9M zWWtR`T8(y z*L?jo1+sk0kbE!_10vKcgaj=Dg&K_PBz*&x>7K#st%7CZwHBnT<(t4!q(k$CL89oa zk#97RyA+VoW8ckgJX@Y<{!JRh0VZM+TpJH#ys^c8z9uM|Jqn7edqEWU{BpDQSw_2s zrpU7{(pS?M4wL@*bJD#P50h(BSkiW!dFc6 z=A_%qXh}&P^7p33mlFtyu8y%364V6<^6{|>92#U&nxaurdP`!zFD90U%OPiRvhI>I ze9G^&#BKFgAmeniRn6KMh%tT$4+2|ve5S!-CbZ~8lou{6?Zc~0?ujJdZ&n<*3lux{ zHA-ko(9ggjD4nP8P&t3jwpMN-`du9~5G5&8thO=@xH4Cjs+YPJzY7R31C#Xh1Y9qr zTtHKh=ehp~%zArh6WFh-E;#`Ct|!2K$42&G6mzDlF7+2fd+42jA6v=GIMyeCU^SB%4BskqVzE8_IOJNTs}7QyGx6=v zqm|$pQ;_};8`P!$ui6XUBthtA*$O$v*G-HI)l#>J!?dVpNU2-+0t<~(`LN~&j!gEm zWZ)={lx5C8D!z8pjXk8cr=LX9ejV%Df9C#0jPcc4lme1k9cY3eKh_c;N|9W1gCG<{HrU`i}G#=);Wf{o^6_9vT4q{pX!r0o0G} z7Y@zg&||-us!uOQ8?V&Yf01`AB|pSQebd~v!h#K<_qpwtRNG6AKLPXv#4zflW;DcD zuYLCS7L@9S&cMfJBNCs_x?}C0o9(#dwTa*UQm1?4FF^&wSPAp?NT!xb4^KR??5}kW zY4^)ZZFs2nv%Ep{Lo8t`s0){GHBDp7e&Jx?raz=ch!rfNQ?lKW8s!Yo zwz$8)ErfKwe2<%ka-~=r$n*S6pizh4AbffWh2uYMkCd$tHxR}RnS@L-dXQG_=F~%z zhFG$J4W=gWPkjAdib{!*j64fOoRk9m%2vD*xU5qU{1rp4e{5-1TfXI50Okzfdv11F zRx@dCU_WCP*oQT5n$7lyX}6)9<*PY`D~5m%&(3AGL0~57?wc_!iRJUMC%qYJ2co;N z-?y@1{1|>~TLZE;3~?pG?$a{(cd;hv-Fqm%)XE6Q=z7T{@yb|#YFuuc-gF=7%1A0_(Y(hd zKGw?tZ*f!t>f|Y)cxnR^<4yJWr%#JYKCB=t5pexFu<1+Xv1Xj!Qxxeo{AUY)J4u~%=4@k zh+)>e_Oi0h3pXrs)hWvtr-Zs}6@_gBo$ohG!OA|qb7KwiWq8L~Pk>X9)*tVc7W3bw zdf)Ea9G(E*AaD#HjVHhjkeN}e-J$#()cdf?_cL!;0i$ieX&;|SG~%x9{Wd<6!n{@6 zK=9jhZMMe~tNUNW^4$R)Nf#QV)eLPJBoU=5>uFY}+V9JIcQ#^qiHxbTU17bMD2DB4 z*PTNBUkYD@1ykf6`FEkywn4Q#?_WqPSK~@dow7`HELYX(u9V*p1T>g>1x=mO{c)1m zdd4r!X!+(LBPBu%%Z@8=2&mk)1}e^HKOmF0B}L{QNj+mEDFPbacZaW(_Uf*fcgLUc z3@*{bw#P-&{WM&XGR<7lPIqWT|1bx%+X`kg=E>7SzL^S~`Ds>&P(#UZFt&#ri!+GJ zRBK36-P~^G)NWr4Lt}%<1TE|3Gxn9SAxfFBxWvsrf%72M3X-bVs3C=?x>QqIK4a-n zRDXug{M{^X1$E0crsgcv<)>EeP_i_5OL$q{D&3ZpJP*+PZOJF{c^iLu)ooe*cmskx869xMouo&xb8vg>sCkiT=j9xa-DGij}8@__wz3ejKh&* zG~nKcIYe38`BklZmJt1%P?}ZvIYdCJoA7Uh0LJm7)ELPG&B+FSD$qd`{aez-2PXR8 zV(3C+vc&>xV$fFT6jbzZ5&q2XVaBUX9iF)-9%1svaBxwCN^!|UBHeQtcJ>*9 zKv)UyHU^W^<_vuOOXWCf5% zexQ*GD^z|wnVh)=#@ip?qTfp~nW{s+AD`~g;YpBWB=P>D^j#}{T{jktwKq^_GVvQD zvXvJ}=(nw1Dol97*iu67)^=q45*$SVkBvwG5L{|eyvstrkNkxfX8op0}+XPn@ zv}i5WOR5`w#UwRcBW#6suXc76%k~Nk{#YPg? zv`)LO{R~_C%6N&@JUY^Lv9R!Je53Xzx}&`)c`3%x)Si|x&QmYh>*Oe~%BM{`BKb$1 zb)GeY&dr|St!kxZ?a0zXJH-3Q!p$+Nj>q6Ib$8R(ZR1RvD$^r?$c$e6u+g8#(v7iL z^FmpC>|On(IE@2lw%kF#ihBBhq0?3=v|n8TVJJhTu+LXw+2jIAFcH{T**Z4;#^N`) z;EORsIw-4NnsVST7eZP!11%7;_Q|O)l^c@DI<|&}+u1?i~yU*mzAZj|_em@zCJ3B=l;lf7YP8wquAAwOl`XJmto9^mM*_ z?atG0(ZHLF7eNz-t4>1BQJ6g3>jGLDN#r~)5m7z8v_0cJufV-p3zIYI{4a-_fL7Wu zZWB6Rm4+d-VXz;Nkz3U8%%Mvo)$a4T>{0Xb2gg+$CN`}iKdi0lV&7rvPOesov9v)R zN%oqFzEJPp*qR3Kjc*t2zr>QTmL3LAXzP3b>UBBwJWfSFw`gJr9-&%}k9p)0ZGw`< z4lg&DOXiLshoefMHIrMn!Rn3smN9t>YQ2%U$wT9#j^gxX4+uV0dK4wa7q?z(M&E-# z--FSeTdF{Z_&LwJB>cD!EF8Q02~_s3g3i?;`X?AkQHQ?g%^S&+uEne>I&)4hsD>qw zTLxP%(h}Rmr93H1nO>+N5yn;#jqa#UOLrj|e_9^rJb3xei=DS5p;iUFBpG2`Z?S?!s{|D}Zd?B7vj?wd zM46~Ki)mTYg{i&Y*;hW*|8jvCeASxy>7*=ryl)JWh}K=Q+v3QD2g>lrm?~y zqP}3asY7DJlFT}xU~o*4RgouXTJ6Ptv4C23VxIqc^wuH`Et;`wEvCj|F*%!MyS|pW$&;Zv;#j99dw&R@+MO7rt<#9u@O@anj8muXSqnP+? z6N94pq8nT*qSAQ=#1*kpUEy}DMd4S47F~h>QGbNxUV!jEg0_wYQD89his4g-9m$xY zuQTR0H_bFd*leNoeiNW@MxTAq+J~h}2;E3u;etXuDhK!)4-Hx7w}a6hrtrgEJ9FFD z(ymwdZ@Ud#!^@0BuoFEeBj1=l0XmC@7EY8Cdwq2mTGQU96-L~+=@HKU@%k$CZgNb5 z%vd`&?6f6$p{-0pUxKV+LrX{9I+1K^wBN7-{E#C7?wdU9MIh|%fc_Ec{2fqga32(f z^mzw*w!u6)n=&q3783F2gi1*C6;(9Dus-RQ>ZUm9B9Hc(KTHLpq#)xGEg8Dc9QmeO z2r)d37edxRda2eDL8H25#K8WFsLtW6l2g3vzF$r4>#1YXB3m%KHhaGb@}IpjdCawu z{8U>}r&NJ=uTE)WTh2Y#uU9CAlxvuRd9rN>3XCX zkz7|vuwkdd=GTBHG^NK=*4%V1xCFY+SXCKwCMm4NHoq{X_#$Ar0nJVt=Y0a$wTX4h zW4hqVGFrjuya@928%Rg2|0HfMwWQxAd5L7k>yXeIh<-@bK{O{REoHBg`@M{zbYa}D z`K&Zqpv7N3ihNvPBleF8zsq|mFL; zZoihe5xHIk)sdj@qjs{tFnY%eCO>1TnQvV?#E8n(Ez0T!T+YOM1kifjh&xK8MWxX& z*rwlmR_-wdL*5KR zlS+_D&u`B8Ax^(GNeLrXg=cnZ~Ocg=|7O7aR zodaHPVu{%6A%Ov48GGg&gg}u3fu4*#YXg!_Lx03Lrr{&76DTBM&ZfDMF_ib9mPhRu zSUvMtcW6OJg8_9^zd_$Q4c>DaBq@R?;g)yNGD|}8t}b$Bd)ls+ zSt+0q(bD@y2OcA5eo~Mm{lyl{+X@nsk3+*T%vuCJm~5B_8^&{E=e~E&;_o{uhxjOy zzr9Ve8VHW}%9PZul2pB;tvdY$p<8khzi?a7EM0w#%TNSAui{IBES~#``F2LmFX?r3 zguLU6p;`Woow1|Bl2C1yOXYTJo_X#fn&dk)RpUOe93UBtHOG2!!}XvP;l#$H-NK_a zMyKf1SJwZ$+Y8C^tp0gx(e7)H~r7= ziiw}Q_UPg(%$yT}oX;=C!n}#rhujqJ!EbQO-Ew!>Yun0~vwb>{BnDF3QB*w^%vD2c z$%EJ!J~eQXr!4{2PG{M(^x?StFGB5QNLdi+I@=Mo^GVpTH;CvHLn7FbNyVo!_Mk>G zQ0Vv5-3E$8hGyZE?0K{41l2UQKcpN=NyYGsCxDavDq{FoyD--w26d23i#C;73W+Z( zoEEH`uy0krz?=8Eu7;?HvVGGN?WUXRL>20bW|P(+ZQu$o)S{U1hLOSvl^gbBK}(v) zuPTILKg|-;$%)5ABbxOXb~L`{Y&H0ZR|Mk3Ut0`)sEb{n4dSiq)p*Vt3da}opQ$q& zEstfg1D$cgW8}+$BNLr*my?DsZ+NdDXE%dVjaPD%Bz6 z#M{5PC4R#-1X1%N-ET305ZRV7H#YX*8r!tQt%@9ZSzO>pS~Yox>U@Be7%2l2F3>~z zLAX|5k|MUE=P+K%=jGwCH#@@5&ceZ}0RnK^O?riJ_+gYWyL7w|P}UdaBeTiZy+{33~c#cbyCKhL+BJFPDgP-eig_^$sBZ2=Zo&Ic=SD zY@UPC=mW$JgOp;Gg-WUe1YPWp4lkUVK8Gz&%-7kQqX9&bBji9mjt_NVcsP<0@Q!>o zL*#wBQ~K?yQuh|qxkD!Lrpzeo);*xnQQ!0Sx#sq|Alc8CHfur4d0x#`lWx_!P_OBq zGt_m|zdg0IQDm$IEQH@)vF%s6$1U&%zI+u}%=(KdgD0{Y}8rQ;gi1%k@}!5}bBe!79cSL`D_i@^NnLw$pCFYFQ}Pud=CebIc*i z`j^csl&B(w2!F1^-@aQrV;5m$8?nYG<+|0Z&HF!i!OjjpMvj6DH%`9@cPO>uW-tv= zMlsG^z0@@DQhzg^ z0A~<$+VXFA_%&DWvdbhXqYv6MqE zMmwOF1}?^Gv>x@qIe}3QZe_9%imwR8sVBf=;20~tld8+rAkYsa_^QCj~HkmLR zP>WE*!OWto4e{&tKK)B-2@DJ_V~xSrOV3ck-aDvtaBt;^`Rc<=OD=|%{bg0j#oDlz zK1_8<`&J6=Wz`OLR6_IsPke0dIHScoV+rK^uj0Zqs2(J8q*+n^B%zF_ zAO?B6p-tOq-w(H;PXIi~$Jln&vo6f4ifMh9byzZ)fMLaI)Z0qg-&yi9NpEM~C&NUs zw196Io15KbH-vSNTEWjUynH*JPi0}^STCyI>_nAF@T}|U4Jy${&VFYx!TA3Ehai;@ zN}s*9qZzfQ4O-W3t~gu5xF)TCrE0J+^EGO0jR=IZlO+kOq9&P@IV-&Bc%{Rzac0uZ z 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 From d47e1fbcd21f09696f0be727ade43ba81519d5c5 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:27:14 +0000 Subject: [PATCH 070/162] Update infowindow.py --- infowindow.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infowindow.py b/infowindow.py index 9ed4555..a5b9b99 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,14 +135,14 @@ def main(): current_task_y = 25 for todo_item in todo_items: - try: - due = datetime.datetime(todo_item['due'][1:4],todo_item['due'][6:7],todo_item['due'][9:10]) - logging.info(str(due)) - if datetime.datetime.now() - due >= 0: - continue - except: - pass - finally: + if todo_item['due'] + due = datetime.datetime(todo_item['due']) #[1:4],todo_item['due'][6:7],todo_item['due'][9:10]) + logging.info(str(due)) + # if datetime.datetime.now() - due >= 0: + # continue + #except: + #pass + #finally: #todo_itemstime.strptime(date2, "%d/%m/%Y") if 2156103501 in todo_item['labels']: red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) From 78175d6d02e247528483b950bb7d52bc7421e390 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:28:36 +0000 Subject: [PATCH 071/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index a5b9b99..270b4e0 100755 --- a/infowindow.py +++ b/infowindow.py @@ -135,7 +135,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: - if todo_item['due'] + if todo_item['due']: due = datetime.datetime(todo_item['due']) #[1:4],todo_item['due'][6:7],todo_item['due'][9:10]) logging.info(str(due)) # if datetime.datetime.now() - due >= 0: From e5455abd92fd1d6c909e6d4a67095d6764d78c9c Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:29:56 +0000 Subject: [PATCH 072/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 270b4e0..33904d7 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - due = datetime.datetime(todo_item['due']) #[1:4],todo_item['due'][6:7],todo_item['due'][9:10]) + due = datetime.datetime(int(todo_item['due'])) #[1:4],todo_item['due'][6:7],todo_item['due'][9:10]) logging.info(str(due)) # if datetime.datetime.now() - due >= 0: # continue From 32919258e4c503c3f0af4580ebf1c95080d51ef2 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:32:14 +0000 Subject: [PATCH 073/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 33904d7..94ba1cf 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - due = datetime.datetime(int(todo_item['due'])) #[1:4],todo_item['due'][6:7],todo_item['due'][9:10]) + due = datetime.datetime(int(todo_item['due'][1:4])),int(todo_item['due'][6:7]),int(todo_item['due'][9:10])) logging.info(str(due)) # if datetime.datetime.now() - due >= 0: # continue From 06bbc3f3e278619cf49162e2649020cc8ee3177a Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:32:48 +0000 Subject: [PATCH 074/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 94ba1cf..d9af784 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - due = datetime.datetime(int(todo_item['due'][1:4])),int(todo_item['due'][6:7]),int(todo_item['due'][9:10])) + due = datetime.datetime(int(todo_item['due'][1:4]),int(todo_item['due'][6:7]),int(todo_item['due'][9:10])) logging.info(str(due)) # if datetime.datetime.now() - due >= 0: # continue From d802f180f4ceeea58fcfc8c21b9af7f2470eec94 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:33:57 +0000 Subject: [PATCH 075/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index d9af784..4be322c 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - due = datetime.datetime(int(todo_item['due'][1:4]),int(todo_item['due'][6:7]),int(todo_item['due'][9:10])) + due = int(todo_item['due'][1:4]) + int(todo_item['due'][6:7]) + int(todo_item['due'][9:10]) logging.info(str(due)) # if datetime.datetime.now() - due >= 0: # continue From 3c051a1c1524401bfc4261a9d69df949cd2ba158 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:50:16 +0000 Subject: [PATCH 076/162] Update infowindow.py --- infowindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index 4be322c..f132650 100755 --- a/infowindow.py +++ b/infowindow.py @@ -7,6 +7,7 @@ import logging import string import datetime +import rfc3339 from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -71,7 +72,6 @@ def HandleException(et, val, tb): sys.excepthook = HandleException - # helper to calculate max char width and height def get_max_char_size(black, chars, font): max_x = 0 @@ -136,8 +136,8 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - due = int(todo_item['due'][1:4]) + int(todo_item['due'][6:7]) + int(todo_item['due'][9:10]) - logging.info(str(due)) + #due = int(todo_item['due'][1:4]) + int(todo_item['due'][6:7]) + int(todo_item['due'][9:10]) + logging.info(str(todo_item['due'])) # if datetime.datetime.now() - due >= 0: # continue #except: From a3a9b40b5989ea97831ed2f32d8d814b609552ac Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:50:21 +0000 Subject: [PATCH 077/162] Update mod_todoist.py --- mod_infowindow/mod_todo/mod_todoist.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mod_infowindow/mod_todo/mod_todoist.py b/mod_infowindow/mod_todo/mod_todoist.py index c35cc81..016cf2a 100755 --- a/mod_infowindow/mod_todo/mod_todoist.py +++ b/mod_infowindow/mod_todo/mod_todoist.py @@ -1,6 +1,9 @@ import todoist import logging +import rfc3339 +def get_date_string(date_object): + return rfc3339.rfc3339(date_object) class ToDo: def __init__(self, opts): @@ -11,7 +14,10 @@ def __init__(self, opts): else: self.api = todoist.TodoistAPI(opts['api_key']) self.api.sync() + #convert todoist dates to date objects + + def list(self): items = [] # Loop through original array from Todoist and pull out @@ -22,6 +28,8 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], + "labels": item['labels'], + "due": get_date_string(item['due']) }) # Sort the array by priority From 5d1e10b62599ebbc60bc69cb0497cbf3dd256f83 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:52:07 +0000 Subject: [PATCH 078/162] Update mod_todoist.py --- mod_infowindow/mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/mod_todo/mod_todoist.py b/mod_infowindow/mod_todo/mod_todoist.py index 016cf2a..5e08fdd 100755 --- a/mod_infowindow/mod_todo/mod_todoist.py +++ b/mod_infowindow/mod_todo/mod_todoist.py @@ -29,7 +29,7 @@ def list(self): "content": item['content'], "priority": item['priority'], "labels": item['labels'], - "due": get_date_string(item['due']) + "due": get_date_string(item['due']['date']) }) # Sort the array by priority From 9afcfd0e999d285855458133934143b6bdb087a9 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 22:56:24 +0000 Subject: [PATCH 079/162] Update mod_todoist.py --- mod_infowindow/mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_infowindow/mod_todo/mod_todoist.py b/mod_infowindow/mod_todo/mod_todoist.py index 5e08fdd..846fab6 100755 --- a/mod_infowindow/mod_todo/mod_todoist.py +++ b/mod_infowindow/mod_todo/mod_todoist.py @@ -29,7 +29,7 @@ def list(self): "content": item['content'], "priority": item['priority'], "labels": item['labels'], - "due": get_date_string(item['due']['date']) + "due": item['due']['date'] }) # Sort the array by priority From 2e6a709c66d659a9aadc6378cac2763c0f435c24 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:00:59 +0000 Subject: [PATCH 080/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 3185557..37274d9 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -23,7 +23,7 @@ def list(self): "content": item['content'], "priority": item['priority'], "labels":item['labels'], - "due":item['due'] + "due":item['due']['date'] }) # Sort the array by priority From f1e6c92e24823c56d2e983d3c8cf5df28792acb0 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:02:11 +0000 Subject: [PATCH 081/162] Delete mod_infowindow/resources directory --- mod_infowindow/resources/black.png | Bin 662 -> 0 bytes mod_infowindow/resources/red.png | Bin 1101 -> 0 bytes mod_infowindow/resources/white.png | Bin 1100 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 mod_infowindow/resources/black.png delete mode 100755 mod_infowindow/resources/red.png delete mode 100755 mod_infowindow/resources/white.png diff --git a/mod_infowindow/resources/black.png b/mod_infowindow/resources/black.png deleted file mode 100755 index 41e62e226a9dee366b3e4960696d1850b149d2eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU~J$33NT3c^&|l~Dw)pC0iMpz3I#>^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6O}g9TNEG+3|Cf97srr_TW=2c%lRHw5O||%Q~loCIAeDt(yP< diff --git a/mod_infowindow/resources/red.png b/mod_infowindow/resources/red.png deleted file mode 100755 index 9298929c318bafaf5e25d5071b48ba6642ff8562..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1101 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU~J$33NT3c^&|l~Dw)pC0iMpz3I#>^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6P30;SH6=M7|?q>T^vIyZoR$aD9FITaoE7&{-4*5Y&Jri zd&{`q^{_|?FgGz8Dv(UEu`nk&7$}fV9cf5#;Nf5+k?LSP*dQUmLk2~L-=Kj(y5B%y zLb~6efkB4fKw-k{)j&@C0X32rf8_g-^X_+~x z3=A3*=T6w`btFKbb^n@0N>M+XS9oyUW|a=U`J?$@QsUWl30qF7MaK9fd2G_z*Oc&)Mp1{-IAM`SSrgPt-7 zGgd6MF9Qm)mw5WRvOi&z6O~qRuHW(l7|^>tT^vIyZoR$aD9FITaoFI%{^yF!a-A)4 z)~w}jOgtTo2OA^=NT!&Z7!4I91W2cn91IlLSeQwqjx;1V@Nlq^L6PA%Xkd`;H&B?6 z?l)*)kl{CAm~=3z9v~=3> BK^p)7 From 93262b087ccdbe205584c30764d4e542163375ea Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:02:18 +0000 Subject: [PATCH 082/162] Delete mod_infowindow/mod_weather directory --- mod_infowindow/mod_weather/__init__.py | 0 mod_infowindow/mod_weather/mod_owm.py | 109 ------------------------- 2 files changed, 109 deletions(-) delete mode 100755 mod_infowindow/mod_weather/__init__.py delete mode 100755 mod_infowindow/mod_weather/mod_owm.py diff --git a/mod_infowindow/mod_weather/__init__.py b/mod_infowindow/mod_weather/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/mod_infowindow/mod_weather/mod_owm.py b/mod_infowindow/mod_weather/mod_owm.py deleted file mode 100755 index 82352df..0000000 --- a/mod_infowindow/mod_weather/mod_owm.py +++ /dev/null @@ -1,109 +0,0 @@ -import requests -from datetime import datetime as dt -import os -import math -from PIL import Image -import logging - - -class Weather: - def __init__(self, options): - logging.debug("Weather API: Open Weather Map") - self.api_key = options['api_key'] - self.icon_path = "icons/" - self.city = options['city'] - self.units = options['units'] - self.timeformat = options['timeformat'] - - def pngToBmp(self, icon): - img = Image.open(self.icon_path+str(icon)) - r,g,b,a = img.split() - # img.merge("RGB", (r, g, b)) - basename = os.path.splitext(icon)[0] - img = img.convert('1') - img.save(self.icon_path+basename+".bmp") - return basename+".bmp" - - def getIcon(self, iconUrl): - # check for icon - bn = os.path.basename(iconUrl) - for root, dirs, files in os.walk(self.icon_path): - if not bn in files: - with open(self.icon_path+bn, "wb") as file: - response = requests.get(iconUrl) - file.write(response.content) - file.close() - - return self.pngToBmp(bn) - - def degreesToTextDesc(self, deg): - if deg > 337.5: return u"N" - if deg > 292.5: return u"NW" - if deg > 247.5: return u"W" - if deg > 202.5: return u"SW" - if deg > 157.5: return u"S" - if deg > 122.5: return u"SE" - if deg > 67.5: return u"E" - if deg > 22.5: return u"NE" - return u"N" - - def list(self): - url = 'http://api.openweathermap.org/data/2.5/weather' - r = requests.get('{}?q={}&units={}&appid={}'.format(url, self.city, self.units, self.api_key)) - - data = r.json() - - # Sunrise and Sunset. - if self.timeformat == "12h": - sunrise = dt.fromtimestamp(data['sys'].get('sunrise')).strftime('%I:%M %p') - sunset = dt.fromtimestamp(data['sys'].get('sunset')).strftime('%I:%M %p') - else: - sunrise = dt.fromtimestamp(data['sys'].get('sunrise')).strftime('%H:%M') - sunset = dt.fromtimestamp(data['sys'].get('sunset')).strftime('%H:%M') - - # Rain and Snow - wTypes = ['rain', 'snow'] - for wType in wTypes: - # Check to see if dictionary has values for rain or snow. - # if it does NOT, set zero values for consistancy. - if data.has_key(wType): - setattr(self, wType, { - "1h": data[wType].get('1h'), - "3h": data[wType].get('3h') - }) - else: - setattr(self, wType, { - "1h": 0, - "3h": 0 - }) - - # Fetch Wind Data - wind = { - "dir": self.degreesToTextDesc(data['wind'].get('deg')), - "speed": int(round(data['wind'].get('speed'))) - #"speed": 33 - } - - #icon = self.getIcon("http://openweathermap.org/img/wn/"+data['weather'][0].get('icon')+".png") - icon = os.path.basename(data['weather'][0].get('icon'))+".bmp" - - return { - "description": data['weather'][0].get('description'), - "humidity": data['main'].get('humidity'), - "temp_cur": int(math.ceil(data['main'].get('temp'))), - #"temp_cur": int(9), - "temp_min": int(math.ceil(data['main'].get('temp_min'))), - "temp_max": int(math.ceil(data['main'].get('temp_max'))), - #"temp_min": int(100), - #"temp_max": int(112), - "sunrise": sunrise, - "sunset": sunset, - "rain": self.rain, - "snow": self.snow, - "wind": wind, - "icon": icon - } - - - - From 9f5be131f8a05be5f54c59237d34b6684da58c12 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:02:27 +0000 Subject: [PATCH 083/162] Delete mod_infowindow/mod_utils directory --- mod_infowindow/mod_utils/__init__.py | 0 mod_infowindow/mod_utils/iw_utils.py | 21 ------ mod_infowindow/mod_utils/mod_google_auth.py | 75 --------------------- 3 files changed, 96 deletions(-) delete mode 100755 mod_infowindow/mod_utils/__init__.py delete mode 100755 mod_infowindow/mod_utils/iw_utils.py delete mode 100755 mod_infowindow/mod_utils/mod_google_auth.py diff --git a/mod_infowindow/mod_utils/__init__.py b/mod_infowindow/mod_utils/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/mod_infowindow/mod_utils/iw_utils.py b/mod_infowindow/mod_utils/iw_utils.py deleted file mode 100755 index 6551943..0000000 --- a/mod_infowindow/mod_utils/iw_utils.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys - -def isCron(): - if len(sys.argv) == 2: - if(sys.argv[1] == '--cron'): - return True - return False - -def getCWD(): - path = os.path.dirname(os.path.realpath(sys.argv[0])) - return path - -# Custom Error handler. This function will display the error message -# on the e-ink display and exit. -def HandleError(msg): - print("ERROR IN PROGRAM ======================================") - print("Program requires user input. Please run from console") - print("ERR: " + msg) - print("END ERROR =============================================") - quit() diff --git a/mod_infowindow/mod_utils/mod_google_auth.py b/mod_infowindow/mod_utils/mod_google_auth.py deleted file mode 100755 index 5e5aa80..0000000 --- a/mod_infowindow/mod_utils/mod_google_auth.py +++ /dev/null @@ -1,75 +0,0 @@ -from google_auth_oauthlib import flow -from google.auth.transport.requests import Request -from mod_utils import iw_utils -import pickle -import os.path -import sys -import logging - -logger = logging.getLogger(__name__) - - -class GoogleAuth: - def __init__(self): - logger.info("Initializing Module: GoogleAuth") - self.scopes = [ - 'https://www.googleapis.com/auth/calendar', - 'https://www.googleapis.com/auth/tasks' - ] - - self.creds = None - self.path = os.path.dirname(os.path.realpath(sys.argv[0])) - - def login(self): - - # Check for pickle. - # if os.path.exists('token.pickle'): - pickle_token_file_path = os.path.join(self.path, 'token.pickle') - if os.path.exists(pickle_token_file_path): - logger.info("token.pickle Exists. Attempting read") - with open(pickle_token_file_path, 'rb') as token: - self.creds = pickle.load(token) - else: - logger.info("%s NOT FOUND" % pickle_token_file_path) - - # If there are no valid creds, let user login. - # If we get to this point there is a user interaction that needs - # to happen. Must generate some sort of display on e-ink to let the - # user know that they need to run interactivly. - if not self.creds or not self.creds.valid: - logger.info("Credentials do not exist, or are not valid.") - - # Requires input from user. Write error to e-ink if is run from cron. - # if iw_utils.isCron(): - # iw_utils.HandleError("Google Credentials do not exist, or are not valid") - - if self.creds and self.creds.expired and self.creds.refresh_token: - logging.info("Refreshing Google Auth Credentials") - self.creds.refresh(Request()) - else: - # Check to see if google_secret.json exists. Throw error if not - google_secrets_file_path = os.path.join(self.path, 'google_secret.json') - if not os.path.exists(google_secrets_file_path): - logger.info("%s does not exist" % google_secrets_file_path) - - # Requires input from user. Write error to e-ink if is run from cron. - if iw_utils.isCron(): - iw_utils.HandleError('Message') - - appflow = flow.InstalledAppFlow.from_client_secrets_file( - google_secrets_file_path, self.scopes - ) - - if iw_utils.isCron(): - appflow.run_local_server() - else: - appflow.run_console() - - self.creds = appflow.credentials - - # Write pickle file - logger.info("Writing %s file", pickle_token_file_path) - with open(pickle_token_file_path, 'wb') as token: - pickle.dump(self.creds, token) - - return self.creds From 8c4a486fe1d52e02ddf42653d640a135bfc2c595 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:02:34 +0000 Subject: [PATCH 084/162] Delete mod_infowindow/mod_calendar directory --- mod_infowindow/mod_calendar/__init__.py | 0 mod_infowindow/mod_calendar/mod_google.py | 82 ----------------------- 2 files changed, 82 deletions(-) delete mode 100755 mod_infowindow/mod_calendar/__init__.py delete mode 100755 mod_infowindow/mod_calendar/mod_google.py diff --git a/mod_infowindow/mod_calendar/__init__.py b/mod_infowindow/mod_calendar/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/mod_infowindow/mod_calendar/mod_google.py b/mod_infowindow/mod_calendar/mod_google.py deleted file mode 100755 index 563963a..0000000 --- a/mod_infowindow/mod_calendar/mod_google.py +++ /dev/null @@ -1,82 +0,0 @@ -from mod_utils import mod_google_auth -from googleapiclient.discovery import build -from dateutil.parser import parse as dtparse -from datetime import datetime as dt -import logging - -# Silence goofy google deprecated errors -logging.getLogger('googleapiclient.discovery_cache').setLevel(logging.ERROR) - - -class Cal: - def __init__(self, options): - ga = mod_google_auth.GoogleAuth() - self.creds = ga.login() - self.timeformat = options["timeformat"] - self.additional = options["additional"] - self.ignored = options["ignored"] - - def list(self): - calendar_ids = [] - events = {} - items = [] - - service = build('calendar', 'v3', credentials=self.creds) - now = dt.utcnow().isoformat() + 'Z' - - page_token = None - while True: - calendar_list = service.calendarList().list(pageToken=page_token).execute() - for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys(): - if calendar_list_entry['primary']: - calendar_ids.append(calendar_list_entry['id']) - elif calendar_list_entry['summary'] in self.additional: - calendar_ids.append(calendar_list_entry['id']) - page_token = calendar_list.get('nextPageToken') - if not page_token: - break - - for id in calendar_ids: - result = service.events().list(calendarId=id, timeMin=now, - maxResults=20, - singleEvents=True, - orderBy='startTime').execute() - - for event in result.get('items', []): - if event['summary'] in self.ignored: - continue - initial_start = event['start'].get('dateTime', event['start'].get('date')) - start = "%s-0" % initial_start - counter = 0 - while start in events.keys(): - counter += 1 - start = "%s-%s" % (initial_start, counter) - - events[start] = event - - # 2019-11-05T10:00:00-08:00 - - for event_key in sorted(events.keys()): - start = events[event_key]['start'].get('dateTime', events[event_key]['start'].get('date')) - if int(dt.strftime(dtparse(start), format='%Y%m%d')) <= int(dt.strftime(dt.today(), format='%Y%m%d')): - today = True - else: - today = False - - # Sunrise and Sunset. - if self.timeformat == "12h": - st_date = dt.strftime(dtparse(start), format='%m-%d') - st_time = dt.strftime(dtparse(start), format='%I:%M %p') - else: - st_date = dt.strftime(dtparse(start), format='%d.%m') - st_time = dt.strftime(dtparse(start), format='%H:%M') - - items.append({ - "date": st_date, - "time": st_time, - "content": events[event_key]['summary'], - "today": today - }) - - return items From 87e43f155d214a6b7ab403f0b991ffbabb99fbc9 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:02:43 +0000 Subject: [PATCH 085/162] Delete mod_infowindow/mod_infowindow directory --- mod_infowindow/mod_infowindow/__init__.py | 0 mod_infowindow/mod_infowindow/infowindow.py | 100 -------------------- 2 files changed, 100 deletions(-) delete mode 100755 mod_infowindow/mod_infowindow/__init__.py delete mode 100755 mod_infowindow/mod_infowindow/infowindow.py diff --git a/mod_infowindow/mod_infowindow/__init__.py b/mod_infowindow/mod_infowindow/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/mod_infowindow/mod_infowindow/infowindow.py b/mod_infowindow/mod_infowindow/infowindow.py deleted file mode 100755 index 33ba920..0000000 --- a/mod_infowindow/mod_infowindow/infowindow.py +++ /dev/null @@ -1,100 +0,0 @@ -from driver import epd7in5b -from PIL import Image -from PIL import ImageDraw -from PIL import ImageFont -from PIL import ImageChops -import os, sys -import logging -import tempfile - - -class InfoWindow: - def __init__(self, options, colour): - self.epd = epd7in5b.EPD() - self.epd.init() - self.width = 880 - self.height = 528 - self.image = Image.new('L', (880, 528), 255) - self.draw = ImageDraw.Draw(self.image) - self.fonts = {} - self.colour = colour - self.initFonts() - self.tmpImagePath = os.path.join(tempfile.gettempdir(), colour+".png") - self.timeformat = options['timeformat'] - - def getCWD(self): - path = os.path.dirname(os.path.realpath(sys.argv[0])) - return path - - def getImage(self): - return self.image - - def getDraw(self): - return self.draw - - def getEpd(self): - return self.epd - - def line(self, left_1, top_1, left_2, top_2, fill=0, width=1): - self.draw.line((left_1, top_1, left_2, top_2), fill=fill) - - def rectangle(self, tl, tr, bl, br, fill=0): - self.draw.rectangle(((tl, tr), (bl, br)), fill=fill) - - def text(self, left, top, text, font, fill=0): - font = self.fonts[font] - self.draw.text((left, top), text, font=font, fill=fill) - return self.draw.textsize(text, font=font) - - def textwidth(self, text, font): - font = self.fonts[font] - ascent, descent = font.getmetrics() - text_width = font.getmask(text).getbbox()[2] - text_height = font.getmask(text).getbbox()[3] + descent - text_width = (text_width/2) - return text_width - - def rotate(self, angle): - self.image.rotate(angle) - - # def chord(self, x, y, xx, yy, xxx, yyy, fill): - # self.draw.chord((x, y, xx, yy), xxx, yyy, fill) - - def bitmap(self, x, y, image_path): - bitmap = Image.open(self.getCWD()+"/icons/"+image_path) - # self.image.paste((0, 0), (x, y), 'black', bitmap) - self.draw.bitmap((x, y), bitmap) - - - - def getFont(self, font_name): - return self.fonts[font_name] - - def initFonts(self): - roboto = self.getCWD()+"/fonts/roboto/Roboto-" - self.fonts = { - - 'robotoBlack24': ImageFont.truetype(roboto+"Black.ttf", 24), - 'robotoBlack18': ImageFont.truetype(roboto+"Black.ttf", 18), - 'robotoRegular18': ImageFont.truetype(roboto+"Regular.ttf", 18), - 'robotoRegular14': ImageFont.truetype(roboto+"Regular.ttf", 14), - 'robotoBlack48': ImageFont.truetype(roboto+"Black.ttf", 48), - 'robotoBlack36': ImageFont.truetype(roboto+"Black.ttf", 36) - } - - def truncate(self, string, font, max_size): - num_chars = len(string) - for char in string: - (np_x, np_y) = self.getFont(font).getsize(string) - if np_x >= max_size: - string = string[:-1] - - if np_x <= max_size: - return string - - return string - - def rtext(self, right, top, text, font, fill=0): - font = self.fonts[font] - self.draw.text((right, top), text, font=font, fill=fill) - return self.draw.textsize(text, font=font) From 67cf905b413b093279251f44c30a134f399a63b2 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:07:50 +0000 Subject: [PATCH 086/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 37274d9..06f07e4 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -22,9 +22,12 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], - "labels":item['labels'], - "due":item['due']['date'] - }) + "labels":item['labels'] + }) + if item['due']: + items.append({ + "due":item['due']['date'] + }) # Sort the array by priority items = sorted(items, key = lambda i: i['priority']) From 2fc8f5d877e8b94efbcfa4357345b63b6a19107f Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:08:12 +0000 Subject: [PATCH 087/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 06f07e4..56a7eb5 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -24,7 +24,7 @@ def list(self): "priority": item['priority'], "labels":item['labels'] }) - if item['due']: + if item['due']: items.append({ "due":item['due']['date'] }) From 56bd0a4b38b3cb76d06ae9420ccc0c51c35dea66 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:09:20 +0000 Subject: [PATCH 088/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 56a7eb5..7592be7 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -24,10 +24,7 @@ def list(self): "priority": item['priority'], "labels":item['labels'] }) - if item['due']: - items.append({ - "due":item['due']['date'] - }) + # Sort the array by priority items = sorted(items, key = lambda i: i['priority']) From 15bf0f7daf97bc8e070480eb6f7a3520d7d65845 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:11:15 +0000 Subject: [PATCH 089/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 7592be7..b070ea9 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -19,6 +19,10 @@ def list(self): if self.api: for item in self.api.state['items']: if item['checked'] == 0: + if item['due']: + items.append({ + "due":item['due']['date'] + }) items.append({ "content": item['content'], "priority": item['priority'], From ef005abe12f614ca86a4acf14cc75f4b555ec54c Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:16:53 +0000 Subject: [PATCH 090/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index b070ea9..32568f8 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -21,13 +21,17 @@ def list(self): if item['checked'] == 0: if item['due']: items.append({ + "content": item['content'], + "priority": item['priority'], + "labels":item['labels'] "due":item['due']['date'] }) - items.append({ - "content": item['content'], - "priority": item['priority'], - "labels":item['labels'] - }) + else: + items.append({ + "content": item['content'], + "priority": item['priority'], + "labels":item['labels'] + }) # Sort the array by priority From 1ab01109b95caa5bc4f1c60a9b0e8f0e8f855b9a Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:17:21 +0000 Subject: [PATCH 091/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 32568f8..5235b3a 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -23,7 +23,7 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], - "labels":item['labels'] + "labels":item['labels'], "due":item['due']['date'] }) else: From 6e7311d809c3d9b7d1e1cf250fc99e1839436c7f Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:18:46 +0000 Subject: [PATCH 092/162] Update mod_todoist.py --- mod_todo/mod_todoist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mod_todo/mod_todoist.py b/mod_todo/mod_todoist.py index 5235b3a..2199faf 100644 --- a/mod_todo/mod_todoist.py +++ b/mod_todo/mod_todoist.py @@ -30,7 +30,8 @@ def list(self): items.append({ "content": item['content'], "priority": item['priority'], - "labels":item['labels'] + "labels":item['labels'], + "due":0 }) From d245a908b86f0a0f8ee313299016b3776429bd78 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:20:17 +0000 Subject: [PATCH 093/162] Delete mod_infowindow/mod_todo directory --- mod_infowindow/mod_todo/__init__.py | 0 mod_infowindow/mod_todo/mod_google.py | 37 ---------------------- mod_infowindow/mod_todo/mod_grocy.py | 40 ----------------------- mod_infowindow/mod_todo/mod_teamwork.py | 38 ---------------------- mod_infowindow/mod_todo/mod_todoist.py | 42 ------------------------- 5 files changed, 157 deletions(-) delete mode 100755 mod_infowindow/mod_todo/__init__.py delete mode 100755 mod_infowindow/mod_todo/mod_google.py delete mode 100755 mod_infowindow/mod_todo/mod_grocy.py delete mode 100755 mod_infowindow/mod_todo/mod_teamwork.py delete mode 100755 mod_infowindow/mod_todo/mod_todoist.py diff --git a/mod_infowindow/mod_todo/__init__.py b/mod_infowindow/mod_todo/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/mod_infowindow/mod_todo/mod_google.py b/mod_infowindow/mod_todo/mod_google.py deleted file mode 100755 index 88bef1d..0000000 --- a/mod_infowindow/mod_todo/mod_google.py +++ /dev/null @@ -1,37 +0,0 @@ -from mod_utils import mod_google_auth -from googleapiclient.discovery import build -import logging - -logger = logging.getLogger(__name__) - - -class ToDo: - def __init__(self, api_key): - # This module authenticates from Google Auth API. We pull in the auth module - # wrapper to keep it clean. - logger.info("Initializing Module: ToDo: GOOGLE") - ga = mod_google_auth.GoogleAuth() - self.creds = ga.login() - - def list(self): - logging.info("Entering ToDo.list()") - service = build('tasks', 'v1', credentials=self.creds) - - items = [] - - # Fetch Results from all lists where todo is in the name - tasklists = service.tasklists().list().execute() - for tasklist in tasklists['items']: - if "todo" in tasklist['title'].lower(): - results = service.tasks().list(tasklist=tasklist['id']).execute() - - # Loop through results and format them for ingest - if 'items' in results.keys(): - for task in results['items']: - items.append({ - "content": task['title'], - "priority": task['position'] - }) - - # Return results to main program - return items diff --git a/mod_infowindow/mod_todo/mod_grocy.py b/mod_infowindow/mod_todo/mod_grocy.py deleted file mode 100755 index eb13d26..0000000 --- a/mod_infowindow/mod_todo/mod_grocy.py +++ /dev/null @@ -1,40 +0,0 @@ -import requests -import logging -import json -import datetime - - -class test: - def __init__(self, opts): - logging.debug("Grocy API: GROCY") - self.api = False - headers = { - 'accept': 'application/json', - 'GROCY-API-KEY': opts['api_key'], - } - if not opts['api_key']: - logging.warning("Not loading Todo API, since no api key is configured") - else: - self.api = requests.get("https://"+opts['server']+":"+opts['port']+"/api/stock", headers=headers) - - def list(self): - today = datetime.date.today() - items = [] - # Loop through original array from Grocy and pull out items by soonest best before date - if self.api: - data = json.loads(self.api.content) - for item in data: - logging.debug(item['product']['name']) - best_before = item['best_before_date'] - best_before_date = datetime.date(int(best_before[0:4]),int(best_before[5:7]),int(best_before[8:10])) - days = best_before_date - today - items.append({ - "content": str(days.days) + " " + item['product']['name'], - "best_before": item['best_before_date'], - "days": str(days.days) - }) - - items = sorted(items, key = lambda i: i['best_before']) - - - return items diff --git a/mod_infowindow/mod_todo/mod_teamwork.py b/mod_infowindow/mod_todo/mod_teamwork.py deleted file mode 100755 index dc41a92..0000000 --- a/mod_infowindow/mod_todo/mod_teamwork.py +++ /dev/null @@ -1,38 +0,0 @@ -import urllib2, base64 -import json -import logging - - -class ToDo: - def __init__(self, opts): - logging.debug("Todo API: TEAMWORK") - self.company = opts['site'] - self.key = opts['api_key'] - - def list(self): - action = "tasks.json?sort=priority" - request = urllib2.Request("https://{0}/{1}".format(self.company, action)) - request.add_header("Authorization", "BASIC " + base64.b64encode(self.key + ":xxx")) - - response = urllib2.urlopen(request) - data = json.loads(response.read()) - items = [] - - for task in data['todo-items']: - if task['priority'] == 'high': - priority = 1 - elif task['priority'] == 'medium': - priority = 2 - elif task['priority'] == 'low': - priority = 3 - elif task['priority'] == 'None': - priority = 4 - else: - priority = 8 - - items.append({ - "content": task['content'], - "priority": priority - }) - - return items diff --git a/mod_infowindow/mod_todo/mod_todoist.py b/mod_infowindow/mod_todo/mod_todoist.py deleted file mode 100755 index 846fab6..0000000 --- a/mod_infowindow/mod_todo/mod_todoist.py +++ /dev/null @@ -1,42 +0,0 @@ -import todoist -import logging -import rfc3339 - -def get_date_string(date_object): - return rfc3339.rfc3339(date_object) - -class ToDo: - def __init__(self, opts): - logging.debug("Todo API: TODOIST") - self.api = False - if not opts['api_key']: - logging.warning("Not loading Todo API, since no api key is configured") - else: - self.api = todoist.TodoistAPI(opts['api_key']) - self.api.sync() - #convert todoist dates to date objects - - - - def list(self): - items = [] - # Loop through original array from Todoist and pull out - # items of interest - if self.api: - for item in self.api.state['items']: - if item['checked'] == 0: - items.append({ - "content": item['content'], - "priority": item['priority'], - "labels": item['labels'], - "due": item['due']['date'] - }) - - # Sort the array by priority - items = sorted(items, key = lambda i: i['priority']) - unicode(items).encode('utf8') - # Reverse list, since Todoist sets priority in reverse. - # On web interface HIGH=Priority1, but stored in API as 4. who knows?! - items.reverse() - - return items From de1854bb16e0c3d14daf67cf512f32baad08f6a5 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:24:40 +0000 Subject: [PATCH 094/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index f132650..82fe001 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,8 +136,8 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - #due = int(todo_item['due'][1:4]) + int(todo_item['due'][6:7]) + int(todo_item['due'][9:10]) - logging.info(str(todo_item['due'])) + date = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f') + logging.info(str(date)) # if datetime.datetime.now() - due >= 0: # continue #except: From ae5c633032afa0af5f3737c6e0f661ae05c39c40 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:26:10 +0000 Subject: [PATCH 095/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 82fe001..4dc4f7e 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f') + date = datetime.datetime.strptime(todo_item['due'], '%Y-%m-%d %H:%M:%S.%f') logging.info(str(date)) # if datetime.datetime.now() - due >= 0: # continue From 56ec3d1fb4cb864ddf2800eb89439da77ee6bec5 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:27:15 +0000 Subject: [PATCH 096/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 4dc4f7e..9c4cfe3 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'], '%Y-%m-%d %H:%M:%S.%f') + date = datetime.datetime.strptime(todo_item['due'][0:9], '%Y-%m-%d') logging.info(str(date)) # if datetime.datetime.now() - due >= 0: # continue From 8c04132af235eda3bb2a182e91b7a0f1b11d4e3a Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:27:59 +0000 Subject: [PATCH 097/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 9c4cfe3..0736f69 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:9], '%Y-%m-%d') + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d') logging.info(str(date)) # if datetime.datetime.now() - due >= 0: # continue From 4f6291abfe2c5a33af8eb99bc5e6f3f8d9caaf69 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:38:24 +0000 Subject: [PATCH 098/162] Update infowindow.py --- infowindow.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/infowindow.py b/infowindow.py index 0736f69..7e09348 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,26 +136,24 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d') - logging.info(str(date)) - # if datetime.datetime.now() - due >= 0: - # continue - #except: - #pass - #finally: - #todo_itemstime.strptime(date2, "%d/%m/%Y") - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d') + else: + date = 0 + if datetime.datetime.now() - date >= 0: + logging.info(str(date)) + + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO From d7ba604d0a33c5831d398f56f411c512c09638d7 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:43:22 +0000 Subject: [PATCH 099/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 7e09348..d1e3a63 100755 --- a/infowindow.py +++ b/infowindow.py @@ -138,8 +138,8 @@ def main(): if todo_item['due']: date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d') else: - date = 0 - if datetime.datetime.now() - date >= 0: + date = datetime.datetime.now().date() + if datetime.datetime.now().date() >= date: logging.info(str(date)) if 2156103501 in todo_item['labels']: From 63174613d38ae19976b40bb1d9c34e2497577609 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:45:29 +0000 Subject: [PATCH 100/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index d1e3a63..e5e7173 100755 --- a/infowindow.py +++ b/infowindow.py @@ -136,7 +136,7 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d') + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() else: date = datetime.datetime.now().date() if datetime.datetime.now().date() >= date: From 4c143259ed7107f4ea242fcba6e68b9a43d35a13 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 1 Feb 2021 23:51:41 +0000 Subject: [PATCH 101/162] Update infowindow.py --- infowindow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index e5e7173..a551559 100755 --- a/infowindow.py +++ b/infowindow.py @@ -7,7 +7,6 @@ import logging import string import datetime -import rfc3339 from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -256,14 +255,12 @@ def main(): diff = ImageChops.difference(red.image, old_image) if not diff.getbbox(): new_image_found += 1 - logging.info("No change to red") if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 - logging.info("No change to black") if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") From 6bf9daa52f9a471571be762ee8387914adcb50e3 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:06:44 +0000 Subject: [PATCH 102/162] Update infowindow.py --- infowindow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index a551559..2e139b8 100755 --- a/infowindow.py +++ b/infowindow.py @@ -139,8 +139,7 @@ def main(): else: date = datetime.datetime.now().date() if datetime.datetime.now().date() >= date: - logging.info(str(date)) - + if 2156103501 in todo_item['labels']: red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) From 0bc996324c486482927ab49f2dbe67f567e1d6f4 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:06:58 +0000 Subject: [PATCH 103/162] Update infowindow.py --- infowindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2e139b8..1456488 100755 --- a/infowindow.py +++ b/infowindow.py @@ -1,5 +1,4 @@ #!/usr/bin/env python2 -# 880 528 import sys import os.path From 47da46b2c0bf1dd9a761fc36f8d1f4e9235c4010 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:12:50 +0000 Subject: [PATCH 104/162] Update infowindow.py --- infowindow.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infowindow.py b/infowindow.py index 1456488..b43fe6a 100755 --- a/infowindow.py +++ b/infowindow.py @@ -93,7 +93,7 @@ def main(): red = infowindow.InfoWindow(infowindow_opts, "red") black = infowindow.InfoWindow(infowindow_opts, "black") - # Calendar / Todo Title Line + # Title Line black.line(0, 0, 880, 0) # Top Line red.rectangle(1, 1, 880, 24) # Red Rectangle black.line(0, 25, 880, 25) # Bottom Black Line @@ -112,7 +112,7 @@ def main(): calendar_entry_font = "robotoBlack18" tasks_font = "robotoBlack18" - # Dividing line + # Dividing lines black.line(286, 24, 286, 528) # Left Black line red.rectangle(287, 24, 296, 528) # Red Rectangle black.line(297, 24, 297, 528) # Right Black line @@ -121,7 +121,7 @@ def main(): red.rectangle(584, 24, 593, 528) # Red Rectangle black.line(594, 24, 594, 528) # Right Black line - # DISPLAY TODO INFO + # DISPLAY TODO INFO # ========================================================================= todo_items = todo.list() logging.debug("Todo Items") @@ -153,7 +153,7 @@ def main(): logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - # DISPLAY GROCY INFO + # DISPLAY GROCY INFO # ========================================================================= grocy_items = grocy.list() logging.debug("Grocy Items") @@ -242,7 +242,7 @@ def main(): # logging.debug("ITEM: "+str(cal_item['date']), str(cal_item['time']), str(cal_item['content'])) logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) - # Write to screen + # Write to screen # ========================================================================= red.image = red.image.rotate(rotation) black.image = black.image.rotate(rotation) @@ -251,16 +251,16 @@ def main(): if os.path.exists(red.tmpImagePath): old_image = Image.open(red.tmpImagePath) diff = ImageChops.difference(red.image, old_image) - if not diff.getbbox(): + if diff.getbbox(): new_image_found += 1 if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) - if not diff.getbbox(): + if diff.getbbox(): new_image_found += 1 - if new_image_found < 2: + if new_image_found > 0: logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) black.image.save(black.tmpImagePath) From b93e5d93e6d4bf6b47577a6fa56100156936cb7c Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:19:22 +0000 Subject: [PATCH 105/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 563963a..873638b 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -28,7 +28,7 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys(): + if "primar" in calendar_list_entry.keys(): if calendar_list_entry['primary']: calendar_ids.append(calendar_list_entry['id']) elif calendar_list_entry['summary'] in self.additional: From 705a1626706e84b9f498073d892382eebdd5e4b4 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:26:16 +0000 Subject: [PATCH 106/162] Update mod_google.py --- mod_calendar/mod_google.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 873638b..534c7ef 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -13,7 +13,7 @@ def __init__(self, options): ga = mod_google_auth.GoogleAuth() self.creds = ga.login() self.timeformat = options["timeformat"] - self.additional = options["additional"] + self.include = options["include"] self.ignored = options["ignored"] def list(self): @@ -28,10 +28,7 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primar" in calendar_list_entry.keys(): - if calendar_list_entry['primary']: - calendar_ids.append(calendar_list_entry['id']) - elif calendar_list_entry['summary'] in self.additional: + if calendar_list_entry['summary'] in self.include: calendar_ids.append(calendar_list_entry['id']) page_token = calendar_list.get('nextPageToken') if not page_token: From 5a74749c97c5d9c332a83f2e232b0c71020c6554 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:38:06 +0000 Subject: [PATCH 107/162] Update mod_google.py --- mod_calendar/mod_google.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 534c7ef..5b999f7 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -28,8 +28,12 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if calendar_list_entry['summary'] in self.include: - calendar_ids.append(calendar_list_entry['id']) + if "primary" in calendar_list_entry.keys(): + if calendar_list_entry['primary']: + calendar_ids.append(calendar_list_entry['id']) + print(str(calendar_list_entry['primary']) + elif calendar_list_entry['summary'] in self.include: + calendar_ids.append(calendar_list_entry['id']) page_token = calendar_list.get('nextPageToken') if not page_token: break From bde823d65f8ccfacbdb0252ba3839bc97c4bcda2 Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:40:02 +0000 Subject: [PATCH 108/162] Update mod_google.py --- mod_calendar/mod_google.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 5b999f7..ad5ee7b 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -24,16 +24,16 @@ def list(self): service = build('calendar', 'v3', credentials=self.creds) now = dt.utcnow().isoformat() + 'Z' - page_token = None + page_token = None while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys(): + if "primary" in calendar_list_entry.keys(): if calendar_list_entry['primary']: calendar_ids.append(calendar_list_entry['id']) - print(str(calendar_list_entry['primary']) - elif calendar_list_entry['summary'] in self.include: - calendar_ids.append(calendar_list_entry['id']) + print(str(calendar_list_entry['primary'])) + elif calendar_list_entry['summary'] in self.include: + calendar_ids.append(calendar_list_entry['id']) page_token = calendar_list.get('nextPageToken') if not page_token: break From 99a97f9c651dfda3b729602c4d55eb36c8ce5dee Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:40:28 +0000 Subject: [PATCH 109/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index ad5ee7b..a21087c 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -24,7 +24,7 @@ def list(self): service = build('calendar', 'v3', credentials=self.creds) now = dt.utcnow().isoformat() + 'Z' - page_token = None + page_token = None while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: From b7dab42118bd8c0022066d8f5982bbbd5d58a6ae Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:42:52 +0000 Subject: [PATCH 110/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index a21087c..d042c84 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -28,7 +28,7 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys(): + if "primary" in calendar_list_entry.keys() and in self.include: if calendar_list_entry['primary']: calendar_ids.append(calendar_list_entry['id']) print(str(calendar_list_entry['primary'])) From 6d0baba79b28c94534da2ab6758f36cdae050adc Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:44:15 +0000 Subject: [PATCH 111/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index d042c84..f4a6937 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -28,7 +28,7 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys() and in self.include: + if "primary" in calendar_list_entry.keys() and "primary" in self.include: if calendar_list_entry['primary']: calendar_ids.append(calendar_list_entry['id']) print(str(calendar_list_entry['primary'])) From 42776be91bfd936c634d6a9bf7abc6ceaeb996db Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:46:52 +0000 Subject: [PATCH 112/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index f4a6937..8d615ce 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -28,7 +28,7 @@ def list(self): while True: calendar_list = service.calendarList().list(pageToken=page_token).execute() for calendar_list_entry in calendar_list['items']: - if "primary" in calendar_list_entry.keys() and "primary" in self.include: + if "primary" in calendar_list_entry.keys() and "Primary" in self.include: if calendar_list_entry['primary']: calendar_ids.append(calendar_list_entry['id']) print(str(calendar_list_entry['primary'])) From d85fe50347a980dbdda65ddea93b82afb83cb16e Mon Sep 17 00:00:00 2001 From: justcop Date: Tue, 2 Feb 2021 00:53:54 +0000 Subject: [PATCH 113/162] Update infowindow.py --- infowindow.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index b43fe6a..94b3039 100755 --- a/infowindow.py +++ b/infowindow.py @@ -16,7 +16,6 @@ # Replace the mod_ with one of: # TODO: mod_todoist, mod_teamwork # CALENDAR: mod_google, mod_ical -# WEATHER: mod_owm, mod_wunderground from mod_utils import iw_utils from mod_todo import mod_todoist as modTodo # TODO from mod_calendar import mod_google as modCalendar # CALENDAR @@ -35,11 +34,9 @@ todo_opts = config_data["todo"] grocy_opts = config_data["grocy"] calendar_opts = config_data["calendar"] -weather_opts = config_data["weather"] infowindow_opts = {} # give the timeformat to all the modules needing it calendar_opts["timeformat"] = config_data["general"]["timeformat"] -weather_opts["timeformat"] = config_data["general"]["timeformat"] infowindow_opts["timeformat"] = config_data["general"]["timeformat"] infowindow_opts["cell_spacing"] = config_data["general"]["cell_spacing"] From 74104a1af83da0522308f563e8e70025f5dc5354 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 13:14:50 +0000 Subject: [PATCH 114/162] Update config.json-sample --- config.json-sample | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/config.json-sample b/config.json-sample index 9362e48..65ce7fb 100644 --- a/config.json-sample +++ b/config.json-sample @@ -1,22 +1,22 @@ { "general": { - "rotation": 180, + "rotation": 0, "timeformat": "12h", - "charset": "latin1", + "charset": "utf-8", "cell_spacing": 2 }, "todo": { - "api_key": "1234" + "api_key": "" }, "calendar": { - "additional": ["Contacts", "Birthdays"], - "ignored": ["Buy ticket!"], - "today_text_color": "red", + "include": [""], + "ignored": [""], + "today_text_color": "black", "today_background_color": "white" }, - "weather": { - "api_key": "1234", - "city": "Sacramento,US", - "units": "imperial" + "grocy": { + "api_key": "", + "server": "", + "port": "" } } From 8c85dfd7de939ac8b1be17303d2d292ba04554a1 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 13:20:41 +0000 Subject: [PATCH 115/162] Update infowindow.py --- infowindow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infowindow.py b/infowindow.py index 94b3039..9e509a4 100755 --- a/infowindow.py +++ b/infowindow.py @@ -202,9 +202,9 @@ def main(): current_calendar_y = 26 for cal_item in cal_items: - font_color = 'black' + text = black if cal_item['today']: - font_color = calendar_opts['today_text_color'] + text = red black.rectangle(0, current_calendar_y, 285, (current_calendar_y + line_height), calendar_opts['today_background_color']) @@ -219,17 +219,17 @@ def main(): 'black') # draw event date - black.text((infowindow_opts["cell_spacing"]), + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), cal_item['date'].encode(charset).strip(), calendar_date_font, font_color) # draw event time - black.text((infowindow_opts["cell_spacing"]), + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), cal_item['time'].encode(charset).strip(), calendar_date_font, font_color) # draw event text calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] - black.text(calendar_event_text_start, + text.text(calendar_event_text_start, (current_calendar_y + ((line_height - it_y) / 2)), black.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), calendar_entry_font, font_color) From f7303d378be0b5ac265569490859da3c899c0500 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 13:23:08 +0000 Subject: [PATCH 116/162] Update infowindow.py --- infowindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infowindow.py b/infowindow.py index 9e509a4..159dd94 100755 --- a/infowindow.py +++ b/infowindow.py @@ -202,6 +202,7 @@ def main(): current_calendar_y = 26 for cal_item in cal_items: + font_color = 'black' text = black if cal_item['today']: text = red From ccb5e540526c6caa70585027bca14f79bbf784a9 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 21:42:56 +0000 Subject: [PATCH 117/162] Update infowindow.py --- infowindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index 159dd94..251f24b 100755 --- a/infowindow.py +++ b/infowindow.py @@ -249,16 +249,16 @@ def main(): if os.path.exists(red.tmpImagePath): old_image = Image.open(red.tmpImagePath) diff = ImageChops.difference(red.image, old_image) - if diff.getbbox(): + if !diff.getbbox(): new_image_found += 1 if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) - if diff.getbbox(): + if !diff.getbbox(): new_image_found += 1 - if new_image_found > 0: + if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) black.image.save(black.tmpImagePath) From cd3e73e6fbda8ab91d11720175e4f89448cdd96d Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 21:46:23 +0000 Subject: [PATCH 118/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 251f24b..13ce8e3 100755 --- a/infowindow.py +++ b/infowindow.py @@ -249,13 +249,13 @@ def main(): if os.path.exists(red.tmpImagePath): old_image = Image.open(red.tmpImagePath) diff = ImageChops.difference(red.image, old_image) - if !diff.getbbox(): + if not diff.getbbox(): new_image_found += 1 if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) - if !diff.getbbox(): + if not diff.getbbox(): new_image_found += 1 if new_image_found < 2: From cd8859bc7d7a7b97f56c6d76da538b754b96613e Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 6 Feb 2021 21:54:42 +0000 Subject: [PATCH 119/162] Update mod_grocy.py --- mod_todo/mod_grocy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mod_todo/mod_grocy.py b/mod_todo/mod_grocy.py index d8e28cb..7233dc4 100755 --- a/mod_todo/mod_grocy.py +++ b/mod_todo/mod_grocy.py @@ -24,7 +24,6 @@ def list(self): if self.api: data = json.loads(self.api.content) for item in data: - logging.debug(item['product']['name']) best_before = item['best_before_date'] best_before_date = datetime.date(int(best_before[0:4]),int(best_before[5:7]),int(best_before[8:10])) days = best_before_date - today From 922958c2ff8b509a91c6b3705858bf09ea4fafe2 Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 15:57:39 +0000 Subject: [PATCH 120/162] Update infowindow.py --- infowindow.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 13ce8e3..9c11ba9 100755 --- a/infowindow.py +++ b/infowindow.py @@ -102,7 +102,11 @@ def main(): red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') - + + # Updated Time + updatetime = datetime.datetime.now() + text_width = red.textwidth(updatetime.strftime("%d %b %H:%m"), 'robotoBlack18') + red.text(880 - text_width, 0, updatetime.strftime("%d %b %H:%m"), 'robotoBlack18', 'white') # Set some things calendar_date_font = "robotoRegular14" From 362522dbf2d99bf3412e7c158c6e2e7c1bc9f51b Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 17:35:49 +0000 Subject: [PATCH 121/162] Update infowindow.py --- infowindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 9c11ba9..a73c6a0 100755 --- a/infowindow.py +++ b/infowindow.py @@ -105,8 +105,9 @@ def main(): # Updated Time updatetime = datetime.datetime.now() - text_width = red.textwidth(updatetime.strftime("%d %b %H:%m"), 'robotoBlack18') - red.text(880 - text_width, 0, updatetime.strftime("%d %b %H:%m"), 'robotoBlack18', 'white') + updatetime = updatetime.strftime("%d %b %H:%m") + text_width = red.textwidth(updatetime, 'robotoBlack18') + red.text(880 - text_width, 0, updatetime, 'robotoBlack18', 'white') # Set some things calendar_date_font = "robotoRegular14" From 059a81ca56cf87c8875d7d373c0e746d8d019606 Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 17:38:15 +0000 Subject: [PATCH 122/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index a73c6a0..90061bb 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,8 +106,8 @@ def main(): # Updated Time updatetime = datetime.datetime.now() updatetime = updatetime.strftime("%d %b %H:%m") - text_width = red.textwidth(updatetime, 'robotoBlack18') - red.text(880 - text_width, 0, updatetime, 'robotoBlack18', 'white') + text_width = red.textwidth(updatetime, 'robotoRegular14') + red.text(880 - text_width, 0, updatetime, 'robotoRegular14', 'white') # Set some things calendar_date_font = "robotoRegular14" From 345caa127eb2353b83555e97558d405814cb0fba Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 17:45:00 +0000 Subject: [PATCH 123/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 90061bb..7112a37 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,7 +106,7 @@ def main(): # Updated Time updatetime = datetime.datetime.now() updatetime = updatetime.strftime("%d %b %H:%m") - text_width = red.textwidth(updatetime, 'robotoRegular14') + text_width = red.textwidth(updatetime, 'robotoRegular14') * 2 red.text(880 - text_width, 0, updatetime, 'robotoRegular14', 'white') # Set some things From 33b0818648e5c772e3053664a846ce82c98489ec Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 18:02:26 +0000 Subject: [PATCH 124/162] Update infowindow.py --- infowindow.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infowindow.py b/infowindow.py index 7112a37..be582ba 100755 --- a/infowindow.py +++ b/infowindow.py @@ -103,12 +103,6 @@ def main(): text_width = red.textwidth("TASKS", 'robotoBlack24') red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') - # Updated Time - updatetime = datetime.datetime.now() - updatetime = updatetime.strftime("%d %b %H:%m") - text_width = red.textwidth(updatetime, 'robotoRegular14') * 2 - red.text(880 - text_width, 0, updatetime, 'robotoRegular14', 'white') - # Set some things calendar_date_font = "robotoRegular14" calendar_entry_font = "robotoBlack18" @@ -264,6 +258,12 @@ def main(): new_image_found += 1 if new_image_found < 2: + # Updated Time + updatetime = datetime.datetime.now() + updatetime = updatetime.strftime("%d %b %H:%m") + text_width = red.textwidth(updatetime, 'robotoRegular14') * 2 + red.text(877 - text_width, 0, updatetime, 'robotoRegular14', 'white') + logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) black.image.save(black.tmpImagePath) From 8127ed72cca9d17f92650440377d83dbb4a818a5 Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 18:08:36 +0000 Subject: [PATCH 125/162] Update infowindow.py --- infowindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index be582ba..0b12959 100755 --- a/infowindow.py +++ b/infowindow.py @@ -262,7 +262,9 @@ def main(): updatetime = datetime.datetime.now() updatetime = updatetime.strftime("%d %b %H:%m") text_width = red.textwidth(updatetime, 'robotoRegular14') * 2 - red.text(877 - text_width, 0, updatetime, 'robotoRegular14', 'white') + red.text(880 - text_width, 0, updatetime, 'robotoRegular14', 'white') + red.image = red.image.rotate(rotation) + logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) From ea99c51ce6fd9aee8318a400932629c0f2ba6d22 Mon Sep 17 00:00:00 2001 From: justcop Date: Thu, 11 Mar 2021 18:22:49 +0000 Subject: [PATCH 126/162] Update infowindow.py --- infowindow.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/infowindow.py b/infowindow.py index 0b12959..2abb3df 100755 --- a/infowindow.py +++ b/infowindow.py @@ -257,15 +257,7 @@ def main(): if not diff.getbbox(): new_image_found += 1 - if new_image_found < 2: - # Updated Time - updatetime = datetime.datetime.now() - updatetime = updatetime.strftime("%d %b %H:%m") - text_width = red.textwidth(updatetime, 'robotoRegular14') * 2 - red.text(880 - text_width, 0, updatetime, 'robotoRegular14', 'white') - red.image = red.image.rotate(rotation) - - + if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) black.image.save(black.tmpImagePath) From c0f1c71a80e7e73f5688ece617310a247a4e71e7 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 12 Apr 2021 11:55:15 +0100 Subject: [PATCH 127/162] Update infowindow.py --- infowindow.py | 54 ++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/infowindow.py b/infowindow.py index 2abb3df..d521d07 100755 --- a/infowindow.py +++ b/infowindow.py @@ -151,31 +151,37 @@ def main(): # DISPLAY GROCY INFO # ========================================================================= - grocy_items = grocy.list() - logging.debug("Grocy Items") - logging.debug("-----------------------------------------------------------------------") - - #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for grocy_item in grocy_items: - (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days']) + " ") - if int(grocy_item['days']) < 3: - text = red - else: - text = black + try: + grocy_items = grocy.list() + logging.debug("Grocy Items") + logging.debug("-----------------------------------------------------------------------") + + #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for grocy_item in grocy_items: + (np_x, np_y) = red.getFont(tasks_font).getsize(str(grocy_item['days']) + " ") + if int(grocy_item['days']) < 3: + text = red + else: + text = black - text.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days'] + " "), tasks_font) - (op_x, op_y) = text.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) - text.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), text.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) - red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) - - - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) + text.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days'] + " "), tasks_font) + (op_x, op_y) = text.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) + text.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), text.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) + red.line(298, (current_task_y + line_height + 1), 582, (current_task_y + line_height + 1)) + + + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) + except: + logging.debug("Grocy Failed") + logging.debug("-----------------------------------------------------------------------") + red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) + # DISPLAY CALENDAR INFO # ========================================================================= From 9c69441a1b6d53092fb703b581de195992b44188 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 12 Apr 2021 11:58:32 +0100 Subject: [PATCH 128/162] Update infowindow.py --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index d521d07..7de3243 100755 --- a/infowindow.py +++ b/infowindow.py @@ -177,10 +177,10 @@ def main(): # set next loop height current_task_y = (current_task_y + line_height + 2) logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) - except: - logging.debug("Grocy Failed") - logging.debug("-----------------------------------------------------------------------") - red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) + except: + logging.debug("Grocy Failed") + logging.debug("-----------------------------------------------------------------------") + red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) # DISPLAY CALENDAR INFO From 179267c9754c110f28acfe02f9306fb036a48b4e Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 10 May 2021 12:27:44 +0100 Subject: [PATCH 129/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 8d615ce..5cfd347 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -67,7 +67,7 @@ def list(self): # Sunrise and Sunset. if self.timeformat == "12h": - st_date = dt.strftime(dtparse(start), format='%m-%d') + st_date = dt.strftime(dtparse(start), format='%a %d-%h') st_time = dt.strftime(dtparse(start), format='%I:%M %p') else: st_date = dt.strftime(dtparse(start), format='%d.%m') From d250926304a2e4e9f15c12968dc9b30feb312798 Mon Sep 17 00:00:00 2001 From: justcop Date: Mon, 10 May 2021 13:08:00 +0100 Subject: [PATCH 130/162] Update mod_google.py --- mod_calendar/mod_google.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_calendar/mod_google.py b/mod_calendar/mod_google.py index 5cfd347..3b8b753 100644 --- a/mod_calendar/mod_google.py +++ b/mod_calendar/mod_google.py @@ -67,7 +67,7 @@ def list(self): # Sunrise and Sunset. if self.timeformat == "12h": - st_date = dt.strftime(dtparse(start), format='%a %d-%h') + st_date = dt.strftime(dtparse(start), format='%a %d-%m') st_time = dt.strftime(dtparse(start), format='%I:%M %p') else: st_date = dt.strftime(dtparse(start), format='%d.%m') From ec0b3c106838fccc4c7869cb0ce57f88ddedc376 Mon Sep 17 00:00:00 2001 From: justcop Date: Fri, 9 Jul 2021 15:11:43 +0100 Subject: [PATCH 131/162] Update infowindow.py Add date to the header to help identify if not updating --- infowindow.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infowindow.py b/infowindow.py index 7de3243..c630369 100755 --- a/infowindow.py +++ b/infowindow.py @@ -103,6 +103,12 @@ def main(): text_width = red.textwidth("TASKS", 'robotoBlack24') red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') + #Date header + text_width = black.textwidth(datetime.today().strftime('%a'), 'robotoBlack18') + black.text(297 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(datetime.today().strftime('%-d %b'), 'robotoBlack18') + black.text(594 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') + # Set some things calendar_date_font = "robotoRegular14" calendar_entry_font = "robotoBlack18" From 0885029d860928042f8af4c5f58e3c4a997ae359 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:12:39 +0100 Subject: [PATCH 132/162] Update infowindow.py --- infowindow.py | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/infowindow.py b/infowindow.py index c630369..34b123f 100755 --- a/infowindow.py +++ b/infowindow.py @@ -82,7 +82,7 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - todo = modTodo.ToDo(todo_opts) + #todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts) @@ -125,34 +125,34 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - todo_items = todo.list() - logging.debug("Todo Items") - logging.debug("-----------------------------------------------------------------------") + #todo_items = todo.list() + #logging.debug("Todo Items") + #logging.debug("-----------------------------------------------------------------------") #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for todo_item in todo_items: - if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - else: - date = datetime.datetime.now().date() - if datetime.datetime.now().date() >= date: - - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + #(t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + #line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + #current_task_y = 25 + #for todo_item in todo_items: + # if todo_item['due']: + # date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + # else: + # date = datetime.datetime.now().date() + # if datetime.datetime.now().date() >= date: + # + # if 2156103501 in todo_item['labels']: + # red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # # set next loop height + # current_task_y = (current_task_y + line_height + 2) + # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # if todo_item['priority'] > 2: + # black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # # set next loop height + # current_task_y = (current_task_y + line_height + 2) + # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO From 6d1a5f1ec330f1c3821cf9f13a69e1f89b1c1f01 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:16:54 +0100 Subject: [PATCH 133/162] Update infowindow.py --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 34b123f..f71699e 100755 --- a/infowindow.py +++ b/infowindow.py @@ -104,10 +104,10 @@ def main(): red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') #Date header - text_width = black.textwidth(datetime.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') - text_width = black.textwidth(datetime.today().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(datetime.now().strftime('%a'), 'robotoBlack18') + black.text(297 - text_width, 0, datetime.now().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(datetime.now().strftime('%-d %b'), 'robotoBlack18') + black.text(594 - text_width, 0, datetime.now().strftime('%a'), 'robotoBlack18', 'black') # Set some things calendar_date_font = "robotoRegular14" From 7081f77f24696936b829c713663ce34b295cf3c6 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:19:01 +0100 Subject: [PATCH 134/162] Update infowindow.py --- infowindow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index f71699e..6138182 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,6 +6,7 @@ import logging import string import datetime +from datetime import date from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -104,10 +105,10 @@ def main(): red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') #Date header - text_width = black.textwidth(datetime.now().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, datetime.now().strftime('%a'), 'robotoBlack18', 'black') - text_width = black.textwidth(datetime.now().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, datetime.now().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') + black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') + black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') # Set some things calendar_date_font = "robotoRegular14" From 56f224399f0abb5a9d84c861a370145bddcd11dd Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:22:32 +0100 Subject: [PATCH 135/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 6138182..5267437 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,9 +106,9 @@ def main(): #Date header text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') # Set some things calendar_date_font = "robotoRegular14" From 7a094e035edcae4cba9ac504ea153c7a30f281cc Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:30:40 +0100 Subject: [PATCH 136/162] Update infowindow.py --- infowindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 5267437..6e67386 100755 --- a/infowindow.py +++ b/infowindow.py @@ -109,7 +109,8 @@ def main(): black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - + logging.info(date.today().strftime('%a')) + # Set some things calendar_date_font = "robotoRegular14" calendar_entry_font = "robotoBlack18" From f809a279ff3c2cb2e8d70028bfead365253148f8 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:34:11 +0100 Subject: [PATCH 137/162] Update infowindow.py --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 6e67386..2240ddb 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,10 +106,10 @@ def main(): #Date header text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - logging.info(date.today().strftime('%a')) + black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') + text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack24') + black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') + logging.info(date.today().strftime('%-d %b')) # Set some things calendar_date_font = "robotoRegular14" From dd5b182ad00f4ae5032aa9f3d8394a522ec4c61f Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:47:03 +0100 Subject: [PATCH 138/162] Update infowindow.py --- infowindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2240ddb..29182c1 100755 --- a/infowindow.py +++ b/infowindow.py @@ -98,7 +98,8 @@ def main(): # Titles text_width = red.textwidth("CALENDAR", 'robotoBlack24') - red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') +# red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') + red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') text_width = red.textwidth("FRIDGE", 'robotoBlack24') red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') From 060c0367afd2313f25f6b8414cd2ec9247b92c0b Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:51:39 +0100 Subject: [PATCH 139/162] Update infowindow.py --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 29182c1..2bc754d 100755 --- a/infowindow.py +++ b/infowindow.py @@ -99,7 +99,7 @@ def main(): # Titles text_width = red.textwidth("CALENDAR", 'robotoBlack24') # red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') - red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') + red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') text_width = red.textwidth("FRIDGE", 'robotoBlack24') red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') @@ -107,9 +107,9 @@ def main(): #Date header text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') - text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack24') - black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack24', 'white') + black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') + black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') logging.info(date.today().strftime('%-d %b')) # Set some things From 817429d25248bb9b0146562d2efa861e24687107 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 12:53:45 +0100 Subject: [PATCH 140/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2bc754d..62490fd 100755 --- a/infowindow.py +++ b/infowindow.py @@ -99,7 +99,7 @@ def main(): # Titles text_width = red.textwidth("CALENDAR", 'robotoBlack24') # red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') - red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = red.textwidth("FRIDGE", 'robotoBlack24') red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') From 88dda064572e8b1fef7fe59fd28a1e8d93d8af22 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:06:57 +0100 Subject: [PATCH 141/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index 62490fd..10f74b1 100755 --- a/infowindow.py +++ b/infowindow.py @@ -107,9 +107,9 @@ def main(): #Date header text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') + black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) # Set some things From b4fee13597b1101669ca99b06d2b82f3783d3207 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:11:13 +0100 Subject: [PATCH 142/162] Update infowindow.py --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 10f74b1..e01c7e8 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,10 +106,10 @@ def main(): red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') #Date header - text_width = black.textwidth(date.today().strftime('%a'), 'robotoBlack18') - black.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - text_width = black.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - black.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') + red.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') + red.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) # Set some things From d48263f4a3be408f0cbadc46acd44dfb2955c508 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:13:05 +0100 Subject: [PATCH 143/162] Update infowindow.py --- infowindow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index e01c7e8..f7da763 100755 --- a/infowindow.py +++ b/infowindow.py @@ -98,8 +98,7 @@ def main(): # Titles text_width = red.textwidth("CALENDAR", 'robotoBlack24') -# red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') - red.text(143 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + red.text(143 - text_width, 0, "CALENDAR", 'robotoBlack24', 'white') text_width = red.textwidth("FRIDGE", 'robotoBlack24') red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') @@ -109,7 +108,7 @@ def main(): text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') red.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - red.text(594 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + red.text(594 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) # Set some things From ed26b22316c6f3893068dd9ebae227e5d82d9b7b Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:16:04 +0100 Subject: [PATCH 144/162] Update infowindow.py --- infowindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infowindow.py b/infowindow.py index f7da763..bcc25d5 100755 --- a/infowindow.py +++ b/infowindow.py @@ -106,9 +106,9 @@ def main(): #Date header text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') - red.text(297 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - red.text(594 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') + red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) # Set some things From f66b169a9af91b47b6458f37f830aaa17200d7c2 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:19:20 +0100 Subject: [PATCH 145/162] Update infowindow.py --- infowindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infowindow.py b/infowindow.py index bcc25d5..f7a9997 100755 --- a/infowindow.py +++ b/infowindow.py @@ -107,6 +107,7 @@ def main(): #Date header text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') + black.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) From 98ecf83ee3664fcc4a3e00feca5dbab0da2ef7b8 Mon Sep 17 00:00:00 2001 From: justcop Date: Wed, 14 Jul 2021 13:27:33 +0100 Subject: [PATCH 146/162] Update infowindow.py --- infowindow.py | 59 +++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/infowindow.py b/infowindow.py index f7a9997..24e2b13 100755 --- a/infowindow.py +++ b/infowindow.py @@ -83,7 +83,7 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - #todo = modTodo.ToDo(todo_opts) + todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts) @@ -107,7 +107,6 @@ def main(): #Date header text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - black.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'black') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') logging.info(date.today().strftime('%-d %b')) @@ -128,34 +127,34 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - #todo_items = todo.list() - #logging.debug("Todo Items") - #logging.debug("-----------------------------------------------------------------------") - - #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - #(t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - #line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - #current_task_y = 25 - #for todo_item in todo_items: - # if todo_item['due']: - # date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - # else: - # date = datetime.datetime.now().date() - # if datetime.datetime.now().date() >= date: - # - # if 2156103501 in todo_item['labels']: - # red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # # set next loop height - # current_task_y = (current_task_y + line_height + 2) - # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - # if todo_item['priority'] > 2: - # black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # # set next loop height - # current_task_y = (current_task_y + line_height + 2) - # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + todo_items = todo.list() + logging.debug("Todo Items") + logging.debug("-----------------------------------------------------------------------") + + (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for todo_item in todo_items: + if todo_item['due']: + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + else: + date = datetime.datetime.now().date() + if datetime.datetime.now().date() >= date: + + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO From 743f1f7de955072d81bdd8866454f4e697ee46c8 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 23 Oct 2021 22:48:08 +0100 Subject: [PATCH 147/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 24e2b13..353af8d 100755 --- a/infowindow.py +++ b/infowindow.py @@ -105,7 +105,7 @@ def main(): red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') #Date header - text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') + text_width = red.textwidth(date.today().strftime('%a')), 'robotoBlack18') red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') From 31f00199d631b6ee3b6b8c66dae096fd9acf9641 Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 23 Oct 2021 22:50:21 +0100 Subject: [PATCH 148/162] Update infowindow.py --- infowindow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infowindow.py b/infowindow.py index 353af8d..7103637 100755 --- a/infowindow.py +++ b/infowindow.py @@ -5,8 +5,8 @@ import json import logging import string -import datetime -from datetime import date +#import datetime +from datetime import datetime from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -105,7 +105,7 @@ def main(): red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') #Date header - text_width = red.textwidth(date.today().strftime('%a')), 'robotoBlack18') + text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') From ee5618111d53fa416c2c782a9905d5576aba0425 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:11:36 +0100 Subject: [PATCH 149/162] Update infowindow.py --- infowindow.py | 58 +++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/infowindow.py b/infowindow.py index 7103637..feb088c 100755 --- a/infowindow.py +++ b/infowindow.py @@ -83,7 +83,7 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - todo = modTodo.ToDo(todo_opts) + # todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts) @@ -127,34 +127,34 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - todo_items = todo.list() - logging.debug("Todo Items") - logging.debug("-----------------------------------------------------------------------") - - (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for todo_item in todo_items: - if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - else: - date = datetime.datetime.now().date() - if datetime.datetime.now().date() >= date: - - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + #todo_items = todo.list() + #logging.debug("Todo Items") + #logging.debug("-----------------------------------------------------------------------") + + #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + #(t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + #line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + #current_task_y = 25 + #for todo_item in todo_items: + # if todo_item['due']: + # date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + # else: + # date = datetime.datetime.now().date() + # if datetime.datetime.now().date() >= date: + # + # if 2156103501 in todo_item['labels']: + # red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # # set next loop height + # current_task_y = (current_task_y + line_height + 2) + # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # if todo_item['priority'] > 2: + # black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # # set next loop height + # current_task_y = (current_task_y + line_height + 2) + # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO From f8f4a69a5d86d36caf631951e69cd4e116521187 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:13:13 +0100 Subject: [PATCH 150/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index feb088c..5955062 100755 --- a/infowindow.py +++ b/infowindow.py @@ -6,7 +6,7 @@ import logging import string #import datetime -from datetime import datetime +from datetime import date from PIL import Image from PIL import ImageDraw from PIL import ImageFont From 0a1f480b513dc8eaa549868d197fd69340e42a2c Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:16:37 +0100 Subject: [PATCH 151/162] Update infowindow.py --- infowindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 5955062..3fef23e 100755 --- a/infowindow.py +++ b/infowindow.py @@ -5,7 +5,6 @@ import json import logging import string -#import datetime from datetime import date from PIL import Image from PIL import ImageDraw From 81e11f95e1365649ce197d1eebf7f8269e1f0c09 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:23:08 +0100 Subject: [PATCH 152/162] Update infowindow.py --- infowindow.py | 56 +++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/infowindow.py b/infowindow.py index 3fef23e..f992c33 100755 --- a/infowindow.py +++ b/infowindow.py @@ -126,34 +126,34 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - #todo_items = todo.list() - #logging.debug("Todo Items") - #logging.debug("-----------------------------------------------------------------------") - - #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - #(t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - #line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - #current_task_y = 25 - #for todo_item in todo_items: - # if todo_item['due']: - # date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - # else: - # date = datetime.datetime.now().date() - # if datetime.datetime.now().date() >= date: - # - # if 2156103501 in todo_item['labels']: - # red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # # set next loop height - # current_task_y = (current_task_y + line_height + 2) - # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - # if todo_item['priority'] > 2: - # black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - # red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # # set next loop height - # current_task_y = (current_task_y + line_height + 2) - # logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + todo_items = todo.list() + logging.debug("Todo Items") + logging.debug("-----------------------------------------------------------------------") + + (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for todo_item in todo_items: + if todo_item['due']: + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + else: + date = datetime.datetime.now().date() + if datetime.datetime.now().date() >= date: + + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO From 8b479031e6d66a9c0b391af07ddf9b1ec7716aca Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:29:36 +0100 Subject: [PATCH 153/162] Update infowindow.py --- infowindow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infowindow.py b/infowindow.py index f992c33..dd3beb6 100755 --- a/infowindow.py +++ b/infowindow.py @@ -137,10 +137,10 @@ def main(): current_task_y = 25 for todo_item in todo_items: if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + caldate = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() else: - date = datetime.datetime.now().date() - if datetime.datetime.now().date() >= date: + calcdate = datetime.datetime.now().date() + if datetime.datetime.now().date() >= caldate: if 2156103501 in todo_item['labels']: red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) @@ -234,7 +234,7 @@ def main(): # draw event date text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), - cal_item['date'].encode(charset).strip(), calendar_date_font, font_color) + cal_item['caldate'].encode(charset).strip(), calendar_date_font, font_color) # draw event time text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), @@ -249,7 +249,7 @@ def main(): # set new line height for next round current_calendar_y = (current_calendar_y + line_height + 2) - # logging.debug("ITEM: "+str(cal_item['date']), str(cal_item['time']), str(cal_item['content'])) + # logging.debug("ITEM: "+str(cal_item['caldate']), str(cal_item['time']), str(cal_item['content'])) logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) # Write to screen From a329bee6402729eb40ea87299ae9d3050a97d6ce Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:30:59 +0100 Subject: [PATCH 154/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index dd3beb6..46255fa 100755 --- a/infowindow.py +++ b/infowindow.py @@ -82,7 +82,7 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - # todo = modTodo.ToDo(todo_opts) + todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts) From a022cc1c90d26279e64cb69ad0ae3702b061574b Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 12:38:05 +0100 Subject: [PATCH 155/162] Update infowindow.py --- infowindow.py | 140 ++++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/infowindow.py b/infowindow.py index 46255fa..3ba7e1f 100755 --- a/infowindow.py +++ b/infowindow.py @@ -82,9 +82,12 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - todo = modTodo.ToDo(todo_opts) - cal = modCalendar.Cal(calendar_opts) - grocy = modGrocy.test(grocy_opts) + try: + todo = modTodo.ToDo(todo_opts) + try: + cal = modCalendar.Cal(calendar_opts) + try: + grocy = modGrocy.test(grocy_opts) # Setup e-ink initial drawings red = infowindow.InfoWindow(infowindow_opts, "red") @@ -126,34 +129,35 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - todo_items = todo.list() - logging.debug("Todo Items") - logging.debug("-----------------------------------------------------------------------") - - (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for todo_item in todo_items: - if todo_item['due']: - caldate = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - else: - calcdate = datetime.datetime.now().date() - if datetime.datetime.now().date() >= caldate: + try: + todo_items = todo.list() + logging.debug("Todo Items") + logging.debug("-----------------------------------------------------------------------") + + (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for todo_item in todo_items: + if todo_item['due']: + caldate = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + else: + calcdate = datetime.datetime.now().date() + if datetime.datetime.now().date() >= caldate: - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO @@ -192,65 +196,65 @@ def main(): # DISPLAY CALENDAR INFO # ========================================================================= - cal_items = cal.list() - logging.debug("Calendar Items") - logging.debug("-----------------------------------------------------------------------") + cal_items = cal.list() + logging.debug("Calendar Items") + logging.debug("-----------------------------------------------------------------------") - if calendar_opts['timeformat'] == "12h": - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') - dt_x = dt_x + (4 * t_x) - if t_y > dt_y: - dt_y = t_y + if calendar_opts['timeformat'] == "12h": + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') + dt_x = dt_x + (4 * t_x) + if t_y > dt_y: + dt_y = t_y - else: - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') - dt_x = dt_x + (4 * t_x) + else: + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') + dt_x = dt_x + (4 * t_x) - (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) + (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) - line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) + line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) - current_calendar_y = 26 - for cal_item in cal_items: - font_color = 'black' - text = black - if cal_item['today']: - text = red - black.rectangle(0, current_calendar_y, + current_calendar_y = 26 + for cal_item in cal_items: + font_color = 'black' + text = black + if cal_item['today']: + text = red + black.rectangle(0, current_calendar_y, 285, (current_calendar_y + line_height), calendar_opts['today_background_color']) - # draw horizontal line - red.line(0, (current_calendar_y + line_height + 1), + # draw horizontal line + red.line(0, (current_calendar_y + line_height + 1), 285, (current_calendar_y + line_height + 1), 'black') - # draw vertical line - red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, + # draw vertical line + red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, (dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), (current_calendar_y + line_height), 'black') - # draw event date - text.text((infowindow_opts["cell_spacing"]), + # draw event date + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), cal_item['caldate'].encode(charset).strip(), calendar_date_font, font_color) - # draw event time - text.text((infowindow_opts["cell_spacing"]), + # draw event time + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), cal_item['time'].encode(charset).strip(), calendar_date_font, font_color) - # draw event text - calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 - max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] - text.text(calendar_event_text_start, + # draw event text + calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 + max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] + text.text(calendar_event_text_start, (current_calendar_y + ((line_height - it_y) / 2)), black.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), calendar_entry_font, font_color) - # set new line height for next round - current_calendar_y = (current_calendar_y + line_height + 2) - # logging.debug("ITEM: "+str(cal_item['caldate']), str(cal_item['time']), str(cal_item['content'])) - logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) + # set new line height for next round + current_calendar_y = (current_calendar_y + line_height + 2) + # logging.debug("ITEM: "+str(cal_item['caldate']), str(cal_item['time']), str(cal_item['content'])) + logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) # Write to screen # ========================================================================= From 28296b721a4f39d20e6d6454586799fe94991eac Mon Sep 17 00:00:00 2001 From: justcop Date: Sat, 23 Oct 2021 22:48:08 +0100 Subject: [PATCH 156/162] Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py Update infowindow.py --- infowindow.py | 143 ++++++++++++++++++++++++++------------------------ 1 file changed, 73 insertions(+), 70 deletions(-) diff --git a/infowindow.py b/infowindow.py index 24e2b13..3ba7e1f 100755 --- a/infowindow.py +++ b/infowindow.py @@ -5,7 +5,6 @@ import json import logging import string -import datetime from datetime import date from PIL import Image from PIL import ImageDraw @@ -83,9 +82,12 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - todo = modTodo.ToDo(todo_opts) - cal = modCalendar.Cal(calendar_opts) - grocy = modGrocy.test(grocy_opts) + try: + todo = modTodo.ToDo(todo_opts) + try: + cal = modCalendar.Cal(calendar_opts) + try: + grocy = modGrocy.test(grocy_opts) # Setup e-ink initial drawings red = infowindow.InfoWindow(infowindow_opts, "red") @@ -127,34 +129,35 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - todo_items = todo.list() - logging.debug("Todo Items") - logging.debug("-----------------------------------------------------------------------") - - (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for todo_item in todo_items: - if todo_item['due']: - date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - else: - date = datetime.datetime.now().date() - if datetime.datetime.now().date() >= date: + try: + todo_items = todo.list() + logging.debug("Todo Items") + logging.debug("-----------------------------------------------------------------------") + + (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for todo_item in todo_items: + if todo_item['due']: + caldate = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + else: + calcdate = datetime.datetime.now().date() + if datetime.datetime.now().date() >= caldate: - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) # DISPLAY GROCY INFO @@ -193,65 +196,65 @@ def main(): # DISPLAY CALENDAR INFO # ========================================================================= - cal_items = cal.list() - logging.debug("Calendar Items") - logging.debug("-----------------------------------------------------------------------") + cal_items = cal.list() + logging.debug("Calendar Items") + logging.debug("-----------------------------------------------------------------------") - if calendar_opts['timeformat'] == "12h": - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') - dt_x = dt_x + (4 * t_x) - if t_y > dt_y: - dt_y = t_y + if calendar_opts['timeformat'] == "12h": + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') + dt_x = dt_x + (4 * t_x) + if t_y > dt_y: + dt_y = t_y - else: - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') - dt_x = dt_x + (4 * t_x) + else: + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') + dt_x = dt_x + (4 * t_x) - (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) + (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) - line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) + line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) - current_calendar_y = 26 - for cal_item in cal_items: - font_color = 'black' - text = black - if cal_item['today']: - text = red - black.rectangle(0, current_calendar_y, + current_calendar_y = 26 + for cal_item in cal_items: + font_color = 'black' + text = black + if cal_item['today']: + text = red + black.rectangle(0, current_calendar_y, 285, (current_calendar_y + line_height), calendar_opts['today_background_color']) - # draw horizontal line - red.line(0, (current_calendar_y + line_height + 1), + # draw horizontal line + red.line(0, (current_calendar_y + line_height + 1), 285, (current_calendar_y + line_height + 1), 'black') - # draw vertical line - red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, + # draw vertical line + red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, (dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), (current_calendar_y + line_height), 'black') - # draw event date - text.text((infowindow_opts["cell_spacing"]), + # draw event date + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), - cal_item['date'].encode(charset).strip(), calendar_date_font, font_color) - # draw event time - text.text((infowindow_opts["cell_spacing"]), + cal_item['caldate'].encode(charset).strip(), calendar_date_font, font_color) + # draw event time + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), cal_item['time'].encode(charset).strip(), calendar_date_font, font_color) - # draw event text - calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 - max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] - text.text(calendar_event_text_start, + # draw event text + calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 + max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] + text.text(calendar_event_text_start, (current_calendar_y + ((line_height - it_y) / 2)), black.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), calendar_entry_font, font_color) - # set new line height for next round - current_calendar_y = (current_calendar_y + line_height + 2) - # logging.debug("ITEM: "+str(cal_item['date']), str(cal_item['time']), str(cal_item['content'])) - logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) + # set new line height for next round + current_calendar_y = (current_calendar_y + line_height + 2) + # logging.debug("ITEM: "+str(cal_item['caldate']), str(cal_item['time']), str(cal_item['content'])) + logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) # Write to screen # ========================================================================= From 95857aee7c7255212c057c2db4d2c13bdd218e1a Mon Sep 17 00:00:00 2001 From: Justin Copitch Date: Sun, 24 Oct 2021 13:08:07 +0100 Subject: [PATCH 157/162] Revert "Update infowindow.py" This reverts commit 9c69441a1b6d53092fb703b581de195992b44188. --- infowindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infowindow.py b/infowindow.py index 3ba7e1f..71f5303 100755 --- a/infowindow.py +++ b/infowindow.py @@ -188,10 +188,10 @@ def main(): # set next loop height current_task_y = (current_task_y + line_height + 2) logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) - except: - logging.debug("Grocy Failed") - logging.debug("-----------------------------------------------------------------------") - red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) + except: + logging.debug("Grocy Failed") + logging.debug("-----------------------------------------------------------------------") + red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) # DISPLAY CALENDAR INFO From c68ba48c04bc881af9ab43de46a790c137e654ec Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 13:10:32 +0100 Subject: [PATCH 158/162] Update infowindow.py --- infowindow.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/infowindow.py b/infowindow.py index 3ba7e1f..5aab831 100755 --- a/infowindow.py +++ b/infowindow.py @@ -82,12 +82,9 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - try: - todo = modTodo.ToDo(todo_opts) - try: - cal = modCalendar.Cal(calendar_opts) - try: - grocy = modGrocy.test(grocy_opts) + todo = modTodo.ToDo(todo_opts) + cal = modCalendar.Cal(calendar_opts) + grocy = modGrocy.test(grocy_opts) # Setup e-ink initial drawings red = infowindow.InfoWindow(infowindow_opts, "red") From 8c2dffd4eace8c001fc54eb027e24f46d16d38c1 Mon Sep 17 00:00:00 2001 From: Justin Copitch Date: Sun, 24 Oct 2021 13:21:23 +0100 Subject: [PATCH 159/162] - --- infowindow.py | 181 ++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 93 deletions(-) diff --git a/infowindow.py b/infowindow.py index 71f5303..fb3045f 100755 --- a/infowindow.py +++ b/infowindow.py @@ -5,7 +5,7 @@ import json import logging import string -from datetime import date +import datetime from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -82,12 +82,9 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules - try: - todo = modTodo.ToDo(todo_opts) - try: - cal = modCalendar.Cal(calendar_opts) - try: - grocy = modGrocy.test(grocy_opts) + todo = modTodo.ToDo(todo_opts) + cal = modCalendar.Cal(calendar_opts) + grocy = modGrocy.test(grocy_opts) # Setup e-ink initial drawings red = infowindow.InfoWindow(infowindow_opts, "red") @@ -105,14 +102,13 @@ def main(): red.text(440 - text_width, 0, "FRIDGE", 'robotoBlack24', 'white') text_width = red.textwidth("TASKS", 'robotoBlack24') red.text(737 - text_width, 0, "TASKS", 'robotoBlack24', 'white') - + #Date header - text_width = red.textwidth(date.today().strftime('%a'), 'robotoBlack18') - red.text(292 - text_width, 0, date.today().strftime('%a'), 'robotoBlack18', 'white') - text_width = red.textwidth(date.today().strftime('%-d %b'), 'robotoBlack18') - red.text(589 - text_width, 0, date.today().strftime('%-d %b'), 'robotoBlack18', 'white') - logging.info(date.today().strftime('%-d %b')) - + text_width = black.textwidth(datetime.today().strftime('%a'), 'robotoBlack18') + black.text(297 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') + text_width = black.textwidth(datetime.today().strftime('%-d %b'), 'robotoBlack18') + black.text(594 - text_width, 0, datetime.today().strftime('%a'), 'robotoBlack18', 'black') + # Set some things calendar_date_font = "robotoRegular14" calendar_entry_font = "robotoBlack18" @@ -129,36 +125,35 @@ def main(): # DISPLAY TODO INFO # ========================================================================= - try: - todo_items = todo.list() - logging.debug("Todo Items") - logging.debug("-----------------------------------------------------------------------") - - (t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') - (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) - line_height = t_y + (2 * infowindow_opts["cell_spacing"]) - - current_task_y = 25 - for todo_item in todo_items: - if todo_item['due']: - caldate = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() - else: - calcdate = datetime.datetime.now().date() - if datetime.datetime.now().date() >= caldate: - - if 2156103501 in todo_item['labels']: - red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - if todo_item['priority'] > 2: - black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) - red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) - # set next loop height - current_task_y = (current_task_y + line_height + 2) - logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) - + todo_items = todo.list() + logging.debug("Todo Items") + logging.debug("-----------------------------------------------------------------------") + + #(t_x, t_y) = red.getFont(tasks_font).getsize('JgGj') + (t_x, t_y) = get_max_char_size(red, string.printable, tasks_font) + line_height = t_y + (2 * infowindow_opts["cell_spacing"]) + + current_task_y = 25 + for todo_item in todo_items: + if todo_item['due']: + date = datetime.datetime.strptime(todo_item['due'][0:10], '%Y-%m-%d').date() + else: + date = datetime.datetime.now().date() + if datetime.datetime.now().date() >= date: + + if 2156103501 in todo_item['labels']: + red.text(595, (current_task_y + infowindow_opts["cell_spacing"]), red.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + if todo_item['priority'] > 2: + black.text(595, (current_task_y + infowindow_opts["cell_spacing"]), black.truncate(todo_item['content'].encode(charset).strip(), tasks_font, 286), tasks_font) + red.line(595, (current_task_y + line_height + 1), 880, (current_task_y + line_height + 1)) + # set next loop height + current_task_y = (current_task_y + line_height + 2) + logging.debug("ITEM: %s" % todo_item['content'].encode(charset).strip()) + # DISPLAY GROCY INFO # ========================================================================= @@ -178,7 +173,7 @@ def main(): text = red else: text = black - + text.text(298, (current_task_y + infowindow_opts["cell_spacing"]), str(grocy_item['days'] + " "), tasks_font) (op_x, op_y) = text.getFont(tasks_font).getsize(black.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x)) text.text(583 - op_x, (current_task_y + infowindow_opts["cell_spacing"]), text.truncate(grocy_item['content'].encode(charset).strip(), tasks_font, 286 - np_x), tasks_font) @@ -188,93 +183,93 @@ def main(): # set next loop height current_task_y = (current_task_y + line_height + 2) logging.debug("ITEM: %s" % grocy_item['content'].encode(charset).strip()) - except: - logging.debug("Grocy Failed") - logging.debug("-----------------------------------------------------------------------") - red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) - + except: + logging.debug("Grocy Failed") + logging.debug("-----------------------------------------------------------------------") + red.text(298, (25 + infowindow_opts["cell_spacing"]), str("Grocy failed to load"), tasks_font) + # DISPLAY CALENDAR INFO # ========================================================================= - cal_items = cal.list() - logging.debug("Calendar Items") - logging.debug("-----------------------------------------------------------------------") + cal_items = cal.list() + logging.debug("Calendar Items") + logging.debug("-----------------------------------------------------------------------") - if calendar_opts['timeformat'] == "12h": - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') - dt_x = dt_x + (4 * t_x) - if t_y > dt_y: - dt_y = t_y + if calendar_opts['timeformat'] == "12h": + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize(': pm') + dt_x = dt_x + (4 * t_x) + if t_y > dt_y: + dt_y = t_y - else: - (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) - (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') - dt_x = dt_x + (4 * t_x) + else: + (t_x, t_y) = get_max_char_size(red, string.digits, calendar_date_font) + (dt_x, dt_y) = black.getFont(calendar_date_font).getsize('.') + dt_x = dt_x + (4 * t_x) - (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) + (it_x, it_y) = get_max_char_size(red, string.printable, calendar_entry_font) - line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) + line_height = (2 * dt_y) + (2 * infowindow_opts["cell_spacing"]) - current_calendar_y = 26 - for cal_item in cal_items: - font_color = 'black' - text = black - if cal_item['today']: - text = red - black.rectangle(0, current_calendar_y, + current_calendar_y = 26 + for cal_item in cal_items: + font_color = 'black' + text = black + if cal_item['today']: + text = red + black.rectangle(0, current_calendar_y, 285, (current_calendar_y + line_height), calendar_opts['today_background_color']) - # draw horizontal line - red.line(0, (current_calendar_y + line_height + 1), + # draw horizontal line + red.line(0, (current_calendar_y + line_height + 1), 285, (current_calendar_y + line_height + 1), 'black') - # draw vertical line - red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, + # draw vertical line + red.line((dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), current_calendar_y, (dt_x + (2 * infowindow_opts["cell_spacing"]) + 1), (current_calendar_y + line_height), 'black') - # draw event date - text.text((infowindow_opts["cell_spacing"]), + # draw event date + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + infowindow_opts["cell_spacing"]), - cal_item['caldate'].encode(charset).strip(), calendar_date_font, font_color) - # draw event time - text.text((infowindow_opts["cell_spacing"]), + cal_item['date'].encode(charset).strip(), calendar_date_font, font_color) + # draw event time + text.text((infowindow_opts["cell_spacing"]), (current_calendar_y + ((line_height - 2 * infowindow_opts["cell_spacing"]) / 2)), cal_item['time'].encode(charset).strip(), calendar_date_font, font_color) - # draw event text - calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 - max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] - text.text(calendar_event_text_start, + # draw event text + calendar_event_text_start = dt_x + (3 * infowindow_opts["cell_spacing"]) + 1 + max_event_text_length = 285 - calendar_event_text_start - infowindow_opts["cell_spacing"] + text.text(calendar_event_text_start, (current_calendar_y + ((line_height - it_y) / 2)), black.truncate(cal_item['content'].encode(charset).strip(), calendar_entry_font, max_event_text_length), calendar_entry_font, font_color) - # set new line height for next round - current_calendar_y = (current_calendar_y + line_height + 2) - # logging.debug("ITEM: "+str(cal_item['caldate']), str(cal_item['time']), str(cal_item['content'])) - logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) + # set new line height for next round + current_calendar_y = (current_calendar_y + line_height + 2) + # logging.debug("ITEM: "+str(cal_item['date']), str(cal_item['time']), str(cal_item['content'])) + logging.debug("ITEM: %s" % cal_item['content'].encode(charset).strip()) # Write to screen # ========================================================================= red.image = red.image.rotate(rotation) black.image = black.image.rotate(rotation) - + new_image_found = 0 if os.path.exists(red.tmpImagePath): old_image = Image.open(red.tmpImagePath) diff = ImageChops.difference(red.image, old_image) if not diff.getbbox(): new_image_found += 1 - + if os.path.exists(black.tmpImagePath): old_image = Image.open(black.tmpImagePath) diff = ImageChops.difference(black.image, old_image) if not diff.getbbox(): new_image_found += 1 - if new_image_found < 2: + if new_image_found < 2: logging.info("New information in the image detected. Updating the screen.") red.image.save(red.tmpImagePath) black.image.save(black.tmpImagePath) @@ -282,6 +277,6 @@ def main(): red.epd.sleep() else: logging.info("No new information found. Not updating the screen.") - + if __name__ == '__main__': main() From 6c1b12e139d51d059f8681a3570093cfa6c55307 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 14:15:44 +0100 Subject: [PATCH 160/162] Update infowindow.py --- infowindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infowindow.py b/infowindow.py index fb3045f..2153e37 100755 --- a/infowindow.py +++ b/infowindow.py @@ -32,6 +32,7 @@ rotation = config_data["general"]["rotation"] charset = config_data["general"]["charset"] todo_opts = config_data["todo"] +logging.info(todo_opts) grocy_opts = config_data["grocy"] calendar_opts = config_data["calendar"] infowindow_opts = {} From 760d305c219af35e59fb5cf3a78cff44bb33b524 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 14:20:59 +0100 Subject: [PATCH 161/162] Update infowindow.py --- infowindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infowindow.py b/infowindow.py index 2153e37..fc82134 100755 --- a/infowindow.py +++ b/infowindow.py @@ -32,7 +32,6 @@ rotation = config_data["general"]["rotation"] charset = config_data["general"]["charset"] todo_opts = config_data["todo"] -logging.info(todo_opts) grocy_opts = config_data["grocy"] calendar_opts = config_data["calendar"] infowindow_opts = {} @@ -83,6 +82,7 @@ def get_max_char_size(black, chars, font): # Main Program ################################################################ def main(): # Instantiate API modules + logging.info(str(todo_opts)) todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts) From 3703d4ab1db1e1017cef24f3c5037c52b1bfccb7 Mon Sep 17 00:00:00 2001 From: justcop Date: Sun, 24 Oct 2021 14:22:59 +0100 Subject: [PATCH 162/162] Update infowindow.py --- infowindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/infowindow.py b/infowindow.py index fc82134..91d8294 100755 --- a/infowindow.py +++ b/infowindow.py @@ -83,6 +83,7 @@ def get_max_char_size(black, chars, font): def main(): # Instantiate API modules logging.info(str(todo_opts)) + logging.info(str(calendar_opts)) todo = modTodo.ToDo(todo_opts) cal = modCalendar.Cal(calendar_opts) grocy = modGrocy.test(grocy_opts)