Fix solar spacing, add power bar and PV curve views
This commit is contained in:
237
matrix.py
237
matrix.py
@@ -41,7 +41,7 @@ except:
|
||||
pass
|
||||
|
||||
# Number of views
|
||||
NUM_VIEWS = 5
|
||||
NUM_VIEWS = 7
|
||||
|
||||
# View names (Spanish)
|
||||
VIEW_NAMES = {
|
||||
@@ -50,6 +50,8 @@ VIEW_NAMES = {
|
||||
2: "Fecha",
|
||||
3: "Tesla",
|
||||
4: "Solar",
|
||||
5: "Energia",
|
||||
6: "Curva PV",
|
||||
}
|
||||
|
||||
# Spanish translations
|
||||
@@ -131,6 +133,8 @@ class Matrix64Display(SampleBase):
|
||||
self.hdd_temps = None
|
||||
self.tesla = None
|
||||
self.solar = None
|
||||
self.pv_history = [] # list of (timestamp, production_w) for today's curve
|
||||
self.pv_history_date = None # track current day to reset history
|
||||
|
||||
# View state
|
||||
self.current_view = 0
|
||||
@@ -255,6 +259,14 @@ class Matrix64Display(SampleBase):
|
||||
|
||||
self.tesla = get_tesla_status()
|
||||
self.solar = get_solar_status()
|
||||
# Record PV history for today's curve
|
||||
if self.solar and self.solar.get("production") is not None:
|
||||
now = datetime.datetime.now()
|
||||
today = now.date()
|
||||
if self.pv_history_date != today:
|
||||
self.pv_history = []
|
||||
self.pv_history_date = today
|
||||
self.pv_history.append((now.hour + now.minute / 60.0, self.solar["production"]))
|
||||
except Exception as e:
|
||||
print(f"Error updating data: {e}")
|
||||
|
||||
@@ -528,15 +540,16 @@ class Matrix64Display(SampleBase):
|
||||
graphics.DrawLine(canvas, 0, 10, 63, 10, line_color)
|
||||
|
||||
if self.solar:
|
||||
# Solar production (W)
|
||||
y = 18
|
||||
# Solar production
|
||||
production = self.solar.get("production")
|
||||
if production is not None:
|
||||
prod_color = graphics.Color(255, 200, 0) if production > 0 else graphics.Color(100, 100, 100)
|
||||
graphics.DrawText(canvas, data_font, 2, 20, label_color, "PV")
|
||||
prod_str = f"{int(production)}W"
|
||||
graphics.DrawText(canvas, data_font, 16, 20, prod_color, prod_str)
|
||||
graphics.DrawText(canvas, small_font, 1, y, label_color, "PV")
|
||||
graphics.DrawText(canvas, small_font, 14, y, prod_color, f"{int(production)}W")
|
||||
|
||||
# Battery % and power
|
||||
# Battery
|
||||
y += 8
|
||||
battery_pct = self.solar.get("battery_pct")
|
||||
battery_power = self.solar.get("battery_power")
|
||||
if battery_pct is not None:
|
||||
@@ -546,57 +559,225 @@ class Matrix64Display(SampleBase):
|
||||
bat_color = graphics.Color(255, 200, 0)
|
||||
else:
|
||||
bat_color = graphics.Color(255, 50, 50)
|
||||
graphics.DrawText(canvas, data_font, 2, 30, label_color, "Bat")
|
||||
graphics.DrawText(canvas, data_font, 20, 30, bat_color, f"{int(battery_pct)}%")
|
||||
graphics.DrawText(canvas, small_font, 1, y, label_color, "Bat")
|
||||
graphics.DrawText(canvas, small_font, 16, y, bat_color, f"{int(battery_pct)}%")
|
||||
if battery_power is not None:
|
||||
if battery_power < 0:
|
||||
bp_color = graphics.Color(0, 200, 255) # Charging (blue)
|
||||
bp_color = graphics.Color(0, 200, 255)
|
||||
bp_str = f"{int(abs(battery_power))}W"
|
||||
elif battery_power > 0:
|
||||
bp_color = graphics.Color(255, 150, 0) # Discharging (orange)
|
||||
bp_color = graphics.Color(255, 150, 0)
|
||||
bp_str = f"{int(battery_power)}W"
|
||||
else:
|
||||
bp_color = graphics.Color(100, 100, 100)
|
||||
bp_str = "0W"
|
||||
graphics.DrawText(canvas, data_font, 44, 30, bp_color, bp_str)
|
||||
graphics.DrawText(canvas, small_font, 38, y, bp_color, bp_str)
|
||||
|
||||
# Grid power (negative = exporting)
|
||||
# Grid
|
||||
y += 8
|
||||
grid_power = self.solar.get("grid_power")
|
||||
if grid_power is not None:
|
||||
if grid_power < 0:
|
||||
grid_color = graphics.Color(0, 255, 100) # Exporting (green)
|
||||
grid_color = graphics.Color(0, 255, 100)
|
||||
grid_label = "Exp"
|
||||
grid_str = f"{int(abs(grid_power))}W"
|
||||
else:
|
||||
grid_color = graphics.Color(255, 100, 100) # Importing (red)
|
||||
grid_color = graphics.Color(255, 100, 100)
|
||||
grid_label = "Imp"
|
||||
grid_str = f"{int(grid_power)}W"
|
||||
graphics.DrawText(canvas, data_font, 2, 40, label_color, grid_label)
|
||||
graphics.DrawText(canvas, data_font, 20, 40, grid_color, grid_str)
|
||||
graphics.DrawText(canvas, small_font, 1, y, label_color, grid_label)
|
||||
graphics.DrawText(canvas, small_font, 16, y, grid_color, grid_str)
|
||||
|
||||
# Home consumption (minus Tesla charger)
|
||||
# Home consumption (minus Tesla)
|
||||
y += 8
|
||||
load_power = self.solar.get("load_power")
|
||||
tesla_power = self.solar.get("tesla_power", 0)
|
||||
if load_power is not None:
|
||||
home_power = max(0, int(load_power - tesla_power))
|
||||
graphics.DrawText(canvas, data_font, 2, 50, label_color, "Casa")
|
||||
graphics.DrawText(canvas, data_font, 26, 50, humidity_color, f"{home_power}W")
|
||||
if tesla_power > 0:
|
||||
tesla_color = graphics.Color(200, 50, 50)
|
||||
graphics.DrawText(canvas, small_font, 2, 57, label_color, "Tesla")
|
||||
graphics.DrawText(canvas, small_font, 26, 57, tesla_color, f"{int(tesla_power)}W")
|
||||
graphics.DrawText(canvas, small_font, 1, y, label_color, "Casa")
|
||||
graphics.DrawText(canvas, small_font, 20, y, humidity_color, f"{home_power}W")
|
||||
|
||||
# Today's energy
|
||||
# Tesla charging
|
||||
if tesla_power > 0:
|
||||
y += 8
|
||||
tesla_color = graphics.Color(200, 50, 50)
|
||||
graphics.DrawText(canvas, small_font, 1, y, label_color, "Tesla")
|
||||
graphics.DrawText(canvas, small_font, 24, y, tesla_color, f"{int(tesla_power)}W")
|
||||
|
||||
# Today's energy (always at bottom)
|
||||
today = self.solar.get("today_energy")
|
||||
if today is not None:
|
||||
graphics.DrawText(canvas, data_font, 2, 60, label_color, "Hoy")
|
||||
graphics.DrawText(canvas, data_font, 20, 60, graphics.Color(255, 200, 0), f"{today:.1f}kWh")
|
||||
graphics.DrawText(canvas, small_font, 1, 63, label_color, "Hoy")
|
||||
graphics.DrawText(canvas, small_font, 16, 63, graphics.Color(255, 200, 0), f"{today:.1f}kWh")
|
||||
else:
|
||||
graphics.DrawText(canvas, data_font, 4, 32, label_color, "Sin datos")
|
||||
|
||||
if self.auto_cycle:
|
||||
graphics.DrawText(canvas, small_font, 58, 62, label_color, "A")
|
||||
|
||||
def draw_view_5(self, canvas, fonts, colors):
|
||||
"""Vista Energia - Power flow bars"""
|
||||
time_font, data_font, small_font = fonts
|
||||
time_color, humidity_color, hdd_color, label_color, line_color = colors
|
||||
|
||||
title = "Energia"
|
||||
title_len = len(title) * 5
|
||||
title_x = (64 - title_len) // 2
|
||||
graphics.DrawText(canvas, data_font, title_x, 8, time_color, title)
|
||||
graphics.DrawLine(canvas, 0, 10, 63, 10, line_color)
|
||||
|
||||
if not self.solar:
|
||||
graphics.DrawText(canvas, data_font, 4, 32, label_color, "Sin datos")
|
||||
return
|
||||
|
||||
production = self.solar.get("production", 0) or 0
|
||||
load_power = self.solar.get("load_power", 0) or 0
|
||||
tesla_power = self.solar.get("tesla_power", 0) or 0
|
||||
grid_power = self.solar.get("grid_power", 0) or 0
|
||||
battery_power = self.solar.get("battery_power", 0) or 0
|
||||
home_power = max(0, load_power - tesla_power)
|
||||
|
||||
bar_w = 60
|
||||
bar_x = 2
|
||||
|
||||
# --- Production bar (what PV generates) ---
|
||||
graphics.DrawText(canvas, small_font, 1, 18, graphics.Color(255, 200, 0), "Produccion")
|
||||
max_pv = max(production, 1)
|
||||
pv_len = min(bar_w, int(bar_w * production / max(max_pv, 1)))
|
||||
for y in range(20, 26):
|
||||
graphics.DrawLine(canvas, bar_x, y, bar_x + bar_w - 1, y, graphics.Color(15, 15, 15))
|
||||
if pv_len > 0:
|
||||
graphics.DrawLine(canvas, bar_x, y, bar_x + pv_len - 1, y, graphics.Color(255, 200, 0))
|
||||
graphics.DrawText(canvas, small_font, bar_x, 32, graphics.Color(200, 200, 200), f"{int(production)}W")
|
||||
|
||||
# --- Consumption bar (stacked: Home + Tesla) ---
|
||||
graphics.DrawText(canvas, small_font, 1, 38, humidity_color, "Consumo")
|
||||
total_consumption = home_power + tesla_power
|
||||
max_val = max(production, total_consumption, 1)
|
||||
for y in range(40, 46):
|
||||
graphics.DrawLine(canvas, bar_x, y, bar_x + bar_w - 1, y, graphics.Color(15, 15, 15))
|
||||
|
||||
if total_consumption > 0:
|
||||
home_len = int(bar_w * home_power / max_val)
|
||||
tesla_len = int(bar_w * tesla_power / max_val)
|
||||
# Home portion (blue)
|
||||
if home_len > 0:
|
||||
for y in range(40, 46):
|
||||
graphics.DrawLine(canvas, bar_x, y, bar_x + home_len - 1, y, graphics.Color(80, 160, 255))
|
||||
# Tesla portion (red)
|
||||
if tesla_len > 0:
|
||||
for y in range(40, 46):
|
||||
graphics.DrawLine(canvas, bar_x + home_len, y, bar_x + home_len + tesla_len - 1, y, graphics.Color(200, 50, 50))
|
||||
|
||||
# Legend
|
||||
graphics.DrawText(canvas, small_font, bar_x, 52, graphics.Color(80, 160, 255), f"Casa {int(home_power)}W")
|
||||
if tesla_power > 0:
|
||||
graphics.DrawText(canvas, small_font, 36, 52, graphics.Color(200, 50, 50), f"T {int(tesla_power)}W")
|
||||
|
||||
# Grid status at bottom
|
||||
if grid_power < 0:
|
||||
graphics.DrawText(canvas, small_font, 1, 62, graphics.Color(0, 255, 100), f"Export {int(abs(grid_power))}W")
|
||||
elif grid_power > 0:
|
||||
graphics.DrawText(canvas, small_font, 1, 62, graphics.Color(255, 100, 100), f"Import {int(grid_power)}W")
|
||||
else:
|
||||
graphics.DrawText(canvas, small_font, 1, 62, graphics.Color(100, 100, 100), "Red: 0W")
|
||||
|
||||
if self.auto_cycle:
|
||||
graphics.DrawText(canvas, small_font, 58, 62, label_color, "A")
|
||||
|
||||
def draw_view_6(self, canvas, fonts, colors):
|
||||
"""Vista Curva PV - Today's photovoltaic production curve"""
|
||||
time_font, data_font, small_font = fonts
|
||||
time_color, humidity_color, hdd_color, label_color, line_color = colors
|
||||
|
||||
title = "Curva PV"
|
||||
title_len = len(title) * 5
|
||||
title_x = (64 - title_len) // 2
|
||||
graphics.DrawText(canvas, data_font, title_x, 8, time_color, title)
|
||||
graphics.DrawLine(canvas, 0, 10, 63, 10, line_color)
|
||||
|
||||
# Graph area: x=2..61 (60px), y=14..57 (44px)
|
||||
graph_x = 2
|
||||
graph_w = 60
|
||||
graph_y_top = 14
|
||||
graph_y_bot = 57
|
||||
graph_h = graph_y_bot - graph_y_top
|
||||
|
||||
# Time axis: 6:00 to 21:00 (15 hours of daylight)
|
||||
t_start = 6.0
|
||||
t_end = 21.0
|
||||
t_range = t_end - t_start
|
||||
|
||||
# Axis lines
|
||||
graphics.DrawLine(canvas, graph_x, graph_y_bot, graph_x + graph_w - 1, graph_y_bot, line_color)
|
||||
graphics.DrawLine(canvas, graph_x, graph_y_top, graph_x, graph_y_bot, line_color)
|
||||
|
||||
# Time labels
|
||||
graphics.DrawText(canvas, small_font, graph_x, 63, label_color, "6")
|
||||
graphics.DrawText(canvas, small_font, graph_x + 18, 63, label_color, "10")
|
||||
graphics.DrawText(canvas, small_font, graph_x + 38, 63, label_color, "15")
|
||||
graphics.DrawText(canvas, small_font, graph_x + 52, 63, label_color, "20")
|
||||
|
||||
if not self.pv_history:
|
||||
graphics.DrawText(canvas, small_font, 10, 36, label_color, "Sin datos")
|
||||
return
|
||||
|
||||
# Find max production for scaling
|
||||
max_prod = max(p for _, p in self.pv_history)
|
||||
if max_prod <= 0:
|
||||
max_prod = 1
|
||||
|
||||
# Draw max value label
|
||||
if max_prod >= 1000:
|
||||
max_label = f"{max_prod / 1000:.1f}kW"
|
||||
else:
|
||||
max_label = f"{int(max_prod)}W"
|
||||
graphics.DrawText(canvas, small_font, graph_x + 1, graph_y_top + 5, graphics.Color(255, 200, 0), max_label)
|
||||
|
||||
# Plot curve - bucket data points into pixel columns
|
||||
buckets = [[] for _ in range(graph_w)]
|
||||
for t, p in self.pv_history:
|
||||
if t_start <= t <= t_end:
|
||||
col = int((t - t_start) / t_range * (graph_w - 1))
|
||||
col = max(0, min(graph_w - 1, col))
|
||||
buckets[col].append(p)
|
||||
|
||||
prev_y = None
|
||||
pv_color = graphics.Color(255, 200, 0)
|
||||
fill_color = graphics.Color(60, 50, 0)
|
||||
|
||||
for col in range(graph_w):
|
||||
if buckets[col]:
|
||||
avg_p = sum(buckets[col]) / len(buckets[col])
|
||||
pixel_y = graph_y_bot - int((avg_p / max_prod) * graph_h)
|
||||
pixel_y = max(graph_y_top, min(graph_y_bot, pixel_y))
|
||||
|
||||
# Fill area under curve
|
||||
if pixel_y < graph_y_bot:
|
||||
for fy in range(pixel_y + 1, graph_y_bot):
|
||||
canvas.SetPixel(graph_x + col, fy, 60, 50, 0)
|
||||
|
||||
# Draw point
|
||||
canvas.SetPixel(graph_x + col, pixel_y, 255, 200, 0)
|
||||
|
||||
# Connect to previous point
|
||||
if prev_y is not None:
|
||||
dy = pixel_y - prev_y
|
||||
steps = max(abs(dy), 1)
|
||||
for s in range(1, steps):
|
||||
iy = prev_y + int(dy * s / steps)
|
||||
canvas.SetPixel(graph_x + col, iy, 255, 200, 0)
|
||||
|
||||
prev_y = pixel_y
|
||||
|
||||
# Current production value
|
||||
if self.solar and self.solar.get("production") is not None:
|
||||
cur = int(self.solar["production"])
|
||||
graphics.DrawText(canvas, small_font, 30, graph_y_top + 5, graphics.Color(200, 200, 200), f"{cur}W")
|
||||
|
||||
if self.auto_cycle:
|
||||
graphics.DrawText(canvas, small_font, 58, 62, label_color, "A")
|
||||
|
||||
def run(self):
|
||||
canvas = self.matrix.CreateFrameCanvas()
|
||||
|
||||
@@ -659,6 +840,10 @@ class Matrix64Display(SampleBase):
|
||||
self.draw_view_3(canvas, fonts, colors)
|
||||
elif self.current_view == 4:
|
||||
self.draw_view_4(canvas, fonts, colors)
|
||||
elif self.current_view == 5:
|
||||
self.draw_view_5(canvas, fonts, colors)
|
||||
elif self.current_view == 6:
|
||||
self.draw_view_6(canvas, fonts, colors)
|
||||
|
||||
# Camera alert overlay (highest priority)
|
||||
if self.mqtt.is_alert_active():
|
||||
|
||||
Reference in New Issue
Block a user