Move from waveshare e-ink display to pimoroni's inkyphat

(run install_inkyphat_lib.sh to install dependencies)
This commit is contained in:
Pierrick C 2018-08-12 13:17:59 +02:00
parent ac55131ac5
commit c65b4d3c44
4 changed files with 1185 additions and 407 deletions

View File

@ -1,271 +0,0 @@
##
# @filename : epd2in13.py
# @brief : Implements for e-paper library
# @author : Yehui from Waveshare
#
# Copyright (C) Waveshare September 9 2017
#
# 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 epdif
from PIL import Image
import RPi.GPIO as GPIO
# Display resolution
EPD_WIDTH = 128
EPD_HEIGHT = 250
# EPD2IN13 commands
DRIVER_OUTPUT_CONTROL = 0x01
BOOSTER_SOFT_START_CONTROL = 0x0C
GATE_SCAN_START_POSITION = 0x0F
DEEP_SLEEP_MODE = 0x10
DATA_ENTRY_MODE_SETTING = 0x11
SW_RESET = 0x12
TEMPERATURE_SENSOR_CONTROL = 0x1A
MASTER_ACTIVATION = 0x20
DISPLAY_UPDATE_CONTROL_1 = 0x21
DISPLAY_UPDATE_CONTROL_2 = 0x22
WRITE_RAM = 0x24
WRITE_VCOM_REGISTER = 0x2C
WRITE_LUT_REGISTER = 0x32
SET_DUMMY_LINE_PERIOD = 0x3A
SET_GATE_TIME = 0x3B
BORDER_WAVEFORM_CONTROL = 0x3C
SET_RAM_X_ADDRESS_START_END_POSITION = 0x44
SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45
SET_RAM_X_ADDRESS_COUNTER = 0x4E
SET_RAM_Y_ADDRESS_COUNTER = 0x4F
TERMINATE_FRAME_READ_WRITE = 0xFF
class EPD:
def __init__(self):
self.reset_pin = epdif.RST_PIN
self.dc_pin = epdif.DC_PIN
self.busy_pin = epdif.BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
self.lut = self.lut_full_update
lut_full_update = [
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
]
lut_partial_update = [
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
def digital_write(self, pin, value):
epdif.epd_digital_write(pin, value)
def digital_read(self, pin):
return epdif.epd_digital_read(pin)
def delay_ms(self, delaytime):
epdif.epd_delay_ms(delaytime)
def send_command(self, command):
self.digital_write(self.dc_pin, GPIO.LOW)
# the parameter type is list but not int
# so use [command] instead of command
epdif.spi_transfer([command])
def send_data(self, data):
self.digital_write(self.dc_pin, GPIO.HIGH)
# the parameter type is list but not int
# so use [data] instead of data
epdif.spi_transfer([data])
def init(self, lut):
if (epdif.epd_init() != 0):
return -1
# EPD hardware init start
self.lut = lut
self.reset()
self.send_command(DRIVER_OUTPUT_CONTROL)
self.send_data((EPD_HEIGHT - 1) & 0xFF)
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
self.send_command(BOOSTER_SOFT_START_CONTROL)
self.send_data(0xD7)
self.send_data(0xD6)
self.send_data(0x9D)
self.send_command(WRITE_VCOM_REGISTER)
self.send_data(0xA8) # VCOM 7C
self.send_command(SET_DUMMY_LINE_PERIOD)
self.send_data(0x1A) # 4 dummy lines per gate
self.send_command(SET_GATE_TIME)
self.send_data(0x08) # 2us per line
self.send_command(DATA_ENTRY_MODE_SETTING)
self.send_data(0x03) # X increment Y increment
self.set_lut(self.lut)
# EPD hardware init end
return 0
def wait_until_idle(self):
while(self.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
self.delay_ms(20)
##
# @brief: module reset.
# often used to awaken the module in deep sleep,
##
def reset(self):
self.digital_write(self.reset_pin, GPIO.LOW) # module reset
self.delay_ms(200)
self.digital_write(self.reset_pin, GPIO.HIGH)
self.delay_ms(200)
##
# @brief: set the look-up table register
##
def set_lut(self, lut):
self.lut = lut
self.send_command(WRITE_LUT_REGISTER)
# the length of look-up table is 30 bytes
for i in range(0, len(lut)):
self.send_data(self.lut[i])
##
# @brief: convert an image to a buffer
##
def get_frame_buffer(self, image):
buf = [0x00] * (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[(x + y * self.width) / 8] |= 0x80 >> (x % 8)
return buf
##
# @brief: put an image to the frame memory.
# this won't update the display.
##
def set_frame_memory(self, image, x, y):
if (image == None or x < 0 or y < 0):
return
image_monocolor = image.convert('1')
image_width, image_height = image_monocolor.size
# x point must be the multiple of 8 or the last 3 bits will be ignored
x = x & 0xF8
image_width = image_width & 0xF8
if (x + image_width >= self.width):
x_end = self.width - 1
else:
x_end = x + image_width - 1
if (y + image_height >= self.height):
y_end = self.height - 1
else:
y_end = y + image_height - 1
self.set_memory_area(x, y, x_end, y_end)
# send the image data
pixels = image_monocolor.load()
byte_to_send = 0x00
for j in range(y, y_end + 1):
self.set_memory_pointer(x, j)
self.send_command(WRITE_RAM)
# 1 byte = 8 pixels, steps of i = 8
for i in range(x, x_end + 1):
# Set the bits for the column of pixels at the current position.
if pixels[i - x, j - y] != 0:
byte_to_send |= 0x80 >> (i % 8)
if (i % 8 == 7):
self.send_data(byte_to_send)
byte_to_send = 0x00
##
# @brief: clear the frame memory with the specified color.
# this won't update the display.
##
def clear_frame_memory(self, color):
self.set_memory_area(0, 0, self.width - 1, self.height - 1)
self.set_memory_pointer(0, 0)
self.send_command(WRITE_RAM)
# send the color data
for i in range(0, int(self.width / 8 * self.height)):
self.send_data(color)
##
# @brief: update the display
# there are 2 memory areas embedded in the e-paper display
# but once this function is called,
# the the next action of SetFrameMemory or ClearFrame will
# set the other memory area.
##
def display_frame(self):
self.send_command(DISPLAY_UPDATE_CONTROL_2)
self.send_data(0xC4)
self.send_command(MASTER_ACTIVATION)
self.send_command(TERMINATE_FRAME_READ_WRITE)
self.wait_until_idle()
##
# @brief: specify the memory area for data R/W
##
def set_memory_area(self, x_start, y_start, x_end, y_end):
self.send_command(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(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)
##
# @brief: specify the start point for data R/W
##
def set_memory_pointer(self, x, y):
self.send_command(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 >> 3) & 0xFF)
self.send_command(SET_RAM_Y_ADDRESS_COUNTER)
self.send_data(y & 0xFF)
self.send_data((y >> 8) & 0xFF)
self.wait_until_idle()
##
# @brief: After this command is transmitted, the chip would enter the
# deep-sleep mode to save power.
# The deep sleep mode would return to standby by hardware reset.
# You can use reset() to awaken or init() to initialize
##
def sleep(self):
self.send_command(DEEP_SLEEP_MODE)
self.wait_until_idle()
### END OF FILE ###

View File

@ -1,63 +0,0 @@
##
# @filename : epdif.py
# @brief : EPD hardware interface implements (GPIO, SPI)
# @author : Yehui from Waveshare
#
# Copyright (C) Waveshare July 10 2017
#
# 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 spidev
import RPi.GPIO as GPIO
import time
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# SPI device, bus = 0, device = 0
SPI = spidev.SpiDev(0, 0)
def epd_digital_write(pin, value):
GPIO.output(pin, value)
def epd_digital_read(pin):
return GPIO.input(BUSY_PIN)
def epd_delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_transfer(data):
SPI.writebytes(data)
def epd_init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
SPI.max_speed_hz = 2000000
SPI.mode = 0b00
return 0;
### END OF FILE ###

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ Display data from MQTT broker on ePaper screen
@author: arofarn @author: arofarn
""" """
__version__ = 0.2 __version__ = 0.3
########### ###########
# IMPORTS # # IMPORTS #
@ -30,29 +30,20 @@ import paho.mqtt.client as mqtt
import time import time
import json import json
import netifaces, socket import netifaces, socket
import epd2in13 import inkyphat
from PIL import Image from PIL import Image
from PIL import ImageDraw from PIL import ImageDraw
from PIL import ImageFont from PIL import ImageFont
##########
# Config #
##########
mqtt_client_id = "epaper_display" mqtt_client_id = "epaper_display"
# Colors codes # Colors codes
white = 255 white = inkyphat.WHITE
black = 0 black = inkyphat.BLACK
grey = inkyphat.RED
def update_epd():
"""
Update ePaper Display
"""
# 90° rotation + 180° if parameter epd_rotate different of 0
# The small translation corrects an offset after rotation of 270°
screen = image.rotate(90 + (epd_rotate * 180),
expand=True,
translate=(-6*epd_rotate, 0))
# Put the picture to display in frame memory and display the new frame
epd.set_frame_memory(screen, 0, 0)
epd.display_frame()
############# #############
# CALLBACKS # # CALLBACKS #
@ -92,21 +83,23 @@ def on_message(client, userdata, msg):
val['unit'] = val['unit'].replace('deg', '°') val['unit'] = val['unit'].replace('deg', '°')
#List of 'displayable' data types and their coordinate on screen #List of 'displayable' data types and their coordinate on screen
coord_type = { 'AT' : (12, 50), coord_type = { 'AT' : (1, data1_line),
'RH' : (137, 50), 'RH' : (inkyphat.WIDTH / 2 + 12, data1_line),
'AP' : (12, 72), 'AP' : (1, data2_line),
'ALTI' : (137, 72), 'ALTI' : (inkyphat.WIDTH / 2 + 12, data2_line),
} }
print(val) print(val)
if val['type'] in coord_type: if val['type'] in coord_type:
#Erase old data #Erase old data
draw.rectangle(coord_type[val['type']] + (coord_type[val['type']][0] + 112, coord_type[val['type']][1] + 20), fill=white) inkyphat.rectangle(coord_type[val['type']] + (coord_type[val['type']][0] + inkyphat.WIDTH / 2 - 2,
coord_type[val['type']][1] + 16),
fill=white)
#Draw new values #Draw new values
draw.text(coord_type[val['type']], inkyphat.text(coord_type[val['type']],
"{:6.1f}{} ".format(val['value'], val['unit']), "{:6.1f}{} ".format(val['value'], val['unit']),
font=font20_bold, font=data_font,
fill=black) fill=black)
update_epd() inkyphat.show()
# Print feather's date and time on epaper display # Print feather's date and time on epaper display
elif subtopics[0] == "SYS": elif subtopics[0] == "SYS":
@ -121,11 +114,11 @@ def on_message(client, userdata, msg):
dt_texte = payload dt_texte = payload
print(dt_texte) print(dt_texte)
# First erase old text # First erase old text
draw.rectangle((0, 34, epd2in13.EPD_HEIGHT, 48), fill=white) inkyphat.rectangle((0, 31, inkyphat.WIDTH, 45), fill=white)
# and draw the new date # and draw the new date
draw.text((0, 34), dt_texte, font=font14, fill=black) inkyphat.text((0, 31), dt_texte, font=font12, fill=black)
update_epd() inkyphat.show()
def on_message_camera(client, userdata, msg): def on_message_camera(client, userdata, msg):
"""Update display with info from camera (camera shooting new picture or name """Update display with info from camera (camera shooting new picture or name
@ -136,15 +129,15 @@ def on_message_camera(client, userdata, msg):
# If a picture is been taken # If a picture is been taken
if pl == "1" and msg.topic == camera_mqtt_topic + "/shooting" : if pl == "1" and msg.topic == camera_mqtt_topic + "/shooting" :
draw.rectangle((0, 106, epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), fill=white) inkyphat.rectangle((0, inkyphat.HEIGHT - 13, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
draw.text((0, 106), "Shooting photo... ", font=font14, fill=black) inkyphat.text((0, inkyphat.HEIGHT - 13), "Shooting photo... ", font=font12, fill=black)
update_epd() inkyphat.show()
#Last picture name (with date/time) #Last picture name (with date/time)
if msg.topic == camera_mqtt_topic + "/last_photo": if msg.topic == camera_mqtt_topic + "/last_photo":
draw.rectangle((0, 106, epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), fill=white) inkyphat.rectangle((0, inkyphat.HEIGHT - 12, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
draw.text((0, 106), "Last: " + pl, font=font14, fill=black) inkyphat.text((0, inkyphat.HEIGHT - 12), "Last: " + pl, font=font12, fill=black)
update_epd() inkyphat.show()
def on_disconnect(client, userdata, msg): def on_disconnect(client, userdata, msg):
@ -158,51 +151,52 @@ def on_disconnect(client, userdata, msg):
######## ########
#Screen init. #Screen init.
epd = epd2in13.EPD() inkyphat.set_colour("black")
epd.init(epd.lut_full_update)
#List of TrueType fonts #List of TrueType fonts
font12 = ImageFont.truetype(epd_font_file, 12) font10 = inkyphat.ImageFont.truetype(epd_font_file, 10)
font14 = ImageFont.truetype(epd_font_file, 14) font11 = inkyphat.ImageFont.truetype(epd_font_file, 11)
font16 = ImageFont.truetype(epd_font_file, 16) font12 = inkyphat.ImageFont.truetype(epd_font_file, 12)
font20 = ImageFont.truetype(epd_font_file, 20) font14 = inkyphat.ImageFont.truetype(epd_font_file, 14)
font12_bold = ImageFont.truetype(epd_bold_font_file, 12) font16 = inkyphat.ImageFont.truetype(epd_font_file, 16)
font14_bold = ImageFont.truetype(epd_bold_font_file, 14) font20 = inkyphat.ImageFont.truetype(epd_font_file, 20)
font16_bold = ImageFont.truetype(epd_bold_font_file, 16) font12_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 12)
font20_bold = ImageFont.truetype(epd_bold_font_file, 20) font14_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 14)
#font12_obl = ImageFont.truetype(epd_italic_font_file, 12) font16_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 16)
#font14_obl = ImageFont.truetype(epd_italic_font_file, 14) font20_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 20)
#font16_obl = ImageFont.truetype(epd_italic_font_file, 16) #font12_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 12)
#font14_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 14)
#font16_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 16)
#New image buffer # Lines and fonts
# Memo : 0=black, 255= white/blank title_line = 1
image = Image.new('1', (epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), 255) title_font = font14_bold
draw = ImageDraw.Draw(image) hostname_line = 19
hostname_font = font11
date_line = 31
date_font = font12
data1_line = 44
data2_line = 62
data_font = font16_bold
data_table_bottom_line = data2_line + 17
#Draw title in white on black #Draw title in white on black
draw.rectangle((0, 0, epd2in13.EPD_HEIGHT, 18), fill=black) inkyphat.rectangle((0, 0, inkyphat.WIDTH, 17), fill=grey)
draw.text((60, 1), inkyphat.text((60, 1), "Projet Camétéo", font=title_font, fill=white)
"Projet Camétéo",
font=font16_bold,
fill=white)
#Write hostname and IP address #Write hostname and IP address
draw.text((0, 19), inkyphat.text((0, 19),
"{} : IP= {}".format(socket.gethostname(), "{} : {}".format(socket.gethostname(),
netifaces.ifaddresses('wlan0')[2][0]['addr']), netifaces.ifaddresses('wlan0')[2][0]['addr']),
font = font12, font = hostname_font,
fill = black ) fill = black )
# Draw the black lines for data table # Draw the black lines for data table
draw.line((0,49,epd2in13.EPD_HEIGHT, 49), fill=black) inkyphat.line((0, data1_line - 1, inkyphat.WIDTH, data1_line - 1), fill=black)
draw.line((0,71,epd2in13.EPD_HEIGHT, 71), fill=black) inkyphat.line((0, data2_line - 1, inkyphat.WIDTH, data2_line - 1), fill=black)
draw.line((0,93,epd2in13.EPD_HEIGHT, 93), fill=black) inkyphat.line((0, data_table_bottom_line, inkyphat.WIDTH, data_table_bottom_line), fill=black)
draw.line((125, 49, 125, 93), fill=black) inkyphat.line((inkyphat.WIDTH / 2, data1_line - 1, inkyphat.WIDTH / 2, data_table_bottom_line), fill=black)
#Display on ePaper screen inkyphat.show()
epd.clear_frame_memory(0xFF)
update_epd()
#Toggle to partial refresh
epd.init(epd.lut_partial_update)
#Connect to MQTT broker and loop... #Connect to MQTT broker and loop...
mqtt_client = mqtt.Client(mqtt_client_id, clean_session=True) mqtt_client = mqtt.Client(mqtt_client_id, clean_session=True)