OBP60v/pages/barograph.py

367 lines
11 KiB
Python

"""
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 = 9999
vmax = 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()