Browse Source

Add frontend code

Andrea Fazzi 1 year ago
parent
commit
39d873ad52
48 changed files with 9730 additions and 0 deletions
  1. 2 0
      frontend/.gitignore
  2. 0 0
      frontend/lib/waveshare_epd/__init__.py
  3. BIN
      frontend/lib/waveshare_epd/__pycache__/__init__.cpython-39.pyc
  4. BIN
      frontend/lib/waveshare_epd/__pycache__/epd7in5_V2.cpython-39.pyc
  5. BIN
      frontend/lib/waveshare_epd/__pycache__/epdconfig.cpython-39.pyc
  6. 349 0
      frontend/lib/waveshare_epd/epd1in02.py
  7. 260 0
      frontend/lib/waveshare_epd/epd1in54.py
  8. 316 0
      frontend/lib/waveshare_epd/epd1in54_V2.py
  9. 222 0
      frontend/lib/waveshare_epd/epd1in54b.py
  10. 177 0
      frontend/lib/waveshare_epd/epd1in54b_V2.py
  11. 156 0
      frontend/lib/waveshare_epd/epd1in54c.py
  12. 228 0
      frontend/lib/waveshare_epd/epd2in13.py
  13. 323 0
      frontend/lib/waveshare_epd/epd2in13_V2.py
  14. 397 0
      frontend/lib/waveshare_epd/epd2in13_V3.py
  15. 161 0
      frontend/lib/waveshare_epd/epd2in13b_V3.py
  16. 162 0
      frontend/lib/waveshare_epd/epd2in13bc.py
  17. 361 0
      frontend/lib/waveshare_epd/epd2in13d.py
  18. 234 0
      frontend/lib/waveshare_epd/epd2in66.py
  19. 188 0
      frontend/lib/waveshare_epd/epd2in66b.py
  20. 527 0
      frontend/lib/waveshare_epd/epd2in7.py
  21. 272 0
      frontend/lib/waveshare_epd/epd2in7b.py
  22. 186 0
      frontend/lib/waveshare_epd/epd2in7b_V2.py
  23. 204 0
      frontend/lib/waveshare_epd/epd2in9.py
  24. 303 0
      frontend/lib/waveshare_epd/epd2in9_V2.py
  25. 161 0
      frontend/lib/waveshare_epd/epd2in9b_V3.py
  26. 158 0
      frontend/lib/waveshare_epd/epd2in9bc.py
  27. 303 0
      frontend/lib/waveshare_epd/epd2in9d.py
  28. 453 0
      frontend/lib/waveshare_epd/epd3in7.py
  29. 236 0
      frontend/lib/waveshare_epd/epd4in01f.py
  30. 663 0
      frontend/lib/waveshare_epd/epd4in2.py
  31. 153 0
      frontend/lib/waveshare_epd/epd4in2b_V2.py
  32. 151 0
      frontend/lib/waveshare_epd/epd4in2bc.py
  33. 216 0
      frontend/lib/waveshare_epd/epd5in65f.py
  34. 203 0
      frontend/lib/waveshare_epd/epd5in83.py
  35. 170 0
      frontend/lib/waveshare_epd/epd5in83_V2.py
  36. 174 0
      frontend/lib/waveshare_epd/epd5in83b_V2.py
  37. 203 0
      frontend/lib/waveshare_epd/epd5in83bc.py
  38. 185 0
      frontend/lib/waveshare_epd/epd7in5.py
  39. 182 0
      frontend/lib/waveshare_epd/epd7in5_HD.py
  40. 279 0
      frontend/lib/waveshare_epd/epd7in5_V2.py
  41. 208 0
      frontend/lib/waveshare_epd/epd7in5b_HD.py
  42. 192 0
      frontend/lib/waveshare_epd/epd7in5b_V2.py
  43. 204 0
      frontend/lib/waveshare_epd/epd7in5bc.py
  44. 160 0
      frontend/lib/waveshare_epd/epdconfig.py
  45. BIN
      frontend/lib/waveshare_epd/sysfs_gpio.so
  46. BIN
      frontend/lib/waveshare_epd/sysfs_software_spi.so
  47. 41 0
      frontend/main.py
  48. 7 0
      frontend/update.bash

+ 2 - 0
frontend/.gitignore

@@ -0,0 +1,2 @@
+screen.*
+*.log

+ 0 - 0
frontend/lib/waveshare_epd/__init__.py


BIN
frontend/lib/waveshare_epd/__pycache__/__init__.cpython-39.pyc


BIN
frontend/lib/waveshare_epd/__pycache__/epd7in5_V2.cpython-39.pyc


BIN
frontend/lib/waveshare_epd/__pycache__/epdconfig.cpython-39.pyc


+ 349 - 0
frontend/lib/waveshare_epd/epd1in02.py

@@ -0,0 +1,349 @@
+# *****************************************************************************
+# * | File        :	  epd1in54.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2019-06-20
+# # | Info        :   python demo
+# -----------------------------------------------------------------------------
+# ******************************************************************************/
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documnetation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to  whom the Software is
+# furished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+import logging
+from . import epdconfig
+
+# Display resolution
+EPD_WIDTH       = 80
+EPD_HEIGHT      = 128
+
+logger = logging.getLogger(__name__)
+
+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
+    
+    #full screen update LUT
+
+    lut_w1 =[
+    0x60,  0x5A,  0x5A,  0x00,  0x00,  0x01,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    ]
+    
+    lut_b1 =[
+    0x90,  0x5A,  0x5A,  0x00,  0x00,  0x01,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    ]
+
+    # partial screen update LUT
+    lut_w = [
+    0x60,  0x01,  0x01,  0x00,  0x00,  0x01,  
+    0x80,  0x1f,  0x00,  0x00,  0x00,  0x01,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    ]
+    
+    lut_b = [
+    0x90,  0x01,  0x01,  0x00,  0x00,  0x01,  
+    0x40,  0x1f,  0x00,  0x00,  0x00,  0x01,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  
+    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)         # module reset
+        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):
+        logger.debug("e-Paper busy")
+        self.send_command(0x71)
+        busy = epdconfig.digital_read(self.busy_pin)
+        busy =not(busy & 0x01)
+        while(busy):
+            self.send_command(0x71)
+            busy = epdconfig.digital_read(self.busy_pin)
+            busy =not(busy & 0x01)
+        epdconfig.delay_ms(800)
+        logger.debug("e-Paper busy release")        
+
+    def TurnOnDisplay(self):
+        self.send_command(0x12)
+        epdconfig.delay_ms(10)
+        self.ReadBusy()
+
+    def SetFulltReg(self):
+        self.send_command(0x23)
+        for count in range(0, 42):
+            self.send_data(self.lut_w1[count]) 
+        
+        self.send_command(0x24)
+        for count in range(0, 42):
+            self.send_data(self.lut_b1[count])     
+
+    def SetPartReg(self):
+        self.send_command(0x23)
+        for count in range(0, 42):
+            self.send_data(self.lut_w[count]) 
+        
+        self.send_command(0x24)
+        for count in range(0, 42):
+            self.send_data(self.lut_b[count])     
+
+    def Init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0xD2)			
+        self.send_data(0x3F)
+
+        self.send_command(0x00)  			
+        self.send_data (0x6F)  #from outside
+
+        self.send_command(0x01)  #power setting
+        self.send_data (0x03)	    
+        self.send_data (0x00)
+        self.send_data (0x2b)		
+        self.send_data (0x2b) 
+
+        self.send_command(0x06)  #Configuring the charge pump
+        self.send_data(0x3f)
+
+        self.send_command(0x2A)  #Setting XON and the options of LUT
+        self.send_data(0x00) 
+        self.send_data(0x00) 
+
+        self.send_command(0x30)  #Set the clock frequency
+        self.send_data(0x17) #50Hz
+
+        self.send_command(0x50)  #Set VCOM and data output interval
+        self.send_data(0x57)			
+
+        self.send_command(0x60)  #Set The non-overlapping period of Gate and Source.
+        self.send_data(0x22)
+
+        self.send_command(0x61)  #resolution setting
+        self.send_data (0x50)    #source 128 	 
+        self.send_data (0x80)       
+
+        self.send_command(0x82)  #sets VCOM_DC value
+        self.send_data(0x12)  #-1v
+
+        self.send_command(0xe3)#Set POWER SAVING
+        self.send_data(0x33)
+        self.SetFulltReg()	
+        self.send_command(0x04)     		#power on
+        self.ReadBusy()
+        # EPD hardware init end
+        return 0
+    
+    def Partial_Init(self):
+        self.reset()
+        
+        self.send_command(0xD2)
+        self.send_data(0x3F)
+
+        self.send_command(0x00)
+        self.send_data (0x6F)  #from outside
+
+        self.send_command(0x01)  #power setting
+        self.send_data (0x03)
+        self.send_data (0x00)
+        self.send_data (0x2b)
+        self.send_data (0x2b)
+
+        self.send_command(0x06)  #Configuring the charge pump
+        self.send_data(0x3f)
+
+        self.send_command(0x2A)  #Setting XON and the options of LUT
+        self.send_data(0x00)
+        self.send_data(0x00)
+
+        self.send_command(0x30)  #Set the clock frequency
+        self.send_data(0x17)
+
+        self.send_command(0x50)  #Set VCOM and data output interval
+        self.send_data(0xf2)
+
+        self.send_command(0x60)  #Set The non-overlapping period of Gate and Source.
+        self.send_data(0x22)
+
+        self.send_command(0x82)  #Set VCOM_DC value
+        self.send_data(0x12)#-1v
+
+        self.send_command(0xe3)#Set POWER SAVING
+        self.send_data(0x33)
+
+        self.SetPartReg()	
+
+        self.send_command(0x04)#Set POWER SAVING	
+        self.ReadBusy()
+        # EPD hardware init end
+        return 0
+    
+    def getbuffer(self, image):
+        buf = [0xFF] * (int(self.width / 8) * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, image):
+        if (image == None):
+            return
+        # Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
+        if(self.width % 8 == 0):
+            Width = self.width / 8
+        else:
+            Width = self.width / 8 + 1
+            
+        self.send_command(0x10)
+        for j in range(0, self.height):
+            for i in range(0, int(Width)):
+                self.send_data(0xff)  
+        
+        self.send_command(0x13)
+        for j in range(0, self.height):
+            for i in range(0, int(Width)):
+                self.send_data(image[i + j * int(Width)])  
+        self.TurnOnDisplay()
+        
+    def Clear(self):
+        # Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
+        if(self.width % 8 == 0):
+            Width = self.width / 8
+        else:
+            Width = self.width / 8 + 1
+            
+        Height = self.height
+        
+        self.send_command(0x10)
+        for j in range(0, Height):
+            for i in range(0, int(Width)):
+                self.send_data(0x00)  
+        
+        self.send_command(0x13)
+        for j in range(0, Height):
+            for i in range(0, int(Width)):
+                self.send_data(0xff)  
+        self.TurnOnDisplay()
+
+    def DisplayPartial(self, old_Image, Image):
+
+        # Set partial Windows */
+        self.send_command(0x91)		#This command makes the display enter partial mode
+        self.send_command(0x90)		#resolution setting
+        self.send_data(0)           #x-start
+        self.send_data(79)       #x-end
+
+        self.send_data(0)
+        self.send_data(127)  #y-end
+        self.send_data(0x00)
+       
+        # Width = (self.width % 8 == 0)? (self.width / 8 ): (self.width / 8 + 1)
+        if(self.width % 8 == 0):
+            Width = self.width / 8
+        else:
+            Width = self.width / 8 + 1
+            
+        Height = self.height
+        # send data
+        self.send_command(0x10)
+        for j in range(0, Height):
+            for i in range(0, int(Width)):
+                self.send_data(old_Image[i + j * int(Width)])
+
+        self.send_command(0x13)
+        for j in range(0, Height):
+            for i in range(0, int(Width)):
+                self.send_data(Image[i + j * int(Width)])
+
+        # Set partial refresh
+        self.TurnOnDisplay()
+
+    def Sleep(self):
+        self.send_command(0x50)
+        self.send_data(0xf7)
+        self.send_command(0x02)
+        self.ReadBusy()
+        self.send_command(0x07)
+        self.send_data(0xA5)
+        epdconfig.delay_ms(200)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 260 - 0
frontend/lib/waveshare_epd/epd1in54.py

@@ -0,0 +1,260 @@
+# *****************************************************************************
+# * | File        :	  epd1in54.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V3.1
+# * | Date        :   2019-06-20
+# # | Info        :   python demo
+# -----------------------------------------------------------------------------
+# V3.1(2019-06-18):
+# 2.remove commands define:
+#   #define PANEL_SETTING                               0x00
+#   #define POWER_SETTING                               0x01
+#   #define POWER_OFF                                   0x02
+#   #define POWER_OFF_SEQUENCE_SETTING                  0x03
+#   #define POWER_ON                                    0x04
+#   #define POWER_ON_MEASURE                            0x05
+#   #define BOOSTER_SOFT_START                          0x06
+#   #define DEEP_SLEEP                                  0x07
+#   #define DATA_START_TRANSMISSION_1                   0x10
+#   #define DATA_STOP                                   0x11
+#   #define DISPLAY_REFRESH                             0x12
+#   #define DATA_START_TRANSMISSION_2                   0x13
+#   #define PLL_CONTROL                                 0x30
+#   #define TEMPERATURE_SENSOR_COMMAND                  0x40
+#   #define TEMPERATURE_SENSOR_CALIBRATION              0x41
+#   #define TEMPERATURE_SENSOR_WRITE                    0x42
+#   #define TEMPERATURE_SENSOR_READ                     0x43
+#   #define VCOM_AND_DATA_INTERVAL_SETTING              0x50
+#   #define LOW_POWER_DETECTION                         0x51
+#   #define TCON_SETTING                                0x60
+#   #define TCON_RESOLUTION                             0x61
+#   #define SOURCE_AND_GATE_START_SETTING               0x62
+#   #define GET_STATUS                                  0x71
+#   #define AUTO_MEASURE_VCOM                           0x80
+#   #define VCOM_VALUE                                  0x81
+#   #define VCM_DC_SETTING_REGISTER                     0x82
+#   #define PROGRAM_MODE                                0xA0
+#   #define ACTIVE_PROGRAM                              0xA1
+#   #define READ_OTP_DATA                               0xA2
+# -----------------------------------------------------------------------------
+# V3.0(2018-11-01):
+# # 1.Remove:
+#   digital_write(self, pin, value)
+#   digital_read(self, pin)
+#   delay_ms(self, delaytime)
+#   set_lut(self, lut)
+#   self.lut = self.lut_full_update
+# * 2.Change:
+#   display_frame -> TurnOnDisplay
+#   set_memory_area -> SetWindow
+#   set_memory_pointer -> SetCursor
+# * 3.How to use
+#   epd = epd1in54.EPD()
+#   epd.init(epd.lut_full_update)
+#   image = Image.new('1', (epd1in54.EPD_WIDTH, epd1in54.EPD_HEIGHT), 255)
+#   ...
+#   drawing ......
+#   ...
+#   epd.display(getbuffer(image))
+# ******************************************************************************/
+# 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       = 200
+EPD_HEIGHT      = 200
+
+logger = logging.getLogger(__name__)
+
+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
+
+    lut_full_update = [
+        0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 
+        0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 
+        0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, 
+        0x35, 0x51, 0x51, 0x19, 0x01, 0x00
+    ]
+
+    lut_partial_update  = [
+        0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+        0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 
+        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)         # module reset
+        epdconfig.delay_ms(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.debug("e-Paper busy release")
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xC4)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
+        
+        self.ReadBusy()
+
+    def SetWindow(self, x_start, y_start, x_end, y_end):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x_start >> 3) & 0xFF)
+        self.send_data((x_end >> 3) & 0xFF)
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(y_start & 0xFF)
+        self.send_data((y_start >> 8) & 0xFF)
+        self.send_data(y_end & 0xFF)
+        self.send_data((y_end >> 8) & 0xFF)
+
+    def SetCursor(self, x, y):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x >> 3) & 0xFF)
+        
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(y & 0xFF)
+        self.send_data((y >> 8) & 0xFF)
+        # self.ReadBusy()
+        
+    def init(self, lut):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
+        self.send_data((EPD_HEIGHT - 1) & 0xFF)
+        self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
+        self.send_data(0x00) # GD = 0 SM = 0 TB = 0
+        
+        self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
+        self.send_data(0xD7)
+        self.send_data(0xD6)
+        self.send_data(0x9D)
+        
+        self.send_command(0x2C) # WRITE_VCOM_REGISTER
+        self.send_data(0xA8) # VCOM 7C
+        
+        self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
+        self.send_data(0x1A) # 4 dummy lines per gate
+        
+        self.send_command(0x3B) # SET_GATE_TIME
+        self.send_data(0x08) # 2us per line
+        
+        self.send_command(0x11) # DATA_ENTRY_MODE_SETTING
+        self.send_data(0x03) # X increment Y increment
+        
+        # set the look-up table register
+        self.send_command(0x32)
+        for i in range(0, len(lut)):
+            self.send_data(lut[i])
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        buf = [0xFF] * (int(self.width / 8) * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, image):
+        if (image == None):
+            return
+            
+        self.SetWindow(0, 0, self.width, self.height)
+        for j in range(0, self.height):
+            self.SetCursor(0, j)
+            self.send_command(0x24)
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+        self.TurnOnDisplay()
+        
+    def Clear(self, color):
+        # self.SetWindow(0, 0, self.width - 1, self.height - 1)
+        # send the color data
+        self.SetWindow(0, 0, self.width, self.height)
+        # epdconfig.digital_write(self.dc_pin, 1)
+        # epdconfig.digital_write(self.cs_pin, 0)
+        for j in range(0, self.height):
+            self.SetCursor(0, j)
+            self.send_command(0x24)
+            for i in range(0, int(self.width / 8)):
+                self.send_data(color)
+        # epdconfig.digital_write(self.cs_pin, 1)
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0x10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 316 - 0
frontend/lib/waveshare_epd/epd1in54_V2.py

