Compare commits
13 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
798782a17c | 6 years ago |
|
|
43902ee635 | 6 years ago |
|
|
09f61f0015 | 6 years ago |
|
|
3d236bd02d | 6 years ago |
|
|
00a15f0c81 | 6 years ago |
|
|
b597543189 | 6 years ago |
|
|
59730b4cec | 6 years ago |
|
|
1fca6884ea | 6 years ago |
|
|
f51b752c42 | 6 years ago |
|
|
2ece1ae187 | 6 years ago |
|
|
dd2d9ea4d1 | 6 years ago |
|
|
4a11359190 | 6 years ago |
|
|
159c0e9ea0 | 6 years ago |
@ -0,0 +1,10 @@
|
|||||||
|
Metadata-Version: 1.0
|
||||||
|
Name: Notiframe
|
||||||
|
Version: 0.1.dev0
|
||||||
|
Summary: UNKNOWN
|
||||||
|
Home-page: https://git.distreon.net/josef/NotiFrame
|
||||||
|
Author: UNKNOWN
|
||||||
|
Author-email: UNKNOWN
|
||||||
|
License: UNKNOWN
|
||||||
|
Description: UNKNOWN
|
||||||
|
Platform: UNKNOWN
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
README.md
|
||||||
|
setup.py
|
||||||
|
Notiframe.egg-info/PKG-INFO
|
||||||
|
Notiframe.egg-info/SOURCES.txt
|
||||||
|
Notiframe.egg-info/dependency_links.txt
|
||||||
|
Notiframe.egg-info/top_level.txt
|
||||||
|
notiframe/Notiframe.py
|
||||||
|
notiframe/__init__.py
|
||||||
|
notiframe/test.py
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
notiframe
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"python.linting.pylintEnabled": true,
|
|
||||||
"python.linting.enabled": true,
|
|
||||||
"python.pythonPath": "/usr/bin/python2"
|
|
||||||
}
|
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from collections import namedtuple
|
||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
import toml
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
#
|
||||||
|
class Manager():
|
||||||
|
"""
|
||||||
|
Manager class to manage a notiframe session.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.manager_id = gen_id()
|
||||||
|
self.devices = []
|
||||||
|
#widget class register list - updated with available widget classes
|
||||||
|
|
||||||
|
def add_device(self, name, resolution):
|
||||||
|
"""
|
||||||
|
Adds a new device to the manager's device list
|
||||||
|
"""
|
||||||
|
self.devices.append(Device(name, resolution))
|
||||||
|
|
||||||
|
def remove_device(self, dev_id):
|
||||||
|
"""
|
||||||
|
Removes a device from the manager's device list
|
||||||
|
"""
|
||||||
|
for dev in self.devices:
|
||||||
|
if dev.device_id == dev_id:
|
||||||
|
self.devices.remove(dev)
|
||||||
|
break
|
||||||
|
|
||||||
|
def import_config_file(self, path):
|
||||||
|
"""
|
||||||
|
Loads a config from file into the manager, reconstructs manager state from config
|
||||||
|
"""
|
||||||
|
# get toml config file, convert to named tuple
|
||||||
|
# return named tuple containing all layouts
|
||||||
|
config = toml.load(path)
|
||||||
|
#config = convert(uni_config)
|
||||||
|
self.devices = [Device.load(device_data) for device_data in config['devices']]
|
||||||
|
|
||||||
|
def export_config_file(self, path):
|
||||||
|
"""
|
||||||
|
Save the current manager state to file
|
||||||
|
"""
|
||||||
|
# take tuple containing all layouts and dump as toml
|
||||||
|
preserved_data = [device.save() for device in self.devices]
|
||||||
|
with open(path, "w+") as config_file:
|
||||||
|
config_file.write(toml.dumps({'devices': preserved_data}))
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""
|
||||||
|
Render a canvas image from the present data
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pull_latest(self):
|
||||||
|
"""
|
||||||
|
Pull the latest network data requested by each widget
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Device():
|
||||||
|
"""
|
||||||
|
Device class to contain a device's properties, layout and widgets
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(saved_state):
|
||||||
|
"""
|
||||||
|
Loads a device from saved state dict of the device
|
||||||
|
"""
|
||||||
|
new_device = Device(
|
||||||
|
name=saved_state['name'], resolution=saved_state['resolution'], device_id=saved_state['device_id'])
|
||||||
|
for widget_state in saved_state['widgets']:
|
||||||
|
new_device.add_widget(Widget.load(widget_state))
|
||||||
|
return new_device
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Returns a dict with saved state of the device
|
||||||
|
"""
|
||||||
|
return_state = {}
|
||||||
|
return_state['name'] = self.name
|
||||||
|
return_state['resolution'] = self.resolution
|
||||||
|
return_state['device_id'] = self.device_id
|
||||||
|
return_state['widgets'] = [widget.save() for widget in self.widgets]
|
||||||
|
return return_state
|
||||||
|
|
||||||
|
def __init__(self, name, resolution, device_id=None):
|
||||||
|
# attributes
|
||||||
|
self.name = name
|
||||||
|
self.resolution = resolution
|
||||||
|
if(device_id is None):
|
||||||
|
self.device_id = gen_id()
|
||||||
|
else:
|
||||||
|
self.device_id = device_id
|
||||||
|
|
||||||
|
# widgets container
|
||||||
|
self.widgets = []
|
||||||
|
|
||||||
|
# generated
|
||||||
|
self.device_image = None
|
||||||
|
|
||||||
|
def add_widget(self, widget):
|
||||||
|
"""
|
||||||
|
Adds a new widget to the device's widget list
|
||||||
|
"""
|
||||||
|
self.widgets.append(widget)
|
||||||
|
|
||||||
|
def remove_widget(self, widget_id):
|
||||||
|
"""
|
||||||
|
Removes a widget from the device's widget list
|
||||||
|
"""
|
||||||
|
for widg in self.widgets:
|
||||||
|
if widg.widget_id == widget_id:
|
||||||
|
self.widgets.remove(widg)
|
||||||
|
break
|
||||||
|
|
||||||
|
class Widget():
|
||||||
|
"""
|
||||||
|
Widget class to hold properties common to all widgets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(saved_state):
|
||||||
|
"""
|
||||||
|
Loads a widget from saved state dict of the widget
|
||||||
|
"""
|
||||||
|
widget_classes = {'Widget': Widget, 'BasicTextWidget': BasicTextWidget} # this list maintained in the module or manager...
|
||||||
|
new_widget_class = widget_classes[saved_state['w_type']]
|
||||||
|
new_widget = new_widget_class(
|
||||||
|
position=saved_state['position'], dimensions=saved_state['dimensions'], widget_id=saved_state['widget_id'])
|
||||||
|
return new_widget
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Returns a dict with saved state of the widget
|
||||||
|
"""
|
||||||
|
return_state = {}
|
||||||
|
return_state['w_type'] = 'Widget'
|
||||||
|
return_state['position'] = self.position
|
||||||
|
return_state['dimensions'] = self.dimensions
|
||||||
|
return_state['widget_id'] = self.widget_id
|
||||||
|
return return_state
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""
|
||||||
|
Returns image of widget
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, position, dimensions, widget_id=None):
|
||||||
|
if(widget_id is None):
|
||||||
|
self.widget_id = gen_id()
|
||||||
|
else:
|
||||||
|
self.widget_id = widget_id
|
||||||
|
self.position = position # xy grid space location to draw the top left corner in
|
||||||
|
self.dimensions = dimensions # xy in terms of grid spaces
|
||||||
|
|
||||||
|
class BasicTextWidget(Widget):
|
||||||
|
"""
|
||||||
|
A widget that displays a simple string of text
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(saved_state):
|
||||||
|
"""
|
||||||
|
Loads a widget from dict saved state of the widget
|
||||||
|
"""
|
||||||
|
new_widget = BasicTextWidget(
|
||||||
|
position=saved_state['position'], dimensions=saved_state['dimensions'], widget_id=saved_state['widget_id'], text=saved_state['text'])
|
||||||
|
return new_widget
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Returns a dict with saved state of the widget
|
||||||
|
"""
|
||||||
|
return_state = super().save()
|
||||||
|
return_state['text'] = self.text
|
||||||
|
return_state['w_type'] = 'BasicTextWidget'
|
||||||
|
return return_state
|
||||||
|
|
||||||
|
def __init__(self, position, dimensions, widget_id, text, font_size, font):
|
||||||
|
super().__init__(position, dimensions, widget_id)
|
||||||
|
|
||||||
|
self.text = text
|
||||||
|
self.font_size = font_size
|
||||||
|
self.font = None
|
||||||
|
|
||||||
|
def get_image(self):
|
||||||
|
"""
|
||||||
|
Returns a widget image via the cache or render method. For now just returns example black image
|
||||||
|
"""
|
||||||
|
img = Image.new('1', (self.dimensions[0], self.dimensions[1]), 0) #255 black
|
||||||
|
return img
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""
|
||||||
|
Render an image of the widget from the present data
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Global Functions
|
||||||
|
#
|
||||||
|
def gen_id():
|
||||||
|
"""
|
||||||
|
Generates a uuid (v4) string
|
||||||
|
"""
|
||||||
|
return str(uuid.uuid4())
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from notiframe import Notiframe
|
||||||
|
import os
|
||||||
|
from flask import Flask, render_template, request, redirect, url_for, send_file
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
mgr = Notiframe.Manager()
|
||||||
|
|
||||||
|
@app.route('/<device_id>/get_image', methods=['GET']) #when requesting this http endpoint, use eg. /2y7d8s9k/get_image with '2y7d8s9k' being the device id
|
||||||
|
def display(device_id):
|
||||||
|
img = mgr.get_image(device_id)
|
||||||
|
return send_file(img)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0')
|
||||||
@ -0,0 +1 @@
|
|||||||
|
from notiframe import Notiframe
|
||||||
@ -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
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
from distutils.core import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='Notiframe',
|
||||||
|
version='0.1dev',
|
||||||
|
url='https://git.distreon.net/josef/NotiFrame',
|
||||||
|
packages=['notiframe',],
|
||||||
|
install_requires=[
|
||||||
|
"toml",
|
||||||
|
"preserve@git+https://git.distreon.net/novirium/python-preserve.git"
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
'dev': [
|
||||||
|
'pylint',
|
||||||
|
'pytest',
|
||||||
|
'pytest-cov',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
long_description=open('README.md').read(),
|
||||||
|
long_description_content_type='text/markdown'
|
||||||
|
)
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
from notiframe import Notiframe
|
||||||
|
import os
|
||||||
|
import toml
|
||||||
|
|
||||||
|
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
def test_config_dumpload(tmp_path):
|
||||||
|
path = os.path.join(tmp_path, 'config.toml')
|
||||||
|
|
||||||
|
mgr = Notiframe.Manager()
|
||||||
|
mgr.add_device('test', [1,2])
|
||||||
|
mgr.export_config_file(path)
|
||||||
|
|
||||||
|
mgr = Notiframe.Manager()
|
||||||
|
mgr.import_config_file(path)
|
||||||
|
assert mgr.devices[0].name == 'test'
|
||||||