Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
921bcc56e5 |
4 changed files with 41 additions and 303 deletions
138
fb_gui.py
138
fb_gui.py
|
@ -1,138 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from framebuffer import Framebuffer
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
class TextBox:
|
|
||||||
def __init__(self, pos, size, max_jitter, color, background, fitting_text):
|
|
||||||
self.color = color
|
|
||||||
self.background = background
|
|
||||||
self.pos = pos
|
|
||||||
self.bounds = (size[0]+1, size[1]+1)
|
|
||||||
font_size = 100
|
|
||||||
while (self.bounds[0] > size[0]-max_jitter) or (self.bounds[1] > size[1]-max_jitter):
|
|
||||||
font_size-=1
|
|
||||||
self.font = ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf", font_size)
|
|
||||||
self.bounds = self.font.getsize(fitting_text)
|
|
||||||
self.size = size
|
|
||||||
self.img = Image.new("RGB", self.bounds)
|
|
||||||
self.draw = ImageDraw.Draw(self.img)
|
|
||||||
self.offset = (int((self.size[0]-self.bounds[0])/2), int((self.size[1]-self.bounds[1])/2))
|
|
||||||
|
|
||||||
def draw_text(self, target_img, text, jitter_pos):
|
|
||||||
self.draw.rectangle((0, 0, self.img.width-1, self.img.height-1), self.background, self.background, 1)
|
|
||||||
self.draw.text((jitter_pos[0],0), text, font=self.font, fill=self.color)
|
|
||||||
x=self.pos[0]+self.offset[0]
|
|
||||||
y=self.pos[1]+self.offset[1]
|
|
||||||
target_img.paste(self.img, (x,y))
|
|
||||||
|
|
||||||
###
|
|
||||||
# CONFIGURATION
|
|
||||||
###
|
|
||||||
|
|
||||||
DISPLAY_SIZE=(320,240)
|
|
||||||
|
|
||||||
CLOCK_FORMAT = "%H:%M"
|
|
||||||
TEMP_FORMAT = "% 3.1f°C"
|
|
||||||
|
|
||||||
BKGND_COLOR=(0,0,0)
|
|
||||||
LINE_COLOR=(255,255,255)
|
|
||||||
|
|
||||||
JITTER=5
|
|
||||||
DELAY=0.3
|
|
||||||
TEMP_DELAY=10
|
|
||||||
|
|
||||||
GPIO_TOOL=['gpio','-g']
|
|
||||||
PWM_MODE=['mode','18','pwm']
|
|
||||||
DAY_BRIGHT=['pwm','18','800']
|
|
||||||
NIGHT_BRIGHT=['pwm','18','200']
|
|
||||||
|
|
||||||
now_parts=time.localtime()
|
|
||||||
time_str = time.strftime(CLOCK_FORMAT, now_parts)
|
|
||||||
temp_str = TEMP_FORMAT % -10.0
|
|
||||||
|
|
||||||
clock_box=TextBox( (10,10), (300,120), JITTER, (255,255,255), BKGND_COLOR, time_str)
|
|
||||||
temp_box =TextBox( (120,180), (190,50), JITTER, (255,0,0), BKGND_COLOR, temp_str)
|
|
||||||
|
|
||||||
display = Framebuffer(1)
|
|
||||||
fb_img = Image.new(mode="RGB", size=DISPLAY_SIZE)
|
|
||||||
fb_draw = ImageDraw.Draw(fb_img)
|
|
||||||
|
|
||||||
mean_temp = 0
|
|
||||||
|
|
||||||
def fetch_temp(bridge, user, sensor):
|
|
||||||
r=requests.get(f"http://{bridge}/api/{user}/sensors/{str(sensor)}")
|
|
||||||
if r:
|
|
||||||
return r.json()["state"]["temperature"]/100
|
|
||||||
return None
|
|
||||||
|
|
||||||
def jitter():
|
|
||||||
return (random.randint(0,JITTER), random.randint(0,JITTER))
|
|
||||||
|
|
||||||
# Main loop:
|
|
||||||
jitter_pos = jitter()
|
|
||||||
|
|
||||||
old_time=""
|
|
||||||
old_temp=""
|
|
||||||
|
|
||||||
subprocess.run(GPIO_TOOL+PWM_MODE)
|
|
||||||
subprocess.run(GPIO_TOOL+NIGHT_BRIGHT)
|
|
||||||
|
|
||||||
fb_draw.rectangle(((0,0),DISPLAY_SIZE),BKGND_COLOR, BKGND_COLOR,1)
|
|
||||||
temp_time=time.time()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
changed = False
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
now_parts = time.localtime(now)
|
|
||||||
|
|
||||||
if now-temp_time > TEMP_DELAY:
|
|
||||||
cur_temp = fetch_temp("philips-hue", "GQ03rw1saUS0n88G5yj9j7-TsteFIE1yxtlBOgzD", 71)
|
|
||||||
if cur_temp:
|
|
||||||
mean_temp = cur_temp
|
|
||||||
temp_time = now
|
|
||||||
|
|
||||||
if now_parts.tm_sec == 0:
|
|
||||||
jitter_pos = jitter()
|
|
||||||
|
|
||||||
if (now_parts.tm_min == 0) and (now_parts.tm_hour > 7 and now_parts.tm_hour < 19):
|
|
||||||
subprocess.run(GPIO_TOOL+DAY_BRIGHT)
|
|
||||||
else:
|
|
||||||
subprocess.run(GPIO_TOOL+NIGHT_BRIGHT)
|
|
||||||
|
|
||||||
time_str = time.strftime(CLOCK_FORMAT, now_parts)
|
|
||||||
temp_str = TEMP_FORMAT % mean_temp
|
|
||||||
|
|
||||||
if time_str != old_time:
|
|
||||||
clock_box.draw_text(fb_img, time_str, jitter_pos)
|
|
||||||
old_time = time_str
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if temp_str != old_temp:
|
|
||||||
temp_box.draw_text(fb_img, temp_str, jitter_pos)
|
|
||||||
old_temp = temp_str
|
|
||||||
changed=True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
display.show(fb_img)
|
|
||||||
|
|
||||||
while time.time() - now < DELAY:
|
|
||||||
time.sleep(DELAY)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
clock_box.draw_text(fb_img, "off" ,(0,0))
|
|
||||||
temp_box.draw_text(fb_img,"",(0,0))
|
|
||||||
display.show(fb_img)
|
|
||||||
subprocess.run(GPIO_TOOL+NIGHT_BRIGHT)
|
|
||||||
|
|
125
framebuffer.py
125
framebuffer.py
|
@ -1,125 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""Framebuffer helper that makes lots of simpifying assumptions
|
|
||||||
|
|
||||||
bits_per_pixel assumed memory layout
|
|
||||||
16 rgb565
|
|
||||||
24 rgb
|
|
||||||
32 argb
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
|
|
||||||
def _read_and_convert_to_ints(filename):
|
|
||||||
with open(filename, "r") as fp:
|
|
||||||
content = fp.read()
|
|
||||||
tokens = content.strip().split(",")
|
|
||||||
return [int(t) for t in tokens if t]
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_argb(image: Image):
|
|
||||||
return bytes([x for r, g, b in image.getdata() for x in (255, r, g, b)])
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_rgb565(image: Image):
|
|
||||||
return bytes([x for r, g, b in image.getdata()
|
|
||||||
for x in ((g & 0x1c) << 3 | (b >> 3), r & 0xf8 | (g >> 3))])
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_1_argb(image: Image):
|
|
||||||
return bytes([x for p in image.getdata()
|
|
||||||
for x in (255, p, p, p)])
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_1_rgb(image: Image):
|
|
||||||
return bytes([x for p in image.getdata()
|
|
||||||
for x in (p, p, p)])
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_1_rgb565(image: Image):
|
|
||||||
return bytes([(255 if x else 0) for p in image.getdata()
|
|
||||||
for x in (p, p)])
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_rgba_rgb565_numpy(image: Image):
|
|
||||||
flat = numpy.frombuffer(image.tobytes(), dtype=numpy.uint32)
|
|
||||||
# note, this is assumes little endian byteorder and results in
|
|
||||||
# the following packing of an integer:
|
|
||||||
# bits 0-7: red, 8-15: green, 16-23: blue, 24-31: alpha
|
|
||||||
flat = ((flat & 0xf8) << 8) | ((flat & 0xfc00) >> 5) | ((flat & 0xf80000) >> 19)
|
|
||||||
return flat.astype(numpy.uint16).tobytes()
|
|
||||||
|
|
||||||
|
|
||||||
def _converter_no_change(image: Image):
|
|
||||||
return image.tobytes()
|
|
||||||
|
|
||||||
# anything that does not use numpy is hopelessly slow
|
|
||||||
_CONVERTER = {
|
|
||||||
("RGBA", 16): _converter_rgba_rgb565_numpy,
|
|
||||||
("RGB", 16): _converter_rgb565,
|
|
||||||
("RGB", 24): _converter_no_change,
|
|
||||||
("RGB", 32): _converter_argb,
|
|
||||||
# note numpy does not work well with mode="1" images as
|
|
||||||
# image.tobytes() loses pixel color info
|
|
||||||
("1", 16): _converter_1_rgb565,
|
|
||||||
("1", 24): _converter_1_rgb,
|
|
||||||
("1", 32): _converter_1_argb,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Framebuffer(object):
|
|
||||||
|
|
||||||
def __init__(self, device_no: int):
|
|
||||||
self.path = "/dev/fb%d" % device_no
|
|
||||||
config_dir = "/sys/class/graphics/fb%d/" % device_no
|
|
||||||
self.size = tuple(_read_and_convert_to_ints(
|
|
||||||
config_dir + "/virtual_size"))
|
|
||||||
self.stride = _read_and_convert_to_ints(config_dir + "/stride")[0]
|
|
||||||
self.bits_per_pixel = _read_and_convert_to_ints(
|
|
||||||
config_dir + "/bits_per_pixel")[0]
|
|
||||||
assert self.stride == self.bits_per_pixel // 8 * self.size[0]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
args = (self.path, self.size, self.stride, self.bits_per_pixel)
|
|
||||||
return "%s size:%s stride:%s bits_per_pixel:%s" % args
|
|
||||||
|
|
||||||
# Note: performance is terrible even for medium resolutions
|
|
||||||
def show(self, image: Image):
|
|
||||||
converter = _CONVERTER[(image.mode, self.bits_per_pixel)]
|
|
||||||
assert image.size == self.size
|
|
||||||
out = converter(image)
|
|
||||||
with open(self.path, "wb") as fp:
|
|
||||||
fp.write(out)
|
|
||||||
|
|
||||||
def on(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def off(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import time
|
|
||||||
from PIL import ImageDraw
|
|
||||||
|
|
||||||
|
|
||||||
def TestFrameBuffer(i):
|
|
||||||
fb = Framebuffer(i)
|
|
||||||
print(fb)
|
|
||||||
image = Image.new("RGBA", fb.size)
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
draw.rectangle(((0, 0), fb.size), fill="green")
|
|
||||||
draw.ellipse(((0, 0), fb.size), fill="blue", outline="red")
|
|
||||||
draw.line(((0, 0), fb.size), fill="green", width=2)
|
|
||||||
start = time.time()
|
|
||||||
for i in range(5):
|
|
||||||
fb.show(image)
|
|
||||||
stop = time.time()
|
|
||||||
print("fps: %.2f" % (10 / (stop - start)))
|
|
||||||
|
|
||||||
|
|
||||||
for i in [1]:
|
|
||||||
TestFrameBuffer(i)
|
|
|
@ -1,15 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Wetterstation
|
|
||||||
Wants=network.target
|
|
||||||
Before=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
PIDFile=/run/wetter.pid
|
|
||||||
ExecStart=/home/pi/wetter/gui/fb_gui.py
|
|
||||||
WorkingDirectory=/home/pi/wetter/gui
|
|
||||||
User=pi
|
|
||||||
Group=pi
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -10,14 +10,17 @@ from board import SCK, MOSI, MISO, D8, D24, D23, D18
|
||||||
from adafruit_rgb_display import color565
|
from adafruit_rgb_display import color565
|
||||||
import adafruit_rgb_display.ili9341 as ili9341
|
import adafruit_rgb_display.ili9341 as ili9341
|
||||||
|
|
||||||
from raspyrfm import *
|
#from raspyrfm import *
|
||||||
import sensors
|
#import sensors
|
||||||
from sensors import rawsensor
|
#from sensors import rawsensor
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
class TextBox:
|
class TextBox:
|
||||||
def __init__(self, pos, size, max_jitter, color, background, fitting_text):
|
def __init__(self, pos, size, max_jitter, color, background, fitting_text):
|
||||||
self.color = color
|
self.color = color
|
||||||
|
@ -91,22 +94,22 @@ backlight.direction = digitalio.Direction.OUTPUT
|
||||||
backlight.value = True
|
backlight.value = True
|
||||||
|
|
||||||
|
|
||||||
if raspyrfm_test(5, RFM69):
|
#if raspyrfm_test(5, RFM69):
|
||||||
print("Found RaspyRFM mod")
|
# print("Found RaspyRFM mod")
|
||||||
rfm = RaspyRFM(5, RFM69) #when using the RaspyRFM twin
|
# rfm = RaspyRFM(5, RFM69) #when using the RaspyRFM twin
|
||||||
else:
|
#else:
|
||||||
print("No RFM69 module found!")
|
# print("No RFM69 module found!")
|
||||||
exit()
|
# exit()
|
||||||
|
|
||||||
rfm.set_params(
|
#rfm.set_params(
|
||||||
Freq = 868.30, #MHz center frequency
|
# Freq = 868.30, #MHz center frequency
|
||||||
Datarate = 17.241, #kbit/s baudrate
|
# Datarate = 17.241, #kbit/s baudrate
|
||||||
ModulationType = rfm69.FSK, #modulation
|
# ModulationType = rfm69.FSK, #modulation
|
||||||
Deviation = 30, #kHz frequency deviation
|
# Deviation = 30, #kHz frequency deviation
|
||||||
SyncPattern = [0x2d, 0xd4], #syncword
|
# SyncPattern = [0x2d, 0xd4], #syncword
|
||||||
Bandwidth = 150, #kHz bandwidth
|
# Bandwidth = 150, #kHz bandwidth
|
||||||
RssiThresh = -105, #dBm RSSI threshold
|
# RssiThresh = -105, #dBm RSSI threshold
|
||||||
)
|
#)
|
||||||
|
|
||||||
|
|
||||||
mean_temp = 0
|
mean_temp = 0
|
||||||
|
@ -127,10 +130,16 @@ def update_temp():
|
||||||
if temps:
|
if temps:
|
||||||
mean_temp = sum(temps) / len(temps)
|
mean_temp = sum(temps) / len(temps)
|
||||||
|
|
||||||
|
def fetch_temp(bridge, user, sensor):
|
||||||
|
r=requests.get(f"http://{bridge}/api/{user}/sensors/{str(sensor)}")
|
||||||
|
if r:
|
||||||
|
return r.json()["state"]["temperature"]/100
|
||||||
|
return None
|
||||||
|
|
||||||
temp_thread = threading.Thread(target=update_temp, name="Temp Thread")
|
|
||||||
|
|
||||||
temp_thread.start()
|
#temp_thread = threading.Thread(target=update_temp, name="Temp Thread")
|
||||||
|
|
||||||
|
#temp_thread.start()
|
||||||
|
|
||||||
|
|
||||||
#display.vline(0,clock_box.size[1],1,color565(LINE_COLOR))
|
#display.vline(0,clock_box.size[1],1,color565(LINE_COLOR))
|
||||||
|
@ -147,7 +156,10 @@ old_temp=""
|
||||||
display.fill(color565(BKGND_COLOR))
|
display.fill(color565(BKGND_COLOR))
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
check_pixel = display.read((0,0)
|
cur_temp = fetch_temp("philips-hue", "GQ03rw1saUS0n88G5yj9j7-TsteFIE1yxtlBOgzD", 71)
|
||||||
|
if cur_temp:
|
||||||
|
mean_temp = cur_temp
|
||||||
|
|
||||||
#display.reset()
|
#display.reset()
|
||||||
#time.sleep(0.5)
|
#time.sleep(0.5)
|
||||||
|
|
||||||
|
@ -157,7 +169,11 @@ try:
|
||||||
time_str = time.strftime(CLOCK_FORMAT, now_parts)
|
time_str = time.strftime(CLOCK_FORMAT, now_parts)
|
||||||
temp_str = TEMP_FORMAT % mean_temp
|
temp_str = TEMP_FORMAT % mean_temp
|
||||||
|
|
||||||
if time_str != old_time:
|
# display.reset()
|
||||||
|
display.init()
|
||||||
|
# display.fill(color565(BKGND_COLOR))
|
||||||
|
|
||||||
|
if True: #time_str != old_time:
|
||||||
clock_box.draw_text(display, time_str, jitter_pos)
|
clock_box.draw_text(display, time_str, jitter_pos)
|
||||||
old_time = time_str
|
old_time = time_str
|
||||||
|
|
||||||
|
@ -168,7 +184,7 @@ try:
|
||||||
# backlight.duty_cycle = DAY_BRIGHT
|
# backlight.duty_cycle = DAY_BRIGHT
|
||||||
# else:
|
# else:
|
||||||
# backlight.duty_cycle = NIGHT_BRIGHT
|
# backlight.duty_cycle = NIGHT_BRIGHT
|
||||||
if temp_str != old_temp:
|
if True: #temp_str != old_temp:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
temp_box.draw_text(display, temp_str, jitter_pos)
|
temp_box.draw_text(display, temp_str, jitter_pos)
|
||||||
old_temp = temp_str
|
old_temp = temp_str
|
||||||
|
@ -178,7 +194,7 @@ try:
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
clock_box.draw_text(display, "off" ,(0,0))
|
clock_box.draw_text(display, "off" ,(0,0))
|
||||||
temp_box.draw_text(display,"",(0,0))
|
# temp_box.draw_text(display,"",(0,0))
|
||||||
shutdown=True
|
shutdown=True
|
||||||
temp_thread.join()
|
# temp_thread.join()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue