Files
home-assistant/notebooks/dishwasher_done.ipynb
T

715 KiB

In [1]:
import pandas as pd
from datetime import datetime
from zoneinfo import ZoneInfo

from utils.database import get_state

local_tz = ZoneInfo("Europe/Copenhagen")

df = get_state(
    entity_id="sensor.dishwasher_power",
    # limit=100,
    start_time=datetime(2026, 4, 1, 0, 0, tzinfo=local_tz),
    end_time=datetime(2026, 4, 13, 21, 0, tzinfo=local_tz),
)

# Convert time to Copenhagen timezone
df["time"] = df["time"].dt.tz_convert(local_tz)

# Convert state to numeric, forcing errors to NaN
df["state"] = pd.to_numeric(df["state"], errors="coerce")

df.head()
Out [1]:
time state
0 2026-04-13 19:11:26.464020+02:00 0.0
1 2026-04-13 19:10:23.729820+02:00 NaN
2 2026-04-13 13:23:15.374583+02:00 0.0
3 2026-04-13 12:34:36.715038+02:00 0.0
4 2026-04-13 12:34:34.650176+02:00 1.6
In [2]:
import plotly.express as px

# Create interactive line plot
fig = px.line(
    df,
    x="time",
    y="state",
    title="Power Consumption Over Time",
    labels={"time": "Time", "state": "Power (W)"},
)

# Customize hover template
fig.update_traces(hovertemplate="<b>Time:</b> %{x}<br><b>Power:</b> %{y:.2f} W<extra></extra>")

fig.update_layout(height=600, hovermode="x unified")

fig.show()
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]

Aggregate over 1 minute pieces to avoid varying sampling frequency

In [3]:
# Calculate running average with time-based window
window_size = "1min"  # Can use: '1min', '5min', '15min', '1h', etc.
df["state_smoothed"] = df.rolling(window=window_size, on="time", center=True)["state"].mean()

# Create plot with both raw and smoothed data
fig = px.line(
    df,
    x="time",
    y=["state", "state_smoothed"],
    title=f"Power Consumption - Raw vs Smoothed (window={window_size})",
    labels={"time": "Time", "value": "Power (W)"},
)

# Update trace names
fig.data[0].name = "Raw"
fig.data[1].name = "Smoothed"

# Customize hover template
fig.update_traces(hovertemplate="<b>Time:</b> %{x}<br><b>Power:</b> %{y:.2f} W<extra></extra>")

fig.update_layout(height=600, hovermode="x unified", legend_title_text="Data Type")

fig.show()
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]
In [4]:
# Calculate running average with time-based window
window_size = "5min"  # Can use: '1min', '5min', '15min', '1h', etc.
df["state_baseline"] = df.rolling(window=window_size, on="time")["state"].mean()

# Create plot with both raw and smoothed data
fig = px.line(
    df,
    x="time",
    y=["state", "state_baseline"],
    title=f"Power Consumption - Smoothed vs Baseline (window={window_size})",
    labels={"time": "Time", "value": "Power (W)"},
)

# Update trace names
fig.data[0].name = "Smoothed"
fig.data[1].name = "Baseline"

# Customize hover template
fig.update_traces(hovertemplate="<b>Time:</b> %{x}<br><b>Power:</b> %{y:.2f} W<extra></extra>")

fig.update_layout(height=600, hovermode="x unified", legend_title_text="Data Type")

fig.show()
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]
In [6]:
# Identify periods when power above 1W
threshold = 1

# Create boolean mask when starting burner
df["above_threshold"] = df["state_baseline"] > threshold

# Identify high-to-low transitions (when power drops from >1W to ≤1W)
# This occurs when current row is below threshold AND previous row was above threshold
df["high_to_low"] = (df["above_threshold"]) & df["above_threshold"].ne(df["above_threshold"].shift())

# Extract the transition moments
transitions = df[df["high_to_low"]][["time", "state_baseline"]]

print(f"Found {len(transitions)} high-to-low transitions:\n")
print(transitions.to_string(index=False))

# Show statistics about the transitions
if len(transitions) > 0:
    print(f"\n\nTransition Summary:")
    print(f"  Average power at transition: {transitions['state_baseline'].mean():.2f}W")
    print(f"  Min power at transition: {transitions['state_baseline'].min():.2f}W")
    print(f"  Max power at transition: {transitions['state_baseline'].max():.2f}W")
Found 4 high-to-low transitions:

                            time  state_baseline
2026-04-13 12:34:00.044517+02:00        3.933333
2026-04-10 00:10:10.660551+02:00        1.200000
2026-04-07 01:05:35.778996+02:00        1.300000
2026-04-04 13:55:00.034284+02:00        2.220000


Transition Summary:
  Average power at transition: 2.16W
  Min power at transition: 1.20W
  Max power at transition: 3.93W
In [7]:
# Visualize the high-to-low transitions on the power consumption plot
import plotly.graph_objects as go

fig = go.Figure()

# Add the main power consumption line
fig.add_trace(go.Scatter(
    x=df["time"], 
    y=df["state_baseline"],
    mode="lines",
    name="Power Consumption Baseline",
    line=dict(color="blue", width=1),
    hovertemplate="<b>Time:</b> %{x}<br><b>Power:</b> %{y:.2f} W<extra></extra>"
))

# Add threshold line
fig.add_hline(y=threshold, line_dash="dash", line_color="gray", 
              annotation_text=f"Threshold ({threshold}W)", 
              annotation_position="right")

# Add markers for high-to-low transitions
if len(transitions) > 0:
    fig.add_trace(go.Scatter(
        x=transitions["time"],
        y=transitions["state_baseline"],
        mode="markers",
        name="High→Low Transitions",
        marker=dict(color="red", size=10, symbol="circle"),
        hovertemplate="<b>Transition Time:</b> %{x}<br><b>Power:</b> %{y:.2f} W<extra></extra>"
    ))

fig.update_layout(
    title="Power Consumption with High-to-Low Transitions",
    xaxis_title="Time",
    yaxis_title="Power (W)",
    height=600,
    hovermode="x unified"
)

fig.show()
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]

Conclusion

Info message should be sent out when the washing machine 5min average drops below 1W.

Cell:
[Cell type raw - unsupported, skipped]
Cell:
[Cell type raw - unsupported, skipped]