Skip to content

Commit

Permalink
Merge pull request #56 from loiccoyle/chore/cleanup_waveshare_lib
Browse files Browse the repository at this point in the history
refactor: cleanup waveshare lib
  • Loading branch information
loiccoyle committed Jun 16, 2024
2 parents 9c29da6 + 1e4f8ee commit e9cc23a
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 1,045 deletions.
11 changes: 5 additions & 6 deletions tests/unit/test_display.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from typing import Literal
from unittest import TestCase

import pandas as pd
Expand All @@ -14,22 +13,22 @@


class EPDMock(EPDMonochrome):
width = 122
height = 250

def __init__(self) -> None:
self.is_init = False
self.width = 122
self.height = 250

def init(self) -> Literal[0, -1]:
def init(self) -> None:
self.is_init = True
return 0

def getbuffer(self, image: Image.Image) -> bytearray:
return bytearray()

def display(self, image: bytearray) -> None:
pass

def Clear(self) -> None:
def clear(self) -> None:
pass

def sleep(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tinyticker/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def init_epd(self):
"""Initialize the ePaper display module."""
self._log.info("Init ePaper display.")
self.epd.init()
self.epd.Clear()
self.epd.clear()

def text(self, text: str, show: bool = False, **kwargs) -> Tuple[Figure, Axes]:
"""Create a `plt.Figure` and `plt.Axes` with centered text.
Expand Down
68 changes: 54 additions & 14 deletions tinyticker/waveshare_lib/_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import math
from abc import abstractmethod
from typing import Literal, Optional, Tuple, Type
from typing import Optional, Tuple, Type

import numpy as np
from PIL import Image
Expand All @@ -14,8 +15,13 @@ class EPDBase:
width: int
height: int

@abstractmethod
def __init__(self, device: Type[RaspberryPi] = RaspberryPi) -> None: ...
def __init__(self, Device: Type[RaspberryPi] = RaspberryPi) -> None:
self.device = Device()
self.reset_pin = self.device.RST_PIN
self.dc_pin = self.device.DC_PIN
self.busy_pin = self.device.BUSY_PIN
self.cs_pin = self.device.CS_PIN
self._blank = bytearray([0xFF] * math.ceil(self.width / 8) * self.height)

@property
def size(self) -> Tuple[int, int]:
Expand All @@ -28,12 +34,8 @@ def size(self) -> Tuple[int, int]:
)

@abstractmethod
def init(self) -> Literal[0, -1]:
"""Initializes the display.
Returns:
The initialization status. It can be either 0 or -1.
"""
def init(self) -> None:
"""Initialize the display."""
...

def getbuffer(self, image: Image.Image) -> bytearray:
Expand Down Expand Up @@ -61,12 +63,39 @@ def getbuffer(self, image: Image.Image) -> bytearray:
image = image.convert("1", dither=Image.Dither.NONE)
return bytearray(image.tobytes())

@abstractmethod
def Clear(self) -> None:
"""Clear the display."""
...
def send_command(self, command: int) -> None:
"""Send command to the display.
Args:
command: The command to send.
"""
self.device.digital_write(self.dc_pin, 0)
self.device.digital_write(self.cs_pin, 0)
self.device.spi_writebyte([command])
self.device.digital_write(self.cs_pin, 1)

def send_data(self, data: int) -> None:
"""Send data to the display.
Args:
data: The data to send.
"""
self.device.digital_write(self.dc_pin, 1)
self.device.digital_write(self.cs_pin, 0)
self.device.spi_writebyte([data])
self.device.digital_write(self.cs_pin, 1)

def send_data2(self, data: bytearray) -> None:
"""Send a bunch of data bytes to the display.
Args:
data: The data to send.
"""
self.device.digital_write(self.dc_pin, 1)
self.device.digital_write(self.cs_pin, 0)
self.device.spi_writebyte2(data)
self.device.digital_write(self.cs_pin, 1)

@abstractmethod
def sleep(self) -> None:
"""Put the display into sleep mode."""
...
Expand All @@ -80,6 +109,11 @@ def show(self, image: Image.Image) -> None:
"""
...

@abstractmethod
def clear(self) -> None:
"""Clear the display."""
...


class EPDMonochrome(EPDBase):
"""EPD with only black and white color"""
Expand All @@ -93,6 +127,9 @@ def display(self, image: bytearray) -> None:
"""
...

def clear(self) -> None:
self.display(self._blank)

def show(self, image: Image.Image) -> None:
self.display(self.getbuffer(image))

Expand All @@ -112,6 +149,9 @@ def display(
"""
...

def clear(self) -> None:
self.display(self._blank, highlights=self._blank)

def show(self, image: Image.Image) -> None:
threshold = 20
highlight_buffer = None
Expand Down
1 change: 0 additions & 1 deletion tinyticker/waveshare_lib/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ def module_init(self):
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")
Expand Down
92 changes: 7 additions & 85 deletions tinyticker/waveshare_lib/epd2in13.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,13 @@
# *****************************************************************************
# * | File : epd2in13.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 typing import Type

from ._base import EPDMonochrome
from .device import RaspberryPi

# Display resolution
EPD_WIDTH = 122
EPD_HEIGHT = 250

logger = logging.getLogger(__name__)


class EPD(EPDMonochrome):
def __init__(self, device: Type[RaspberryPi] = RaspberryPi):
self.device = device()
self.reset_pin = self.device.RST_PIN
self.dc_pin = self.device.DC_PIN
self.busy_pin = self.device.BUSY_PIN
self.cs_pin = self.device.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT

width = 122
height = 250
lut_full_update = [
0x22,
0x55,
Expand Down Expand Up @@ -83,7 +41,6 @@ def __init__(self, device: Type[RaspberryPi] = RaspberryPi):
0x00,
]

# Hardware reset
def reset(self):
self.device.digital_write(self.reset_pin, 1)
self.device.delay_ms(200)
Expand All @@ -92,21 +49,11 @@ def reset(self):
self.device.digital_write(self.reset_pin, 1)
self.device.delay_ms(200)

def send_command(self, command):
self.device.digital_write(self.dc_pin, 0)
self.device.digital_write(self.cs_pin, 0)
self.device.spi_writebyte([command])
self.device.digital_write(self.cs_pin, 1)

def send_data(self, data):
self.device.digital_write(self.dc_pin, 1)
self.device.digital_write(self.cs_pin, 0)
self.device.spi_writebyte([data])
self.device.digital_write(self.cs_pin, 1)

def ReadBusy(self):
logger.debug("e-Paper busy")
while self.device.digital_read(self.busy_pin) == 1: # 0: idle, 1: busy
self.device.delay_ms(100)
logger.debug("e-Paper busy release")

def TurnOnDisplay(self):
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
Expand All @@ -119,13 +66,12 @@ def TurnOnDisplay(self):
logger.debug("e-Paper busy release")

def init(self):
if self.device.module_init() != 0:
return -1
self.device.module_init()
# EPD hardware init start
self.reset()
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
self.send_data((EPD_HEIGHT - 1) & 0xFF)
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
self.send_data((self.height - 1) & 0xFF)
self.send_data(((self.height - 1) >> 8) & 0xFF)
self.send_data(0x00) # GD = 0 SM = 0 TB = 0

self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
Expand Down Expand Up @@ -153,11 +99,6 @@ def init(self):
for count in range(30):
self.send_data(self.lut_full_update[count])

return 0

##
# @brief: specify the memory area for data R/W
##
def SetWindows(self, x_start, y_start, x_end, y_end):
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
self.send_data((x_start >> 3) & 0xFF)
Expand All @@ -168,9 +109,6 @@ def SetWindows(self, x_start, y_start, x_end, y_end):
self.send_data(y_end & 0xFF)
self.send_data((y_end >> 8) & 0xFF)

##
# @brief: specify the start point for data R/W
##
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
Expand All @@ -194,24 +132,8 @@ def display(self, image):
self.send_data(image[i + j * linewidth])
self.TurnOnDisplay()

def Clear(self, color=0xFF):
if self.width % 8 == 0:
linewidth = int(self.width / 8)
else:
linewidth = int(self.width / 8) + 1

self.SetWindows(0, 0, self.width, self.height)
for j in range(0, self.height):
self.SetCursor(0, j)
self.send_command(0x24)
for _ in range(0, linewidth):
self.send_data(color)
self.TurnOnDisplay()

def sleep(self):
self.send_command(0x10) # enter deep sleep
self.send_data(0x01)
self.device.delay_ms(100)

self.device.delay_ms(2000)
self.device.module_exit()
Loading

0 comments on commit e9cc23a

Please sign in to comment.