@@ -0,0 +1,316 @@
+# *****************************************************************************
+# * | File        :	  epd1in54_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1
+# * | 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       = 200
+EPD_HEIGHT      = 200
+
+logger = logging.getLogger(__name__)
+
+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
+        
+    # waveform full refresh
+    WF_Full_1IN54 = [
+    0x80,	0x48,	0x40,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0x40,	0x48,	0x80,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0x80,	0x48,	0x40,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0x40,	0x48,	0x80,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0xA,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x8,	0x1,	0x0,	0x8,	0x1,	0x0,	0x2,					
+    0xA,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x22,	0x22,	0x22,	0x22,	0x22,	0x22,	0x0,	0x0,	0x0,			
+    0x22,	0x17,	0x41,	0x0,	0x32,	0x20
+    ]
+
+    # waveform partial refresh(fast)
+    WF_PARTIAL_1IN54_0 = [
+    0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0xF,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x1,0x1,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+    0x02,0x17,0x41,0xB0,0x32,0x28,
+    ]
+        
+    # 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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):
+            epdconfig.delay_ms(20)
+        logger.debug("e-Paper busy release")
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xc7)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.ReadBusy()
+    
+    def TurnOnDisplayPart(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xcF)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.ReadBusy()
+
+    def lut(self, lut):
+        self.send_command(0x32) # WRITE_LUT_REGISTER
+        for i in range(0, len(lut)):
+            self.send_data(lut[i])
+            
+    def set_lut(self, lut):
+        self.lut(lut)
+        
+        self.send_command(0x3f)
+        self.send_data(lut[153])
+        
+        self.send_command(0x03)
+        self.send_data(lut[154])
+        
+        self.send_command(0x04)
+        self.send_data(lut[155])
+        self.send_data(lut[156])
+        self.send_data(lut[157])
+        
+        self.send_command(0x2c)
+        self.send_data(lut[158])
+      
+    def SetWindows(self, Xstart, Ystart, Xend, Yend):
+        self.send_command(0x44); # SET_RAM_X_ADDRESS_START_END_POSITION
+        self.send_data((Xstart>>3) & 0xFF);
+        self.send_data((Xend>>3) & 0xFF);
+        
+        self.send_command(0x45); # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(Ystart & 0xFF);
+        self.send_data((Ystart >> 8) & 0xFF);
+        self.send_data(Yend & 0xFF);
+        self.send_data((Yend >> 8) & 0xFF);
+    
+
+    def SetCursor(self, Xstart, Ystart):
+        self.send_command(0x4E); # SET_RAM_X_ADDRESS_COUNTER
+        self.send_data(Xstart & 0xFF);
+
+        self.send_command(0x4F); # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(Ystart & 0xFF);
+        self.send_data((Ystart >> 8) & 0xFF);
+
+    def init(self, isPartial):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        if(isPartial):
+            logger.debug("partial refresh")
+            self.reset()
+            self.ReadBusy()
+            
+            self.set_lut(self.WF_PARTIAL_1IN54_0)
+            
+            self.send_command(0x37)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x40)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            
+            self.send_command(0x3c)  # BorderWavefrom
+            self.send_data(0x80)
+            
+            self.send_command(0x22)
+            self.send_data(0xc0)
+            self.send_command(0x20)
+            self.ReadBusy()
+        
+        else:
+            logger.debug("full refresh")
+            # EPD hardware init start
+            self.reset()
+            
+            self.ReadBusy()
+            self.send_command(0x12) # SWRESET (software reset)
+            self.ReadBusy()
+            
+            self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
+            self.send_data(0xC7) # (EPD_HEIGHT - 1) & 0xFF
+            self.send_data(0x00) # ((EPD_HEIGHT - 1) >> 8) & 0xFF
+            self.send_data(0x01) # GD = 0 SM = 0 TB = 0
+            
+            self.send_command(0x11) # data entry mode
+            self.send_data(0x01)
+                      
+            self.SetWindows(0, self.height-1, self.width-1, 0) # Set Windows
+    
+            self.send_command(0x3C) # BorderWavefrom
+            self.send_data(0x01)
+
+            self.send_command(0x18)
+            self.send_data(0x80)
+
+            self.send_command(0x22) # #Load Temperature and waveform setting.
+            self.send_data(0XB1)
+            self.send_command(0x20)
+
+            self.SetCursor(0, self.height-1) # Set Cursor
+            
+            self.ReadBusy()
+            
+            self.set_lut(self.WF_Full_1IN54) # Set lut
+        
+    def Clear(self, color):
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(color)
+                
+        self.TurnOnDisplay()
+        
+    def getbuffer(self, image):
+        buf = [0xFF] * (int(self.width/8) * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, image):
+        if (image == None):
+            return
+            
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+        self.TurnOnDisplay()
+        
+    def displayPartBaseImage(self, image):
+        if (image == None):
+            return
+        
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])
+        
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])
+                
+        self.TurnOnDisplay()
+        
+    def displayPart(self, image):
+        if (image == None):
+            return
+        
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])
+                
+        self.TurnOnDisplayPart()
+        
+    def sleep(self):
+        self.send_command(0x10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 222 - 0
frontend/lib/waveshare_epd/epd1in54b.py

@@ -0,0 +1,222 @@
+# *****************************************************************************
+# * | File        :	  epd1in54b.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       = 200
+EPD_HEIGHT      = 200
+
+logger = logging.getLogger(__name__)
+
+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
+
+    lut_vcom0 = [0x0E, 0x14, 0x01, 0x0A, 0x06, 0x04, 0x0A, 0x0A, 0x0F, 0x03, 0x03, 0x0C, 0x06, 0x0A, 0x00]
+    lut_w = [0x0E, 0x14, 0x01, 0x0A, 0x46, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x86, 0x0A, 0x04]
+    lut_b = [0x0E, 0x14, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x4A, 0x04]
+    lut_g1 = [0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04]
+    lut_g2 = [0x8E, 0x94, 0x01, 0x8A, 0x06, 0x04, 0x8A, 0x4A, 0x0F, 0x83, 0x43, 0x0C, 0x06, 0x0A, 0x04]
+    lut_vcom1 = [0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    lut_red0 = [0x83, 0x5D, 0x01, 0x81, 0x48, 0x23, 0x77, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    lut_red1 = [0x03, 0x1D, 0x01, 0x01, 0x08, 0x23, 0x37, 0x37, 0x01, 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) # module reset
+        epdconfig.delay_ms(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):
+            epdconfig.delay_ms(100)    
+        logger.debug("e-Paper busy release")
+      
+    def set_lut_bw(self):
+        self.send_command(0x20) # vcom
+        for count in range(0, 15):
+            self.send_data(self.lut_vcom0[count])
+        self.send_command(0x21) # ww --
+        for count in range(0, 15):
+            self.send_data(self.lut_w[count])
+        self.send_command(0x22) # bw r
+        for count in range(0, 15):
+            self.send_data(self.lut_b[count])
+        self.send_command(0x23) # wb w
+        for count in range(0, 15):
+            self.send_data(self.lut_g1[count])
+        self.send_command(0x24) # bb b
+        for count in range(0, 15):
+            self.send_data(self.lut_g2[count])
+
+    def set_lut_red(self):
+        self.send_command(0x25)
+        for count in range(0, 15):
+            self.send_data(self.lut_vcom1[count])
+        self.send_command(0x26)
+        for count in range(0, 15):
+            self.send_data(self.lut_red0[count])
+        self.send_command(0x27)
+        for count in range(0, 15):
+            self.send_data(self.lut_red1[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(0x07)
+        self.send_data(0x00)
+        self.send_data(0x08)
+        self.send_data(0x00)
+        self.send_command(0x06) # BOOSTER_SOFT_START
+        self.send_data(0x07)
+        self.send_data(0x07)
+        self.send_data(0x07)
+        self.send_command(0x04) # POWER_ON
+
+        self.ReadBusy()
+
+        self.send_command(0X00) # PANEL_SETTING
+        self.send_data(0xCF)
+        self.send_command(0X50) # VCOM_AND_DATA_INTERVAL_SETTING
+        self.send_data(0x17)
+        self.send_command(0x30) # PLL_CONTROL
+        self.send_data(0x39)
+        self.send_command(0x61) # TCON_RESOLUTION set x and y
+        self.send_data(0xC8)
+        self.send_data(0x00)
+        self.send_data(0xC8)
+        self.send_command(0x82) # VCM_DC_SETTING_REGISTER
+        self.send_data(0x0E)
+        
+        self.set_lut_bw()
+        self.set_lut_red()
+        return 0
+
+    def getbuffer(self, image):
+        buf = [0xFF] * int(self.width * self.height / 8)
+        # Set buffer to value of Python Imaging Library image.
+        # Image must be in mode 1.
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        if imwidth != self.width or imheight != self.height:
+            raise ValueError('Image must be same dimensions as display \
+                ({0}x{1}).' .format(self.width, self.height))
+
+        pixels = image_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(self, blackimage, redimage):
+        # send black data
+        if (blackimage != None):
+            self.send_command(0x10) # DATA_START_TRANSMISSION_1
+            for i in range(0, int(self.width * self.height / 8)):
+                temp = 0x00
+                for bit in range(0, 4):
+                    if (blackimage[i] & (0x80 >> bit) != 0):
+                        temp |= 0xC0 >> (bit * 2)
+                self.send_data(temp)  
+                temp = 0x00
+                for bit in range(4, 8):
+                    if (blackimage[i] & (0x80 >> bit) != 0):
+                        temp |= 0xC0 >> ((bit - 4) * 2)
+                self.send_data(temp)
+                
+        # send red data        
+        if (redimage != None):
+            self.send_command(0x13) # DATA_START_TRANSMISSION_2
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(redimage[i])  
+
+        self.send_command(0x12) # DISPLAY_REFRESH
+        self.ReadBusy()
+
+    def Clear(self):
+        self.send_command(0x10) # DATA_START_TRANSMISSION_1
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+            
+        self.send_command(0x13) # DATA_START_TRANSMISSION_2
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+
+        self.send_command(0x12) # DISPLAY_REFRESH
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
+        self.send_data(0x17)
+        self.send_command(0x82) # to solve Vcom drop 
+        self.send_data(0x00)        
+        self.send_command(0x01) # power setting      
+        self.send_data(0x02) # gate switch to external
+        self.send_data(0x00)
+        self.send_data(0x00) 
+        self.send_data(0x00) 
+        self.ReadBusy()
+        
+        self.send_command(0x02) # power off
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 177 - 0
frontend/lib/waveshare_epd/epd1in54b_V2.py

@@ -0,0 +1,177 @@
+# *****************************************************************************
+# * | File        :	  epd1in54b.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       = 200
+EPD_HEIGHT      = 200
+
+logger = logging.getLogger(__name__)
+
+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) # module reset
+        epdconfig.delay_ms(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):
+            epdconfig.delay_ms(100)    
+        logger.debug("e-Paper busy release")
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.ReadBusy()   
+        self.send_command(0x12)  #SWRESET
+        self.ReadBusy()   
+
+        self.send_command(0x01) #Driver output control      
+        self.send_data(0xC7)
+        self.send_data(0x00)
+        self.send_data(0x01)
+
+        self.send_command(0x11) #data entry mode       
+        self.send_data(0x01)
+
+        self.send_command(0x44) #set Ram-X address start/end position   
+        self.send_data(0x00)
+        self.send_data(0x18)    #0x18-->(24+1)*8=200
+
+        self.send_command(0x45) #set Ram-Y address start/end position          
+        self.send_data(0xC7)    #0xC7-->(199+1)=200
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00) 
+
+        self.send_command(0x3C) #BorderWavefrom
+        self.send_data(0x05)
+
+        self.send_command(0x18) #Read built-in temperature sensor
+        self.send_data(0x80)
+
+        self.send_command(0x4E)   # set RAM x address count to 0
+        self.send_data(0x00)
+        self.send_command(0x4F)   # set RAM y address count to 0X199    
+        self.send_data(0xC7)
+        self.send_data(0x00)
+        self.ReadBusy()
+        return 0
+
+    def getbuffer(self, image):
+        buf = [0xFF] * int(self.width * self.height / 8)
+        # Set buffer to value of Python Imaging Library image.
+        # Image must be in mode 1.
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        if imwidth != self.width or imheight != self.height:
+            raise ValueError('Image must be same dimensions as display \
+                ({0}x{1}).' .format(self.width, self.height))
+
+        pixels = image_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(self, blackimage, redimage):
+        # send black data
+        if (blackimage != None):
+            self.send_command(0x24) # DATA_START_TRANSMISSION_1
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(blackimage[i])
+                
+        # send red data        
+        if (redimage != None):
+            self.send_command(0x26) # DATA_START_TRANSMISSION_2
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(~redimage[i])  
+
+        self.send_command(0x22) # DISPLAY_REFRESH
+        self.send_data(0xF7)
+        self.send_command(0x20) # DISPLAY_REFRESH
+        self.ReadBusy()
+
+    def Clear(self):
+        self.send_command(0x24) # DATA_START_TRANSMISSION_1
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+            
+        self.send_command(0x26) # DATA_START_TRANSMISSION_2
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+
+        self.send_command(0x22) # DISPLAY_REFRESH
+        self.send_data(0xF7)
+        self.send_command(0x20) # DISPLAY_REFRESH
+        self.ReadBusy()
+
+
+    def sleep(self):
+        self.send_command(0x10) #enter deep sleep
+        self.send_data(0x01) 
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 156 - 0
frontend/lib/waveshare_epd/epd1in54c.py

@@ -0,0 +1,156 @@
+# *****************************************************************************
+# * | File        :	  epd1in54c.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       = 152
+EPD_HEIGHT      = 152
+
+logger = logging.getLogger(__name__)
+
+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(10) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(1)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(10)   
+
+    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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      #  0: idle, 1: busy
+            epdconfig.delay_ms(200)                
+        logger.debug("e-Paper busy release")
+     
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        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(0x0f) # LUT from OTP,160x296
+        self.send_data(0x0d) # VCOM to 0V fast
+        
+        self.send_command(0x61) # resolution setting
+        self.send_data(0x98)
+        self.send_data(0x00)
+        self.send_data(0x98)
+        
+        self.send_command(0x50)
+        self.send_data(0x77)
+
+    def getbuffer(self, image):
+        buf = [0xFF] * (int(self.width/8) * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, blackimage, yellowimage):
+        self.send_command(0x10)
+        logger.debug("blackimage")
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(blackimage[i])
+        self.send_command(0x13)
+        logger.debug("yellowimage")
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(yellowimage[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()
+
+    #  after this, call epd.init() to awaken the module
+    def sleep(self):
+        self.send_command(0X02)  #  power off
+        self.ReadBusy() 
+        self.send_command(0X07)  #  deep sleep
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 228 - 0
frontend/lib/waveshare_epd/epd2in13.py

@@ -0,0 +1,228 @@
+# *****************************************************************************
+# * | File        :	  epd2in13.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
+import numpy as np
+
+# Display resolution
+EPD_WIDTH       = 122
+EPD_HEIGHT      = 250
+
+logger = logging.getLogger(__name__)
+
+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
+        
+    lut_full_update = [
+        0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0x00
+    ]
+
+    lut_partial_update  = [
+        0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x0F, 0x01, 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(5)
+        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):        
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)            
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xC4)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
+        
+        logger.debug("e-Paper busy")
+        self.ReadBusy()
+        logger.debug("e-Paper busy release")
+
+    def init(self, lut):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
+        self.send_data((EPD_HEIGHT - 1) & 0xFF)
+        self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
+        self.send_data(0x00) # GD = 0 SM = 0 TB = 0
+        
+        self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
+        self.send_data(0xD7)
+        self.send_data(0xD6)
+        self.send_data(0x9D)
+        
+        self.send_command(0x2C) # WRITE_VCOM_REGISTER
+        self.send_data(0xA8) # VCOM 7C
+        
+        self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
+        self.send_data(0x1A) # 4 dummy lines per gate
+        
+        self.send_command(0x3B) # SET_GATE_TIME
+        self.send_data(0x08) # 2us per line
+        
+        self.send_command(0X3C) # BORDER_WAVEFORM_CONTROL
+        self.send_data(0x03)      
+        
+        self.send_command(0X11) # DATA_ENTRY_MODE_SETTING
+        self.send_data(0x03) # X increment; Y increment
+        
+        # WRITE_LUT_REGISTER
+        self.send_command(0x32)
+        for count in range(30):
+            self.send_data(lut[count])
+
+        return 0
+        
+##
+ #  @brief: specify the memory area for data R/W
+ ##
+    def SetWindows(self, x_start, y_start, x_end, y_end):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        self.send_data((x_start >> 3) & 0xFF)
+        self.send_data((x_end >> 3) & 0xFF)
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(y_start & 0xFF)
+        self.send_data((y_start >> 8) & 0xFF)
+        self.send_data(y_end & 0xFF)
+        self.send_data((y_end >> 8) & 0xFF)
+
+##
+ #  @brief: specify the start point for data R/W
+ ##
+    def SetCursor(self, x, y):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x >> 3) & 0xFF)
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(y & 0xFF)
+        self.send_data((y >> 8) & 0xFF)
+        self.ReadBusy()
+        
+    def getbuffer(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+         
+        buf = [0xFF] * (linewidth * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        
+        if(imwidth == self.width and imheight == self.height):
+            logger.debug("Vertical")
+            for y in range(imheight):
+                for x in range(imwidth):                    
+                    if pixels[x, y] == 0:
+                        # x = imwidth - x
+                        buf[int(x / 8) + y * linewidth] &= ~(0x80 >> (x % 8))
+        elif(imwidth == self.height and imheight == self.width):
+            logger.debug("Horizontal")
+            for y in range(imheight):
+                for x in range(imwidth):
+                    newx = y
+                    newy = self.height - x - 1
+                    if pixels[x, y] == 0:
+                        # newy = imwidth - newy - 1
+                        buf[int(newx / 8) + newy*linewidth] &= ~(0x80 >> (y % 8))
+        return buf   
+
+        
+    def display(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.SetWindows(0, 0, self.width, self.height);
+        for j in range(0, self.height):
+            self.SetCursor(0, j);
+            self.send_command(0x24);
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+        self.TurnOnDisplay()
+    
+    def Clear(self, color):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.SetWindows(0, 0, self.width, self.height);
+        for j in range(0, self.height):
+            self.SetCursor(0, j);
+            self.send_command(0x24);
+            for i in range(0, linewidth):
+                self.send_data(color)   
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0x10) #enter deep sleep
+        self.send_data(0x01)
+        epdconfig.delay_ms(100)
+         
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+        
+### END OF FILE ###
+

+ 323 - 0
frontend/lib/waveshare_epd/epd2in13_V2.py

@@ -0,0 +1,323 @@
+# *****************************************************************************
+# * | File        :	  epd2in13_V2.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
+import numpy as np
+
+# Display resolution
+EPD_WIDTH       = 122
+EPD_HEIGHT      = 250
+
+logger = logging.getLogger(__name__)
+
+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
+        
+    FULL_UPDATE = 0
+    PART_UPDATE = 1
+    lut_full_update= [
+        0x80,0x60,0x40,0x00,0x00,0x00,0x00,             #LUT0: BB:     VS 0 ~7
+        0x10,0x60,0x20,0x00,0x00,0x00,0x00,             #LUT1: BW:     VS 0 ~7
+        0x80,0x60,0x40,0x00,0x00,0x00,0x00,             #LUT2: WB:     VS 0 ~7
+        0x10,0x60,0x20,0x00,0x00,0x00,0x00,             #LUT3: WW:     VS 0 ~7
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT4: VCOM:   VS 0 ~7
+
+        0x03,0x03,0x00,0x00,0x02,                       # TP0 A~D RP0
+        0x09,0x09,0x00,0x00,0x02,                       # TP1 A~D RP1
+        0x03,0x03,0x00,0x00,0x02,                       # TP2 A~D RP2
+        0x00,0x00,0x00,0x00,0x00,                       # TP3 A~D RP3
+        0x00,0x00,0x00,0x00,0x00,                       # TP4 A~D RP4
+        0x00,0x00,0x00,0x00,0x00,                       # TP5 A~D RP5
+        0x00,0x00,0x00,0x00,0x00,                       # TP6 A~D RP6
+
+        0x15,0x41,0xA8,0x32,0x30,0x0A,
+    ]
+
+    lut_partial_update = [ #20 bytes
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT0: BB:     VS 0 ~7
+        0x80,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT1: BW:     VS 0 ~7
+        0x40,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT2: WB:     VS 0 ~7
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT3: WW:     VS 0 ~7
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,             #LUT4: VCOM:   VS 0 ~7
+
+        0x0A,0x00,0x00,0x00,0x00,                       # TP0 A~D RP0
+        0x00,0x00,0x00,0x00,0x00,                       # TP1 A~D RP1
+        0x00,0x00,0x00,0x00,0x00,                       # TP2 A~D RP2
+        0x00,0x00,0x00,0x00,0x00,                       # TP3 A~D RP3
+        0x00,0x00,0x00,0x00,0x00,                       # TP4 A~D RP4
+        0x00,0x00,0x00,0x00,0x00,                       # TP5 A~D RP5
+        0x00,0x00,0x00,0x00,0x00,                       # TP6 A~D RP6
+
+        0x15,0x41,0xA8,0x32,0x30,0x0A,
+    ]
+        
+    # 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(5)
+        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):
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)    
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22)
+        self.send_data(0xC7)
+        self.send_command(0x20)        
+        self.ReadBusy()
+        
+    def TurnOnDisplayPart(self):
+        self.send_command(0x22)
+        self.send_data(0x0c)
+        self.send_command(0x20)        
+        self.ReadBusy()
+        
+    def init(self, update):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        if(update == self.FULL_UPDATE):
+            self.ReadBusy()
+            self.send_command(0x12) # soft reset
+            self.ReadBusy()
+
+            self.send_command(0x74) #set analog block control
+            self.send_data(0x54)
+            self.send_command(0x7E) #set digital block control
+            self.send_data(0x3B)
+
+            self.send_command(0x01) #Driver output control
+            self.send_data(0xF9)
+            self.send_data(0x00)
+            self.send_data(0x00)
+
+            self.send_command(0x11) #data entry mode
+            self.send_data(0x01)
+
+            self.send_command(0x44) #set Ram-X address start/end position
+            self.send_data(0x00)
+            self.send_data(0x0F)    #0x0C-->(15+1)*8=128
+
+            self.send_command(0x45) #set Ram-Y address start/end position
+            self.send_data(0xF9)   #0xF9-->(249+1)=250
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            
+            self.send_command(0x3C) #BorderWavefrom
+            self.send_data(0x03)
+
+            self.send_command(0x2C)     #VCOM Voltage
+            self.send_data(0x55)    #
+
+            self.send_command(0x03)
+            self.send_data(self.lut_full_update[70])
+
+            self.send_command(0x04) #
+            self.send_data(self.lut_full_update[71])
+            self.send_data(self.lut_full_update[72])
+            self.send_data(self.lut_full_update[73])
+
+            self.send_command(0x3A)     #Dummy Line
+            self.send_data(self.lut_full_update[74])
+            self.send_command(0x3B)     #Gate time
+            self.send_data(self.lut_full_update[75])
+
+            self.send_command(0x32)
+            for count in range(70):
+                self.send_data(self.lut_full_update[count])
+
+            self.send_command(0x4E)   # set RAM x address count to 0
+            self.send_data(0x00)
+            self.send_command(0x4F)   # set RAM y address count to 0X127
+            self.send_data(0xF9)
+            self.send_data(0x00)
+            self.ReadBusy()
+        else:
+            self.send_command(0x2C)     #VCOM Voltage
+            self.send_data(0x26)
+
+            self.ReadBusy()
+
+            self.send_command(0x32)
+            for count in range(70):
+                self.send_data(self.lut_partial_update[count])
+
+            self.send_command(0x37)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x40)
+            self.send_data(0x00)
+            self.send_data(0x00)
+
+            self.send_command(0x22)
+            self.send_data(0xC0)
+            self.send_command(0x20)
+            self.ReadBusy()
+
+            self.send_command(0x3C) #BorderWavefrom
+            self.send_data(0x01)
+        return 0
+
+    def getbuffer(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+         
+        buf = [0xFF] * (linewidth * self.height)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        
+        if(imwidth == self.width and imheight == self.height):
+            logger.debug("Vertical")
+            for y in range(imheight):
+                for x in range(imwidth):                    
+                    if pixels[x, y] == 0:
+                        x = imwidth - x
+                        buf[int(x / 8) + y * linewidth] &= ~(0x80 >> (x % 8))
+        elif(imwidth == self.height and imheight == self.width):
+            logger.debug("Horizontal")
+            for y in range(imheight):
+                for x in range(imwidth):
+                    newx = y
+                    newy = self.height - x - 1
+                    if pixels[x, y] == 0:
+                        newy = imwidth - newy - 1
+                        buf[int(newx / 8) + newy*linewidth] &= ~(0x80 >> (y % 8))
+        return buf   
+        
+        
+    def display(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+        self.TurnOnDisplay()
+        
+    def displayPartial(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+                
+                
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(~image[i + j * linewidth])  
+        self.TurnOnDisplayPart()
+
+    def displayPartBaseImage(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+                
+                
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])  
+        self.TurnOnDisplay()
+    
+    def Clear(self, color):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+        # logger.debug(linewidth)
+        
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(color)
+                
+        # self.send_command(0x26)
+        # for j in range(0, self.height):
+            # for i in range(0, linewidth):
+                # self.send_data(color)   
+                
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        # self.send_command(0x22) #POWER OFF
+        # self.send_data(0xC3)
+        # self.send_command(0x20)
+
+        self.send_command(0x10) #enter deep sleep
+        self.send_data(0x03)
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 397 - 0
frontend/lib/waveshare_epd/epd2in13_V3.py

@@ -0,0 +1,397 @@
+# *****************************************************************************
+# * | File        :	  epd2in13_V3.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.1
+# * | Date        :   2021-10-30
+# # | 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
+import numpy as np
+
+# Display resolution
+EPD_WIDTH       = 122
+EPD_HEIGHT      = 250
+
+logger = logging.getLogger(__name__)
+
+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
+        
+    lut_partial_update= [
+        0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x14,0x0,0x0,0x0,0x0,0x0,0x0,  
+        0x1,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x1,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+        0x22,0x17,0x41,0x00,0x32,0x36,
+    ]
+
+    lut_full_update = [ 
+        0x80,0x4A,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x40,0x4A,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x80,0x4A,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x40,0x4A,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0xF,0x0,0x0,0x0,0x0,0x0,0x0,
+        0xF,0x0,0x0,0xF,0x0,0x0,0x2,
+        0xF,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x1,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+        0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+        0x22,0x17,0x41,0x0,0x32,0x36,
+    ]
+        
+    '''
+    function :Hardware reset
+    parameter:
+    '''
+    def reset(self):
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(2)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20)   
+
+    '''
+    function :send command
+    parameter:
+     command : Command register
+    '''
+    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)
+
+    '''
+    function :send data
+    parameter:
+     data : Write data
+    '''
+    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)
+    
+    '''
+    function :Wait until the busy_pin goes LOW
+    parameter:
+    '''
+    def ReadBusy(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(10)  
+        logger.debug("e-Paper busy release")
+
+    '''
+    function : Turn On Display
+    parameter:
+    '''
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # Display Update Control
+        self.send_data(0xC7)
+        self.send_command(0x20) # Activate Display Update Sequence
+        self.ReadBusy()
+    
+    '''
+    function : Turn On Display Part
+    parameter:
+    '''
+    def TurnOnDisplayPart(self):
+        self.send_command(0x22) # Display Update Control
+        self.send_data(0x0f)    # fast:0x0c, quality:0x0f, 0xcf
+        self.send_command(0x20) # Activate Display Update Sequence
+        self.ReadBusy()
+    
+    '''
+    function : Set lut
+    parameter:
+        lut : lut data
+    '''    
+    def Lut(self, lut):
+        self.send_command(0x32)
+        for i in range(0, 153):
+            self.send_data(lut[i])
+        self.ReadBusy()
+    
+    '''
+    function : Send lut data and configuration
+    parameter:
+        lut : lut data 
+    '''
+    def SetLut(self, lut):
+        self.Lut(lut)
+        self.send_command(0x3f)
+        self.send_data(lut[153])
+        self.send_command(0x03)     # gate voltage
+        self.send_data(lut[154])
+        self.send_command(0x04)     # source voltage
+        self.send_data(lut[155])    # VSH
+        self.send_data(lut[156])    # VSH2
+        self.send_data(lut[157])    # VSL
+        self.send_command(0x2c)     # VCOM
+        self.send_data(lut[158])
+    
+    '''
+    function : Setting the display window
+    parameter:
+        xstart : X-axis starting position
+        ystart : Y-axis starting position
+        xend : End position of X-axis
+        yend : End position of Y-axis
+    '''
+    def SetWindow(self, x_start, y_start, x_end, y_end):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x_start>>3) & 0xFF)
+        self.send_data((x_end>>3) & 0xFF)
+        
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(y_start & 0xFF)
+        self.send_data((y_start >> 8) & 0xFF)
+        self.send_data(y_end & 0xFF)
+        self.send_data((y_end >> 8) & 0xFF)
+
+    '''
+    function : Set Cursor
+    parameter:
+        x : X-axis starting position
+        y : Y-axis starting position
+    '''
+    def SetCursor(self, x, y):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data(x & 0xFF)
+        
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(y & 0xFF)
+        self.send_data((y >> 8) & 0xFF)
+    
+    '''
+    function : Initialize the e-Paper register
+    parameter:
+    '''
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.ReadBusy()
+        self.send_command(0x12)  #SWRESET
+        self.ReadBusy() 
+
+        self.send_command(0x01) #Driver output control      
+        self.send_data(0xf9)
+        self.send_data(0x00)
+        self.send_data(0x00)
+    
+        self.send_command(0x11) #data entry mode       
+        self.send_data(0x03)
+
+        self.SetWindow(0, 0, self.width-1, self.height-1)
+        self.SetCursor(0, 0)
+        
+        self.send_command(0x3c)
+        self.send_data(0x05)
+
+        self.send_command(0x21) #  Display update control
+        self.send_data(0x00)
+        self.send_data(0x80)
+    
+        self.send_command(0x18)
+        self.send_data(0x80)
+        
+        self.ReadBusy()
+        
+        self.SetLut(self.lut_full_update)
+        return 0
+
+    '''
+    function : Display images
+    parameter:
+        image : Image data
+    '''
+    def getbuffer(self, image):
+        img = image
+        imwidth, imheight = img.size
+        if(imwidth == self.width and imheight == self.height):
+            img = img.convert('1')
+        elif(imwidth == self.height and imheight == self.width):
+            # image has correct dimensions, but needs to be rotated
+            img = img.rotate(90, expand=True).convert('1')
+        else:
+            logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
+            # return a blank buffer
+            return [0x00] * (int(self.width/8) * self.height)
+
+        buf = bytearray(img.tobytes('raw'))
+        return buf
+        
+    '''
+    function : Sends the image buffer in RAM to e-Paper and displays
+    parameter:
+        image : Image data
+    '''
+    def display(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+        self.TurnOnDisplay()
+    
+    '''
+    function : Sends the image buffer in RAM to e-Paper and partial refresh
+    parameter:
+        image : Image data
+    '''
+    def displayPartial(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(1)
+        epdconfig.digital_write(self.reset_pin, 1)  
+        
+        self.SetLut(self.lut_partial_update)
+        self.send_command(0x37)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x40)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)  
+        self.send_data(0x00)
+
+        self.send_command(0x3C) #BorderWavefrom
+        self.send_data(0x80)
+
+        self.send_command(0x22) 
+        self.send_data(0xC0)
+        self.send_command(0x20)
+        self.ReadBusy()
+
+        self.SetWindow(0, 0, self.width - 1, self.height - 1)
+        self.SetCursor(0, 0)
+        
+        self.send_command(0x24) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+        self.TurnOnDisplayPart()
+
+    '''
+    function : Refresh a base image
+    parameter:
+        image : Image data
+    '''
+    def displayPartBaseImage(self, image):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])   
+                
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(image[i + j * linewidth])  
+        self.TurnOnDisplay()
+    
+    '''
+    function : Clear screen
+    parameter:
+    '''
+    def Clear(self, color):
+        if self.width%8 == 0:
+            linewidth = int(self.width/8)
+        else:
+            linewidth = int(self.width/8) + 1
+        # logger.debug(linewidth)
+        
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, linewidth):
+                self.send_data(color)
+                
+        self.TurnOnDisplay()
+
+    '''
+    function : Enter sleep mode
+    parameter:
+    '''
+    def sleep(self):
+        self.send_command(0x10) #enter deep sleep
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 161 - 0
frontend/lib/waveshare_epd/epd2in13b_V3.py

