Better folder structure, added a few ambient sensors

This commit is contained in:
Remy Moll
2021-04-15 19:50:37 +02:00
parent c61ee3ea72
commit 03d419f8f7
45 changed files with 711 additions and 1285 deletions

View File

@@ -1,2 +0,0 @@
# Placeholder
from . import led

View File

@@ -1,91 +0,0 @@
import time
import numpy as np
try:
from . import unicorn
output = unicorn.ClockOut
except ImportError:
from . import sim
output = sim.ClockOut
# import sim
# output = sim.ClockOut
class OutputHandler():
"""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 SHOW(self):
# self.output.set_matrix(self)
self.OUT.put(self.array)
# 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

View File

@@ -1,27 +0,0 @@
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()

44
clock/cin.py Normal file
View File

@@ -0,0 +1,44 @@
import datetime
import time
from threading import Thread, Timer
from . import hardware, helpers
class SensorReadout:
"""Overview class for (actual and potential) sensor sources"""
def __init__(self, prst=object):
""""""
self.persistence = prst
self.sensor_modules = { # we already call them, they are objects and not classes anymore
"temperature" : hardware.sensors.TemperatureModule(),
"humidity" : hardware.sensors.HumidityModule(),
"brightness" : hardware.sensors.BrightnessModule(),
# more to come?
}
def start(self):
helpers.timer.RepeatedTimer(300, self.spread_measure)
def spread_measure(self):
results = dict((el,[]) for el in self.sensor_modules.keys()) # create an empty dict with a list for each readout-type
for _ in range(5): # number of measures to average out
for name in self.sensor_modules.keys():
measure = self.sensor_modules[name].readout()
results[name].append(measure)
time.sleep(3)
self.save_results(results)
def save_results(self, results):
current_minute = int(datetime.datetime.now().timestamp() // 60)
self.persistence["clock"]["sensors"]["time"] += [current_minute]
for name in results.keys():
keep_value = sum(results[name]) / len(results[name])
self.persistence["clock"]["sensors"][name] += [keep_value]

View File

@@ -1,13 +1,12 @@
import datetime
import time
import json
from threading import Thread, Timer
from threading import Thread
import numpy
from . import api, helpers
from . import hardware, helpers
class ClockFace(object):
class ClockFace:
"""Actual functions one might need for a clock"""
def __init__(self, text_speed=18, prst=object):
@@ -19,10 +18,10 @@ class ClockFace(object):
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.IO = hardware.led.get_handler()
self.shape = self.IO.shape # (16,32) for now
self.MOP = helpers.helper.MatrixOperations(self.shape, default_colors={"primary": self.primary, "secondary": self.secondary, "error": self.error})
self.output_thread = ""
@@ -41,7 +40,8 @@ class ClockFace(object):
self.clock_loop()
while datetime.datetime.now().strftime("%H%M%S")[-2:] != "00":
pass
RepeatedTimer(60, self.clock_loop)
helpers.timer.RepeatedTimer(60, self.clock_loop)
self.clock_loop()
def clock_loop(self):
@@ -99,8 +99,7 @@ class ClockFace(object):
def set_face(self):
"""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()
self.IO.put(face * self.brightness)
def set_brightness(self, value=-1, overwrite=[]):
@@ -122,6 +121,7 @@ class ClockFace(object):
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
@@ -132,39 +132,7 @@ class ClockFace(object):
for i in range(frames):
visible = pixels[:,i:width+i]
self.IO.array = visible*self.brightness
self.IO.SHOW()
self.IO.put(visible*self.brightness)
time.sleep(sleep_time)
time.sleep(10 * sleep_time)
#######################################################
class RepeatedTimer(object):
def __init__(self, interval, function, *args, **kwargs):
self._timer = None
self.interval = interval
self.function = function
self.args = args
self.kwargs = kwargs
self.is_running = False
self.next_call = time.time()
self.start()
def _run(self):
self.is_running = False
self.start()
self.function(*self.args, **self.kwargs)
def start(self):
if not self.is_running:
self.next_call += self.interval
self._timer = Timer(self.next_call - time.time(), self._run)
self._timer.start()
self.is_running = True
def stop(self):
self._timer.cancel()
self.is_running = False

View File

@@ -0,0 +1,2 @@
# Placeholder
from . import led, sensors

17
clock/hardware/led.py Normal file
View File

