Erstveröffentlichung Weihnachten 2024
This commit is contained in:
366
pages/barograph.py
Normal file
366
pages/barograph.py
Normal file
@@ -0,0 +1,366 @@
|
||||
"""
|
||||
|
||||
Siehe auch: Steamrock Digital Barometer
|
||||
|
||||
Meßwert alls 15 Minuten:
|
||||
Es wird in hPa gemessen mit einer Nachkommastelle
|
||||
84 Stunden * 4 Werte je Stunde = 336 Meßwerte
|
||||
Tendenzwert über 3 Stunden
|
||||
|
||||
Je Zoomstufe wird ein eigener Buffer vorgehalten um ein sauberes
|
||||
Diagramm zu erhalten. Überall gilt: Je Pixel ein Meßwert.
|
||||
|
||||
Drucktendenz:
|
||||
- 1 hour tendency
|
||||
- 3 hour tendency
|
||||
|
||||
Verschiedene Datenquellen auswählbar:
|
||||
- intern (BME280, BMP280)
|
||||
- N2K generisch
|
||||
- Historie von
|
||||
- Yacht devices
|
||||
- Capteurs?
|
||||
|
||||
Das Diagramm wird mit Ursprung rechts unten (x0, y0) gezeichnet,
|
||||
da die Werte in der Vergangenhait liegen, also links vom Ursprung.
|
||||
|
||||
Damit eine saubere Skala auf der Y-Achse erreicht wird, gibt einige
|
||||
feste Skalierungen.
|
||||
Standard: 20hPa von unten nach oben, z.B. 1015, 1020, 1025, 1030, 1035
|
||||
|
||||
"""
|
||||
|
||||
import time
|
||||
import cairo
|
||||
from .page import Page
|
||||
|
||||
class Barograph(Page):
|
||||
|
||||
def __init__(self, pageno, cfg, boatdata):
|
||||
super().__init__(pageno, cfg, boatdata)
|
||||
# Meßwert alle 15 Minuten:
|
||||
# 84 Stunden * 4 Werte je Stunde = 336 Meßwerte
|
||||
self.bd = boatdata
|
||||
self.source = 'I' # (I)ntern, e(X)tern
|
||||
self.zoom = (1, 2, 3, 6, 12)
|
||||
self.zoomindex = 4
|
||||
self.series = (75, 150, 300, 600, 900)
|
||||
|
||||
# Y-Axis
|
||||
self.vmin = 0
|
||||
self.vmax = 0
|
||||
self.scalemin = 1000
|
||||
self.scalemax = 1020
|
||||
self.scalestep = 5
|
||||
|
||||
# Tendenzwert über 3 Stunden
|
||||
self.hist3 = None
|
||||
self.hist1 = None
|
||||
|
||||
self.buttonlabel[1] = '+'
|
||||
self.buttonlabel[2] = '-'
|
||||
self.buttonlabel[5] = 'SRC'
|
||||
|
||||
self.refresh = time.time() - 30
|
||||
|
||||
def handle_key(self, buttonid):
|
||||
# TODO Serie auswählen aufgrund Zoomlevel
|
||||
if buttonid == 1:
|
||||
# Zoom in
|
||||
if self.zoomindex > 0:
|
||||
self.zoomindex -= 1
|
||||
self.refresh = time.time() - 30
|
||||
elif buttonid == 2:
|
||||
# Zoom out
|
||||
if self.zoomindex < len(self.zoom) - 1:
|
||||
self.zoomindex += 1
|
||||
self.refresh = time.time() - 30
|
||||
if buttonid == 5:
|
||||
# Source
|
||||
if self.source == 'I':
|
||||
self.source = 'X'
|
||||
else:
|
||||
self.source = 'I'
|
||||
|
||||
# Testausgabe der Datenerfassung
|
||||
data = []
|
||||
vmin = data[0]
|
||||
vmax = data[0]
|
||||
i = self.series[self.zoomindex]
|
||||
for value in self.bd.history['press'].series[i].get():
|
||||
v = value / 10
|
||||
data.append(v)
|
||||
if v < vmin and v != 0:
|
||||
vmin = v
|
||||
elif v > vmax and v != 0:
|
||||
vmax = v
|
||||
print(f"Werte: vmin={vmin}, vmax={vmax}")
|
||||
ymin, ymax, step = self.getYScale(vmin, vmax)
|
||||
print(f"Skala: ymin={ymin}, ymax={ymax}, step={step}")
|
||||
print(f"zoomindex={self.zoomindex}, series={self.series[self.zoomindex]}")
|
||||
|
||||
hist1a = self.bd.history['press'].series[i].getvalue(3600)
|
||||
hist1b = self.bd.history['press'].series[i].getvalue3(3600)
|
||||
trend1 = data[0] - hist1b
|
||||
print(f"{hist1a} / {hist1b} -> Trend1: {trend1:.1f}")
|
||||
|
||||
def loadData(self):
|
||||
"""
|
||||
Transfer data from history to page buffer
|
||||
set y-axis according to data
|
||||
"""
|
||||
self.data = []
|
||||
self.vmin = 9999
|
||||
self.vmax = 0
|
||||
i = self.series[self.zoomindex]
|
||||
for value in self.bd.history['press'].series[i].get():
|
||||
v = value / 10
|
||||
self.data.append(v)
|
||||
if v < self.vmin and v != 0:
|
||||
self.vmin = v
|
||||
elif v > self.vmax and v != 0:
|
||||
self.vmax = v
|
||||
self.scalemin, self.scalemax, self.scalestep = self.getYScale(self.vmin, self.vmax)
|
||||
return True
|
||||
|
||||
def drawTrend(self, ctx, code, x, y, w):
|
||||
"""
|
||||
One hour Trend
|
||||
0: Stationary <= 1 hPa
|
||||
1: Rising >1 and <= 2 hPa
|
||||
2: Rising fast >2 and <= 3 hPa
|
||||
3: Rising very fast >3 hPa
|
||||
-1: Falling
|
||||
-2: Falling fast
|
||||
-3: Falling very fast
|
||||
"""
|
||||
trend1map = {
|
||||
-3: "Falling_Very_Fast.png", # > 3 hPa
|
||||
-2: "Falling_Fast.png", # > 2 and <= 3 hPa
|
||||
-1: "Falling.png", # > 1 and <= 2 hPa
|
||||
0: "Stationary.png", # <= +/- 1 hPa
|
||||
1: "Rising.png", # < -1 and >= -2 hPa
|
||||
2: "Rising_Fast.png", # < -2 and >= -3 hPa
|
||||
3: "Rising_Very_Fast.png" # < -3 hPa
|
||||
}
|
||||
|
||||
if code == 0:
|
||||
# Pfeil horizontal rechts
|
||||
ctx.move_to(x, y - w / 2)
|
||||
ctx.line_to(x + w, y - w / 2)
|
||||
ctx.draw()
|
||||
# Position merken
|
||||
ctx.line_to(x - w / 4, y - w)
|
||||
ctx.line_to(x - w / 4, y)
|
||||
ctx.line_to(x + w, y - w / 2)
|
||||
ctx.fill()
|
||||
elif code == 1:
|
||||
# Pfeil schräg nach oben
|
||||
pass
|
||||
elif code == 2:
|
||||
# Pfeil gerade nach oben
|
||||
pass
|
||||
elif code == 3:
|
||||
# Doppelpfeil nach oben
|
||||
pass
|
||||
elif code == -1:
|
||||
# Pfeil schräg nach unten
|
||||
pass
|
||||
elif code == -2:
|
||||
# Pfeil gerade nach unten
|
||||
pass
|
||||
elif code == -3:
|
||||
# Doppelpfeil nach unten
|
||||
pass
|
||||
|
||||
def drawWMOCode(self, ctx, code, x, y, w):
|
||||
"""
|
||||
Three hour code
|
||||
Code 0 to 8:
|
||||
0: Increasing, then decreasing; athmospheric pressure the same
|
||||
as or higher than three hours ago
|
||||
1: Increasing then steady; or increasing, then increasing more
|
||||
slowly; athmospheric pressure now higher than three hours ago
|
||||
2: Increasing (steadily or unsteadily); athmospheric pressure
|
||||
now higher than three hours ago
|
||||
3: Decreasing or steady, then increasing; or increasing then
|
||||
increasing more rapidly; athmospheric pressure now higher
|
||||
than three hours ago
|
||||
4: Steady; athmospheric pressure is the same as three hours ago
|
||||
5: Decreasing, then increasing; athmospheric pressure now is the
|
||||
same as or lower than three hours ago
|
||||
6:
|
||||
7:
|
||||
8:
|
||||
"""
|
||||
pass
|
||||
|
||||
def getYScale(self, vmin, vmax):
|
||||
# Y-Achse aufgrund Meßwerten einstellen
|
||||
diff = vmax - vmin
|
||||
if diff < 20:
|
||||
step = 5
|
||||
elif diff <= 40:
|
||||
step = 10
|
||||
else:
|
||||
step = 15
|
||||
vmin = int(vmin - (vmin % step)) # Nächstes Vielfaches nach oben
|
||||
vmax = int(vmax + step - (vmax % step)) # Nächstes Vielfaches nach unten
|
||||
return (vmin, vmax, step)
|
||||
|
||||
def draw(self, ctx):
|
||||
"""
|
||||
Darstellung angelehnt an klassisches Gerät
|
||||
Daten werden im nichtflüchtigen Speicher gehalten
|
||||
Da sich die Daten langsam verändern, reicht es, diese z.B. nur alle
|
||||
30 Sekunden oder langsamer zu laden.
|
||||
Der aktuelle Wert oben ist natürlich nicht alt.
|
||||
|
||||
Datenreihen
|
||||
- 1 Woche, stündlich: 7 * 24 = 168 Meßwerte
|
||||
- 1 Tag, alle 10 min: 24 * 6 = 144 Meßwerte
|
||||
Der Druck wird in zwei Bytes abgespeichert. Es wird eine Nachkommastelle
|
||||
verwendet. Um ohne Fließkommazahlen auszukommen wird der Maßwert einfach
|
||||
mit 10 multipliziert.
|
||||
|
||||
Darstellung wie Steamrock:
|
||||
1 Pixel entspricht einem Meßwert alle 15min.
|
||||
1 Tag hat dementsprechend eine Breite von 48px
|
||||
"""
|
||||
|
||||
timestamp = time.time()
|
||||
if timestamp - self.refresh >= 30:
|
||||
self.refresh = timestamp
|
||||
self.loadData()
|
||||
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.select_font_face("Ubuntu", cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD)
|
||||
|
||||
# Datenquelle rechts oben
|
||||
ctx.set_font_size(16)
|
||||
ctx.move_to(330, 50)
|
||||
if self.source == 'I':
|
||||
ctx.show_text("BMP280")
|
||||
else:
|
||||
ctx.show_text("N2K Bus")
|
||||
ctx.stroke()
|
||||
# Zoomstufe
|
||||
datastep = self.series[self.zoomindex]
|
||||
if datastep > 120:
|
||||
if datastep % 60 == 0:
|
||||
fmt = "{:.0f} min"
|
||||
else:
|
||||
fmt = "{:.1f} min"
|
||||
datastep /= 60
|
||||
else:
|
||||
fmt = '{} s'
|
||||
self.draw_text_center(ctx, 360, 62, fmt.format(datastep))
|
||||
|
||||
# Aktueller Luftdruck hPa
|
||||
ctx.set_font_size(32)
|
||||
self.draw_text_center(ctx, 200, 40, self.bd.pressure.format())
|
||||
#self.draw_text_center(ctx, 200, 40, "1019.2")
|
||||
ctx.set_font_size(16)
|
||||
self.draw_text_center(ctx, 200, 62, "hPa")
|
||||
|
||||
# Trend
|
||||
ctx.set_font_size(16)
|
||||
# TODO Trend linie
|
||||
#trend =
|
||||
self.draw_text_center(ctx, 295, 62, "0.0")
|
||||
|
||||
# min/max
|
||||
ctx.move_to(10, 38)
|
||||
ctx.show_text(f"min: {self.vmin}")
|
||||
ctx.move_to(10, 50)
|
||||
ctx.show_text(f"max: {self.vmax}")
|
||||
|
||||
# Alarm
|
||||
self.draw_text_center(ctx, 70, 62, "Alarm Off")
|
||||
|
||||
# Hintergrundrahmen
|
||||
ctx.set_line_width(2)
|
||||
|
||||
ctx.move_to(0, 75)
|
||||
ctx.line_to(400, 75)
|
||||
|
||||
ctx.move_to(130, 20)
|
||||
ctx.line_to(130, 75)
|
||||
|
||||
ctx.move_to(270, 20)
|
||||
ctx.line_to(270, 75)
|
||||
|
||||
ctx.move_to(325, 20)
|
||||
ctx.line_to(325, 75)
|
||||
|
||||
ctx.stroke()
|
||||
|
||||
|
||||
# Diagramm
|
||||
# --------
|
||||
ymin = self.scalemin
|
||||
ymax = self.scalemax
|
||||
yn = self.scalestep
|
||||
ystep = (ymax - ymin) / yn
|
||||
|
||||
xstep = 48
|
||||
|
||||
# Ursprung ist rechts unten
|
||||
x0 = 350
|
||||
y0 = 270
|
||||
w = 7 * 48
|
||||
h = 180
|
||||
|
||||
ctx.set_line_width(1)
|
||||
ctx.rectangle(x0 - w + 0.5, y0 - h + 0.5, w, h)
|
||||
ctx.stroke()
|
||||
|
||||
# X-Achse sind Stunden
|
||||
xn = 0
|
||||
for xt in [x * -1 * self.zoom[self.zoomindex] for x in range(1,7)]:
|
||||
xn += 1
|
||||
ctx.move_to(x0 - xn * xstep + 0.5, y0)
|
||||
ctx.line_to(x0 - xn * xstep + 0.5, y0 - h)
|
||||
ctx.stroke()
|
||||
self.draw_text_center(ctx, x0 - xn * xstep + 0.5, y0 - 8, str(xt), fill=True)
|
||||
ctx.stroke()
|
||||
#for x in (1, 2, 3, 4, 5, 6):
|
||||
# ctx.move_to(x0 - x * 48 + 0.5, y0 + 0.5)
|
||||
# ctx.line_to(x0 - x * 48 + 0.5, y0 - h + 0.5)
|
||||
#ctx.stroke()
|
||||
|
||||
# Y-Achse
|
||||
ctx.move_to(x0 + 5.5, y0 + 0.5)
|
||||
ctx.line_to(x0 + 5.5, y0 - h)
|
||||
ctx.move_to(x0 - w - 5.5, y0 + 0.5)
|
||||
ctx.line_to(x0 - w - 5.5, y0 -h )
|
||||
ctx.stroke()
|
||||
|
||||
dy = 9 # Pixel je hPa
|
||||
ysmin = self.scalemin
|
||||
ysmax = self.scalemax
|
||||
|
||||
y = y0 + 0.5
|
||||
ystep = self.scalestep
|
||||
ys = ysmin
|
||||
while y >= y0 - h:
|
||||
if ys % ystep == 0:
|
||||
ctx.move_to(x0 + 10, y + 5.5)
|
||||
ctx.show_text(str(ys))
|
||||
ctx.move_to(x0 - w - 5, y)
|
||||
ctx.line_to(x0 + 5, y)
|
||||
else:
|
||||
ctx.move_to(x0, y)
|
||||
ctx.line_to(x0 + 5, y)
|
||||
ctx.move_to(x0 - w - 5, y)
|
||||
ctx.line_to(x0 - w, y)
|
||||
y -= dy
|
||||
ys += 1
|
||||
ctx.stroke()
|
||||
|
||||
# Meßdaten
|
||||
for v in self.data:
|
||||
x0 -= 1
|
||||
if v > 0:
|
||||
ctx.rectangle(x0, y0 - (v - ysmin) * dy, 1.5, 1.5)
|
||||
ctx.fill()
|
||||
Reference in New Issue
Block a user