@@ -0,0 +1,161 @@
+# *****************************************************************************
+# * | File        :	  epd2in13bc.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       = 104
+EPD_HEIGHT      = 212
+
+logger = logging.getLogger(__name__)
+
+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):
+        logger.debug("e-Paper busy")
+        self.send_command(0x71);
+        while(epdconfig.digital_read(self.busy_pin) == 0): 
+            self.send_command(0x71);
+            epdconfig.delay_ms(100)
+        logger.debug("e-Paper busy release")
+
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+        self.send_command(0x04);  
+        self.ReadBusy();#waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x00);    #panel setting
+        self.send_data(0x0f);   #LUT from OTP,128x296
+        self.send_data(0x89);    #Temperature sensor, boost and other related timing settings
+
+        self.send_command(0x61);    #resolution setting
+        self.send_data (0x68);  
+        self.send_data (0x00);  
+        self.send_data (0xD4);
+
+        self.send_command(0X50);    #VCOM AND DATA INTERVAL SETTING
+        self.send_data(0x77);   #WBmode:VBDF 17|D7 VBDW 97 VBDB 57
+                            # WBRmode:VBDF F7 VBDW 77 VBDB 37  VBDR B7
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, 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) # REFRESH
+        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(0xFF)
+        
+        self.send_command(0x12) # REFRESH
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0X50) 
+        self.send_data(0xf7)
+        self.send_command(0X02) 
+        self.ReadBusy()
+        self.send_command(0x07) # DEEP_SLEEP
+        self.send_data(0xA5) # check code
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 162 - 0
frontend/lib/waveshare_epd/epd2in13bc.py

