From f4fa2597810125b5e5e1af4ce4c913d4ac8ed913 Mon Sep 17 00:00:00 2001 From: Ronan Gaillard Date: Sun, 27 Oct 2019 16:02:00 +0100 Subject: [PATCH] Add support for Waveshare 1.54 inch screen --- pwnagotchi/ui/display.py | 5 +- pwnagotchi/ui/hw/__init__.py | 6 +- .../hw/libs/waveshare/v154inch/epd1in54b.py | 219 ++++++++++++++++++ .../hw/libs/waveshare/v154inch/epdconfig.py | 154 ++++++++++++ pwnagotchi/ui/hw/waveshare154inch.py | 47 ++++ pwnagotchi/utils.py | 3 + 6 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py create mode 100644 pwnagotchi/ui/hw/waveshare154inch.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index d218044..b0ff106 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -49,7 +49,10 @@ class Display(View): return self._implementation.name == 'lcdhat' def is_dfrobot(self): - return self._implementation.name == 'dfrobot' + return self._implementation.name == 'dfrobot' + + def is_waveshare154inch(self): + return self._implementation.name == 'waveshare154inch' def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 66e50e4..3be215f 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -6,6 +6,7 @@ from pwnagotchi.ui.hw.dfrobot import DFRobot from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch +from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch def display_for(config): @@ -32,4 +33,7 @@ def display_for(config): return WaveshareV2(config) elif config['ui']['display']['type'] == 'waveshare27inch': - return Waveshare27inch(config) \ No newline at end of file + return Waveshare27inch(config) + + elif config['ui']['display']['type'] == 'waveshare154inch': + return Waveshare154inch(config) diff --git a/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py new file mode 100644 index 0000000..7b241c9 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epd1in54b.py @@ -0,0 +1,219 @@ +# ***************************************************************************** +# * | 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 + +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(10) + 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): + logging.debug("e-Paper busy") + while(epdconfig.digital_read(self.busy_pin) == 0): + epdconfig.delay_ms(100) + logging.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.module_exit() + +### END OF FILE ### + diff --git a/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py new file mode 100644 index 0000000..861f43d --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/v154inch/epdconfig.py @@ -0,0 +1,154 @@ +# /***************************************************************************** +# * | File : epdconfig.py +# * | Author : Waveshare team +# * | Function : Hardware underlying interface +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-06-21 +# * | Info : +# ****************************************************************************** +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +import os +import logging +import sys +import time + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import spidev + import RPi.GPIO + + self.GPIO = RPi.GPIO + + # SPI device, bus = 0, device = 0 + self.SPI = spidev.SpiDev(0, 0) + + 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 module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.max_speed_hz = 4000000 + self.SPI.mode = 0b00 + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.close() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +class JetsonNano: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + + def __init__(self): + import ctypes + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.SPI = None + for find_dir in find_dirs: + so_filename = os.path.join(find_dir, 'sysfs_software_spi.so') + if os.path.exists(so_filename): + self.SPI = ctypes.cdll.LoadLibrary(so_filename) + break + if self.SPI is None: + raise RuntimeError('Cannot find sysfs_software_spi.so') + + import Jetson.GPIO + self.GPIO = Jetson.GPIO + + def digital_write(self, pin, value): + self.GPIO.output(pin, value) + + def digital_read(self, pin): + return self.GPIO.input(self.BUSY_PIN) + + def delay_ms(self, delaytime): + time.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.SPI.SYSFS_software_spi_transfer(data[0]) + + def module_init(self): + self.GPIO.setmode(self.GPIO.BCM) + self.GPIO.setwarnings(False) + self.GPIO.setup(self.RST_PIN, self.GPIO.OUT) + self.GPIO.setup(self.DC_PIN, self.GPIO.OUT) + self.GPIO.setup(self.CS_PIN, self.GPIO.OUT) + self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN) + self.SPI.SYSFS_software_spi_begin() + return 0 + + def module_exit(self): + logging.debug("spi end") + self.SPI.SYSFS_software_spi_end() + + logging.debug("close 5V, Module enters 0 power consumption ...") + self.GPIO.output(self.RST_PIN, 0) + self.GPIO.output(self.DC_PIN, 0) + + self.GPIO.cleanup() + + +if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'): + implementation = RaspberryPi() +else: + implementation = JetsonNano() + +for func in [x for x in dir(implementation) if not x.startswith('_')]: + setattr(sys.modules[__name__], func, getattr(implementation, func)) + + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/waveshare154inch.py b/pwnagotchi/ui/hw/waveshare154inch.py new file mode 100644 index 0000000..fc9cfa7 --- /dev/null +++ b/pwnagotchi/ui/hw/waveshare154inch.py @@ -0,0 +1,47 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class Waveshare154inch(DisplayImpl): + def __init__(self, config): + super(Waveshare154inch, self).__init__(config, 'waveshare_1_54inch') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 200 + self._layout['height'] = 200 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (135, 0) + self._layout['line1'] = [0, 14, 200, 14] + self._layout['line2'] = [0, 186, 200, 186] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 187) + self._layout['mode'] = (170, 187) + self._layout['status'] = { + 'pos': (5, 90), + 'font': fonts.Medium, + 'max': 20 + } + return self._layout + + def initialize(self): + logging.info("initializing waveshare v154 display") + from pwnagotchi.ui.hw.libs.waveshare.v154inch.epd1in54b import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + buf = self._display.getbuffer(canvas) + self._display.display(buf, None) + + def clear(self): + pass + #self._display.Clear() diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py index 968abd4..c474ae2 100644 --- a/pwnagotchi/utils.py +++ b/pwnagotchi/utils.py @@ -97,6 +97,9 @@ def load_config(args): elif config['ui']['display']['type'] in ('dfrobot', 'df'): config['ui']['display']['type'] = 'dfrobot' + elif config['ui']['display']['type'] in ('ws_154inch', 'ws154inch', 'waveshare_154inch', 'waveshare154inch'): + config['ui']['display']['type'] = 'waveshare154inch' + else: print("unsupported display type %s" % config['ui']['display']['type']) exit(1)