def POST(self): qdict = web.input() station_seconds = {} for station in stations.enabled_stations(): mm_str = "mm" + str(station.index) ss_str = "ss" + str(station.index) if mm_str in qdict and ss_str in qdict: seconds = int(qdict[mm_str] or 0) * 60 + int(qdict[ss_str] or 0) station_seconds[station.index] = seconds run_once.set(station_seconds) raise web.seeother('/')
def predicted_schedule(start_time, end_time): """Determines all schedules for the given time range. To calculate what should currently be active, a start time of some time (a day) ago should be used.""" adjustment = level_adjustments.total_adjustment() max_usage = options.max_usage delay_delta = datetime.timedelta(seconds=options.station_delay) rain_block_start = datetime.datetime.now() rain_block_end = rain_blocks.block_end() skip_intervals = log.finished_runs() + log.active_runs() current_active = [ interval for interval in skip_intervals if not interval['blocked'] ] usage_changes = {} for active in current_active: start = active['start'] end = active['end'] if start not in usage_changes: usage_changes[start] = 0 if end not in usage_changes: usage_changes[end] = 0 usage_changes[start] += active['usage'] usage_changes[end] -= active['usage'] station_schedules = {} # Get run-once information: for station in stations.enabled_stations(): run_once_intervals = run_once.active_intervals(start_time, end_time, station.index) for interval in run_once_intervals: if station.index not in station_schedules: station_schedules[station.index] = [] program_name = _('Run-Once') new_schedule = { 'active': None, 'program': -1, 'program_name': program_name, 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%s-%d' % (str(interval['start']), str(program_name), station.index), 'usage': station.usage } station_schedules[station.index].append(new_schedule) # Get run-now information: if programs.run_now_program is not None: program = programs.run_now_program for station in sorted(program.stations): run_now_intervals = program.active_intervals( start_time, end_time, station) for interval in run_now_intervals: if station >= stations.count( ) or stations.master == station or stations.master_two == station or not stations[ station].enabled: continue if station not in station_schedules: station_schedules[station] = [] program_name = "%s " % program.name + _('Run-Now') new_schedule = { 'active': None, 'program': -1, 'program_name': program_name, 'fixed': program. fixed, # True for ignore water level else program.fixed for use water level in run now-program xx 'cut_off': 0, 'manual': True, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%s-%d' % (str(interval['start']), str(program_name), station), 'usage': stations.get(station).usage } station_schedules[station].append(new_schedule) # Aggregate per station: for program in programs.get(): if not program.enabled: continue for station in sorted(program.stations): program_intervals = program.active_intervals( start_time, end_time, station) if station >= stations.count( ) or stations.master == station or stations.master_two == station or not stations[ station].enabled: continue if station not in station_schedules: station_schedules[station] = [] for interval in program_intervals: if current_active and current_active[-1][ 'original_start'] > interval['start']: continue new_schedule = { 'active': None, 'program': program.index, 'program_name': program.name, # Save it because programs can be renamed 'fixed': program.fixed, 'cut_off': program.cut_off / 100.0, 'manual': program.manual, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%d-%d' % (str(interval['start']), program.index, station), 'usage': stations.get(station).usage } station_schedules[station].append(new_schedule) # Make lists sorted on start time, check usage for station in station_schedules: if 0 < max_usage < stations.get(station).usage: station_schedules[station] = [] # Impossible to schedule else: station_schedules[station].sort(key=lambda inter: inter['start']) all_intervals = [] # Adjust for weather and remove overlap: for station, schedule in station_schedules.iteritems(): for interval in schedule: if not interval['fixed']: time_delta = interval['end'] - interval['start'] time_delta = datetime.timedelta( seconds=(time_delta.days * 24 * 3600 + time_delta.seconds) * adjustment) interval['end'] = interval['start'] + time_delta interval['adjustment'] = adjustment else: interval['adjustment'] = 1.0 last_end = datetime.datetime(2000, 1, 1) for interval in schedule: if last_end > interval['start']: time_delta = last_end - interval['start'] interval['start'] += time_delta interval['end'] += time_delta last_end = interval['end'] new_interval = {'station': station} new_interval.update(interval) all_intervals.append(new_interval) # Make list of entries sorted on duration and time (stable sorted on station #) all_intervals.sort(key=lambda inter: inter['end'] - inter['start']) all_intervals.sort(key=lambda inter: inter['start']) # If we have processed some intervals before, we should skip all that were scheduled before them for to_skip in skip_intervals: index = 0 while index < len(all_intervals): interval = all_intervals[index] if interval['original_start'] < to_skip['original_start'] and ( not to_skip['blocked'] or interval['blocked']): del all_intervals[index] elif interval['uid'] == to_skip['uid']: del all_intervals[index] break else: index += 1 # And make sure manual programs get priority: all_intervals.sort(key=lambda inter: not inter['manual']) # Try to add each interval for interval in all_intervals: if not interval['manual'] and not options.scheduler_enabled: interval['blocked'] = 'disabled scheduler' continue elif not interval['manual'] and not stations.get(interval['station']).ignore_rain and \ rain_block_start <= interval['start'] < rain_block_end: interval['blocked'] = 'rain delay' continue elif not interval['manual'] and not stations.get( interval['station']).ignore_rain and inputs.rain_sensed(): interval['blocked'] = 'rain sensor' continue elif not interval[ 'fixed'] and interval['adjustment'] < interval['cut_off']: interval['blocked'] = 'cut-off' continue if max_usage > 0: usage_keys = sorted(usage_changes.keys()) start_usage = 0 start_key_index = -1 for index, key in enumerate(usage_keys): if key > interval['start']: break start_key_index = index start_usage += usage_changes[key] failed = False finished = False while not failed and not finished: parallel_usage = 0 parallel_current = 0 for index in range(start_key_index + 1, len(usage_keys)): key = usage_keys[index] if key >= interval['end']: break parallel_current += usage_changes[key] parallel_usage = max(parallel_usage, parallel_current) if start_usage + parallel_usage + interval[ 'usage'] <= max_usage: start = interval['start'] end = interval['end'] if start not in usage_changes: usage_changes[start] = 0 if end not in usage_changes: usage_changes[end] = 0 usage_changes[start] += interval['usage'] usage_changes[end] -= interval['usage'] finished = True else: while not failed: # Shift this interval to next possibility start_key_index += 1 # No more options if start_key_index >= len(usage_keys): failed = True else: next_option = usage_keys[start_key_index] next_change = usage_changes[next_option] start_usage += next_change # Lower usage at this starting point: if next_change < 0: skip_delay = False if options.min_runtime > 0: # Try to determine how long we have been running at this point: min_runtime_delta = datetime.timedelta( seconds=options.min_runtime) temp_usage = 0 running_since = next_option not_running_since = next_option for temp_index in range( 0, start_key_index): temp_usage_key = usage_keys[temp_index] if temp_usage < 0.01 and usage_changes[ temp_usage_key] > 0 and temp_usage_key - not_running_since > datetime.timedelta( seconds=3): running_since = temp_usage_key temp_usage += usage_changes[ temp_usage_key] if temp_usage < 0.01 and usage_changes[ temp_usage_key] < 0: not_running_since = temp_usage_key if next_option - running_since < min_runtime_delta: skip_delay = True if skip_delay: time_to_next = next_option - interval[ 'start'] else: time_to_next = next_option + delay_delta - interval[ 'start'] interval['start'] += time_to_next interval['end'] += time_to_next break if failed: logging.warning('Could not schedule %s.', interval['uid']) interval['blocked'] = 'scheduler error' all_intervals.sort(key=lambda inter: inter['start']) return all_intervals
def predicted_schedule(start_time, end_time): """Determines all schedules for the given time range. To calculate what should currently be active, a start time of some time (a day) ago should be used.""" adjustment = level_adjustments.total_adjustment() max_usage = options.max_usage delay_delta = datetime.timedelta(seconds=options.station_delay) rain_block_start = datetime.datetime.now() rain_block_end = rain_blocks.block_end() skip_intervals = log.finished_runs() + log.active_runs() current_active = [interval for interval in skip_intervals if not interval['blocked']] usage_changes = {} for active in current_active: start = active['start'] end = active['end'] if start not in usage_changes: usage_changes[start] = 0 if end not in usage_changes: usage_changes[end] = 0 usage_changes[start] += active['usage'] usage_changes[end] -= active['usage'] station_schedules = {} # Get run-once information: for station in stations.enabled_stations(): run_once_intervals = run_once.active_intervals(start_time, end_time, station.index) for interval in run_once_intervals: if station.index not in station_schedules: station_schedules[station.index] = [] new_schedule = { 'active': None, 'program': -1, 'program_name': "Run-Once", 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%s-%d' % (str(interval['start']), "Run-Once", station.index), 'usage': station.usage } station_schedules[station.index].append(new_schedule) # Get run-now information: if programs.run_now_program is not None: program = programs.run_now_program for station in sorted(program.stations): run_now_intervals = program.active_intervals(start_time, end_time, station) for interval in run_now_intervals: if station >= stations.count() or stations.master == station or not stations[station].enabled: continue if station not in station_schedules: station_schedules[station] = [] program_name = "%s (Run-Now)" % program.name new_schedule = { 'active': None, 'program': -1, 'program_name': program_name, 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%s-%d' % (str(interval['start']), program_name, station), 'usage': stations.get(station).usage } station_schedules[station].append(new_schedule) # Aggregate per station: for program in programs.get(): if not program.enabled: continue for station in sorted(program.stations): program_intervals = program.active_intervals(start_time, end_time, station) if station >= stations.count() or stations.master == station or not stations[station].enabled: continue if station not in station_schedules: station_schedules[station] = [] for interval in program_intervals: if current_active and current_active[-1]['original_start'] > interval['start']: continue new_schedule = { 'active': None, 'program': program.index, 'program_name': program.name, # Save it because programs can be renamed 'fixed': program.fixed, 'cut_off': program.cut_off/100.0, 'manual': program.manual, 'blocked': False, 'start': interval['start'], 'original_start': interval['start'], 'end': interval['end'], 'uid': '%s-%d-%d' % (str(interval['start']), program.index, station), 'usage': stations.get(station).usage } station_schedules[station].append(new_schedule) # Make lists sorted on start time, check usage for station in station_schedules: if 0 < max_usage < stations.get(station).usage: station_schedules[station] = [] # Impossible to schedule else: station_schedules[station].sort(key=lambda inter: inter['start']) all_intervals = [] # Adjust for weather and remove overlap: for station, schedule in station_schedules.iteritems(): for interval in schedule: if not interval['fixed']: time_delta = interval['end'] - interval['start'] time_delta = datetime.timedelta(seconds=(time_delta.days * 24 * 3600 + time_delta.seconds) * adjustment) interval['end'] = interval['start'] + time_delta interval['adjustment'] = adjustment else: interval['adjustment'] = 1.0 last_end = datetime.datetime(2000, 1, 1) for interval in schedule: if last_end > interval['start']: time_delta = last_end - interval['start'] interval['start'] += time_delta interval['end'] += time_delta last_end = interval['end'] new_interval = { 'station': station } new_interval.update(interval) all_intervals.append(new_interval) # Make list of entries sorted on duration and time (stable sorted on station #) all_intervals.sort(key=lambda inter: inter['end'] - inter['start']) all_intervals.sort(key=lambda inter: inter['start']) # If we have processed some intervals before, we should skip all that were scheduled before them for to_skip in skip_intervals: index = 0 while index < len(all_intervals): interval = all_intervals[index] if interval['original_start'] < to_skip['original_start'] and (not to_skip['blocked'] or interval['blocked']): del all_intervals[index] elif interval['uid'] == to_skip['uid']: del all_intervals[index] break else: index += 1 # And make sure manual programs get priority: all_intervals.sort(key=lambda inter: not inter['manual']) # Try to add each interval for interval in all_intervals: if not interval['manual'] and not options.scheduler_enabled: interval['blocked'] = 'disabled scheduler' continue elif not interval['manual'] and not stations.get(interval['station']).ignore_rain and \ rain_block_start <= interval['start'] < rain_block_end: interval['blocked'] = 'rain delay' continue elif not interval['manual'] and not stations.get(interval['station']).ignore_rain and inputs.rain_sensed(): interval['blocked'] = 'rain sensor' continue elif not interval['fixed'] and interval['adjustment'] < interval['cut_off']: interval['blocked'] = 'cut-off' continue if max_usage > 0: usage_keys = sorted(usage_changes.keys()) start_usage = 0 start_key_index = -1 for index, key in enumerate(usage_keys): if key > interval['start']: break start_key_index = index start_usage += usage_changes[key] failed = False finished = False while not failed and not finished: parallel_usage = 0 parallel_current = 0 for index in range(start_key_index+1, len(usage_keys)): key = usage_keys[index] if key >= interval['end']: break parallel_current += usage_changes[key] parallel_usage = max(parallel_usage, parallel_current) if start_usage + parallel_usage + interval['usage'] <= max_usage: start = interval['start'] end = interval['end'] if start not in usage_changes: usage_changes[start] = 0 if end not in usage_changes: usage_changes[end] = 0 usage_changes[start] += interval['usage'] usage_changes[end] -= interval['usage'] finished = True else: while not failed: # Shift this interval to next possibility start_key_index += 1 # No more options if start_key_index >= len(usage_keys): failed = True else: next_option = usage_keys[start_key_index] next_change = usage_changes[next_option] start_usage += next_change # Lower usage at this starting point: if next_change < 0: skip_delay = False if options.min_runtime > 0: # Try to determine how long we have been running at this point: min_runtime_delta = datetime.timedelta(seconds=options.min_runtime) temp_usage = 0 running_since = next_option not_running_since = next_option for temp_index in range(0, start_key_index): temp_usage_key = usage_keys[temp_index] if temp_usage < 0.01 and usage_changes[temp_usage_key] > 0 and temp_usage_key - not_running_since > datetime.timedelta(seconds=3): running_since = temp_usage_key temp_usage += usage_changes[temp_usage_key] if temp_usage < 0.01 and usage_changes[temp_usage_key] < 0: not_running_since = temp_usage_key if next_option - running_since < min_runtime_delta: skip_delay = True if skip_delay: time_to_next = next_option - interval['start'] else: time_to_next = next_option + delay_delta - interval['start'] interval['start'] += time_to_next interval['end'] += time_to_next break if failed: logging.warning('Could not schedule %s.', interval['uid']) interval['blocked'] = 'scheduler error' all_intervals.sort(key=lambda inter: inter['start']) return all_intervals
def run(self): weather.add_callback(self.update) self._sleep(10) # Wait for weather callback before starting while not self._stop_event.is_set(): try: log.clear(NAME) if plugin_options['enabled']: log.debug(NAME, _(u'Checking weather status') + '...') info = [] days = 0 total_info = {'rain_mm': 0.0} for day in range(-plugin_options['days_history'], plugin_options['days_forecast'] + 1): check_date = datetime.date.today( ) + datetime.timedelta(days=day) hourly_data = weather.get_hourly_data(check_date) if hourly_data: days += 1 info += hourly_data total_info['rain_mm'] += weather.get_rain(check_date) log.info( NAME, _(u'Using') + ' %d ' % days + _(u'days of information.')) total_info.update({ 'temp_c': sum([val['temperature'] for val in info]) / len(info), 'wind_ms': sum([val['windSpeed'] for val in info]) / len(info), 'humidity': sum([val['humidity'] for val in info]) / len(info) }) # We assume that the default 100% provides 4mm water per day (normal need) # We calculate what we will need to provide using the mean data of X days around today water_needed = 4 * days # 4mm per day water_needed *= 1 + (total_info['temp_c'] - 20) / 15 # 5 => 0%, 35 => 200% water_needed *= 1 + (total_info['wind_ms'] / 100 ) # 0 => 100%, 20 => 120% water_needed *= 1 - (total_info['humidity'] - 50) / 200 # 0 => 125%, 100 => 75% water_needed = round(water_needed, 1) water_left = water_needed - total_info['rain_mm'] water_left = round(max(0, min(100, water_left)), 1) water_adjustment = round((water_left / (4 * days)) * 100, 1) water_adjustment = float( max(plugin_options['wl_min'], min(plugin_options['wl_max'], water_adjustment))) log.info( NAME, _(u'Water needed') + ' %d ' % days + _('days') + ': %.1fmm' % water_needed) log.info( NAME, _(u'Total rainfall') + ': %.1fmm' % total_info['rain_mm']) log.info(NAME, u'_______________________________') log.info(NAME, _(u'Irrigation needed') + ': %.1fmm' % water_left) log.info( NAME, _(u'Weather Adjustment') + ': %.1f%%' % water_adjustment) level_adjustments[NAME] = water_adjustment / 100 if plugin_options['protect_enabled']: current_data = weather.get_current_data() temp_local_unit = current_data[ 'temperature'] if options.temp_unit == "C" else 32.0 + 9.0 / 5.0 * current_data[ 'temperature'] log.debug( NAME, _(u'Temperature') + ': %.1f %s' % (temp_local_unit, options.temp_unit)) month = time.localtime().tm_mon # Current month. if temp_local_unit < plugin_options[ 'protect_temp'] and month in plugin_options[ 'protect_months']: station_seconds = {} for station in stations.enabled_stations(): if station.index in plugin_options[ 'protect_stations']: station_seconds[ station.index] = plugin_options[ 'protect_minutes'] * 60 else: station_seconds[station.index] = 0 for station in stations.enabled_stations(): if run_once.is_active(datetime.datetime.now(), station.index): break else: log.debug(NAME, _(u'Protection activated.')) run_once.set(station_seconds) self._sleep(3600) else: log.clear(NAME) log.info(NAME, _(u'Plug-in is disabled.')) if NAME in level_adjustments: del level_adjustments[NAME] self._sleep(24 * 3600) except Exception: log.error( NAME, _(u'Weather-based water level plug-in') + ':\n' + traceback.format_exc()) self._sleep(3600) weather.remove_callback(self.update)
def run(self): weather.add_callback(self.update) self._sleep(10) # Wait for weather callback before starting while not self._stop.is_set(): try: log.clear(NAME) if plugin_options['enabled']: log.debug(NAME, _('Checking weather status') + '...') history = weather.get_wunderground_history( plugin_options['days_history']) forecast = weather.get_wunderground_forecast( plugin_options['days_forecast']) today = weather.get_wunderground_conditions() info = {} for day in range(-20, 20): if day in history: day_info = history[day] elif day in forecast: day_info = forecast[day] else: continue info[day] = day_info if 0 in info and 'rain_mm' in today: day_time = datetime.datetime.now().time() day_left = 1.0 - (day_time.hour * 60 + day_time.minute) / 24.0 / 60 info[0]['rain_mm'] = info[0][ 'rain_mm'] * day_left + today['rain_mm'] if not info: log.info(NAME, str(history)) log.info(NAME, str(today)) log.info(NAME, str(forecast)) raise Exception(_('No information available!')) log.info( NAME, _('Using') + ' %d ' % len(info) + _('days of information.')) total_info = { 'temp_c': sum([val['temp_c'] for val in info.values()]) / len(info), 'rain_mm': sum([val['rain_mm'] for val in info.values()]), 'wind_ms': sum([val['wind_ms'] for val in info.values()]) / len(info), 'humidity': sum([val['humidity'] for val in info.values()]) / len(info) } # We assume that the default 100% provides 4mm water per day (normal need) # We calculate what we will need to provide using the mean data of X days around today water_needed = 4 * len(info) # 4mm per day water_needed *= 1 + (total_info['temp_c'] - 20) / 15 # 5 => 0%, 35 => 200% water_needed *= 1 + (total_info['wind_ms'] / 100 ) # 0 => 100%, 20 => 120% water_needed *= 1 - (total_info['humidity'] - 50) / 200 # 0 => 125%, 100 => 75% water_needed = round(water_needed, 1) water_left = water_needed - total_info['rain_mm'] water_left = round(max(0, min(100, water_left)), 1) water_adjustment = round( (water_left / (4 * len(info))) * 100, 1) water_adjustment = float( max(plugin_options['wl_min'], min(plugin_options['wl_max'], water_adjustment))) log.info( NAME, _('Water needed') + ' %d ' % len(info) + _('days') + ': %.1fmm' % water_needed) log.info( NAME, _('Total rainfall') + ': %.1fmm' % total_info['rain_mm']) log.info(NAME, '_______________________________') log.info(NAME, _('Irrigation needed') + ': %.1fmm' % water_left) log.info( NAME, _('Weather Adjustment') + ': %.1f%%' % water_adjustment) level_adjustments[NAME] = water_adjustment / 100 if plugin_options['protect_enabled']: log.debug( NAME, _('Temperature') + ': %s' % today['temperature_string']) month = time.localtime().tm_mon # Current month. cold = (today['temp_c'] < plugin_options['protect_temp']) if options.temp_unit == "C" else \ (today['temp_f'] < plugin_options['protect_temp']) if cold and month in plugin_options['protect_months']: station_seconds = {} for station in stations.enabled_stations(): if station.index in plugin_options[ 'protect_stations']: station_seconds[ station.index] = plugin_options[ 'protect_minutes'] * 60 else: station_seconds[station.index] = 0 for station in stations.enabled_stations(): if run_once.is_active(datetime.datetime.now(), station.index): break else: log.debug(NAME, _('Protection activated.')) run_once.set(station_seconds) self._sleep(3600) else: log.clear(NAME) log.info(NAME, _('Plug-in is disabled.')) if NAME in level_adjustments: del level_adjustments[NAME] self._sleep(24 * 3600) except Exception: log.error( NAME, _('Weather-based water level plug-in') + ':\n' + traceback.format_exc()) self._sleep(3600) weather.remove_callback(self.update)