@@ -0,0 +1,162 @@
+# *****************************************************************************
+# * | File        :	  epd2in13bc.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       = 104
+EPD_HEIGHT      = 212
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.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)
+        
+        self.send_command(0x04) # POWER_ON
+        self.ReadBusy()
+        
+        self.send_command(0x00) # PANEL_SETTING
+        self.send_data(0x8F)
+        
+        self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
+        self.send_data(0xF0)
+        
+        self.send_command(0x61) # RESOLUTION_SETTING
+        self.send_data(self.width & 0xff)
+        self.send_data(self.height >> 8)
+        self.send_data(self.height & 0xff)
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, 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(0x92)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(imagered[i])
+        # self.send_command(0x92)
+        
+        self.send_command(0x12) # REFRESH
+        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(0x92) 
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+        self.send_command(0x92)
+        
+        self.send_command(0x12) # REFRESH
+        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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 361 - 0
frontend/lib/waveshare_epd/epd2in13d.py

@@ -0,0 +1,361 @@
+# *****************************************************************************
+# * | File        :	  epd2in13d.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       = 104
+EPD_HEIGHT      = 212
+
+logger = logging.getLogger(__name__)
+
+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
+
+    lut_vcomDC = [  
+        0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00,
+    ]
+
+    lut_ww = [  
+        0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
+        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, 0x0F, 0x0F, 0x00, 0x00, 0x03,
+        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, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_bb = [ 
+        0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    
+    lut_vcom1 = [  
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00,
+    ]
+
+    lut_ww1 = [  
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_bw1 = [  
+        0x80, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_wb1 = [
+        0x40, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_bb1 = [ 
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            self.send_command(0x71)
+            epdconfig.delay_ms(100)  
+        logger.debug("e-Paper busy release")
+        
+    def TurnOnDisplay(self):
+        self.send_command(0x12)
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+        
+    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)
+        self.send_data(0x00)
+        self.send_data(0x2b)
+        self.send_data(0x2b)
+        self.send_data(0x03)
+
+        self.send_command(0x06)	# boost 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(0xbf) # LUT from OTP,128x296
+        self.send_data(0x0d) # VCOM to 0V fast
+
+        self.send_command(0x30)	# PLL setting
+        self.send_data(0x3a) # 3a 100HZ   29 150Hz 39 200HZ	31 171HZ
+
+        self.send_command(0x61)	# resolution setting
+        self.send_data(self.width)
+        self.send_data((self.height >> 8) & 0xff)
+        self.send_data(self.height& 0xff)
+
+        self.send_command(0x82)	# vcom_DC setting
+        self.send_data(0x28)
+        return 0
+        
+    def SetFullReg(self):
+        self.send_command(0x82)
+        self.send_data(0x00)
+        self.send_command(0X50)
+        self.send_data(0x97)
+        
+        self.send_command(0x20) # vcom
+        for count in range(0, 44):
+            self.send_data(self.lut_vcomDC[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_wb[count])
+        self.send_command(0x24) # bb b
+        for count in range(0, 42):
+            self.send_data(self.lut_bb[count])
+    
+    def SetPartReg(self):
+        self.send_command(0x82)
+        self.send_data(0x03)
+        self.send_command(0X50)
+        self.send_data(0x47)
+        
+        self.send_command(0x20) # vcom
+        for count in range(0, 44):
+            self.send_data(self.lut_vcom1[count])
+        self.send_command(0x21) # ww --
+        for count in range(0, 42):
+            self.send_data(self.lut_ww1[count])
+        self.send_command(0x22) # bw r
+        for count in range(0, 42):
+            self.send_data(self.lut_bw1[count])
+        self.send_command(0x23) # wb w
+        for count in range(0, 42):
+            self.send_data(self.lut_wb1[count])
+        self.send_command(0x24) # bb b
+        for count in range(0, 42):
+            self.send_data(self.lut_bb1[count])
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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):
+        if (Image == None):
+            return
+            
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(image[i])
+        epdconfig.delay_ms(10)
+        
+        self.SetFullReg()
+        self.TurnOnDisplay()
+        
+    def DisplayPartial(self, image):   
+        if (Image == None):
+            return
+            
+        self.send_command(0x91)
+        self.send_command(0x90)
+        self.send_data(0)
+        self.send_data(self.width - 1)
+
+        self.send_data(0)
+        self.send_data(0)
+        self.send_data(int(self.height / 256))
+        self.send_data(self.height % 256 - 1)
+        self.send_data(0x28)
+            
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(image[i])
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(~image[i])
+        epdconfig.delay_ms(10)
+        
+        self.SetPartReg()
+        self.TurnOnDisplay()
+        
+    def Clear(self, color):
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+        epdconfig.delay_ms(10)
+        
+        self.SetFullReg()
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0X50)
+        self.send_data(0xf7)
+        self.send_command(0X02) # power off
+        self.send_command(0X07) # deep sleep  
+        self.send_data(0xA5)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 234 - 0
frontend/lib/waveshare_epd/epd2in66.py

@@ -0,0 +1,234 @@
+# *****************************************************************************
+# * | File        :	  epd2in66.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-07-22
+# # | 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       = 152
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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
+
+    WF_PARTIAL = [
+        0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x40,0x40,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x0A,0x00,0x00,0x00,0x00,0x00,0x02,0x01,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x22,0x22,0x22,0x22,0x22,0x22,
+        0x00,0x00,0x00,0x22,0x17,0x41,0xB0,0x32,0x36,
+    ]
+
+        
+    # 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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      #  0: idle, 1: busy
+            epdconfig.delay_ms(200) 
+        logger.debug("e-Paper busy release") 
+
+
+    def init(self, mode):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x12)
+        epdconfig.delay_ms(300)
+        self.ReadBusy()
+
+        self.send_command(0x11) # setting gaet number
+        self.send_data(0x03)
+        self.send_command(0x44) # set gate voltage
+        self.send_data(0x01)
+        self.send_data(0x13)
+        self.send_command(0x45) # set source voltage
+        self.send_data(0x0)
+        self.send_data(0x0)
+        self.send_data(0x28)
+        self.send_data(0x01)
+    
+        if(mode == 0):      #full
+            self.send_command(0x3C)
+            self.send_data(0x01)
+            
+        elif(mode == 1):        #partial
+            self.load_lut(self.WF_PARTIAL)
+            self.send_command(0x37) # set display option, these setting turn on previous function
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)  
+            self.send_data(0x40)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)  
+
+            self.send_command(0x3C)
+            self.send_data(0x80)
+
+            self.send_command(0x22)
+            self.send_data(0xcf)
+            
+            self.send_command(0x20)
+            self.ReadBusy()
+
+        else:
+            logger.debug("There is no such mode") 
+
+        return 0
+
+
+    def load_lut(self, lut):
+        self.send_command(0x32)
+        for i in range(0, 153):
+            self.send_data(lut[i])
+
+
+    def turnon_display(self):
+        self.send_command(0x20)
+        self.ReadBusy()
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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):
+        if (image == None):
+            return            
+
+        self.send_command(0x4E)
+        self.send_data(0x01)
+        self.send_command(0x4F)
+        self.send_data(0x27)
+        self.send_data(0x01)
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+
+        self.turnon_display()
+        
+
+    def Clear(self):
+        self.send_command(0x4E)
+        self.send_data(0x01)
+        self.send_command(0x4F)
+        self.send_data(0x27)
+        self.send_data(0x01)
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(0xff)   
+
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(0xff) 
+
+        self.turnon_display()
+
+
+    def sleep(self):
+        self.send_command(0X10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 188 - 0
frontend/lib/waveshare_epd/epd2in66b.py

@@ -0,0 +1,188 @@
+# *****************************************************************************
+# * | File        :	  epd2in66b.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-12-01
+# # | 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       = 152
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      #  0: idle, 1: busy
+            epdconfig.delay_ms(20) 
+        logger.debug("e-Paper busy release") 
+
+
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x12)
+        epdconfig.delay_ms(30)
+        self.ReadBusy()
+
+        self.send_command(0x11) # setting gaet number
+        self.send_data(0x03)
+        
+        self.setWindows(0, 0, self.width-1, self.height-1)
+        
+        self.send_command(0x21)
+        self.send_data(0x00)
+        self.send_data(0x80)
+        
+        self.setCursor(0, 0)
+        self.ReadBusy()
+
+        return 0
+
+    def setWindows(self, Xstart, Ystart, Xend, Yend):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        self.send_data((Xstart>>3) & 0x1F)
+        self.send_data((Xend>>3) & 0x1F)
+        
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(Ystart & 0xFF)
+        self.send_data((Ystart >> 8) & 0x01)
+        self.send_data(Yend & 0xFF)
+        self.send_data((Yend >> 8) & 0x01)
+
+    def setCursor(self, Xstart, Ystart):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        self.send_data(Xstart & 0x1F)
+
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(Ystart & 0xFF)
+        self.send_data((Ystart >> 8) & 0x01)
+        
+    def turnon_display(self):
+        self.send_command(0x20)
+        self.ReadBusy()
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, Blackimage, Redimage):
+        if (Blackimage == None or Redimage == None):
+            return            
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(Blackimage[i + j * int(self.width / 8)])   
+
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(~Redimage[i + j * int(self.width / 8)]) 
+                
+        self.turnon_display()
+        
+
+    def Clear(self):
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(0xff)   
+
+        self.send_command(0x26)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(0x00) 
+
+        self.turnon_display()
+
+
+    def sleep(self):
+        self.send_command(0X10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 527 - 0
frontend/lib/waveshare_epd/epd2in7.py

@@ -0,0 +1,527 @@
+# *****************************************************************************
+# * | File        :	  epd2in7.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       = 176
+EPD_HEIGHT      = 264
+
+GRAY1  = 0xff #white
+GRAY2  = 0xC0
+GRAY3  = 0x80 #gray
+GRAY4  = 0x00 #Blackest
+
+logger = logging.getLogger(__name__)
+
+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_vcom_dc = [0x00, 0x00,
+        0x00, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x60, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x00, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+    ]
+    lut_ww = [
+        0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_bw = [
+        0x40, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x40, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0xA0, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_bb = [
+        0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_wb = [
+        0x80, 0x08, 0x00, 0x00, 0x00, 0x02,
+        0x90, 0x28, 0x28, 0x00, 0x00, 0x01,
+        0x80, 0x14, 0x00, 0x00, 0x00, 0x01,
+        0x50, 0x12, 0x12, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    ###################full screen update LUT######################
+    #0~3 gray
+    gray_lut_vcom = [
+    0x00, 0x00,
+    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
+    gray_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
+    gray_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
+    gray_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
+    gray_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(5)
+        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):        
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      #  0: idle, 1: busy
+            epdconfig.delay_ms(200)                
+        logger.debug("e-Paper busy release")
+
+    def set_lut(self):
+        self.send_command(0x20) # vcom
+        for count in range(0, 44):
+            self.send_data(self.lut_vcom_dc[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)
+        for count in range(0, 44):        #vcom
+            self.send_data(self.gray_lut_vcom[count])
+            
+        self.send_command(0x21)							#red not use
+        for count in range(0, 42): 
+            self.send_data(self.gray_lut_ww[count])
+
+        self.send_command(0x22)							#bw r
+        for count in range(0, 42): 
+            self.send_data(self.gray_lut_bw[count])
+
+        self.send_command(0x23)							#wb w
+        for count in range(0, 42): 
+            self.send_data(self.gray_lut_wb[count])
+
+        self.send_command(0x24)							#bb b
+        for count in range(0, 42): 
+            self.send_data(self.gray_lut_bb[count])
+
+        self.send_command(0x25)							#vcom
+        for count in range(0, 42): 
+            self.send_data(self.gray_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_data(0x09) # VDHR
+        
+        self.send_command(0x06) # BOOSTER_SOFT_START
+        self.send_data(0x07)
+        self.send_data(0x07)
+        self.send_data(0x17)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x60)
+        self.send_data(0xA5)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x89)
+        self.send_data(0xA5)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x90)
+        self.send_data(0x00)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x93)
+        self.send_data(0x2A)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0xA0)
+        self.send_data(0xA5)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0xA1)
+        self.send_data(0x00)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x73)
+        self.send_data(0x41)
+        
+        self.send_command(0x16) # PARTIAL_DISPLAY_REFRESH
+        self.send_data(0x00)
+        self.send_command(0x04) # POWER_ON
+        self.ReadBusy()
+
+        self.send_command(0x00) # PANEL_SETTING
+        self.send_data(0xAF) # KW-BF   KWR-AF    BWROTP 0f
+        
+        self.send_command(0x30) # PLL_CONTROL
+        self.send_data(0x3A) # 3A 100HZ   29 150Hz 39 200HZ    31 171HZ
+    
+        self.send_command(0X50)			#VCOM AND DATA INTERVAL SETTING			
+        self.send_data(0x57)
+        
+        self.send_command(0x82) # VCM_DC_SETTING_REGISTER
+        self.send_data(0x12)
+        self.set_lut()
+        return 0
+
+    def Init_4Gray(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        self.reset()
+        
+        self.send_command(0x01)			#POWER SETTING
+        self.send_data (0x03)
+        self.send_data (0x00)    
+        self.send_data (0x2b)															 
+        self.send_data (0x2b)		
+
+
+        self.send_command(0x06)         #booster soft start
+        self.send_data (0x07)		#A
+        self.send_data (0x07)		#B
+        self.send_data (0x17)		#C 
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0x60)
+        self.send_data (0xA5)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0x89)
+        self.send_data (0xA5)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0x90)
+        self.send_data (0x00)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0x93)
+        self.send_data (0x2A)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0xa0)
+        self.send_data (0xa5)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0xa1)
+        self.send_data (0x00)
+
+        self.send_command(0xF8)         #boost??
+        self.send_data (0x73)
+        self.send_data (0x41)
+
+        self.send_command(0x16)
+        self.send_data(0x00)	
+
+        self.send_command(0x04)
+        self.ReadBusy()
+
+        self.send_command(0x00)			#panel setting
+        self.send_data(0xbf)		#KW-BF   KWR-AF	BWROTP 0f
+
+        self.send_command(0x30)			#PLL setting
+        self.send_data (0x90)      	#100hz 
+
+        self.send_command(0x61)			#resolution setting
+        self.send_data (0x00)		#176
+        self.send_data (0xb0)     	 
+        self.send_data (0x01)		#264
+        self.send_data (0x08)
+
+        self.send_command(0x82)			#vcom_DC setting
+        self.send_data (0x12)
+
+        self.send_command(0X50)			#VCOM AND DATA INTERVAL SETTING			
+        self.send_data(0x57)
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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 getbuffer_4Gray(self, image):
+        # logger.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
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.debug("Horizontal")
+            for x in range(imwidth):
+                for y in range(imheight):
+                    newx = y
+                    newy = self.height - x - 1
+                    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, 5808):                     #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 |= 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, 5808):                #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, color=0xFF):
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(color)
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(color)
+        self.send_command(0x12) 
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0X50)
+        self.send_data(0xf7)
+        self.send_command(0X02)
+        self.send_command(0X07)
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 272 - 0
frontend/lib/waveshare_epd/epd2in7b.py