@@ -0,0 +1,17 @@
from . import unicorn as led
# or neopixel soon:
# from . import neopixel as led
def get_handler():
OUT = led.ClockOut()
shape = OUT.shape
if led.SETUP_FAIL:
# we use the sim
del OUT
from . import sim
OUT = sim.ClockOut(shape)
return OUT

View File

@@ -0,0 +1,51 @@
import time
import numpy as np
import colorsys
import random
try:
import rpi_ws281x as ws
except ImportError:
from unittest.mock import Mock
ws = Mock()
SETUP_FAIL = True
class ClockOut:
def __init__(self):
self.shape = (45, 20) # H x W
num = self.shape[0] * self.shape[1]
pin = 18
freq = 800000 # maybe higher
dma = 5
invert = False
brightness = 100
channel = 0
led_type = None # ??
self.strip = ws.PixelStrip(num, pin, freq, dma, invert, brightness, channel, led_type)
self.strip.begin()
def put(self, matrix):
self.render(matrix)
def render(self, matrix):
p = 0
for i in range(matrix.shape[0]):
for j in range(matrix.shape[1]):
col = int(ws.Color(*matrix[i,j]))
self.strip.setPixelColor(p, col)
p += 1
self.strip.show()
# test = ClockOut()
# z = np.zeros((30,30, 3), dtype=int)
# for i in range(30):
# for j in range(30):
# z[i, j, ...] = [random.randint(0,255), random.randint(0,255), random.randint(0,255)]
# test.put(z)
# #time.sleep(0.1)

80
clock/hardware/sensors.py Normal file
View File

@@ -0,0 +1,80 @@
import time
import logging
logger = logging.getLogger(__name__)
class TempSim:
"""Simulates a temperature for running on windows"""
temperature = 23 # return a celsius value
humidity = 0.3
class SensorModule:
def __init__(self):
logger.info("Using module " + self.__class__.__name__)
class LightSim:
def input(self, *args):
return 1
try:
import board
import adafruit_dht
dht11 = adafruit_dht.DHT11(board.D18)
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.IN)
except ImportError:
dht11 = TempSim()
GPIO = LightSim()
class TemperatureModule(SensorModule):
"""Takes readouts from the DHT 11
Returns: temperature"""
def __init__(self):
super().__init__()
self.device = dht11
def readout(self):
try:
temp = self.device.temperature
except:
time.sleep(1)
try:
temp = self.device.temperature
except:
temp = -1
return temp
class HumidityModule(SensorModule):
"""Takes readouts from the DHT 11
Returns: humidity"""
def __init__(self):
super().__init__()
self.device = self.device = dht11
def readout(self):
try:
hum = self.device.humidity
except:
time.sleep(1)
try:
hum = self.device.humidity
except:
hum = -1
return hum
class BrightnessModule(SensorModule):
"""Returns one for HIGH and zero for LOW"""
def __init__(self):
super().__init__()
def readout(self):
# The sensor is reversed: 0 when bright and 1 if dark
light = GPIO.input(4)
if light == 0:
return 1
else:
return 0

52
clock/hardware/sim.py Normal file
View File

@@ -0,0 +1,52 @@
import sys
import colorsys
import pygame.gfxdraw
import time
import pygame
import numpy
class ClockOut:
"""Creates a drawable window in case the real hardware is not accessible. For development"""
def __init__(self, shape):
self.pixel_size = 20
self.shape = shape
self.pixels = numpy.zeros((*shape,3), dtype=int)
self.WIDTH = shape[1]
self.HEIGHT = shape[0]
self.window_width = self.WIDTH * self.pixel_size
self.window_height = self.HEIGHT * self.pixel_size
pygame.init()
pygame.display.set_caption("Unicorn HAT simulator")
self.screen = pygame.display.set_mode([self.window_width, self.window_height])
def put(self, matrix):
self.screen.fill((0, 0, 0))
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT:
print("Exiting...")
pygame.quit()
sys.exit()
self.pixels = matrix
self.draw_pixels()
pygame.display.flip()
pygame.event.pump()
def draw_pixels(self):
p = self.pixel_size
r = int(p / 4)
for i in range(self.HEIGHT):
for j in range(self.WIDTH):
w_x = int(j * p + p / 2)
#w_y = int((self.HEIGHT - 1 - y) * p + p / 2)
w_y = int(i * p + p / 2)
color = self.pixels[i,j,:]
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)

