| @@ -180,5 +180,5 @@ EOF | |||||||
| 	fi | 	fi | ||||||
|  |  | ||||||
|     echo -e "\e[1;36m"You can test if the programm works by running:"\e[0m" |     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 | fi | ||||||
|   | |||||||
							
								
								
									
										478
									
								
								modules/drivers/epd_4_in_2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										478
									
								
								modules/drivers/epd_4_in_2.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										148
									
								
								modules/drivers/epd_4_in_2_colour.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								modules/drivers/epd_4_in_2_colour.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										200
									
								
								modules/drivers/epd_5_in_83.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								modules/drivers/epd_5_in_83.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										200
									
								
								modules/drivers/epd_5_in_83_colour.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								modules/drivers/epd_5_in_83_colour.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										202
									
								
								modules/drivers/epd_7_in_5.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								modules/drivers/epd_7_in_5.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										201
									
								
								modules/drivers/epd_7_in_5_colour.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								modules/drivers/epd_7_in_5_colour.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										170
									
								
								modules/drivers/epd_7_in_5_v2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								modules/drivers/epd_7_in_5_v2.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										173
									
								
								modules/drivers/epd_7_in_5_v2_colour.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								modules/drivers/epd_7_in_5_v2_colour.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
|  |  | ||||||
							
								
								
									
										154
									
								
								modules/drivers/epdconfig.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								modules/drivers/epdconfig.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ### | ||||||
							
								
								
									
										1
									
								
								modules/drivers/init.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/drivers/init.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | #nothing in here. What did you expect? | ||||||