@@ -0,0 +1,272 @@
+# *****************************************************************************
+# * | File        :	  epd2in7b.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       = 176
+EPD_HEIGHT      = 264
+
+logger = logging.getLogger(__name__)
+
+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
+
+    lut_vcom_dc = [
+        0x00, 0x00,
+        0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01,
+        0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10,
+        0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
+        0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
+        0x00, 0x23, 0x00, 0x00, 0x00, 0x01
+    ]
+
+    lut_ww = [
+        0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
+        0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
+        0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
+        0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
+        0x00, 0x23, 0x00, 0x00, 0x00, 0x01
+    ]
+
+    # R22H    r
+    lut_bw = [
+        0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01,
+        0x00, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
+        0x90, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0xB0, 0x04, 0x10, 0x00, 0x00, 0x05,
+        0xB0, 0x03, 0x0E, 0x00, 0x00, 0x0A,
+        0xC0, 0x23, 0x00, 0x00, 0x00, 0x01
+    ]
+
+    # R23H    w
+    lut_bb = [
+        0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
+        0x40, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
+        0x80, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
+        0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
+        0x00, 0x23, 0x00, 0x00, 0x00, 0x01
+    ]
+    # R24H    b
+    lut_wb = [
+        0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01,
+        0x20, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10,
+        0x10, 0x0A, 0x0A, 0x00, 0x00, 0x08,
+        0x00, 0x04, 0x10, 0x00, 0x00, 0x05,
+        0x00, 0x03, 0x0E, 0x00, 0x00, 0x0A,
+        0x00, 0x23, 0x00, 0x00, 0x00, 0x01
+    ]
+
+    # 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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.debug("e-Paper busy release")
+        
+    def set_lut(self):
+        self.send_command(0x20)               # vcom
+        for count in range(0, 44):
+            self.send_data(self.lut_vcom_dc[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 init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+
+        self.send_command(0x04) # POWER_ON
+        self.ReadBusy()
+
+        self.send_command(0x00) # PANEL_SETTING
+        self.send_data(0xaf) #KW-BF   KWR-AF    BWROTP 0f
+        
+        self.send_command(0x30) # PLL_CONTROL
+        self.send_data(0x3a) #3A 100HZ   29 150Hz 39 200HZ    31 171HZ
+
+        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_data(0x09) # VDHR
+
+        self.send_command(0x06) # BOOSTER_SOFT_START
+        self.send_data(0x07)
+        self.send_data(0x07)
+        self.send_data(0x17)
+
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x60)
+        self.send_data(0xA5)
+
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x89)
+        self.send_data(0xA5)
+
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x90)
+        self.send_data(0x00)
+        
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x93)
+        self.send_data(0x2A)
+
+        # Power optimization
+        self.send_command(0xF8)
+        self.send_data(0x73)
+        self.send_data(0x41)
+
+        self.send_command(0x82) # VCM_DC_SETTING_REGISTER
+        self.send_data(0x12)                   
+        self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
+        self.send_data(0x87) # define by OTP
+
+        self.set_lut()
+
+        self.send_command(0x16) # PARTIAL_DISPLAY_REFRESH
+        self.send_data(0x00)
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, 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(0x11)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(~imagered[i])
+        self.send_command(0x11)
+        
+        self.send_command(0x12) 
+        self.ReadBusy()
+        
+    def Clear(self, color=0x00):
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(color)
+        self.send_command(0x11) 
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(color)
+        self.send_command(0x11)
+        
+        self.send_command(0x12) 
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0X50)
+        self.send_data(0xf7)
+        self.send_command(0X02)
+        self.send_command(0X07)
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 186 - 0
frontend/lib/waveshare_epd/epd2in7b_V2.py

@@ -0,0 +1,186 @@
+# *****************************************************************************
+# * | File        :	  epd2in7b_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-10-22
+# # | 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       = 176
+EPD_HEIGHT      = 264
+
+logger = logging.getLogger(__name__)
+
+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)   
+
+    # Send Command
+    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)
+
+    # Send Data
+    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)
+        
+    # Read Busy
+    def ReadBusy(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(10)
+        logger.debug("e-Paper busy release")
+            
+    # Setting the display window
+    def SetWindows(self, Xstart, Ystart, Xend, Yend):
+        self.send_command(0x44)
+        self.send_data((Xstart >> 3) & 0xff)
+        self.send_data((Xend >> 3) & 0xff)
+        
+        self.send_command(0x45)
+        self.send_data(Ystart & 0xff)
+        self.send_data((Ystart >> 8) & 0xff)
+        self.send_data(Yend & 0xff)
+        self.send_data((Yend >> 8) & 0xff)
+    
+    # Set Cursor
+    def SetCursor(self, Xstart, Ystart):
+        self.send_command(0x4E)
+        self.send_data(Xstart & 0xff)
+        self.send_command(0x4F)
+        self.send_data(Ystart & 0xff)
+        self.send_data((Ystart >> 8) & 0xff)
+        
+    # Initialize the e-Paper register
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+
+        self.ReadBusy() 
+        self.send_command(0x12)      
+        self.ReadBusy() 
+        
+        self.send_command(0x00)     
+        self.send_data(0x27) 
+        self.send_data(0x01) 
+        self.send_data(0x00) 
+        
+        self.send_command(0x11)     
+        self.send_data(0x03) 
+        
+        self.SetWindows(0, 0, self.width-1, self.height-1)
+        self.SetCursor(0, 0)
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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
+    
+    # Sends the image buffer in RAM to e-Paper and displays
+    def display(self, imageblack, imagered):
+        Width = self.width / 8 
+        Height = self.height 
+
+        self.send_command(0x24) 
+        for i in range(0, int(Width * Height)):
+            self.send_data(imageblack[i]) 
+
+        self.send_command(0x26) 
+        for i in range(0, int(Width * Height)):
+            self.send_data(~imagered[i]) 
+        
+        self.TurnOnDisplay()
+
+    # Clear the screen
+    def Clear(self):
+        self.send_command(0x24)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xff)
+
+        self.send_command(0x26)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+            
+        self.TurnOnDisplay()
+        
+    # Turn on display
+    def TurnOnDisplay(self):
+        self.send_command(0x20)
+        self.ReadBusy()
+
+    # Enter sleep mode
+    def sleep(self):
+        self.send_command(0x10)
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 204 - 0
frontend/lib/waveshare_epd/epd2in9.py

@@ -0,0 +1,204 @@
+# *****************************************************************************
+# * | File        :	  epd2in9.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       = 128
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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
+
+    lut_full_update = [
+        0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+    ]
+
+    lut_partial_update  = [
+        0x10, 0x18, 0x18, 0x08, 0x18, 0x18,
+        0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
+        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(5)
+        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):
+        while(epdconfig.digital_read(self.busy_pin) == 1):      #  0: idle, 1: busy
+            epdconfig.delay_ms(200) 
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xC4)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
+        
+        logger.debug("e-Paper busy")
+        self.ReadBusy()
+        logger.debug("e-Paper busy release")  
+
+    def SetWindow(self, x_start, y_start, x_end, y_end):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x_start >> 3) & 0xFF)
+        self.send_data((x_end >> 3) & 0xFF)
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(y_start & 0xFF)
+        self.send_data((y_start >> 8) & 0xFF)
+        self.send_data(y_end & 0xFF)
+        self.send_data((y_end >> 8) & 0xFF)
+
+    def SetCursor(self, x, y):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x >> 3) & 0xFF)
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(y & 0xFF)
+        self.send_data((y >> 8) & 0xFF)
+        self.ReadBusy()
+        
+    def init(self, lut):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
+        self.send_data((EPD_HEIGHT - 1) & 0xFF)
+        self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
+        self.send_data(0x00) # GD = 0 SM = 0 TB = 0
+        
+        self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL 
+        self.send_data(0xD7)
+        self.send_data(0xD6)
+        self.send_data(0x9D)
+        
+        self.send_command(0x2C) # WRITE_VCOM_REGISTER
+        self.send_data(0xA8) # VCOM 7C
+        
+        self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
+        self.send_data(0x1A) # 4 dummy lines per gate
+        
+        self.send_command(0x3B) # SET_GATE_TIME
+        self.send_data(0x08) # 2us per line
+        
+        self.send_command(0x11) # DATA_ENTRY_MODE_SETTING
+        self.send_data(0x03) # X increment Y increment
+        
+        self.send_command(0x32) # WRITE_LUT_REGISTER
+        for i in range(0, len(lut)):
+            self.send_data(lut[i])
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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):
+        if (image == None):
+            return            
+        self.SetWindow(0, 0, self.width - 1, self.height - 1)
+        for j in range(0, self.height):
+            self.SetCursor(0, j)
+            self.send_command(0x24) # WRITE_RAM
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+        self.TurnOnDisplay()
+        
+    def Clear(self, color):
+        self.SetWindow(0, 0, self.width - 1, self.height - 1)
+        for j in range(0, self.height):
+            self.SetCursor(0, j)
+            self.send_command(0x24) # WRITE_RAM
+            for i in range(0, int(self.width / 8)):
+                self.send_data(color)   
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0x10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 303 - 0
frontend/lib/waveshare_epd/epd2in9_V2.py

@@ -0,0 +1,303 @@
+# *****************************************************************************
+# * | File        :	  epd2in9_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-10-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       = 128
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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
+        
+    WF_PARTIAL_2IN9 = [
+    0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0A,0x0,0x0,0x0,0x0,0x0,0x1,  
+    0x1,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x1,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+    0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+    0x22,0x17,0x41,0xB0,0x32,0x36,
+    ]
+
+    WS_20_30 = [									
+    0x80,	0x66,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x40,	0x0,	0x0,	0x0,
+    0x10,	0x66,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x20,	0x0,	0x0,	0x0,
+    0x80,	0x66,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x40,	0x0,	0x0,	0x0,
+    0x10,	0x66,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x20,	0x0,	0x0,	0x0,
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,
+    0x14,	0x8,	0x0,	0x0,	0x0,	0x0,	0x2,					
+    0xA,	0xA,	0x0,	0xA,	0xA,	0x0,	0x1,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x14,	0x8,	0x0,	0x1,	0x0,	0x0,	0x1,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x1,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	0x0,					
+    0x44,	0x44,	0x44,	0x44,	0x44,	0x44,	0x0,	0x0,	0x0,			
+    0x22,	0x17,	0x41,	0x0,	0x32,	0x36
+    ]
+
+    # Hardware reset
+    def reset(self):
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(50) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(2)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(50)   
+
+    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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      #  0: idle, 1: busy
+            epdconfig.delay_ms(10) 
+        logger.debug("e-Paper busy release")  
+
+    def TurnOnDisplay(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0xc7)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.ReadBusy()
+
+    def TurnOnDisplay_Partial(self):
+        self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
+        self.send_data(0x0F)
+        self.send_command(0x20) # MASTER_ACTIVATION
+        self.ReadBusy()
+
+    def lut(self, lut):
+        self.send_command(0x32)
+        for i in range(0, 153):
+            self.send_data(lut[i])
+        self.ReadBusy()
+
+    def SetLut(self, lut):
+        self.lut(lut)
+        self.send_command(0x3f)
+        self.send_data(lut[153])
+        self.send_command(0x03);	# gate voltage
+        self.send_data(lut[154])
+        self.send_command(0x04);	# source voltage
+        self.send_data(lut[155])	# VSH
+        self.send_data(lut[156])	# VSH2
+        self.send_data(lut[157])	# VSL
+        self.send_command(0x2c);		# VCOM
+        self.send_data(lut[158])
+
+    def SetWindow(self, x_start, y_start, x_end, y_end):
+        self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data((x_start>>3) & 0xFF)
+        self.send_data((x_end>>3) & 0xFF)
+        self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
+        self.send_data(y_start & 0xFF)
+        self.send_data((y_start >> 8) & 0xFF)
+        self.send_data(y_end & 0xFF)
+        self.send_data((y_end >> 8) & 0xFF)
+
+    def SetCursor(self, x, y):
+        self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
+        # x point must be the multiple of 8 or the last 3 bits will be ignored
+        self.send_data(x & 0xFF)
+        
+        self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
+        self.send_data(y & 0xFF)
+        self.send_data((y >> 8) & 0xFF)
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start     
+        self.reset()
+
+        self.ReadBusy()
+        self.send_command(0x12)  #SWRESET
+        self.ReadBusy() 
+
+        self.send_command(0x01) #Driver output control      
+        self.send_data(0x27)
+        self.send_data(0x01)
+        self.send_data(0x00)
+    
+        self.send_command(0x11) #data entry mode       
+        self.send_data(0x03)
+
+        self.SetWindow(0, 0, self.width-1, self.height-1)
+
+        self.send_command(0x21) #  Display update control
+        self.send_data(0x00)
+        self.send_data(0x80)
+    
+        self.SetCursor(0, 0)
+        self.ReadBusy()
+
+        self.SetLut(self.WS_20_30)
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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):
+        if (image == None):
+            return            
+        self.send_command(0x24) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+        self.TurnOnDisplay()
+
+    def display_Base(self, image):
+        if (image == None):
+            return   
+            
+        self.send_command(0x24) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])
+                
+        self.send_command(0x26) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+                
+        self.TurnOnDisplay()
+        
+    def display_Partial(self, image):
+        if (image == None):
+            return
+            
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(2)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(2)   
+        
+        self.SetLut(self.WF_PARTIAL_2IN9)
+        self.send_command(0x37)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x40)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x00)  
+        self.send_data(0x00)
+
+        self.send_command(0x3C) #BorderWavefrom
+        self.send_data(0x80)
+
+        self.send_command(0x22) 
+        self.send_data(0xC0)
+        self.send_command(0x20)
+        self.ReadBusy()
+
+        self.SetWindow(0, 0, self.width - 1, self.height - 1)
+        self.SetCursor(0, 0)
+        
+        self.send_command(0x24) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+        self.TurnOnDisplay_Partial()
+
+    def Clear(self, color):
+        self.send_command(0x24) # WRITE_RAM
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(color)   
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0x10) # DEEP_SLEEP_MODE
+        self.send_data(0x01)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 161 - 0
frontend/lib/waveshare_epd/epd2in9b_V3.py

@@ -0,0 +1,161 @@
+# *****************************************************************************
+# * | File        :	  epd2in9b_V3.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.1
+# * | Date        :   2020-12-03
+# # | 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       = 128
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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):
+        logger.debug("e-Paper busy")
+        self.send_command(0X71)
+        while(epdconfig.digital_read(self.busy_pin) == 0):      #  0: idle, 1: busy
+            self.send_command(0X71)
+            epdconfig.delay_ms(200)                
+        logger.debug("e-Paper busy release")
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x04)  
+        self.ReadBusy()#waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x00)    #panel setting
+        self.send_data(0x0f)   #LUT from OTP,128x296
+        self.send_data(0x89)    #Temperature sensor, boost and other related timing settings
+
+        self.send_command(0x61)    #resolution setting
+        self.send_data (0x80)  
+        self.send_data (0x01)  
+        self.send_data (0x28)
+
+        self.send_command(0X50)    #VCOM AND DATA INTERVAL SETTING
+        self.send_data(0x77)   #WBmode:VBDF 17|D7 VBDW 97 VBDB 57
+                            # WBRmode:VBDF F7 VBDW 77 VBDB 37  VBDR B7
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, blackimage, ryimage): # ryimage: red or yellow image
+        if (blackimage != None):
+            self.send_command(0X10)
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(blackimage[i])        
+        if (ryimage != None):
+            self.send_command(0X13)
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(ryimage[i])
+
+        self.send_command(0x12)
+        epdconfig.delay_ms(200) 
+        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)
+        epdconfig.delay_ms(200) 
+        self.ReadBusy()
+        
+    def sleep(self):
+        self.send_command(0X02) # power off
+        self.ReadBusy()
+        self.send_command(0X07) # deep sleep
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 158 - 0
frontend/lib/waveshare_epd/epd2in9bc.py

