Repository navigation
Expand file tree
/
Copy pathconst.py
More file actions
149 lines (132 loc) · 9.38 KB
/
Copy pathconst.py
File metadata and controls
149 lines (132 loc) · 9.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# -----------------------------------------------------------------------------
# Predbat Home Battery System
# Copyright Trefor Southwell 2026 - All Rights Reserved
# This application maybe used for personal use only and not for commercial use
# -----------------------------------------------------------------------------
# fmt off
# pylint: disable=consider-using-f-string
# pylint: disable=line-too-long
# pylint: disable=attribute-defined-outside-init
"""Global constants and default values used throughout PredBat.
Defines time formats, prediction intervals, retry limits, conversion factors,
mode options, and other constants shared across all modules.
"""
from datetime import datetime, timedelta
TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
TIME_FORMAT_SECONDS = "%Y-%m-%dT%H:%M:%S.%f%z"
TIME_FORMAT_SOLCAST = "%Y-%m-%dT%H:%M:%S.%f0%z" # 2024-05-31T18:00:00.0000000Z
TIME_FORMAT_OCTOPUS = "%Y-%m-%d %H:%M:%S%z"
TIME_FORMAT_SOLIS = "%Y-%m-%d %H:%M:%S"
PREDICT_STEP = 5
# Dynamic load: how long a car inside one of its charging slots must show "not charging" before its
# slots, and their cheap rate, are cancelled. car_charging_now reports the car directly, so two
# minutes (one charger/integration update after the slot starts) is enough. The load test reads a
# PREDICT_STEP average that the rest of the house also moves, so it needs two full periods.
DYNAMIC_LOAD_CAR_SENSOR_MINUTES = 2
DYNAMIC_LOAD_CAR_LOAD_MINUTES = 10
# Extra cloud divergence applied to the PV10 scenario on top of the computed cloud factor, so the
# downside case diverges harder than the central one
CLOUD_FACTOR_PV10 = 0.2
# Window the envelope cloud model conserves energy over. Matches the half-hour rate slot so the
# modulation can never shift PV across a slot boundary, and gives six PREDICT_STEP buckets - enough
# granularity for the duty cycle to follow the p10/p50/p90 band's own asymmetry.
CLOUD_WINDOW_MINUTES = 30
# Ceiling for the p90 series' own modulation, as a multiple of DC array kWp. p90 is the top
# percentile the forecaster publishes, so its upside has no next percentile to reach for and is
# extrapolated instead; this rail stops a wide band extrapolating into a physically impossible
# array output. 1.2 allows for cloud-edge enhancement above nameplate.
CLOUD_ARRAY_MARGIN = 1.2
# Sentinel fetch_pv_forecast() returns when the forecast source declares no array size (Solcast and
# the HA integrations). Treated as "unknown", not as a real 9999 kWp array.
PV_ARRAY_KWP_UNKNOWN = 9999
RUN_EVERY = 5
# Forecast scenarios simulated by the planner.
# PV_SCENARIO_PV10 must remain 1 so it stays interchangeable with the legacy pv10 boolean.
PV_SCENARIO_NOMINAL = 0
PV_SCENARIO_PV10 = 1
PV_SCENARIO_PV90 = 2
LOAD_FORECAST_HISTORY_MAX_DAYS = 30 # Max days of history used by the weighted-bucket load forecast (days_previous_auto)
CONFIG_ROOTS = ["/config", "/conf", "/homeassistant", "./"]
TIME_FORMAT_HA = "%Y-%m-%dT%H:%M:%S%z"
TIME_FORMAT_HA_TZ = "%Y-%m-%dT%H:%M:%S.%f%z"
TIME_FORMAT_DAILY = "%Y-%m-%d"
TIMEOUT = 60 * 5
CONFIG_REFRESH_PERIOD = 60 * 8
INVERTER_MAX_RETRY = 10 # Maximum number of retries for inverter commands
# How Inverter spends one write_and_poll_sleep interval while waiting for a written value to appear
# on an entity Predbat publishes itself. The interval is a timeout, not a known duration, so it is
# polled with a backoff rather than slept through in one go - see Inverter._poll_after_write. Reads
# come from the local websocket state cache, so a poll is close to free.
INVERTER_WRITE_POLL_INTERVAL = 0.25 # Seconds before the second look, doubling after each miss
INVERTER_WRITE_POLL_MAX_INTERVAL = 2.0 # Ceiling for that backoff
INVERTER_MAX_RETRY_REST = 5 # Maximum number of retries for inverter REST commands
# Inverter clock skew bands, measured as (inverter time - Predbat computer time) in minutes.
# At or above the restart threshold Predbat warns loudly and triggers auto_restart. Between the warn
# and restart thresholds nothing used to be said at all, yet the drift still shifts every charge and
# export slot Predbat writes (compensation via inverter_clock_skew_* is manual only), so a moderate
# steady skew showed up only as expensive grid import at the edges of every window - see #4989/#4927.
INVERTER_CLOCK_SKEW_RESTART_MINUTES = 30
INVERTER_CLOCK_SKEW_WARN_MINUTES = 5
INVERTER_CLOCK_SKEW_WARN_REPEAT_MINUTES = 60 # Minimum gap between repeats of the moderate-skew warning, per inverter, so it doesn't fire every 5-minute cycle
INVERTER_REST_TIMEOUT = 10 # Seconds to wait for a REST response before giving up (local network call, should be fast)
INVERTER_QUICK_UPDATE_SECONDS = 60 # Minimum seconds between quick inverter data updates, also the balance re-apply interval
PREDBAT_MAX_CARS = 8 # Matches PK_MAX_CARS in prediction_kernel.cpp and the car_charging_rate/_1../_7 config items - the hard ceiling on num_cars
CAR_CHARGING_LIMIT_UNCAPPED = 9999.0 # Model-facing car charge limit (kWh) that makes predict()'s fill clamp inert - larger than any real car battery (#4967)
DEBUG_ENABLE_MAX_HOURS = 2 # Auto-disable switch.predbat_debug_enable after this long left on, to bound the raw per-cycle debug.yaml disk writes it triggers (and the C++ kernel bypass it forces) if left on by accident - the rotating debug-history buffer covers longer-term history at a coarser interval instead
# How far ahead a manual override may be placed. The two horizons differ on purpose: a manual
# charge/export/freeze/demand slot is bounded by the plan, since Predbat can only act on a slot the
# plan reaches, whereas a rate override is future tariff data that only has to be REMEMBERED until
# the plan reaches it - e.g. an energy supplier announcing a free-electricity hour several days out.
# 7 days is the ceiling the 'Day HH:MM' selection format can express anyway.
MANUAL_TIME_MAX_MINUTES = 48 * 60
MANUAL_RATE_MAX_MINUTES = 7 * 24 * 60
# 240v x 100 amps x 3 phases / 1000 to kW / 60 minutes in an hour is the maximum kWh in a 1 minute period
MAX_INCREMENT = 240 * 100 * 3 / 1000 / 60
MINUTE_WATT = 60 * 1000
INVERTER_TEST = False # Run inverter control self test
# Sentinel values for an export window's target SoC/limit (export_limits_best and friends).
# A real target is any value below EXPORT_LIMIT_FREEZE, expressed as a percentage 0-100
# (see calc_percent_limit) with the fractional part sometimes encoding a low-power export rate.
# prediction_kernel.cpp hardcodes the same two literals independently (it can't import this
# file) - unlike PREDBAT_MAX_CARS/PK_MAX_CARS above, they aren't yet named there too, so a value
# change here needs the matching literals found and updated by hand, in lockstep with a parity
# revision bump and a rebuild of all platform binaries.
EXPORT_LIMIT_FREEZE = 99.0 # Hold SoC, export only genuine PV surplus - no forced discharge
EXPORT_LIMIT_IDLE = 100.0 # Export window disabled entirely
# Export modes - the three states an export window can be in. These name what the packed value
# above already encodes; they are the vocabulary the rest of the code should ask in, rather than
# each caller re-deriving intent by comparing against the two sentinels (which several modules
# currently do, inconsistently). See export_mode_of()/export_target_of()/export_power_of() in
# utils.py for the accessors that read them.
EXPORT_MODE_TARGET = 0 # Force export down to a target SoC percentage, optionally at reduced power
EXPORT_MODE_FREEZE = 1 # Hold SoC, export only genuine PV surplus
EXPORT_MODE_IDLE = 2 # Window disabled entirely
# Full export power - the power level a target window exports at unless the planner has chosen a
# reduced rate. 1.0 = the inverter's configured maximum export rate.
FULL_EXPORT_POWER = 1.0
# Export power levels the planner tries for a low-power target export, as a fraction of full rate.
# These are the powers themselves, not the packed fractions they used to be written as: the
# encoding stores 1 - power, so the old ladder's 0.3/0.5/0.7 meant 70%/50%/30% rate and read
# backwards at the call site.
LOW_EXPORT_POWER_LEVELS = [0.7, 0.5, 0.3]
# Schema version for the debug yaml dump and the persisted plan. Bump when a field's *shape*
# changes, not when one is added or removed - a reader can detect those itself, but it cannot tell
# a new encoding from an old one when both are, say, a list of numbers. Absent means "before
# versioning", which is any dump written before this was introduced; those are still read, so a
# bug report from an older release keeps working (see export_limit_from_stored).
DEBUG_SCHEMA_VERSION = 1
# Create an array of times in the day in 5-minute intervals
BASE_TIME = datetime.strptime("00:00:00", "%H:%M:%S")
OPTIONS_TIME = [((BASE_TIME + timedelta(seconds=minute * 60)).strftime("%H:%M:%S")) for minute in range(0, 24 * 60, 5)]
# Inverter modes
PREDBAT_MODE_OPTIONS = ["Monitor", "Control SOC only", "Control charge", "Control charge & discharge"]
PREDBAT_MODE_MONITOR = 0
PREDBAT_MODE_CONTROL_SOC = 1
PREDBAT_MODE_CONTROL_CHARGE = 2
PREDBAT_MODE_CONTROL_CHARGEDISCHARGE = 3
# Predbat's core charge/export status strings, ordered most-active-first. Used two ways: execute.py
# resolves one headline status across a multi-inverter fleet (#4446), and output.py breaks a tie when
# a history slot's dominant state is ambiguous - e.g. an even split between two states (#4843). Both
# need the same answer to "which of these two states is the more significant one to show".
CHARGE_STATE_PRECEDENCE = ["Charging", "Freeze charging", "Hold charging"]
EXPORT_STATE_PRECEDENCE = ["Exporting", "Freeze exporting", "Hold exporting"]