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
"""
__version__ = 0.2
__version__ = 0.3
###########
# IMPORTS #
@ -30,29 +30,20 @@ import paho.mqtt.client as mqtt
import time
import json
import netifaces, socket
import epd2in13
import inkyphat
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
##########
# Config #
##########
mqtt_client_id = "epaper_display"
# Colors codes
white = 255
black = 0
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()
white = inkyphat.WHITE
black = inkyphat.BLACK
grey = inkyphat.RED
#############
# CALLBACKS #
@ -92,21 +83,23 @@ def on_message(client, userdata, msg):
val['unit'] = val['unit'].replace('deg', '°')
#List of 'displayable' data types and their coordinate on screen
coord_type = { 'AT' : (12, 50),
'RH' : (137, 50),
'AP' : (12, 72),
'ALTI' : (137, 72),
coord_type = { 'AT' : (1, data1_line),
'RH' : (inkyphat.WIDTH / 2 + 12, data1_line),
'AP' : (1, data2_line),
'ALTI' : (inkyphat.WIDTH / 2 + 12, data2_line),
}
print(val)
if val['type'] in coord_type:
#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.text(coord_type[val['type']],
"{:6.1f}{} ".format(val['value'], val['unit']),
font=font20_bold,
fill=black)
update_epd()
inkyphat.text(coord_type[val['type']],
"{:6.1f}{} ".format(val['value'], val['unit']),
font=data_font,
fill=black)
inkyphat.show()
# Print feather's date and time on epaper display
elif subtopics[0] == "SYS":
@ -121,11 +114,11 @@ def on_message(client, userdata, msg):
dt_texte = payload
print(dt_texte)
# 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
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):
"""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 pl == "1" and msg.topic == camera_mqtt_topic + "/shooting" :
draw.rectangle((0, 106, epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), fill=white)
draw.text((0, 106), "Shooting photo... ", font=font14, fill=black)
update_epd()
inkyphat.rectangle((0, inkyphat.HEIGHT - 13, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
inkyphat.text((0, inkyphat.HEIGHT - 13), "Shooting photo... ", font=font12, fill=black)
inkyphat.show()
#Last picture name (with date/time)
if msg.topic == camera_mqtt_topic + "/last_photo":
draw.rectangle((0, 106, epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), fill=white)
draw.text((0, 106), "Last: " + pl, font=font14, fill=black)
update_epd()
inkyphat.rectangle((0, inkyphat.HEIGHT - 12, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
inkyphat.text((0, inkyphat.HEIGHT - 12), "Last: " + pl, font=font12, fill=black)
inkyphat.show()
def on_disconnect(client, userdata, msg):
@ -158,51 +151,52 @@ def on_disconnect(client, userdata, msg):
########
#Screen init.
epd = epd2in13.EPD()
epd.init(epd.lut_full_update)
inkyphat.set_colour("black")
#List of TrueType fonts
font12 = ImageFont.truetype(epd_font_file, 12)
font14 = ImageFont.truetype(epd_font_file, 14)
font16 = ImageFont.truetype(epd_font_file, 16)
font20 = ImageFont.truetype(epd_font_file, 20)
font12_bold = ImageFont.truetype(epd_bold_font_file, 12)
font14_bold = ImageFont.truetype(epd_bold_font_file, 14)
font16_bold = ImageFont.truetype(epd_bold_font_file, 16)
font20_bold = ImageFont.truetype(epd_bold_font_file, 20)
#font12_obl = ImageFont.truetype(epd_italic_font_file, 12)
#font14_obl = ImageFont.truetype(epd_italic_font_file, 14)
#font16_obl = ImageFont.truetype(epd_italic_font_file, 16)
font10 = inkyphat.ImageFont.truetype(epd_font_file, 10)
font11 = inkyphat.ImageFont.truetype(epd_font_file, 11)
font12 = inkyphat.ImageFont.truetype(epd_font_file, 12)
font14 = inkyphat.ImageFont.truetype(epd_font_file, 14)
font16 = inkyphat.ImageFont.truetype(epd_font_file, 16)
font20 = inkyphat.ImageFont.truetype(epd_font_file, 20)
font12_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 12)
font14_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 14)
font16_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 16)
font20_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 20)
#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
# Memo : 0=black, 255= white/blank
image = Image.new('1', (epd2in13.EPD_HEIGHT, epd2in13.EPD_WIDTH), 255)
draw = ImageDraw.Draw(image)
# Lines and fonts
title_line = 1
title_font = font14_bold
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.rectangle((0, 0, epd2in13.EPD_HEIGHT, 18), fill=black)
draw.text((60, 1),
"Projet Camétéo",
font=font16_bold,
fill=white)
inkyphat.rectangle((0, 0, inkyphat.WIDTH, 17), fill=grey)
inkyphat.text((60, 1), "Projet Camétéo", font=title_font, fill=white)
#Write hostname and IP address
draw.text((0, 19),
"{} : IP= {}".format(socket.gethostname(),
netifaces.ifaddresses('wlan0')[2][0]['addr']),
font = font12,
fill=black )
inkyphat.text((0, 19),
"{} : {}".format(socket.gethostname(),
netifaces.ifaddresses('wlan0')[2][0]['addr']),
font = hostname_font,
fill = black )
# Draw the black lines for data table
draw.line((0,49,epd2in13.EPD_HEIGHT, 49), fill=black)
draw.line((0,71,epd2in13.EPD_HEIGHT, 71), fill=black)
draw.line((0,93,epd2in13.EPD_HEIGHT, 93), fill=black)
draw.line((125, 49, 125, 93), fill=black)
inkyphat.line((0, data1_line - 1, inkyphat.WIDTH, data1_line - 1), fill=black)
inkyphat.line((0, data2_line - 1, inkyphat.WIDTH, data2_line - 1), fill=black)
inkyphat.line((0, data_table_bottom_line, inkyphat.WIDTH, data_table_bottom_line), fill=black)
inkyphat.line((inkyphat.WIDTH / 2, data1_line - 1, inkyphat.WIDTH / 2, data_table_bottom_line), fill=black)
#Display on ePaper screen
epd.clear_frame_memory(0xFF)
update_epd()
#Toggle to partial refresh
epd.init(epd.lut_partial_update)
inkyphat.show()
#Connect to MQTT broker and loop...
mqtt_client = mqtt.Client(mqtt_client_id, clean_session=True)