@@ -0,0 +1,158 @@
+# *****************************************************************************
+# * | File        :	  epd2in9bc.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       = 128
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      #  0: idle, 1: busy
+            epdconfig.delay_ms(200)                
+        logger.debug("e-Paper busy release")
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x06) # boost
+        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(0x8F)
+        self.send_command(0X50) # VCOM_AND_DATA_INTERVAL_SETTING
+        self.send_data(0x77)
+        self.send_command(0x61) # TCON_RESOLUTION
+        self.send_data (0x80)
+        self.send_data (0x01)
+        self.send_data (0x28)
+        # self.send_command(VCM_DC_SETTING_REGISTER)
+        # self.send_data (0x0A)
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, blackimage, ryimage): # ryimage: red or yellow image
+        if (blackimage != None):
+            self.send_command(0X10)
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(blackimage[i])        
+        if (ryimage != None):
+            self.send_command(0X13)
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(ryimage[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)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 303 - 0
frontend/lib/waveshare_epd/epd2in9d.py

@@ -0,0 +1,303 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+
+# *****************************************************************************
+# * | File        :   epd2in9d.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V2.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       = 128
+EPD_HEIGHT      = 296
+
+logger = logging.getLogger(__name__)
+
+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
+    
+    lut_vcom1 = [  
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00,
+    ]
+
+    lut_ww1 = [  
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_bw1 = [  
+        0x80, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_wb1 = [
+        0x40, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    lut_bb1 = [ 
+        0x00, 0x19, 0x01, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        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(20) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(5)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20)   
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(5)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20)  
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(5)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20)  
+
+    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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            self.send_command(0x71)
+            epdconfig.delay_ms(10)  
+        logger.debug("e-Paper busy release")
+        
+    def TurnOnDisplay(self):
+        self.send_command(0x12)
+        epdconfig.delay_ms(10)
+        self.ReadBusy()
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x04)
+        self.ReadBusy() #waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x00)     #panel setting
+        self.send_data(0x1f)        # LUT from OTP,KW-BF   KWR-AF    BWROTP 0f   BWOTP 1f
+
+        self.send_command(0x61)     #resolution setting
+        self.send_data (0x80)       
+        self.send_data (0x01)   
+        self.send_data (0x28)   
+
+        self.send_command(0X50) #VCOM AND DATA INTERVAL SETTING     
+        self.send_data(0x97)        #WBmode:VBDF 17|D7 VBDW 97 VBDB 57  WBRmode:VBDF F7 VBDW 77 VBDB 37  VBDR B7
+
+        return 0
+    
+    def SetPartReg(self):
+
+        self.send_command(0x01) #POWER SETTING
+        self.send_data(0x03)
+        self.send_data(0x00)
+        self.send_data(0x2b)
+        self.send_data(0x2b)
+        self.send_data(0x03)
+
+        self.send_command(0x06) #boost 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(0xbf)     #LUT from OTP,128x296
+
+        self.send_command(0x30) #PLL setting
+        self.send_data(0x3a)     # 3a 100HZ   29 150Hz 39 200HZ 31 171HZ
+
+        self.send_command(0x61) #resolution setting
+        self.send_data(self.width)
+        self.send_data((self.height >> 8) & 0xff)
+        self.send_data(self.height & 0xff)
+
+        self.send_command(0x82) #vcom_DC setting
+        self.send_data(0x12)
+
+        self.send_command(0X50)
+        self.send_data(0x97)
+        
+        self.send_command(0x20)         # vcom
+        for count in range(0, 44):
+            self.send_data(self.lut_vcom1[count])
+        self.send_command(0x21)         # ww --
+        for count in range(0, 42):
+            self.send_data(self.lut_ww1[count])
+        self.send_command(0x22)         # bw r
+        for count in range(0, 42):
+            self.send_data(self.lut_bw1[count])
+        self.send_command(0x23)         # wb w
+        for count in range(0, 42):
+            self.send_data(self.lut_wb1[count])
+        self.send_command(0x24)         # bb b
+        for count in range(0, 42):
+            self.send_data(self.lut_bb1[count])
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(image[i])
+        epdconfig.delay_ms(10)
+        
+        self.TurnOnDisplay()
+        
+    def DisplayPartial(self, image):
+        self.SetPartReg()
+        self.send_command(0x91)
+        self.send_command(0x90)
+        self.send_data(0)
+        self.send_data(self.width - 1)
+
+        self.send_data(0)
+        self.send_data(0)
+        self.send_data(int(self.height / 256))
+        self.send_data(self.height % 256 - 1)
+        self.send_data(0x28)
+            
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(image[i])
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(~image[i])
+        epdconfig.delay_ms(10)
+          
+        self.TurnOnDisplay()
+        
+    def Clear(self, color):
+        self.send_command(0x10)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00)
+        epdconfig.delay_ms(10)
+        
+        self.send_command(0x13)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xFF)
+        epdconfig.delay_ms(10)
+        
+        self.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0X50)
+        self.send_data(0xf7)
+        self.send_command(0X02)         #power off
+        self.send_command(0X07)         #deep sleep  
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 453 - 0
frontend/lib/waveshare_epd/epd3in7.py

@@ -0,0 +1,453 @@
+# *****************************************************************************
+# * | File        :	  epd3in7.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-07-16
+# # | 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       = 280
+EPD_HEIGHT      = 480
+
+GRAY1  = 0xff #white
+GRAY2  = 0xC0 #Close to white
+GRAY3  = 0x80 #Close to black
+GRAY4  = 0x00 #black
+
+logger = logging.getLogger(__name__)
+
+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_4Gray_GC = [
+        0x2A,0x06,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x28,0x06,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x20,0x06,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x14,0x06,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x02,0x02,0x0A,0x00,0x00,0x00,0x08,0x08,0x02,
+        0x00,0x02,0x02,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x22,0x22,0x22,0x22,0x22
+    ]
+
+    lut_1Gray_GC  = [
+        0x2A,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x05,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x2A,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x05,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x02,0x03,0x0A,0x00,0x02,0x06,0x0A,0x05,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x22,0x22,0x22,0x22,0x22
+    ]
+
+    lut_1Gray_DU  = [
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x01,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x0A,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x05,0x05,0x00,0x05,0x03,0x05,0x05,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x22,0x22,0x22,0x22,0x22
+    ]
+
+    lut_1Gray_A2  = [
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x03,0x05,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 
+        0x22,0x22,0x22,0x22,0x22
+    ]
+        
+    # 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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      #  0: idle, 1: busy
+            epdconfig.delay_ms(10) 
+        logger.debug("e-Paper busy release") 
+
+
+    def init(self, mode):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.send_command(0x12)
+        epdconfig.delay_ms(300)
+        
+        self.send_command(0x46)
+        self.send_data(0xF7)
+        self.ReadBusy()
+        self.send_command(0x47)
+        self.send_data(0xF7)
+        self.ReadBusy()
+        
+        self.send_command(0x01) # setting gaet number
+        self.send_data(0xDF)
+        self.send_data(0x01)
+        self.send_data(0x00)
+
+        self.send_command(0x03) # set gate voltage
+        self.send_data(0x00)
+
+        self.send_command(0x04) # set source voltage
+        self.send_data(0x41)
+        self.send_data(0xA8)
+        self.send_data(0x32)
+
+        self.send_command(0x11) # set data entry sequence
+        self.send_data(0x03)
+
+        self.send_command(0x3C) # set border 
+        self.send_data(0x03)
+        
+        self.send_command(0x0C) # set booster strength
+        self.send_data(0xAE)
+        self.send_data(0xC7)
+        self.send_data(0xC3)
+        self.send_data(0xC0)
+        self.send_data(0xC0)
+
+        self.send_command(0x18) # set internal sensor on
+        self.send_data(0x80)
+         
+        self.send_command(0x2C) # set vcom value
+        self.send_data(0x44)
+        
+        if(mode == 0):   #4Gray
+            self.send_command(0x37) # set display option, these setting turn on previous function
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)  
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)
+            self.send_data(0x00)  
+        elif(mode == 1):      #1Gray
+            self.send_command(0x37) # set display option, these setting turn on previous function
+            self.send_data(0x00)     #can switch 1 gray or 4 gray
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+            self.send_data(0xFF)  
+            self.send_data(0x4F)
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+            self.send_data(0xFF)
+        else:
+            logger.debug("There is no such mode") 
+
+        self.send_command(0x44) # setting X direction start/end position of RAM
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0x17)
+        self.send_data(0x01)
+
+        self.send_command(0x45) # setting Y direction start/end position of RAM
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_data(0xDF)
+        self.send_data(0x01)
+
+        self.send_command(0x22) # Display Update Control 2
+        self.send_data(0xCF)
+        return 0
+
+
+    def load_lut(self, lut):
+        self.send_command(0x32)
+        for i in range(0, 105):
+            self.send_data(lut[i])
+
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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 getbuffer_4Gray(self, image):
+        # logger.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
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.debug("Horizontal")
+            for x in range(imwidth):
+                for y in range(imheight):
+                    newx = y
+                    newy = imwidth - x - 1
+                    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_4Gray(self, image):
+        if (image == None):
+            return            
+
+        self.send_command(0x4E)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_command(0x4F)
+        self.send_data(0x00)
+        self.send_data(0x00)
+
+        self.send_command(0x24)
+        for i in range(0, (int)(self.height*(self.width/8))):
+            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.send_command(0x4E)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_command(0x4F)
+        self.send_data(0x00)
+        self.send_data(0x00)
+
+        self.send_command(0x26)
+        for i in range(0, (int)(self.height*(self.width/8))):
+            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.load_lut(self.lut_4Gray_GC)
+        self.send_command(0x22)
+        self.send_data(0xC7)
+        self.send_command(0x20)
+        self.ReadBusy()   
+
+
+    def display_1Gray(self, image):
+        if (image == None):
+            return            
+
+        self.send_command(0x4E)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_command(0x4F)
+        self.send_data(0x00)
+        self.send_data(0x00)
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(image[i + j * int(self.width / 8)])   
+
+        self.load_lut(self.lut_1Gray_A2)
+        self.send_command(0x20)
+        self.ReadBusy()   
+        
+
+    def Clear(self, color, mode):
+        self.send_command(0x4E)
+        self.send_data(0x00)
+        self.send_data(0x00)
+        self.send_command(0x4F)
+        self.send_data(0x00)
+        self.send_data(0x00)
+
+        self.send_command(0x24)
+        for j in range(0, self.height):
+            for i in range(0, int(self.width / 8)):
+                self.send_data(0xff)   
+
+        if(mode == 0):              #4Gray
+            self.send_command(0x26)
+            for j in range(0, self.height):
+                for i in range(0, int(self.width / 8)):
+                    self.send_data(0xff) 
+            self.load_lut(self.lut_4Gray_GC)
+            self.send_command(0x22)
+            self.send_data(0xC7)
+        elif(mode == 1):            #1Gray
+            self.load_lut(self.lut_1Gray_DU)
+        else:
+            logger.debug("There is no such mode") 
+
+        self.send_command(0x20)
+        self.ReadBusy()   
+
+
+    def sleep(self):
+        self.send_command(0X50) # DEEP_SLEEP_MODE
+        self.send_data(0xf7)
+        self.send_command(0X02) #power off
+        self.send_command(0X07) #deep sleep
+        self.send_data(0xA5)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+
+### END OF FILE ###
+

+ 236 - 0
frontend/lib/waveshare_epd/epd4in01f.py

@@ -0,0 +1,236 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+# *****************************************************************************
+# * | File        :	  epd4in01f.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-11-06
+# # | 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      = 400
+
+logger = logging.getLogger(__name__)
+
+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.BLACK  = 0x000000   #   0000  BGR
+        self.WHITE  = 0xffffff   #   0001
+        self.GREEN  = 0x00ff00   #   0010
+        self.BLUE   = 0xff0000   #   0011
+        self.RED    = 0x0000ff   #   0100
+        self.YELLOW = 0x00ffff   #   0101
+        self.ORANGE = 0x0080ff   #   0110
+        
+        
+    # 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(1)
+        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 ReadBusyHigh(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(10)
+        logger.debug("e-Paper busy release")
+        
+    def ReadBusyLow(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(10)    
+        logger.debug("e-Paper busy release")
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.ReadBusyHigh()
+        self.send_command(0x00);
+        self.send_data(0x2f);
+        self.send_data(0x00);
+        self.send_command(0x01);
+        self.send_data(0x37);
+        self.send_data(0x00);
+        self.send_data(0x05);
+        self.send_data(0x05);
+        self.send_command(0x03);
+        self.send_data(0x00);
+        self.send_command(0x06);
+        self.send_data(0xC7);
+        self.send_data(0xC7);
+        self.send_data(0x1D);
+        self.send_command(0x41);
+        self.send_data(0x00);
+        self.send_command(0x50);
+        self.send_data(0x37);
+        self.send_command(0x60);
+        self.send_data(0x22);
+        self.send_command(0x61);
+        self.send_data(0x02);
+        self.send_data(0x80);
+        self.send_data(0x01);
+        self.send_data(0x90);
+        self.send_command(0xE3);
+        self.send_data(0xAA);
+        
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        buf = [0x00] * int(self.width * self.height / 2)
+        image_monocolor = image.convert('RGB')#Picture mode conversion
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        logger.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.
+                    Add = int((x + y * self.width) / 2)
+                    Color = 0;
+                    if (pixels[x, y][0] == 0 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 0):
+                        Color = 0
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 255):
+                        Color = 1
+                    elif (pixels[x, y][0] == 0 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 0):
+                        Color = 2
+                    elif (pixels[x, y][0] == 0 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 255):
+                        Color = 3
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 0):
+                        Color = 4
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 0):
+                        Color = 5
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 128 and pixels[x, y][2] == 0):
+                        Color = 6
+                    
+                    data_t = buf[Add]&(~(0xF0 >> ((x % 2)*4)))
+                    buf[Add] = data_t | ((Color << 4) >> ((x % 2)*4));
+                        
+        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   
+                    Add = int((newx + newy*self.width) / 2)
+                    Color = 0;
+                    if (pixels[x, y][0] == 0 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 0):
+                        Color = 0
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 255):
+                        Color = 1
+                    elif (pixels[x, y][0] == 0 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 0):
+                        Color = 2
+                    elif (pixels[x, y][0] == 0 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 255):
+                        Color = 3
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 0 and pixels[x, y][2] == 0):
+                        Color = 4
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 255 and pixels[x, y][2] == 0):
+                        Color = 5
+                    elif (pixels[x, y][0] == 255 and  pixels[x, y][1] == 128 and pixels[x, y][2] == 0):
+                        Color = 6
+                    
+                    data_t = buf[Add]&(~(0xF0 >> ((newx % 2)*4)))
+                    buf[Add] = data_t | ((Color << 4) >> ((newx % 2)*4));
+        return buf
+
+    def display(self,image):
+        self.send_command(0x61)#Set Resolution setting
+        self.send_data(0x02)
+        self.send_data(0x80)
+        self.send_data(0x01)
+        self.send_data(0x90)
+        self.send_command(0x10)
+        for i in range(0, int(EPD_HEIGHT)):
+            for j in range(0, int(EPD_WIDTH/2)):
+                self.send_data((image[j+(int(EPD_WIDTH/2)*i)]))
+        self.send_command(0x04)#0x04
+        self.ReadBusyHigh()
+        self.send_command(0x12)#0x12
+        self.ReadBusyHigh()
+        self.send_command(0x02)  #0x02
+        self.ReadBusyLow()
+        # epdconfig.delay_ms(500)
+        
+    def Clear(self):
+        self.send_command(0x61)#Set Resolution setting
+        self.send_data(0x02)
+        self.send_data(0x80)
+        self.send_data(0x01)
+        self.send_data(0x90)
+        self.send_command(0x10)
+        for i in range(0, int(EPD_HEIGHT)):
+            for j in range(0, int(EPD_WIDTH/2)):
+                self.send_data(0x11)
+        #BLACK   0x00    /// 0000
+        #WHITE   0x11    /// 0001
+        #GREEN   0x22    /// 0010
+        #BLUE    0x33    /// 0011
+        #RED     0x44    /// 0100
+        #YELLOW  0x55    /// 0101
+        #ORANGE  0x66    /// 0110
+        #CLEAN   0x77    /// 0111   unavailable  Afterimage
+        self.send_command(0x04)#0x04
+        self.ReadBusyHigh()
+        self.send_command(0x12)#0x12
+        self.ReadBusyHigh()
+        self.send_command(0x02)  #0x02
+        self.ReadBusyLow()
+        # epdconfig.delay_ms(500)
+
+    def sleep(self):
+        # epdconfig.delay_ms(500)
+        self.send_command(0x07) # DEEP_SLEEP
+        self.send_data(0XA5)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()   
+        

+ 663 - 0
frontend/lib/waveshare_epd/epd4in2.py

