diff --git a/README.md b/README.md index e69de29..af068ad 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,4 @@ +# waveshare-epd + +a repo full of python things to show things on a waveshare 2.13in e-paper display +each repo should have a readme.md going over what each script does \ No newline at end of file diff --git a/status/DIN Bold.ttf b/status/DIN Bold.ttf new file mode 100755 index 0000000..db558e4 Binary files /dev/null and b/status/DIN Bold.ttf differ diff --git a/status/DIN Medium.ttf b/status/DIN Medium.ttf new file mode 100755 index 0000000..8cff3e9 Binary files /dev/null and b/status/DIN Medium.ttf differ diff --git a/status/Meiryo.ttf b/status/Meiryo.ttf new file mode 100644 index 0000000..2bc98fd Binary files /dev/null and b/status/Meiryo.ttf differ diff --git a/status/TREBUCBD.ttf b/status/TREBUCBD.ttf new file mode 100755 index 0000000..5df79b2 Binary files /dev/null and b/status/TREBUCBD.ttf differ diff --git a/status/__pycache__/epd2in13_V3.cpython-39.pyc b/status/__pycache__/epd2in13_V3.cpython-39.pyc new file mode 100644 index 0000000..4e3d853 Binary files /dev/null and b/status/__pycache__/epd2in13_V3.cpython-39.pyc differ diff --git a/status/__pycache__/epdconfig.cpython-39.pyc b/status/__pycache__/epdconfig.cpython-39.pyc new file mode 100644 index 0000000..9d5ce13 Binary files /dev/null and b/status/__pycache__/epdconfig.cpython-39.pyc differ diff --git a/status/epd2in13_V3.py b/status/epd2in13_V3.py new file mode 100644 index 0000000..7972a54 --- /dev/null +++ b/status/epd2in13_V3.py @@ -0,0 +1,387 @@ +# ***************************************************************************** +# * | File : epd2in13_V3.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-08-9 +# # | 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 +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 + + 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) + + # 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(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): + 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.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 ### + diff --git a/status/epd2in13_V3.pyc b/status/epd2in13_V3.pyc new file mode 100644 index 0000000..e752ed7 Binary files /dev/null and b/status/epd2in13_V3.pyc differ diff --git a/status/epdconfig.py b/status/epdconfig.py new file mode 100644 index 0000000..f692f43 --- /dev/null +++ b/status/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 ### diff --git a/status/epdconfig.pyc b/status/epdconfig.pyc new file mode 100644 index 0000000..d23168a Binary files /dev/null and b/status/epdconfig.pyc differ diff --git a/status/legacy/clock.py b/status/legacy/clock.py new file mode 100755 index 0000000..9c34973 --- /dev/null +++ b/status/legacy/clock.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +# -*- coding:utf-8 -*- +import sys +import os +picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic') +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +try: + logging.info("epd2in13_V3 Demo") + + epd = epd2in13_V3.EPD() + logging.info("init and Clear") + epd.init() + epd.Clear(0xFF) + + # Drawing on the image + font42 = ImageFont.truetype(os.path.join(picdir, '/home/rintyuu/temp/TREBUCBD.ttf'), 42) + font24 = ImageFont.truetype(os.path.join(picdir, '/home/rintyuu/temp/trebuc.ttf'), 24) + + logging.info("Showing time...") + time_image = Image.new('1', (epd.height, epd.width), 255) + time_draw = ImageDraw.Draw(time_image) + date_draw = ImageDraw.Draw(time_image) + previous_time = '' + previous_date = '' + +# keep in mind that the resolution of the display is 250 × 122. + + while True: + current_time = time.strftime('%H:%M:%S') + if current_time != previous_time: + time_draw.rectangle((0, 0, 250, 61), fill=255) + time_draw.text((0, 0), current_time, font=font42, fill=0) + epd.displayPartial(epd.getbuffer(time_image)) + previous_time = current_time + current_date = time.strftime('%m/%d/%Y') + if current_date != previous_date: + time_draw.rectangle((0, 0, 125, 61), fill=255) + time_draw.text((0, 61), current_date, font=font24, fill=0) + epd.displayPartial(epd.getbuffer(time_image)) + previous_date = current_date + time.sleep(5) + +except IOError as e: + logging.info(e) + +except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() diff --git a/status/legacy/clock2.py b/status/legacy/clock2.py new file mode 100755 index 0000000..9c58a4a --- /dev/null +++ b/status/legacy/clock2.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +# -*- coding:utf-8 -*- +import sys +import os +picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic') +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +try: + logging.info("epd2in13_V3 Demo") + + epd = epd2in13_V3.EPD() + logging.info("init and Clear") + epd.init() + epd.Clear(0xFF) + + # Drawing on the image + font42 = ImageFont.truetype(os.path.join(picdir, '/home/rintyuu/temp/TREBUCBD.ttf'), 42) + font24 = ImageFont.truetype(os.path.join(picdir, '/home/rintyuu/temp/trebuc.ttf'), 24) + + logging.info("Showing time...") + time_image = Image.new('1', (epd.height, epd.width), 255) + time_draw = ImageDraw.Draw(time_image) + date_draw = ImageDraw.Draw(time_image) + previous_time = '' + previous_date = '' + +# keep in mind that the resolution of the display is 250 × 122. + + while True: + current_time = time.strftime('%H:%M:%S') + if current_time != previous_time: + time_draw.rectangle((0, 0, 250, 51), fill=255) + time_draw.text((0, 0), current_time, font=font42, fill=0) + epd.displayPartial(epd.getbuffer(time_image)) + previous_time = current_time + current_date = time.strftime('%m/%d/%Y') + if current_date != previous_date: + date_draw.rectangle((0, 51, 250, 112), fill=255) + date_draw.text((0, 51), current_date, font=font24, fill=0) + epd.displayPartial(epd.getbuffer(time_image)) + previous_date = current_date + time.sleep(5) + +except IOError as e: + logging.info(e) + +except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() diff --git a/status/readme.md b/status/readme.md new file mode 100644 index 0000000..811cee7 --- /dev/null +++ b/status/readme.md @@ -0,0 +1,4 @@ +# status + +shows the current status of network (connection status, ssid) as well as the time and date. +useful to show messages to others not to unplug from power, and what network rpi is connected to. \ No newline at end of file diff --git a/status/status.py b/status/status.py new file mode 100644 index 0000000..274a061 --- /dev/null +++ b/status/status.py @@ -0,0 +1,62 @@ +import subprocess +import time +import epd2in13_V3 +from PIL import Image, ImageDraw, ImageFont + +def get_wifi_status(): + try: + # Get the SSID of the currently connected Wi-Fi network + result = subprocess.run(['iwgetid', '-r'], capture_output=True, text=True) + ssid = result.stdout.strip() + if ssid: + return "OK", ssid + else: + return "Disconnected", "N/A" + except Exception as e: + return "Error", str(e) + +def get_time_date(): + current_time = time.strftime('%I:%M %p') + current_date = time.strftime('%a, %b %d, %Y') + return current_time, current_date + +def main(): + # Initialize the e-paper display + epd = epd2in13_V3.EPD() + epd.init() + epd.Clear(0xFF) # White background + + # Load fonts + font_large = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 18) + font_small = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 14) + + while True: + # Create a new image with white background + image = Image.new('1', (epd.height, epd.width), 255) # '1' for 1-bit color + draw = ImageDraw.Draw(image) + + # Get connection status and SSID + status, ssid = get_wifi_status() + + # Get current time and date + current_time, current_date = get_time_date() + + # Draw text on image + draw.text((10, 10), f"CONNECTION: {status}", font=font_large, fill=0) + draw.text((10, 30), f"SSID: {ssid}", font=font_large, fill=0) + draw.text((10, 50), f"{current_time}", font=font_large, fill=0) + draw.text((10, 70), f"{current_date}", font=font_large, fill=0) + draw.text((10, 100), "DO NOT UNPLUG | rintyuu.dev", font=font_small, fill=0) + + # Rotate image if necessary + rotated_image = image.rotate(180) + + # Display the image + epd.display(epd.getbuffer(rotated_image)) + epd.sleep() + + # Wait for a minute before updating + time.sleep(60) + +if __name__ == "__main__": + main() diff --git a/status/status2.py b/status/status2.py new file mode 100644 index 0000000..a990caa --- /dev/null +++ b/status/status2.py @@ -0,0 +1,95 @@ +import sys +import os +import subprocess +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont + +picdir = os.path.dirname(os.path.realpath(__file__)) +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +logging.basicConfig(level=logging.DEBUG) + +def get_wifi_status(): + try: + # Get the SSID of the currently connected Wi-Fi network + result = subprocess.run(['iwgetid', '-r'], capture_output=True, text=True) + ssid = result.stdout.strip() + if ssid: + return "OK", ssid + else: + return "Disconnected", "N/A" + except Exception as e: + return "Error", str(e) + +def main(): + try: + logging.info("epd2in13_V3 datetime and network info") + + epd = epd2in13_V3.EPD() + logging.info("init and Clear") + epd.init() + epd.Clear(0xFF) + + # Load fonts + font18 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 18) + font14 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 14) + + clock_refresh_interval = 4 # Refresh every minute + logging.info("Showing network info, time, date, and footer...") + time_image = Image.new('1', (epd.height, epd.width), 255) + draw = ImageDraw.Draw(time_image) + + previous_clock_time = '' + previous_date = '' + previous_status = '' + previous_ssid = '' + + while True: + # Get current time and date + current_time = time.strftime('%l:%M %p').lstrip().lower() + current_date = time.strftime('%a, %b %d, \'%y') + + # Get network status and SSID + status, ssid = get_wifi_status() + + if current_time != previous_clock_time or current_date != previous_date or status != previous_status or ssid != previous_ssid: + # Clear previous display + draw.rectangle((0, 0, 250, 122), fill=255) + + # Draw network status and SSID + draw.text((0, 0), f"CONNECTION: {status}", font=font18, fill=0) + draw.text((0, 20), f"SSID: {ssid}", font=font18, fill=0) + + # Draw the time and date + draw.text((0, 40), current_time, font=font18, fill=0) + draw.text((0, 60), current_date, font=font18, fill=0) + + # Footer text + draw.text((0, 100), "DO NOT UNPLUG | rintyuu.dev", font=font14, fill=0) + + # Rotate and display + rotated_image = time_image.rotate(180) + epd.displayPartial(epd.getbuffer(rotated_image)) + + # Update previous values + previous_clock_time = current_time + previous_date = current_date + previous_status = status + previous_ssid = ssid + + time.sleep(clock_refresh_interval) + + except IOError as e: + logging.info(e) + + except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() + +if __name__ == "__main__": + main() diff --git a/status/status3.py b/status/status3.py new file mode 100644 index 0000000..e8e1c16 --- /dev/null +++ b/status/status3.py @@ -0,0 +1,96 @@ +import sys +import os +import subprocess +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont + +picdir = os.path.dirname(os.path.realpath(__file__)) +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +logging.basicConfig(level=logging.DEBUG) + +def get_wifi_status(): + try: + # Get the SSID of the currently connected Wi-Fi network + result = subprocess.run(['iwgetid', '-r'], capture_output=True, text=True) + ssid = result.stdout.strip() + if ssid: + return "OK", ssid + else: + return "Disconnected", "N/A" + except Exception as e: + return "Error", str(e) + +def main(): + try: + logging.info("epd2in13_V3 datetime and network info") + + epd = epd2in13_V3.EPD() + logging.info("init and Clear") + epd.init() + epd.Clear(0xFF) + + # Load fonts + font18 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 18) + font16 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 14) # Increased footer font size + + clock_refresh_interval = 2 # Refresh every minute + logging.info("Showing network info, time, date, and footer...") + time_image = Image.new('1', (epd.height, epd.width), 255) + draw = ImageDraw.Draw(time_image) + + previous_clock_time = '' + previous_date = '' + previous_status = '' + previous_ssid = '' + + while True: + # Get current time and date + current_time = time.strftime('%l:%M %p').lstrip().lower() + current_date = time.strftime('%a, %b %d, \'%y') + + # Get network status and SSID + status, ssid = get_wifi_status() + + if current_time != previous_clock_time or current_date != previous_date or status != previous_status or ssid != previous_ssid: + # Clear previous display + draw.rectangle((0, 0, 250, 122), fill=255) + + # Draw network status and SSID + draw.text((10, 0), f"CONNECTION: {status}", font=font18, fill=0) + draw.text((10, 20), f"SSID: {ssid}", font=font18, fill=0) + + # Draw the time and date + draw.text((10, 40), current_time, font=font18, fill=0) + draw.text((10, 60), current_date, font=font18, fill=0) + + # Clear footer area and draw footer text (full refresh for better clarity) + draw.rectangle((0, 90, 250, 122), fill=255) # Clear footer area + draw.text((10, 100), "DO NOT UNPLUG | rintyuu.dev", font=font16, fill=0) + + # Rotate and display + rotated_image = time_image.rotate(180) + epd.display(epd.getbuffer(rotated_image)) # Use full display update for clearer result + + # Update previous values + previous_clock_time = current_time + previous_date = current_date + previous_status = status + previous_ssid = ssid + + time.sleep(clock_refresh_interval) + + except IOError as e: + logging.info(e) + + except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() + +if __name__ == "__main__": + main() diff --git a/status/status4.py b/status/status4.py new file mode 100644 index 0000000..d2e707d --- /dev/null +++ b/status/status4.py @@ -0,0 +1,102 @@ +import sys +import os +import subprocess +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont + +picdir = os.path.dirname(os.path.realpath(__file__)) +libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib') +if os.path.exists(libdir): + sys.path.append(libdir) + +logging.basicConfig(level=logging.DEBUG) + +def get_wifi_status(): + try: + # Get the SSID of the currently connected Wi-Fi network + result = subprocess.run(['iwgetid', '-r'], capture_output=True, text=True) + ssid = result.stdout.strip() + if ssid: + return "OK", ssid + else: + return "Disconnected", "N/A" + except Exception as e: + return "Error", str(e) + +def main(): + try: + logging.info("epd2in13_V3 datetime and network info") + + epd = epd2in13_V3.EPD() + logging.info("init and Clear") + epd.init() + epd.Clear(0xFF) + + # Load fonts + font18 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 18) + font16 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 14) + + clock_refresh_interval = 5 # Refresh every 5 seconds + motd_refresh_interval = 120 # Refresh every minute + + time_image = Image.new('1', (epd.height, epd.width), 255) + draw = ImageDraw.Draw(time_image) + + previous_clock_time = '' + previous_date = '' + previous_status = '' + previous_ssid = '' + previous_footer_refresh = 0 + + while True: + # Get current time and date + current_time = time.strftime('%l:%M %p').lstrip().lower() + current_date = time.strftime('%a, %b %d, \'%y') + + # Get network status and SSID + status, ssid = get_wifi_status() + + # If time, date, status, or SSID has changed, update + if current_time != previous_clock_time or current_date != previous_date or status != previous_status or ssid != previous_ssid: + # Clear and redraw only if there's a change + if status != previous_status or ssid != previous_ssid: + draw.rectangle((0, 0, 250, 40), fill=255) # Clear network status and SSID area + draw.text((0, 0), f"CONNECTION: {status}", font=font18, fill=0) + draw.text((0, 20), f"SSID: {ssid}", font=font18, fill=0) + epd.displayPartial(epd.getbuffer(time_image.rotate(180))) # Partial refresh for status and SSID + + # Update the time and date with partial refresh + if current_time != previous_clock_time or current_date != previous_date: + draw.rectangle((0, 40, 250, 80), fill=255) # Clear time and date area + draw.text((0, 40), current_time, font=font18, fill=0) + draw.text((0, 60), current_date, font=font18, fill=0) + epd.displayPartial(epd.getbuffer(time_image.rotate(180))) # Partial refresh for time and date + + # Only refresh footer every minute + if time.time() - previous_footer_refresh >= motd_refresh_interval: + draw.rectangle((0, 90, 250, 122), fill=255) # Clear footer area + draw.text((0, 100), "DO NOT UNPLUG | rintyuu.dev", font=font16, fill=0) + epd.displayPartial(epd.getbuffer(time_image.rotate(180))) # Partial refresh for footer + previous_footer_refresh = time.time() + + # Update previous values + previous_clock_time = current_time + previous_date = current_date + previous_status = status + previous_ssid = ssid + + # Wait for the interval before refreshing + time.sleep(clock_refresh_interval) + + except IOError as e: + logging.info(e) + + except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() + +if __name__ == "__main__": + main() diff --git a/status/times.ttf b/status/times.ttf new file mode 100755 index 0000000..51261a0 Binary files /dev/null and b/status/times.ttf differ diff --git a/status/trebuc.ttf b/status/trebuc.ttf new file mode 100644 index 0000000..b3bd292 Binary files /dev/null and b/status/trebuc.ttf differ diff --git a/status/trebucit.ttf b/status/trebucit.ttf new file mode 100644 index 0000000..41edbfa Binary files /dev/null and b/status/trebucit.ttf differ diff --git a/waveshare-clock/LICENSE b/waveshare-clock/LICENSE new file mode 100644 index 0000000..1130a1b --- /dev/null +++ b/waveshare-clock/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 theusermc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation 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 +furnished 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 FOR 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. diff --git a/waveshare-clock/README.md b/waveshare-clock/README.md new file mode 100644 index 0000000..380f8af --- /dev/null +++ b/waveshare-clock/README.md @@ -0,0 +1,19 @@ +# waveshare-clock +just a simple digital clock that shows the current system time and date on a waveshare 2.13 (v3) display. + +requirements: + + - [raspberry pi 0 w](https://www.raspberrypi.com/products/raspberry-pi-zero-w/) + - [waveshare 2.13 v3](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT) + - python3 + - some python knowledge + +to clone and run, + + git clone https://github.com/theusermc/waveshare-clock.git + cd waveshare-clock + python3 clock.py +i've also included the `epd2in13_V3` and `epdconfig` libraries and a couple of fonts to make life more simple. + +you also might want to install Pillow if you haven't already: +`pip install Pillow` diff --git a/waveshare-clock/clock.py b/waveshare-clock/clock.py new file mode 100644 index 0000000..58a3020 --- /dev/null +++ b/waveshare-clock/clock.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +# -*- coding:utf-8 -*- +import sys +import os + +picdir = os.path.dirname(os.path.realpath(__file__)) +libdir = os.path.join( + os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "lib" +) +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +try: + logging.info("epd2in13_V3 Demo") + + epd = epd2in13_V3.EPD() + logging.info("init") + epd.init() + logging.info("clearing") + epd.Clear(0xFF) + + # Drawing on the image + font42 = ImageFont.truetype( + os.path.join(picdir, "trebucbold.ttf"), 42 + ) + font24 = ImageFont.truetype( + os.path.join(picdir, "trebuc.ttf"), 24 + ) + + logging.info("Now displaying time") + time_image = Image.new("1", (epd.height, epd.width), 255) + time_draw = ImageDraw.Draw(time_image) + date_draw = ImageDraw.Draw(time_image) + previous_time = "" + previous_date = "" + + # While editing the parameters, + # keep in mind that the resolution of the display is 250 × 122. + + while True: + current_time = time.strftime("%H:%M:%S") + if current_time != previous_time: + time_draw.rectangle((0, 0, 250, 51), fill=255) + time_draw.text((0, 0), current_time, font=font42, fill=0) + rotated_time_image = time_image.rotate(180) + epd.displayPartial(epd.getbuffer(rotated_time_image)) + previous_time = current_time + current_date = time.strftime("%m/%d/%Y") + if current_date != previous_date: + date_draw.rectangle((0, 51, 250, 112), fill=255) + date_draw.text((0, 51), current_date, font=font24, fill=0) + rotated_date_image = time_image.rotate(180) + epd.displayPartial(epd.getbuffer(rotated_date_image)) + previous_date = current_date + time.sleep(5) + +except IOError as e: + logging.info(e) + +except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() diff --git a/waveshare-clock/epd2in13_V3.py b/waveshare-clock/epd2in13_V3.py new file mode 100644 index 0000000..eb6e5ab --- /dev/null +++ b/waveshare-clock/epd2in13_V3.py @@ -0,0 +1,387 @@ +# ***************************************************************************** +# * | File : epd2in13_V3.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.2 +# * | Date : 2022-08-9 +# # | 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 +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 + + 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) + + # 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(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): + 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.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 ### + diff --git a/waveshare-clock/epdconfig.py b/waveshare-clock/epdconfig.py new file mode 100644 index 0000000..b415940 --- /dev/null +++ b/waveshare-clock/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 ### diff --git a/waveshare-clock/motd.json b/waveshare-clock/motd.json new file mode 100644 index 0000000..fd13bbc --- /dev/null +++ b/waveshare-clock/motd.json @@ -0,0 +1,5 @@ +[ + "Hi!", + "How are you doing?", + "Good morning/afternoon/evening!" +] diff --git a/waveshare-clock/old/clock.py b/waveshare-clock/old/clock.py new file mode 100644 index 0000000..f23fdb0 --- /dev/null +++ b/waveshare-clock/old/clock.py @@ -0,0 +1,73 @@ +#!/usr/bin/python3 +# -*- coding:utf-8 -*- +import sys +import os + +picdir = os.path.join( + os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "pic" +) +libdir = os.path.join( + os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "lib" +) +if os.path.exists(libdir): + sys.path.append(libdir) + +import logging +import epd2in13_V3 +import time +from PIL import Image, ImageDraw, ImageFont +import traceback + +logging.basicConfig(level=logging.DEBUG) + +try: + logging.info("epd2in13_V3 Demo") + + epd = epd2in13_V3.EPD() + logging.info("init") + epd.init() + logging.info("clearing") + epd.Clear(0xFF) + + # Drawing on the image + font42 = ImageFont.truetype( + os.path.join(picdir, "trebucbold.ttf"), 42 + ) + font24 = ImageFont.truetype( + os.path.join(picdir, "trebuc.ttf"), 24 + ) + + logging.info("Now displaying time") + time_image = Image.new("1", (epd.height, epd.width), 255) + time_draw = ImageDraw.Draw(time_image) + date_draw = ImageDraw.Draw(time_image) + previous_time = "" + previous_date = "" + + # While editing the parameters, + # keep in mind that the resolution of the display is 250 × 122. + + while True: + current_time = time.strftime("%H:%M:%S") + if current_time != previous_time: + time_draw.rectangle((0, 0, 250, 51), fill=255) + time_draw.text((0, 0), current_time, font=font42, fill=0) + rotated_time_image = time_image.rotate(180) + epd.displayPartial(epd.getbuffer(rotated_time_image)) + previous_time = current_time + current_date = time.strftime("%m/%d/%Y") + if current_date != previous_date: + date_draw.rectangle((0, 51, 250, 112), fill=255) + date_draw.text((0, 51), current_date, font=font24, fill=0) + rotated_date_image = time_image.rotate(180) + epd.displayPartial(epd.getbuffer(rotated_date_image)) + previous_date = current_date + time.sleep(5) + +except IOError as e: + logging.info(e) + +except KeyboardInterrupt: + logging.info("ctrl + c:") + epd2in13_V3.epdconfig.module_exit() + exit() diff --git a/waveshare-clock/readme.md b/waveshare-clock/readme.md new file mode 100644 index 0000000..d1c66da --- /dev/null +++ b/waveshare-clock/readme.md @@ -0,0 +1,3 @@ +# waveshare-clock + +a basic python script that shows the date and time on the waveshare 2.13in e-paper display, with custom messages (motd) support ootb \ No newline at end of file diff --git a/waveshare-clock/trebuc.ttf b/waveshare-clock/trebuc.ttf new file mode 100644 index 0000000..b3bd292 Binary files /dev/null and b/waveshare-clock/trebuc.ttf differ diff --git a/waveshare-clock/trebucbold.ttf b/waveshare-clock/trebucbold.ttf new file mode 100644 index 0000000..5df79b2 Binary files /dev/null and b/waveshare-clock/trebucbold.ttf differ diff --git a/waveshare-clock/trebucit.ttf b/waveshare-clock/trebucit.ttf new file mode 100644 index 0000000..41edbfa Binary files /dev/null and b/waveshare-clock/trebucit.ttf differ