""" 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()