diff --git a/controller/display/e-paper/lib/waveshare_epd/epd2in9_V2.py b/controller/display/e-paper/lib/waveshare_epd/epd2in9_V2.py index d84510b3c..b10451631 100644 --- a/controller/display/e-paper/lib/waveshare_epd/epd2in9_V2.py +++ b/controller/display/e-paper/lib/waveshare_epd/epd2in9_V2.py @@ -1,5 +1,5 @@ # ***************************************************************************** -# * | File : epd2in9_V2.py +# * | File : epd2in9_V2.py # * | Author : Waveshare team # * | Function : Electronic paper driver # * | Info : @@ -52,14 +52,14 @@ def __init__(self): self.GRAY2 = GRAY2 self.GRAY3 = GRAY3 #gray self.GRAY4 = GRAY4 #Blackest - + WF_PARTIAL_2IN9 = [ 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, - 0x0A,0x0,0x0,0x0,0x0,0x0,0x1, + 0x0A,0x0,0x0,0x0,0x0,0x0,0x1, 0x1,0x0,0x0,0x0,0x0,0x0,0x0, 0x1,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0, @@ -75,79 +75,79 @@ def __init__(self): 0x22,0x17,0x41,0xB0,0x32,0x36, ] - WS_20_30 = [ - 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, - 0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, - 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, - 0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x2, - 0xA, 0xA, 0x0, 0xA, 0xA, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0, 0x0, 0x0, - 0x22, 0x17, 0x41, 0x0, 0x32, 0x36 + WS_20_30 = [ + 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, + 0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, + 0x80, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, + 0x10, 0x66, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14, 0x8, 0x0, 0x0, 0x0, 0x0, 0x2, + 0xA, 0xA, 0x0, 0xA, 0xA, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x14, 0x8, 0x0, 0x1, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0, 0x0, 0x0, + 0x22, 0x17, 0x41, 0x0, 0x32, 0x36 + ] + + Gray4 = [ + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x60, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, + 0x1E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x22, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00, + 0x22, 0x17, 0x41, 0xAE, 0x32, 0x28, ] - Gray4 = [ - 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x28, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x2A, 0x60, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, - 0x1E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x22, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00, - 0x22, 0x17, 0x41, 0xAE, 0x32, 0x28, - ] - - WF_FULL = [ - 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x42, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00, - 0x22, 0x17, 0x41, 0xAE, 0x32, 0x38] + WF_FULL = [ + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x19, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x42, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00, + 0x22, 0x17, 0x41, 0xAE, 0x32, 0x38] # Hardware reset def reset(self): epdconfig.digital_write(self.reset_pin, 1) - epdconfig.delay_ms(50) + epdconfig.delay_ms(50) epdconfig.digital_write(self.reset_pin, 0) epdconfig.delay_ms(2) epdconfig.digital_write(self.reset_pin, 1) - epdconfig.delay_ms(50) + epdconfig.delay_ms(50) def send_command(self, command): epdconfig.digital_write(self.dc_pin, 0) @@ -161,18 +161,18 @@ def send_data(self, data): epdconfig.spi_writebyte([data]) epdconfig.digital_write(self.cs_pin, 1) - # send a lot of data + # send a lot of data def send_data2(self, data): epdconfig.digital_write(self.dc_pin, 1) epdconfig.digital_write(self.cs_pin, 0) epdconfig.spi_writebyte2(data) epdconfig.digital_write(self.cs_pin, 1) - + def ReadBusy(self): logger.debug("e-Paper busy") while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy - epdconfig.delay_ms(10) - logger.debug("e-Paper busy release") + epdconfig.delay_ms(10) + logger.debug("e-Paper busy release") def TurnOnDisplay(self): self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2 @@ -196,13 +196,13 @@ def SetLut(self, lut): self.lut(lut) self.send_command(0x3f) self.send_data(lut[153]) - self.send_command(0x03); # gate voltage + 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_command(0x04); # source voltage + self.send_data(lut[155]) # VSH + self.send_data(lut[156]) # VSH2 + self.send_data(lut[157]) # VSL + self.send_command(0x2c); # VCOM self.send_data(lut[158]) def SetWindow(self, x_start, y_start, x_end, y_end): @@ -220,27 +220,27 @@ def SetCursor(self, x, y): self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER # x point must be the multiple of 8 or the last 3 bits will be ignored self.send_data(x & 0xFF) - + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER self.send_data(y & 0xFF) self.send_data((y >> 8) & 0xFF) - + def init(self): if (epdconfig.module_init() != 0): return -1 - # EPD hardware init start + # EPD hardware init start self.reset() self.ReadBusy() self.send_command(0x12) #SWRESET - self.ReadBusy() + self.ReadBusy() - self.send_command(0x01) #Driver output control + self.send_command(0x01) #Driver output control self.send_data(0x27) self.send_data(0x01) self.send_data(0x00) - - self.send_command(0x11) #data entry mode + + self.send_command(0x11) #data entry mode self.send_data(0x03) self.SetWindow(0, 0, self.width-1, self.height-1) @@ -248,48 +248,48 @@ def init(self): self.send_command(0x21) # Display update control self.send_data(0x00) self.send_data(0x80) - + self.SetCursor(0, 0) self.ReadBusy() self.SetLut(self.WS_20_30) # EPD hardware init end return 0 - + def init_Fast(self): if (epdconfig.module_init() != 0): return -1 - # EPD hardware init start + # EPD hardware init start self.reset() self.ReadBusy() self.send_command(0x12) #SWRESET - self.ReadBusy() + self.ReadBusy() - self.send_command(0x01) #Driver output control + self.send_command(0x01) #Driver output control self.send_data(0x27) self.send_data(0x01) self.send_data(0x00) - - self.send_command(0x11) #data entry mode + + self.send_command(0x11) #data entry mode self.send_data(0x03) self.SetWindow(0, 0, self.width-1, self.height-1) - self.send_command(0x3C) + self.send_command(0x3C) self.send_data(0x05) self.send_command(0x21) # Display update control self.send_data(0x00) self.send_data(0x80) - + self.SetCursor(0, 0) self.ReadBusy() self.SetLut(self.WF_FULL) # EPD hardware init end return 0 - + def Init_4Gray(self): if (epdconfig.module_init() != 0): return -1 @@ -298,21 +298,21 @@ def Init_4Gray(self): self.ReadBusy() self.send_command(0x12) #SWRESET - self.ReadBusy() + self.ReadBusy() - self.send_command(0x01) #Driver output control + self.send_command(0x01) #Driver output control self.send_data(0x27) self.send_data(0x01) self.send_data(0x00) - - self.send_command(0x11) #data entry mode + + self.send_command(0x11) #data entry mode self.send_data(0x03) self.SetWindow(8, 0, self.width, self.height-1) self.send_command(0x3C) self.send_data(0x04) - + self.SetCursor(1, 0) self.ReadBusy() @@ -343,7 +343,7 @@ def getbuffer(self, image): if pixels[x, y] == 0: buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8)) return buf - + def getbuffer_4Gray(self, image): # logger.debug("bufsiz = ",int(self.width/8) * self.height) buf = [0xFF] * (int(self.width / 4) * self.height) @@ -364,7 +364,7 @@ def getbuffer_4Gray(self, image): i= i+1 if(i%4 == 0): buf[int((x + (y * self.width))/4)] = ((pixels[x-3, y]&0xc0) | (pixels[x-2, y]&0xc0)>>2 | (pixels[x-1, y]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) - + elif(imwidth == self.height and imheight == self.width): logger.debug("Horizontal") for x in range(imwidth): @@ -377,28 +377,30 @@ def getbuffer_4Gray(self, image): pixels[x, y] = 0x40 i= i+1 if(i%4 == 0): - buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) + buf[int((newx + (newy * self.width))/4)] = ((pixels[x, y-3]&0xc0) | (pixels[x, y-2]&0xc0)>>2 | (pixels[x, y-1]&0xc0)>>4 | (pixels[x, y]&0xc0)>>6) return buf def display(self, image): if (image == None): - return + return self.send_command(0x24) # WRITE_RAM - self.send_data2(image) + self.send_data2(image) self.TurnOnDisplay() def display_Base(self, image): if (image == None): - return - + return + self.send_command(0x24) # WRITE_RAM self.send_data2(image) - + self.send_command(0x26) # WRITE_RAM - self.send_data2(image) - + self.send_data2(image) + self.TurnOnDisplay() + self._prev_image = list(image) + def display_4Gray(self, image): self.send_command(0x24) for i in range(0, 4736): @@ -406,39 +408,39 @@ def display_4Gray(self, image): for j in range(0, 2): temp1 = image[i*2+j] for k in range(0, 2): - temp2 = temp1&0xC0 + temp2 = temp1&0xC0 if(temp2 == 0xC0): temp3 |= 0x00 elif(temp2 == 0x00): - temp3 |= 0x01 - elif(temp2 == 0x80): - temp3 |= 0x01 + temp3 |= 0x01 + elif(temp2 == 0x80): + temp3 |= 0x01 else: #0x40 - temp3 |= 0x00 - temp3 <<= 1 - + temp3 |= 0x00 + temp3 <<= 1 + temp1 <<= 2 - temp2 = temp1&0xC0 - if(temp2 == 0xC0): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): temp3 |= 0x00 - elif(temp2 == 0x00): + elif(temp2 == 0x00): temp3 |= 0x01 elif(temp2 == 0x80): temp3 |= 0x01 else : #0x40 - temp3 |= 0x00 - if(j!=1 or k!=1): + temp3 |= 0x00 + if(j!=1 or k!=1): temp3 <<= 1 temp1 <<= 2 self.send_data(temp3) - - self.send_command(0x26) + + self.send_command(0x26) for i in range(0, 4736): temp3=0 for j in range(0, 2): temp1 = image[i*2+j] for k in range(0, 2): - temp2 = temp1&0xC0 + temp2 = temp1&0xC0 if(temp2 == 0xC0): temp3 |= 0x00 elif(temp2 == 0x00): @@ -446,35 +448,35 @@ def display_4Gray(self, image): elif(temp2 == 0x80): temp3 |= 0x00 else: #0x40 - temp3 |= 0x01 - temp3 <<= 1 - + temp3 |= 0x01 + temp3 <<= 1 + temp1 <<= 2 - temp2 = temp1&0xC0 - if(temp2 == 0xC0): + temp2 = temp1&0xC0 + if(temp2 == 0xC0): temp3 |= 0x00 - elif(temp2 == 0x00): + elif(temp2 == 0x00): temp3 |= 0x01 elif(temp2 == 0x80): - temp3 |= 0x00 + temp3 |= 0x00 else: #0x40 - temp3 |= 0x01 - if(j!=1 or k!=1): + temp3 |= 0x01 + if(j!=1 or k!=1): temp3 <<= 1 temp1 <<= 2 self.send_data(temp3) self.TurnOnDisplay() - + def display_Partial(self, image): if (image == None): return - + epdconfig.digital_write(self.reset_pin, 0) epdconfig.delay_ms(2) epdconfig.digital_write(self.reset_pin, 1) - epdconfig.delay_ms(2) - + epdconfig.delay_ms(2) + self.SetLut(self.WF_PARTIAL_2IN9) self.send_command(0x37) self.send_data(0x00) @@ -485,24 +487,37 @@ def display_Partial(self, image): self.send_data(0x40) self.send_data(0x00) 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_command(0x22) self.send_data(0xC0) self.send_command(0x20) self.ReadBusy() self.SetWindow(0, 0, self.width - 1, self.height - 1) + + # Write previous image to "old" buffer so controller knows current screen state self.SetCursor(0, 0) - - self.send_command(0x24) # WRITE_RAM - self.send_data2(image) + self.send_command(0x26) # WRITE_RAM (old/previous) + if hasattr(self, '_prev_image') and self._prev_image is not None: + self.send_data2(self._prev_image) + else: + self.send_data2(image) + + # Write new image to "new" buffer + self.SetCursor(0, 0) + self.send_command(0x24) # WRITE_RAM (new/current) + self.send_data2(image) + self.TurnOnDisplay_Partial() + # Remember this image as the baseline for the next partial + self._prev_image = list(image) + def Clear(self, color=0xFF): if self.width%8 == 0: @@ -511,17 +526,16 @@ def Clear(self, color=0xFF): linewidth = int(self.width/8) + 1 self.send_command(0x24) # WRITE_RAM - self.send_data2([color] * int(self.height * linewidth)) + self.send_data2([color] * int(self.height * linewidth)) self.TurnOnDisplay() self.send_command(0x26) # WRITE_RAM - self.send_data2([color] * int(self.height * linewidth)) + self.send_data2([color] * int(self.height * linewidth)) 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 ### - diff --git a/controller/display/main.py b/controller/display/main.py index 9605b2cb6..e413dd767 100644 --- a/controller/display/main.py +++ b/controller/display/main.py @@ -8,7 +8,7 @@ from pprint import pprint import aiomqtt # type: ignore -from PIL import Image, ImageDraw, ImageFont # type: ignore +from PIL import Image, ImageDraw, ImageFont, ImageOps # type: ignore import helpers @@ -37,7 +37,6 @@ if os.path.exists(libdir): sys.path.append(libdir) -machine_name = None epd = None fontsmall = ImageFont.truetype(os.path.join(picdir, "Font.ttc"), 18) fontnormal = ImageFont.truetype(os.path.join(picdir, "Font.ttc"), 19) @@ -47,96 +46,161 @@ epd2in9_V2 = None logo = Image.open(os.path.join(dirname, "fairscope.bmp")) +logo_inverted = ImageOps.invert(logo.convert("L")).convert("1") width = None height = None -BAR_HEIGHT = 26 +BAR_HEIGHT = 30 -def drawStatus(status=""): +def drawURL(url): assert draw is not None assert width is not None assert height is not None - # Black bar across the bottom - draw.rectangle((0, height - BAR_HEIGHT, width, height), fill=0) - # White text centered in the bar + # White bar across the bottom, black text (partial-refresh friendly) + draw.rectangle((0, height - BAR_HEIGHT, width, height), fill=255) draw.text( - (width // 2, height - BAR_HEIGHT // 2), text=status, anchor="mm", font=fontnormal, fill=255 + (width // 2, height - BAR_HEIGHT // 2), text=url, anchor="mm", font=fontnormal, fill=0 ) -def drawMachineName(machine_name): +def drawHostname(hostname): assert width is not None assert height is not None assert draw is not None - # Black bar across the top - draw.rectangle((0, 0, width, BAR_HEIGHT), fill=0) - # White text centered in the bar - draw.text( - (width // 2, BAR_HEIGHT // 2 + 2), text=machine_name, anchor="mm", font=fontbig, fill=255 - ) + # White bar across the top, black text (partial-refresh friendly) + draw.rectangle((0, 0, width, BAR_HEIGHT), fill=255) + draw.text((width // 2, BAR_HEIGHT // 2 + 2), text=hostname, anchor="mm", font=fontbig, fill=0) def drawBrand(): assert width is not None assert height is not None assert image is not None - # Paste logo centered in the middle white area (between the two bars) + assert draw is not None + # Black center area between the white bars middle_top = BAR_HEIGHT middle_bottom = height - BAR_HEIGHT + draw.rectangle((0, middle_top, width, middle_bottom), fill=0) + # Paste inverted logo (white on black) centered middle_h = middle_bottom - middle_top - x = (width - logo.width) // 2 - y = middle_top + (middle_h - logo.height) // 2 - image.paste(logo, (x, y)) + x = (width - logo_inverted.width) // 2 + y = middle_top + (middle_h - logo_inverted.height) // 2 + image.paste(logo_inverted, (x, y)) -def render(status=""): +def init_display(url="", hostname=""): + """Full refresh to establish a clean baseline. Used once at startup.""" assert epd is not None assert width is not None assert height is not None global image, draw - if image is None or draw is None: - image = Image.new("1", (width, height), 255) - draw = ImageDraw.Draw(image) + image = Image.new("1", (width, height), 255) + draw = ImageDraw.Draw(image) - # clear screen - # # TODO: only clear relevant area ? - draw.rectangle((0, 0, height, width), fill=255) - - # top black bar with machine_name - drawMachineName(machine_name) - # center logo + drawHostname(hostname) drawBrand() - # bottom black bar with status - drawStatus(status) + drawURL(url) epd.init() epd.Clear(0xFF) + epd.display_Base(epd.getbuffer(image)) + epd.sleep() + + +def update_url(url): + """Partial refresh to update only the bottom bar. No full screen flash.""" + assert epd is not None + assert draw is not None + + drawURL(url) + + epd.init() + epd.display_Partial(epd.getbuffer(image)) + epd.sleep() + + +def update_hostname(hostname): + """Partial refresh to update only the top bar. No full screen flash.""" + assert epd is not None + assert draw is not None + + drawHostname(hostname) + epd.init() + epd.display_Partial(epd.getbuffer(image)) + epd.sleep() + + +def render(url="", hostname=""): + """Partial refresh to update both bars. No full screen flash.""" + assert epd is not None + assert draw is not None + assert width is not None + assert height is not None + + drawHostname(hostname) + drawURL(url) + + epd.init() epd.display_Partial(epd.getbuffer(image)) epd.sleep() async def configure(config): - status = config.get("status", "") - render(status) + url = config.get("url", "") + machine_name = config.get("machine-name", "") + render(url, machine_name) + + +def render_off(hostname=""): + """Full refresh with inverted colors to indicate power off.""" + assert epd is not None + assert draw is not None + assert width is not None + assert height is not None + assert image is not None + + # Black bar across the top, white hostname + draw.rectangle((0, 0, width, BAR_HEIGHT), fill=0) + draw.text((width // 2, BAR_HEIGHT // 2 + 2), text=hostname, anchor="mm", font=fontbig, fill=255) + + # White center with black logo (original, non-inverted) + middle_top = BAR_HEIGHT + middle_bottom = height - BAR_HEIGHT + draw.rectangle((0, middle_top, width, middle_bottom), fill=255) + middle_h = middle_bottom - middle_top + x = (width - logo.width) // 2 + y = middle_top + (middle_h - logo.height) // 2 + image.paste(logo, (x, y)) + + # Black bar across the bottom, white "OFF" + draw.rectangle((0, height - BAR_HEIGHT, width, height), fill=0) + draw.text( + (width // 2, height - BAR_HEIGHT // 2), text="OFF", anchor="mm", font=fontnormal, fill=255 + ) + + epd.init() + epd.display_Base(epd.getbuffer(image)) + epd.sleep() async def clear(): assert epd is not None assert width is not None assert height is not None - # not functional + + global image, draw + image = Image.new("1", (width, height), 255) + draw = ImageDraw.Draw(image) + epd.init() epd.Clear(0xFF) + epd.display_Base(epd.getbuffer(image)) epd.sleep() - # image = Image.new("1", (width, height), 255) - # draw = ImageDraw.Draw(image) - # draw.rectangle((0, 0, height, width), fill=255) - # epd.display_Partial(epd.getbuffer(image)) async def start() -> None: @@ -144,7 +208,7 @@ async def start() -> None: if (await helpers.get_hat_version()) != 3.3: sys.exit() - global epd, epd2in9_V2, width, height, machine_name + global epd, epd2in9_V2, width, height from waveshare_epd import epd2in9_V2 # type: ignore epd = epd2in9_V2.EPD() @@ -152,9 +216,9 @@ async def start() -> None: width = epd.height height = epd.width + url = "http://192.168.4.1" machine_name = helpers.get_machine_name() - - render(status="http://192.168.4.1") + init_display(url=url, hostname=machine_name) global client client = aiomqtt.Client(hostname="localhost", port=1883, protocol=aiomqtt.ProtocolVersion.V5) @@ -191,7 +255,8 @@ async def handle_action(action: str, payload) -> None: async def stop() -> None: if epd is not None: - render(status="OFF") + machine_name = helpers.get_machine_name() + render_off(hostname=machine_name) loop.stop()