Browse Source

Merge branch 'master' of ssh://git.andreafazzi.eu:10022/andrea/alba

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

+ 2 - 0
frontend/.gitignore

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

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


+ 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,