@ -13,24 +13,15 @@ from datetime import datetime, date, timedelta
|
||||
from time import sleep
|
||||
from dateutil.rrule import *
|
||||
from dateutil.parser import parse
|
||||
import arrow
|
||||
import re
|
||||
import random
|
||||
import gc
|
||||
|
||||
try:
|
||||
import feedparser
|
||||
except ImportError:
|
||||
print("Please install feedparser with: sudo pip3 install feedparser")
|
||||
print("and")
|
||||
print("pip3 install feedparser")
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError:
|
||||
print("Please install numpy with: sudo apt-get install python3-numpy")
|
||||
import feedparser
|
||||
import numpy as np
|
||||
|
||||
from settings import *
|
||||
from icon_positions_locations import *
|
||||
from image_data import *
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
||||
import pyowm
|
||||
@ -40,29 +31,23 @@ try:
|
||||
except Exception as e:
|
||||
print("Something didn't work right, maybe you're offline?"+e.reason)
|
||||
|
||||
if display_colours is "bwr":
|
||||
import epd7in5b
|
||||
epd = epd7in5b.EPD()
|
||||
|
||||
if display_colours is "bw":
|
||||
import epd7in5
|
||||
epd = epd7in5.EPD()
|
||||
import e_paper_drivers
|
||||
epd = e_paper_drivers.EPD()
|
||||
|
||||
from calibration import calibration
|
||||
|
||||
EPD_WIDTH = 640
|
||||
EPD_HEIGHT = 384
|
||||
font = ImageFont.truetype(path+'Assistant-Regular.ttf', 18)
|
||||
|
||||
default = ImageFont.truetype(fpath+'NotoSans/NotoSans-SemiCondensedLight.ttf', 18)
|
||||
semi = ImageFont.truetype(fpath+'NotoSans/NotoSans-SemiCondensed.ttf', 18)
|
||||
bold = ImageFont.truetype(fpath+'NotoSans/NotoSans-SemiCondensedMedium.ttf', 18)
|
||||
month_font = ImageFont.truetype(fpath+'NotoSans/NotoSans-SemiCondensedLight.ttf', 40)
|
||||
|
||||
im_open = Image.open
|
||||
|
||||
owm = pyowm.OWM(api_key)
|
||||
|
||||
possible_update_values = [10, 15, 20, 30, 60]
|
||||
if int(update_interval) not in possible_update_values:
|
||||
print('Selected update-interval: ',update_interval, 'minutes')
|
||||
print('Please select an update interval from these values:', possible_update_values)
|
||||
raise ValueError
|
||||
|
||||
"""Main loop starts from here"""
|
||||
def main():
|
||||
calibration_countdown = 'initial'
|
||||
@ -73,6 +58,7 @@ def main():
|
||||
year = int(time.now().strftime('%Y'))
|
||||
mins = int(time.strftime("%M"))
|
||||
seconds = int(time.strftime("%S"))
|
||||
now = arrow.now()
|
||||
|
||||
for i in range(1):
|
||||
print('_________Starting new loop___________'+'\n')
|
||||
@ -94,213 +80,174 @@ def main():
|
||||
"""Create a blank white page first"""
|
||||
image = Image.new('RGB', (EPD_HEIGHT, EPD_WIDTH), 'white')
|
||||
|
||||
"""Add the icon with the current month's name"""
|
||||
image.paste(im_open(mpath+str(time.strftime("%B")+'.jpeg')), monthplace)
|
||||
|
||||
"""Add the line seperating the weather and Calendar section"""
|
||||
image.paste(seperator, seperatorplace)
|
||||
|
||||
"""Add weekday-icons (Mon, Tue...) and draw a circle on the
|
||||
current weekday"""
|
||||
if (week_starts_on is "Monday"):
|
||||
calendar.setfirstweekday(calendar.MONDAY)
|
||||
image.paste(weekmon, weekplace)
|
||||
image.paste(weekday, weekdaysmon[(time.strftime("%a"))], weekday)
|
||||
|
||||
"""For those whose week starts on Sunday, change accordingly"""
|
||||
if (week_starts_on is "Sunday"):
|
||||
calendar.setfirstweekday(calendar.SUNDAY)
|
||||
image.paste(weeksun, weekplace)
|
||||
image.paste(weekday, weekdayssun[(time.strftime("%a"))], weekday)
|
||||
|
||||
"""Using the built-in calendar function, draw icons for each
|
||||
number of the month (1,2,3,...28,29,30)"""
|
||||
cal = calendar.monthcalendar(time.year, time.month)
|
||||
|
||||
for numbers in cal[0]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)])
|
||||
for numbers in cal[1]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['b'+str(cal[1].index(numbers)+1)])
|
||||
for numbers in cal[2]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['c'+str(cal[2].index(numbers)+1)])
|
||||
for numbers in cal[3]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['d'+str(cal[3].index(numbers)+1)])
|
||||
for numbers in cal[4]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['e'+str(cal[4].index(numbers)+1)])
|
||||
if len(cal) is 6:
|
||||
for numbers in cal[5]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['f'+str(cal[5].index(numbers)+1)])
|
||||
|
||||
"""Custom function to display text on the E-Paper.
|
||||
Tuple refers to the x and y coordinates of the E-Paper display,
|
||||
with (0, 0) being the top left corner of the display."""
|
||||
def write_text(box_width, box_height, text, tuple):
|
||||
text_width, text_height = font.getsize(text)
|
||||
if (text_width, text_height) > (box_width, box_height):
|
||||
raise ValueError('Sorry, your text is too big for the box')
|
||||
else:
|
||||
x = int((box_width / 2) - (text_width / 2))
|
||||
y = int((box_height / 2) - (text_height / 2))
|
||||
space = Image.new('RGB', (box_width, box_height), color='white')
|
||||
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
||||
image.paste(space, tuple)
|
||||
|
||||
"""Connect to Openweathermap API to fetch weather data"""
|
||||
print("Connecting to Openweathermap API servers...")
|
||||
if owm.is_API_online() is True:
|
||||
observation = owm.weather_at_place(location)
|
||||
print("weather data:")
|
||||
weather = observation.get_weather()
|
||||
weathericon = weather.get_weather_icon_name()
|
||||
Humidity = str(weather.get_humidity())
|
||||
cloudstatus = str(weather.get_clouds())
|
||||
weather_description = (str(weather.get_status()))
|
||||
|
||||
if units is "metric":
|
||||
Temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
|
||||
windspeed = str(int(weather.get_wind()['speed']))
|
||||
write_text(50, 35, Temperature + " °C", (334, 0))
|
||||
write_text(100, 35, windspeed+" km/h", (114, 0))
|
||||
|
||||
if units is "imperial":
|
||||
Temperature = str(int(weather.get_temperature('fahrenheit')['temp']))
|
||||
windspeed = str(int(weather.get_wind()['speed']*0.621))
|
||||
write_text(50, 35, Temperature + " °F", (334, 0))
|
||||
write_text(100, 35, windspeed+" mph", (114, 0))
|
||||
|
||||
if hours is "24":
|
||||
sunrisetime = str(datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))).strftime('%-H:%M'))
|
||||
sunsettime = str(datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))).strftime('%-H:%M'))
|
||||
|
||||
if hours is "12":
|
||||
sunrisetime = str(datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))).strftime('%-I:%M'))
|
||||
sunsettime = str(datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))).strftime('%-I:%M'))
|
||||
|
||||
print('Temperature: '+Temperature+' °C')
|
||||
print('Humidity: '+Humidity+'%')
|
||||
#print('Icon code: '+weathericon)
|
||||
print('weather-icon name: '+weathericons[weathericon])
|
||||
print('Wind speed: '+windspeed+'km/h')
|
||||
print('Sunrise-time: '+sunrisetime)
|
||||
print('Sunset time: '+sunsettime)
|
||||
print('Cloudiness: ' + cloudstatus+'%')
|
||||
print('Weather description: '+weather_description+'\n')
|
||||
|
||||
"""Add the weather icon at the top left corner"""
|
||||
image.paste(im_open(wpath+weathericons[weathericon]+'.jpeg'), wiconplace)
|
||||
|
||||
"""Add the temperature icon at it's position"""
|
||||
image.paste(tempicon, tempplace)
|
||||
|
||||
"""Add the humidity icon and display the humidity"""
|
||||
image.paste(humicon, humplace)
|
||||
write_text(50, 35, Humidity + " %", (334, 35))
|
||||
|
||||
"""Add the sunrise icon and display the sunrise time"""
|
||||
image.paste(sunriseicon, sunriseplace)
|
||||
write_text(50, 35, sunrisetime, (249, 0))
|
||||
|
||||
"""Add the sunset icon and display the sunrise time"""
|
||||
image.paste(sunseticon, sunsetplace)
|
||||
write_text(50, 35, sunsettime, (249, 35))
|
||||
|
||||
"""Add the wind icon at it's position"""
|
||||
image.paste(windicon, windiconspace)
|
||||
|
||||
"""Add a short weather description"""
|
||||
write_text(144, 35, weather_description, (70, 35))
|
||||
|
||||
else:
|
||||
"""If no response was received from the openweathermap
|
||||
api server, add the cloud with question mark"""
|
||||
image.paste(no_response, wiconplace)
|
||||
|
||||
"""Algorithm for filtering and sorting events from your
|
||||
iCalendar/s"""
|
||||
print('Fetching events from your calendar'+'\n')
|
||||
events_this_month = []
|
||||
upcoming = []
|
||||
today = date.today()
|
||||
|
||||
"""Create a time span using the events_max_range value (in days)
|
||||
to filter events in that range"""
|
||||
time_span = today + timedelta(days=int(events_max_range))
|
||||
|
||||
for icalendars in ical_urls:
|
||||
decode = str(urlopen(icalendars).read().decode())
|
||||
beginAlarmIndex = 0
|
||||
while beginAlarmIndex >= 0:
|
||||
beginAlarmIndex = decode.find('BEGIN:VALARM')
|
||||
if beginAlarmIndex >= 0:
|
||||
endAlarmIndex = decode.find('END:VALARM')
|
||||
decode = decode[:beginAlarmIndex] + decode[endAlarmIndex+12:]
|
||||
ical = Calendar(decode)
|
||||
for events in ical.events:
|
||||
if re.search('RRULE',str(events)) is not None:
|
||||
r = re.search('RRULE:(.+?)\n',str(events))
|
||||
r_start = re.search('DTSTART:(.+?)\n',str(events))
|
||||
if r_start is not None: # if r_start is None the format of DTSTART is not recognized
|
||||
if time.now().month == 12:
|
||||
r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year+1, 1, 1)
|
||||
else:
|
||||
r_string=(r.group(1).rstrip()+';UNTIL='+'%04d%02d%02d'+'T000000Z') % (time.now().year, time.now().month+1, 1)
|
||||
rule=rrulestr(r_string,dtstart=parse(r_start.group(1)))
|
||||
for i in rule:
|
||||
if i.year == time.now().year and i.month == time.now().month and i.day >= time.now().day:
|
||||
upcoming.append({'date':str(time.now().year) + " " + time.now().strftime('%m')+ " " + str(i.day).zfill(2), 'event':events.name})
|
||||
if i.day not in events_this_month:
|
||||
events_this_month.append(i.day)
|
||||
# uncomment this line to see fetched recurring events
|
||||
#print ("Appended recurring event: " + events.name + " on " + str(time.now().year) + " " + time.now().strftime('%m')+ " " + str(i.day).zfill(2))
|
||||
else:
|
||||
if events.begin.date().month == today.month:
|
||||
if int((events.begin).format('D')) not in events_this_month:
|
||||
events_this_month.append(int((events.begin).format('D')))
|
||||
if today <= events.begin.date() <= time_span:
|
||||
upcoming.append({'date':events.begin.format('YYYY MM DD'), 'event':events.name})
|
||||
|
||||
|
||||
def takeDate(elem):
|
||||
return elem['date']
|
||||
|
||||
upcoming.sort(key=takeDate)
|
||||
|
||||
#print('Upcoming events:',upcoming) #Display fetched events
|
||||
|
||||
def write_text_left(box_width, box_height, text, tuple):
|
||||
"""Custom function to display text on the E-Paper"""
|
||||
def write_text(box_width, box_height, text, tuple, font=default, alignment='middle'):
|
||||
text_width, text_height = font.getsize(text)
|
||||
while (text_width, text_height) > (box_width, box_height):
|
||||
text=text[0:-1]
|
||||
text_width, text_height = font.getsize(text)
|
||||
y = int((box_height / 2) - (text_height / 2))
|
||||
if alignment is "" or "middle" or None:
|
||||
x = int((box_width / 2) - (text_width / 2))
|
||||
if alignment is 'left':
|
||||
x = 0
|
||||
y = int((box_height / 2) - (text_height / 1.7))
|
||||
space = Image.new('RGB', (box_width, box_height), color='white')
|
||||
ImageDraw.Draw(space).text((0, y), text, fill='black', font=font)
|
||||
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
||||
image.paste(space, tuple)
|
||||
|
||||
"""Write event dates and names on the E-Paper"""
|
||||
if additional_feature is "events":
|
||||
if len(cal) is 5:
|
||||
del upcoming[6:]
|
||||
"""Check if internet is available by trying to reach google"""
|
||||
def internet_available():
|
||||
try:
|
||||
urlopen('https://google.com',timeout=5)
|
||||
return True
|
||||
except URLError as err:
|
||||
return False
|
||||
|
||||
for dates in range(len(upcoming)):
|
||||
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b')
|
||||
write_text(70, 25, readable_date, date_positions['d'+str(dates+1)])
|
||||
for events in range(len(upcoming)):
|
||||
write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)])
|
||||
"""Connect to Openweathermap API and fetch weather data"""
|
||||
if top_section is "Weather" and api_key != "" and owm.is_API_online() is True:
|
||||
try:
|
||||
print("Connecting to Openweathermap API servers...")
|
||||
observation = owm.weather_at_place(location)
|
||||
print("weather data:")
|
||||
weather = observation.get_weather()
|
||||
weathericon = weather.get_weather_icon_name()
|
||||
Humidity = str(weather.get_humidity())
|
||||
cloudstatus = str(weather.get_clouds())
|
||||
weather_description = (str(weather.get_status()))
|
||||
|
||||
if units is "metric":
|
||||
Temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
|
||||
windspeed = str(int(weather.get_wind()['speed']))
|
||||
write_text(50, 35, Temperature + " °C", (334, 0))
|
||||
write_text(100, 35, windspeed+" km/h", (114, 0))
|
||||
|
||||
if units is "imperial":
|
||||
Temperature = str(int(weather.get_temperature('fahrenheit')['temp']))
|
||||
windspeed = str(int(weather.get_wind()['speed']*0.621))
|
||||
write_text(50, 35, Temperature + " °F", (334, 0))
|
||||
write_text(100, 35, windspeed+" mph", (114, 0))
|
||||
|
||||
if hours is "24":
|
||||
sunrisetime = str(datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))).strftime('%-H:%M'))
|
||||
sunsettime = str(datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))).strftime('%-H:%M'))
|
||||
|
||||
if hours is "12":
|
||||
sunrisetime = str(datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix'))).strftime('%-I:%M'))
|
||||
sunsettime = str(datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix'))).strftime('%-I:%M'))
|
||||
|
||||
"""Show the fetched weather data"""
|
||||
print('Temperature: '+ Temperature+' °C')
|
||||
print('Humidity: '+ Humidity+'%')
|
||||
print('weather-icon name: '+weathericons[weathericon])
|
||||
print('Wind speed: '+ windspeed+'km/h')
|
||||
print('Sunrise-time: '+ sunrisetime)
|
||||
print('Sunset time: '+ sunsettime)
|
||||
print('Cloudiness: ' + cloudstatus+'%')
|
||||
print('Weather description: '+ weather_description+'\n')
|
||||
|
||||
"""Add the weather icon at the top left corner"""
|
||||
image.paste(im_open(wpath + weathericons[weathericon] +'.jpeg'), wiconplace)
|
||||
|
||||
"""Add the temperature icon at it's position"""
|
||||
image.paste(tempicon, tempplace)
|
||||
|
||||
"""Add the humidity icon and display the humidity"""
|
||||
image.paste(humicon, humplace)
|
||||
write_text(50, 35, Humidity + " %", (334, 35))
|
||||
|
||||
"""Add the sunrise icon and display the sunrise time"""
|
||||
image.paste(sunriseicon, sunriseplace)
|
||||
write_text(50, 35, sunrisetime, (249, 0))
|
||||
|
||||
"""Add the sunset icon and display the sunrise time"""
|
||||
image.paste(sunseticon, sunsetplace)
|
||||
write_text(50, 35, sunsettime, (249, 35))
|
||||
|
||||
"""Add the wind icon at it's position"""
|
||||
image.paste(windicon, windiconspace)
|
||||
|
||||
"""Add a short weather description"""
|
||||
write_text(144, 35, weather_description, (70, 35))
|
||||
|
||||
except Exception as e:
|
||||
"""If no response was received from the openweathermap
|
||||
api server, add the cloud with question mark"""
|
||||
print('__________OWM-ERROR!__________'+'\n')
|
||||
print('Reason: ',e+'\n')
|
||||
image.paste(no_response, wiconplace)
|
||||
pass
|
||||
|
||||
"""Set the Calendar to start on the day specified by the settings file """
|
||||
if week_starts_on is "Monday":
|
||||
calendar.setfirstweekday(calendar.MONDAY)
|
||||
|
||||
"""For those whose week starts on Sunday, change accordingly"""
|
||||
if week_starts_on is "Sunday":
|
||||
calendar.setfirstweekday(calendar.SUNDAY)
|
||||
|
||||
"""Using the built-in calendar to generate the monthly Calendar
|
||||
template"""
|
||||
cal = calendar.monthcalendar(time.year, time.month)
|
||||
|
||||
if middle_section is "Calendar":
|
||||
"""Add the icon with the current month's name"""
|
||||
write_text(384,60, now.format('MMMM',locale=language), monthplace, font=month_font)
|
||||
|
||||
"""Add the line seperating the weather and Calendar section"""
|
||||
image.paste(seperator, seperatorplace)
|
||||
|
||||
"""Create a list containing the weekday abbrevations for the
|
||||
chosen language"""
|
||||
if week_starts_on is "Monday":
|
||||
prev_weekstart = now.replace(days = - now.weekday())
|
||||
image.paste(weekday, weekday_pos['pos'+str(now.weekday())], weekday)
|
||||
if week_starts_on is "Sunday":
|
||||
prev_weekstart = now.replace(days = - now.isoweekday())
|
||||
image.paste(weekday, weekday_pos['pos'+str(now.isoweekday())], weekday)
|
||||
|
||||
weekday_names_list = []
|
||||
for i in range(7):
|
||||
weekday_name = prev_weekstart.replace(days=+i)
|
||||
weekday_names_list.append(weekday_name.format('ddd',locale=language))
|
||||
|
||||
for i in range(len(weekday_names_list)):
|
||||
write_text(54, 28, weekday_names_list[i], weekday_pos['pos'+str(i)])
|
||||
|
||||
"""Create the calendar template of the current month"""
|
||||
for numbers in cal[0]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['a'+str(cal[0].index(numbers)+1)])
|
||||
for numbers in cal[1]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['b'+str(cal[1].index(numbers)+1)])
|
||||
for numbers in cal[2]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['c'+str(cal[2].index(numbers)+1)])
|
||||
for numbers in cal[3]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['d'+str(cal[3].index(numbers)+1)])
|
||||
for numbers in cal[4]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['e'+str(cal[4].index(numbers)+1)])
|
||||
if len(cal) is 6:
|
||||
del upcoming[4:]
|
||||
for numbers in cal[5]:
|
||||
image.paste(im_open(dpath+str(numbers)+'.jpeg'), positions['f'+str(cal[5].index(numbers)+1)])
|
||||
|
||||
for dates in range(len(upcoming)):
|
||||
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b')
|
||||
write_text(70, 25, readable_date, date_positions['d'+str(dates+3)])
|
||||
for events in range(len(upcoming)):
|
||||
write_text_left(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)])
|
||||
"""Draw a larger square on today's date"""
|
||||
today = time.day
|
||||
if today in cal[0]:
|
||||
image.paste(dateicon, positions['a'+str(cal[0].index(today)+1)], dateicon)
|
||||
if today in cal[1]:
|
||||
image.paste(dateicon, positions['b'+str(cal[1].index(today)+1)], dateicon)
|
||||
if today in cal[2]:
|
||||
image.paste(dateicon, positions['c'+str(cal[2].index(today)+1)], dateicon)
|
||||
if today in cal[3]:
|
||||
image.paste(dateicon, positions['d'+str(cal[3].index(today)+1)], dateicon)
|
||||
if today in cal[4]:
|
||||
image.paste(dateicon, positions['e'+str(cal[4].index(today)+1)], dateicon)
|
||||
if len(cal) is 6 and today in cal[5]:
|
||||
image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon)
|
||||
|
||||
"""Add rss-feeds at the bottom section of the Calendar"""
|
||||
if additional_feature is "rss":
|
||||
if bottom_section is "RSS" and rss_feeds != []:
|
||||
|
||||
def multiline_text(text, max_width):
|
||||
"""Custom function to display longer text into multiple lines (wrapping)"""
|
||||
def multiline_text(text, max_width, font=default):
|
||||
lines = []
|
||||
if font.getsize(text)[0] <= max_width:
|
||||
lines.append(text)
|
||||
@ -318,69 +265,154 @@ def main():
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
"""Parse the RSS-feed titles and save them to a list"""
|
||||
rss_feed = []
|
||||
for feeds in rss_feeds:
|
||||
text = feedparser.parse(feeds)
|
||||
for posts in text.entries:
|
||||
rss_feed.append(posts.title)
|
||||
rss_feed.append(posts.summary)#title
|
||||
|
||||
"""Shuffle the list to prevent displaying the same titles over and over"""
|
||||
random.shuffle(rss_feed)
|
||||
news = []
|
||||
|
||||
if len(cal) is 5:
|
||||
"""Remove all titles except the first 4 or 6,
|
||||
depenfing on how much space is available on the """
|
||||
if middle_section is 'Calendar' and len(cal) is 5 or middle_section is 'Agenda':
|
||||
del rss_feed[6:]
|
||||
|
||||
if len(cal) is 6:
|
||||
del rss_feed[4:]
|
||||
|
||||
"""Split titles of the rss feeds into lines that can fit
|
||||
on the Calendar and add them to a list"""
|
||||
for title in range(len(rss_feeds)):
|
||||
news.append(multiline_text(rss_feed[title], 384))
|
||||
|
||||
news = [j for i in news for j in i]
|
||||
|
||||
if len(cal) is 5:
|
||||
"""Display the split lines of the titles"""
|
||||
if middle_section is 'Calendar' and len(cal) is 5 or middle_section is 'Agenda':
|
||||
if len(news) > 6:
|
||||
del news[6:]
|
||||
for lines in range(len(news)):
|
||||
write_text_left(384, 25, news[lines], rss_places['line_'+str(lines+1)])
|
||||
write_text(384, 25, news[lines], rss_places['line_'+str(lines+1)], alignment = 'left')
|
||||
|
||||
if len(cal) is 6:
|
||||
if len(news) > 4:
|
||||
del news[4:]
|
||||
for lines in range(len(news)):
|
||||
write_text_left(384, 25, news[lines], rss_places['line_'+str(lines+3)])
|
||||
write_text(384, 25, news[lines], rss_places['line_'+str(lines+3)], alignment = 'left')
|
||||
|
||||
"""Draw smaller squares on days with events"""
|
||||
for numbers in events_this_month:
|
||||
if numbers in cal[0]:
|
||||
image.paste(eventicon, positions['a'+str(cal[0].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[1]:
|
||||
image.paste(eventicon, positions['b'+str(cal[1].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[2]:
|
||||
image.paste(eventicon, positions['c'+str(cal[2].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[3]:
|
||||
image.paste(eventicon, positions['d'+str(cal[3].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[4]:
|
||||
image.paste(eventicon, positions['e'+str(cal[4].index(numbers)+1)], eventicon)
|
||||
if len(cal) is 6:
|
||||
if numbers in cal[5]:
|
||||
|
||||
if middle_section is "Calendar" or "Agenda":
|
||||
"""Algorithm for filtering and sorting events from your
|
||||
iCalendar/s"""
|
||||
events_this_month = []
|
||||
upcoming = []
|
||||
today = time.today()
|
||||
|
||||
"""Create a time span using the events_max_range value (in days)
|
||||
to filter events in that range"""
|
||||
agenda_max_days = arrow.now().replace(days=+22)
|
||||
calendar_max_days = arrow.now().replace(days=+int(events_max_range))
|
||||
if internet_available() is True:
|
||||
print('Internet connection test passed'+'\n')
|
||||
print('Fetching events from your calendar'+'\n')
|
||||
for icalendars in ical_urls:
|
||||
decode = str(urlopen(icalendars).read().decode())
|
||||
beginAlarmIndex = 0
|
||||
while beginAlarmIndex >= 0:
|
||||
beginAlarmIndex = decode.find('BEGIN:VALARM')
|
||||
if beginAlarmIndex >= 0:
|
||||
endAlarmIndex = decode.find('END:VALARM')
|
||||
decode = decode[:beginAlarmIndex] + decode[endAlarmIndex+12:]
|
||||
ical = Calendar(decode)
|
||||
for events in ical.events:
|
||||
if events.begin.date().year == today.year and events.begin.date().month is today.month:
|
||||
if int((events.begin).format('D')) not in events_this_month:
|
||||
events_this_month.append(int((events.begin).format('D')))
|
||||
if middle_section is 'Agenda' and events in ical.timeline.included(now, agenda_max_days):
|
||||
upcoming.append(events)
|
||||
if middle_section is 'Calendar' and events in ical.timeline.included(now, calendar_max_days):
|
||||
upcoming.append(events)
|
||||
|
||||
def event_begins(elem):
|
||||
return elem.begin
|
||||
|
||||
upcoming.sort(key=event_begins)
|
||||
|
||||
else:
|
||||
print("Could not fetch events from your iCalendar.")
|
||||
print("Either the internet connection is too weak or we're offline.")
|
||||
|
||||
|
||||
if middle_section is 'Agenda':
|
||||
"""For the agenda view, create a list containing dates and events of the next 22 days"""
|
||||
if len(upcoming) is not 0:
|
||||
while (upcoming[-1].begin.date().day - now.day) + len(upcoming) >= 22:
|
||||
del upcoming[-1]
|
||||
agenda_list = []
|
||||
for i in range(22):
|
||||
date = now.replace(days=+i)
|
||||
agenda_list.append({'value':date.format('ddd D MMM YY', locale=language),'type':'date'})
|
||||
for events in upcoming:
|
||||
if events.begin.date().day == date.day:
|
||||
if not events.all_day:
|
||||
agenda_list.append({'value':events.begin.format('HH:mm')+ ' '+ str(events.name), 'type':'timed_event'})
|
||||
else:
|
||||
agenda_list.append({'value':events.name, 'type':'full_day_event'})
|
||||
if bottom_section is not "":
|
||||
del agenda_list[16:]
|
||||
image.paste(seperator2, agenda_view_lines['line17'])
|
||||
|
||||
if bottom_section is "":
|
||||
del agenda_list[22:]
|
||||
image.paste(seperator2, agenda_view_lines['line22'])
|
||||
|
||||
for lines in range(len(agenda_list)):
|
||||
if agenda_list[lines]['type'] is 'date':
|
||||
write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)], font=semi, alignment='left')
|
||||
image.paste(seperator2, agenda_view_lines['line'+str(lines+1)])
|
||||
elif agenda_list[lines]['type'] is 'timed_event':
|
||||
write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)], alignment='left')
|
||||
else:
|
||||
write_text(384, 25, agenda_list[lines]['value'], agenda_view_lines['line'+str(lines+1)])
|
||||
|
||||
if middle_section is 'Calendar':
|
||||
"""Draw smaller squares on days with events"""
|
||||
for numbers in events_this_month:
|
||||
if numbers in cal[0]:
|
||||
image.paste(eventicon, positions['a'+str(cal[0].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[1]:
|
||||
image.paste(eventicon, positions['b'+str(cal[1].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[2]:
|
||||
image.paste(eventicon, positions['c'+str(cal[2].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[3]:
|
||||
image.paste(eventicon, positions['d'+str(cal[3].index(numbers)+1)], eventicon)
|
||||
if numbers in cal[4]:
|
||||
image.paste(eventicon, positions['e'+str(cal[4].index(numbers)+1)], eventicon)
|
||||
if len(cal) is 6 and numbers in cal[5]:
|
||||
image.paste(eventicon, positions['f'+str(cal[5].index(numbers)+1)], eventicon)
|
||||
|
||||
"""Draw a larger square on today's date"""
|
||||
today = time.day
|
||||
if today in cal[0]:
|
||||
image.paste(dateicon, positions['a'+str(cal[0].index(today)+1)], dateicon)
|
||||
if today in cal[1]:
|
||||
image.paste(dateicon, positions['b'+str(cal[1].index(today)+1)], dateicon)
|
||||
if today in cal[2]:
|
||||
image.paste(dateicon, positions['c'+str(cal[2].index(today)+1)], dateicon)
|
||||
if today in cal[3]:
|
||||
image.paste(dateicon, positions['d'+str(cal[3].index(today)+1)], dateicon)
|
||||
if today in cal[4]:
|
||||
image.paste(dateicon, positions['e'+str(cal[4].index(today)+1)], dateicon)
|
||||
if len(cal) is 6:
|
||||
if today in cal[5]:
|
||||
image.paste(dateicon, positions['f'+str(cal[5].index(today)+1)], dateicon)
|
||||
"""Write event dates and names on the E-Paper"""
|
||||
if bottom_section is "Events":
|
||||
if len(cal) is 5:
|
||||
del upcoming[6:]
|
||||
for dates in range(len(upcoming)):
|
||||
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b')
|
||||
write_text(70, 25, readable_date, date_positions['d'+str(dates+1)])
|
||||
for events in range(len(upcoming)):
|
||||
write_text(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+1)], alignment = 'left')
|
||||
|
||||
if len(cal) is 6:
|
||||
del upcoming[4:]
|
||||
for dates in range(len(upcoming)):
|
||||
readable_date = datetime.strptime(upcoming[dates]['date'], '%Y %m %d').strftime('%-d %b')
|
||||
write_text(70, 25, readable_date, date_positions['d'+str(dates+3)])
|
||||
for events in range(len(upcoming)):
|
||||
write_text(314, 25, (upcoming[events]['event']), event_positions['e'+str(events+3)], alignment = 'left')
|
||||
|
||||
|
||||
"""
|
||||
Map all pixels of the generated image to red, white and black
|
||||
@ -396,7 +428,7 @@ def main():
|
||||
if display_colours is "bw":
|
||||
buffer[np.logical_and(r > 240, g > 240)] = [255,255,255] #white
|
||||
buffer[g < 255] = [0,0,0] #black
|
||||
|
||||
|
||||
improved_image = Image.fromarray(buffer).rotate(270, expand=True)
|
||||
print('Initialising E-Paper Display')
|
||||
epd.init()
|
||||
@ -407,20 +439,25 @@ def main():
|
||||
print('______Powering off the E-Paper until the next loop______'+'\n')
|
||||
epd.sleep()
|
||||
|
||||
del events_this_month
|
||||
del upcoming
|
||||
if middle_section is 'Calendar':
|
||||
del events_this_month
|
||||
del upcoming
|
||||
del weekday_names_list
|
||||
|
||||
if additional_feature is "rss":
|
||||
if bottom_section is 'RSS':
|
||||
del rss_feed
|
||||
del news
|
||||
|
||||
if middle_section is 'Agenda':
|
||||
del agenda_list
|
||||
|
||||
del buffer
|
||||
del image
|
||||
del improved_image
|
||||
gc.collect()
|
||||
|
||||
if calibration_countdown is 'initial':
|
||||
calibration_countdown = 0
|
||||
calibration_countdown = 0
|
||||
calibration_countdown += 1
|
||||
|
||||
for i in range(1):
|
||||
@ -433,7 +470,7 @@ def main():
|
||||
for update_times in timings:
|
||||
if update_times >= mins:
|
||||
sleep_for_minutes = update_times - mins
|
||||
|
||||
|
||||
next_update_countdown = sleep_for_minutes*60 + (60-seconds)
|
||||
|
||||
print(sleep_for_minutes,'Minutes and ', (60-seconds),'Seconds left until next loop')
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Settings-File Generator</title>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
||||
@ -20,7 +21,7 @@ body{
|
||||
<div class="ts header">
|
||||
Setting Generator
|
||||
<div class="sub header"><a href="https://github.com/aceisace/Inky-Calendar">For Inky-Calendar Project of Ace-Innovation Laboratory (by aceisace)</a><br>
|
||||
<img src="https://github.com/aceisace/Inky-Calendar/blob/Stable/Gallery/Inky-Calendar-logo.png?raw=true" width="800">
|
||||
<img src="https://github.com/aceisace/Inky-Calendar/blob/Stable/Gallery/Inky-Calendar-logo.png?raw=true" width="800" alt="Inky-Calendar-logo">
|
||||
<div>
|
||||
</div>
|
||||
<ins>If no value is filled in for any of the row, the default value will be used.</ins>
|
||||
@ -30,12 +31,12 @@ body{
|
||||
</div>
|
||||
<form class="ts form">
|
||||
<div class="field">
|
||||
<label>iCalendar URL. If you want to add multiple URLs, seperate each one with a comma.</label>
|
||||
<label>Google iCalendar URL. If you want to add multiple URLs, seperate each one with a comma.</label>
|
||||
<input id="ical_urls" type="text" placeholder="https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>RSS-Feed URL. To add more than one feed, seperate each URL with a comma.</label>
|
||||
<label>RSS-Feeds. To add more than one feed, seperate each URL with a comma.</label>
|
||||
<input id="rss_urls" type="text" placeholder="http://feeds.bbci.co.uk/news/world/rss.xml#">
|
||||
</div>
|
||||
|
||||
@ -61,31 +62,33 @@ body{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>What do you want to be displayed below the Calendar?</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="event_feature" type="radio" name="af">
|
||||
<label for="event_feature">Events from my iCalendar</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="rss_feature" type="radio" name="af" checked>
|
||||
<label for="rss_feature">RSS-Feeds</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Openweathermap API Key</label>
|
||||
<details class="ts accordion">
|
||||
<summary>
|
||||
<i class="dropdown icon"></i> Info
|
||||
</summary>
|
||||
<div class="content">
|
||||
<p> Please insert your own Openweathermap API-key to fetch the latest weather info. To find out how to create your own key, please click here: <a href="https://github.com/aceisace/Inky-Calendar/wiki/Openweathermap-API">Creating an openweathermap api-key</a>. If you don't add an api-key, the top section will not show any weather info</p></div>
|
||||
</details>
|
||||
<input id="api_key" type="text" placeholder="">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field">
|
||||
<label>Location</label>
|
||||
<label>Location (for weather data)</label>
|
||||
<details class="ts accordion">
|
||||
<summary>
|
||||
<i class="dropdown icon"></i> Info
|
||||
</summary>
|
||||
<div class="content">
|
||||
<p>Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on <a href="https://openweathermap.org/">openweathermap</a>. The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: <a href="https://countrycode.org/">(find iso-code)</a></p></div>
|
||||
</details>
|
||||
<input id="location" type="text" placeholder="Stuttgart, DE">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Week starts on</label>
|
||||
<label>On which day does the week start on in your country?</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="week_monday" type="radio" name="hr" checked>
|
||||
@ -104,7 +107,14 @@ body{
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>At which hours should the display be calibrated?</label>
|
||||
<label>At which hours (in 24 hour-format) should the display be calibrated? Leave blank if you're not sure.</label>
|
||||
<details class="ts accordion">
|
||||
<summary>
|
||||
<i class="dropdown icon"></i> Info
|
||||
</summary>
|
||||
<div class="content">
|
||||
<p>Calibration refers to the process of flushing the display with a single colour to prevent 'ghosting' (an effect specific to E-Paper displays where the remnants of the previous image can be seen on the current one). It takes several minutes to finish the calibration(around 10 mins for the 2-colour displays and around 20 mins for the 3-colour displays) so please choose hours where you are less likely to need the display. It is recommended to calibrate at least thrice a day.</p></div>
|
||||
</details>
|
||||
<input id="calibration_hours" type="text" placeholder="0,12,18">
|
||||
</div>
|
||||
|
||||
@ -129,14 +139,54 @@ body{
|
||||
<input id="language_en" type="radio" name="la" checked>
|
||||
<label for="language_en">English</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_zh_tw" type="radio" name="la">
|
||||
<label for="language_zh_tw">Chinese/Taiwanese</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_de" type="radio" name="la">
|
||||
<label for="language_de">German</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_ru" type="radio" name="la">
|
||||
<label for="language_ru">Russian</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_it" type="radio" name="la">
|
||||
<label for="language_it">Italian</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_es" type="radio" name="la">
|
||||
<label for="language_es">Spanish</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_fr" type="radio" name="la">
|
||||
<label for="language_fr">French</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_el" type="radio" name="la">
|
||||
<label for="language_el">Greek</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_sv" type="radio" name="la">
|
||||
<label for="language_sv">Swedish</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_nl" type="radio" name="la">
|
||||
<label for="language_nl">Dutch</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_pl" type="radio" name="la">
|
||||
<label for="language_pl">Polish</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_ua" type="radio" name="la">
|
||||
<label for="language_ua">Ukrainian</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_nb" type="radio" name="la">
|
||||
<label for="language_nb">Norwegian</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="language_vi" type="radio" name="la">
|
||||
<label for="language_vi">Vietnamese</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -157,13 +207,63 @@ body{
|
||||
<div class="field">
|
||||
<label>Which hour-format do you prefer?</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="24_hours" type="radio" name="tf" checked>
|
||||
<label for="24_hours">24-hour format</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="12_hours" type="radio" name="tf">
|
||||
<label for="12_hours">12-hour format</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>What should be displayed in the top section?</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="24_hours" type="radio" name="tf" checked>
|
||||
<label for="24_hours">24-hour format</label>
|
||||
<input id="Weather" type="radio" name="ts" checked>
|
||||
<label for="Weather">Events from my iCalendar</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="top_blank" type="radio" name="ts">
|
||||
<label for="top_blank">Nothing</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>What should be displayed in the middle (main) section?</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="Calendar" type="radio" name="ms" checked>
|
||||
<label for="Calendar">A monthly Calendar</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="Agenda" type="radio" name="ms">
|
||||
<label for="Agenda">Agenda-like overview</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="middle_blank" type="radio" name="ms">
|
||||
<label for="middle_blank">Nothing</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>What should be displayed in the bottom section? If you've chosen the Agenda View for the middle section, should choose either RSS-feeds or nothing.</label>
|
||||
<div class="ts checkboxes">
|
||||
<div class="ts radio checkbox">
|
||||
<input id="RSS" type="radio" name="bs" checked>
|
||||
<label for="RSS">RSS-feeds</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="Events" type="radio" name="bs">
|
||||
<label for="Events">Events from my iCalendar</label>
|
||||
</div>
|
||||
<div class="ts radio checkbox">
|
||||
<input id="bottom_blank" type="radio" name="bs">
|
||||
<label for="bottom_blank">Nothing</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,7 +272,7 @@ body{
|
||||
<br>
|
||||
<button class="ts primary button" onClick="generate();">Generate</button>
|
||||
<br><br>
|
||||
<kbd>Developed by Toby Chui for Inky-Calendar Project, modified by aceisace Licensed under MIT</kbd>
|
||||
<kbd>Developed by Toby Chui for Inky-Calendar Project, modified by aceisace. Licensed under MIT</kbd>
|
||||
<details class="ts accordion">
|
||||
<summary>
|
||||
<i class="dropdown icon"></i> MIT License
|
||||
@ -192,7 +292,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
<br>
|
||||
|
||||
<script>
|
||||
var template = 'ical_urls = [\n"{ical_urls}"\n]\nrss_feeds = [\n"{rss_urls}"\n]\nupdate_interval = "{update_interval}"\nadditional_feature = "{additional_feature}"\napi_key = "{api_key}"\nlocation = "{location}"\nweek_starts_on = "{week_starts_on}"\nevents_max_range = "{events_max_range}"\ncalibration_hours = [{calibration_hours}]\ndisplay_colours = "{display_colours}"\nlanguage = "{language}"\nunits = "{units}"\nhours = "{hours}"';
|
||||
var template = 'ical_urls = ["{ical_urls}"]\nrss_feeds = ["{rss_urls}"]\nupdate_interval = "{update_interval}"\napi_key = "{api_key}"\nlocation = "{location}"\nweek_starts_on = "{week_starts_on}"\nevents_max_range = "{events_max_range}"\ncalibration_hours = [{calibration_hours}]\ndisplay_colours = "{display_colours}"\nlanguage = "{language}"\nunits = "{units}"\nhours = "{hours}"\ntop_section = "{top_section}"\nmiddle_section = "{middle_section}"\nbottom_section = "{bottom_section}"';
|
||||
|
||||
function generate(){
|
||||
var ical_urls = $("#ical_urls").val().trim();
|
||||
@ -217,11 +317,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
update_interval = "60";
|
||||
}
|
||||
|
||||
var additional_feature = "events";
|
||||
if ($('#rss').is(':checked')){
|
||||
additional_feature = "rss";
|
||||
}
|
||||
|
||||
var api_key = $("#api_key").val().trim();
|
||||
if (api_key == ""){
|
||||
api_key = "";
|
||||
@ -256,8 +351,38 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
if ($('#language_de').is(':checked')){
|
||||
language = "de";
|
||||
}
|
||||
if ($('#language_zh_tw').is(':checked')){
|
||||
language = "zh_tw";
|
||||
if ($('#language_ru').is(':checked')){
|
||||
language = "ru";
|
||||
}
|
||||
if ($('#language_it').is(':checked')){
|
||||
language = "it";
|
||||
}
|
||||
if ($('#language_es').is(':checked')){
|
||||
language = "es";
|
||||
}
|
||||
if ($('#language_fr').is(':checked')){
|
||||
language = "fr";
|
||||
}
|
||||
if ($('#language_el').is(':checked')){
|
||||
language = "el";
|
||||
}
|
||||
if ($('#language_sv').is(':checked')){
|
||||
language = "sv";
|
||||
}
|
||||
if ($('#language_nl').is(':checked')){
|
||||
language = "nl";
|
||||
}
|
||||
if ($('#language_pl').is(':checked')){
|
||||
language = "pl";
|
||||
}
|
||||
if ($('#language_ua').is(':checked')){
|
||||
language = "ua";
|
||||
}
|
||||
if ($('#language_nb').is(':checked')){
|
||||
language = "nb";
|
||||
}
|
||||
if ($('#language_vi').is(':checked')){
|
||||
language = "vi";
|
||||
}
|
||||
|
||||
var units = "metric";
|
||||
@ -269,8 +394,31 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
if ($('#12_hours').is(':checked')){
|
||||
hours = "12";
|
||||
}
|
||||
//console.log(ical_urls, rss_urls,update_interval, additional_feature, api_key, location, week_starts_on, events_max_range, calibration_hours, display_colours, language, units, hours);
|
||||
createPythonSetting(ical_urls, rss_urls,update_interval, additional_feature, api_key, location, week_starts_on, events_max_range, calibration_hours, display_colours, language, units, hours);
|
||||
|
||||
var top_section = "Weather";
|
||||
if ($('#top_blank').is(':checked')){
|
||||
top_section = "";
|
||||
}
|
||||
|
||||
var middle_section = "Calendar";
|
||||
if ($('#Agenda').is(':checked')){
|
||||
middle_section = "Agenda";
|
||||
}
|
||||
if ($('#middle_blank').is(':checked')){
|
||||
middle_section = "";
|
||||
}
|
||||
|
||||
var bottom_section = "RSS";
|
||||
if ($('#Events').is(':checked')){
|
||||
bottom_section = "Events";
|
||||
}
|
||||
if ($('#bottom_blank').is(':checked')){
|
||||
bottom_section = "";
|
||||
}
|
||||
|
||||
|
||||
//console.log(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, events_max_range, calibration_hours, display_colours, language, units, hours, top_section, middle_section, bottom_section);
|
||||
createPythonSetting(ical_urls, rss_urls, update_interval, api_key, location, week_starts_on, events_max_range, calibration_hours, display_colours, language, units, hours, top_section, middle_section, bottom_section);
|
||||
}
|
||||
|
||||
function rk(content,key,value){
|
||||
@ -278,21 +426,25 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
return content.split("{" + key + "}").join(value);
|
||||
}
|
||||
|
||||
function createPythonSetting(a,b,c,d,e,f,g,h,i,j,k,l,m){
|
||||
function createPythonSetting(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){
|
||||
var box = template;
|
||||
box = rk(box,"ical_urls",a);
|
||||
box = rk(box,"rss_urls",b);
|
||||
box = rk(box,"update_interval",c);
|
||||
box = rk(box,"additional_feature",d);
|
||||
box = rk(box,"api_key",e);
|
||||
box = rk(box,"location",f);
|
||||
box = rk(box,"week_starts_on",g);
|
||||
box = rk(box,"events_max_range",h);
|
||||
box = rk(box,"calibration_hours",i);
|
||||
box = rk(box,"display_colours",j);
|
||||
box = rk(box,"language",k);
|
||||
box = rk(box,"units",l);
|
||||
box = rk(box,"hours",m);
|
||||
box = rk(box,"api_key",d);
|
||||
box = rk(box,"location",e);
|
||||
box = rk(box,"week_starts_on",f);
|
||||
box = rk(box,"events_max_range",g);
|
||||
box = rk(box,"calibration_hours",h);
|
||||
box = rk(box,"display_colours",i);
|
||||
box = rk(box,"language",j);
|
||||
box = rk(box,"units",k);
|
||||
box = rk(box,"hours",l);
|
||||
box = rk(box,"top_section",m);
|
||||
box = rk(box,"middle_section",n);
|
||||
box = rk(box,"bottom_section",o);
|
||||
|
||||
|
||||
var config = new Blob([box], {type : "text/plain"});
|
||||
var link = document.createElement('link');
|
||||
link.href = window.URL.createObjectURL(config);
|
||||
|
@ -9,18 +9,14 @@ ghosting.
|
||||
from __future__ import print_function
|
||||
import time
|
||||
from settings import display_colours
|
||||
from icon_positions_locations import black, white, red
|
||||
from image_data import black, white, red
|
||||
|
||||
def calibration():
|
||||
"""Function for Calibration"""
|
||||
if display_colours == "bwr":
|
||||
import epd7in5b
|
||||
epd = epd7in5b.EPD()
|
||||
print('_________Calibration for 3-Colour E-Paper started_________'+'\n')
|
||||
if display_colours == "bw":
|
||||
import epd7in5
|
||||
epd = epd7in5.EPD()
|
||||
print('_________Calibration for 2-Colour E-Paper started_________'+'\n')
|
||||
import e_paper_drivers
|
||||
epd = e_paper_drivers.EPD()
|
||||
print('_________Calibration for E-Paper started_________'+'\n')
|
||||
|
||||
for i in range(2):
|
||||
epd.init()
|
||||
print('Calibrating black...')
|
||||
@ -32,7 +28,7 @@ def calibration():
|
||||
epd.display_frame(epd.get_frame_buffer(white))
|
||||
epd.sleep()
|
||||
print('Cycle', str(i+1)+'/2', 'complete'+'\n')
|
||||
print('Calibration complete')
|
||||
print('Calibration complete')
|
||||
|
||||
def main():
|
||||
"""Added timer"""
|
||||
|
@ -1,183 +1,213 @@
|
||||
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 = [0x00] * int(self.width * self.height / 4)
|
||||
# Set buffer to value of Python Imaging Library image.
|
||||
# Image must be in mode L.
|
||||
image_grayscale = image.convert('L', dither=None)
|
||||
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: # black
|
||||
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
||||
elif pixels[x, y] == 76: #was 110 # convert gray to red
|
||||
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
||||
buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2)
|
||||
else: # white
|
||||
buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2)
|
||||
return buf #due to python2 -> python3, int had to be added in 'get_frame
|
||||
|
||||
def display_frame(self, frame_buffer):
|
||||
self.send_command(DATA_START_TRANSMISSION_1)
|
||||
for i in range(0, int(self.width / 4 * self.height)):
|
||||
#the above line had to be modified due to python2 -> python3
|
||||
#the issue lies in division, which returns integers in python2
|
||||
#but floats in python3
|
||||
temp1 = frame_buffer[i]
|
||||
j = 0
|
||||
while (j < 4):
|
||||
if ((temp1 & 0xC0) == 0xC0):
|
||||
temp2 = 0x03
|
||||
elif ((temp1 & 0xC0) == 0x00):
|
||||
temp2 = 0x00
|
||||
else:
|
||||
temp2 = 0x04
|
||||
temp2 = (temp2 << 4) & 0xFF
|
||||
temp1 = (temp1 << 2) & 0xFF
|
||||
j += 1
|
||||
if((temp1 & 0xC0) == 0xC0):
|
||||
temp2 |= 0x03
|
||||
elif ((temp1 & 0xC0) == 0x00):
|
||||
temp2 |= 0x00
|
||||
else:
|
||||
temp2 |= 0x04
|
||||
temp1 = (temp1 << 2) & 0xFF
|
||||
self.send_data(temp2)
|
||||
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 ###
|
||||
|
||||
import epdif
|
||||
from PIL import Image
|
||||
import RPi.GPIO as GPIO
|
||||
from settings import display_colours
|
||||
|
||||
# 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):
|
||||
if display_colours is 'bwr':
|
||||
buf = [0x00] * int(self.width * self.height / 4)
|
||||
image_grayscale = image.convert('L', dither=None)
|
||||
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: # black
|
||||
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
||||
elif pixels[x, y] == 76: # convert gray to red
|
||||
buf[int((x + y * self.width) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
||||
buf[int((x + y * self.width) / 4)] |= 0x40 >> (x % 4 * 2)
|
||||
else: # white
|
||||
buf[int((x + y * self.width) / 4)] |= 0xC0 >> (x % 4 * 2)
|
||||
return buf
|
||||
|
||||
if display_colours is 'bw':
|
||||
buf = [0x00] * int(self.width * self.height / 8)
|
||||
image_monocolor = image.convert('1')
|
||||
imwidth, imheight = image_monocolor.size
|
||||
if imwidth != self.width or imheight != self.height:
|
||||
raise ValueError('Image must be same dimensions as display \
|
||||
({0}x{1}).' .format(self.width, self.height))
|
||||
pixels = image_monocolor.load()
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] != 0:
|
||||
buf[int((x + y * self.width) / 8)] |= 0x80 >> (x % 8)
|
||||
return buf
|
||||
|
||||
|
||||
def display_frame(self, frame_buffer):
|
||||
self.send_command(DATA_START_TRANSMISSION_1)
|
||||
if display_colours is 'bwr':
|
||||
for i in range(0, int(self.width / 4 * self.height)):
|
||||
temp1 = frame_buffer[i]
|
||||
j = 0
|
||||
while (j < 4):
|
||||
if ((temp1 & 0xC0) == 0xC0):
|
||||
temp2 = 0x03
|
||||
elif ((temp1 & 0xC0) == 0x00):
|
||||
temp2 = 0x00
|
||||
else:
|
||||
temp2 = 0x04
|
||||
temp2 = (temp2 << 4) & 0xFF
|
||||
temp1 = (temp1 << 2) & 0xFF
|
||||
j += 1
|
||||
if((temp1 & 0xC0) == 0xC0):
|
||||
temp2 |= 0x03
|
||||
elif ((temp1 & 0xC0) == 0x00):
|
||||
temp2 |= 0x00
|
||||
else:
|
||||
temp2 |= 0x04
|
||||
temp1 = (temp1 << 2) & 0xFF
|
||||
self.send_data(temp2)
|
||||
j += 1
|
||||
if display_colours is 'bw':
|
||||
for i in range(0, 30720):
|
||||
temp1 = frame_buffer[i]
|
||||
j = 0
|
||||
while (j < 8):
|
||||
if(temp1 & 0x80):
|
||||
temp2 = 0x03
|
||||
else:
|
||||
temp2 = 0x00
|
||||
temp2 = (temp2 << 4) & 0xFF
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
j += 1
|
||||
if(temp1 & 0x80):
|
||||
temp2 |= 0x03
|
||||
else:
|
||||
temp2 |= 0x00
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
self.send_data(temp2)
|
||||
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)
|
@ -1,180 +0,0 @@
|
||||
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 = [0x00] * int(self.width * self.height / 8)
|
||||
# Set buffer to value of Python Imaging Library image.
|
||||
# Image must be in mode 1.
|
||||
image_monocolor = image.convert('1') #with ot withour dithering?
|
||||
imwidth, imheight = image_monocolor.size
|
||||
if imwidth != self.width or imheight != self.height:
|
||||
raise ValueError('Image must be same dimensions as display \
|
||||
({0}x{1}).' .format(self.width, self.height))
|
||||
|
||||
pixels = image_monocolor.load()
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
# Set the bits for the column of pixels at the current position.
|
||||
if pixels[x, y] != 0:
|
||||
buf[int((x + y * self.width) / 8)] |= 0x80 >> (x % 8)
|
||||
return buf
|
||||
|
||||
def display_frame(self, frame_buffer):
|
||||
self.send_command(DATA_START_TRANSMISSION_1)
|
||||
for i in range(0, 30720):
|
||||
temp1 = frame_buffer[i]
|
||||
j = 0
|
||||
while (j < 8):
|
||||
if(temp1 & 0x80):
|
||||
temp2 = 0x03
|
||||
else:
|
||||
temp2 = 0x00
|
||||
temp2 = (temp2 << 4) & 0xFF
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
j += 1
|
||||
if(temp1 & 0x80):
|
||||
temp2 |= 0x03
|
||||
else:
|
||||
temp2 |= 0x00
|
||||
temp1 = (temp1 << 1) & 0xFF
|
||||
self.send_data(temp2)
|
||||
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 ###
|
92
Calendar/fonts/NotoSans/LICENSE_OFL.txt
Normal file
@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensed.ttf
Normal file
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensedExtraLight.ttf
Normal file
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensedLight.ttf
Normal file
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensedMedium.ttf
Normal file
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensedSemiBold.ttf
Normal file
BIN
Calendar/fonts/NotoSans/NotoSans-SemiCondensedThin.ttf
Normal file
11
Calendar/fonts/NotoSans/README.txt
Normal file
@ -0,0 +1,11 @@
|
||||
This package is part of the noto project. Visit
|
||||
google.com/get/noto for more information.
|
||||
|
||||
Built on 2017-10-24 from the following noto repository:
|
||||
-----
|
||||
Repo: noto-fonts
|
||||
Tag: v2017-10-24-phase3-second-cleanup
|
||||
Date: 2017-10-24 12:10:34 GMT
|
||||
Commit: 8ef14e6c606a7a0ef3943b9ca01fd49445620d79
|
||||
|
||||
Remove some files that aren't for release.
|
@ -6,24 +6,25 @@ It also contains the positions of these icons on the E-Paper display
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
from settings import language
|
||||
im_open = Image.open
|
||||
import os
|
||||
|
||||
path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
|
||||
if path != "" and path[-1] != "/":
|
||||
path += "/"
|
||||
|
||||
path = '/home/pi/Inky-Calendar/Calendar/'
|
||||
wpath = path+'weather-icons/'
|
||||
mpath = path+'translations/'+language+'/months/'
|
||||
weekpath = path+'translations/'+language+'/week/'
|
||||
dpath = path+'days/'
|
||||
opath = path+'other/'
|
||||
fpath = path+'fonts/'
|
||||
|
||||
weekday = im_open(opath+'weekday.png')
|
||||
eventicon = im_open(opath+'event.png')
|
||||
dateicon = im_open(opath+'today.png')
|
||||
seperator = im_open(opath+'seperator.jpeg')
|
||||
seperator2 = im_open(opath+'seperator2.jpeg')
|
||||
tempicon = im_open(opath+'temperature.jpeg')
|
||||
humicon = im_open(opath+'humidity.jpeg')
|
||||
weekmon = im_open(weekpath+'week-mon.jpeg')
|
||||
weeksun = im_open(weekpath+'week-sun.jpeg')
|
||||
no_response = im_open(opath+'cloud-no-response.jpeg')
|
||||
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
|
||||
sunseticon = im_open(opath+'wi-sunset.jpeg')
|
||||
@ -42,6 +43,22 @@ windiconspace = (79, 0)
|
||||
sunriseplace = (214, 0)
|
||||
sunsetplace = (214, 35)
|
||||
|
||||
|
||||
col = 0
|
||||
agenda_view_lines = {
|
||||
'line1': (col, 75), 'line2': (col, 100),
|
||||
'line3': (col, 125), 'line4': (col, 150),
|
||||
'line5': (col, 175), 'line6': (col, 200),
|
||||
'line7': (col, 225), 'line8': (col, 250),
|
||||
'line9': (col, 275), 'line10': (col, 300),
|
||||
'line11': (col, 325), 'line12': (col, 350),
|
||||
'line13': (col, 375), 'line14': (col, 400),
|
||||
'line15': (col, 425), 'line16': (col, 450),
|
||||
'line17': (col, 475), 'line18': (col, 500),
|
||||
'line19': (col, 525), 'line20': (col, 550),
|
||||
'line21': (col, 575), 'line22': (col, 600),
|
||||
}
|
||||
|
||||
rss_places = {
|
||||
'line_1' : (0, 490), 'line_2' : (0, 515), 'line_3' : (0, 540),
|
||||
'line_4' : (0, 565), 'line_5' : (0, 590), 'line_6' : (0, 615)
|
||||
@ -105,16 +122,10 @@ positions = {
|
||||
|
||||
week_row = 134
|
||||
|
||||
weekdaysmon = {
|
||||
'Mon': (col1, week_row), 'Tue': (col2, week_row), 'Wed': (col3, week_row),
|
||||
'Thu': (col4, week_row), 'Fri': (col5, week_row), 'Sat': (col6, week_row),
|
||||
'Sun': (col7, week_row)
|
||||
}
|
||||
|
||||
weekdayssun = {
|
||||
'Sun': (col1, week_row), 'Mon': (col2, week_row), 'Tue': (col3, week_row),
|
||||
'Wed': (col4, week_row), 'Thu': (col5, week_row), 'Fri': (col6, week_row),
|
||||
'Sat': (col7, week_row)
|
||||
weekday_pos = {
|
||||
'pos0': (col1, week_row), 'pos1': (col2, week_row), 'pos2': (col3, week_row),
|
||||
'pos3': (col4, week_row), 'pos4': (col5, week_row), 'pos5': (col6, week_row),
|
||||
'pos6': (col7, week_row)
|
||||
}
|
||||
|
||||
weathericons = {
|
BIN
Calendar/other/seperator2.jpeg
Normal file
After Width: | Height: | Size: 749 B |
@ -1,11 +1,6 @@
|
||||
ical_urls = [
|
||||
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
|
||||
]
|
||||
rss_feeds = [
|
||||
"http://feeds.bbci.co.uk/news/world/rss.xml#"
|
||||
]
|
||||
ical_urls = ["https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"]
|
||||
rss_feeds = ["http://feeds.bbci.co.uk/news/world/rss.xml#"]
|
||||
update_interval = "60"
|
||||
additional_feature = "events"
|
||||
api_key = ""
|
||||
location = "Stuttgart, DE"
|
||||
week_starts_on = "Monday"
|
||||
@ -15,3 +10,6 @@ display_colours = "bwr"
|
||||
language = "en"
|
||||
units = "metric"
|
||||
hours = "24"
|
||||
top_section = "Weather"
|
||||
middle_section = "Agenda"
|
||||
bottom_section = "RSS"
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
@ -46,6 +46,8 @@ if [ "$option" = 1 ]; then
|
||||
sleep 2
|
||||
cp -r /home/pi/Inky-Calendar /home/pi/Inky-Calendar-old
|
||||
sudo rm -r /home/pi/Inky-Calendar
|
||||
mv /home/pi/Inky-Calendar-old/Calendar /home/pi/Inky-Calendar-old/Calendar-old #added due to relative path which searches for
|
||||
# the 'Calendar' folder
|
||||
echo "Updating now..."
|
||||
cd
|
||||
else
|
||||
@ -238,6 +240,7 @@ stdout_logfile = /home/pi/Inky-Calendar/E-Paper.log
|
||||
stdout_logfile_maxbytes = 1MB
|
||||
stderr_logfile = /home/pi/Inky-Calendar/E-Paper-err.log
|
||||
stderr_logfile_maxbytes = 1MB
|
||||
autorestart = true
|
||||
EOF
|
||||
|
||||
sudo service supervisor start E-Paper
|
||||
|
47
README.md
@ -15,7 +15,7 @@ A software written in python3 that allows you to transform an E-Paper display (l
|
||||
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
|
||||
|
||||
## News:
|
||||
* **Updated and optimised the installer for faster installation and updating (Mid April 2019)
|
||||
* **Updated and optimised the installer for faster installation and updating (Mid April 2019)**
|
||||
* **Added a user-friendly Web-UI for adding details to the programm** (Credit to TobyChui for the template)
|
||||
* **Version 1.5 released (Early February 2019) with a new layout, displayed events and many back-end improvements**
|
||||
|
||||
@ -25,7 +25,7 @@ This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E
|
||||
</p>
|
||||
|
||||
## Main features
|
||||
* Monthly Calendar which automatically updates itself to the current day
|
||||
* Monthly Calendar which automatically update itself to the current day
|
||||
* Fetch appointments/events from your Google Calendar and display them on the Display
|
||||
* Fetch live weather data (temperature, humidity, sunrise- & sunset time, wind speed, weather-icon) from Openweathermap servers and display them on the E-Paper
|
||||
* Fetch RSS-feeds from given RSS-feed URLs and display the content (news, quotes etc.) on the E-Paper
|
||||
@ -40,7 +40,7 @@ This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E
|
||||
* Or: Raspberry Pi Zero W. In this case, you'll need to solder 2x20 pin GPIO headers yourself
|
||||
* MicroSD card (min. 4GB)
|
||||
* MicroUSB cable (for power)
|
||||
* Something to be used as a case (e.g. a (RIBBA) picture frame or a 3D-printed case)
|
||||
* Something to be used as a case (e.g. a RIBBA photo-frame or a 3D-printed case)
|
||||
|
||||
# Setup
|
||||
## Getting the Raspberry Pi Zero W ready
|
||||
@ -56,7 +56,9 @@ This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E
|
||||
## Installing required packages for python 3.x
|
||||
Execute the following command in the Terminal to install all required packages. This will work on both, Raspbian Stretch with Desktop and Raspbian Stretch lite.
|
||||
|
||||
**`bash -c "$(curl -sL https://raw.githubusercontent.com/aceisace/Inky-Calendar/Stable/Installer-with-debug.sh)"`**
|
||||
|
||||
## Attention: this is the Installer of the master branch (development branch). It is not guaranteed to work and is only here for testing
|
||||
**`bash -c "$(curl -sL https://raw.githubusercontent.com/aceisace/Inky-Calendar/master/Installer-with-debug.sh)"`**
|
||||
|
||||
If the Installer should fail for any reason, kindly open an issue and paste the error. Thanks.
|
||||
|
||||
@ -65,33 +67,28 @@ If the Installer should fail for any reason, kindly open an issue and paste the
|
||||
<img src="https://github.com/aceisace/Inky-Calendar/blob/Stable/Gallery/installer-v1.6-part1.png" width="650"><img src="https://github.com/aceisace/Inky-Calendar/blob/Stable/Gallery/installer-v1.6-part2.png" width="650">
|
||||
|
||||
## Adding details to the programm
|
||||
There are currently 2 ways to add details to the programm, the user-friendly method and the legacy method. It's recommended to use the
|
||||
user-friendly option first. If you encounter any bugs, please use the legacy method and write a comment describing the bug.
|
||||
|
||||
1) **User-friendly method**
|
||||
To use the new WEB-UI (Web-User-Interface), simply double-click the file Settings-Web-UI.html located in /home/pi/Inky-Calendar/Calendar/ to open up the document with the browser (Chrome etc.). Next, fill in the details (you can use the table below for a reference) and click on generate to create your settings.py file. Please add your details without these signs `""`. For example, your api_key should be entered like this: `wadiln3ilioejo` and _not_ `"wadiln3ilioejo"`. Lastly, copy the generated 'settings.py' file to /home/pi/Inky-Calendar/Calendar (the same path where the settings.py file is) and try starting the main script with:
|
||||
To add details to the programm, please use the web-ui (user-interface). Simply double-click the file Settings-Web-UI.html located in /home/pi/Inky-Calendar/Calendar/ to open up the document with the browser (Chrome etc.). Next, fill in the details (you can use the table below for a reference) and click on generate to create your settings.py file. Lastly, copy the generated 'settings.py' file to /home/pi/Inky-Calendar/Calendar (the same path where the settings.py file is) and try starting the main script with:
|
||||
python3.5 /home/pi/Inky-Calendar/Calendar/E-Paper.py.
|
||||
If you encounter any issues, please leave a comment here or via email. Thanks in advance.
|
||||
|
||||
2) **Legacy method**
|
||||
Navigate to the home directory, open 'Inky-Calendar' and open the file 'settings.py' inside the Calendar folder. Adjust the values using the list below as a reference. You can edit the settings.py file by typing:
|
||||
`nano /home/pi/Inky-Calendar/Calendar/settings.py` in the Terminal.
|
||||
If you encounter any issues, please leave a comment in the issues or via email. Thanks in advance.
|
||||
|
||||
## This part will soon shift to the settings web-ui
|
||||
| Parameter | Description |
|
||||
| :---: | :---: |
|
||||
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma, for example: "ical-url1", "ical-url2"|
|
||||
| rss_feeds | Here, you can add RSS-feed URLs which are used to fetch news etc. for example: "rss-url1", "rss-url2"|
|
||||
| update_interval | How often should be Display be updated in one hour? The default option is `"60"`, which means once every 30 hour. You can select one of the values from: `"10"`, `"15"`, `"20"`, `"30"`, `"60"`. Please note that ghosting will occur when updating too frequently. To prevent ghosting, it's recommended to run 1 calibration for every 6 updates. For example, with an update interval of 1 hour, the calibration should be executed every 6 hours. |
|
||||
| additional_feature | What would you like the Display to show in the section below the Calendar? The default option is `"rss"` (for RSS-feeds. You may choose `"events`" if you want to display events instead of RSS-feeds|
|
||||
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma, for example: ical-url1, ical-url2|
|
||||
| rss_feeds | Here, you can add RSS-feed URLs which are used to fetch news etc. for example: rss-url1, rss-url2|
|
||||
| update_interval | How often should be Display be updated in one hour? The default option is `60`, which means once every hour. You can select one of the values from: `10`, `15`, `20`, `30`, `60`. Please note that ghosting will occur when updating too frequently. To prevent ghosting, it's recommended to run 1 calibration for every 6 updates. For example, with an update interval of 1 hour, the calibration should be executed every 6 hours. |
|
||||
| api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info |
|
||||
| location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/) |
|
||||
| week_starts_on | When does the week start on your Region? Possible options are `"Monday"` or `"Sunday"`|
|
||||
|events_max_range| How far in the future should events from your iCalendar be fetched. The value is given in days. By default, events in the next 60 days will be fetched from the Calendar. Can be any integer from `"1"` to `"365"`|
|
||||
| calibration_hours | At which hours would you like the Display to 'calibrate' itself? Calibration is strongly recommended at least 3 times a day. In the list, you have to include hours in 24-hour format, seperated by a comma. The default option is `[1,12,18]` and refers to 1 am, 12 am, and 6 pm |
|
||||
display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper|
|
||||
| language | Choosing the language allows changing the language of the month and week-icons. Possible options are `"en"` for english, `"de"` for german and `"zh-tw"` for Taiwan Chinese (Hong Kong Chinese)|
|
||||
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"` or `"imperial"`|
|
||||
|hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.|
|
||||
| week_starts_on | When does the week start on your Region? Possible options are `Monday` or `Sunday`|
|
||||
| events_max_range | How far in the future should events from your iCalendar be fetched. The value is given in days. By default, events in the next 60 days will be fetched from the Calendar. Can be any integer from `1` to `365`|
|
||||
| calibration_hours | At which hours would you like the Display to 'calibrate' itself? Calibration is strongly recommended at least 3 times a day. In the list, you have to include hours in 24-hour format, seperated by a comma. The default option is `1,12,18` and refers to 1 am, 12 am, and 6 pm |
|
||||
| display_colours | This should normally be set by the installer when you choose the type of your display. Options include `black-white` if you're using the black and white E-Paper or `black-white-red/yellow` when you're using the black-white-red or black-white-yellow E-Paper|
|
||||
| language | Choosing the language allows changing the language of the month and week-icons. Possible options are `en` for english, `de` for german and `zh-tw` for Taiwan Chinese (Hong Kong Chinese)|
|
||||
| units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `metric` or `imperial`|
|
||||
| hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `24` for 24-hours and `12` for 12-hours.|
|
||||
| top_section | What would you like the Display to show in the top section? Currently, only weather is available|
|
||||
| middle_section | What would you like the Display to show in the main section? The default option is `Calendar` (for RSS-feeds. You may choose `Agenda` if you want to display events instead of the Calendar |
|
||||
| bottom_section | What would you like the Display to show in the bottom section? |
|
||||
|
||||
## iCalendar
|
||||
Currently, only Google Calendar is fully supported and has proven to run more stable than others. While it is possible that a non-Google iCalendar may work, it is often not the case. If you're not using Google-Calendar and the script is throwing errors related to your iCalendar, please export your iCalendar (as an .ics file), create a new Calendar at Google Calendar and import your previous Calendar's .ics file. After importing, navigate to the section 'Integrate Calendar', copy the 'Secret address in iCal format' and paste it in the ical_urls section in the settings.py file (see instructions above).
|
||||
|
28
auto-update.sh
Normal file
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for updating the Inky-Calendar software. This will automatically
|
||||
# transfer the user's own details with the placeholders in the settings.py file
|
||||
|
||||
# To-do: Delete the old settings.py file after all operations are done
|
||||
|
||||
in="/home/pi/settings.py.old"
|
||||
out="/home/pi/Inky-Calendar/Calendar/settings.py"
|
||||
|
||||
# replace template iCalendar URLs with user-defined URLs
|
||||
sed -n -e "/^ical_urls/r $in" -i -e "/^ical_urls/d" $out
|
||||
sed -n -e "/^rss_feeds/r $in" -i -e "/^rss_feeds/d" $out
|
||||
sed -n -e "/^update_interval/r $in" -i -e "/^update_interval/d" $out
|
||||
sed -n -e "/^api_key/r $in" -i -e "/^api_key/d" $out
|
||||
sed -n -e "/^location/r $in" -i -e "/^location/d" $out
|
||||
sed -n -e "/^week_starts_on/r $in" -i -e "/^week_starts_on/d" $out
|
||||
sed -n -e "/^events_max_range/r $in" -i -e "/^events_max_range/d" $out
|
||||
sed -n -e "/^calibration_hours/r $in" -i -e "/^calibration_hours/d" $out
|
||||
sed -n -e "/^display_colours/r $in" -i -e "/^display_colours/d" $out
|
||||
sed -n -e "/^language/r $in" -i -e "/^language/d" $out
|
||||
sed -n -e "/^units/r $in" -i -e "/^units/d" $out
|
||||
sed -n -e "/^hours/r $in" -i -e "/^hours/d" $out
|
||||
sed -n -e "/^top_section/r $in" -i -e "/^top_section/d" $out
|
||||
sed -n -e "/^middle_section/r $in" -i -e "/^middle_section/d" $out
|
||||
sed -n -e "/^bottom_section/r $in" -i -e "/^bottom_section/d" $out
|
||||
|
||||
echo -e 'All operations done'
|