Better LED-interface (to be tested)
This commit is contained in:
parent
b2007f2111
commit
c61ee3ea72
@ -103,15 +103,16 @@ class Clock(BotFunc):
|
||||
self.clock.set_brightness(value=1)
|
||||
start_color = numpy.array([153, 0, 51])
|
||||
end_color = numpy.array([255, 255, 0])
|
||||
empty = numpy.zeros((16,32))
|
||||
ones = empty
|
||||
ones[ones == 0] = 1
|
||||
col_show = numpy.zeros((*self.clock.shape, 3))
|
||||
col_show[:,:,...] = start_color
|
||||
|
||||
gradient = end_color - start_color
|
||||
# 20 steps should be fine => sleep_time = duration / 20
|
||||
for i in range(20):
|
||||
ct = i/20 * gradient
|
||||
col = [int(x) for x in ct+start_color]
|
||||
self.clock.IO.set_matrix(ones,colors=[col])
|
||||
col_show[:,:,...] = [int(x) for x in ct+start_color]
|
||||
self.clock.IO.array = col_show
|
||||
self.clock.IO.SHOW()
|
||||
time.sleep(int(duration) / 20)
|
||||
|
||||
self.clock.run(output,(duration,))
|
||||
@ -123,17 +124,19 @@ class Clock(BotFunc):
|
||||
frequency = update.message.text
|
||||
|
||||
def output(duration, frequency):
|
||||
self.set_brightness(value=1)
|
||||
self.clock.set_brightness(value=1)
|
||||
duration = int(duration)
|
||||
frequency = int(frequency)
|
||||
n = duration * frequency / 2
|
||||
empty = numpy.zeros((16,32))
|
||||
empty = numpy.zeros((*self.clock.shape,3))
|
||||
red = empty.copy()
|
||||
red[red == 0] = 3
|
||||
red[...,0] = 255
|
||||
for i in range(int(n)):
|
||||
self.IO.set_matrix(red)
|
||||
self.clock.IO.array = red
|
||||
self.clock.IO.SHOW()
|
||||
time.sleep(1/frequency)
|
||||
self.IO.set_matrix(empty)
|
||||
self.clock.IO.array = empty
|
||||
self.clock.IO.SHOW()
|
||||
time.sleep(1/frequency)
|
||||
|
||||
if not(duration == 0 or frequency == 0):
|
||||
@ -151,8 +154,8 @@ class Clock(BotFunc):
|
||||
id = img.file_id
|
||||
|
||||
file = bot.getFile(id).download_as_bytearray()
|
||||
width = self.clock.IO.width
|
||||
height = self.clock.IO.height
|
||||
width = self.clock.shape[1]
|
||||
height = self.clock.shape[0]
|
||||
|
||||
img = Image.open(io.BytesIO(file))
|
||||
im_height = img.height
|
||||
@ -166,7 +169,8 @@ class Clock(BotFunc):
|
||||
a = numpy.asarray(t)
|
||||
|
||||
def output(image, duration):
|
||||
self.clock.IO.set_matrix(image)
|
||||
self.clock.IO.array = image
|
||||
self.clock.IO.SHOW()
|
||||
time.sleep(int(duration) * 60)
|
||||
|
||||
self.clock.run(output,(a, duration))
|
||||
@ -176,7 +180,7 @@ class Clock(BotFunc):
|
||||
def exec_show_message(self, update: Update, context: CallbackContext) -> None:
|
||||
message_str = update.message.text
|
||||
update.message.reply_text("Now showing: " + message_str)
|
||||
self.clock.run(self.clock.IO.text_scroll,(message_str, self.clock.tspeed, [200,200,200]))
|
||||
self.clock.run(self.clock.text_scroll,(message_str,))
|
||||
return ConversationHandler.END
|
||||
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
# Placeholder
|
||||
from . import led
|
@ -1,130 +0,0 @@
|
||||
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", height)
|
||||
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,0,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]],
|
||||
"-" : [[0,0,0],[0,0,0],[1,1,1],[0,0,0],[0,0,0]],
|
||||
"-1" : [[0,0,1],[0,0,1],[1,1,1],[0,0,1],[0,0,1]],
|
||||
"error" : [[1,0,1],[1,0,1],[0,1,0],[1,0,1],[1,0,1]],
|
||||
}
|
||||
|
||||
##place of numbers, invariant
|
||||
digit_position = [[2,4], [2,10], [9,4], [9,10]]
|
||||
|
||||
def time_converter(top="", bottom=""):
|
||||
"""Converts 4-digit time to a pixel-matrix
|
||||
returns: np.array((16, 16))"""
|
||||
|
||||
pixels = np.zeros((16,16),dtype=np.uint8)
|
||||
|
||||
if bottom == "" or top == "":
|
||||
top = datetime.datetime.now().strftime("%H")
|
||||
bottom = datetime.datetime.now().strftime("%M")
|
||||
|
||||
if len(top) < 2:
|
||||
top = "0" * (2 - len(top)) + top
|
||||
if len(bottom) < 2:
|
||||
bottom = "0" * (2 - len(bottom)) + bottom
|
||||
|
||||
if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2):
|
||||
time_split = 4*["-"]
|
||||
elif "error" in top and "error" in bottom:
|
||||
time_split = 4*["error"]
|
||||
else:
|
||||
time_split = [i for i in top] + [i for i in bottom]
|
||||
|
||||
if "-1" in top and len(top) != 2:
|
||||
time_split = ["-1", top[-1]] + [i for i in bottom]
|
||||
if "-1" in bottom and len(bottom) != 2:
|
||||
time_split = [i for i in top] + ["-1", bottom[-1]]
|
||||
|
||||
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.copy() #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
|
||||
|
||||
|
||||
weather_categories = {
|
||||
"Clouds": "cloud",
|
||||
"Rain": "rain and cloud",
|
||||
"Thunderstorm": "thunder and cloud",
|
||||
"Drizzle": "rain and cloud",
|
||||
"Snow": "snow and cloud",
|
||||
"Clear": "sun",
|
||||
"Mist": "fog and clouds",
|
||||
"Smoke": "Smoke",
|
||||
"Haze": "Haze",
|
||||
"Dust": "Dust",
|
||||
"Fog": "fog",
|
||||
"Sand": "Sand",
|
||||
"Dust": "Dust",
|
||||
"Ash": "Ash",
|
||||
"Squal": "Squal",
|
||||
"Tornado": "Tornado",
|
||||
"error" : "moon"
|
||||
}
|
||||
|
||||
def weather_converter(name):
|
||||
result = np.zeros((16,16))
|
||||
cwd = __file__.replace("\\","/") # for windows
|
||||
cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is)
|
||||
if len(cwd) == 0:
|
||||
cwd = "."
|
||||
icon_spritesheet = cwd + "/weather-icons.bmp"
|
||||
|
||||
icons = Image.open(icon_spritesheet)
|
||||
icons_full = np.array(icons)
|
||||
|
||||
icon_loc = ["sun","moon","sun and clouds", "moon and clouds", "cloud","fog and clouds","2 clouds", "3 clouds", "rain and cloud", "rain and clouds", "rain and cloud and sun", "rain and cloud and moon", "thunder and cloud", "thunder and cloud and moon", "snow and cloud", "snow and cloud and moon", "fog","fog night"]
|
||||
#ordered 1 2 \n 3 4 \ 5 5 ...
|
||||
name = weather_categories[name]
|
||||
try:
|
||||
iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2
|
||||
# x and y coords
|
||||
except:
|
||||
return np.zeros((16,16,3))
|
||||
|
||||
icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...]
|
||||
return icon_single
|
@ -1 +0,0 @@
|
||||
# Placeholder
|
@ -1,93 +0,0 @@
|
||||
import sys
|
||||
import colorsys
|
||||
import pygame.gfxdraw
|
||||
import time
|
||||
import pygame
|
||||
import numpy
|
||||
|
||||
class UnicornHat(object):
|
||||
def __init__(self, width, height):
|
||||
# Compat with old library
|
||||
|
||||
# Set some defaults
|
||||
self.rotation(0)
|
||||
self.pixel_size = 20
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.pixels = numpy.zeros((self.height,self.width,3), dtype=int)
|
||||
|
||||
self.window_width = self.width * self.pixel_size
|
||||
self.window_height = self.height * self.pixel_size
|
||||
|
||||
self.brightness = 1
|
||||
# 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):
|
||||
self.pixels[x][y] = r, g, b
|
||||
|
||||
|
||||
def set_matrix(self, matrix):
|
||||
self.pixels = matrix
|
||||
self.show()
|
||||
|
||||
|
||||
def draw(self):
|
||||
for event in pygame.event.get(): # User did something
|
||||
if event.type == pygame.QUIT:
|
||||
print("Exiting...")
|
||||
sys.exit()
|
||||
|
||||
for i in range(self.height):
|
||||
for j in range(self.width):
|
||||
self.draw_led(i,j)
|
||||
|
||||
|
||||
def draw_led(self,i, j):
|
||||
p = self.pixel_size
|
||||
w_x = int(j * p + p / 2)
|
||||
#w_y = int((self.height - 1 - y) * p + p / 2)
|
||||
w_y = int(i * p + p / 2)
|
||||
r = int(p / 4)
|
||||
color = self.pixels[i,j,:]*self.brightness
|
||||
color = color.astype("int")
|
||||
|
||||
pygame.gfxdraw.aacircle(self.screen, w_x, w_y, r, color)
|
||||
pygame.gfxdraw.filled_circle(self.screen, w_x, w_y, r, color)
|
||||
|
||||
|
||||
def show(self):
|
||||
self.clear()
|
||||
self.draw()
|
||||
pygame.display.flip()
|
||||
pygame.event.pump()
|
||||
#time.sleep(5)
|
||||
|
||||
|
||||
def get_shape(self):
|
||||
return (self.width, self.height)
|
||||
|
||||
|
||||
def set_brightness(self, brightness):
|
||||
self.brightness = brightness
|
||||
|
||||
|
||||
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 off(self):
|
||||
print("Closing window")
|
||||
pygame.quit()
|
@ -1,13 +0,0 @@
|
||||
import unicorn
|
||||
|
||||
led = unicorn.UnicornHat(32,16)
|
||||
r = 0
|
||||
b = 0
|
||||
for i in range(16*32):
|
||||
x = i % 32
|
||||
y = i // 32
|
||||
r += (x % 16 == 0)*5
|
||||
b+= (y % 16 == 0)*5
|
||||
led.set_pixel(x,y, r, 200, b)
|
||||
if i%2 == 0:
|
||||
led.show()
|
@ -1,151 +0,0 @@
|
||||
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
|
||||
##################################
|
||||
# GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
|
||||
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 = 1
|
||||
self.buffer = numpy.zeros((self.HEIGHT,self.WIDTH,3), dtype=int)
|
||||
|
||||
self.reset_clock()
|
||||
|
||||
|
||||
def reset_clock(self):
|
||||
GPIO.output(self.PIN_CS, GPIO.LOW)
|
||||
time.sleep(0.00001)
|
||||
GPIO.output(self.PIN_CS, GPIO.HIGH)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 set_brightness(self, b):
|
||||
"""Set the display brightness between 0.0 and 1.0.
|
||||
:param b: Brightness from 0.0 to 1.0 (default 1)
|
||||
"""
|
||||
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_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 width
|
||||
:param y: Vertical position from 0 to height
|
||||
: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[y][x] = r, g, b
|
||||
|
||||
def set_matrix(self, matrix):
|
||||
"""Sets a height x width matrix directly"""
|
||||
self.reset_clock()
|
||||
self.buffer = matrix
|
||||
self.show()
|
||||
|
||||
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
|
||||
buff2 = numpy.rot90(self.buffer[:self.HEIGHT,:16],3)
|
||||
buff1 = numpy.rot90(self.buffer[:self.HEIGHT,16:32],1)
|
||||
##########################################################
|
||||
|
||||
buff1, buff2 = [(x.reshape(768) * self.brightness).astype(numpy.uint8).tolist() for x in (buff1, buff2)]
|
||||
|
||||
self.spi_write(buff1, buff2)
|
||||
|
||||
time.sleep(self.DELAY)
|
175
clock/api/led.py
175
clock/api/led.py
@ -1,122 +1,91 @@
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
from clock.api import converter
|
||||
|
||||
|
||||
try:
|
||||
from clock.api.hat import unicorn as HAT
|
||||
from . import unicorn
|
||||
output = unicorn.ClockOut
|
||||
except ImportError:
|
||||
print("Using the simulator")
|
||||
from clock.api.hat import sim as HAT
|
||||
|
||||
from . import sim
|
||||
output = sim.ClockOut
|
||||
|
||||
# import sim
|
||||
# output = sim.ClockOut
|
||||
|
||||
class OutputHandler():
|
||||
def __init__(self, width, height, primary = [200, 200, 200], secondary = [10, 200, 10], error = [200, 10, 10]):
|
||||
"""width is presumed to be larger than height"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.output = HAT.UnicornHat(width, height)
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
self.red = error
|
||||
"""Matrix of led-points (RGB- values). It has the two given dimensions + a third which is given by the color values"""
|
||||
|
||||
# def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
|
||||
# strides=None, order=None, info=None):
|
||||
# # Create the ndarray instance of our type, given the usual
|
||||
# # ndarray input arguments. This will call the standard
|
||||
# # ndarray constructor, but return an object of our type.
|
||||
# # It also triggers a call to InfoArray.__array_finalize__
|
||||
|
||||
# # expand the given tuple (flat display) to a 3d array containing the colors as well
|
||||
# nshape = (*shape, 3)
|
||||
# obj = super(OutputHandler, subtype).__new__(subtype, nshape, "int",
|
||||
# buffer, offset, strides,
|
||||
# order)
|
||||
# # set the new 'info' attribute to the value passed
|
||||
# obj.info = info
|
||||
# obj.OUT = output(shape)
|
||||
# # Finally, we must return the newly created object:
|
||||
# return obj
|
||||
def __init__(self, shape):
|
||||
nshape = (*shape, 3)
|
||||
self.array = np.array(shape, dtype=np.uint8)
|
||||
self.OUT = output(shape)
|
||||
|
||||
|
||||
# def __array_finalize__(self, obj):
|
||||
# self.OUT = sim.ClockOut()
|
||||
# # ``self`` is a new object resulting from
|
||||
# # ndarray.__new__(), therefore it only has
|
||||
# # attributes that the ndarray.__new__ constructor gave it -
|
||||
# # i.e. those of a standard ndarray.
|
||||
# #
|
||||
# # We could have got to the ndarray.__new__ call in 3 ways:
|
||||
# # From an explicit constructor - e.g. InfoArray():
|
||||
# # obj is None
|
||||
# # (we're in the middle of the InfoArray.__new__
|
||||
# # constructor, and self.info will be set when we return to
|
||||
# # InfoArray.__new__)
|
||||
# if obj is None: return
|
||||
# # From view casting - e.g arr.view(InfoArray):
|
||||
# # obj is arr
|
||||
# # (type(obj) can be InfoArray)
|
||||
# # From new-from-template - e.g infoarr[:3]
|
||||
# # type(obj) is InfoArray
|
||||
# #
|
||||
# # Note that it is here, rather than in the __new__ method,
|
||||
# # that we set the default value for 'info', because this
|
||||
# # method sees all creation of default objects - with the
|
||||
# # InfoArray.__new__ constructor, but also with
|
||||
# # arr.view(InfoArray).
|
||||
# self.info = getattr(obj, 'info', None)
|
||||
|
||||
# # We do not need to return anything
|
||||
|
||||
|
||||
|
||||
def set_matrix(self, matrix, quadrant = 1, colors = []):
|
||||
"""assumes 1 for primary, 2 for secondary color (everything beyond is treated as an error)
|
||||
quadrant: 1,2,3,4 : 4|1
|
||||
___
|
||||
3|2
|
||||
"""
|
||||
def SHOW(self):
|
||||
# self.output.set_matrix(self)
|
||||
|
||||
# reshape to the main size: (eg 32x16) (always aligns the given matrix on top left.)
|
||||
|
||||
if len(matrix.shape) != 3:
|
||||
# add depth (rgb values)
|
||||
matrix = self.matrix_add_depth(matrix,colors)
|
||||
self.set_matrix_rgb(matrix,quadrant)
|
||||
self.OUT.put(self.array)
|
||||
|
||||
|
||||
def matrix_add_depth(self, matrix, colors = []):
|
||||
"""transforms a 2d-array with 0,1,2 to a 3d-array with the rgb values for primary and secondary color"""
|
||||
|
||||
c1 = self.primary
|
||||
c2 = self.secondary
|
||||
c3 = self.red
|
||||
if len(colors) > 0:
|
||||
c1 = colors[0]
|
||||
if len(colors) > 1:
|
||||
c2 = colors[1]
|
||||
if len(colors) > 2:
|
||||
c3 = colors[2]
|
||||
if len(colors) > 3:
|
||||
print("Too many colors")
|
||||
# def __init__(self, width, height, primary = [200, 200, 200], secondary = [10, 200, 10], error = [200, 10, 10]):
|
||||
# """width is presumed to be larger than height"""
|
||||
# self.width = width
|
||||
# self.height = height
|
||||
# self.output = HAT.UnicornHat(width, height)
|
||||
# self.primary = primary
|
||||
# self.secondary = secondary
|
||||
# self.red = error
|
||||
|
||||
|
||||
r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int)
|
||||
for i in range(matrix.shape[0]):
|
||||
for j in range(matrix.shape[1]):
|
||||
t = int(matrix[i, j])
|
||||
if t == 0:
|
||||
r3[i, j, :] = [0,0,0]
|
||||
elif t == 1:
|
||||
r3[i, j, :] = c1
|
||||
elif t == 2:
|
||||
r3[i, j, :] = c2
|
||||
else:
|
||||
r3[i, j, :] = c3
|
||||
return r3
|
||||
|
||||
|
||||
def set_matrix_rgb(self, matrix, quadrant=1):
|
||||
result = np.zeros((self.height, self.width,3))
|
||||
if quadrant == 1:
|
||||
result[:matrix.shape[0], self.width-matrix.shape[1]:,...] = matrix
|
||||
elif quadrant == 2:
|
||||
result[self.height-matrix.shape[0]:, self.width-matrix.shape[1]:,...] = matrix
|
||||
elif quadrant == 3:
|
||||
result[self.height-matrix.shape[0]:, :matrix.shape[1],...] = matrix
|
||||
else: # 4 or more
|
||||
result[:matrix.shape[0], :matrix.shape[1],...] = matrix
|
||||
|
||||
self.output.set_matrix(result)
|
||||
|
||||
|
||||
def clock_face(self, weather):
|
||||
"""weather as a dict"""
|
||||
hour = converter.time_converter()
|
||||
day = converter.date_converter()
|
||||
face1 = hour + day
|
||||
face1_3d = self.matrix_add_depth(face1)
|
||||
|
||||
if weather["show"] == "weather":
|
||||
face2_3d = converter.weather_converter(weather["weather"])
|
||||
else:
|
||||
face2 = converter.time_converter(top=str(weather["low"]), bottom=str(weather["high"]))
|
||||
face2 = np.concatenate((face2[:8,...],2*face2[8:,...]))
|
||||
face2_3d = self.matrix_add_depth(face2,[[0, 102, 255],[255, 102, 0]])
|
||||
|
||||
face = np.zeros((max(face1_3d.shape[0],face2_3d.shape[0]),face1_3d.shape[1]+face2_3d.shape[1],3))
|
||||
|
||||
face[:face1_3d.shape[0],:face1_3d.shape[1],...] = face1_3d
|
||||
face[:face2_3d.shape[0],face1_3d.shape[1]:,...] = face2_3d
|
||||
self.set_matrix_rgb(face)
|
||||
|
||||
|
||||
def text_scroll(self, text, speed, color):
|
||||
pixels = converter.text_converter(text, 12)
|
||||
sleep_time = 1 / speed
|
||||
if color == "":
|
||||
colors = []
|
||||
else:
|
||||
colors = [color]
|
||||
|
||||
frames = pixels.shape[1] - self.width
|
||||
if frames <= 0:
|
||||
frames = 1
|
||||
|
||||
for i in range(frames):
|
||||
visible = pixels[:,i:self.width+i]
|
||||
self.set_matrix(visible,4,colors)
|
||||
time.sleep(sleep_time)
|
||||
time.sleep(10 * sleep_time)
|
||||
|
27
clock/api/sim.py
Normal file
27
clock/api/sim.py
Normal file
@ -0,0 +1,27 @@
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
mpl.rcParams['toolbar'] = 'None'
|
||||
mpl.use('WX')
|
||||
|
||||
|
||||
|
||||
|
||||
class ClockOut():
|
||||
"""Simulate a clock output on a computer screen"""
|
||||
def __init__(self, shape):
|
||||
plt.axis('off')
|
||||
plt.ion()
|
||||
nshape = (*shape, 3)
|
||||
zero = np.zeros(nshape)
|
||||
self.figure, ax = plt.subplots()
|
||||
ax.set_axis_off()
|
||||
i = Image.fromarray(zero, "RGB")
|
||||
self.canvas = ax.imshow(i)
|
||||
|
||||
def put(self, matrix):
|
||||
matrix_rescale = matrix / 255
|
||||
self.canvas.set_array(matrix_rescale)
|
||||
self.figure.canvas.draw()
|
||||
self.figure.canvas.flush_events()
|
89
clock/api/unicorn.py
Normal file
89
clock/api/unicorn.py
Normal file
@ -0,0 +1,89 @@
|
||||
import colorsys
|
||||
import time
|
||||
import numpy
|
||||
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
|
||||
class ClockOut(object):
|
||||
def __init__(self, shape):
|
||||
self.PIN_CLK = 11
|
||||
##################################
|
||||
# GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
|
||||
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.HEIGHT = shape[0] #16
|
||||
self.WIDTH = shape[1] #32
|
||||
|
||||
self.reset_clock()
|
||||
|
||||
|
||||
def reset_clock(self):
|
||||
GPIO.output(self.PIN_CS, GPIO.LOW)
|
||||
time.sleep(0.00001)
|
||||
GPIO.output(self.PIN_CS, GPIO.HIGH)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 put(self, matrix):
|
||||
"""Sets a height x width matrix directly"""
|
||||
self.reset_clock()
|
||||
self.show(matrix)
|
||||
|
||||
|
||||
def clear(self):
|
||||
"""Clear the buffer."""
|
||||
zero = np.zero((self.HEIGHT, self. WIDTH))
|
||||
self.put(zero)
|
||||
|
||||
|
||||
def show(self, matrix):
|
||||
"""Output the contents of the buffer to Unicorn HAT HD."""
|
||||
##########################################################
|
||||
## Change to desire
|
||||
buff2 = numpy.rot90(matrix[:self.HEIGHT,:16],3)
|
||||
buff1 = numpy.rot90(matrix[:self.HEIGHT,16:32],1)
|
||||
##########################################################
|
||||
|
||||
buff1, buff2 = [(x.reshape(768)).astype(numpy.uint8).tolist() for x in (buff1, buff2)]
|
||||
|
||||
self.spi_write(buff1, buff2)
|
||||
|
||||
time.sleep(self.DELAY)
|
1
clock/helpers/__init__.py
Normal file
1
clock/helpers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import helper
|
202
clock/helpers/helper.py
Normal file
202
clock/helpers/helper.py
Normal file
@ -0,0 +1,202 @@
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import numpy as np
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
####### bulky hard-coded values:
|
||||
digits = {
|
||||
"1" : [[0,0,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]],
|
||||
"-" : [[0,0,0],[0,0,0],[1,1,1],[0,0,0],[0,0,0]],
|
||||
"-1" : [[0,0,1],[0,0,1],[1,1,1],[0,0,1],[0,0,1]],
|
||||
"error" : [[1,0,1],[1,0,1],[0,1,0],[1,0,1],[1,0,1]],
|
||||
} # these are 2-d arrays as we only work with one or 2 colors, and not the whole rgb spectrum
|
||||
|
||||
##place of numbers, invariant (for the given shape of 16x32)
|
||||
digit_position = [[2,4], [2,10], [9,4], [9,10]]
|
||||
|
||||
weather_categories = {
|
||||
"Clouds": "cloud",
|
||||
"Rain": "rain and cloud",
|
||||
"Thunderstorm": "thunder and cloud",
|
||||
"Drizzle": "rain and cloud",
|
||||
"Snow": "snow and cloud",
|
||||
"Clear": "sun",
|
||||
"Mist": "fog and clouds",
|
||||
"Smoke": "Smoke",
|
||||
"Haze": "Haze",
|
||||
"Dust": "Dust",
|
||||
"Fog": "fog",
|
||||
"Sand": "Sand",
|
||||
"Dust": "Dust",
|
||||
"Ash": "Ash",
|
||||
"Squal": "Squal",
|
||||
"Tornado": "Tornado",
|
||||
"error" : "moon"
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
||||
|
||||
class MatrixOperations():
|
||||
"""Helper functions to generate frequently-used images"""
|
||||
def __init__(self, shape, default_colors):
|
||||
self.shape = shape
|
||||
# shape is going to be (16,32) for the moment
|
||||
self.primary = default_colors["primary"]
|
||||
self.secondary = default_colors["secondary"]
|
||||
self.error = default_colors["error"]
|
||||
|
||||
|
||||
def time_converter(self, top="", bottom=""):
|
||||
"""Converts 4-digit time to a pixel-matrix
|
||||
returns: np.array wich fills one half of self.shape (horizontally)"""
|
||||
nshape = (self.shape[0], int(self.shape[1]/2))
|
||||
pixels = np.zeros(nshape,dtype=np.uint8)
|
||||
|
||||
if bottom == "" or top == "":
|
||||
top = datetime.datetime.now().strftime("%H")
|
||||
bottom = datetime.datetime.now().strftime("%M")
|
||||
|
||||
if len(top) < 2:
|
||||
top = "0" * (2 - len(top)) + top
|
||||
if len(bottom) < 2:
|
||||
bottom = "0" * (2 - len(bottom)) + bottom
|
||||
|
||||
if ("-" in top and len(top) > 2) or ("-" in bottom and len(bottom) > 2):
|
||||
time_split = 4*["-"]
|
||||
elif "error" in top and "error" in bottom:
|
||||
time_split = 4*["error"]
|
||||
else:
|
||||
time_split = [i for i in top] + [i for i in bottom]
|
||||
|
||||
if "-1" in top and len(top) != 2:
|
||||
time_split = ["-1", top[-1]] + [i for i in bottom]
|
||||
if "-1" in bottom and len(bottom) != 2:
|
||||
time_split = [i for i in top] + ["-1", bottom[-1]]
|
||||
|
||||
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
|
||||
|
||||
|
||||
def date_converter(self):
|
||||
nshape = (self.shape[0], int(self.shape[1]/2))
|
||||
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.copy() #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(nshape)
|
||||
pixels += lrow
|
||||
return pixels
|
||||
|
||||
|
||||
def weather_converter(self, name):
|
||||
"""Fills one half of the screen with weather info."""
|
||||
nshape = (self.shape[0], int(self.shape[1]/2))
|
||||
result = np.zeros(nshape)
|
||||
cwd = __file__.replace("\\","/") # for windows
|
||||
cwd = cwd.rsplit("/", 1)[0] # the current working directory (where this file is)
|
||||
if len(cwd) == 0:
|
||||
cwd = "."
|
||||
icon_spritesheet = cwd + "/weather-icons.bmp"
|
||||
|
||||
icons = Image.open(icon_spritesheet)
|
||||
icons_full = np.array(icons)
|
||||
|
||||
icon_loc = ["sun","moon","sun and clouds", "moon and clouds", "cloud","fog and clouds","2 clouds", "3 clouds", "rain and cloud", "rain and clouds", "rain and cloud and sun", "rain and cloud and moon", "thunder and cloud", "thunder and cloud and moon", "snow and cloud", "snow and cloud and moon", "fog","fog night"]
|
||||
#ordered 1 2 \n 3 4 \ 5 5 ...
|
||||
name = weather_categories[name]
|
||||
try:
|
||||
iy, ix = int(icon_loc.index(name)/2), icon_loc.index(name)%2
|
||||
# x and y coords
|
||||
except:
|
||||
return np.zeros((*nshape,3))
|
||||
|
||||
icon_single = icons_full[16*iy:16*(iy + 1),16*ix:16*(ix + 1),...]
|
||||
return icon_single
|
||||
|
||||
|
||||
def matrix_add_depth(self, matrix, colors = []):
|
||||
"""transforms a 2d-array with 0,1,2 to a 3d-array with the rgb values for primary and secondary color"""
|
||||
|
||||
c1 = self.primary
|
||||
c2 = self.secondary
|
||||
c3 = self.error
|
||||
if len(colors) > 0:
|
||||
c1 = colors[0]
|
||||
if len(colors) > 1:
|
||||
c2 = colors[1]
|
||||
if len(colors) > 2:
|
||||
c3 = colors[2]
|
||||
if len(colors) > 3:
|
||||
print("Too many colors")
|
||||
|
||||
r3 = np.zeros((matrix.shape[0],matrix.shape[1],3),dtype=int)
|
||||
for i in range(matrix.shape[0]):
|
||||
for j in range(matrix.shape[1]):
|
||||
t = int(matrix[i, j])
|
||||
if t == 0:
|
||||
r3[i, j, :] = [0,0,0]
|
||||
elif t == 1:
|
||||
r3[i, j, :] = c1
|
||||
elif t == 2:
|
||||
r3[i, j, :] = c2
|
||||
else:
|
||||
r3[i, j, :] = c3
|
||||
return r3
|
||||
|
||||
|
||||
|
||||
def clock_face(self, weather):
|
||||
"""weather as a dict"""
|
||||
hour = self.time_converter()
|
||||
day = self.date_converter()
|
||||
face1 = hour + day
|
||||
face1_3d = self.matrix_add_depth(face1)
|
||||
|
||||
if weather["show"] == "weather":
|
||||
face2_3d = self.weather_converter(weather["weather"])
|
||||
else:
|
||||
face2 = self.time_converter(top=str(weather["low"]), bottom=str(weather["high"]))
|
||||
face2 = np.concatenate((face2[:8,...],2*face2[8:,...]))
|
||||
face2_3d = self.matrix_add_depth(face2,[[0, 102, 255],[255, 102, 0]])
|
||||
|
||||
face = np.zeros((max(face1_3d.shape[0],face2_3d.shape[0]),face1_3d.shape[1]+face2_3d.shape[1],3))
|
||||
|
||||
face[:face1_3d.shape[0],:face1_3d.shape[1],...] = face1_3d
|
||||
face[:face2_3d.shape[0],face1_3d.shape[1]:,...] = face2_3d
|
||||
|
||||
return face
|
||||
|
||||
|
||||
def text_converter(self, text, height, color):
|
||||
"""Converts a text to a pixel-matrix
|
||||
returns: np.array((16, x, 3))"""
|
||||
|
||||
font = ImageFont.truetype("verdanab.ttf", height)
|
||||
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)
|
||||
pixels3d = self.matrix_add_depth(pixels, color)
|
||||
return pixels3d
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
@ -4,19 +4,26 @@ import json
|
||||
from threading import Thread, Timer
|
||||
import numpy
|
||||
|
||||
from clock.api import led
|
||||
from . import api, helpers
|
||||
|
||||
|
||||
class ClockFace(object):
|
||||
"""Actual functions one might need for a clock"""
|
||||
|
||||
def __init__(self, text_speed=18, prst=""):
|
||||
def __init__(self, text_speed=18, prst=object):
|
||||
""""""
|
||||
# added by the launcher, we have self.modules (dict)
|
||||
|
||||
self.persistence = prst
|
||||
self.IO = led.OutputHandler(32,16)
|
||||
# hard coded, but can be changed to taste
|
||||
self.tspeed = text_speed
|
||||
self.primary = [200, 200, 200]
|
||||
self.secondary = [10, 200, 10]
|
||||
self.error = [200, 10, 10]
|
||||
self.shape = (16,32)
|
||||
# shape: (16,32) is hard-coded for the moment
|
||||
self.persistence = prst
|
||||
self.IO = api.led.OutputHandler(self.shape)
|
||||
self.MOP = helpers.helper.MatrixOperations(self.shape, default_colors={"primary": self.primary, "secondary": self.secondary, "error": self.error})
|
||||
|
||||
self.output_thread = ""
|
||||
# Action the thread is currently performing
|
||||
@ -25,11 +32,13 @@ class ClockFace(object):
|
||||
|
||||
self.weather = {"weather":"", "high":"", "low":"", "show":"temps"}
|
||||
self.weather_raw = {}
|
||||
# different?
|
||||
|
||||
self.brightness = 1
|
||||
self.brightness_overwrite = {"value" : 1, "duration" : 0}
|
||||
|
||||
|
||||
def start(self):
|
||||
self.clock_loop()
|
||||
while datetime.datetime.now().strftime("%H%M%S")[-2:] != "00":
|
||||
pass
|
||||
RepeatedTimer(60, self.clock_loop)
|
||||
@ -61,7 +70,7 @@ class ClockFace(object):
|
||||
next = "weather"
|
||||
self.weather["show"] = next
|
||||
|
||||
self.set_face()
|
||||
self.run(self.set_face,())
|
||||
|
||||
|
||||
def run(self, command, kw=()):
|
||||
@ -76,7 +85,7 @@ class ClockFace(object):
|
||||
n = self.output_queue.pop(0)
|
||||
enhanced_run(n[0],n[1])
|
||||
else:
|
||||
self.IO.clock_face(self.weather)
|
||||
self.set_face()
|
||||
|
||||
if len(self.output_thread) == 0:
|
||||
t = Thread(target=enhanced_run, args=(command, kw))
|
||||
@ -88,18 +97,19 @@ class ClockFace(object):
|
||||
############################################################################
|
||||
### basic clock commands
|
||||
def set_face(self):
|
||||
""""""
|
||||
self.run(self.IO.clock_face,(self.weather,))
|
||||
"""Set the clock face (time + weather) by getting updated info - gets called every minute"""
|
||||
face = self.MOP.clock_face(self.weather)
|
||||
self.IO.array = face * self.brightness
|
||||
self.IO.SHOW()
|
||||
|
||||
|
||||
def set_brightness(self, overwrite=[],value=-1):
|
||||
def set_brightness(self, value=-1, overwrite=[]):
|
||||
"""Checks, what brightness rules to apply"""
|
||||
|
||||
if value != -1:
|
||||
self.IO.output.set_brightness(value)
|
||||
self.brightness = value
|
||||
return
|
||||
|
||||
|
||||
if len(overwrite) != 0:
|
||||
self.brightness_overwrite = overwrite
|
||||
|
||||
@ -110,8 +120,22 @@ class ClockFace(object):
|
||||
else:
|
||||
brightness = 0.01
|
||||
|
||||
self.IO.output.set_brightness(brightness)
|
||||
self.brightness = brightness
|
||||
|
||||
def text_scroll(self, text, color=[[200,200,200]]):
|
||||
pixels = self.MOP.text_converter(text, 12, color)
|
||||
sleep_time = 1 / self.tspeed
|
||||
width = self.shape[1]
|
||||
frames = pixels.shape[1] - width
|
||||
if frames <= 0:
|
||||
frames = 1
|
||||
|
||||
for i in range(frames):
|
||||
visible = pixels[:,i:width+i]
|
||||
self.IO.array = visible*self.brightness
|
||||
self.IO.SHOW()
|
||||
time.sleep(sleep_time)
|
||||
time.sleep(10 * sleep_time)
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user