View File

@@ -1,30 +1,37 @@
import colorsys
import time
import numpy
import RPi.GPIO as GPIO
try:
import RPi.GPIO as GPIO
except ImportError:
from unittest.mock import Mock
GPIO = Mock()
SETUP_FAIL = True
class ClockOut(object):
def __init__(self, shape):
class ClockOut:
def __init__(self):
self.PIN_CLK = 11
##################################
# Hardcoded vaules:
# GPIO Pins for the actual signal. The other ones are for signal clocks and resets.
self.PINS_DAT = [10, 22]
##################################
self.PIN_CS = 8
# for data transmission
self.SOF = 0x72
self.DELAY = 1.0/120
# shape of 2 unicorn hats
self.shape = (16, 32)
##################################
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.HEIGHT = self.shape[0] #16
self.WIDTH = self.shape[1] #32
self.reset_clock()
@@ -60,8 +67,6 @@ class ClockOut(object):
GPIO.output(self.PIN_CLK, GPIO.LOW)
def put(self, matrix):
"""Sets a height x width matrix directly"""
self.reset_clock()
@@ -81,7 +86,7 @@ class ClockOut(object):
buff2 = numpy.rot90(matrix[:self.HEIGHT,:16],3)
buff1 = numpy.rot90(matrix[:self.HEIGHT,16:32],1)
##########################################################
# separated these are: 16x16x3 arrays
buff1, buff2 = [(x.reshape(768)).astype(numpy.uint8).tolist() for x in (buff1, buff2)]
self.spi_write(buff1, buff2)

View File

@@ -1 +1 @@
from . import helper
from . import helper, timer

View File

@@ -3,46 +3,12 @@ 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)
# bulky hard-coded values:
from . import shapes
digits = shapes.digits
weather_categories = shapes.weather_categories
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))

103
clock/helpers/shapes.py Normal file
View File

@@ -0,0 +1,103 @@
import numpy as np
digits = {
"1" : np.array([
[0,0,1],
[0,0,1],
[0,0,1],
[0,0,1],
[0,0,1]]),
"2" : np.array([
[1,1,1],
[0,0,1],
[1,1,1],
[1,0,0],
[1,1,1]]),
"3" : np.array([
[1,1,1],
[0,0,1],
[1,1,1],
[0,0,1],
[1,1,1]]),
"4" : np.array([
[1,0,1],
[1,0,1],
[1,1,1],
[0,0,1],
[0,0,1]]),
"5" : np.array([
[1,1,1],
[1,0,0],
[1,1,1],
[0,0,1],
[1,1,1]]),
"6" : np.array([
[1,1,1],
[1,0,0],
[1,1,1],
[1,0,1],
[1,1,1]]),
"7" : np.array([
[1,1,1],
[0,0,1],
[0,0,1],
[0,0,1],
[0,0,1]]),
"8" : np.array([
[1,1,1],
[1,0,1],
[1,1,1],
[1,0,1],
[1,1,1]]),
"9" : np.array([
[1,1,1],
[1,0,1],
[1,1,1],
[0,0,1],
[1,1,1]]),
"0" : np.array([
[1,1,1],
[1,0,1],
[1,0,1],
[1,0,1],
[1,1,1]]),
"-" : np.array([
[0,0,0],
[0,0,0],
[1,1,1],
[0,0,0],
[0,0,0]]),
"-1" : np.array([
[0,0,1],
[0,0,1],
[1,1,1],
[0,0,1],
[0,0,1]]),
"error" : np.array([
[1,0,1],
[1,0,1],
[0,1,0],
[1,0,1],
[1,0,1]]),
}
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"
}

29
clock/helpers/timer.py Normal file
View File

@@ -0,0 +1,29 @@
from threading import Timer
import time
class RepeatedTimer(object):
def __init__(self, interval, function, *args, **kwargs):
self._timer = None
self.interval = interval
self.function = function
self.args = args
self.kwargs = kwargs
self.is_running = False
self.next_call = time.time()
self.start()
def _run(self):
self.is_running = False
self.start()
self.function(*self.args, **self.kwargs)
def start(self):
if not self.is_running:
self.next_call += self.interval
self._timer = Timer(self.next_call - time.time(), self._run)
self._timer.start()
self.is_running = True
def stop(self):
self._timer.cancel()
self.is_running = False