@ -1,6 +0,0 @@
|
||||
*.py[cod]
|
||||
*.secret
|
||||
|
||||
test/*
|
||||
.DS_Store
|
||||
.directory
|
||||
@ -1,5 +0,0 @@
|
||||
{
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.pythonPath": "/usr/bin/python2"
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
import re
|
||||
import time
|
||||
#import widgets.widget as widget
|
||||
import argparse
|
||||
import os
|
||||
import toml
|
||||
|
||||
import requests
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
import drivers.epd7in5b
|
||||
testMode = False
|
||||
except:
|
||||
testMode = True
|
||||
|
||||
canvas_url_black = 'http://0.0.0.0:5000/display'
|
||||
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
parser = argparse.ArgumentParser(description="NotiFrame display")
|
||||
parser.add_argument('-t', '--test', help='enable test mode', action='store_true')
|
||||
args = parser.parse_args()
|
||||
if args.test:
|
||||
testMode = True
|
||||
|
||||
print("RUNNING IN TESTMODE: "+str(testMode))
|
||||
if not testMode:
|
||||
epd = drivers.epd7in5b.EPD()
|
||||
epd.init()
|
||||
|
||||
def render(index):
|
||||
while(True):
|
||||
print("requesting display image")
|
||||
try:
|
||||
blk = requests.get(canvas_url_black)
|
||||
#ylw = requests.get(urlylw)
|
||||
image_black = Image.open(BytesIO(blk.content))
|
||||
image_yellow = None
|
||||
#image_yellow = Image.open(BytesIO(ylw.content))
|
||||
#draw_black.rectangle(xy=((0,0), image_black.size), fill=255)
|
||||
#draw_yellow.rectangle(xy=((0,0), image_yellow.size), fill=255)
|
||||
if not testMode:
|
||||
epd.display_frame(epd.get_frame_buffer(image_black.rotate(180)),epd.get_frame_buffer(image_yellow.rotate(180)))
|
||||
else:
|
||||
image_black.save(os.path.join(mydir, 'test/imgBlackDisplayClient.bmp'))
|
||||
#image_yellow.save(os.path.join(mydir, 'test/imgYellow.bmp'))
|
||||
except:
|
||||
print("failed to load display image")
|
||||
i =+ 1
|
||||
time.sleep(20)
|
||||
i = 0
|
||||
render(i)
|
||||
@ -1,203 +0,0 @@
|
||||
##
|
||||
# @filename : epd7in5.py
|
||||
# @brief : Implements for Dual-color e-paper library
|
||||
# @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 epdif
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# Display resolution
|
||||
EPD_WIDTH = 640
|
||||
EPD_HEIGHT = 384
|
||||
|
||||
# EPD7IN5 commands
|
||||
PANEL_SETTING = 0x00
|
||||
POWER_SETTING = 0x01
|
||||
POWER_OFF = 0x02
|
||||
POWER_OFF_SEQUENCE_SETTING = 0x03
|
||||
POWER_ON = 0x04
|
||||
POWER_ON_MEASURE = 0x05
|
||||
BOOSTER_SOFT_START = 0x06
|
||||
DEEP_SLEEP = 0x07
|
||||
DATA_START_TRANSMISSION_1 = 0x10
|
||||
DATA_STOP = 0x11
|
||||
DISPLAY_REFRESH = 0x12
|
||||
IMAGE_PROCESS = 0x13
|
||||
LUT_FOR_VCOM = 0x20
|
||||
LUT_BLUE = 0x21
|
||||
LUT_WHITE = 0x22
|
||||
LUT_GRAY_1 = 0x23
|
||||
LUT_GRAY_2 = 0x24
|
||||
LUT_RED_0 = 0x25
|
||||
LUT_RED_1 = 0x26
|
||||
LUT_RED_2 = 0x27
|
||||
LUT_RED_3 = 0x28
|
||||
LUT_XON = 0x29
|
||||
PLL_CONTROL = 0x30
|
||||
TEMPERATURE_SENSOR_COMMAND = 0x40
|
||||
TEMPERATURE_CALIBRATION = 0x41
|
||||
TEMPERATURE_SENSOR_WRITE = 0x42
|
||||
TEMPERATURE_SENSOR_READ = 0x43
|
||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
|
||||
LOW_POWER_DETECTION = 0x51
|
||||
TCON_SETTING = 0x60
|
||||
TCON_RESOLUTION = 0x61
|
||||
SPI_FLASH_CONTROL = 0x65
|
||||
REVISION = 0x70
|
||||
GET_STATUS = 0x71
|
||||
AUTO_MEASUREMENT_VCOM = 0x80
|
||||
READ_VCOM_VALUE = 0x81
|
||||
VCM_DC_SETTING = 0x82
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
if (epdif.epd_init() != 0):
|
||||
return -1
|
||||
self.reset()
|
||||
self.send_command(POWER_SETTING)
|
||||
self.send_data(0x37)
|
||||
self.send_data(0x00)
|
||||
self.send_command(PANEL_SETTING)
|
||||
self.send_data(0xCF)
|
||||
self.send_data(0x08)
|
||||
self.send_command(BOOSTER_SOFT_START)
|
||||
self.send_data(0xc7)
|
||||
self.send_data(0xcc)
|
||||
self.send_data(0x28)
|
||||
self.send_command(POWER_ON)
|
||||
self.wait_until_idle()
|
||||
self.send_command(PLL_CONTROL)
|
||||
self.send_data(0x3c)
|
||||
self.send_command(TEMPERATURE_CALIBRATION)
|
||||
self.send_data(0x00)
|
||||
self.send_command(VCOM_AND_DATA_INTERVAL_SETTING)
|
||||
self.send_data(0x77)
|
||||
self.send_command(TCON_SETTING)
|
||||
self.send_data(0x22)
|
||||
self.send_command(TCON_RESOLUTION)
|
||||
self.send_data(0x02) #source 640
|
||||
self.send_data(0x80)
|
||||
self.send_data(0x01) #gate 384
|
||||
self.send_data(0x80)
|
||||
self.send_command(VCM_DC_SETTING)
|
||||
self.send_data(0x1E) #decide by LUT file
|
||||
self.send_command(0xe5) #FLASH MODE
|
||||
self.send_data(0x03)
|
||||
|
||||
def wait_until_idle(self):
|
||||
while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle
|
||||
self.delay_ms(100)
|
||||
|
||||
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)
|
||||
|
||||
def get_frame_buffer(self, image):
|
||||
buf = [0xFF] * (self.width * self.height / 8)
|
||||
# Set buffer to value of Python Imaging Library image.
|
||||
# Image must be in mode L.
|
||||
image_grayscale = image.convert('1')
|
||||
imwidth, imheight = image_grayscale.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_grayscale.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
|
||||
|
||||
def display_frame(self, frame_buffer_black, frame_buffer_red):
|
||||
self.send_command(DATA_START_TRANSMISSION_1)
|
||||
for i in range(0, self.width / 8 * self.height):
|
||||
temp1 = frame_buffer_black[i]
|
||||
temp2 = frame_buffer_red[i]
|
||||
j = 0
|
||||
while (j < 8):
|
||||
if ((temp2 & 0x80) == 0x00):
|
||||
temp3 = 0x04 #red
|
||||
elif ((temp1 & 0x80) == 0x00):
|
||||
temp3 = 0x00 #black
|
||||
else:
|
||||
temp3 = 0x03 #white
|
||||
|
||||
temp3 = (temp3 << 4) & 0xFF
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
temp2 = (temp2 << 1) & 0xFF
|
||||
j += 1
|
||||
if((temp2 & 0x80) == 0x00):
|
||||
temp3 |= 0x04 #red
|
||||
elif ((temp1 & 0x80) == 0x00):
|
||||
temp3 |= 0x00 #black
|
||||
else:
|
||||
temp3 |= 0x03 #white
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
temp2 = (temp2 << 1) & 0xFF
|
||||
self.send_data(temp3)
|
||||
j += 1
|
||||
self.send_command(DISPLAY_REFRESH)
|
||||
self.delay_ms(100)
|
||||
self.wait_until_idle()
|
||||
|
||||
def sleep(self):
|
||||
self.send_command(POWER_OFF)
|
||||
self.wait_until_idle()
|
||||
self.send_command(DEEP_SLEEP)
|
||||
self.send_data(0xa5)
|
||||
|
||||
### END OF 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 ###
|
||||
|
Before Width: | Height: | Size: 30 KiB |
@ -1,239 +0,0 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, send_file
|
||||
import toml
|
||||
import os
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
import re
|
||||
import time
|
||||
import widgets.widget as widget
|
||||
import argparse
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
# print(mydir)
|
||||
###mydir = os.path.join(mydir, '../')
|
||||
# print(mydir)
|
||||
|
||||
def convert(input):
|
||||
if isinstance(input, dict):
|
||||
return dict((convert(key), convert(value)) for key, value in input.iteritems())
|
||||
elif isinstance(input, list):
|
||||
return [convert(element) for element in input]
|
||||
elif isinstance(input, unicode):
|
||||
return input.encode('utf-8')
|
||||
else:
|
||||
return input
|
||||
|
||||
####################################################
|
||||
####################################################
|
||||
####################################################
|
||||
|
||||
uni_config = toml.load(os.path.join(mydir, 'config.toml'))
|
||||
config = convert(uni_config)
|
||||
|
||||
config2 = {'resWidth': 640, 'resHeight': 384, 'cellsWidth': 3, 'cellsHeight': 3, 'widgets': [
|
||||
{'type': 'image', 'posX': 0, 'posY': 0, 'width': 3, 'height': 3, 'bwStyle': 'mono', 'scaleMode': 'fill', 'filename': 'forest.jpg'},
|
||||
{'type': 'trello', 'posX': 0, 'posY': 0, 'width': 1, 'height': 3, 'board': 'Organisation', 'list': 'Plans'},
|
||||
{'type': 'trello', 'posX': 1, 'posY': 0, 'width': 2, 'height': 3, 'board': 'E-paper', 'list': 'To Do:'}
|
||||
]}
|
||||
|
||||
cwidth = int(round(int(config['resWidth'])/int(config['cellsWidth'])))
|
||||
cheight = int(round(int(config['resHeight'])/int(config['cellsHeight'])))
|
||||
|
||||
image_yellow = Image.new('1', (config['resWidth'], config['resHeight']), 255) # 255: clear the frame
|
||||
draw_yellow = ImageDraw.Draw(image_yellow)
|
||||
image_black = Image.new('1', (config['resWidth'], config['resHeight']), 255) # 255: clear the frame
|
||||
draw_black = ImageDraw.Draw(image_black)
|
||||
|
||||
####################################################
|
||||
####################################################
|
||||
####################################################
|
||||
|
||||
# HOMEPAGE
|
||||
@app.route('/home', methods=['GET', 'POST'])
|
||||
def home(): # Toml config passed to html page via two dicts.
|
||||
# Form data passed back to webserver as JSON.
|
||||
config = read_config()
|
||||
widgLists, sysList = prep_dict_for_web(config)
|
||||
|
||||
if request.method == 'POST':
|
||||
jsonData = request.get_json()
|
||||
print(jsonData)
|
||||
jsonData = convert(jsonData)
|
||||
|
||||
# update system variables
|
||||
for key in sysList:
|
||||
print("%" + jsonData[key] + "$")
|
||||
if jsonData[key]:
|
||||
if isInt(jsonData[key]):
|
||||
#print(request.form[key] + " is int")
|
||||
sysList[key] = int(jsonData[key])
|
||||
else:
|
||||
sysList[key] = jsonData[key]
|
||||
# update widget variables
|
||||
# for i in range(len(widgLists)):
|
||||
# for key in widgLists[i]:
|
||||
# if request.form[key+str(i)]:
|
||||
# widgLists[i][key] = request.form[key+str(i)]
|
||||
for i in range(len(widgLists)):
|
||||
for key in widgLists[i]:
|
||||
# print(request.form[key+str(i)])
|
||||
if jsonData[key + str(i)]:
|
||||
if isInt(jsonData[key + str(i)]):
|
||||
#print(request.form[key+str(i)] + " is int")
|
||||
widgLists[i][key] = int(jsonData[key + str(i)])
|
||||
else:
|
||||
widgLists[i][key] = jsonData[key + str(i)]
|
||||
|
||||
update_config(widgLists, sysList)
|
||||
|
||||
widgTypes = widgetTypes()
|
||||
|
||||
return render_template('home.html', title='Overview', widgLists=widgLists, sysList=sysList, widgTypes=widgTypes)
|
||||
|
||||
@app.route('/newWidget', methods=['GET', 'POST'])
|
||||
def newWidget(): #
|
||||
#if request.method == 'GET':
|
||||
widgType = request.args['type']
|
||||
conf_defaults = pull_default_conf(widgType)
|
||||
#import pdb; pdb.set_trace()
|
||||
if request.method == 'POST':
|
||||
#widgType = request.form['ty']
|
||||
jsonData = request.get_json()
|
||||
print(jsonData)
|
||||
jsonData = convert(jsonData)
|
||||
|
||||
return render_template('newWidget.html', title='Overview', conf_defaults=conf_defaults)
|
||||
|
||||
|
||||
@app.route('/display', methods=['GET', 'POST'])
|
||||
def display():
|
||||
location = 'test/imgBlack.bmp'
|
||||
updateWidgets()
|
||||
return send_file(location)
|
||||
|
||||
|
||||
def pull_default_conf(widgType=None):
|
||||
uni_config = toml.load(os.path.join(mydir, 'config.toml'))
|
||||
config = convert(uni_config)
|
||||
|
||||
conf_dict = {
|
||||
'trello': {
|
||||
'board': {'default': '', 'datatype': 'text'},
|
||||
'list': {'default': '', 'datatype': 'text'},
|
||||
'width': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsWidth']},
|
||||
'height': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsHeight']},
|
||||
'posX': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsWidth']-1},
|
||||
'posY': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsHeight']-1}
|
||||
},
|
||||
'text': {
|
||||
'text': {'default': 'abcdefghijklmnopqrstuvwxyz', 'datatype': 'text'},
|
||||
'width': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsWidth']},
|
||||
'height': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsHeight']},
|
||||
'posX': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsWidth']-1},
|
||||
'posY': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsHeight']-1}
|
||||
},
|
||||
'image': {
|
||||
'file': {'default': 'img.jpg', 'datatype': 'text'},
|
||||
'bwMode': {'default': 'mono', 'datatype': 'text'},
|
||||
'scaleMode': {'default': 'fill', 'datatype': 'text'},
|
||||
'width': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsWidth']},
|
||||
'height': {'default': 1, 'datatype': 'number', 'min': 1, 'max': config['cellsHeight']},
|
||||
'posX': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsWidth']-1},
|
||||
'posY': {'default': 0, 'datatype': 'number', 'min': 0, 'max': config['cellsHeight']-1}
|
||||
}
|
||||
}
|
||||
|
||||
if widgType != None:
|
||||
conf_dict = conf_dict.get(widgType)
|
||||
|
||||
return conf_dict
|
||||
|
||||
def widgetTypes():
|
||||
dflt_conf = pull_default_conf()
|
||||
widgTypes = []
|
||||
for key in dflt_conf.keys():
|
||||
widgTypes.append(key)
|
||||
return widgTypes
|
||||
|
||||
def isInt(s):
|
||||
try:
|
||||
int(s)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def prep_dict_for_web(config):
|
||||
widgLists = config['widgets'] # list of dicts
|
||||
sysList = {} # dict
|
||||
for key in config:
|
||||
if key != 'widgets':
|
||||
sysList[key] = config[key]
|
||||
return widgLists, sysList
|
||||
|
||||
|
||||
def read_config():
|
||||
uni_config = toml.load(os.path.join(mydir, 'config.toml'))
|
||||
config = convert(uni_config)
|
||||
# print(config)
|
||||
# config = {'widgets': [
|
||||
#{'width': 3, 'posX': 0, 'posY': 0, 'scaleMode': 'fill', 'bwStyle': 'mono', 'type': 'image', 'filename': 'forest.jpg', 'height': 3},
|
||||
#{'list': 'Plans', 'height': 3, 'width': 1, 'board': 'Organisation', 'posX': 0, 'posY': 0, 'type': 'trello'},
|
||||
#{'list': 'To Do:', 'height': 3, 'width': 2, 'board': 'E-paper', 'posX': 1, 'posY': 0, 'type': 'trello'}
|
||||
# ], 'cellsHeight': 3, 'resHeight': 384, 'resWidth': 640, 'cellsWidth': 3}
|
||||
return config
|
||||
|
||||
|
||||
def update_config(widgLists, sysList):
|
||||
config = {'widgets': widgLists}
|
||||
for key in sysList:
|
||||
config[key] = sysList[key]
|
||||
convert(config)
|
||||
path = os.path.join(mydir, 'config.toml')
|
||||
with open(path, "w+") as config_file:
|
||||
config_file.write(toml.dumps(config))
|
||||
|
||||
####################################################
|
||||
####################################################
|
||||
####################################################
|
||||
|
||||
def initWidgets():
|
||||
widgetList = []
|
||||
for widg_conf in config['widgets']:
|
||||
if widg_conf['type'] == 'image':
|
||||
widgetList.append(widget.ImageWidget(cwidth, cheight, (widg_conf['posX'], widg_conf['posY']),
|
||||
(widg_conf['width'], widg_conf['height']), widg_conf['bwStyle'], widg_conf['scaleMode'],
|
||||
os.path.join(mydir, os.path.join('widgets/resources/images/', widg_conf['filename']))))
|
||||
if widg_conf['type'] == 'trello':
|
||||
widgetList.append(widget.TrelloWidget(cwidth, cheight, (widg_conf['posX'], widg_conf['posY']),
|
||||
(widg_conf['width'], widg_conf['height']), widg_conf['board'], widg_conf['list']))
|
||||
return widgetList
|
||||
|
||||
def drawWidget(w):
|
||||
coordX = w.cellX*cwidth
|
||||
coordY = w.cellY*cheight
|
||||
image_black.paste(im=w.image_black, mask=w.mask_black, box=(coordX, coordY))
|
||||
image_yellow.paste(w.image_yellow, (coordX, coordY))
|
||||
|
||||
def updateWidgets():
|
||||
for widg in widgetList:
|
||||
print(" updating trello widget")
|
||||
widg.updateWidget()
|
||||
print("")
|
||||
draw_black.rectangle(xy=((0,0), image_black.size), fill=255)
|
||||
draw_yellow.rectangle(xy=((0,0), image_yellow.size), fill=255)
|
||||
for widg in widgetList:
|
||||
drawWidget(widg)
|
||||
image_black.save(os.path.join(mydir, 'test/imgBlack.bmp'))
|
||||
image_yellow.save(os.path.join(mydir, 'test/imgYellow.bmp'))
|
||||
|
||||
widgetList = initWidgets()
|
||||
|
||||
####################################################
|
||||
####################################################
|
||||
####################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0')
|
||||
@ -1,32 +0,0 @@
|
||||
resHeight = 384
|
||||
cellsHeight = 3
|
||||
resWidth = 640
|
||||
cellsWidth = 3
|
||||
[[widgets]]
|
||||
bwStyle = "mono"
|
||||
filename = "forest.jpg"
|
||||
width = 3
|
||||
posX = 0
|
||||
posY = 0
|
||||
scaleMode = "fill"
|
||||
height = 3
|
||||
type = "image"
|
||||
|
||||
[[widgets]]
|
||||
list = "Plans"
|
||||
height = 3
|
||||
width = 1
|
||||
board = "Organisation"
|
||||
posX = 0
|
||||
posY = 0
|
||||
type = "trello"
|
||||
|
||||
[[widgets]]
|
||||
list = "Active:"
|
||||
height = 3
|
||||
width = 2
|
||||
board = "E-paper"
|
||||
posX = 1
|
||||
posY = 0
|
||||
type = "trello"
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 891 KiB |
|
Before Width: | Height: | Size: 759 KiB |
@ -1,507 +0,0 @@
|
||||
from __future__ import division
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
|
||||
from trello import TrelloClient
|
||||
import os
|
||||
import requests
|
||||
from requests_oauthlib import OAuth1Session
|
||||
import re
|
||||
import toml
|
||||
|
||||
import time
|
||||
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
t = time.time()
|
||||
def timer():
|
||||
global t
|
||||
newT = time.time()
|
||||
print("Time " + str(newT-t))
|
||||
t = newT
|
||||
|
||||
def textBox(text, font, position, limits, colour, draw_black):
|
||||
print(" textbox")
|
||||
timer()
|
||||
str = ""
|
||||
limit = [0, 0]
|
||||
limit[0] = limits[0] - position[0]
|
||||
limit[1] = limits[1] - position[1]
|
||||
words = text.split(" ")
|
||||
i = 0 # index of first word
|
||||
sizex, sizey = draw_black.multiline_textsize(
|
||||
str, font) # size of the textbox
|
||||
# while more words to be added to string AND string textbox height < height limit
|
||||
for word in words:
|
||||
sizex, sizey = draw_black.multiline_textsize(str + word, font)
|
||||
sizex2, sizey2 = draw_black.multiline_textsize(str, font)
|
||||
if sizex < limit[0]: # if width of gen textbox < width limit
|
||||
str += word + " "
|
||||
else:
|
||||
sizex, sizey = draw_black.multiline_textsize(
|
||||
str + "\n" + word, font)
|
||||
if(sizey > limit[1]):
|
||||
break
|
||||
else:
|
||||
str = str.rstrip(" ")
|
||||
str += "\n" + word + " "
|
||||
sizex, sizey = draw_black.multiline_textsize(str, font)
|
||||
draw_black.text(position, str, font=font, fill=0)
|
||||
return sizex, sizey
|
||||
|
||||
|
||||
def putImage(imagepath, maskpath, position, colour):
|
||||
if type(imagepath) is str:
|
||||
img = Image.open(imagepath)
|
||||
else:
|
||||
img = imagepath
|
||||
if type(imagepath) is str:
|
||||
mask = Image.open(maskpath).convert(mode='1')
|
||||
else:
|
||||
mask = maskpath
|
||||
if colour is "black":
|
||||
image_black.paste(img, position, mask)
|
||||
if colour is "yellow":
|
||||
image_yellow.paste(img, position, mask)
|
||||
black_invert = ImageOps.invert(image_black.convert('L'))
|
||||
draw_yellow.bitmap((0, 0), black_invert, 255)
|
||||
|
||||
def roundRect(draw, topLeft, bottomRight, arcsize, type):
|
||||
print(" roundrect")
|
||||
timer()
|
||||
a = 180
|
||||
arcStart = topLeft
|
||||
arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize)
|
||||
lastLineEnd = (arcStart[0], arcStart[1]+arcsize/2)
|
||||
lineStart = (arcStart[0]+arcsize/2, arcStart[1])
|
||||
if type is 'outline':
|
||||
draw.arc((arcStart, arcEnd), a, a+90)
|
||||
elif type is 'fill':
|
||||
draw.ellipse(xy=(arcStart, arcEnd), fill=0)
|
||||
|
||||
a = (a + 90) % 360
|
||||
arcStart = (bottomRight[0]-arcsize, topLeft[1])
|
||||
arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize)
|
||||
lineEnd = (arcStart[0]+arcsize/2, arcStart[1])
|
||||
if type is 'outline':
|
||||
draw.line((lineStart, lineEnd))
|
||||
elif type is 'fill':
|
||||
box1corner1 = lineStart
|
||||
lineStart = (arcEnd[0], arcEnd[1]-arcsize/2)
|
||||
if type is 'outline':
|
||||
draw.arc((arcStart, arcEnd), a, a+90)
|
||||
elif type is 'fill':
|
||||
draw.ellipse(xy=(arcStart, arcEnd), fill=0)
|
||||
|
||||
a = (a + 90) % 360
|
||||
arcStart = (bottomRight[0]-arcsize, bottomRight[1]-arcsize)
|
||||
arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize)
|
||||
lineEnd = (arcEnd[0], arcEnd[1]-arcsize/2)
|
||||
if type is 'outline':
|
||||
draw.line((lineStart, lineEnd))
|
||||
lineStart = (arcEnd[0]-arcsize/2, arcEnd[1])
|
||||
if type is 'outline':
|
||||
draw.arc((arcStart, arcEnd), a, a+90)
|
||||
elif type is 'fill':
|
||||
draw.ellipse(xy=(arcStart, arcEnd), fill=0)
|
||||
box2corner1 = lastLineEnd
|
||||
box2corner2 = lineEnd
|
||||
|
||||
a = (a + 90) % 360
|
||||
arcStart = (topLeft[0], bottomRight[1]-arcsize)
|
||||
arcEnd = (arcStart[0] + arcsize, arcStart[1] + arcsize)
|
||||
lineEnd = (arcEnd[0]-arcsize/2, arcEnd[1])
|
||||
draw.line((lineStart, lineEnd))
|
||||
|
||||
if type is 'outline':
|
||||
draw.arc((arcStart, arcEnd), a, a+90)
|
||||
elif type is 'fill':
|
||||
box1corner2 = lineStart
|
||||
draw.ellipse(xy=(arcStart, arcEnd), fill=0)
|
||||
|
||||
draw.rectangle(xy=(box1corner1, box1corner2), fill=0)
|
||||
draw.rectangle(xy=(box2corner1, box2corner2), fill=0)
|
||||
|
||||
lineStart = (arcStart[0], arcStart[1]+arcsize/2)
|
||||
|
||||
draw.line((lineStart, lastLineEnd))
|
||||
|
||||
def convert(input):
|
||||
if isinstance(input, dict):
|
||||
return dict((convert(key), convert(value)) for key, value in input.iteritems())
|
||||
elif isinstance(input, list):
|
||||
return [convert(element) for element in input]
|
||||
elif isinstance(input, unicode):
|
||||
return input.encode('utf-8')
|
||||
else:
|
||||
return input
|
||||
|
||||
|
||||
class TextWidget():
|
||||
# this example widget will have a set size: 3x1 cells (one column)
|
||||
# an instance of this widget class can be created and pasted to canvas
|
||||
def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), stringIn=""):
|
||||
self.width = dim[0]
|
||||
self.height = dim[1]
|
||||
self.wt = cwidth * dim[0]
|
||||
self.ht = cheight * dim[1]
|
||||
self.cellX = xy[0]
|
||||
self.cellY = xy[1]
|
||||
self.image_yellow = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_yellow = ImageDraw.Draw(self.image_yellow)
|
||||
self.image_black = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_black = ImageDraw.Draw(self.image_black)
|
||||
self.mask_black = Image.new('1', (self.wt, self.ht), 255)
|
||||
self.draw_mask_black = ImageDraw.Draw(self.mask_black)
|
||||
self.font = ImageFont.truetype(os.path.join(mydir, 'resources/fonts/DejaVuSans.ttf'), 16)
|
||||
self.stringInput = stringIn
|
||||
self.updateWidget()
|
||||
|
||||
def drawText(self, str):
|
||||
# define parameters for text placement
|
||||
position = (0, 0)
|
||||
limit = (self.wt, self.ht)
|
||||
# write the string in the widget
|
||||
textBox(str, self.font, position, limit, "black", self.draw_black)
|
||||
|
||||
def saveImages(self):
|
||||
self.image_black.save(os.path.join(mydir, '../test/imgBlackWidget.bmp'))
|
||||
self.image_yellow.save(os.path.join(mydir, '../test/imgYellowWidget.bmp'))
|
||||
|
||||
def updateWidget(self):
|
||||
if self.stringInput is not None:
|
||||
self.draw_black.rectangle((0, 0, self.wt, self.ht), fill=255)
|
||||
self.draw_yellow.rectangle((0, 0, self.wt, self.ht), fill=255)
|
||||
self.drawText(self.stringInput)
|
||||
self.saveImages()
|
||||
|
||||
|
||||
class ImageWidget():
|
||||
# this widget displays an image
|
||||
# needs to recieve an image file as input. Then resize and convert to monochrome.
|
||||
def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), bwMode="mono", scaleMode="fill", img=""):
|
||||
self.width = dim[0]
|
||||
self.height = dim[1]
|
||||
self.wt = cwidth * dim[0]
|
||||
self.ht = cheight * dim[1]
|
||||
self.cellX = xy[0]
|
||||
self.cellY = xy[1]
|
||||
self.image_yellow = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_yellow = ImageDraw.Draw(self.image_yellow)
|
||||
self.image_black = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_black = ImageDraw.Draw(self.image_black)
|
||||
self.mask_black = Image.new('1', (self.wt, self.ht), 255)
|
||||
self.draw_mask_black = ImageDraw.Draw(self.mask_black)
|
||||
self.bwMode = bwMode
|
||||
self.scaleMode = scaleMode
|
||||
print(img)
|
||||
self.image = Image.open(img)
|
||||
self.updateWidget()
|
||||
|
||||
def resizeImg(self, img):
|
||||
# scale image down until whole image fits inside the cell
|
||||
iRatio = img.size[0] / img.size[1] # image ratio
|
||||
cRatio = self.wt / self.ht # canvas ratio
|
||||
if cRatio > iRatio: # height is limiting dimension
|
||||
fixedImg = img.resize((int(self.ht * iRatio), self.ht))
|
||||
return fixedImg
|
||||
if cRatio < iRatio: # width is limiting dimension
|
||||
fixedImg = img.resize((self.wt, int(self.wt / iRatio)))
|
||||
return fixedImg
|
||||
fixedImg = img
|
||||
return fixedImg
|
||||
|
||||
def fillImg(self, img):
|
||||
# enlarge the image and crop to fill the widget canvas
|
||||
iRatio = img.size[0] / img.size[1] # image ratio
|
||||
cRatio = self.wt / self.ht # canvas ratio
|
||||
if cRatio > iRatio:
|
||||
fixedImg = img.resize((self.wt, int(self.wt / iRatio)))
|
||||
offset = 0.5 * (fixedImg.size[1] - self.ht) # centre by height
|
||||
cropped = fixedImg.crop((0, offset, self.wt, offset + self.ht - 1))
|
||||
return cropped
|
||||
if cRatio < iRatio:
|
||||
fixedImg = img.resize((int(self.ht * iRatio), self.ht))
|
||||
offset = 0.5 * (fixedImg.size[0] - self.wt) # centre by width
|
||||
cropped = fixedImg.crop((offset, 0, offset + self.wt, self.ht - 1))
|
||||
return cropped
|
||||
return img.resize((self.wt, self.ht))
|
||||
|
||||
def autoThresholdLimit(self, img):
|
||||
gscale = img.convert(mode="L")
|
||||
gscale.save(os.path.join(mydir, '../test/greyscale.bmp'))
|
||||
extrema = gscale.getextrema()
|
||||
limit = extrema[0] + 0.4 * (extrema[1] - extrema[0])
|
||||
self.thresLim = limit
|
||||
|
||||
def threshold(self, val):
|
||||
if(val > self.thresLim):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def bwImg(self, img):
|
||||
self.autoThresholdLimit(img)
|
||||
if self.bwMode == "mono":
|
||||
return img.convert('L').point(self.threshold, '1')
|
||||
if self.bwMode == "dither":
|
||||
return img.convert(mode='1')
|
||||
|
||||
def pasteImg(self, img):
|
||||
iRatio = img.size[0] / img.size[1] # image ratio
|
||||
cRatio = self.wt / self.ht # canvas ratio
|
||||
if cRatio > iRatio: # needs to be centred horizontally
|
||||
offset = int(0.5 * (self.wt - img.size[0]))
|
||||
self.image_black.paste(img, (offset, 0))
|
||||
if cRatio < iRatio: # needs to be centred vertically
|
||||
offset = int(0.5 * (self.ht - img.size[1]))
|
||||
self.image_black.paste(img, (0, offset))
|
||||
|
||||
def saveImages(self):
|
||||
self.image_black.save(os.path.join(mydir, '../test/imgBlackWidget.bmp'))
|
||||
self.image_yellow.save(os.path.join(mydir, '../test/imgYellowWidget.bmp'))
|
||||
|
||||
def updateWidget(self):
|
||||
if self.image is not None:
|
||||
if self.scaleMode == "fill":
|
||||
rsImg = self.fillImg(self.image)
|
||||
if self.scaleMode == "resize":
|
||||
rsImg = self.resizeImg(self.image)
|
||||
if self.scaleMode == "none":
|
||||
rsImg = self.image
|
||||
newImg = self.bwImg(rsImg)
|
||||
self.pasteImg(newImg)
|
||||
self.saveImages()
|
||||
|
||||
|
||||
class TrelloWidget():
|
||||
# displays your trello boards in text form
|
||||
# card function: Name, description in one 'cell' or box with rounded corners
|
||||
|
||||
def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1), boardName=None, listName=None):
|
||||
self.boardName = boardName
|
||||
self.listName = listName
|
||||
|
||||
|
||||
|
||||
self.width = dim[0]
|
||||
self.height = dim[1]
|
||||
self.wt = cwidth * dim[0]
|
||||
self.ht = cheight * dim[1]
|
||||
self.cellX = xy[0]
|
||||
self.cellY = xy[1]
|
||||
self.image_yellow = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_yellow = ImageDraw.Draw(self.image_yellow)
|
||||
self.image_black = Image.new(
|
||||
'1', (self.wt, self.ht), 255) # 255: clear the frame
|
||||
self.draw_black = ImageDraw.Draw(self.image_black)
|
||||
self.mask_black = Image.new('1', (self.wt, self.ht), 255)
|
||||
self.draw_mask_black = ImageDraw.Draw(self.mask_black)
|
||||
self.lastY = 0
|
||||
self.cardLastY = 0
|
||||
fontpath = os.path.join(mydir, 'resources/fonts/DejaVuSans.ttf')
|
||||
self.font1 = ImageFont.truetype(fontpath, 12)
|
||||
self.font2 = ImageFont.truetype(fontpath, 13)
|
||||
self.font3 = ImageFont.truetype(fontpath, 10)
|
||||
|
||||
uni_config = toml.load(os.path.join(os.path.join(mydir, '..'), 'config.toml'))
|
||||
config = convert(uni_config)
|
||||
self.conf_dict = {
|
||||
'type': {'default': 'trello'},
|
||||
'board': {'default': ''},
|
||||
'list': {'default': ''},
|
||||
'width': {'default': 1, 'min': 1, 'max': config['cellsWidth']},
|
||||
'height': {'default': 1, 'min': 1, 'max': config['cellsHeight']},
|
||||
'posX': {'default': 0, 'min': 0, 'max': config['cellsWidth']-1},
|
||||
'posY': {'default': 0, 'min': 0, 'max': config['cellsHeight']-1}
|
||||
}
|
||||
|
||||
self.updateWidget()
|
||||
|
||||
def initTrelloClient(self):
|
||||
print("CONNECTING TO TRELLO")
|
||||
timer()
|
||||
self.trello_key = 'a3d8f7c04c266e5c9571f6b7747aa353'
|
||||
self.trello_secret = 'd4ac9968d997aa3e5a0ebf129a627b03701520de6ff19834c81d6d9c56fe7e9b'
|
||||
try:
|
||||
with open('tokenfile.secret', 'r') as file:
|
||||
token = file.readline().rstrip()
|
||||
secret = file.readline().rstrip()
|
||||
self.client = TrelloClient(
|
||||
api_key=self.trello_key,
|
||||
api_secret=self.trello_secret,
|
||||
token=token,
|
||||
token_secret=secret
|
||||
)
|
||||
all_boards = self.client.list_boards()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("token expired or does not exist\nCreating new token")
|
||||
self.access_token, self.resource_owner_key, self.resource_owner_secret = self.create_oauth_token()
|
||||
with open('tokenfile.secret', 'w') as file:
|
||||
file.write(self.access_token["oauth_token"] +
|
||||
"\n" + self.access_token["oauth_token_secret"])
|
||||
self.client = TrelloClient(
|
||||
api_key=self.trello_key,
|
||||
api_secret=self.trello_secret,
|
||||
token=self.access_token["oauth_token"],
|
||||
token_secret=self.access_token["oauth_token_secret"]
|
||||
)
|
||||
|
||||
def create_oauth_token(self, expiration=None, scope=None, key=None, secret=None, name=None, output=True):
|
||||
request_token_url = 'https://trello.com/1/OAuthGetRequestToken'
|
||||
authorize_url = 'https://trello.com/1/OAuthAuthorizeToken'
|
||||
access_token_url = 'https://trello.com/1/OAuthGetAccessToken'
|
||||
|
||||
expiration = os.environ.get('TRELLO_EXPIRATION', "30days")
|
||||
scope = scope or os.environ.get('TRELLO_SCOPE', 'read,write')
|
||||
#trello_key = key or os.environ['TRELLO_API_KEY']
|
||||
#trello_secret = secret or os.environ['TRELLO_API_SECRET']
|
||||
name = name or os.environ.get('TRELLO_NAME', 'py-trello')
|
||||
|
||||
session = OAuth1Session(client_key=self.trello_key,
|
||||
client_secret=self.trello_secret)
|
||||
response = session.fetch_request_token(request_token_url)
|
||||
resource_owner_key, resource_owner_secret = response.get(
|
||||
'oauth_token'), response.get('oauth_token_secret')
|
||||
|
||||
print("Go to the following link in your browser:")
|
||||
print("{authorize_url}?oauth_token={oauth_token}&scope={scope}&expiration={expiration}&name={name}".format(
|
||||
authorize_url=authorize_url,
|
||||
oauth_token=resource_owner_key,
|
||||
expiration=expiration,
|
||||
scope=scope,
|
||||
name=name
|
||||
))
|
||||
|
||||
accepted = "n"
|
||||
while accepted.lower() == "n":
|
||||
accepted = raw_input('Have you authorized me? (y/n) ')
|
||||
|
||||
#oauth_verifier = ""
|
||||
# if input('Have you authorized me? (y/n) ') is "y":
|
||||
# oauth_verifier = input('What is the PIN? ')
|
||||
|
||||
oauth_verifier = raw_input('What is the PIN? ')
|
||||
|
||||
session = OAuth1Session(client_key=self.trello_key, client_secret=self.trello_secret,
|
||||
resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret,
|
||||
verifier=oauth_verifier)
|
||||
access_token = session.fetch_access_token(access_token_url)
|
||||
|
||||
return access_token, resource_owner_key, resource_owner_secret
|
||||
|
||||
def updateWidget(self):
|
||||
print(" init trello client")
|
||||
timer()
|
||||
self.initTrelloClient()
|
||||
print(" generating canvas")
|
||||
timer()
|
||||
self.image_black = Image.new('1', (self.wt, self.ht), 255)
|
||||
self.mask_black = Image.new('1', (self.wt, self.ht), 255)
|
||||
print(" cards from board")
|
||||
timer()
|
||||
card_list = self.cardsFromBoard(self.boardName, self.listName)
|
||||
print(" print cards")
|
||||
timer()
|
||||
self.printCards(card_list)
|
||||
print(" saving images")
|
||||
timer()
|
||||
self.image_black.save(os.path.join(mydir, '../test/card_image.bmp'))
|
||||
self.mask_black.save(os.path.join(mydir, '../test/mask.bmp'))
|
||||
print(" done")
|
||||
timer()
|
||||
#self.draw_black.rectangle((0, 0, self.wt, self.ht), fill=255) #inclusive/exclusive dimensions?
|
||||
#self.draw_yellow.rectangle((0, 0, self.wt, self.ht), fill=255)
|
||||
#textBox(content, self.font, (0,0), (self.wt, self.ht), "black", self.draw_black)
|
||||
|
||||
def printCards(self, card_list):
|
||||
for cardImg in card_list:
|
||||
self.image_black.paste(cardImg[0], (0, self.lastY))
|
||||
self.mask_black.paste(cardImg[1], (0, self.lastY))
|
||||
self.lastY += cardImg[0].size[1]
|
||||
self.lastY = 0
|
||||
|
||||
def makeCard(self, card):
|
||||
print(" making card")
|
||||
timer()
|
||||
# calculate card dimensions for an image to draw the card on
|
||||
# then draw the card on the widget canvas
|
||||
# card dimensions: wt/width, ht/height (cards as large as one cell)
|
||||
arcsize = 20 #23
|
||||
offset = 10 #15
|
||||
padding = 8
|
||||
#padding = 5*arcsize/16 + offset
|
||||
card_wt = int(self.wt)
|
||||
card_ht = int(self.ht)*5
|
||||
self.cardLastY = 0 + offset + padding
|
||||
|
||||
card_image = Image.new('1', (card_wt, card_ht), 255)
|
||||
card_draw = ImageDraw.Draw(card_image)
|
||||
|
||||
self.drawText(card.name, self.font1, card_draw, card_image, card_wt, offset, padding)
|
||||
self.cardLastY += 4
|
||||
if card.description != "":
|
||||
self.drawText(card.description, self.font3, card_draw, card_image, card_wt, offset, padding)
|
||||
|
||||
card_ht = self.cardLastY+padding
|
||||
|
||||
mask_image = Image.new('1', (card_wt, card_ht+1), 255)
|
||||
mask_draw = ImageDraw.Draw(mask_image)
|
||||
|
||||
roundRect(card_draw, (offset, offset), (card_wt-offset, card_ht), arcsize, 'outline')
|
||||
roundRect(mask_draw, (offset, offset), (card_wt-offset, card_ht), arcsize, 'fill')
|
||||
|
||||
card_image = card_image.crop((0, 0, card_wt, card_ht+1))
|
||||
mask_image = ImageOps.invert(mask_image.convert('L'))
|
||||
|
||||
card_image.save(os.path.join(mydir, '../test/card_image.bmp'))
|
||||
self.cardLastY = 0
|
||||
return card_image, mask_image
|
||||
|
||||
def drawText(self, text, font, draw, ima, card_wt, offset, padding):
|
||||
#self.cardLastY += 4
|
||||
sizex, sizey = textBox(text, font, (offset+padding, self.cardLastY),
|
||||
(card_wt-(offset+padding), ima.size[1]), "black", draw)
|
||||
# find predicted height of textbox and set lastY to it
|
||||
self.cardLastY += sizey
|
||||
|
||||
def cardsFromBoard(self, boardName, listName):
|
||||
cards = []
|
||||
all_boards = self.client.list_boards()
|
||||
print(" 1")
|
||||
timer()
|
||||
for board in all_boards:
|
||||
print(" 2")
|
||||
timer()
|
||||
if board.name == boardName:
|
||||
slctd_board = board
|
||||
slctd_board.list_lists()
|
||||
all_lists_in_board = slctd_board.all_lists()
|
||||
for lst in all_lists_in_board:
|
||||
print(" 3")
|
||||
timer()
|
||||
if lst.name == listName:
|
||||
card_list = lst.list_cards()
|
||||
for crd in card_list:
|
||||
print(" 4")
|
||||
timer()
|
||||
cards.append(self.makeCard(crd))
|
||||
return cards
|
||||
|
||||
|
||||
class surfWidget():
|
||||
# this widget displays an image
|
||||
def __init__(self, cwidth, cheight, xy=(0, 0), dim=(1, 1)):
|
||||
self.name = ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
Run EinkPaper.py to start the program. To run in test mode, add argument -t or --test.
|
||||
Test mode will save generated images to the working directory rather than export them to an external display.
|
||||
Test mode will auto run if no external display package is detected.
|
||||
|
||||
EinkPaper.py will construct widget classes from widgets.py. Widgets are constructed in the initWidgets() function in EinkPaper.py.
|
||||
|
||||
The image widget is currently set to resize (to fit the whole image on the widget w/ no cropping), then to either monochrome or greyscale dither the image as specified in initWidgets(). The zoom function is currently broken and to be fixed.
|
||||
|
||||
--- Program flow ---
|
||||
|
||||
config data --> EinkPaper.py
|
||||
|
||||
Configuration of the widgets to be displayed is stored in the Einkpaper.py initWidgets() function. A new instance of a widget class is added to the widgetList array.
|
||||
|
||||
General form: widget."widget name"(cwidth, cheight, x coord, y coord, width(by cell), height(by cell), OTHER ARGUMENTS...)
|
||||
e.g.
|
||||
widgetList.append(widget.ImageWidget(cwidth, cheight, 1, 1, 2, 2, "mono", "calvin.jpg"))
|
||||
|
||||
Text widget has one 'other argument', it is the string to be displayed on the widget. Image widget has three other args, the type of black and white filter to process the image with, the type of image size transformation to be performed, and the location or filename of the image. The two b&w filters are "mono" for monochrome, and "dither" for greyscale dithering. Image transforms are "fill", "resize", and "none".
|
||||
The threshold limit for calculating the cutoff for black/white pixels in the monochrome function is calculated in autoThresholdLimit(). The limit is: the lowest pixel value + a factor between 0 and 1 x the pixel value range of the image. The pixel value range is found by performing getextrema() on the image. By default the factor is set to 0.4, or 40%.
|
||||
|
||||
Widgets will be drawn on the final canvas in the order they are appended to the widgetList array.
|
||||
|
||||
widget.py --> EinkPaper.py
|
||||
|
||||
When an instance of a widget class is created it will perform necessary computations, network tasks, and image operations to store an updated image object. This image object is the image of the widget to be passed back to EinkPaper.py's render() function. The size of this image will be some multiple of the cell size as specified in EinkPaper.py.
|
||||
|
||||
--- Running the program ---
|
||||
|
||||
The program is built for python2.
|
||||
$ python EinkPaper.py
|
||||
If the display package is present, will run with test mode disabled. Will periodically update each widget and each iteration will display the latest version on the external display. If the package is missing, will run in test mode, saving the generated canvas images to the working directory.
|
||||
|
||||
Or manually enable test mode:
|
||||
$ python EinkPaper.py -t
|
||||
or
|
||||
$ python EinkPaper.py --test
|
||||