diff --git a/.idea/misc.xml b/.idea/misc.xml
index 6a0f074b..d0f22c9f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/pwnagotchi.iml b/.idea/pwnagotchi.iml
index ec511368..b258c893 100644
--- a/.idea/pwnagotchi.iml
+++ b/.idea/pwnagotchi.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py
index 2507b623..65d633e1 100644
--- a/pwnagotchi/plugins/default/auto-update.py
+++ b/pwnagotchi/plugins/default/auto-update.py
@@ -131,7 +131,7 @@ def install(display, update):
logging.info("[update] stopping %s ..." % update['service'])
os.system("service %s stop" % update['service'])
os.system("mv %s %s" % (source_path, dest_path))
- os.system("chmod +x /usr/local/bin/%s" % name)
+ os.chmod("/usr/local/bin/%s" % name, 0o755)
logging.info("[update] restarting %s ..." % update['service'])
os.system("service %s start" % update['service'])
else:
diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py
index f4c47648..6c6a09c3 100644
--- a/pwnagotchi/ui/display.py
+++ b/pwnagotchi/ui/display.py
@@ -40,6 +40,9 @@ class Display(View):
def is_waveshare_v3(self):
return self._implementation.name == 'waveshare_3'
+ def is_waveshare_v4(self):
+ return self._implementation.name == 'waveshare_4'
+
def is_waveshare27inch(self):
return self._implementation.name == 'waveshare27inch'
diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py
index e1045681..6968f025 100644
--- a/pwnagotchi/ui/hw/__init__.py
+++ b/pwnagotchi/ui/hw/__init__.py
@@ -7,6 +7,7 @@ from pwnagotchi.ui.hw.dfrobot2 import DFRobotV2
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
from pwnagotchi.ui.hw.waveshare3 import WaveshareV3
+from pwnagotchi.ui.hw.waveshare4 import WaveshareV4
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
from pwnagotchi.ui.hw.waveshare27inchV2 import Waveshare27inchV2
from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
@@ -48,6 +49,9 @@ def display_for(config):
elif config['ui']['display']['type'] == 'waveshare_3':
return WaveshareV3(config)
+ elif config['ui']['display']['type'] == 'waveshare_4':
+ return WaveshareV4(config)
+
elif config['ui']['display']['type'] == 'waveshare27inch':
return Waveshare27inch(config)
diff --git a/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py b/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py
new file mode 100644
index 00000000..a446e4cd
--- /dev/null
+++ b/pwnagotchi/ui/hw/libs/waveshare/v4/epd2in13_V4.py
@@ -0,0 +1,349 @@
+# *****************************************************************************
+# * | File : epd2in13_V4.py
+# * | Author : Waveshare team
+# * | Function : Electronic paper driver
+# * | Info :
+# *----------------
+# * | This version: V1.0
+# * | Date : 2023-06-25
+# # | 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 = 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
+
+ '''
+ 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)
+
+ # 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)
+
+ '''
+ 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(0xf7)
+ self.send_command(0x20) # Activate Display Update Sequence
+ self.ReadBusy()
+
+ '''
+ function : Turn On Display Fast
+ parameter:
+ '''
+ def TurnOnDisplay_Fast(self):
+ self.send_command(0x22) # Display Update Control
+ self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf
+ 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(0xff) # fast:0x0c, quality:0x0f, 0xcf
+ self.send_command(0x20) # Activate Display Update Sequence
+ self.ReadBusy()
+
+
+ '''
+ 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()
+
+ return 0
+
+ '''
+ function : Initialize the e-Paper fast register
+ parameter:
+ '''
+ def init_fast(self):
+ if (epdconfig.module_init() != 0):
+ return -1
+ # EPD hardware init start
+ self.reset()
+
+ self.send_command(0x12) #SWRESET
+ self.ReadBusy()
+
+ self.send_command(0x18) # Read built-in temperature sensor
+ self.send_command(0x80)
+
+ 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(0x22) # Load temperature value
+ self.send_data(0xB1)
+ self.send_command(0x20)
+ self.ReadBusy()
+
+ self.send_command(0x1A) # Write to temperature register
+ self.send_data(0x64)
+ self.send_data(0x00)
+
+ self.send_command(0x22) # Load temperature value
+ self.send_data(0x91)
+ self.send_command(0x20)
+ self.ReadBusy()
+
+ 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):
+ self.send_command(0x24)
+ self.send_data2(image)
+ self.TurnOnDisplay()
+
+ '''
+ function : Sends the image buffer in RAM to e-Paper and fast displays
+ parameter:
+ image : Image data
+ '''
+ def display_fast(self, image):
+ self.send_command(0x24)
+ self.send_data2(image)
+ self.TurnOnDisplay_Fast()
+ '''
+ function : Sends the image buffer in RAM to e-Paper and partial refresh
+ parameter:
+ image : Image data
+ '''
+ def displayPartial(self, image):
+ epdconfig.digital_write(self.reset_pin, 0)
+ epdconfig.delay_ms(1)
+ epdconfig.digital_write(self.reset_pin, 1)
+
+ self.send_command(0x3C) # BorderWavefrom
+ self.send_data(0x80)
+
+ 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(0x24) # WRITE_RAM
+ self.send_data2(image)
+ self.TurnOnDisplayPart()
+
+ '''
+ function : Refresh a base image
+ parameter:
+ image : Image data
+ '''
+ def displayPartBaseImage(self, image):
+ self.send_command(0x24)
+ self.send_data2(image)
+
+ self.send_command(0x26)
+ self.send_data2(image)
+ self.TurnOnDisplay()
+
+ '''
+ function : Clear screen
+ parameter:
+ '''
+ def Clear(self, color=0xFF):
+ if self.width%8 == 0:
+ linewidth = int(self.width/8)
+ else:
+ linewidth = int(self.width/8) + 1
+ # logger.debug(linewidth)
+
+ self.send_command(0x24)
+ self.send_data2([color] * int(self.height * linewidth))
+ 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 ###
\ No newline at end of file
diff --git a/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py
new file mode 100644
index 00000000..0c134192
--- /dev/null
+++ b/pwnagotchi/ui/hw/libs/waveshare/v4/epdconfig.py
@@ -0,0 +1,243 @@
+# /*****************************************************************************
+# * | File : epdconfig.py
+# * | Author : Waveshare team
+# * | Function : Hardware underlying interface
+# * | Info :
+# *----------------
+# * | This version: V1.2
+# * | Date : 2022-10-29
+# * | Info :
+# ******************************************************************************
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documnetation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+import os
+import logging
+import sys
+import time
+
+logger = logging.getLogger(__name__)
+
+
+class RaspberryPi:
+ # Pin definition
+ RST_PIN = 17
+ DC_PIN = 25
+ CS_PIN = 8
+ BUSY_PIN = 24
+ PWR_PIN = 18
+
+ def __init__(self):
+ import spidev
+ import RPi.GPIO
+
+ self.GPIO = RPi.GPIO
+ self.SPI = spidev.SpiDev()
+
+ def digital_write(self, pin, value):
+ self.GPIO.output(pin, value)
+
+ def digital_read(self, pin):
+ return self.GPIO.input(pin)
+
+ def delay_ms(self, delaytime):
+ time.sleep(delaytime / 1000.0)
+
+ def spi_writebyte(self, data):
+ self.SPI.writebytes(data)
+
+ def spi_writebyte2(self, data):
+ self.SPI.writebytes2(data)
+
+ def module_init(self):
+ self.GPIO.setmode(self.GPIO.BCM)
+ self.GPIO.setwarnings(False)
+ self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
+
+ self.GPIO.output(self.PWR_PIN, 1)
+
+ # SPI device, bus = 0, device = 0
+ self.SPI.open(0, 0)
+ self.SPI.max_speed_hz = 4000000
+ self.SPI.mode = 0b00
+ return 0
+
+ def module_exit(self):
+ logger.debug("spi end")
+ self.SPI.close()
+
+ logger.debug("close 5V, Module enters 0 power consumption ...")
+ self.GPIO.output(self.RST_PIN, 0)
+ self.GPIO.output(self.DC_PIN, 0)
+ self.GPIO.output(self.PWR_PIN, 0)
+
+ self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
+
+
+class JetsonNano:
+ # Pin definition
+ RST_PIN = 17
+ DC_PIN = 25
+ CS_PIN = 8
+ BUSY_PIN = 24
+ PWR_PIN = 18
+
+ def __init__(self):
+ import ctypes
+ find_dirs = [
+ os.path.dirname(os.path.realpath(__file__)),
+ '/usr/local/lib',
+ '/usr/lib',
+ ]
+ self.SPI = None
+ for find_dir in find_dirs:
+ so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
+ if os.path.exists(so_filename):
+ self.SPI = ctypes.cdll.LoadLibrary(so_filename)
+ break
+ if self.SPI is None:
+ raise RuntimeError('Cannot find sysfs_software_spi.so')
+
+ import Jetson.GPIO
+ self.GPIO = Jetson.GPIO
+
+ def digital_write(self, pin, value):
+ self.GPIO.output(pin, value)
+
+ def digital_read(self, pin):
+ return self.GPIO.input(self.BUSY_PIN)
+
+ def delay_ms(self, delaytime):
+ time.sleep(delaytime / 1000.0)
+
+ def spi_writebyte(self, data):
+ self.SPI.SYSFS_software_spi_transfer(data[0])
+
+ def spi_writebyte2(self, data):
+ for i in range(len(data)):
+ self.SPI.SYSFS_software_spi_transfer(data[i])
+
+ def module_init(self):
+ self.GPIO.setmode(self.GPIO.BCM)
+ self.GPIO.setwarnings(False)
+ self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
+
+ self.GPIO.output(self.PWR_PIN, 1)
+
+ self.SPI.SYSFS_software_spi_begin()
+ return 0
+
+ def module_exit(self):
+ logger.debug("spi end")
+ self.SPI.SYSFS_software_spi_end()
+
+ logger.debug("close 5V, Module enters 0 power consumption ...")
+ self.GPIO.output(self.RST_PIN, 0)
+ self.GPIO.output(self.DC_PIN, 0)
+ self.GPIO.output(self.PWR_PIN, 0)
+
+ self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN, self.PWR_PIN])
+
+
+class SunriseX3:
+ # Pin definition
+ RST_PIN = 17
+ DC_PIN = 25
+ CS_PIN = 8
+ BUSY_PIN = 24
+ PWR_PIN = 18
+ Flag = 0
+
+ def __init__(self):
+ import spidev
+ import Hobot.GPIO
+
+ self.GPIO = Hobot.GPIO
+ self.SPI = spidev.SpiDev()
+
+ def digital_write(self, pin, value):
+ self.GPIO.output(pin, value)
+
+ def digital_read(self, pin):
+ return self.GPIO.input(pin)
+
+ def delay_ms(self, delaytime):
+ time.sleep(delaytime / 1000.0)
+
+ def spi_writebyte(self, data):
+ self.SPI.writebytes(data)
+
+ def spi_writebyte2(self, data):
+ # for i in range(len(data)):
+ # self.SPI.writebytes([data[i]])
+ self.SPI.xfer3(data)
+
+ def module_init(self):
+ if self.Flag == 0:
+ self.Flag = 1
+ self.GPIO.setmode(self.GPIO.BCM)
+ self.GPIO.setwarnings(False)
+ self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.PWR_PIN, self.GPIO.OUT)
+ self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
+
+ self.GPIO.output(self.PWR_PIN, 1)
+
+ # SPI device, bus = 0, device = 0
+ self.SPI.open(2, 0)
+ self.SPI.max_speed_hz = 4000000
+ self.SPI.mode = 0b00
+ return 0
+ else:
+ return 0
+
+ def module_exit(self):
+ logger.debug("spi end")
+ self.SPI.close()
+
+ logger.debug("close 5V, Module enters 0 power consumption ...")
+ self.Flag = 0
+ self.GPIO.output(self.RST_PIN, 0)
+ self.GPIO.output(self.DC_PIN, 0)
+ self.GPIO.output(self.PWR_PIN, 0)
+
+ self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
+
+
+if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
+ implementation = RaspberryPi()
+elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
+ implementation = SunriseX3()
+else:
+ implementation = JetsonNano()
+
+for func in [x for x in dir(implementation) if not x.startswith('_')]:
+ setattr(sys.modules[__name__], func, getattr(implementation, func))
+
+### END OF FILE ###
\ No newline at end of file
diff --git a/pwnagotchi/ui/hw/waveshare4.py b/pwnagotchi/ui/hw/waveshare4.py
new file mode 100644
index 00000000..95ad74b7
--- /dev/null
+++ b/pwnagotchi/ui/hw/waveshare4.py
@@ -0,0 +1,50 @@
+import logging
+
+import pwnagotchi.ui.fonts as fonts
+from pwnagotchi.ui.hw.base import DisplayImpl
+from PIL import Image
+
+class WaveshareV4(DisplayImpl):
+ def __init__(self, config):
+ super(WaveshareV4, self).__init__(config, 'waveshare_4')
+ 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 v4 display")
+ from pwnagotchi.ui.hw.libs.waveshare.v4.epd2in13_V4 import EPD
+ self._display = EPD()
+ self._display.init()
+ self._display.Clear(0xFF)
+
+
+ def render(self, canvas):
+ buf = self._display.getbuffer(canvas)
+ self._display.displayPartial(buf)
+
+
+ def clear(self):
+ self._display.Clear(0xFF)
\ No newline at end of file
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index 6625e3dd..2393f518 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -254,6 +254,9 @@ def load_config(args):
elif config['ui']['display']['type'] in ('ws_3', 'ws3', 'waveshare_3', 'waveshare3'):
config['ui']['display']['type'] = 'waveshare_3'
+ elif config['ui']['display']['type'] in ('ws_4', 'ws4', 'waveshare_4', 'waveshare4'):
+ config['ui']['display']['type'] = 'waveshare_4'
+
elif config['ui']['display']['type'] in ('ws_27inch', 'ws27inch', 'waveshare_27inch', 'waveshare27inch'):
config['ui']['display']['type'] = 'waveshare27inch'