462 lines
23 KiB
Python
462 lines
23 KiB
Python
"""Constants for the WashData integration."""
|
|
|
|
DOMAIN = "ha_washdata"
|
|
|
|
# Configuration keys
|
|
CONF_POWER_SENSOR = "power_sensor"
|
|
CONF_NAME = "name"
|
|
CONF_MIN_POWER = "min_power"
|
|
CONF_OFF_DELAY = "off_delay"
|
|
CONF_NOTIFY_SERVICE = "notify_service" # Deprecated - kept for migration only
|
|
CONF_NOTIFY_ACTIONS = "notify_actions"
|
|
CONF_NOTIFY_PEOPLE = "notify_people"
|
|
CONF_NOTIFY_ONLY_WHEN_HOME = "notify_only_when_home"
|
|
CONF_NOTIFY_FIRE_EVENTS = "notify_fire_events"
|
|
CONF_NOTIFY_EVENTS = "notify_events" # Deprecated - kept for migration only
|
|
CONF_NOTIFY_START_SERVICES = "notify_start_services"
|
|
CONF_NOTIFY_FINISH_SERVICES = "notify_finish_services"
|
|
CONF_NOTIFY_LIVE_SERVICES = "notify_live_services"
|
|
CONF_NO_UPDATE_ACTIVE_TIMEOUT = "no_update_active_timeout"
|
|
CONF_LOW_POWER_NO_UPDATE_TIMEOUT = "low_power_no_update_timeout"
|
|
CONF_SMOOTHING_WINDOW = "smoothing_window"
|
|
CONF_SAMPLING_INTERVAL = "sampling_interval"
|
|
CONF_START_DURATION_THRESHOLD = (
|
|
"start_duration_threshold" # Debounce for start detection
|
|
)
|
|
CONF_DEVICE_TYPE = "device_type"
|
|
CONF_PROFILE_DURATION_TOLERANCE = "profile_duration_tolerance"
|
|
|
|
|
|
CONF_INTERRUPTED_MIN_SECONDS = "interrupted_min_seconds" # Internal use only
|
|
CONF_PROGRESS_RESET_DELAY = "progress_reset_delay"
|
|
CONF_LEARNING_CONFIDENCE = "learning_confidence"
|
|
CONF_DURATION_TOLERANCE = "duration_tolerance"
|
|
CONF_AUTO_LABEL_CONFIDENCE = "auto_label_confidence"
|
|
CONF_AUTO_MAINTENANCE = "auto_maintenance"
|
|
CONF_PROFILE_MATCH_INTERVAL = "profile_match_interval"
|
|
CONF_PROFILE_MATCH_MIN_DURATION_RATIO = "profile_match_min_duration_ratio"
|
|
CONF_PROFILE_MATCH_MAX_DURATION_RATIO = "profile_match_max_duration_ratio"
|
|
CONF_MAX_PAST_CYCLES = "max_past_cycles"
|
|
CONF_MAX_FULL_TRACES_PER_PROFILE = "max_full_traces_per_profile"
|
|
CONF_MAX_FULL_TRACES_UNLABELED = "max_full_traces_unlabeled"
|
|
CONF_WATCHDOG_INTERVAL = "watchdog_interval" # Derived from sampling_interval
|
|
CONF_MATCH_PERSISTENCE = "match_persistence"
|
|
CONF_COMPLETION_MIN_SECONDS = "completion_min_seconds"
|
|
CONF_NOTIFY_BEFORE_END_MINUTES = "notify_before_end_minutes"
|
|
CONF_APPLY_SUGGESTIONS = "apply_suggestions"
|
|
CONF_RUNNING_DEAD_ZONE = "running_dead_zone" # Seconds after start to ignore power dips
|
|
CONF_END_REPEAT_COUNT = "end_repeat_count" # Number of times end condition must be met
|
|
CONF_SHOW_ADVANCED = "show_advanced" # Toggle advanced settings
|
|
CONF_MIN_OFF_GAP = "min_off_gap" # Minimum gap to separate cycles (seconds)
|
|
CONF_START_ENERGY_THRESHOLD = "start_energy_threshold" # Wh required to confirm start
|
|
CONF_END_ENERGY_THRESHOLD = "end_energy_threshold" # Wh allowed during end candidates
|
|
CONF_START_THRESHOLD_W = "start_threshold_w" # Custom power threshold for STARTING
|
|
CONF_STOP_THRESHOLD_W = (
|
|
"stop_threshold_w" # Custom power threshold for ENDING (hysteresis)
|
|
)
|
|
CONF_EXPOSE_DEBUG_ENTITIES = "expose_debug_entities" # Expose detailed debug sensors
|
|
CONF_SAVE_DEBUG_TRACES = (
|
|
"save_debug_traces" # Improve historical cycle data with rich debug info
|
|
)
|
|
# Cycle interruption detection settings (not exposed in UI, but used internally)
|
|
CONF_ABRUPT_DROP_WATTS = "abrupt_drop_watts" # Power cliff threshold for interrupted status
|
|
CONF_ABRUPT_DROP_RATIO = "abrupt_drop_ratio" # Relative drop ratio for interrupted status
|
|
CONF_ABRUPT_HIGH_LOAD_FACTOR = "abrupt_high_load_factor" # High load factor threshold
|
|
CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD = "auto_tune_noise_events_threshold" # Noise events before auto-tune
|
|
CONF_EXTERNAL_END_TRIGGER_ENABLED = "external_end_trigger_enabled" # Enable external cycle end trigger
|
|
CONF_EXTERNAL_END_TRIGGER = "external_end_trigger" # Binary sensor entity for external cycle end
|
|
CONF_EXTERNAL_END_TRIGGER_INVERTED = "external_end_trigger_inverted" # Invert external trigger logic (trigger on OFF)
|
|
CONF_ANTI_WRINKLE_ENABLED = "anti_wrinkle_enabled" # Dryer anti-wrinkle shielding
|
|
CONF_ANTI_WRINKLE_MAX_POWER = "anti_wrinkle_max_power" # W threshold for anti-wrinkle spikes
|
|
CONF_ANTI_WRINKLE_MAX_DURATION = "anti_wrinkle_max_duration" # Seconds to treat as anti-wrinkle
|
|
CONF_ANTI_WRINKLE_EXIT_POWER = "anti_wrinkle_exit_power" # W threshold for true-off exit
|
|
CONF_DELAY_START_DETECT_ENABLED = "delay_start_detect_enabled" # Enable delayed-start detection
|
|
CONF_DELAY_CONFIRM_SECONDS = "delay_confirm_seconds" # Seconds power must stay in standby band before DELAY_WAIT engages
|
|
CONF_DELAY_TIMEOUT_HOURS = "delay_timeout_hours" # Safety timeout (hours) while waiting to start
|
|
|
|
# Deprecated since 0.4.5: drain-spike model replaced by band-based DELAY_WAIT.
|
|
# Kept only so older Store/options blobs don't raise KeyError during migration.
|
|
CONF_DELAY_DRAIN_MIN_POWER = "delay_drain_min_power"
|
|
CONF_DELAY_DRAIN_MAX_POWER = "delay_drain_max_power"
|
|
CONF_DELAY_DRAIN_MAX_DURATION = "delay_drain_max_duration"
|
|
|
|
|
|
NOTIFY_EVENT_START = "cycle_start"
|
|
NOTIFY_EVENT_FINISH = "cycle_finish"
|
|
NOTIFY_EVENT_LIVE = "cycle_live"
|
|
NOTIFY_EVENT_CLEAN = "cycle_clean" # Laundry still inside after cycle ends
|
|
|
|
CONF_NOTIFY_TITLE = "notify_title"
|
|
CONF_NOTIFY_ICON = "notify_icon"
|
|
CONF_NOTIFY_START_MESSAGE = "notify_start_message"
|
|
CONF_NOTIFY_FINISH_MESSAGE = "notify_finish_message"
|
|
CONF_NOTIFY_PRE_COMPLETE_MESSAGE = "notify_pre_complete_message"
|
|
CONF_NOTIFY_LIVE_INTERVAL_SECONDS = "notify_live_interval_seconds"
|
|
CONF_NOTIFY_LIVE_OVERRUN_PERCENT = "notify_live_overrun_percent"
|
|
CONF_NOTIFY_LIVE_CHRONOMETER = "notify_live_chronometer"
|
|
CONF_NOTIFY_REMINDER_MESSAGE = "notify_reminder_message" # Distinct one-time pre-end alert
|
|
CONF_NOTIFY_TIMEOUT_SECONDS = "notify_timeout_seconds" # Auto-dismiss after N seconds (0 = never)
|
|
CONF_NOTIFY_CHANNEL = "notify_channel" # Android channel for status/live/reminder
|
|
CONF_NOTIFY_FINISH_CHANNEL = "notify_finish_channel" # Distinct Android channel for finished/clean
|
|
CONF_ENERGY_PRICE_STATIC = "energy_price_static"
|
|
CONF_ENERGY_PRICE_ENTITY = "energy_price_entity"
|
|
|
|
# Door sensor & pause
|
|
CONF_DOOR_SENSOR_ENTITY = "door_sensor_entity" # Optional binary_sensor for machine door
|
|
CONF_PAUSE_CUTS_POWER = "pause_cuts_power" # Also turn off switch entity when pausing
|
|
CONF_SWITCH_ENTITY = "switch_entity" # Optional switch entity toggled on pause/resume
|
|
CONF_NOTIFY_UNLOAD_DELAY_MINUTES = "notify_unload_delay_minutes" # Minutes before "laundry waiting" nag
|
|
CONF_NOTIFY_UNLOAD_MESSAGE = "notify_unload_message" # Template for the clean-laundry nag message
|
|
|
|
# Optional link to an existing HA device (e.g. the smart plug or appliance).
|
|
# When set, the WashData device is exposed as "Connected via <device>" through
|
|
# the device registry's via_device relationship. Stores a device registry id.
|
|
CONF_LINKED_DEVICE = "linked_device"
|
|
|
|
DEFAULT_NOTIFY_TITLE = "WashData: {device}"
|
|
DEFAULT_NOTIFY_START_MESSAGE = "{device} started."
|
|
DEFAULT_NOTIFY_FINISH_MESSAGE = "{device} finished. Duration: {duration}m."
|
|
DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE = "{device}: Less than {minutes} minutes remaining."
|
|
DEFAULT_NOTIFY_REMINDER_MESSAGE = "{device}: about {minutes} minutes left."
|
|
DEFAULT_NOTIFY_LIVE_WAITING_MESSAGE = "{device}: No profile matched yet."
|
|
DEFAULT_NOTIFY_ONLY_WHEN_HOME = False
|
|
DEFAULT_NOTIFY_FIRE_EVENTS = True
|
|
DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS = 300
|
|
DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT = 20
|
|
DEFAULT_NOTIFY_LIVE_CHRONOMETER = False
|
|
DEFAULT_NOTIFY_TIMEOUT_SECONDS = 0 # 0 = notifications never auto-dismiss
|
|
DEFAULT_NOTIFY_CHANNEL = "" # Empty = omit channel (companion app default)
|
|
DEFAULT_NOTIFY_FINISH_CHANNEL = "" # Empty = reuse status channel
|
|
DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES = 60 # 1 hour before "still waiting" nag notification
|
|
DEFAULT_NOTIFY_UNLOAD_MESSAGE = "{device} finished {duration}m ago - laundry is still inside."
|
|
|
|
# Defaults
|
|
DEFAULT_MIN_POWER = 2.0 # Watts
|
|
DEFAULT_OFF_DELAY = 180 # Seconds (3 minutes, safer for 60s polling)
|
|
DEFAULT_NAME = "Washing Machine"
|
|
# Seconds without updates while active before forced stop (publish-on-change sockets)
|
|
DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT = 600 # 10 minutes
|
|
DEFAULT_SMOOTHING_WINDOW = 2
|
|
DEFAULT_SAMPLING_INTERVAL = 30.0 # Seconds
|
|
DEFAULT_START_DURATION_THRESHOLD = 5.0 # Seconds (debounce)
|
|
DEFAULT_START_ENERGY_THRESHOLD = 0.2 # Wh - Require some energy accumulation before starting
|
|
DEFAULT_END_ENERGY_THRESHOLD = 0.05 # Wh - Require effectively zero energy to end
|
|
DEFAULT_DEVICE_TYPE = "washing_machine"
|
|
DEFAULT_PROFILE_DURATION_TOLERANCE = 0.25
|
|
|
|
DEFAULT_INTERRUPTED_MIN_SECONDS = 150 # Internal use only, not exposed
|
|
|
|
DEFAULT_PROGRESS_RESET_DELAY = 1800 # Seconds (30 minutes state expiry/unload window)
|
|
DEFAULT_LEARNING_CONFIDENCE = 0.6 # Minimum confidence to request user verification
|
|
DEFAULT_DURATION_TOLERANCE = 0.10 # Allow ±10% duration variance before flagging
|
|
DEFAULT_AUTO_LABEL_CONFIDENCE = 0.9 # High confidence auto-label threshold
|
|
DEFAULT_AUTO_MAINTENANCE = True # Enable nightly cleanup by default
|
|
DEFAULT_COMPLETION_MIN_SECONDS = 600 # 10 minutes
|
|
DEFAULT_NOTIFY_BEFORE_END_MINUTES = 0 # Disabled
|
|
DEFAULT_PROFILE_MATCH_INTERVAL = (
|
|
300 # Seconds between profile matching attempts (5 minutes)
|
|
)
|
|
DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO = 0.10 # Allow match after 10% of expected duration
|
|
DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO = (
|
|
1.3 # Maximum duration ratio (130% of profile) - hidden default
|
|
)
|
|
DEFAULT_MAX_PAST_CYCLES = 200
|
|
DEFAULT_MAX_FULL_TRACES_PER_PROFILE = 20
|
|
DEFAULT_MAX_FULL_TRACES_UNLABELED = 20
|
|
DEFAULT_WATCHDOG_INTERVAL = 30 # Derived: 2 * sampling_interval + 1
|
|
DEFAULT_MATCH_PERSISTENCE = 3
|
|
DEFAULT_RUNNING_DEAD_ZONE = 3 # Seconds after start to ignore power dips
|
|
DEFAULT_END_REPEAT_COUNT = 1 # 1 = current behavior (no repeat required)
|
|
|
|
# Matching & Termination Stability
|
|
DEFAULT_MATCH_REVERT_RATIO = 0.4 # Drop from peak score to revert to detecting
|
|
DEFAULT_DEFER_FINISH_CONFIDENCE = 0.55 # Minimum confidence to defer cycle finish
|
|
|
|
# Cycle interruption detection defaults (internal)
|
|
DEFAULT_ABRUPT_DROP_WATTS = 500.0 # Power cliff detection threshold (W)
|
|
DEFAULT_ABRUPT_DROP_RATIO = 0.6 # 60% drop considered abrupt
|
|
DEFAULT_ABRUPT_HIGH_LOAD_FACTOR = 5.0 # High load factor threshold
|
|
DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD = 3 # Ghost cycles before threshold adjustment
|
|
|
|
# Anti-wrinkle defaults (advanced; disabled by default)
|
|
DEFAULT_ANTI_WRINKLE_ENABLED = False
|
|
DEFAULT_ANTI_WRINKLE_MAX_POWER = 400.0 # W
|
|
DEFAULT_ANTI_WRINKLE_MAX_DURATION = 60.0 # s
|
|
DEFAULT_ANTI_WRINKLE_EXIT_POWER = 0.8 # W
|
|
|
|
# Delayed-start detection defaults (disabled by default).
|
|
#
|
|
# The detector watches for sustained power between stop_threshold_w and
|
|
# start_threshold_w (the "standby band"): a machine sitting in that band
|
|
# for at least DEFAULT_DELAY_CONFIRM_SECONDS is in delayed-start mode, not
|
|
# off and not running. Short menu-navigation peaks above the band are
|
|
# ignored because they don't sustain long enough to satisfy the normal
|
|
# start-duration gate.
|
|
DEFAULT_DELAY_START_DETECT_ENABLED = False
|
|
DEFAULT_DELAY_CONFIRM_SECONDS = 60.0 # s — sustained standby before DELAY_WAIT engages
|
|
DEFAULT_DELAY_TIMEOUT_HOURS = 8.0 # h — give up waiting after this long
|
|
|
|
# Pump Monitor settings (pump device type only)
|
|
CONF_PUMP_STUCK_DURATION = "pump_stuck_duration" # Seconds before a running pump is flagged as stuck
|
|
DEFAULT_PUMP_STUCK_DURATION = 1800 # 30 min - typical sump pump runs <60 s; 30 min implies motor is jammed
|
|
EVENT_PUMP_STUCK = "ha_washdata_pump_stuck" # Fired when stuck-pump threshold is exceeded
|
|
|
|
# Profile Matching Thresholds
|
|
CONF_PROFILE_MATCH_THRESHOLD = "profile_match_threshold"
|
|
CONF_PROFILE_UNMATCH_THRESHOLD = "profile_unmatch_threshold"
|
|
|
|
DEFAULT_PROFILE_MATCH_THRESHOLD = 0.4
|
|
DEFAULT_PROFILE_UNMATCH_THRESHOLD = 0.35
|
|
|
|
CONF_DTW_BANDWIDTH = "dtw_bandwidth"
|
|
DEFAULT_DTW_BANDWIDTH = 0.20 # 20% Sakoe-Chiba constraint
|
|
|
|
CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS = "suppress_feedback_notifications"
|
|
DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS = False # Show persistent notifications by default
|
|
|
|
# States
|
|
STATE_OFF = "off"
|
|
STATE_DELAY_WAIT = "delay_wait"
|
|
STATE_IDLE = "idle"
|
|
STATE_STARTING = "starting"
|
|
STATE_RUNNING = "running"
|
|
STATE_PAUSED = "paused"
|
|
STATE_USER_PAUSED = "user_paused"
|
|
STATE_ENDING = "ending"
|
|
STATE_FINISHED = "finished"
|
|
STATE_ANTI_WRINKLE = "anti_wrinkle"
|
|
STATE_INTERRUPTED = "interrupted"
|
|
STATE_FORCE_STOPPED = "force_stopped"
|
|
STATE_RINSE = "rinse"
|
|
STATE_UNKNOWN = "unknown"
|
|
STATE_CLEAN = "clean" # Cycle ended but door not yet opened (laundry still inside)
|
|
|
|
# Cycle Status (how the cycle ended)
|
|
CYCLE_STATUS_COMPLETED = "completed" # Natural completion (power dropped)
|
|
CYCLE_STATUS_INTERRUPTED = (
|
|
"interrupted" # Abnormal/short run or abrupt power cliff (likely user/power abort)
|
|
)
|
|
CYCLE_STATUS_FORCE_STOPPED = "force_stopped" # Watchdog forced end (sensor offline)
|
|
CYCLE_STATUS_RESUMED = "resumed" # Cycle was restored from storage after restart
|
|
|
|
# Device Types
|
|
DEVICE_TYPE_WASHING_MACHINE = "washing_machine"
|
|
DEVICE_TYPE_DRYER = "dryer"
|
|
DEVICE_TYPE_WASHER_DRYER = "washer_dryer"
|
|
DEVICE_TYPE_DISHWASHER = "dishwasher"
|
|
DEVICE_TYPE_COFFEE_MACHINE = "coffee_machine"
|
|
DEVICE_TYPE_EV = "ev"
|
|
DEVICE_TYPE_AIR_FRYER = "air_fryer"
|
|
DEVICE_TYPE_HEAT_PUMP = "heat_pump"
|
|
DEVICE_TYPE_BREAD_MAKER = "bread_maker"
|
|
DEVICE_TYPE_PUMP = "pump"
|
|
DEVICE_TYPE_OVEN = "oven"
|
|
# Generic / unsupported bucket. Ships intentionally generic defaults that are
|
|
# not tuned for any specific appliance, so the user must configure thresholds,
|
|
# timeouts, and matching parameters themselves. Also serves as the runtime
|
|
# fallback when a deprecated device type is hard-removed (see
|
|
# DEPRECATED_DEVICE_TYPE_FALLBACK below). No curated phase catalog and no
|
|
# device-type-specific branches in the runtime, so behavior is whatever the
|
|
# user dials in.
|
|
DEVICE_TYPE_OTHER = "other"
|
|
|
|
DEVICE_TYPES = {
|
|
DEVICE_TYPE_WASHING_MACHINE: "Washing Machine",
|
|
DEVICE_TYPE_DRYER: "Dryer",
|
|
DEVICE_TYPE_WASHER_DRYER: "Washer-Dryer Combo",
|
|
DEVICE_TYPE_DISHWASHER: "Dishwasher",
|
|
DEVICE_TYPE_COFFEE_MACHINE: "Coffee Machine",
|
|
DEVICE_TYPE_EV: "Electric Vehicle",
|
|
DEVICE_TYPE_AIR_FRYER: "Air Fryer",
|
|
DEVICE_TYPE_HEAT_PUMP: "Heat Pump",
|
|
DEVICE_TYPE_BREAD_MAKER: "Bread Maker",
|
|
DEVICE_TYPE_PUMP: "Pump / Sump Pump",
|
|
DEVICE_TYPE_OVEN: "Oven",
|
|
DEVICE_TYPE_OTHER: "Other (Advanced)",
|
|
}
|
|
|
|
# Device types that ship as deprecated. They fail one of WashData's three fit
|
|
# tests (user-selected discrete program, reproducible power signature, clean
|
|
# return to OFF) so profile matching and time-remaining estimation produce
|
|
# noise rather than signal. Kept in DEVICE_TYPES so existing config entries
|
|
# load unchanged; filtered out of the new-entry picker in the config flow,
|
|
# shown with a "(deprecated)" suffix when an existing entry already uses one,
|
|
# and surfaced via a one-shot persistent_notification on integration startup.
|
|
# Planned hard removal: 0.4.6 (two release cycles after this deprecation).
|
|
DEPRECATED_DEVICE_TYPES = frozenset({
|
|
DEVICE_TYPE_COFFEE_MACHINE,
|
|
DEVICE_TYPE_EV,
|
|
DEVICE_TYPE_HEAT_PUMP,
|
|
DEVICE_TYPE_OVEN,
|
|
})
|
|
|
|
# Fallback device_type used at runtime once a deprecated type is hard-removed.
|
|
# "Other (Advanced)" intentionally ships generic defaults so the integration
|
|
# does not silently pretend an orphaned entry behaves like a washing machine.
|
|
# Stored options are preserved as-is, so a user who had hand-tuned thresholds
|
|
# on the old deprecated type keeps those values; the integration just stops
|
|
# layering device-specific defaults underneath them.
|
|
DEPRECATED_DEVICE_TYPE_FALLBACK = DEVICE_TYPE_OTHER
|
|
|
|
# Device Type Defaults
|
|
# Device Type Defaults (Maps)
|
|
|
|
DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE = {
|
|
DEVICE_TYPE_DISHWASHER: 14400, # 4 hours (Drying can be long)
|
|
DEVICE_TYPE_HEAT_PUMP: 14400, # 4 hours (Heat pumps can run a long time with slow updates)
|
|
DEVICE_TYPE_BREAD_MAKER: 7200, # 2 hours (Proving/Rising is very low-power for extended periods)
|
|
DEVICE_TYPE_PUMP: DEFAULT_PUMP_STUCK_DURATION + 60, # Must exceed stuck-alarm threshold so the alarm fires before the watchdog
|
|
DEVICE_TYPE_OVEN: 14400, # 4 hours (Slow roasts and pyrolytic self-clean can run for hours with thermostat-driven silence)
|
|
}
|
|
|
|
DEFAULT_MAX_DEFERRAL_SECONDS = 14400 # 4 hours max safe deferral
|
|
|
|
# Issue #43: dishwasher end-of-cycle pump-out handling.
|
|
#
|
|
# A dishwasher's wash→drying drain wind-down produces brief power spikes mid
|
|
# ENDING that, prior to the issue #43 fix, would set _end_spike_seen=True and
|
|
# pre-arm Smart Termination — so the cycle closed at 99% of expected, BEFORE
|
|
# the real end-of-cycle pump-out at ~99.5% of expected. The pump-out then
|
|
# registered as a brand-new cycle.
|
|
#
|
|
# Two coordinated thresholds gate the fix. They MUST agree: the wait window
|
|
# in _should_defer_finish (DISHWASHER_END_SPIKE_WAIT_SECONDS) is the upper
|
|
# bound for keeping the cycle open without an end spike, and Smart
|
|
# Termination's own wait branch in STATE_ENDING uses the SAME constant so the
|
|
# two paths release the cycle at the same moment.
|
|
#
|
|
# A spike at < DISHWASHER_END_SPIKE_MIN_PROGRESS of expected duration is
|
|
# ignored for end-spike tracking (the cycle still stays in ENDING via the
|
|
# existing long_ending_tail path - this only governs the smart-termination
|
|
# pre-arming).
|
|
DISHWASHER_END_SPIKE_MIN_PROGRESS = 0.85
|
|
# Widened from 300s to 1800s after issue #43 follow-up:
|
|
# real-world user reports showed Smart Termination misfiring ~4 min before the
|
|
# end-of-cycle pump-out, and the original 5-min escape hatch wasn't generous
|
|
# enough to cover that gap before the next reading arrived. 30 min is plenty
|
|
# to capture even the latest pump-outs while still guaranteeing the cycle
|
|
# closes eventually for dishwashers that have no pump-out at all.
|
|
DISHWASHER_END_SPIKE_WAIT_SECONDS = 1800.0
|
|
|
|
DEFAULT_OFF_DELAY_BY_DEVICE = {
|
|
DEVICE_TYPE_DISHWASHER: 1800, # 30 min (Drying)
|
|
DEVICE_TYPE_COFFEE_MACHINE: 300, # 5 min (Warming/Pause handling)
|
|
DEVICE_TYPE_HEAT_PUMP: 600, # 10 min (Defrosting pauses)
|
|
DEVICE_TYPE_BREAD_MAKER: 300, # 5 min (Keep-warm phase after baking)
|
|
DEVICE_TYPE_PUMP: 20, # 20 s (Pumps cut off sharply; no warm-down phase)
|
|
DEVICE_TYPE_OVEN: 600, # 10 min (Thermostat off-cycles can be long while holding temp)
|
|
}
|
|
|
|
# Device-specific progress smoothing thresholds (percentage points)
|
|
# These control how much backward progress is allowed before heavy damping kicks in
|
|
DEVICE_SMOOTHING_THRESHOLDS = {
|
|
DEVICE_TYPE_WASHING_MACHINE: 5.0, # Can have repeating phases (rinse cycles)
|
|
DEVICE_TYPE_DRYER: 3.0, # More linear, less phase repetition
|
|
DEVICE_TYPE_WASHER_DRYER: 5.0, # Combined washer+dryer, use washer defaults
|
|
DEVICE_TYPE_DISHWASHER: 5.0, # Similar to washing machine with distinct phases
|
|
DEVICE_TYPE_COFFEE_MACHINE: 2.0, # Short cycles, rapid transitions, less tolerance
|
|
DEVICE_TYPE_AIR_FRYER: 2.0, # Constant load with sudden drop
|
|
DEVICE_TYPE_HEAT_PUMP: 5.0, # Variable load, long periods
|
|
DEVICE_TYPE_BREAD_MAKER: 5.0, # Large power swings between kneading, proving, baking
|
|
DEVICE_TYPE_PUMP: 2.0, # Binary on/off spikes; minimal smoothing needed
|
|
DEVICE_TYPE_OVEN: 5.0, # Bistable thermostat cycling between full heat and 0 W
|
|
}
|
|
|
|
CONF_VERIFICATION_POLL_INTERVAL = "verification_poll_interval" # Internal setting
|
|
DEFAULT_VERIFICATION_POLL_INTERVAL = 15 # Seconds (rapid checks after delay)
|
|
|
|
# Device specific completion thresholds (min run time to be considered a valid "completed" cycle)
|
|
DEVICE_COMPLETION_THRESHOLDS = {
|
|
DEVICE_TYPE_WASHING_MACHINE: 600, # 10 min
|
|
DEVICE_TYPE_DRYER: 600, # 10 min
|
|
DEVICE_TYPE_WASHER_DRYER: 600, # 10 min (same as washer)
|
|
DEVICE_TYPE_DISHWASHER: 900, # 15 min
|
|
DEVICE_TYPE_COFFEE_MACHINE: 60, # 1 min (Filter coffee cycle)
|
|
DEVICE_TYPE_EV: 600, # 10 min
|
|
DEVICE_TYPE_AIR_FRYER: 300, # 5 min minimum
|
|
DEVICE_TYPE_HEAT_PUMP: 900, # 15 min minimum
|
|
DEVICE_TYPE_BREAD_MAKER: 1800, # 30 min (even express bread takes 30+ min)
|
|
DEVICE_TYPE_PUMP: 5, # 5 s - pump cycles can be under 30 seconds
|
|
DEVICE_TYPE_OVEN: 600, # 10 min (covers quick reheats and ignores brief preheating tests)
|
|
}
|
|
|
|
# Default min_off_gap by device type (seconds)
|
|
# If gap between cycles is larger than this, force new cycle.
|
|
# If smaller, and we deemed previous as 'ended' but technically could be same,
|
|
# we might want to handle that (though strict state machine usually suffices if tuned well).
|
|
# Default min_off_gap by device type (seconds)
|
|
# Default min_off_gap by device type (seconds)
|
|
DEFAULT_MIN_OFF_GAP_BY_DEVICE = {
|
|
DEVICE_TYPE_WASHING_MACHINE: 480, # 8 min (Soak handling)
|
|
DEVICE_TYPE_DRYER: 300, # 5 min (Cool down gaps?)
|
|
DEVICE_TYPE_WASHER_DRYER: 600, # 10 min (longer for combined cycles)
|
|
DEVICE_TYPE_DISHWASHER: 3600, # 1 hour (Drying pauses)
|
|
DEVICE_TYPE_COFFEE_MACHINE: 120, # 2 min (Session grouping)
|
|
DEVICE_TYPE_EV: 900, # 15 min (Brief unplug/replug)
|
|
DEVICE_TYPE_AIR_FRYER: 120, # 2 min (Shaking food)
|
|
DEVICE_TYPE_HEAT_PUMP: 1800, # 30 min (Defrost cycle / resting gap)
|
|
DEVICE_TYPE_BREAD_MAKER: 600, # 10 min (Resting between knead/prove keeps same cycle together)
|
|
DEVICE_TYPE_PUMP: 60, # 1 min (Pumps can cycle every 3-5 min in heavy rain)
|
|
DEVICE_TYPE_OVEN: 900, # 15 min (Bridge thermostat off-windows so one bake stays a single cycle)
|
|
}
|
|
DEFAULT_MIN_OFF_GAP = 60 # Scalar fallback
|
|
|
|
# Default start energy threshold by device type (Wh)
|
|
# Filter noise spikes (1000W * 0.01s = 0.002Wh).
|
|
# Must be significant enough to imply mechanical work.
|
|
DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE = {
|
|
DEVICE_TYPE_WASHING_MACHINE: 0.2, # ~50W for 15s or 200W for 3s
|
|
DEVICE_TYPE_DRYER: 0.5, # Heater kicks in hard
|
|
DEVICE_TYPE_WASHER_DRYER: 0.3, # Mix of washer and dryer
|
|
DEVICE_TYPE_DISHWASHER: 0.2, # Pump/Heater
|
|
DEVICE_TYPE_COFFEE_MACHINE: 0.05, # Short heater burst
|
|
DEVICE_TYPE_EV: 0.5, # High power charging
|
|
DEVICE_TYPE_AIR_FRYER: 0.2, # Heater kicks in
|
|
DEVICE_TYPE_HEAT_PUMP: 0.2, # Compressor spins up
|
|
DEVICE_TYPE_BREAD_MAKER: 0.2, # Kneading motor starts (~200W for a few seconds)
|
|
DEVICE_TYPE_PUMP: 0.003, # ~100W motor for ~0.1 s is enough to confirm a pump cycle
|
|
DEVICE_TYPE_OVEN: 0.5, # Heating element kicks in hard (~2-3 kW) - high gate filters incidental light/fan draws
|
|
}
|
|
# Default sampling interval by device type
|
|
DEFAULT_SAMPLING_INTERVAL_BY_DEVICE = {
|
|
# 2s captures the rapid 0<->150W motor/heater oscillation in wet appliances;
|
|
# the 30s global default discards those spikes and undersamples the cycle.
|
|
DEVICE_TYPE_WASHING_MACHINE: 2.0,
|
|
DEVICE_TYPE_WASHER_DRYER: 2.0,
|
|
DEVICE_TYPE_DISHWASHER: 2.0,
|
|
DEVICE_TYPE_COFFEE_MACHINE: 10.0, # 10s is sufficient for brew cycles
|
|
DEVICE_TYPE_PUMP: 10.0, # 10s - pump cycles can be <30 s; 30s default would miss them
|
|
}
|
|
|
|
# Default profile match min duration ratio by device type
|
|
DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE = {
|
|
DEVICE_TYPE_DISHWASHER: 0.10,
|
|
}
|
|
|
|
# Storage
|
|
STORAGE_VERSION = 5
|
|
STORAGE_KEY = "ha_washdata"
|
|
|
|
# Notification events
|
|
EVENT_CYCLE_STARTED = "ha_washdata_cycle_started"
|
|
EVENT_CYCLE_ENDED = "ha_washdata_cycle_ended"
|
|
|
|
# Signals
|
|
SIGNAL_WASHER_UPDATE = "ha_washdata_update_{}"
|
|
|
|
# Learning & Feedback
|
|
|
|
SERVICE_SUBMIT_FEEDBACK = (
|
|
"ha_washdata.submit_cycle_feedback" # Service to submit feedback
|
|
)
|
|
|
|
# Recorder
|
|
STATE_RECORDING = "recording"
|
|
CONF_RECORD_MODE = "record_mode"
|
|
SERVICE_RECORD_START = "record_start"
|
|
SERVICE_RECORD_STOP = "record_stop"
|
|
|
|
# Thresholds for trim suggestions
|
|
SHORT_SILENCE_THRESHOLD_S = 600 # 10 minutes
|
|
TRIM_BUFFER_S = 60.0 # 1 minute buffer
|