From 0c5c6058a555ef8fa9e1025ce1f30d18262ded33 Mon Sep 17 00:00:00 2001 From: Jeroen Oudshoorn Date: Fri, 19 Jan 2024 07:28:31 +0100 Subject: [PATCH] Added display waveshare2in13g --- pwnagotchi/ui/display.py | 3 + pwnagotchi/ui/hw/__init__.py | 1 + .../ui/hw/libs/waveshare/v2in13g/epd2in13g.py | 242 ++++++++++++++++++ pwnagotchi/ui/hw/waveshare2in13g.py | 46 ++++ pwnagotchi/utils.py | 3 + 5 files changed, 295 insertions(+) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v2in13g/epd2in13g.py create mode 100644 pwnagotchi/ui/hw/waveshare2in13g.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index bbc7bd32..cd7a56b7 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -103,6 +103,9 @@ class Display(View): def is_waveshare2in13d(self): return self._implementation.name == 'waveshare2in13d' + def is_waveshare2in13g(self): + return self._implementation.name == 'waveshare2in13g' + def is_waveshare2in23g(self): return self._implementation.name == 'waveshare2in23g' diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 11ec7bf3..0d276a46 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -16,6 +16,7 @@ from pwnagotchi.ui.hw.waveshare1in44lcd import Waveshare144lcd from pwnagotchi.ui.hw.waveshare1in54b import Waveshare154inchb from pwnagotchi.ui.hw.waveshare2in13bc import Waveshare213bc from pwnagotchi.ui.hw.waveshare2in13d import Waveshare213d +from pwnagotchi.ui.hw.waveshare2in13g import Waveshare2in13g from pwnagotchi.ui.hw.waveshare2in13b_V4 import Waveshare213bV4 from pwnagotchi.ui.hw.waveshare3in5lcd import Waveshare35lcd from pwnagotchi.ui.hw.spotpear24in import Spotpear24inch diff --git a/pwnagotchi/ui/hw/libs/waveshare/v2in13g/epd2in13g.py b/pwnagotchi/ui/hw/libs/waveshare/v2in13g/epd2in13g.py new file mode 100644 index 00000000..9a186a1b --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v2in13g/epd2in13g.py @@ -0,0 +1,242 @@ +# ***************************************************************************** +# * | File : epd2in13g.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2023-05-29 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# ******************************************************************************/ +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import logging +from .. import epdconfig + +import PIL +from PIL import Image +import io + +# Display resolution +EPD_WIDTH = 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 + self.BLACK = 0x000000 # 00 BGR + self.WHITE = 0xffffff # 01 + self.YELLOW = 0x00ffff # 10 + self.RED = 0x0000ff # 11 + self.Gate_BITS = EPD_HEIGHT + if self.width < 128: + self.Source_BITS = 128 + else: + self.Source_BITS = self.width + + # 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 H") + epdconfig.delay_ms(100) + while (epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy + epdconfig.delay_ms(5) + logger.debug("e-Paper busy release") + + def SetWindow(self): + self.send_command(0x61) # 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(int(self.Source_BITS / 256)) + self.send_data(self.Source_BITS % 256) + self.send_data(int(self.Gate_BITS / 256)) + self.send_data(self.Gate_BITS % 256) + + def TurnOnDisplay(self): + self.send_command(0x12) # DISPLAY_REFRESH + self.send_data(0X00) + self.ReadBusy() + + def init(self): + if (epdconfig.module_init() != 0): + return -1 + # EPD hardware init start + + self.reset() + + self.ReadBusy() + self.send_command(0x4D) + self.send_data(0x78) + + self.send_command(0x00) + self.send_data(0x0F) + self.send_data(0x29) + + self.send_command(0x01) + self.send_data(0x07) + self.send_data(0x00) + + self.send_command(0x03) + self.send_data(0x10) + self.send_data(0x54) + self.send_data(0x44) + + self.send_command(0x06) + self.send_data(0x05) + self.send_data(0x00) + self.send_data(0x3F) + self.send_data(0x0A) + self.send_data(0x25) + self.send_data(0x12) + self.send_data(0x1A) + + self.send_command(0x50) + self.send_data(0x37) + + self.send_command(0x60) + self.send_data(0x02) + self.send_data(0x02) + + self.SetWindow() + + self.send_command(0xE7) + self.send_data(0x1C) + + self.send_command(0xE3) + self.send_data(0x22) + + self.send_command(0xB4) + self.send_data(0xD0) + self.send_command(0xB5) + self.send_data(0x03) + + self.send_command(0xE9) + self.send_data(0x01) + + self.send_command(0x30) + self.send_data(0x08) + + self.send_command(0x04) + self.ReadBusy() + return 0 + + def getbuffer(self, image): + # Create a pallette with the 4 colors supported by the panel + pal_image = Image.new("P", (1, 1)) + pal_image.putpalette((0, 0, 0, 255, 255, 255, 255, 255, 0, 255, 0, 0) + (0, 0, 0) * 252) + + # Check if we need to rotate the image + imwidth, imheight = image.size + if (imwidth == self.width and imheight == self.height): + image_temp = image + elif (imwidth == self.height and imheight == self.width): + image_temp = image.rotate(90, expand=True) + else: + logger.warning( + "Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, self.width, self.height)) + + # Convert the soruce image to the 4 colors, dithering if needed + image_4color = image_temp.convert("RGB").quantize(palette=pal_image) + buf_4color = bytearray(image_4color.tobytes('raw')) + + # into a single byte to transfer to the panel + if self.width % 4 == 0: + Width = self.width // 4 + else: + Width = self.width // 4 + 1 + Height = self.height + buf = [0x00] * int(Width * Height) + idx = 0 + for j in range(0, Height): + for i in range(0, Width): + if i == Width - 1: + buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx + 1] << 4) + idx = idx + 2 + else: + buf[i + j * Width] = (buf_4color[idx] << 6) + (buf_4color[idx + 1] << 4) + ( + buf_4color[idx + 2] << 2) + buf_4color[idx + 3] + idx = idx + 4 + return buf + + def display(self, image): + if self.width % 4 == 0: + Width = self.width // 4 + else: + Width = self.width // 4 + 1 + Height = self.height + + self.send_command(0x10) + for j in range(0, Height): + for i in range(0, self.Source_BITS // 4): + if i < 31: + self.send_data(image[i + j * Width]) + else: + self.send_data(0x00) + + self.TurnOnDisplay() + + def Clear(self, color=0x55): + Width = self.Source_BITS // 4 + Height = self.height + + self.send_command(0x10) + for j in range(0, Height): + for i in range(0, Width): + self.send_data(color) + self.TurnOnDisplay() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + epdconfig.delay_ms(100) + + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/waveshare2in13g.py b/pwnagotchi/ui/hw/waveshare2in13g.py new file mode 100644 index 00000000..f74dcb79 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare2in13g.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare2in13g(DisplayImpl): + def __init__(self, config): + super(Waveshare2in13g, self).__init__(config, 'waveshare2in13g') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35, 25, 9) + self._layout['width'] = 250 + self._layout['height'] = 122 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (185, 0) + self._layout['line1'] = [0, 14, 250, 14] + self._layout['line2'] = [0, 108, 250, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (225, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.status_font(fonts.Medium), + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v2in13g display") + from pwnagotchi.ui.hw.libs.waveshare.v2in13g.epd2in13g import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.displayPartial(buf) + + def clear(self): + self._display.Clear() diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index b58f59f1..3ab6091d 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -306,6 +306,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('ws_213d', 'ws213d', 'waveshare2in13d', 'waveshare_213d', 'waveshare213d'): config['ui']['display']['type'] = 'waveshare2in13d' + elif config['ui']['display']['type'] in ('ws_213g', 'waveshare2in13g', 'waveshare213g', 'ws213g', 'waveshare_213g'): + config['ui']['display']['type'] = 'waveshare2in13g' + elif config['ui']['display']['type'] in ('ws_213bc', 'ws213bc', 'waveshare2in13bc', 'waveshare_213bc', 'waveshare213bc'): config['ui']['display']['type'] = 'waveshare2in13bc'