| @@ -1,28 +1,22 @@ | |||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
| # -*- coding: utf-8 -*- | # -*- 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 | Copyright by aceisace | ||||||
| """ | """ | ||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| from configuration import * | from configuration import * | ||||||
| from settings import * |  | ||||||
| import arrow | import arrow | ||||||
| from time import sleep | from time import sleep | ||||||
| import gc | 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""" | """Perepare for execution of main programm""" | ||||||
| calibration_countdown = 'initial' | calibration_countdown = 'initial' | ||||||
|  | skip_calibration = False | ||||||
| image_cleanup() | image_cleanup() | ||||||
|  |  | ||||||
| """Check time and calibrate display if time """ | """Check time and calibrate display if time """ | ||||||
| @@ -36,8 +30,6 @@ while True: | |||||||
|       'D MMM YYYY'), now.format('HH:mm'))) |       'D MMM YYYY'), now.format('HH:mm'))) | ||||||
|     print('-----------Main programm started now----------') |     print('-----------Main programm started now----------') | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     """------------------Calibration check----------------""" |     """------------------Calibration check----------------""" | ||||||
|     if skip_calibration != True: |     if skip_calibration != True: | ||||||
|       print('Calibration..', end = ' ') |       print('Calibration..', end = ' ') | ||||||
| @@ -45,10 +37,10 @@ while True: | |||||||
|         if calibration_countdown == 'initial': |         if calibration_countdown == 'initial': | ||||||
|           print('required. Performing calibration now.') |           print('required. Performing calibration now.') | ||||||
|           calibration_countdown = 0 |           calibration_countdown = 0 | ||||||
|           display.calibrate_display(3) |           calibrate_display(3) | ||||||
|         else: |         else: | ||||||
|           if calibration_countdown % (60 // int(update_interval)) == 0: |           if calibration_countdown % (60 // int(update_interval)) == 0: | ||||||
|             display.calibrate_display(3) |             calibrate_display(3) | ||||||
|             calibration_countdown = 0 |             calibration_countdown = 0 | ||||||
|       else: |       else: | ||||||
|         print('not required. Continuing...') |         print('not required. Continuing...') | ||||||
| @@ -56,43 +48,50 @@ while True: | |||||||
|       print('Calibration skipped!. Please note that not calibrating e-paper', |       print('Calibration skipped!. Please note that not calibrating e-paper', | ||||||
|             'displays causes ghosting') |             'displays causes ghosting') | ||||||
|  |  | ||||||
|  |  | ||||||
|     """----------------Generating and assembling images------""" |     """----------------Generating and assembling images------""" | ||||||
|     if top_section == 'Weather': |     try: | ||||||
|       try: |       top_section_module = importlib.import_module(top_section) | ||||||
|         weather.main() |       top_section_image = Image.open(image_path + top_section+'.png') | ||||||
|         weather_image = Image.open(image_path + 'weather.png') |       image.paste(top_section_image, (0, 0)) | ||||||
|         image.paste(weather_image, (0, 0)) |     except: | ||||||
|       except: |       pass | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     if middle_section == 'Calendar': |     try: | ||||||
|       try: |       middle_section_module = importlib.import_module(middle_section) | ||||||
|         calendar.main() |       middle_section_image = Image.open(image_path + middle_section+'.png') | ||||||
|         calendar_image = Image.open(image_path + 'calendar.png') |       image.paste(middle_section_image, (0, middle_section_offset)) | ||||||
|         image.paste(calendar_image, (0, middle_section_offset)) |     except: | ||||||
|       except: |       pass | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     if middle_section == 'Agenda': |     try: | ||||||
|       try: |       bottom_section_module = importlib.import_module(bottom_section) | ||||||
|         agenda.main() |       bottom_section_image = Image.open(image_path + bottom_section+'.png') | ||||||
|         agenda_image = Image.open(image_path + 'agenda.png') |       image.paste(bottom_section_image, (0, bottom_section_offset)) | ||||||
|         image.paste(agenda_image, (0, middle_section_offset)) |     except: | ||||||
|       except: |       pass | ||||||
|         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 |  | ||||||
|  |  | ||||||
|     image.save(image_path + 'canvas.png') |     image.save(image_path + 'canvas.png') | ||||||
|  |  | ||||||
|     """---------Refreshing E-Paper with newly created image-----------""" |     """---------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-----------------""" |     """--------------Post processing after main loop-----------------""" | ||||||
|     """Collect some garbage to free up some resources""" |     """Collect some garbage to free up some resources""" | ||||||
| @@ -106,12 +105,15 @@ while True: | |||||||
|     """Calculate duration until next display refresh""" |     """Calculate duration until next display refresh""" | ||||||
|     for _ in range(1): |     for _ in range(1): | ||||||
|       update_timings = [(60 - int(update_interval)*updates) for updates in |       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] |       for _ in update_timings: | ||||||
|       refresh_countdown = minutes[0]*60 + (60 - now.second) |         if now.minute <= _: | ||||||
|  |           minutes = _ - now.minute | ||||||
|  |           break | ||||||
|  |  | ||||||
|       print('{0} Minutes left until next refresh'.format(minutes[0])) |       refresh_countdown = minutes*60 + (60 - now.second) | ||||||
|  |       print('{0} Minutes left until next refresh'.format(minutes)) | ||||||
|  |  | ||||||
|       del update_timings, minutes, image |       del update_timings, minutes, image | ||||||
|       sleep(refresh_countdown) |       sleep(refresh_countdown) | ||||||
|   | |||||||
| @@ -7,8 +7,6 @@ Copyright by aceisace | |||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| from inkycal_icalendar import fetch_events | from inkycal_icalendar import fetch_events | ||||||
| from configuration import* | from configuration import* | ||||||
| from settings import * |  | ||||||
| import arrow |  | ||||||
|  |  | ||||||
| fontsize = 14 | fontsize = 14 | ||||||
| show_events = True | show_events = True | ||||||
| @@ -45,100 +43,103 @@ else: | |||||||
| line_pos = [(border_left, int(top_section_height + border_top + line * line_height)) | line_pos = [(border_left, int(top_section_height + border_top + line * line_height)) | ||||||
|   for line in range(max_lines)] |   for line in range(max_lines)] | ||||||
|  |  | ||||||
| def main(): | def generate_image(): | ||||||
|   try: |   if middle_section == 'inkycal_agenda' and internet_available() == True: | ||||||
|     clear_image('middle_section') |     try: | ||||||
|     if not bottom_section: |       clear_image('middle_section') | ||||||
|       clear_image('bottom_section') |       if not bottom_section: | ||||||
|  |         clear_image('bottom_section') | ||||||
|  |  | ||||||
|     print('Agenda module: Generating image...', end = '') |       print('Agenda module: Generating image...', end = '') | ||||||
|     now = arrow.now(get_tz()) |       now = arrow.now(get_tz()) | ||||||
|     today_start = arrow.get(now.year, now.month, now.day) |       today_start = arrow.get(now.year, now.month, now.day) | ||||||
|  |  | ||||||
|     """Create a list of dictionaries containing dates of the next days""" |       """Create a list of dictionaries containing dates of the next days""" | ||||||
|     agenda_events = [{'date':today_start.replace(days=+_), |       agenda_events = [{'date':today_start.replace(days=+_), | ||||||
|       'date_str': now.replace(days=+_).format('ddd D MMM',locale=language), |         'date_str': now.replace(days=+_).format('ddd D MMM',locale=language), | ||||||
|       'type':'date'} for _ in range(max_lines)] |         'type':'date'} for _ in range(max_lines)] | ||||||
|  |  | ||||||
|     """Copy the list from the icalendar module with some conditions""" |       """Copy the list from the icalendar module with some conditions""" | ||||||
|     upcoming_events = fetch_events() |       upcoming_events = fetch_events() | ||||||
|     filtered_events = [events for events in upcoming_events if |       filtered_events = [events for events in upcoming_events if | ||||||
|                        events.end > now] |                          events.end > now] | ||||||
|  |  | ||||||
|     """Set print_events_to True to print all events in this month""" |       """Set print_events_to True to print all events in this month""" | ||||||
|     if print_events == True and filtered_events: |       if print_events == True and filtered_events: | ||||||
|       auto_line_width = max(len(_.name) for _ in 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: |       for events in filtered_events: | ||||||
|         print('{0} {1} | {2} | {3} | All day ='.format(events.name, |         if not events.all_day: | ||||||
|           ' '* (auto_line_width - len(events.name)), events.begin.format(style), |           agenda_events.append({'date': events.begin, 'time': events.begin.format( | ||||||
|           events.end.format(style)), events.all_day) |             '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 |       """Sort events and dates in chronological order""" | ||||||
|     and create a ready-to-display list for the agenda view""" |       agenda_events = sorted(agenda_events, key = lambda event: event['date']) | ||||||
|     for events in filtered_events: |  | ||||||
|       if not events.all_day: |       """Crop the agenda_events in case it's too long""" | ||||||
|         agenda_events.append({'date': events.begin, 'time': events.begin.format( |       del agenda_events[max_lines:] | ||||||
|           'HH:mm' if hours == '24' else 'hh:mm a'), 'name':str(events.name), |  | ||||||
|           'type':'timed_event'}) |       """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: |       else: | ||||||
|         if events.duration.days == 1: |         agenda_image = image.crop((0,middle_section_offset,display_width, display_height)) | ||||||
|           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'}) |  | ||||||
|  |  | ||||||
|     """Sort events and dates in chronological order""" |       agenda_image.save(image_path+'inkycal_agenda.png') | ||||||
|     agenda_events = sorted(agenda_events, key = lambda event: event['date']) |       print('Done') | ||||||
|  |  | ||||||
|     """Crop the agenda_events in case it's too long""" |     except Exception as e: | ||||||
|     del agenda_events[max_lines:] |       """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""" | def main(): | ||||||
|     if show_events == True: |   generate_image() | ||||||
|       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'] | main() | ||||||
|           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() |  | ||||||
|   | |||||||
| @@ -7,9 +7,6 @@ Copyright by aceisace | |||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| import calendar | import calendar | ||||||
| from configuration import * | from configuration import * | ||||||
| from settings import * |  | ||||||
| import arrow |  | ||||||
| from PIL import Image, ImageDraw |  | ||||||
|  |  | ||||||
| print_events = False | print_events = False | ||||||
| show_events = True | 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)+ | event_lines = [(border_left,(bottom_section_offset - events_height)+ | ||||||
|   int(events_height/max_event_lines*_)) for _ in range(max_event_lines)] |   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(): | def main(): | ||||||
|   try: |   generate_image() | ||||||
|     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""" | main() | ||||||
|     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() |  | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| @@ -44,8 +44,8 @@ def fetch_events(): | |||||||
|           if events.all_day and events.duration.days > 1: |           if events.all_day and events.duration.days > 1: | ||||||
|             events.end = events.end.replace(days=-2) |             events.end = events.end.replace(days=-2) | ||||||
|           else: |           else: | ||||||
|             events.begin = events.begin.to(timezone) |  | ||||||
|             events.end = events.end.to(timezone) |             events.end = events.end.to(timezone) | ||||||
|  |             events.begin = events.begin.to(timezone) | ||||||
|           try: |           try: | ||||||
|             rule = re.search('RRULE:(.+?)\n', event_str).group(0)[:-2] |             rule = re.search('RRULE:(.+?)\n', event_str).group(0)[:-2] | ||||||
|             if re.search('UNTIL=(.+?);', rule) and not re.search('UNTIL=(.+?)Z;', rule): |             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) |       events.end = events.end.replace(days=-2) | ||||||
|  |  | ||||||
|     if not events.all_day: |     if not events.all_day: | ||||||
|       events.begin = events.begin.to(timezone) |  | ||||||
|       events.end = events.end.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 |   """ 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.""" |   this one, copy the list or the contents to another one.""" | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ Copyright by aceisace | |||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| import feedparser | import feedparser | ||||||
| from random import shuffle | from random import shuffle | ||||||
| from settings import * |  | ||||||
| from configuration import * | from configuration import * | ||||||
|  |  | ||||||
| fontsize = 14 | fontsize = 14 | ||||||
| @@ -33,8 +32,8 @@ y_padding = int( (bottom_section_height % line_height) / 2 ) | |||||||
| line_positions = [(border_left, bottom_section_offset + | line_positions = [(border_left, bottom_section_offset + | ||||||
|   border_top + y_padding + _*line_height ) for _ in range(max_lines)] |   border_top + y_padding + _*line_height ) for _ in range(max_lines)] | ||||||
|  |  | ||||||
| def main(): | def generate_image(): | ||||||
|   if bottom_section == "RSS" and rss_feeds != [] and internet_available() == True: |   if bottom_section == "inkycal_rss" and rss_feeds != [] and internet_available() == True: | ||||||
|     try: |     try: | ||||||
|       clear_image('bottom_section') |       clear_image('bottom_section') | ||||||
|       print('RSS module: Connectivity check passed. Generating image...', |       print('RSS module: Connectivity check passed. Generating image...', | ||||||
| @@ -71,7 +70,7 @@ def main(): | |||||||
|       del filtered_feeds, parsed_feeds |       del filtered_feeds, parsed_feeds | ||||||
|  |  | ||||||
|       rss_image = crop_image(image, 'bottom_section') |       rss_image = crop_image(image, 'bottom_section') | ||||||
|       rss_image.save(image_path+'rss.png') |       rss_image.save(image_path+'inkycal_rss.png') | ||||||
|       print('Done') |       print('Done') | ||||||
|  |  | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
| @@ -81,5 +80,7 @@ def main(): | |||||||
|       print('Reason: ',e) |       print('Reason: ',e) | ||||||
|       pass |       pass | ||||||
|  |  | ||||||
| if __name__ == '__main__': | def main(): | ||||||
|   main() |   generate_image() | ||||||
|  |  | ||||||
|  | main() | ||||||
|   | |||||||
| @@ -10,10 +10,7 @@ Copyright by aceisace | |||||||
| """ | """ | ||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| import pyowm | import pyowm | ||||||
| from settings import * |  | ||||||
| from configuration import * | from configuration import * | ||||||
| from PIL import Image, ImageDraw, ImageFont |  | ||||||
| import arrow |  | ||||||
| import math, decimal | import math, decimal | ||||||
| dec = decimal.Decimal | dec = decimal.Decimal | ||||||
|  |  | ||||||
| @@ -130,7 +127,7 @@ def to_units(kelvin): | |||||||
|   return conversion |   return conversion | ||||||
|  |  | ||||||
| def red_temp(negative_temperature): | 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' |     colour = 'red' | ||||||
|   else: |   else: | ||||||
|     colour = 'black' |     colour = 'black' | ||||||
| @@ -160,9 +157,9 @@ while font.getsize('hg')[1] <= (row_height * fill_height): | |||||||
|   fontsize += 1 |   fontsize += 1 | ||||||
|   font = ImageFont.truetype(NotoSans+'.ttf', fontsize) |   font = ImageFont.truetype(NotoSans+'.ttf', fontsize) | ||||||
|  |  | ||||||
| def main(): | def generate_image(): | ||||||
|   """Connect to Openweathermap API and fetch weather data""" |   """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: |     try: | ||||||
|       clear_image('top_section') |       clear_image('top_section') | ||||||
|       print('Weather module: Connectivity check passed, Generating image...', |       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((coloumn7, line_start_y, coloumn7, line_end_y), fill='black') | ||||||
|       draw.line((0, top_section_height-border_top, top_section_width- |       draw.line((0, top_section_height-border_top, top_section_width- | ||||||
|         border_left, top_section_height-border_top), |         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 = crop_image(image, 'top_section')     | ||||||
|       weather_image.save(image_path+'weather.png') |       weather_image.save(image_path+'inkycal_weather.png') | ||||||
|       print('Done') |       print('Done') | ||||||
|  |  | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
| @@ -348,8 +345,10 @@ def main(): | |||||||
|       write_text(coloumn_width*6, row_height, message, humidity_icon_now_pos, |       write_text(coloumn_width*6, row_height, message, humidity_icon_now_pos, | ||||||
|         font = font) |         font = font) | ||||||
|       weather_image = crop_image(image, 'top_section') |       weather_image = crop_image(image, 'top_section') | ||||||
|       weather_image.save(image_path+'weather.png') |       weather_image.save(image_path+'inkycal_weather.png') | ||||||
|       pass |       pass | ||||||
|  |  | ||||||
| if __name__ == '__main__': | def main(): | ||||||
|   main() |   generate_image() | ||||||
|  |  | ||||||
|  | main() | ||||||
|   | |||||||
| @@ -9,18 +9,27 @@ Copyright by aceisace | |||||||
| """ | """ | ||||||
| from PIL import Image, ImageDraw, ImageFont, ImageColor | from PIL import Image, ImageDraw, ImageFont, ImageColor | ||||||
| import numpy | import numpy | ||||||
|  | import arrow | ||||||
| from urllib.request import urlopen | from urllib.request import urlopen | ||||||
| from settings import language | from settings import * | ||||||
| from pytz import timezone | from pytz import timezone | ||||||
| import os | import os | ||||||
| from glob import glob | from glob import glob | ||||||
|  | import importlib | ||||||
|  |  | ||||||
| """Set the image background colour and text colour""" | """Set the image background colour and text colour""" | ||||||
| background_colour = 'white' | background_colour = 'white' | ||||||
| text_colour = 'black' | text_colour = 'black' | ||||||
|  |  | ||||||
| """Set the display height and width (in pixels)""" | """Set some display parameters""" | ||||||
| display_height, display_width = 640, 384 | 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""" | """Create 3 sections of the display, based on percentage""" | ||||||
| top_section_width = middle_section_width = bottom_section_width = display_width | 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+'*'): |   for temp_files in glob(image_path+'*'): | ||||||
|       os.remove(temp_files) |       os.remove(temp_files) | ||||||
|   print('Done') |   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() | ||||||
|   | |||||||
| @@ -43,6 +43,10 @@ body{ | |||||||
|       <div class="field"> |       <div class="field"> | ||||||
|         <label>How often should the display be refreshed?</label> |         <label>How often should the display be refreshed?</label> | ||||||
|         <div class="ts checkboxes"> |         <div class="ts checkboxes"> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="update_10_mins" type="radio" name="aa"> | ||||||
|  |             <label for="update_10_mins">every 10 minutes. Not recommended for 3-colour E-Papers.</label> | ||||||
|  |           </div> | ||||||
|           <div class="ts radio checkbox"> |           <div class="ts radio checkbox"> | ||||||
|             <input id="update_15_mins" type="radio" name="aa"> |             <input id="update_15_mins" type="radio" name="aa"> | ||||||
|             <label for="update_15_mins">every 15 minutes</label> |             <label for="update_15_mins">every 15 minutes</label> | ||||||
| @@ -114,15 +118,39 @@ body{ | |||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div class="field"> |       <div class="field"> | ||||||
|         <label>Which Colours does your E-Paper Display support?</label> |         <label>Which E-Paper model are you using?</label> | ||||||
|         <div class="ts checkboxes"> |         <div class="ts checkboxes"> | ||||||
|           <div class="ts radio checkbox"> |           <div class="ts radio checkbox"> | ||||||
|             <input id="colour" type="radio" name="dp" checked> |             <input id="epd_7_in_5_v2_colour" type="radio" name="dp" checked> | ||||||
|             <label for="colour">Coloured (3 colours)</label> |             <label for="epd_7_in_5_v2_colour">7.5" v2 (800x400px) colour</label> | ||||||
|           </div> |           </div> | ||||||
|           <div class="ts radio checkbox"> |           <div class="ts radio checkbox"> | ||||||
|             <input id="black-and-white" type="radio" name="dp"> |             <input id="epd_7_in_5_v2" type="radio" name="dp"> | ||||||
|             <label for="black-and-white">Black and White</label> |             <label for="epd_7_in_5_v2">7.5" v2 (800x400px) black-white</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_7_in_5_colour" type="radio" name="dp"> | ||||||
|  |             <label for="epd_7_in_5_colour">7.5" v1 (600x384px) colour</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_7_in_5" type="radio" name="dp"> | ||||||
|  |             <label for="epd_7_in_5">7.5" v1 (600x384px) black-white</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_5_in_83_colour" type="radio" name="dp"> | ||||||
|  |             <label for="epd_5_in_83_colour">5.83" colour</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_5_in_83" type="radio" name="dp"> | ||||||
|  |             <label for="epd_5_in_83">5.83" black-white</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_4_in_2_colour" type="radio" name="dp"> | ||||||
|  |             <label for="epd_4_in_2_colour">4.2" colour</label> | ||||||
|  |           </div> | ||||||
|  |           <div class="ts radio checkbox"> | ||||||
|  |             <input id="epd_4_in_2" type="radio" name="dp"> | ||||||
|  |             <label for="epd_4_in_2">4.2" black-white</label> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -305,7 +333,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
|   <br> |   <br> | ||||||
|    |    | ||||||
|   <script> |   <script> | ||||||
|   var template = 'ical_urls = [{ical_urls}]\nrss_feeds = [{rss_urls}]\nupdate_interval = "{update_interval}"\napi_key = "{api_key}"\nlocation = "{location}"\nweek_starts_on = "{week_starts_on}"\ncalibration_hours = [{calibration_hours}]\ndisplay_type = "{display_colours}"\nlanguage = "{language}"\nunits = "{units}"\nhours = "{hours}"\ntop_section = "{top_section}"\nmiddle_section = "{middle_section}"\nbottom_section = "{bottom_section}"'; |   var template = 'ical_urls = [{ical_urls}]\nrss_feeds = [{rss_urls}]\nupdate_interval = "{update_interval}"\napi_key = "{api_key}"\nlocation = "{location}"\nweek_starts_on = "{week_starts_on}"\ncalibration_hours = [{calibration_hours}]\nmodel = "{model}"\nlanguage = "{language}"\nunits = "{units}"\nhours = "{hours}"\ntop_section = "{top_section}"\nmiddle_section = "{middle_section}"\nbottom_section = "{bottom_section}"'; | ||||||
|    |    | ||||||
|   function generate(){ |   function generate(){ | ||||||
|     var ical_urls = $("#ical_urls").val().trim(); |     var ical_urls = $("#ical_urls").val().trim(); | ||||||
| @@ -319,6 +347,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     var update_interval = "60"; |     var update_interval = "60"; | ||||||
|  |     if ($('#update_10_mins').is(':checked')){ | ||||||
|  |       update_interval = "10"; | ||||||
|  |     } | ||||||
|     if ($('#update_15_mins').is(':checked')){ |     if ($('#update_15_mins').is(':checked')){ | ||||||
|       update_interval = "15"; |       update_interval = "15"; | ||||||
|     } |     } | ||||||
| @@ -352,9 +383,27 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
|       calibration_hours = $("#calibration_hours").attr("placeholder"); |       calibration_hours = $("#calibration_hours").attr("placeholder"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var display_colours = "black_and_white"; |     var model = "epd_7_in_5_v2_colour"; | ||||||
|     if ($('#colour').is(':checked')){ |     if ($('#epd_7_in_5_v2').is(':checked')){ | ||||||
|       display_colours = "colour"; |       model = "epd_7_in_5_v2"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_7_in_5_colour').is(':checked')){ | ||||||
|  |       model = "epd_7_in_5_colour"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_7_in_5').is(':checked')){ | ||||||
|  |       model = "epd_7_in_5"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_5_in_83_colour').is(':checked')){ | ||||||
|  |       model = "epd_5_in_83_colour"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_5_in_83').is(':checked')){ | ||||||
|  |       model = "epd_5_in_83"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_4_in_2_colour').is(':checked')){ | ||||||
|  |       model = "epd_4_in_2_colour"; | ||||||
|  |     } | ||||||
|  |     if ($('#epd_4_in_2').is(':checked')){ | ||||||
|  |       model = "epd_4_in_2"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var language = "en"; |     var language = "en"; | ||||||
| @@ -417,30 +466,27 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
|       hours = "12"; |       hours = "12"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var top_section = "Weather"; |     var top_section = "inkycal_weather"; | ||||||
|     if ($('#top_blank').is(':checked')){ |     if ($('#top_blank').is(':checked')){ | ||||||
|       top_section = ""; |       top_section = ""; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     var middle_section = "Calendar"; |     var middle_section = "inkycal_calendar"; | ||||||
|     if ($('#Agenda').is(':checked')){ |     if ($('#Agenda').is(':checked')){ | ||||||
|       middle_section = "Agenda"; |       middle_section = "inkycal_agenda"; | ||||||
|     } |     } | ||||||
|     if ($('#middle_blank').is(':checked')){ |     if ($('#middle_blank').is(':checked')){ | ||||||
|       middle_section = ""; |       middle_section = ""; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var bottom_section = "RSS"; |     var bottom_section = "inkycal_rss"; | ||||||
|     if ($('#Events').is(':checked')){ |  | ||||||
|       bottom_section = "Events"; |  | ||||||
|     } |  | ||||||
|     if ($('#bottom_blank').is(':checked')){ |     if ($('#bottom_blank').is(':checked')){ | ||||||
|       bottom_section = ""; |       bottom_section = ""; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     //console.log(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, display_type, language, units, hours, top_section, middle_section, bottom_section); |     //console.log(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section); | ||||||
|     createPythonSetting(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, display_colours, language, units, hours, top_section, middle_section, bottom_section); |     createPythonSetting(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, calibration_hours, model, language, units, hours, top_section, middle_section, bottom_section); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   function rk(content,key,value){ |   function rk(content,key,value){ | ||||||
| @@ -457,7 +503,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI | |||||||
|     box = rk(box,"location",e); |     box = rk(box,"location",e); | ||||||
|     box = rk(box,"week_starts_on",f); |     box = rk(box,"week_starts_on",f); | ||||||
|     box = rk(box,"calibration_hours",g); |     box = rk(box,"calibration_hours",g); | ||||||
|     box = rk(box,"display_colours",h); |     box = rk(box,"model",h); | ||||||
|     box = rk(box,"language",i); |     box = rk(box,"language",i); | ||||||
|     box = rk(box,"units",j); |     box = rk(box,"units",j); | ||||||
|     box = rk(box,"hours",k); |     box = rk(box,"hours",k); | ||||||
|   | |||||||
| @@ -1,13 +1,12 @@ | |||||||
| ical_urls = ["https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"] | ical_urls = ["https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"] | ||||||
| rss_feeds = ["http://feeds.bbci.co.uk/news/world/rss.xml#"] # Use any RSS feed | rss_feeds = ["http://feeds.bbci.co.uk/news/world/rss.xml#"] # Use any RSS feed | ||||||
|  |  | ||||||
|  |  | ||||||
| update_interval = "60"         # "15" # "30" # "60" | update_interval = "60"         # "15" # "30" # "60" | ||||||
| api_key = ""                   # Your openweathermap API-KEY -> "api-key" | api_key = ""                   # Your openweathermap API-KEY -> "api-key" | ||||||
| location = "Stuttgart, DE"     # "City name, Country code" | location = "Stuttgart, DE"     # "City name, Country code" | ||||||
| week_starts_on = "Monday"      # "Monday" # "Sunday" | week_starts_on = "Monday"      # "Monday" # "Sunday" | ||||||
| calibration_hours = [0,12,18]  # Do not change unless required | calibration_hours = [0,12,18]  # Do not change unlesss you know what you are doing | ||||||
| display_type = "colour"        # "colour" # "black_and_white" | model = "epd_7_in_5_v2_colour" # Choose the E-Paper model (see below) | ||||||
| language = "en"                # "en" # "de" # "fr" # "jp" etc. | language = "en"                # "en" # "de" # "fr" # "jp" etc. | ||||||
| units = "metric"               # "metric" # "imperial" | units = "metric"               # "metric" # "imperial" | ||||||
| hours = "24"                   # "24" # "12" | hours = "24"                   # "24" # "12" | ||||||
| @@ -26,3 +25,12 @@ bottom_section = "RSS"         # "RSS" | |||||||
| # URLs should have this sign (") on both side -> "url1" | # URLs should have this sign (") on both side -> "url1" | ||||||
| # If more than one URL is used, separate each one with a comma -> "url1", "url2" | # If more than one URL is used, separate each one with a comma -> "url1", "url2" | ||||||
|  |  | ||||||
|  | """Supported E-Paper models""" | ||||||
|  | # epd_7_in_5_v2_colour # 7.5" high-res black-white-red/yellow | ||||||
|  | # epd_7_in_5_v2        # 7.5" high-res black-white | ||||||
|  | # epd_7_in_5_colour    # 7.5" black-white-red/yellow | ||||||
|  | # epd_7_in_5           # 7.5" black-white | ||||||
|  | # epd_5_in_83_colour   # 5.83" black-white-red/yellow | ||||||
|  | # epd_5_in_83          # 5.83" black-white | ||||||
|  | # epd_4_in_2_colour    # 4.2" black-white-red/yellow | ||||||
|  | # epd_4_in_2           # 4.2" black-white | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user