start fresh

master
Josef Dabrowski 6 years ago
parent 81e40142f7
commit 159c0e9ea0

6
.gitignore vendored

@ -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 ###

Binary file not shown.

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"

@ -1,100 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/style.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
<div>
<!-- EDIT EXISTING CONFIG -->
<h2>EDIT EXISTING CONFIG</h2>
<form action="/home" class='form' method="post">
{% for n in range(widgLists| length) %}
<h4>Widget {{n}}</h4>
{% for key, value in widgLists[n].iteritems() %}
{{key}}: <input type="text" name="{{key}}{{n}}" value="{{value}}">
<br>
{% endfor %}
<br>
{% endfor %}
<h4>System Settings</h4>
{% for key, value in sysList.iteritems() %}
{{key}}: <input type="text" name="{{key}}" value="{{value}}">
<br>
{% endfor %}
<br>
<input type="submit" value="Submit">
</form>
<!-- ADD NEW WIDGET -->
<h2>ADD NEW WIDGET</h2>
<form action="/newWidget" class="form" method="get">
<h4>New Widget</h4>
Type: <select id="type" name="type">
{% for type in widgTypes %}
<option value="{{type}}">{{type}}</option>
{% endfor %}
</select>
<input type="submit" value="Submit">
</form>
</div>
<script>
/**
* Retrieves input data from a form and returns it as a JSON object.
* @param {HTMLFormControlsCollection} elements the form elements
* @return {Object} form data as an object literal
*/
const isValidElement = element => {
return element.name && element.value;
};
const formToJSON = elements => [].reduce.call(elements, (data, element) => {
if (isValidElement(element)) {
data[element.name] = element.value;
}
return data;
}, {});
const handleFormSubmit = event => {
// Stop the form from submitting since were handling that with AJAX.
event.preventDefault();
// Call our function to get the form data.
const data = formToJSON(form.elements);
/**
// Demo only: print the form data onscreen as a formatted JSON object.
const dataContainer = document.getElementsByClassName('results__display')[0];
// Use `JSON.stringify()` to make the output valid, human-readable JSON.
dataContainer.textContent = JSON.stringify(data, null, " ");
// ...this is where wed actually do something with the form data...
**/
var textContent = JSON.stringify(data, null, " ");
var request = new XMLHttpRequest();
request.open('POST', '/home', true);
request.setRequestHeader('Content-Type', 'application/json');
request.send(textContent);
};
/*
* This is where things actually get started. We find the form element using
* its class name, then attach the `handleFormSubmit()` function to the
* `submit` event.
*/
const form = document.getElementsByClassName('form')[0];
form.addEventListener('submit', handleFormSubmit);
</script>
</body>
</html>

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/style.css" rel="stylesheet" type="text/css" media="all">
<!-- jquery, popper.js, bootstrap4 beta -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</head>
<body>
<div>
<!-- SPECIFY NEW CONFIG DETAILS -->
<h2>SPECIFY NEW CONFIG DETAILS</h2>
<form action="/newWidget" class='form' method="post">
{% for key, value in conf_defaults.iteritems() %}
{{key}}:
{% if value['datatype'] == 'text' %}
<input type="text" name="{{key}}" value="{{value['default']}}">
{% endif %}
{% if value['datatype'] == 'number' %}
<input type="number" name="{{key}}" value="{{value['default']}}" min="{{value['min']}}" max="{{value['max']}}">
{% endif %}
<br>
<br>
{% endfor %}
<input type="submit" value="Submit">
</form>
</div>
<script>
/**
* Retrieves input data from a form and returns it as a JSON object.
* @param {HTMLFormControlsCollection} elements the form elements
* @return {Object} form data as an object literal
*/
const isValidElement = element => {
return element.name && element.value;
};
const formToJSON = elements => [].reduce.call(elements, (data, element) => {
if (isValidElement(element)) {
data[element.name] = element.value;
}
return data;
}, {});
const handleFormSubmit = event => {
// Stop the form from submitting since were handling that with AJAX.
event.preventDefault();
// Call our function to get the form data.
const data = formToJSON(form.elements);
/**
// Demo only: print the form data onscreen as a formatted JSON object.
const dataContainer = document.getElementsByClassName('results__display')[0];
// Use `JSON.stringify()` to make the output valid, human-readable JSON.
dataContainer.textContent = JSON.stringify(data, null, " ");
// ...this is where wed actually do something with the form data...
**/
var textContent = JSON.stringify(data, null, " ");
var request = new XMLHttpRequest();
request.open('POST', '/newWidget', true);
request.setRequestHeader('Content-Type', 'application/json');
request.send(textContent);
};
/*
* This is where things actually get started. We find the form element using
* its class name, then attach the `handleFormSubmit()` function to the
* `submit` event.
*/
const form = document.getElementsByClassName('form')[0];
form.addEventListener('submit', handleFormSubmit);
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 891 KiB

Binary file not shown.

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
Loading…
Cancel
Save