@@ -0,0 +1,663 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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
+        self.DATA   = [0x00] * 15000
+
+    lut_vcom0 = [
+    0x00, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x0F, 0x0F, 0x00, 0x00, 0x01, 
+    0x00, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 
+    ]
+    lut_ww = [
+    0x50, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x90, 0x0F, 0x0F, 0x00, 0x00, 0x01, 
+    0xA0, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_bw = [
+    0x50, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x90, 0x0F, 0x0F, 0x00, 0x00, 0x01, 
+    0xA0, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_wb = [
+    0xA0, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x90, 0x0F, 0x0F, 0x00, 0x00, 0x01, 
+    0x50, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    lut_bb = [
+    0x20, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x90, 0x0F, 0x0F, 0x00, 0x00, 0x01, 
+    0x10, 0x08, 0x08, 0x00, 0x00, 0x02, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+    #******************************partial screen update LUT*********************************/
+    EPD_4IN2_Partial_lut_vcom1 =[
+    0x00, 0x01, 0x20, 0x01, 0x00, 0x01, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    EPD_4IN2_Partial_lut_ww1 =[
+    0x00, 0x01, 0x20, 0x01, 0x00, 0x01, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]
+
+    EPD_4IN2_Partial_lut_bw1 =[
+    0x20, 0x01, 0x20, 0x01, 0x00, 0x01, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    ]
+
+    EPD_4IN2_Partial_lut_wb1 =[
+    0x10, 0x01, 0x20, 0x01, 0x00, 0x01, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+    ]
+
+    EPD_4IN2_Partial_lut_bb1 =[
+    0x00, 0x01,0x20, 0x01, 0x00, 0x01, 
+    0x00, 0x00,0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00,0x00, 0x00, 0x00, 0x00, 
+    0x00, 0x00,0x00, 0x00, 0x00, 0x00, 
+    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(10) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(10)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(10)   
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(10)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(10)   
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(10)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(10)
+
+    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, 36):
+            self.send_data(self.lut_vcom0[count])
+            
+        self.send_command(0x21)         # ww --
+        for count in range(0, 36):
+            self.send_data(self.lut_ww[count])
+            
+        self.send_command(0x22)         # bw r
+        for count in range(0, 36):
+            self.send_data(self.lut_bw[count])
+            
+        self.send_command(0x23)         # wb w
+        for count in range(0, 36):
+            self.send_data(self.lut_bb[count])
+            
+        self.send_command(0x24)         # bb b
+        for count in range(0, 36):
+            self.send_data(self.lut_wb[count])
+
+
+    def Partial_SetLut(self):
+        self.send_command(0x20);
+        for count in range(0, 44):      
+            self.send_data(self.EPD_4IN2_Partial_lut_vcom1[count])
+
+        self.send_command(0x21);
+        for count in range(0, 42):      
+            self.send_data(self.EPD_4IN2_Partial_lut_ww1[count])
+        
+        self.send_command(0x22);
+        for count in range(0, 42):     
+            self.send_data(self.EPD_4IN2_Partial_lut_bw1[count])
+
+        self.send_command(0x23);
+        for count in range(0, 42):      
+            self.send_data(self.EPD_4IN2_Partial_lut_wb1[count])
+
+        self.send_command(0x24);
+        for count in range(0, 42):      
+            self.send_data(self.EPD_4IN2_Partial_lut_bb1[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_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(0x12)
+
+        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_Partial(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_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(0x12)
+
+        self.send_command(0X50) # VCOM AND DATA INTERVAL SETTING
+        self.send_data(0x07) # 97white border 77black border  VBDF 17|D7 VBDW 97 VBDB 57  VBDF F7 VBDW 77 VBDB 37  VBDR B7
+    
+        self.Partial_SetLut();
+        # 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):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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):
+        # logger.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
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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(0x92); 
+        self.set_lut();
+        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 EPD_4IN2_PartialDisplay(self, X_start, Y_start, X_end, Y_end, Image):
+        # EPD_WIDTH       = 400
+        # EPD_HEIGHT      = 300
+        if(EPD_WIDTH % 8 != 0):
+           Width = int(EPD_WIDTH / 8) + 1;
+        else:
+            Width = int(EPD_WIDTH / 8);
+        Height = EPD_HEIGHT;
+        
+        if(X_start % 8 != 0):
+            X_start = int(X_start/8)*8+8
+        if(X_end % 8 != 0):
+            X_end = int(X_end/8)*8+8
+        
+        
+        self.send_command(0x91);  #This command makes the display enter partial mode
+        self.send_command(0x90);  #resolution setting
+        self.send_data (int(X_start/256));
+        self.send_data (int(X_start%256));   #x-start    
+        
+        self.send_data (int(X_end /256));  
+        self.send_data (int(X_end %256)-1);  #x-end
+
+        self.send_data (int(Y_start/256));
+        self.send_data (int(Y_start%256));   #y-start    
+        
+
+        self.send_data (int(Y_end/256));  
+        self.send_data (int(Y_end%256)-1);  #y-end
+        self.send_data (0x28); 
+
+        self.send_command(0x10);        #writes Old data to SRAM for programming
+        for j in range(0, int(Y_end - Y_start)):
+            for i in range(0, int(X_end/8) - int(X_start/8)):
+                self.send_data(self.DATA[(Y_start + j)*Width + int(X_start/8) + i]);
+            
+        self.send_command(0x13);     #writes New data to SRAM.
+        for j in range(0, int(Y_end - Y_start)):
+            for i in range(0, int(X_end/8) - int(X_start/8)):
+                self.send_data(~Image[(Y_start + j)*Width + int(X_start/8) + i]);
+                self.DATA[(Y_start + j)*Width + int(X_start/8) + i] = ~Image[(Y_start + j)*Width + int(X_start/8) + i]
+            
+        self.send_command(0x12);   #DISPLAY REFRESH                
+        epdconfig.delay_ms(200)    #The delay here is necessary, 200uS at least!!!     
+        self.ReadBusy()
+
+
+    def display_4Gray(self, image):
+        self.send_command(0x92); 
+        self.set_lut();
+        self.send_command(0x10)
+        for i in range(0, int(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, int(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.delay_ms(2000)
+        epdconfig.module_exit()
+        
+### END OF FILE ###
+

+ 153 - 0
frontend/lib/waveshare_epd/epd4in2b_V2.py

@@ -0,0 +1,153 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        self.send_command(0x71);
+        while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
+            self.send_command(0x71);
+            epdconfig.delay_ms(20)
+        logger.debug("e-Paper busy release")
+            
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+        
+        self.send_command(0x04); 
+        self.ReadBusy();
+
+        self.send_command(0x00);
+        self.send_data(0x0f);
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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(20)
+        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) 
+        epdconfig.delay_ms(20)
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0X50);
+        self.send_data(0xf7);		#border floating	
+
+        self.send_command(0X02);  	#power off
+        self.ReadBusy(); #waiting for the electronic paper IC to release the idle signal
+        self.send_command(0X07);  	#deep sleep
+        self.send_data(0xA5);
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 151 - 0
frontend/lib/waveshare_epd/epd4in2bc.py

@@ -0,0 +1,151 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.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):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 216 - 0
frontend/lib/waveshare_epd/epd5in65f.py

@@ -0,0 +1,216 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+# *****************************************************************************
+# * | File        :	  epd5in65f.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-03-02
+# # | 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
+
+import PIL
+from PIL import Image
+import io
+
+# Display resolution
+EPD_WIDTH       = 600
+EPD_HEIGHT      = 448
+
+logger = logging.getLogger(__name__)
+
+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.BLACK  = 0x000000   #   0000  BGR
+        self.WHITE  = 0xffffff   #   0001
+        self.GREEN  = 0x00ff00   #   0010
+        self.BLUE   = 0xff0000   #   0011
+        self.RED    = 0x0000ff   #   0100
+        self.YELLOW = 0x00ffff   #   0101
+        self.ORANGE = 0x0080ff   #   0110
+
+
+    # Hardware reset
+    def reset(self):
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(600)
+        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 send_data_bulk(self, data):
+        epdconfig.digital_write(self.dc_pin, 1)
+        epdconfig.digital_write(self.cs_pin, 0)
+        epdconfig.spi_writebyte2(data)
+        epdconfig.digital_write(self.cs_pin, 1)
+
+    def ReadBusyHigh(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.debug("e-Paper busy release")
+
+    def ReadBusyLow(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 1):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.debug("e-Paper busy release")
+
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+
+        self.ReadBusyHigh()
+        self.send_command(0x00)
+        self.send_data(0xEF)
+        self.send_data(0x08)
+        self.send_command(0x01)
+        self.send_data(0x37)
+        self.send_data(0x00)
+        self.send_data(0x23)
+        self.send_data(0x23)
+        self.send_command(0x03)
+        self.send_data(0x00)
+        self.send_command(0x06)
+        self.send_data(0xC7)
+        self.send_data(0xC7)
+        self.send_data(0x1D)
+        self.send_command(0x30)
+        self.send_data(0x3c)
+        self.send_command(0x41)
+        self.send_data(0x00)
+        self.send_command(0x50)
+        self.send_data(0x37)
+        self.send_command(0x60)
+        self.send_data(0x22)
+        self.send_command(0x61)
+        self.send_data(0x02)
+        self.send_data(0x58)
+        self.send_data(0x01)
+        self.send_data(0xC0)
+        self.send_command(0xE3)
+        self.send_data(0xAA)
+
+        epdconfig.delay_ms(100)
+        self.send_command(0x50)
+        self.send_data(0x37)
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        # Create a pallette with the 7 colors supported by the panel
+        pal_image = Image.new("P", (1,1))
+        pal_image.putpalette( (0,0,0,  255,255,255,  0,255,0,   0,0,255,  255,0,0,  255,255,0, 255,128,0) + (0,0,0)*249)
+
+        # Check if we need to rotate the image
+        imwidth, imheight = image.size
+        if(imwidth == self.width and imheight == self.height):
+            image_temp = image
+        elif(imwidth == self.height and imheight == self.width):
+            image_temp = image.rotate(90, expand=True)
+        else:
+            logger.warning("Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, self.width, self.height))
+
+        # Convert the soruce image to the 7 colors, dithering if needed
+        image_7color = image_temp.convert("RGB").quantize(palette=pal_image)
+        buf_7color = bytearray(image_7color.tobytes('raw'))
+
+        # PIL does not support 4 bit color, so pack the 4 bits of color
+        # into a single byte to transfer to the panel
+        buf = [0x00] * int(self.width * self.height / 2)
+        idx = 0
+        for i in range(0, len(buf_7color), 2):
+            buf[idx] = (buf_7color[i] << 4) + buf_7color[i+1]
+            idx += 1
+
+        return buf
+
+    def display(self,image):
+        self.send_command(0x61) #Set Resolution setting
+        self.send_data(0x02)
+        self.send_data(0x58)
+        self.send_data(0x01)
+        self.send_data(0xC0)
+        self.send_command(0x10)
+
+        self.send_data_bulk(image)
+        self.send_command(0x04) #0x04
+        self.ReadBusyHigh()
+        self.send_command(0x12) #0x12
+        self.ReadBusyHigh()
+        self.send_command(0x02) #0x02
+        self.ReadBusyLow()
+        epdconfig.delay_ms(500)
+
+    def Clear(self):
+        self.send_command(0x61) #Set Resolution setting
+        self.send_data(0x02)
+        self.send_data(0x58)
+        self.send_data(0x01)
+        self.send_data(0xC0)
+        self.send_command(0x10)
+
+        # Set all pixels to white
+        buf = [0x11] * int(self.width * self.height / 2)
+        self.send_data_bulk(buf)
+
+        self.send_command(0x04) #0x04
+        self.ReadBusyHigh()
+        self.send_command(0x12) #0x12
+        self.ReadBusyHigh()
+        self.send_command(0x02) #0x02
+        self.ReadBusyLow()
+        epdconfig.delay_ms(500)
+
+    def sleep(self):
+        epdconfig.delay_ms(500)
+        self.send_command(0x07) # DEEP_SLEEP
+        self.send_data(0XA5)
+        epdconfig.digital_write(self.reset_pin, 0)
+
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()

+ 203 - 0
frontend/lib/waveshare_epd/epd5in83.py

@@ -0,0 +1,203 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)    
+        logger.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()
+        logger.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.delay_ms(2000)
+        epdconfig.module_exit()
+        
+### END OF FILE ###
+

+ 170 - 0
frontend/lib/waveshare_epd/epd5in83_V2.py

@@ -0,0 +1,170 @@
+# *****************************************************************************
+# * | File        :	  epd5in83_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-12-09
+# # | 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       = 648
+EPD_HEIGHT      = 480
+
+logger = logging.getLogger(__name__)
+
+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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      
+            epdconfig.delay_ms(20)    
+        logger.debug("e-Paper busy release")
+        
+    def TurnOnDisplay(self):
+        self.send_command(0x12);    #POWER ON
+        epdconfig.delay_ms(100)   
+        self.ReadBusy();  
+    
+    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()   #waiting for the electronic paper IC to release the idle signal
+
+        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 (0x02)		#source 648
+        self.send_data (0x88)
+        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):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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(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(~image[i])
+        self.TurnOnDisplay()
+        
+    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.TurnOnDisplay()
+
+    def sleep(self):
+        self.send_command(0x02) # POWER_OFF
+        self.ReadBusy()
+        self.send_command(0x07) # DEEP_SLEEP
+        self.send_data(0XA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+        
+### END OF FILE ###
+

+ 174 - 0
frontend/lib/waveshare_epd/epd5in83b_V2.py

@@ -0,0 +1,174 @@
+# *****************************************************************************
+# * | File        :	  epd5in83b_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2020-07-04
+# # | 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       = 648
+EPD_HEIGHT      = 480
+
+logger = logging.getLogger(__name__)
+
+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(1)
+        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):
+        logger.debug("e-Paper busy")
+        self.send_command(0X71)
+        while(epdconfig.digital_read(self.busy_pin) == 0):      #  0: idle, 1: busy
+            self.send_command(0X71)
+            epdconfig.delay_ms(200)
+        logger.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 (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()   #waiting for the electronic paper IC to release the idle signal
+
+        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 (0x02)       #source 648
+        self.send_data (0x88)
+        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):
+        # logger.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()
+        # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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, imageblack, imagered):
+        if (imageblack != None):
+            self.send_command(0X10)
+            for i in range(0, int(self.width * self.height / 8)):
+                self.send_data(imageblack[i])        
+        if (imagered != None):
+            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(200) 
+        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(200) 
+        self.ReadBusy()
+
+    def sleep(self):
+        self.send_command(0X02) # power off
+        self.ReadBusy()
+        self.send_command(0X07) # deep sleep
+        self.send_data(0xA5)
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 203 - 0
frontend/lib/waveshare_epd/epd5in83bc.py

@@ -0,0 +1,203 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.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):
+        # logger.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()
+        logger.debug('imwidth = %d  imheight =  %d ',imwidth, imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 185 - 0
frontend/lib/waveshare_epd/epd7in5.py

@@ -0,0 +1,185 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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 send_data2(self, data):
+        epdconfig.digital_write(self.dc_pin, 1)
+        epdconfig.digital_write(self.cs_pin, 0)
+        epdconfig.spi_writebyte2(data)
+        epdconfig.digital_write(self.cs_pin, 1)
+        
+    def ReadBusy(self):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)    
+        logger.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_data2([0x37, 0x00])
+        
+        self.send_command(0x00) # PANEL_SETTING
+        self.send_data2([0xCF, 0x08])
+        
+        self.send_command(0x06) # BOOSTER_SOFT_START
+        self.send_data2([0xc7, 0xcc, 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):
+        img = image
+        imwidth, imheight = img.size
+        halfwidth = int(self.width / 2)
+        buf = [0x33] * halfwidth * self.height
+        
+        if(imwidth == self.width and imheight == self.height):
+            img = img.convert('1')
+        elif(imwidth == self.height and imheight == self.width):
+            img = img.rotate(90, expand=True).convert('1')
+            imwidth, imheight = img.size
+        else:
+            logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
+            # return a blank buffer
+            return buf
+        
+        pixels = img.load()
+
+        for y in range(imheight):
+            offset = y * halfwidth
+            for x in range(1, imwidth, 2):
+                i = offset + x // 2
+                if(pixels[x-1, y] > 191):
+                    if(pixels[x, y] > 191):
+                        buf[i] = 0x33
+                    else:
+                        buf[i] = 0x30
+                else:
+                    if(pixels[x, y] > 191):
+                        buf[i] = 0x03
+                    else:
+                        buf[i] = 0x00
+        return buf
+        
+    def display(self, image):
+        self.send_command(0x10)
+        self.send_data2(image)
+        self.send_command(0x12)
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+        
+    def Clear(self):
+        buf = [0x33] * int(self.width * self.height / 2)
+        self.send_command(0x10)
+        self.send_data2(buf)
+        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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 182 - 0
frontend/lib/waveshare_epd/epd7in5_HD.py

@@ -0,0 +1,182 @@
+# *****************************************************************************
+# * | 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       = 880
+EPD_HEIGHT      = 528
+
+logger = logging.getLogger(__name__)
+
+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 send_data2(self, data):
+        epdconfig.digital_write(self.dc_pin, 1)
+        epdconfig.digital_write(self.cs_pin, 0)
+        epdconfig.spi_writebyte2(data)
+        epdconfig.digital_write(self.cs_pin, 1)
+        
+    def ReadBusy(self):
+        logger.debug("e-Paper busy")
+        busy = epdconfig.digital_read(self.busy_pin)
+        while(busy == 1):
+            busy = epdconfig.digital_read(self.busy_pin)
+        epdconfig.delay_ms(200)
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        self.ReadBusy();
+        self.send_command(0x12);  #SWRESET
+        self.ReadBusy();
+
+        self.send_command(0x46);  # Auto Write Red RAM
+        self.send_data(0xf7);
+        self.ReadBusy();
+        self.send_command(0x47);  # Auto Write  B/W RAM
+        self.send_data(0xf7);
+        self.ReadBusy();
+
+        self.send_command(0x0C);  # Soft start setting
+        self.send_data2([0xAE, 0xC7, 0xC3, 0xC0, 0x40])
+
+        self.send_command(0x01);  # Set MUX as 527
+        self.send_data2([0xAF, 0x02, 0x01])
+
+        self.send_command(0x11);  # Data entry mode      
+        self.send_data(0x01);
+
+        self.send_command(0x44); 
+        self.send_data2([0x00, 0x00, 0x6F, 0x03]) # RAM x address start at 0
+        self.send_command(0x45); 
+        self.send_data2([0xAF, 0x02, 0x00, 0x00])
+
+        self.send_command(0x3C); # VBD
+        self.send_data(0x05); # LUT1, for white
+
+        self.send_command(0x18);
+        self.send_data(0X80);
+
+
+        self.send_command(0x22);
+        self.send_data(0XB1); #Load Temperature and waveform setting.
+        self.send_command(0x20);
+        self.ReadBusy();
+
+        self.send_command(0x4E); # set RAM x address count to 0;
+        self.send_data2([0x00, 0x00])
+        self.send_command(0x4F); 
+        self.send_data2([0x00, 0x00])
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        img = image
+        imwidth, imheight = img.size
+        if(imwidth == self.width and imheight == self.height):
+            img = img.convert('1')
+        elif(imwidth == self.height and imheight == self.width):
+            img = img.rotate(90, expand=True).convert('1')
+        else:
+            logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
+            # return a blank buffer
+            return [0xff] * int(self.width * self.height / 8)
+
+        buf = bytearray(img.tobytes('raw'))
+        return buf
+        
+    def display(self, image):
+        self.send_command(0x4F); 
+        self.send_data2([0x00, 0x00])
+        self.send_command(0x24);
+        self.send_data2(image)
+        self.send_command(0x22);
+        self.send_data(0xF7);#Load LUT from MCU(0x32)
+        self.send_command(0x20);
+        epdconfig.delay_ms(10);
+        self.ReadBusy();
+        
+    def Clear(self):
+        buf = [0xff] * int(self.width * self.height / 8)
+        self.send_command(0x4F); 
+        self.send_data2([0x00, 0x00])
+        self.send_command(0x24)
+        self.send_data2(buf)
+            
+        self.send_command(0x26)
+        self.send_data2(buf)
+                
+        self.send_command(0x22);
+        self.send_data(0xF7);#Load LUT from MCU(0x32)
+        self.send_command(0x20);
+        epdconfig.delay_ms(10);
+        self.ReadBusy();
+
+    def sleep(self):
+        self.send_command(0x10);
+        self.send_data(0x01);
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 279 - 0
frontend/lib/waveshare_epd/epd7in5_V2.py

@@ -0,0 +1,279 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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
+    
+    Voltage_Frame_7IN5_V2 = [
+	0x6, 0x3F, 0x3F, 0x11, 0x24, 0x7, 0x17,
+    ]
+
+    LUT_VCOM_7IN5_V2 = [	
+        0x0,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0xF,	0x1,	0xF,	0x1,	0x2,	
+        0x0,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+    ]	
+
+    LUT_WW_7IN5_V2 = [
+        0x10,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x84,	0xF,	0x1,	0xF,	0x1,	0x2,	
+        0x20,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+    ]
+
+    LUT_BW_7IN5_V2 = [	
+        0x10,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x84,	0xF,	0x1,	0xF,	0x1,	0x2,	
+        0x20,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+    ]
+
+    LUT_WB_7IN5_V2 = [
+        0x80,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x84,	0xF,	0x1,	0xF,	0x1,	0x2,	
+        0x40,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+    ]
+
+    LUT_BB_7IN5_V2 = [
+        0x80,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x84,	0xF,	0x1,	0xF,	0x1,	0x2,	
+        0x40,	0xF,	0xF,	0x0,	0x0,	0x1,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+        0x0,	0x0,	0x0,	0x0,	0x0,	0x0,	
+    ]
+
+    # Hardware reset
+    def reset(self):
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20) 
+        epdconfig.digital_write(self.reset_pin, 0)
+        epdconfig.delay_ms(2)
+        epdconfig.digital_write(self.reset_pin, 1)
+        epdconfig.delay_ms(20)   
+
+    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 send_data2(self, data):
+        epdconfig.digital_write(self.dc_pin, 1)
+        epdconfig.digital_write(self.cs_pin, 0)
+        epdconfig.SPI.writebytes2(data)
+        epdconfig.digital_write(self.cs_pin, 1)
+
+    def ReadBusy(self):
+        logger.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(20)
+        logger.debug("e-Paper busy release")
+        
+    def SetLut(self, lut_vcom, lut_ww, lut_bw, lut_wb, lut_bb):
+        self.send_command(0x20)
+        for count in range(0, 42):
+            self.send_data(lut_vcom[count])
+
+        self.send_command(0x21)
+        for count in range(0, 42):
+            self.send_data(lut_ww[count])
+
+        self.send_command(0x22)
+        for count in range(0, 42):
+            self.send_data(lut_bw[count])
+
+        self.send_command(0x23)
+        for count in range(0, 42):
+            self.send_data(lut_wb[count])
+
+        self.send_command(0x24)
+        for count in range(0, 42):
+            self.send_data(lut_bb[count])
+
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+        # EPD hardware init start
+        self.reset()
+        
+        # self.send_command(0x06)     # btst
+        # self.send_data(0x17)
+        # self.send_data(0x17)
+        # self.send_data(0x28)        # If an exception is displayed, try using 0x38
+        # self.send_data(0x17)
+        
+        # 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(0x01);  # power setting
+        self.send_data(0x17);  # 1-0=11: internal power
+        self.send_data(self.Voltage_Frame_7IN5_V2[6]);  # VGH&VGL
+        self.send_data(self.Voltage_Frame_7IN5_V2[1]);  # VSH
+        self.send_data(self.Voltage_Frame_7IN5_V2[2]);  #  VSL
+        self.send_data(self.Voltage_Frame_7IN5_V2[3]);  #  VSHR
+        
+        self.send_command(0x82); # VCOM DC Setting
+        self.send_data(self.Voltage_Frame_7IN5_V2[4]);  # VCOM
+
+        self.send_command(0x06);  # Booster Setting
+        self.send_data(0x27);
+        self.send_data(0x27);
+        self.send_data(0x2F);
+        self.send_data(0x17);
+        
+        self.send_command(0x30);   # OSC Setting
+        self.send_data(self.Voltage_Frame_7IN5_V2[0]);  # 2-0=100: N=4  ; 5-3=111: M=7  ;  3C=50Hz     3A=100HZ
+
+        self.send_command(0x04) #POWER ON
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+
+        self.send_command(0X00)			#PANNEL SETTING
+        self.send_data(0x3F)   #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)
+
+        self.send_command(0x65);  # Resolution setting
+        self.send_data(0x00);
+        self.send_data(0x00); # 800*480
+        self.send_data(0x00);
+        self.send_data(0x00);
+
+        self.SetLut(self.LUT_VCOM_7IN5_V2, self.LUT_WW_7IN5_V2, self.LUT_BW_7IN5_V2, self.LUT_WB_7IN5_V2, self.LUT_BB_7IN5_V2)
+        # EPD hardware init end
+        return 0
+
+    def getbuffer(self, image):
+        img = image
+        imwidth, imheight = img.size
+        if(imwidth == self.width and imheight == self.height):
+            img = img.convert('1')
+        elif(imwidth == self.height and imheight == self.width):
+            # image has correct dimensions, but needs to be rotated
+            img = img.rotate(90, expand=True).convert('1')
+        else:
+            logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
+            # return a blank buffer
+            return [0x00] * (int(self.width/8) * self.height)
+
+        buf = bytearray(img.tobytes('raw'))
+        # The bytes need to be inverted, because in the PIL world 0=black and 1=white, but
+        # in the e-paper world 0=white and 1=black.
+        for i in range(len(buf)):
+            buf[i] ^= 0xFF
+        return buf
+
+    def display(self, image):
+        self.send_command(0x13)
+        self.send_data2(image)
+
+        self.send_command(0x12)
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+
+    def Clear(self):
+        buf = [0x00] * (int(self.width/8) * self.height)
+        self.send_command(0x10)
+        self.send_data2(buf)
+        self.send_command(0x13)
+        self.send_data2(buf)
+        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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 208 - 0
frontend/lib/waveshare_epd/epd7in5b_HD.py

@@ -0,0 +1,208 @@
+# *****************************************************************************
+# * | File        :	  epd7in5bc_HD.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2019-06-20
+# # | Info        :   python demo
+# -----------------------------------------------------------------------------
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documnetation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to  whom the Software is
+# furished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+
+import logging
+from . import epdconfig
+
+# Display resolution
+EPD_WIDTH       = 880
+EPD_HEIGHT      = 528
+
+logger = logging.getLogger(__name__)
+
+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):
+        logger.debug("e-Paper busy")
+        busy = epdconfig.digital_read(self.busy_pin)
+        while(busy == 1):
+            busy = epdconfig.digital_read(self.busy_pin)
+        epdconfig.delay_ms(200)
+            
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+        
+        self.send_command(0x12); 		  #SWRESET
+        self.ReadBusy();        #waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x46);  # Auto Write RAM
+        self.send_data(0xF7);
+        self.ReadBusy();        #waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x47);  # Auto Write RAM
+        self.send_data(0xF7);
+        self.ReadBusy();        #waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x0C);  # Soft start setting
+        self.send_data(0xAE);
+        self.send_data(0xC7);
+        self.send_data(0xC3);
+        self.send_data(0xC0);
+        self.send_data(0x40);   
+
+        self.send_command(0x01);  # Set MUX as 527
+        self.send_data(0xAF);
+        self.send_data(0x02);
+        self.send_data(0x01);
+
+        self.send_command(0x11);  # Data entry mode
+        self.send_data(0x01);
+
+        self.send_command(0x44);
+        self.send_data(0x00); # RAM x address start at 0
+        self.send_data(0x00);
+        self.send_data(0x6F); # RAM x address end at 36Fh -> 879
+        self.send_data(0x03);
+        self.send_command(0x45);
+        self.send_data(0xAF); # RAM y address start at 20Fh;
+        self.send_data(0x02);
+        self.send_data(0x00); # RAM y address end at 00h;
+        self.send_data(0x00);
+
+        self.send_command(0x3C); # VBD
+        self.send_data(0x01); # LUT1, for white
+
+        self.send_command(0x18);
+        self.send_data(0X80);
+        self.send_command(0x22);
+        self.send_data(0XB1);	#Load Temperature and waveform setting.
+        self.send_command(0x20);
+        self.ReadBusy();        #waiting for the electronic paper IC to release the idle signal
+
+        self.send_command(0x4E); 
+        self.send_data(0x00);
+        self.send_data(0x00);
+        self.send_command(0x4F); 
+        self.send_data(0xAF);
+        self.send_data(0x02);
+        
+        return 0
+
+    def getbuffer(self, image):
+        # logger.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()
+        logger.debug('imwidth = %d  imheight =  %d ',imwidth, imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.debug("Vertical")
+            for y in range(imheight):
+                for x in range(imwidth):
+                    newx = y
+                    newy = self.height - x - 1
+                    if pixels[x, y] == 0:
+                        buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
+        return buf
+
+    def display(self, imageblack, imagered):
+        self.send_command(0x4F); 
+        self.send_data(0xAf);
+        
+        self.send_command(0x24)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(imageblack[i]);
+        
+        
+        self.send_command(0x26)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(~imagered[i]);
+        
+        self.send_command(0x22);
+        self.send_data(0xC7);    #Load LUT from MCU(0x32)
+        self.send_command(0x20);
+        epdconfig.delay_ms(200);      #!!!The delay here is necessary, 200uS at least!!!     
+        self.ReadBusy();
+        
+    def Clear(self):
+        self.send_command(0x4F); 
+        self.send_data(0xAf);
+        
+        self.send_command(0x24)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0xff);
+        
+        
+        self.send_command(0x26)
+        for i in range(0, int(self.width * self.height / 8)):
+            self.send_data(0x00);
+        
+        self.send_command(0x22);
+        self.send_data(0xC7);    #Load LUT from MCU(0x32)
+        self.send_command(0x20);
+        epdconfig.delay_ms(200);      #!!!The delay here is necessary, 200uS at least!!!     
+        self.ReadBusy();
+
+    def sleep(self):
+        self.send_command(0x10);  	#deep sleep
+        self.send_data(0x01);
+        
+        epdconfig.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 192 - 0
frontend/lib/waveshare_epd/epd7in5b_V2.py

@@ -0,0 +1,192 @@
+# *****************************************************************************
+# * | File        :	  epd7in5b_V2.py
+# * | Author      :   Waveshare team
+# * | Function    :   Electronic paper driver
+# * | Info        :
+# *----------------
+# * | This version:   V4.2
+# * | Date        :   2022-01-08
+# # | 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
+
+logger = logging.getLogger(__name__)
+
+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 send_data2(self, data): #faster
+        epdconfig.digital_write(self.dc_pin, 1)
+        epdconfig.digital_write(self.cs_pin, 0)
+        epdconfig.SPI.writebytes2(data)
+        epdconfig.digital_write(self.cs_pin, 1)
+
+    def ReadBusy(self):
+        logger.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)
+        logger.debug("e-Paper busy release")
+        
+    def init(self):
+        if (epdconfig.module_init() != 0):
+            return -1
+            
+        self.reset()
+        
+        # self.send_command(0x06)     # btst
+        # self.send_data(0x17)
+        # self.send_data(0x17)
+        # self.send_data(0x38)        # If an exception is displayed, try using 0x38
+        # self.send_data(0x17)
+
+        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);
+
+        self.send_command(0x65);
+        self.send_data(0x00);
+        self.send_data(0x00);
+        self.send_data(0x00);
+        self.send_data(0x00);
+    
+        return 0
+
+    def getbuffer(self, image):
+        img = image
+        imwidth, imheight = img.size
+        if(imwidth == self.width and imheight == self.height):
+            img = img.convert('1')
+        elif(imwidth == self.height and imheight == self.width):
+            # image has correct dimensions, but needs to be rotated
+            img = img.rotate(90, expand=True).convert('1')
+        else:
+            logger.warning("Wrong image dimensions: must be " + str(self.width) + "x" + str(self.height))
+            # return a blank buffer
+            return [0x00] * (int(self.width/8) * self.height)
+
+        buf = bytearray(img.tobytes('raw'))
+        # The bytes need to be inverted, because in the PIL world 0=black and 1=white, but
+        # in the e-paper world 0=white and 1=black.
+        for i in range(len(buf)):
+            buf[i] ^= 0xFF
+        return buf
+
+    def display(self, imageblack, imagered):
+        self.send_command(0x10)
+        # The black bytes need to be inverted back from what getbuffer did
+        for i in range(len(imageblack)):
+            imageblack[i] ^= 0xFF
+        self.send_data2(imageblack)
+
+        self.send_command(0x13)
+        self.send_data2(imagered)
+        
+        self.send_command(0x12)
+        epdconfig.delay_ms(100)
+        self.ReadBusy()
+        
+    def Clear(self):
+        buf = [0x00] * (int(self.width/8) * self.height)
+        buf2 = [0xff] * (int(self.width/8) * self.height)
+        self.send_command(0x10)
+        self.send_data2(buf2)
+            
+        self.send_command(0x13)
+        self.send_data2(buf)
+                
+        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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 204 - 0
frontend/lib/waveshare_epd/epd7in5bc.py

@@ -0,0 +1,204 @@
+# *****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+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(5)
+        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):
+        logger.debug("e-Paper busy")
+        while(epdconfig.digital_read(self.busy_pin) == 0):      # 0: idle, 1: busy
+            epdconfig.delay_ms(100)
+        logger.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):
+        # logger.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()
+        logger.debug('imwidth = %d  imheight =  %d ',imwidth, imheight)
+        if(imwidth == self.width and imheight == self.height):
+            logger.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):
+            logger.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.delay_ms(2000)
+        epdconfig.module_exit()
+### END OF FILE ###
+

+ 160 - 0
frontend/lib/waveshare_epd/epdconfig.py

@@ -0,0 +1,160 @@
+# /*****************************************************************************
+# * | 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
+
+logger = logging.getLogger(__name__)
+
+
+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
+        self.SPI = spidev.SpiDev()
+
+    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 spi_writebyte2(self, data):
+        self.SPI.writebytes2(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)
+
+        # SPI device, bus = 0, device = 0
+        self.SPI.open(0, 0)
+        self.SPI.max_speed_hz = 4000000
+        self.SPI.mode = 0b00
+        return 0
+
+    def module_exit(self):
+        logger.debug("spi end")
+        self.SPI.close()
+
+        logger.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([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN])
+
+
+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):
+        logger.debug("spi end")
+        self.SPI.SYSFS_software_spi_end()
+
+        logger.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([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN])
+
+
+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 ###

BIN
frontend/lib/waveshare_epd/sysfs_gpio.so


BIN
frontend/lib/waveshare_epd/sysfs_software_spi.so


+ 41 - 0
frontend/main.py

@@ -0,0 +1,41 @@
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
+import sys
+import os
+
+libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'frontend/lib')
+
+if os.path.exists(libdir):
+    sys.path.append(libdir)
+
+import logging
+from waveshare_epd import epd7in5_V2
+import time
+from PIL import Image
+import traceback
+
+logging.basicConfig(level=logging.DEBUG)
+
+try:
+    logging.info("Updating Alba screen...")
+    epd = epd7in5_V2.EPD()
+    
+    logging.info("Init and clear...")
+    epd.init()
+    epd.Clear()
+
+    logging.info("Read the BMP file...")
+    Himage = Image.open('/home/andrea/src/alba/frontend/screen.bmp')
+    epd.display(epd.getbuffer(Himage))
+    time.sleep(2)
+
+    logging.info("Go to sleep...")
+    epd.sleep()
+    
+except IOError as e:
+    logging.info(e)
+    
+except KeyboardInterrupt:    
+    logging.info("ctrl + c:")
+    epd7in5_V2.epdconfig.module_exit()
+    exit()

+ 7 - 0
frontend/update.bash

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+BASE_PATH=/home/andrea/src/alba/frontend/
+
+wget -O $BASE_PATH/screen.bmp https://alba-backend.andreafazzi.eu/
+/usr/bin/python $BASE_PATH/main.py
+