From 3fb3c4f63fe10fb28d2c4569f8469645c624c081 Mon Sep 17 00:00:00 2001 From: grizzly Date: Sat, 23 Aug 2025 15:08:14 -0400 Subject: [PATCH] plop --- .gitignore | 4 + display.py | 18 + document.typ | 43 ++ icons/sun.svg | 1 + lib/__pycache__/epd4in2.cpython-311.pyc | Bin 0 -> 28410 bytes lib/__pycache__/epdconfig.cpython-311.pyc | Bin 0 -> 16466 bytes lib/epd4in2.py | 678 ++++++++++++++++++++++ lib/epdconfig.py | 322 ++++++++++ requirements.txt | 6 + update.sh | 7 + 10 files changed, 1079 insertions(+) create mode 100644 .gitignore create mode 100644 display.py create mode 100644 document.typ create mode 100644 icons/sun.svg create mode 100644 lib/__pycache__/epd4in2.cpython-311.pyc create mode 100644 lib/__pycache__/epdconfig.cpython-311.pyc create mode 100644 lib/epd4in2.py create mode 100644 lib/epdconfig.py create mode 100644 requirements.txt create mode 100755 update.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f282ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv/ +*.png +*.bpm +*.json diff --git a/display.py b/display.py new file mode 100644 index 0000000..4816811 --- /dev/null +++ b/display.py @@ -0,0 +1,18 @@ +from lib import epd4in2 +import os +#picdir = os.path.join(os.path.dirname(os.path.realpath(__file__)),'pic' +from PIL import Image + +epd = epd4in2.EPD() +epd.init() + +image = Image.open('./document_ready.bmp') +image = image.convert('L') +epd.display_4Gray(epd.getbuffer_4Gray(image)) +epd.sleep() + +## version grayscale +#epd.Init_4Gray() +#Himage = Image.open(os.path.join(picdir, 'book_base.bmp')) +#epd.display_4Gray(epd.getbuffer_4Gray(Himage)) + diff --git a/document.typ b/document.typ new file mode 100644 index 0000000..a564fbf --- /dev/null +++ b/document.typ @@ -0,0 +1,43 @@ +#set page(width: 86mm, height: 65mm) + +#let weekdays = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") +#let today = datetime.today() +#let weather = json("weather.json") + +#align(center)[ + #text(size:2em)[#today.display("[weekday] [month repr:long] [year]")] + + #box( + stroke: 0.2em+black + )[ + #grid( + columns: (1fr,1fr,1fr,1fr,1fr,1fr,1fr), + inset:0.5em, + rows:1, + align:center, + fill: (x, _) => + if x>4 { luma(240) } + else { white }, + + stroke: (x, _) => + if x== weekdays.position(it => {it == today.display("[weekday repr:short]")}) {0.15em+black} + else { none}, + [Lun],[Mar],[Mer],[Jeu],[Ven],[Sam],[Dim] + ) + ] + +#let icon = none +#if weather.current.weather_code == 0{ + icon = image("icons/sun.svg", width:3em) +} + + #grid( + columns: (1fr,4fr), + align: (center+horizon, left+horizon), + [#icon],[#text(size:3em)[#weather.current.temperature_2m #weather.current_units.temperature_2m]] + ) + + + + +] diff --git a/icons/sun.svg b/icons/sun.svg new file mode 100644 index 0000000..ce67d35 --- /dev/null +++ b/icons/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/__pycache__/epd4in2.cpython-311.pyc b/lib/__pycache__/epd4in2.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a78773a00e3dfe93022148faebe09f5cfbc717b GIT binary patch literal 28410 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 literal 0 HcmV?d00001 diff --git a/lib/__pycache__/epdconfig.cpython-311.pyc b/lib/__pycache__/epdconfig.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b21258a9363d90cfc6072e51669000f91a941a45 GIT binary patch literal 16466 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 (x % 8)) + elif imwidth == self.height and imheight == self.width: + logger.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def getbuffer_4Gray(self, image): + # logger.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 4) * self.height) + image_monocolor = image.convert('L') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + i = 0 + # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if imwidth == self.width and imheight == self.height: + logger.debug("Vertical") + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0xC0: + pixels[x, y] = 0x80 + elif pixels[x, y] == 0x80: + pixels[x, y] = 0x40 + i = i + 1 + if i % 4 == 0: + buf[int((x + (y * self.width)) / 4)] = ( + (pixels[x - 3, y] & 0xc0) | (pixels[x - 2, y] & 0xc0) >> 2 | ( + pixels[x - 1, y] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6) + + elif imwidth == self.height and imheight == self.width: + logger.debug("Horizontal") + for x in range(imwidth): + for y in range(imheight): + newx = y + newy = x + if pixels[x, y] == 0xC0: + pixels[x, y] = 0x80 + elif pixels[x, y] == 0x80: + pixels[x, y] = 0x40 + i = i + 1 + if i % 4 == 0: + buf[int((newx + (newy * self.width)) / 4)] = ( + (pixels[x, y - 3] & 0xc0) | (pixels[x, y - 2] & 0xc0) >> 2 | ( + pixels[x, y - 1] & 0xc0) >> 4 | (pixels[x, y] & 0xc0) >> 6) + + return buf + + def display(self, image): + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + self.send_command(0x92) + self.set_lut() + self.send_command(0x10) + self.send_data2([0xFF] * int(self.width * linewidth)) + + self.send_command(0x13) + self.send_data2(image) + + self.send_command(0x12) + self.ReadBusy() + + def EPD_4IN2_PartialDisplay(self, X_start, Y_start, X_end, Y_end, Image): + # EPD_WIDTH = 400 + # EPD_HEIGHT = 300 + + if EPD_WIDTH % 8 != 0: + Width = int(EPD_WIDTH / 8) + 1 + else: + Width = int(EPD_WIDTH / 8) + Height = EPD_HEIGHT + + if X_start % 8 != 0: + X_start = int(X_start / 8) + 1 + else: + X_start = int(X_start / 8) + if X_end % 8 != 0: + X_end = int(X_end / 8) + 1 + else: + X_end = int(X_end / 8) + + buf = [0x00] * (Y_end - Y_start) * (X_end - X_start) + + self.send_command(0x91) # This command makes the display enter partial mode + self.send_command(0x90) # resolution setting + self.send_data(int(X_start * 8 / 256)) + self.send_data(int(X_start * 8 % 256)) # x-start + + self.send_data(int(X_end * 8 / 256)) + self.send_data(int(X_end * 8 % 256) - 1) # x-end + + self.send_data(int(Y_start / 256)) + self.send_data(int(Y_start % 256)) # y-start + + self.send_data(int(Y_end / 256)) + self.send_data(int(Y_end % 256) - 1) # y-end + self.send_data(0x28) + + self.send_command(0x10) # writes Old data to SRAM for programming + for j in range(0, Y_end - Y_start): + for i in range(0, X_end - X_start): + buf[j * (X_end - X_start) + i] = self.DATA[(Y_start + j) * Width + X_start + i] + self.send_data2(buf) + + self.send_command(0x13) # writes New data to SRAM. + for j in range(0, Y_end - Y_start): + for i in range(0, X_end - X_start): + buf[j * (X_end - X_start) + i] = ~Image[(Y_start + j) * Width + X_start + i] + self.DATA[(Y_start + j) * Width + X_start + i] = ~Image[(Y_start + j) * Width + X_start / 8 + i] + self.send_data2(buf) + + self.send_command(0x12) # DISPLAY REFRESH + epdconfig.delay_ms(200) # The delay here is necessary, 200uS at least!!! + self.ReadBusy() + + def display_4Gray(self, image): + self.send_command(0x92) + self.set_lut() + self.send_command(0x10) + + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + buf = [0x00] * self.height * linewidth + + for i in range(0, int(EPD_WIDTH * EPD_HEIGHT / 8)): # EPD_WIDTH * EPD_HEIGHT / 4 + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: + temp3 |= 0x01 # white + elif temp2 == 0x00: + temp3 |= 0x00 # black + elif temp2 == 0x80: + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: # white + temp3 |= 0x01 + elif temp2 == 0x00: # black + temp3 |= 0x00 + elif temp2 == 0x80: + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + if j != 1 or k != 1: + temp3 <<= 1 + temp1 <<= 2 + buf[i] = temp3 + self.send_data2(buf) + + self.send_command(0x13) + + for i in range(0, int(EPD_WIDTH * EPD_HEIGHT / 8)): # 5808*4 46464 + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: + temp3 |= 0x01 # white + elif temp2 == 0x00: + temp3 |= 0x00 # black + elif temp2 == 0x80: + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: # white + temp3 |= 0x01 + elif temp2 == 0x00: # black + temp3 |= 0x00 + elif temp2 == 0x80: + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + if j != 1 or k != 1: + temp3 <<= 1 + temp1 <<= 2 + buf[i] = temp3 + self.send_data2(buf) + + self.Gray_SetLut() + self.send_command(0x12) + epdconfig.delay_ms(200) + self.ReadBusy() + # pass + + def Clear(self): + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + self.send_command(0x10) + self.send_data2([0xff] * int(self.height * linewidth)) + + self.send_command(0x13) + self.send_data2([0xff] * int(self.height * linewidth)) + + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0XA5) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() + +### END OF FILE ### diff --git a/lib/epdconfig.py b/lib/epdconfig.py new file mode 100644 index 0000000..b390252 --- /dev/null +++ b/lib/epdconfig.py @@ -0,0 +1,322 @@ +# /***************************************************************************** +# * | 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 +import subprocess + +from ctypes import * + +logger = logging.getLogger(__name__) + + +class RaspberryPi: + # Pin definition + RST_PIN = 17 + DC_PIN = 25 + CS_PIN = 8 + BUSY_PIN = 24 + PWR_PIN = 18 + MOSI_PIN = 10 + SCLK_PIN = 11 + + def __init__(self): + import spidev + import gpiozero + + self.SPI = spidev.SpiDev() + self.GPIO_RST_PIN = gpiozero.LED(self.RST_PIN) + self.GPIO_DC_PIN = gpiozero.LED(self.DC_PIN) + # self.GPIO_CS_PIN = gpiozero.LED(self.CS_PIN) + self.GPIO_PWR_PIN = gpiozero.LED(self.PWR_PIN) + self.GPIO_BUSY_PIN = gpiozero.Button(self.BUSY_PIN, pull_up = False) + + + + def digital_write(self, pin, value): + if pin == self.RST_PIN: + if value: + self.GPIO_RST_PIN.on() + else: + self.GPIO_RST_PIN.off() + elif pin == self.DC_PIN: + if value: + self.GPIO_DC_PIN.on() + else: + self.GPIO_DC_PIN.off() + # elif pin == self.CS_PIN: + # if value: + # self.GPIO_CS_PIN.on() + # else: + # self.GPIO_CS_PIN.off() + elif pin == self.PWR_PIN: + if value: + self.GPIO_PWR_PIN.on() + else: + self.GPIO_PWR_PIN.off() + + def digital_read(self, pin): + if pin == self.BUSY_PIN: + return self.GPIO_BUSY_PIN.value + elif pin == self.RST_PIN: + return self.RST_PIN.value + elif pin == self.DC_PIN: + return self.DC_PIN.value + # elif pin == self.CS_PIN: + # return self.CS_PIN.value + elif pin == self.PWR_PIN: + return self.PWR_PIN.value + + 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 DEV_SPI_write(self, data): + self.DEV_SPI.DEV_SPI_SendData(data) + + def DEV_SPI_nwrite(self, data): + self.DEV_SPI.DEV_SPI_SendnData(data) + + def DEV_SPI_read(self): + return self.DEV_SPI.DEV_SPI_ReadData() + + def module_init(self, cleanup=False): + self.GPIO_PWR_PIN.on() + + if cleanup: + find_dirs = [ + os.path.dirname(os.path.realpath(__file__)), + '/usr/local/lib', + '/usr/lib', + ] + self.DEV_SPI = None + for find_dir in find_dirs: + val = int(os.popen('getconf LONG_BIT').read()) + logging.debug("System is %d bit"%val) + if val == 64: + so_filename = os.path.join(find_dir, 'DEV_Config_64.so') + else: + so_filename = os.path.join(find_dir, 'DEV_Config_32.so') + if os.path.exists(so_filename): + self.DEV_SPI = CDLL(so_filename) + break + if self.DEV_SPI is None: + RuntimeError('Cannot find DEV_Config.so') + + self.DEV_SPI.DEV_Module_Init() + + else: + # 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, cleanup=False): + logger.debug("spi end") + self.SPI.close() + + self.GPIO_RST_PIN.off() + self.GPIO_DC_PIN.off() + self.GPIO_PWR_PIN.off() + logger.debug("close 5V, Module enters 0 power consumption ...") + + if cleanup: + self.GPIO_RST_PIN.close() + self.GPIO_DC_PIN.close() + # self.GPIO_CS_PIN.close() + self.GPIO_PWR_PIN.close() + self.GPIO_BUSY_PIN.close() + + + + + +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 sys.version_info[0] == 2: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE) +else: + process = subprocess.Popen("cat /proc/cpuinfo | grep Raspberry", shell=True, stdout=subprocess.PIPE, text=True) +output, _ = process.communicate() +if sys.version_info[0] == 2: + output = output.decode(sys.stdout.encoding) + +if "Raspberry" in output: + 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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7a3ed80 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +colorzero==2.0 +gpiozero==2.0.1 +lgpio==0.2.2.0 +pillow==11.3.0 +rpi-lgpio==0.6 +spidev==3.7 diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..947098d --- /dev/null +++ b/update.sh @@ -0,0 +1,7 @@ +echo "Generate document..." +./typst compile --format png document.typ +echo "Convert to image..." +mogrify -resize 400x300^ -gravity center -extent 400x300 document_ready.png document_ready.bmp +echo "Update display..." +python3 display.py +echo "Done."