diff --git a/Installer.sh b/Installer.sh index 3ee5004..6cfc1a5 100644 --- a/Installer.sh +++ b/Installer.sh @@ -180,5 +180,5 @@ EOF fi echo -e "\e[1;36m"You can test if the programm works by running:"\e[0m" - echo -e "\e[1;36m"python3 /home/"$USER"/Inky-Calendar/Calendar/inkycal.py"\e[0m" + echo -e "\e[1;36m"python3 /home/"$USER"/Inky-Calendar/modules/inkycal.py"\e[0m" fi diff --git a/modules/drivers/epd_4_in_2.py b/modules/drivers/epd_4_in_2.py new file mode 100644 index 0000000..5a15fc5 --- /dev/null +++ b/modules/drivers/epd_4_in_2.py @@ -0,0 +1,478 @@ +# ***************************************************************************** +# * | File : epd4in2.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 +from PIL import Image +import RPi.GPIO as GPIO + +# Display resolution +EPD_WIDTH = 400 +EPD_HEIGHT = 300 + +GRAY1 = 0xff #white +GRAY2 = 0xC0 +GRAY3 = 0x80 #gray +GRAY4 = 0x00 #Blackest + +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 + self.GRAY1 = GRAY1 #white + self.GRAY2 = GRAY2 + self.GRAY3 = GRAY3 #gray + self.GRAY4 = GRAY4 #Blackest + + lut_vcom0 = [ + 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_ww = [ + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_bw = [ + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_wb = [ + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + lut_bb = [ + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, + 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, + 0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, + 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ] + + #******************************gray*********************************/ + #0~3 gray + EPD_4IN2_4Gray_lut_vcom =[ + 0x00 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, + 0x60 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, + 0x00 ,0x14 ,0x00 ,0x00 ,0x00 ,0x01, + 0x00 ,0x13 ,0x0A ,0x01 ,0x00 ,0x01, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 + ] + #R21 + EPD_4IN2_4Gray_lut_ww =[ + 0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, + 0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, + 0x10 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, + 0xA0 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + ] + #R22H r + EPD_4IN2_4Gray_lut_bw =[ + 0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, + 0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, + 0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, + 0x99 ,0x0C ,0x01 ,0x03 ,0x04 ,0x01, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + ] + #R23H w + EPD_4IN2_4Gray_lut_wb =[ + 0x40 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, + 0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, + 0x00 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, + 0x99 ,0x0B ,0x04 ,0x04 ,0x01 ,0x01, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + ] + #R24H b + EPD_4IN2_4Gray_lut_bb =[ + 0x80 ,0x0A ,0x00 ,0x00 ,0x00 ,0x01, + 0x90 ,0x14 ,0x14 ,0x00 ,0x00 ,0x01, + 0x20 ,0x14 ,0x0A ,0x00 ,0x00 ,0x01, + 0x50 ,0x13 ,0x01 ,0x00 ,0x00 ,0x01, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + ] + + # 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(10) + 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): + self.send_command(0x71) + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + self.send_command(0x71) + epdconfig.delay_ms(100) + + def set_lut(self): + self.send_command(0x20) # vcom + for count in range(0, 44): + self.send_data(self.lut_vcom0[count]) + + self.send_command(0x21) # ww -- + for count in range(0, 42): + self.send_data(self.lut_ww[count]) + + self.send_command(0x22) # bw r + for count in range(0, 42): + self.send_data(self.lut_bw[count]) + + self.send_command(0x23) # wb w + for count in range(0, 42): + self.send_data(self.lut_bb[count]) + + self.send_command(0x24) # bb b + for count in range(0, 42): + self.send_data(self.lut_wb[count]) + + def Gray_SetLut(self): + self.send_command(0x20) #vcom + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_vcom[count]) + + self.send_command(0x21) #red not use + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_ww[count]) + + self.send_command(0x22) #bw r + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_bw[count]) + + self.send_command(0x23) #wb w + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_wb[count]) + + self.send_command(0x24) #bb b + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_bb[count]) + + self.send_command(0x25) #vcom + for count in range(0, 42): + self.send_data(self.EPD_4IN2_4Gray_lut_ww[count]) + + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) # VDS_EN, VDG_EN + self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2b) # VDH + self.send_data(0x2b) # VDL + + self.send_command(0x06) # boost soft start + self.send_data(0x17) + self.send_data(0x17) + self.send_data(0x17) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0xbf) # KW-BF KWR-AF BWROTP 0f + self.send_data(0x0d) + + self.send_command(0x30) # PLL setting + self.send_data(0x3c) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x61) # resolution setting + self.send_data(0x01) + self.send_data(0x90) # 128 + self.send_data(0x01) + self.send_data(0x2c) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x28) + + self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING + self.send_data(0x97) # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + + self.set_lut() + # EPD hardware init end + return 0 + + def Init_4Gray(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) #POWER SETTING + self.send_data (0x03) + self.send_data (0x00) #VGH=20V,VGL=-20V + self.send_data (0x2b) #VDH=15V + self.send_data (0x2b) #VDL=-15V + self.send_data (0x13) + + self.send_command(0x06) #booster soft start + self.send_data (0x17) #A + self.send_data (0x17) #B + self.send_data (0x17) #C + + self.send_command(0x04) + self.ReadBusy() + + self.send_command(0x00) #panel setting + self.send_data(0x3f) #KW-3f KWR-2F BWROTP 0f BWOTP 1f + + self.send_command(0x30) #PLL setting + self.send_data (0x3c) #100hz + + self.send_command(0x61) #resolution setting + self.send_data (0x01) #400 + self.send_data (0x90) + self.send_data (0x01) #300 + self.send_data (0x2c) + + self.send_command(0x82) #vcom_DC setting + self.send_data (0x12) + + self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING + self.send_data(0x97) + + 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 getbuffer_4Gray(self, image): + # logging.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 4) * self.height) + image_monocolor = image.convert('L') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + i=0 + # logging.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if(imwidth == self.width and imheight == self.height): + logging.debug("Vertical") + 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] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i= i+1 + if(i%4 == 0): + buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) + + elif(imwidth == self.height and imheight == self.width): + logging.debug("Horizontal") + for x in range(imwidth): + for y in range(imheight): + newx = y + newy = x + if(pixels[x, y] == 0xC0): + pixels[x, y] = 0x80 + elif (pixels[x, y] == 0x80): + pixels[x, y] = 0x40 + i= i+1 + if(i%4 == 0): + buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) + + return buf + + def display(self, image): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(image[i]) + + self.send_command(0x12) + self.ReadBusy() + + def display_4Gray(self, image): + self.send_command(0x10) + for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): # EPD_WIDTH * EPD_HEIGHT / 4 + temp3=0 + for j in range(0, 2): + temp1 = image[i*2+j] + for k in range(0, 2): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x01#white + elif(temp2 == 0x00): + temp3 |= 0x00 #black + elif(temp2 == 0x80): + temp3 |= 0x01 #gray1 + else: #0x40 + temp3 |= 0x00 #gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): #white + temp3 |= 0x01 + elif(temp2 == 0x00): #black + temp3 |= 0x00 + elif(temp2 == 0x80): + temp3 |= 0x01 #gray1 + else : #0x40 + temp3 |= 0x00 #gray2 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.send_command(0x13) + + for i in range(0, EPD_WIDTH * EPD_HEIGHT / 8): #5808*4 46464 + temp3=0 + for j in range(0, 2): + temp1 = image[i*2+j] + for k in range(0, 2): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): + temp3 |= 0x01#white + elif(temp2 == 0x00): + temp3 |= 0x00 #black + elif(temp2 == 0x80): + temp3 |= 0x00 #gray1 + else: #0x40 + temp3 |= 0x01 #gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1&0xC0 + if(temp2 == 0xC0): #white + temp3 |= 0x01 + elif(temp2 == 0x00): #black + temp3 |= 0x00 + elif(temp2 == 0x80): + temp3 |= 0x00 #gray1 + else: #0x40 + temp3 |= 0x01 #gray2 + if(j!=1 or k!=1): + temp3 <<= 1 + temp1 <<= 2 + self.send_data(temp3) + + self.Gray_SetLut() + self.send_command(0x12) + epdconfig.delay_ms(200) + self.ReadBusy() + # pass + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() + +### END OF FILE ### + diff --git a/modules/drivers/epd_4_in_2_colour.py b/modules/drivers/epd_4_in_2_colour.py new file mode 100644 index 0000000..d29465a --- /dev/null +++ b/modules/drivers/epd_4_in_2_colour.py @@ -0,0 +1,148 @@ +# ***************************************************************************** +# * | File : epd4in2bc.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 400 +EPD_HEIGHT = 300 + +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(10) + 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") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data (0x17) + self.send_data (0x17) + self.send_data (0x17) # 07 0f 17 1f 27 2F 37 2f + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0x0F) # LUT from OTP + + 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(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imageblack[i]) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imagered[i]) + + self.send_command(0x12) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xFF) + + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0xA5) # check code + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epd_5_in_83.py b/modules/drivers/epd_5_in_83.py new file mode 100644 index 0000000..a0131b7 --- /dev/null +++ b/modules/drivers/epd_5_in_83.py @@ -0,0 +1,200 @@ +# ***************************************************************************** +# * | File : epd5in83.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 600 +EPD_HEIGHT = 448 + +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(10) + 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") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x37) + self.send_data(0x00) + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0xCF) + self.send_data(0x08) + + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data(0xc7) + self.send_data(0xcc) + self.send_data(0x28) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x3c) + + self.send_command(0x41) # TEMPERATURE_CALIBRATION + self.send_data(0x00) + + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING + self.send_data(0x77) + + self.send_command(0x60) # TCON_SETTING + self.send_data(0x22) + + self.send_command(0x61) # TCON_RESOLUTION + self.send_data(0x02) # source 600 + self.send_data(0x58) + self.send_data(0x01) # gate 448 + self.send_data(0xC0) + + self.send_command(0x82) # VCM_DC_SETTING + self.send_data(0x1E) # decide by LUT file + + self.send_command(0xe5) # FLASH MODE + self.send_data(0x03) + + # EPD hardware init end + return 0 + + def getbuffer(self, image): + buf = [0x00] * int(self.width * self.height / 4) + 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): + 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] < 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) + elif(imwidth == self.height and imheight == self.width): + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] < 64: # black + buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) + elif pixels[x, y] < 192: # convert gray to red + buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) + buf[int((newx + newy*self.width) / 4)] |= 0x40 >> (y % 4 * 2) + else: # white + buf[int((newx + newy*self.width) / 4)] |= 0xC0 >> (y % 4 * 2) + return buf + + def display(self, image): + self.send_command(0x10) + for i in range(0, int(self.width / 4 * self.height)): + temp1 = image[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 + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width / 4 * self.height)): + for j in range(0, 4): + self.send_data(0x33) + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() + +### END OF FILE ### + diff --git a/modules/drivers/epd_5_in_83_colour.py b/modules/drivers/epd_5_in_83_colour.py new file mode 100644 index 0000000..19c8a40 --- /dev/null +++ b/modules/drivers/epd_5_in_83_colour.py @@ -0,0 +1,200 @@ +# ***************************************************************************** +# * | File : epd5in83b.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 600 +EPD_HEIGHT = 448 + +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(10) + 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") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x37) + self.send_data(0x00) + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0xCF) + self.send_data(0x08) + + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A + self.send_command(0X82) # VCOM VOLTAGE SETTING + self.send_data(0x28) # all temperature range + + self.send_command(0x06) # boost + self.send_data(0xc7) + self.send_data(0xcc) + self.send_data(0x15) + + self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING + self.send_data(0x77) + + self.send_command(0X60) # TCON SETTING + self.send_data(0x22) + + self.send_command(0X65) # FLASH CONTROL + self.send_data(0x00) + + self.send_command(0x61) # tres + self.send_data(0x02) # source 600 + self.send_data(0x58) + self.send_data(0x01) # gate 448 + self.send_data(0xc0) + + self.send_command(0xe5) # FLASH MODE + self.send_data(0x03) + self.send_data(0x03) + + 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(0x10) + for i in range(0, int(self.width / 8 * self.height)): + temp1 = imageblack[i] + temp2 = imagered[i] + j = 0 + while (j < 8): + if ((temp2 & 0x80) == 0x00): + temp3 = 0x04 #red + elif ((temp1 & 0x80) == 0x00): + temp3 = 0x00 #black + else: + temp3 = 0x03 #white + + temp3 = (temp3 << 4) & 0xFF + temp1 = (temp1 << 1) & 0xFF + temp2 = (temp2 << 1) & 0xFF + j += 1 + if((temp2 & 0x80) == 0x00): + temp3 |= 0x04 #red + elif ((temp1 & 0x80) == 0x00): + temp3 |= 0x00 #black + else: + temp3 |= 0x03 #white + temp1 = (temp1 << 1) & 0xFF + temp2 = (temp2 << 1) & 0xFF + self.send_data(temp3) + j += 1 + + self.send_command(0x04) # POWER ON + self.ReadBusy() + self.send_command(0x12) # display refresh + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width / 8 * self.height)): + self.send_data(0x33) + self.send_data(0x33) + self.send_data(0x33) + self.send_data(0x33) + + self.send_command(0x04) # POWER ON + self.ReadBusy() + self.send_command(0x12) # display refresh + epdconfig.delay_ms(100) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0xA5) # check code + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epd_7_in_5.py b/modules/drivers/epd_7_in_5.py new file mode 100644 index 0000000..80f488d --- /dev/null +++ b/modules/drivers/epd_7_in_5.py @@ -0,0 +1,202 @@ +# ***************************************************************************** +# * | File : epd7in5.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 640 +EPD_HEIGHT = 384 + +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(10) + 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") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x37) + self.send_data(0x00) + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0xCF) + self.send_data(0x08) + + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data(0xc7) + self.send_data(0xcc) + self.send_data(0x28) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x3c) + + self.send_command(0x41) # TEMPERATURE_CALIBRATION + self.send_data(0x00) + + self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING + self.send_data(0x77) + + self.send_command(0x60) # TCON_SETTING + self.send_data(0x22) + + self.send_command(0x61) # TCON_RESOLUTION + self.send_data(EPD_WIDTH >> 8) #source 640 + self.send_data(EPD_WIDTH & 0xff) + self.send_data(EPD_HEIGHT >> 8) #gate 384 + self.send_data(EPD_HEIGHT & 0xff) + + self.send_command(0x82) # VCM_DC_SETTING + self.send_data(0x1E) # decide by LUT file + + self.send_command(0xe5) # FLASH MODE + self.send_data(0x03) + + # EPD hardware init end + return 0 + + def getbuffer(self, image): + logging.debug("1234") + buf = [0x00] * int(self.width * self.height / 4) + 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): + 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] < 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) + elif(imwidth == self.height and imheight == self.width): + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] < 64: # black + buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) + elif pixels[x, y] < 192: # convert gray to red + buf[int((newx + newy*self.width) / 4)] &= ~(0xC0 >> (y % 4 * 2)) + buf[int((newx + newy*self.width) / 4)] |= 0x40 >> (y % 4 * 2) + else: # white + buf[int((newx + newy*self.width) / 4)] |= 0xC0 >> (y % 4 * 2) + return buf + + def display(self, image): + self.send_command(0x10) + for i in range(0, int(self.width / 4 * self.height)): + temp1 = image[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 + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width / 4 * self.height)): + for j in range(0, 4): + self.send_data(0x33) + + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epd_7_in_5_colour.py b/modules/drivers/epd_7_in_5_colour.py new file mode 100644 index 0000000..7ec3959 --- /dev/null +++ b/modules/drivers/epd_7_in_5_colour.py @@ -0,0 +1,201 @@ +# ***************************************************************************** +# * | File : epd7in5bc.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 640 +EPD_HEIGHT = 384 + +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(10) + 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") + while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(100) + logging.debug("e-Paper busy release") + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + + self.reset() + + self.send_command(0x01) # POWER_SETTING + self.send_data(0x37) + self.send_data(0x00) + + self.send_command(0x00) # PANEL_SETTING + self.send_data(0xCF) + self.send_data(0x08) + + self.send_command(0x30) # PLL_CONTROL + self.send_data(0x3A) # PLL: 0-15:0x3C, 15+:0x3A + + self.send_command(0x82) # VCM_DC_SETTING + self.send_data(0x28) #all temperature range + + self.send_command(0x06) # BOOSTER_SOFT_START + self.send_data(0xc7) + self.send_data(0xcc) + self.send_data(0x15) + + self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING + self.send_data(0x77) + + self.send_command(0x60) # TCON_SETTING + self.send_data(0x22) + + self.send_command(0x65) # FLASH CONTROL + self.send_data(0x00) + + self.send_command(0x61) # TCON_RESOLUTION + self.send_data(self.width >> 8) # source 640 + self.send_data(self.width & 0xff) + self.send_data(self.height >> 8) # gate 384 + self.send_data(self.height & 0xff) + + self.send_command(0xe5) # FLASH MODE + self.send_data(0x03) + + 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(0x10) + for i in range(0, int(self.width / 8 * self.height)): + temp1 = imageblack[i] + temp2 = imagered[i] + j = 0 + while (j < 8): + if ((temp2 & 0x80) == 0x00): + temp3 = 0x04 #red + elif ((temp1 & 0x80) == 0x00): + temp3 = 0x00 #black + else: + temp3 = 0x03 #white + + temp3 = (temp3 << 4) & 0xFF + temp1 = (temp1 << 1) & 0xFF + temp2 = (temp2 << 1) & 0xFF + j += 1 + if((temp2 & 0x80) == 0x00): + temp3 |= 0x04 #red + elif ((temp1 & 0x80) == 0x00): + temp3 |= 0x00 #black + else: + temp3 |= 0x03 #white + temp1 = (temp1 << 1) & 0xFF + temp2 = (temp2 << 1) & 0xFF + self.send_data(temp3) + j += 1 + + self.send_command(0x04) # POWER ON + self.ReadBusy() + self.send_command(0x12) # display refresh + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width / 8 * self.height)): + self.send_data(0x33) + self.send_data(0x33) + self.send_data(0x33) + self.send_data(0x33) + + self.send_command(0x04) # POWER ON + self.ReadBusy() + self.send_command(0x12) # display refresh + epdconfig.delay_ms(100) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epd_7_in_5_v2.py b/modules/drivers/epd_7_in_5_v2.py new file mode 100644 index 0000000..ceff4ce --- /dev/null +++ b/modules/drivers/epd_7_in_5_v2.py @@ -0,0 +1,170 @@ +# ***************************************************************************** +# * | File : epd7in5.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 800 +EPD_HEIGHT = 480 + +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(2) + 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") + self.send_command(0x71) + busy = epdconfig.digital_read(self.busy_pin) + while(busy == 0): + self.send_command(0x71) + busy = epdconfig.digital_read(self.busy_pin) + epdconfig.delay_ms(200) + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) #POWER SETTING + self.send_data(0x07) + self.send_data(0x07) #VGH=20V,VGL=-20V + self.send_data(0x3f) #VDH=15V + self.send_data(0x3f) #VDL=-15V + + self.send_command(0x04) #POWER ON + epdconfig.delay_ms(100) + self.ReadBusy() + + self.send_command(0X00) #PANNEL SETTING + self.send_data(0x1F) #KW-3f KWR-2F BWROTP 0f BWOTP 1f + + self.send_command(0x61) #tres + self.send_data(0x03) #source 800 + self.send_data(0x20) + self.send_data(0x01) #gate 480 + self.send_data(0xE0) + + self.send_command(0X15) + self.send_data(0x00) + + self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING + self.send_data(0x10) + self.send_data(0x07) + + self.send_command(0X60) #TCON SETTING + self.send_data(0x22) + + # EPD hardware init end + 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("Vertical") + 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("Horizontal") + 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, image): + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(~image[i]); + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epd_7_in_5_v2_colour.py b/modules/drivers/epd_7_in_5_v2_colour.py new file mode 100644 index 0000000..9d631d3 --- /dev/null +++ b/modules/drivers/epd_7_in_5_v2_colour.py @@ -0,0 +1,173 @@ +# ***************************************************************************** +# * | File : epd7in5bc.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V4.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 = 800 +EPD_HEIGHT = 480 + +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") + self.send_command(0x71) + busy = epdconfig.digital_read(self.busy_pin) + while(busy == 0): + self.send_command(0x71) + 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(0x01); #POWER SETTING + self.send_data(0x07); + self.send_data(0x07); #VGH=20V,VGL=-20V + self.send_data(0x3f); #VDH=15V + self.send_data(0x3f); #VDL=-15V + + self.send_command(0x04); #POWER ON + epdconfig.delay_ms(100); + self.ReadBusy(); + + self.send_command(0X00); #PANNEL SETTING + self.send_data(0x0F); #KW-3f KWR-2F BWROTP 0f BWOTP 1f + + self.send_command(0x61); #tres + self.send_data(0x03); #source 800 + self.send_data(0x20); + self.send_data(0x01); #gate 480 + self.send_data(0xE0); + + self.send_command(0X15); + self.send_data(0x00); + + self.send_command(0X50); #VCOM AND DATA INTERVAL SETTING + self.send_data(0x11); + self.send_data(0x07); + + self.send_command(0X60); #TCON SETTING + self.send_data(0x22); + + 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(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(imageblack[i]); + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(~imagered[i]); + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def Clear(self): + self.send_command(0x10) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0xff) + + self.send_command(0x13) + for i in range(0, int(self.width * self.height / 8)): + self.send_data(0x00) + + self.send_command(0x12) + epdconfig.delay_ms(100) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.module_exit() +### END OF FILE ### + diff --git a/modules/drivers/epdconfig.py b/modules/drivers/epdconfig.py new file mode 100644 index 0000000..7f50759 --- /dev/null +++ b/modules/drivers/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() #removed as it causes some problems + + 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/modules/drivers/init.py b/modules/drivers/init.py new file mode 100644 index 0000000..9b2c532 --- /dev/null +++ b/modules/drivers/init.py @@ -0,0 +1 @@ +#nothing in here. What did you expect? \ No newline at end of file diff --git a/modules/inkycal.py b/modules/inkycal.py index 3ee2f93..7a576cc 100644 --- a/modules/inkycal.py +++ b/modules/inkycal.py @@ -1,28 +1,22 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- """ -Main script of Inky-Calendar software. +v1.7.1 + +Main file of Inky-Calendar software. Creates dynamic images for each section, +assembles them and sends it to the E-Paper + Copyright by aceisace """ from __future__ import print_function from configuration import * -from settings import * import arrow from time import sleep import gc -import inkycal_drivers as drivers - -import inkycal_rss as rss -import inkycal_weather as weather -import inkycal_calendar as calendar -import inkycal_agenda as agenda - - -display = drivers.EPD() -skip_calibration = False """Perepare for execution of main programm""" calibration_countdown = 'initial' +skip_calibration = False image_cleanup() """Check time and calibrate display if time """ @@ -30,14 +24,12 @@ while True: now = arrow.now(tz=get_tz()) for _ in range(1): image = Image.new('RGB', (display_width, display_height), background_colour) - + """------------------Add short info------------------""" print('Current Date: {0} \nCurrent Time: {1}'.format(now.format( 'D MMM YYYY'), now.format('HH:mm'))) print('-----------Main programm started now----------') - - """------------------Calibration check----------------""" if skip_calibration != True: print('Calibration..', end = ' ') @@ -45,10 +37,10 @@ while True: if calibration_countdown == 'initial': print('required. Performing calibration now.') calibration_countdown = 0 - display.calibrate_display(3) + calibrate_display(3) else: if calibration_countdown % (60 // int(update_interval)) == 0: - display.calibrate_display(3) + calibrate_display(3) calibration_countdown = 0 else: print('not required. Continuing...') @@ -56,43 +48,50 @@ while True: print('Calibration skipped!. Please note that not calibrating e-paper', 'displays causes ghosting') + """----------------Generating and assembling images------""" - if top_section == 'Weather': - try: - weather.main() - weather_image = Image.open(image_path + 'weather.png') - image.paste(weather_image, (0, 0)) - except: - pass + try: + top_section_module = importlib.import_module(top_section) + top_section_image = Image.open(image_path + top_section+'.png') + image.paste(top_section_image, (0, 0)) + except: + pass - if middle_section == 'Calendar': - try: - calendar.main() - calendar_image = Image.open(image_path + 'calendar.png') - image.paste(calendar_image, (0, middle_section_offset)) - except: - pass - - if middle_section == 'Agenda': - try: - agenda.main() - agenda_image = Image.open(image_path + 'agenda.png') - image.paste(agenda_image, (0, middle_section_offset)) - except: - pass - - if bottom_section == 'RSS': - try: - rss.main() - rss_image = Image.open(image_path + 'rss.png') - image.paste(rss_image, (0, bottom_section_offset)) - except: - pass + try: + middle_section_module = importlib.import_module(middle_section) + middle_section_image = Image.open(image_path + middle_section+'.png') + image.paste(middle_section_image, (0, middle_section_offset)) + except: + pass - image.save(image_path + 'canvas.png') + try: + bottom_section_module = importlib.import_module(bottom_section) + bottom_section_image = Image.open(image_path + bottom_section+'.png') + image.paste(bottom_section_image, (0, bottom_section_offset)) + except: + pass + + image.save(image_path + 'canvas.png') """---------Refreshing E-Paper with newly created image-----------""" - display.show_image(image, reduce_colours= True) + epaper = driver.EPD() + print('Initialising E-Paper...', end = '') + epaper.init() + print('Done') + + if three_colour_support == True: + print('Sending image data and refreshing display...', end='') + black_im, red_im = split_colours(image) + epaper.display(epaper.getbuffer(black_im), epaper.getbuffer(red_im)) + print('Done') + else: + print('Sending image data and refreshing display...', end='') + epaper.display(epaper.getbuffer(image.convert('1', dither=True))) + print('Done') + + print('Sending E-Paper to deep sleep...', end = '') + epaper.sleep() + print('Done') """--------------Post processing after main loop-----------------""" """Collect some garbage to free up some resources""" @@ -106,12 +105,15 @@ while True: """Calculate duration until next display refresh""" for _ in range(1): update_timings = [(60 - int(update_interval)*updates) for updates in - range(60//int(update_interval))] + range(60//int(update_interval))][::-1] - minutes = [i - now.minute for i in update_timings if i >= now.minute] - refresh_countdown = minutes[0]*60 + (60 - now.second) + for _ in update_timings: + if now.minute <= _: + minutes = _ - now.minute + break + + refresh_countdown = minutes*60 + (60 - now.second) + print('{0} Minutes left until next refresh'.format(minutes)) - print('{0} Minutes left until next refresh'.format(minutes[0])) - del update_timings, minutes, image sleep(refresh_countdown) diff --git a/modules/inkycal_agenda.py b/modules/inkycal_agenda.py index f1d227c..e25a7da 100644 --- a/modules/inkycal_agenda.py +++ b/modules/inkycal_agenda.py @@ -7,8 +7,6 @@ Copyright by aceisace from __future__ import print_function from inkycal_icalendar import fetch_events from configuration import* -from settings import * -import arrow fontsize = 14 show_events = True @@ -45,100 +43,103 @@ else: line_pos = [(border_left, int(top_section_height + border_top + line * line_height)) for line in range(max_lines)] -def main(): - try: - clear_image('middle_section') - if not bottom_section: - clear_image('bottom_section') +def generate_image(): + if middle_section == 'inkycal_agenda' and internet_available() == True: + try: + clear_image('middle_section') + if not bottom_section: + clear_image('bottom_section') - print('Agenda module: Generating image...', end = '') - now = arrow.now(get_tz()) - today_start = arrow.get(now.year, now.month, now.day) + print('Agenda module: Generating image...', end = '') + now = arrow.now(get_tz()) + today_start = arrow.get(now.year, now.month, now.day) - """Create a list of dictionaries containing dates of the next days""" - agenda_events = [{'date':today_start.replace(days=+_), - 'date_str': now.replace(days=+_).format('ddd D MMM',locale=language), - 'type':'date'} for _ in range(max_lines)] + """Create a list of dictionaries containing dates of the next days""" + agenda_events = [{'date':today_start.replace(days=+_), + 'date_str': now.replace(days=+_).format('ddd D MMM',locale=language), + 'type':'date'} for _ in range(max_lines)] - """Copy the list from the icalendar module with some conditions""" - upcoming_events = fetch_events() - filtered_events = [events for events in upcoming_events if - events.end > now] + """Copy the list from the icalendar module with some conditions""" + upcoming_events = fetch_events() + filtered_events = [events for events in upcoming_events if + events.end > now] - """Set print_events_to True to print all events in this month""" - if print_events == True and filtered_events: - auto_line_width = max(len(_.name) for _ in filtered_events) + """Set print_events_to True to print all events in this month""" + if print_events == True and filtered_events: + auto_line_width = max(len(_.name) for _ in filtered_events) + for events in filtered_events: + print('{0} {1} | {2} | {3} | All day ='.format(events.name, + ' '* (auto_line_width - len(events.name)), events.begin.format(style), + events.end.format(style)), events.all_day) + + """Convert the event-timings from utc to the specified locale's time + and create a ready-to-display list for the agenda view""" for events in filtered_events: - print('{0} {1} | {2} | {3} | All day ='.format(events.name, - ' '* (auto_line_width - len(events.name)), events.begin.format(style), - events.end.format(style)), events.all_day) + if not events.all_day: + agenda_events.append({'date': events.begin, 'time': events.begin.format( + 'HH:mm' if hours == '24' else 'hh:mm a'), 'name':str(events.name), + 'type':'timed_event'}) + else: + if events.duration.days == 1: + agenda_events.append({'date': events.begin,'time': all_day_str, + 'name': events.name,'type':'full_day_event'}) + else: + for day in range(events.duration.days): + agenda_events.append({'date': events.begin.replace(days=+day), + 'time': all_day_str,'name':events.name, 'type':'full_day_event'}) - """Convert the event-timings from utc to the specified locale's time - and create a ready-to-display list for the agenda view""" - for events in filtered_events: - if not events.all_day: - agenda_events.append({'date': events.begin, 'time': events.begin.format( - 'HH:mm' if hours == '24' else 'hh:mm a'), 'name':str(events.name), - 'type':'timed_event'}) + """Sort events and dates in chronological order""" + agenda_events = sorted(agenda_events, key = lambda event: event['date']) + + """Crop the agenda_events in case it's too long""" + del agenda_events[max_lines:] + + """Display all events, dates and times on the display""" + if show_events == True: + previous_date = None + for events in range(len(agenda_events)): + if agenda_events[events]['type'] == 'date': + if previous_date == None or previous_date != agenda_events[events][ + 'date']: + write_text(date_col_width, line_height, + agenda_events[events]['date_str'], line_pos[events], font = font) + + previous_date = agenda_events[events]['date'] + draw.line((date_col_start, line_pos[events][1], + line_width,line_pos[events][1]), fill = 'red' if three_colour_support == True' else 'black') + + elif agenda_events[events]['type'] == 'timed_event': + write_text(time_col_width, line_height, agenda_events[events]['time'], + (time_col_start, line_pos[events][1]), font = font) + + write_text(event_col_width, line_height, ('• '+agenda_events[events][ + 'name']), (event_col_start, line_pos[events][1]), + alignment = 'left', font = font) + + else: + write_text(time_col_width, line_height, agenda_events[events]['time'], + (time_col_start, line_pos[events][1]), font = font) + + write_text(event_col_width, line_height, ('• '+agenda_events[events]['name']), + (event_col_start, line_pos[events][1]), alignment = 'left', font = font) + + """Crop the image to show only the middle section""" + if bottom_section: + agenda_image = crop_image(image, 'middle_section') else: - if events.duration.days == 1: - agenda_events.append({'date': events.begin,'time': all_day_str, - 'name': events.name,'type':'full_day_event'}) - else: - for day in range(events.duration.days): - agenda_events.append({'date': events.begin.replace(days=+day), - 'time': all_day_str,'name':events.name, 'type':'full_day_event'}) + agenda_image = image.crop((0,middle_section_offset,display_width, display_height)) - """Sort events and dates in chronological order""" - agenda_events = sorted(agenda_events, key = lambda event: event['date']) + agenda_image.save(image_path+'inkycal_agenda.png') + print('Done') - """Crop the agenda_events in case it's too long""" - del agenda_events[max_lines:] + except Exception as e: + """If something went wrong, print a Error message on the Terminal""" + print('Failed!') + print('Error in Agenda module!') + print('Reason: ',e) + pass - """Display all events, dates and times on the display""" - if show_events == True: - previous_date = None - for events in range(len(agenda_events)): - if agenda_events[events]['type'] == 'date': - if previous_date == None or previous_date != agenda_events[events][ - 'date']: - write_text(date_col_width, line_height, - agenda_events[events]['date_str'], line_pos[events], font = font) +def main(): + generate_image() - previous_date = agenda_events[events]['date'] - draw.line((date_col_start, line_pos[events][1], - line_width,line_pos[events][1]), fill = 'red' if display_type == 'colour' else 'black') - - elif agenda_events[events]['type'] == 'timed_event': - write_text(time_col_width, line_height, agenda_events[events]['time'], - (time_col_start, line_pos[events][1]), font = font) - - write_text(event_col_width, line_height, ('• '+agenda_events[events][ - 'name']), (event_col_start, line_pos[events][1]), - alignment = 'left', font = font) - - else: - write_text(time_col_width, line_height, agenda_events[events]['time'], - (time_col_start, line_pos[events][1]), font = font) - - write_text(event_col_width, line_height, ('• '+agenda_events[events]['name']), - (event_col_start, line_pos[events][1]), alignment = 'left', font = font) - - """Crop the image to show only the middle section""" - if bottom_section: - agenda_image = crop_image(image, 'middle_section') - else: - agenda_image = image.crop((0,middle_section_offset,display_width, display_height)) - - agenda_image.save(image_path+'agenda.png') - print('Done') - - except Exception as e: - """If something went wrong, print a Error message on the Terminal""" - print('Failed!') - print('Error in Agenda module!') - print('Reason: ',e) - pass - -if __name__ == '__main__': - main() +main() diff --git a/modules/inkycal_calendar.py b/modules/inkycal_calendar.py index 04d92c4..8f79da3 100644 --- a/modules/inkycal_calendar.py +++ b/modules/inkycal_calendar.py @@ -7,9 +7,6 @@ Copyright by aceisace from __future__ import print_function import calendar from configuration import * -from settings import * -import arrow -from PIL import Image, ImageDraw print_events = False show_events = True @@ -68,147 +65,149 @@ max_event_lines = (events_height - border_top) // (font.getsize('hg')[1] event_lines = [(border_left,(bottom_section_offset - events_height)+ int(events_height/max_event_lines*_)) for _ in range(max_event_lines)] +def generate_image(): + if middle_section == "inkycal_calendar" and internet_available() == True: + try: + clear_image('middle_section') + print('Calendar module: Generating image...', end = '') + now = arrow.now(tz = get_tz()) + + """Set up the Calendar template based on personal preferences""" + if week_starts_on == "Monday": + calendar.setfirstweekday(calendar.MONDAY) + weekstart = now.replace(days = - now.weekday()) + else: + calendar.setfirstweekday(calendar.SUNDAY) + weekstart = now.replace(days = - now.isoweekday()) + + """Write the name of the current month at the correct position""" + write_text(main_area_width, month_name_height, + str(now.format('MMMM',locale=language)), (border_left, + middle_section_offset), autofit = True) + + """Set up weeknames in local language and add to main section""" + weekday_names = [weekstart.replace(days=+_).format('ddd',locale=language) + for _ in range(7)] + + for _ in range(len(weekday_pos)): + write_text(icon_width, weekdays_height, weekday_names[_], + weekday_pos[_], autofit = True) + + """Create a calendar template and flatten (remove nestings)""" + flatten = lambda z: [x for y in z for x in y] + calendar_flat = flatten(calendar.monthcalendar(now.year, now.month)) + + """Add the numbers on the correct positions""" + for i in range(len(calendar_flat)): + if calendar_flat[i] != 0: + write_text(icon_width, icon_height, str(calendar_flat[i]), grid[i]) + + """Draw a red/black circle with the current day of month in white""" + icon = Image.new('RGBA', (icon_width, icon_height)) + current_day_pos = grid[calendar_flat.index(now.day)] + x_circle,y_circle = int(icon_width/2), int(icon_height/2) + radius = int(icon_width * 0.25) + text_width, text_height = default.getsize(str(now.day)) + x_text = int((icon_width / 2) - (text_width / 2)) + y_text = int((icon_height / 2) - (text_height / 1.7)) + ImageDraw.Draw(icon).ellipse((x_circle-radius, y_circle-radius, + x_circle+radius, y_circle+radius), fill= 'red' if + three_colour_support == True else 'black', outline=None) + ImageDraw.Draw(icon).text((x_text, y_text), str(now.day), fill='white', + font=bold) + image.paste(icon, current_day_pos, icon) + + """Create some reference points for the current month""" + days_current_month = calendar.monthrange(now.year, now.month)[1] + month_start = now.floor('month') + month_end = now.ceil('month') + + if show_events == True: + """Filter events which begin before the end of this month""" + upcoming_events = fetch_events() + + calendar_events = [events for events in upcoming_events if + month_start <= events.end <= month_end ] + + """Find days with events in the current month""" + days_with_events = [] + for events in calendar_events: + if events.duration.days <= 1: + days_with_events.append(int(events.begin.format('D'))) + else: + for day in range(events.duration.days): + days_with_events.append( + int(events.begin.replace(days=+i).format('D'))) + days_with_events = set(days_with_events) + + if event_icon == 'dot': + for days in days_with_events: + write_text(icon_width, int(icon_height * 0.2), '•', + (grid[calendar_flat.index(days)][0], + int(grid[calendar_flat.index(days)][1] + icon_height*0.8))) + + if event_icon == 'square': + square_size = int(icon_width * 0.6) + center_x = int((icon_width - square_size) / 2) + center_y = int((icon_height - square_size) / 2) + for days in days_with_events: + draw_square((int(grid[calendar_flat.index(days)][0]+center_x), + int(grid[calendar_flat.index(days)][1] + center_y )), + 8, square_size , square_size) + + + """Add a small section showing events of today and tomorrow""" + event_list = ['{0} {1} {2} : {3}'.format(today_in_your_language, + at_in_your_language, event.begin.format('HH:mm' if hours == 24 else + 'hh:mm'), event.name) for event in calendar_events if event.begin.day + == now.day and now < event.end] + + event_list += ['{0} {1} {2} : {3}'.format(tomorrow_in_your_language, + at_in_your_language, event.begin.format('HH:mm' if hours == 24 else + 'hh:mm'), event.name) for event in calendar_events if event.begin.day + == now.replace(days=1).day] + + after_two_days = now.replace(days=2).floor('day') + + event_list += ['{0} {1} {2} : {3}'.format(event.begin.format('D MMM'), + at_in_your_language, event.begin.format('HH:mm' if hours == 24 else + 'hh:mm'), event.name) for event in upcoming_events if event.end > + after_two_days] + + del event_list[max_event_lines:] + + if event_list: + for lines in event_list: + write_text(main_area_width, int(events_height/max_event_lines), lines, + event_lines[event_list.index(lines)], alignment='left', + fill_height = 0.7) + else: + write_text(main_area_width, int(events_height/max_event_lines), + 'No upcoming events.', event_lines[0], alignment='left', + fill_height = 0.7) + + """Set print_events_to True to print all events in this month""" + style = 'DD MMM YY HH:mm' + if print_events == True and calendar_events: + line_width = max(len(_.name) for _ in calendar_events) + for events in calendar_events: + print('{0} {1} | {2} | {3} | All day ='.format(events.name, + ' ' * (line_width - len(events.name)), events.begin.format(style), + events.end.format(style)), events.all_day) + + calendar_image = crop_image(image, 'middle_section') + calendar_image.save(image_path+'inkycal_calendar.png') + + print('Done') + + except Exception as e: + """If something went wrong, print a Error message on the Terminal""" + print('Failed!') + print('Error in Calendar module!') + print('Reason: ',e) + pass + def main(): - try: - clear_image('middle_section') - print('Calendar module: Generating image...', end = '') - now = arrow.now(tz = get_tz()) + generate_image() - """Set up the Calendar template based on personal preferences""" - if week_starts_on == "Monday": - calendar.setfirstweekday(calendar.MONDAY) - weekstart = now.replace(days = - now.weekday()) - else: - calendar.setfirstweekday(calendar.SUNDAY) - weekstart = now.replace(days = - now.isoweekday()) - - """Write the name of the current month at the correct position""" - write_text(main_area_width, month_name_height, - str(now.format('MMMM',locale=language)), (border_left, - middle_section_offset), autofit = True) - - """Set up weeknames in local language and add to main section""" - weekday_names = [weekstart.replace(days=+_).format('ddd',locale=language) - for _ in range(7)] - - for _ in range(len(weekday_pos)): - write_text(icon_width, weekdays_height, weekday_names[_], - weekday_pos[_], autofit = True) - - """Create a calendar template and flatten (remove nestings)""" - flatten = lambda z: [x for y in z for x in y] - calendar_flat = flatten(calendar.monthcalendar(now.year, now.month)) - - """Add the numbers on the correct positions""" - for i in range(len(calendar_flat)): - if calendar_flat[i] != 0: - write_text(icon_width, icon_height, str(calendar_flat[i]), grid[i]) - - """Draw a red/black circle with the current day of month in white""" - icon = Image.new('RGBA', (icon_width, icon_height)) - current_day_pos = grid[calendar_flat.index(now.day)] - x_circle,y_circle = int(icon_width/2), int(icon_height/2) - radius = int(icon_width * 0.25) - text_width, text_height = default.getsize(str(now.day)) - x_text = int((icon_width / 2) - (text_width / 2)) - y_text = int((icon_height / 2) - (text_height / 1.7)) - ImageDraw.Draw(icon).ellipse((x_circle-radius, y_circle-radius, - x_circle+radius, y_circle+radius), fill= 'red' if - display_type == 'colour' else 'black', outline=None) - ImageDraw.Draw(icon).text((x_text, y_text), str(now.day), fill='white', - font=bold) - image.paste(icon, current_day_pos, icon) - - """Create some reference points for the current month""" - days_current_month = calendar.monthrange(now.year, now.month)[1] - month_start = now.floor('month') - month_end = now.ceil('month') - - if show_events == True: - """Filter events which begin before the end of this month""" - upcoming_events = fetch_events() - - calendar_events = [events for events in upcoming_events if - month_start <= events.end <= month_end ] - - """Find days with events in the current month""" - days_with_events = [] - for events in calendar_events: - if events.duration.days <= 1: - days_with_events.append(int(events.begin.format('D'))) - else: - for day in range(events.duration.days): - days_with_events.append( - int(events.begin.replace(days=+i).format('D'))) - days_with_events = set(days_with_events) - - if event_icon == 'dot': - for days in days_with_events: - write_text(icon_width, int(icon_height * 0.2), '•', - (grid[calendar_flat.index(days)][0], - int(grid[calendar_flat.index(days)][1] + icon_height*0.8))) - - if event_icon == 'square': - square_size = int(icon_width *0.6) - center_x = int((icon_width - square_size) / 2) - center_y = int((icon_height - square_size) / 2) - for days in days_with_events: - draw_square((int(grid[calendar_flat.index(days)][0]+center_x), - int(grid[calendar_flat.index(days)][1] + center_y )), - 8, square_size , square_size) - - - """Add a small section showing events of today and tomorrow""" - event_list = ['{0} {1} {2} : {3}'.format(today_in_your_language, - at_in_your_language, event.begin.format('HH:mm' if hours == 24 else - 'hh:mm'), event.name) for event in calendar_events if event.begin.day - == now.day and now < event.end] - - event_list += ['{0} {1} {2} : {3}'.format(tomorrow_in_your_language, - at_in_your_language, event.begin.format('HH:mm' if hours == 24 else - 'hh:mm'), event.name) for event in calendar_events if event.begin.day - == now.replace(days=1).day] - - after_two_days = now.replace(days=2).floor('day') - - event_list += ['{0} {1} {2} : {3}'.format(event.begin.format('D MMM'), - at_in_your_language, event.begin.format('HH:mm' if hours == 24 else - 'hh:mm'), event.name) for event in upcoming_events if event.end > - after_two_days] - - del event_list[max_event_lines:] - - if event_list: - for lines in event_list: - write_text(main_area_width, int(events_height/max_event_lines), lines, - event_lines[event_list.index(lines)], alignment='left', - fill_height = 0.7) - else: - write_text(main_area_width, int(events_height/max_event_lines), - 'No upcoming events.', event_lines[0], alignment='left', - fill_height = 0.7) - - """Set print_events_to True to print all events in this month""" - style = 'DD MMM YY HH:mm' - if print_events == True and calendar_events: - line_width = max(len(_.name) for _ in calendar_events) - for events in calendar_events: - print('{0} {1} | {2} | {3} | All day ='.format(events.name, - ' ' * (line_width - len(events.name)), events.begin.format(style), - events.end.format(style)), events.all_day) - - calendar_image = crop_image(image, 'middle_section') - calendar_image.save(image_path+'calendar.png') - - print('Done') - - except Exception as e: - """If something went wrong, print a Error message on the Terminal""" - print('Failed!') - print('Error in Calendar module!') - print('Reason: ',e) - pass - - -if __name__ == '__main__': - main() +main() diff --git a/modules/inkycal_drivers.py b/modules/inkycal_drivers.py deleted file mode 100644 index 3bd8e6e..0000000 --- a/modules/inkycal_drivers.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- -""" -Drivers file for Inky-Calendar software. -Handles E-Paper display related tasks -""" - -from PIL import Image -import RPi.GPIO as GPIO -from settings import display_type -import numpy -import spidev -import RPi.GPIO as GPIO -from time import sleep - -RST_PIN = 17 -DC_PIN = 25 -CS_PIN = 8 -BUSY_PIN = 24 - -EPD_WIDTH = 640 -EPD_HEIGHT = 384 - -SPI = spidev.SpiDev(0, 0) - -def epd_digital_write(pin, value): - GPIO.output(pin, value) - -def epd_digital_read(pin): - return GPIO.input(BUSY_PIN) - -def epd_delay_ms(delaytime): - sleep(delaytime / 1000.0) - -def spi_transfer(data): - SPI.writebytes(data) - -def epd_init(): - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - GPIO.setup(RST_PIN, GPIO.OUT) - GPIO.setup(DC_PIN, GPIO.OUT) - GPIO.setup(CS_PIN, GPIO.OUT) - GPIO.setup(BUSY_PIN, GPIO.IN) - SPI.max_speed_hz = 4000000 - SPI.mode = 0b00 - return 0; - -# 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 = RST_PIN - self.dc_pin = DC_PIN - self.busy_pin = BUSY_PIN - self.width = EPD_WIDTH - self.height = EPD_HEIGHT - - def digital_write(self, pin, value): - epd_digital_write(pin, value) - - def digital_read(self, pin): - return epd_digital_read(pin) - - def delay_ms(self, delaytime): - epd_delay_ms(delaytime) - - def send_command(self, command): - self.digital_write(self.dc_pin, GPIO.LOW) - spi_transfer([command]) - - def send_data(self, data): - self.digital_write(self.dc_pin, GPIO.HIGH) - spi_transfer([data]) - - def init(self): - if (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 - self.delay_ms(100) - - 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 calibrate_display(self, no_of_cycles): - """Function for Calibration""" - - if display_type == 'colour': - packets = int(self.width / 2 * self.height) - if display_type == 'black_and_white': - packets = int(self.width / 4 * self.height) - - white, red, black = 0x33, 0x04, 0x00 - - self.init() - print('----------Started calibration of E-Paper display----------') - for _ in range(no_of_cycles): - self.send_command(DATA_START_TRANSMISSION_1) - print('Calibrating black...') - [self.send_data(black) for i in range(packets)] - self.send_command(DISPLAY_REFRESH) - self.wait_until_idle() - - if display_type == 'colour': - print('Calibrating red...') - self.send_command(DATA_START_TRANSMISSION_1) - [self.send_data(red) for i in range(packets)] - self.send_command(DISPLAY_REFRESH) - self.wait_until_idle() - - print('Calibrating white...') - self.send_command(DATA_START_TRANSMISSION_1) - [self.send_data(white) for i in range(packets)] - self.send_command(DISPLAY_REFRESH) - self.wait_until_idle() - - print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) - - print('-----------Calibration complete----------') - self.sleep() - - def reduce_colours(self, image): - buffer = numpy.array(image) - r,g,b = buffer[:,:,0], buffer[:,:,1], buffer[:,:,2] - - if display_type == "colour": - buffer[numpy.logical_and(r <= 180, r == g)] = [0,0,0] #black - buffer[numpy.logical_and(r >= 150, g >= 150)] = [255,255,255] #white - buffer[numpy.logical_and(r >= 150, g <= 90)] = [255,0,0] #red - - image = Image.fromarray(buffer) - return image - - def clear(self, colour='white'): - if display_type == 'colour': - packets = int(self.width / 2 * self.height) - if display_type == 'black_and_white': - packets = int(self.width / 4 * self.height) - - if colour == 'white': data = 0x33 - if colour == 'red': data = 0x04 - if colour == 'black': data = 0x00 - - self.init() - self.send_command(DATA_START_TRANSMISSION_1) - [self.send_data(data) for _ in range(packets)] - self.send_command(DISPLAY_REFRESH) - print('waiting until E-Paper is not busy') - self.delay_ms(100) - self.wait_until_idle() - print('E-Paper free') - self.sleep() - - def get_frame_buffer(self, image): - imwidth, imheight = image.size - if imwidth == self.height and imheight == self.width: - image = image.rotate(270, expand = True) - print('Rotated image by 270 degrees...', end= '') - elif imwidth != self.width or imheight != self.height: - raise ValueError('Image must be same dimensions as display \ - ({0}x{1}).' .format(self.width, self.height)) - else: - print('Image size OK') - imwidth, imheight = image.size - - if display_type == 'colour': - buf = [0x00] * int(self.width * self.height / 4) - image_grayscale = image.convert('L') - 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] == 0: # black - buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2)) - elif pixels[x, y] == 76: # 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) - - if display_type == 'black_and_white': - buf = [0x00] * int(self.width * self.height / 8) - image_monocolor = image.convert('1', dither = True) - - pixels = image_monocolor.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] != 0: - buf[int((x + y * self.width) / 8)] |= 0x80 >> (x % 8) - - return buf - - def display_frame(self, frame_buffer): - self.send_command(DATA_START_TRANSMISSION_1) - if display_type == 'colour': - 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 #white - elif ((temp1 & 0xC0) == 0x00): - temp2 = 0x00 #black - else: - temp2 = 0x04 #red - temp2 = (temp2 << 4) & 0xFF - temp1 = (temp1 << 2) & 0xFF - j += 1 - if((temp1 & 0xC0) == 0xC0): - temp2 |= 0x03 #white - elif ((temp1 & 0xC0) == 0x00): - temp2 |= 0x00 #black - else: - temp2 |= 0x04 #red - temp1 = (temp1 << 2) & 0xFF - self.send_data(temp2) - j += 1 - - if display_type == 'black_and_white': - for i in range(0, 30720): - temp1 = frame_buffer[i] - j = 0 - while (j < 8): - if(temp1 & 0x80): - temp2 = 0x03 #white - else: - temp2 = 0x00 #black - temp2 = (temp2 << 4) & 0xFF - temp1 = (temp1 << 1) & 0xFF - j += 1 - if(temp1 & 0x80): - temp2 |= 0x03 #white - else: - temp2 |= 0x00 #black - temp1 = (temp1 << 1) & 0xFF - self.send_data(temp2) - j += 1 - - self.send_command(DISPLAY_REFRESH) - self.delay_ms(100) - self.wait_until_idle() - - def show_image(self, image, reduce_colours = True): - print('Initialising E-Paper Display...', end='') - self.init() - sleep(5) - print('Done') - - if reduce_colours == True: - print('Optimising Image for E-Paper displays...', end = '') - image = self.reduce_colours(image) - print('Done') - else: - print('No colour optimisation done on image') - - print('Creating image buffer and sending it to E-Paper display...', end='') - data = self.get_frame_buffer(image) - print('Done') - print('Refreshing display...', end = '') - self.display_frame(data) - print('Done') - print('Sending E-Paper to deep sleep mode...',end='') - self.sleep() - print('Done') - - def sleep(self): - self.send_command(POWER_OFF) - self.wait_until_idle() - self.send_command(DEEP_SLEEP) - self.send_data(0xa5) diff --git a/modules/inkycal_icalendar.py b/modules/inkycal_icalendar.py index a0bfa96..c61b248 100644 --- a/modules/inkycal_icalendar.py +++ b/modules/inkycal_icalendar.py @@ -44,8 +44,8 @@ def fetch_events(): if events.all_day and events.duration.days > 1: events.end = events.end.replace(days=-2) else: - events.begin = events.begin.to(timezone) events.end = events.end.to(timezone) + events.begin = events.begin.to(timezone) try: rule = re.search('RRULE:(.+?)\n', event_str).group(0)[:-2] if re.search('UNTIL=(.+?);', rule) and not re.search('UNTIL=(.+?)Z;', rule): @@ -76,8 +76,8 @@ def fetch_events(): events.end = events.end.replace(days=-2) if not events.all_day: - events.begin = events.begin.to(timezone) events.end = events.end.to(timezone) + events.begin = events.begin.to(timezone) """ The list upcoming_events should not be modified. If you need the data from this one, copy the list or the contents to another one.""" diff --git a/modules/inkycal_rss.py b/modules/inkycal_rss.py index 5c2e114..c3ae165 100644 --- a/modules/inkycal_rss.py +++ b/modules/inkycal_rss.py @@ -7,7 +7,6 @@ Copyright by aceisace from __future__ import print_function import feedparser from random import shuffle -from settings import * from configuration import * fontsize = 14 @@ -33,8 +32,8 @@ y_padding = int( (bottom_section_height % line_height) / 2 ) line_positions = [(border_left, bottom_section_offset + border_top + y_padding + _*line_height ) for _ in range(max_lines)] -def main(): - if bottom_section == "RSS" and rss_feeds != [] and internet_available() == True: +def generate_image(): + if bottom_section == "inkycal_rss" and rss_feeds != [] and internet_available() == True: try: clear_image('bottom_section') print('RSS module: Connectivity check passed. Generating image...', @@ -71,7 +70,7 @@ def main(): del filtered_feeds, parsed_feeds rss_image = crop_image(image, 'bottom_section') - rss_image.save(image_path+'rss.png') + rss_image.save(image_path+'inkycal_rss.png') print('Done') except Exception as e: @@ -81,5 +80,7 @@ def main(): print('Reason: ',e) pass -if __name__ == '__main__': - main() +def main(): + generate_image() + +main() diff --git a/modules/inkycal_weather.py b/modules/inkycal_weather.py index 4fb3ff8..1510067 100644 --- a/modules/inkycal_weather.py +++ b/modules/inkycal_weather.py @@ -10,10 +10,7 @@ Copyright by aceisace """ from __future__ import print_function import pyowm -from settings import * from configuration import * -from PIL import Image, ImageDraw, ImageFont -import arrow import math, decimal dec = decimal.Decimal @@ -130,7 +127,7 @@ def to_units(kelvin): return conversion def red_temp(negative_temperature): - if display_type == 'colour' and negative_temperature[0] == '-' and units == 'metric': + if three_colour_support == True and negative_temperature[0] == '-' and units == 'metric': colour = 'red' else: colour = 'black' @@ -160,9 +157,9 @@ while font.getsize('hg')[1] <= (row_height * fill_height): fontsize += 1 font = ImageFont.truetype(NotoSans+'.ttf', fontsize) -def main(): +def generate_image(): """Connect to Openweathermap API and fetch weather data""" - if top_section == "Weather" and api_key != "" and owm.is_API_online() is True: + if top_section == "inkycal_weather" and api_key != "" and owm.is_API_online() is True: try: clear_image('top_section') print('Weather module: Connectivity check passed, Generating image...', @@ -331,10 +328,10 @@ def main(): draw.line((coloumn7, line_start_y, coloumn7, line_end_y), fill='black') draw.line((0, top_section_height-border_top, top_section_width- border_left, top_section_height-border_top), - fill='red' if display_type == 'colour' else 'black' , width=3) + fill='red' if three_colour_support == 'True' else 'black' , width=3) weather_image = crop_image(image, 'top_section') - weather_image.save(image_path+'weather.png') + weather_image.save(image_path+'inkycal_weather.png') print('Done') except Exception as e: @@ -348,8 +345,10 @@ def main(): write_text(coloumn_width*6, row_height, message, humidity_icon_now_pos, font = font) weather_image = crop_image(image, 'top_section') - weather_image.save(image_path+'weather.png') + weather_image.save(image_path+'inkycal_weather.png') pass -if __name__ == '__main__': - main() +def main(): + generate_image() + +main() diff --git a/settings/configuration.py b/settings/configuration.py index 895cc7d..7eb5a11 100644 --- a/settings/configuration.py +++ b/settings/configuration.py @@ -9,18 +9,27 @@ Copyright by aceisace """ from PIL import Image, ImageDraw, ImageFont, ImageColor import numpy +import arrow from urllib.request import urlopen -from settings import language +from settings import * from pytz import timezone import os from glob import glob +import importlib """Set the image background colour and text colour""" background_colour = 'white' text_colour = 'black' -"""Set the display height and width (in pixels)""" -display_height, display_width = 640, 384 +"""Set some display parameters""" +driver = importlib.import_module('drivers.'+model) +display_height, display_width = driver.EPD_WIDTH, driver.EPD_HEIGHT + +"""Check if the display supports 3 colours""" +if 'colour' in model: + three_colour_support = True +else: + three_colour_support = False """Create 3 sections of the display, based on percentage""" top_section_width = middle_section_width = bottom_section_width = display_width @@ -189,3 +198,50 @@ def image_cleanup(): for temp_files in glob(image_path+'*'): os.remove(temp_files) print('Done') + +def split_colours(image): + if three_colour_support == True: + """Split image into two, one for red pixels, the other for black pixels""" + buffer = numpy.array(image.convert('RGB')) + red, green = buffer[:, :, 0], buffer[:, :, 1] + buffer_red, buffer_black = numpy.array(image), numpy.array(image) + + buffer_red[numpy.logical_and(red >= 200, green <= 90)] = [0,0,0] #red->black + red1 = buffer_red[:,:,0] + buffer_red[red1 != 0] = [255,255,255] #white + red_im = Image.fromarray(buffer_red).convert('1',dither=True).rotate(270,expand=True) + + buffer_black[numpy.logical_and(red <= 180, red == green)] = [0,0,0] #black + red2 = buffer_black[:,:,0] + buffer_black[red2 != 0] = [255,255,255] # white + black_im = Image.fromarray(buffer_black).convert('1', dither=True).rotate(270,expand=True) + return black_im, red_im + +def calibrate_display(no_of_cycles): + """How many times should each colour be calibrated? Default is 3""" + epaper = driver.EPD() + epaper.init() + + white = Image.new('1', (display_width, display_height), 'white') + black = Image.new('1', (display_width, display_height), 'black') + + print('----------Started calibration of E-Paper display----------') + if 'colour' in model: + for _ in range(no_of_cycles): + print('Calibrating black...') + epaper.display(epaper.getbuffer(black), epaper.getbuffer(white)) + print('Calibrating red/yellow...') + epaper.display(epaper.getbuffer(white), epaper.getbuffer(black)) + print('Calibrating white...') + epaper.display(epaper.getbuffer(white), epaper.getbuffer(white)) + print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) + else: + for _ in range(no_of_cycles): + print('Calibrating black...') + epaper.display(epaper.getbuffer(black)) + print('Calibrating white...') + epaper.display(epaper.getbuffer(white)), + print('Cycle {0} of {1} complete'.format(_+1, no_of_cycles)) + + print('-----------Calibration complete----------') + epaper.sleep() diff --git a/settings/settings-UI.html b/settings/settings-UI.html index 0b62078..411ddcd 100644 --- a/settings/settings-UI.html +++ b/settings/settings-UI.html @@ -43,6 +43,10 @@ body{
+
+ + +
@@ -114,15 +118,39 @@ body{
- +
- - + +
- - + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
@@ -305,7 +333,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI