Skip to content

Commit a7a300e

Browse files
committed
cover-control.py: Don't close window b/c cloudy weather
1 parent aa64e76 commit a7a300e

File tree

3 files changed

+46
-55
lines changed

3 files changed

+46
-55
lines changed

cover-control.py

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ def sun_is_not_shining():
100100
return mqttclient.is_power_below(int(config['PV_PEAK_POWER']) / 8)
101101

102102
is_closed = None
103-
close_window_at = None
103+
window_is_closed = None
104104
async def apply_schedule():
105105
global is_closed
106+
global window_is_closed
106107
global schedule
107108
global radar_rain
108-
global close_window_at
109109

110110
try:
111111
now = datetime.datetime.now(datetime.timezone.utc).astimezone()
@@ -121,27 +121,20 @@ async def apply_schedule():
121121

122122
# To prevent unnecessary movement:
123123
# If the sunscreen will be opened in the next two time frames, we don't close it.
124-
if close_now:
125-
if current_schedule['WEATHER_PREDICTION'].iloc[2] == 'bad':
126-
close_now = False
127-
reason = '⏲'
124+
if close_now and current_schedule['WEATHER_PREDICTION'].iloc[2] == 'bad':
125+
close_now = False
128126

129127
# The forecast might be incorrect or outdated.
130128
# If the radar detects unexpected precipitation, we must open the suncreen.
131-
if close_now and radar_rain:
129+
if radar_rain:
132130
close_now = False
133131
reason = '🌦'
134132

135133
close_window_reason = reason
136-
if close_window_at is None:
137-
# Window is already closed
134+
if window_is_closed:
138135
close_window_now = False
139136
else:
140-
close_window_now = (
141-
now > close_window_at or
142-
current_schedule['WEATHER_PREDICTION'].iloc[0] == 'bad' or
143-
radar_rain
144-
)
137+
close_window_now = radar_rain or current_schedule['CLOSE_WINDOW'].iloc[0]
145138

146139
# The sunscreen should be open during low irradiation.
147140
# An open window may stay open.
@@ -166,56 +159,46 @@ async def apply_schedule():
166159
if close_window_now:
167160
logging.info('Fenster werden geschlossen')
168161
arduinoclient.close_window()
169-
close_window_at = None
170162
arduinoclient.open_curtain()
171163
tuyaclient.open_curtain()
172164
if is_closed is not None:
173-
if close_window_now:
165+
if close_window_now and window_is_closed is not None:
174166
await telegram.bot_send('Die Markise wird eingefahren und die Fenster werden geschlossen {}'.format(reason))
175167
else:
176168
await telegram.bot_send('Die Markise wird eingefahren {}'.format(reason))
169+
if close_window_now:
170+
window_is_closed = True
177171
is_closed = close_now
178172
else:
179173
if close_window_now:
180174
logging.info('Fenster werden automatisch geschlossen {}'.format(close_window_reason))
181175
arduinoclient.close_window()
182-
close_window_at = None
183-
await telegram.bot_send(text='Die Fenster werden geschlossen {}'.format(close_window_reason))
176+
if window_is_closed is not None:
177+
await telegram.bot_send(text='Die Fenster werden geschlossen {}'.format(close_window_reason))
178+
window_is_closed = True
184179

185180
except:
186181
logging.exception('Fehler beim Anwenden des Plans')
187182

188183

189184
async def open_window(args):
190-
global close_window_at
185+
global window_is_closed
191186

192187
if len(args) != 1:
193188
return
194189

195190
if args[0] == 'auf':
196191
logging.info('Fenster werden geöffnet')
197-
close_window_at = weather.get_next_sunset()
198192
arduinoclient.open_window()
193+
window_is_closed = False
199194
await telegram.bot_send(text='Die Fenster werden geöffnet')
200195

201196
if args[0] == 'zu':
202197
logging.info('Fenster werden geschlossen')
203198
arduinoclient.close_window()
204-
close_window_at = None
199+
window_is_closed = True
205200
await telegram.bot_send(text='Die Fenster werden geschlossen')
206201

207-
if args[0].isnumeric():
208-
minutes = float(args[0])
209-
if minutes < 1:
210-
minutes = 60
211-
212-
now = datetime.datetime.now(datetime.timezone.utc).astimezone()
213-
close_window_at = now + datetime.timedelta(minutes = minutes)
214-
215-
logging.info('Fenster werden geöffnet')
216-
arduinoclient.open_window()
217-
await telegram.bot_send(text='Die Fenster werden für {:g} Minuten geöffnet'.format(minutes))
218-
219202
loop = None
220203
async def main():
221204
global config

modules/mqttclient.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
power_history = []
99

1010
def is_power_above(threshold):
11-
if len(power_history) < 10:
11+
if len(power_history) == 0:
1212
return False
1313
else:
1414
return min(power_history) > threshold
1515

1616
def is_power_below(threshold):
17-
if len(power_history) < 10:
17+
if len(power_history) == 0:
1818
return False
1919
else:
2020
return max(power_history) < threshold

modules/weather.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,50 +97,58 @@ def get_sunscreen_schedule():
9797
})
9898

9999
# Default
100-
schedule = pd.DataFrame({'WEATHER_PREDICTION': 'ok', 'REASON': ''}, index = forecast.index, columns=['WEATHER_PREDICTION', 'REASON'])
100+
schedule = pd.DataFrame({'WEATHER_PREDICTION': 'ok', 'CLOSE_WINDOW': False, 'REASON': ''}, index = forecast.index, columns=['WEATHER_PREDICTION', 'CLOSE_WINDOW', 'REASON'])
101101

102102
not_sunny_idx = forecast[DwdMosmixParameter.LARGE.SUNSHINE_DURATION.value] < 5 * 60
103-
schedule[not_sunny_idx] = ['bad', '⛅']
103+
schedule.loc[not_sunny_idx, 'REASON'] = '⛅'
104104
good_idx = forecast[DwdMosmixParameter.LARGE.SUNSHINE_DURATION.value] >= 10 * 60
105+
bad_idx = not_sunny_idx
105106

106107
cloudy_idx = forecast[DwdMosmixParameter.LARGE.CLOUD_COVER_EFFECTIVE.value] > 7/8 * 100.0
107-
schedule[cloudy_idx] = ['bad', '☁️']
108+
schedule.loc[cloudy_idx, 'REASON'] = '☁️'
108109
good_idx &= forecast[DwdMosmixParameter.LARGE.CLOUD_COVER_EFFECTIVE.value] < 6/8 * 100.0
110+
bad_idx |= cloudy_idx
111+
112+
rainy_idx = ((forecast[DwdMosmixParameter.LARGE.PROBABILITY_PRECIPITATION_LAST_1H.value] > 45.0) & (forecast[DwdMosmixParameter.LARGE.PRECIPITATION_DURATION.value] > 120)) | (forecast[DwdMosmixParameter.LARGE.PROBABILITY_DRIZZLE_LAST_1H.value] > 45.0)
113+
schedule.loc[rainy_idx, 'REASON'] = '🌧'
114+
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_PRECIPITATION_LAST_1H.value] < 30.0
115+
good_idx &= forecast[DwdMosmixParameter.LARGE.PRECIPITATION_DURATION.value] < 60
116+
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_DRIZZLE_LAST_1H.value] < 30.0
117+
bad_idx |= rainy_idx
109118

110119
dewy_idx = (forecast[DwdMosmixParameter.LARGE.TEMPERATURE_DEW_POINT_MEAN_200.value] > forecast[DwdMosmixParameter.LARGE.TEMPERATURE_AIR_MEAN_200.value]) | (forecast[DwdMosmixParameter.LARGE.PROBABILITY_FOG_LAST_1H.value] > 45.0)
111-
schedule[dewy_idx] = ['bad', '🌫']
120+
schedule.loc[dewy_idx, 'REASON'] = '🌫'
121+
schedule.loc[dewy_idx, 'CLOSE_WINDOW'] = True
112122
good_idx &= forecast[DwdMosmixParameter.LARGE.TEMPERATURE_DEW_POINT_MEAN_200.value] + forecast[DwdMosmixParameter.LARGE.ERROR_ABSOLUTE_TEMPERATURE_DEW_POINT_MEAN_200.value] < forecast[DwdMosmixParameter.LARGE.TEMPERATURE_AIR_MEAN_200.value] - forecast[DwdMosmixParameter.LARGE.ERROR_ABSOLUTE_TEMPERATURE_AIR_MEAN_200.value]
113123
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_FOG_LAST_1H.value] < 30.0
124+
bad_idx |= dewy_idx
114125

115126
cold_idx = forecast[DwdMosmixParameter.LARGE.TEMPERATURE_AIR_MEAN_200.value] - forecast[DwdMosmixParameter.LARGE.ERROR_ABSOLUTE_TEMPERATURE_AIR_MEAN_200.value] < 277.15 # 4 °C
116-
schedule[cold_idx] = ['bad', '❄️']
127+
schedule.loc[cold_idx, 'REASON'] = '❄️'
128+
schedule.loc[cold_idx, 'CLOSE_WINDOW'] = True
117129
good_idx &= forecast[DwdMosmixParameter.LARGE.TEMPERATURE_AIR_MEAN_200.value] >= 285.15 # 12 °C
130+
bad_idx |= cold_idx
118131

119132
windy_idx = forecast[DwdMosmixParameter.LARGE.WIND_GUST_MAX_LAST_1H.value] > 11
120-
schedule[windy_idx] = ['bad', '💨']
133+
schedule.loc[windy_idx, 'REASON'] = '💨'
134+
schedule.loc[windy_idx, 'CLOSE_WINDOW'] = True
121135
good_idx &= forecast[DwdMosmixParameter.LARGE.WIND_GUST_MAX_LAST_1H.value] < 10
122-
123-
rainy_idx = ((forecast[DwdMosmixParameter.LARGE.PROBABILITY_PRECIPITATION_LAST_1H.value] > 45.0) & (forecast[DwdMosmixParameter.LARGE.PRECIPITATION_DURATION.value] > 120)) | (forecast[DwdMosmixParameter.LARGE.PROBABILITY_DRIZZLE_LAST_1H.value] > 45.0)
124-
schedule[rainy_idx] = ['bad', '🌧']
125-
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_PRECIPITATION_LAST_1H.value] < 30.0
126-
good_idx &= forecast[DwdMosmixParameter.LARGE.PRECIPITATION_DURATION.value] < 60
127-
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_DRIZZLE_LAST_1H.value] < 30.0
136+
bad_idx |= windy_idx
128137

129138
thundery_idx = forecast[DwdMosmixParameter.LARGE.PROBABILITY_THUNDER_LAST_1H.value] > 45.0
130-
schedule[thundery_idx] = ['bad', '⛈']
139+
schedule.loc[thundery_idx, 'REASON'] = '⛈'
140+
schedule.loc[thundery_idx, 'CLOSE_WINDOW'] = True
131141
good_idx &= forecast[DwdMosmixParameter.LARGE.PROBABILITY_THUNDER_LAST_1H.value] < 30.0
142+
bad_idx |= thundery_idx
132143

133-
schedule[good_idx] = ['good', '☀️']
134-
135-
# Don't close before sunrise
136-
sunrise = astral.sun.sunrise(observer)
137-
schedule.loc[sunrise] = ['bad', '🌙']
144+
schedule[good_idx] = ['good', False, '☀️']
145+
schedule.loc[bad_idx, 'WEATHER_PREDICTION'] = 'bad'
138146

139-
# Open at sunset
147+
# Open sunscreen and close window at sunset
140148
sunset = astral.sun.sunset(observer)
141149
index_after_sunset = schedule.index.where(schedule.index.to_pydatetime() > sunset).min()
142150
schedule.loc[sunset] = schedule.loc[index_after_sunset]
143-
schedule.loc[index_after_sunset] = ['bad', '🌙']
151+
schedule.loc[index_after_sunset] = ['bad', True, '🌙']
144152

145153
schedule = schedule.sort_index()
146154

0 commit comments

Comments
 (0)