Add tstat class and demo.

pull/8/head
Peter Hinch 2021-07-06 19:12:42 +01:00
rodzic 68e6875beb
commit fdb75ef4d7
7 zmienionych plików z 265 dodań i 4 usunięć

Wyświetl plik

@ -903,9 +903,11 @@ Keyword only args:
LED. An integer will create a `Label` of that width for later use.
Methods:
1. `color` arg `c=None` Change the LED color to `c`. If `c` is `None` the LED
1. `value` arg `val=None` If `True` is passed, lights the `LED` in its current
color. `False` extinguishes it. `None` has no effect. Returns current value.
2. `color` arg `c=None` Change the LED color to `c`. If `c` is `None` the LED
is turned off (rendered in the background color).
2. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
3. `text` Updates the label if present (otherwise throws a `ValueError`). Args:
* `text=None` The text to display. If `None` displays last value.
* ` invert=False` If true, show inverse text.
* `fgcolor=None` Foreground color: if `None` the `Writer` default is used.

Wyświetl plik

@ -26,7 +26,7 @@ class BaseScreen(Screen):
col = 2
row = 2
self.lbl = Label(wri, row + 45, col + 50, 35, bdcolor=RED, bgcolor=DARKGREEN)
# Instntiate Label first, because Slider callback will run now.
# Instantiate Label first, because Slider callback will run now.
# See linked_sliders.py for another approach.
Slider(wri, row, col, callback=self.slider_cb,
bdcolor=RED, slotcolor=BLUE,

77
gui/demos/tstat.py 100644
Wyświetl plik

@ -0,0 +1,77 @@
# tstat.py nanogui demo for the Tstat class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Usage:
# import gui.demos.tstat
# hardware_setup must be imported before other modules because of RAM use.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.sliders import Slider
from gui.widgets.label import Label
from gui.widgets.tstat import Tstat
from gui.widgets.led import LED
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def btncb(btn, reg, low, high):
reg.adjust(low, high)
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 10
sl = Slider(wri, row, col, callback=self.slider_cb,
bdcolor=RED, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'))
self.ts = Tstat(wri, row, sl.mcol + 5, divisions = 4, ptcolor=YELLOW, height=100, width=15,
style=Tstat.BAR, legends=('0.0', '0.5', '1.0'))
reg = self.ts.add_region(0.4, 0.6, LIGHTRED, self.ts_cb)
al = self.ts.add_region(0.9, 1.0, RED, self.al_cb)
self.lbl = Label(wri, row, self.ts.mcol + 5, 35, bdcolor=RED, bgcolor=BLACK)
self.led = LED(wri, row + 30, self.ts.mcol + 5, color=YELLOW, bdcolor=BLACK)
btn = Button(wri, row, self.lbl.mcol + 5,
text='down', litcolor=RED, bgcolor=DARKGREEN,
callback=btncb, args=(reg, 0.2, 0.3))
Button(wri, btn.mrow + 5, self.lbl.mcol + 5,
text='up', litcolor=RED, bgcolor=DARKGREEN,
callback=btncb, args=(reg, 0.5, 0.6))
CloseButton(wri)
def slider_cb(self, s):
if hasattr(self, 'lbl'):
v = s.value()
self.lbl.value('{:5.3f}'.format(v))
self.ts.value(v)
def ts_cb(self, reg, reason):
# Turn on if T drops below low threshold when it had been above high threshold. Or
# in the case of a low going drop so fast it never registered as being within bounds
if reason == reg.EX_WA_IB or reason == reg.T_IB:
print('Turning on')
self.led.value(True)
elif reason == reg.EX_WB_IA or reason == reg.T_IA:
print('Turning off')
self.led.value(False)
def al_cb(self, reg, reason):
if reason == reg.EN_WB or reason == reg.T_IA:
print('Alarm')
def test():
print('Tstat demo.')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -54,6 +54,7 @@ class Meter(Widget):
x1 = self.col + width
y0 = self.row
y1 = self.row + height
self.preshow(x0, y1, width, height) # Subclass draws regions
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
@ -66,3 +67,6 @@ class Meter(Widget):
else:
w = width / 2
display.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)
def preshow(self, x, y, width, height):
pass # For subclass

Wyświetl plik

@ -0,0 +1,124 @@
# tstat.py Extension to nanogui providing the Tstat class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Usage:
# from gui.widgets.tstat import Tstat
from gui.core.ugui import display
from gui.core.colors import *
from gui.widgets.meter import Meter
class Region:
# Callback reasons
EX_WB_IA = 1 # Exit region. Was below. Is above.
EX_WB_IB = 2 # Exit, was below, is below
EX_WA_IA = 4 # Exit, was above, is above.
EX_WA_IB = 8 # Exit, was above, is below
T_IA = 16 # Transit, is above
T_IB = 32 # Transit, is below
EN_WA = 64 # Entry, was above
EN_WB = 128 # Entry, was below
def __init__(self, tstat, vlo, vhi, color, callback, args):
self.tstat = tstat
if vlo >= vhi:
raise ValueError('TStat Region: vlo must be <= vhi')
self.vlo = vlo
self.vhi = vhi
self.color = color
self.cb = callback
self.args = args
self.is_in = False # Value is in region
self.fa = False # Entered from above
self.vprev = self.tstat.value()
def do_check(self, v):
cb = self.cb
args = self.args
if v < self.vlo:
if not self.is_in:
if self.vprev > self.vhi: # Low going transit
cb(self, self.T_IB, *args)
return # Was and is outside: no action.
# Was in the region, find direction of exit
self.is_in = False
reason = self.EX_WA_IB if self.fa else self.EX_WB_IB
cb(self, reason, *args)
elif v > self.vhi:
if not self.is_in:
if self.vprev < self.vlo:
cb(self, self.T_IA, *args)
return
# Was already in region
self.is_in = False
reason = self.EX_WA_IA if self.fa else self.EX_WB_IA
cb(self, reason, *args)
else: # v is in range
if self.is_in:
return # Nothing to do
self.is_in = True
if self.vprev > self.vhi:
self.fa = True # Save entry direction
cb(self, self.EN_WA, *args)
elif self.vprev < self.vlo:
self.fa = False
cb(self, self.EN_WB, *args)
def check(self, v):
self.do_check(v)
self.vprev = v # do_check gets value at prior check
def adjust(self, vlo, vhi):
old_vlo = self.vlo
old_vhi = self.vhi
self.vlo = vlo
self.vhi = vhi
vc = self.tstat.value()
self.tstat.draw = True
# Despatch cases where there is nothing to do.
# Outside both regions on same side
if vc < vlo and vc < old_vlo:
return
if vc > vhi and vc > old_vhi:
return
is_in = vlo <= vc <= vhi # Currently inside
if is_in and self.is_in: # Regions overlapped
return # Still inside so no action
if is_in: # Inside new region but not in old
self.check(vc) # Treat as if entering new region from previous value
else: # Outside new region
if not self.is_in: # Also outside old region
# Lay between old and new regions. Force
# a traverse of new
self.vprev = vlo - 0.1 if vc > vhi else vhi + 0.1
# If it was in old region treat as if leaving it
self.check(vc)
class Tstat(Meter):
def __init__(self, *args, **kwargs):
self.regions = set()
super().__init__(*args, **kwargs)
def add_region(self, vlo, vhi, color, callback, args=()):
reg = Region(self, vlo, vhi, color, callback, args)
self.regions.add(reg)
return reg
# Called by subclass prior to drawing scale and data
def preshow(self, x, y, width, height):
for r in self.regions:
ht = round(height * (r.vhi - r.vlo))
y1 = y - round(height * r.vhi)
display.fill_rect(x, y1, width, ht, r.color)
def value(self, n=None, color=None):
if n is None:
return super().value()
v = super().value(n, color)
for r in self.regions:
r.check(v)
return v

Wyświetl plik

@ -51,4 +51,4 @@ sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease, 5) # Encoder
display = Display(ssd, nxt, sel, prev, increase, decrease)

Wyświetl plik

@ -0,0 +1,54 @@
# ili9341_pico.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# As written, supports:
# ili9341 240x320 displays on Pi Pico
# Edit the driver import for other displays.
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING
# Pico Display
# GPIO Pin
# 3v3 36 Vin
# IO6 9 CLK Hardware SPI0
# IO7 10 DATA (AKA SI MOSI)
# IO8 11 DC
# IO9 12 Rst
# Gnd 13 Gnd
# IO10 14 CS
# Pushbuttons are wired between the pin and Gnd
# Pico pin Meaning
# 16 Operate current control
# 17 Decrease value of current control
# 18 Select previous control
# 19 Select next control
# 20 Increase value of current control
from machine import Pin, SPI, freq
import gc
from drivers.ili93xx.ili9341 import ILI9341 as SSD
freq(250_000_000) # RP2 overclock
# Create and export an SSD instance
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
prst = Pin(9, Pin.OUT, value=1)
pcs = Pin(10, Pin.OUT, value=1)
spi = SPI(0, baudrate=30_000_000)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, usd=True)
from gui.core.ugui import Display
# Create and export a Display instance
# Define control buttons
nxt = Pin(19, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease, 5) # Encoder