diff --git a/dashboard.py b/dashboard.py new file mode 100644 index 0000000..5b601c0 --- /dev/null +++ b/dashboard.py @@ -0,0 +1,139 @@ +import datetime +import json +from dashboard_api import led_out +import websocket +#from websocket import create_connection +import colorsys +import signal +import time +from threading import Thread +#for downloading messages: + + + +################################################################################ +#VAR SETUP: +create_connection = websocket.create_connection +access_token = credentials.pushbullet_token +PB_ws_url = "wss://stream.pushbullet.com/websocket/"+access_token +notification_stream = create_connection(PB_ws_url) +notification_list = [] + + +################################################################################ +#start of actual programm. +class DashBoard(object): + """docstring for DashBoard.""" + + def __init__(self): + + +def display_time(four_digit_time): + datetoshow = datetime.datetime.today().weekday() + if datetoshow==4: + if int(four_digit_time) >= 620 and int(four_digit_time) < 2130: + brightness = 1 + else: + brightness = 0 + elif datetoshow==5: + if int(four_digit_time) >= 1030 and int(four_digit_time) < 2130: + brightness = 1 + else: + brightness = 0 + elif datetoshow==6: + if int(four_digit_time) >= 1030 and int(four_digit_time) < 2115: + brightness = 1 + else: + brightness = 0 + else: + if int(four_digit_time) >= 620 and int(four_digit_time) < 2115: + brightness = 1 + else: + brightness = 0 + print(four_digit_time, datetoshow, len(notification_list), brightness) + output_led.output_time(four_digit_time, datetoshow, len(notification_list), brightness) + + +def error_file(error_digit): + try: + message_to_append = "Error "+error_digit+": "+errors.errors[error_digit] + message_to_log=str(datetime.datetime.now().strftime("%d/%m/%y - %H:%M")) + " " + message_to_append + "\n" + with open("../global_files/fatal-errors.txt","a") as f: + f.write(message_to_log) + f.close() + #file gets uploaded through cronjob + except: + display_time("9999") + + +def get_notifications(): + global notification_stream + try: + output = notification_stream.recv() + output = json.loads(output) + except: + notification_stream = create_connection(PB_ws_url) + output = notification_stream.recv() + output = json.loads(output) + + if output["type"] == "push": + if output["push"]["type"] == "mirror": + notification_title = output["push"]["title"] + notification_content = output["push"]["body"] + notification_id = output["push"]["notification_id"] + notification_type = "notification" + notification_all_content = {"title":notification_title,"content":notification_content,"id":notification_id} + elif output["push"]["type"] == "dismissal": + notification_type = "dismissal" + notification_all_content = output["push"]["notification_id"] + elif output["type"] == "nop": + notification_type = "status_test" + notification_all_content = [] + else: + with open("../global_files/logs.txt","a") as f: + f.write(str(datetime.datetime.now().strftime("%d/%m/%y - %H:%M")) + " unnown notification" + "\n") + f.close() + + return notification_type,notification_all_content + + +def run_script(): + previous_time = False + while True: + strtimerightformat = str(datetime.datetime.now().time())[0:5] + four_digit_time = strtimerightformat[0:2]+strtimerightformat[3:] + if running_var.display_notif_running == False: + display_time(four_digit_time) + time.sleep(5) + + + +################################################################################ +#Programm flow +time_thread = Thread(target=run_script) +time_thread.start() +print("UP") + +while True: + notification_type,notification_content = get_notifications() + if notification_type == "notification": + for _ in range(len(notification_list)): + if str(notification_content["id"]) in notification_list[_].values(): + del notification_list[_] + notification_list.append(notification_content) + + notification_thread = Thread(target=output_led.output_notification, args=(notification_content,)) + if running_var.display_notif_running: + error_file(8) + else: + running_var.display_notif_running = True + notification_thread.start() + + elif notification_type == "dismissal": + try: + for _ in range(len(notification_list)): + if notification_content in notification_list[_].values(): + del notification_list[_] + + except: + notification_list = [] diff --git a/api/__init__.py b/dashboard_api/__init__.py similarity index 100% rename from api/__init__.py rename to dashboard_api/__init__.py diff --git a/dashboard_api/converter.py b/dashboard_api/converter.py new file mode 100644 index 0000000..455212c --- /dev/null +++ b/dashboard_api/converter.py @@ -0,0 +1,65 @@ +from PIL import Image, ImageDraw, ImageFont +import numpy as np +import datetime +"""Two colors: 1 main color and 1 accent color. These are labeled in the matrix as 1 and 2""" + +def text_converter(text, height): + """Converts a text to a pixel-matrix + returns: np.array((16, x))""" + + font = ImageFont.truetype("verdanab.ttf", 16) + size = font.getsize(text) + img = Image.new("1",size,"black") + draw = ImageDraw.Draw(img) + draw.text((0, 0), text, "white", font=font) + pixels = np.array(img, dtype=np.uint8) + return pixels + + +digits = { + 1 : [[0,1,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1]], + 2 : [[1,1,1],[0,0,1],[1,1,1],[1,0,0],[1,1,1]], + 3 : [[1,1,1],[0,0,1],[1,1,1],[0,0,1],[1,1,1]], + 4 : [[1,0,1],[1,0,1],[1,1,1],[0,0,1],[0,0,1]], + 5 : [[1,1,1],[1,0,0],[1,1,1],[0,0,1],[1,1,1]], + 6 : [[1,1,1],[1,0,0],[1,1,1],[1,0,1],[1,1,1]], + 7 : [[1,1,1],[0,0,1],[0,0,1],[0,0,1],[0,0,1]], + 8 : [[1,1,1],[1,0,1],[1,1,1],[1,0,1],[1,1,1]], + 9 : [[1,1,1],[1,0,1],[1,1,1],[0,0,1],[1,1,1]], + 0 : [[1,1,1],[1,0,1],[1,0,1],[1,0,1],[1,1,1]] +} + +##place of numbers, invariable +digit_position = [[3,1], [3,9], [9,1], [9,9]] + +def time_converter(): + """Converts 4-digit time to a pixel-matrix + returns: np.array((16, 16))""" + time = datetime.datetime.now().strftime("%H%M") + pixels = np.zeros((16,16),dtype=np.uint8) + time = "0" * (4 - len(str(time))) + str(time) + time_split = [int(i) for i in time] + + for i in range(4): + x = digit_position[i][0] + y = digit_position[i][1] + number = digits[time_split[i]] + pixels[x: x + 5, y: y + 3] = np.array(number) + + + return pixels + + +days = np.append(np.zeros((15,16)), np.array([0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,1])).reshape((16,16)) + +def date_converter(): + today = datetime.datetime.today() + weekday = datetime.datetime.weekday(today) + # size of the reduced array according to weekday + size = [2,4,6,8,10,13,16] + + pixels = days #base color background + lrow = np.append(pixels[15,:size[weekday]], [0 for i in range(16 - size[weekday])]) + lrow = np.append(np.zeros((15,16)), lrow).reshape((16,16)) + pixels += lrow + return pixels diff --git a/dashboard_api/hat/__init__.py b/dashboard_api/hat/__init__.py new file mode 100644 index 0000000..dcf2c80 --- /dev/null +++ b/dashboard_api/hat/__init__.py @@ -0,0 +1 @@ +# Placeholder diff --git a/dashboard_api/hat/sim.py b/dashboard_api/hat/sim.py new file mode 100644 index 0000000..65ff3d2 --- /dev/null +++ b/dashboard_api/hat/sim.py @@ -0,0 +1,115 @@ +import sys +import colorsys +import pygame.gfxdraw +import time +import pygame + +class UnicornHat(object): + def __init__(self, width, height, rotation_offset = 0): + # Compat with old library + + # Set some defaults + self.rotation_offset = rotation_offset + self.rotation(0) + self.pixels = [(0, 0, 0)] * width * height + self.pixel_size = 25 + self.height = height + self.width = width + self.window_width = width * self.pixel_size + self.window_height = height * self.pixel_size + + # Init pygame and off we go + pygame.init() + pygame.display.set_caption("Unicorn HAT simulator") + self.screen = pygame.display.set_mode([self.window_width, self.window_height]) + self.clear() + + def set_pixel(self, x, y, r, g, b): + i = (x * self.width) + y + self.pixels[i] = [int(r), int(g), int(b)] + + def draw(self): + for event in pygame.event.get(): # User did something + if event.type == pygame.QUIT: + print("Exiting...") + sys.exit() + + for x in range(self.width): + for y in range(self.height): + self.draw_led(x, y) + + def show(self): + self.clear() + self.draw() + pygame.display.flip() + #time.sleep(5) + + def draw_led(self, x, y): + p = self.pixel_size + w_x = int(x * p + p / 2) + w_y = int((self.height - 1 - y) * p + p / 2) + r = int(p / 4) + color = self.pixels[self.index(x, y)] + pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color) + pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color) + + def get_shape(self): + return (self.width, self.height) + + def brightness(self, *args): + pass + + def rotation(self, r): + self._rotation = int(round(r/90.0)) % 3 + + def clear(self): + self.screen.fill((0, 0, 0)) + + def get_rotation(self): + return self._rotation * 90 + + def set_layout(self, *args): + pass + + def set_pixel_hsv(self, x, y, h, s=1.0, v=1.0): + r, g, b = [int(n*255) for n in colorsys.hsv_to_rgb(h, s, v)] + self.set_pixel(x, y, r, g, b) + + def off(self): + print("Closing window") + #pygame.quit() + + def index(self, x, y): + # Offset to match device rotation + rot = (self.get_rotation() + self.rotation_offset) % 360 + + if rot == 0: + xx = x + yy = y + elif rot == 90: + xx = self.height - 1 - y + yy = x + elif rot == 180: + xx = self.width - 1 - x + yy = self.height - 1 - y + elif rot == 270: + xx = y + yy = self.width - 1 - x + return (xx * self.width) + yy + + +""" +# SD hats works as expected +#unicornhat = UnicornHatSim(8,8) +#unicornphat = UnicornHatSim(8, 4) + +# Unicornhat HD seems to be the other way around (not that there's anything wrong with that), so we rotate it 180° +# unicornhathd = UnicornHatSim(16, 16, 180) +twohats = UnicornHatSim(16, 32, 180) + + +for i in range(16): + twohats.set_pixel(i,i, 200,200,200) + twohats.show() + time.sleep(1) +""" diff --git a/dashboard_api/hat/unicorn.py b/dashboard_api/hat/unicorn.py new file mode 100644 index 0000000..52ee423 --- /dev/null +++ b/dashboard_api/hat/unicorn.py @@ -0,0 +1,140 @@ +import colorsys +import time +import numpy + +import RPi.GPIO as GPIO + + +class UnicornHat(object): + def __init__(self, width, height, rotation_offset = 0): + self.PIN_CLK = 11 + ################################## + self.PINS_DAT = [10, 22] + ################################## + self.PIN_CS = 8 + + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(self.PIN_CS, GPIO.OUT, initial=GPIO.HIGH) + GPIO.setup(self.PIN_CLK, GPIO.OUT, initial=GPIO.LOW) + GPIO.setup(self.PINS_DAT, GPIO.OUT, initial=GPIO.LOW) + + self.SOF = 0x72 + self.DELAY = 1.0/120 + self.WIDTH = width #32 + self.HEIGHT = height #16 + + self.rotation = 1 + self.brightness = 0.5 + self.buffer = numpy.zeros((self.WIDTH,self.HEIGHT,3), dtype=int) + + + + def spi_write(self, buf1, buf2): + GPIO.output(self.PIN_CS, GPIO.LOW) + + self.spi_write_byte(self.SOF, self.SOF) + + for x in range(len(buf1)): + b1, b2= buf1[x], buf2[x] + self.spi_write_byte(b1, b2) + time.sleep(0.0000001) + + GPIO.output(self.PIN_CS, GPIO.HIGH) + + def spi_write_byte(self, b1, b2): + for x in range(8): + GPIO.output(self.PINS_DAT[0], b1 & 0b10000000) + GPIO.output(self.PINS_DAT[1], b2 & 0b10000000) + GPIO.output(self.PIN_CLK, GPIO.HIGH) + b1 <<= 1 + b2 <<= 1 + time.sleep(0.00000001) + GPIO.output(self.PIN_CLK, GPIO.LOW) + + def brightness(self, b): + """Set the display brightness between 0.0 and 1.0. + :param b: Brightness from 0.0 to 1.0 (default 0.5) + """ + self.brightness = b + + def rotation(self, r): + """Set the display rotation in degrees. + Actual rotation will be snapped to the nearest 90 degrees. + """ + self.rotation = int(round(r/90.0)) + + def get_rotation(self): + """Returns the display rotation in degrees.""" + return self.rotation * 90 + + def set_layout(self, pixel_map=None): + """Does nothing, for library compatibility with Unicorn HAT.""" + pass + + def set_all(self, r, g, b): + self.buffer[:] = r, g, b + + def set_pixel(self, x, y, r, g, b): + """Set a single pixel to RGB colour. + :param x: Horizontal position from 0 to 15 + :param y: Veritcal position from 0 to 15 + :param r: Amount of red from 0 to 255 + :param g: Amount of green from 0 to 255 + :param b: Amount of blue from 0 to 255 + """ + self.buffer[x][y] = r, g, b + + def set_pixel_hsv(self, x, y, h, s=1.0, v=1.0): + """set a single pixel to a colour using HSV. + :param x: Horizontal position from 0 to 15 + :param y: Veritcal position from 0 to 15 + :param h: Hue from 0.0 to 1.0 ( IE: degrees around hue wheel/360.0 ) + :param s: Saturation from 0.0 to 1.0 + :param v: Value (also known as brightness) from 0.0 to 1.0 + """ + + r, g, b = [int(n*255) for n in colorsys.hsv_to_rgb(h, s, v)] + self.set_pixel(x, y, r, g, b) + + def get_pixel(self, x, y): + return tuple(self.buffer[x][y]) + + def shade_pixels(self, shader): + for x in range(self.WIDTH): + for y in range(self.HEIGHT): + r, g, b = shader(x, y) + self.set_pixel(x, y, r, g, b) + + def get_pixels(self): + return self.buffer + + def get_shape(self): + """Return the shape (width, height) of the display.""" + + return self.WIDTH, self.HEIGHT + + def clear(self): + """Clear the buffer.""" + self.buffer.fill(0) + + def off(self): + """Clear the buffer and immediately update Unicorn HAT HD. + Turns off all pixels. + """ + self.clear() + self.show() + + def show(self): + """Output the contents of the buffer to Unicorn HAT HD.""" + ########################################################## + ## Change to desire + _buf1 = numpy.rot90(self.buffer[:16,:self.HEIGHT],2) + _buf2 = numpy.rot90(self.buffer[16:32,:self.HEIGHT],2) + ########################################################## + + _buf1, _buf2 = _buf1, _buf2 = [(x.reshape(768) * self.brightness).astype(numpy.uint8).tolist() for x in (_buf1, _buf2)] + + self.spi_write(_buf1, _buf2) + + time.sleep(self.DELAY) diff --git a/dashboard_api/led_out.py b/dashboard_api/led_out.py new file mode 100644 index 0000000..2dbff1f --- /dev/null +++ b/dashboard_api/led_out.py @@ -0,0 +1,78 @@ +import time +import numpy as np +from threading import Thread + +import converter +try: + from hat import unicorn as HAT +except ImportError: + print("Using the simulator") + from hat import sim as HAT + + + +class OutputHandler(): + def __init__(self, width, height): + """width is presumed to be larger than height""" + self.width = width + self.height = height + self.output = HAT.UnicornHat(width, height) + self.threads = [] + self.running = False + self.primary = [200, 200, 200] + self.secondary = [10, 200, 10] + self.red = [200, 10, 10] + + + + def stop(self): + for t in threads: + t.stop() + self.output.off() + + + def run(self, func, args): + self.running = True + t = Thread(target=func,args=tuple(args)) + t.start() + self.threads.append(t) + + + def set_matrix(self, matrix): + """assumes 1 for primary, 2 for secondary color""" + + for x in range(matrix.shape[0]): + for y in range(matrix.shape[1]): + if matrix[x,y] == 1: + self.output.set_pixel(x,y,self.primary[0],self.primary[1],self.primary[2]) + elif matrix[x,y] == 2: + self.output.set_pixel(x,y,self.secondary[0],self.secondary[1],self.secondary[2]) + self.output.show() + + def clock_face(self): + hour = converter.time_converter() + day = converter.date_converter() + + self.set_matrix(hour + day) + self.running = False + + + def text_scroll(self, text, speed): + pixels = converter.text_converter(text,16) + sleep_time = 1 / speed + frames = pixels.shape[1] - 16 + if frames <= 0: + frames = 1 + for i in range(frames): + #self.output.clear() + #self.set_matrix(np.zeros((16,16))) + self.set_matrix(pixels[:,i:16+i]) + time.sleep(sleep_time) + + + self.clock_face() + + +test = OutputHandler(16,32) +test.clock_face() +test.text_scroll("Hello world. How are you?",4) diff --git a/main.py b/main.py index 407a221..e9b35da 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from api import telegram, google, reddit, weather +from web_api import telegram, google, reddit, weather from persistence import rw as pvars import requests diff --git a/web_api/__init__.py b/web_api/__init__.py new file mode 100644 index 0000000..dcf2c80 --- /dev/null +++ b/web_api/__init__.py @@ -0,0 +1 @@ +# Placeholder diff --git a/api/google.py b/web_api/google.py similarity index 100% rename from api/google.py rename to web_api/google.py diff --git a/api/reddit.py b/web_api/reddit.py similarity index 100% rename from api/reddit.py rename to web_api/reddit.py diff --git a/api/telegram.py b/web_api/telegram.py similarity index 100% rename from api/telegram.py rename to web_api/telegram.py diff --git a/api/weather.py b/web_api/weather.py similarity index 100% rename from api/weather.py rename to web_api/weather.py